@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
package/src/Canvas.cc ADDED
@@ -0,0 +1,965 @@
1
+ // Copyright (c) 2010 LearnBoost <tj@learnboost.com>
2
+
3
+ #include "Canvas.h"
4
+
5
+ #include <algorithm> // std::min
6
+ #include <assert.h>
7
+ #include <cairo-pdf.h>
8
+ #include <cairo-svg.h>
9
+ #include "CanvasRenderingContext2d.h"
10
+ #include "closure.h"
11
+ #include <cstring>
12
+ #include <cctype>
13
+ #include <ctime>
14
+ #include <glib.h>
15
+ #include "PNG.h"
16
+ #include "register_font.h"
17
+ #include <sstream>
18
+ #include <stdlib.h>
19
+ #include <string>
20
+ #include <unordered_set>
21
+ #include "Util.h"
22
+ #include <vector>
23
+ #include "node_buffer.h"
24
+
25
+ #ifdef HAVE_JPEG
26
+ #include "JPEGStream.h"
27
+ #endif
28
+
29
+ #include "backend/ImageBackend.h"
30
+ #include "backend/PdfBackend.h"
31
+ #include "backend/SvgBackend.h"
32
+
33
+ #define GENERIC_FACE_ERROR \
34
+ "The second argument to registerFont is required, and should be an object " \
35
+ "with at least a family (string) and optionally weight (string/number) " \
36
+ "and style (string)."
37
+
38
+ #define CHECK_RECEIVER(prop) \
39
+ if (!Canvas::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { \
40
+ Nan::ThrowTypeError("Method " #prop " called on incompatible receiver"); \
41
+ return; \
42
+ }
43
+
44
+ using namespace v8;
45
+ using namespace std;
46
+
47
+ Nan::Persistent<FunctionTemplate> Canvas::constructor;
48
+
49
+ std::vector<FontFace> font_face_list;
50
+
51
+ /*
52
+ * Initialize Canvas.
53
+ */
54
+
55
+ void
56
+ Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
57
+ Nan::HandleScope scope;
58
+
59
+ // Constructor
60
+ Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Canvas::New);
61
+ constructor.Reset(ctor);
62
+ ctor->InstanceTemplate()->SetInternalFieldCount(1);
63
+ ctor->SetClassName(Nan::New("Canvas").ToLocalChecked());
64
+
65
+ // Prototype
66
+ Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
67
+ Nan::SetPrototypeMethod(ctor, "toBuffer", ToBuffer);
68
+ Nan::SetPrototypeMethod(ctor, "streamPNGSync", StreamPNGSync);
69
+ Nan::SetPrototypeMethod(ctor, "streamPDFSync", StreamPDFSync);
70
+ #ifdef HAVE_JPEG
71
+ Nan::SetPrototypeMethod(ctor, "streamJPEGSync", StreamJPEGSync);
72
+ #endif
73
+ Nan::SetAccessor(proto, Nan::New("type").ToLocalChecked(), GetType);
74
+ Nan::SetAccessor(proto, Nan::New("stride").ToLocalChecked(), GetStride);
75
+ Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth, SetWidth);
76
+ Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight, SetHeight);
77
+
78
+ Nan::SetTemplate(proto, "PNG_NO_FILTERS", Nan::New<Uint32>(PNG_NO_FILTERS));
79
+ Nan::SetTemplate(proto, "PNG_FILTER_NONE", Nan::New<Uint32>(PNG_FILTER_NONE));
80
+ Nan::SetTemplate(proto, "PNG_FILTER_SUB", Nan::New<Uint32>(PNG_FILTER_SUB));
81
+ Nan::SetTemplate(proto, "PNG_FILTER_UP", Nan::New<Uint32>(PNG_FILTER_UP));
82
+ Nan::SetTemplate(proto, "PNG_FILTER_AVG", Nan::New<Uint32>(PNG_FILTER_AVG));
83
+ Nan::SetTemplate(proto, "PNG_FILTER_PAETH", Nan::New<Uint32>(PNG_FILTER_PAETH));
84
+ Nan::SetTemplate(proto, "PNG_ALL_FILTERS", Nan::New<Uint32>(PNG_ALL_FILTERS));
85
+
86
+ // Class methods
87
+ Nan::SetMethod(ctor, "_registerFont", RegisterFont);
88
+ Nan::SetMethod(ctor, "_deregisterAllFonts", DeregisterAllFonts);
89
+
90
+ Local<Context> ctx = Nan::GetCurrentContext();
91
+ Nan::Set(target,
92
+ Nan::New("Canvas").ToLocalChecked(),
93
+ ctor->GetFunction(ctx).ToLocalChecked());
94
+ }
95
+
96
+ /*
97
+ * Initialize a Canvas with the given width and height.
98
+ */
99
+
100
+ NAN_METHOD(Canvas::New) {
101
+ if (!info.IsConstructCall()) {
102
+ return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
103
+ }
104
+
105
+ Backend* backend = NULL;
106
+ if (info[0]->IsNumber()) {
107
+ int width = Nan::To<uint32_t>(info[0]).FromMaybe(0), height = 0;
108
+
109
+ if (info[1]->IsNumber()) height = Nan::To<uint32_t>(info[1]).FromMaybe(0);
110
+
111
+ if (info[2]->IsString()) {
112
+ if (0 == strcmp("pdf", *Nan::Utf8String(info[2])))
113
+ backend = new PdfBackend(width, height);
114
+ else if (0 == strcmp("svg", *Nan::Utf8String(info[2])))
115
+ backend = new SvgBackend(width, height);
116
+ else
117
+ backend = new ImageBackend(width, height);
118
+ }
119
+ else
120
+ backend = new ImageBackend(width, height);
121
+ }
122
+ else if (info[0]->IsObject()) {
123
+ if (Nan::New(ImageBackend::constructor)->HasInstance(info[0]) ||
124
+ Nan::New(PdfBackend::constructor)->HasInstance(info[0]) ||
125
+ Nan::New(SvgBackend::constructor)->HasInstance(info[0])) {
126
+ backend = Nan::ObjectWrap::Unwrap<Backend>(Nan::To<Object>(info[0]).ToLocalChecked());
127
+ }else{
128
+ return Nan::ThrowTypeError("Invalid arguments");
129
+ }
130
+ }
131
+ else {
132
+ backend = new ImageBackend(0, 0);
133
+ }
134
+
135
+ if (!backend->isSurfaceValid()) {
136
+ delete backend;
137
+ return Nan::ThrowError(backend->getError());
138
+ }
139
+
140
+ Canvas* canvas = new Canvas(backend);
141
+ canvas->Wrap(info.This());
142
+
143
+ backend->setCanvas(canvas);
144
+
145
+ info.GetReturnValue().Set(info.This());
146
+ }
147
+
148
+ /*
149
+ * Get type string.
150
+ */
151
+
152
+ NAN_GETTER(Canvas::GetType) {
153
+ CHECK_RECEIVER(Canvas.GetType);
154
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
155
+ info.GetReturnValue().Set(Nan::New<String>(canvas->backend()->getName()).ToLocalChecked());
156
+ }
157
+
158
+ /*
159
+ * Get stride.
160
+ */
161
+ NAN_GETTER(Canvas::GetStride) {
162
+ CHECK_RECEIVER(Canvas.GetStride);
163
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
164
+ info.GetReturnValue().Set(Nan::New<Number>(canvas->stride()));
165
+ }
166
+
167
+ /*
168
+ * Get width.
169
+ */
170
+
171
+ NAN_GETTER(Canvas::GetWidth) {
172
+ CHECK_RECEIVER(Canvas.GetWidth);
173
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
174
+ info.GetReturnValue().Set(Nan::New<Number>(canvas->getWidth()));
175
+ }
176
+
177
+ /*
178
+ * Set width.
179
+ */
180
+
181
+ NAN_SETTER(Canvas::SetWidth) {
182
+ CHECK_RECEIVER(Canvas.SetWidth);
183
+ if (value->IsNumber()) {
184
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
185
+ canvas->backend()->setWidth(Nan::To<uint32_t>(value).FromMaybe(0));
186
+ canvas->resurface(info.This());
187
+ }
188
+ }
189
+
190
+ /*
191
+ * Get height.
192
+ */
193
+
194
+ NAN_GETTER(Canvas::GetHeight) {
195
+ CHECK_RECEIVER(Canvas.GetHeight);
196
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
197
+ info.GetReturnValue().Set(Nan::New<Number>(canvas->getHeight()));
198
+ }
199
+
200
+ /*
201
+ * Set height.
202
+ */
203
+
204
+ NAN_SETTER(Canvas::SetHeight) {
205
+ CHECK_RECEIVER(Canvas.SetHeight);
206
+ if (value->IsNumber()) {
207
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
208
+ canvas->backend()->setHeight(Nan::To<uint32_t>(value).FromMaybe(0));
209
+ canvas->resurface(info.This());
210
+ }
211
+ }
212
+
213
+ /*
214
+ * EIO toBuffer callback.
215
+ */
216
+
217
+ void
218
+ Canvas::ToPngBufferAsync(uv_work_t *req) {
219
+ PngClosure* closure = static_cast<PngClosure*>(req->data);
220
+
221
+ closure->status = canvas_write_to_png_stream(
222
+ closure->canvas->surface(),
223
+ PngClosure::writeVec,
224
+ closure);
225
+ }
226
+
227
+ #ifdef HAVE_JPEG
228
+ void
229
+ Canvas::ToJpegBufferAsync(uv_work_t *req) {
230
+ JpegClosure* closure = static_cast<JpegClosure*>(req->data);
231
+ write_to_jpeg_buffer(closure->canvas->surface(), closure);
232
+ }
233
+ #endif
234
+
235
+ /*
236
+ * EIO after toBuffer callback.
237
+ */
238
+
239
+ void
240
+ Canvas::ToBufferAsyncAfter(uv_work_t *req) {
241
+ Nan::HandleScope scope;
242
+ Nan::AsyncResource async("canvas:ToBufferAsyncAfter");
243
+ Closure* closure = static_cast<Closure*>(req->data);
244
+ delete req;
245
+
246
+ if (closure->status) {
247
+ Local<Value> argv[1] = { Canvas::Error(closure->status) };
248
+ closure->cb.Call(1, argv, &async);
249
+ } else {
250
+ Local<Object> buf = Nan::CopyBuffer((char*)&closure->vec[0], closure->vec.size()).ToLocalChecked();
251
+ Local<Value> argv[2] = { Nan::Null(), buf };
252
+ closure->cb.Call(sizeof argv / sizeof *argv, argv, &async);
253
+ }
254
+
255
+ closure->canvas->Unref();
256
+ delete closure;
257
+ }
258
+
259
+ static void parsePNGArgs(Local<Value> arg, PngClosure& pngargs) {
260
+ if (arg->IsObject()) {
261
+ Local<Object> obj = Nan::To<Object>(arg).ToLocalChecked();
262
+
263
+ Local<Value> cLevel = Nan::Get(obj, Nan::New("compressionLevel").ToLocalChecked()).ToLocalChecked();
264
+ if (cLevel->IsUint32()) {
265
+ uint32_t val = Nan::To<uint32_t>(cLevel).FromMaybe(0);
266
+ // See quote below from spec section 4.12.5.5.
267
+ if (val <= 9) pngargs.compressionLevel = val;
268
+ }
269
+
270
+ Local<Value> rez = Nan::Get(obj, Nan::New("resolution").ToLocalChecked()).ToLocalChecked();
271
+ if (rez->IsUint32()) {
272
+ uint32_t val = Nan::To<uint32_t>(rez).FromMaybe(0);
273
+ if (val > 0) pngargs.resolution = val;
274
+ }
275
+
276
+ Local<Value> filters = Nan::Get(obj, Nan::New("filters").ToLocalChecked()).ToLocalChecked();
277
+ if (filters->IsUint32()) pngargs.filters = Nan::To<uint32_t>(filters).FromMaybe(0);
278
+
279
+ Local<Value> palette = Nan::Get(obj, Nan::New("palette").ToLocalChecked()).ToLocalChecked();
280
+ if (palette->IsUint8ClampedArray()) {
281
+ Local<Uint8ClampedArray> palette_ta = palette.As<Uint8ClampedArray>();
282
+ pngargs.nPaletteColors = palette_ta->Length();
283
+ if (pngargs.nPaletteColors % 4 != 0) {
284
+ throw "Palette length must be a multiple of 4.";
285
+ }
286
+ pngargs.nPaletteColors /= 4;
287
+ Nan::TypedArrayContents<uint8_t> _paletteColors(palette_ta);
288
+ pngargs.palette = *_paletteColors;
289
+ // Optional background color index:
290
+ Local<Value> backgroundIndexVal = Nan::Get(obj, Nan::New("backgroundIndex").ToLocalChecked()).ToLocalChecked();
291
+ if (backgroundIndexVal->IsUint32()) {
292
+ pngargs.backgroundIndex = static_cast<uint8_t>(Nan::To<uint32_t>(backgroundIndexVal).FromMaybe(0));
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ #ifdef HAVE_JPEG
299
+ static void parseJPEGArgs(Local<Value> arg, JpegClosure& jpegargs) {
300
+ // "If Type(quality) is not Number, or if quality is outside that range, the
301
+ // user agent must use its default quality value, as if the quality argument
302
+ // had not been given." - 4.12.5.5
303
+ if (arg->IsObject()) {
304
+ Local<Object> obj = Nan::To<Object>(arg).ToLocalChecked();
305
+
306
+ Local<Value> qual = Nan::Get(obj, Nan::New("quality").ToLocalChecked()).ToLocalChecked();
307
+ if (qual->IsNumber()) {
308
+ double quality = Nan::To<double>(qual).FromMaybe(0);
309
+ if (quality >= 0.0 && quality <= 1.0) {
310
+ jpegargs.quality = static_cast<uint32_t>(100.0 * quality);
311
+ }
312
+ }
313
+
314
+ Local<Value> chroma = Nan::Get(obj, Nan::New("chromaSubsampling").ToLocalChecked()).ToLocalChecked();
315
+ if (chroma->IsBoolean()) {
316
+ bool subsample = Nan::To<bool>(chroma).FromMaybe(0);
317
+ jpegargs.chromaSubsampling = subsample ? 2 : 1;
318
+ } else if (chroma->IsNumber()) {
319
+ jpegargs.chromaSubsampling = Nan::To<uint32_t>(chroma).FromMaybe(0);
320
+ }
321
+
322
+ Local<Value> progressive = Nan::Get(obj, Nan::New("progressive").ToLocalChecked()).ToLocalChecked();
323
+ if (!progressive->IsUndefined()) {
324
+ jpegargs.progressive = Nan::To<bool>(progressive).FromMaybe(0);
325
+ }
326
+ }
327
+ }
328
+ #endif
329
+
330
+ #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
331
+
332
+ static inline void setPdfMetaStr(cairo_surface_t* surf, Local<Object> opts,
333
+ cairo_pdf_metadata_t t, const char* pName) {
334
+ auto propName = Nan::New(pName).ToLocalChecked();
335
+ auto propValue = Nan::Get(opts, propName).ToLocalChecked();
336
+ if (propValue->IsString()) {
337
+ // (copies char data)
338
+ cairo_pdf_surface_set_metadata(surf, t, *Nan::Utf8String(propValue));
339
+ }
340
+ }
341
+
342
+ static inline void setPdfMetaDate(cairo_surface_t* surf, Local<Object> opts,
343
+ cairo_pdf_metadata_t t, const char* pName) {
344
+ auto propName = Nan::New(pName).ToLocalChecked();
345
+ auto propValue = Nan::Get(opts, propName).ToLocalChecked();
346
+ if (propValue->IsDate()) {
347
+ auto date = static_cast<time_t>(propValue.As<v8::Date>()->ValueOf() / 1000); // ms -> s
348
+ char buf[sizeof "2011-10-08T07:07:09Z"];
349
+ strftime(buf, sizeof buf, "%FT%TZ", gmtime(&date));
350
+ cairo_pdf_surface_set_metadata(surf, t, buf);
351
+ }
352
+ }
353
+
354
+ static void setPdfMetadata(Canvas* canvas, Local<Object> opts) {
355
+ cairo_surface_t* surf = canvas->surface();
356
+
357
+ setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_TITLE, "title");
358
+ setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_AUTHOR, "author");
359
+ setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_SUBJECT, "subject");
360
+ setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_KEYWORDS, "keywords");
361
+ setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_CREATOR, "creator");
362
+ setPdfMetaDate(surf, opts, CAIRO_PDF_METADATA_CREATE_DATE, "creationDate");
363
+ setPdfMetaDate(surf, opts, CAIRO_PDF_METADATA_MOD_DATE, "modDate");
364
+ }
365
+
366
+ #endif // CAIRO 16+
367
+
368
+ /*
369
+ * Converts/encodes data to a Buffer. Async when a callback function is passed.
370
+
371
+ * PDF canvases:
372
+ (any) => Buffer
373
+ ("application/pdf", config) => Buffer
374
+
375
+ * SVG canvases:
376
+ (any) => Buffer
377
+
378
+ * ARGB data:
379
+ ("raw") => Buffer
380
+
381
+ * PNG-encoded
382
+ () => Buffer
383
+ (undefined|"image/png", {compressionLevel?: number, filter?: number}) => Buffer
384
+ ((err: null|Error, buffer) => any)
385
+ ((err: null|Error, buffer) => any, undefined|"image/png", {compressionLevel?: number, filter?: number})
386
+
387
+ * JPEG-encoded
388
+ ("image/jpeg") => Buffer
389
+ ("image/jpeg", {quality?: number, progressive?: Boolean, chromaSubsampling?: Boolean|number}) => Buffer
390
+ ((err: null|Error, buffer) => any, "image/jpeg")
391
+ ((err: null|Error, buffer) => any, "image/jpeg", {quality?: number, progressive?: Boolean, chromaSubsampling?: Boolean|number})
392
+ */
393
+
394
+ NAN_METHOD(Canvas::ToBuffer) {
395
+ cairo_status_t status;
396
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
397
+
398
+ // Vector canvases, sync only
399
+ const std::string name = canvas->backend()->getName();
400
+ if (name == "pdf" || name == "svg") {
401
+ // mime type may be present, but it's not checked
402
+ PdfSvgClosure* closure;
403
+ if (name == "pdf") {
404
+ closure = static_cast<PdfBackend*>(canvas->backend())->closure();
405
+ #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
406
+ if (info[1]->IsObject()) { // toBuffer("application/pdf", config)
407
+ setPdfMetadata(canvas, Nan::To<Object>(info[1]).ToLocalChecked());
408
+ }
409
+ #endif // CAIRO 16+
410
+ } else {
411
+ closure = static_cast<SvgBackend*>(canvas->backend())->closure();
412
+ }
413
+
414
+ cairo_surface_finish(canvas->surface());
415
+ Local<Object> buf = Nan::CopyBuffer((char*)&closure->vec[0], closure->vec.size()).ToLocalChecked();
416
+ info.GetReturnValue().Set(buf);
417
+ return;
418
+ }
419
+
420
+ // Raw ARGB data -- just a memcpy()
421
+ if (info[0]->StrictEquals(Nan::New<String>("raw").ToLocalChecked())) {
422
+ cairo_surface_t *surface = canvas->surface();
423
+ cairo_surface_flush(surface);
424
+ if (canvas->nBytes() > node::Buffer::kMaxLength) {
425
+ Nan::ThrowError("Data exceeds maximum buffer length.");
426
+ return;
427
+ }
428
+ const unsigned char *data = cairo_image_surface_get_data(surface);
429
+ Isolate* iso = Nan::GetCurrentContext()->GetIsolate();
430
+ Local<Object> buf = node::Buffer::Copy(iso, reinterpret_cast<const char*>(data), canvas->nBytes()).ToLocalChecked();
431
+ info.GetReturnValue().Set(buf);
432
+ return;
433
+ }
434
+
435
+ // Sync PNG, default
436
+ if (info[0]->IsUndefined() || info[0]->StrictEquals(Nan::New<String>("image/png").ToLocalChecked())) {
437
+ try {
438
+ PngClosure closure(canvas);
439
+ parsePNGArgs(info[1], closure);
440
+ if (closure.nPaletteColors == 0xFFFFFFFF) {
441
+ Nan::ThrowError("Palette length must be a multiple of 4.");
442
+ return;
443
+ }
444
+
445
+ Nan::TryCatch try_catch;
446
+ status = canvas_write_to_png_stream(canvas->surface(), PngClosure::writeVec, &closure);
447
+
448
+ if (try_catch.HasCaught()) {
449
+ try_catch.ReThrow();
450
+ } else if (status) {
451
+ throw status;
452
+ } else {
453
+ // TODO it's possible to avoid this copy
454
+ Local<Object> buf = Nan::CopyBuffer((char *)&closure.vec[0], closure.vec.size()).ToLocalChecked();
455
+ info.GetReturnValue().Set(buf);
456
+ }
457
+ } catch (cairo_status_t ex) {
458
+ Nan::ThrowError(Canvas::Error(ex));
459
+ } catch (const char* ex) {
460
+ Nan::ThrowError(ex);
461
+ }
462
+ return;
463
+ }
464
+
465
+ // Async PNG
466
+ if (info[0]->IsFunction() &&
467
+ (info[1]->IsUndefined() || info[1]->StrictEquals(Nan::New<String>("image/png").ToLocalChecked()))) {
468
+
469
+ PngClosure* closure;
470
+ try {
471
+ closure = new PngClosure(canvas);
472
+ parsePNGArgs(info[2], *closure);
473
+ } catch (cairo_status_t ex) {
474
+ Nan::ThrowError(Canvas::Error(ex));
475
+ return;
476
+ } catch (const char* ex) {
477
+ Nan::ThrowError(ex);
478
+ return;
479
+ }
480
+
481
+ canvas->Ref();
482
+ closure->cb.Reset(info[0].As<Function>());
483
+
484
+ uv_work_t* req = new uv_work_t;
485
+ req->data = closure;
486
+ // Make sure the surface exists since we won't have an isolate context in the async block:
487
+ canvas->surface();
488
+ uv_queue_work(uv_default_loop(), req, ToPngBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter);
489
+
490
+ return;
491
+ }
492
+
493
+ #ifdef HAVE_JPEG
494
+ // Sync JPEG
495
+ Local<Value> jpegStr = Nan::New<String>("image/jpeg").ToLocalChecked();
496
+ if (info[0]->StrictEquals(jpegStr)) {
497
+ try {
498
+ JpegClosure closure(canvas);
499
+ parseJPEGArgs(info[1], closure);
500
+
501
+ Nan::TryCatch try_catch;
502
+ write_to_jpeg_buffer(canvas->surface(), &closure);
503
+
504
+ if (try_catch.HasCaught()) {
505
+ try_catch.ReThrow();
506
+ } else {
507
+ // TODO it's possible to avoid this copy.
508
+ Local<Object> buf = Nan::CopyBuffer((char *)&closure.vec[0], closure.vec.size()).ToLocalChecked();
509
+ info.GetReturnValue().Set(buf);
510
+ }
511
+ } catch (cairo_status_t ex) {
512
+ Nan::ThrowError(Canvas::Error(ex));
513
+ }
514
+ return;
515
+ }
516
+
517
+ // Async JPEG
518
+ if (info[0]->IsFunction() && info[1]->StrictEquals(jpegStr)) {
519
+ JpegClosure* closure = new JpegClosure(canvas);
520
+ parseJPEGArgs(info[2], *closure);
521
+
522
+ canvas->Ref();
523
+ closure->cb.Reset(info[0].As<Function>());
524
+
525
+ uv_work_t* req = new uv_work_t;
526
+ req->data = closure;
527
+ // Make sure the surface exists since we won't have an isolate context in the async block:
528
+ canvas->surface();
529
+ uv_queue_work(uv_default_loop(), req, ToJpegBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter);
530
+
531
+ return;
532
+ }
533
+ #endif
534
+ }
535
+
536
+ /*
537
+ * Canvas::StreamPNG callback.
538
+ */
539
+
540
+ static cairo_status_t
541
+ streamPNG(void *c, const uint8_t *data, unsigned len) {
542
+ Nan::HandleScope scope;
543
+ Nan::AsyncResource async("canvas:StreamPNG");
544
+ PngClosure* closure = (PngClosure*) c;
545
+ Local<Object> buf = Nan::CopyBuffer((char *)data, len).ToLocalChecked();
546
+ Local<Value> argv[3] = {
547
+ Nan::Null()
548
+ , buf
549
+ , Nan::New<Number>(len) };
550
+ closure->cb.Call(sizeof argv / sizeof *argv, argv, &async);
551
+ return CAIRO_STATUS_SUCCESS;
552
+ }
553
+
554
+ /*
555
+ * Stream PNG data synchronously. TODO async
556
+ * StreamPngSync(this, options: {palette?: Uint8ClampedArray, backgroundIndex?: uint32, compressionLevel: uint32, filters: uint32})
557
+ */
558
+
559
+ NAN_METHOD(Canvas::StreamPNGSync) {
560
+ if (!info[0]->IsFunction())
561
+ return Nan::ThrowTypeError("callback function required");
562
+
563
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
564
+
565
+ PngClosure closure(canvas);
566
+ parsePNGArgs(info[1], closure);
567
+
568
+ closure.cb.Reset(Local<Function>::Cast(info[0]));
569
+
570
+ Nan::TryCatch try_catch;
571
+
572
+ cairo_status_t status = canvas_write_to_png_stream(canvas->surface(), streamPNG, &closure);
573
+
574
+ if (try_catch.HasCaught()) {
575
+ try_catch.ReThrow();
576
+ return;
577
+ } else if (status) {
578
+ Local<Value> argv[1] = { Canvas::Error(status) };
579
+ Nan::Call(closure.cb, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv);
580
+ } else {
581
+ Local<Value> argv[3] = {
582
+ Nan::Null()
583
+ , Nan::Null()
584
+ , Nan::New<Uint32>(0) };
585
+ Nan::Call(closure.cb, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv);
586
+ }
587
+ return;
588
+ }
589
+
590
+
591
+ struct PdfStreamInfo {
592
+ Local<Function> fn;
593
+ uint32_t len;
594
+ uint8_t* data;
595
+ };
596
+
597
+
598
+ /*
599
+ * Canvas::StreamPDF FreeCallback
600
+ */
601
+
602
+ void stream_pdf_free(char *, void *) {}
603
+
604
+ /*
605
+ * Canvas::StreamPDF callback.
606
+ */
607
+
608
+ static cairo_status_t
609
+ streamPDF(void *c, const uint8_t *data, unsigned len) {
610
+ Nan::HandleScope scope;
611
+ Nan::AsyncResource async("canvas:StreamPDF");
612
+ PdfStreamInfo* streaminfo = static_cast<PdfStreamInfo*>(c);
613
+ // TODO this is technically wrong, we're returning a pointer to the data in a
614
+ // vector in a class with automatic storage duration. If the canvas goes out
615
+ // of scope while we're in the handler, a use-after-free could happen.
616
+ Local<Object> buf = Nan::NewBuffer(const_cast<char *>(reinterpret_cast<const char *>(data)), len, stream_pdf_free, 0).ToLocalChecked();
617
+ Local<Value> argv[3] = {
618
+ Nan::Null()
619
+ , buf
620
+ , Nan::New<Number>(len) };
621
+ async.runInAsyncScope(Nan::GetCurrentContext()->Global(), streaminfo->fn, sizeof argv / sizeof *argv, argv);
622
+ return CAIRO_STATUS_SUCCESS;
623
+ }
624
+
625
+
626
+ cairo_status_t canvas_write_to_pdf_stream(cairo_surface_t *surface, cairo_write_func_t write_func, PdfStreamInfo* streaminfo) {
627
+ size_t whole_chunks = streaminfo->len / PAGE_SIZE;
628
+ size_t remainder = streaminfo->len - whole_chunks * PAGE_SIZE;
629
+
630
+ for (size_t i = 0; i < whole_chunks; ++i) {
631
+ write_func(streaminfo, &streaminfo->data[i * PAGE_SIZE], PAGE_SIZE);
632
+ }
633
+
634
+ if (remainder) {
635
+ write_func(streaminfo, &streaminfo->data[whole_chunks * PAGE_SIZE], remainder);
636
+ }
637
+
638
+ return CAIRO_STATUS_SUCCESS;
639
+ }
640
+
641
+ /*
642
+ * Stream PDF data synchronously.
643
+ */
644
+
645
+ NAN_METHOD(Canvas::StreamPDFSync) {
646
+ if (!info[0]->IsFunction())
647
+ return Nan::ThrowTypeError("callback function required");
648
+
649
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.Holder());
650
+
651
+ if (canvas->backend()->getName() != "pdf")
652
+ return Nan::ThrowTypeError("wrong canvas type");
653
+
654
+ #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
655
+ if (info[1]->IsObject()) {
656
+ setPdfMetadata(canvas, Nan::To<Object>(info[1]).ToLocalChecked());
657
+ }
658
+ #endif
659
+
660
+ cairo_surface_finish(canvas->surface());
661
+
662
+ PdfSvgClosure* closure = static_cast<PdfBackend*>(canvas->backend())->closure();
663
+ Local<Function> fn = info[0].As<Function>();
664
+ PdfStreamInfo streaminfo;
665
+ streaminfo.fn = fn;
666
+ streaminfo.data = &closure->vec[0];
667
+ streaminfo.len = closure->vec.size();
668
+
669
+ Nan::TryCatch try_catch;
670
+
671
+ cairo_status_t status = canvas_write_to_pdf_stream(canvas->surface(), streamPDF, &streaminfo);
672
+
673
+ if (try_catch.HasCaught()) {
674
+ try_catch.ReThrow();
675
+ } else if (status) {
676
+ Local<Value> error = Canvas::Error(status);
677
+ Nan::Call(fn, Nan::GetCurrentContext()->Global(), 1, &error);
678
+ } else {
679
+ Local<Value> argv[3] = {
680
+ Nan::Null()
681
+ , Nan::Null()
682
+ , Nan::New<Uint32>(0) };
683
+ Nan::Call(fn, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv);
684
+ }
685
+ }
686
+
687
+ /*
688
+ * Stream JPEG data synchronously.
689
+ */
690
+
691
+ #ifdef HAVE_JPEG
692
+ static uint32_t getSafeBufSize(Canvas* canvas) {
693
+ // Don't allow the buffer size to exceed the size of the canvas (#674)
694
+ // TODO not sure if this is really correct, but it fixed #674
695
+ return (std::min)(canvas->getWidth() * canvas->getHeight() * 4, static_cast<int>(PAGE_SIZE));
696
+ }
697
+
698
+ NAN_METHOD(Canvas::StreamJPEGSync) {
699
+ if (!info[1]->IsFunction())
700
+ return Nan::ThrowTypeError("callback function required");
701
+
702
+ Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
703
+ JpegClosure closure(canvas);
704
+ parseJPEGArgs(info[0], closure);
705
+ closure.cb.Reset(Local<Function>::Cast(info[1]));
706
+
707
+ Nan::TryCatch try_catch;
708
+ uint32_t bufsize = getSafeBufSize(canvas);
709
+ write_to_jpeg_stream(canvas->surface(), bufsize, &closure);
710
+
711
+ if (try_catch.HasCaught()) {
712
+ try_catch.ReThrow();
713
+ }
714
+ return;
715
+ }
716
+ #endif
717
+
718
+ char *
719
+ str_value(Local<Value> val, const char *fallback, bool can_be_number) {
720
+ if (val->IsString() || (can_be_number && val->IsNumber())) {
721
+ return strdup(*Nan::Utf8String(val));
722
+ } else if (fallback) {
723
+ return strdup(fallback);
724
+ } else {
725
+ return NULL;
726
+ }
727
+ }
728
+
729
+ NAN_METHOD(Canvas::RegisterFont) {
730
+ if (!info[0]->IsString()) {
731
+ return Nan::ThrowError("Wrong argument type");
732
+ } else if (!info[1]->IsObject()) {
733
+ return Nan::ThrowError(GENERIC_FACE_ERROR);
734
+ }
735
+
736
+ Nan::Utf8String filePath(info[0]);
737
+ PangoFontDescription *sys_desc = get_pango_font_description((unsigned char *) *filePath);
738
+
739
+ if (!sys_desc) return Nan::ThrowError("Could not parse font file");
740
+
741
+ PangoFontDescription *user_desc = pango_font_description_new();
742
+
743
+ // now check the attrs, there are many ways to be wrong
744
+ Local<Object> js_user_desc = Nan::To<Object>(info[1]).ToLocalChecked();
745
+ Local<String> family_prop = Nan::New<String>("family").ToLocalChecked();
746
+ Local<String> weight_prop = Nan::New<String>("weight").ToLocalChecked();
747
+ Local<String> style_prop = Nan::New<String>("style").ToLocalChecked();
748
+
749
+ char *family = str_value(Nan::Get(js_user_desc, family_prop).ToLocalChecked(), NULL, false);
750
+ char *weight = str_value(Nan::Get(js_user_desc, weight_prop).ToLocalChecked(), "normal", true);
751
+ char *style = str_value(Nan::Get(js_user_desc, style_prop).ToLocalChecked(), "normal", false);
752
+
753
+ if (family && weight && style) {
754
+ pango_font_description_set_weight(user_desc, Canvas::GetWeightFromCSSString(weight));
755
+ pango_font_description_set_style(user_desc, Canvas::GetStyleFromCSSString(style));
756
+ pango_font_description_set_family(user_desc, family);
757
+
758
+ auto found = std::find_if(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) {
759
+ return pango_font_description_equal(f.sys_desc, sys_desc);
760
+ });
761
+
762
+ if (found != font_face_list.end()) {
763
+ pango_font_description_free(found->user_desc);
764
+ found->user_desc = user_desc;
765
+ } else if (register_font((unsigned char *) *filePath)) {
766
+ FontFace face;
767
+ face.user_desc = user_desc;
768
+ face.sys_desc = sys_desc;
769
+ strncpy((char *)face.file_path, (char *) *filePath, 1023);
770
+ font_face_list.push_back(face);
771
+ } else {
772
+ pango_font_description_free(user_desc);
773
+ Nan::ThrowError("Could not load font to the system's font host");
774
+ }
775
+ } else {
776
+ pango_font_description_free(user_desc);
777
+ Nan::ThrowError(GENERIC_FACE_ERROR);
778
+ }
779
+
780
+ free(family);
781
+ free(weight);
782
+ free(style);
783
+ }
784
+
785
+ NAN_METHOD(Canvas::DeregisterAllFonts) {
786
+ // Unload all fonts from pango to free up memory
787
+ bool success = true;
788
+
789
+ std::for_each(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) {
790
+ if (!deregister_font( (unsigned char *)f.file_path )) success = false;
791
+ pango_font_description_free(f.user_desc);
792
+ pango_font_description_free(f.sys_desc);
793
+ });
794
+
795
+ font_face_list.clear();
796
+ if (!success) Nan::ThrowError("Could not deregister one or more fonts");
797
+ }
798
+
799
+ /*
800
+ * Initialize cairo surface.
801
+ */
802
+
803
+ Canvas::Canvas(Backend* backend) : ObjectWrap() {
804
+ _backend = backend;
805
+ }
806
+
807
+ /*
808
+ * Destroy cairo surface.
809
+ */
810
+
811
+ Canvas::~Canvas() {
812
+ if (_backend != NULL) {
813
+ delete _backend;
814
+ }
815
+ }
816
+
817
+ /*
818
+ * Get a PangoStyle from a CSS string (like "italic")
819
+ */
820
+
821
+ PangoStyle
822
+ Canvas::GetStyleFromCSSString(const char *style) {
823
+ PangoStyle s = PANGO_STYLE_NORMAL;
824
+
825
+ if (strlen(style) > 0) {
826
+ if (0 == strcmp("italic", style)) {
827
+ s = PANGO_STYLE_ITALIC;
828
+ } else if (0 == strcmp("oblique", style)) {
829
+ s = PANGO_STYLE_OBLIQUE;
830
+ }
831
+ }
832
+
833
+ return s;
834
+ }
835
+
836
+ /*
837
+ * Get a PangoWeight from a CSS string ("bold", "100", etc)
838
+ */
839
+
840
+ PangoWeight
841
+ Canvas::GetWeightFromCSSString(const char *weight) {
842
+ PangoWeight w = PANGO_WEIGHT_NORMAL;
843
+
844
+ if (strlen(weight) > 0) {
845
+ if (0 == strcmp("bold", weight)) {
846
+ w = PANGO_WEIGHT_BOLD;
847
+ } else if (0 == strcmp("100", weight)) {
848
+ w = PANGO_WEIGHT_THIN;
849
+ } else if (0 == strcmp("200", weight)) {
850
+ w = PANGO_WEIGHT_ULTRALIGHT;
851
+ } else if (0 == strcmp("300", weight)) {
852
+ w = PANGO_WEIGHT_LIGHT;
853
+ } else if (0 == strcmp("400", weight)) {
854
+ w = PANGO_WEIGHT_NORMAL;
855
+ } else if (0 == strcmp("500", weight)) {
856
+ w = PANGO_WEIGHT_MEDIUM;
857
+ } else if (0 == strcmp("600", weight)) {
858
+ w = PANGO_WEIGHT_SEMIBOLD;
859
+ } else if (0 == strcmp("700", weight)) {
860
+ w = PANGO_WEIGHT_BOLD;
861
+ } else if (0 == strcmp("800", weight)) {
862
+ w = PANGO_WEIGHT_ULTRABOLD;
863
+ } else if (0 == strcmp("900", weight)) {
864
+ w = PANGO_WEIGHT_HEAVY;
865
+ }
866
+ }
867
+
868
+ return w;
869
+ }
870
+
871
+ /*
872
+ * Given a user description, return a description that will select the
873
+ * font either from the system or @font-face
874
+ */
875
+
876
+ PangoFontDescription *
877
+ Canvas::ResolveFontDescription(const PangoFontDescription *desc) {
878
+ // One of the user-specified families could map to multiple SFNT family names
879
+ // if someone registered two different fonts under the same family name.
880
+ // https://drafts.csswg.org/css-fonts-3/#font-style-matching
881
+ FontFace best;
882
+ istringstream families(pango_font_description_get_family(desc));
883
+ unordered_set<string> seen_families;
884
+ string resolved_families;
885
+ bool first = true;
886
+
887
+ for (string family; getline(families, family, ','); ) {
888
+ string renamed_families;
889
+ for (auto& ff : font_face_list) {
890
+ string pangofamily = string(pango_font_description_get_family(ff.user_desc));
891
+ if (streq_casein(family, pangofamily)) {
892
+ const char* sys_desc_family_name = pango_font_description_get_family(ff.sys_desc);
893
+ bool unseen = seen_families.find(sys_desc_family_name) == seen_families.end();
894
+ bool better = best.user_desc == nullptr || pango_font_description_better_match(desc, best.user_desc, ff.user_desc);
895
+
896
+ // Avoid sending duplicate SFNT font names due to a bug in Pango for macOS:
897
+ // https://bugzilla.gnome.org/show_bug.cgi?id=762873
898
+ if (unseen) {
899
+ seen_families.insert(sys_desc_family_name);
900
+
901
+ if (better) {
902
+ renamed_families = string(sys_desc_family_name) + (renamed_families.size() ? "," : "") + renamed_families;
903
+ } else {
904
+ renamed_families = renamed_families + (renamed_families.size() ? "," : "") + sys_desc_family_name;
905
+ }
906
+ }
907
+
908
+ if (first && better) best = ff;
909
+ }
910
+ }
911
+
912
+ if (resolved_families.size()) resolved_families += ',';
913
+ resolved_families += renamed_families.size() ? renamed_families : family;
914
+ first = false;
915
+ }
916
+
917
+ PangoFontDescription* ret = pango_font_description_copy(best.sys_desc ? best.sys_desc : desc);
918
+ pango_font_description_set_family(ret, resolved_families.c_str());
919
+
920
+ return ret;
921
+ }
922
+
923
+ /*
924
+ * Re-alloc the surface, destroying the previous.
925
+ */
926
+
927
+ void
928
+ Canvas::resurface(Local<Object> canvas) {
929
+ Nan::HandleScope scope;
930
+ Local<Value> context;
931
+
932
+ backend()->recreateSurface();
933
+
934
+ // Reset context
935
+ context = Nan::Get(canvas, Nan::New<String>("context").ToLocalChecked()).ToLocalChecked();
936
+ if (!context->IsUndefined()) {
937
+ Context2d *context2d = ObjectWrap::Unwrap<Context2d>(Nan::To<Object>(context).ToLocalChecked());
938
+ cairo_t *prev = context2d->context();
939
+ context2d->setContext(createCairoContext());
940
+ context2d->resetState();
941
+ cairo_destroy(prev);
942
+ }
943
+ }
944
+
945
+ /**
946
+ * Wrapper around cairo_create()
947
+ * (do not call cairo_create directly, call this instead)
948
+ */
949
+ cairo_t*
950
+ Canvas::createCairoContext() {
951
+ cairo_t* ret = cairo_create(surface());
952
+ cairo_set_line_width(ret, 1); // Cairo defaults to 2
953
+ return ret;
954
+ }
955
+
956
+ /*
957
+ * Construct an Error from the given cairo status.
958
+ */
959
+
960
+ Local<Value>
961
+ Canvas::Error(cairo_status_t status) {
962
+ return Exception::Error(Nan::New<String>(cairo_status_to_string(status)).ToLocalChecked());
963
+ }
964
+
965
+ #undef CHECK_RECEIVER