UNPKG

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