1 |
|
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 | using namespace v8;
|
39 | using namespace std;
|
40 |
|
41 | Nan::Persistent<FunctionTemplate> Canvas::constructor;
|
42 |
|
43 | std::vector<FontFace> font_face_list;
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | void
|
50 | Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
|
51 | Nan::HandleScope scope;
|
52 |
|
53 |
|
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 |
|
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 |
|
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 |
|
91 |
|
92 |
|
93 | NAN_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 |
|
143 |
|
144 |
|
145 | NAN_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 |
|
152 |
|
153 | NAN_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 |
|
160 |
|
161 |
|
162 | NAN_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 |
|
169 |
|
170 |
|
171 | NAN_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 |
|
181 |
|
182 |
|
183 | NAN_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 |
|
190 |
|
191 |
|
192 | NAN_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 |
|
202 |
|
203 |
|
204 | void
|
205 | Canvas::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
|
215 | void
|
216 | Canvas::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 |
|
224 |
|
225 |
|
226 | void
|
227 | Canvas::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 |
|
246 | static 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 |
|
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 |
|
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 |
|
285 | static void parseJPEGArgs(Local<Value> arg, JpegClosure& jpegargs) {
|
286 |
|
287 |
|
288 |
|
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 |
|
315 | static uint32_t getSafeBufSize(Canvas* canvas) {
|
316 |
|
317 |
|
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 |
|
323 | static 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 |
|
329 | cairo_pdf_surface_set_metadata(surf, t, *Nan::Utf8String(propValue));
|
330 | }
|
331 | }
|
332 |
|
333 | static 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);
|
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 |
|
345 | static 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
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 |
|
385 | NAN_METHOD(Canvas::ToBuffer) {
|
386 | cairo_status_t status;
|
387 | Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
388 |
|
389 |
|
390 | const std::string name = canvas->backend()->getName();
|
391 | if (name == "pdf" || name == "svg") {
|
392 |
|
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()) {
|
398 | setPdfMetadata(canvas, Nan::To<Object>(info[1]).ToLocalChecked());
|
399 | }
|
400 | #endif
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
529 |
|
530 |
|
531 | static cairo_status_t
|
532 | streamPNG(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 |
|
547 |
|
548 |
|
549 |
|
550 | NAN_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 |
|
582 | struct PdfStreamInfo {
|
583 | Local<Function> fn;
|
584 | uint32_t len;
|
585 | uint8_t* data;
|
586 | };
|
587 |
|
588 |
|
589 |
|
590 |
|
591 |
|
592 |
|
593 | void stream_pdf_free(char *, void *) {}
|
594 |
|
595 |
|
596 |
|
597 |
|
598 |
|
599 | static cairo_status_t
|
600 | streamPDF(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 |
|
614 | cairo_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 |
|
631 |
|
632 |
|
633 | NAN_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 |
|
677 |
|
678 |
|
679 | #ifdef HAVE_JPEG
|
680 |
|
681 | NAN_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 |
|
702 | char *
|
703 | str_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 |
|
713 | NAN_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 |
|
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 |
|
770 |
|
771 |
|
772 | Canvas::Canvas(Backend* backend) : ObjectWrap() {
|
773 | _backend = backend;
|
774 | }
|
775 |
|
776 |
|
777 |
|
778 |
|
779 |
|
780 | Canvas::~Canvas() {
|
781 | if (_backend != NULL) {
|
782 | delete _backend;
|
783 | }
|
784 | }
|
785 |
|
786 |
|
787 |
|
788 |
|
789 |
|
790 | PangoStyle
|
791 | Canvas::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 |
|
807 |
|
808 |
|
809 | PangoWeight
|
810 | Canvas::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 |
|
840 | bool 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 |
|
848 |
|
849 |
|
850 |
|
851 | PangoFontDescription *
|
852 | Canvas::ResolveFontDescription(const PangoFontDescription *desc) {
|
853 |
|
854 |
|
855 |
|
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 |
|
871 |
|
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 |
|
897 |
|
898 |
|
899 | void
|
900 | Canvas::resurface(Local<Object> canvas) {
|
901 | Nan::HandleScope scope;
|
902 | Local<Value> context;
|
903 |
|
904 | backend()->recreateSurface();
|
905 |
|
906 |
|
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 |
|
919 |
|
920 |
|
921 | cairo_t*
|
922 | Canvas::createCairoContext() {
|
923 | cairo_t* ret = cairo_create(surface());
|
924 | cairo_set_line_width(ret, 1);
|
925 | return ret;
|
926 | }
|
927 |
|
928 |
|
929 |
|
930 |
|
931 |
|
932 | Local<Value>
|
933 | Canvas::Error(cairo_status_t status) {
|
934 | return Exception::Error(Nan::New<String>(cairo_status_to_string(status)).ToLocalChecked());
|
935 | }
|