@shqld/canvas 2.11.2-rc.1

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