UNPKG

91 kBtext/x-cView Raw
1// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
2
3#include "CanvasRenderingContext2d.h"
4
5#include <algorithm>
6#include "backend/ImageBackend.h"
7#include <cairo/cairo-pdf.h>
8#include "Canvas.h"
9#include "CanvasGradient.h"
10#include "CanvasPattern.h"
11#include <cmath>
12#include <cstdlib>
13#include "Image.h"
14#include "ImageData.h"
15#include <limits>
16#include <map>
17#include "Point.h"
18#include <string>
19#include "Util.h"
20#include <vector>
21
22using namespace v8;
23
24// Windows doesn't support the C99 names for these
25#ifdef _MSC_VER
26#define isnan(x) _isnan(x)
27#define isinf(x) (!_finite(x))
28#endif
29
30#ifndef isnan
31#define isnan(x) std::isnan(x)
32#define isinf(x) std::isinf(x)
33#endif
34
35Nan::Persistent<FunctionTemplate> Context2d::constructor;
36
37/*
38 * Rectangle arg assertions.
39 */
40
41#define RECT_ARGS \
42 double args[4]; \
43 if(!checkArgs(info, args, 4)) \
44 return; \
45 double x = args[0]; \
46 double y = args[1]; \
47 double width = args[2]; \
48 double height = args[3];
49
50/*
51 * Text baselines.
52 */
53
54enum {
55 TEXT_BASELINE_ALPHABETIC
56 , TEXT_BASELINE_TOP
57 , TEXT_BASELINE_BOTTOM
58 , TEXT_BASELINE_MIDDLE
59 , TEXT_BASELINE_IDEOGRAPHIC
60 , TEXT_BASELINE_HANGING
61};
62
63/*
64 * Simple helper macro for a rather verbose function call.
65 */
66
67#define PANGO_LAYOUT_GET_METRICS(LAYOUT) pango_context_get_metrics( \
68 pango_layout_get_context(LAYOUT), \
69 pango_layout_get_font_description(LAYOUT), \
70 pango_context_get_language(pango_layout_get_context(LAYOUT)))
71
72inline static bool checkArgs(const Nan::FunctionCallbackInfo<Value> &info, double *args, int argsNum, int offset = 0){
73 int argsEnd = offset + argsNum;
74 bool areArgsValid = true;
75
76 for (int i = offset; i < argsEnd; i++) {
77 double val = Nan::To<double>(info[i]).FromMaybe(0);
78
79 if (areArgsValid) {
80 if (val != val ||
81 val == std::numeric_limits<double>::infinity() ||
82 val == -std::numeric_limits<double>::infinity()) {
83 // We should continue the loop instead of returning immediately
84 // See https://html.spec.whatwg.org/multipage/canvas.html
85
86 areArgsValid = false;
87 continue;
88 }
89
90 args[i - offset] = val;
91 }
92 }
93
94 return areArgsValid;
95}
96
97Nan::Persistent<Function> Context2d::_DOMMatrix;
98Nan::Persistent<Function> Context2d::_parseFont;
99
100/*
101 * Initialize Context2d.
102 */
103
104void
105Context2d::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
106 Nan::HandleScope scope;
107
108 // Constructor
109 Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Context2d::New);
110 constructor.Reset(ctor);
111 ctor->InstanceTemplate()->SetInternalFieldCount(1);
112 ctor->SetClassName(Nan::New("CanvasRenderingContext2D").ToLocalChecked());
113
114 // Prototype
115 Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
116 Nan::SetPrototypeMethod(ctor, "drawImage", DrawImage);
117 Nan::SetPrototypeMethod(ctor, "putImageData", PutImageData);
118 Nan::SetPrototypeMethod(ctor, "getImageData", GetImageData);
119 Nan::SetPrototypeMethod(ctor, "createImageData", CreateImageData);
120 Nan::SetPrototypeMethod(ctor, "addPage", AddPage);
121 Nan::SetPrototypeMethod(ctor, "save", Save);
122 Nan::SetPrototypeMethod(ctor, "restore", Restore);
123 Nan::SetPrototypeMethod(ctor, "rotate", Rotate);
124 Nan::SetPrototypeMethod(ctor, "translate", Translate);
125 Nan::SetPrototypeMethod(ctor, "transform", Transform);
126 Nan::SetPrototypeMethod(ctor, "resetTransform", ResetTransform);
127 Nan::SetPrototypeMethod(ctor, "setTransform", SetTransform);
128 Nan::SetPrototypeMethod(ctor, "isPointInPath", IsPointInPath);
129 Nan::SetPrototypeMethod(ctor, "scale", Scale);
130 Nan::SetPrototypeMethod(ctor, "clip", Clip);
131 Nan::SetPrototypeMethod(ctor, "fill", Fill);
132 Nan::SetPrototypeMethod(ctor, "stroke", Stroke);
133 Nan::SetPrototypeMethod(ctor, "fillText", FillText);
134 Nan::SetPrototypeMethod(ctor, "strokeText", StrokeText);
135 Nan::SetPrototypeMethod(ctor, "fillRect", FillRect);
136 Nan::SetPrototypeMethod(ctor, "strokeRect", StrokeRect);
137 Nan::SetPrototypeMethod(ctor, "clearRect", ClearRect);
138 Nan::SetPrototypeMethod(ctor, "rect", Rect);
139 Nan::SetPrototypeMethod(ctor, "measureText", MeasureText);
140 Nan::SetPrototypeMethod(ctor, "moveTo", MoveTo);
141 Nan::SetPrototypeMethod(ctor, "lineTo", LineTo);
142 Nan::SetPrototypeMethod(ctor, "bezierCurveTo", BezierCurveTo);
143 Nan::SetPrototypeMethod(ctor, "quadraticCurveTo", QuadraticCurveTo);
144 Nan::SetPrototypeMethod(ctor, "beginPath", BeginPath);
145 Nan::SetPrototypeMethod(ctor, "closePath", ClosePath);
146 Nan::SetPrototypeMethod(ctor, "arc", Arc);
147 Nan::SetPrototypeMethod(ctor, "arcTo", ArcTo);
148 Nan::SetPrototypeMethod(ctor, "ellipse", Ellipse);
149 Nan::SetPrototypeMethod(ctor, "setLineDash", SetLineDash);
150 Nan::SetPrototypeMethod(ctor, "getLineDash", GetLineDash);
151 Nan::SetPrototypeMethod(ctor, "createPattern", CreatePattern);
152 Nan::SetPrototypeMethod(ctor, "createLinearGradient", CreateLinearGradient);
153 Nan::SetPrototypeMethod(ctor, "createRadialGradient", CreateRadialGradient);
154 SetProtoAccessor(proto, Nan::New("pixelFormat").ToLocalChecked(), GetFormat, NULL, ctor);
155 SetProtoAccessor(proto, Nan::New("patternQuality").ToLocalChecked(), GetPatternQuality, SetPatternQuality, ctor);
156 SetProtoAccessor(proto, Nan::New("imageSmoothingEnabled").ToLocalChecked(), GetImageSmoothingEnabled, SetImageSmoothingEnabled, ctor);
157 SetProtoAccessor(proto, Nan::New("globalCompositeOperation").ToLocalChecked(), GetGlobalCompositeOperation, SetGlobalCompositeOperation, ctor);
158 SetProtoAccessor(proto, Nan::New("globalAlpha").ToLocalChecked(), GetGlobalAlpha, SetGlobalAlpha, ctor);
159 SetProtoAccessor(proto, Nan::New("shadowColor").ToLocalChecked(), GetShadowColor, SetShadowColor, ctor);
160 SetProtoAccessor(proto, Nan::New("miterLimit").ToLocalChecked(), GetMiterLimit, SetMiterLimit, ctor);
161 SetProtoAccessor(proto, Nan::New("lineWidth").ToLocalChecked(), GetLineWidth, SetLineWidth, ctor);
162 SetProtoAccessor(proto, Nan::New("lineCap").ToLocalChecked(), GetLineCap, SetLineCap, ctor);
163 SetProtoAccessor(proto, Nan::New("lineJoin").ToLocalChecked(), GetLineJoin, SetLineJoin, ctor);
164 SetProtoAccessor(proto, Nan::New("lineDashOffset").ToLocalChecked(), GetLineDashOffset, SetLineDashOffset, ctor);
165 SetProtoAccessor(proto, Nan::New("shadowOffsetX").ToLocalChecked(), GetShadowOffsetX, SetShadowOffsetX, ctor);
166 SetProtoAccessor(proto, Nan::New("shadowOffsetY").ToLocalChecked(), GetShadowOffsetY, SetShadowOffsetY, ctor);
167 SetProtoAccessor(proto, Nan::New("shadowBlur").ToLocalChecked(), GetShadowBlur, SetShadowBlur, ctor);
168 SetProtoAccessor(proto, Nan::New("antialias").ToLocalChecked(), GetAntiAlias, SetAntiAlias, ctor);
169 SetProtoAccessor(proto, Nan::New("textDrawingMode").ToLocalChecked(), GetTextDrawingMode, SetTextDrawingMode, ctor);
170 SetProtoAccessor(proto, Nan::New("quality").ToLocalChecked(), GetQuality, SetQuality, ctor);
171 SetProtoAccessor(proto, Nan::New("currentTransform").ToLocalChecked(), GetCurrentTransform, SetCurrentTransform, ctor);
172 SetProtoAccessor(proto, Nan::New("fillStyle").ToLocalChecked(), GetFillStyle, SetFillStyle, ctor);
173 SetProtoAccessor(proto, Nan::New("strokeStyle").ToLocalChecked(), GetStrokeStyle, SetStrokeStyle, ctor);
174 SetProtoAccessor(proto, Nan::New("font").ToLocalChecked(), GetFont, SetFont, ctor);
175 SetProtoAccessor(proto, Nan::New("textBaseline").ToLocalChecked(), GetTextBaseline, SetTextBaseline, ctor);
176 SetProtoAccessor(proto, Nan::New("textAlign").ToLocalChecked(), GetTextAlign, SetTextAlign, ctor);
177 Local<Context> ctx = Nan::GetCurrentContext();
178 Nan::Set(target, Nan::New("CanvasRenderingContext2d").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked());
179 Nan::Set(target, Nan::New("CanvasRenderingContext2dInit").ToLocalChecked(), Nan::New<Function>(SaveExternalModules));
180}
181
182/*
183 * Create a cairo context.
184 */
185
186Context2d::Context2d(Canvas *canvas) {
187 _canvas = canvas;
188 _context = canvas->createCairoContext();
189 _layout = pango_cairo_create_layout(_context);
190 state = states[stateno = 0] = (canvas_state_t *) malloc(sizeof(canvas_state_t));
191
192 resetState(true);
193}
194
195/*
196 * Destroy cairo context.
197 */
198
199Context2d::~Context2d() {
200 while(stateno >= 0) {
201 pango_font_description_free(states[stateno]->fontDescription);
202 free(states[stateno--]);
203 }
204 g_object_unref(_layout);
205 cairo_destroy(_context);
206 _resetPersistentHandles();
207}
208
209/*
210 * Reset canvas state.
211 */
212
213void Context2d::resetState(bool init) {
214 if (!init) {
215 pango_font_description_free(state->fontDescription);
216 }
217
218 state->shadowBlur = 0;
219 state->shadowOffsetX = state->shadowOffsetY = 0;
220 state->globalAlpha = 1;
221 state->textAlignment = -1;
222 state->fillPattern = nullptr;
223 state->strokePattern = nullptr;
224 state->fillGradient = nullptr;
225 state->strokeGradient = nullptr;
226 state->textBaseline = TEXT_BASELINE_ALPHABETIC;
227 rgba_t transparent = { 0, 0, 0, 1 };
228 rgba_t transparent_black = { 0, 0, 0, 0 };
229 state->fill = transparent;
230 state->stroke = transparent;
231 state->shadow = transparent_black;
232 state->patternQuality = CAIRO_FILTER_GOOD;
233 state->imageSmoothingEnabled = true;
234 state->textDrawingMode = TEXT_DRAW_PATHS;
235 state->fontDescription = pango_font_description_from_string("sans serif");
236 pango_font_description_set_absolute_size(state->fontDescription, 10 * PANGO_SCALE);
237 pango_layout_set_font_description(_layout, state->fontDescription);
238
239 _resetPersistentHandles();
240}
241
242void Context2d::_resetPersistentHandles() {
243 _fillStyle.Reset();
244 _strokeStyle.Reset();
245 _font.Reset();
246 _textBaseline.Reset();
247 _textAlign.Reset();
248}
249
250/*
251 * Save cairo / canvas state.
252 */
253
254void
255Context2d::save() {
256 if (stateno < CANVAS_MAX_STATES) {
257 cairo_save(_context);
258 states[++stateno] = (canvas_state_t *) malloc(sizeof(canvas_state_t));
259 memcpy(states[stateno], state, sizeof(canvas_state_t));
260 states[stateno]->fontDescription = pango_font_description_copy(states[stateno-1]->fontDescription);
261 state = states[stateno];
262 }
263}
264
265/*
266 * Restore cairo / canvas state.
267 */
268
269void
270Context2d::restore() {
271 if (stateno > 0) {
272 cairo_restore(_context);
273 pango_font_description_free(states[stateno]->fontDescription);
274 free(states[stateno]);
275 states[stateno] = NULL;
276 state = states[--stateno];
277 pango_layout_set_font_description(_layout, state->fontDescription);
278 }
279}
280
281/*
282 * Save flat path.
283 */
284
285void
286Context2d::savePath() {
287 _path = cairo_copy_path_flat(_context);
288 cairo_new_path(_context);
289}
290
291/*
292 * Restore flat path.
293 */
294
295void
296Context2d::restorePath() {
297 cairo_new_path(_context);
298 cairo_append_path(_context, _path);
299 cairo_path_destroy(_path);
300}
301
302/*
303 * Create temporary surface for gradient or pattern transparency
304 */
305cairo_pattern_t*
306create_transparent_gradient(cairo_pattern_t *source, float alpha) {
307 double x0;
308 double y0;
309 double x1;
310 double y1;
311 double r0;
312 double r1;
313 int count;
314 int i;
315 double offset;
316 double r;
317 double g;
318 double b;
319 double a;
320 cairo_pattern_t *newGradient;
321 cairo_pattern_type_t type = cairo_pattern_get_type(source);
322 cairo_pattern_get_color_stop_count(source, &count);
323 if (type == CAIRO_PATTERN_TYPE_LINEAR) {
324 cairo_pattern_get_linear_points (source, &x0, &y0, &x1, &y1);
325 newGradient = cairo_pattern_create_linear(x0, y0, x1, y1);
326 } else if (type == CAIRO_PATTERN_TYPE_RADIAL) {
327 cairo_pattern_get_radial_circles(source, &x0, &y0, &r0, &x1, &y1, &r1);
328 newGradient = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1);
329 } else {
330 Nan::ThrowError("Unexpected gradient type");
331 return NULL;
332 }
333 for ( i = 0; i < count; i++ ) {
334 cairo_pattern_get_color_stop_rgba(source, i, &offset, &r, &g, &b, &a);
335 cairo_pattern_add_color_stop_rgba(newGradient, offset, r, g, b, a * alpha);
336 }
337 return newGradient;
338}
339
340cairo_pattern_t*
341create_transparent_pattern(cairo_pattern_t *source, float alpha) {
342 cairo_surface_t *surface;
343 cairo_pattern_get_surface(source, &surface);
344 int width = cairo_image_surface_get_width(surface);
345 int height = cairo_image_surface_get_height(surface);
346 cairo_surface_t *mask_surface = cairo_image_surface_create(
347 CAIRO_FORMAT_ARGB32,
348 width,
349 height);
350 cairo_t *mask_context = cairo_create(mask_surface);
351 if (cairo_status(mask_context) != CAIRO_STATUS_SUCCESS) {
352 Nan::ThrowError("Failed to initialize context");
353 return NULL;
354 }
355 cairo_set_source(mask_context, source);
356 cairo_paint_with_alpha(mask_context, alpha);
357 cairo_destroy(mask_context);
358 cairo_pattern_t* newPattern = cairo_pattern_create_for_surface(mask_surface);
359 cairo_surface_destroy(mask_surface);
360 return newPattern;
361}
362
363/*
364 * Fill and apply shadow.
365 */
366
367void
368Context2d::setFillRule(v8::Local<v8::Value> value) {
369 cairo_fill_rule_t rule = CAIRO_FILL_RULE_WINDING;
370 if (value->IsString()) {
371 Nan::Utf8String str(value);
372 if (std::strcmp(*str, "evenodd") == 0) {
373 rule = CAIRO_FILL_RULE_EVEN_ODD;
374 }
375 }
376 cairo_set_fill_rule(_context, rule);
377}
378
379void
380Context2d::fill(bool preserve) {
381 cairo_pattern_t *new_pattern;
382 if (state->fillPattern) {
383 if (state->globalAlpha < 1) {
384 new_pattern = create_transparent_pattern(state->fillPattern, state->globalAlpha);
385 if (new_pattern == NULL) {
386 // failed to allocate; Nan::ThrowError has already been called, so return from this fn.
387 return;
388 }
389 cairo_set_source(_context, new_pattern);
390 cairo_pattern_destroy(new_pattern);
391 } else {
392 cairo_set_source(_context, state->fillPattern);
393 }
394 repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->fillPattern);
395 if (NO_REPEAT == repeat) {
396 cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE);
397 } else {
398 cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
399 }
400 } else if (state->fillGradient) {
401 if (state->globalAlpha < 1) {
402 new_pattern = create_transparent_gradient(state->fillGradient, state->globalAlpha);
403 if (new_pattern == NULL) {
404 // failed to recognize gradient; Nan::ThrowError has already been called, so return from this fn.
405 return;
406 }
407 cairo_pattern_set_filter(new_pattern, state->patternQuality);
408 cairo_set_source(_context, new_pattern);
409 cairo_pattern_destroy(new_pattern);
410 } else {
411 cairo_pattern_set_filter(state->fillGradient, state->patternQuality);
412 cairo_set_source(_context, state->fillGradient);
413 }
414 } else {
415 setSourceRGBA(state->fill);
416 }
417 if (preserve) {
418 hasShadow()
419 ? shadow(cairo_fill_preserve)
420 : cairo_fill_preserve(_context);
421 } else {
422 hasShadow()
423 ? shadow(cairo_fill)
424 : cairo_fill(_context);
425 }
426}
427
428/*
429 * Stroke and apply shadow.
430 */
431
432void
433Context2d::stroke(bool preserve) {
434 cairo_pattern_t *new_pattern;
435 if (state->strokePattern) {
436 if (state->globalAlpha < 1) {
437 new_pattern = create_transparent_pattern(state->strokePattern, state->globalAlpha);
438 if (new_pattern == NULL) {
439 // failed to allocate; Nan::ThrowError has already been called, so return from this fn.
440 return;
441 }
442 cairo_set_source(_context, new_pattern);
443 cairo_pattern_destroy(new_pattern);
444 } else {
445 cairo_set_source(_context, state->strokePattern);
446 }
447 repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->strokePattern);
448 if (NO_REPEAT == repeat) {
449 cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE);
450 } else {
451 cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
452 }
453 } else if (state->strokeGradient) {
454 if (state->globalAlpha < 1) {
455 new_pattern = create_transparent_gradient(state->strokeGradient, state->globalAlpha);
456 if (new_pattern == NULL) {
457 // failed to recognize gradient; Nan::ThrowError has already been called, so return from this fn.
458 return;
459 }
460 cairo_pattern_set_filter(new_pattern, state->patternQuality);
461 cairo_set_source(_context, new_pattern);
462 cairo_pattern_destroy(new_pattern);
463 } else {
464 cairo_pattern_set_filter(state->strokeGradient, state->patternQuality);
465 cairo_set_source(_context, state->strokeGradient);
466 }
467 } else {
468 setSourceRGBA(state->stroke);
469 }
470
471 if (preserve) {
472 hasShadow()
473 ? shadow(cairo_stroke_preserve)
474 : cairo_stroke_preserve(_context);
475 } else {
476 hasShadow()
477 ? shadow(cairo_stroke)
478 : cairo_stroke(_context);
479 }
480}
481
482/*
483 * Apply shadow with the given draw fn.
484 */
485
486void
487Context2d::shadow(void (fn)(cairo_t *cr)) {
488 cairo_path_t *path = cairo_copy_path_flat(_context);
489 cairo_save(_context);
490
491 // shadowOffset is unaffected by current transform
492 cairo_matrix_t path_matrix;
493 cairo_get_matrix(_context, &path_matrix);
494 cairo_identity_matrix(_context);
495
496 // Apply shadow
497 cairo_push_group(_context);
498
499 // No need to invoke blur if shadowBlur is 0
500 if (state->shadowBlur) {
501 // find out extent of path
502 double x1, y1, x2, y2;
503 if (fn == cairo_fill || fn == cairo_fill_preserve) {
504 cairo_fill_extents(_context, &x1, &y1, &x2, &y2);
505 } else {
506 cairo_stroke_extents(_context, &x1, &y1, &x2, &y2);
507 }
508
509 // create new image surface that size + padding for blurring
510 double dx = x2-x1, dy = y2-y1;
511 cairo_user_to_device_distance(_context, &dx, &dy);
512 int pad = state->shadowBlur * 2;
513 cairo_surface_t *shadow_surface = cairo_image_surface_create(
514 CAIRO_FORMAT_ARGB32,
515 dx + 2 * pad,
516 dy + 2 * pad);
517 cairo_t *shadow_context = cairo_create(shadow_surface);
518
519 // transform path to the right place
520 cairo_translate(shadow_context, pad-x1, pad-y1);
521 cairo_transform(shadow_context, &path_matrix);
522
523 // draw the path and blur
524 cairo_set_line_width(shadow_context, cairo_get_line_width(_context));
525 cairo_new_path(shadow_context);
526 cairo_append_path(shadow_context, path);
527 setSourceRGBA(shadow_context, state->shadow);
528 fn(shadow_context);
529 blur(shadow_surface, state->shadowBlur);
530
531 // paint to original context
532 cairo_set_source_surface(_context, shadow_surface,
533 x1 - pad + state->shadowOffsetX + 1,
534 y1 - pad + state->shadowOffsetY + 1);
535 cairo_paint(_context);
536 cairo_destroy(shadow_context);
537 cairo_surface_destroy(shadow_surface);
538 } else {
539 // Offset first, then apply path's transform
540 cairo_translate(
541 _context
542 , state->shadowOffsetX
543 , state->shadowOffsetY);
544 cairo_transform(_context, &path_matrix);
545
546 // Apply shadow
547 cairo_new_path(_context);
548 cairo_append_path(_context, path);
549 setSourceRGBA(state->shadow);
550
551 fn(_context);
552 }
553
554 // Paint the shadow
555 cairo_pop_group_to_source(_context);
556 cairo_paint(_context);
557
558 // Restore state
559 cairo_restore(_context);
560 cairo_new_path(_context);
561 cairo_append_path(_context, path);
562 fn(_context);
563
564 cairo_path_destroy(path);
565}
566
567/*
568 * Set source RGBA for the current context
569 */
570
571void
572Context2d::setSourceRGBA(rgba_t color) {
573 setSourceRGBA(_context, color);
574}
575
576/*
577 * Set source RGBA
578 */
579
580void
581Context2d::setSourceRGBA(cairo_t *ctx, rgba_t color) {
582 cairo_set_source_rgba(
583 ctx
584 , color.r
585 , color.g
586 , color.b
587 , color.a * state->globalAlpha);
588}
589
590/*
591 * Check if the context has a drawable shadow.
592 */
593
594bool
595Context2d::hasShadow() {
596 return state->shadow.a
597 && (state->shadowBlur || state->shadowOffsetX || state->shadowOffsetY);
598}
599
600/*
601 * Blur the given surface with the given radius.
602 */
603
604void
605Context2d::blur(cairo_surface_t *surface, int radius) {
606 // Steve Hanov, 2009
607 // Released into the public domain.
608 radius = radius * 0.57735f + 0.5f;
609 // get width, height
610 int width = cairo_image_surface_get_width( surface );
611 int height = cairo_image_surface_get_height( surface );
612 unsigned* precalc =
613 (unsigned*)malloc(width*height*sizeof(unsigned));
614 cairo_surface_flush( surface );
615 unsigned char* src = cairo_image_surface_get_data( surface );
616 double mul=1.f/((radius*2)*(radius*2));
617 int channel;
618
619 // The number of times to perform the averaging. According to wikipedia,
620 // three iterations is good enough to pass for a gaussian.
621 const int MAX_ITERATIONS = 3;
622 int iteration;
623
624 for ( iteration = 0; iteration < MAX_ITERATIONS; iteration++ ) {
625 for( channel = 0; channel < 4; channel++ ) {
626 int x,y;
627
628 // precomputation step.
629 unsigned char* pix = src;
630 unsigned* pre = precalc;
631
632 pix += channel;
633 for (y=0;y<height;y++) {
634 for (x=0;x<width;x++) {
635 int tot=pix[0];
636 if (x>0) tot+=pre[-1];
637 if (y>0) tot+=pre[-width];
638 if (x>0 && y>0) tot-=pre[-width-1];
639 *pre++=tot;
640 pix += 4;
641 }
642 }
643
644 // blur step.
645 pix = src + (int)radius * width * 4 + (int)radius * 4 + channel;
646 for (y=radius;y<height-radius;y++) {
647 for (x=radius;x<width-radius;x++) {
648 int l = x < radius ? 0 : x - radius;
649 int t = y < radius ? 0 : y - radius;
650 int r = x + radius >= width ? width - 1 : x + radius;
651 int b = y + radius >= height ? height - 1 : y + radius;
652 int tot = precalc[r+b*width] + precalc[l+t*width] -
653 precalc[l+b*width] - precalc[r+t*width];
654 *pix=(unsigned char)(tot*mul);
655 pix += 4;
656 }
657 pix += (int)radius * 2 * 4;
658 }
659 }
660 }
661
662 cairo_surface_mark_dirty(surface);
663 free(precalc);
664}
665
666/*
667 * Initialize a new Context2d with the given canvas.
668 */
669
670NAN_METHOD(Context2d::New) {
671 if (!info.IsConstructCall()) {
672 return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
673 }
674
675 if (!info[0]->IsObject())
676 return Nan::ThrowTypeError("Canvas expected");
677 Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
678 if (!Nan::New(Canvas::constructor)->HasInstance(obj))
679 return Nan::ThrowTypeError("Canvas expected");
680 Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(obj);
681
682 bool isImageBackend = canvas->backend()->getName() == "image";
683 if (isImageBackend) {
684 cairo_format_t format = ImageBackend::DEFAULT_FORMAT;
685 if (info[1]->IsObject()) {
686 Local<Object> ctxAttributes = Nan::To<Object>(info[1]).ToLocalChecked();
687
688 Local<Value> pixelFormat = Nan::Get(ctxAttributes, Nan::New("pixelFormat").ToLocalChecked()).ToLocalChecked();
689 if (pixelFormat->IsString()) {
690 Nan::Utf8String utf8PixelFormat(pixelFormat);
691 if (!strcmp(*utf8PixelFormat, "RGBA32")) format = CAIRO_FORMAT_ARGB32;
692 else if (!strcmp(*utf8PixelFormat, "RGB24")) format = CAIRO_FORMAT_RGB24;
693 else if (!strcmp(*utf8PixelFormat, "A8")) format = CAIRO_FORMAT_A8;
694 else if (!strcmp(*utf8PixelFormat, "RGB16_565")) format = CAIRO_FORMAT_RGB16_565;
695 else if (!strcmp(*utf8PixelFormat, "A1")) format = CAIRO_FORMAT_A1;
696#ifdef CAIRO_FORMAT_RGB30
697 else if (!strcmp(utf8PixelFormat, "RGB30")) format = CAIRO_FORMAT_RGB30;
698#endif
699 }
700
701 // alpha: false forces use of RGB24
702 Local<Value> alpha = Nan::Get(ctxAttributes, Nan::New("alpha").ToLocalChecked()).ToLocalChecked();
703 if (alpha->IsBoolean() && !Nan::To<bool>(alpha).FromMaybe(false)) {
704 format = CAIRO_FORMAT_RGB24;
705 }
706 }
707 static_cast<ImageBackend*>(canvas->backend())->setFormat(format);
708 }
709
710 Context2d *context = new Context2d(canvas);
711
712 context->Wrap(info.This());
713 info.GetReturnValue().Set(info.This());
714}
715
716/*
717 * Save some external modules as private references.
718 */
719
720NAN_METHOD(Context2d::SaveExternalModules) {
721 _DOMMatrix.Reset(Nan::To<Function>(info[0]).ToLocalChecked());
722 _parseFont.Reset(Nan::To<Function>(info[1]).ToLocalChecked());
723}
724
725/*
726* Get format (string).
727*/
728
729NAN_GETTER(Context2d::GetFormat) {
730 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
731 std::string pixelFormatString;
732 switch (context->canvas()->backend()->getFormat()) {
733 case CAIRO_FORMAT_ARGB32: pixelFormatString = "RGBA32"; break;
734 case CAIRO_FORMAT_RGB24: pixelFormatString = "RGB24"; break;
735 case CAIRO_FORMAT_A8: pixelFormatString = "A8"; break;
736 case CAIRO_FORMAT_A1: pixelFormatString = "A1"; break;
737 case CAIRO_FORMAT_RGB16_565: pixelFormatString = "RGB16_565"; break;
738#ifdef CAIRO_FORMAT_RGB30
739 case CAIRO_FORMAT_RGB30: pixelFormatString = "RGB30"; break;
740#endif
741 default: return info.GetReturnValue().SetNull();
742 }
743 info.GetReturnValue().Set(Nan::New<String>(pixelFormatString).ToLocalChecked());
744}
745
746/*
747 * Create a new page.
748 */
749
750NAN_METHOD(Context2d::AddPage) {
751 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
752 if (context->canvas()->backend()->getName() != "pdf") {
753 return Nan::ThrowError("only PDF canvases support .addPage()");
754 }
755 cairo_show_page(context->context());
756 int width = Nan::To<int32_t>(info[0]).FromMaybe(0);
757 int height = Nan::To<int32_t>(info[1]).FromMaybe(0);
758 if (width < 1) width = context->canvas()->getWidth();
759 if (height < 1) height = context->canvas()->getHeight();
760 cairo_pdf_surface_set_size(context->canvas()->surface(), width, height);
761 return;
762}
763
764/*
765 * Put image data.
766 *
767 * - imageData, dx, dy
768 * - imageData, dx, dy, sx, sy, sw, sh
769 *
770 */
771
772NAN_METHOD(Context2d::PutImageData) {
773 if (!info[0]->IsObject())
774 return Nan::ThrowTypeError("ImageData expected");
775 Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
776 if (!Nan::New(ImageData::constructor)->HasInstance(obj))
777 return Nan::ThrowTypeError("ImageData expected");
778
779 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
780 ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(obj);
781
782 uint8_t *src = imageData->data();
783 uint8_t *dst = context->canvas()->data();
784
785 int dstStride = context->canvas()->stride();
786 int Bpp = dstStride / context->canvas()->getWidth();
787 int srcStride = Bpp * imageData->width();
788
789 int sx = 0
790 , sy = 0
791 , sw = 0
792 , sh = 0
793 , dx = Nan::To<int32_t>(info[1]).FromMaybe(0)
794 , dy = Nan::To<int32_t>(info[2]).FromMaybe(0)
795 , rows
796 , cols;
797
798 switch (info.Length()) {
799 // imageData, dx, dy
800 case 3:
801 sw = imageData->width();
802 sh = imageData->height();
803 break;
804 // imageData, dx, dy, sx, sy, sw, sh
805 case 7:
806 sx = Nan::To<int32_t>(info[3]).FromMaybe(0);
807 sy = Nan::To<int32_t>(info[4]).FromMaybe(0);
808 sw = Nan::To<int32_t>(info[5]).FromMaybe(0);
809 sh = Nan::To<int32_t>(info[6]).FromMaybe(0);
810 // fix up negative height, width
811 if (sw < 0) sx += sw, sw = -sw;
812 if (sh < 0) sy += sh, sh = -sh;
813 // clamp the left edge
814 if (sx < 0) sw += sx, sx = 0;
815 if (sy < 0) sh += sy, sy = 0;
816 // clamp the right edge
817 if (sx + sw > imageData->width()) sw = imageData->width() - sx;
818 if (sy + sh > imageData->height()) sh = imageData->height() - sy;
819 // start destination at source offset
820 dx += sx;
821 dy += sy;
822 break;
823 default:
824 return Nan::ThrowError("invalid arguments");
825 }
826
827 // chop off outlying source data
828 if (dx < 0) sw += dx, sx -= dx, dx = 0;
829 if (dy < 0) sh += dy, sy -= dy, dy = 0;
830 // clamp width at canvas size
831 // Need to wrap std::min calls using parens to prevent macro expansion on
832 // windows. See http://stackoverflow.com/questions/5004858/stdmin-gives-error
833 cols = (std::min)(sw, context->canvas()->getWidth() - dx);
834 rows = (std::min)(sh, context->canvas()->getHeight() - dy);
835
836 if (cols <= 0 || rows <= 0) return;
837
838 switch (context->canvas()->backend()->getFormat()) {
839 case CAIRO_FORMAT_ARGB32: {
840 src += sy * srcStride + sx * 4;
841 dst += dstStride * dy + 4 * dx;
842 for (int y = 0; y < rows; ++y) {
843 uint8_t *dstRow = dst;
844 uint8_t *srcRow = src;
845 for (int x = 0; x < cols; ++x) {
846 // rgba
847 uint8_t r = *srcRow++;
848 uint8_t g = *srcRow++;
849 uint8_t b = *srcRow++;
850 uint8_t a = *srcRow++;
851
852 // argb
853 // performance optimization: fully transparent/opaque pixels can be
854 // processed more efficiently.
855 if (a == 0) {
856 *dstRow++ = 0;
857 *dstRow++ = 0;
858 *dstRow++ = 0;
859 *dstRow++ = 0;
860 } else if (a == 255) {
861 *dstRow++ = b;
862 *dstRow++ = g;
863 *dstRow++ = r;
864 *dstRow++ = a;
865 } else {
866 float alpha = (float)a / 255;
867 *dstRow++ = b * alpha;
868 *dstRow++ = g * alpha;
869 *dstRow++ = r * alpha;
870 *dstRow++ = a;
871 }
872 }
873 dst += dstStride;
874 src += srcStride;
875 }
876 break;
877 }
878 case CAIRO_FORMAT_RGB24: {
879 src += sy * srcStride + sx * 4;
880 dst += dstStride * dy + 4 * dx;
881 for (int y = 0; y < rows; ++y) {
882 uint8_t *dstRow = dst;
883 uint8_t *srcRow = src;
884 for (int x = 0; x < cols; ++x) {
885 // rgba
886 uint8_t r = *srcRow++;
887 uint8_t g = *srcRow++;
888 uint8_t b = *srcRow++;
889 srcRow++;
890
891 // argb
892 *dstRow++ = b;
893 *dstRow++ = g;
894 *dstRow++ = r;
895 *dstRow++ = 255;
896 }
897 dst += dstStride;
898 src += srcStride;
899 }
900 break;
901 }
902 case CAIRO_FORMAT_A8: {
903 src += sy * srcStride + sx;
904 dst += dstStride * dy + dx;
905 if (srcStride == dstStride && cols == dstStride) {
906 // fast path: strides are the same and doing a full-width put
907 memcpy(dst, src, cols * rows);
908 } else {
909 for (int y = 0; y < rows; ++y) {
910 memcpy(dst, src, cols);
911 dst += dstStride;
912 src += srcStride;
913 }
914 }
915 break;
916 }
917 case CAIRO_FORMAT_A1: {
918 // TODO Should this be totally packed, or maintain a stride divisible by 4?
919 Nan::ThrowError("putImageData for CANVAS_FORMAT_A1 is not yet implemented");
920 break;
921 }
922 case CAIRO_FORMAT_RGB16_565: {
923 src += sy * srcStride + sx * 2;
924 dst += dstStride * dy + 2 * dx;
925 for (int y = 0; y < rows; ++y) {
926 memcpy(dst, src, cols * 2);
927 dst += dstStride;
928 src += srcStride;
929 }
930 break;
931 }
932#ifdef CAIRO_FORMAT_RGB30
933 case CAIRO_FORMAT_RGB30: {
934 // TODO
935 Nan::ThrowError("putImageData for CANVAS_FORMAT_RGB30 is not yet implemented");
936 break;
937 }
938#endif
939 default: {
940 Nan::ThrowError("Invalid pixel format");
941 break;
942 }
943 }
944
945 cairo_surface_mark_dirty_rectangle(
946 context->canvas()->surface()
947 , dx
948 , dy
949 , cols
950 , rows);
951}
952
953/*
954 * Get image data.
955 *
956 * - sx, sy, sw, sh
957 *
958 */
959
960NAN_METHOD(Context2d::GetImageData) {
961 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
962 Canvas *canvas = context->canvas();
963
964 int sx = Nan::To<int32_t>(info[0]).FromMaybe(0);
965 int sy = Nan::To<int32_t>(info[1]).FromMaybe(0);
966 int sw = Nan::To<int32_t>(info[2]).FromMaybe(0);
967 int sh = Nan::To<int32_t>(info[3]).FromMaybe(0);
968
969 if (!sw)
970 return Nan::ThrowError("IndexSizeError: The source width is 0.");
971 if (!sh)
972 return Nan::ThrowError("IndexSizeError: The source height is 0.");
973
974 int width = canvas->getWidth();
975 int height = canvas->getHeight();
976
977 if (!width)
978 return Nan::ThrowTypeError("Canvas width is 0");
979 if (!height)
980 return Nan::ThrowTypeError("Canvas height is 0");
981
982 // WebKit and Firefox have this behavior:
983 // Flip the coordinates so the origin is top/left-most:
984 if (sw < 0) {
985 sx += sw;
986 sw = -sw;
987 }
988 if (sh < 0) {
989 sy += sh;
990 sh = -sh;
991 }
992
993 if (sx + sw > width) sw = width - sx;
994 if (sy + sh > height) sh = height - sy;
995
996 // WebKit/moz functionality. node-canvas used to return in either case.
997 if (sw <= 0) sw = 1;
998 if (sh <= 0) sh = 1;
999
1000 // Non-compliant. "Pixels outside the canvas must be returned as transparent
1001 // black." This instead clips the returned array to the canvas area.
1002 if (sx < 0) {
1003 sw += sx;
1004 sx = 0;
1005 }
1006 if (sy < 0) {
1007 sh += sy;
1008 sy = 0;
1009 }
1010
1011 int srcStride = canvas->stride();
1012 int bpp = srcStride / width;
1013 int size = sw * sh * bpp;
1014 int dstStride = sw * bpp;
1015
1016 uint8_t *src = canvas->data();
1017
1018 Local<ArrayBuffer> buffer = ArrayBuffer::New(Isolate::GetCurrent(), size);
1019 Local<TypedArray> dataArray;
1020
1021 if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565) {
1022 dataArray = Uint16Array::New(buffer, 0, size >> 1);
1023 } else {
1024 dataArray = Uint8ClampedArray::New(buffer, 0, size);
1025 }
1026
1027 Nan::TypedArrayContents<uint8_t> typedArrayContents(dataArray);
1028 uint8_t* dst = *typedArrayContents;
1029
1030 switch (canvas->backend()->getFormat()) {
1031 case CAIRO_FORMAT_ARGB32: {
1032 // Rearrange alpha (argb -> rgba), undo alpha pre-multiplication,
1033 // and store in big-endian format
1034 for (int y = 0; y < sh; ++y) {
1035 uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
1036 for (int x = 0; x < sw; ++x) {
1037 int bx = x * 4;
1038 uint32_t *pixel = row + x + sx;
1039 uint8_t a = *pixel >> 24;
1040 uint8_t r = *pixel >> 16;
1041 uint8_t g = *pixel >> 8;
1042 uint8_t b = *pixel;
1043 dst[bx + 3] = a;
1044
1045 // Performance optimization: fully transparent/opaque pixels can be
1046 // processed more efficiently.
1047 if (a == 0 || a == 255) {
1048 dst[bx + 0] = r;
1049 dst[bx + 1] = g;
1050 dst[bx + 2] = b;
1051 } else {
1052 // Undo alpha pre-multiplication
1053 float alphaR = (float)255 / a;
1054 dst[bx + 0] = (int)((float)r * alphaR);
1055 dst[bx + 1] = (int)((float)g * alphaR);
1056 dst[bx + 2] = (int)((float)b * alphaR);
1057 }
1058
1059 }
1060 dst += dstStride;
1061 }
1062 break;
1063 }
1064 case CAIRO_FORMAT_RGB24: {
1065 // Rearrange alpha (argb -> rgba) and store in big-endian format
1066 for (int y = 0; y < sh; ++y) {
1067 uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
1068 for (int x = 0; x < sw; ++x) {
1069 int bx = x * 4;
1070 uint32_t *pixel = row + x + sx;
1071 uint8_t r = *pixel >> 16;
1072 uint8_t g = *pixel >> 8;
1073 uint8_t b = *pixel;
1074
1075 dst[bx + 0] = r;
1076 dst[bx + 1] = g;
1077 dst[bx + 2] = b;
1078 dst[bx + 3] = 255;
1079 }
1080 dst += dstStride;
1081 }
1082 break;
1083 }
1084 case CAIRO_FORMAT_A8: {
1085 for (int y = 0; y < sh; ++y) {
1086 uint8_t *row = (uint8_t *)(src + srcStride * (y + sy));
1087 memcpy(dst, row + sx, dstStride);
1088 dst += dstStride;
1089 }
1090 break;
1091 }
1092 case CAIRO_FORMAT_A1: {
1093 // TODO Should this be totally packed, or maintain a stride divisible by 4?
1094 Nan::ThrowError("getImageData for CANVAS_FORMAT_A1 is not yet implemented");
1095 break;
1096 }
1097 case CAIRO_FORMAT_RGB16_565: {
1098 for (int y = 0; y < sh; ++y) {
1099 uint16_t *row = (uint16_t *)(src + srcStride * (y + sy));
1100 memcpy(dst, row + sx, dstStride);
1101 dst += dstStride;
1102 }
1103 break;
1104 }
1105#ifdef CAIRO_FORMAT_RGB30
1106 case CAIRO_FORMAT_RGB30: {
1107 // TODO
1108 Nan::ThrowError("getImageData for CANVAS_FORMAT_RGB30 is not yet implemented");
1109 break;
1110 }
1111#endif
1112 default: {
1113 // Unlikely
1114 Nan::ThrowError("Invalid pixel format");
1115 break;
1116 }
1117 }
1118
1119 const int argc = 3;
1120 Local<Int32> swHandle = Nan::New(sw);
1121 Local<Int32> shHandle = Nan::New(sh);
1122 Local<Value> argv[argc] = { dataArray, swHandle, shHandle };
1123
1124 Local<Function> ctor = Nan::GetFunction(Nan::New(ImageData::constructor)).ToLocalChecked();
1125 Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
1126
1127 info.GetReturnValue().Set(instance);
1128}
1129
1130/**
1131 * Create `ImageData` with the given dimensions or
1132 * `ImageData` instance for dimensions.
1133 */
1134
1135NAN_METHOD(Context2d::CreateImageData){
1136 Isolate *iso = Isolate::GetCurrent();
1137 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1138 Canvas *canvas = context->canvas();
1139 int32_t width, height;
1140
1141 if (info[0]->IsObject()) {
1142 Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
1143 width = Nan::To<int32_t>(Nan::Get(obj, Nan::New("width").ToLocalChecked()).ToLocalChecked()).FromMaybe(0);
1144 height = Nan::To<int32_t>(Nan::Get(obj, Nan::New("height").ToLocalChecked()).ToLocalChecked()).FromMaybe(0);
1145 } else {
1146 width = Nan::To<int32_t>(info[0]).FromMaybe(0);
1147 height = Nan::To<int32_t>(info[1]).FromMaybe(0);
1148 }
1149
1150 int stride = canvas->stride();
1151 double Bpp = static_cast<double>(stride) / canvas->getWidth();
1152 int nBytes = static_cast<int>(Bpp * width * height + .5);
1153
1154 Local<ArrayBuffer> ab = ArrayBuffer::New(iso, nBytes);
1155 Local<Object> arr;
1156
1157 if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565)
1158 arr = Uint16Array::New(ab, 0, nBytes / 2);
1159 else
1160 arr = Uint8ClampedArray::New(ab, 0, nBytes);
1161
1162 const int argc = 3;
1163 Local<Value> argv[argc] = { arr, Nan::New(width), Nan::New(height) };
1164
1165 Local<Function> ctor = Nan::GetFunction(Nan::New(ImageData::constructor)).ToLocalChecked();
1166 Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
1167
1168 info.GetReturnValue().Set(instance);
1169}
1170
1171/*
1172 * Take a transform matrix and return its components
1173 * 0: angle, 1: scaleX, 2: scaleY, 3: skewX, 4: translateX, 5: translateY
1174 */
1175void decompose_matrix(cairo_matrix_t matrix, double *destination) {
1176 double denom = pow(matrix.xx, 2) + pow(matrix.yx, 2);
1177 destination[0] = atan2(matrix.yx, matrix.xx);
1178 destination[1] = sqrt(denom);
1179 destination[2] = (matrix.xx * matrix.yy - matrix.xy * matrix.yx) / destination[1];
1180 destination[3] = atan2(matrix.xx * matrix.xy + matrix.yx * matrix.yy, denom);
1181 destination[4] = matrix.x0;
1182 destination[5] = matrix.y0;
1183}
1184
1185/*
1186 * Draw image src image to the destination (context).
1187 *
1188 * - dx, dy
1189 * - dx, dy, dw, dh
1190 * - sx, sy, sw, sh, dx, dy, dw, dh
1191 *
1192 */
1193
1194NAN_METHOD(Context2d::DrawImage) {
1195 int infoLen = info.Length();
1196 if (infoLen != 3 && infoLen != 5 && infoLen != 9)
1197 return Nan::ThrowTypeError("Invalid arguments");
1198
1199 if (!info[0]->IsObject())
1200 return Nan::ThrowTypeError("The first argument must be an object");
1201
1202 double args[8];
1203 if(!checkArgs(info, args, infoLen - 1, 1))
1204 return;
1205
1206 double sx = 0
1207 , sy = 0
1208 , sw = 0
1209 , sh = 0
1210 , dx = 0
1211 , dy = 0
1212 , dw = 0
1213 , dh = 0
1214 , source_w = 0
1215 , source_h = 0;
1216
1217 cairo_surface_t *surface;
1218
1219 Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
1220
1221 // Image
1222 if (Nan::New(Image::constructor)->HasInstance(obj)) {
1223 Image *img = Nan::ObjectWrap::Unwrap<Image>(obj);
1224 if (!img->isComplete()) {
1225 return Nan::ThrowError("Image given has not completed loading");
1226 }
1227 source_w = sw = img->width;
1228 source_h = sh = img->height;
1229 surface = img->surface();
1230
1231 // Canvas
1232 } else if (Nan::New(Canvas::constructor)->HasInstance(obj)) {
1233 Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(obj);
1234 source_w = sw = canvas->getWidth();
1235 source_h = sh = canvas->getHeight();
1236 surface = canvas->surface();
1237
1238 // Invalid
1239 } else {
1240 return Nan::ThrowTypeError("Image or Canvas expected");
1241 }
1242
1243 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1244 cairo_t *ctx = context->context();
1245
1246 // Arguments
1247 switch (infoLen) {
1248 // img, sx, sy, sw, sh, dx, dy, dw, dh
1249 case 9:
1250 sx = args[0];
1251 sy = args[1];
1252 sw = args[2];
1253 sh = args[3];
1254 dx = args[4];
1255 dy = args[5];
1256 dw = args[6];
1257 dh = args[7];
1258 break;
1259 // img, dx, dy, dw, dh
1260 case 5:
1261 dx = args[0];
1262 dy = args[1];
1263 dw = args[2];
1264 dh = args[3];
1265 break;
1266 // img, dx, dy
1267 case 3:
1268 dx = args[0];
1269 dy = args[1];
1270 dw = sw;
1271 dh = sh;
1272 break;
1273 }
1274
1275 if (!(sw && sh && dw && dh))
1276 return;
1277
1278 // Start draw
1279 cairo_save(ctx);
1280
1281 cairo_matrix_t matrix;
1282 double transforms[6];
1283 cairo_get_matrix(context->context(), &matrix);
1284 decompose_matrix(matrix, transforms);
1285 // extract the scale value from the current transform so that we know how many pixels we
1286 // need for our extra canvas in the drawImage operation.
1287 double current_scale_x = std::abs(transforms[1]);
1288 double current_scale_y = std::abs(transforms[2]);
1289 double extra_dx = 0;
1290 double extra_dy = 0;
1291 double fx = dw / sw * current_scale_x; // transforms[1] is scale on X
1292 double fy = dh / sh * current_scale_y; // transforms[2] is scale on X
1293 bool needScale = dw != sw || dh != sh;
1294 bool needCut = sw != source_w || sh != source_h || sx < 0 || sy < 0;
1295 bool sameCanvas = surface == context->canvas()->surface();
1296 bool needsExtraSurface = sameCanvas || needCut || needScale;
1297 cairo_surface_t *surfTemp = NULL;
1298 cairo_t *ctxTemp = NULL;
1299
1300 if (needsExtraSurface) {
1301 // we want to create the extra surface as small as possible.
1302 // fx and fy are the total scaling we need to apply to sw, sh.
1303 // from sw and sh we want to remove the part that is outside the source_w and soruce_h
1304 double real_w = sw;
1305 double real_h = sh;
1306 double translate_x = 0;
1307 double translate_y = 0;
1308 // if sx or sy are negative, a part of the area represented by sw and sh is empty
1309 // because there are empty pixels, so we cut it out.
1310 // On the other hand if sx or sy are positive, but sw and sh extend outside the real
1311 // source pixels, we cut the area in that case too.
1312 if (sx < 0) {
1313 extra_dx = -sx * fx;
1314 real_w = sw + sx;
1315 } else if (sx + sw > source_w) {
1316 real_w = sw - (sx + sw - source_w);
1317 }
1318 if (sy < 0) {
1319 extra_dy = -sy * fy;
1320 real_h = sh + sy;
1321 } else if (sy + sh > source_h) {
1322 real_h = sh - (sy + sh - source_h);
1323 }
1324 // if after cutting we are still bigger than source pixels, we restrict again
1325 if (real_w > source_w) {
1326 real_w = source_w;
1327 }
1328 if (real_h > source_h) {
1329 real_h = source_h;
1330 }
1331 // TODO: find a way to limit the surfTemp to real_w and real_h if fx and fy are bigger than 1.
1332 // there are no more pixel than the one available in the source, no need to create a bigger surface.
1333 surfTemp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, round(real_w * fx), round(real_h * fy));
1334 ctxTemp = cairo_create(surfTemp);
1335 cairo_scale(ctxTemp, fx, fy);
1336 if (sx > 0) {
1337 translate_x = sx;
1338 }
1339 if (sy > 0) {
1340 translate_y = sy;
1341 }
1342 cairo_set_source_surface(ctxTemp, surface, -translate_x, -translate_y);
1343 cairo_pattern_set_filter(cairo_get_source(ctxTemp), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST);
1344 cairo_pattern_set_extend(cairo_get_source(ctxTemp), CAIRO_EXTEND_REFLECT);
1345 cairo_paint_with_alpha(ctxTemp, 1);
1346 surface = surfTemp;
1347 }
1348 // apply shadow if there is one
1349 if (context->hasShadow()) {
1350 if(context->state->shadowBlur) {
1351 // we need to create a new surface in order to blur
1352 int pad = context->state->shadowBlur * 2;
1353 cairo_surface_t *shadow_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dw + 2 * pad, dh + 2 * pad);
1354 cairo_t *shadow_context = cairo_create(shadow_surface);
1355
1356 // mask and blur
1357 context->setSourceRGBA(shadow_context, context->state->shadow);
1358 cairo_mask_surface(shadow_context, surface, pad, pad);
1359 context->blur(shadow_surface, context->state->shadowBlur);
1360
1361 // paint
1362 // @note: ShadowBlur looks different in each browser. This implementation matches chrome as close as possible.
1363 // The 1.4 offset comes from visual tests with Chrome. I have read the spec and part of the shadowBlur
1364 // implementation, and its not immediately clear why an offset is necessary, but without it, the result
1365 // in chrome is different.
1366 cairo_set_source_surface(ctx, shadow_surface,
1367 dx + context->state->shadowOffsetX - pad + 1.4,
1368 dy + context->state->shadowOffsetY - pad + 1.4);
1369 cairo_paint(ctx);
1370 // cleanup
1371 cairo_destroy(shadow_context);
1372 cairo_surface_destroy(shadow_surface);
1373 } else {
1374 context->setSourceRGBA(context->state->shadow);
1375 cairo_mask_surface(ctx, surface,
1376 dx + (context->state->shadowOffsetX),
1377 dy + (context->state->shadowOffsetY));
1378 }
1379 }
1380
1381 double scaled_dx = dx;
1382 double scaled_dy = dy;
1383
1384 if (needsExtraSurface && (current_scale_x != 1 || current_scale_y != 1)) {
1385 // in this case our surface contains already current_scale_x, we need to scale back
1386 cairo_scale(ctx, 1 / current_scale_x, 1 / current_scale_y);
1387 scaled_dx *= current_scale_x;
1388 scaled_dy *= current_scale_y;
1389 }
1390 // Paint
1391 cairo_set_source_surface(ctx, surface, scaled_dx + extra_dx, scaled_dy + extra_dy);
1392 cairo_pattern_set_filter(cairo_get_source(ctx), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST);
1393 cairo_pattern_set_extend(cairo_get_source(ctx), CAIRO_EXTEND_NONE);
1394 cairo_paint_with_alpha(ctx, context->state->globalAlpha);
1395
1396 cairo_restore(ctx);
1397
1398 if (needsExtraSurface) {
1399 cairo_destroy(ctxTemp);
1400 cairo_surface_destroy(surfTemp);
1401 }
1402}
1403
1404/*
1405 * Get global alpha.
1406 */
1407
1408NAN_GETTER(Context2d::GetGlobalAlpha) {
1409 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1410 info.GetReturnValue().Set(Nan::New<Number>(context->state->globalAlpha));
1411}
1412
1413/*
1414 * Set global alpha.
1415 */
1416
1417NAN_SETTER(Context2d::SetGlobalAlpha) {
1418 double n = Nan::To<double>(value).FromMaybe(0);
1419 if (n >= 0 && n <= 1) {
1420 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1421 context->state->globalAlpha = n;
1422 }
1423}
1424
1425/*
1426 * Get global composite operation.
1427 */
1428
1429NAN_GETTER(Context2d::GetGlobalCompositeOperation) {
1430 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1431 cairo_t *ctx = context->context();
1432
1433 const char *op = "source-over";
1434 switch (cairo_get_operator(ctx)) {
1435 // composite modes:
1436 case CAIRO_OPERATOR_CLEAR: op = "clear"; break;
1437 case CAIRO_OPERATOR_SOURCE: op = "copy"; break;
1438 case CAIRO_OPERATOR_DEST: op = "destination"; break;
1439 case CAIRO_OPERATOR_OVER: op = "source-over"; break;
1440 case CAIRO_OPERATOR_DEST_OVER: op = "destination-over"; break;
1441 case CAIRO_OPERATOR_IN: op = "source-in"; break;
1442 case CAIRO_OPERATOR_DEST_IN: op = "destination-in"; break;
1443 case CAIRO_OPERATOR_OUT: op = "source-out"; break;
1444 case CAIRO_OPERATOR_DEST_OUT: op = "destination-out"; break;
1445 case CAIRO_OPERATOR_ATOP: op = "source-atop"; break;
1446 case CAIRO_OPERATOR_DEST_ATOP: op = "destination-atop"; break;
1447 case CAIRO_OPERATOR_XOR: op = "xor"; break;
1448 case CAIRO_OPERATOR_ADD: op = "lighter"; break;
1449 // blend modes:
1450 // Note: "source-over" and "normal" are synonyms. Chrome and FF both report
1451 // "source-over" after setting gCO to "normal".
1452 // case CAIRO_OPERATOR_OVER: op = "normal";
1453 case CAIRO_OPERATOR_MULTIPLY: op = "multiply"; break;
1454 case CAIRO_OPERATOR_SCREEN: op = "screen"; break;
1455 case CAIRO_OPERATOR_OVERLAY: op = "overlay"; break;
1456 case CAIRO_OPERATOR_DARKEN: op = "darken"; break;
1457 case CAIRO_OPERATOR_LIGHTEN: op = "lighten"; break;
1458 case CAIRO_OPERATOR_COLOR_DODGE: op = "color-dodge"; break;
1459 case CAIRO_OPERATOR_COLOR_BURN: op = "color-burn"; break;
1460 case CAIRO_OPERATOR_HARD_LIGHT: op = "hard-light"; break;
1461 case CAIRO_OPERATOR_SOFT_LIGHT: op = "soft-light"; break;
1462 case CAIRO_OPERATOR_DIFFERENCE: op = "difference"; break;
1463 case CAIRO_OPERATOR_EXCLUSION: op = "exclusion"; break;
1464 case CAIRO_OPERATOR_HSL_HUE: op = "hue"; break;
1465 case CAIRO_OPERATOR_HSL_SATURATION: op = "saturation"; break;
1466 case CAIRO_OPERATOR_HSL_COLOR: op = "color"; break;
1467 case CAIRO_OPERATOR_HSL_LUMINOSITY: op = "luminosity"; break;
1468 // non-standard:
1469 case CAIRO_OPERATOR_SATURATE: op = "saturate"; break;
1470 }
1471
1472 info.GetReturnValue().Set(Nan::New(op).ToLocalChecked());
1473}
1474
1475/*
1476 * Set pattern quality.
1477 */
1478
1479NAN_SETTER(Context2d::SetPatternQuality) {
1480 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1481 Nan::Utf8String quality(Nan::To<String>(value).ToLocalChecked());
1482 if (0 == strcmp("fast", *quality)) {
1483 context->state->patternQuality = CAIRO_FILTER_FAST;
1484 } else if (0 == strcmp("good", *quality)) {
1485 context->state->patternQuality = CAIRO_FILTER_GOOD;
1486 } else if (0 == strcmp("best", *quality)) {
1487 context->state->patternQuality = CAIRO_FILTER_BEST;
1488 } else if (0 == strcmp("nearest", *quality)) {
1489 context->state->patternQuality = CAIRO_FILTER_NEAREST;
1490 } else if (0 == strcmp("bilinear", *quality)) {
1491 context->state->patternQuality = CAIRO_FILTER_BILINEAR;
1492 }
1493}
1494
1495/*
1496 * Get pattern quality.
1497 */
1498
1499NAN_GETTER(Context2d::GetPatternQuality) {
1500 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1501 const char *quality;
1502 switch (context->state->patternQuality) {
1503 case CAIRO_FILTER_FAST: quality = "fast"; break;
1504 case CAIRO_FILTER_BEST: quality = "best"; break;
1505 case CAIRO_FILTER_NEAREST: quality = "nearest"; break;
1506 case CAIRO_FILTER_BILINEAR: quality = "bilinear"; break;
1507 default: quality = "good";
1508 }
1509 info.GetReturnValue().Set(Nan::New(quality).ToLocalChecked());
1510}
1511
1512/*
1513 * Set ImageSmoothingEnabled value.
1514 */
1515
1516NAN_SETTER(Context2d::SetImageSmoothingEnabled) {
1517 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1518 context->state->imageSmoothingEnabled = Nan::To<bool>(value).FromMaybe(false);
1519}
1520
1521/*
1522 * Get pattern quality.
1523 */
1524
1525NAN_GETTER(Context2d::GetImageSmoothingEnabled) {
1526 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1527 info.GetReturnValue().Set(Nan::New<Boolean>(context->state->imageSmoothingEnabled));
1528}
1529
1530/*
1531 * Set global composite operation.
1532 */
1533
1534NAN_SETTER(Context2d::SetGlobalCompositeOperation) {
1535 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1536 cairo_t *ctx = context->context();
1537 Nan::Utf8String opStr(Nan::To<String>(value).ToLocalChecked()); // Unlike CSS colors, this *is* case-sensitive
1538 const std::map<std::string, cairo_operator_t> blendmodes = {
1539 // composite modes:
1540 {"clear", CAIRO_OPERATOR_CLEAR},
1541 {"copy", CAIRO_OPERATOR_SOURCE},
1542 {"destination", CAIRO_OPERATOR_DEST}, // this seems to have been omitted from the spec
1543 {"source-over", CAIRO_OPERATOR_OVER},
1544 {"destination-over", CAIRO_OPERATOR_DEST_OVER},
1545 {"source-in", CAIRO_OPERATOR_IN},
1546 {"destination-in", CAIRO_OPERATOR_DEST_IN},
1547 {"source-out", CAIRO_OPERATOR_OUT},
1548 {"destination-out", CAIRO_OPERATOR_DEST_OUT},
1549 {"source-atop", CAIRO_OPERATOR_ATOP},
1550 {"destination-atop", CAIRO_OPERATOR_DEST_ATOP},
1551 {"xor", CAIRO_OPERATOR_XOR},
1552 {"lighter", CAIRO_OPERATOR_ADD},
1553 // blend modes:
1554 {"normal", CAIRO_OPERATOR_OVER},
1555 {"multiply", CAIRO_OPERATOR_MULTIPLY},
1556 {"screen", CAIRO_OPERATOR_SCREEN},
1557 {"overlay", CAIRO_OPERATOR_OVERLAY},
1558 {"darken", CAIRO_OPERATOR_DARKEN},
1559 {"lighten", CAIRO_OPERATOR_LIGHTEN},
1560 {"color-dodge", CAIRO_OPERATOR_COLOR_DODGE},
1561 {"color-burn", CAIRO_OPERATOR_COLOR_BURN},
1562 {"hard-light", CAIRO_OPERATOR_HARD_LIGHT},
1563 {"soft-light", CAIRO_OPERATOR_SOFT_LIGHT},
1564 {"difference", CAIRO_OPERATOR_DIFFERENCE},
1565 {"exclusion", CAIRO_OPERATOR_EXCLUSION},
1566 {"hue", CAIRO_OPERATOR_HSL_HUE},
1567 {"saturation", CAIRO_OPERATOR_HSL_SATURATION},
1568 {"color", CAIRO_OPERATOR_HSL_COLOR},
1569 {"luminosity", CAIRO_OPERATOR_HSL_LUMINOSITY},
1570 // non-standard:
1571 {"saturate", CAIRO_OPERATOR_SATURATE}
1572 };
1573 auto op = blendmodes.find(*opStr);
1574 if (op != blendmodes.end()) cairo_set_operator(ctx, op->second);
1575}
1576
1577/*
1578 * Get shadow offset x.
1579 */
1580
1581NAN_GETTER(Context2d::GetShadowOffsetX) {
1582 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1583 info.GetReturnValue().Set(Nan::New<Number>(context->state->shadowOffsetX));
1584}
1585
1586/*
1587 * Set shadow offset x.
1588 */
1589
1590NAN_SETTER(Context2d::SetShadowOffsetX) {
1591 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1592 context->state->shadowOffsetX = Nan::To<double>(value).FromMaybe(0);
1593}
1594
1595/*
1596 * Get shadow offset y.
1597 */
1598
1599NAN_GETTER(Context2d::GetShadowOffsetY) {
1600 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1601 info.GetReturnValue().Set(Nan::New<Number>(context->state->shadowOffsetY));
1602}
1603
1604/*
1605 * Set shadow offset y.
1606 */
1607
1608NAN_SETTER(Context2d::SetShadowOffsetY) {
1609 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1610 context->state->shadowOffsetY = Nan::To<double>(value).FromMaybe(0);
1611}
1612
1613/*
1614 * Get shadow blur.
1615 */
1616
1617NAN_GETTER(Context2d::GetShadowBlur) {
1618 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1619 info.GetReturnValue().Set(Nan::New<Number>(context->state->shadowBlur));
1620}
1621
1622/*
1623 * Set shadow blur.
1624 */
1625
1626NAN_SETTER(Context2d::SetShadowBlur) {
1627 int n = Nan::To<double>(value).FromMaybe(0);
1628 if (n >= 0) {
1629 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1630 context->state->shadowBlur = n;
1631 }
1632}
1633
1634/*
1635 * Get current antialiasing setting.
1636 */
1637
1638NAN_GETTER(Context2d::GetAntiAlias) {
1639 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1640 const char *aa;
1641 switch (cairo_get_antialias(context->context())) {
1642 case CAIRO_ANTIALIAS_NONE: aa = "none"; break;
1643 case CAIRO_ANTIALIAS_GRAY: aa = "gray"; break;
1644 case CAIRO_ANTIALIAS_SUBPIXEL: aa = "subpixel"; break;
1645 default: aa = "default";
1646 }
1647 info.GetReturnValue().Set(Nan::New(aa).ToLocalChecked());
1648}
1649
1650/*
1651 * Set antialiasing.
1652 */
1653
1654NAN_SETTER(Context2d::SetAntiAlias) {
1655 Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
1656 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1657 cairo_t *ctx = context->context();
1658 cairo_antialias_t a;
1659 if (0 == strcmp("none", *str)) {
1660 a = CAIRO_ANTIALIAS_NONE;
1661 } else if (0 == strcmp("default", *str)) {
1662 a = CAIRO_ANTIALIAS_DEFAULT;
1663 } else if (0 == strcmp("gray", *str)) {
1664 a = CAIRO_ANTIALIAS_GRAY;
1665 } else if (0 == strcmp("subpixel", *str)) {
1666 a = CAIRO_ANTIALIAS_SUBPIXEL;
1667 } else {
1668 a = cairo_get_antialias(ctx);
1669 }
1670 cairo_set_antialias(ctx, a);
1671}
1672
1673/*
1674 * Get text drawing mode.
1675 */
1676
1677NAN_GETTER(Context2d::GetTextDrawingMode) {
1678 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1679 const char *mode;
1680 if (context->state->textDrawingMode == TEXT_DRAW_PATHS) {
1681 mode = "path";
1682 } else if (context->state->textDrawingMode == TEXT_DRAW_GLYPHS) {
1683 mode = "glyph";
1684 } else {
1685 mode = "unknown";
1686 }
1687 info.GetReturnValue().Set(Nan::New(mode).ToLocalChecked());
1688}
1689
1690/*
1691 * Set text drawing mode.
1692 */
1693
1694NAN_SETTER(Context2d::SetTextDrawingMode) {
1695 Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
1696 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1697 if (0 == strcmp("path", *str)) {
1698 context->state->textDrawingMode = TEXT_DRAW_PATHS;
1699 } else if (0 == strcmp("glyph", *str)) {
1700 context->state->textDrawingMode = TEXT_DRAW_GLYPHS;
1701 }
1702}
1703
1704/*
1705 * Get filter.
1706 */
1707
1708NAN_GETTER(Context2d::GetQuality) {
1709 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1710 const char *filter;
1711 switch (cairo_pattern_get_filter(cairo_get_source(context->context()))) {
1712 case CAIRO_FILTER_FAST: filter = "fast"; break;
1713 case CAIRO_FILTER_BEST: filter = "best"; break;
1714 case CAIRO_FILTER_NEAREST: filter = "nearest"; break;
1715 case CAIRO_FILTER_BILINEAR: filter = "bilinear"; break;
1716 default: filter = "good";
1717 }
1718 info.GetReturnValue().Set(Nan::New(filter).ToLocalChecked());
1719}
1720
1721/*
1722 * Set filter.
1723 */
1724
1725NAN_SETTER(Context2d::SetQuality) {
1726 Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
1727 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1728 cairo_filter_t filter;
1729 if (0 == strcmp("fast", *str)) {
1730 filter = CAIRO_FILTER_FAST;
1731 } else if (0 == strcmp("best", *str)) {
1732 filter = CAIRO_FILTER_BEST;
1733 } else if (0 == strcmp("nearest", *str)) {
1734 filter = CAIRO_FILTER_NEAREST;
1735 } else if (0 == strcmp("bilinear", *str)) {
1736 filter = CAIRO_FILTER_BILINEAR;
1737 } else {
1738 filter = CAIRO_FILTER_GOOD;
1739 }
1740 cairo_pattern_set_filter(cairo_get_source(context->context()), filter);
1741}
1742
1743/*
1744 * Get current transform.
1745 */
1746
1747NAN_GETTER(Context2d::GetCurrentTransform) {
1748 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1749 Isolate *iso = Isolate::GetCurrent();
1750
1751 Local<Float64Array> arr = Float64Array::New(ArrayBuffer::New(iso, 48), 0, 6);
1752 Nan::TypedArrayContents<double> dest(arr);
1753 cairo_matrix_t matrix;
1754 cairo_get_matrix(context->context(), &matrix);
1755 (*dest)[0] = matrix.xx;
1756 (*dest)[1] = matrix.yx;
1757 (*dest)[2] = matrix.xy;
1758 (*dest)[3] = matrix.yy;
1759 (*dest)[4] = matrix.x0;
1760 (*dest)[5] = matrix.y0;
1761
1762 const int argc = 1;
1763 Local<Value> argv[argc] = { arr };
1764 Local<Object> instance = Nan::NewInstance(_DOMMatrix.Get(iso), argc, argv).ToLocalChecked();
1765
1766 info.GetReturnValue().Set(instance);
1767}
1768
1769/*
1770 * Set current transform.
1771 */
1772
1773NAN_SETTER(Context2d::SetCurrentTransform) {
1774 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1775 Local<Context> ctx = Nan::GetCurrentContext();
1776 Local<Object> mat = Nan::To<Object>(value).ToLocalChecked();
1777
1778#if NODE_MAJOR_VERSION >= 8
1779 if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) {
1780 return Nan::ThrowTypeError("Expected DOMMatrix");
1781 }
1782#endif
1783
1784 cairo_matrix_t matrix;
1785 cairo_matrix_init(&matrix,
1786 Nan::To<double>(Nan::Get(mat, Nan::New("a").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
1787 Nan::To<double>(Nan::Get(mat, Nan::New("b").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
1788 Nan::To<double>(Nan::Get(mat, Nan::New("c").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
1789 Nan::To<double>(Nan::Get(mat, Nan::New("d").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
1790 Nan::To<double>(Nan::Get(mat, Nan::New("e").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
1791 Nan::To<double>(Nan::Get(mat, Nan::New("f").ToLocalChecked()).ToLocalChecked()).FromMaybe(0)
1792 );
1793
1794 cairo_transform(context->context(), &matrix);
1795}
1796
1797/*
1798 * Get current fill style.
1799 */
1800
1801NAN_GETTER(Context2d::GetFillStyle) {
1802 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1803 Isolate *iso = Isolate::GetCurrent();
1804 Local<Value> style;
1805
1806 if (context->_fillStyle.IsEmpty())
1807 style = context->_getFillColor();
1808 else
1809 style = context->_fillStyle.Get(iso);
1810
1811 info.GetReturnValue().Set(style);
1812}
1813
1814/*
1815 * Set current fill style.
1816 */
1817
1818NAN_SETTER(Context2d::SetFillStyle) {
1819 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1820
1821 if (Nan::New(Gradient::constructor)->HasInstance(value) ||
1822 Nan::New(Pattern::constructor)->HasInstance(value)) {
1823 context->_fillStyle.Reset(value);
1824
1825 Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
1826 if (Nan::New(Gradient::constructor)->HasInstance(obj)){
1827 Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
1828 context->state->fillGradient = grad->pattern();
1829 } else if(Nan::New(Pattern::constructor)->HasInstance(obj)){
1830 Pattern *pattern = Nan::ObjectWrap::Unwrap<Pattern>(obj);
1831 context->state->fillPattern = pattern->pattern();
1832 }
1833 } else {
1834 MaybeLocal<String> mstr = Nan::To<String>(value);
1835 if (mstr.IsEmpty()) return;
1836 Local<String> str = mstr.ToLocalChecked();
1837 context->_fillStyle.Reset();
1838 context->_setFillColor(str);
1839 }
1840}
1841
1842/*
1843 * Get current stroke style.
1844 */
1845
1846NAN_GETTER(Context2d::GetStrokeStyle) {
1847 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1848 Local<Value> style;
1849
1850 if (context->_strokeStyle.IsEmpty())
1851 style = context->_getStrokeColor();
1852 else
1853 style = context->_strokeStyle.Get(Isolate::GetCurrent());
1854
1855 info.GetReturnValue().Set(style);
1856}
1857
1858/*
1859 * Set current stroke style.
1860 */
1861
1862NAN_SETTER(Context2d::SetStrokeStyle) {
1863 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1864
1865 if (Nan::New(Gradient::constructor)->HasInstance(value) ||
1866 Nan::New(Pattern::constructor)->HasInstance(value)) {
1867 context->_strokeStyle.Reset(value);
1868
1869 Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
1870 if (Nan::New(Gradient::constructor)->HasInstance(obj)){
1871 Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
1872 context->state->strokeGradient = grad->pattern();
1873 } else if(Nan::New(Pattern::constructor)->HasInstance(obj)){
1874 Pattern *pattern = Nan::ObjectWrap::Unwrap<Pattern>(obj);
1875 context->state->strokePattern = pattern->pattern();
1876 } else {
1877 return Nan::ThrowTypeError("Gradient or Pattern expected");
1878 }
1879 } else {
1880 MaybeLocal<String> mstr = Nan::To<String>(value);
1881 if (mstr.IsEmpty()) return;
1882 Local<String> str = mstr.ToLocalChecked();
1883 context->_strokeStyle.Reset();
1884 context->_setStrokeColor(str);
1885 }
1886}
1887
1888/*
1889 * Get miter limit.
1890 */
1891
1892NAN_GETTER(Context2d::GetMiterLimit) {
1893 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1894 info.GetReturnValue().Set(Nan::New<Number>(cairo_get_miter_limit(context->context())));
1895}
1896
1897/*
1898 * Set miter limit.
1899 */
1900
1901NAN_SETTER(Context2d::SetMiterLimit) {
1902 double n = Nan::To<double>(value).FromMaybe(0);
1903 if (n > 0) {
1904 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1905 cairo_set_miter_limit(context->context(), n);
1906 }
1907}
1908
1909/*
1910 * Get line width.
1911 */
1912
1913NAN_GETTER(Context2d::GetLineWidth) {
1914 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1915 info.GetReturnValue().Set(Nan::New<Number>(cairo_get_line_width(context->context())));
1916}
1917
1918/*
1919 * Set line width.
1920 */
1921
1922NAN_SETTER(Context2d::SetLineWidth) {
1923 double n = Nan::To<double>(value).FromMaybe(0);
1924 if (n > 0 && n != std::numeric_limits<double>::infinity()) {
1925 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1926 cairo_set_line_width(context->context(), n);
1927 }
1928}
1929
1930/*
1931 * Get line join.
1932 */
1933
1934NAN_GETTER(Context2d::GetLineJoin) {
1935 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1936 const char *join;
1937 switch (cairo_get_line_join(context->context())) {
1938 case CAIRO_LINE_JOIN_BEVEL: join = "bevel"; break;
1939 case CAIRO_LINE_JOIN_ROUND: join = "round"; break;
1940 default: join = "miter";
1941 }
1942 info.GetReturnValue().Set(Nan::New(join).ToLocalChecked());
1943}
1944
1945/*
1946 * Set line join.
1947 */
1948
1949NAN_SETTER(Context2d::SetLineJoin) {
1950 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1951 cairo_t *ctx = context->context();
1952 Nan::Utf8String type(Nan::To<String>(value).ToLocalChecked());
1953 if (0 == strcmp("round", *type)) {
1954 cairo_set_line_join(ctx, CAIRO_LINE_JOIN_ROUND);
1955 } else if (0 == strcmp("bevel", *type)) {
1956 cairo_set_line_join(ctx, CAIRO_LINE_JOIN_BEVEL);
1957 } else {
1958 cairo_set_line_join(ctx, CAIRO_LINE_JOIN_MITER);
1959 }
1960}
1961
1962/*
1963 * Get line cap.
1964 */
1965
1966NAN_GETTER(Context2d::GetLineCap) {
1967 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1968 const char *cap;
1969 switch (cairo_get_line_cap(context->context())) {
1970 case CAIRO_LINE_CAP_ROUND: cap = "round"; break;
1971 case CAIRO_LINE_CAP_SQUARE: cap = "square"; break;
1972 default: cap = "butt";
1973 }
1974 info.GetReturnValue().Set(Nan::New(cap).ToLocalChecked());
1975}
1976
1977/*
1978 * Set line cap.
1979 */
1980
1981NAN_SETTER(Context2d::SetLineCap) {
1982 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
1983 cairo_t *ctx = context->context();
1984 Nan::Utf8String type(Nan::To<String>(value).ToLocalChecked());
1985 if (0 == strcmp("round", *type)) {
1986 cairo_set_line_cap(ctx, CAIRO_LINE_CAP_ROUND);
1987 } else if (0 == strcmp("square", *type)) {
1988 cairo_set_line_cap(ctx, CAIRO_LINE_CAP_SQUARE);
1989 } else {
1990 cairo_set_line_cap(ctx, CAIRO_LINE_CAP_BUTT);
1991 }
1992}
1993
1994/*
1995 * Check if the given point is within the current path.
1996 */
1997
1998NAN_METHOD(Context2d::IsPointInPath) {
1999 if (info[0]->IsNumber() && info[1]->IsNumber()) {
2000 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2001 cairo_t *ctx = context->context();
2002 double x = Nan::To<double>(info[0]).FromMaybe(0)
2003 , y = Nan::To<double>(info[1]).FromMaybe(0);
2004 context->setFillRule(info[2]);
2005 info.GetReturnValue().Set(Nan::New<Boolean>(cairo_in_fill(ctx, x, y) || cairo_in_stroke(ctx, x, y)));
2006 return;
2007 }
2008 info.GetReturnValue().Set(Nan::False());
2009}
2010
2011/*
2012 * Set shadow color.
2013 */
2014
2015NAN_SETTER(Context2d::SetShadowColor) {
2016 short ok;
2017 Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
2018 uint32_t rgba = rgba_from_string(*str, &ok);
2019 if (ok) {
2020 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2021 context->state->shadow = rgba_create(rgba);
2022 }
2023}
2024
2025/*
2026 * Get shadow color.
2027 */
2028
2029NAN_GETTER(Context2d::GetShadowColor) {
2030 char buf[64];
2031 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2032 rgba_to_string(context->state->shadow, buf, sizeof(buf));
2033 info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
2034}
2035
2036/*
2037 * Set fill color, used internally for fillStyle=
2038 */
2039
2040void Context2d::_setFillColor(Local<Value> arg) {
2041 short ok;
2042 Nan::Utf8String str(arg);
2043 uint32_t rgba = rgba_from_string(*str, &ok);
2044 if (!ok) return;
2045 state->fillPattern = state->fillGradient = NULL;
2046 state->fill = rgba_create(rgba);
2047}
2048
2049/*
2050 * Get fill color.
2051 */
2052
2053Local<Value> Context2d::_getFillColor() {
2054 char buf[64];
2055 rgba_to_string(state->fill, buf, sizeof(buf));
2056 return Nan::New<String>(buf).ToLocalChecked();
2057}
2058
2059/*
2060 * Set stroke color, used internally for strokeStyle=
2061 */
2062
2063void Context2d::_setStrokeColor(Local<Value> arg) {
2064 short ok;
2065 Nan::Utf8String str(arg);
2066 uint32_t rgba = rgba_from_string(*str, &ok);
2067 if (!ok) return;
2068 state->strokePattern = state->strokeGradient = NULL;
2069 state->stroke = rgba_create(rgba);
2070}
2071
2072/*
2073 * Get stroke color.
2074 */
2075
2076Local<Value> Context2d::_getStrokeColor() {
2077 char buf[64];
2078 rgba_to_string(state->stroke, buf, sizeof(buf));
2079 return Nan::New<String>(buf).ToLocalChecked();
2080}
2081
2082NAN_METHOD(Context2d::CreatePattern) {
2083 Local<Value> image = info[0];
2084 Local<Value> repetition = info[1];
2085
2086 if (!Nan::To<bool>(repetition).FromMaybe(false))
2087 repetition = Nan::New("repeat").ToLocalChecked();
2088
2089 const int argc = 2;
2090 Local<Value> argv[argc] = { image, repetition };
2091
2092 Local<Function> ctor = Nan::GetFunction(Nan::New(Pattern::constructor)).ToLocalChecked();
2093 Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
2094
2095 info.GetReturnValue().Set(instance);
2096}
2097
2098NAN_METHOD(Context2d::CreateLinearGradient) {
2099 const int argc = 4;
2100 Local<Value> argv[argc] = { info[0], info[1], info[2], info[3] };
2101
2102 Local<Function> ctor = Nan::GetFunction(Nan::New(Gradient::constructor)).ToLocalChecked();
2103 Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
2104
2105 info.GetReturnValue().Set(instance);
2106}
2107
2108NAN_METHOD(Context2d::CreateRadialGradient) {
2109 const int argc = 6;
2110 Local<Value> argv[argc] = { info[0], info[1], info[2], info[3], info[4], info[5] };
2111
2112 Local<Function> ctor = Nan::GetFunction(Nan::New(Gradient::constructor)).ToLocalChecked();
2113 Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
2114
2115 info.GetReturnValue().Set(instance);
2116}
2117
2118/*
2119 * Bezier curve.
2120 */
2121
2122NAN_METHOD(Context2d::BezierCurveTo) {
2123 double args[6];
2124 if(!checkArgs(info, args, 6))
2125 return;
2126
2127 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2128 cairo_curve_to(context->context()
2129 , args[0]
2130 , args[1]
2131 , args[2]
2132 , args[3]
2133 , args[4]
2134 , args[5]);
2135}
2136
2137/*
2138 * Quadratic curve approximation from libsvg-cairo.
2139 */
2140
2141NAN_METHOD(Context2d::QuadraticCurveTo) {
2142 double args[4];
2143 if(!checkArgs(info, args, 4))
2144 return;
2145
2146 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2147 cairo_t *ctx = context->context();
2148
2149 double x, y
2150 , x1 = args[0]
2151 , y1 = args[1]
2152 , x2 = args[2]
2153 , y2 = args[3];
2154
2155 cairo_get_current_point(ctx, &x, &y);
2156
2157 if (0 == x && 0 == y) {
2158 x = x1;
2159 y = y1;
2160 }
2161
2162 cairo_curve_to(ctx
2163 , x + 2.0 / 3.0 * (x1 - x), y + 2.0 / 3.0 * (y1 - y)
2164 , x2 + 2.0 / 3.0 * (x1 - x2), y2 + 2.0 / 3.0 * (y1 - y2)
2165 , x2
2166 , y2);
2167}
2168
2169/*
2170 * Save state.
2171 */
2172
2173NAN_METHOD(Context2d::Save) {
2174 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2175 context->save();
2176}
2177
2178/*
2179 * Restore state.
2180 */
2181
2182NAN_METHOD(Context2d::Restore) {
2183 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2184 context->restore();
2185}
2186
2187/*
2188 * Creates a new subpath.
2189 */
2190
2191NAN_METHOD(Context2d::BeginPath) {
2192 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2193 cairo_new_path(context->context());
2194}
2195
2196/*
2197 * Marks the subpath as closed.
2198 */
2199
2200NAN_METHOD(Context2d::ClosePath) {
2201 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2202 cairo_close_path(context->context());
2203}
2204
2205/*
2206 * Rotate transformation.
2207 */
2208
2209NAN_METHOD(Context2d::Rotate) {
2210 double args[1];
2211 if(!checkArgs(info, args, 1))
2212 return;
2213
2214 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2215 cairo_rotate(context->context(), args[0]);
2216}
2217
2218/*
2219 * Modify the CTM.
2220 */
2221
2222NAN_METHOD(Context2d::Transform) {
2223 double args[6];
2224 if(!checkArgs(info, args, 6))
2225 return;
2226
2227 cairo_matrix_t matrix;
2228 cairo_matrix_init(&matrix
2229 , args[0]
2230 , args[1]
2231 , args[2]
2232 , args[3]
2233 , args[4]
2234 , args[5]);
2235
2236 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2237 cairo_transform(context->context(), &matrix);
2238}
2239
2240/*
2241 * Reset the CTM, used internally by setTransform().
2242 */
2243
2244NAN_METHOD(Context2d::ResetTransform) {
2245 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2246 cairo_identity_matrix(context->context());
2247}
2248
2249/*
2250 * Reset transform matrix to identity, then apply the given args.
2251 */
2252
2253NAN_METHOD(Context2d::SetTransform) {
2254 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2255 cairo_identity_matrix(context->context());
2256 Context2d::Transform(info);
2257}
2258
2259/*
2260 * Translate transformation.
2261 */
2262
2263NAN_METHOD(Context2d::Translate) {
2264 double args[2];
2265 if(!checkArgs(info, args, 2))
2266 return;
2267
2268 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2269 cairo_translate(context->context(), args[0], args[1]);
2270}
2271
2272/*
2273 * Scale transformation.
2274 */
2275
2276NAN_METHOD(Context2d::Scale) {
2277 double args[2];
2278 if(!checkArgs(info, args, 2))
2279 return;
2280
2281 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2282 cairo_scale(context->context(), args[0], args[1]);
2283}
2284
2285/*
2286 * Use path as clipping region.
2287 */
2288
2289NAN_METHOD(Context2d::Clip) {
2290 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2291 context->setFillRule(info[0]);
2292 cairo_t *ctx = context->context();
2293 cairo_clip_preserve(ctx);
2294}
2295
2296/*
2297 * Fill the path.
2298 */
2299
2300NAN_METHOD(Context2d::Fill) {
2301 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2302 context->setFillRule(info[0]);
2303 context->fill(true);
2304}
2305
2306/*
2307 * Stroke the path.
2308 */
2309
2310NAN_METHOD(Context2d::Stroke) {
2311 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2312 context->stroke(true);
2313}
2314
2315/*
2316 * Helper for fillText/strokeText
2317 */
2318
2319double
2320get_text_scale(PangoLayout *layout, double maxWidth) {
2321
2322 PangoRectangle logical_rect;
2323 pango_layout_get_pixel_extents(layout, NULL, &logical_rect);
2324
2325 if (logical_rect.width > maxWidth) {
2326 return maxWidth / logical_rect.width;
2327 } else {
2328 return 1.0;
2329 }
2330}
2331
2332void
2333paintText(const Nan::FunctionCallbackInfo<Value> &info, bool stroke) {
2334 int argsNum = info.Length() >= 4 ? 3 : 2;
2335
2336 if (argsNum == 3 && info[3]->IsUndefined())
2337 argsNum = 2;
2338
2339 double args[3];
2340 if(!checkArgs(info, args, argsNum, 1))
2341 return;
2342
2343 Nan::Utf8String str(Nan::To<String>(info[0]).ToLocalChecked());
2344 double x = args[0];
2345 double y = args[1];
2346 double scaled_by = 1;
2347
2348 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2349 PangoLayout *layout = context->layout();
2350
2351 pango_layout_set_text(layout, *str, -1);
2352 pango_cairo_update_layout(context->context(), layout);
2353
2354 if (argsNum == 3) {
2355 scaled_by = get_text_scale(layout, args[2]);
2356 cairo_save(context->context());
2357 cairo_scale(context->context(), scaled_by, 1);
2358 }
2359
2360 context->savePath();
2361 if (context->state->textDrawingMode == TEXT_DRAW_GLYPHS) {
2362 if (stroke == true) { context->stroke(); } else { context->fill(); }
2363 context->setTextPath(x / scaled_by, y);
2364 } else if (context->state->textDrawingMode == TEXT_DRAW_PATHS) {
2365 context->setTextPath(x / scaled_by, y);
2366 if (stroke == true) { context->stroke(); } else { context->fill(); }
2367 }
2368 context->restorePath();
2369 if (argsNum == 3) {
2370 cairo_restore(context->context());
2371 }
2372}
2373
2374/*
2375 * Fill text at (x, y).
2376 */
2377
2378NAN_METHOD(Context2d::FillText) {
2379 paintText(info, false);
2380}
2381
2382/*
2383 * Stroke text at (x ,y).
2384 */
2385
2386NAN_METHOD(Context2d::StrokeText) {
2387 paintText(info, true);
2388}
2389
2390/*
2391 * Gets the baseline adjustment in device pixels
2392 */
2393inline double getBaselineAdjustment(PangoLayout* layout, short baseline) {
2394 PangoRectangle logical_rect;
2395 pango_layout_line_get_extents(pango_layout_get_line(layout, 0), NULL, &logical_rect);
2396
2397 double scale = 1.0 / PANGO_SCALE;
2398 double ascent = scale * pango_layout_get_baseline(layout);
2399 double descent = scale * logical_rect.height - ascent;
2400
2401 switch (baseline) {
2402 case TEXT_BASELINE_ALPHABETIC:
2403 return ascent;
2404 case TEXT_BASELINE_MIDDLE:
2405 return (ascent + descent) / 2.0;
2406 case TEXT_BASELINE_BOTTOM:
2407 return ascent + descent;
2408 default:
2409 return 0;
2410 }
2411}
2412
2413/*
2414 * Set text path for the string in the layout at (x, y).
2415 * This function is called by paintText and won't behave correctly
2416 * if is not called from there.
2417 * it needs pango_layout_set_text and pango_cairo_update_layout to be called before
2418 */
2419
2420void
2421Context2d::setTextPath(double x, double y) {
2422 PangoRectangle logical_rect;
2423
2424 switch (state->textAlignment) {
2425 // center
2426 case 0:
2427 pango_layout_get_pixel_extents(_layout, NULL, &logical_rect);
2428 x -= logical_rect.width / 2;
2429 break;
2430 // right
2431 case 1:
2432 pango_layout_get_pixel_extents(_layout, NULL, &logical_rect);
2433 x -= logical_rect.width;
2434 break;
2435 }
2436
2437 y -= getBaselineAdjustment(_layout, state->textBaseline);
2438
2439 cairo_move_to(_context, x, y);
2440 if (state->textDrawingMode == TEXT_DRAW_PATHS) {
2441 pango_cairo_layout_path(_context, _layout);
2442 } else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) {
2443 pango_cairo_show_layout(_context, _layout);
2444 }
2445}
2446
2447/*
2448 * Adds a point to the current subpath.
2449 */
2450
2451NAN_METHOD(Context2d::LineTo) {
2452 double args[2];
2453 if(!checkArgs(info, args, 2))
2454 return;
2455
2456 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2457 cairo_line_to(context->context(), args[0], args[1]);
2458}
2459
2460/*
2461 * Creates a new subpath at the given point.
2462 */
2463
2464NAN_METHOD(Context2d::MoveTo) {
2465 double args[2];
2466 if(!checkArgs(info, args, 2))
2467 return;
2468
2469 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2470 cairo_move_to(context->context(), args[0], args[1]);
2471}
2472
2473/*
2474 * Get font.
2475 */
2476
2477NAN_GETTER(Context2d::GetFont) {
2478 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2479 Isolate *iso = Isolate::GetCurrent();
2480 Local<Value> font;
2481
2482 if (context->_font.IsEmpty())
2483 font = Nan::New("10px sans-serif").ToLocalChecked();
2484 else
2485 font = context->_font.Get(iso);
2486
2487 info.GetReturnValue().Set(font);
2488}
2489
2490/*
2491 * Set font:
2492 * - weight
2493 * - style
2494 * - size
2495 * - unit
2496 * - family
2497 */
2498
2499NAN_SETTER(Context2d::SetFont) {
2500 if (!value->IsString()) return;
2501
2502 Isolate *iso = Isolate::GetCurrent();
2503 Local<Context> ctx = Nan::GetCurrentContext();
2504
2505 Local<String> str = Nan::To<String>(value).ToLocalChecked();
2506 if (!str->Length()) return;
2507
2508 const int argc = 1;
2509 Local<Value> argv[argc] = { value };
2510
2511 Local<Value> mparsed = Nan::Call(_parseFont.Get(iso), ctx->Global(), argc, argv).ToLocalChecked();
2512 // parseFont returns undefined for invalid CSS font strings
2513 if (mparsed->IsUndefined()) return;
2514 Local<Object> font = Nan::To<Object>(mparsed).ToLocalChecked();
2515
2516 Nan::Utf8String weight(Nan::Get(font, Nan::New("weight").ToLocalChecked()).ToLocalChecked());
2517 Nan::Utf8String style(Nan::Get(font, Nan::New("style").ToLocalChecked()).ToLocalChecked());
2518 double size = Nan::To<double>(Nan::Get(font, Nan::New("size").ToLocalChecked()).ToLocalChecked()).FromMaybe(0);
2519 Nan::Utf8String unit(Nan::Get(font, Nan::New("unit").ToLocalChecked()).ToLocalChecked());
2520 Nan::Utf8String family(Nan::Get(font, Nan::New("family").ToLocalChecked()).ToLocalChecked());
2521
2522 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2523
2524 PangoFontDescription *desc = pango_font_description_copy(context->state->fontDescription);
2525 pango_font_description_free(context->state->fontDescription);
2526
2527 pango_font_description_set_style(desc, Canvas::GetStyleFromCSSString(*style));
2528 pango_font_description_set_weight(desc, Canvas::GetWeightFromCSSString(*weight));
2529
2530 if (strlen(*family) > 0) pango_font_description_set_family(desc, *family);
2531
2532 PangoFontDescription *sys_desc = Canvas::ResolveFontDescription(desc);
2533 pango_font_description_free(desc);
2534
2535 if (size > 0) pango_font_description_set_absolute_size(sys_desc, size * PANGO_SCALE);
2536
2537 context->state->fontDescription = sys_desc;
2538 pango_layout_set_font_description(context->_layout, sys_desc);
2539
2540 context->_font.Reset(value);
2541}
2542
2543/*
2544 * Get text baseline.
2545 */
2546
2547NAN_GETTER(Context2d::GetTextBaseline) {
2548 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2549 Isolate *iso = Isolate::GetCurrent();
2550 Local<Value> font;
2551
2552 if (context->_textBaseline.IsEmpty())
2553 font = Nan::New("alphabetic").ToLocalChecked();
2554 else
2555 font = context->_textBaseline.Get(iso);
2556
2557 info.GetReturnValue().Set(font);
2558}
2559
2560/*
2561 * Set text baseline.
2562 */
2563
2564NAN_SETTER(Context2d::SetTextBaseline) {
2565 if (!value->IsString()) return;
2566
2567 Nan::Utf8String opStr(Nan::To<String>(value).ToLocalChecked());
2568 const std::map<std::string, int32_t> modes = {
2569 {"alphabetic", 0},
2570 {"top", 1},
2571 {"bottom", 2},
2572 {"middle", 3},
2573 {"ideographic", 4},
2574 {"hanging", 5}
2575 };
2576 auto op = modes.find(*opStr);
2577 if (op == modes.end()) return;
2578
2579 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2580 context->state->textBaseline = op->second;
2581 context->_textBaseline.Reset(value);
2582}
2583
2584/*
2585 * Get text align.
2586 */
2587
2588NAN_GETTER(Context2d::GetTextAlign) {
2589 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2590 Isolate *iso = Isolate::GetCurrent();
2591 Local<Value> font;
2592
2593 if (context->_textAlign.IsEmpty())
2594 font = Nan::New("start").ToLocalChecked();
2595 else
2596 font = context->_textAlign.Get(iso);
2597
2598 info.GetReturnValue().Set(font);
2599}
2600
2601/*
2602 * Set text align.
2603 */
2604
2605NAN_SETTER(Context2d::SetTextAlign) {
2606 if (!value->IsString()) return;
2607
2608 Nan::Utf8String opStr(Nan::To<String>(value).ToLocalChecked());
2609 const std::map<std::string, int32_t> modes = {
2610 {"center", 0},
2611 {"left", -1},
2612 {"start", -1},
2613 {"right", 1},
2614 {"end", 1}
2615 };
2616 auto op = modes.find(*opStr);
2617 if (op == modes.end()) return;
2618
2619 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2620 context->state->textAlignment = op->second;
2621 context->_textAlign.Reset(value);
2622}
2623
2624/*
2625 * Return the given text extents.
2626 * TODO: Support for:
2627 * hangingBaseline, ideographicBaseline,
2628 * fontBoundingBoxAscent, fontBoundingBoxDescent
2629 */
2630
2631NAN_METHOD(Context2d::MeasureText) {
2632 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2633 cairo_t *ctx = context->context();
2634
2635 Nan::Utf8String str(Nan::To<String>(info[0]).ToLocalChecked());
2636 Local<Object> obj = Nan::New<Object>();
2637
2638 PangoRectangle _ink_rect, _logical_rect;
2639 float_rectangle ink_rect, logical_rect;
2640 PangoFontMetrics *metrics;
2641 PangoLayout *layout = context->layout();
2642
2643 pango_layout_set_text(layout, *str, -1);
2644 pango_cairo_update_layout(ctx, layout);
2645
2646 // Normally you could use pango_layout_get_pixel_extents and be done, or use
2647 // pango_extents_to_pixels, but both of those round the pixels, so we have to
2648 // divide by PANGO_SCALE manually
2649 pango_layout_get_extents(layout, &_ink_rect, &_logical_rect);
2650
2651 float inverse_pango_scale = 1. / PANGO_SCALE;
2652
2653 logical_rect.x = _logical_rect.x * inverse_pango_scale;
2654 logical_rect.y = _logical_rect.y * inverse_pango_scale;
2655 logical_rect.width = _logical_rect.width * inverse_pango_scale;
2656 logical_rect.height = _logical_rect.height * inverse_pango_scale;
2657
2658 ink_rect.x = _ink_rect.x * inverse_pango_scale;
2659 ink_rect.y = _ink_rect.y * inverse_pango_scale;
2660 ink_rect.width = _ink_rect.width * inverse_pango_scale;
2661 ink_rect.height = _ink_rect.height * inverse_pango_scale;
2662
2663 metrics = PANGO_LAYOUT_GET_METRICS(layout);
2664
2665 double x_offset;
2666 switch (context->state->textAlignment) {
2667 case 0: // center
2668 x_offset = logical_rect.width / 2;
2669 break;
2670 case 1: // right
2671 x_offset = logical_rect.width;
2672 break;
2673 default: // left
2674 x_offset = 0.0;
2675 }
2676
2677 cairo_matrix_t matrix;
2678 cairo_get_matrix(ctx, &matrix);
2679 double y_offset = getBaselineAdjustment(layout, context->state->textBaseline);
2680
2681 Nan::Set(obj,
2682 Nan::New<String>("width").ToLocalChecked(),
2683 Nan::New<Number>(logical_rect.width)).Check();
2684 Nan::Set(obj,
2685 Nan::New<String>("actualBoundingBoxLeft").ToLocalChecked(),
2686 Nan::New<Number>(x_offset - PANGO_LBEARING(logical_rect))).Check();
2687 Nan::Set(obj,
2688 Nan::New<String>("actualBoundingBoxRight").ToLocalChecked(),
2689 Nan::New<Number>(x_offset + PANGO_RBEARING(logical_rect))).Check();
2690 Nan::Set(obj,
2691 Nan::New<String>("actualBoundingBoxAscent").ToLocalChecked(),
2692 Nan::New<Number>(y_offset + PANGO_ASCENT(ink_rect))).Check();
2693 Nan::Set(obj,
2694 Nan::New<String>("actualBoundingBoxDescent").ToLocalChecked(),
2695 Nan::New<Number>(PANGO_DESCENT(ink_rect) - y_offset)).Check();
2696 Nan::Set(obj,
2697 Nan::New<String>("emHeightAscent").ToLocalChecked(),
2698 Nan::New<Number>(-(PANGO_ASCENT(logical_rect) - y_offset))).Check();
2699 Nan::Set(obj,
2700 Nan::New<String>("emHeightDescent").ToLocalChecked(),
2701 Nan::New<Number>(PANGO_DESCENT(logical_rect) - y_offset)).Check();
2702 Nan::Set(obj,
2703 Nan::New<String>("alphabeticBaseline").ToLocalChecked(),
2704 Nan::New<Number>(-(pango_font_metrics_get_ascent(metrics) * inverse_pango_scale - y_offset))).Check();
2705
2706 pango_font_metrics_unref(metrics);
2707
2708 info.GetReturnValue().Set(obj);
2709}
2710
2711/*
2712 * Set line dash
2713 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
2714 */
2715
2716NAN_METHOD(Context2d::SetLineDash) {
2717 if (!info[0]->IsArray()) return;
2718 Local<Array> dash = Local<Array>::Cast(info[0]);
2719 uint32_t dashes = dash->Length() & 1 ? dash->Length() * 2 : dash->Length();
2720 uint32_t zero_dashes = 0;
2721 std::vector<double> a(dashes);
2722 for (uint32_t i=0; i<dashes; i++) {
2723 Local<Value> d = Nan::Get(dash, i % dash->Length()).ToLocalChecked();
2724 if (!d->IsNumber()) return;
2725 a[i] = Nan::To<double>(d).FromMaybe(0);
2726 if (a[i] == 0) zero_dashes++;
2727 if (a[i] < 0 || isnan(a[i]) || isinf(a[i])) return;
2728 }
2729
2730 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2731 cairo_t *ctx = context->context();
2732 double offset;
2733 cairo_get_dash(ctx, NULL, &offset);
2734 if (zero_dashes == dashes) {
2735 std::vector<double> b(0);
2736 cairo_set_dash(ctx, b.data(), 0, offset);
2737 } else {
2738 cairo_set_dash(ctx, a.data(), dashes, offset);
2739 }
2740}
2741
2742/*
2743 * Get line dash
2744 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
2745 */
2746NAN_METHOD(Context2d::GetLineDash) {
2747 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2748 cairo_t *ctx = context->context();
2749 int dashes = cairo_get_dash_count(ctx);
2750 std::vector<double> a(dashes);
2751 cairo_get_dash(ctx, a.data(), NULL);
2752
2753 Local<Array> dash = Nan::New<Array>(dashes);
2754 for (int i=0; i<dashes; i++) {
2755 Nan::Set(dash, Nan::New<Number>(i), Nan::New<Number>(a[i])).Check();
2756 }
2757
2758 info.GetReturnValue().Set(dash);
2759}
2760
2761/*
2762 * Set line dash offset
2763 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
2764 */
2765NAN_SETTER(Context2d::SetLineDashOffset) {
2766 double offset = Nan::To<double>(value).FromMaybe(0);
2767 if (isnan(offset) || isinf(offset)) return;
2768
2769 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2770 cairo_t *ctx = context->context();
2771
2772 int dashes = cairo_get_dash_count(ctx);
2773 std::vector<double> a(dashes);
2774 cairo_get_dash(ctx, a.data(), NULL);
2775 cairo_set_dash(ctx, a.data(), dashes, offset);
2776}
2777
2778/*
2779 * Get line dash offset
2780 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
2781 */
2782NAN_GETTER(Context2d::GetLineDashOffset) {
2783 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2784 cairo_t *ctx = context->context();
2785 double offset;
2786 cairo_get_dash(ctx, NULL, &offset);
2787
2788 info.GetReturnValue().Set(Nan::New<Number>(offset));
2789}
2790
2791/*
2792 * Fill the rectangle defined by x, y, width and height.
2793 */
2794
2795NAN_METHOD(Context2d::FillRect) {
2796 RECT_ARGS;
2797 if (0 == width || 0 == height) return;
2798 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2799 cairo_t *ctx = context->context();
2800 context->savePath();
2801 cairo_rectangle(ctx, x, y, width, height);
2802 context->fill();
2803 context->restorePath();
2804}
2805
2806/*
2807 * Stroke the rectangle defined by x, y, width and height.
2808 */
2809
2810NAN_METHOD(Context2d::StrokeRect) {
2811 RECT_ARGS;
2812 if (0 == width && 0 == height) return;
2813 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2814 cairo_t *ctx = context->context();
2815 context->savePath();
2816 cairo_rectangle(ctx, x, y, width, height);
2817 context->stroke();
2818 context->restorePath();
2819}
2820
2821/*
2822 * Clears all pixels defined by x, y, width and height.
2823 */
2824
2825NAN_METHOD(Context2d::ClearRect) {
2826 RECT_ARGS;
2827 if (0 == width || 0 == height) return;
2828 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2829 cairo_t *ctx = context->context();
2830 cairo_save(ctx);
2831 context->savePath();
2832 cairo_rectangle(ctx, x, y, width, height);
2833 cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR);
2834 cairo_fill(ctx);
2835 context->restorePath();
2836 cairo_restore(ctx);
2837}
2838
2839/*
2840 * Adds a rectangle subpath.
2841 */
2842
2843NAN_METHOD(Context2d::Rect) {
2844 RECT_ARGS;
2845 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2846 cairo_t *ctx = context->context();
2847 if (width == 0) {
2848 cairo_move_to(ctx, x, y);
2849 cairo_line_to(ctx, x, y + height);
2850 } else if (height == 0) {
2851 cairo_move_to(ctx, x, y);
2852 cairo_line_to(ctx, x + width, y);
2853 } else {
2854 cairo_rectangle(ctx, x, y, width, height);
2855 }
2856}
2857
2858/*
2859 * Adds an arc at x, y with the given radis and start/end angles.
2860 */
2861
2862NAN_METHOD(Context2d::Arc) {
2863 if (!info[0]->IsNumber()
2864 || !info[1]->IsNumber()
2865 || !info[2]->IsNumber()
2866 || !info[3]->IsNumber()
2867 || !info[4]->IsNumber()) return;
2868
2869 bool anticlockwise = Nan::To<bool>(info[5]).FromMaybe(false);
2870
2871 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2872 cairo_t *ctx = context->context();
2873
2874 if (anticlockwise && M_PI * 2 != Nan::To<double>(info[4]).FromMaybe(0)) {
2875 cairo_arc_negative(ctx
2876 , Nan::To<double>(info[0]).FromMaybe(0)
2877 , Nan::To<double>(info[1]).FromMaybe(0)
2878 , Nan::To<double>(info[2]).FromMaybe(0)
2879 , Nan::To<double>(info[3]).FromMaybe(0)
2880 , Nan::To<double>(info[4]).FromMaybe(0));
2881 } else {
2882 cairo_arc(ctx
2883 , Nan::To<double>(info[0]).FromMaybe(0)
2884 , Nan::To<double>(info[1]).FromMaybe(0)
2885 , Nan::To<double>(info[2]).FromMaybe(0)
2886 , Nan::To<double>(info[3]).FromMaybe(0)
2887 , Nan::To<double>(info[4]).FromMaybe(0));
2888 }
2889}
2890
2891/*
2892 * Adds an arcTo point (x0,y0) to (x1,y1) with the given radius.
2893 *
2894 * Implementation influenced by WebKit.
2895 */
2896
2897NAN_METHOD(Context2d::ArcTo) {
2898 double args[5];
2899 if(!checkArgs(info, args, 5))
2900 return;
2901
2902 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
2903 cairo_t *ctx = context->context();
2904
2905 // Current path point
2906 double x, y;
2907 cairo_get_current_point(ctx, &x, &y);
2908 Point<float> p0(x, y);
2909
2910 // Point (x0,y0)
2911 Point<float> p1(args[0], args[1]);
2912
2913 // Point (x1,y1)
2914 Point<float> p2(args[2], args[3]);
2915
2916 float radius = args[4];
2917
2918 if ((p1.x == p0.x && p1.y == p0.y)
2919 || (p1.x == p2.x && p1.y == p2.y)
2920 || radius == 0.f) {
2921 cairo_line_to(ctx, p1.x, p1.y);
2922 return;
2923 }
2924
2925 Point<float> p1p0((p0.x - p1.x),(p0.y - p1.y));
2926 Point<float> p1p2((p2.x - p1.x),(p2.y - p1.y));
2927 float p1p0_length = sqrtf(p1p0.x * p1p0.x + p1p0.y * p1p0.y);
2928 float p1p2_length = sqrtf(p1p2.x * p1p2.x + p1p2.y * p1p2.y);
2929
2930 double cos_phi = (p1p0.x * p1p2.x + p1p0.y * p1p2.y) / (p1p0_length * p1p2_length);
2931 // all points on a line logic
2932 if (-1 == cos_phi) {
2933 cairo_line_to(ctx, p1.x, p1.y);
2934 return;
2935 }
2936
2937 if (1 == cos_phi) {
2938 // add infinite far away point
2939 unsigned int max_length = 65535;
2940 double factor_max = max_length / p1p0_length;
2941 Point<float> ep((p0.x + factor_max * p1p0.x), (p0.y + factor_max * p1p0.y));
2942 cairo_line_to(ctx, ep.x, ep.y);
2943 return;
2944 }
2945
2946 float tangent = radius / tan(acos(cos_phi) / 2);
2947 float factor_p1p0 = tangent / p1p0_length;
2948 Point<float> t_p1p0((p1.x + factor_p1p0 * p1p0.x), (p1.y + factor_p1p0 * p1p0.y));
2949
2950 Point<float> orth_p1p0(p1p0.y, -p1p0.x);
2951 float orth_p1p0_length = sqrt(orth_p1p0.x * orth_p1p0.x + orth_p1p0.y * orth_p1p0.y);
2952 float factor_ra = radius / orth_p1p0_length;
2953
2954 double cos_alpha = (orth_p1p0.x * p1p2.x + orth_p1p0.y * p1p2.y) / (orth_p1p0_length * p1p2_length);
2955 if (cos_alpha < 0.f)
2956 orth_p1p0 = Point<float>(-orth_p1p0.x, -orth_p1p0.y);
2957
2958 Point<float> p((t_p1p0.x + factor_ra * orth_p1p0.x), (t_p1p0.y + factor_ra * orth_p1p0.y));
2959
2960 orth_p1p0 = Point<float>(-orth_p1p0.x, -orth_p1p0.y);
2961 float sa = acos(orth_p1p0.x / orth_p1p0_length);
2962 if (orth_p1p0.y < 0.f)
2963 sa = 2 * M_PI - sa;
2964
2965 bool anticlockwise = false;
2966
2967 float factor_p1p2 = tangent / p1p2_length;
2968 Point<float> t_p1p2((p1.x + factor_p1p2 * p1p2.x), (p1.y + factor_p1p2 * p1p2.y));
2969 Point<float> orth_p1p2((t_p1p2.x - p.x),(t_p1p2.y - p.y));
2970 float orth_p1p2_length = sqrtf(orth_p1p2.x * orth_p1p2.x + orth_p1p2.y * orth_p1p2.y);
2971 float ea = acos(orth_p1p2.x / orth_p1p2_length);
2972
2973 if (orth_p1p2.y < 0) ea = 2 * M_PI - ea;
2974 if ((sa > ea) && ((sa - ea) < M_PI)) anticlockwise = true;
2975 if ((sa < ea) && ((ea - sa) > M_PI)) anticlockwise = true;
2976
2977 cairo_line_to(ctx, t_p1p0.x, t_p1p0.y);
2978
2979 if (anticlockwise && M_PI * 2 != radius) {
2980 cairo_arc_negative(ctx
2981 , p.x
2982 , p.y
2983 , radius
2984 , sa
2985 , ea);
2986 } else {
2987 cairo_arc(ctx
2988 , p.x
2989 , p.y
2990 , radius
2991 , sa
2992 , ea);
2993 }
2994}
2995
2996/*
2997 * Adds an ellipse to the path which is centered at (x, y) position with the
2998 * radii radiusX and radiusY starting at startAngle and ending at endAngle
2999 * going in the given direction by anticlockwise (defaulting to clockwise).
3000 */
3001
3002NAN_METHOD(Context2d::Ellipse) {
3003 double args[7];
3004 if(!checkArgs(info, args, 7))
3005 return;
3006
3007 double radiusX = args[2];
3008 double radiusY = args[3];
3009
3010 if (radiusX == 0 || radiusY == 0) return;
3011
3012 double x = args[0];
3013 double y = args[1];
3014 double rotation = args[4];
3015 double startAngle = args[5];
3016 double endAngle = args[6];
3017 bool anticlockwise = Nan::To<bool>(info[7]).FromMaybe(false);
3018
3019 Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
3020 cairo_t *ctx = context->context();
3021
3022 // See https://www.cairographics.org/cookbook/ellipses/
3023 double xRatio = radiusX / radiusY;
3024
3025 cairo_matrix_t save_matrix;
3026 cairo_get_matrix(ctx, &save_matrix);
3027 cairo_translate(ctx, x, y);
3028 cairo_rotate(ctx, rotation);
3029 cairo_scale(ctx, xRatio, 1.0);
3030 cairo_translate(ctx, -x, -y);
3031 if (anticlockwise && M_PI * 2 != args[4]) {
3032 cairo_arc_negative(ctx,
3033 x,
3034 y,
3035 radiusY,
3036 startAngle,
3037 endAngle);
3038 } else {
3039 cairo_arc(ctx,
3040 x,
3041 y,
3042 radiusY,
3043 startAngle,
3044 endAngle);
3045 }
3046 cairo_set_matrix(ctx, &save_matrix);
3047}