diff --git a/CHANGELOG.md b/CHANGELOG.md index e0768f8c3..97b2c2661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ project adheres to [Semantic Versioning](http://semver.org/). * Avoid calling virtual methods in constructors/destructors to avoid bypassing virtual dispatch. (#2229) * Remove unused private field `backend` in the `Backend` class. (#2229) * Add Node.js v20 to CI. (#2237) +* Migrated to N-API (by way of node-addon-api) and removed libuv and v8 dependencies ### Added * Added string tags to support class detection ### Fixed diff --git a/Readme.md b/Readme.md index cc945aa9a..c029e27cb 100644 --- a/Readme.md +++ b/Readme.md @@ -13,7 +13,7 @@ $ npm install canvas By default, binaries for macOS, Linux and Windows will be downloaded. If you want to build from source, use `npm install --build-from-source` and see the **Compiling** section below. -The minimum version of Node.js required is **6.0.0**. +The minimum version of Node.js required is **10.20.0**. ### Compiling diff --git a/binding.gyp b/binding.gyp index 19a33e816..166842641 100644 --- a/binding.gyp +++ b/binding.gyp @@ -57,7 +57,8 @@ }, { 'target_name': 'canvas', - 'include_dirs': ["=6" + "node": ">=10.20.0" }, "license": "MIT" } diff --git a/src/Backends.cc b/src/Backends.cc index 2256c32b6..3a557669c 100644 --- a/src/Backends.cc +++ b/src/Backends.cc @@ -4,15 +4,15 @@ #include "backend/PdfBackend.h" #include "backend/SvgBackend.h" -using namespace v8; +using namespace Napi; -void Backends::Initialize(Local target) { - Nan::HandleScope scope; +void +Backends::Initialize(Napi::Env env, Napi::Object exports) { + Napi::Object obj = Napi::Object::New(env); - Local obj = Nan::New(); ImageBackend::Initialize(obj); PdfBackend::Initialize(obj); SvgBackend::Initialize(obj); - Nan::Set(target, Nan::New("Backends").ToLocalChecked(), obj).Check(); + exports.Set("Backends", obj); } diff --git a/src/Backends.h b/src/Backends.h index dbea053ce..66a1c1db8 100644 --- a/src/Backends.h +++ b/src/Backends.h @@ -1,10 +1,9 @@ #pragma once #include "backend/Backend.h" -#include -#include +#include -class Backends : public Nan::ObjectWrap { +class Backends : public Napi::ObjectWrap { public: - static void Initialize(v8::Local target); + static void Initialize(Napi::Env env, Napi::Object exports); }; diff --git a/src/Canvas.cc b/src/Canvas.cc index 0cfe750d6..2555605f9 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -1,7 +1,7 @@ // Copyright (c) 2010 LearnBoost #include "Canvas.h" - +#include "InstanceData.h" #include // std::min #include #include @@ -35,17 +35,8 @@ "with at least a family (string) and optionally weight (string/number) " \ "and style (string)." -#define CHECK_RECEIVER(prop) \ - if (!Canvas::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { \ - Nan::ThrowTypeError("Method " #prop " called on incompatible receiver"); \ - return; \ - } - -using namespace v8; using namespace std; -Nan::Persistent Canvas::constructor; - std::vector font_face_list; /* @@ -53,138 +44,138 @@ std::vector font_face_list; */ void -Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) { - Nan::HandleScope scope; +Canvas::Initialize(Napi::Env& env, Napi::Object& exports) { + Napi::HandleScope scope(env); + InstanceData* data = env.GetInstanceData(); // Constructor - Local ctor = Nan::New(Canvas::New); - constructor.Reset(ctor); - ctor->InstanceTemplate()->SetInternalFieldCount(1); - ctor->SetClassName(Nan::New("Canvas").ToLocalChecked()); - - // Prototype - Local proto = ctor->PrototypeTemplate(); - Nan::SetPrototypeMethod(ctor, "toBuffer", ToBuffer); - Nan::SetPrototypeMethod(ctor, "streamPNGSync", StreamPNGSync); - Nan::SetPrototypeMethod(ctor, "streamPDFSync", StreamPDFSync); + Napi::Function ctor = DefineClass(env, "Canvas", { + InstanceMethod<&Canvas::ToBuffer>("toBuffer"), + InstanceMethod<&Canvas::StreamPNGSync>("streamPNGSync"), + InstanceMethod<&Canvas::StreamPDFSync>("streamPDFSync"), #ifdef HAVE_JPEG - Nan::SetPrototypeMethod(ctor, "streamJPEGSync", StreamJPEGSync); + InstanceMethod<&Canvas::StreamJPEGSync>("streamJPEGSync"), #endif - Nan::SetAccessor(proto, Nan::New("type").ToLocalChecked(), GetType); - Nan::SetAccessor(proto, Nan::New("stride").ToLocalChecked(), GetStride); - Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth, SetWidth); - Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight, SetHeight); - - Nan::SetTemplate(proto, "PNG_NO_FILTERS", Nan::New(PNG_NO_FILTERS)); - Nan::SetTemplate(proto, "PNG_FILTER_NONE", Nan::New(PNG_FILTER_NONE)); - Nan::SetTemplate(proto, "PNG_FILTER_SUB", Nan::New(PNG_FILTER_SUB)); - Nan::SetTemplate(proto, "PNG_FILTER_UP", Nan::New(PNG_FILTER_UP)); - Nan::SetTemplate(proto, "PNG_FILTER_AVG", Nan::New(PNG_FILTER_AVG)); - Nan::SetTemplate(proto, "PNG_FILTER_PAETH", Nan::New(PNG_FILTER_PAETH)); - Nan::SetTemplate(proto, "PNG_ALL_FILTERS", Nan::New(PNG_ALL_FILTERS)); - - // Class methods - Nan::SetMethod(ctor, "_registerFont", RegisterFont); - Nan::SetMethod(ctor, "_deregisterAllFonts", DeregisterAllFonts); + InstanceAccessor<&Canvas::GetType>("type"), + InstanceAccessor<&Canvas::GetStride>("stride"), + InstanceAccessor<&Canvas::GetWidth, &Canvas::SetWidth>("width"), + InstanceAccessor<&Canvas::GetHeight, &Canvas::SetHeight>("height"), + InstanceValue("PNG_NO_FILTERS", Napi::Number::New(env, PNG_NO_FILTERS)), + InstanceValue("PNG_FILTER_NONE", Napi::Number::New(env, PNG_FILTER_NONE)), + InstanceValue("PNG_FILTER_SUB", Napi::Number::New(env, PNG_FILTER_SUB)), + InstanceValue("PNG_FILTER_UP", Napi::Number::New(env, PNG_FILTER_UP)), + InstanceValue("PNG_FILTER_AVG", Napi::Number::New(env, PNG_FILTER_AVG)), + InstanceValue("PNG_FILTER_PAETH", Napi::Number::New(env, PNG_FILTER_PAETH)), + InstanceValue("PNG_ALL_FILTERS", Napi::Number::New(env, PNG_ALL_FILTERS)), + StaticMethod<&Canvas::RegisterFont>("_registerFont"), + StaticMethod<&Canvas::DeregisterAllFonts>("_deregisterAllFonts") + }); - Local ctx = Nan::GetCurrentContext(); - Nan::Set(target, - Nan::New("Canvas").ToLocalChecked(), - ctor->GetFunction(ctx).ToLocalChecked()); + data->CanvasCtor = Napi::Persistent(ctor); + exports.Set("Canvas", ctor); } /* * Initialize a Canvas with the given width and height. */ -NAN_METHOD(Canvas::New) { - if (!info.IsConstructCall()) { - return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'"); - } - +Canvas::Canvas(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info), env(info.Env()) { + InstanceData* data = env.GetInstanceData(); + ctor = Napi::Persistent(data->CanvasCtor.Value()); Backend* backend = NULL; - if (info[0]->IsNumber()) { - int width = Nan::To(info[0]).FromMaybe(0), height = 0; - - if (info[1]->IsNumber()) height = Nan::To(info[1]).FromMaybe(0); - - if (info[2]->IsString()) { - if (0 == strcmp("pdf", *Nan::Utf8String(info[2]))) - backend = new PdfBackend(width, height); - else if (0 == strcmp("svg", *Nan::Utf8String(info[2]))) - backend = new SvgBackend(width, height); - else - backend = new ImageBackend(width, height); + Napi::Object jsBackend; + + if (info[0].IsNumber()) { + Napi::Number width = info[0].As(); + Napi::Number height = Napi::Number::New(env, 0); + + if (info[1].IsNumber()) height = info[1].As(); + + if (info[2].IsString()) { + std::string str = info[2].As(); + if (str == "pdf") { + Napi::Maybe instance = data->PdfBackendCtor.New({ width, height }); + if (instance.IsJust()) backend = PdfBackend::Unwrap(jsBackend = instance.Unwrap()); + } else if (str == "svg") { + Napi::Maybe instance = data->SvgBackendCtor.New({ width, height }); + if (instance.IsJust()) backend = SvgBackend::Unwrap(jsBackend = instance.Unwrap()); + } else { + Napi::Maybe instance = data->ImageBackendCtor.New({ width, height }); + if (instance.IsJust()) backend = ImageBackend::Unwrap(jsBackend = instance.Unwrap()); + } + } else { + Napi::Maybe instance = data->ImageBackendCtor.New({ width, height }); + if (instance.IsJust()) backend = ImageBackend::Unwrap(jsBackend = instance.Unwrap()); } - else - backend = new ImageBackend(width, height); - } - else if (info[0]->IsObject()) { - if (Nan::New(ImageBackend::constructor)->HasInstance(info[0]) || - Nan::New(PdfBackend::constructor)->HasInstance(info[0]) || - Nan::New(SvgBackend::constructor)->HasInstance(info[0])) { - backend = Nan::ObjectWrap::Unwrap(Nan::To(info[0]).ToLocalChecked()); - }else{ - return Nan::ThrowTypeError("Invalid arguments"); + } else if (info[0].IsObject()) { + jsBackend = info[0].As(); + if (jsBackend.InstanceOf(data->ImageBackendCtor.Value()).UnwrapOr(false)) { + backend = ImageBackend::Unwrap(jsBackend); + } else if (jsBackend.InstanceOf(data->PdfBackendCtor.Value()).UnwrapOr(false)) { + backend = PdfBackend::Unwrap(jsBackend); + } else if (jsBackend.InstanceOf(data->SvgBackendCtor.Value()).UnwrapOr(false)) { + backend = SvgBackend::Unwrap(jsBackend); + } else { + Napi::TypeError::New(env, "Invalid arguments").ThrowAsJavaScriptException(); + return; } - } - else { - backend = new ImageBackend(0, 0); + } else { + Napi::Number width = Napi::Number::New(env, 0); + Napi::Number height = Napi::Number::New(env, 0); + Napi::Maybe instance = data->ImageBackendCtor.New({ width, height }); + if (instance.IsJust()) backend = ImageBackend::Unwrap(jsBackend = instance.Unwrap()); } if (!backend->isSurfaceValid()) { - const char *error = backend->getError(); - delete backend; - return Nan::ThrowError(error); + Napi::Error::New(env, backend->getError()).ThrowAsJavaScriptException(); + return; } - Canvas* canvas = new Canvas(backend); - canvas->Wrap(info.This()); + backend->setCanvas(this); - backend->setCanvas(canvas); - - info.GetReturnValue().Set(info.This()); + // Note: the backend gets destroyed when the jsBackend is GC'd. The cleaner + // way would be to only store the jsBackend and unwrap it when the c++ ref is + // needed, but that's slower and a burden. The _backend might be null if we + // returned early, but since an exception was thrown it gets destroyed soon. + _backend = backend; + _jsBackend = Napi::Persistent(jsBackend); } /* * Get type string. */ -NAN_GETTER(Canvas::GetType) { - CHECK_RECEIVER(Canvas.GetType); - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(canvas->backend()->getName()).ToLocalChecked()); +Napi::Value +Canvas::GetType(const Napi::CallbackInfo& info) { + return Napi::String::New(env, backend()->getName()); } /* * Get stride. */ -NAN_GETTER(Canvas::GetStride) { - CHECK_RECEIVER(Canvas.GetStride); - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(canvas->stride())); +Napi::Value +Canvas::GetStride(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, stride()); } /* * Get width. */ -NAN_GETTER(Canvas::GetWidth) { - CHECK_RECEIVER(Canvas.GetWidth); - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(canvas->getWidth())); +Napi::Value +Canvas::GetWidth(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, getWidth()); } /* * Set width. */ -NAN_SETTER(Canvas::SetWidth) { - CHECK_RECEIVER(Canvas.SetWidth); - if (value->IsNumber()) { - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); - canvas->backend()->setWidth(Nan::To(value).FromMaybe(0)); - canvas->resurface(info.This()); +void +Canvas::SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsNumber()) { + backend()->setWidth(value.As().Uint32Value()); + resurface(info.This().As()); } } @@ -192,22 +183,20 @@ NAN_SETTER(Canvas::SetWidth) { * Get height. */ -NAN_GETTER(Canvas::GetHeight) { - CHECK_RECEIVER(Canvas.GetHeight); - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(canvas->getHeight())); +Napi::Value +Canvas::GetHeight(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, getHeight()); } /* * Set height. */ -NAN_SETTER(Canvas::SetHeight) { - CHECK_RECEIVER(Canvas.SetHeight); - if (value->IsNumber()) { - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); - canvas->backend()->setHeight(Nan::To(value).FromMaybe(0)); - canvas->resurface(info.This()); +void +Canvas::SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsNumber()) { + backend()->setHeight(value.As().Uint32Value()); + resurface(info.This().As()); } } @@ -216,8 +205,8 @@ NAN_SETTER(Canvas::SetHeight) { */ void -Canvas::ToPngBufferAsync(uv_work_t *req) { - PngClosure* closure = static_cast(req->data); +Canvas::ToPngBufferAsync(Closure* base) { + PngClosure* closure = static_cast(base); closure->status = canvas_write_to_png_stream( closure->canvas->surface(), @@ -227,102 +216,84 @@ Canvas::ToPngBufferAsync(uv_work_t *req) { #ifdef HAVE_JPEG void -Canvas::ToJpegBufferAsync(uv_work_t *req) { - JpegClosure* closure = static_cast(req->data); +Canvas::ToJpegBufferAsync(Closure* base) { + JpegClosure* closure = static_cast(base); write_to_jpeg_buffer(closure->canvas->surface(), closure); } #endif -/* - * EIO after toBuffer callback. - */ +static void +parsePNGArgs(Napi::Value arg, PngClosure& pngargs) { + if (arg.IsObject()) { + Napi::Object obj = arg.As(); + Napi::Value cLevel; -void -Canvas::ToBufferAsyncAfter(uv_work_t *req) { - Nan::HandleScope scope; - Nan::AsyncResource async("canvas:ToBufferAsyncAfter"); - Closure* closure = static_cast(req->data); - delete req; - - if (closure->status) { - Local argv[1] = { Canvas::Error(closure->status) }; - closure->cb.Call(1, argv, &async); - } else { - Local buf = Nan::CopyBuffer((char*)&closure->vec[0], closure->vec.size()).ToLocalChecked(); - Local argv[2] = { Nan::Null(), buf }; - closure->cb.Call(sizeof argv / sizeof *argv, argv, &async); - } - - closure->canvas->Unref(); - delete closure; -} - -static void parsePNGArgs(Local arg, PngClosure& pngargs) { - if (arg->IsObject()) { - Local obj = Nan::To(arg).ToLocalChecked(); - - Local cLevel = Nan::Get(obj, Nan::New("compressionLevel").ToLocalChecked()).ToLocalChecked(); - if (cLevel->IsUint32()) { - uint32_t val = Nan::To(cLevel).FromMaybe(0); + if (obj.Get("compressionLevel").UnwrapTo(&cLevel) && cLevel.IsNumber()) { + uint32_t val = cLevel.As().Uint32Value(); // See quote below from spec section 4.12.5.5. if (val <= 9) pngargs.compressionLevel = val; } - Local rez = Nan::Get(obj, Nan::New("resolution").ToLocalChecked()).ToLocalChecked(); - if (rez->IsUint32()) { - uint32_t val = Nan::To(rez).FromMaybe(0); + Napi::Value rez; + if (obj.Get("resolution").UnwrapTo(&rez) && rez.IsNumber()) { + uint32_t val = rez.As().Uint32Value(); if (val > 0) pngargs.resolution = val; } - Local filters = Nan::Get(obj, Nan::New("filters").ToLocalChecked()).ToLocalChecked(); - if (filters->IsUint32()) pngargs.filters = Nan::To(filters).FromMaybe(0); + Napi::Value filters; + if (obj.Get("filters").UnwrapTo(&filters) && filters.IsNumber()) { + pngargs.filters = filters.As().Uint32Value(); + } - Local palette = Nan::Get(obj, Nan::New("palette").ToLocalChecked()).ToLocalChecked(); - if (palette->IsUint8ClampedArray()) { - Local palette_ta = palette.As(); - pngargs.nPaletteColors = palette_ta->Length(); - if (pngargs.nPaletteColors % 4 != 0) { - throw "Palette length must be a multiple of 4."; - } - pngargs.nPaletteColors /= 4; - Nan::TypedArrayContents _paletteColors(palette_ta); - pngargs.palette = *_paletteColors; - // Optional background color index: - Local backgroundIndexVal = Nan::Get(obj, Nan::New("backgroundIndex").ToLocalChecked()).ToLocalChecked(); - if (backgroundIndexVal->IsUint32()) { - pngargs.backgroundIndex = static_cast(Nan::To(backgroundIndexVal).FromMaybe(0)); + Napi::Value palette; + if (obj.Get("palette").UnwrapTo(&palette) && palette.IsTypedArray()) { + Napi::TypedArray palette_ta = palette.As(); + if (palette_ta.TypedArrayType() == napi_uint8_clamped_array) { + pngargs.nPaletteColors = palette_ta.ElementLength(); + if (pngargs.nPaletteColors % 4 != 0) { + throw "Palette length must be a multiple of 4."; + } + pngargs.palette = palette_ta.As().Data(); + pngargs.nPaletteColors /= 4; + // Optional background color index: + Napi::Value backgroundIndexVal; + if (obj.Get("backgroundIndex").UnwrapTo(&backgroundIndexVal) && backgroundIndexVal.IsNumber()) { + pngargs.backgroundIndex = backgroundIndexVal.As().Uint32Value(); + } } } } } #ifdef HAVE_JPEG -static void parseJPEGArgs(Local arg, JpegClosure& jpegargs) { +static void parseJPEGArgs(Napi::Value arg, JpegClosure& jpegargs) { // "If Type(quality) is not Number, or if quality is outside that range, the // user agent must use its default quality value, as if the quality argument // had not been given." - 4.12.5.5 - if (arg->IsObject()) { - Local obj = Nan::To(arg).ToLocalChecked(); + if (arg.IsObject()) { + Napi::Object obj = arg.As(); - Local qual = Nan::Get(obj, Nan::New("quality").ToLocalChecked()).ToLocalChecked(); - if (qual->IsNumber()) { - double quality = Nan::To(qual).FromMaybe(0); + Napi::Value qual; + if (obj.Get("quality").UnwrapTo(&qual) && qual.IsNumber()) { + double quality = qual.As().DoubleValue(); if (quality >= 0.0 && quality <= 1.0) { jpegargs.quality = static_cast(100.0 * quality); } } - Local chroma = Nan::Get(obj, Nan::New("chromaSubsampling").ToLocalChecked()).ToLocalChecked(); - if (chroma->IsBoolean()) { - bool subsample = Nan::To(chroma).FromMaybe(0); - jpegargs.chromaSubsampling = subsample ? 2 : 1; - } else if (chroma->IsNumber()) { - jpegargs.chromaSubsampling = Nan::To(chroma).FromMaybe(0); + Napi::Value chroma; + if (obj.Get("chromaSubsampling").UnwrapTo(&chroma)) { + if (chroma.IsBoolean()) { + bool subsample = chroma.As().Value(); + jpegargs.chromaSubsampling = subsample ? 2 : 1; + } else if (chroma.IsNumber()) { + jpegargs.chromaSubsampling = chroma.As().Uint32Value(); + } } - Local progressive = Nan::Get(obj, Nan::New("progressive").ToLocalChecked()).ToLocalChecked(); - if (!progressive->IsUndefined()) { - jpegargs.progressive = Nan::To(progressive).FromMaybe(0); + Napi::Value progressive; + if (obj.Get("progressive").UnwrapTo(&progressive) && progressive.IsBoolean()) { + jpegargs.progressive = progressive.As().Value(); } } } @@ -330,29 +301,27 @@ static void parseJPEGArgs(Local arg, JpegClosure& jpegargs) { #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) -static inline void setPdfMetaStr(cairo_surface_t* surf, Local opts, - cairo_pdf_metadata_t t, const char* pName) { - auto propName = Nan::New(pName).ToLocalChecked(); - auto propValue = Nan::Get(opts, propName).ToLocalChecked(); - if (propValue->IsString()) { +static inline void setPdfMetaStr(cairo_surface_t* surf, Napi::Object opts, + cairo_pdf_metadata_t t, const char* propName) { + Napi::Value propValue; + if (opts.Get(propName).UnwrapTo(&propValue) && propValue.IsString()) { // (copies char data) - cairo_pdf_surface_set_metadata(surf, t, *Nan::Utf8String(propValue)); + cairo_pdf_surface_set_metadata(surf, t, propValue.As().Utf8Value().c_str()); } } -static inline void setPdfMetaDate(cairo_surface_t* surf, Local opts, - cairo_pdf_metadata_t t, const char* pName) { - auto propName = Nan::New(pName).ToLocalChecked(); - auto propValue = Nan::Get(opts, propName).ToLocalChecked(); - if (propValue->IsDate()) { - auto date = static_cast(propValue.As()->ValueOf() / 1000); // ms -> s +static inline void setPdfMetaDate(cairo_surface_t* surf, Napi::Object opts, + cairo_pdf_metadata_t t, const char* propName) { + Napi::Value propValue; + if (opts.Get(propName).UnwrapTo(&propValue) && propValue.IsDate()) { + auto date = static_cast(propValue.As().ValueOf() / 1000); // ms -> s char buf[sizeof "2011-10-08T07:07:09Z"]; strftime(buf, sizeof buf, "%FT%TZ", gmtime(&date)); cairo_pdf_surface_set_metadata(surf, t, buf); } } -static void setPdfMetadata(Canvas* canvas, Local opts) { +static void setPdfMetadata(Canvas* canvas, Napi::Object opts) { cairo_surface_t* surf = canvas->surface(); setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_TITLE, "title"); @@ -392,146 +361,137 @@ static void setPdfMetadata(Canvas* canvas, Local opts) { ((err: null|Error, buffer) => any, "image/jpeg", {quality?: number, progressive?: Boolean, chromaSubsampling?: Boolean|number}) */ -NAN_METHOD(Canvas::ToBuffer) { +Napi::Value +Canvas::ToBuffer(const Napi::CallbackInfo& info) { + EncodingWorker *worker = new EncodingWorker(info.Env()); cairo_status_t status; - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); // Vector canvases, sync only - const std::string name = canvas->backend()->getName(); + const std::string name = backend()->getName(); if (name == "pdf" || name == "svg") { // mime type may be present, but it's not checked PdfSvgClosure* closure; if (name == "pdf") { - closure = static_cast(canvas->backend())->closure(); + closure = static_cast(backend())->closure(); #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) - if (info[1]->IsObject()) { // toBuffer("application/pdf", config) - setPdfMetadata(canvas, Nan::To(info[1]).ToLocalChecked()); + if (info[1].IsObject()) { // toBuffer("application/pdf", config) + setPdfMetadata(this, info[1].As()); } #endif // CAIRO 16+ } else { - closure = static_cast(canvas->backend())->closure(); + closure = static_cast(backend())->closure(); } - cairo_surface_finish(canvas->surface()); - Local buf = Nan::CopyBuffer((char*)&closure->vec[0], closure->vec.size()).ToLocalChecked(); - info.GetReturnValue().Set(buf); - return; + cairo_surface_finish(surface()); + return Napi::Buffer::Copy(env, &closure->vec[0], closure->vec.size()); } // Raw ARGB data -- just a memcpy() - if (info[0]->StrictEquals(Nan::New("raw").ToLocalChecked())) { - cairo_surface_t *surface = canvas->surface(); + if (info[0].StrictEquals(Napi::String::New(env, "raw"))) { + cairo_surface_t *surface = this->surface(); cairo_surface_flush(surface); - if (canvas->nBytes() > node::Buffer::kMaxLength) { - Nan::ThrowError("Data exceeds maximum buffer length."); - return; + if (nBytes() > node::Buffer::kMaxLength) { + Napi::Error::New(env, "Data exceeds maximum buffer length.").ThrowAsJavaScriptException(); + return env.Undefined(); } - const unsigned char *data = cairo_image_surface_get_data(surface); - Isolate* iso = Nan::GetCurrentContext()->GetIsolate(); - Local buf = node::Buffer::Copy(iso, reinterpret_cast(data), canvas->nBytes()).ToLocalChecked(); - info.GetReturnValue().Set(buf); - return; + return Napi::Buffer::Copy(env, cairo_image_surface_get_data(surface), nBytes()); } // Sync PNG, default - if (info[0]->IsUndefined() || info[0]->StrictEquals(Nan::New("image/png").ToLocalChecked())) { + if (info[0].IsUndefined() || info[0].StrictEquals(Napi::String::New(env, "image/png"))) { try { - PngClosure closure(canvas); + PngClosure closure(this); parsePNGArgs(info[1], closure); if (closure.nPaletteColors == 0xFFFFFFFF) { - Nan::ThrowError("Palette length must be a multiple of 4."); - return; + Napi::Error::New(env, "Palette length must be a multiple of 4.").ThrowAsJavaScriptException(); + return env.Undefined(); } - Nan::TryCatch try_catch; - status = canvas_write_to_png_stream(canvas->surface(), PngClosure::writeVec, &closure); + status = canvas_write_to_png_stream(surface(), PngClosure::writeVec, &closure); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - } else if (status) { - throw status; - } else { - // TODO it's possible to avoid this copy - Local buf = Nan::CopyBuffer((char *)&closure.vec[0], closure.vec.size()).ToLocalChecked(); - info.GetReturnValue().Set(buf); + if (!env.IsExceptionPending()) { + if (status) { + throw status; // TODO: throw in js? + } else { + // TODO it's possible to avoid this copy + return Napi::Buffer::Copy(env, &closure.vec[0], closure.vec.size()); + } } } catch (cairo_status_t ex) { - Nan::ThrowError(Canvas::Error(ex)); + CairoError(ex).ThrowAsJavaScriptException(); } catch (const char* ex) { - Nan::ThrowError(ex); + Napi::Error::New(env, ex).ThrowAsJavaScriptException(); + } - return; + + return env.Undefined(); } // Async PNG - if (info[0]->IsFunction() && - (info[1]->IsUndefined() || info[1]->StrictEquals(Nan::New("image/png").ToLocalChecked()))) { + if (info[0].IsFunction() && + (info[1].IsUndefined() || info[1].StrictEquals(Napi::String::New(env, "image/png")))) { PngClosure* closure; try { - closure = new PngClosure(canvas); + closure = new PngClosure(this); parsePNGArgs(info[2], *closure); } catch (cairo_status_t ex) { - Nan::ThrowError(Canvas::Error(ex)); - return; + CairoError(ex).ThrowAsJavaScriptException(); + return env.Undefined(); } catch (const char* ex) { - Nan::ThrowError(ex); - return; + Napi::Error::New(env, ex).ThrowAsJavaScriptException(); + return env.Undefined(); } - canvas->Ref(); - closure->cb.Reset(info[0].As()); + Ref(); + closure->cb = Napi::Persistent(info[0].As()); - uv_work_t* req = new uv_work_t; - req->data = closure; // Make sure the surface exists since we won't have an isolate context in the async block: - canvas->surface(); - uv_queue_work(uv_default_loop(), req, ToPngBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter); + surface(); + worker->Init(&ToPngBufferAsync, closure); + worker->Queue(); - return; + return env.Undefined(); } #ifdef HAVE_JPEG // Sync JPEG - Local jpegStr = Nan::New("image/jpeg").ToLocalChecked(); - if (info[0]->StrictEquals(jpegStr)) { + Napi::Value jpegStr = Napi::String::New(env, "image/jpeg"); + if (info[0].StrictEquals(jpegStr)) { try { - JpegClosure closure(canvas); + JpegClosure closure(this); parseJPEGArgs(info[1], closure); - Nan::TryCatch try_catch; - write_to_jpeg_buffer(canvas->surface(), &closure); + write_to_jpeg_buffer(surface(), &closure); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - } else { + if (!env.IsExceptionPending()) { // TODO it's possible to avoid this copy. - Local buf = Nan::CopyBuffer((char *)&closure.vec[0], closure.vec.size()).ToLocalChecked(); - info.GetReturnValue().Set(buf); + return Napi::Buffer::Copy(env, &closure.vec[0], closure.vec.size()); } } catch (cairo_status_t ex) { - Nan::ThrowError(Canvas::Error(ex)); + CairoError(ex).ThrowAsJavaScriptException(); + return env.Undefined(); } - return; + return env.Undefined(); } // Async JPEG - if (info[0]->IsFunction() && info[1]->StrictEquals(jpegStr)) { - JpegClosure* closure = new JpegClosure(canvas); + if (info[0].IsFunction() && info[1].StrictEquals(jpegStr)) { + JpegClosure* closure = new JpegClosure(this); parseJPEGArgs(info[2], *closure); - canvas->Ref(); - closure->cb.Reset(info[0].As()); + Ref(); + closure->cb = Napi::Persistent(info[0].As()); - uv_work_t* req = new uv_work_t; - req->data = closure; // Make sure the surface exists since we won't have an isolate context in the async block: - canvas->surface(); - uv_queue_work(uv_default_loop(), req, ToJpegBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter); - - return; + surface(); + worker->Init(&ToJpegBufferAsync, closure); + worker->Queue(); + return env.Undefined(); } #endif + + return env.Undefined(); } /* @@ -540,15 +500,12 @@ NAN_METHOD(Canvas::ToBuffer) { static cairo_status_t streamPNG(void *c, const uint8_t *data, unsigned len) { - Nan::HandleScope scope; - Nan::AsyncResource async("canvas:StreamPNG"); PngClosure* closure = (PngClosure*) c; - Local buf = Nan::CopyBuffer((char *)data, len).ToLocalChecked(); - Local argv[3] = { - Nan::Null() - , buf - , Nan::New(len) }; - closure->cb.Call(sizeof argv / sizeof *argv, argv, &async); + Napi::Env env = closure->canvas->env; + Napi::HandleScope scope(env); + Napi::AsyncContext async(env, "canvas:StreamPNG"); + Napi::Value buf = Napi::Buffer::Copy(env, data, len); + closure->cb.MakeCallback(env.Global(), { env.Null(), buf, Napi::Number::New(env, len) }, async); return CAIRO_STATUS_SUCCESS; } @@ -557,69 +514,51 @@ streamPNG(void *c, const uint8_t *data, unsigned len) { * StreamPngSync(this, options: {palette?: Uint8ClampedArray, backgroundIndex?: uint32, compressionLevel: uint32, filters: uint32}) */ -NAN_METHOD(Canvas::StreamPNGSync) { - if (!info[0]->IsFunction()) - return Nan::ThrowTypeError("callback function required"); - - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); +void +Canvas::StreamPNGSync(const Napi::CallbackInfo& info) { + if (!info[0].IsFunction()) { + Napi::TypeError::New(env, "callback function required").ThrowAsJavaScriptException(); + return; + } - PngClosure closure(canvas); + PngClosure closure(this); parsePNGArgs(info[1], closure); - closure.cb.Reset(Local::Cast(info[0])); - - Nan::TryCatch try_catch; + closure.cb = Napi::Persistent(info[0].As()); - cairo_status_t status = canvas_write_to_png_stream(canvas->surface(), streamPNG, &closure); + cairo_status_t status = canvas_write_to_png_stream(surface(), streamPNG, &closure); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } else if (status) { - Local argv[1] = { Canvas::Error(status) }; - Nan::Call(closure.cb, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv); - } else { - Local argv[3] = { - Nan::Null() - , Nan::Null() - , Nan::New(0) }; - Nan::Call(closure.cb, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv); + if (!env.IsExceptionPending()) { + if (status) { + closure.cb.Call(env.Global(), { CairoError(status).Value() }); + } else { + closure.cb.Call(env.Global(), { env.Null(), env.Null(), Napi::Number::New(env, 0) }); + } } - return; } struct PdfStreamInfo { - Local fn; + Napi::Function fn; uint32_t len; uint8_t* data; }; - -/* - * Canvas::StreamPDF FreeCallback - */ - -void stream_pdf_free(char *, void *) {} - /* * Canvas::StreamPDF callback. */ static cairo_status_t streamPDF(void *c, const uint8_t *data, unsigned len) { - Nan::HandleScope scope; - Nan::AsyncResource async("canvas:StreamPDF"); PdfStreamInfo* streaminfo = static_cast(c); + Napi::Env env = streaminfo->fn.Env(); + Napi::HandleScope scope(env); + Napi::AsyncContext async(env, "canvas:StreamPDF"); // TODO this is technically wrong, we're returning a pointer to the data in a // vector in a class with automatic storage duration. If the canvas goes out // of scope while we're in the handler, a use-after-free could happen. - Local buf = Nan::NewBuffer(const_cast(reinterpret_cast(data)), len, stream_pdf_free, 0).ToLocalChecked(); - Local argv[3] = { - Nan::Null() - , buf - , Nan::New(len) }; - async.runInAsyncScope(Nan::GetCurrentContext()->Global(), streaminfo->fn, sizeof argv / sizeof *argv, argv); + Napi::Value buf = Napi::Buffer::New(env, (uint8_t *)(data), len); + streaminfo->fn.MakeCallback(env.Global(), { env.Null(), buf, Napi::Number::New(env, len) }, async); return CAIRO_STATUS_SUCCESS; } @@ -643,45 +582,41 @@ cairo_status_t canvas_write_to_pdf_stream(cairo_surface_t *surface, cairo_write_ * Stream PDF data synchronously. */ -NAN_METHOD(Canvas::StreamPDFSync) { - if (!info[0]->IsFunction()) - return Nan::ThrowTypeError("callback function required"); - - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.Holder()); +void +Canvas::StreamPDFSync(const Napi::CallbackInfo& info) { + if (!info[0].IsFunction()) { + Napi::TypeError::New(env, "callback function required").ThrowAsJavaScriptException(); + return; + } - if (canvas->backend()->getName() != "pdf") - return Nan::ThrowTypeError("wrong canvas type"); + if (backend()->getName() != "pdf") { + Napi::TypeError::New(env, "wrong canvas type").ThrowAsJavaScriptException(); + return; + } #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0) - if (info[1]->IsObject()) { - setPdfMetadata(canvas, Nan::To(info[1]).ToLocalChecked()); + if (info[1].IsObject()) { + setPdfMetadata(this, info[1].As()); } #endif - cairo_surface_finish(canvas->surface()); + cairo_surface_finish(surface()); - PdfSvgClosure* closure = static_cast(canvas->backend())->closure(); - Local fn = info[0].As(); + PdfSvgClosure* closure = static_cast(backend())->closure(); + Napi::Function fn = info[0].As(); PdfStreamInfo streaminfo; streaminfo.fn = fn; streaminfo.data = &closure->vec[0]; streaminfo.len = closure->vec.size(); - Nan::TryCatch try_catch; - - cairo_status_t status = canvas_write_to_pdf_stream(canvas->surface(), streamPDF, &streaminfo); + cairo_status_t status = canvas_write_to_pdf_stream(surface(), streamPDF, &streaminfo); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - } else if (status) { - Local error = Canvas::Error(status); - Nan::Call(fn, Nan::GetCurrentContext()->Global(), 1, &error); - } else { - Local argv[3] = { - Nan::Null() - , Nan::Null() - , Nan::New(0) }; - Nan::Call(fn, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv); + if (!env.IsExceptionPending()) { + if (status) { + fn.Call(env.Global(), { CairoError(status).Value() }); + } else { + fn.Call(env.Global(), { env.Null(), env.Null(), Napi::Number::New(env, 0) }); + } } } @@ -696,60 +631,64 @@ static uint32_t getSafeBufSize(Canvas* canvas) { return (std::min)(canvas->getWidth() * canvas->getHeight() * 4, static_cast(PAGE_SIZE)); } -NAN_METHOD(Canvas::StreamJPEGSync) { - if (!info[1]->IsFunction()) - return Nan::ThrowTypeError("callback function required"); +void +Canvas::StreamJPEGSync(const Napi::CallbackInfo& info) { + if (!info[1].IsFunction()) { + Napi::TypeError::New(env, "callback function required").ThrowAsJavaScriptException(); + return; + } - Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); - JpegClosure closure(canvas); + JpegClosure closure(this); parseJPEGArgs(info[0], closure); - closure.cb.Reset(Local::Cast(info[1])); - - Nan::TryCatch try_catch; - uint32_t bufsize = getSafeBufSize(canvas); - write_to_jpeg_stream(canvas->surface(), bufsize, &closure); + closure.cb = Napi::Persistent(info[1].As()); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - } - return; + uint32_t bufsize = getSafeBufSize(this); + write_to_jpeg_stream(surface(), bufsize, &closure); } #endif char * -str_value(Local val, const char *fallback, bool can_be_number) { - if (val->IsString() || (can_be_number && val->IsNumber())) { - return strdup(*Nan::Utf8String(val)); - } else if (fallback) { - return strdup(fallback); - } else { - return NULL; +str_value(Napi::Maybe maybe, const char *fallback, bool can_be_number) { + Napi::Value val; + if (maybe.UnwrapTo(&val)) { + if (val.IsString() || (can_be_number && val.IsNumber())) { + Napi::String strVal; + if (val.ToString().UnwrapTo(&strVal)) return strdup(strVal.Utf8Value().c_str()); + } else if (fallback) { + return strdup(fallback); + } } + + return NULL; } -NAN_METHOD(Canvas::RegisterFont) { - if (!info[0]->IsString()) { - return Nan::ThrowError("Wrong argument type"); - } else if (!info[1]->IsObject()) { - return Nan::ThrowError(GENERIC_FACE_ERROR); +void +Canvas::RegisterFont(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + if (!info[0].IsString()) { + Napi::Error::New(env, "Wrong argument type").ThrowAsJavaScriptException(); + return; + } else if (!info[1].IsObject()) { + Napi::Error::New(env, GENERIC_FACE_ERROR).ThrowAsJavaScriptException(); + return; } - Nan::Utf8String filePath(info[0]); - PangoFontDescription *sys_desc = get_pango_font_description((unsigned char *) *filePath); + std::string filePath = info[0].As(); + PangoFontDescription *sys_desc = get_pango_font_description((unsigned char *)(filePath.c_str())); - if (!sys_desc) return Nan::ThrowError("Could not parse font file"); + if (!sys_desc) { + Napi::Error::New(env, "Could not parse font file").ThrowAsJavaScriptException(); + return; + } PangoFontDescription *user_desc = pango_font_description_new(); // now check the attrs, there are many ways to be wrong - Local js_user_desc = Nan::To(info[1]).ToLocalChecked(); - Local family_prop = Nan::New("family").ToLocalChecked(); - Local weight_prop = Nan::New("weight").ToLocalChecked(); - Local style_prop = Nan::New("style").ToLocalChecked(); + Napi::Object js_user_desc = info[1].As(); - char *family = str_value(Nan::Get(js_user_desc, family_prop).ToLocalChecked(), NULL, false); - char *weight = str_value(Nan::Get(js_user_desc, weight_prop).ToLocalChecked(), "normal", true); - char *style = str_value(Nan::Get(js_user_desc, style_prop).ToLocalChecked(), "normal", false); + char *family = str_value(js_user_desc.Get("family"), NULL, false); + char *weight = str_value(js_user_desc.Get("weight"), "normal", true); + char *style = str_value(js_user_desc.Get("style"), "normal", false); if (family && weight && style) { pango_font_description_set_weight(user_desc, Canvas::GetWeightFromCSSString(weight)); @@ -763,19 +702,22 @@ NAN_METHOD(Canvas::RegisterFont) { if (found != font_face_list.end()) { pango_font_description_free(found->user_desc); found->user_desc = user_desc; - } else if (register_font((unsigned char *) *filePath)) { + } else if (register_font((unsigned char *) filePath.c_str())) { FontFace face; face.user_desc = user_desc; face.sys_desc = sys_desc; - strncpy((char *)face.file_path, (char *) *filePath, 1023); + strncpy((char *)face.file_path, (char *) filePath.c_str(), 1023); font_face_list.push_back(face); } else { pango_font_description_free(user_desc); - Nan::ThrowError("Could not load font to the system's font host"); + Napi::Error::New(env, "Could not load font to the system's font host").ThrowAsJavaScriptException(); + } } else { pango_font_description_free(user_desc); - Nan::ThrowError(GENERIC_FACE_ERROR); + if (!env.IsExceptionPending()) { + Napi::Error::New(env, GENERIC_FACE_ERROR).ThrowAsJavaScriptException(); + } } free(family); @@ -783,7 +725,9 @@ NAN_METHOD(Canvas::RegisterFont) { free(style); } -NAN_METHOD(Canvas::DeregisterAllFonts) { +void +Canvas::DeregisterAllFonts(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); // Unload all fonts from pango to free up memory bool success = true; @@ -794,25 +738,7 @@ NAN_METHOD(Canvas::DeregisterAllFonts) { }); font_face_list.clear(); - if (!success) Nan::ThrowError("Could not deregister one or more fonts"); -} - -/* - * Initialize cairo surface. - */ - -Canvas::Canvas(Backend* backend) : ObjectWrap() { - _backend = backend; -} - -/* - * Destroy cairo surface. - */ - -Canvas::~Canvas() { - if (_backend != NULL) { - delete _backend; - } + if (!success) Napi::Error::New(env, "Could not deregister one or more fonts").ThrowAsJavaScriptException(); } /* @@ -926,21 +852,19 @@ Canvas::ResolveFontDescription(const PangoFontDescription *desc) { */ void -Canvas::resurface(Local canvas) { - Nan::HandleScope scope; - Local context; - - backend()->recreateSurface(); - - // Reset context - context = Nan::Get(canvas, Nan::New("context").ToLocalChecked()).ToLocalChecked(); - if (!context->IsUndefined()) { - Context2d *context2d = ObjectWrap::Unwrap(Nan::To(context).ToLocalChecked()); - cairo_t *prev = context2d->context(); - context2d->setContext(createCairoContext()); - context2d->resetState(); - cairo_destroy(prev); - } +Canvas::resurface(Napi::Object This) { + Napi::HandleScope scope(env); + Napi::Value context; + + if (This.Get("context").UnwrapTo(&context) && context.IsObject()) { + backend()->recreateSurface(); + // Reset context + Context2d *context2d = Context2d::Unwrap(context.As()); + cairo_t *prev = context2d->context(); + context2d->setContext(createCairoContext()); + context2d->resetState(); + cairo_destroy(prev); + } } /** @@ -958,9 +882,7 @@ Canvas::createCairoContext() { * Construct an Error from the given cairo status. */ -Local -Canvas::Error(cairo_status_t status) { - return Exception::Error(Nan::New(cairo_status_to_string(status)).ToLocalChecked()); +Napi::Error +Canvas::CairoError(cairo_status_t status) { + return Napi::Error::New(env, cairo_status_to_string(status)); } - -#undef CHECK_RECEIVER diff --git a/src/Canvas.h b/src/Canvas.h index 60d3b4216..5f35b356b 100644 --- a/src/Canvas.h +++ b/src/Canvas.h @@ -2,12 +2,14 @@ #pragma once +struct Closure; + #include "backend/Backend.h" +#include "closure.h" #include #include "dll_visibility.h" -#include +#include #include -#include #include #include @@ -49,27 +51,26 @@ enum canvas_draw_mode_t : uint8_t { * Canvas. */ -class Canvas: public Nan::ObjectWrap { +class Canvas : public Napi::ObjectWrap { public: - static Nan::Persistent constructor; - static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target); - static NAN_METHOD(New); - static NAN_METHOD(ToBuffer); - static NAN_GETTER(GetType); - static NAN_GETTER(GetStride); - static NAN_GETTER(GetWidth); - static NAN_GETTER(GetHeight); - static NAN_SETTER(SetWidth); - static NAN_SETTER(SetHeight); - static NAN_METHOD(StreamPNGSync); - static NAN_METHOD(StreamPDFSync); - static NAN_METHOD(StreamJPEGSync); - static NAN_METHOD(RegisterFont); - static NAN_METHOD(DeregisterAllFonts); - static v8::Local Error(cairo_status_t status); - static void ToPngBufferAsync(uv_work_t *req); - static void ToJpegBufferAsync(uv_work_t *req); - static void ToBufferAsyncAfter(uv_work_t *req); + Canvas(const Napi::CallbackInfo& info); + static void Initialize(Napi::Env& env, Napi::Object& target); + + Napi::Value ToBuffer(const Napi::CallbackInfo& info); + Napi::Value GetType(const Napi::CallbackInfo& info); + Napi::Value GetStride(const Napi::CallbackInfo& info); + Napi::Value GetWidth(const Napi::CallbackInfo& info); + Napi::Value GetHeight(const Napi::CallbackInfo& info); + void SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value); + void StreamPNGSync(const Napi::CallbackInfo& info); + void StreamPDFSync(const Napi::CallbackInfo& info); + void StreamJPEGSync(const Napi::CallbackInfo& info); + static void RegisterFont(const Napi::CallbackInfo& info); + static void DeregisterAllFonts(const Napi::CallbackInfo& info); + Napi::Error CairoError(cairo_status_t status); + static void ToPngBufferAsync(Closure* closure); + static void ToJpegBufferAsync(Closure* closure); static PangoWeight GetWeightFromCSSString(const char *weight); static PangoStyle GetStyleFromCSSString(const char *style); static PangoFontDescription *ResolveFontDescription(const PangoFontDescription *desc); @@ -81,16 +82,18 @@ class Canvas: public Nan::ObjectWrap { DLL_PUBLIC inline uint8_t *data(){ return cairo_image_surface_get_data(surface()); } DLL_PUBLIC inline int stride(){ return cairo_image_surface_get_stride(surface()); } DLL_PUBLIC inline std::size_t nBytes(){ - return static_cast(getHeight()) * stride(); + return static_cast(backend()->getHeight()) * stride(); } DLL_PUBLIC inline int getWidth() { return backend()->getWidth(); } DLL_PUBLIC inline int getHeight() { return backend()->getHeight(); } - Canvas(Backend* backend); - void resurface(v8::Local canvas); + void resurface(Napi::Object This); + + Napi::Env env; private: - ~Canvas(); Backend* _backend; + Napi::ObjectReference _jsBackend; + Napi::FunctionReference ctor; }; diff --git a/src/CanvasError.h b/src/CanvasError.h index cb751e312..535d153fa 100644 --- a/src/CanvasError.h +++ b/src/CanvasError.h @@ -1,6 +1,7 @@ #pragma once #include +#include class CanvasError { public: @@ -20,4 +21,17 @@ class CanvasError { path.clear(); cerrno = 0; } + bool empty() { + return cerrno == 0 && message.empty(); + } + Napi::Error toError(Napi::Env env) { + if (cerrno) { + Napi::Error err = Napi::Error::New(env, strerror(cerrno)); + if (!syscall.empty()) err.Value().Set("syscall", syscall); + if (!path.empty()) err.Value().Set("path", path); + return err; + } else { + return Napi::Error::New(env, message); + } + } }; diff --git a/src/CanvasGradient.cc b/src/CanvasGradient.cc index 280fc2e8c..9c2d42360 100644 --- a/src/CanvasGradient.cc +++ b/src/CanvasGradient.cc @@ -1,123 +1,113 @@ // Copyright (c) 2010 LearnBoost #include "CanvasGradient.h" +#include "InstanceData.h" #include "Canvas.h" #include "color.h" -using namespace v8; - -Nan::Persistent Gradient::constructor; +using namespace Napi; /* * Initialize CanvasGradient. */ void -Gradient::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) { - Nan::HandleScope scope; - - // Constructor - Local ctor = Nan::New(Gradient::New); - constructor.Reset(ctor); - ctor->InstanceTemplate()->SetInternalFieldCount(1); - ctor->SetClassName(Nan::New("CanvasGradient").ToLocalChecked()); - - // Prototype - Nan::SetPrototypeMethod(ctor, "addColorStop", AddColorStop); - Local ctx = Nan::GetCurrentContext(); - Nan::Set(target, - Nan::New("CanvasGradient").ToLocalChecked(), - ctor->GetFunction(ctx).ToLocalChecked()); +Gradient::Initialize(Napi::Env& env, Napi::Object& exports) { + Napi::HandleScope scope(env); + InstanceData* data = env.GetInstanceData(); + + Napi::Function ctor = DefineClass(env, "CanvasGradient", { + InstanceMethod<&Gradient::AddColorStop>("addColorStop") + }); + + exports.Set("CanvasGradient", ctor); + data->CanvasGradientCtor = Napi::Persistent(ctor); } /* * Initialize a new CanvasGradient. */ -NAN_METHOD(Gradient::New) { - if (!info.IsConstructCall()) { - return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'"); - } - +Gradient::Gradient(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info), env(info.Env()) { // Linear - if (4 == info.Length()) { - Gradient *grad = new Gradient( - Nan::To(info[0]).FromMaybe(0) - , Nan::To(info[1]).FromMaybe(0) - , Nan::To(info[2]).FromMaybe(0) - , Nan::To(info[3]).FromMaybe(0)); - grad->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); + if ( + 4 == info.Length() && + info[0].IsNumber() && + info[1].IsNumber() && + info[2].IsNumber() && + info[3].IsNumber() + ) { + double x0 = info[0].As().DoubleValue(); + double y0 = info[1].As().DoubleValue(); + double x1 = info[2].As().DoubleValue(); + double y1 = info[3].As().DoubleValue(); + _pattern = cairo_pattern_create_linear(x0, y0, x1, y1); return; } // Radial - if (6 == info.Length()) { - Gradient *grad = new Gradient( - Nan::To(info[0]).FromMaybe(0) - , Nan::To(info[1]).FromMaybe(0) - , Nan::To(info[2]).FromMaybe(0) - , Nan::To(info[3]).FromMaybe(0) - , Nan::To(info[4]).FromMaybe(0) - , Nan::To(info[5]).FromMaybe(0)); - grad->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); + if ( + 6 == info.Length() && + info[0].IsNumber() && + info[1].IsNumber() && + info[2].IsNumber() && + info[3].IsNumber() && + info[4].IsNumber() && + info[5].IsNumber() + ) { + double x0 = info[0].As().DoubleValue(); + double y0 = info[1].As().DoubleValue(); + double r0 = info[2].As().DoubleValue(); + double x1 = info[3].As().DoubleValue(); + double y1 = info[4].As().DoubleValue(); + double r1 = info[5].As().DoubleValue(); + _pattern = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1); return; } - return Nan::ThrowTypeError("invalid arguments"); + Napi::TypeError::New(env, "invalid arguments").ThrowAsJavaScriptException(); } /* * Add color stop. */ -NAN_METHOD(Gradient::AddColorStop) { - if (!info[0]->IsNumber()) - return Nan::ThrowTypeError("offset required"); - if (!info[1]->IsString()) - return Nan::ThrowTypeError("color string required"); +void +Gradient::AddColorStop(const Napi::CallbackInfo& info) { + if (!info[0].IsNumber()) { + Napi::TypeError::New(env, "offset required").ThrowAsJavaScriptException(); + return; + } + + if (!info[1].IsString()) { + Napi::TypeError::New(env, "color string required").ThrowAsJavaScriptException(); + return; + } - Gradient *grad = Nan::ObjectWrap::Unwrap(info.This()); short ok; - Nan::Utf8String str(info[1]); - uint32_t rgba = rgba_from_string(*str, &ok); + std::string str = info[1].As(); + uint32_t rgba = rgba_from_string(str.c_str(), &ok); if (ok) { rgba_t color = rgba_create(rgba); cairo_pattern_add_color_stop_rgba( - grad->pattern() - , Nan::To(info[0]).FromMaybe(0) + _pattern + , info[0].As().DoubleValue() , color.r , color.g , color.b , color.a); } else { - return Nan::ThrowTypeError("parse color failed"); + Napi::TypeError::New(env, "parse color failed").ThrowAsJavaScriptException(); } } -/* - * Initialize linear gradient. - */ - -Gradient::Gradient(double x0, double y0, double x1, double y1) { - _pattern = cairo_pattern_create_linear(x0, y0, x1, y1); -} - -/* - * Initialize radial gradient. - */ - -Gradient::Gradient(double x0, double y0, double r0, double x1, double y1, double r1) { - _pattern = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1); -} /* * Destroy the pattern. */ Gradient::~Gradient() { - cairo_pattern_destroy(_pattern); + if (_pattern) cairo_pattern_destroy(_pattern); } diff --git a/src/CanvasGradient.h b/src/CanvasGradient.h index b6902c428..103e80748 100644 --- a/src/CanvasGradient.h +++ b/src/CanvasGradient.h @@ -2,21 +2,19 @@ #pragma once -#include -#include +#include #include -class Gradient: public Nan::ObjectWrap { +class Gradient : public Napi::ObjectWrap { public: - static Nan::Persistent constructor; - static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target); - static NAN_METHOD(New); - static NAN_METHOD(AddColorStop); - Gradient(double x0, double y0, double x1, double y1); - Gradient(double x0, double y0, double r0, double x1, double y1, double r1); + static void Initialize(Napi::Env& env, Napi::Object& target); + Gradient(const Napi::CallbackInfo& info); + void AddColorStop(const Napi::CallbackInfo& info); inline cairo_pattern_t *pattern(){ return _pattern; } + ~Gradient(); + + Napi::Env env; private: - ~Gradient(); cairo_pattern_t *_pattern; }; diff --git a/src/CanvasPattern.cc b/src/CanvasPattern.cc index fa3848b37..55b8bb7fb 100644 --- a/src/CanvasPattern.cc +++ b/src/CanvasPattern.cc @@ -4,122 +4,115 @@ #include "Canvas.h" #include "Image.h" +#include "InstanceData.h" -using namespace v8; +using namespace Napi; const cairo_user_data_key_t *pattern_repeat_key; -Nan::Persistent Pattern::constructor; -Nan::Persistent Pattern::_DOMMatrix; - /* * Initialize CanvasPattern. */ void -Pattern::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) { - Nan::HandleScope scope; +Pattern::Initialize(Napi::Env& env, Napi::Object& exports) { + Napi::HandleScope scope(env); + InstanceData* data = env.GetInstanceData(); // Constructor - Local ctor = Nan::New(Pattern::New); - constructor.Reset(ctor); - ctor->InstanceTemplate()->SetInternalFieldCount(1); - ctor->SetClassName(Nan::New("CanvasPattern").ToLocalChecked()); - Nan::SetPrototypeMethod(ctor, "setTransform", SetTransform); + Napi::Function ctor = DefineClass(env, "CanvasPattern", { + InstanceMethod<&Pattern::setTransform>("setTransform") + }); // Prototype - Local ctx = Nan::GetCurrentContext(); - Nan::Set(target, Nan::New("CanvasPattern").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked()); - Nan::Set(target, Nan::New("CanvasPatternInit").ToLocalChecked(), Nan::New(SaveExternalModules)); -} - -/* - * Save some external modules as private references. - */ - -NAN_METHOD(Pattern::SaveExternalModules) { - _DOMMatrix.Reset(Nan::To(info[0]).ToLocalChecked()); + exports.Set("CanvasPattern", ctor); + data->CanvasPatternCtor = Napi::Persistent(ctor); } /* * Initialize a new CanvasPattern. */ -NAN_METHOD(Pattern::New) { - if (!info.IsConstructCall()) { - return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'"); +Pattern::Pattern(const Napi::CallbackInfo& info) : ObjectWrap(info), env(info.Env()) { + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "Image or Canvas expected").ThrowAsJavaScriptException(); + return; } + Napi::Object obj = info[0].As(); + InstanceData* data = env.GetInstanceData(); cairo_surface_t *surface; - Local obj = Nan::To(info[0]).ToLocalChecked(); - // Image - if (Nan::New(Image::constructor)->HasInstance(obj)) { - Image *img = Nan::ObjectWrap::Unwrap(obj); + if (obj.InstanceOf(data->ImageCtor.Value()).UnwrapOr(false)) { + Image *img = Image::Unwrap(obj); if (!img->isComplete()) { - return Nan::ThrowError("Image given has not completed loading"); + Napi::Error::New(env, "Image given has not completed loading").ThrowAsJavaScriptException(); + return; } surface = img->surface(); // Canvas - } else if (Nan::New(Canvas::constructor)->HasInstance(obj)) { - Canvas *canvas = Nan::ObjectWrap::Unwrap(obj); + } else if (obj.InstanceOf(data->CanvasCtor.Value()).UnwrapOr(false)) { + Canvas *canvas = Canvas::Unwrap(obj); surface = canvas->surface(); // Invalid } else { - return Nan::ThrowTypeError("Image or Canvas expected"); + if (!env.IsExceptionPending()) { + Napi::TypeError::New(env, "Image or Canvas expected").ThrowAsJavaScriptException(); + } + return; } - repeat_type_t repeat = REPEAT; - if (0 == strcmp("no-repeat", *Nan::Utf8String(info[1]))) { - repeat = NO_REPEAT; - } else if (0 == strcmp("repeat-x", *Nan::Utf8String(info[1]))) { - repeat = REPEAT_X; - } else if (0 == strcmp("repeat-y", *Nan::Utf8String(info[1]))) { - repeat = REPEAT_Y; + _pattern = cairo_pattern_create_for_surface(surface); + + if (info[1].IsString()) { + if ("no-repeat" == info[1].As().Utf8Value()) { + _repeat = NO_REPEAT; + } else if ("repeat-x" == info[1].As().Utf8Value()) { + _repeat = REPEAT_X; + } else if ("repeat-y" == info[1].As().Utf8Value()) { + _repeat = REPEAT_Y; + } } - Pattern *pattern = new Pattern(surface, repeat); - pattern->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); + + cairo_pattern_set_user_data(_pattern, pattern_repeat_key, &_repeat, NULL); } /* * Set the pattern-space to user-space transform. */ -NAN_METHOD(Pattern::SetTransform) { - Pattern *pattern = Nan::ObjectWrap::Unwrap(info.This()); - Local ctx = Nan::GetCurrentContext(); - Local mat = Nan::To(info[0]).ToLocalChecked(); - -#if NODE_MAJOR_VERSION >= 8 - if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) { - return Nan::ThrowTypeError("Expected DOMMatrix"); +void +Pattern::setTransform(const Napi::CallbackInfo& info) { + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException(); + return; } -#endif + + Napi::Object mat = info[0].As(); + + InstanceData* data = env.GetInstanceData(); + if (!mat.InstanceOf(data->DOMMatrixCtor.Value()).UnwrapOr(false)) { + if (!env.IsExceptionPending()) { + Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException(); + } + return; + } + + Napi::Value one = Napi::Number::New(env, 1); + Napi::Value zero = Napi::Number::New(env, 0); cairo_matrix_t matrix; cairo_matrix_init(&matrix, - Nan::To(Nan::Get(mat, Nan::New("a").ToLocalChecked()).ToLocalChecked()).FromMaybe(1), - Nan::To(Nan::Get(mat, Nan::New("b").ToLocalChecked()).ToLocalChecked()).FromMaybe(0), - Nan::To(Nan::Get(mat, Nan::New("c").ToLocalChecked()).ToLocalChecked()).FromMaybe(0), - Nan::To(Nan::Get(mat, Nan::New("d").ToLocalChecked()).ToLocalChecked()).FromMaybe(1), - Nan::To(Nan::Get(mat, Nan::New("e").ToLocalChecked()).ToLocalChecked()).FromMaybe(0), - Nan::To(Nan::Get(mat, Nan::New("f").ToLocalChecked()).ToLocalChecked()).FromMaybe(0) + mat.Get("a").UnwrapOr(one).As().DoubleValue(), + mat.Get("b").UnwrapOr(zero).As().DoubleValue(), + mat.Get("c").UnwrapOr(zero).As().DoubleValue(), + mat.Get("d").UnwrapOr(one).As().DoubleValue(), + mat.Get("e").UnwrapOr(zero).As().DoubleValue(), + mat.Get("f").UnwrapOr(zero).As().DoubleValue() ); cairo_matrix_invert(&matrix); - cairo_pattern_set_matrix(pattern->_pattern, &matrix); -} - - -/* - * Initialize pattern. - */ - -Pattern::Pattern(cairo_surface_t *surface, repeat_type_t repeat) { - _pattern = cairo_pattern_create_for_surface(surface); - _repeat = repeat; - cairo_pattern_set_user_data(_pattern, pattern_repeat_key, &_repeat, NULL); + cairo_pattern_set_matrix(_pattern, &matrix); } repeat_type_t Pattern::get_repeat_type_for_cairo_pattern(cairo_pattern_t *pattern) { @@ -132,5 +125,5 @@ repeat_type_t Pattern::get_repeat_type_for_cairo_pattern(cairo_pattern_t *patter */ Pattern::~Pattern() { - cairo_pattern_destroy(_pattern); + if (_pattern) cairo_pattern_destroy(_pattern); } diff --git a/src/CanvasPattern.h b/src/CanvasPattern.h index 29e2171b6..1f768e03b 100644 --- a/src/CanvasPattern.h +++ b/src/CanvasPattern.h @@ -3,8 +3,7 @@ #pragma once #include -#include -#include +#include /* * Canvas types. @@ -19,19 +18,16 @@ typedef enum { extern const cairo_user_data_key_t *pattern_repeat_key; -class Pattern: public Nan::ObjectWrap { +class Pattern : public Napi::ObjectWrap { public: - static Nan::Persistent constructor; - static Nan::Persistent _DOMMatrix; - static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target); - static NAN_METHOD(New); - static NAN_METHOD(SaveExternalModules); - static NAN_METHOD(SetTransform); + Pattern(const Napi::CallbackInfo& info); + static void Initialize(Napi::Env& env, Napi::Object& target); + void setTransform(const Napi::CallbackInfo& info); static repeat_type_t get_repeat_type_for_cairo_pattern(cairo_pattern_t *pattern); - Pattern(cairo_surface_t *surface, repeat_type_t repeat); inline cairo_pattern_t *pattern(){ return _pattern; } - private: ~Pattern(); + Napi::Env env; + private: cairo_pattern_t *_pattern; - repeat_type_t _repeat; + repeat_type_t _repeat = REPEAT; }; diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 5bfe08d6a..56e68d899 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -8,6 +8,7 @@ #include "Canvas.h" #include "CanvasGradient.h" #include "CanvasPattern.h" +#include "InstanceData.h" #include #include #include "Image.h" @@ -19,10 +20,6 @@ #include "Util.h" #include -using namespace v8; - -Nan::Persistent Context2d::constructor; - /* * Rectangle arg assertions. */ @@ -36,12 +33,6 @@ Nan::Persistent Context2d::constructor; double width = args[2]; \ double height = args[3]; -#define CHECK_RECEIVER(prop) \ - if (!Context2d::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { \ - Nan::ThrowTypeError("Method " #prop " called on incompatible receiver"); \ - return; \ - } - constexpr double twoPi = M_PI * 2.; /* @@ -53,12 +44,29 @@ constexpr double twoPi = M_PI * 2.; pango_layout_get_font_description(LAYOUT), \ pango_context_get_language(pango_layout_get_context(LAYOUT))) -inline static bool checkArgs(const Nan::FunctionCallbackInfo &info, double *args, int argsNum, int offset = 0){ - int argsEnd = offset + argsNum; +inline static bool checkArgs(const Napi::CallbackInfo&info, double *args, int argsNum, int offset = 0){ + Napi::Env env = info.Env(); + int argsEnd = std::min(9, offset + argsNum); bool areArgsValid = true; + napi_value argv[9]; + size_t argc = 9; + napi_get_cb_info(env, static_cast(info), &argc, argv, nullptr, nullptr); + for (int i = offset; i < argsEnd; i++) { - double val = Nan::To(info[i]).FromMaybe(0); + napi_valuetype type; + double val = 0; + + napi_typeof(env, argv[i], &type); + if (type == napi_number) { + // fast path + napi_get_value_double(env, argv[i], &val); + } else { + napi_value num; + if (napi_coerce_to_number(env, argv[i], &num) == napi_ok) { + napi_get_value_double(env, num, &val); + } + } if (areArgsValid) { if (!std::isfinite(val)) { @@ -76,100 +84,139 @@ inline static bool checkArgs(const Nan::FunctionCallbackInfo &info, doubl return areArgsValid; } -Nan::Persistent Context2d::_DOMMatrix; -Nan::Persistent Context2d::_parseFont; - /* * Initialize Context2d. */ void -Context2d::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) { - Nan::HandleScope scope; - - // Constructor - Local ctor = Nan::New(Context2d::New); - constructor.Reset(ctor); - ctor->InstanceTemplate()->SetInternalFieldCount(1); - ctor->SetClassName(Nan::New("CanvasRenderingContext2D").ToLocalChecked()); - - // Prototype - Local proto = ctor->PrototypeTemplate(); - Nan::SetPrototypeMethod(ctor, "drawImage", DrawImage); - Nan::SetPrototypeMethod(ctor, "putImageData", PutImageData); - Nan::SetPrototypeMethod(ctor, "getImageData", GetImageData); - Nan::SetPrototypeMethod(ctor, "createImageData", CreateImageData); - Nan::SetPrototypeMethod(ctor, "addPage", AddPage); - Nan::SetPrototypeMethod(ctor, "save", Save); - Nan::SetPrototypeMethod(ctor, "restore", Restore); - Nan::SetPrototypeMethod(ctor, "rotate", Rotate); - Nan::SetPrototypeMethod(ctor, "translate", Translate); - Nan::SetPrototypeMethod(ctor, "transform", Transform); - Nan::SetPrototypeMethod(ctor, "getTransform", GetTransform); - Nan::SetPrototypeMethod(ctor, "resetTransform", ResetTransform); - Nan::SetPrototypeMethod(ctor, "setTransform", SetTransform); - Nan::SetPrototypeMethod(ctor, "isPointInPath", IsPointInPath); - Nan::SetPrototypeMethod(ctor, "scale", Scale); - Nan::SetPrototypeMethod(ctor, "clip", Clip); - Nan::SetPrototypeMethod(ctor, "fill", Fill); - Nan::SetPrototypeMethod(ctor, "stroke", Stroke); - Nan::SetPrototypeMethod(ctor, "fillText", FillText); - Nan::SetPrototypeMethod(ctor, "strokeText", StrokeText); - Nan::SetPrototypeMethod(ctor, "fillRect", FillRect); - Nan::SetPrototypeMethod(ctor, "strokeRect", StrokeRect); - Nan::SetPrototypeMethod(ctor, "clearRect", ClearRect); - Nan::SetPrototypeMethod(ctor, "rect", Rect); - Nan::SetPrototypeMethod(ctor, "roundRect", RoundRect); - Nan::SetPrototypeMethod(ctor, "measureText", MeasureText); - Nan::SetPrototypeMethod(ctor, "moveTo", MoveTo); - Nan::SetPrototypeMethod(ctor, "lineTo", LineTo); - Nan::SetPrototypeMethod(ctor, "bezierCurveTo", BezierCurveTo); - Nan::SetPrototypeMethod(ctor, "quadraticCurveTo", QuadraticCurveTo); - Nan::SetPrototypeMethod(ctor, "beginPath", BeginPath); - Nan::SetPrototypeMethod(ctor, "closePath", ClosePath); - Nan::SetPrototypeMethod(ctor, "arc", Arc); - Nan::SetPrototypeMethod(ctor, "arcTo", ArcTo); - Nan::SetPrototypeMethod(ctor, "ellipse", Ellipse); - Nan::SetPrototypeMethod(ctor, "setLineDash", SetLineDash); - Nan::SetPrototypeMethod(ctor, "getLineDash", GetLineDash); - Nan::SetPrototypeMethod(ctor, "createPattern", CreatePattern); - Nan::SetPrototypeMethod(ctor, "createLinearGradient", CreateLinearGradient); - Nan::SetPrototypeMethod(ctor, "createRadialGradient", CreateRadialGradient); - Nan::SetAccessor(proto, Nan::New("pixelFormat").ToLocalChecked(), GetFormat); - Nan::SetAccessor(proto, Nan::New("patternQuality").ToLocalChecked(), GetPatternQuality, SetPatternQuality); - Nan::SetAccessor(proto, Nan::New("imageSmoothingEnabled").ToLocalChecked(), GetImageSmoothingEnabled, SetImageSmoothingEnabled); - Nan::SetAccessor(proto, Nan::New("globalCompositeOperation").ToLocalChecked(), GetGlobalCompositeOperation, SetGlobalCompositeOperation); - Nan::SetAccessor(proto, Nan::New("globalAlpha").ToLocalChecked(), GetGlobalAlpha, SetGlobalAlpha); - Nan::SetAccessor(proto, Nan::New("shadowColor").ToLocalChecked(), GetShadowColor, SetShadowColor); - Nan::SetAccessor(proto, Nan::New("miterLimit").ToLocalChecked(), GetMiterLimit, SetMiterLimit); - Nan::SetAccessor(proto, Nan::New("lineWidth").ToLocalChecked(), GetLineWidth, SetLineWidth); - Nan::SetAccessor(proto, Nan::New("lineCap").ToLocalChecked(), GetLineCap, SetLineCap); - Nan::SetAccessor(proto, Nan::New("lineJoin").ToLocalChecked(), GetLineJoin, SetLineJoin); - Nan::SetAccessor(proto, Nan::New("lineDashOffset").ToLocalChecked(), GetLineDashOffset, SetLineDashOffset); - Nan::SetAccessor(proto, Nan::New("shadowOffsetX").ToLocalChecked(), GetShadowOffsetX, SetShadowOffsetX); - Nan::SetAccessor(proto, Nan::New("shadowOffsetY").ToLocalChecked(), GetShadowOffsetY, SetShadowOffsetY); - Nan::SetAccessor(proto, Nan::New("shadowBlur").ToLocalChecked(), GetShadowBlur, SetShadowBlur); - Nan::SetAccessor(proto, Nan::New("antialias").ToLocalChecked(), GetAntiAlias, SetAntiAlias); - Nan::SetAccessor(proto, Nan::New("textDrawingMode").ToLocalChecked(), GetTextDrawingMode, SetTextDrawingMode); - Nan::SetAccessor(proto, Nan::New("quality").ToLocalChecked(), GetQuality, SetQuality); - Nan::SetAccessor(proto, Nan::New("currentTransform").ToLocalChecked(), GetCurrentTransform, SetCurrentTransform); - Nan::SetAccessor(proto, Nan::New("fillStyle").ToLocalChecked(), GetFillStyle, SetFillStyle); - Nan::SetAccessor(proto, Nan::New("strokeStyle").ToLocalChecked(), GetStrokeStyle, SetStrokeStyle); - Nan::SetAccessor(proto, Nan::New("font").ToLocalChecked(), GetFont, SetFont); - Nan::SetAccessor(proto, Nan::New("textBaseline").ToLocalChecked(), GetTextBaseline, SetTextBaseline); - Nan::SetAccessor(proto, Nan::New("textAlign").ToLocalChecked(), GetTextAlign, SetTextAlign); - Local ctx = Nan::GetCurrentContext(); - Nan::Set(target, Nan::New("CanvasRenderingContext2d").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked()); - Nan::Set(target, Nan::New("CanvasRenderingContext2dInit").ToLocalChecked(), Nan::New(SaveExternalModules)); +Context2d::Initialize(Napi::Env& env, Napi::Object& exports) { + Napi::HandleScope scope(env); + InstanceData* data = env.GetInstanceData(); + + Napi::Function ctor = DefineClass(env, "CanvasRenderingContext2D", { + InstanceMethod<&Context2d::DrawImage>("drawImage"), + InstanceMethod<&Context2d::PutImageData>("putImageData"), + InstanceMethod<&Context2d::GetImageData>("getImageData"), + InstanceMethod<&Context2d::CreateImageData>("createImageData"), + InstanceMethod<&Context2d::AddPage>("addPage"), + InstanceMethod<&Context2d::Save>("save"), + InstanceMethod<&Context2d::Restore>("restore"), + InstanceMethod<&Context2d::Rotate>("rotate"), + InstanceMethod<&Context2d::Translate>("translate"), + InstanceMethod<&Context2d::Transform>("transform"), + InstanceMethod<&Context2d::GetTransform>("getTransform"), + InstanceMethod<&Context2d::ResetTransform>("resetTransform"), + InstanceMethod<&Context2d::SetTransform>("setTransform"), + InstanceMethod<&Context2d::IsPointInPath>("isPointInPath"), + InstanceMethod<&Context2d::Scale>("scale"), + InstanceMethod<&Context2d::Clip>("clip"), + InstanceMethod<&Context2d::Fill>("fill"), + InstanceMethod<&Context2d::Stroke>("stroke"), + InstanceMethod<&Context2d::FillText>("fillText"), + InstanceMethod<&Context2d::StrokeText>("strokeText"), + InstanceMethod<&Context2d::FillRect>("fillRect"), + InstanceMethod<&Context2d::StrokeRect>("strokeRect"), + InstanceMethod<&Context2d::ClearRect>("clearRect"), + InstanceMethod<&Context2d::Rect>("rect"), + InstanceMethod<&Context2d::RoundRect>("roundRect"), + InstanceMethod<&Context2d::MeasureText>("measureText"), + InstanceMethod<&Context2d::MoveTo>("moveTo"), + InstanceMethod<&Context2d::LineTo>("lineTo"), + InstanceMethod<&Context2d::BezierCurveTo>("bezierCurveTo"), + InstanceMethod<&Context2d::QuadraticCurveTo>("quadraticCurveTo"), + InstanceMethod<&Context2d::BeginPath>("beginPath"), + InstanceMethod<&Context2d::ClosePath>("closePath"), + InstanceMethod<&Context2d::Arc>("arc"), + InstanceMethod<&Context2d::ArcTo>("arcTo"), + InstanceMethod<&Context2d::Ellipse>("ellipse"), + InstanceMethod<&Context2d::SetLineDash>("setLineDash"), + InstanceMethod<&Context2d::GetLineDash>("getLineDash"), + InstanceMethod<&Context2d::CreatePattern>("createPattern"), + InstanceMethod<&Context2d::CreateLinearGradient>("createLinearGradient"), + InstanceMethod<&Context2d::CreateRadialGradient>("createRadialGradient"), + InstanceAccessor<&Context2d::GetFormat>("pixelFormat"), + InstanceAccessor<&Context2d::GetPatternQuality, &Context2d::SetPatternQuality>("patternQuality"), + InstanceAccessor<&Context2d::GetImageSmoothingEnabled, &Context2d::SetImageSmoothingEnabled>("imageSmoothingEnabled"), + InstanceAccessor<&Context2d::GetGlobalCompositeOperation, &Context2d::SetGlobalCompositeOperation>("globalCompositeOperation"), + InstanceAccessor<&Context2d::GetGlobalAlpha, &Context2d::SetGlobalAlpha>("globalAlpha"), + InstanceAccessor<&Context2d::GetShadowColor, &Context2d::SetShadowColor>("shadowColor"), + InstanceAccessor<&Context2d::GetMiterLimit, &Context2d::SetMiterLimit>("miterLimit"), + InstanceAccessor<&Context2d::GetLineWidth, &Context2d::SetLineWidth>("lineWidth"), + InstanceAccessor<&Context2d::GetLineCap, &Context2d::SetLineCap>("lineCap"), + InstanceAccessor<&Context2d::GetLineJoin, &Context2d::SetLineJoin>("lineJoin"), + InstanceAccessor<&Context2d::GetLineDashOffset, &Context2d::SetLineDashOffset>("lineDashOffset"), + InstanceAccessor<&Context2d::GetShadowOffsetX, &Context2d::SetShadowOffsetX>("shadowOffsetX"), + InstanceAccessor<&Context2d::GetShadowOffsetY, &Context2d::SetShadowOffsetY>("shadowOffsetY"), + InstanceAccessor<&Context2d::GetShadowBlur, &Context2d::SetShadowBlur>("shadowBlur"), + InstanceAccessor<&Context2d::GetAntiAlias, &Context2d::SetAntiAlias>("antialias"), + InstanceAccessor<&Context2d::GetTextDrawingMode, &Context2d::SetTextDrawingMode>("textDrawingMode"), + InstanceAccessor<&Context2d::GetQuality, &Context2d::SetQuality>("quality"), + InstanceAccessor<&Context2d::GetCurrentTransform, &Context2d::SetCurrentTransform>("currentTransform"), + InstanceAccessor<&Context2d::GetFillStyle, &Context2d::SetFillStyle>("fillStyle"), + InstanceAccessor<&Context2d::GetStrokeStyle, &Context2d::SetStrokeStyle>("strokeStyle"), + InstanceAccessor<&Context2d::GetFont, &Context2d::SetFont>("font"), + InstanceAccessor<&Context2d::GetTextBaseline, &Context2d::SetTextBaseline>("textBaseline"), + InstanceAccessor<&Context2d::GetTextAlign, &Context2d::SetTextAlign>("textAlign") + }); + + exports.Set("CanvasRenderingContext2d", ctor); + data->Context2dCtor = Napi::Persistent(ctor); } /* * Create a cairo context. */ -Context2d::Context2d(Canvas *canvas) { - _canvas = canvas; - _context = canvas->createCairoContext(); +Context2d::Context2d(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info), env(info.Env()) { + InstanceData* data = env.GetInstanceData(); + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "Canvas expected").ThrowAsJavaScriptException(); + return; + } + + Napi::Object obj = info[0].As(); + if (!obj.InstanceOf(data->CanvasCtor.Value()).UnwrapOr(false)) { + if (!env.IsExceptionPending()) { + Napi::TypeError::New(env, "Canvas expected").ThrowAsJavaScriptException(); + } + return; + } + + _canvas = Canvas::Unwrap(obj); + + bool isImageBackend = _canvas->backend()->getName() == "image"; + if (isImageBackend) { + cairo_format_t format = ImageBackend::DEFAULT_FORMAT; + + if (info[1].IsObject()) { + Napi::Object ctxAttributes = info[1].As(); + Napi::Value pixelFormat; + + if (ctxAttributes.Get("pixelFormat").UnwrapTo(&pixelFormat) && pixelFormat.IsString()) { + std::string utf8PixelFormat = pixelFormat.As(); + if (utf8PixelFormat == "RGBA32") format = CAIRO_FORMAT_ARGB32; + else if (utf8PixelFormat == "RGB24") format = CAIRO_FORMAT_RGB24; + else if (utf8PixelFormat == "A8") format = CAIRO_FORMAT_A8; + else if (utf8PixelFormat == "RGB16_565") format = CAIRO_FORMAT_RGB16_565; + else if (utf8PixelFormat == "A1") format = CAIRO_FORMAT_A1; +#ifdef CAIRO_FORMAT_RGB30 + else if (utf8PixelFormat == "RGB30") format = CAIRO_FORMAT_RGB30; +#endif + } + + // alpha: false forces use of RGB24 + Napi::Value alpha; + + if (ctxAttributes.Get("alpha").UnwrapTo(&alpha) && alpha.IsBoolean() && !alpha.As().Value()) { + format = CAIRO_FORMAT_RGB24; + } + } + + static_cast(_canvas->backend())->setFormat(format); + } + + _context = _canvas->createCairoContext(); _layout = pango_cairo_create_layout(_context); // As of January 2023, Pango rounds glyph positions which renders text wider @@ -188,8 +235,8 @@ Context2d::Context2d(Canvas *canvas) { */ Context2d::~Context2d() { - g_object_unref(_layout); - cairo_destroy(_context); + if (_layout) g_object_unref(_layout); + if (_context) cairo_destroy(_context); _resetPersistentHandles(); } @@ -283,7 +330,6 @@ create_transparent_gradient(cairo_pattern_t *source, float alpha) { cairo_pattern_get_radial_circles(source, &x0, &y0, &r0, &x1, &y1, &r1); newGradient = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1); } else { - Nan::ThrowError("Unexpected gradient type"); return NULL; } for ( i = 0; i < count; i++ ) { @@ -305,7 +351,6 @@ create_transparent_pattern(cairo_pattern_t *source, float alpha) { height); cairo_t *mask_context = cairo_create(mask_surface); if (cairo_status(mask_context) != CAIRO_STATUS_SUCCESS) { - Nan::ThrowError("Failed to initialize context"); return NULL; } cairo_set_source(mask_context, source); @@ -321,11 +366,11 @@ create_transparent_pattern(cairo_pattern_t *source, float alpha) { */ void -Context2d::setFillRule(v8::Local value) { +Context2d::setFillRule(Napi::Value value) { cairo_fill_rule_t rule = CAIRO_FILL_RULE_WINDING; - if (value->IsString()) { - Nan::Utf8String str(value); - if (std::strcmp(*str, "evenodd") == 0) { + if (value.IsString()) { + std::string str = value.As().Utf8Value(); + if (str == "evenodd") { rule = CAIRO_FILL_RULE_EVEN_ODD; } } @@ -340,7 +385,8 @@ Context2d::fill(bool preserve) { if (state->globalAlpha < 1) { new_pattern = create_transparent_pattern(state->fillPattern, state->globalAlpha); if (new_pattern == NULL) { - // failed to allocate; Nan::ThrowError has already been called, so return from this fn. + Napi::Error::New(env, "Failed to initialize context").ThrowAsJavaScriptException(); + // failed to allocate return; } cairo_set_source(_context, new_pattern); @@ -385,7 +431,8 @@ Context2d::fill(bool preserve) { if (state->globalAlpha < 1) { new_pattern = create_transparent_gradient(state->fillGradient, state->globalAlpha); if (new_pattern == NULL) { - // failed to recognize gradient; Nan::ThrowError has already been called, so return from this fn. + Napi::Error::New(env, "Unexpected gradient type").ThrowAsJavaScriptException(); + // failed to recognize gradient return; } cairo_pattern_set_filter(new_pattern, state->patternQuality); @@ -423,7 +470,8 @@ Context2d::stroke(bool preserve) { if (state->globalAlpha < 1) { new_pattern = create_transparent_pattern(state->strokePattern, state->globalAlpha); if (new_pattern == NULL) { - // failed to allocate; Nan::ThrowError has already been called, so return from this fn. + Napi::Error::New(env, "Failed to initialize context").ThrowAsJavaScriptException(); + // failed to allocate return; } cairo_set_source(_context, new_pattern); @@ -442,7 +490,8 @@ Context2d::stroke(bool preserve) { if (state->globalAlpha < 1) { new_pattern = create_transparent_gradient(state->strokeGradient, state->globalAlpha); if (new_pattern == NULL) { - // failed to recognize gradient; Nan::ThrowError has already been called, so return from this fn. + Napi::Error::New(env, "Unexpected gradient type").ThrowAsJavaScriptException(); + // failed to recognize gradient return; } cairo_pattern_set_filter(new_pattern, state->patternQuality); @@ -668,74 +717,14 @@ Context2d::blur(cairo_surface_t *surface, int radius) { free(precalc); } -/* - * Initialize a new Context2d with the given canvas. - */ - -NAN_METHOD(Context2d::New) { - if (!info.IsConstructCall()) { - return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'"); - } - - if (!info[0]->IsObject()) - return Nan::ThrowTypeError("Canvas expected"); - Local obj = Nan::To(info[0]).ToLocalChecked(); - if (!Nan::New(Canvas::constructor)->HasInstance(obj)) - return Nan::ThrowTypeError("Canvas expected"); - Canvas *canvas = Nan::ObjectWrap::Unwrap(obj); - - bool isImageBackend = canvas->backend()->getName() == "image"; - if (isImageBackend) { - cairo_format_t format = ImageBackend::DEFAULT_FORMAT; - if (info[1]->IsObject()) { - Local ctxAttributes = Nan::To(info[1]).ToLocalChecked(); - - Local pixelFormat = Nan::Get(ctxAttributes, Nan::New("pixelFormat").ToLocalChecked()).ToLocalChecked(); - if (pixelFormat->IsString()) { - Nan::Utf8String utf8PixelFormat(pixelFormat); - if (!strcmp(*utf8PixelFormat, "RGBA32")) format = CAIRO_FORMAT_ARGB32; - else if (!strcmp(*utf8PixelFormat, "RGB24")) format = CAIRO_FORMAT_RGB24; - else if (!strcmp(*utf8PixelFormat, "A8")) format = CAIRO_FORMAT_A8; - else if (!strcmp(*utf8PixelFormat, "RGB16_565")) format = CAIRO_FORMAT_RGB16_565; - else if (!strcmp(*utf8PixelFormat, "A1")) format = CAIRO_FORMAT_A1; -#ifdef CAIRO_FORMAT_RGB30 - else if (!strcmp(utf8PixelFormat, "RGB30")) format = CAIRO_FORMAT_RGB30; -#endif - } - - // alpha: false forces use of RGB24 - Local alpha = Nan::Get(ctxAttributes, Nan::New("alpha").ToLocalChecked()).ToLocalChecked(); - if (alpha->IsBoolean() && !Nan::To(alpha).FromMaybe(false)) { - format = CAIRO_FORMAT_RGB24; - } - } - static_cast(canvas->backend())->setFormat(format); - } - - Context2d *context = new Context2d(canvas); - - context->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); -} - -/* - * Save some external modules as private references. - */ - -NAN_METHOD(Context2d::SaveExternalModules) { - _DOMMatrix.Reset(Nan::To(info[0]).ToLocalChecked()); - _parseFont.Reset(Nan::To(info[1]).ToLocalChecked()); -} - /* * Get format (string). */ -NAN_GETTER(Context2d::GetFormat) { - CHECK_RECEIVER(Context2d.GetFormat); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value +Context2d::GetFormat(const Napi::CallbackInfo& info) { std::string pixelFormatString; - switch (context->canvas()->backend()->getFormat()) { + switch (canvas()->backend()->getFormat()) { case CAIRO_FORMAT_ARGB32: pixelFormatString = "RGBA32"; break; case CAIRO_FORMAT_RGB24: pixelFormatString = "RGB24"; break; case CAIRO_FORMAT_A8: pixelFormatString = "A8"; break; @@ -744,27 +733,28 @@ NAN_GETTER(Context2d::GetFormat) { #ifdef CAIRO_FORMAT_RGB30 case CAIRO_FORMAT_RGB30: pixelFormatString = "RGB30"; break; #endif - default: return info.GetReturnValue().SetNull(); + default: return env.Null(); } - info.GetReturnValue().Set(Nan::New(pixelFormatString).ToLocalChecked()); + return Napi::String::New(env, pixelFormatString); } /* * Create a new page. */ -NAN_METHOD(Context2d::AddPage) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - if (context->canvas()->backend()->getName() != "pdf") { - return Nan::ThrowError("only PDF canvases support .addPage()"); +void +Context2d::AddPage(const Napi::CallbackInfo& info) { + if (canvas()->backend()->getName() != "pdf") { + Napi::Error::New(env, "only PDF canvases support .addPage()").ThrowAsJavaScriptException(); + return; } - cairo_show_page(context->context()); - int width = Nan::To(info[0]).FromMaybe(0); - int height = Nan::To(info[1]).FromMaybe(0); - if (width < 1) width = context->canvas()->getWidth(); - if (height < 1) height = context->canvas()->getHeight(); - cairo_pdf_surface_set_size(context->canvas()->surface(), width, height); - return; + cairo_show_page(context()); + Napi::Number zero = Napi::Number::New(env, 0); + int width = info[0].ToNumber().UnwrapOr(zero).Int32Value(); + int height = info[1].ToNumber().UnwrapOr(zero).Int32Value(); + if (width < 1) width = canvas()->getWidth(); + if (height < 1) height = canvas()->getHeight(); + cairo_pdf_surface_set_size(canvas()->surface(), width, height); } /* @@ -775,29 +765,37 @@ NAN_METHOD(Context2d::AddPage) { * */ -NAN_METHOD(Context2d::PutImageData) { - if (!info[0]->IsObject()) - return Nan::ThrowTypeError("ImageData expected"); - Local obj = Nan::To(info[0]).ToLocalChecked(); - if (!Nan::New(ImageData::constructor)->HasInstance(obj)) - return Nan::ThrowTypeError("ImageData expected"); +void +Context2d::PutImageData(const Napi::CallbackInfo& info) { + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "ImageData expected").ThrowAsJavaScriptException(); + return; + } + Napi::Object obj = info[0].As(); + InstanceData* data = env.GetInstanceData(); + if (!obj.InstanceOf(data->ImageDataCtor.Value()).UnwrapOr(false)) { + if (!env.IsExceptionPending()) { + Napi::TypeError::New(env, "ImageData expected").ThrowAsJavaScriptException(); + } + return; + } - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - ImageData *imageData = Nan::ObjectWrap::Unwrap(obj); + ImageData *imageData = ImageData::Unwrap(obj); + Napi::Number zero = Napi::Number::New(env, 0); uint8_t *src = imageData->data(); - uint8_t *dst = context->canvas()->data(); + uint8_t *dst = canvas()->data(); - int dstStride = context->canvas()->stride(); - int Bpp = dstStride / context->canvas()->getWidth(); + int dstStride = canvas()->stride(); + int Bpp = dstStride / canvas()->getWidth(); int srcStride = Bpp * imageData->width(); int sx = 0 , sy = 0 , sw = 0 , sh = 0 - , dx = Nan::To(info[1]).FromMaybe(0) - , dy = Nan::To(info[2]).FromMaybe(0) + , dx = info[1].ToNumber().UnwrapOr(zero).Int32Value() + , dy = info[2].ToNumber().UnwrapOr(zero).Int32Value() , rows , cols; @@ -809,10 +807,10 @@ NAN_METHOD(Context2d::PutImageData) { break; // imageData, dx, dy, sx, sy, sw, sh case 7: - sx = Nan::To(info[3]).FromMaybe(0); - sy = Nan::To(info[4]).FromMaybe(0); - sw = Nan::To(info[5]).FromMaybe(0); - sh = Nan::To(info[6]).FromMaybe(0); + sx = info[3].ToNumber().UnwrapOr(zero).Int32Value(); + sy = info[4].ToNumber().UnwrapOr(zero).Int32Value(); + sw = info[5].ToNumber().UnwrapOr(zero).Int32Value(); + sh = info[6].ToNumber().UnwrapOr(zero).Int32Value(); // fix up negative height, width if (sw < 0) sx += sw, sw = -sw; if (sh < 0) sy += sh, sh = -sh; @@ -827,7 +825,8 @@ NAN_METHOD(Context2d::PutImageData) { dy += sy; break; default: - return Nan::ThrowError("invalid arguments"); + Napi::Error::New(env, "invalid arguments").ThrowAsJavaScriptException(); + return; } // chop off outlying source data @@ -836,12 +835,12 @@ NAN_METHOD(Context2d::PutImageData) { // clamp width at canvas size // Need to wrap std::min calls using parens to prevent macro expansion on // windows. See http://stackoverflow.com/questions/5004858/stdmin-gives-error - cols = (std::min)(sw, context->canvas()->getWidth() - dx); - rows = (std::min)(sh, context->canvas()->getHeight() - dy); + cols = (std::min)(sw, canvas()->getWidth() - dx); + rows = (std::min)(sh, canvas()->getHeight() - dy); if (cols <= 0 || rows <= 0) return; - switch (context->canvas()->backend()->getFormat()) { + switch (canvas()->backend()->getFormat()) { case CAIRO_FORMAT_ARGB32: { src += sy * srcStride + sx * 4; dst += dstStride * dy + 4 * dx; @@ -922,7 +921,8 @@ NAN_METHOD(Context2d::PutImageData) { } case CAIRO_FORMAT_A1: { // TODO Should this be totally packed, or maintain a stride divisible by 4? - Nan::ThrowError("putImageData for CANVAS_FORMAT_A1 is not yet implemented"); + Napi::Error::New(env, "putImageData for CANVAS_FORMAT_A1 is not yet implemented").ThrowAsJavaScriptException(); + break; } case CAIRO_FORMAT_RGB16_565: { @@ -938,18 +938,19 @@ NAN_METHOD(Context2d::PutImageData) { #ifdef CAIRO_FORMAT_RGB30 case CAIRO_FORMAT_RGB30: { // TODO - Nan::ThrowError("putImageData for CANVAS_FORMAT_RGB30 is not yet implemented"); + Napi::Error::New(env, "putImageData for CANVAS_FORMAT_RGB30 is not yet implemented").ThrowAsJavaScriptException(); + break; } #endif default: { - Nan::ThrowError("Invalid pixel format or not an image canvas"); + Napi::Error::New(env, "Invalid pixel format or not an image canvas").ThrowAsJavaScriptException(); return; } } cairo_surface_mark_dirty_rectangle( - context->canvas()->surface() + canvas()->surface() , dx , dy , cols @@ -963,27 +964,36 @@ NAN_METHOD(Context2d::PutImageData) { * */ -NAN_METHOD(Context2d::GetImageData) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - Canvas *canvas = context->canvas(); +Napi::Value +Context2d::GetImageData(const Napi::CallbackInfo& info) { + Napi::Number zero = Napi::Number::New(env, 0); + Canvas *canvas = this->canvas(); - int sx = Nan::To(info[0]).FromMaybe(0); - int sy = Nan::To(info[1]).FromMaybe(0); - int sw = Nan::To(info[2]).FromMaybe(0); - int sh = Nan::To(info[3]).FromMaybe(0); + int sx = info[0].ToNumber().UnwrapOr(zero).Int32Value(); + int sy = info[1].ToNumber().UnwrapOr(zero).Int32Value(); + int sw = info[2].ToNumber().UnwrapOr(zero).Int32Value(); + int sh = info[3].ToNumber().UnwrapOr(zero).Int32Value(); - if (!sw) - return Nan::ThrowError("IndexSizeError: The source width is 0."); - if (!sh) - return Nan::ThrowError("IndexSizeError: The source height is 0."); + if (!sw) { + Napi::Error::New(env, "IndexSizeError: The source width is 0.").ThrowAsJavaScriptException(); + return env.Undefined(); + } + if (!sh) { + Napi::Error::New(env, "IndexSizeError: The source height is 0.").ThrowAsJavaScriptException(); + return env.Undefined(); + } int width = canvas->getWidth(); int height = canvas->getHeight(); - if (!width) - return Nan::ThrowTypeError("Canvas width is 0"); - if (!height) - return Nan::ThrowTypeError("Canvas height is 0"); + if (!width) { + Napi::TypeError::New(env, "Canvas width is 0").ThrowAsJavaScriptException(); + return env.Undefined(); + } + if (!height) { + Napi::TypeError::New(env, "Canvas height is 0").ThrowAsJavaScriptException(); + return env.Undefined(); + } // WebKit and Firefox have this behavior: // Flip the coordinates so the origin is top/left-most: @@ -1021,17 +1031,16 @@ NAN_METHOD(Context2d::GetImageData) { uint8_t *src = canvas->data(); - Local buffer = ArrayBuffer::New(Isolate::GetCurrent(), size); - Local dataArray; + Napi::ArrayBuffer buffer = Napi::ArrayBuffer::New(env, size); + Napi::TypedArray dataArray; if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565) { - dataArray = Uint16Array::New(buffer, 0, size >> 1); + dataArray = Napi::Uint16Array::New(env, size >> 1, buffer, 0); } else { - dataArray = Uint8ClampedArray::New(buffer, 0, size); + dataArray = Napi::Uint8Array::New(env, size, buffer, 0, napi_uint8_clamped_array); } - Nan::TypedArrayContents typedArrayContents(dataArray); - uint8_t* dst = *typedArrayContents; + uint8_t *dst = (uint8_t *)buffer.Data(); switch (canvas->backend()->getFormat()) { case CAIRO_FORMAT_ARGB32: { @@ -1097,7 +1106,8 @@ NAN_METHOD(Context2d::GetImageData) { } case CAIRO_FORMAT_A1: { // TODO Should this be totally packed, or maintain a stride divisible by 4? - Nan::ThrowError("getImageData for CANVAS_FORMAT_A1 is not yet implemented"); + Napi::Error::New(env, "getImageData for CANVAS_FORMAT_A1 is not yet implemented").ThrowAsJavaScriptException(); + break; } case CAIRO_FORMAT_RGB16_565: { @@ -1111,26 +1121,24 @@ NAN_METHOD(Context2d::GetImageData) { #ifdef CAIRO_FORMAT_RGB30 case CAIRO_FORMAT_RGB30: { // TODO - Nan::ThrowError("getImageData for CANVAS_FORMAT_RGB30 is not yet implemented"); + Napi::Error::New(env, "getImageData for CANVAS_FORMAT_RGB30 is not yet implemented").ThrowAsJavaScriptException(); + break; } #endif default: { // Unlikely - Nan::ThrowError("Invalid pixel format or not an image canvas"); - return; + Napi::Error::New(env, "Invalid pixel format or not an image canvas").ThrowAsJavaScriptException(); + return env.Null(); } } - const int argc = 3; - Local swHandle = Nan::New(sw); - Local shHandle = Nan::New(sh); - Local argv[argc] = { dataArray, swHandle, shHandle }; - - Local ctor = Nan::GetFunction(Nan::New(ImageData::constructor)).ToLocalChecked(); - Local instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked(); + Napi::Number swHandle = Napi::Number::New(env, sw); + Napi::Number shHandle = Napi::Number::New(env, sh); + Napi::Function ctor = env.GetInstanceData()->ImageDataCtor.Value(); + Napi::Maybe ret = ctor.New({ dataArray, swHandle, shHandle }); - info.GetReturnValue().Set(instance); + return ret.IsJust() ? ret.Unwrap() : env.Undefined(); } /** @@ -1138,40 +1146,37 @@ NAN_METHOD(Context2d::GetImageData) { * `ImageData` instance for dimensions. */ -NAN_METHOD(Context2d::CreateImageData){ - Isolate *iso = Isolate::GetCurrent(); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - Canvas *canvas = context->canvas(); +Napi::Value +Context2d::CreateImageData(const Napi::CallbackInfo& info){ + Canvas *canvas = this->canvas(); + Napi::Number zero = Napi::Number::New(env, 0); int32_t width, height; - if (info[0]->IsObject()) { - Local obj = Nan::To(info[0]).ToLocalChecked(); - width = Nan::To(Nan::Get(obj, Nan::New("width").ToLocalChecked()).ToLocalChecked()).FromMaybe(0); - height = Nan::To(Nan::Get(obj, Nan::New("height").ToLocalChecked()).ToLocalChecked()).FromMaybe(0); + if (info[0].IsObject()) { + Napi::Object obj = info[0].As(); + width = obj.Get("width").UnwrapOr(zero).ToNumber().UnwrapOr(zero).Int32Value(); + height = obj.Get("height").UnwrapOr(zero).ToNumber().UnwrapOr(zero).Int32Value(); } else { - width = Nan::To(info[0]).FromMaybe(0); - height = Nan::To(info[1]).FromMaybe(0); + width = info[0].ToNumber().UnwrapOr(zero).Int32Value(); + height = info[1].ToNumber().UnwrapOr(zero).Int32Value(); } int stride = canvas->stride(); double Bpp = static_cast(stride) / canvas->getWidth(); int nBytes = static_cast(Bpp * width * height + .5); - Local ab = ArrayBuffer::New(iso, nBytes); - Local arr; + Napi::ArrayBuffer ab = Napi::ArrayBuffer::New(env, nBytes); + Napi::Value arr; if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565) - arr = Uint16Array::New(ab, 0, nBytes / 2); + arr = Napi::Uint16Array::New(env, nBytes / 2, ab, 0); else - arr = Uint8ClampedArray::New(ab, 0, nBytes); + arr = Napi::Uint8Array::New(env, nBytes, ab, 0, napi_uint8_clamped_array); - const int argc = 3; - Local argv[argc] = { arr, Nan::New(width), Nan::New(height) }; + Napi::Function ctor = env.GetInstanceData()->ImageDataCtor.Value(); + Napi::Maybe ret = ctor.New({ arr, Napi::Number::New(env, width), Napi::Number::New(env, height) }); - Local ctor = Nan::GetFunction(Nan::New(ImageData::constructor)).ToLocalChecked(); - Local instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked(); - - info.GetReturnValue().Set(instance); + return ret.IsJust() ? ret.Unwrap() : env.Undefined(); } /* @@ -1197,13 +1202,19 @@ void decompose_matrix(cairo_matrix_t matrix, double *destination) { * */ -NAN_METHOD(Context2d::DrawImage) { +void +Context2d::DrawImage(const Napi::CallbackInfo& info) { int infoLen = info.Length(); - if (infoLen != 3 && infoLen != 5 && infoLen != 9) - return Nan::ThrowTypeError("Invalid arguments"); - if (!info[0]->IsObject()) - return Nan::ThrowTypeError("The first argument must be an object"); + if (infoLen != 3 && infoLen != 5 && infoLen != 9) { + Napi::TypeError::New(env, "Invalid arguments").ThrowAsJavaScriptException(); + return; + } + + if (!info[0].IsObject()) { + Napi::TypeError::New(env, "The first argument must be an object").ThrowAsJavaScriptException(); + return; + } double args[8]; if(!checkArgs(info, args, infoLen - 1, 1)) @@ -1222,32 +1233,35 @@ NAN_METHOD(Context2d::DrawImage) { cairo_surface_t *surface; - Local obj = Nan::To(info[0]).ToLocalChecked(); + Napi::Object obj = info[0].As(); // Image - if (Nan::New(Image::constructor)->HasInstance(obj)) { - Image *img = Nan::ObjectWrap::Unwrap(obj); + if (obj.InstanceOf(env.GetInstanceData()->ImageCtor.Value()).UnwrapOr(false)) { + Image *img = Image::Unwrap(obj); if (!img->isComplete()) { - return Nan::ThrowError("Image given has not completed loading"); + Napi::Error::New(env, "Image given has not completed loading").ThrowAsJavaScriptException(); + return; } source_w = sw = img->width; source_h = sh = img->height; surface = img->surface(); // Canvas - } else if (Nan::New(Canvas::constructor)->HasInstance(obj)) { - Canvas *canvas = Nan::ObjectWrap::Unwrap(obj); + } else if (obj.InstanceOf(env.GetInstanceData()->CanvasCtor.Value()).UnwrapOr(false)) { + Canvas *canvas = Canvas::Unwrap(obj); source_w = sw = canvas->getWidth(); source_h = sh = canvas->getHeight(); surface = canvas->surface(); // Invalid } else { - return Nan::ThrowTypeError("Image or Canvas expected"); + if (!env.IsExceptionPending()) { + Napi::TypeError::New(env, "Image or Canvas expected").ThrowAsJavaScriptException(); + } + return; } - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = context(); // Arguments switch (infoLen) { @@ -1286,7 +1300,7 @@ NAN_METHOD(Context2d::DrawImage) { cairo_matrix_t matrix; double transforms[6]; - cairo_get_matrix(context->context(), &matrix); + cairo_get_matrix(ctx, &matrix); decompose_matrix(matrix, transforms); // extract the scale value from the current transform so that we know how many pixels we // need for our extra canvas in the drawImage operation. @@ -1298,7 +1312,7 @@ NAN_METHOD(Context2d::DrawImage) { double fy = dh / sh * current_scale_y; // transforms[2] is scale on X bool needScale = dw != sw || dh != sh; bool needCut = sw != source_w || sh != source_h || sx < 0 || sy < 0; - bool sameCanvas = surface == context->canvas()->surface(); + bool sameCanvas = surface == canvas()->surface(); bool needsExtraSurface = sameCanvas || needCut || needScale; cairo_surface_t *surfTemp = NULL; cairo_t *ctxTemp = NULL; @@ -1346,23 +1360,23 @@ NAN_METHOD(Context2d::DrawImage) { translate_y = sy; } cairo_set_source_surface(ctxTemp, surface, -translate_x, -translate_y); - cairo_pattern_set_filter(cairo_get_source(ctxTemp), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST); + cairo_pattern_set_filter(cairo_get_source(ctxTemp), state->imageSmoothingEnabled ? state->patternQuality : CAIRO_FILTER_NEAREST); cairo_pattern_set_extend(cairo_get_source(ctxTemp), CAIRO_EXTEND_REFLECT); cairo_paint_with_alpha(ctxTemp, 1); surface = surfTemp; } // apply shadow if there is one - if (context->hasShadow()) { - if(context->state->shadowBlur) { + if (hasShadow()) { + if(state->shadowBlur) { // we need to create a new surface in order to blur - int pad = context->state->shadowBlur * 2; + int pad = state->shadowBlur * 2; cairo_surface_t *shadow_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dw + 2 * pad, dh + 2 * pad); cairo_t *shadow_context = cairo_create(shadow_surface); // mask and blur - context->setSourceRGBA(shadow_context, context->state->shadow); + setSourceRGBA(shadow_context, state->shadow); cairo_mask_surface(shadow_context, surface, pad, pad); - context->blur(shadow_surface, context->state->shadowBlur); + blur(shadow_surface, state->shadowBlur); // paint // @note: ShadowBlur looks different in each browser. This implementation matches chrome as close as possible. @@ -1370,17 +1384,17 @@ NAN_METHOD(Context2d::DrawImage) { // implementation, and its not immediately clear why an offset is necessary, but without it, the result // in chrome is different. cairo_set_source_surface(ctx, shadow_surface, - dx + context->state->shadowOffsetX - pad + 1.4, - dy + context->state->shadowOffsetY - pad + 1.4); + dx + state->shadowOffsetX - pad + 1.4, + dy + state->shadowOffsetY - pad + 1.4); cairo_paint(ctx); // cleanup cairo_destroy(shadow_context); cairo_surface_destroy(shadow_surface); } else { - context->setSourceRGBA(context->state->shadow); + setSourceRGBA(state->shadow); cairo_mask_surface(ctx, surface, - dx + (context->state->shadowOffsetX), - dy + (context->state->shadowOffsetY)); + dx + (state->shadowOffsetX), + dy + (state->shadowOffsetY)); } } @@ -1395,9 +1409,9 @@ NAN_METHOD(Context2d::DrawImage) { } // Paint cairo_set_source_surface(ctx, surface, scaled_dx + extra_dx, scaled_dy + extra_dy); - cairo_pattern_set_filter(cairo_get_source(ctx), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST); + cairo_pattern_set_filter(cairo_get_source(ctx), state->imageSmoothingEnabled ? state->patternQuality : CAIRO_FILTER_NEAREST); cairo_pattern_set_extend(cairo_get_source(ctx), CAIRO_EXTEND_NONE); - cairo_paint_with_alpha(ctx, context->state->globalAlpha); + cairo_paint_with_alpha(ctx, state->globalAlpha); cairo_restore(ctx); @@ -1411,22 +1425,21 @@ NAN_METHOD(Context2d::DrawImage) { * Get global alpha. */ -NAN_GETTER(Context2d::GetGlobalAlpha) { - CHECK_RECEIVER(Context2d.GetGlobalAlpha); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(context->state->globalAlpha)); +Napi::Value +Context2d::GetGlobalAlpha(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, state->globalAlpha); } /* * Set global alpha. */ -NAN_SETTER(Context2d::SetGlobalAlpha) { - CHECK_RECEIVER(Context2d.SetGlobalAlpha); - double n = Nan::To(value).FromMaybe(0); - if (n >= 0 && n <= 1) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->state->globalAlpha = n; +void +Context2d::SetGlobalAlpha(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Maybe numberValue = value.ToNumber(); + if (numberValue.IsJust()) { + double n = numberValue.Unwrap().DoubleValue(); + if (n >= 0 && n <= 1) state->globalAlpha = n; } } @@ -1434,10 +1447,9 @@ NAN_SETTER(Context2d::SetGlobalAlpha) { * Get global composite operation. */ -NAN_GETTER(Context2d::GetGlobalCompositeOperation) { - CHECK_RECEIVER(Context2d.GetGlobalCompositeOperation); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); +Napi::Value +Context2d::GetGlobalCompositeOperation(const Napi::CallbackInfo& info) { + cairo_t *ctx = context(); const char *op{}; switch (cairo_get_operator(ctx)) { @@ -1479,27 +1491,28 @@ NAN_GETTER(Context2d::GetGlobalCompositeOperation) { default: op = "source-over"; } - info.GetReturnValue().Set(Nan::New(op).ToLocalChecked()); + return Napi::String::New(env, op); } /* * Set pattern quality. */ -NAN_SETTER(Context2d::SetPatternQuality) { - CHECK_RECEIVER(Context2d.SetPatternQuality); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - Nan::Utf8String quality(Nan::To(value).ToLocalChecked()); - if (0 == strcmp("fast", *quality)) { - context->state->patternQuality = CAIRO_FILTER_FAST; - } else if (0 == strcmp("good", *quality)) { - context->state->patternQuality = CAIRO_FILTER_GOOD; - } else if (0 == strcmp("best", *quality)) { - context->state->patternQuality = CAIRO_FILTER_BEST; - } else if (0 == strcmp("nearest", *quality)) { - context->state->patternQuality = CAIRO_FILTER_NEAREST; - } else if (0 == strcmp("bilinear", *quality)) { - context->state->patternQuality = CAIRO_FILTER_BILINEAR; +void +Context2d::SetPatternQuality(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsString()) { + std::string quality = value.As().Utf8Value(); + if (quality == "fast") { + state->patternQuality = CAIRO_FILTER_FAST; + } else if (quality == "good") { + state->patternQuality = CAIRO_FILTER_GOOD; + } else if (quality == "best") { + state->patternQuality = CAIRO_FILTER_BEST; + } else if (quality == "nearest") { + state->patternQuality = CAIRO_FILTER_NEAREST; + } else if (quality == "bilinear") { + state->patternQuality = CAIRO_FILTER_BILINEAR; + } } } @@ -1507,148 +1520,146 @@ NAN_SETTER(Context2d::SetPatternQuality) { * Get pattern quality. */ -NAN_GETTER(Context2d::GetPatternQuality) { - CHECK_RECEIVER(Context2d.GetPatternQuality); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value +Context2d::GetPatternQuality(const Napi::CallbackInfo& info) { const char *quality; - switch (context->state->patternQuality) { + switch (state->patternQuality) { case CAIRO_FILTER_FAST: quality = "fast"; break; case CAIRO_FILTER_BEST: quality = "best"; break; case CAIRO_FILTER_NEAREST: quality = "nearest"; break; case CAIRO_FILTER_BILINEAR: quality = "bilinear"; break; default: quality = "good"; } - info.GetReturnValue().Set(Nan::New(quality).ToLocalChecked()); + return Napi::String::New(env, quality); } /* * Set ImageSmoothingEnabled value. */ -NAN_SETTER(Context2d::SetImageSmoothingEnabled) { - CHECK_RECEIVER(Context2d.SetImageSmoothingEnabled); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->state->imageSmoothingEnabled = Nan::To(value).FromMaybe(false); +void +Context2d::SetImageSmoothingEnabled(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Boolean boolValue; + if (value.ToBoolean().UnwrapTo(&boolValue)) state->imageSmoothingEnabled = boolValue.Value(); } /* * Get pattern quality. */ -NAN_GETTER(Context2d::GetImageSmoothingEnabled) { - CHECK_RECEIVER(Context2d.GetImageSmoothingEnabled); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(context->state->imageSmoothingEnabled)); +Napi::Value +Context2d::GetImageSmoothingEnabled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(env, state->imageSmoothingEnabled); } /* * Set global composite operation. */ -NAN_SETTER(Context2d::SetGlobalCompositeOperation) { - CHECK_RECEIVER(Context2d.SetGlobalCompositeOperation); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); - Nan::Utf8String opStr(Nan::To(value).ToLocalChecked()); // Unlike CSS colors, this *is* case-sensitive - const std::map blendmodes = { - // composite modes: - {"clear", CAIRO_OPERATOR_CLEAR}, - {"copy", CAIRO_OPERATOR_SOURCE}, - {"destination", CAIRO_OPERATOR_DEST}, // this seems to have been omitted from the spec - {"source-over", CAIRO_OPERATOR_OVER}, - {"destination-over", CAIRO_OPERATOR_DEST_OVER}, - {"source-in", CAIRO_OPERATOR_IN}, - {"destination-in", CAIRO_OPERATOR_DEST_IN}, - {"source-out", CAIRO_OPERATOR_OUT}, - {"destination-out", CAIRO_OPERATOR_DEST_OUT}, - {"source-atop", CAIRO_OPERATOR_ATOP}, - {"destination-atop", CAIRO_OPERATOR_DEST_ATOP}, - {"xor", CAIRO_OPERATOR_XOR}, - {"lighter", CAIRO_OPERATOR_ADD}, - // blend modes: - {"normal", CAIRO_OPERATOR_OVER}, - {"multiply", CAIRO_OPERATOR_MULTIPLY}, - {"screen", CAIRO_OPERATOR_SCREEN}, - {"overlay", CAIRO_OPERATOR_OVERLAY}, - {"darken", CAIRO_OPERATOR_DARKEN}, - {"lighten", CAIRO_OPERATOR_LIGHTEN}, - {"color-dodge", CAIRO_OPERATOR_COLOR_DODGE}, - {"color-burn", CAIRO_OPERATOR_COLOR_BURN}, - {"hard-light", CAIRO_OPERATOR_HARD_LIGHT}, - {"soft-light", CAIRO_OPERATOR_SOFT_LIGHT}, - {"difference", CAIRO_OPERATOR_DIFFERENCE}, - {"exclusion", CAIRO_OPERATOR_EXCLUSION}, - {"hue", CAIRO_OPERATOR_HSL_HUE}, - {"saturation", CAIRO_OPERATOR_HSL_SATURATION}, - {"color", CAIRO_OPERATOR_HSL_COLOR}, - {"luminosity", CAIRO_OPERATOR_HSL_LUMINOSITY}, - // non-standard: - {"saturate", CAIRO_OPERATOR_SATURATE} - }; - auto op = blendmodes.find(*opStr); - if (op != blendmodes.end()) cairo_set_operator(ctx, op->second); +void +Context2d::SetGlobalCompositeOperation(const Napi::CallbackInfo& info, const Napi::Value& value) { + cairo_t *ctx = this->context(); + Napi::String opStr; + if (value.ToString().UnwrapTo(&opStr)) { // Unlike CSS colors, this *is* case-sensitive + const std::map blendmodes = { + // composite modes: + {"clear", CAIRO_OPERATOR_CLEAR}, + {"copy", CAIRO_OPERATOR_SOURCE}, + {"destination", CAIRO_OPERATOR_DEST}, // this seems to have been omitted from the spec + {"source-over", CAIRO_OPERATOR_OVER}, + {"destination-over", CAIRO_OPERATOR_DEST_OVER}, + {"source-in", CAIRO_OPERATOR_IN}, + {"destination-in", CAIRO_OPERATOR_DEST_IN}, + {"source-out", CAIRO_OPERATOR_OUT}, + {"destination-out", CAIRO_OPERATOR_DEST_OUT}, + {"source-atop", CAIRO_OPERATOR_ATOP}, + {"destination-atop", CAIRO_OPERATOR_DEST_ATOP}, + {"xor", CAIRO_OPERATOR_XOR}, + {"lighter", CAIRO_OPERATOR_ADD}, + // blend modes: + {"normal", CAIRO_OPERATOR_OVER}, + {"multiply", CAIRO_OPERATOR_MULTIPLY}, + {"screen", CAIRO_OPERATOR_SCREEN}, + {"overlay", CAIRO_OPERATOR_OVERLAY}, + {"darken", CAIRO_OPERATOR_DARKEN}, + {"lighten", CAIRO_OPERATOR_LIGHTEN}, + {"color-dodge", CAIRO_OPERATOR_COLOR_DODGE}, + {"color-burn", CAIRO_OPERATOR_COLOR_BURN}, + {"hard-light", CAIRO_OPERATOR_HARD_LIGHT}, + {"soft-light", CAIRO_OPERATOR_SOFT_LIGHT}, + {"difference", CAIRO_OPERATOR_DIFFERENCE}, + {"exclusion", CAIRO_OPERATOR_EXCLUSION}, + {"hue", CAIRO_OPERATOR_HSL_HUE}, + {"saturation", CAIRO_OPERATOR_HSL_SATURATION}, + {"color", CAIRO_OPERATOR_HSL_COLOR}, + {"luminosity", CAIRO_OPERATOR_HSL_LUMINOSITY}, + // non-standard: + {"saturate", CAIRO_OPERATOR_SATURATE} + }; + auto op = blendmodes.find(opStr.Utf8Value()); + if (op != blendmodes.end()) cairo_set_operator(ctx, op->second); + } } /* * Get shadow offset x. */ -NAN_GETTER(Context2d::GetShadowOffsetX) { - CHECK_RECEIVER(Context2d.GetShadowOffsetX); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(context->state->shadowOffsetX)); +Napi::Value +Context2d::GetShadowOffsetX(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, state->shadowOffsetX); } /* * Set shadow offset x. */ -NAN_SETTER(Context2d::SetShadowOffsetX) { - CHECK_RECEIVER(Context2d.SetShadowOffsetX); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->state->shadowOffsetX = Nan::To(value).FromMaybe(0); +void +Context2d::SetShadowOffsetX(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Number numberValue; + if (value.ToNumber().UnwrapTo(&numberValue)) state->shadowOffsetX = numberValue.DoubleValue(); } /* * Get shadow offset y. */ -NAN_GETTER(Context2d::GetShadowOffsetY) { - CHECK_RECEIVER(Context2d.GetShadowOffsetY); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(context->state->shadowOffsetY)); +Napi::Value +Context2d::GetShadowOffsetY(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, state->shadowOffsetY); } /* * Set shadow offset y. */ -NAN_SETTER(Context2d::SetShadowOffsetY) { - CHECK_RECEIVER(Context2d.SetShadowOffsetY); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->state->shadowOffsetY = Nan::To(value).FromMaybe(0); +void +Context2d::SetShadowOffsetY(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Number numberValue; + if (value.ToNumber().UnwrapTo(&numberValue)) state->shadowOffsetY = numberValue.DoubleValue(); } /* * Get shadow blur. */ -NAN_GETTER(Context2d::GetShadowBlur) { - CHECK_RECEIVER(Context2d.GetShadowBlur); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(context->state->shadowBlur)); +Napi::Value +Context2d::GetShadowBlur(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, state->shadowBlur); } /* * Set shadow blur. */ -NAN_SETTER(Context2d::SetShadowBlur) { - CHECK_RECEIVER(Context2d.SetShadowBlur); - int n = Nan::To(value).FromMaybe(0); - if (n >= 0) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->state->shadowBlur = n; +void +Context2d::SetShadowBlur(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Number n; + if (value.ToNumber().UnwrapTo(&n)) { + double v = n.DoubleValue(); + if (v >= 0 && v <= std::numeric_limitsshadowBlur)>::max()) { + state->shadowBlur = v; + } } } @@ -1656,73 +1667,76 @@ NAN_SETTER(Context2d::SetShadowBlur) { * Get current antialiasing setting. */ -NAN_GETTER(Context2d::GetAntiAlias) { - CHECK_RECEIVER(Context2d.GetAntiAlias); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value +Context2d::GetAntiAlias(const Napi::CallbackInfo& info) { const char *aa; - switch (cairo_get_antialias(context->context())) { + switch (cairo_get_antialias(context())) { case CAIRO_ANTIALIAS_NONE: aa = "none"; break; case CAIRO_ANTIALIAS_GRAY: aa = "gray"; break; case CAIRO_ANTIALIAS_SUBPIXEL: aa = "subpixel"; break; default: aa = "default"; } - info.GetReturnValue().Set(Nan::New(aa).ToLocalChecked()); + return Napi::String::New(env, aa); } /* * Set antialiasing. */ -NAN_SETTER(Context2d::SetAntiAlias) { - CHECK_RECEIVER(Context2d.SetAntiAlias); - Nan::Utf8String str(Nan::To(value).ToLocalChecked()); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); - cairo_antialias_t a; - if (0 == strcmp("none", *str)) { - a = CAIRO_ANTIALIAS_NONE; - } else if (0 == strcmp("default", *str)) { - a = CAIRO_ANTIALIAS_DEFAULT; - } else if (0 == strcmp("gray", *str)) { - a = CAIRO_ANTIALIAS_GRAY; - } else if (0 == strcmp("subpixel", *str)) { - a = CAIRO_ANTIALIAS_SUBPIXEL; - } else { - a = cairo_get_antialias(ctx); +void +Context2d::SetAntiAlias(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::String stringValue; + + if (value.ToString().UnwrapTo(&stringValue)) { + std::string str = stringValue.Utf8Value(); + cairo_t *ctx = context(); + cairo_antialias_t a; + if (str == "none") { + a = CAIRO_ANTIALIAS_NONE; + } else if (str == "default") { + a = CAIRO_ANTIALIAS_DEFAULT; + } else if (str == "gray") { + a = CAIRO_ANTIALIAS_GRAY; + } else if (str == "subpixel") { + a = CAIRO_ANTIALIAS_SUBPIXEL; + } else { + a = cairo_get_antialias(ctx); + } + cairo_set_antialias(ctx, a); } - cairo_set_antialias(ctx, a); } /* * Get text drawing mode. */ -NAN_GETTER(Context2d::GetTextDrawingMode) { - CHECK_RECEIVER(Context2d.GetTextDrawingMode); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value +Context2d::GetTextDrawingMode(const Napi::CallbackInfo& info) { const char *mode; - if (context->state->textDrawingMode == TEXT_DRAW_PATHS) { + if (state->textDrawingMode == TEXT_DRAW_PATHS) { mode = "path"; - } else if (context->state->textDrawingMode == TEXT_DRAW_GLYPHS) { + } else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) { mode = "glyph"; } else { mode = "unknown"; } - info.GetReturnValue().Set(Nan::New(mode).ToLocalChecked()); + return Napi::String::New(env, mode); } /* * Set text drawing mode. */ -NAN_SETTER(Context2d::SetTextDrawingMode) { - CHECK_RECEIVER(Context2d.SetTextDrawingMode); - Nan::Utf8String str(Nan::To(value).ToLocalChecked()); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - if (0 == strcmp("path", *str)) { - context->state->textDrawingMode = TEXT_DRAW_PATHS; - } else if (0 == strcmp("glyph", *str)) { - context->state->textDrawingMode = TEXT_DRAW_GLYPHS; +void +Context2d::SetTextDrawingMode(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::String stringValue; + if (value.ToString().UnwrapTo(&stringValue)) { + std::string str = stringValue.Utf8Value(); + if (str == "path") { + state->textDrawingMode = TEXT_DRAW_PATHS; + } else if (str == "glyph") { + state->textDrawingMode = TEXT_DRAW_GLYPHS; + } } } @@ -1730,79 +1744,77 @@ NAN_SETTER(Context2d::SetTextDrawingMode) { * Get filter. */ -NAN_GETTER(Context2d::GetQuality) { - CHECK_RECEIVER(Context2d.GetQuality); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value +Context2d::GetQuality(const Napi::CallbackInfo& info) { const char *filter; - switch (cairo_pattern_get_filter(cairo_get_source(context->context()))) { + switch (cairo_pattern_get_filter(cairo_get_source(context()))) { case CAIRO_FILTER_FAST: filter = "fast"; break; case CAIRO_FILTER_BEST: filter = "best"; break; case CAIRO_FILTER_NEAREST: filter = "nearest"; break; case CAIRO_FILTER_BILINEAR: filter = "bilinear"; break; default: filter = "good"; } - info.GetReturnValue().Set(Nan::New(filter).ToLocalChecked()); + return Napi::String::New(env, filter); } /* * Set filter. */ -NAN_SETTER(Context2d::SetQuality) { - CHECK_RECEIVER(Context2d.SetQuality); - Nan::Utf8String str(Nan::To(value).ToLocalChecked()); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_filter_t filter; - if (0 == strcmp("fast", *str)) { - filter = CAIRO_FILTER_FAST; - } else if (0 == strcmp("best", *str)) { - filter = CAIRO_FILTER_BEST; - } else if (0 == strcmp("nearest", *str)) { - filter = CAIRO_FILTER_NEAREST; - } else if (0 == strcmp("bilinear", *str)) { - filter = CAIRO_FILTER_BILINEAR; - } else { - filter = CAIRO_FILTER_GOOD; +void +Context2d::SetQuality(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::String stringValue; + if (value.ToString().UnwrapTo(&stringValue)) { + std::string str = stringValue.Utf8Value(); + cairo_filter_t filter; + if (str == "fast") { + filter = CAIRO_FILTER_FAST; + } else if (str == "best") { + filter = CAIRO_FILTER_BEST; + } else if (str == "nearest") { + filter = CAIRO_FILTER_NEAREST; + } else if (str == "bilinear") { + filter = CAIRO_FILTER_BILINEAR; + } else { + filter = CAIRO_FILTER_GOOD; + } + cairo_pattern_set_filter(cairo_get_source(context()), filter); } - cairo_pattern_set_filter(cairo_get_source(context->context()), filter); } /* * Helper for get current transform matrix */ -Local -get_current_transform(Context2d *context) { - Isolate *iso = Isolate::GetCurrent(); - - Local arr = Float64Array::New(ArrayBuffer::New(iso, 48), 0, 6); - Nan::TypedArrayContents dest(arr); +Napi::Value +Context2d::get_current_transform() { + Napi::Float64Array arr = Napi::Float64Array::New(env, 6); + double *dest = arr.Data(); cairo_matrix_t matrix; - cairo_get_matrix(context->context(), &matrix); - (*dest)[0] = matrix.xx; - (*dest)[1] = matrix.yx; - (*dest)[2] = matrix.xy; - (*dest)[3] = matrix.yy; - (*dest)[4] = matrix.x0; - (*dest)[5] = matrix.y0; - - const int argc = 1; - Local argv[argc] = { arr }; - return Nan::NewInstance(context->_DOMMatrix.Get(iso), argc, argv).ToLocalChecked(); + cairo_get_matrix(context(), &matrix); + dest[0] = matrix.xx; + dest[1] = matrix.yx; + dest[2] = matrix.xy; + dest[3] = matrix.yy; + dest[4] = matrix.x0; + dest[5] = matrix.y0; + Napi::Maybe ret = env.GetInstanceData()->DOMMatrixCtor.Value().New({ arr }); + return ret.IsJust() ? ret.Unwrap() : env.Undefined(); } /* * Helper for get/set transform. */ -void parse_matrix_from_object(cairo_matrix_t &matrix, Local mat) { +void parse_matrix_from_object(cairo_matrix_t &matrix, Napi::Object mat) { + Napi::Value zero = Napi::Number::New(mat.Env(), 0); cairo_matrix_init(&matrix, - Nan::To(Nan::Get(mat, Nan::New("a").ToLocalChecked()).ToLocalChecked()).FromMaybe(0), - Nan::To(Nan::Get(mat, Nan::New("b").ToLocalChecked()).ToLocalChecked()).FromMaybe(0), - Nan::To(Nan::Get(mat, Nan::New("c").ToLocalChecked()).ToLocalChecked()).FromMaybe(0), - Nan::To(Nan::Get(mat, Nan::New("d").ToLocalChecked()).ToLocalChecked()).FromMaybe(0), - Nan::To(Nan::Get(mat, Nan::New("e").ToLocalChecked()).ToLocalChecked()).FromMaybe(0), - Nan::To(Nan::Get(mat, Nan::New("f").ToLocalChecked()).ToLocalChecked()).FromMaybe(0) + mat.Get("a").UnwrapOr(zero).As().DoubleValue(), + mat.Get("b").UnwrapOr(zero).As().DoubleValue(), + mat.Get("c").UnwrapOr(zero).As().DoubleValue(), + mat.Get("d").UnwrapOr(zero).As().DoubleValue(), + mat.Get("e").UnwrapOr(zero).As().DoubleValue(), + mat.Get("f").UnwrapOr(zero).As().DoubleValue() ); } @@ -1811,78 +1823,70 @@ void parse_matrix_from_object(cairo_matrix_t &matrix, Local mat) { * Get current transform. */ -NAN_GETTER(Context2d::GetCurrentTransform) { - CHECK_RECEIVER(Context2d.GetCurrentTransform); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - Local instance = get_current_transform(context); - - info.GetReturnValue().Set(instance); +Napi::Value +Context2d::GetCurrentTransform(const Napi::CallbackInfo& info) { + return get_current_transform(); } /* * Set current transform. */ -NAN_SETTER(Context2d::SetCurrentTransform) { - CHECK_RECEIVER(Context2d.SetCurrentTransform); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - Local ctx = Nan::GetCurrentContext(); - Local mat = Nan::To(value).ToLocalChecked(); +void +Context2d::SetCurrentTransform(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Object mat; -#if NODE_MAJOR_VERSION >= 8 - if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) { - return Nan::ThrowTypeError("Expected DOMMatrix"); - } -#endif + if (value.ToObject().UnwrapTo(&mat)) { + if (!mat.InstanceOf(env.GetInstanceData()->DOMMatrixCtor.Value()).UnwrapOr(false)) { + if (!env.IsExceptionPending()) { + Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException(); + } + return; + } - cairo_matrix_t matrix; - parse_matrix_from_object(matrix, mat); + cairo_matrix_t matrix; + parse_matrix_from_object(matrix, mat); - cairo_transform(context->context(), &matrix); + cairo_transform(context(), &matrix); + } } /* * Get current fill style. */ -NAN_GETTER(Context2d::GetFillStyle) { - CHECK_RECEIVER(Context2d.GetFillStyle); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - Isolate *iso = Isolate::GetCurrent(); - Local style; +Napi::Value +Context2d::GetFillStyle(const Napi::CallbackInfo& info) { + Napi::Value style; - if (context->_fillStyle.IsEmpty()) - style = context->_getFillColor(); + if (_fillStyle.IsEmpty()) + style = _getFillColor(); else - style = context->_fillStyle.Get(iso); + style = _fillStyle.Value(); - info.GetReturnValue().Set(style); + return style; } /* * Set current fill style. */ -NAN_SETTER(Context2d::SetFillStyle) { - CHECK_RECEIVER(Context2d.SetFillStyle); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - - if (value->IsString()) { - MaybeLocal mstr = Nan::To(value); - if (mstr.IsEmpty()) return; - Local str = mstr.ToLocalChecked(); - context->_fillStyle.Reset(); - context->_setFillColor(str); - } else if (value->IsObject()) { - Local obj = Nan::To(value).ToLocalChecked(); - if (Nan::New(Gradient::constructor)->HasInstance(obj)) { - context->_fillStyle.Reset(value); - Gradient *grad = Nan::ObjectWrap::Unwrap(obj); - context->state->fillGradient = grad->pattern(); - } else if (Nan::New(Pattern::constructor)->HasInstance(obj)) { - context->_fillStyle.Reset(value); - Pattern *pattern = Nan::ObjectWrap::Unwrap(obj); - context->state->fillPattern = pattern->pattern(); +void +Context2d::SetFillStyle(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsString()) { + _fillStyle.Reset(); + _setFillColor(value.As()); + } else if (value.IsObject()) { + InstanceData *data = env.GetInstanceData(); + Napi::Object obj = value.As(); + if (obj.InstanceOf(data->CanvasGradientCtor.Value()).UnwrapOr(false)) { + _fillStyle.Reset(obj); + Gradient *grad = Gradient::Unwrap(obj); + state->fillGradient = grad->pattern(); + } else if (obj.InstanceOf(data->CanvasPatternCtor.Value()).UnwrapOr(false)) { + _fillStyle.Reset(obj); + Pattern *pattern = Pattern::Unwrap(obj); + state->fillPattern = pattern->pattern(); } } } @@ -1891,43 +1895,38 @@ NAN_SETTER(Context2d::SetFillStyle) { * Get current stroke style. */ -NAN_GETTER(Context2d::GetStrokeStyle) { - CHECK_RECEIVER(Context2d.GetStrokeStyle); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - Local style; +Napi::Value +Context2d::GetStrokeStyle(const Napi::CallbackInfo& info) { + Napi::Value style; - if (context->_strokeStyle.IsEmpty()) - style = context->_getStrokeColor(); + if (_strokeStyle.IsEmpty()) + style = _getStrokeColor(); else - style = context->_strokeStyle.Get(Isolate::GetCurrent()); + style = _strokeStyle.Value(); - info.GetReturnValue().Set(style); + return style; } /* * Set current stroke style. */ -NAN_SETTER(Context2d::SetStrokeStyle) { - CHECK_RECEIVER(Context2d.SetStrokeStyle); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - - if (value->IsString()) { - MaybeLocal mstr = Nan::To(value); - if (mstr.IsEmpty()) return; - Local str = mstr.ToLocalChecked(); - context->_strokeStyle.Reset(); - context->_setStrokeColor(str); - } else if (value->IsObject()) { - Local obj = Nan::To(value).ToLocalChecked(); - if (Nan::New(Gradient::constructor)->HasInstance(obj)) { - context->_strokeStyle.Reset(value); - Gradient *grad = Nan::ObjectWrap::Unwrap(obj); - context->state->strokeGradient = grad->pattern(); - } else if (Nan::New(Pattern::constructor)->HasInstance(obj)) { - context->_strokeStyle.Reset(value); - Pattern *pattern = Nan::ObjectWrap::Unwrap(obj); - context->state->strokePattern = pattern->pattern(); +void +Context2d::SetStrokeStyle(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsString()) { + _strokeStyle.Reset(); + _setStrokeColor(value.As()); + } else if (value.IsObject()) { + InstanceData *data = env.GetInstanceData(); + Napi::Object obj = value.As(); + if (obj.InstanceOf(data->CanvasGradientCtor.Value()).UnwrapOr(false)) { + _strokeStyle.Reset(obj); + Gradient *grad = Gradient::Unwrap(obj); + state->strokeGradient = grad->pattern(); + } else if (obj.InstanceOf(data->CanvasPatternCtor.Value()).UnwrapOr(false)) { + _strokeStyle.Reset(value); + Pattern *pattern = Pattern::Unwrap(obj); + state->strokePattern = pattern->pattern(); } } } @@ -1936,22 +1935,21 @@ NAN_SETTER(Context2d::SetStrokeStyle) { * Get miter limit. */ -NAN_GETTER(Context2d::GetMiterLimit) { - CHECK_RECEIVER(Context2d.GetMiterLimit); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(cairo_get_miter_limit(context->context()))); +Napi::Value +Context2d::GetMiterLimit(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, cairo_get_miter_limit(context())); } /* * Set miter limit. */ -NAN_SETTER(Context2d::SetMiterLimit) { - CHECK_RECEIVER(Context2d.SetMiterLimit); - double n = Nan::To(value).FromMaybe(0); - if (n > 0) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_set_miter_limit(context->context(), n); +void +Context2d::SetMiterLimit(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Maybe numberValue = value.ToNumber(); + if (numberValue.IsJust()) { + double n = numberValue.Unwrap().DoubleValue(); + if (n > 0) cairo_set_miter_limit(context(), n); } } @@ -1959,22 +1957,23 @@ NAN_SETTER(Context2d::SetMiterLimit) { * Get line width. */ -NAN_GETTER(Context2d::GetLineWidth) { - CHECK_RECEIVER(Context2d.GetLineWidth); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(cairo_get_line_width(context->context()))); +Napi::Value +Context2d::GetLineWidth(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, cairo_get_line_width(context())); } /* * Set line width. */ -NAN_SETTER(Context2d::SetLineWidth) { - CHECK_RECEIVER(Context2d.SetLineWidth); - double n = Nan::To(value).FromMaybe(0); - if (n > 0 && n != std::numeric_limits::infinity()) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_set_line_width(context->context(), n); +void +Context2d::SetLineWidth(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Maybe numberValue = value.ToNumber(); + if (numberValue.IsJust()) { + double n = numberValue.Unwrap().DoubleValue(); + if (n > 0 && n != std::numeric_limits::infinity()) { + cairo_set_line_width(context(), n); + } } } @@ -1982,33 +1981,35 @@ NAN_SETTER(Context2d::SetLineWidth) { * Get line join. */ -NAN_GETTER(Context2d::GetLineJoin) { - CHECK_RECEIVER(Context2d.GetLineJoin); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value +Context2d::GetLineJoin(const Napi::CallbackInfo& info) { const char *join; - switch (cairo_get_line_join(context->context())) { + switch (cairo_get_line_join(context())) { case CAIRO_LINE_JOIN_BEVEL: join = "bevel"; break; case CAIRO_LINE_JOIN_ROUND: join = "round"; break; default: join = "miter"; } - info.GetReturnValue().Set(Nan::New(join).ToLocalChecked()); + return Napi::String::New(env, join); } /* * Set line join. */ -NAN_SETTER(Context2d::SetLineJoin) { - CHECK_RECEIVER(Context2d.SetLineJoin); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); - Nan::Utf8String type(Nan::To(value).ToLocalChecked()); - if (0 == strcmp("round", *type)) { - cairo_set_line_join(ctx, CAIRO_LINE_JOIN_ROUND); - } else if (0 == strcmp("bevel", *type)) { - cairo_set_line_join(ctx, CAIRO_LINE_JOIN_BEVEL); - } else { - cairo_set_line_join(ctx, CAIRO_LINE_JOIN_MITER); +void +Context2d::SetLineJoin(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Maybe stringValue = value.ToString(); + cairo_t *ctx = context(); + + if (stringValue.IsJust()) { + std::string type = stringValue.Unwrap().Utf8Value(); + if (type == "round") { + cairo_set_line_join(ctx, CAIRO_LINE_JOIN_ROUND); + } else if (type == "bevel") { + cairo_set_line_join(ctx, CAIRO_LINE_JOIN_BEVEL); + } else { + cairo_set_line_join(ctx, CAIRO_LINE_JOIN_MITER); + } } } @@ -2016,33 +2017,35 @@ NAN_SETTER(Context2d::SetLineJoin) { * Get line cap. */ -NAN_GETTER(Context2d::GetLineCap) { - CHECK_RECEIVER(Context2d.GetLineCap); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value +Context2d::GetLineCap(const Napi::CallbackInfo& info) { const char *cap; - switch (cairo_get_line_cap(context->context())) { + switch (cairo_get_line_cap(context())) { case CAIRO_LINE_CAP_ROUND: cap = "round"; break; case CAIRO_LINE_CAP_SQUARE: cap = "square"; break; default: cap = "butt"; } - info.GetReturnValue().Set(Nan::New(cap).ToLocalChecked()); + return Napi::String::New(env, cap); } /* * Set line cap. */ -NAN_SETTER(Context2d::SetLineCap) { - CHECK_RECEIVER(Context2d.SetLineCap); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); - Nan::Utf8String type(Nan::To(value).ToLocalChecked()); - if (0 == strcmp("round", *type)) { - cairo_set_line_cap(ctx, CAIRO_LINE_CAP_ROUND); - } else if (0 == strcmp("square", *type)) { - cairo_set_line_cap(ctx, CAIRO_LINE_CAP_SQUARE); - } else { - cairo_set_line_cap(ctx, CAIRO_LINE_CAP_BUTT); +void +Context2d::SetLineCap(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Maybe stringValue = value.ToString(); + cairo_t *ctx = context(); + + if (stringValue.IsJust()) { + std::string type = stringValue.Unwrap().Utf8Value(); + if (type == "round") { + cairo_set_line_cap(ctx, CAIRO_LINE_CAP_ROUND); + } else if (type == "square") { + cairo_set_line_cap(ctx, CAIRO_LINE_CAP_SQUARE); + } else { + cairo_set_line_cap(ctx, CAIRO_LINE_CAP_BUTT); + } } } @@ -2050,31 +2053,30 @@ NAN_SETTER(Context2d::SetLineCap) { * Check if the given point is within the current path. */ -NAN_METHOD(Context2d::IsPointInPath) { - if (info[0]->IsNumber() && info[1]->IsNumber()) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); - double x = Nan::To(info[0]).FromMaybe(0) - , y = Nan::To(info[1]).FromMaybe(0); - context->setFillRule(info[2]); - info.GetReturnValue().Set(Nan::New(cairo_in_fill(ctx, x, y) || cairo_in_stroke(ctx, x, y))); - return; +Napi::Value +Context2d::IsPointInPath(const Napi::CallbackInfo& info) { + if (info[0].IsNumber() && info[1].IsNumber()) { + cairo_t *ctx = context(); + double x = info[0].As(), y = info[1].As(); + setFillRule(info[2]); + return Napi::Boolean::New(env, cairo_in_fill(ctx, x, y) || cairo_in_stroke(ctx, x, y)); } - info.GetReturnValue().Set(Nan::False()); + return Napi::Boolean::New(env, false); } /* * Set shadow color. */ -NAN_SETTER(Context2d::SetShadowColor) { - CHECK_RECEIVER(Context2d.SetShadowColor); +void +Context2d::SetShadowColor(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Maybe stringValue = value.ToString(); short ok; - Nan::Utf8String str(Nan::To(value).ToLocalChecked()); - uint32_t rgba = rgba_from_string(*str, &ok); - if (ok) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->state->shadow = rgba_create(rgba); + + if (stringValue.IsJust()) { + std::string str = stringValue.Unwrap().Utf8Value(); + uint32_t rgba = rgba_from_string(str.c_str(), &ok); + if (ok) state->shadow = rgba_create(rgba); } } @@ -2082,45 +2084,54 @@ NAN_SETTER(Context2d::SetShadowColor) { * Get shadow color. */ -NAN_GETTER(Context2d::GetShadowColor) { - CHECK_RECEIVER(Context2d.GetShadowColor); +Napi::Value +Context2d::GetShadowColor(const Napi::CallbackInfo& info) { char buf[64]; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - rgba_to_string(context->state->shadow, buf, sizeof(buf)); - info.GetReturnValue().Set(Nan::New(buf).ToLocalChecked()); + rgba_to_string(state->shadow, buf, sizeof(buf)); + return Napi::String::New(env, buf); } /* * Set fill color, used internally for fillStyle= */ -void Context2d::_setFillColor(Local arg) { +void +Context2d::_setFillColor(Napi::Value arg) { + Napi::Maybe stringValue = arg.ToString(); short ok; - Nan::Utf8String str(arg); - uint32_t rgba = rgba_from_string(*str, &ok); - if (!ok) return; - state->fillPattern = state->fillGradient = NULL; - state->fill = rgba_create(rgba); + + if (stringValue.IsJust()) { + Napi::String str = stringValue.Unwrap(); + char buf[128] = {0}; + napi_status status = napi_get_value_string_utf8(env, str, buf, sizeof(buf) - 1, nullptr); + if (status != napi_ok) return; + uint32_t rgba = rgba_from_string(buf, &ok); + if (!ok) return; + state->fillPattern = state->fillGradient = NULL; + state->fill = rgba_create(rgba); + } } /* * Get fill color. */ -Local Context2d::_getFillColor() { +Napi::Value +Context2d::_getFillColor() { char buf[64]; rgba_to_string(state->fill, buf, sizeof(buf)); - return Nan::New(buf).ToLocalChecked(); + return Napi::String::New(env, buf); } /* * Set stroke color, used internally for strokeStyle= */ -void Context2d::_setStrokeColor(Local arg) { +void +Context2d::_setStrokeColor(Napi::Value arg) { short ok; - Nan::Utf8String str(arg); - uint32_t rgba = rgba_from_string(*str, &ok); + std::string str = arg.As(); + uint32_t rgba = rgba_from_string(str.c_str(), &ok); if (!ok) return; state->strokePattern = state->strokeGradient = NULL; state->stroke = rgba_create(rgba); @@ -2130,59 +2141,46 @@ void Context2d::_setStrokeColor(Local arg) { * Get stroke color. */ -Local Context2d::_getStrokeColor() { +Napi::Value +Context2d::_getStrokeColor() { char buf[64]; rgba_to_string(state->stroke, buf, sizeof(buf)); - return Nan::New(buf).ToLocalChecked(); + return Napi::String::New(env, buf); } -NAN_METHOD(Context2d::CreatePattern) { - Local image = info[0]; - Local repetition = info[1]; - - if (!Nan::To(repetition).FromMaybe(false)) - repetition = Nan::New("repeat").ToLocalChecked(); - - const int argc = 2; - Local argv[argc] = { image, repetition }; - - Local ctor = Nan::GetFunction(Nan::New(Pattern::constructor)).ToLocalChecked(); - Local instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked(); - - info.GetReturnValue().Set(instance); +Napi::Value +Context2d::CreatePattern(const Napi::CallbackInfo& info) { + Napi::Function ctor = env.GetInstanceData()->CanvasPatternCtor.Value(); + Napi::Maybe ret = ctor.New({ info[0], info[1] }); + return ret.IsJust() ? ret.Unwrap() : env.Undefined(); } -NAN_METHOD(Context2d::CreateLinearGradient) { - const int argc = 4; - Local argv[argc] = { info[0], info[1], info[2], info[3] }; - - Local ctor = Nan::GetFunction(Nan::New(Gradient::constructor)).ToLocalChecked(); - Local instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked(); +Napi::Value +Context2d::CreateLinearGradient(const Napi::CallbackInfo& info) { + Napi::Function ctor = env.GetInstanceData()->CanvasGradientCtor.Value(); + Napi::Maybe ret = ctor.New({ info[0], info[1], info[2], info[3] }); + return ret.IsJust() ? ret.Unwrap() : env.Undefined(); - info.GetReturnValue().Set(instance); } -NAN_METHOD(Context2d::CreateRadialGradient) { - const int argc = 6; - Local argv[argc] = { info[0], info[1], info[2], info[3], info[4], info[5] }; - - Local ctor = Nan::GetFunction(Nan::New(Gradient::constructor)).ToLocalChecked(); - Local instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked(); - - info.GetReturnValue().Set(instance); +Napi::Value +Context2d::CreateRadialGradient(const Napi::CallbackInfo& info) { + Napi::Function ctor = env.GetInstanceData()->CanvasGradientCtor.Value(); + Napi::Maybe ret = ctor.New({ info[0], info[1], info[2], info[3], info[4], info[5] }); + return ret.IsJust() ? ret.Unwrap() : env.Undefined(); } /* * Bezier curve. */ -NAN_METHOD(Context2d::BezierCurveTo) { +void +Context2d::BezierCurveTo(const Napi::CallbackInfo& info) { double args[6]; if(!checkArgs(info, args, 6)) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_curve_to(context->context() + cairo_curve_to(context() , args[0] , args[1] , args[2] @@ -2195,13 +2193,13 @@ NAN_METHOD(Context2d::BezierCurveTo) { * Quadratic curve approximation from libsvg-cairo. */ -NAN_METHOD(Context2d::QuadraticCurveTo) { +void +Context2d::QuadraticCurveTo(const Napi::CallbackInfo& info) { double args[4]; if(!checkArgs(info, args, 4)) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = context(); double x, y , x1 = args[0] @@ -2227,56 +2225,57 @@ NAN_METHOD(Context2d::QuadraticCurveTo) { * Save state. */ -NAN_METHOD(Context2d::Save) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->save(); +void +Context2d::Save(const Napi::CallbackInfo& info) { + save(); } /* * Restore state. */ -NAN_METHOD(Context2d::Restore) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->restore(); +void +Context2d::Restore(const Napi::CallbackInfo& info) { + restore(); } /* * Creates a new subpath. */ -NAN_METHOD(Context2d::BeginPath) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_new_path(context->context()); +void +Context2d::BeginPath(const Napi::CallbackInfo& info) { + cairo_new_path(context()); } /* * Marks the subpath as closed. */ -NAN_METHOD(Context2d::ClosePath) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_close_path(context->context()); +void +Context2d::ClosePath(const Napi::CallbackInfo& info) { + cairo_close_path(context()); } /* * Rotate transformation. */ -NAN_METHOD(Context2d::Rotate) { +void +Context2d::Rotate(const Napi::CallbackInfo& info) { double args[1]; if(!checkArgs(info, args, 1)) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_rotate(context->context(), args[0]); + cairo_rotate(context(), args[0]); } /* * Modify the CTM. */ -NAN_METHOD(Context2d::Transform) { +void +Context2d::Transform(const Napi::CallbackInfo& info) { double args[6]; if(!checkArgs(info, args, 6)) return; @@ -2290,52 +2289,49 @@ NAN_METHOD(Context2d::Transform) { , args[4] , args[5]); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_transform(context->context(), &matrix); + cairo_transform(context(), &matrix); } /* * Get the CTM */ -NAN_METHOD(Context2d::GetTransform) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - Local instance = get_current_transform(context); - - info.GetReturnValue().Set(instance); +Napi::Value +Context2d::GetTransform(const Napi::CallbackInfo& info) { + return get_current_transform(); } /* * Reset the CTM, used internally by setTransform(). */ -NAN_METHOD(Context2d::ResetTransform) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_identity_matrix(context->context()); +void +Context2d::ResetTransform(const Napi::CallbackInfo& info) { + cairo_identity_matrix(context()); } /* * Reset transform matrix to identity, then apply the given args. */ -NAN_METHOD(Context2d::SetTransform) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - if (info.Length() == 1) { - Local mat = Nan::To(info[0]).ToLocalChecked(); +void +Context2d::SetTransform(const Napi::CallbackInfo& info) { + Napi::Object mat; - #if NODE_MAJOR_VERSION >= 8 - Local ctx = Nan::GetCurrentContext(); - if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) { - return Nan::ThrowTypeError("Expected DOMMatrix"); + if (info.Length() == 1 && info[0].ToObject().UnwrapTo(&mat)) { + if (!mat.InstanceOf(env.GetInstanceData()->DOMMatrixCtor.Value()).UnwrapOr(false)) { + if (!env.IsExceptionPending()) { + Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException(); } - #endif + return; + } cairo_matrix_t matrix; parse_matrix_from_object(matrix, mat); - cairo_set_matrix(context->context(), &matrix); + cairo_set_matrix(context(), &matrix); } else { - cairo_identity_matrix(context->context()); + cairo_identity_matrix(context()); Context2d::Transform(info); } } @@ -2344,36 +2340,36 @@ NAN_METHOD(Context2d::SetTransform) { * Translate transformation. */ -NAN_METHOD(Context2d::Translate) { +void +Context2d::Translate(const Napi::CallbackInfo& info) { double args[2]; if(!checkArgs(info, args, 2)) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_translate(context->context(), args[0], args[1]); + cairo_translate(context(), args[0], args[1]); } /* * Scale transformation. */ -NAN_METHOD(Context2d::Scale) { +void +Context2d::Scale(const Napi::CallbackInfo& info) { double args[2]; if(!checkArgs(info, args, 2)) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_scale(context->context(), args[0], args[1]); + cairo_scale(context(), args[0], args[1]); } /* * Use path as clipping region. */ -NAN_METHOD(Context2d::Clip) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->setFillRule(info[0]); - cairo_t *ctx = context->context(); +void +Context2d::Clip(const Napi::CallbackInfo& info) { + setFillRule(info[0]); + cairo_t *ctx = context(); cairo_clip_preserve(ctx); } @@ -2381,19 +2377,19 @@ NAN_METHOD(Context2d::Clip) { * Fill the path. */ -NAN_METHOD(Context2d::Fill) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->setFillRule(info[0]); - context->fill(true); +void +Context2d::Fill(const Napi::CallbackInfo& info) { + setFillRule(info[0]); + fill(true); } /* * Stroke the path. */ -NAN_METHOD(Context2d::Stroke) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->stroke(true); +void +Context2d::Stroke(const Napi::CallbackInfo& info) { + stroke(true); } /* @@ -2414,44 +2410,47 @@ get_text_scale(PangoLayout *layout, double maxWidth) { } void -paintText(const Nan::FunctionCallbackInfo &info, bool stroke) { +Context2d::paintText(const Napi::CallbackInfo&info, bool stroke) { int argsNum = info.Length() >= 4 ? 3 : 2; - if (argsNum == 3 && info[3]->IsUndefined()) + if (argsNum == 3 && info[3].IsUndefined()) argsNum = 2; double args[3]; if(!checkArgs(info, args, argsNum, 1)) return; - Nan::Utf8String str(Nan::To(info[0]).ToLocalChecked()); + Napi::String strValue; + + if (!info[0].ToString().UnwrapTo(&strValue)) return; + + std::string str = strValue.Utf8Value(); double x = args[0]; double y = args[1]; double scaled_by = 1; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - PangoLayout *layout = context->layout(); + PangoLayout *layout = this->layout(); - pango_layout_set_text(layout, *str, -1); - pango_cairo_update_layout(context->context(), layout); + pango_layout_set_text(layout, str.c_str(), -1); + pango_cairo_update_layout(context(), layout); if (argsNum == 3) { scaled_by = get_text_scale(layout, args[2]); - cairo_save(context->context()); - cairo_scale(context->context(), scaled_by, 1); + cairo_save(context()); + cairo_scale(context(), scaled_by, 1); } - context->savePath(); - if (context->state->textDrawingMode == TEXT_DRAW_GLYPHS) { - if (stroke == true) { context->stroke(); } else { context->fill(); } - context->setTextPath(x / scaled_by, y); - } else if (context->state->textDrawingMode == TEXT_DRAW_PATHS) { - context->setTextPath(x / scaled_by, y); - if (stroke == true) { context->stroke(); } else { context->fill(); } + savePath(); + if (state->textDrawingMode == TEXT_DRAW_GLYPHS) { + if (stroke == true) { this->stroke(); } else { this->fill(); } + setTextPath(x / scaled_by, y); + } else if (state->textDrawingMode == TEXT_DRAW_PATHS) { + setTextPath(x / scaled_by, y); + if (stroke == true) { this->stroke(); } else { this->fill(); } } - context->restorePath(); + restorePath(); if (argsNum == 3) { - cairo_restore(context->context()); + cairo_restore(context()); } } @@ -2459,7 +2458,8 @@ paintText(const Nan::FunctionCallbackInfo &info, bool stroke) { * Fill text at (x, y). */ -NAN_METHOD(Context2d::FillText) { +void +Context2d::FillText(const Napi::CallbackInfo& info) { paintText(info, false); } @@ -2467,7 +2467,8 @@ NAN_METHOD(Context2d::FillText) { * Stroke text at (x ,y). */ -NAN_METHOD(Context2d::StrokeText) { +void +Context2d::StrokeText(const Napi::CallbackInfo& info) { paintText(info, true); } @@ -2532,37 +2533,35 @@ Context2d::setTextPath(double x, double y) { * Adds a point to the current subpath. */ -NAN_METHOD(Context2d::LineTo) { +void +Context2d::LineTo(const Napi::CallbackInfo& info) { double args[2]; if(!checkArgs(info, args, 2)) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_line_to(context->context(), args[0], args[1]); + cairo_line_to(context(), args[0], args[1]); } /* * Creates a new subpath at the given point. */ -NAN_METHOD(Context2d::MoveTo) { +void +Context2d::MoveTo(const Napi::CallbackInfo& info) { double args[2]; if(!checkArgs(info, args, 2)) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_move_to(context->context(), args[0], args[1]); + cairo_move_to(context(), args[0], args[1]); } /* * Get font. */ -NAN_GETTER(Context2d::GetFont) { - CHECK_RECEIVER(Context2d.GetFont); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - - info.GetReturnValue().Set(Nan::New(context->state->font).ToLocalChecked()); +Napi::Value +Context2d::GetFont(const Napi::CallbackInfo& info) { + return Napi::String::New(env, state->font); } /* @@ -2574,46 +2573,44 @@ NAN_GETTER(Context2d::GetFont) { * - family */ -NAN_SETTER(Context2d::SetFont) { - CHECK_RECEIVER(Context2d.SetFont); - if (!value->IsString()) return; +void +Context2d::SetFont(const Napi::CallbackInfo& info, const Napi::Value& value) { + InstanceData* data = env.GetInstanceData(); - Isolate *iso = Isolate::GetCurrent(); - Local ctx = Nan::GetCurrentContext(); + if (!value.IsString()) return; - Local str = Nan::To(value).ToLocalChecked(); - if (!str->Length()) return; + if (!value.As().Utf8Value().length()) return; - const int argc = 1; - Local argv[argc] = { value }; + Napi::Value mparsed; - Local mparsed = Nan::Call(_parseFont.Get(iso), ctx->Global(), argc, argv).ToLocalChecked(); // parseFont returns undefined for invalid CSS font strings - if (mparsed->IsUndefined()) return; - Local font = Nan::To(mparsed).ToLocalChecked(); + if (!data->parseFont.Call({ value }).UnwrapTo(&mparsed) || mparsed.IsUndefined()) return; - Nan::Utf8String weight(Nan::Get(font, Nan::New("weight").ToLocalChecked()).ToLocalChecked()); - Nan::Utf8String style(Nan::Get(font, Nan::New("style").ToLocalChecked()).ToLocalChecked()); - double size = Nan::To(Nan::Get(font, Nan::New("size").ToLocalChecked()).ToLocalChecked()).FromMaybe(0); - Nan::Utf8String unit(Nan::Get(font, Nan::New("unit").ToLocalChecked()).ToLocalChecked()); - Nan::Utf8String family(Nan::Get(font, Nan::New("family").ToLocalChecked()).ToLocalChecked()); + Napi::Object font = mparsed.As(); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); + Napi::String empty = Napi::String::New(env, ""); + Napi::Number zero = Napi::Number::New(env, 0); - PangoFontDescription *desc = pango_font_description_copy(context->state->fontDescription); - pango_font_description_free(context->state->fontDescription); + std::string weight = font.Get("weight").UnwrapOr(empty).ToString().UnwrapOr(empty).Utf8Value(); + std::string style = font.Get("style").UnwrapOr(empty).ToString().UnwrapOr(empty).Utf8Value(); + double size = font.Get("size").UnwrapOr(zero).ToNumber().UnwrapOr(zero).DoubleValue(); + std::string unit = font.Get("unit").UnwrapOr(empty).ToString().UnwrapOr(empty).Utf8Value(); + std::string family = font.Get("family").UnwrapOr(empty).ToString().UnwrapOr(empty).Utf8Value(); - pango_font_description_set_style(desc, Canvas::GetStyleFromCSSString(*style)); - pango_font_description_set_weight(desc, Canvas::GetWeightFromCSSString(*weight)); + PangoFontDescription *desc = pango_font_description_copy(state->fontDescription); + pango_font_description_free(state->fontDescription); - if (strlen(*family) > 0) { + pango_font_description_set_style(desc, Canvas::GetStyleFromCSSString(style.c_str())); + pango_font_description_set_weight(desc, Canvas::GetWeightFromCSSString(weight.c_str())); + + if (family.length() > 0) { // See #1643 - Pango understands "sans" whereas CSS uses "sans-serif" - std::string s1(*family); + std::string s1(family); std::string s2("sans-serif"); if (streq_casein(s1, s2)) { pango_font_description_set_family(desc, "sans"); } else { - pango_font_description_set_family(desc, *family); + pango_font_description_set_family(desc, family.c_str()); } } @@ -2622,21 +2619,20 @@ NAN_SETTER(Context2d::SetFont) { if (size > 0) pango_font_description_set_absolute_size(sys_desc, size * PANGO_SCALE); - context->state->fontDescription = sys_desc; - pango_layout_set_font_description(context->_layout, sys_desc); + state->fontDescription = sys_desc; + pango_layout_set_font_description(_layout, sys_desc); - context->state->font = *Nan::Utf8String(value); + state->font = value.As().Utf8Value().c_str(); } /* * Get text baseline. */ -NAN_GETTER(Context2d::GetTextBaseline) { - CHECK_RECEIVER(Context2d.GetTextBaseline); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value +Context2d::GetTextBaseline(const Napi::CallbackInfo& info) { const char* baseline; - switch (context->state->textBaseline) { + switch (state->textBaseline) { default: case TEXT_BASELINE_ALPHABETIC: baseline = "alphabetic"; break; case TEXT_BASELINE_TOP: baseline = "top"; break; @@ -2645,18 +2641,18 @@ NAN_GETTER(Context2d::GetTextBaseline) { case TEXT_BASELINE_IDEOGRAPHIC: baseline = "ideographic"; break; case TEXT_BASELINE_HANGING: baseline = "hanging"; break; } - info.GetReturnValue().Set(Nan::New(baseline).ToLocalChecked()); + return Napi::String::New(env, baseline); } /* * Set text baseline. */ -NAN_SETTER(Context2d::SetTextBaseline) { - CHECK_RECEIVER(Context2d.SetTextBaseline); - if (!value->IsString()) return; +void +Context2d::SetTextBaseline(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (!value.IsString()) return; - Nan::Utf8String opStr(Nan::To(value).ToLocalChecked()); + std::string opStr = value.As(); const std::map modes = { {"alphabetic", TEXT_BASELINE_ALPHABETIC}, {"top", TEXT_BASELINE_TOP}, @@ -2665,22 +2661,20 @@ NAN_SETTER(Context2d::SetTextBaseline) { {"ideographic", TEXT_BASELINE_IDEOGRAPHIC}, {"hanging", TEXT_BASELINE_HANGING} }; - auto op = modes.find(*opStr); + auto op = modes.find(opStr); if (op == modes.end()) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->state->textBaseline = op->second; + state->textBaseline = op->second; } /* * Get text align. */ -NAN_GETTER(Context2d::GetTextAlign) { - CHECK_RECEIVER(Context2d.GetTextAlign); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value +Context2d::GetTextAlign(const Napi::CallbackInfo& info) { const char* align; - switch (context->state->textAlignment) { + switch (state->textAlignment) { default: // TODO the default is supposed to be "start" case TEXT_ALIGNMENT_LEFT: align = "left"; break; @@ -2689,18 +2683,18 @@ NAN_GETTER(Context2d::GetTextAlign) { case TEXT_ALIGNMENT_RIGHT: align = "right"; break; case TEXT_ALIGNMENT_END: align = "end"; break; } - info.GetReturnValue().Set(Nan::New(align).ToLocalChecked()); + return Napi::String::New(env, align); } /* * Set text align. */ -NAN_SETTER(Context2d::SetTextAlign) { - CHECK_RECEIVER(Context2d.SetTextAlign); - if (!value->IsString()) return; +void +Context2d::SetTextAlign(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (!value.IsString()) return; - Nan::Utf8String opStr(Nan::To(value).ToLocalChecked()); + std::string opStr = value.As(); const std::map modes = { {"center", TEXT_ALIGNMENT_CENTER}, {"left", TEXT_ALIGNMENT_LEFT}, @@ -2708,11 +2702,10 @@ NAN_SETTER(Context2d::SetTextAlign) { {"right", TEXT_ALIGNMENT_RIGHT}, {"end", TEXT_ALIGNMENT_END} }; - auto op = modes.find(*opStr); + auto op = modes.find(opStr); if (op == modes.end()) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - context->state->textAlignment = op->second; + state->textAlignment = op->second; } /* @@ -2722,19 +2715,21 @@ NAN_SETTER(Context2d::SetTextAlign) { * fontBoundingBoxAscent, fontBoundingBoxDescent */ -NAN_METHOD(Context2d::MeasureText) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); +Napi::Value +Context2d::MeasureText(const Napi::CallbackInfo& info) { + cairo_t *ctx = this->context(); + + Napi::String str; + if (!info[0].ToString().UnwrapTo(&str)) return env.Undefined(); - Nan::Utf8String str(Nan::To(info[0]).ToLocalChecked()); - Local obj = Nan::New(); + Napi::Object obj = Napi::Object::New(env); PangoRectangle _ink_rect, _logical_rect; float_rectangle ink_rect, logical_rect; PangoFontMetrics *metrics; - PangoLayout *layout = context->layout(); + PangoLayout *layout = this->layout(); - pango_layout_set_text(layout, *str, -1); + pango_layout_set_text(layout, str.Utf8Value().c_str(), -1); pango_cairo_update_layout(ctx, layout); // Normally you could use pango_layout_get_pixel_extents and be done, or use @@ -2757,7 +2752,7 @@ NAN_METHOD(Context2d::MeasureText) { metrics = PANGO_LAYOUT_GET_METRICS(layout); double x_offset; - switch (context->state->textAlignment) { + switch (state->textAlignment) { case TEXT_ALIGNMENT_CENTER: x_offset = logical_rect.width / 2.; break; @@ -2773,36 +2768,20 @@ NAN_METHOD(Context2d::MeasureText) { cairo_matrix_t matrix; cairo_get_matrix(ctx, &matrix); - double y_offset = getBaselineAdjustment(layout, context->state->textBaseline); - - Nan::Set(obj, - Nan::New("width").ToLocalChecked(), - Nan::New(logical_rect.width)).Check(); - Nan::Set(obj, - Nan::New("actualBoundingBoxLeft").ToLocalChecked(), - Nan::New(PANGO_LBEARING(ink_rect) + x_offset)).Check(); - Nan::Set(obj, - Nan::New("actualBoundingBoxRight").ToLocalChecked(), - Nan::New(PANGO_RBEARING(ink_rect) - x_offset)).Check(); - Nan::Set(obj, - Nan::New("actualBoundingBoxAscent").ToLocalChecked(), - Nan::New(y_offset + PANGO_ASCENT(ink_rect))).Check(); - Nan::Set(obj, - Nan::New("actualBoundingBoxDescent").ToLocalChecked(), - Nan::New(PANGO_DESCENT(ink_rect) - y_offset)).Check(); - Nan::Set(obj, - Nan::New("emHeightAscent").ToLocalChecked(), - Nan::New(-(PANGO_ASCENT(logical_rect) - y_offset))).Check(); - Nan::Set(obj, - Nan::New("emHeightDescent").ToLocalChecked(), - Nan::New(PANGO_DESCENT(logical_rect) - y_offset)).Check(); - Nan::Set(obj, - Nan::New("alphabeticBaseline").ToLocalChecked(), - Nan::New(-(pango_font_metrics_get_ascent(metrics) * inverse_pango_scale - y_offset))).Check(); + double y_offset = getBaselineAdjustment(layout, state->textBaseline); + + obj.Set("width", Napi::Number::New(env, logical_rect.width)); + obj.Set("actualBoundingBoxLeft", Napi::Number::New(env, PANGO_LBEARING(ink_rect) + x_offset)); + obj.Set("actualBoundingBoxRight", Napi::Number::New(env, PANGO_RBEARING(ink_rect) - x_offset)); + obj.Set("actualBoundingBoxAscent", Napi::Number::New(env, y_offset + PANGO_ASCENT(ink_rect))); + obj.Set("actualBoundingBoxDescent", Napi::Number::New(env, PANGO_DESCENT(ink_rect) - y_offset)); + obj.Set("emHeightAscent", Napi::Number::New(env, -(PANGO_ASCENT(logical_rect) - y_offset))); + obj.Set("emHeightDescent", Napi::Number::New(env, PANGO_DESCENT(logical_rect) - y_offset)); + obj.Set("alphabeticBaseline", Napi::Number::New(env, -(pango_font_metrics_get_ascent(metrics) * inverse_pango_scale - y_offset))); pango_font_metrics_unref(metrics); - info.GetReturnValue().Set(obj); + return obj; } /* @@ -2810,22 +2789,22 @@ NAN_METHOD(Context2d::MeasureText) { * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash */ -NAN_METHOD(Context2d::SetLineDash) { - if (!info[0]->IsArray()) return; - Local dash = Local::Cast(info[0]); - uint32_t dashes = dash->Length() & 1 ? dash->Length() * 2 : dash->Length(); +void +Context2d::SetLineDash(const Napi::CallbackInfo& info) { + if (!info[0].IsArray()) return; + Napi::Array dash = info[0].As(); + uint32_t dashes = dash.Length() & 1 ? dash.Length() * 2 : dash.Length(); uint32_t zero_dashes = 0; std::vector a(dashes); for (uint32_t i=0; i d = Nan::Get(dash, i % dash->Length()).ToLocalChecked(); - if (!d->IsNumber()) return; - a[i] = Nan::To(d).FromMaybe(0); + Napi::Number d; + if (!dash.Get(i % dash.Length()).UnwrapTo(&d) || !d.IsNumber()) return; + a[i] = d.As().DoubleValue(); if (a[i] == 0) zero_dashes++; if (a[i] < 0 || !std::isfinite(a[i])) return; } - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = this->context(); double offset; cairo_get_dash(ctx, NULL, &offset); if (zero_dashes == dashes) { @@ -2840,32 +2819,33 @@ NAN_METHOD(Context2d::SetLineDash) { * Get line dash * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash */ -NAN_METHOD(Context2d::GetLineDash) { - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); +Napi::Value +Context2d::GetLineDash(const Napi::CallbackInfo& info) { + cairo_t *ctx = this->context(); int dashes = cairo_get_dash_count(ctx); std::vector a(dashes); cairo_get_dash(ctx, a.data(), NULL); - Local dash = Nan::New(dashes); + Napi::Array dash = Napi::Array::New(env, dashes); for (int i=0; i(i), Nan::New(a[i])).Check(); + dash.Set(Napi::Number::New(env, i), Napi::Number::New(env, a[i])); } - info.GetReturnValue().Set(dash); + return dash; } /* * Set line dash offset * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash */ -NAN_SETTER(Context2d::SetLineDashOffset) { - CHECK_RECEIVER(Context2d.SetLineDashOffset); - double offset = Nan::To(value).FromMaybe(0); +void +Context2d::SetLineDashOffset(const Napi::CallbackInfo& info, const Napi::Value& value) { + Napi::Number numberValue; + if (!value.ToNumber().UnwrapTo(&numberValue)) return; + double offset = numberValue.DoubleValue(); if (!std::isfinite(offset)) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = this->context(); int dashes = cairo_get_dash_count(ctx); std::vector a(dashes); @@ -2877,61 +2857,60 @@ NAN_SETTER(Context2d::SetLineDashOffset) { * Get line dash offset * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash */ -NAN_GETTER(Context2d::GetLineDashOffset) { - CHECK_RECEIVER(Context2d.GetLineDashOffset); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); +Napi::Value +Context2d::GetLineDashOffset(const Napi::CallbackInfo& info) { + cairo_t *ctx = this->context(); double offset; cairo_get_dash(ctx, NULL, &offset); - info.GetReturnValue().Set(Nan::New(offset)); + return Napi::Number::New(env, offset); } /* * Fill the rectangle defined by x, y, width and height. */ -NAN_METHOD(Context2d::FillRect) { +void +Context2d::FillRect(const Napi::CallbackInfo& info) { RECT_ARGS; if (0 == width || 0 == height) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); - context->savePath(); + cairo_t *ctx = context(); + savePath(); cairo_rectangle(ctx, x, y, width, height); - context->fill(); - context->restorePath(); + fill(); + restorePath(); } /* * Stroke the rectangle defined by x, y, width and height. */ -NAN_METHOD(Context2d::StrokeRect) { +void +Context2d::StrokeRect(const Napi::CallbackInfo& info) { RECT_ARGS; if (0 == width && 0 == height) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); - context->savePath(); + cairo_t *ctx = context(); + savePath(); cairo_rectangle(ctx, x, y, width, height); - context->stroke(); - context->restorePath(); + stroke(); + restorePath(); } /* * Clears all pixels defined by x, y, width and height. */ -NAN_METHOD(Context2d::ClearRect) { +void +Context2d::ClearRect(const Napi::CallbackInfo& info) { RECT_ARGS; if (0 == width || 0 == height) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = context(); cairo_save(ctx); - context->savePath(); + savePath(); cairo_rectangle(ctx, x, y, width, height); cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR); cairo_fill(ctx); - context->restorePath(); + restorePath(); cairo_restore(ctx); } @@ -2939,10 +2918,10 @@ NAN_METHOD(Context2d::ClearRect) { * Adds a rectangle subpath. */ -NAN_METHOD(Context2d::Rect) { +void +Context2d::Rect(const Napi::CallbackInfo& info) { RECT_ARGS; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = context(); if (width == 0) { cairo_move_to(ctx, x, y); cairo_line_to(ctx, x, y + height); @@ -2972,29 +2951,34 @@ void elli_arc(cairo_t* ctx, double xc, double yc, double rx, double ry, double a } inline static -bool getRadius(Point& p, const Local& v) { - if (v->IsObject()) { // 5.1 DOMPointInit - auto rx = Nan::Get(v.As(), Nan::New("x").ToLocalChecked()).ToLocalChecked(); - auto ry = Nan::Get(v.As(), Nan::New("y").ToLocalChecked()).ToLocalChecked(); - if (rx->IsNumber() && ry->IsNumber()) { - auto rxv = Nan::To(rx).FromJust(); - auto ryv = Nan::To(ry).FromJust(); +bool getRadius(Point& p, const Napi::Value& v) { + Napi::Env env = v.Env(); + if (v.IsObject()) { // 5.1 DOMPointInit + Napi::Value rx; + Napi::Value ry; + auto rxMaybe = v.As().Get("x"); + auto ryMaybe = v.As().Get("y"); + if (rxMaybe.UnwrapTo(&rx) && rx.IsNumber() && ryMaybe.UnwrapTo(&ry) && ry.IsNumber()) { + auto rxv = rx.As().DoubleValue(); + auto ryv = ry.As().DoubleValue(); if (!std::isfinite(rxv) || !std::isfinite(ryv)) return true; if (rxv < 0 || ryv < 0) { - Nan::ThrowRangeError("radii must be positive."); + Napi::RangeError::New(env, "radii must be positive.").ThrowAsJavaScriptException(); + return true; } p.x = rxv; p.y = ryv; return false; } - } else if (v->IsNumber()) { // 5.2 unrestricted double - auto rv = Nan::To(v).FromJust(); + } else if (v.IsNumber()) { // 5.2 unrestricted double + auto rv = v.As().DoubleValue(); if (!std::isfinite(rv)) return true; if (rv < 0) { - Nan::ThrowRangeError("radii must be positive."); + Napi::RangeError::New(env, "radii must be positive.").ThrowAsJavaScriptException(); + return true; } p.x = p.y = rv; @@ -3007,30 +2991,30 @@ bool getRadius(Point& p, const Local& v) { * https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect * x, y, w, h, [radius|[radii]] */ -NAN_METHOD(Context2d::RoundRect) { +void +Context2d::RoundRect(const Napi::CallbackInfo& info) { RECT_ARGS; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = this->context(); // 4. Let normalizedRadii be an empty list Point normalizedRadii[4]; size_t nRadii = 4; - if (info[4]->IsUndefined()) { + if (info[4].IsUndefined()) { for (size_t i = 0; i < 4; i++) normalizedRadii[i].x = normalizedRadii[i].y = 0.; - } else if (info[4]->IsArray()) { - auto radiiList = info[4].As(); - nRadii = radiiList->Length(); + } else if (info[4].IsArray()) { + auto radiiList = info[4].As(); + nRadii = radiiList.Length(); if (!(nRadii >= 1 && nRadii <= 4)) { - Nan::ThrowRangeError("radii must be a list of one, two, three or four radii."); + Napi::RangeError::New(env, "radii must be a list of one, two, three or four radii.").ThrowAsJavaScriptException(); return; } // 5. For each radius of radii for (size_t i = 0; i < nRadii; i++) { - auto r = Nan::Get(radiiList, i).ToLocalChecked(); - if (getRadius(normalizedRadii[i], r)) + Napi::Value r; + if (!radiiList.Get(i).UnwrapTo(&r) || getRadius(normalizedRadii[i], r)) return; } @@ -3177,7 +3161,8 @@ static double adjustEndAngle(double startAngle, double endAngle, bool counterclo * Adds an arc at x, y with the given radii and start/end angles. */ -NAN_METHOD(Context2d::Arc) { +void +Context2d::Arc(const Napi::CallbackInfo& info) { double args[5]; if(!checkArgs(info, args, 5)) return; @@ -3189,14 +3174,15 @@ NAN_METHOD(Context2d::Arc) { auto endAngle = args[4]; if (radius < 0) { - Nan::ThrowRangeError("The radius provided is negative."); + Napi::RangeError::New(env, "The radius provided is negative.").ThrowAsJavaScriptException(); return; } - bool counterclockwise = Nan::To(info[5]).FromMaybe(false); + Napi::Boolean counterclockwiseValue; + if (!info[5].ToBoolean().UnwrapTo(&counterclockwiseValue)) return; + bool counterclockwise = counterclockwiseValue.Value(); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = context(); canonicalizeAngle(startAngle, endAngle); endAngle = adjustEndAngle(startAngle, endAngle, counterclockwise); @@ -3214,13 +3200,13 @@ NAN_METHOD(Context2d::Arc) { * Implementation influenced by WebKit. */ -NAN_METHOD(Context2d::ArcTo) { +void +Context2d::ArcTo(const Napi::CallbackInfo& info) { double args[5]; if(!checkArgs(info, args, 5)) return; - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = context(); // Current path point double x, y; @@ -3319,7 +3305,8 @@ NAN_METHOD(Context2d::ArcTo) { * going in the given direction by anticlockwise (defaulting to clockwise). */ -NAN_METHOD(Context2d::Ellipse) { +void +Context2d::Ellipse(const Napi::CallbackInfo& info) { double args[7]; if(!checkArgs(info, args, 7)) return; @@ -3334,10 +3321,12 @@ NAN_METHOD(Context2d::Ellipse) { double rotation = args[4]; double startAngle = args[5]; double endAngle = args[6]; - bool anticlockwise = Nan::To(info[7]).FromMaybe(false); + Napi::Boolean anticlockwiseValue; + + if (!info[7].ToBoolean().UnwrapTo(&anticlockwiseValue)) return; + bool anticlockwise = anticlockwiseValue.Value(); - Context2d *context = Nan::ObjectWrap::Unwrap(info.This()); - cairo_t *ctx = context->context(); + cairo_t *ctx = context(); // See https://www.cairographics.org/cookbook/ellipses/ double xRatio = radiusX / radiusY; @@ -3365,5 +3354,3 @@ NAN_METHOD(Context2d::Ellipse) { } cairo_set_matrix(ctx, &save_matrix); } - -#undef CHECK_RECEIVER diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index 8ea4d60b8..745106e2d 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -5,7 +5,7 @@ #include "cairo.h" #include "Canvas.h" #include "color.h" -#include "nan.h" +#include "napi.h" #include #include @@ -81,108 +81,103 @@ typedef struct { float height; } float_rectangle; -class Context2d : public Nan::ObjectWrap { +class Context2d : public Napi::ObjectWrap { public: std::stack states; canvas_state_t *state; - Context2d(Canvas *canvas); - static Nan::Persistent _DOMMatrix; - static Nan::Persistent _parseFont; - static Nan::Persistent constructor; - static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target); - static NAN_METHOD(New); - static NAN_METHOD(SaveExternalModules); - static NAN_METHOD(DrawImage); - static NAN_METHOD(PutImageData); - static NAN_METHOD(Save); - static NAN_METHOD(Restore); - static NAN_METHOD(Rotate); - static NAN_METHOD(Translate); - static NAN_METHOD(Scale); - static NAN_METHOD(Transform); - static NAN_METHOD(GetTransform); - static NAN_METHOD(ResetTransform); - static NAN_METHOD(SetTransform); - static NAN_METHOD(IsPointInPath); - static NAN_METHOD(BeginPath); - static NAN_METHOD(ClosePath); - static NAN_METHOD(AddPage); - static NAN_METHOD(Clip); - static NAN_METHOD(Fill); - static NAN_METHOD(Stroke); - static NAN_METHOD(FillText); - static NAN_METHOD(StrokeText); - static NAN_METHOD(SetFont); - static NAN_METHOD(SetFillColor); - static NAN_METHOD(SetStrokeColor); - static NAN_METHOD(SetStrokePattern); - static NAN_METHOD(SetTextAlignment); - static NAN_METHOD(SetLineDash); - static NAN_METHOD(GetLineDash); - static NAN_METHOD(MeasureText); - static NAN_METHOD(BezierCurveTo); - static NAN_METHOD(QuadraticCurveTo); - static NAN_METHOD(LineTo); - static NAN_METHOD(MoveTo); - static NAN_METHOD(FillRect); - static NAN_METHOD(StrokeRect); - static NAN_METHOD(ClearRect); - static NAN_METHOD(Rect); - static NAN_METHOD(RoundRect); - static NAN_METHOD(Arc); - static NAN_METHOD(ArcTo); - static NAN_METHOD(Ellipse); - static NAN_METHOD(GetImageData); - static NAN_METHOD(CreateImageData); - static NAN_METHOD(GetStrokeColor); - static NAN_METHOD(CreatePattern); - static NAN_METHOD(CreateLinearGradient); - static NAN_METHOD(CreateRadialGradient); - static NAN_GETTER(GetFormat); - static NAN_GETTER(GetPatternQuality); - static NAN_GETTER(GetImageSmoothingEnabled); - static NAN_GETTER(GetGlobalCompositeOperation); - static NAN_GETTER(GetGlobalAlpha); - static NAN_GETTER(GetShadowColor); - static NAN_GETTER(GetMiterLimit); - static NAN_GETTER(GetLineCap); - static NAN_GETTER(GetLineJoin); - static NAN_GETTER(GetLineWidth); - static NAN_GETTER(GetLineDashOffset); - static NAN_GETTER(GetShadowOffsetX); - static NAN_GETTER(GetShadowOffsetY); - static NAN_GETTER(GetShadowBlur); - static NAN_GETTER(GetAntiAlias); - static NAN_GETTER(GetTextDrawingMode); - static NAN_GETTER(GetQuality); - static NAN_GETTER(GetCurrentTransform); - static NAN_GETTER(GetFillStyle); - static NAN_GETTER(GetStrokeStyle); - static NAN_GETTER(GetFont); - static NAN_GETTER(GetTextBaseline); - static NAN_GETTER(GetTextAlign); - static NAN_SETTER(SetPatternQuality); - static NAN_SETTER(SetImageSmoothingEnabled); - static NAN_SETTER(SetGlobalCompositeOperation); - static NAN_SETTER(SetGlobalAlpha); - static NAN_SETTER(SetShadowColor); - static NAN_SETTER(SetMiterLimit); - static NAN_SETTER(SetLineCap); - static NAN_SETTER(SetLineJoin); - static NAN_SETTER(SetLineWidth); - static NAN_SETTER(SetLineDashOffset); - static NAN_SETTER(SetShadowOffsetX); - static NAN_SETTER(SetShadowOffsetY); - static NAN_SETTER(SetShadowBlur); - static NAN_SETTER(SetAntiAlias); - static NAN_SETTER(SetTextDrawingMode); - static NAN_SETTER(SetQuality); - static NAN_SETTER(SetCurrentTransform); - static NAN_SETTER(SetFillStyle); - static NAN_SETTER(SetStrokeStyle); - static NAN_SETTER(SetFont); - static NAN_SETTER(SetTextBaseline); - static NAN_SETTER(SetTextAlign); + Context2d(const Napi::CallbackInfo& info); + static void Initialize(Napi::Env& env, Napi::Object& target); + void DrawImage(const Napi::CallbackInfo& info); + void PutImageData(const Napi::CallbackInfo& info); + void Save(const Napi::CallbackInfo& info); + void Restore(const Napi::CallbackInfo& info); + void Rotate(const Napi::CallbackInfo& info); + void Translate(const Napi::CallbackInfo& info); + void Scale(const Napi::CallbackInfo& info); + void Transform(const Napi::CallbackInfo& info); + Napi::Value GetTransform(const Napi::CallbackInfo& info); + void ResetTransform(const Napi::CallbackInfo& info); + void SetTransform(const Napi::CallbackInfo& info); + Napi::Value IsPointInPath(const Napi::CallbackInfo& info); + void BeginPath(const Napi::CallbackInfo& info); + void ClosePath(const Napi::CallbackInfo& info); + void AddPage(const Napi::CallbackInfo& info); + void Clip(const Napi::CallbackInfo& info); + void Fill(const Napi::CallbackInfo& info); + void Stroke(const Napi::CallbackInfo& info); + void FillText(const Napi::CallbackInfo& info); + void StrokeText(const Napi::CallbackInfo& info); + static Napi::Value SetFont(const Napi::CallbackInfo& info); + static Napi::Value SetFillColor(const Napi::CallbackInfo& info); + static Napi::Value SetStrokeColor(const Napi::CallbackInfo& info); + static Napi::Value SetStrokePattern(const Napi::CallbackInfo& info); + static Napi::Value SetTextAlignment(const Napi::CallbackInfo& info); + void SetLineDash(const Napi::CallbackInfo& info); + Napi::Value GetLineDash(const Napi::CallbackInfo& info); + Napi::Value MeasureText(const Napi::CallbackInfo& info); + void BezierCurveTo(const Napi::CallbackInfo& info); + void QuadraticCurveTo(const Napi::CallbackInfo& info); + void LineTo(const Napi::CallbackInfo& info); + void MoveTo(const Napi::CallbackInfo& info); + void FillRect(const Napi::CallbackInfo& info); + void StrokeRect(const Napi::CallbackInfo& info); + void ClearRect(const Napi::CallbackInfo& info); + void Rect(const Napi::CallbackInfo& info); + void RoundRect(const Napi::CallbackInfo& info); + void Arc(const Napi::CallbackInfo& info); + void ArcTo(const Napi::CallbackInfo& info); + void Ellipse(const Napi::CallbackInfo& info); + Napi::Value GetImageData(const Napi::CallbackInfo& info); + Napi::Value CreateImageData(const Napi::CallbackInfo& info); + static Napi::Value GetStrokeColor(const Napi::CallbackInfo& info); + Napi::Value CreatePattern(const Napi::CallbackInfo& info); + Napi::Value CreateLinearGradient(const Napi::CallbackInfo& info); + Napi::Value CreateRadialGradient(const Napi::CallbackInfo& info); + Napi::Value GetFormat(const Napi::CallbackInfo& info); + Napi::Value GetPatternQuality(const Napi::CallbackInfo& info); + Napi::Value GetImageSmoothingEnabled(const Napi::CallbackInfo& info); + Napi::Value GetGlobalCompositeOperation(const Napi::CallbackInfo& info); + Napi::Value GetGlobalAlpha(const Napi::CallbackInfo& info); + Napi::Value GetShadowColor(const Napi::CallbackInfo& info); + Napi::Value GetMiterLimit(const Napi::CallbackInfo& info); + Napi::Value GetLineCap(const Napi::CallbackInfo& info); + Napi::Value GetLineJoin(const Napi::CallbackInfo& info); + Napi::Value GetLineWidth(const Napi::CallbackInfo& info); + Napi::Value GetLineDashOffset(const Napi::CallbackInfo& info); + Napi::Value GetShadowOffsetX(const Napi::CallbackInfo& info); + Napi::Value GetShadowOffsetY(const Napi::CallbackInfo& info); + Napi::Value GetShadowBlur(const Napi::CallbackInfo& info); + Napi::Value GetAntiAlias(const Napi::CallbackInfo& info); + Napi::Value GetTextDrawingMode(const Napi::CallbackInfo& info); + Napi::Value GetQuality(const Napi::CallbackInfo& info); + Napi::Value GetCurrentTransform(const Napi::CallbackInfo& info); + Napi::Value GetFillStyle(const Napi::CallbackInfo& info); + Napi::Value GetStrokeStyle(const Napi::CallbackInfo& info); + Napi::Value GetFont(const Napi::CallbackInfo& info); + Napi::Value GetTextBaseline(const Napi::CallbackInfo& info); + Napi::Value GetTextAlign(const Napi::CallbackInfo& info); + void SetPatternQuality(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetImageSmoothingEnabled(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetGlobalCompositeOperation(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetGlobalAlpha(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetShadowColor(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetMiterLimit(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetLineCap(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetLineJoin(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetLineWidth(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetLineDashOffset(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetShadowOffsetX(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetShadowOffsetY(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetShadowBlur(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetAntiAlias(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetTextDrawingMode(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetQuality(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetCurrentTransform(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetFillStyle(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetStrokeStyle(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetFont(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetTextBaseline(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetTextAlign(const Napi::CallbackInfo& info, const Napi::Value& value); inline void setContext(cairo_t *ctx) { _context = ctx; } inline cairo_t *context(){ return _context; } inline Canvas *canvas(){ return _canvas; } @@ -198,7 +193,7 @@ class Context2d : public Nan::ObjectWrap { void restorePath(); void saveState(); void restoreState(); - void inline setFillRule(v8::Local value); + void inline setFillRule(Napi::Value value); void fill(bool preserve = false); void stroke(bool preserve = false); void save(); @@ -206,20 +201,23 @@ class Context2d : public Nan::ObjectWrap { void setFontFromState(); void resetState(); inline PangoLayout *layout(){ return _layout; } + ~Context2d(); + Napi::Env env; private: - ~Context2d(); void _resetPersistentHandles(); - v8::Local _getFillColor(); - v8::Local _getStrokeColor(); - void _setFillColor(v8::Local arg); - void _setFillPattern(v8::Local arg); - void _setStrokeColor(v8::Local arg); - void _setStrokePattern(v8::Local arg); - Nan::Persistent _fillStyle; - Nan::Persistent _strokeStyle; + Napi::Value _getFillColor(); + Napi::Value _getStrokeColor(); + Napi::Value get_current_transform(); + void _setFillColor(Napi::Value arg); + void _setFillPattern(Napi::Value arg); + void _setStrokeColor(Napi::Value arg); + void _setStrokePattern(Napi::Value arg); + void paintText(const Napi::CallbackInfo&, bool); + Napi::Reference _fillStyle; + Napi::Reference _strokeStyle; Canvas *_canvas; - cairo_t *_context; + cairo_t *_context = nullptr; cairo_path_t *_path; - PangoLayout *_layout; + PangoLayout *_layout = nullptr; }; diff --git a/src/Image.cc b/src/Image.cc index 301257769..a1f376136 100644 --- a/src/Image.cc +++ b/src/Image.cc @@ -1,6 +1,7 @@ // Copyright (c) 2010 LearnBoost #include "Image.h" +#include "InstanceData.h" #include "bmp/BMPParser.h" #include "Canvas.h" @@ -8,6 +9,7 @@ #include #include #include +#include /* Cairo limit: * https://lists.cairographics.org/archives/cairo/2010-December/021422.html @@ -36,98 +38,88 @@ struct canvas_jpeg_error_mgr: jpeg_error_mgr { */ typedef struct { + Napi::Env* env; unsigned len; uint8_t *buf; } read_closure_t; -using namespace v8; - -Nan::Persistent Image::constructor; - /* * Initialize Image. */ void -Image::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) { - Nan::HandleScope scope; - - Local ctor = Nan::New(Image::New); - constructor.Reset(ctor); - ctor->InstanceTemplate()->SetInternalFieldCount(1); - ctor->SetClassName(Nan::New("Image").ToLocalChecked()); - - // Prototype - Local proto = ctor->PrototypeTemplate(); - Nan::SetAccessor(proto, Nan::New("complete").ToLocalChecked(), GetComplete); - Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth, SetWidth); - Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight, SetHeight); - Nan::SetAccessor(proto, Nan::New("naturalWidth").ToLocalChecked(), GetNaturalWidth); - Nan::SetAccessor(proto, Nan::New("naturalHeight").ToLocalChecked(), GetNaturalHeight); - Nan::SetAccessor(proto, Nan::New("dataMode").ToLocalChecked(), GetDataMode, SetDataMode); - - ctor->Set(Nan::New("MODE_IMAGE").ToLocalChecked(), Nan::New(DATA_IMAGE)); - ctor->Set(Nan::New("MODE_MIME").ToLocalChecked(), Nan::New(DATA_MIME)); - - Local ctx = Nan::GetCurrentContext(); - Nan::Set(target, Nan::New("Image").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked()); +Image::Initialize(Napi::Env& env, Napi::Object& exports) { + InstanceData *data = env.GetInstanceData(); + Napi::HandleScope scope(env); + + Napi::Function ctor = DefineClass(env, "Image", { + InstanceAccessor<&Image::GetComplete>("complete"), + InstanceAccessor<&Image::GetWidth, &Image::SetWidth>("width"), + InstanceAccessor<&Image::GetHeight, &Image::SetHeight>("height"), + InstanceAccessor<&Image::GetNaturalWidth>("naturalWidth"), + InstanceAccessor<&Image::GetNaturalHeight>("naturalHeight"), + InstanceAccessor<&Image::GetDataMode, &Image::SetDataMode>("dataMode"), + StaticValue("MODE_IMAGE", Napi::Number::New(env, DATA_IMAGE)), + StaticValue("MODE_MIME", Napi::Number::New(env, DATA_MIME)) + }); // Used internally in lib/image.js - NAN_EXPORT(target, GetSource); - NAN_EXPORT(target, SetSource); + exports.Set("GetSource", Napi::Function::New(env, &GetSource)); + exports.Set("SetSource", Napi::Function::New(env, &SetSource)); + + data->ImageCtor = Napi::Persistent(ctor); + exports.Set("Image", ctor); } /* * Initialize a new Image. */ -NAN_METHOD(Image::New) { - if (!info.IsConstructCall()) { - return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'"); - } - - Image *img = new Image; - img->data_mode = DATA_IMAGE; - img->Wrap(info.This()); - Nan::Set(info.This(), Nan::New("onload").ToLocalChecked(), Nan::Null()).Check(); - Nan::Set(info.This(), Nan::New("onerror").ToLocalChecked(), Nan::Null()).Check(); - info.GetReturnValue().Set(info.This()); +Image::Image(const Napi::CallbackInfo& info) : ObjectWrap(info), env(info.Env()) { + data_mode = DATA_IMAGE; + info.This().ToObject().Unwrap().Set("onload", env.Null()); + info.This().ToObject().Unwrap().Set("onerror", env.Null()); + filename = NULL; + _data = nullptr; + _data_len = 0; + _surface = NULL; + width = height = 0; + naturalWidth = naturalHeight = 0; + state = DEFAULT; +#ifdef HAVE_RSVG + _rsvg = NULL; + _is_svg = false; + _svg_last_width = _svg_last_height = 0; +#endif } /* * Get complete boolean. */ -NAN_GETTER(Image::GetComplete) { - info.GetReturnValue().Set(Nan::New(true)); +Napi::Value +Image::GetComplete(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(env, true); } /* * Get dataMode. */ -NAN_GETTER(Image::GetDataMode) { - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - Nan::ThrowTypeError("Method Image.GetDataMode called on incompatible receiver"); - return; - } - Image *img = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(img->data_mode)); +Napi::Value +Image::GetDataMode(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, data_mode); } /* * Set dataMode. */ -NAN_SETTER(Image::SetDataMode) { - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - Nan::ThrowTypeError("Method Image.SetDataMode called on incompatible receiver"); - return; - } - if (value->IsNumber()) { - Image *img = Nan::ObjectWrap::Unwrap(info.This()); - int mode = Nan::To(value).FromMaybe(0); - img->data_mode = (data_mode_t) mode; +void +Image::SetDataMode(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsNumber()) { + int mode = value.As().Uint32Value(); + data_mode = (data_mode_t) mode; } } @@ -135,40 +127,28 @@ NAN_SETTER(Image::SetDataMode) { * Get natural width */ -NAN_GETTER(Image::GetNaturalWidth) { - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - Nan::ThrowTypeError("Method Image.GetNaturalWidth called on incompatible receiver"); - return; - } - Image *img = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(img->naturalWidth)); +Napi::Value +Image::GetNaturalWidth(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, naturalWidth); } /* * Get width. */ -NAN_GETTER(Image::GetWidth) { - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - Nan::ThrowTypeError("Method Image.GetWidth called on incompatible receiver"); - return; - } - Image *img = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(img->width)); +Napi::Value +Image::GetWidth(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, width); } /* * Set width. */ -NAN_SETTER(Image::SetWidth) { - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - Nan::ThrowTypeError("Method Image.SetWidth called on incompatible receiver"); - return; - } - if (value->IsNumber()) { - Image *img = Nan::ObjectWrap::Unwrap(info.This()); - img->width = Nan::To(value).FromMaybe(0); +void +Image::SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsNumber()) { + width = value.As().Uint32Value(); } } @@ -176,40 +156,27 @@ NAN_SETTER(Image::SetWidth) { * Get natural height */ -NAN_GETTER(Image::GetNaturalHeight) { - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - Nan::ThrowTypeError("Method Image.GetNaturalHeight called on incompatible receiver"); - return; - } - Image *img = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(img->naturalHeight)); +Napi::Value +Image::GetNaturalHeight(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, naturalHeight); } /* * Get height. */ -NAN_GETTER(Image::GetHeight) { - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - Nan::ThrowTypeError("Method Image.GetHeight called on incompatible receiver"); - return; - } - Image *img = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(img->height)); +Napi::Value +Image::GetHeight(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, height); } /* * Set height. */ -NAN_SETTER(Image::SetHeight) { - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - // #1534 - Nan::ThrowTypeError("Method Image.SetHeight called on incompatible receiver"); - return; - } - if (value->IsNumber()) { - Image *img = Nan::ObjectWrap::Unwrap(info.This()); - img->height = Nan::To(value).FromMaybe(0); +void +Image::SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value) { + if (value.IsNumber()) { + height = value.As().Uint32Value(); } } @@ -217,14 +184,11 @@ NAN_SETTER(Image::SetHeight) { * Get src path. */ -NAN_METHOD(Image::GetSource){ - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - // #1534 - Nan::ThrowTypeError("Method Image.GetSource called on incompatible receiver"); - return; - } - Image *img = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(img->filename ? img->filename : "").ToLocalChecked()); +Napi::Value +Image::GetSource(const Napi::CallbackInfo& info){ + Napi::Env env = info.Env(); + Image *img = Image::Unwrap(info.This().As()); + return Napi::String::New(env, img->filename ? img->filename : ""); } /* @@ -235,7 +199,7 @@ void Image::clearData() { if (_surface) { cairo_surface_destroy(_surface); - Nan::AdjustExternalMemory(-_data_len); + Napi::MemoryManagement::AdjustExternalMemory(env, -_data_len); _data_len = 0; _surface = NULL; } @@ -262,55 +226,49 @@ Image::clearData() { * Set src path. */ -NAN_METHOD(Image::SetSource){ - if (!Image::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - // #1534 - Nan::ThrowTypeError("Method Image.SetSource called on incompatible receiver"); - return; - } - Image *img = Nan::ObjectWrap::Unwrap(info.This()); +void +Image::SetSource(const Napi::CallbackInfo& info){ + Napi::Env env = info.Env(); + Napi::Object This = info.This().As(); + Image *img = Image::Unwrap(This); + cairo_status_t status = CAIRO_STATUS_READ_ERROR; - Local value = info[0]; + Napi::Value value = info[0]; img->clearData(); // Clear errno in case some unrelated previous syscall failed errno = 0; // url string - if (value->IsString()) { - Nan::Utf8String src(value); + if (value.IsString()) { + std::string src = value.As().Utf8Value(); if (img->filename) free(img->filename); - img->filename = strdup(*src); + img->filename = strdup(src.c_str()); status = img->load(); // Buffer - } else if (node::Buffer::HasInstance(value)) { - uint8_t *buf = (uint8_t *) node::Buffer::Data(Nan::To(value).ToLocalChecked()); - unsigned len = node::Buffer::Length(Nan::To(value).ToLocalChecked()); + } else if (value.IsBuffer()) { + uint8_t *buf = value.As>().Data(); + unsigned len = value.As>().Length(); status = img->loadFromBuffer(buf, len); } if (status) { - Local onerrorFn = Nan::Get(info.This(), Nan::New("onerror").ToLocalChecked()).ToLocalChecked(); - if (onerrorFn->IsFunction()) { - Local argv[1]; - CanvasError errorInfo = img->errorInfo; - if (errorInfo.cerrno) { - argv[0] = Nan::ErrnoException(errorInfo.cerrno, errorInfo.syscall.c_str(), errorInfo.message.c_str(), errorInfo.path.c_str()); - } else if (!errorInfo.message.empty()) { - argv[0] = Nan::Error(Nan::New(errorInfo.message).ToLocalChecked()); + Napi::Value onerrorFn; + if (This.Get("onerror").UnwrapTo(&onerrorFn) && onerrorFn.IsFunction()) { + Napi::Error arg; + if (img->errorInfo.empty()) { + arg = Napi::Error::New(env, Napi::String::New(env, cairo_status_to_string(status))); } else { - argv[0] = Nan::Error(Nan::New(cairo_status_to_string(status)).ToLocalChecked()); + arg = img->errorInfo.toError(env); } - Local ctx = Nan::GetCurrentContext(); - Nan::Call(onerrorFn.As(), ctx->Global(), 1, argv); + onerrorFn.As().Call({ arg.Value() }); } } else { img->loaded(); - Local onloadFn = Nan::Get(info.This(), Nan::New("onload").ToLocalChecked()).ToLocalChecked(); - if (onloadFn->IsFunction()) { - Local ctx = Nan::GetCurrentContext(); - Nan::Call(onloadFn.As(), ctx->Global(), 0, NULL); + Napi::Value onloadFn; + if (This.Get("onload").UnwrapTo(&onloadFn) && onloadFn.IsFunction()) { + onloadFn.As().Call({}); } } } @@ -380,6 +338,7 @@ Image::loadPNGFromBuffer(uint8_t *buf) { read_closure_t closure; closure.len = 0; closure.buf = buf; + closure.env = &env; _surface = cairo_image_surface_create_from_png_stream(readPNG, &closure); cairo_status_t status = cairo_surface_status(_surface); if (status) return status; @@ -398,25 +357,6 @@ Image::readPNG(void *c, uint8_t *data, unsigned int len) { return CAIRO_STATUS_SUCCESS; } -/* - * Initialize a new Image. - */ - -Image::Image() { - filename = NULL; - _data = nullptr; - _data_len = 0; - _surface = NULL; - width = height = 0; - naturalWidth = naturalHeight = 0; - state = DEFAULT; -#ifdef HAVE_RSVG - _rsvg = NULL; - _is_svg = false; - _svg_last_width = _svg_last_height = 0; -#endif -} - /* * Destroy image and associated surface. */ @@ -444,13 +384,13 @@ Image::load() { void Image::loaded() { - Nan::HandleScope scope; + Napi::HandleScope scope(env); state = COMPLETE; width = naturalWidth = cairo_image_surface_get_width(_surface); height = naturalHeight = cairo_image_surface_get_height(_surface); _data_len = naturalHeight * cairo_image_surface_get_stride(_surface); - Nan::AdjustExternalMemory(_data_len); + Napi::MemoryManagement::AdjustExternalMemory(env, _data_len); } /* @@ -467,7 +407,8 @@ cairo_surface_t *Image::surface() { cairo_status_t status = renderSVGToSurface(); if (status != CAIRO_STATUS_SUCCESS) { g_object_unref(_rsvg); - Nan::ThrowError(Canvas::Error(status)); + Napi::Error::New(env, cairo_status_to_string(status)).ThrowAsJavaScriptException(); + return NULL; } } @@ -1010,7 +951,8 @@ Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) { void clearMimeData(void *closure) { - Nan::AdjustExternalMemory( + Napi::MemoryManagement::AdjustExternalMemory( + *static_cast(closure)->env, -static_cast((static_cast(closure)->len))); free(static_cast(closure)->buf); free(closure); @@ -1039,10 +981,11 @@ Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) { memcpy(mime_data, data, len); + mime_closure->env = &env; mime_closure->buf = mime_data; mime_closure->len = len; - Nan::AdjustExternalMemory(len); + Napi::MemoryManagement::AdjustExternalMemory(env, len); return cairo_surface_set_mime_data(_surface , mime_type diff --git a/src/Image.h b/src/Image.h index 62bc3f13b..6b9b9593b 100644 --- a/src/Image.h +++ b/src/Image.h @@ -5,9 +5,8 @@ #include #include "CanvasError.h" #include -#include +#include #include // node < 7 uses libstdc++ on macOS which lacks complete c++11 -#include #ifdef HAVE_JPEG #include @@ -34,25 +33,26 @@ using JPEGDecodeL = std::function; -class Image: public Nan::ObjectWrap { +class Image : public Napi::ObjectWrap { public: char *filename; int width, height; int naturalWidth, naturalHeight; - static Nan::Persistent constructor; - static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target); - static NAN_METHOD(New); - static NAN_GETTER(GetComplete); - static NAN_GETTER(GetWidth); - static NAN_GETTER(GetHeight); - static NAN_GETTER(GetNaturalWidth); - static NAN_GETTER(GetNaturalHeight); - static NAN_GETTER(GetDataMode); - static NAN_SETTER(SetDataMode); - static NAN_SETTER(SetWidth); - static NAN_SETTER(SetHeight); - static NAN_METHOD(GetSource); - static NAN_METHOD(SetSource); + Napi::Env env; + static Napi::FunctionReference constructor; + static void Initialize(Napi::Env& env, Napi::Object& target); + Image(const Napi::CallbackInfo& info); + Napi::Value GetComplete(const Napi::CallbackInfo& info); + Napi::Value GetWidth(const Napi::CallbackInfo& info); + Napi::Value GetHeight(const Napi::CallbackInfo& info); + Napi::Value GetNaturalWidth(const Napi::CallbackInfo& info); + Napi::Value GetNaturalHeight(const Napi::CallbackInfo& info); + Napi::Value GetDataMode(const Napi::CallbackInfo& info); + void SetDataMode(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value); + void SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value); + static Napi::Value GetSource(const Napi::CallbackInfo& info); + static void SetSource(const Napi::CallbackInfo& info); inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); } inline int stride(){ return cairo_image_surface_get_stride(_surface); } static int isPNG(uint8_t *data); @@ -90,7 +90,7 @@ class Image: public Nan::ObjectWrap { CanvasError errorInfo; void loaded(); cairo_status_t load(); - Image(); + ~Image(); enum { DEFAULT @@ -123,5 +123,4 @@ class Image: public Nan::ObjectWrap { int _svg_last_width; int _svg_last_height; #endif - ~Image(); }; diff --git a/src/ImageData.cc b/src/ImageData.cc index 03da2e270..b9f556bb3 100644 --- a/src/ImageData.cc +++ b/src/ImageData.cc @@ -1,146 +1,132 @@ // Copyright (c) 2010 LearnBoost #include "ImageData.h" - -using namespace v8; - -Nan::Persistent ImageData::constructor; +#include "InstanceData.h" /* * Initialize ImageData. */ void -ImageData::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) { - Nan::HandleScope scope; - - // Constructor - Local ctor = Nan::New(ImageData::New); - constructor.Reset(ctor); - ctor->InstanceTemplate()->SetInternalFieldCount(1); - ctor->SetClassName(Nan::New("ImageData").ToLocalChecked()); - - // Prototype - Local proto = ctor->PrototypeTemplate(); - Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth); - Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight); - Local ctx = Nan::GetCurrentContext(); - Nan::Set(target, Nan::New("ImageData").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked()); +ImageData::Initialize(Napi::Env& env, Napi::Object& exports) { + Napi::HandleScope scope(env); + + InstanceData *data = env.GetInstanceData(); + + Napi::Function ctor = DefineClass(env, "ImageData", { + InstanceAccessor<&ImageData::GetWidth>("width"), + InstanceAccessor<&ImageData::GetHeight>("height") + }); + + exports.Set("ImageData", ctor); + data->ImageDataCtor = Napi::Persistent(ctor); } /* * Initialize a new ImageData object. */ -NAN_METHOD(ImageData::New) { - if (!info.IsConstructCall()) { - return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'"); - } - - Local dataArray; +ImageData::ImageData(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info), env(info.Env()) { + Napi::TypedArray dataArray; uint32_t width; uint32_t height; int length; - if (info[0]->IsUint32() && info[1]->IsUint32()) { - width = Nan::To(info[0]).FromMaybe(0); + if (info[0].IsNumber() && info[1].IsNumber()) { + width = info[0].As().Uint32Value(); if (width == 0) { - Nan::ThrowRangeError("The source width is zero."); + Napi::RangeError::New(env, "The source width is zero.").ThrowAsJavaScriptException(); return; } - height = Nan::To(info[1]).FromMaybe(0); + height = info[1].As().Uint32Value(); if (height == 0) { - Nan::ThrowRangeError("The source height is zero."); + Napi::RangeError::New(env, "The source height is zero.").ThrowAsJavaScriptException(); return; } length = width * height * 4; // ImageData(w, h) constructor assumes 4 BPP; documented. - dataArray = Uint8ClampedArray::New(ArrayBuffer::New(Isolate::GetCurrent(), length), 0, length); + dataArray = Napi::Uint8Array::New(env, length, napi_uint8_clamped_array); + } else if ( + info[0].IsTypedArray() && + info[0].As().TypedArrayType() == napi_uint8_clamped_array && + info[1].IsNumber() + ) { + dataArray = info[0].As(); - } else if (info[0]->IsUint8ClampedArray() && info[1]->IsUint32()) { - dataArray = info[0].As(); - - length = dataArray->Length(); + length = dataArray.ElementLength(); if (length == 0) { - Nan::ThrowRangeError("The input data has a zero byte length."); + Napi::RangeError::New(env, "The input data has a zero byte length.").ThrowAsJavaScriptException(); return; } // Don't assert that the ImageData length is a multiple of four because some // data formats are not 4 BPP. - width = Nan::To(info[1]).FromMaybe(0); + width = info[1].As().Uint32Value(); if (width == 0) { - Nan::ThrowRangeError("The source width is zero."); + Napi::RangeError::New(env, "The source width is zero.").ThrowAsJavaScriptException(); return; } // Don't assert that the byte length is a multiple of 4 * width, ditto. - if (info[2]->IsUint32()) { // Explicit height given - height = Nan::To(info[2]).FromMaybe(0); + if (info[2].IsNumber()) { // Explicit height given + height = info[2].As().Uint32Value(); } else { // Calculate height assuming 4 BPP int size = length / 4; height = size / width; } + } else if ( + info[0].IsTypedArray() && + info[0].As().TypedArrayType() == napi_uint16_array && + info[1].IsNumber() + ) { // Intended for RGB16_565 format + dataArray = info[0].As(); + + length = dataArray.ElementLength(); + if (length == 0) { + Napi::RangeError::New(env, "The input data has a zero byte length.").ThrowAsJavaScriptException(); + return; + } - } else if (info[0]->IsUint16Array() && info[1]->IsUint32()) { // Intended for RGB16_565 format - dataArray = info[0].As(); - - length = dataArray->Length(); - if (length == 0) { - Nan::ThrowRangeError("The input data has a zero byte length."); - return; - } - - width = Nan::To(info[1]).FromMaybe(0); - if (width == 0) { - Nan::ThrowRangeError("The source width is zero."); - return; - } - - if (info[2]->IsUint32()) { // Explicit height given - height = Nan::To(info[2]).FromMaybe(0); - } else { // Calculate height assuming 2 BPP - int size = length / 2; - height = size / width; - } + width = info[1].As().Uint32Value(); + if (width == 0) { + Napi::RangeError::New(env, "The source width is zero.").ThrowAsJavaScriptException(); + return; + } + if (info[2].IsNumber()) { // Explicit height given + height = info[2].As().Uint32Value(); + } else { // Calculate height assuming 2 BPP + int size = length / 2; + height = size / width; + } } else { - Nan::ThrowTypeError("Expected (Uint8ClampedArray, width[, height]), (Uint16Array, width[, height]) or (width, height)"); + Napi::TypeError::New(env, "Expected (Uint8ClampedArray, width[, height]), (Uint16Array, width[, height]) or (width, height)").ThrowAsJavaScriptException(); return; } - Nan::TypedArrayContents dataPtr(dataArray); + _width = width; + _height = height; + _data = dataArray.As().Data(); - ImageData *imageData = new ImageData(reinterpret_cast(*dataPtr), width, height); - imageData->Wrap(info.This()); - Nan::Set(info.This(), Nan::New("data").ToLocalChecked(), dataArray).Check(); - info.GetReturnValue().Set(info.This()); + info.This().As().Set("data", dataArray); } /* * Get width. */ -NAN_GETTER(ImageData::GetWidth) { - if (!ImageData::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - Nan::ThrowTypeError("Method ImageData.GetWidth called on incompatible receiver"); - return; - } - ImageData *imageData = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(imageData->width())); +Napi::Value +ImageData::GetWidth(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, width()); } /* * Get height. */ -NAN_GETTER(ImageData::GetHeight) { - if (!ImageData::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { - Nan::ThrowTypeError("Method ImageData.GetHeight called on incompatible receiver"); - return; - } - ImageData *imageData = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(imageData->height())); +Napi::Value +ImageData::GetHeight(const Napi::CallbackInfo& info) { + return Napi::Number::New(env, height()); } diff --git a/src/ImageData.h b/src/ImageData.h index 4832b37b2..32d6037d1 100644 --- a/src/ImageData.h +++ b/src/ImageData.h @@ -2,22 +2,21 @@ #pragma once -#include +#include #include // node < 7 uses libstdc++ on macOS which lacks complete c++11 -#include -class ImageData: public Nan::ObjectWrap { +class ImageData : public Napi::ObjectWrap { public: - static Nan::Persistent constructor; - static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target); - static NAN_METHOD(New); - static NAN_GETTER(GetWidth); - static NAN_GETTER(GetHeight); + static void Initialize(Napi::Env& env, Napi::Object& exports); + ImageData(const Napi::CallbackInfo& info); + Napi::Value GetWidth(const Napi::CallbackInfo& info); + Napi::Value GetHeight(const Napi::CallbackInfo& info); inline int width() { return _width; } inline int height() { return _height; } inline uint8_t *data() { return _data; } - ImageData(uint8_t *data, int width, int height) : _width(width), _height(height), _data(data) {} + + Napi::Env env; private: int _width; diff --git a/src/InstanceData.h b/src/InstanceData.h new file mode 100644 index 000000000..939f2a488 --- /dev/null +++ b/src/InstanceData.h @@ -0,0 +1,15 @@ +#include + +struct InstanceData { + Napi::FunctionReference ImageBackendCtor; + Napi::FunctionReference PdfBackendCtor; + Napi::FunctionReference SvgBackendCtor; + Napi::FunctionReference CanvasCtor; + Napi::FunctionReference CanvasGradientCtor; + Napi::FunctionReference DOMMatrixCtor; + Napi::FunctionReference ImageCtor; + Napi::FunctionReference parseFont; + Napi::FunctionReference Context2dCtor; + Napi::FunctionReference ImageDataCtor; + Napi::FunctionReference CanvasPatternCtor; +}; diff --git a/src/JPEGStream.h b/src/JPEGStream.h index b8efeed21..43c74f139 100644 --- a/src/JPEGStream.h +++ b/src/JPEGStream.h @@ -23,18 +23,15 @@ init_closure_destination(j_compress_ptr cinfo){ boolean empty_closure_output_buffer(j_compress_ptr cinfo){ - Nan::HandleScope scope; - Nan::AsyncResource async("canvas:empty_closure_output_buffer"); closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest; + Napi::Env env = dest->closure->canvas->Env(); + Napi::HandleScope scope(env); + Napi::AsyncContext async(env, "canvas:empty_closure_output_buffer"); - v8::Local buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize).ToLocalChecked(); + Napi::Object buf = Napi::Buffer::New(env, (char *)dest->buffer, dest->bufsize); // emit "data" - v8::Local argv[2] = { - Nan::Null() - , buf - }; - dest->closure->cb.Call(sizeof argv / sizeof *argv, argv, &async); + dest->closure->cb.MakeCallback(env.Global(), {env.Null(), buf}, async); dest->buffer = (JOCTET *)malloc(dest->bufsize); cinfo->dest->next_output_byte = dest->buffer; @@ -44,25 +41,18 @@ empty_closure_output_buffer(j_compress_ptr cinfo){ void term_closure_destination(j_compress_ptr cinfo){ - Nan::HandleScope scope; - Nan::AsyncResource async("canvas:term_closure_destination"); closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest; + Napi::Env env = dest->closure->canvas->Env(); + Napi::HandleScope scope(env); + Napi::AsyncContext async(env, "canvas:term_closure_destination"); /* emit remaining data */ - v8::Local buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize - dest->pub.free_in_buffer).ToLocalChecked(); + Napi::Object buf = Napi::Buffer::New(env, (char *)dest->buffer, dest->bufsize - dest->pub.free_in_buffer); - v8::Local data_argv[2] = { - Nan::Null() - , buf - }; - dest->closure->cb.Call(sizeof data_argv / sizeof *data_argv, data_argv, &async); + dest->closure->cb.MakeCallback(env.Global(), {env.Null(), buf}, async); // emit "end" - v8::Local end_argv[2] = { - Nan::Null() - , Nan::Null() - }; - dest->closure->cb.Call(sizeof end_argv / sizeof *end_argv, end_argv, &async); + dest->closure->cb.MakeCallback(env.Global(), {env.Null(), env.Null()}, async); } void diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 9f2b39dd3..14d67e7b5 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -1,29 +1,21 @@ #include "Backend.h" #include +#include -Backend::Backend(std::string name, int width, int height) - : name(name) - , width(width) - , height(height) -{} +Backend::Backend(std::string name, Napi::CallbackInfo& info) : name(name), env(info.Env()) { + int width = 0; + int height = 0; + if (info[0].IsNumber()) width = info[0].As().Int32Value(); + if (info[1].IsNumber()) height = info[1].As().Int32Value(); + this->width = width; + this->height = height; +} Backend::~Backend() { Backend::destroySurface(); } -void Backend::init(const Nan::FunctionCallbackInfo &info) { - int width = 0; - int height = 0; - if (info[0]->IsNumber()) width = Nan::To(info[0]).FromMaybe(0); - if (info[1]->IsNumber()) height = Nan::To(info[1]).FromMaybe(0); - - Backend *backend = construct(width, height); - - backend->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); -} - void Backend::setCanvas(Canvas* _canvas) { this->canvas = _canvas; diff --git a/src/backend/Backend.h b/src/backend/Backend.h index f8448c41a..d23573b6e 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -3,13 +3,12 @@ #include #include "../dll_visibility.h" #include -#include +#include #include -#include class Canvas; -class Backend : public Nan::ObjectWrap +class Backend { private: const std::string name; @@ -21,11 +20,11 @@ class Backend : public Nan::ObjectWrap cairo_surface_t* surface = nullptr; Canvas* canvas = nullptr; - Backend(std::string name, int width, int height); - static void init(const Nan::FunctionCallbackInfo &info); - static Backend *construct(int width, int height){ return nullptr; } + Backend(std::string name, Napi::CallbackInfo& info); public: + Napi::Env env; + virtual ~Backend(); void setCanvas(Canvas* canvas); diff --git a/src/backend/ImageBackend.cc b/src/backend/ImageBackend.cc index d354d92cc..682c56b18 100644 --- a/src/backend/ImageBackend.cc +++ b/src/backend/ImageBackend.cc @@ -1,16 +1,14 @@ #include "ImageBackend.h" +#include "../InstanceData.h" +#include +#include -using namespace v8; - -ImageBackend::ImageBackend(int width, int height) - : Backend("image", width, height) - {} - -Backend *ImageBackend::construct(int width, int height){ - return new ImageBackend(width, height); +ImageBackend::ImageBackend(Napi::CallbackInfo& info) : Napi::ObjectWrap(info), Backend("image", info) +{ } -// This returns an approximate value only, suitable for Nan::AdjustExternalMemory. +// This returns an approximate value only, suitable for +// Napi::MemoryManagement:: AdjustExternalMemory. // The formats that don't map to intrinsic types (RGB30, A1) round up. int32_t ImageBackend::approxBytesPerPixel() { switch (format) { @@ -35,7 +33,7 @@ cairo_surface_t* ImageBackend::createSurface() { assert(!surface); surface = cairo_image_surface_create(format, width, height); assert(surface); - Nan::AdjustExternalMemory(approxBytesPerPixel() * width * height); + Napi::MemoryManagement::AdjustExternalMemory(env, approxBytesPerPixel() * width * height); return surface; } @@ -43,7 +41,7 @@ void ImageBackend::destroySurface() { if (surface) { cairo_surface_destroy(surface); surface = nullptr; - Nan::AdjustExternalMemory(-approxBytesPerPixel() * width * height); + Napi::MemoryManagement::AdjustExternalMemory(env, -approxBytesPerPixel() * width * height); } } @@ -55,20 +53,11 @@ void ImageBackend::setFormat(cairo_format_t _format) { this->format = _format; } -Nan::Persistent ImageBackend::constructor; - -void ImageBackend::Initialize(Local target) { - Nan::HandleScope scope; - - Local ctor = Nan::New(ImageBackend::New); - ImageBackend::constructor.Reset(ctor); - ctor->InstanceTemplate()->SetInternalFieldCount(1); - ctor->SetClassName(Nan::New("ImageBackend").ToLocalChecked()); - Nan::Set(target, - Nan::New("ImageBackend").ToLocalChecked(), - Nan::GetFunction(ctor).ToLocalChecked()).Check(); -} +Napi::FunctionReference ImageBackend::constructor; -NAN_METHOD(ImageBackend::New) { - init(info); +void ImageBackend::Initialize(Napi::Object target) { + Napi::Env env = target.Env(); + Napi::Function ctor = DefineClass(env, "ImageBackend", {}); + InstanceData* data = env.GetInstanceData(); + data->ImageBackendCtor = Napi::Persistent(ctor); } diff --git a/src/backend/ImageBackend.h b/src/backend/ImageBackend.h index f68dacfdb..032907f0f 100644 --- a/src/backend/ImageBackend.h +++ b/src/backend/ImageBackend.h @@ -1,9 +1,9 @@ #pragma once #include "Backend.h" -#include +#include -class ImageBackend : public Backend +class ImageBackend : public Napi::ObjectWrap, public Backend { private: cairo_surface_t* createSurface(); @@ -11,16 +11,14 @@ class ImageBackend : public Backend cairo_format_t format = DEFAULT_FORMAT; public: - ImageBackend(int width, int height); - static Backend *construct(int width, int height); + ImageBackend(Napi::CallbackInfo& info); cairo_format_t getFormat(); void setFormat(cairo_format_t format); int32_t approxBytesPerPixel(); - static Nan::Persistent constructor; - static void Initialize(v8::Local target); - static NAN_METHOD(New); + static Napi::FunctionReference constructor; + static void Initialize(Napi::Object target); const static cairo_format_t DEFAULT_FORMAT = CAIRO_FORMAT_ARGB32; }; diff --git a/src/backend/PdfBackend.cc b/src/backend/PdfBackend.cc index fe831a68d..ce214a044 100644 --- a/src/backend/PdfBackend.cc +++ b/src/backend/PdfBackend.cc @@ -1,13 +1,11 @@ #include "PdfBackend.h" #include +#include "../InstanceData.h" #include "../Canvas.h" #include "../closure.h" -using namespace v8; - -PdfBackend::PdfBackend(int width, int height) - : Backend("pdf", width, height) { +PdfBackend::PdfBackend(Napi::CallbackInfo& info) : Napi::ObjectWrap(info), Backend("pdf", info) { PdfBackend::createSurface(); } @@ -17,10 +15,6 @@ PdfBackend::~PdfBackend() { destroySurface(); } -Backend *PdfBackend::construct(int width, int height){ - return new PdfBackend(width, height); -} - cairo_surface_t* PdfBackend::createSurface() { if (!_closure) _closure = new PdfSvgClosure(canvas); surface = cairo_pdf_surface_create_for_stream(PdfSvgClosure::writeVec, _closure, width, height); @@ -33,21 +27,10 @@ cairo_surface_t* PdfBackend::recreateSurface() { return surface; } - -Nan::Persistent PdfBackend::constructor; - -void PdfBackend::Initialize(Local target) { - Nan::HandleScope scope; - - Local ctor = Nan::New(PdfBackend::New); - PdfBackend::constructor.Reset(ctor); - ctor->InstanceTemplate()->SetInternalFieldCount(1); - ctor->SetClassName(Nan::New("PdfBackend").ToLocalChecked()); - Nan::Set(target, - Nan::New("PdfBackend").ToLocalChecked(), - Nan::GetFunction(ctor).ToLocalChecked()).Check(); -} - -NAN_METHOD(PdfBackend::New) { - init(info); +void +PdfBackend::Initialize(Napi::Object target) { + Napi::Env env = target.Env(); + InstanceData* data = env.GetInstanceData(); + Napi::Function ctor = DefineClass(env, "PdfBackend", {}); + data->PdfBackendCtor = Napi::Persistent(ctor); } diff --git a/src/backend/PdfBackend.h b/src/backend/PdfBackend.h index 03656f500..59aa0fedd 100644 --- a/src/backend/PdfBackend.h +++ b/src/backend/PdfBackend.h @@ -2,9 +2,9 @@ #include "Backend.h" #include "../closure.h" -#include +#include -class PdfBackend : public Backend +class PdfBackend : public Napi::ObjectWrap, public Backend { private: cairo_surface_t* createSurface(); @@ -14,11 +14,10 @@ class PdfBackend : public Backend PdfSvgClosure* _closure = NULL; inline PdfSvgClosure* closure() { return _closure; } - PdfBackend(int width, int height); + PdfBackend(Napi::CallbackInfo& info); ~PdfBackend(); - static Backend *construct(int width, int height); - static Nan::Persistent constructor; - static void Initialize(v8::Local target); - static NAN_METHOD(New); + static Napi::FunctionReference constructor; + static void Initialize(Napi::Object target); + static Napi::Value New(const Napi::CallbackInfo& info); }; diff --git a/src/backend/SvgBackend.cc b/src/backend/SvgBackend.cc index 7d4181fc2..530d0b571 100644 --- a/src/backend/SvgBackend.cc +++ b/src/backend/SvgBackend.cc @@ -1,14 +1,15 @@ #include "SvgBackend.h" #include +#include #include "../Canvas.h" #include "../closure.h" +#include "../InstanceData.h" #include -using namespace v8; +using namespace Napi; -SvgBackend::SvgBackend(int width, int height) - : Backend("svg", width, height) { +SvgBackend::SvgBackend(Napi::CallbackInfo& info) : Napi::ObjectWrap(info), Backend("svg", info) { SvgBackend::createSurface(); } @@ -21,10 +22,6 @@ SvgBackend::~SvgBackend() { destroySurface(); } -Backend *SvgBackend::construct(int width, int height){ - return new SvgBackend(width, height); -} - cairo_surface_t* SvgBackend::createSurface() { assert(!_closure); _closure = new PdfSvgClosure(canvas); @@ -42,20 +39,10 @@ cairo_surface_t* SvgBackend::recreateSurface() { } -Nan::Persistent SvgBackend::constructor; - -void SvgBackend::Initialize(Local target) { - Nan::HandleScope scope; - - Local ctor = Nan::New(SvgBackend::New); - SvgBackend::constructor.Reset(ctor); - ctor->InstanceTemplate()->SetInternalFieldCount(1); - ctor->SetClassName(Nan::New("SvgBackend").ToLocalChecked()); - Nan::Set(target, - Nan::New("SvgBackend").ToLocalChecked(), - Nan::GetFunction(ctor).ToLocalChecked()).Check(); -} - -NAN_METHOD(SvgBackend::New) { - init(info); +void +SvgBackend::Initialize(Napi::Object target) { + Napi::Env env = target.Env(); + Napi::Function ctor = DefineClass(env, "SvgBackend", {}); + InstanceData* data = env.GetInstanceData(); + data->SvgBackendCtor = Napi::Persistent(ctor); } diff --git a/src/backend/SvgBackend.h b/src/backend/SvgBackend.h index 6377b438b..301ec831c 100644 --- a/src/backend/SvgBackend.h +++ b/src/backend/SvgBackend.h @@ -2,9 +2,9 @@ #include "Backend.h" #include "../closure.h" -#include +#include -class SvgBackend : public Backend +class SvgBackend : public Napi::ObjectWrap, public Backend { private: cairo_surface_t* createSurface(); @@ -14,11 +14,8 @@ class SvgBackend : public Backend PdfSvgClosure* _closure = NULL; inline PdfSvgClosure* closure() { return _closure; } - SvgBackend(int width, int height); + SvgBackend(Napi::CallbackInfo& info); ~SvgBackend(); - static Backend *construct(int width, int height); - static Nan::Persistent constructor; - static void Initialize(v8::Local target); - static NAN_METHOD(New); + static void Initialize(Napi::Object target); }; diff --git a/src/closure.cc b/src/closure.cc index e821e7f22..3290db2e5 100644 --- a/src/closure.cc +++ b/src/closure.cc @@ -1,4 +1,5 @@ #include "closure.h" +#include "Canvas.h" #ifdef HAVE_JPEG void JpegClosure::init_destination(j_compress_ptr cinfo) { @@ -24,3 +25,28 @@ void JpegClosure::term_destination(j_compress_ptr cinfo) { } #endif +void +EncodingWorker::Init(void (*work_fn)(Closure*), Closure* closure) { + this->work_fn = work_fn; + this->closure = closure; +} + +void +EncodingWorker::Execute() { + this->work_fn(this->closure); +} + +void +EncodingWorker::OnWorkComplete(Napi::Env env, napi_status status) { + Napi::HandleScope scope(env); + + if (closure->status) { + closure->cb.Call({ closure->canvas->CairoError(closure->status).Value() }); + } else { + Napi::Object buf = Napi::Buffer::Copy(env, &closure->vec[0], closure->vec.size()); + closure->cb.Call({ env.Null(), buf }); + } + + closure->canvas->Unref(); + delete closure; +} diff --git a/src/closure.h b/src/closure.h index 3126114eb..ce5ec489c 100644 --- a/src/closure.h +++ b/src/closure.h @@ -8,7 +8,7 @@ #include #endif -#include +#include #include #include // node < 7 uses libstdc++ on macOS which lacks complete c++11 #include @@ -23,7 +23,7 @@ struct Closure { std::vector vec; - Nan::Callback cb; + Napi::FunctionReference cb; Canvas* canvas = nullptr; cairo_status_t status = CAIRO_STATUS_SUCCESS; @@ -79,3 +79,15 @@ struct JpegClosure : Closure { } }; #endif + +class EncodingWorker : public Napi::AsyncWorker { + public: + EncodingWorker(Napi::Env env): Napi::AsyncWorker(env) {}; + void Init(void (*work_fn)(Closure*), Closure* closure); + void Execute() override; + void OnWorkComplete(Napi::Env env, napi_status status) override; + + private: + void (*work_fn)(Closure*) = nullptr; + Closure* closure = nullptr; +}; diff --git a/src/init.cc b/src/init.cc index fd143973e..ad9207846 100644 --- a/src/init.cc +++ b/src/init.cc @@ -21,27 +21,47 @@ #include "CanvasRenderingContext2d.h" #include "Image.h" #include "ImageData.h" +#include "InstanceData.h" #include #include FT_FREETYPE_H -using namespace v8; +/* + * Save some external modules as private references. + */ + +static void +setDOMMatrix(const Napi::CallbackInfo& info) { + InstanceData* data = info.Env().GetInstanceData(); + data->DOMMatrixCtor = Napi::Persistent(info[0].As()); +} + +static void +setParseFont(const Napi::CallbackInfo& info) { + InstanceData* data = info.Env().GetInstanceData(); + data->parseFont = Napi::Persistent(info[0].As()); +} // Compatibility with Visual Studio versions prior to VS2015 #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf _snprintf #endif -NAN_MODULE_INIT(init) { - Backends::Initialize(target); - Canvas::Initialize(target); - Image::Initialize(target); - ImageData::Initialize(target); - Context2d::Initialize(target); - Gradient::Initialize(target); - Pattern::Initialize(target); +Napi::Object init(Napi::Env env, Napi::Object exports) { + env.SetInstanceData(new InstanceData()); - Nan::Set(target, Nan::New("cairoVersion").ToLocalChecked(), Nan::New(cairo_version_string()).ToLocalChecked()).Check(); + Backends::Initialize(env, exports); + Canvas::Initialize(env, exports); + Image::Initialize(env, exports); + ImageData::Initialize(env, exports); + Context2d::Initialize(env, exports); + Gradient::Initialize(env, exports); + Pattern::Initialize(env, exports); + + exports.Set("setDOMMatrix", Napi::Function::New(env, &setDOMMatrix)); + exports.Set("setParseFont", Napi::Function::New(env, &setParseFont)); + + exports.Set("cairoVersion", Napi::String::New(env, cairo_version_string())); #ifdef HAVE_JPEG #ifndef JPEG_LIB_VERSION_MAJOR @@ -67,28 +87,30 @@ NAN_MODULE_INIT(init) { } else { snprintf(jpeg_version, 10, "%d", JPEG_LIB_VERSION_MAJOR); } - Nan::Set(target, Nan::New("jpegVersion").ToLocalChecked(), Nan::New(jpeg_version).ToLocalChecked()).Check(); + exports.Set("jpegVersion", Napi::String::New(env, jpeg_version)); #endif #ifdef HAVE_GIF #ifndef GIF_LIB_VERSION char gif_version[10]; snprintf(gif_version, 10, "%d.%d.%d", GIFLIB_MAJOR, GIFLIB_MINOR, GIFLIB_RELEASE); - Nan::Set(target, Nan::New("gifVersion").ToLocalChecked(), Nan::New(gif_version).ToLocalChecked()).Check(); + exports.Set("gifVersion", Napi::String::New(env, gif_version)); #else - Nan::Set(target, Nan::New("gifVersion").ToLocalChecked(), Nan::New(GIF_LIB_VERSION).ToLocalChecked()).Check(); + exports.Set("gifVersion", Napi::String::New(env, GIF_LIB_VERSION)); #endif #endif #ifdef HAVE_RSVG - Nan::Set(target, Nan::New("rsvgVersion").ToLocalChecked(), Nan::New(LIBRSVG_VERSION).ToLocalChecked()).Check(); + exports.Set("rsvgVersion", Napi::String::New(env, LIBRSVG_VERSION)); #endif - Nan::Set(target, Nan::New("pangoVersion").ToLocalChecked(), Nan::New(PANGO_VERSION_STRING).ToLocalChecked()).Check(); + exports.Set("pangoVersion", Napi::String::New(env, PANGO_VERSION_STRING)); char freetype_version[10]; snprintf(freetype_version, 10, "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); - Nan::Set(target, Nan::New("freetypeVersion").ToLocalChecked(), Nan::New(freetype_version).ToLocalChecked()).Check(); + exports.Set("freetypeVersion", Napi::String::New(env, freetype_version)); + + return exports; } -NODE_MODULE(canvas, init); +NODE_API_MODULE(canvas, init); diff --git a/test/canvas.test.js b/test/canvas.test.js index 9573688f5..c3b83b271 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -31,7 +31,7 @@ describe('Canvas', function () { it('Prototype and ctor are well-shaped, don\'t hit asserts on accessors (GH-803)', function () { const c = new Canvas(10, 10) - assert.throws(function () { Canvas.prototype.width }, /incompatible receiver/) + assert.throws(function () { Canvas.prototype.width }, /invalid argument/i) assert(!c.hasOwnProperty('width')) assert('width' in c) assert('width' in Canvas.prototype) diff --git a/test/image.test.js b/test/image.test.js index ec1631a10..a5d6f415c 100644 --- a/test/image.test.js +++ b/test/image.test.js @@ -24,7 +24,7 @@ const bmpDir = path.join(__dirname, '/fixtures/bmp') describe('Image', function () { it('Prototype and ctor are well-shaped, don\'t hit asserts on accessors (GH-803)', function () { const img = new Image() - assert.throws(function () { Image.prototype.width }, /incompatible receiver/) + assert.throws(function () { Image.prototype.width }, /invalid argument/i) assert(!img.hasOwnProperty('width')) assert('width' in img) assert(Image.prototype.hasOwnProperty('width')) @@ -182,7 +182,7 @@ describe('Image', function () { it('returns a nice, coded error for fopen failures', function (done) { const img = new Image() img.onerror = err => { - assert.equal(err.code, 'ENOENT') + assert.equal(err.message, 'No such file or directory') assert.equal(err.path, 'path/to/nothing') assert.equal(err.syscall, 'fopen') assert.strictEqual(img.complete, true) diff --git a/test/imageData.test.js b/test/imageData.test.js index 04b117b45..774bcf14e 100644 --- a/test/imageData.test.js +++ b/test/imageData.test.js @@ -9,7 +9,7 @@ const assert = require('assert') describe('ImageData', function () { it('Prototype and ctor are well-shaped, don\'t hit asserts on accessors (GH-803)', function () { - assert.throws(function () { ImageData.prototype.width }, /incompatible receiver/) + assert.throws(function () { ImageData.prototype.width }, /invalid argument/i) }) it('stringifies as [object ImageData]', function () {