Ui
yfx
2026-03-13 ad8ef500e27b88fa6ef8d4ed39ccc8bb9264139c
Ui
239 files added
43359 ■■■■■ changed files
.hbuilderx/launch.json 14 ●●●●● patch | view | raw | blame | history
node_modules/.package-lock.json 12 ●●●●● patch | view | raw | blame | history
node_modules/canvas/CHANGELOG.md 1052 ●●●●● patch | view | raw | blame | history
node_modules/canvas/Readme.md 600 ●●●●● patch | view | raw | blame | history
node_modules/canvas/binding.gyp 230 ●●●●● patch | view | raw | blame | history
node_modules/canvas/browser.js 35 ●●●●● patch | view | raw | blame | history
node_modules/canvas/build/Release/canvas.exp patch | view | raw | blame | history
node_modules/canvas/build/Release/canvas.iobj patch | view | raw | blame | history
node_modules/canvas/build/Release/canvas.ipdb patch | view | raw | blame | history
node_modules/canvas/build/Release/canvas.lib patch | view | raw | blame | history
node_modules/canvas/build/Release/canvas.node patch | view | raw | blame | history
node_modules/canvas/build/Release/canvas.pdb patch | view | raw | blame | history
node_modules/canvas/build/Release/libbrotlicommon.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libbrotlidec.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libbz2-1.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libcairo-2.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libcairo-gobject-2.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libdatrie-1.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libexpat-1.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libffi-8.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libfontconfig-1.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libfreetype-6.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libfribidi-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libgcc_s_seh-1.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libgdk_pixbuf-2.0-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libgif-7.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libgio-2.0-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libglib-2.0-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libgmodule-2.0-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libgobject-2.0-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libgraphite2.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libharfbuzz-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libiconv-2.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libintl-8.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libjpeg-8.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/liblzma-5.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libpango-1.0-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libpangocairo-1.0-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libpangoft2-1.0-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libpangowin32-1.0-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libpcre2-8-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libpixman-1-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libpng16-16.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/librsvg-2-2.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libstdc++-6.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libthai-0.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libwinpthread-1.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/libxml2-2.dll patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/canvas.node.recipe 11 ●●●●● patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/CL.command.1.tlog patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/CL.read.1.tlog patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/CL.write.1.tlog patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/canvas.lastbuildstate 2 ●●●●● patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/canvas.write.1u.tlog patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/link.command.1.tlog patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/link.read.1.tlog patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/link.write.1.tlog patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/Backends.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/Canvas.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/CanvasGradient.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/CanvasPattern.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/CanvasRenderingContext2d.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/Image.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/ImageData.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/backend/Backend.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/backend/ImageBackend.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/backend/PdfBackend.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/backend/SvgBackend.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/bmp/BMPParser.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/closure.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/color.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/init.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/src/register_font.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/obj/canvas/win_delay_load_hook.obj patch | view | raw | blame | history
node_modules/canvas/build/Release/zlib1.dll patch | view | raw | blame | history
node_modules/canvas/changes 19 ●●●●● patch | view | raw | blame | history
node_modules/canvas/index.js 94 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/DOMMatrix.js 620 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/bindings.js 13 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/canvas.js 113 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/context2d.js 14 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/image.js 96 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/jpegstream.js 41 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/parse-font.js 101 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/pattern.js 17 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/pdfstream.js 35 ●●●●● patch | view | raw | blame | history
node_modules/canvas/lib/pngstream.js 42 ●●●●● patch | view | raw | blame | history
node_modules/canvas/package.json 72 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/Backends.cc 18 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/Backends.h 10 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/Canvas.cc 965 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/Canvas.h 96 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/CanvasError.h 23 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/CanvasGradient.cc 123 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/CanvasGradient.h 22 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/CanvasPattern.cc 136 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/CanvasPattern.h 37 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/CanvasRenderingContext2d.cc 3360 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/CanvasRenderingContext2d.cc.orig 3381 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/CanvasRenderingContext2d.h 225 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/CanvasRenderingContext2d.h.orig 226 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/Image.cc 1434 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/Image.h 127 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/ImageData.cc 146 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/ImageData.h 27 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/InstanceData.h 8 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/JPEGStream.h 167 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/PNG.h 292 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/Point.h 11 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/Util.h 9 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/backend/Backend.cc 112 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/backend/Backend.h 69 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/backend/ImageBackend.cc 74 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/backend/ImageBackend.h 26 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/backend/PdfBackend.cc 53 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/backend/PdfBackend.h 24 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/backend/SvgBackend.cc 61 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/backend/SvgBackend.h 24 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/bmp/BMPParser.cc 457 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/bmp/BMPParser.h 60 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/bmp/LICENSE.md 24 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/closure.cc 26 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/closure.h 81 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/color.cc 779 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/color.h 30 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/dll_visibility.h 20 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/init.cc 94 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/register_font.cc 408 ●●●●● patch | view | raw | blame | history
node_modules/canvas/src/register_font.h 7 ●●●●● patch | view | raw | blame | history
node_modules/canvas/types/Readme.md 3 ●●●●● patch | view | raw | blame | history
node_modules/canvas/types/index.d.ts 484 ●●●●● patch | view | raw | blame | history
node_modules/canvas/util/has_lib.js 119 ●●●●● patch | view | raw | blame | history
node_modules/canvas/util/win_jpeg_lookup.js 21 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/CONTRIBUTING.md 28 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/LICENSE 24 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/README.md 275 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/aes.js 234 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/blowfish.js 471 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/bower.json 39 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/cipher-core.js 895 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/core.js 807 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/crypto-js.js 6657 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/docs/QuickStartGuide.wiki 470 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/enc-base64.js 136 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/enc-base64url.js 148 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/enc-hex.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/enc-latin1.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/enc-utf16.js 149 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/enc-utf8.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/evpkdf.js 134 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/format-hex.js 66 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/format-openssl.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/hmac-md5.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/hmac-ripemd160.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/hmac-sha1.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/hmac-sha224.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/hmac-sha256.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/hmac-sha3.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/hmac-sha384.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/hmac-sha512.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/hmac.js 143 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/index.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/lib-typedarrays.js 76 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/md5.js 268 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/mode-cfb.js 80 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/mode-ctr-gladman.js 116 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/mode-ctr.js 58 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/mode-ecb.js 40 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/mode-ofb.js 54 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/package.json 42 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/pad-ansix923.js 49 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/pad-iso10126.js 44 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/pad-iso97971.js 40 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/pad-nopadding.js 30 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/pad-pkcs7.js 18 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/pad-zeropadding.js 47 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/pbkdf2.js 145 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/rabbit-legacy.js 190 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/rabbit.js 192 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/rc4.js 139 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/ripemd160.js 267 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/sha1.js 150 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/sha224.js 80 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/sha256.js 199 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/sha3.js 326 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/sha384.js 83 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/sha512.js 326 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/tripledes.js 779 ●●●●● patch | view | raw | blame | history
node_modules/crypto-js/x64-core.js 304 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/LICENSE 21 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/README.md 280 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/css/pdfh5.css 212 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/css/style.css 1242 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/git.pdf patch | view | raw | blame | history
node_modules/pdfh5/index.html 232 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/js/jquery-3.6.0.min.js 2 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/js/jquery.qrcode.min.js 28 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/js/pdf-obfuscated.js 1 ●●●● patch | view | raw | blame | history
node_modules/pdfh5/js/pdf.js 22 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/js/pdf.worker.js 22 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/js/pdfh5.js 2164 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/js/pdfh5.worker.js patch | view | raw | blame | history
node_modules/pdfh5/js/pinch-zoom.js 927 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/js/pinch-zoom.min.js 1 ●●●● patch | view | raw | blame | history
node_modules/pdfh5/package.json 61 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/pdf.html 166 ●●●●● patch | view | raw | blame | history
node_modules/pdfh5/pdfh5.png patch | view | raw | blame | history
node_modules/pdfh5/test.pdf patch | view | raw | blame | history
node_modules/pdfh5/vconsole.min.js 10 ●●●●● patch | view | raw | blame | history
pages/addAddr/addAddr.vue 157 ●●●●● patch | view | raw | blame | history
pages/addBank/addBank.vue 415 ●●●●● patch | view | raw | blame | history
pages/address/address.vue 123 ●●●●● patch | view | raw | blame | history
pages/agree/agree.vue 22 ●●●●● patch | view | raw | blame | history
pages/bank/bank.vue 121 ●●●●● patch | view | raw | blame | history
pages/carts/carts.vue 249 ●●●●● patch | view | raw | blame | history
pages/cates/cates.vue 131 ●●●●● patch | view | raw | blame | history
pages/catesList/catesList.vue 56 ●●●●● patch | view | raw | blame | history
pages/collect/collect.vue 121 ●●●●● patch | view | raw | blame | history
pages/customer/customer.vue 146 ●●●●● patch | view | raw | blame | history
pages/edu/edu.vue 94 ●●●●● patch | view | raw | blame | history
pages/entrust/entrust.vue 367 ●●●●● patch | view | raw | blame | history
pages/esign/esign.vue 25 ●●●●● patch | view | raw | blame | history
pages/face/face.vue 175 ●●●●● patch | view | raw | blame | history
pages/findpass/findpass.vue 159 ●●●●● patch | view | raw | blame | history
pages/ginfo/ginfo.vue 203 ●●●●● patch | view | raw | blame | history
pages/idcard/idcard.vue 331 ●●●●● patch | view | raw | blame | history
pages/index/index.vue 304 ●●●●● patch | view | raw | blame | history
pages/job/job.vue 259 ●●●●● patch | view | raw | blame | history
pages/login/login.vue 218 ●●●●● patch | view | raw | blame | history
pages/logs/logs.vue 47 ●●●●● patch | view | raw | blame | history
pages/my/my.vue 297 ●●●●● patch | view | raw | blame | history
pages/order/order.vue 388 ●●●●● patch | view | raw | blame | history
pages/orderinfo/orderinfo.vue 388 ●●●●● patch | view | raw | blame | history
pages/pays/pays.vue 410 ●●●●● patch | view | raw | blame | history
pages/profile/profile.vue 223 ●●●●● patch | view | raw | blame | history
pages/regist/regist.vue 170 ●●●●● patch | view | raw | blame | history
pages/search/search.vue 79 ●●●●● patch | view | raw | blame | history
pages/wallet/wallet.vue 71 ●●●●● patch | view | raw | blame | history
pages/web/index.vue 35 ●●●●● patch | view | raw | blame | history
.hbuilderx/launch.json
New file
@@ -0,0 +1,14 @@
{
    "version" : "1.0",
    "configurations" : [
        {
            "customPlaygroundType" : "local",
            "playground" : "custom",
            "type" : "uni-app:app-android"
        },
        {
            "playground" : "standard",
            "type" : "uni-app:app-ios"
        }
    ]
}
node_modules/.package-lock.json
New file
@@ -0,0 +1,12 @@
{
  "name": "gold",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {
    "node_modules/crypto-js": {
      "version": "4.2.0",
      "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
      "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
    }
  }
}
node_modules/canvas/CHANGELOG.md
New file
@@ -0,0 +1,1052 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this
project adheres to [Semantic Versioning](http://semver.org/).
(Unreleased)
==================
### Changed
### Added
### Fixed
2.11.2
==================
### Fixed
* Building on Windows in CI (and maybe other Windows configurations?) (#2216)
2.11.1
==================
### Fixed
* Add missing property `canvas` to the `CanvasRenderingContext2D` type
* Fixed glyph positions getting rounded, resulting text having a slight `letter-spacing` effect
* Fixed `ctx.font` not being restored correctly after `ctx.restore()` (#1946)
2.11.0
==================
### Fixed
* Replace triple-slash directive in types with own types to avoid polluting TS modules with globals ([#1656](https://github.com/Automattic/node-canvas/issues/1656))
2.10.2
==================
### Fixed
* Fix `Assertion failed: (object->InternalFieldCount() > 0), function Unwrap, file nan_object_wrap.h, line 32.` ([#2025](https://github.com/Automattic/node-canvas/issues/2025))
* `textBaseline` and `textAlign` were not saved/restored by `save()`/`restore()`. ([#1936](https://github.com/Automattic/node-canvas/issues/2029))
* Update nan to v2.17.0 to ensure Node.js v18+ support.
### Changed
* Improve performance and memory usage of `save()`/`restore()`.
* `save()`/`restore()` no longer have a maximum depth (previously 64 states).
2.10.1
==================
### Fixed
* Fix `actualBoundingBoxLeft` and `actualBoundingBoxRight` when `textAlign='center'` or `'right'` ([#1909](https://github.com/Automattic/node-canvas/issues/1909))
* Fix `rgba(r,g,b,0)` with alpha to 0 should parse as transparent, not opaque. ([#2110](https://github.com/Automattic/node-canvas/pull/2110))
2.10.0
==================
### Added
* Export `pangoVersion`
* [`ctx.roundRect()`](https://developer.chrome.com/blog/canvas2d/#round-rect)
### Fixed
* `rgba(r,g,b)` with no alpha should parse as opaque, not transparent. ([#2029](https://github.com/Automattic/node-canvas/issues/2029))
* Typo in `PngConfig.filters` types. ([#2072](https://github.com/Automattic/node-canvas/issues/2072))
* `createPattern()` always used "repeat" mode; now supports "repeat-x" and "repeat-y". ([#2066](https://github.com/Automattic/node-canvas/issues/2066))
* Crashes and hangs when using non-finite values in `context.arc()`. ([#2055](https://github.com/Automattic/node-canvas/issues/2055))
* Incorrect `context.arc()` geometry logic for full ellipses. ([#1808](https://github.com/Automattic/node-canvas/issues/1808), ([#1736](https://github.com/Automattic/node-canvas/issues/1736)))
* Added missing `deregisterAllFonts` to the Typescript declaration file ([#2096](https://github.com/Automattic/node-canvas/pull/2096))
* Add `User-Agent` header when requesting remote images ([#2099](https://github.com/Automattic/node-canvas/issues/2099))
2.9.3
==================
### Fixed
* Wrong fonts used when calling `registerFont` multiple times with the same family name ([#2041](https://github.com/Automattic/node-canvas/issues/2041))
2.9.2
==================
### Fixed
* All exports now work when Canvas is used in ES Modules (ESM). ([#2047](https://github.com/Automattic/node-canvas/pull/2047))
* `npm rebuild` will now re-fetch prebuilt binaries to avoid `NODE_MODULE_VERSION` mismatch errors. ([#1982](https://github.com/Automattic/node-canvas/pull/1982))
2.9.1
==================
### Fixed
* Stringify CanvasGradient, CanvasPattern and ImageData like browsers do. (#1639, #1646)
* Add missing include for `toupper`.
* Throw an error instead of crashing the process if `getImageData` or `putImageData` is called on a PDF or SVG canvas (#1853)
* Compatibility with Typescript 4.6
* Near-perfect font matching on Linux (#1572)
* Fix multi-byte font path support on Windows.
* Allow rebuild of this library
2.9.0
==================
### Changed
* Refactor functions to classes.
* Changed `DOMPoint()` constructor to check for parameter nullability.
* Changed `DOMMatrix.js` to use string literals for non-special cases.
* Remove semicolons from Dommatrix.js.
* Update nan to v2.15.0 to ensure Node.js v14+ support.
* Clean up inf/nan macros and slightly speed up argument checking.
### Added
* Added `deregisterAllFonts` method to free up memory and reduce font conflicts.
### Fixed
* Support Apple M1 Homebrew install that puts canvas install library files in `/opt/homebrew/lib`
2.8.0
==================
### Changed
* Upgrade dtslint
* Upgrade node-pre-gyp to 1.0.0. Note that if you are using special node-pre-gyp
  features like `node_pre_gyp_accessKeyId`, you may need to make changes to your
  installation procedure. See https://github.com/mapbox/node-pre-gyp/blob/master/CHANGELOG.md#100.
* Add Node.js v16 to CI.
* The C++ class method `nBytes()` now returns a size_t. (Because this is a C++
  method only, this is not considered a breaking change.)
### Added
* Add support for  `inverse()` and `invertSelf()` to `DOMMatrix` (#1648)
* Add support for `context.getTransform()` ([#1769](https://github.com/Automattic/node-canvas/pull/1769))
* Add support for `context.setTransform(dommatrix)` ([#1769](https://github.com/Automattic/node-canvas/pull/1769))
### Fixed
* Fix `actualBoundingBoxLeft` and `actualBoundingBoxRight` returned by `measureText` to be the ink rect ([#1776](https://github.com/Automattic/node-canvas/pull/1776), fixes [#1703](https://github.com/Automattic/node-canvas/issues/1703)).
* Fix Pango logging "expect ugly output" on Windows (#1643)
* Fix benchmark for createPNGStream (#1672)
* Fix dangling reference in BackendOperationNotAvailable exception (#1740)
* Fix always-false comparison warning in Canvas.cc.
* Fix Node.js crash when throwing from an onload or onerror handler.
2.7.0
==================
### Changed
* Switch CI to Github Actions. (Adds Windows and macOS builds.)
* Switch prebuilds to GitHub actions in the Automattic/node-canvas repository.
  Previously these were in the [node-gfx/node-canvas-prebuilt](https://github.com/node-gfx/node-canvas-prebuilt)
  and triggered manually.
* Speed up `fillStyle=` and `strokeStyle=`
### Added
* Export `rsvgVersion`.
* CanvasPattern’s `setTransform` method is no longer missing
### Fixed
* Fix BMP issues. (#1497)
* Update typings to support jpg and addPage on NodeCanvasRenderingContext2D (#1509)
* Fix assertion failure when using Visual Studio Code debugger to inspect Image prototype (#1534)
* Fix signed/unsigned comparison warning introduced in 2.6.0, and function cast warnings with GCC8+
* Fix to compile without JPEG support (#1593).
* Fix compile errors with cairo
* Fix Image#complete if the image failed to load.
* Upgrade node-pre-gyp to v0.15.0 to use latest version of needle to fix error when downloading prebuilds.
* Don't throw if `fillStyle` or `strokeStyle` is set to an object, but that object is not a Gradient or Pattern. (This behavior was non-standard: invalid inputs are supposed to be ignored.)
2.6.1
==================
### Fixed
* Ignore `maxWidth` in `fillText` and `strokeText` if it is undefined
* Fix crash (assertion failure) in Node.js 12.x when patterns or gradients are used
* Fix crash (check failure) in Node.js 12.x when using RGB16_565 format. (The
  underlying arraybuffer was incorrectly sized.)
* Fix rendering error when applying shadow width line style (lineCap lineJoin lineDash)
2.6.0
==================
### Changed
* Allow larger buffers to be returned from `toBuffer('raw')`.
### Added
* Support for various BMP headers and color depths (#1435)
### Fixed
* Fix crash when changing canvas width/height while `fillStyle` or `strokeStyle`
  was set to a `CanvasPattern` or `CanvasGradient` (#1357).
* Fix crash when changing width/height of SVG canvases (#1380).
* Fix crash when using `toBuffer('raw')` with large canvases (#1158).
* Clarified meaning of byte ordering for `toBuffer('raw')` in readme. (#1416)
* Fix package.json Typings field to point to Declaration file (#1432)
* Properly check return value from `Set` and `Call`. (#1415)
* Use `Get` version from `Nan` instead of `v8`. (#1415)
2.5.0
==================
### Added
* Support redirects when fetching images (using [simple-get](https://github.com/feross/simple-get)) (#1398)
* Support Node.js v12
### Fixed
* Fix object literal & arrow function syntax usage for IE.
2.4.1
==================
### Fixed
* Guard JPEG width/height against maximum supported (#1385)
* Fix electron 5 and node 12 compatibility
* Fix encoding options (quality) parameter in `canvas.toDataURL()`
2.4.0
==================
### Added
* (Actually) added `resolution` option for `canvas.toBuffer("image/png")` and
  `canvas.createPNGStream()`. This was documented since 2.0.0 but not working.
* Add typescript definitions.
### Fixed
* PDF metadata (added in 2.3.0) wasn't being set with `canvas.createPDFStream()`
* Fix custom "inspect" function deprecation warnings (#1326)
2.3.1
==================
### Fixed
* Fix `canvas.toBuffer()` for JPEGs (#1350)
2.3.0
==================
### Added
* Add support for multiple PDF page sizes
* Add support for embedding document metadata in PDFs
### Fixed
* Don't crash when font string is invalid (bug since 2.2.0) (#1328)
* Fix memory leak in `canvas.toBuffer()` (#1202, #1296)
* Fix memory leak in `ctx.font=` (#1202)
2.2.0
==================
### Added
* BMP support
### Fixed
* Reset context on resurface (#1292)
* Support Jest test framework (#1311)
2.1.0
==================
### Added
* Warn when building with old, unsupported versions of cairo or libjpeg.
2.0.0
==================
**Upgrading from 1.x**
```js
// (1) The Canvas constructor is no longer the default export from the module.
/* old: */
const Canvas = require('canvas')
const mycanvas = new Canvas(width, height)
/* new: */
const { createCanvas, Canvas } = require('canvas')
const mycanvas = createCanvas(width, height)
mycanvas instanceof Canvas // true
/* old: */
const Canvas = require('canvas')
const myimg = new Canvas.Image()
/* new: */
const { Image } = require('canvas')
const myimg = new Image()
// (2) The quality argument for canvas.createJPEGStream/canvas.jpegStream now
//     goes from 0 to 1 instead of from 0 to 100:
canvas.createJPEGStream({ quality: 50 }) // old
canvas.createJPEGStream({ quality: 0.5 }) // new
// (3) The ZLIB compression level and PNG filter options for canvas.toBuffer are
//     now named instead of positional arguments:
canvas.toBuffer(undefined, 3, canvas.PNG_FILTER_NONE) // old
canvas.toBuffer(undefined, { compressionLevel: 3, filters: canvas.PNG_FILTER_NONE }) // new
// or specify the mime type explicitly:
canvas.toBuffer('image/png', { compressionLevel: 3, filters: canvas.PNG_FILTER_NONE }) // new
// (4) #2 also applies for canvas.pngStream, although these arguments were not
//     documented:
canvas.pngStream(3, canvas.PNG_FILTER_NONE) // old
canvas.pngStream({ compressionLevel: 3, filters: canvas.PNG_FILTER_NONE }) // new
// (5) canvas.syncPNGStream() and canvas.syncJPEGStream() have been removed:
canvas.syncPNGStream() // old
canvas.createSyncPNGStream() // old
canvas.createPNGStream() // new
canvas.syncJPEGStream() // old
canvas.createSyncJPEGStream() // old
canvas.createJPEGStream() // new
// (6) Context2d.filter has been renamed to context2d.quality to avoid a
//     conflict with the new standard 'filter' property.
context.filter = 'best' // old
context.quality = 'best' // new
```
### Breaking
 * Drop support for Node.js <6.x
 * Remove sync stream functions (bc53059). Note that most streams are still
   synchronous (run in the main thread); this change just removed `syncPNGStream`
   and `syncJPEGStream`.
 * Pango is now *required* on all platforms (7716ae4).
 * Make the `quality` argument for JPEG output go from 0 to 1 to match HTML spec.
 * Make the `compressionLevel` and `filters` arguments for `canvas.toBuffer()`
   named instead of positional. Same for `canvas.pngStream()`, although these
   arguments were not documented.
 * See also: *Correct some of the `globalCompositeOperator` types* under
   **Fixed**. These changes were bug-fixes, but will break existing code relying
   on the incorrect types.
 * Rename `context2d.filter` to `context2d.quality` to avoid a conflict with the
   new standard 'filter' property. Note that the standard 'filter' property is
   not yet implemented.
### Fixed
 * Fix build with SVG support enabled (#1123)
 * Prevent segfaults caused by loading invalid fonts (#1105)
 * Fix memory leak in font loading
 * Port has_lib.sh to javascript (#872)
 * Correctly sample the edge of images when scaling (#1084)
 * Detect CentOS libjpeg path (b180ea5)
 * Improve measureText accuracy (2bbfec5)
 * Fix memory leak when image callbacks reference the image (1f4b646)
 * Fix putImageData(data, negative, negative) (2102e25)
 * Fix SVG recognition when loading from buffer (77749e6)
 * Re-rasterize SVG when drawing to a context and dimensions changed (79bf232)
 * Prevent JPEG errors from crashing process (#1124)
 * Improve handling of invalid arguments (#1129)
 * Fix repeating patterns when drawing a canvas to itself (#1136)
 * Prevent segfaults caused by creating a too large canvas
 * Fix parse-font regex to allow for whitespaces.
 * Allow assigning non-string values to fillStyle and strokeStyle
 * Fix drawing zero-width and zero-height images.
 * Fix DEP0005 deprecation warning
 * Don't assume `data:` URIs assigned to `img.src` are always base64-encoded
 * Fix formatting of color strings (e.g. `ctx.fillStyle`) on 32-bit platforms
 * Explicitly export symbols for the C++ API
 * Named CSS colors should match case-insensitive
 * Correct some of the `globalCompositeOperator` types to match the spec:
     * "hsl-hue" is now "hue"
     * "hsl-saturation" is now "saturation"
     * "hsl-color" is now "color"
     * "hsl-luminosity" is now "luminosity"
     * "darker" is now "darken"
     * "dest" is now "destination"
     * "add" is removed (but is the same as "lighter")
     * "source" is now "copy"
 * Provide better, Node.js core-style coded errors for failed sys calls. (For
   example, provide an error with code 'ENOENT' if setting `img.src` to a path
   that does not exist.)
 * Support reading CMYK, YCCK JPEGs.
 * Hide `Image.prototype.source`
 * Fix behavior of maxWidth (#1088)
 * Fix behavior of textAlignment with maxWidth (#1253)
### Added
 * Prebuilds (#992) with different libc versions to the prebuilt binary (#1140)
 * Support `canvas.getContext("2d", {alpha: boolean})` and
   `canvas.getContext("2d", {pixelFormat: "..."})`
 * Support indexed PNG encoding.
 * Support `currentTransform` (d6714ee)
 * Export `CanvasGradient` (6a4c0ab)
 * Support #RGBA , #RRGGBBAA hex colors (10a82ec)
 * Support maxWidth arg for fill/strokeText (175b40d)
 * Support image.naturalWidth/Height (a5915f8)
 * Render SVG img elements when librsvg is available (1baf00e)
 * Support ellipse method (4d4a726)
 * Browser-compatible API (6a29a23)
 * Support for jpeg on Windows (42e9a74)
 * Support for backends (1a6dffe)
 * Support for `canvas.toBuffer("image/jpeg")`
 * Unified configuration options for `canvas.toBuffer()`, `canvas.pngStream()`
   and `canvas.jpegStream()`
 * ~~Added `resolution` option for `canvas.toBuffer("image/png")` and
   `canvas.createPNGStream()`~~ this was not working
 * Support for `canvas.toDataURI("image/jpeg")` (sync)
 * Support for `img.src = <url>` to match browsers
 * Support reading data URL on `img.src`
 * Readme: add dependencies command for OpenBSD
 * Throw error if calling jpegStream when canvas was not built with JPEG support
 * Emit error if trying to load GIF, SVG or JPEG image when canvas was not built
   with support for that format
1.6.x (unreleased)
==================
### Fixed
 * Make setLineDash able to handle full zeroed dashes (b8cf1d7)
 * Fix reading fillStyle after setting it from gradient to color (a84b2bc)
### Added
 * Support for pattern repeat and no-repeat (#1066)
 * Support for context globalAlpha for gradients and patterns (#1064)
1.6.9 / 2017-12-20
==================
### Fixed
 * Fix some instances of crashes (7c9ec58, 8b792c3)
 * Fix node 0.x compatibility (dca33f7)
1.6.8 / 2017-12-12
==================
### Fixed
 * Faster, more compliant parseFont (4625efa, 37cd969)
1.6.7 / 2017-09-08
==================
### Fixed
 * Minimal backport of #985 (rotated text baselines) (c19edb8)
1.6.6 / 2017-05-03
==================
### Fixed
 * Use .node extension for requiring native module so webpack works (1b05599)
 * Correct text baseline calculation (#1037)
1.6.5 / 2017-03-18
==================
### Changed
 * Parse font using parse-css-font and units-css (d316416)
1.6.4 / 2017-02-26
==================
### Fixed
 * Make sure Canvas#toDataURL is always async if callback is passed (8586d72)
1.6.3 / 2017-02-14
==================
### Fixed
 * Fix isnan() and isinf() on clang (5941e13)
1.6.2 / 2016-10-30
==================
### Fixed
 * Fix deprecation warnings (c264879)
 * Bump nan (e4aea20)
1.6.1 / 2016-10-23
==================
### Fixed
 * Make has_lib.sh work on BSD OSes (1727d66)
1.6.0 / 2016-10-16
==================
 * Support canvas.getBuffer('raw') (#819)
1.5.0 / 2016-09-11
==================
 * Crude PDF stream implementation (#781)
 * Update CI settings (#797)
 * Reduce some of the install warnings (#794)
 * Fix lineDash browser tests never finishing (#793)
 * Add issue template (#791)
1.4.0 / 2016-06-03
==================
 * Add support for evenodd fill rule (#762)
1.3.17 / 2016-06-03
===================
 * Removing redundant duplicate calls (#769)
 * Cleanup examples (#776)
 * Fix CanvasRenderingContext2D class name (#777)
1.3.16 / 2016-05-29
===================
  * Fix leak of data when streaming JPEG (#774)
1.3.15 / 2016-05-09
===================
  * Fix segfault in putImageData (#750)
1.3.14 / 2016-05-05
===================
  * Clamp JPEG buffer size (#739)
1.3.13 / 2016-05-01
===================
  * Bumb NAN version (#759)
1.3.12 / 2016-03-01
===================
  * Expose freetype version (#718)
  * Require new in constructor (#717)
1.3.11 / 2016-03-01
===================
  * Properly clamp quality in toDataURL (#728)
  * Strict mode (#719)
1.3.10 / 2016-02-07
===================
  * Fix segfault on node 0.10.x (#712)
1.3.9 / 2016-01-27
==================
  * Allow to unbind onload/onerror callback handlers (#706)
1.3.8 / 2016-01-22
==================
  * Cleanup build scripts and fix pangocairo detection (#701)
1.3.7 / 2016-01-13
==================
  * Don't unbind onload/onerror callbacks after invoking them (#615)
1.3.6 / 2016-01-06
==================
  * Allow optional arguments in `toDataURL` to be `undefined` and improve `toDataURL`'s spec compliance (#690)
1.3.5 / 2015-12-07
==================
  * Add image/jpeg support to `toDataUrl` (#685)
1.3.4 / 2015-11-21
==================
  * Upgrade nan to 2.1.0 (#671)
1.3.3 / 2015-11-21
==================
  * Fix compilation on Visual Studio 2015 (#670)
1.3.2 / 2015-11-18
==================
  * Fix incorrect Y offset and scaling for shadows (#669)
1.3.1 / 2015-11-09
==================
  * Wrap std::min calls in paranthesis to prevent macro expansion on windows (#660)
1.3.0 / 2015-10-26
==================
  * Expose ImageData constructor and make it more spec-compliant (#569)
1.2.11 / 2015-10-20
===================
  * Implement blur on images (#648)
1.2.10 / 2015-10-12
===================
  * Fix segfault in Canvas#jpegStream (#629)
1.2.9 / 2015-09-14
==================
  * Upgrade to Nan 2.x with support for iojs 3.x and Node.js 4.x (#622)
1.2.8 / 2015-08-30
==================
  * Clean up the tests (#612)
  * Replace CanvasPixelArray with Uint8ClampedArray to be API-compliant (#604)
  * Specify travis iojs versions (#611)
1.2.7 / 2015-07-29
==================
  * Avoid future reserved keyword (#592)
1.2.6 / 2015-07-29
==================
  * Fix the build on windows (#589)
1.2.5 / 2015-07-28
==================
  * Another npm release, since 1.2.4 was botched (see #596)
1.2.4 / 2015-07-23
==================
  * Point `homepage` and `repository` links to [`github.com/Automattic/node-canvas`][repo]
  * Fix Travis builds and Cairo include paths (thanks, Linus Unnebäck!)
1.2.3 / 2015-05-21
==================
  * Update TJ Holowaychuk's username in the readme
  * Fix segmentation fault in `Image::loadFromBuffer` when buffer is empty
  * Optimize getImageData()
  * package: add "license" attribute
  * package: update "nan" to v1.8.4
  * package: append `.git` to "repository" URL
1.2.2 / 2015-04-18
==================
  * Now works on io.js
  * Fix 'drawImage' scaling (the dimensions of the region that gets clipped also needs to be scaled).
  * Fix bug in StreamPNGSync
1.2.1 / 2015-02-10
==================
  * Use non-cairo 1.12 API for shadow blur
1.2.0 / 2015-01-31
==================
  * travis: drop support for node v0.6
  * Merge pull request #507 from salzhrani/iojs
  * io.js compatibility
  * Merge pull request #505 from woodcoder/shadow-blur
  * Fix issue with line width not being correct in stroked shadows.
  * Add another shadow/transform test.
  * Refactor setSourceRGBA to allow the context to be supplied.
  * Simple image shadow (no blurring or handling current transforms) based on image's alpha channel.
  * Test showing issue #133, that images don't have shadows.
  * The +1 on the offset seems to match the browser's output better, but I can't work out why it would be needed (unless it's pixel alignment related).
  * Make the shadow radius more accurately match the browser's, making use of sigma scale as used in SKIA: https://github.com/google/skia/blob/master/src/effects/SkBlurMask.cpp#L26.
  * Create a new image surface to render blurred shadows to, this means that vector formats like PDF will now render blurs.
  * Add recommended calls to flush and dirty buffer, as per http://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-get-data.
  * Add PDF button to test page to easily generate PDF version of the test image.
  * Fix to ensure shadowOffset is unaffected by the current transform.
  * New test illustrating that canvas implementation doesn't translate the shadowOffset.
  * Merge pull request #490 from AllYearbooks/master
  * Merge pull request #501 from motiz88/hsl-color
  * Code style + attribution. Also removed parseClipped() and commented out wrapInt (now wrap_int).
  * Added visual tests for hsl() and hsla() color parsing.
  * Fixed <number> handling in hsl/hsla color parser. parseNumber() was erroring out on numbers with long fractional parts.
  * hsl/hsla color parsing + rebeccapurple hsl() and hsla() color values are now supported, with corresponding unit tests. Also added rebeccapurple (from CSS Color Level 4) to the named color list.
  * float rather than int for drawImage arguments
  * with_pango to true and use fontconfig to load fonts
  * Merge pull request #399 from nulltask/fix/lighten
  * Merge pull request #465 from espadrine/master
  * Merge pull request #470 from tonylukasavage/patch-1
  * Add one-liner MacPorts install to docs
  * Offer SVG output.
  * Readme update: node-gyp.
  * Readme: fix subheading size
  * Readme: remove Gemnasium badge, use SVG for npm badge
  * Readme: add Travis-CI badge
  * change operator lighter to lighten
1.1.6 / 2014-08-01
==================
  * export canvas.CanvasPixelArray instead of canvas.PixelArray which is undefined
  * Glib version test into giflib exists test
  * Giflib 5.1
  * install: use an even older version of giflib (v4.1.6)
  * install: use an older version of giflib (v4.2.3)
  * install: install `giflib`
  * install: use more compatible sh syntax
  * travis: attempt to run the ./install script before testintg
  * travis: test node v0.6, v0.8, v0.10, and v0.11
  * Distinguish between 'add' and 'lighter'
1.1.5 / 2014-06-26
==================
  * Readme: remove Contributors section
  * Readme: update copyright
  * On Windows, copy required DLLs next to ".node" file (#442 @pandell)
  * Duplicate "msvc_settings" for "Debug" configuration
  * Remove unneeded #include <nan.h>
  * Use float constants to prevent double->float conversion warning
  * Ignore Visual C++ 2013 warnings (#441 @pandell)
  * Add algorithm include to CanvasRenderingContext2d.cc for std::min (#435 @kkoopa)
  * Updated NAN to 1.2.0 (#434 @kkoopa)
1.1.4 / 2014-06-08
==================
  * Fix compile error with Visual C++
  * Add support for the lineDash API
  * Update NAN
  * New V8 compatibility
  * Correctly limit bounds in PutImageData to prevent segment fault
  * Fix segfault when onload and onerror are not function
  * Add support for Node 0.11.9
1.1.3 / 2014-01-08
==================
  * Add CAIRO_FORMAT_INVALID
  * Readjust the amount of allocated memory
  * Fix argument index for filter parameter
  * Make has_lib.sh work properly on Debian 64bit
1.1.2 / 2013-10-31
==================
  * NAN dep upgrade, full node@<=0.11.8 compatibility
  * Use node::MakeCallback() instead of v8::Function::Call()
  * Improve nan location discovery
  * Fix enabling gif/jpeg options on Ubuntu 13.04
1.1.1 / 2013-10-09
==================
  * add better support for outdated versions of Cairo
1.1.0 / 2013-08-01
==================
  * add png compression options
  * add jpeg stream progressive mode option
  * fix resource leaks on read errors
1.0.4 / 2013-07-23
==================
  * 0.11.4+ compatibility using NAN
  * fix typo in context2d for imageSmoothingEnabled
1.0.3 / 2013-06-04
==================
  * add "nearest" and "bilinear" to patternQuality
  * fix fread() retval check (items not bytes)
  * removed unneeded private fields
1.0.2 / 2013-03-22
==================
  * add Context2d#imageSmoothingEnabled=
1.0.1 / 2013-02-25
==================
  * travis: test modern node versions
  * change the node-gyp build to use pkg-config
1.0.0 / 2013-01-16
==================
  * add conditional pango font support [Julian Viereck]
  * add `Canvas#{png,jpeg}Stream()` alias of create* legacy methods
  * add support for grayscale JPEGs
  * fix: explicitly cast the after work callback function to "uv_after_work_cb"
  * fix test server for express 3.x
  * fix: call cairo_surface_finish in ~Canvas when pdf
  * remove old 0.4.x binding support. Closes #197
0.13.1 / 2012-08-20
==================
  * fix cases where GIF_LIB_VERSION is not defined
  * fix auto-detection of optional libraries for OS X
  * fix Context2d::SetFont for pango when setting normal weight/style
0.13.0 / 2012-08-12
==================
  * add pango support [c-spencer]
  * add pango / png / jpeg gyp auto-detection [c-spencer]
  * add `.gifVersion` [tootallnate]
  * add `.jpegVersion` [tootallnate]
  * add moar gyp stuff [tootallnate]
  * remove wscript
  * fix `closure_destroy()` with cast for `AdjustAmountOfExternalAllocatedMemory()`
0.12.1 / 2012-06-29
==================
  * fix jpeg malloc Image issue. Closes #160 [c-spencer]
  * Improve Image mode API
  * Add clearData method to handle reassignment of src, and clean up mime data memory handling.
  * Improve how _data_len is managed and use to adjust memory, hide more of mime API behind cairo version conditional.
  * Add optional mime-data tracking to Image.
  * Refactor JPEG decoding into decodeJPEGIntoSurface
0.12.0 / 2012-05-02
==================
  * Added `textDrawingMode` context property [c-spencer]
  * Added additional TextMetrics properties [c-spencer]
0.11.3 / 2012-04-25
==================
  * Fixed `Image` memory leak. Closes #150
  * Fixed Context2d::hasShadow()
0.11.2 / 2012-04-12
==================
  * Fixed: pdf memory leak, free closure and surface in ~Canvas
0.11.1 / 2012-04-10
==================
  * Changed: renamed .nextPage() to .addPage()
0.11.0 / 2012-04-10
==================
  * Added quick PDF support
  * Added `Canvas#type` getter
  * Added ./examples/pdf-images.js
  * Added ./examples/multiple-page-pdf.js
  * Added ./examples/small-pdf.js
0.10.3 / 2012-02-27
==================
  * Fixed quadratic curve starting point for undefined path. Closes #155
0.10.2 / 2012-02-06
==================
  * Fixed: Context2d setters with invalid values ignored
  * Changed: replaced seek with `fstat()`
0.10.1 / 2012-01-31
==================
  * Added _/opt/local/lib_ to wscript [obarthel]
  * Added bounds checking to `rgba_to_string()` [obarthel]
  * Fixed cleanup in JPEG Image loading [obarthel]
  * Fixed missing CSS color table values [obarthel]
0.10.0 / 2012-01-18
==================
  * Added `ctx.createPattern()` [slaskis]
0.9.0 / 2012-01-13
==================
  * Added `createJPEGStream()` [Elijah Hamovitz]
0.8.3 / 2012-01-04
==================
  * Added support for libjpeg62-dev or libjpeg8-dev [wwlinx]
0.8.2 / 2011-12-14
==================
  * Fixed two memory leaks in context2d [Tharit]
  * Fixed `make test-server`
0.8.1 / 2011-10-31
==================
  * Added 0.5.x support [TooTallNate]
  * Fixed `measureText().width`. Closes #126
0.8.0 / 2011-10-28
==================
  * Added data uri support. Closes #49
0.7.3 / 2011-09-14
==================
  * Added better lineTo() / moveTo() exception messages
0.7.2 / 2011-08-30
==================
  * Changed: prefix some private methods with _
0.7.1 / 2011-08-25
==================
  * Added better image format detection
  * Added libpath options to waf configuration; this was necessary to correctly detect gif and jpeg support on FreeBSD
0.7.0 / 2011-07-12
==================
  * Added GIF support [Brian McKinney]
0.6.0 / 2011-06-04
==================
  * Added `Image#src=Buffer` support. Closes #91
  * Added `devDependencies`
  * Added `source-atop` test
  * Added _image-src.js_ example
  * Removed `V8::AdjustAmountOfExternalAllocatedMemory()` call from `toBuffer()`
  * Fixed v8 memory hint when resizing canvas [atomizer]
0.5.4 / 2011-04-20
==================
  * Added; special case of zero-width rectangle [atomizer]
  * Fixed; do not clamp arguments to integer values [atomizer]
  * Fixed; preserve current path during `fillRect()` and `strokeRect()` [atomizer]
  * Fixed; `restorePath()`: clear current path before appending [atomizer]
0.5.3 / 2011-04-11
==================
  * Clamp image bounds in `PixelArray::PixelArray()` [Marcello Bastea-Forte]
0.5.2 / 2011-04-09
==================
  * Changed; make `PNGStream` a real `Stream` [Marcello Bastea-Forte]
0.5.1 / 2011-03-16
==================
  * Fixed (kinda) `img.src=` error handling
  * Fixed; move closure.h down for malloc ref. Closes #80
0.5.0 / 2011-03-14
==================
  * Added several more operators (color-dodge, color-burn, difference, etc)
  * Performance; no longer re-allocating `closure->data` for each png write
  * Fixed freeing of `Context2d` states
  * Fixed text alignment / baseline [Olaf]
  * Fixed HandleScopes [Olaf]
  * Fixed small misc memory leaks
  * Fixed `Buffer` usage for node 0.4.x
0.4.3 / 2011-01-11
==================
  * Fixed font family dereferencing. Closes #72
  * Fixed; stripping of quotes from font-family before applying
  * Fixed duplicate textAlign getter
  * Removed sans-serif default of _Arial_
0.4.2 / 2010-12-28
==================
  * Fixed font size growing issue after successive calls. Closes #70
0.4.1 / 2010-12-18
==================
  * Fixed; toString() first argument of `{fill,stroke}Text()`. Closes #68
0.4.0 / 2010-12-12
==================
  * Added `drawImage()` with `Canvas` instance support. Closes #67
0.3.3 / 2010-11-30
==================
  * Added `CanvasRenderingContext2d#patternQuality` accessor, accepting _fast_, _good_, and _best_
  * Fixed; pre-multiply `putImageData()` components
  * Fixed; `PixelArray` data is not premultiplied
0.3.2 / 2010-11-26
==================
  * Added --profile option to config
  * Fixed `eio_custom` segfault(s). Closes #46
  * Fixed two named colors. Closes #62 [thanks noonat]
  * Fixed a few warnings
  * Fixed; freeing data in `Image::loadJPEG()` on failure
  * Fixed; include _jpeglib_ only when __HAVE_JPEG__
  * Fixed; using `strstr()` instead of `strnstr()`
0.3.1 / 2010-11-24
==================
  * Fixed; `Image` loading is sync until race-condition is resolved
  * Fixed; `Image::loadJPEG()` return status based on errno
0.3.0 / 2010-11-24
==================
  * Added arcTo(). Closes #11
  * Added c color parser, _./examples/ray.js_ is now twice as fast
  * Fixed `putImageData()` bug messing up rgba channels
0.2.1 / 2010-11-19
==================
  * Added image _resize_ example
  * Fixed canvas resizing via `{width,height}=`. Closes #57
  * Fixed `Canvas#getContext()`, caching the CanvasRenderingContext
  * Fixed async image loading (test server still messed)
0.2.0 / 2010-11-18
==================
  * Added jpeg `Image` support (when libjpeg is available)
  * Added _hsl_ / _hsla_ color support. [Tom Carden]
0.1.0 / 2010-11-17
==================
  * Added `Image`
  * Added `ImageData`
  * Added `PixelArray`
  * Added `CanvasRenderingContext2d#drawImage()`
  * Added `CanvasRenderingContext2d#getImageData()`
  * Added `CanvasRenderingContext2d#createImageData()`
  * Added kraken blur benchmark example
  * Added several new tests
  * Fixed instanceof checks for many c++ methods
  * Fixed test runner in firefox [Don Park]
0.0.8 / 2010-11-12
==================
  * Added `CanvasRenderingContext2d#drawImage()`
  * Fixed `free()` call missing stdlib
  * Fixed Image#{width,height} initialization to 0
  * Fixed; load image on non-LOADING state
0.0.7 / 2010-11-12
==================
  * Fixed _lighter_ for older versions of cairo
0.0.6 / 2010-11-12
==================
  * Added `Image`
  * Added conditional support for cairo 1.10.0 operators
0.0.5 / 2010-11-10
==================
  * Added custom port support to _test/server.js_
  * Added more global composite operator support
  * Added `Context2d#antialias=`
  * Added _voronoi_ example
  * Added -D__NDEBUG__ to default build
  * Added __BUFFER_DATA__ macro for backwards compat buffer data access [Don Park]
  * Fixed getter bug preventing patterns from being returned via `fillStyle` etc
  * Fixed; __CAIRO_STATUS_NO_MEMORY___ on failed {re,m}alloc()
  * Fixed; free `Canvas::ToBuffer()` closure data
0.0.4 / 2010-11-09
==================
  * Bump to fix npm engine cache bug...
0.0.3 / 2010-11-09
==================
  * Added async `toDataURL()` support
  * Added async `toBuffer()` support
  * Removed buffer utils
0.0.2 / 2010-11-08
==================
  * Added shadow support (faster/better gaussian blur to come)
  * Added node v0.3 support [Don Park]
  * Added -O3 to build
  * Removed `Canvas#savePNG()` use `Canvas#createPNGStream()`
0.0.1 / 2010-11-04
==================
  * Initial release
[repo]: https://github.com/Automattic/node-canvas
node_modules/canvas/Readme.md
New file
@@ -0,0 +1,600 @@
# node-canvas
![Test](https://github.com/Automattic/node-canvas/workflows/Test/badge.svg)
[![NPM version](https://badge.fury.io/js/canvas.svg)](http://badge.fury.io/js/canvas)
node-canvas is a [Cairo](http://cairographics.org/)-backed Canvas implementation for [Node.js](http://nodejs.org).
## Installation
```bash
$ 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**.
### Compiling
If you don't have a supported OS or processor architecture, or you use `--build-from-source`, the module will be compiled on your system. This requires several dependencies, including Cairo and Pango.
For detailed installation information, see the [wiki](https://github.com/Automattic/node-canvas/wiki/_pages). One-line installation instructions for common OSes are below. Note that libgif/giflib, librsvg and libjpeg are optional and only required if you need GIF, SVG and JPEG support, respectively. Cairo v1.10.0 or later is required.
OS | Command
----- | -----
OS X | Using [Homebrew](https://brew.sh/):<br/>`brew install pkg-config cairo pango libpng jpeg giflib librsvg pixman`
Ubuntu | `sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev`
Fedora | `sudo yum install gcc-c++ cairo-devel pango-devel libjpeg-turbo-devel giflib-devel`
Solaris | `pkgin install cairo pango pkg-config xproto renderproto kbproto xextproto`
OpenBSD | `doas pkg_add cairo pango png jpeg giflib`
Windows | See the [wiki](https://github.com/Automattic/node-canvas/wiki/Installation:-Windows)
Others | See the [wiki](https://github.com/Automattic/node-canvas/wiki)
**Mac OS X v10.11+:** If you have recently updated to Mac OS X v10.11+ and are experiencing trouble when compiling, run the following command: `xcode-select --install`. Read more about the problem [on Stack Overflow](http://stackoverflow.com/a/32929012/148072).
If you have xcode 10.0 or higher installed, in order to build from source you need NPM 6.4.1 or higher.
## Quick Example
```javascript
const { createCanvas, loadImage } = require('canvas')
const canvas = createCanvas(200, 200)
const ctx = canvas.getContext('2d')
// Write "Awesome!"
ctx.font = '30px Impact'
ctx.rotate(0.1)
ctx.fillText('Awesome!', 50, 100)
// Draw line under text
var text = ctx.measureText('Awesome!')
ctx.strokeStyle = 'rgba(0,0,0,0.5)'
ctx.beginPath()
ctx.lineTo(50, 102)
ctx.lineTo(50 + text.width, 102)
ctx.stroke()
// Draw cat with lime helmet
loadImage('examples/images/lime-cat.jpg').then((image) => {
  ctx.drawImage(image, 50, 0, 70, 70)
  console.log('<img src="' + canvas.toDataURL() + '" />')
})
```
## Upgrading from 1.x to 2.x
See the [changelog](https://github.com/Automattic/node-canvas/blob/master/CHANGELOG.md) for a guide to upgrading from 1.x to 2.x.
For version 1.x documentation, see [the v1.x branch](https://github.com/Automattic/node-canvas/tree/v1.x).
## Documentation
This project is an implementation of the Web Canvas API and implements that API as closely as possible. For API documentation, please visit [Mozilla Web Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API). (See [Compatibility Status](https://github.com/Automattic/node-canvas/wiki/Compatibility-Status) for the current API compliance.) All utility methods and non-standard APIs are documented below.
### Utility methods
* [createCanvas()](#createcanvas)
* [createImageData()](#createimagedata)
* [loadImage()](#loadimage)
* [registerFont()](#registerfont)
### Non-standard APIs
* [Image#src](#imagesrc)
* [Image#dataMode](#imagedatamode)
* [Canvas#toBuffer()](#canvastobuffer)
* [Canvas#createPNGStream()](#canvascreatepngstream)
* [Canvas#createJPEGStream()](#canvascreatejpegstream)
* [Canvas#createPDFStream()](#canvascreatepdfstream)
* [Canvas#toDataURL()](#canvastodataurl)
* [CanvasRenderingContext2D#patternQuality](#canvasrenderingcontext2dpatternquality)
* [CanvasRenderingContext2D#quality](#canvasrenderingcontext2dquality)
* [CanvasRenderingContext2D#textDrawingMode](#canvasrenderingcontext2dtextdrawingmode)
* [CanvasRenderingContext2D#globalCompositeOperation = 'saturate'](#canvasrenderingcontext2dglobalcompositeoperation--saturate)
* [CanvasRenderingContext2D#antialias](#canvasrenderingcontext2dantialias)
### createCanvas()
> ```ts
> createCanvas(width: number, height: number, type?: 'PDF'|'SVG') => Canvas
> ```
Creates a Canvas instance. This method works in both Node.js and Web browsers, where there is no Canvas constructor. (See `browser.js` for the implementation that runs in browsers.)
```js
const { createCanvas } = require('canvas')
const mycanvas = createCanvas(200, 200)
const myPDFcanvas = createCanvas(600, 800, 'pdf') // see "PDF Support" section
```
### createImageData()
> ```ts
> createImageData(width: number, height: number) => ImageData
> createImageData(data: Uint8ClampedArray, width: number, height?: number) => ImageData
> // for alternative pixel formats:
> createImageData(data: Uint16Array, width: number, height?: number) => ImageData
> ```
Creates an ImageData instance. This method works in both Node.js and Web browsers.
```js
const { createImageData } = require('canvas')
const width = 20, height = 20
const arraySize = width * height * 4
const mydata = createImageData(new Uint8ClampedArray(arraySize), width)
```
### loadImage()
> ```ts
> loadImage() => Promise<Image>
> ```
Convenience method for loading images. This method works in both Node.js and Web browsers.
```js
const { loadImage } = require('canvas')
const myimg = loadImage('http://server.com/image.png')
myimg.then(() => {
  // do something with image
}).catch(err => {
  console.log('oh no!', err)
})
// or with async/await:
const myimg = await loadImage('http://server.com/image.png')
// do something with image
```
### registerFont()
> ```ts
> registerFont(path: string, { family: string, weight?: string, style?: string }) => void
> ```
To use a font file that is not installed as a system font, use `registerFont()` to register the font with Canvas. *This must be done before the Canvas is created.*
```js
const { registerFont, createCanvas } = require('canvas')
registerFont('comicsans.ttf', { family: 'Comic Sans' })
const canvas = createCanvas(500, 500)
const ctx = canvas.getContext('2d')
ctx.font = '12px "Comic Sans"'
ctx.fillText('Everyone hates this font :(', 250, 10)
```
The second argument is an object with properties that resemble the CSS properties that are specified in `@font-face` rules. You must specify at least `family`. `weight`, and `style` are optional and default to `'normal'`.
### Image#src
> ```ts
> img.src: string|Buffer
> ```
As in browsers, `img.src` can be set to a `data:` URI or a remote URL. In addition, node-canvas allows setting `src` to a local file path or `Buffer` instance.
```javascript
const { Image } = require('canvas')
// From a buffer:
fs.readFile('images/squid.png', (err, squid) => {
  if (err) throw err
  const img = new Image()
  img.onload = () => ctx.drawImage(img, 0, 0)
  img.onerror = err => { throw err }
  img.src = squid
})
// From a local file path:
const img = new Image()
img.onload = () => ctx.drawImage(img, 0, 0)
img.onerror = err => { throw err }
img.src = 'images/squid.png'
// From a remote URL:
img.src = 'http://picsum.photos/200/300'
// ... as above
// From a `data:` URI:
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
// ... as above
```
*Note: In some cases, `img.src=` is currently synchronous. However, you should always use `img.onload` and `img.onerror`, as we intend to make `img.src=` always asynchronous as it is in browsers. See https://github.com/Automattic/node-canvas/issues/1007.*
### Image#dataMode
> ```ts
> img.dataMode: number
> ```
Applies to JPEG images drawn to PDF canvases only.
Setting `img.dataMode = Image.MODE_MIME` or `Image.MODE_MIME|Image.MODE_IMAGE` enables MIME data tracking of images. When MIME data is tracked, PDF canvases can embed JPEGs directly into the output, rather than re-encoding into PNG. This can drastically reduce filesize and speed up rendering.
```javascript
const { Image, createCanvas } = require('canvas')
const canvas = createCanvas(w, h, 'pdf')
const img = new Image()
img.dataMode = Image.MODE_IMAGE // Only image data tracked
img.dataMode = Image.MODE_MIME // Only mime data tracked
img.dataMode = Image.MODE_MIME | Image.MODE_IMAGE // Both are tracked
```
If working with a non-PDF canvas, image data *must* be tracked; otherwise the output will be junk.
Enabling mime data tracking has no benefits (only a slow down) unless you are generating a PDF.
### Canvas#toBuffer()
> ```ts
> canvas.toBuffer((err: Error|null, result: Buffer) => void, mimeType?: string, config?: any) => void
> canvas.toBuffer(mimeType?: string, config?: any) => Buffer
> ```
Creates a [`Buffer`](https://nodejs.org/api/buffer.html) object representing the image contained in the canvas.
* **callback** If provided, the buffer will be provided in the callback instead of being returned by the function. Invoked with an error as the first argument if encoding failed, or the resulting buffer as the second argument if it succeeded. Not supported for mimeType `raw` or for PDF or SVG canvases.
* **mimeType** A string indicating the image format. Valid options are `image/png`, `image/jpeg` (if node-canvas was built with JPEG support), `raw` (unencoded data in BGRA order on little-endian (most) systems, ARGB on big-endian systems; top-to-bottom), `application/pdf` (for PDF canvases) and `image/svg+xml` (for SVG canvases). Defaults to `image/png` for image canvases, or the corresponding type for PDF or SVG canvas.
* **config**
  * For `image/jpeg`, an object specifying the quality (0 to 1), if progressive compression should be used and/or if chroma subsampling should be used: `{quality: 0.75, progressive: false, chromaSubsampling: true}`. All properties are optional.
  * For `image/png`, an object specifying the ZLIB compression level (between 0 and 9), the compression filter(s), the palette (indexed PNGs only), the the background palette index (indexed PNGs only) and/or the resolution (ppi): `{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0, resolution: undefined}`. All properties are optional.
    Note that the PNG format encodes the resolution in pixels per meter, so if you specify `96`, the file will encode 3780 ppm (~96.01 ppi). The resolution is undefined by default to match common browser behavior.
  * For `application/pdf`, an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. All properties are optional and default to `undefined`, except for `creationDate`, which defaults to the current date. *Adding metadata requires Cairo 1.16.0 or later.*
    For a description of these properties, see page 550 of [PDF 32000-1:2008](https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf).
    Note that there is no standard separator for `keywords`. A space is recommended because it is in common use by other applications, and Cairo will enclose the list of keywords in quotes if a comma or semicolon is used.
**Return value**
If no callback is provided, a [`Buffer`](https://nodejs.org/api/buffer.html). If a callback is provided, none.
#### Examples
```js
// Default: buf contains a PNG-encoded image
const buf = canvas.toBuffer()
// PNG-encoded, zlib compression level 3 for faster compression but bigger files, no filtering
const buf2 = canvas.toBuffer('image/png', { compressionLevel: 3, filters: canvas.PNG_FILTER_NONE })
// JPEG-encoded, 50% quality
const buf3 = canvas.toBuffer('image/jpeg', { quality: 0.5 })
// Asynchronous PNG
canvas.toBuffer((err, buf) => {
  if (err) throw err // encoding failed
  // buf is PNG-encoded image
})
canvas.toBuffer((err, buf) => {
  if (err) throw err // encoding failed
  // buf is JPEG-encoded image at 95% quality
}, 'image/jpeg', { quality: 0.95 })
// BGRA pixel values, native-endian
const buf4 = canvas.toBuffer('raw')
const { stride, width } = canvas
// In memory, this is `canvas.height * canvas.stride` bytes long.
// The top row of pixels, in BGRA order on little-endian hardware,
// left-to-right, is:
const topPixelsBGRALeftToRight = buf4.slice(0, width * 4)
// And the third row is:
const row3 = buf4.slice(2 * stride, 2 * stride + width * 4)
// SVG and PDF canvases
const myCanvas = createCanvas(w, h, 'pdf')
myCanvas.toBuffer() // returns a buffer containing a PDF-encoded canvas
// With optional metadata:
myCanvas.toBuffer('application/pdf', {
  title: 'my picture',
  keywords: 'node.js demo cairo',
  creationDate: new Date()
})
```
### Canvas#createPNGStream()
> ```ts
> canvas.createPNGStream(config?: any) => ReadableStream
> ```
Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) that emits PNG-encoded data.
* `config` An object specifying the ZLIB compression level (between 0 and 9), the compression filter(s), the palette (indexed PNGs only) and/or the background palette index (indexed PNGs only): `{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0, resolution: undefined}`. All properties are optional.
#### Examples
```javascript
const fs = require('fs')
const out = fs.createWriteStream(__dirname + '/test.png')
const stream = canvas.createPNGStream()
stream.pipe(out)
out.on('finish', () =>  console.log('The PNG file was created.'))
```
To encode indexed PNGs from canvases with `pixelFormat: 'A8'` or `'A1'`, provide an options object:
```js
const palette = new Uint8ClampedArray([
  //r    g    b    a
    0,  50,  50, 255, // index 1
   10,  90,  90, 255, // index 2
  127, 127, 255, 255
  // ...
])
canvas.createPNGStream({
  palette: palette,
  backgroundIndex: 0 // optional, defaults to 0
})
```
### Canvas#createJPEGStream()
> ```ts
> canvas.createJPEGStream(config?: any) => ReadableStream
> ```
Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) that emits JPEG-encoded data.
*Note: At the moment, `createJPEGStream()` is synchronous under the hood. That is, it runs in the main thread, not in the libuv threadpool.*
* `config` an object specifying the quality (0 to 1), if progressive compression should be used and/or if chroma subsampling should be used: `{quality: 0.75, progressive: false, chromaSubsampling: true}`. All properties are optional.
#### Examples
```javascript
const fs = require('fs')
const out = fs.createWriteStream(__dirname + '/test.jpeg')
const stream = canvas.createJPEGStream()
stream.pipe(out)
out.on('finish', () =>  console.log('The JPEG file was created.'))
// Disable 2x2 chromaSubsampling for deeper colors and use a higher quality
const stream = canvas.createJPEGStream({
  quality: 0.95,
  chromaSubsampling: false
})
```
### Canvas#createPDFStream()
> ```ts
> canvas.createPDFStream(config?: any) => ReadableStream
> ```
* `config` an object specifying optional document metadata: `{title: string, author: string, subject: string, keywords: string, creator: string, creationDate: Date, modDate: Date}`. See `toBuffer()` for more information. *Adding metadata requires Cairo 1.16.0 or later.*
Applies to PDF canvases only. Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) that emits the encoded PDF. `canvas.toBuffer()` also produces an encoded PDF, but `createPDFStream()` can be used to reduce memory usage.
### Canvas#toDataURL()
This is a standard API, but several non-standard calls are supported. The full list of supported calls is:
```js
dataUrl = canvas.toDataURL() // defaults to PNG
dataUrl = canvas.toDataURL('image/png')
dataUrl = canvas.toDataURL('image/jpeg')
dataUrl = canvas.toDataURL('image/jpeg', quality) // quality from 0 to 1
canvas.toDataURL((err, png) => { }) // defaults to PNG
canvas.toDataURL('image/png', (err, png) => { })
canvas.toDataURL('image/jpeg', (err, jpeg) => { }) // sync JPEG is not supported
canvas.toDataURL('image/jpeg', {...opts}, (err, jpeg) => { }) // see Canvas#createJPEGStream for valid options
canvas.toDataURL('image/jpeg', quality, (err, jpeg) => { }) // spec-following; quality from 0 to 1
```
### CanvasRenderingContext2D#patternQuality
> ```ts
> context.patternQuality: 'fast'|'good'|'best'|'nearest'|'bilinear'
> ```
Defaults to `'good'`. Affects pattern (gradient, image, etc.) rendering quality.
### CanvasRenderingContext2D#quality
> ```ts
> context.quality: 'fast'|'good'|'best'|'nearest'|'bilinear'
> ```
Defaults to `'good'`. Like `patternQuality`, but applies to transformations affecting more than just patterns.
### CanvasRenderingContext2D#textDrawingMode
> ```ts
> context.textDrawingMode: 'path'|'glyph'
> ```
Defaults to `'path'`. The effect depends on the canvas type:
* **Standard (image)** `glyph` and `path` both result in rasterized text. Glyph mode is faster than `path`, but may result in lower-quality text, especially when rotated or translated.
* **PDF** `glyph` will embed text instead of paths into the PDF. This is faster to encode, faster to open with PDF viewers, yields a smaller file size and makes the text selectable. The subset of the font needed to render the glyphs will be embedded in the PDF. This is usually the mode you want to use with PDF canvases.
* **SVG** `glyph` does *not* cause `<text>` elements to be produced as one might expect ([cairo bug](https://gitlab.freedesktop.org/cairo/cairo/issues/253)). Rather, `glyph` will create a `<defs>` section with a `<symbol>` for each glyph, then those glyphs be reused via `<use>` elements. `path` mode creates a `<path>` element for each text string. `glyph` mode is faster and yields a smaller file size.
In `glyph` mode, `ctx.strokeText()` and `ctx.fillText()` behave the same (aside from using the stroke and fill style, respectively).
This property is tracked as part of the canvas state in save/restore.
### CanvasRenderingContext2D#globalCompositeOperation = 'saturate'
In addition to all of the standard global composite operations defined by the Canvas specification, the ['saturate'](https://www.cairographics.org/operators/#saturate) operation is also available.
### CanvasRenderingContext2D#antialias
> ```ts
> context.antialias: 'default'|'none'|'gray'|'subpixel'
> ```
Sets the anti-aliasing mode.
## PDF Output Support
node-canvas can create PDF documents instead of images. The canvas type must be set when creating the canvas as follows:
```js
const canvas = createCanvas(200, 500, 'pdf')
```
An additional method `.addPage()` is then available to create multiple page PDFs:
```js
// On first page
ctx.font = '22px Helvetica'
ctx.fillText('Hello World', 50, 80)
ctx.addPage()
// Now on second page
ctx.font = '22px Helvetica'
ctx.fillText('Hello World 2', 50, 80)
canvas.toBuffer() // returns a PDF file
canvas.createPDFStream() // returns a ReadableStream that emits a PDF
// With optional document metadata (requires Cairo 1.16.0):
canvas.toBuffer('application/pdf', {
  title: 'my picture',
  keywords: 'node.js demo cairo',
  creationDate: new Date()
})
```
It is also possible to create pages with different sizes by passing `width` and `height` to the `.addPage()` method:
```js
ctx.font = '22px Helvetica'
ctx.fillText('Hello World', 50, 80)
ctx.addPage(400, 800)
ctx.fillText('Hello World 2', 50, 80)
```
See also:
* [Image#dataMode](#imagedatamode) for embedding JPEGs in PDFs
* [Canvas#createPDFStream()](#canvascreatepdfstream) for creating PDF streams
* [CanvasRenderingContext2D#textDrawingMode](#canvasrenderingcontext2dtextdrawingmode)
  for embedding text instead of paths
## SVG Output Support
node-canvas can create SVG documents instead of images. The canvas type must be set when creating the canvas as follows:
```js
const canvas = createCanvas(200, 500, 'svg')
// Use the normal primitives.
fs.writeFileSync('out.svg', canvas.toBuffer())
```
## SVG Image Support
If librsvg is available when node-canvas is installed, node-canvas can render SVG images to your canvas context. This currently works by rasterizing the SVG image (i.e. drawing an SVG image to an SVG canvas will not preserve the SVG data).
```js
const img = new Image()
img.onload = () => ctx.drawImage(img, 0, 0)
img.onerror = err => { throw err }
img.src = './example.svg'
```
## Image pixel formats (experimental)
node-canvas has experimental support for additional pixel formats, roughly following the [Canvas color space proposal](https://github.com/WICG/canvas-color-space/blob/master/CanvasColorSpaceProposal.md).
```js
const canvas = createCanvas(200, 200)
const ctx = canvas.getContext('2d', { pixelFormat: 'A8' })
```
By default, canvases are created in the `RGBA32` format, which corresponds to the native HTML Canvas behavior. Each pixel is 32 bits. The JavaScript APIs that involve pixel data (`getImageData`, `putImageData`) store the colors in the order {red, green, blue, alpha} without alpha pre-multiplication. (The C++ API stores the colors in the order {alpha, red, green, blue} in native-[endian](https://en.wikipedia.org/wiki/Endianness) ordering, with alpha pre-multiplication.)
These additional pixel formats have experimental support:
* `RGB24` Like `RGBA32`, but the 8 alpha bits are always opaque. This format is always used if the `alpha` context attribute is set to false (i.e. `canvas.getContext('2d', {alpha: false})`). This format can be faster than `RGBA32` because transparency does not need to be calculated.
* `A8` Each pixel is 8 bits. This format can either be used for creating grayscale images (treating each byte as an alpha value), or for creating indexed PNGs (treating each byte as a palette index) (see [the example using alpha values with `fillStyle`](examples/indexed-png-alpha.js) and [the example using `imageData`](examples/indexed-png-image-data.js)).
* `RGB16_565` Each pixel is 16 bits, with red in the upper 5 bits, green in the middle 6 bits, and blue in the lower 5 bits, in native platform endianness. Some hardware devices and frame buffers use this format. Note that PNG does not support this format; when creating a PNG, the image will be converted to 24-bit RGB. This format is thus suboptimal for generating PNGs. `ImageData` instances for this mode use a `Uint16Array` instead of a `Uint8ClampedArray`.
* `A1` Each pixel is 1 bit, and pixels are packed together into 32-bit quantities. The ordering of the bits matches the endianness of the
  platform: on a little-endian machine, the first pixel is the least-significant bit. This format can be used for creating single-color images. *Support for this format is incomplete, see note below.*
* `RGB30` Each pixel is 30 bits, with red in the upper 10, green in the middle 10, and blue in the lower 10. (Requires Cairo 1.12 or later.) *Support for this format is incomplete, see note below.*
Notes and caveats:
* Using a non-default format can affect the behavior of APIs that involve pixel data:
  * `context2d.createImageData` The size of the array returned depends on the number of bit per pixel for the underlying image data format, per the above descriptions.
  * `context2d.getImageData` The format of the array returned depends on the underlying image mode, per the above descriptions. Be aware of platform endianness, which can be determined using node.js's [`os.endianness()`](https://nodejs.org/api/os.html#os_os_endianness)
    function.
  * `context2d.putImageData` As above.
* `A1` and `RGB30` do not yet support `getImageData` or `putImageData`. Have a use case and/or opinion on working with these formats? Open an issue and let us know! (See #935.)
* `A1`, `A8`, `RGB30` and `RGB16_565` with shadow blurs may crash or not render properly.
* The `ImageData(width, height)` and `ImageData(Uint8ClampedArray, width)` constructors assume 4 bytes per pixel. To create an `ImageData` instance with a different number of bytes per pixel, use `new ImageData(new Uint8ClampedArray(size), width, height)` or `new ImageData(new Uint16ClampedArray(size), width, height)`.
## Testing
First make sure you've built the latest version. Get all the deps you need (see [compiling](#compiling) above), and run:
```
npm install --build-from-source
```
For visual tests: `npm run test-server` and point your browser to http://localhost:4000.
For unit tests: `npm run test`.
## Benchmarks
Benchmarks live in the `benchmarks` directory.
## Examples
Examples line in the `examples` directory. Most produce a png image of the same name, and others such as *live-clock.js* launch an HTTP server to be viewed in the browser.
## Original Authors
  - TJ Holowaychuk ([tj](http://github.com/tj))
  - Nathan Rajlich ([TooTallNate](http://github.com/TooTallNate))
  - Rod Vagg ([rvagg](http://github.com/rvagg))
  - Juriy Zaytsev ([kangax](http://github.com/kangax))
## License
### node-canvas
(The MIT License)
Copyright (c) 2010 LearnBoost, and contributors &lt;dev@learnboost.com&gt;
Copyright (c) 2014 Automattic, Inc and contributors &lt;dev@automattic.com&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the 'Software'), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
### BMP parser
See [license](src/bmp/LICENSE.md)
node_modules/canvas/binding.gyp
New file
@@ -0,0 +1,230 @@
{
  'conditions': [
    ['OS=="win"', {
      'variables': {
        'GTK_Root%': 'C:/GTK',  # Set the location of GTK all-in-one bundle
        'with_jpeg%': 'false',
        'with_gif%': 'false',
        'with_rsvg%': 'false',
        'variables': { # Nest jpeg_root to evaluate it before with_jpeg
          'jpeg_root%': '<!(node ./util/win_jpeg_lookup)'
        },
        'jpeg_root%': '<(jpeg_root)', # Take value of nested variable
        'conditions': [
          ['jpeg_root==""', {
            'with_jpeg%': 'false'
          }, {
            'with_jpeg%': 'true'
          }]
        ]
      }
    }, {  # 'OS!="win"'
      'variables': {
        'with_jpeg%': '<!(node ./util/has_lib.js jpeg)',
        'with_gif%': '<!(node ./util/has_lib.js gif)',
        'with_rsvg%': '<!(node ./util/has_lib.js rsvg)'
      }
    }]
  ],
  'targets': [
    {
      'target_name': 'canvas-postbuild',
      'dependencies': ['canvas'],
      'conditions': [
        ['OS=="win"', {
          'copies': [{
            'destination': '<(PRODUCT_DIR)',
            'files': [
              '<(GTK_Root)/bin/zlib1.dll',
              '<(GTK_Root)/bin/libintl-8.dll',
              '<(GTK_Root)/bin/libpng14-14.dll',
              '<(GTK_Root)/bin/libpangocairo-1.0-0.dll',
              '<(GTK_Root)/bin/libpango-1.0-0.dll',
              '<(GTK_Root)/bin/libpangoft2-1.0-0.dll',
              '<(GTK_Root)/bin/libpangowin32-1.0-0.dll',
              '<(GTK_Root)/bin/libcairo-2.dll',
              '<(GTK_Root)/bin/libfontconfig-1.dll',
              '<(GTK_Root)/bin/libfreetype-6.dll',
              '<(GTK_Root)/bin/libglib-2.0-0.dll',
              '<(GTK_Root)/bin/libgobject-2.0-0.dll',
              '<(GTK_Root)/bin/libgmodule-2.0-0.dll',
              '<(GTK_Root)/bin/libgthread-2.0-0.dll',
              '<(GTK_Root)/bin/libexpat-1.dll'
            ]
          }]
        }]
      ]
    },
    {
      'target_name': 'canvas',
      'include_dirs': ["<!(node -e \"require('nan')\")"],
      'sources': [
        'src/backend/Backend.cc',
        'src/backend/ImageBackend.cc',
        'src/backend/PdfBackend.cc',
        'src/backend/SvgBackend.cc',
        'src/bmp/BMPParser.cc',
        'src/Backends.cc',
        'src/Canvas.cc',
        'src/CanvasGradient.cc',
        'src/CanvasPattern.cc',
        'src/CanvasRenderingContext2d.cc',
        'src/closure.cc',
        'src/color.cc',
        'src/Image.cc',
        'src/ImageData.cc',
        'src/init.cc',
        'src/register_font.cc'
      ],
      'conditions': [
        ['OS=="win"', {
          'libraries': [
            '-l<(GTK_Root)/lib/cairo.lib',
            '-l<(GTK_Root)/lib/libpng.lib',
            '-l<(GTK_Root)/lib/pangocairo-1.0.lib',
            '-l<(GTK_Root)/lib/pango-1.0.lib',
            '-l<(GTK_Root)/lib/freetype.lib',
            '-l<(GTK_Root)/lib/glib-2.0.lib',
            '-l<(GTK_Root)/lib/gobject-2.0.lib'
          ],
          'include_dirs': [
            '<(GTK_Root)/include',
            '<(GTK_Root)/include/cairo',
            '<(GTK_Root)/include/pango-1.0',
            '<(GTK_Root)/include/glib-2.0',
            '<(GTK_Root)/include/freetype2',
            '<(GTK_Root)/lib/glib-2.0/include'
          ],
          'defines': [
            '_USE_MATH_DEFINES',  # for M_PI
            'NOMINMAX' # allow std::min/max to work
          ],
          'configurations': {
            'Debug': {
              'msvs_settings': {
                'VCCLCompilerTool': {
                  'WarningLevel': 4,
                  'ExceptionHandling': 1,
                  'DisableSpecificWarnings': [
                    4100, 4611
                  ]
                }
              }
            },
            'Release': {
              'msvs_settings': {
                'VCCLCompilerTool': {
                  'WarningLevel': 4,
                  'ExceptionHandling': 1,
                  'DisableSpecificWarnings': [
                    4100, 4611
                  ]
                }
              }
            }
          }
        }, {  # 'OS!="win"'
          'libraries': [
            '<!@(pkg-config pixman-1 --libs)',
            '<!@(pkg-config cairo --libs)',
            '<!@(pkg-config libpng --libs)',
            '<!@(pkg-config pangocairo --libs)',
            '<!@(pkg-config freetype2 --libs)'
          ],
          'include_dirs': [
            '<!@(pkg-config cairo --cflags-only-I | sed s/-I//g)',
            '<!@(pkg-config libpng --cflags-only-I | sed s/-I//g)',
            '<!@(pkg-config pangocairo --cflags-only-I | sed s/-I//g)',
            '<!@(pkg-config freetype2 --cflags-only-I | sed s/-I//g)'
          ],
          'cflags': ['-Wno-cast-function-type'],
          'cflags!': ['-fno-exceptions'],
          'cflags_cc!': ['-fno-exceptions']
        }],
        ['OS=="mac"', {
          'xcode_settings': {
            'GCC_ENABLE_CPP_EXCEPTIONS': 'YES'
          }
        }],
        ['with_jpeg=="true"', {
          'defines': [
            'HAVE_JPEG'
          ],
          'conditions': [
            ['OS=="win"', {
              'copies': [{
                'destination': '<(PRODUCT_DIR)',
                'files': [
                  '<(jpeg_root)/bin/jpeg62.dll',
                ]
              }],
              'include_dirs': [
                '<(jpeg_root)/include'
              ],
              'libraries': [
                '-l<(jpeg_root)/lib/jpeg.lib',
              ]
            }, {
              'include_dirs': [
                '<!@(pkg-config libjpeg --cflags-only-I | sed s/-I//g)'
              ],
              'libraries': [
                '<!@(pkg-config libjpeg --libs)'
              ]
            }]
          ]
        }],
        ['with_gif=="true"', {
          'defines': [
            'HAVE_GIF'
          ],
          'conditions': [
            ['OS=="win"', {
              'libraries': [
                '-l<(GTK_Root)/lib/gif.lib'
              ]
            }, {
              'include_dirs': [
                '/opt/homebrew/include'
              ],
              'libraries': [
                '-L/opt/homebrew/lib',
                '-lgif'
              ]
            }]
          ]
        }],
        ['with_rsvg=="true"', {
          'defines': [
            'HAVE_RSVG'
          ],
          'conditions': [
            ['OS=="win"', {
              'copies': [{
                'destination': '<(PRODUCT_DIR)',
                'files': [
                  '<(GTK_Root)/bin/librsvg-2-2.dll',
                  '<(GTK_Root)/bin/libgdk_pixbuf-2.0-0.dll',
                  '<(GTK_Root)/bin/libgio-2.0-0.dll',
                  '<(GTK_Root)/bin/libcroco-0.6-3.dll',
                  '<(GTK_Root)/bin/libgsf-1-114.dll',
                  '<(GTK_Root)/bin/libxml2-2.dll'
                ]
              }],
              'libraries': [
                '-l<(GTK_Root)/lib/librsvg-2-2.lib'
              ]
            }, {
              'include_dirs': [
                '<!@(pkg-config librsvg-2.0 --cflags-only-I | sed s/-I//g)'
              ],
              'libraries': [
                '<!@(pkg-config librsvg-2.0 --libs)'
              ]
            }]
          ]
        }]
      ]
    }
  ]
}
node_modules/canvas/browser.js
New file
@@ -0,0 +1,35 @@
/* globals document, ImageData */
const parseFont = require('./lib/parse-font')
exports.parseFont = parseFont
exports.createCanvas = function (width, height) {
  return Object.assign(document.createElement('canvas'), { width: width, height: height })
}
exports.createImageData = function (array, width, height) {
  // Browser implementation of ImageData looks at the number of arguments passed
  switch (arguments.length) {
    case 0: return new ImageData()
    case 1: return new ImageData(array)
    case 2: return new ImageData(array, width)
    default: return new ImageData(array, width, height)
  }
}
exports.loadImage = function (src, options) {
  return new Promise(function (resolve, reject) {
    const image = Object.assign(document.createElement('img'), options)
    function cleanup () {
      image.onload = null
      image.onerror = null
    }
    image.onload = function () { cleanup(); resolve(image) }
    image.onerror = function () { cleanup(); reject(new Error('Failed to load the image "' + src + '"')) }
    image.src = src
  })
}
node_modules/canvas/build/Release/canvas.exp
Binary files differ
node_modules/canvas/build/Release/canvas.iobj
Binary files differ
node_modules/canvas/build/Release/canvas.ipdb
Binary files differ
node_modules/canvas/build/Release/canvas.lib
Binary files differ
node_modules/canvas/build/Release/canvas.node
Binary files differ
node_modules/canvas/build/Release/canvas.pdb
Binary files differ
node_modules/canvas/build/Release/libbrotlicommon.dll
Binary files differ
node_modules/canvas/build/Release/libbrotlidec.dll
Binary files differ
node_modules/canvas/build/Release/libbz2-1.dll
Binary files differ
node_modules/canvas/build/Release/libcairo-2.dll
Binary files differ
node_modules/canvas/build/Release/libcairo-gobject-2.dll
Binary files differ
node_modules/canvas/build/Release/libdatrie-1.dll
Binary files differ
node_modules/canvas/build/Release/libexpat-1.dll
Binary files differ
node_modules/canvas/build/Release/libffi-8.dll
Binary files differ
node_modules/canvas/build/Release/libfontconfig-1.dll
Binary files differ
node_modules/canvas/build/Release/libfreetype-6.dll
Binary files differ
node_modules/canvas/build/Release/libfribidi-0.dll
Binary files differ
node_modules/canvas/build/Release/libgcc_s_seh-1.dll
Binary files differ
node_modules/canvas/build/Release/libgdk_pixbuf-2.0-0.dll
Binary files differ
node_modules/canvas/build/Release/libgif-7.dll
Binary files differ
node_modules/canvas/build/Release/libgio-2.0-0.dll
Binary files differ
node_modules/canvas/build/Release/libglib-2.0-0.dll
Binary files differ
node_modules/canvas/build/Release/libgmodule-2.0-0.dll
Binary files differ
node_modules/canvas/build/Release/libgobject-2.0-0.dll
Binary files differ
node_modules/canvas/build/Release/libgraphite2.dll
Binary files differ
node_modules/canvas/build/Release/libharfbuzz-0.dll
Binary files differ
node_modules/canvas/build/Release/libiconv-2.dll
Binary files differ
node_modules/canvas/build/Release/libintl-8.dll
Binary files differ
node_modules/canvas/build/Release/libjpeg-8.dll
Binary files differ
node_modules/canvas/build/Release/liblzma-5.dll
Binary files differ
node_modules/canvas/build/Release/libpango-1.0-0.dll
Binary files differ
node_modules/canvas/build/Release/libpangocairo-1.0-0.dll
Binary files differ
node_modules/canvas/build/Release/libpangoft2-1.0-0.dll
Binary files differ
node_modules/canvas/build/Release/libpangowin32-1.0-0.dll
Binary files differ
node_modules/canvas/build/Release/libpcre2-8-0.dll
Binary files differ
node_modules/canvas/build/Release/libpixman-1-0.dll
Binary files differ
node_modules/canvas/build/Release/libpng16-16.dll
Binary files differ
node_modules/canvas/build/Release/librsvg-2-2.dll
Binary files differ
node_modules/canvas/build/Release/libstdc++-6.dll
Binary files differ
node_modules/canvas/build/Release/libthai-0.dll
Binary files differ
node_modules/canvas/build/Release/libwinpthread-1.dll
Binary files differ
node_modules/canvas/build/Release/libxml2-2.dll
Binary files differ
node_modules/canvas/build/Release/obj/canvas/canvas.node.recipe
New file
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
  <ProjectOutputs>
    <ProjectOutput>
      <FullPath>D:\a\node-canvas\node-canvas\build\Release\canvas.node</FullPath>
    </ProjectOutput>
  </ProjectOutputs>
  <ContentFiles />
  <SatelliteDlls />
  <NonRecipeFileRefs />
</Project>
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/CL.command.1.tlog
Binary files differ
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/CL.read.1.tlog
Binary files differ
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/CL.write.1.tlog
Binary files differ
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/canvas.lastbuildstate
New file
@@ -0,0 +1,2 @@
PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:VCServicingVersionMFC=14.29.30136:TargetPlatformVersion=10.0.22000.0:
Release|x64|D:\a\node-canvas\node-canvas\build\|
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/canvas.write.1u.tlog
Binary files differ
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/link.command.1.tlog
Binary files differ
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/link.read.1.tlog
Binary files differ
node_modules/canvas/build/Release/obj/canvas/canvas.tlog/link.write.1.tlog
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/Backends.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/Canvas.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/CanvasGradient.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/CanvasPattern.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/CanvasRenderingContext2d.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/Image.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/ImageData.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/backend/Backend.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/backend/ImageBackend.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/backend/PdfBackend.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/backend/SvgBackend.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/bmp/BMPParser.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/closure.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/color.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/init.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/src/register_font.obj
Binary files differ
node_modules/canvas/build/Release/obj/canvas/win_delay_load_hook.obj
Binary files differ
node_modules/canvas/build/Release/zlib1.dll
Binary files differ
node_modules/canvas/changes
New file
@@ -0,0 +1,19 @@
toBuffer and other canvas methods were static and unwrapped
the this arg. this seemed weird. they are now instance
methods since that's how object wrap works, XXX add
instanceof checks?
node-canvas no lunger flushes microtasks when calling stream
cbs XXX wait now that I say it that sounds wrong?
we don't need CHECK_RECEIVER anymore? dbl check that?
TODO: check changes from As<X> to AsX. bad assumption.
dbl check try catch changes?
Error is now CairoError
global this in all callbacks, streamPNGSync now flushes microtasks
streamPDF uses MakeCallback, not runInAsyncScope
node_modules/canvas/index.js
New file
@@ -0,0 +1,94 @@
const Canvas = require('./lib/canvas')
const Image = require('./lib/image')
const CanvasRenderingContext2D = require('./lib/context2d')
const CanvasPattern = require('./lib/pattern')
const parseFont = require('./lib/parse-font')
const packageJson = require('./package.json')
const bindings = require('./lib/bindings')
const fs = require('fs')
const PNGStream = require('./lib/pngstream')
const PDFStream = require('./lib/pdfstream')
const JPEGStream = require('./lib/jpegstream')
const { DOMPoint, DOMMatrix } = require('./lib/DOMMatrix')
function createCanvas (width, height, type) {
  return new Canvas(width, height, type)
}
function createImageData (array, width, height) {
  return new bindings.ImageData(array, width, height)
}
function loadImage (src) {
  return new Promise((resolve, reject) => {
    const image = new Image()
    function cleanup () {
      image.onload = null
      image.onerror = null
    }
    image.onload = () => { cleanup(); resolve(image) }
    image.onerror = (err) => { cleanup(); reject(err) }
    image.src = src
  })
}
/**
 * Resolve paths for registerFont. Must be called *before* creating a Canvas
 * instance.
 * @param src {string} Path to font file.
 * @param fontFace {{family: string, weight?: string, style?: string}} Object
 * specifying font information. `weight` and `style` default to `"normal"`.
 */
function registerFont (src, fontFace) {
  // TODO this doesn't need to be on Canvas; it should just be a static method
  // of `bindings`.
  return Canvas._registerFont(fs.realpathSync(src), fontFace)
}
/**
 * Unload all fonts from pango to free up memory
 */
function deregisterAllFonts () {
  return Canvas._deregisterAllFonts()
}
exports.Canvas = Canvas
exports.Context2d = CanvasRenderingContext2D // Legacy/compat export
exports.CanvasRenderingContext2D = CanvasRenderingContext2D
exports.CanvasGradient = bindings.CanvasGradient
exports.CanvasPattern = CanvasPattern
exports.Image = Image
exports.ImageData = bindings.ImageData
exports.PNGStream = PNGStream
exports.PDFStream = PDFStream
exports.JPEGStream = JPEGStream
exports.DOMMatrix = DOMMatrix
exports.DOMPoint = DOMPoint
exports.registerFont = registerFont
exports.deregisterAllFonts = deregisterAllFonts
exports.parseFont = parseFont
exports.createCanvas = createCanvas
exports.createImageData = createImageData
exports.loadImage = loadImage
exports.backends = bindings.Backends
/** Library version. */
exports.version = packageJson.version
/** Cairo version. */
exports.cairoVersion = bindings.cairoVersion
/** jpeglib version. */
exports.jpegVersion = bindings.jpegVersion
/** gif_lib version. */
exports.gifVersion = bindings.gifVersion ? bindings.gifVersion.replace(/[^.\d]/g, '') : undefined
/** freetype version. */
exports.freetypeVersion = bindings.freetypeVersion
/** rsvg version. */
exports.rsvgVersion = bindings.rsvgVersion
/** pango version. */
exports.pangoVersion = bindings.pangoVersion
node_modules/canvas/lib/DOMMatrix.js
New file
@@ -0,0 +1,620 @@
'use strict'
const util = require('util')
// DOMMatrix per https://drafts.fxtf.org/geometry/#DOMMatrix
class DOMPoint {
  constructor (x, y, z, w) {
    if (typeof x === 'object' && x !== null) {
      w = x.w
      z = x.z
      y = x.y
      x = x.x
    }
    this.x = typeof x === 'number' ? x : 0
    this.y = typeof y === 'number' ? y : 0
    this.z = typeof z === 'number' ? z : 0
    this.w = typeof w === 'number' ? w : 1
  }
}
// Constants to index into _values (col-major)
const M11 = 0; const M12 = 1; const M13 = 2; const M14 = 3
const M21 = 4; const M22 = 5; const M23 = 6; const M24 = 7
const M31 = 8; const M32 = 9; const M33 = 10; const M34 = 11
const M41 = 12; const M42 = 13; const M43 = 14; const M44 = 15
const DEGREE_PER_RAD = 180 / Math.PI
const RAD_PER_DEGREE = Math.PI / 180
function parseMatrix (init) {
  let parsed = init.replace('matrix(', '')
  parsed = parsed.split(',', 7) // 6 + 1 to handle too many params
  if (parsed.length !== 6) throw new Error(`Failed to parse ${init}`)
  parsed = parsed.map(parseFloat)
  return [
    parsed[0], parsed[1], 0, 0,
    parsed[2], parsed[3], 0, 0,
    0, 0, 1, 0,
    parsed[4], parsed[5], 0, 1
  ]
}
function parseMatrix3d (init) {
  let parsed = init.replace('matrix3d(', '')
  parsed = parsed.split(',', 17) // 16 + 1 to handle too many params
  if (parsed.length !== 16) throw new Error(`Failed to parse ${init}`)
  return parsed.map(parseFloat)
}
function parseTransform (tform) {
  const type = tform.split('(', 1)[0]
  switch (type) {
    case 'matrix':
      return parseMatrix(tform)
    case 'matrix3d':
      return parseMatrix3d(tform)
    // TODO This is supposed to support any CSS transform value.
    default:
      throw new Error(`${type} parsing not implemented`)
  }
}
class DOMMatrix {
  constructor (init) {
    this._is2D = true
    this._values = new Float64Array([
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ])
    let i
    if (typeof init === 'string') { // parse CSS transformList
      if (init === '') return // default identity matrix
      const tforms = init.split(/\)\s+/, 20).map(parseTransform)
      if (tforms.length === 0) return
      init = tforms[0]
      for (i = 1; i < tforms.length; i++) init = multiply(tforms[i], init)
    }
    i = 0
    if (init && init.length === 6) {
      setNumber2D(this, M11, init[i++])
      setNumber2D(this, M12, init[i++])
      setNumber2D(this, M21, init[i++])
      setNumber2D(this, M22, init[i++])
      setNumber2D(this, M41, init[i++])
      setNumber2D(this, M42, init[i++])
    } else if (init && init.length === 16) {
      setNumber2D(this, M11, init[i++])
      setNumber2D(this, M12, init[i++])
      setNumber3D(this, M13, init[i++])
      setNumber3D(this, M14, init[i++])
      setNumber2D(this, M21, init[i++])
      setNumber2D(this, M22, init[i++])
      setNumber3D(this, M23, init[i++])
      setNumber3D(this, M24, init[i++])
      setNumber3D(this, M31, init[i++])
      setNumber3D(this, M32, init[i++])
      setNumber3D(this, M33, init[i++])
      setNumber3D(this, M34, init[i++])
      setNumber2D(this, M41, init[i++])
      setNumber2D(this, M42, init[i++])
      setNumber3D(this, M43, init[i++])
      setNumber3D(this, M44, init[i])
    } else if (init !== undefined) {
      throw new TypeError('Expected string or array.')
    }
  }
  toString () {
    return this.is2D
      ? `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})`
      : `matrix3d(${this._values.join(', ')})`
  }
  multiply (other) {
    return newInstance(this._values).multiplySelf(other)
  }
  multiplySelf (other) {
    this._values = multiply(other._values, this._values)
    if (!other.is2D) this._is2D = false
    return this
  }
  preMultiplySelf (other) {
    this._values = multiply(this._values, other._values)
    if (!other.is2D) this._is2D = false
    return this
  }
  translate (tx, ty, tz) {
    return newInstance(this._values).translateSelf(tx, ty, tz)
  }
  translateSelf (tx, ty, tz) {
    if (typeof tx !== 'number') tx = 0
    if (typeof ty !== 'number') ty = 0
    if (typeof tz !== 'number') tz = 0
    this._values = multiply([
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      tx, ty, tz, 1
    ], this._values)
    if (tz !== 0) this._is2D = false
    return this
  }
  scale (scaleX, scaleY, scaleZ, originX, originY, originZ) {
    return newInstance(this._values).scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ)
  }
  scale3d (scale, originX, originY, originZ) {
    return newInstance(this._values).scale3dSelf(scale, originX, originY, originZ)
  }
  scale3dSelf (scale, originX, originY, originZ) {
    return this.scaleSelf(scale, scale, scale, originX, originY, originZ)
  }
  scaleSelf (scaleX, scaleY, scaleZ, originX, originY, originZ) {
    // Not redundant with translate's checks because we need to negate the values later.
    if (typeof originX !== 'number') originX = 0
    if (typeof originY !== 'number') originY = 0
    if (typeof originZ !== 'number') originZ = 0
    this.translateSelf(originX, originY, originZ)
    if (typeof scaleX !== 'number') scaleX = 1
    if (typeof scaleY !== 'number') scaleY = scaleX
    if (typeof scaleZ !== 'number') scaleZ = 1
    this._values = multiply([
      scaleX, 0, 0, 0,
      0, scaleY, 0, 0,
      0, 0, scaleZ, 0,
      0, 0, 0, 1
    ], this._values)
    this.translateSelf(-originX, -originY, -originZ)
    if (scaleZ !== 1 || originZ !== 0) this._is2D = false
    return this
  }
  rotateFromVector (x, y) {
    return newInstance(this._values).rotateFromVectorSelf(x, y)
  }
  rotateFromVectorSelf (x, y) {
    if (typeof x !== 'number') x = 0
    if (typeof y !== 'number') y = 0
    const theta = (x === 0 && y === 0) ? 0 : Math.atan2(y, x) * DEGREE_PER_RAD
    return this.rotateSelf(theta)
  }
  rotate (rotX, rotY, rotZ) {
    return newInstance(this._values).rotateSelf(rotX, rotY, rotZ)
  }
  rotateSelf (rotX, rotY, rotZ) {
    if (rotY === undefined && rotZ === undefined) {
      rotZ = rotX
      rotX = rotY = 0
    }
    if (typeof rotY !== 'number') rotY = 0
    if (typeof rotZ !== 'number') rotZ = 0
    if (rotX !== 0 || rotY !== 0) this._is2D = false
    rotX *= RAD_PER_DEGREE
    rotY *= RAD_PER_DEGREE
    rotZ *= RAD_PER_DEGREE
    let c, s
    c = Math.cos(rotZ)
    s = Math.sin(rotZ)
    this._values = multiply([
      c, s, 0, 0,
      -s, c, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ], this._values)
    c = Math.cos(rotY)
    s = Math.sin(rotY)
    this._values = multiply([
      c, 0, -s, 0,
      0, 1, 0, 0,
      s, 0, c, 0,
      0, 0, 0, 1
    ], this._values)
    c = Math.cos(rotX)
    s = Math.sin(rotX)
    this._values = multiply([
      1, 0, 0, 0,
      0, c, s, 0,
      0, -s, c, 0,
      0, 0, 0, 1
    ], this._values)
    return this
  }
  rotateAxisAngle (x, y, z, angle) {
    return newInstance(this._values).rotateAxisAngleSelf(x, y, z, angle)
  }
  rotateAxisAngleSelf (x, y, z, angle) {
    if (typeof x !== 'number') x = 0
    if (typeof y !== 'number') y = 0
    if (typeof z !== 'number') z = 0
    // Normalize axis
    const length = Math.sqrt(x * x + y * y + z * z)
    if (length === 0) return this
    if (length !== 1) {
      x /= length
      y /= length
      z /= length
    }
    angle *= RAD_PER_DEGREE
    const c = Math.cos(angle)
    const s = Math.sin(angle)
    const t = 1 - c
    const tx = t * x
    const ty = t * y
    // NB: This is the generic transform. If the axis is a major axis, there are
    // faster transforms.
    this._values = multiply([
      tx * x + c, tx * y + s * z, tx * z - s * y, 0,
      tx * y - s * z, ty * y + c, ty * z + s * x, 0,
      tx * z + s * y, ty * z - s * x, t * z * z + c, 0,
      0, 0, 0, 1
    ], this._values)
    if (x !== 0 || y !== 0) this._is2D = false
    return this
  }
  skewX (sx) {
    return newInstance(this._values).skewXSelf(sx)
  }
  skewXSelf (sx) {
    if (typeof sx !== 'number') return this
    const t = Math.tan(sx * RAD_PER_DEGREE)
    this._values = multiply([
      1, 0, 0, 0,
      t, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ], this._values)
    return this
  }
  skewY (sy) {
    return newInstance(this._values).skewYSelf(sy)
  }
  skewYSelf (sy) {
    if (typeof sy !== 'number') return this
    const t = Math.tan(sy * RAD_PER_DEGREE)
    this._values = multiply([
      1, t, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ], this._values)
    return this
  }
  flipX () {
    return newInstance(multiply([
      -1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ], this._values))
  }
  flipY () {
    return newInstance(multiply([
      1, 0, 0, 0,
      0, -1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ], this._values))
  }
  inverse () {
    return newInstance(this._values).invertSelf()
  }
  invertSelf () {
    const m = this._values
    const inv = m.map(v => 0)
    inv[0] = m[5] * m[10] * m[15] -
            m[5] * m[11] * m[14] -
            m[9] * m[6] * m[15] +
            m[9] * m[7] * m[14] +
            m[13] * m[6] * m[11] -
            m[13] * m[7] * m[10]
    inv[4] = -m[4] * m[10] * m[15] +
            m[4] * m[11] * m[14] +
            m[8] * m[6] * m[15] -
            m[8] * m[7] * m[14] -
            m[12] * m[6] * m[11] +
            m[12] * m[7] * m[10]
    inv[8] = m[4] * m[9] * m[15] -
            m[4] * m[11] * m[13] -
            m[8] * m[5] * m[15] +
            m[8] * m[7] * m[13] +
            m[12] * m[5] * m[11] -
            m[12] * m[7] * m[9]
    inv[12] = -m[4] * m[9] * m[14] +
            m[4] * m[10] * m[13] +
            m[8] * m[5] * m[14] -
            m[8] * m[6] * m[13] -
            m[12] * m[5] * m[10] +
            m[12] * m[6] * m[9]
    // If the determinant is zero, this matrix cannot be inverted, and all
    // values should be set to NaN, with the is2D flag set to false.
    const det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]
    if (det === 0) {
      this._values = m.map(v => NaN)
      this._is2D = false
      return this
    }
    inv[1] = -m[1] * m[10] * m[15] +
            m[1] * m[11] * m[14] +
            m[9] * m[2] * m[15] -
            m[9] * m[3] * m[14] -
            m[13] * m[2] * m[11] +
            m[13] * m[3] * m[10]
    inv[5] = m[0] * m[10] * m[15] -
            m[0] * m[11] * m[14] -
            m[8] * m[2] * m[15] +
            m[8] * m[3] * m[14] +
            m[12] * m[2] * m[11] -
            m[12] * m[3] * m[10]
    inv[9] = -m[0] * m[9] * m[15] +
            m[0] * m[11] * m[13] +
            m[8] * m[1] * m[15] -
            m[8] * m[3] * m[13] -
            m[12] * m[1] * m[11] +
            m[12] * m[3] * m[9]
    inv[13] = m[0] * m[9] * m[14] -
            m[0] * m[10] * m[13] -
            m[8] * m[1] * m[14] +
            m[8] * m[2] * m[13] +
            m[12] * m[1] * m[10] -
            m[12] * m[2] * m[9]
    inv[2] = m[1] * m[6] * m[15] -
            m[1] * m[7] * m[14] -
            m[5] * m[2] * m[15] +
            m[5] * m[3] * m[14] +
            m[13] * m[2] * m[7] -
            m[13] * m[3] * m[6]
    inv[6] = -m[0] * m[6] * m[15] +
            m[0] * m[7] * m[14] +
            m[4] * m[2] * m[15] -
            m[4] * m[3] * m[14] -
            m[12] * m[2] * m[7] +
            m[12] * m[3] * m[6]
    inv[10] = m[0] * m[5] * m[15] -
            m[0] * m[7] * m[13] -
            m[4] * m[1] * m[15] +
            m[4] * m[3] * m[13] +
            m[12] * m[1] * m[7] -
            m[12] * m[3] * m[5]
    inv[14] = -m[0] * m[5] * m[14] +
            m[0] * m[6] * m[13] +
            m[4] * m[1] * m[14] -
            m[4] * m[2] * m[13] -
            m[12] * m[1] * m[6] +
            m[12] * m[2] * m[5]
    inv[3] = -m[1] * m[6] * m[11] +
            m[1] * m[7] * m[10] +
            m[5] * m[2] * m[11] -
            m[5] * m[3] * m[10] -
            m[9] * m[2] * m[7] +
            m[9] * m[3] * m[6]
    inv[7] = m[0] * m[6] * m[11] -
            m[0] * m[7] * m[10] -
            m[4] * m[2] * m[11] +
            m[4] * m[3] * m[10] +
            m[8] * m[2] * m[7] -
            m[8] * m[3] * m[6]
    inv[11] = -m[0] * m[5] * m[11] +
            m[0] * m[7] * m[9] +
            m[4] * m[1] * m[11] -
            m[4] * m[3] * m[9] -
            m[8] * m[1] * m[7] +
            m[8] * m[3] * m[5]
    inv[15] = m[0] * m[5] * m[10] -
            m[0] * m[6] * m[9] -
            m[4] * m[1] * m[10] +
            m[4] * m[2] * m[9] +
            m[8] * m[1] * m[6] -
            m[8] * m[2] * m[5]
    inv.forEach((v, i) => { inv[i] = v / det })
    this._values = inv
    return this
  }
  setMatrixValue (transformList) {
    const temp = new DOMMatrix(transformList)
    this._values = temp._values
    this._is2D = temp._is2D
    return this
  }
  transformPoint (point) {
    point = new DOMPoint(point)
    const x = point.x
    const y = point.y
    const z = point.z
    const w = point.w
    const values = this._values
    const nx = values[M11] * x + values[M21] * y + values[M31] * z + values[M41] * w
    const ny = values[M12] * x + values[M22] * y + values[M32] * z + values[M42] * w
    const nz = values[M13] * x + values[M23] * y + values[M33] * z + values[M43] * w
    const nw = values[M14] * x + values[M24] * y + values[M34] * z + values[M44] * w
    return new DOMPoint(nx, ny, nz, nw)
  }
  toFloat32Array () {
    return Float32Array.from(this._values)
  }
  toFloat64Array () {
    return this._values.slice(0)
  }
  static fromMatrix (init) {
    if (!(init instanceof DOMMatrix)) throw new TypeError('Expected DOMMatrix')
    return new DOMMatrix(init._values)
  }
  static fromFloat32Array (init) {
    if (!(init instanceof Float32Array)) throw new TypeError('Expected Float32Array')
    return new DOMMatrix(init)
  }
  static fromFloat64Array (init) {
    if (!(init instanceof Float64Array)) throw new TypeError('Expected Float64Array')
    return new DOMMatrix(init)
  }
  [util.inspect.custom || 'inspect'] (depth, options) {
    if (depth < 0) return '[DOMMatrix]'
    return `DOMMatrix [
      a: ${this.a}
      b: ${this.b}
      c: ${this.c}
      d: ${this.d}
      e: ${this.e}
      f: ${this.f}
      m11: ${this.m11}
      m12: ${this.m12}
      m13: ${this.m13}
      m14: ${this.m14}
      m21: ${this.m21}
      m22: ${this.m22}
      m23: ${this.m23}
      m23: ${this.m23}
      m31: ${this.m31}
      m32: ${this.m32}
      m33: ${this.m33}
      m34: ${this.m34}
      m41: ${this.m41}
      m42: ${this.m42}
      m43: ${this.m43}
      m44: ${this.m44}
      is2D: ${this.is2D}
      isIdentity: ${this.isIdentity} ]`
  }
}
/**
 * Checks that `value` is a number and sets the value.
 */
function setNumber2D (receiver, index, value) {
  if (typeof value !== 'number') throw new TypeError('Expected number')
  return (receiver._values[index] = value)
}
/**
 * Checks that `value` is a number, sets `_is2D = false` if necessary and sets
 * the value.
 */
function setNumber3D (receiver, index, value) {
  if (typeof value !== 'number') throw new TypeError('Expected number')
  if (index === M33 || index === M44) {
    if (value !== 1) receiver._is2D = false
  } else if (value !== 0) receiver._is2D = false
  return (receiver._values[index] = value)
}
Object.defineProperties(DOMMatrix.prototype, {
  m11: { get () { return this._values[M11] }, set (v) { return setNumber2D(this, M11, v) } },
  m12: { get () { return this._values[M12] }, set (v) { return setNumber2D(this, M12, v) } },
  m13: { get () { return this._values[M13] }, set (v) { return setNumber3D(this, M13, v) } },
  m14: { get () { return this._values[M14] }, set (v) { return setNumber3D(this, M14, v) } },
  m21: { get () { return this._values[M21] }, set (v) { return setNumber2D(this, M21, v) } },
  m22: { get () { return this._values[M22] }, set (v) { return setNumber2D(this, M22, v) } },
  m23: { get () { return this._values[M23] }, set (v) { return setNumber3D(this, M23, v) } },
  m24: { get () { return this._values[M24] }, set (v) { return setNumber3D(this, M24, v) } },
  m31: { get () { return this._values[M31] }, set (v) { return setNumber3D(this, M31, v) } },
  m32: { get () { return this._values[M32] }, set (v) { return setNumber3D(this, M32, v) } },
  m33: { get () { return this._values[M33] }, set (v) { return setNumber3D(this, M33, v) } },
  m34: { get () { return this._values[M34] }, set (v) { return setNumber3D(this, M34, v) } },
  m41: { get () { return this._values[M41] }, set (v) { return setNumber2D(this, M41, v) } },
  m42: { get () { return this._values[M42] }, set (v) { return setNumber2D(this, M42, v) } },
  m43: { get () { return this._values[M43] }, set (v) { return setNumber3D(this, M43, v) } },
  m44: { get () { return this._values[M44] }, set (v) { return setNumber3D(this, M44, v) } },
  a: { get () { return this.m11 }, set (v) { return (this.m11 = v) } },
  b: { get () { return this.m12 }, set (v) { return (this.m12 = v) } },
  c: { get () { return this.m21 }, set (v) { return (this.m21 = v) } },
  d: { get () { return this.m22 }, set (v) { return (this.m22 = v) } },
  e: { get () { return this.m41 }, set (v) { return (this.m41 = v) } },
  f: { get () { return this.m42 }, set (v) { return (this.m42 = v) } },
  is2D: { get () { return this._is2D } }, // read-only
  isIdentity: {
    get () {
      const values = this._values
      return (values[M11] === 1 && values[M12] === 0 && values[M13] === 0 && values[M14] === 0 &&
             values[M21] === 0 && values[M22] === 1 && values[M23] === 0 && values[M24] === 0 &&
             values[M31] === 0 && values[M32] === 0 && values[M33] === 1 && values[M34] === 0 &&
             values[M41] === 0 && values[M42] === 0 && values[M43] === 0 && values[M44] === 1)
    }
  }
})
/**
 * Instantiates a DOMMatrix, bypassing the constructor.
 * @param {Float64Array} values Value to assign to `_values`. This is assigned
 *   without copying (okay because all usages are followed by a  multiply).
 */
function newInstance (values) {
  const instance = Object.create(DOMMatrix.prototype)
  instance.constructor = DOMMatrix
  instance._is2D = true
  instance._values = values
  return instance
}
function multiply (A, B) {
  const dest = new Float64Array(16)
  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      let sum = 0
      for (let k = 0; k < 4; k++) {
        sum += A[i * 4 + k] * B[k * 4 + j]
      }
      dest[i * 4 + j] = sum
    }
  }
  return dest
}
module.exports = { DOMMatrix, DOMPoint }
node_modules/canvas/lib/bindings.js
New file
@@ -0,0 +1,13 @@
'use strict'
const bindings = require('../build/Release/canvas.node')
module.exports = bindings
bindings.ImageData.prototype.toString = function () {
    return '[object ImageData]'
}
bindings.CanvasGradient.prototype.toString = function () {
    return '[object CanvasGradient]'
}
node_modules/canvas/lib/canvas.js
New file
@@ -0,0 +1,113 @@
'use strict'
/*!
 * Canvas
 * Copyright (c) 2010 LearnBoost <tj@learnboost.com>
 * MIT Licensed
 */
const bindings = require('./bindings')
const Canvas = module.exports = bindings.Canvas
const Context2d = require('./context2d')
const PNGStream = require('./pngstream')
const PDFStream = require('./pdfstream')
const JPEGStream = require('./jpegstream')
const FORMATS = ['image/png', 'image/jpeg']
const util = require('util')
// TODO || is for Node.js pre-v6.6.0
Canvas.prototype[util.inspect.custom || 'inspect'] = function () {
  return `[Canvas ${this.width}x${this.height}]`
}
Canvas.prototype.getContext = function (contextType, contextAttributes) {
  if (contextType == '2d') {
    const ctx = this._context2d || (this._context2d = new Context2d(this, contextAttributes))
    this.context = ctx
    ctx.canvas = this
    return ctx
  }
}
Canvas.prototype.pngStream =
Canvas.prototype.createPNGStream = function (options) {
  return new PNGStream(this, options)
}
Canvas.prototype.pdfStream =
Canvas.prototype.createPDFStream = function (options) {
  return new PDFStream(this, options)
}
Canvas.prototype.jpegStream =
Canvas.prototype.createJPEGStream = function (options) {
  return new JPEGStream(this, options)
}
Canvas.prototype.toDataURL = function (a1, a2, a3) {
  // valid arg patterns (args -> [type, opts, fn]):
  // [] -> ['image/png', null, null]
  // [qual] -> ['image/png', null, null]
  // [undefined] -> ['image/png', null, null]
  // ['image/png'] -> ['image/png', null, null]
  // ['image/png', qual] -> ['image/png', null, null]
  // [fn] -> ['image/png', null, fn]
  // [type, fn] -> [type, null, fn]
  // [undefined, fn] -> ['image/png', null, fn]
  // ['image/png', qual, fn] -> ['image/png', null, fn]
  // ['image/jpeg', fn] -> ['image/jpeg', null, fn]
  // ['image/jpeg', opts, fn] -> ['image/jpeg', opts, fn]
  // ['image/jpeg', qual, fn] -> ['image/jpeg', {quality: qual}, fn]
  // ['image/jpeg', undefined, fn] -> ['image/jpeg', null, fn]
  // ['image/jpeg'] -> ['image/jpeg', null, fn]
  // ['image/jpeg', opts] -> ['image/jpeg', opts, fn]
  // ['image/jpeg', qual] -> ['image/jpeg', {quality: qual}, fn]
  let type = 'image/png'
  let opts = {}
  let fn
  if (typeof a1 === 'function') {
    fn = a1
  } else {
    if (typeof a1 === 'string' && FORMATS.includes(a1.toLowerCase())) {
      type = a1.toLowerCase()
    }
    if (typeof a2 === 'function') {
      fn = a2
    } else {
      if (typeof a2 === 'object') {
        opts = a2
      } else if (typeof a2 === 'number') {
        opts = { quality: Math.max(0, Math.min(1, a2)) }
      }
      if (typeof a3 === 'function') {
        fn = a3
      } else if (undefined !== a3) {
        throw new TypeError(`${typeof a3} is not a function`)
      }
    }
  }
  if (this.width === 0 || this.height === 0) {
    // Per spec, if the bitmap has no pixels, return this string:
    const str = 'data:,'
    if (fn) {
      setTimeout(() => fn(null, str))
      return
    } else {
      return str
    }
  }
  if (fn) {
    this.toBuffer((err, buf) => {
      if (err) return fn(err)
      fn(null, `data:${type};base64,${buf.toString('base64')}`)
    }, type, opts)
  } else {
    return `data:${type};base64,${this.toBuffer(type, opts).toString('base64')}`
  }
}
node_modules/canvas/lib/context2d.js
New file
@@ -0,0 +1,14 @@
'use strict'
/*!
 * Canvas - Context2d
 * Copyright (c) 2010 LearnBoost <tj@learnboost.com>
 * MIT Licensed
 */
const bindings = require('./bindings')
const parseFont = require('./parse-font')
const { DOMMatrix } = require('./DOMMatrix')
bindings.CanvasRenderingContext2dInit(DOMMatrix, parseFont)
module.exports = bindings.CanvasRenderingContext2d
node_modules/canvas/lib/image.js
New file
@@ -0,0 +1,96 @@
'use strict'
/*!
 * Canvas - Image
 * Copyright (c) 2010 LearnBoost <tj@learnboost.com>
 * MIT Licensed
 */
/**
 * Module dependencies.
 */
const bindings = require('./bindings')
const Image = module.exports = bindings.Image
const util = require('util')
// Lazily loaded simple-get
let get
const { GetSource, SetSource } = bindings
Object.defineProperty(Image.prototype, 'src', {
  /**
   * src setter. Valid values:
   *  * `data:` URI
   *  * Local file path
   *  * HTTP or HTTPS URL
   *  * Buffer containing image data (i.e. not a `data:` URI stored in a Buffer)
   *
   * @param {String|Buffer} val filename, buffer, data URI, URL
   * @api public
   */
  set (val) {
    if (typeof val === 'string') {
      if (/^\s*data:/.test(val)) { // data: URI
        const commaI = val.indexOf(',')
        // 'base64' must come before the comma
        const isBase64 = val.lastIndexOf('base64', commaI) !== -1
        const content = val.slice(commaI + 1)
        setSource(this, Buffer.from(content, isBase64 ? 'base64' : 'utf8'), val)
      } else if (/^\s*https?:\/\//.test(val)) { // remote URL
        const onerror = err => {
          if (typeof this.onerror === 'function') {
            this.onerror(err)
          } else {
            throw err
          }
        }
        if (!get) get = require('simple-get')
        get.concat({
          url: val,
          headers: { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36' }
        }, (err, res, data) => {
          if (err) return onerror(err)
          if (res.statusCode < 200 || res.statusCode >= 300) {
            return onerror(new Error(`Server responded with ${res.statusCode}`))
          }
          setSource(this, data)
        })
      } else { // local file path assumed
        setSource(this, val)
      }
    } else if (Buffer.isBuffer(val)) {
      setSource(this, val)
    }
  },
  get () {
    // TODO https://github.com/Automattic/node-canvas/issues/118
    return getSource(this)
  },
  configurable: true
})
// TODO || is for Node.js pre-v6.6.0
Image.prototype[util.inspect.custom || 'inspect'] = function () {
  return '[Image' +
    (this.complete ? ':' + this.width + 'x' + this.height : '') +
    (this.src ? ' ' + this.src : '') +
    (this.complete ? ' complete' : '') +
    ']'
}
function getSource (img) {
  return img._originalSource || GetSource.call(img)
}
function setSource (img, src, origSrc) {
  SetSource.call(img, src)
  img._originalSource = origSrc
}
node_modules/canvas/lib/jpegstream.js
New file
@@ -0,0 +1,41 @@
'use strict'
/*!
 * Canvas - JPEGStream
 * Copyright (c) 2010 LearnBoost <tj@learnboost.com>
 * MIT Licensed
 */
const { Readable } = require('stream')
function noop () {}
class JPEGStream extends Readable {
  constructor (canvas, options) {
    super()
    if (canvas.streamJPEGSync === undefined) {
      throw new Error('node-canvas was built without JPEG support.')
    }
    this.options = options
    this.canvas = canvas
  }
  _read () {
    // For now we're not controlling the c++ code's data emission, so we only
    // call canvas.streamJPEGSync once and let it emit data at will.
    this._read = noop
    this.canvas.streamJPEGSync(this.options, (err, chunk) => {
      if (err) {
        this.emit('error', err)
      } else if (chunk) {
        this.push(chunk)
      } else {
        this.push(null)
      }
    })
  }
};
module.exports = JPEGStream
node_modules/canvas/lib/parse-font.js
New file
@@ -0,0 +1,101 @@
'use strict'
/**
 * Font RegExp helpers.
 */
const weights = 'bold|bolder|lighter|[1-9]00'
const styles = 'italic|oblique'
const variants = 'small-caps'
const stretches = 'ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded'
const units = 'px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q'
const string = '\'([^\']+)\'|"([^"]+)"|[\\w\\s-]+'
// [ [ <‘font-style’> || <font-variant-css21> || <‘font-weight’> || <‘font-stretch’> ]?
//    <‘font-size’> [ / <‘line-height’> ]? <‘font-family’> ]
// https://drafts.csswg.org/css-fonts-3/#font-prop
const weightRe = new RegExp(`(${weights}) +`, 'i')
const styleRe = new RegExp(`(${styles}) +`, 'i')
const variantRe = new RegExp(`(${variants}) +`, 'i')
const stretchRe = new RegExp(`(${stretches}) +`, 'i')
const sizeFamilyRe = new RegExp(
  `([\\d\\.]+)(${units}) *((?:${string})( *, *(?:${string}))*)`)
/**
 * Cache font parsing.
 */
const cache = {}
const defaultHeight = 16 // pt, common browser default
/**
 * Parse font `str`.
 *
 * @param {String} str
 * @return {Object} Parsed font. `size` is in device units. `unit` is the unit
 *   appearing in the input string.
 * @api private
 */
module.exports = str => {
  // Cached
  if (cache[str]) return cache[str]
  // Try for required properties first.
  const sizeFamily = sizeFamilyRe.exec(str)
  if (!sizeFamily) return // invalid
  // Default values and required properties
  const font = {
    weight: 'normal',
    style: 'normal',
    stretch: 'normal',
    variant: 'normal',
    size: parseFloat(sizeFamily[1]),
    unit: sizeFamily[2],
    family: sizeFamily[3].replace(/["']/g, '').replace(/ *, */g, ',')
  }
  // Optional, unordered properties.
  let weight, style, variant, stretch
  // Stop search at `sizeFamily.index`
  const substr = str.substring(0, sizeFamily.index)
  if ((weight = weightRe.exec(substr))) font.weight = weight[1]
  if ((style = styleRe.exec(substr))) font.style = style[1]
  if ((variant = variantRe.exec(substr))) font.variant = variant[1]
  if ((stretch = stretchRe.exec(substr))) font.stretch = stretch[1]
  // Convert to device units. (`font.unit` is the original unit)
  // TODO: ch, ex
  switch (font.unit) {
    case 'pt':
      font.size /= 0.75
      break
    case 'pc':
      font.size *= 16
      break
    case 'in':
      font.size *= 96
      break
    case 'cm':
      font.size *= 96.0 / 2.54
      break
    case 'mm':
      font.size *= 96.0 / 25.4
      break
    case '%':
      // TODO disabled because existing unit tests assume 100
      // font.size *= defaultHeight / 100 / 0.75
      break
    case 'em':
    case 'rem':
      font.size *= defaultHeight / 0.75
      break
    case 'q':
      font.size *= 96 / 25.4 / 4
      break
  }
  return (cache[str] = font)
}
node_modules/canvas/lib/pattern.js
New file
@@ -0,0 +1,17 @@
'use strict'
/*!
 * Canvas - CanvasPattern
 * Copyright (c) 2010 LearnBoost <tj@learnboost.com>
 * MIT Licensed
 */
const bindings = require('./bindings')
const { DOMMatrix } = require('./DOMMatrix')
bindings.CanvasPatternInit(DOMMatrix)
module.exports = bindings.CanvasPattern
bindings.CanvasPattern.prototype.toString = function () {
    return '[object CanvasPattern]'
}
node_modules/canvas/lib/pdfstream.js
New file
@@ -0,0 +1,35 @@
'use strict'
/*!
 * Canvas - PDFStream
 */
const { Readable } = require('stream')
function noop () {}
class PDFStream extends Readable {
  constructor (canvas, options) {
    super()
    this.canvas = canvas
    this.options = options
  }
  _read () {
    // For now we're not controlling the c++ code's data emission, so we only
    // call canvas.streamPDFSync once and let it emit data at will.
    this._read = noop
    this.canvas.streamPDFSync((err, chunk, len) => {
      if (err) {
        this.emit('error', err)
      } else if (len) {
        this.push(chunk)
      } else {
        this.push(null)
      }
    }, this.options)
  }
}
module.exports = PDFStream
node_modules/canvas/lib/pngstream.js
New file
@@ -0,0 +1,42 @@
'use strict'
/*!
 * Canvas - PNGStream
 * Copyright (c) 2010 LearnBoost <tj@learnboost.com>
 * MIT Licensed
 */
const { Readable } = require('stream')
function noop () {}
class PNGStream extends Readable {
  constructor (canvas, options) {
    super()
    if (options &&
      options.palette instanceof Uint8ClampedArray &&
      options.palette.length % 4 !== 0) {
      throw new Error('Palette length must be a multiple of 4.')
    }
    this.canvas = canvas
    this.options = options || {}
  }
  _read () {
    // For now we're not controlling the c++ code's data emission, so we only
    // call canvas.streamPNGSync once and let it emit data at will.
    this._read = noop
    this.canvas.streamPNGSync((err, chunk, len) => {
      if (err) {
        this.emit('error', err)
      } else if (len) {
        this.push(chunk)
      } else {
        this.push(null)
      }
    }, this.options)
  }
}
module.exports = PNGStream
node_modules/canvas/package.json
New file
@@ -0,0 +1,72 @@
{
  "name": "canvas",
  "description": "Canvas graphics API backed by Cairo",
  "version": "2.11.2",
  "author": "TJ Holowaychuk <tj@learnboost.com>",
  "main": "index.js",
  "browser": "browser.js",
  "contributors": [
    "Nathan Rajlich <nathan@tootallnate.net>",
    "Rod Vagg <r@va.gg>",
    "Juriy Zaytsev <kangax@gmail.com>"
  ],
  "keywords": [
    "canvas",
    "graphic",
    "graphics",
    "pixman",
    "cairo",
    "image",
    "images",
    "pdf"
  ],
  "homepage": "https://github.com/Automattic/node-canvas",
  "repository": "git://github.com/Automattic/node-canvas.git",
  "scripts": {
    "prebenchmark": "node-gyp build",
    "benchmark": "node benchmarks/run.js",
    "lint": "standard examples/*.js test/server.js test/public/*.js benchmarks/run.js lib/context2d.js util/has_lib.js browser.js index.js",
    "test": "mocha test/*.test.js",
    "pretest-server": "node-gyp build",
    "test-server": "node test/server.js",
    "generate-wpt": "node ./test/wpt/generate.js",
    "test-wpt": "mocha test/wpt/generated/*.js",
    "install": "node-pre-gyp install --fallback-to-build --update-binary",
    "dtslint": "dtslint types"
  },
  "binary": {
    "module_name": "canvas",
    "module_path": "build/Release",
    "host": "https://github.com/Automattic/node-canvas/releases/download/",
    "remote_path": "v{version}",
    "package_name": "{module_name}-v{version}-{node_abi}-{platform}-{libc}-{arch}.tar.gz"
  },
  "files": [
    "binding.gyp",
    "lib/",
    "src/",
    "util/",
    "types/index.d.ts"
  ],
  "types": "types/index.d.ts",
  "dependencies": {
    "@mapbox/node-pre-gyp": "^1.0.0",
    "nan": "^2.17.0",
    "simple-get": "^3.0.3"
  },
  "devDependencies": {
    "@types/node": "^10.12.18",
    "assert-rejects": "^1.0.0",
    "dtslint": "^4.0.7",
    "express": "^4.16.3",
    "js-yaml": "^4.1.0",
    "mocha": "^5.2.0",
    "pixelmatch": "^4.0.2",
    "standard": "^12.0.1",
    "typescript": "^4.2.2"
  },
  "engines": {
    "node": ">=6"
  },
  "license": "MIT"
}
node_modules/canvas/src/Backends.cc
New file
@@ -0,0 +1,18 @@
#include "Backends.h"
#include "backend/ImageBackend.h"
#include "backend/PdfBackend.h"
#include "backend/SvgBackend.h"
using namespace v8;
void Backends::Initialize(Local<Object> target) {
  Nan::HandleScope scope;
  Local<Object> obj = Nan::New<Object>();
  ImageBackend::Initialize(obj);
  PdfBackend::Initialize(obj);
  SvgBackend::Initialize(obj);
  Nan::Set(target, Nan::New<String>("Backends").ToLocalChecked(), obj).Check();
}
node_modules/canvas/src/Backends.h
New file
@@ -0,0 +1,10 @@
#pragma once
#include "backend/Backend.h"
#include <nan.h>
#include <v8.h>
class Backends : public Nan::ObjectWrap {
  public:
    static void Initialize(v8::Local<v8::Object> target);
};
node_modules/canvas/src/Canvas.cc
New file
@@ -0,0 +1,965 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "Canvas.h"
#include <algorithm> // std::min
#include <assert.h>
#include <cairo-pdf.h>
#include <cairo-svg.h>
#include "CanvasRenderingContext2d.h"
#include "closure.h"
#include <cstring>
#include <cctype>
#include <ctime>
#include <glib.h>
#include "PNG.h"
#include "register_font.h"
#include <sstream>
#include <stdlib.h>
#include <string>
#include <unordered_set>
#include "Util.h"
#include <vector>
#include "node_buffer.h"
#ifdef HAVE_JPEG
#include "JPEGStream.h"
#endif
#include "backend/ImageBackend.h"
#include "backend/PdfBackend.h"
#include "backend/SvgBackend.h"
#define GENERIC_FACE_ERROR \
  "The second argument to registerFont is required, and should be an object " \
  "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<FunctionTemplate> Canvas::constructor;
std::vector<FontFace> font_face_list;
/*
 * Initialize Canvas.
 */
void
Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
  Nan::HandleScope scope;
  // Constructor
  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Canvas::New);
  constructor.Reset(ctor);
  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(Nan::New("Canvas").ToLocalChecked());
  // Prototype
  Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
  Nan::SetPrototypeMethod(ctor, "toBuffer", ToBuffer);
  Nan::SetPrototypeMethod(ctor, "streamPNGSync", StreamPNGSync);
  Nan::SetPrototypeMethod(ctor, "streamPDFSync", StreamPDFSync);
#ifdef HAVE_JPEG
  Nan::SetPrototypeMethod(ctor, "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<Uint32>(PNG_NO_FILTERS));
  Nan::SetTemplate(proto, "PNG_FILTER_NONE", Nan::New<Uint32>(PNG_FILTER_NONE));
  Nan::SetTemplate(proto, "PNG_FILTER_SUB", Nan::New<Uint32>(PNG_FILTER_SUB));
  Nan::SetTemplate(proto, "PNG_FILTER_UP", Nan::New<Uint32>(PNG_FILTER_UP));
  Nan::SetTemplate(proto, "PNG_FILTER_AVG", Nan::New<Uint32>(PNG_FILTER_AVG));
  Nan::SetTemplate(proto, "PNG_FILTER_PAETH", Nan::New<Uint32>(PNG_FILTER_PAETH));
  Nan::SetTemplate(proto, "PNG_ALL_FILTERS", Nan::New<Uint32>(PNG_ALL_FILTERS));
  // Class methods
  Nan::SetMethod(ctor, "_registerFont", RegisterFont);
  Nan::SetMethod(ctor, "_deregisterAllFonts", DeregisterAllFonts);
  Local<Context> ctx = Nan::GetCurrentContext();
  Nan::Set(target,
           Nan::New("Canvas").ToLocalChecked(),
           ctor->GetFunction(ctx).ToLocalChecked());
}
/*
 * 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'");
  }
  Backend* backend = NULL;
  if (info[0]->IsNumber()) {
    int width = Nan::To<uint32_t>(info[0]).FromMaybe(0), height = 0;
    if (info[1]->IsNumber()) height = Nan::To<uint32_t>(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);
    }
    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<Backend>(Nan::To<Object>(info[0]).ToLocalChecked());
    }else{
      return Nan::ThrowTypeError("Invalid arguments");
    }
  }
  else {
    backend = new ImageBackend(0, 0);
  }
  if (!backend->isSurfaceValid()) {
    delete backend;
    return Nan::ThrowError(backend->getError());
  }
  Canvas* canvas = new Canvas(backend);
  canvas->Wrap(info.This());
  backend->setCanvas(canvas);
  info.GetReturnValue().Set(info.This());
}
/*
 * Get type string.
 */
NAN_GETTER(Canvas::GetType) {
  CHECK_RECEIVER(Canvas.GetType);
  Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  info.GetReturnValue().Set(Nan::New<String>(canvas->backend()->getName()).ToLocalChecked());
}
/*
 * Get stride.
 */
NAN_GETTER(Canvas::GetStride) {
  CHECK_RECEIVER(Canvas.GetStride);
  Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(canvas->stride()));
}
/*
 * Get width.
 */
NAN_GETTER(Canvas::GetWidth) {
  CHECK_RECEIVER(Canvas.GetWidth);
  Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(canvas->getWidth()));
}
/*
 * Set width.
 */
NAN_SETTER(Canvas::SetWidth) {
  CHECK_RECEIVER(Canvas.SetWidth);
  if (value->IsNumber()) {
    Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
    canvas->backend()->setWidth(Nan::To<uint32_t>(value).FromMaybe(0));
    canvas->resurface(info.This());
  }
}
/*
 * Get height.
 */
NAN_GETTER(Canvas::GetHeight) {
  CHECK_RECEIVER(Canvas.GetHeight);
  Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(canvas->getHeight()));
}
/*
 * Set height.
 */
NAN_SETTER(Canvas::SetHeight) {
  CHECK_RECEIVER(Canvas.SetHeight);
  if (value->IsNumber()) {
    Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
    canvas->backend()->setHeight(Nan::To<uint32_t>(value).FromMaybe(0));
    canvas->resurface(info.This());
  }
}
/*
 * EIO toBuffer callback.
 */
void
Canvas::ToPngBufferAsync(uv_work_t *req) {
  PngClosure* closure = static_cast<PngClosure*>(req->data);
  closure->status = canvas_write_to_png_stream(
    closure->canvas->surface(),
    PngClosure::writeVec,
    closure);
}
#ifdef HAVE_JPEG
void
Canvas::ToJpegBufferAsync(uv_work_t *req) {
  JpegClosure* closure = static_cast<JpegClosure*>(req->data);
  write_to_jpeg_buffer(closure->canvas->surface(), closure);
}
#endif
/*
 * EIO after toBuffer callback.
 */
void
Canvas::ToBufferAsyncAfter(uv_work_t *req) {
  Nan::HandleScope scope;
  Nan::AsyncResource async("canvas:ToBufferAsyncAfter");
  Closure* closure = static_cast<Closure*>(req->data);
  delete req;
  if (closure->status) {
    Local<Value> argv[1] = { Canvas::Error(closure->status) };
    closure->cb.Call(1, argv, &async);
  } else {
    Local<Object> buf = Nan::CopyBuffer((char*)&closure->vec[0], closure->vec.size()).ToLocalChecked();
    Local<Value> argv[2] = { Nan::Null(), buf };
    closure->cb.Call(sizeof argv / sizeof *argv, argv, &async);
  }
  closure->canvas->Unref();
  delete closure;
}
static void parsePNGArgs(Local<Value> arg, PngClosure& pngargs) {
  if (arg->IsObject()) {
    Local<Object> obj = Nan::To<Object>(arg).ToLocalChecked();
    Local<Value> cLevel = Nan::Get(obj, Nan::New("compressionLevel").ToLocalChecked()).ToLocalChecked();
    if (cLevel->IsUint32()) {
      uint32_t val = Nan::To<uint32_t>(cLevel).FromMaybe(0);
      // See quote below from spec section 4.12.5.5.
      if (val <= 9) pngargs.compressionLevel = val;
    }
    Local<Value> rez = Nan::Get(obj, Nan::New("resolution").ToLocalChecked()).ToLocalChecked();
    if (rez->IsUint32()) {
      uint32_t val = Nan::To<uint32_t>(rez).FromMaybe(0);
      if (val > 0) pngargs.resolution = val;
    }
    Local<Value> filters = Nan::Get(obj, Nan::New("filters").ToLocalChecked()).ToLocalChecked();
    if (filters->IsUint32()) pngargs.filters = Nan::To<uint32_t>(filters).FromMaybe(0);
    Local<Value> palette = Nan::Get(obj, Nan::New("palette").ToLocalChecked()).ToLocalChecked();
    if (palette->IsUint8ClampedArray()) {
      Local<Uint8ClampedArray> palette_ta = palette.As<Uint8ClampedArray>();
      pngargs.nPaletteColors = palette_ta->Length();
      if (pngargs.nPaletteColors % 4 != 0) {
        throw "Palette length must be a multiple of 4.";
      }
      pngargs.nPaletteColors /= 4;
      Nan::TypedArrayContents<uint8_t> _paletteColors(palette_ta);
      pngargs.palette = *_paletteColors;
      // Optional background color index:
      Local<Value> backgroundIndexVal = Nan::Get(obj, Nan::New("backgroundIndex").ToLocalChecked()).ToLocalChecked();
      if (backgroundIndexVal->IsUint32()) {
        pngargs.backgroundIndex = static_cast<uint8_t>(Nan::To<uint32_t>(backgroundIndexVal).FromMaybe(0));
      }
    }
  }
}
#ifdef HAVE_JPEG
static void parseJPEGArgs(Local<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<Object> obj = Nan::To<Object>(arg).ToLocalChecked();
    Local<Value> qual = Nan::Get(obj, Nan::New("quality").ToLocalChecked()).ToLocalChecked();
    if (qual->IsNumber()) {
      double quality = Nan::To<double>(qual).FromMaybe(0);
      if (quality >= 0.0 && quality <= 1.0) {
        jpegargs.quality = static_cast<uint32_t>(100.0 * quality);
      }
    }
    Local<Value> chroma = Nan::Get(obj, Nan::New("chromaSubsampling").ToLocalChecked()).ToLocalChecked();
    if (chroma->IsBoolean()) {
      bool subsample = Nan::To<bool>(chroma).FromMaybe(0);
      jpegargs.chromaSubsampling = subsample ? 2 : 1;
    } else if (chroma->IsNumber()) {
      jpegargs.chromaSubsampling = Nan::To<uint32_t>(chroma).FromMaybe(0);
    }
    Local<Value> progressive = Nan::Get(obj, Nan::New("progressive").ToLocalChecked()).ToLocalChecked();
    if (!progressive->IsUndefined()) {
      jpegargs.progressive = Nan::To<bool>(progressive).FromMaybe(0);
    }
  }
}
#endif
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
static inline void setPdfMetaStr(cairo_surface_t* surf, Local<Object> 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()) {
    // (copies char data)
    cairo_pdf_surface_set_metadata(surf, t, *Nan::Utf8String(propValue));
  }
}
static inline void setPdfMetaDate(cairo_surface_t* surf, Local<Object> 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<time_t>(propValue.As<v8::Date>()->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<Object> opts) {
  cairo_surface_t* surf = canvas->surface();
  setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_TITLE, "title");
  setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_AUTHOR, "author");
  setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_SUBJECT, "subject");
  setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_KEYWORDS, "keywords");
  setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_CREATOR, "creator");
  setPdfMetaDate(surf, opts, CAIRO_PDF_METADATA_CREATE_DATE, "creationDate");
  setPdfMetaDate(surf, opts, CAIRO_PDF_METADATA_MOD_DATE, "modDate");
}
#endif // CAIRO 16+
/*
 * Converts/encodes data to a Buffer. Async when a callback function is passed.
 * PDF canvases:
    (any) => Buffer
    ("application/pdf", config) => Buffer
 * SVG canvases:
    (any) => Buffer
 * ARGB data:
    ("raw") => Buffer
 * PNG-encoded
    () => Buffer
    (undefined|"image/png", {compressionLevel?: number, filter?: number}) => Buffer
    ((err: null|Error, buffer) => any)
    ((err: null|Error, buffer) => any, undefined|"image/png", {compressionLevel?: number, filter?: number})
 * JPEG-encoded
    ("image/jpeg") => Buffer
    ("image/jpeg", {quality?: number, progressive?: Boolean, chromaSubsampling?: Boolean|number}) => Buffer
    ((err: null|Error, buffer) => any, "image/jpeg")
    ((err: null|Error, buffer) => any, "image/jpeg", {quality?: number, progressive?: Boolean, chromaSubsampling?: Boolean|number})
 */
NAN_METHOD(Canvas::ToBuffer) {
  cairo_status_t status;
  Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  // Vector canvases, sync only
  const std::string name = canvas->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<PdfBackend*>(canvas->backend())->closure();
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
      if (info[1]->IsObject()) { // toBuffer("application/pdf", config)
        setPdfMetadata(canvas, Nan::To<Object>(info[1]).ToLocalChecked());
      }
#endif // CAIRO 16+
    } else {
      closure = static_cast<SvgBackend*>(canvas->backend())->closure();
    }
    cairo_surface_finish(canvas->surface());
    Local<Object> buf = Nan::CopyBuffer((char*)&closure->vec[0], closure->vec.size()).ToLocalChecked();
    info.GetReturnValue().Set(buf);
    return;
  }
  // Raw ARGB data -- just a memcpy()
  if (info[0]->StrictEquals(Nan::New<String>("raw").ToLocalChecked())) {
    cairo_surface_t *surface = canvas->surface();
    cairo_surface_flush(surface);
    if (canvas->nBytes() > node::Buffer::kMaxLength) {
      Nan::ThrowError("Data exceeds maximum buffer length.");
      return;
    }
    const unsigned char *data = cairo_image_surface_get_data(surface);
    Isolate* iso = Nan::GetCurrentContext()->GetIsolate();
    Local<Object> buf = node::Buffer::Copy(iso, reinterpret_cast<const char*>(data), canvas->nBytes()).ToLocalChecked();
    info.GetReturnValue().Set(buf);
    return;
  }
  // Sync PNG, default
  if (info[0]->IsUndefined() || info[0]->StrictEquals(Nan::New<String>("image/png").ToLocalChecked())) {
    try {
      PngClosure closure(canvas);
      parsePNGArgs(info[1], closure);
      if (closure.nPaletteColors == 0xFFFFFFFF) {
        Nan::ThrowError("Palette length must be a multiple of 4.");
        return;
      }
      Nan::TryCatch try_catch;
      status = canvas_write_to_png_stream(canvas->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<Object> buf = Nan::CopyBuffer((char *)&closure.vec[0], closure.vec.size()).ToLocalChecked();
        info.GetReturnValue().Set(buf);
      }
    } catch (cairo_status_t ex) {
      Nan::ThrowError(Canvas::Error(ex));
    } catch (const char* ex) {
      Nan::ThrowError(ex);
    }
    return;
  }
  // Async PNG
  if (info[0]->IsFunction() &&
    (info[1]->IsUndefined() || info[1]->StrictEquals(Nan::New<String>("image/png").ToLocalChecked()))) {
    PngClosure* closure;
    try {
      closure = new PngClosure(canvas);
      parsePNGArgs(info[2], *closure);
    } catch (cairo_status_t ex) {
      Nan::ThrowError(Canvas::Error(ex));
      return;
    } catch (const char* ex) {
      Nan::ThrowError(ex);
      return;
    }
    canvas->Ref();
    closure->cb.Reset(info[0].As<Function>());
    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);
    return;
  }
#ifdef HAVE_JPEG
  // Sync JPEG
  Local<Value> jpegStr = Nan::New<String>("image/jpeg").ToLocalChecked();
  if (info[0]->StrictEquals(jpegStr)) {
    try {
      JpegClosure closure(canvas);
      parseJPEGArgs(info[1], closure);
      Nan::TryCatch try_catch;
      write_to_jpeg_buffer(canvas->surface(), &closure);
      if (try_catch.HasCaught()) {
        try_catch.ReThrow();
      } else {
        // TODO it's possible to avoid this copy.
        Local<Object> buf = Nan::CopyBuffer((char *)&closure.vec[0], closure.vec.size()).ToLocalChecked();
        info.GetReturnValue().Set(buf);
      }
    } catch (cairo_status_t ex) {
      Nan::ThrowError(Canvas::Error(ex));
    }
    return;
  }
  // Async JPEG
  if (info[0]->IsFunction() && info[1]->StrictEquals(jpegStr)) {
    JpegClosure* closure = new JpegClosure(canvas);
    parseJPEGArgs(info[2], *closure);
    canvas->Ref();
    closure->cb.Reset(info[0].As<Function>());
    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;
  }
#endif
}
/*
 * Canvas::StreamPNG callback.
 */
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<Object> buf = Nan::CopyBuffer((char *)data, len).ToLocalChecked();
  Local<Value> argv[3] = {
      Nan::Null()
    , buf
    , Nan::New<Number>(len) };
  closure->cb.Call(sizeof argv / sizeof *argv, argv, &async);
  return CAIRO_STATUS_SUCCESS;
}
/*
 * Stream PNG data synchronously. TODO async
 * 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<Canvas>(info.This());
  PngClosure closure(canvas);
  parsePNGArgs(info[1], closure);
  closure.cb.Reset(Local<Function>::Cast(info[0]));
  Nan::TryCatch try_catch;
  cairo_status_t status = canvas_write_to_png_stream(canvas->surface(), streamPNG, &closure);
  if (try_catch.HasCaught()) {
    try_catch.ReThrow();
    return;
  } else if (status) {
    Local<Value> argv[1] = { Canvas::Error(status) };
    Nan::Call(closure.cb, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv);
  } else {
    Local<Value> argv[3] = {
        Nan::Null()
      , Nan::Null()
      , Nan::New<Uint32>(0) };
    Nan::Call(closure.cb, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv);
  }
  return;
}
struct PdfStreamInfo {
  Local<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<PdfStreamInfo*>(c);
  // 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<Object> buf = Nan::NewBuffer(const_cast<char *>(reinterpret_cast<const char *>(data)), len, stream_pdf_free, 0).ToLocalChecked();
  Local<Value> argv[3] = {
      Nan::Null()
    , buf
    , Nan::New<Number>(len) };
  async.runInAsyncScope(Nan::GetCurrentContext()->Global(), streaminfo->fn, sizeof argv / sizeof *argv, argv);
  return CAIRO_STATUS_SUCCESS;
}
cairo_status_t canvas_write_to_pdf_stream(cairo_surface_t *surface, cairo_write_func_t write_func, PdfStreamInfo* streaminfo) {
  size_t whole_chunks = streaminfo->len / PAGE_SIZE;
  size_t remainder = streaminfo->len - whole_chunks * PAGE_SIZE;
  for (size_t i = 0; i < whole_chunks; ++i) {
    write_func(streaminfo, &streaminfo->data[i * PAGE_SIZE], PAGE_SIZE);
  }
  if (remainder) {
    write_func(streaminfo, &streaminfo->data[whole_chunks * PAGE_SIZE], remainder);
  }
  return CAIRO_STATUS_SUCCESS;
}
/*
 * Stream PDF data synchronously.
 */
NAN_METHOD(Canvas::StreamPDFSync) {
  if (!info[0]->IsFunction())
    return Nan::ThrowTypeError("callback function required");
  Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.Holder());
  if (canvas->backend()->getName() != "pdf")
    return Nan::ThrowTypeError("wrong canvas type");
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
  if (info[1]->IsObject()) {
    setPdfMetadata(canvas, Nan::To<Object>(info[1]).ToLocalChecked());
  }
#endif
  cairo_surface_finish(canvas->surface());
  PdfSvgClosure* closure = static_cast<PdfBackend*>(canvas->backend())->closure();
  Local<Function> fn = info[0].As<Function>();
  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);
  if (try_catch.HasCaught()) {
    try_catch.ReThrow();
  } else if (status) {
    Local<Value> error = Canvas::Error(status);
    Nan::Call(fn, Nan::GetCurrentContext()->Global(), 1, &error);
  } else {
    Local<Value> argv[3] = {
        Nan::Null()
      , Nan::Null()
      , Nan::New<Uint32>(0) };
    Nan::Call(fn, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv);
  }
}
/*
 * Stream JPEG data synchronously.
 */
#ifdef HAVE_JPEG
static uint32_t getSafeBufSize(Canvas* canvas) {
  // Don't allow the buffer size to exceed the size of the canvas (#674)
  // TODO not sure if this is really correct, but it fixed #674
  return (std::min)(canvas->getWidth() * canvas->getHeight() * 4, static_cast<int>(PAGE_SIZE));
}
NAN_METHOD(Canvas::StreamJPEGSync) {
  if (!info[1]->IsFunction())
    return Nan::ThrowTypeError("callback function required");
  Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  JpegClosure closure(canvas);
  parseJPEGArgs(info[0], closure);
  closure.cb.Reset(Local<Function>::Cast(info[1]));
  Nan::TryCatch try_catch;
  uint32_t bufsize = getSafeBufSize(canvas);
  write_to_jpeg_stream(canvas->surface(), bufsize, &closure);
  if (try_catch.HasCaught()) {
    try_catch.ReThrow();
  }
  return;
}
#endif
char *
str_value(Local<Value> 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;
  }
}
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);
  }
  Nan::Utf8String filePath(info[0]);
  PangoFontDescription *sys_desc = get_pango_font_description((unsigned char *) *filePath);
  if (!sys_desc) return Nan::ThrowError("Could not parse font file");
  PangoFontDescription *user_desc = pango_font_description_new();
  // now check the attrs, there are many ways to be wrong
  Local<Object> js_user_desc = Nan::To<Object>(info[1]).ToLocalChecked();
  Local<String> family_prop = Nan::New<String>("family").ToLocalChecked();
  Local<String> weight_prop = Nan::New<String>("weight").ToLocalChecked();
  Local<String> style_prop = Nan::New<String>("style").ToLocalChecked();
  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);
  if (family && weight && style) {
    pango_font_description_set_weight(user_desc, Canvas::GetWeightFromCSSString(weight));
    pango_font_description_set_style(user_desc, Canvas::GetStyleFromCSSString(style));
    pango_font_description_set_family(user_desc, family);
    auto found = std::find_if(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) {
      return pango_font_description_equal(f.sys_desc, sys_desc);
    });
    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)) {
      FontFace face;
      face.user_desc = user_desc;
      face.sys_desc = sys_desc;
      strncpy((char *)face.file_path, (char *) *filePath, 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");
    }
  } else {
    pango_font_description_free(user_desc);
    Nan::ThrowError(GENERIC_FACE_ERROR);
  }
  free(family);
  free(weight);
  free(style);
}
NAN_METHOD(Canvas::DeregisterAllFonts) {
  // Unload all fonts from pango to free up memory
  bool success = true;
  std::for_each(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) {
    if (!deregister_font( (unsigned char *)f.file_path )) success = false;
    pango_font_description_free(f.user_desc);
    pango_font_description_free(f.sys_desc);
  });
  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;
    }
}
/*
 * Get a PangoStyle from a CSS string (like "italic")
 */
PangoStyle
Canvas::GetStyleFromCSSString(const char *style) {
  PangoStyle s = PANGO_STYLE_NORMAL;
  if (strlen(style) > 0) {
    if (0 == strcmp("italic", style)) {
      s = PANGO_STYLE_ITALIC;
    } else if (0 == strcmp("oblique", style)) {
      s = PANGO_STYLE_OBLIQUE;
    }
  }
  return s;
}
/*
 * Get a PangoWeight from a CSS string ("bold", "100", etc)
 */
PangoWeight
Canvas::GetWeightFromCSSString(const char *weight) {
  PangoWeight w = PANGO_WEIGHT_NORMAL;
  if (strlen(weight) > 0) {
    if (0 == strcmp("bold", weight)) {
      w = PANGO_WEIGHT_BOLD;
    } else if (0 == strcmp("100", weight)) {
      w = PANGO_WEIGHT_THIN;
    } else if (0 == strcmp("200", weight)) {
      w = PANGO_WEIGHT_ULTRALIGHT;
    } else if (0 == strcmp("300", weight)) {
      w = PANGO_WEIGHT_LIGHT;
    } else if (0 == strcmp("400", weight)) {
      w = PANGO_WEIGHT_NORMAL;
    } else if (0 == strcmp("500", weight)) {
      w = PANGO_WEIGHT_MEDIUM;
    } else if (0 == strcmp("600", weight)) {
      w = PANGO_WEIGHT_SEMIBOLD;
    } else if (0 == strcmp("700", weight)) {
      w = PANGO_WEIGHT_BOLD;
    } else if (0 == strcmp("800", weight)) {
      w = PANGO_WEIGHT_ULTRABOLD;
    } else if (0 == strcmp("900", weight)) {
      w = PANGO_WEIGHT_HEAVY;
    }
  }
  return w;
}
/*
 * Given a user description, return a description that will select the
 * font either from the system or @font-face
 */
PangoFontDescription *
Canvas::ResolveFontDescription(const PangoFontDescription *desc) {
  // One of the user-specified families could map to multiple SFNT family names
  // if someone registered two different fonts under the same family name.
  // https://drafts.csswg.org/css-fonts-3/#font-style-matching
  FontFace best;
  istringstream families(pango_font_description_get_family(desc));
  unordered_set<string> seen_families;
  string resolved_families;
  bool first = true;
  for (string family; getline(families, family, ','); ) {
    string renamed_families;
    for (auto& ff : font_face_list) {
      string pangofamily = string(pango_font_description_get_family(ff.user_desc));
      if (streq_casein(family, pangofamily)) {
        const char* sys_desc_family_name = pango_font_description_get_family(ff.sys_desc);
        bool unseen = seen_families.find(sys_desc_family_name) == seen_families.end();
        bool better = best.user_desc == nullptr || pango_font_description_better_match(desc, best.user_desc, ff.user_desc);
        // Avoid sending duplicate SFNT font names due to a bug in Pango for macOS:
        // https://bugzilla.gnome.org/show_bug.cgi?id=762873
        if (unseen) {
          seen_families.insert(sys_desc_family_name);
          if (better) {
            renamed_families = string(sys_desc_family_name) + (renamed_families.size() ? "," : "") + renamed_families;
          } else {
            renamed_families = renamed_families + (renamed_families.size() ? "," : "") + sys_desc_family_name;
          }
        }
        if (first && better) best = ff;
      }
    }
    if (resolved_families.size()) resolved_families += ',';
    resolved_families += renamed_families.size() ? renamed_families : family;
    first = false;
  }
  PangoFontDescription* ret = pango_font_description_copy(best.sys_desc ? best.sys_desc : desc);
  pango_font_description_set_family(ret, resolved_families.c_str());
  return ret;
}
/*
 * Re-alloc the surface, destroying the previous.
 */
void
Canvas::resurface(Local<Object> canvas) {
  Nan::HandleScope scope;
  Local<Value> context;
  backend()->recreateSurface();
  // Reset context
    context = Nan::Get(canvas, Nan::New<String>("context").ToLocalChecked()).ToLocalChecked();
    if (!context->IsUndefined()) {
        Context2d *context2d = ObjectWrap::Unwrap<Context2d>(Nan::To<Object>(context).ToLocalChecked());
        cairo_t *prev = context2d->context();
        context2d->setContext(createCairoContext());
        context2d->resetState();
        cairo_destroy(prev);
    }
}
/**
 * Wrapper around cairo_create()
 * (do not call cairo_create directly, call this instead)
 */
cairo_t*
Canvas::createCairoContext() {
  cairo_t* ret = cairo_create(surface());
  cairo_set_line_width(ret, 1); // Cairo defaults to 2
  return ret;
}
/*
 * Construct an Error from the given cairo status.
 */
Local<Value>
Canvas::Error(cairo_status_t status) {
  return Exception::Error(Nan::New<String>(cairo_status_to_string(status)).ToLocalChecked());
}
#undef CHECK_RECEIVER
node_modules/canvas/src/Canvas.h
New file
@@ -0,0 +1,96 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include "backend/Backend.h"
#include <cairo.h>
#include "dll_visibility.h"
#include <nan.h>
#include <pango/pangocairo.h>
#include <v8.h>
#include <vector>
#include <cstddef>
/*
 * FontFace describes a font file in terms of one PangoFontDescription that
 * will resolve to it and one that the user describes it as (like @font-face)
 */
class FontFace {
  public:
    PangoFontDescription *sys_desc = nullptr;
    PangoFontDescription *user_desc = nullptr;
    unsigned char file_path[1024];
};
enum text_baseline_t : uint8_t {
  TEXT_BASELINE_ALPHABETIC = 0,
  TEXT_BASELINE_TOP = 1,
  TEXT_BASELINE_BOTTOM = 2,
  TEXT_BASELINE_MIDDLE = 3,
  TEXT_BASELINE_IDEOGRAPHIC = 4,
  TEXT_BASELINE_HANGING = 5
};
enum text_align_t : int8_t {
  TEXT_ALIGNMENT_LEFT = -1,
  TEXT_ALIGNMENT_CENTER = 0,
  TEXT_ALIGNMENT_RIGHT = 1,
  // Currently same as LEFT and RIGHT without RTL support:
  TEXT_ALIGNMENT_START = -2,
  TEXT_ALIGNMENT_END = 2
};
enum canvas_draw_mode_t : uint8_t {
  TEXT_DRAW_PATHS,
  TEXT_DRAW_GLYPHS
};
/*
 * Canvas.
 */
class Canvas: public Nan::ObjectWrap {
  public:
    static Nan::Persistent<v8::FunctionTemplate> 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<v8::Value> 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);
    static PangoWeight GetWeightFromCSSString(const char *weight);
    static PangoStyle GetStyleFromCSSString(const char *style);
    static PangoFontDescription *ResolveFontDescription(const PangoFontDescription *desc);
    DLL_PUBLIC inline Backend* backend() { return _backend; }
    DLL_PUBLIC inline cairo_surface_t* surface(){ return backend()->getSurface(); }
    cairo_t* createCairoContext();
    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<std::size_t>(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<v8::Object> canvas);
  private:
    ~Canvas();
    Backend* _backend;
};
node_modules/canvas/src/CanvasError.h
New file
@@ -0,0 +1,23 @@
#pragma once
#include <string>
class CanvasError {
  public:
    std::string message;
    std::string syscall;
    std::string path;
    int cerrno = 0;
    void set(const char* iMessage = NULL, const char* iSyscall = NULL, int iErrno = 0, const char* iPath = NULL) {
      if (iMessage) message.assign(iMessage);
      if (iSyscall) syscall.assign(iSyscall);
      cerrno = iErrno;
      if (iPath) path.assign(iPath);
    }
    void reset() {
      message.clear();
      syscall.clear();
      path.clear();
      cerrno = 0;
    }
};
node_modules/canvas/src/CanvasGradient.cc
New file
@@ -0,0 +1,123 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "CanvasGradient.h"
#include "Canvas.h"
#include "color.h"
using namespace v8;
Nan::Persistent<FunctionTemplate> Gradient::constructor;
/*
 * Initialize CanvasGradient.
 */
void
Gradient::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
  Nan::HandleScope scope;
  // Constructor
  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Gradient::New);
  constructor.Reset(ctor);
  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(Nan::New("CanvasGradient").ToLocalChecked());
  // Prototype
  Nan::SetPrototypeMethod(ctor, "addColorStop", AddColorStop);
  Local<Context> ctx = Nan::GetCurrentContext();
  Nan::Set(target,
           Nan::New("CanvasGradient").ToLocalChecked(),
           ctor->GetFunction(ctx).ToLocalChecked());
}
/*
 * Initialize a new CanvasGradient.
 */
NAN_METHOD(Gradient::New) {
  if (!info.IsConstructCall()) {
    return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
  }
  // Linear
  if (4 == info.Length()) {
    Gradient *grad = new Gradient(
        Nan::To<double>(info[0]).FromMaybe(0)
      , Nan::To<double>(info[1]).FromMaybe(0)
      , Nan::To<double>(info[2]).FromMaybe(0)
      , Nan::To<double>(info[3]).FromMaybe(0));
    grad->Wrap(info.This());
    info.GetReturnValue().Set(info.This());
    return;
  }
  // Radial
  if (6 == info.Length()) {
    Gradient *grad = new Gradient(
        Nan::To<double>(info[0]).FromMaybe(0)
      , Nan::To<double>(info[1]).FromMaybe(0)
      , Nan::To<double>(info[2]).FromMaybe(0)
      , Nan::To<double>(info[3]).FromMaybe(0)
      , Nan::To<double>(info[4]).FromMaybe(0)
      , Nan::To<double>(info[5]).FromMaybe(0));
    grad->Wrap(info.This());
    info.GetReturnValue().Set(info.This());
    return;
  }
  return Nan::ThrowTypeError("invalid arguments");
}
/*
 * 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");
  Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(info.This());
  short ok;
  Nan::Utf8String str(info[1]);
  uint32_t rgba = rgba_from_string(*str, &ok);
  if (ok) {
    rgba_t color = rgba_create(rgba);
    cairo_pattern_add_color_stop_rgba(
        grad->pattern()
      , Nan::To<double>(info[0]).FromMaybe(0)
      , color.r
      , color.g
      , color.b
      , color.a);
  } else {
    return Nan::ThrowTypeError("parse color failed");
  }
}
/*
 * 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);
}
node_modules/canvas/src/CanvasGradient.h
New file
@@ -0,0 +1,22 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include <nan.h>
#include <v8.h>
#include <cairo.h>
class Gradient: public Nan::ObjectWrap {
  public:
    static Nan::Persistent<v8::FunctionTemplate> 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);
    inline cairo_pattern_t *pattern(){ return _pattern; }
  private:
    ~Gradient();
    cairo_pattern_t *_pattern;
};
node_modules/canvas/src/CanvasPattern.cc
New file
@@ -0,0 +1,136 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "CanvasPattern.h"
#include "Canvas.h"
#include "Image.h"
using namespace v8;
const cairo_user_data_key_t *pattern_repeat_key;
Nan::Persistent<FunctionTemplate> Pattern::constructor;
Nan::Persistent<Function> Pattern::_DOMMatrix;
/*
 * Initialize CanvasPattern.
 */
void
Pattern::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
  Nan::HandleScope scope;
  // Constructor
  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Pattern::New);
  constructor.Reset(ctor);
  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(Nan::New("CanvasPattern").ToLocalChecked());
  Nan::SetPrototypeMethod(ctor, "setTransform", SetTransform);
  // Prototype
  Local<Context> ctx = Nan::GetCurrentContext();
  Nan::Set(target, Nan::New("CanvasPattern").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked());
  Nan::Set(target, Nan::New("CanvasPatternInit").ToLocalChecked(), Nan::New<Function>(SaveExternalModules));
}
/*
 * Save some external modules as private references.
 */
NAN_METHOD(Pattern::SaveExternalModules) {
  _DOMMatrix.Reset(Nan::To<Function>(info[0]).ToLocalChecked());
}
/*
 * Initialize a new CanvasPattern.
 */
NAN_METHOD(Pattern::New) {
  if (!info.IsConstructCall()) {
    return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
  }
  cairo_surface_t *surface;
  Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
  // Image
  if (Nan::New(Image::constructor)->HasInstance(obj)) {
    Image *img = Nan::ObjectWrap::Unwrap<Image>(obj);
    if (!img->isComplete()) {
      return Nan::ThrowError("Image given has not completed loading");
    }
    surface = img->surface();
  // Canvas
  } else if (Nan::New(Canvas::constructor)->HasInstance(obj)) {
    Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(obj);
    surface = canvas->surface();
  // Invalid
  } else {
    return Nan::ThrowTypeError("Image or Canvas expected");
  }
  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 *pattern = new Pattern(surface, repeat);
  pattern->Wrap(info.This());
  info.GetReturnValue().Set(info.This());
}
/*
 * Set the pattern-space to user-space transform.
 */
NAN_METHOD(Pattern::SetTransform) {
  Pattern *pattern = Nan::ObjectWrap::Unwrap<Pattern>(info.This());
  Local<Context> ctx = Nan::GetCurrentContext();
  Local<Object> mat = Nan::To<Object>(info[0]).ToLocalChecked();
#if NODE_MAJOR_VERSION >= 8
  if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) {
    return Nan::ThrowTypeError("Expected DOMMatrix");
  }
#endif
  cairo_matrix_t matrix;
  cairo_matrix_init(&matrix,
    Nan::To<double>(Nan::Get(mat, Nan::New("a").ToLocalChecked()).ToLocalChecked()).FromMaybe(1),
    Nan::To<double>(Nan::Get(mat, Nan::New("b").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
    Nan::To<double>(Nan::Get(mat, Nan::New("c").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
    Nan::To<double>(Nan::Get(mat, Nan::New("d").ToLocalChecked()).ToLocalChecked()).FromMaybe(1),
    Nan::To<double>(Nan::Get(mat, Nan::New("e").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
    Nan::To<double>(Nan::Get(mat, Nan::New("f").ToLocalChecked()).ToLocalChecked()).FromMaybe(0)
  );
  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);
}
repeat_type_t Pattern::get_repeat_type_for_cairo_pattern(cairo_pattern_t *pattern) {
  void *ud = cairo_pattern_get_user_data(pattern, pattern_repeat_key);
  return *reinterpret_cast<repeat_type_t*>(ud);
}
/*
 * Destroy the pattern.
 */
Pattern::~Pattern() {
  cairo_pattern_destroy(_pattern);
}
node_modules/canvas/src/CanvasPattern.h
New file
@@ -0,0 +1,37 @@
// Copyright (c) 2011 LearnBoost <tj@learnboost.com>
#pragma once
#include <cairo.h>
#include <nan.h>
#include <v8.h>
/*
 * Canvas types.
 */
typedef enum {
  NO_REPEAT,  // match CAIRO_EXTEND_NONE
  REPEAT,  // match CAIRO_EXTEND_REPEAT
  REPEAT_X, // needs custom processing
  REPEAT_Y // needs custom processing
} repeat_type_t;
extern const cairo_user_data_key_t *pattern_repeat_key;
class Pattern: public Nan::ObjectWrap {
  public:
    static Nan::Persistent<v8::FunctionTemplate> constructor;
    static Nan::Persistent<v8::Function> _DOMMatrix;
    static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
    static NAN_METHOD(New);
    static NAN_METHOD(SaveExternalModules);
    static NAN_METHOD(SetTransform);
    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();
    cairo_pattern_t *_pattern;
    repeat_type_t _repeat;
};
node_modules/canvas/src/CanvasRenderingContext2d.cc
New file
@@ -0,0 +1,3360 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "CanvasRenderingContext2d.h"
#include <algorithm>
#include "backend/ImageBackend.h"
#include <cairo-pdf.h>
#include "Canvas.h"
#include "CanvasGradient.h"
#include "CanvasPattern.h"
#include <cmath>
#include <cstdlib>
#include "Image.h"
#include "ImageData.h"
#include <limits>
#include <map>
#include "Point.h"
#include <string>
#include "Util.h"
#include <vector>
using namespace v8;
Nan::Persistent<FunctionTemplate> Context2d::constructor;
/*
 * Rectangle arg assertions.
 */
#define RECT_ARGS \
  double args[4]; \
  if(!checkArgs(info, args, 4)) \
    return; \
  double x = args[0]; \
  double y = args[1]; \
  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.;
/*
 * Simple helper macro for a rather verbose function call.
 */
#define PANGO_LAYOUT_GET_METRICS(LAYOUT) pango_context_get_metrics( \
   pango_layout_get_context(LAYOUT), \
   pango_layout_get_font_description(LAYOUT), \
   pango_context_get_language(pango_layout_get_context(LAYOUT)))
inline static bool checkArgs(const Nan::FunctionCallbackInfo<Value> &info, double *args, int argsNum, int offset = 0){
  int argsEnd = offset + argsNum;
  bool areArgsValid = true;
  for (int i = offset; i < argsEnd; i++) {
    double val = Nan::To<double>(info[i]).FromMaybe(0);
    if (areArgsValid) {
      if (!std::isfinite(val)) {
        // We should continue the loop instead of returning immediately
        // See https://html.spec.whatwg.org/multipage/canvas.html
        areArgsValid = false;
        continue;
      }
      args[i - offset] = val;
    }
  }
  return areArgsValid;
}
Nan::Persistent<Function> Context2d::_DOMMatrix;
Nan::Persistent<Function> Context2d::_parseFont;
/*
 * Initialize Context2d.
 */
void
Context2d::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
  Nan::HandleScope scope;
  // Constructor
  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Context2d::New);
  constructor.Reset(ctor);
  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(Nan::New("CanvasRenderingContext2D").ToLocalChecked());
  // Prototype
  Local<ObjectTemplate> 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<Context> ctx = Nan::GetCurrentContext();
  Nan::Set(target, Nan::New("CanvasRenderingContext2d").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked());
  Nan::Set(target, Nan::New("CanvasRenderingContext2dInit").ToLocalChecked(), Nan::New<Function>(SaveExternalModules));
}
/*
 * Create a cairo context.
 */
Context2d::Context2d(Canvas *canvas) {
  _canvas = canvas;
  _context = canvas->createCairoContext();
  _layout = pango_cairo_create_layout(_context);
  // As of January 2023, Pango rounds glyph positions which renders text wider
  // or narrower than the browser. See #2184 for more information
#if PANGO_VERSION_CHECK(1, 44, 0)
  pango_context_set_round_glyph_positions(pango_layout_get_context(_layout), FALSE);
#endif
  states.emplace();
  state = &states.top();
  pango_layout_set_font_description(_layout, state->fontDescription);
}
/*
 * Destroy cairo context.
 */
Context2d::~Context2d() {
  g_object_unref(_layout);
  cairo_destroy(_context);
  _resetPersistentHandles();
}
/*
 * Reset canvas state.
 */
void Context2d::resetState() {
  states.pop();
  states.emplace();
  pango_layout_set_font_description(_layout, state->fontDescription);
  _resetPersistentHandles();
}
void Context2d::_resetPersistentHandles() {
  _fillStyle.Reset();
  _strokeStyle.Reset();
}
/*
 * Save cairo / canvas state.
 */
void
Context2d::save() {
  cairo_save(_context);
  states.emplace(states.top());
  state = &states.top();
}
/*
 * Restore cairo / canvas state.
 */
void
Context2d::restore() {
  if (states.size() > 1) {
    cairo_restore(_context);
    states.pop();
    state = &states.top();
    pango_layout_set_font_description(_layout, state->fontDescription);
  }
}
/*
 * Save flat path.
 */
void
Context2d::savePath() {
  _path = cairo_copy_path_flat(_context);
  cairo_new_path(_context);
}
/*
 * Restore flat path.
 */
void
Context2d::restorePath() {
  cairo_new_path(_context);
  cairo_append_path(_context, _path);
  cairo_path_destroy(_path);
}
/*
 * Create temporary surface for gradient or pattern transparency
 */
cairo_pattern_t*
create_transparent_gradient(cairo_pattern_t *source, float alpha) {
  double x0;
  double y0;
  double x1;
  double y1;
  double r0;
  double r1;
  int count;
  int i;
  double offset;
  double r;
  double g;
  double b;
  double a;
  cairo_pattern_t *newGradient;
  cairo_pattern_type_t type = cairo_pattern_get_type(source);
  cairo_pattern_get_color_stop_count(source, &count);
  if (type == CAIRO_PATTERN_TYPE_LINEAR) {
    cairo_pattern_get_linear_points (source, &x0, &y0, &x1, &y1);
    newGradient = cairo_pattern_create_linear(x0, y0, x1, y1);
  } else if (type == CAIRO_PATTERN_TYPE_RADIAL) {
    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++ ) {
    cairo_pattern_get_color_stop_rgba(source, i, &offset, &r, &g, &b, &a);
    cairo_pattern_add_color_stop_rgba(newGradient, offset, r, g, b, a * alpha);
  }
  return newGradient;
}
cairo_pattern_t*
create_transparent_pattern(cairo_pattern_t *source, float alpha) {
  cairo_surface_t *surface;
  cairo_pattern_get_surface(source, &surface);
  int width = cairo_image_surface_get_width(surface);
  int height = cairo_image_surface_get_height(surface);
  cairo_surface_t *mask_surface = cairo_image_surface_create(
    CAIRO_FORMAT_ARGB32,
    width,
    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);
  cairo_paint_with_alpha(mask_context, alpha);
  cairo_destroy(mask_context);
  cairo_pattern_t* newPattern = cairo_pattern_create_for_surface(mask_surface);
  cairo_surface_destroy(mask_surface);
  return newPattern;
}
/*
 * Fill and apply shadow.
 */
void
Context2d::setFillRule(v8::Local<v8::Value> value) {
  cairo_fill_rule_t rule = CAIRO_FILL_RULE_WINDING;
  if (value->IsString()) {
    Nan::Utf8String str(value);
    if (std::strcmp(*str, "evenodd") == 0) {
      rule = CAIRO_FILL_RULE_EVEN_ODD;
    }
  }
  cairo_set_fill_rule(_context, rule);
}
void
Context2d::fill(bool preserve) {
  cairo_pattern_t *new_pattern;
  bool needsRestore = false;
  if (state->fillPattern) {
    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.
        return;
      }
      cairo_set_source(_context, new_pattern);
      cairo_pattern_destroy(new_pattern);
    } else {
      cairo_pattern_set_filter(state->fillPattern, state->patternQuality);
      cairo_set_source(_context, state->fillPattern);
    }
    repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->fillPattern);
    if (repeat == NO_REPEAT) {
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE);
    } else if (repeat == REPEAT) {
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
    } else {
      cairo_save(_context);
      cairo_path_t *savedPath = cairo_copy_path(_context);
      cairo_surface_t *patternSurface = nullptr;
      cairo_pattern_get_surface(cairo_get_source(_context), &patternSurface);
      double width, height;
      if (repeat == REPEAT_X) {
        double x1, x2;
        cairo_path_extents(_context, &x1, nullptr, &x2, nullptr);
        width = x2 - x1;
        height = cairo_image_surface_get_height(patternSurface);
      } else {
        double y1, y2;
        cairo_path_extents(_context, nullptr, &y1, nullptr, &y2);
        width = cairo_image_surface_get_width(patternSurface);
        height = y2 - y1;
      }
      cairo_new_path(_context);
      cairo_rectangle(_context, 0, 0, width, height);
      cairo_clip(_context);
      cairo_append_path(_context, savedPath);
      cairo_path_destroy(savedPath);
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
      needsRestore = true;
    }
  } else if (state->fillGradient) {
    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.
        return;
      }
      cairo_pattern_set_filter(new_pattern, state->patternQuality);
      cairo_set_source(_context, new_pattern);
      cairo_pattern_destroy(new_pattern);
    } else {
      cairo_pattern_set_filter(state->fillGradient, state->patternQuality);
      cairo_set_source(_context, state->fillGradient);
    }
  } else {
    setSourceRGBA(state->fill);
  }
  if (preserve) {
    hasShadow()
      ? shadow(cairo_fill_preserve)
      : cairo_fill_preserve(_context);
  } else {
    hasShadow()
      ? shadow(cairo_fill)
      : cairo_fill(_context);
  }
  if (needsRestore) {
    cairo_restore(_context);
  }
}
/*
 * Stroke and apply shadow.
 */
void
Context2d::stroke(bool preserve) {
  cairo_pattern_t *new_pattern;
  if (state->strokePattern) {
    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.
        return;
      }
      cairo_set_source(_context, new_pattern);
      cairo_pattern_destroy(new_pattern);
    } else {
      cairo_pattern_set_filter(state->strokePattern, state->patternQuality);
      cairo_set_source(_context, state->strokePattern);
    }
    repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->strokePattern);
    if (NO_REPEAT == repeat) {
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE);
    } else {
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
    }
  } else if (state->strokeGradient) {
    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.
        return;
      }
      cairo_pattern_set_filter(new_pattern, state->patternQuality);
      cairo_set_source(_context, new_pattern);
      cairo_pattern_destroy(new_pattern);
    } else {
      cairo_pattern_set_filter(state->strokeGradient, state->patternQuality);
      cairo_set_source(_context, state->strokeGradient);
    }
  } else {
    setSourceRGBA(state->stroke);
  }
  if (preserve) {
    hasShadow()
      ? shadow(cairo_stroke_preserve)
      : cairo_stroke_preserve(_context);
  } else {
    hasShadow()
      ? shadow(cairo_stroke)
      : cairo_stroke(_context);
  }
}
/*
 * Apply shadow with the given draw fn.
 */
void
Context2d::shadow(void (fn)(cairo_t *cr)) {
  cairo_path_t *path = cairo_copy_path_flat(_context);
  cairo_save(_context);
  // shadowOffset is unaffected by current transform
  cairo_matrix_t path_matrix;
  cairo_get_matrix(_context, &path_matrix);
  cairo_identity_matrix(_context);
  // Apply shadow
  cairo_push_group(_context);
  // No need to invoke blur if shadowBlur is 0
  if (state->shadowBlur) {
    // find out extent of path
    double x1, y1, x2, y2;
    if (fn == cairo_fill || fn == cairo_fill_preserve) {
      cairo_fill_extents(_context, &x1, &y1, &x2, &y2);
    } else {
      cairo_stroke_extents(_context, &x1, &y1, &x2, &y2);
    }
    // create new image surface that size + padding for blurring
    double dx = x2-x1, dy = y2-y1;
    cairo_user_to_device_distance(_context, &dx, &dy);
    int pad = state->shadowBlur * 2;
    cairo_surface_t *shadow_surface = cairo_image_surface_create(
      CAIRO_FORMAT_ARGB32,
      dx + 2 * pad,
      dy + 2 * pad);
    cairo_t *shadow_context = cairo_create(shadow_surface);
    // transform path to the right place
    cairo_translate(shadow_context, pad-x1, pad-y1);
    cairo_transform(shadow_context, &path_matrix);
    // set lineCap lineJoin lineDash
    cairo_set_line_cap(shadow_context, cairo_get_line_cap(_context));
    cairo_set_line_join(shadow_context, cairo_get_line_join(_context));
    double offset;
    int dashes = cairo_get_dash_count(_context);
    std::vector<double> a(dashes);
    cairo_get_dash(_context, a.data(), &offset);
    cairo_set_dash(shadow_context, a.data(), dashes, offset);
    // draw the path and blur
    cairo_set_line_width(shadow_context, cairo_get_line_width(_context));
    cairo_new_path(shadow_context);
    cairo_append_path(shadow_context, path);
    setSourceRGBA(shadow_context, state->shadow);
    fn(shadow_context);
    blur(shadow_surface, state->shadowBlur);
    // paint to original context
    cairo_set_source_surface(_context, shadow_surface,
      x1 - pad + state->shadowOffsetX + 1,
      y1 - pad + state->shadowOffsetY + 1);
    cairo_paint(_context);
    cairo_destroy(shadow_context);
    cairo_surface_destroy(shadow_surface);
  } else {
    // Offset first, then apply path's transform
    cairo_translate(
        _context
      , state->shadowOffsetX
      , state->shadowOffsetY);
    cairo_transform(_context, &path_matrix);
    // Apply shadow
    cairo_new_path(_context);
    cairo_append_path(_context, path);
    setSourceRGBA(state->shadow);
    fn(_context);
  }
  // Paint the shadow
  cairo_pop_group_to_source(_context);
  cairo_paint(_context);
  // Restore state
  cairo_restore(_context);
  cairo_new_path(_context);
  cairo_append_path(_context, path);
  fn(_context);
  cairo_path_destroy(path);
}
/*
 * Set source RGBA for the current context
 */
void
Context2d::setSourceRGBA(rgba_t color) {
  setSourceRGBA(_context, color);
}
/*
 * Set source RGBA
 */
void
Context2d::setSourceRGBA(cairo_t *ctx, rgba_t color) {
  cairo_set_source_rgba(
      ctx
    , color.r
    , color.g
    , color.b
    , color.a * state->globalAlpha);
}
/*
 * Check if the context has a drawable shadow.
 */
bool
Context2d::hasShadow() {
  return state->shadow.a
    && (state->shadowBlur || state->shadowOffsetX || state->shadowOffsetY);
}
/*
 * Blur the given surface with the given radius.
 */
void
Context2d::blur(cairo_surface_t *surface, int radius) {
  // Steve Hanov, 2009
  // Released into the public domain.
  radius = radius * 0.57735f + 0.5f;
  // get width, height
  int width = cairo_image_surface_get_width( surface );
  int height = cairo_image_surface_get_height( surface );
  unsigned* precalc =
      (unsigned*)malloc(width*height*sizeof(unsigned));
  cairo_surface_flush( surface );
  unsigned char* src = cairo_image_surface_get_data( surface );
  double mul=1.f/((radius*2)*(radius*2));
  int channel;
  // The number of times to perform the averaging. According to wikipedia,
  // three iterations is good enough to pass for a gaussian.
  const int MAX_ITERATIONS = 3;
  int iteration;
  for ( iteration = 0; iteration < MAX_ITERATIONS; iteration++ ) {
      for( channel = 0; channel < 4; channel++ ) {
          int x,y;
          // precomputation step.
          unsigned char* pix = src;
          unsigned* pre = precalc;
          pix += channel;
          for (y=0;y<height;y++) {
              for (x=0;x<width;x++) {
                  int tot=pix[0];
                  if (x>0) tot+=pre[-1];
                  if (y>0) tot+=pre[-width];
                  if (x>0 && y>0) tot-=pre[-width-1];
                  *pre++=tot;
                  pix += 4;
              }
          }
          // blur step.
          pix = src + (int)radius * width * 4 + (int)radius * 4 + channel;
          for (y=radius;y<height-radius;y++) {
              for (x=radius;x<width-radius;x++) {
                  int l = x < radius ? 0 : x - radius;
                  int t = y < radius ? 0 : y - radius;
                  int r = x + radius >= width ? width - 1 : x + radius;
                  int b = y + radius >= height ? height - 1 : y + radius;
                  int tot = precalc[r+b*width] + precalc[l+t*width] -
                      precalc[l+b*width] - precalc[r+t*width];
                  *pix=(unsigned char)(tot*mul);
                  pix += 4;
              }
              pix += (int)radius * 2 * 4;
          }
      }
  }
  cairo_surface_mark_dirty(surface);
  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<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
  if (!Nan::New(Canvas::constructor)->HasInstance(obj))
    return Nan::ThrowTypeError("Canvas expected");
  Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(obj);
  bool isImageBackend = canvas->backend()->getName() == "image";
  if (isImageBackend) {
    cairo_format_t format = ImageBackend::DEFAULT_FORMAT;
    if (info[1]->IsObject()) {
      Local<Object> ctxAttributes = Nan::To<Object>(info[1]).ToLocalChecked();
      Local<Value> 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<Value> alpha = Nan::Get(ctxAttributes, Nan::New("alpha").ToLocalChecked()).ToLocalChecked();
      if (alpha->IsBoolean() && !Nan::To<bool>(alpha).FromMaybe(false)) {
        format = CAIRO_FORMAT_RGB24;
      }
    }
    static_cast<ImageBackend*>(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<Function>(info[0]).ToLocalChecked());
  _parseFont.Reset(Nan::To<Function>(info[1]).ToLocalChecked());
}
/*
* Get format (string).
*/
NAN_GETTER(Context2d::GetFormat) {
  CHECK_RECEIVER(Context2d.GetFormat);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  std::string pixelFormatString;
  switch (context->canvas()->backend()->getFormat()) {
  case CAIRO_FORMAT_ARGB32: pixelFormatString = "RGBA32"; break;
  case CAIRO_FORMAT_RGB24: pixelFormatString = "RGB24"; break;
  case CAIRO_FORMAT_A8: pixelFormatString = "A8"; break;
  case CAIRO_FORMAT_A1: pixelFormatString = "A1"; break;
  case CAIRO_FORMAT_RGB16_565: pixelFormatString = "RGB16_565"; break;
#ifdef CAIRO_FORMAT_RGB30
  case CAIRO_FORMAT_RGB30: pixelFormatString = "RGB30"; break;
#endif
  default: return info.GetReturnValue().SetNull();
  }
  info.GetReturnValue().Set(Nan::New<String>(pixelFormatString).ToLocalChecked());
}
/*
 * Create a new page.
 */
NAN_METHOD(Context2d::AddPage) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  if (context->canvas()->backend()->getName() != "pdf") {
    return Nan::ThrowError("only PDF canvases support .addPage()");
  }
  cairo_show_page(context->context());
  int width = Nan::To<int32_t>(info[0]).FromMaybe(0);
  int height = Nan::To<int32_t>(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;
}
/*
 * Put image data.
 *
 *  - imageData, dx, dy
 *  - imageData, dx, dy, sx, sy, sw, sh
 *
 */
NAN_METHOD(Context2d::PutImageData) {
  if (!info[0]->IsObject())
    return Nan::ThrowTypeError("ImageData expected");
  Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
  if (!Nan::New(ImageData::constructor)->HasInstance(obj))
    return Nan::ThrowTypeError("ImageData expected");
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(obj);
  uint8_t *src = imageData->data();
  uint8_t *dst = context->canvas()->data();
  int dstStride = context->canvas()->stride();
  int Bpp = dstStride / context->canvas()->getWidth();
  int srcStride = Bpp * imageData->width();
  int sx = 0
    , sy = 0
    , sw = 0
    , sh = 0
    , dx = Nan::To<int32_t>(info[1]).FromMaybe(0)
    , dy = Nan::To<int32_t>(info[2]).FromMaybe(0)
    , rows
    , cols;
  switch (info.Length()) {
    // imageData, dx, dy
    case 3:
      sw = imageData->width();
      sh = imageData->height();
      break;
    // imageData, dx, dy, sx, sy, sw, sh
    case 7:
      sx = Nan::To<int32_t>(info[3]).FromMaybe(0);
      sy = Nan::To<int32_t>(info[4]).FromMaybe(0);
      sw = Nan::To<int32_t>(info[5]).FromMaybe(0);
      sh = Nan::To<int32_t>(info[6]).FromMaybe(0);
      // fix up negative height, width
      if (sw < 0) sx += sw, sw = -sw;
      if (sh < 0) sy += sh, sh = -sh;
      // clamp the left edge
      if (sx < 0) sw += sx, sx = 0;
      if (sy < 0) sh += sy, sy = 0;
      // clamp the right edge
      if (sx + sw > imageData->width()) sw = imageData->width() - sx;
      if (sy + sh > imageData->height()) sh = imageData->height() - sy;
      // start destination at source offset
      dx += sx;
      dy += sy;
      break;
    default:
      return Nan::ThrowError("invalid arguments");
  }
  // chop off outlying source data
  if (dx < 0) sw += dx, sx -= dx, dx = 0;
  if (dy < 0) sh += dy, sy -= dy, dy = 0;
  // 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);
  if (cols <= 0 || rows <= 0) return;
  switch (context->canvas()->backend()->getFormat()) {
  case CAIRO_FORMAT_ARGB32: {
    src += sy * srcStride + sx * 4;
    dst += dstStride * dy + 4 * dx;
    for (int y = 0; y < rows; ++y) {
      uint8_t *dstRow = dst;
      uint8_t *srcRow = src;
      for (int x = 0; x < cols; ++x) {
        // rgba
        uint8_t r = *srcRow++;
        uint8_t g = *srcRow++;
        uint8_t b = *srcRow++;
        uint8_t a = *srcRow++;
        // argb
        // performance optimization: fully transparent/opaque pixels can be
        // processed more efficiently.
        if (a == 0) {
          *dstRow++ = 0;
          *dstRow++ = 0;
          *dstRow++ = 0;
          *dstRow++ = 0;
        } else if (a == 255) {
          *dstRow++ = b;
          *dstRow++ = g;
          *dstRow++ = r;
          *dstRow++ = a;
        } else {
          float alpha = (float)a / 255;
          *dstRow++ = b * alpha;
          *dstRow++ = g * alpha;
          *dstRow++ = r * alpha;
          *dstRow++ = a;
        }
      }
      dst += dstStride;
      src += srcStride;
    }
    break;
  }
  case CAIRO_FORMAT_RGB24: {
    src += sy * srcStride + sx * 4;
    dst += dstStride * dy + 4 * dx;
    for (int y = 0; y < rows; ++y) {
      uint8_t *dstRow = dst;
      uint8_t *srcRow = src;
      for (int x = 0; x < cols; ++x) {
        // rgba
        uint8_t r = *srcRow++;
        uint8_t g = *srcRow++;
        uint8_t b = *srcRow++;
        srcRow++;
        // argb
        *dstRow++ = b;
        *dstRow++ = g;
        *dstRow++ = r;
        *dstRow++ = 255;
      }
      dst += dstStride;
      src += srcStride;
    }
    break;
  }
  case CAIRO_FORMAT_A8: {
    src += sy * srcStride + sx;
    dst += dstStride * dy + dx;
    if (srcStride == dstStride && cols == dstStride) {
      // fast path: strides are the same and doing a full-width put
      memcpy(dst, src, cols * rows);
    } else {
      for (int y = 0; y < rows; ++y) {
        memcpy(dst, src, cols);
        dst += dstStride;
        src += srcStride;
      }
    }
    break;
  }
  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");
    break;
  }
  case CAIRO_FORMAT_RGB16_565: {
    src += sy * srcStride + sx * 2;
    dst += dstStride * dy + 2 * dx;
    for (int y = 0; y < rows; ++y) {
      memcpy(dst, src, cols * 2);
      dst += dstStride;
      src += srcStride;
    }
    break;
  }
#ifdef CAIRO_FORMAT_RGB30
  case CAIRO_FORMAT_RGB30: {
    // TODO
    Nan::ThrowError("putImageData for CANVAS_FORMAT_RGB30 is not yet implemented");
    break;
  }
#endif
  default: {
    Nan::ThrowError("Invalid pixel format or not an image canvas");
    return;
  }
  }
  cairo_surface_mark_dirty_rectangle(
      context->canvas()->surface()
    , dx
    , dy
    , cols
    , rows);
}
/*
 * Get image data.
 *
 *  - sx, sy, sw, sh
 *
 */
NAN_METHOD(Context2d::GetImageData) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  Canvas *canvas = context->canvas();
  int sx = Nan::To<int32_t>(info[0]).FromMaybe(0);
  int sy = Nan::To<int32_t>(info[1]).FromMaybe(0);
  int sw = Nan::To<int32_t>(info[2]).FromMaybe(0);
  int sh = Nan::To<int32_t>(info[3]).FromMaybe(0);
  if (!sw)
    return Nan::ThrowError("IndexSizeError: The source width is 0.");
  if (!sh)
    return Nan::ThrowError("IndexSizeError: The source height is 0.");
  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");
  // WebKit and Firefox have this behavior:
  // Flip the coordinates so the origin is top/left-most:
  if (sw < 0) {
    sx += sw;
    sw = -sw;
  }
  if (sh < 0) {
    sy += sh;
    sh = -sh;
  }
  if (sx + sw > width) sw = width - sx;
  if (sy + sh > height) sh = height - sy;
  // WebKit/moz functionality. node-canvas used to return in either case.
  if (sw <= 0) sw = 1;
  if (sh <= 0) sh = 1;
  // Non-compliant. "Pixels outside the canvas must be returned as transparent
  // black." This instead clips the returned array to the canvas area.
  if (sx < 0) {
    sw += sx;
    sx = 0;
  }
  if (sy < 0) {
    sh += sy;
    sy = 0;
  }
  int srcStride = canvas->stride();
  int bpp = srcStride / width;
  int size = sw * sh * bpp;
  int dstStride = sw * bpp;
  uint8_t *src = canvas->data();
  Local<ArrayBuffer> buffer = ArrayBuffer::New(Isolate::GetCurrent(), size);
  Local<TypedArray> dataArray;
  if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565) {
    dataArray = Uint16Array::New(buffer, 0, size >> 1);
  } else {
    dataArray = Uint8ClampedArray::New(buffer, 0, size);
  }
  Nan::TypedArrayContents<uint8_t> typedArrayContents(dataArray);
  uint8_t* dst = *typedArrayContents;
  switch (canvas->backend()->getFormat()) {
  case CAIRO_FORMAT_ARGB32: {
    // Rearrange alpha (argb -> rgba), undo alpha pre-multiplication,
    // and store in big-endian format
    for (int y = 0; y < sh; ++y) {
      uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
      for (int x = 0; x < sw; ++x) {
        int bx = x * 4;
        uint32_t *pixel = row + x + sx;
        uint8_t a = *pixel >> 24;
        uint8_t r = *pixel >> 16;
        uint8_t g = *pixel >> 8;
        uint8_t b = *pixel;
        dst[bx + 3] = a;
        // Performance optimization: fully transparent/opaque pixels can be
        // processed more efficiently.
        if (a == 0 || a == 255) {
          dst[bx + 0] = r;
          dst[bx + 1] = g;
          dst[bx + 2] = b;
        } else {
          // Undo alpha pre-multiplication
          float alphaR = (float)255 / a;
          dst[bx + 0] = (int)((float)r * alphaR);
          dst[bx + 1] = (int)((float)g * alphaR);
          dst[bx + 2] = (int)((float)b * alphaR);
        }
      }
      dst += dstStride;
    }
    break;
  }
  case CAIRO_FORMAT_RGB24: {
  // Rearrange alpha (argb -> rgba) and store in big-endian format
    for (int y = 0; y < sh; ++y) {
    uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
    for (int x = 0; x < sw; ++x) {
      int bx = x * 4;
      uint32_t *pixel = row + x + sx;
      uint8_t r = *pixel >> 16;
      uint8_t g = *pixel >> 8;
      uint8_t b = *pixel;
      dst[bx + 0] = r;
      dst[bx + 1] = g;
      dst[bx + 2] = b;
      dst[bx + 3] = 255;
    }
    dst += dstStride;
    }
    break;
  }
  case CAIRO_FORMAT_A8: {
    for (int y = 0; y < sh; ++y) {
      uint8_t *row = (uint8_t *)(src + srcStride * (y + sy));
      memcpy(dst, row + sx, dstStride);
      dst += dstStride;
    }
    break;
  }
  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");
    break;
  }
  case CAIRO_FORMAT_RGB16_565: {
    for (int y = 0; y < sh; ++y) {
      uint16_t *row = (uint16_t *)(src + srcStride * (y + sy));
      memcpy(dst, row + sx, dstStride);
      dst += dstStride;
    }
    break;
  }
#ifdef CAIRO_FORMAT_RGB30
  case CAIRO_FORMAT_RGB30: {
    // TODO
    Nan::ThrowError("getImageData for CANVAS_FORMAT_RGB30 is not yet implemented");
    break;
  }
#endif
  default: {
    // Unlikely
    Nan::ThrowError("Invalid pixel format or not an image canvas");
    return;
  }
  }
  const int argc = 3;
  Local<Int32> swHandle = Nan::New(sw);
  Local<Int32> shHandle = Nan::New(sh);
  Local<Value> argv[argc] = { dataArray, swHandle, shHandle };
  Local<Function> ctor = Nan::GetFunction(Nan::New(ImageData::constructor)).ToLocalChecked();
  Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
  info.GetReturnValue().Set(instance);
}
/**
 * Create `ImageData` with the given dimensions or
 * `ImageData` instance for dimensions.
 */
NAN_METHOD(Context2d::CreateImageData){
  Isolate *iso = Isolate::GetCurrent();
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  Canvas *canvas = context->canvas();
  int32_t width, height;
  if (info[0]->IsObject()) {
    Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
    width = Nan::To<int32_t>(Nan::Get(obj, Nan::New("width").ToLocalChecked()).ToLocalChecked()).FromMaybe(0);
    height = Nan::To<int32_t>(Nan::Get(obj, Nan::New("height").ToLocalChecked()).ToLocalChecked()).FromMaybe(0);
  } else {
    width = Nan::To<int32_t>(info[0]).FromMaybe(0);
    height = Nan::To<int32_t>(info[1]).FromMaybe(0);
  }
  int stride = canvas->stride();
  double Bpp = static_cast<double>(stride) / canvas->getWidth();
  int nBytes = static_cast<int>(Bpp * width * height + .5);
  Local<ArrayBuffer> ab = ArrayBuffer::New(iso, nBytes);
  Local<Object> arr;
  if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565)
    arr = Uint16Array::New(ab, 0, nBytes / 2);
  else
    arr = Uint8ClampedArray::New(ab, 0, nBytes);
  const int argc = 3;
  Local<Value> argv[argc] = { arr, Nan::New(width), Nan::New(height) };
  Local<Function> ctor = Nan::GetFunction(Nan::New(ImageData::constructor)).ToLocalChecked();
  Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
  info.GetReturnValue().Set(instance);
}
/*
 * Take a transform matrix and return its components
 * 0: angle, 1: scaleX, 2: scaleY, 3: skewX, 4: translateX, 5: translateY
 */
void decompose_matrix(cairo_matrix_t matrix, double *destination) {
  double denom = pow(matrix.xx, 2) + pow(matrix.yx, 2);
  destination[0] = atan2(matrix.yx, matrix.xx);
  destination[1] = sqrt(denom);
  destination[2] = (matrix.xx * matrix.yy - matrix.xy * matrix.yx) / destination[1];
  destination[3] = atan2(matrix.xx * matrix.xy + matrix.yx * matrix.yy, denom);
  destination[4] = matrix.x0;
  destination[5] = matrix.y0;
}
/*
 * Draw image src image to the destination (context).
 *
 *  - dx, dy
 *  - dx, dy, dw, dh
 *  - sx, sy, sw, sh, dx, dy, dw, dh
 *
 */
NAN_METHOD(Context2d::DrawImage) {
  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");
  double args[8];
  if(!checkArgs(info, args, infoLen - 1, 1))
    return;
  double sx = 0
    , sy = 0
    , sw = 0
    , sh = 0
    , dx = 0
    , dy = 0
    , dw = 0
    , dh = 0
    , source_w = 0
    , source_h = 0;
  cairo_surface_t *surface;
  Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
  // Image
  if (Nan::New(Image::constructor)->HasInstance(obj)) {
    Image *img = Nan::ObjectWrap::Unwrap<Image>(obj);
    if (!img->isComplete()) {
      return Nan::ThrowError("Image given has not completed loading");
    }
    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<Canvas>(obj);
    source_w = sw = canvas->getWidth();
    source_h = sh = canvas->getHeight();
    surface = canvas->surface();
  // Invalid
  } else {
    return Nan::ThrowTypeError("Image or Canvas expected");
  }
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  // Arguments
  switch (infoLen) {
    // img, sx, sy, sw, sh, dx, dy, dw, dh
    case 9:
      sx = args[0];
      sy = args[1];
      sw = args[2];
      sh = args[3];
      dx = args[4];
      dy = args[5];
      dw = args[6];
      dh = args[7];
      break;
    // img, dx, dy, dw, dh
    case 5:
      dx = args[0];
      dy = args[1];
      dw = args[2];
      dh = args[3];
      break;
    // img, dx, dy
    case 3:
      dx = args[0];
      dy = args[1];
      dw = sw;
      dh = sh;
      break;
  }
  if (!(sw && sh && dw && dh))
    return;
  // Start draw
  cairo_save(ctx);
  cairo_matrix_t matrix;
  double transforms[6];
  cairo_get_matrix(context->context(), &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.
  double current_scale_x = std::abs(transforms[1]);
  double current_scale_y = std::abs(transforms[2]);
  double extra_dx = 0;
  double extra_dy = 0;
  double fx = dw / sw * current_scale_x; // transforms[1] is scale on X
  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 needsExtraSurface = sameCanvas || needCut || needScale;
  cairo_surface_t *surfTemp = NULL;
  cairo_t *ctxTemp = NULL;
  if (needsExtraSurface) {
    // we want to create the extra surface as small as possible.
    // fx and fy are the total scaling we need to apply to sw, sh.
    // from sw and sh we want to remove the part that is outside the source_w and soruce_h
    double real_w = sw;
    double real_h = sh;
    double translate_x = 0;
    double translate_y = 0;
    // if sx or sy are negative, a part of the area represented by sw and sh is empty
    // because there are empty pixels, so we cut it out.
    // On the other hand if sx or sy are positive, but sw and sh extend outside the real
    // source pixels, we cut the area in that case too.
    if (sx < 0) {
      extra_dx = -sx * fx;
      real_w = sw + sx;
    } else if (sx + sw > source_w) {
      real_w = sw - (sx + sw - source_w);
    }
    if (sy < 0) {
      extra_dy = -sy * fy;
      real_h = sh + sy;
    } else if (sy + sh > source_h) {
      real_h = sh - (sy + sh - source_h);
    }
    // if after cutting we are still bigger than source pixels, we restrict again
    if (real_w > source_w) {
      real_w = source_w;
    }
    if (real_h > source_h) {
      real_h = source_h;
    }
    // TODO: find a way to limit the surfTemp to real_w and real_h if fx and fy are bigger than 1.
    // there are no more pixel than the one available in the source, no need to create a bigger surface.
    surfTemp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, round(real_w * fx), round(real_h * fy));
    ctxTemp = cairo_create(surfTemp);
    cairo_scale(ctxTemp, fx, fy);
    if (sx > 0) {
      translate_x = sx;
    }
    if (sy > 0) {
      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_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) {
      // we need to create a new surface in order to blur
      int pad = context->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);
      cairo_mask_surface(shadow_context, surface, pad, pad);
      context->blur(shadow_surface, context->state->shadowBlur);
      // paint
      // @note: ShadowBlur looks different in each browser. This implementation matches chrome as close as possible.
      //        The 1.4 offset comes from visual tests with Chrome. I have read the spec and part of the shadowBlur
      //        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);
      cairo_paint(ctx);
      // cleanup
      cairo_destroy(shadow_context);
      cairo_surface_destroy(shadow_surface);
    } else {
      context->setSourceRGBA(context->state->shadow);
      cairo_mask_surface(ctx, surface,
        dx + (context->state->shadowOffsetX),
        dy + (context->state->shadowOffsetY));
    }
  }
  double scaled_dx = dx;
  double scaled_dy = dy;
  if (needsExtraSurface && (current_scale_x != 1 || current_scale_y != 1)) {
    // in this case our surface contains already current_scale_x, we need to scale back
    cairo_scale(ctx, 1 / current_scale_x, 1 / current_scale_y);
    scaled_dx *= current_scale_x;
    scaled_dy *= current_scale_y;
  }
  // 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_extend(cairo_get_source(ctx), CAIRO_EXTEND_NONE);
  cairo_paint_with_alpha(ctx, context->state->globalAlpha);
  cairo_restore(ctx);
  if (needsExtraSurface) {
    cairo_destroy(ctxTemp);
    cairo_surface_destroy(surfTemp);
  }
}
/*
 * Get global alpha.
 */
NAN_GETTER(Context2d::GetGlobalAlpha) {
  CHECK_RECEIVER(Context2d.GetGlobalAlpha);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(context->state->globalAlpha));
}
/*
 * Set global alpha.
 */
NAN_SETTER(Context2d::SetGlobalAlpha) {
  CHECK_RECEIVER(Context2d.SetGlobalAlpha);
  double n = Nan::To<double>(value).FromMaybe(0);
  if (n >= 0 && n <= 1) {
    Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
    context->state->globalAlpha = n;
  }
}
/*
 * Get global composite operation.
 */
NAN_GETTER(Context2d::GetGlobalCompositeOperation) {
  CHECK_RECEIVER(Context2d.GetGlobalCompositeOperation);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  const char *op = "source-over";
  switch (cairo_get_operator(ctx)) {
    // composite modes:
    case CAIRO_OPERATOR_CLEAR: op = "clear"; break;
    case CAIRO_OPERATOR_SOURCE: op = "copy"; break;
    case CAIRO_OPERATOR_DEST: op = "destination"; break;
    case CAIRO_OPERATOR_OVER: op = "source-over"; break;
    case CAIRO_OPERATOR_DEST_OVER: op = "destination-over"; break;
    case CAIRO_OPERATOR_IN: op = "source-in"; break;
    case CAIRO_OPERATOR_DEST_IN: op = "destination-in"; break;
    case CAIRO_OPERATOR_OUT: op = "source-out"; break;
    case CAIRO_OPERATOR_DEST_OUT: op = "destination-out"; break;
    case CAIRO_OPERATOR_ATOP: op = "source-atop"; break;
    case CAIRO_OPERATOR_DEST_ATOP: op = "destination-atop"; break;
    case CAIRO_OPERATOR_XOR: op = "xor"; break;
    case CAIRO_OPERATOR_ADD: op = "lighter"; break;
    // blend modes:
    // Note: "source-over" and "normal" are synonyms. Chrome and FF both report
    // "source-over" after setting gCO to "normal".
    // case CAIRO_OPERATOR_OVER: op = "normal";
    case CAIRO_OPERATOR_MULTIPLY: op = "multiply"; break;
    case CAIRO_OPERATOR_SCREEN: op = "screen"; break;
    case CAIRO_OPERATOR_OVERLAY: op = "overlay"; break;
    case CAIRO_OPERATOR_DARKEN: op = "darken"; break;
    case CAIRO_OPERATOR_LIGHTEN: op = "lighten"; break;
    case CAIRO_OPERATOR_COLOR_DODGE: op = "color-dodge"; break;
    case CAIRO_OPERATOR_COLOR_BURN: op = "color-burn"; break;
    case CAIRO_OPERATOR_HARD_LIGHT: op = "hard-light"; break;
    case CAIRO_OPERATOR_SOFT_LIGHT: op = "soft-light"; break;
    case CAIRO_OPERATOR_DIFFERENCE: op = "difference"; break;
    case CAIRO_OPERATOR_EXCLUSION: op = "exclusion"; break;
    case CAIRO_OPERATOR_HSL_HUE: op = "hue"; break;
    case CAIRO_OPERATOR_HSL_SATURATION: op = "saturation"; break;
    case CAIRO_OPERATOR_HSL_COLOR: op = "color"; break;
    case CAIRO_OPERATOR_HSL_LUMINOSITY: op = "luminosity"; break;
    // non-standard:
    case CAIRO_OPERATOR_SATURATE: op = "saturate"; break;
  }
  info.GetReturnValue().Set(Nan::New(op).ToLocalChecked());
}
/*
 * Set pattern quality.
 */
NAN_SETTER(Context2d::SetPatternQuality) {
  CHECK_RECEIVER(Context2d.SetPatternQuality);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  Nan::Utf8String quality(Nan::To<String>(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;
  }
}
/*
 * Get pattern quality.
 */
NAN_GETTER(Context2d::GetPatternQuality) {
  CHECK_RECEIVER(Context2d.GetPatternQuality);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  const char *quality;
  switch (context->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());
}
/*
 * Set ImageSmoothingEnabled value.
 */
NAN_SETTER(Context2d::SetImageSmoothingEnabled) {
  CHECK_RECEIVER(Context2d.SetImageSmoothingEnabled);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->state->imageSmoothingEnabled = Nan::To<bool>(value).FromMaybe(false);
}
/*
 * Get pattern quality.
 */
NAN_GETTER(Context2d::GetImageSmoothingEnabled) {
  CHECK_RECEIVER(Context2d.GetImageSmoothingEnabled);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  info.GetReturnValue().Set(Nan::New<Boolean>(context->state->imageSmoothingEnabled));
}
/*
 * Set global composite operation.
 */
NAN_SETTER(Context2d::SetGlobalCompositeOperation) {
  CHECK_RECEIVER(Context2d.SetGlobalCompositeOperation);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  Nan::Utf8String opStr(Nan::To<String>(value).ToLocalChecked()); // Unlike CSS colors, this *is* case-sensitive
  const std::map<std::string, cairo_operator_t> 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);
}
/*
 * Get shadow offset x.
 */
NAN_GETTER(Context2d::GetShadowOffsetX) {
  CHECK_RECEIVER(Context2d.GetShadowOffsetX);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(context->state->shadowOffsetX));
}
/*
 * Set shadow offset x.
 */
NAN_SETTER(Context2d::SetShadowOffsetX) {
  CHECK_RECEIVER(Context2d.SetShadowOffsetX);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->state->shadowOffsetX = Nan::To<double>(value).FromMaybe(0);
}
/*
 * Get shadow offset y.
 */
NAN_GETTER(Context2d::GetShadowOffsetY) {
  CHECK_RECEIVER(Context2d.GetShadowOffsetY);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(context->state->shadowOffsetY));
}
/*
 * Set shadow offset y.
 */
NAN_SETTER(Context2d::SetShadowOffsetY) {
  CHECK_RECEIVER(Context2d.SetShadowOffsetY);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->state->shadowOffsetY = Nan::To<double>(value).FromMaybe(0);
}
/*
 * Get shadow blur.
 */
NAN_GETTER(Context2d::GetShadowBlur) {
  CHECK_RECEIVER(Context2d.GetShadowBlur);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(context->state->shadowBlur));
}
/*
 * Set shadow blur.
 */
NAN_SETTER(Context2d::SetShadowBlur) {
  CHECK_RECEIVER(Context2d.SetShadowBlur);
  int n = Nan::To<double>(value).FromMaybe(0);
  if (n >= 0) {
    Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
    context->state->shadowBlur = n;
  }
}
/*
 * Get current antialiasing setting.
 */
NAN_GETTER(Context2d::GetAntiAlias) {
  CHECK_RECEIVER(Context2d.GetAntiAlias);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  const char *aa;
  switch (cairo_get_antialias(context->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());
}
/*
 * Set antialiasing.
 */
NAN_SETTER(Context2d::SetAntiAlias) {
  CHECK_RECEIVER(Context2d.SetAntiAlias);
  Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(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);
  }
  cairo_set_antialias(ctx, a);
}
/*
 * Get text drawing mode.
 */
NAN_GETTER(Context2d::GetTextDrawingMode) {
  CHECK_RECEIVER(Context2d.GetTextDrawingMode);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  const char *mode;
  if (context->state->textDrawingMode == TEXT_DRAW_PATHS) {
    mode = "path";
  } else if (context->state->textDrawingMode == TEXT_DRAW_GLYPHS) {
    mode = "glyph";
  } else {
    mode = "unknown";
  }
  info.GetReturnValue().Set(Nan::New(mode).ToLocalChecked());
}
/*
 * Set text drawing mode.
 */
NAN_SETTER(Context2d::SetTextDrawingMode) {
  CHECK_RECEIVER(Context2d.SetTextDrawingMode);
  Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(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;
  }
}
/*
 * Get filter.
 */
NAN_GETTER(Context2d::GetQuality) {
  CHECK_RECEIVER(Context2d.GetQuality);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  const char *filter;
  switch (cairo_pattern_get_filter(cairo_get_source(context->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());
}
/*
 * Set filter.
 */
NAN_SETTER(Context2d::SetQuality) {
  CHECK_RECEIVER(Context2d.SetQuality);
  Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(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;
  }
  cairo_pattern_set_filter(cairo_get_source(context->context()), filter);
}
/*
 * Helper for get current transform matrix
 */
Local<Object>
get_current_transform(Context2d *context) {
  Isolate *iso = Isolate::GetCurrent();
  Local<Float64Array> arr = Float64Array::New(ArrayBuffer::New(iso, 48), 0, 6);
  Nan::TypedArrayContents<double> dest(arr);
  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<Value> argv[argc] = { arr };
  return Nan::NewInstance(context->_DOMMatrix.Get(iso), argc, argv).ToLocalChecked();
}
/*
 * Helper for get/set transform.
 */
void parse_matrix_from_object(cairo_matrix_t &matrix, Local<Object> mat) {
  cairo_matrix_init(&matrix,
    Nan::To<double>(Nan::Get(mat, Nan::New("a").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
    Nan::To<double>(Nan::Get(mat, Nan::New("b").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
    Nan::To<double>(Nan::Get(mat, Nan::New("c").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
    Nan::To<double>(Nan::Get(mat, Nan::New("d").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
    Nan::To<double>(Nan::Get(mat, Nan::New("e").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
    Nan::To<double>(Nan::Get(mat, Nan::New("f").ToLocalChecked()).ToLocalChecked()).FromMaybe(0)
  );
}
/*
 * Get current transform.
 */
NAN_GETTER(Context2d::GetCurrentTransform) {
  CHECK_RECEIVER(Context2d.GetCurrentTransform);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  Local<Object> instance = get_current_transform(context);
  info.GetReturnValue().Set(instance);
}
/*
 * Set current transform.
 */
NAN_SETTER(Context2d::SetCurrentTransform) {
  CHECK_RECEIVER(Context2d.SetCurrentTransform);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  Local<Context> ctx = Nan::GetCurrentContext();
  Local<Object> mat = Nan::To<Object>(value).ToLocalChecked();
#if NODE_MAJOR_VERSION >= 8
  if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) {
    return Nan::ThrowTypeError("Expected DOMMatrix");
  }
#endif
  cairo_matrix_t matrix;
  parse_matrix_from_object(matrix, mat);
  cairo_transform(context->context(), &matrix);
}
/*
 * Get current fill style.
 */
NAN_GETTER(Context2d::GetFillStyle) {
  CHECK_RECEIVER(Context2d.GetFillStyle);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  Isolate *iso = Isolate::GetCurrent();
  Local<Value> style;
  if (context->_fillStyle.IsEmpty())
    style = context->_getFillColor();
  else
    style = context->_fillStyle.Get(iso);
  info.GetReturnValue().Set(style);
}
/*
 * Set current fill style.
 */
NAN_SETTER(Context2d::SetFillStyle) {
  CHECK_RECEIVER(Context2d.SetFillStyle);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  if (value->IsString()) {
    MaybeLocal<String> mstr = Nan::To<String>(value);
    if (mstr.IsEmpty()) return;
    Local<String> str = mstr.ToLocalChecked();
    context->_fillStyle.Reset();
    context->_setFillColor(str);
  } else if (value->IsObject()) {
    Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
    if (Nan::New(Gradient::constructor)->HasInstance(obj)) {
      context->_fillStyle.Reset(value);
      Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
      context->state->fillGradient = grad->pattern();
    } else if (Nan::New(Pattern::constructor)->HasInstance(obj)) {
      context->_fillStyle.Reset(value);
      Pattern *pattern = Nan::ObjectWrap::Unwrap<Pattern>(obj);
      context->state->fillPattern = pattern->pattern();
    }
  }
}
/*
 * Get current stroke style.
 */
NAN_GETTER(Context2d::GetStrokeStyle) {
  CHECK_RECEIVER(Context2d.GetStrokeStyle);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  Local<Value> style;
  if (context->_strokeStyle.IsEmpty())
    style = context->_getStrokeColor();
  else
    style = context->_strokeStyle.Get(Isolate::GetCurrent());
  info.GetReturnValue().Set(style);
}
/*
 * Set current stroke style.
 */
NAN_SETTER(Context2d::SetStrokeStyle) {
  CHECK_RECEIVER(Context2d.SetStrokeStyle);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  if (value->IsString()) {
    MaybeLocal<String> mstr = Nan::To<String>(value);
    if (mstr.IsEmpty()) return;
    Local<String> str = mstr.ToLocalChecked();
    context->_strokeStyle.Reset();
    context->_setStrokeColor(str);
  } else if (value->IsObject()) {
    Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
    if (Nan::New(Gradient::constructor)->HasInstance(obj)) {
      context->_strokeStyle.Reset(value);
      Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
      context->state->strokeGradient = grad->pattern();
    } else if (Nan::New(Pattern::constructor)->HasInstance(obj)) {
      context->_strokeStyle.Reset(value);
      Pattern *pattern = Nan::ObjectWrap::Unwrap<Pattern>(obj);
      context->state->strokePattern = pattern->pattern();
    }
  }
}
/*
 * Get miter limit.
 */
NAN_GETTER(Context2d::GetMiterLimit) {
  CHECK_RECEIVER(Context2d.GetMiterLimit);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(cairo_get_miter_limit(context->context())));
}
/*
 * Set miter limit.
 */
NAN_SETTER(Context2d::SetMiterLimit) {
  CHECK_RECEIVER(Context2d.SetMiterLimit);
  double n = Nan::To<double>(value).FromMaybe(0);
  if (n > 0) {
    Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
    cairo_set_miter_limit(context->context(), n);
  }
}
/*
 * Get line width.
 */
NAN_GETTER(Context2d::GetLineWidth) {
  CHECK_RECEIVER(Context2d.GetLineWidth);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(cairo_get_line_width(context->context())));
}
/*
 * Set line width.
 */
NAN_SETTER(Context2d::SetLineWidth) {
  CHECK_RECEIVER(Context2d.SetLineWidth);
  double n = Nan::To<double>(value).FromMaybe(0);
  if (n > 0 && n != std::numeric_limits<double>::infinity()) {
    Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
    cairo_set_line_width(context->context(), n);
  }
}
/*
 * Get line join.
 */
NAN_GETTER(Context2d::GetLineJoin) {
  CHECK_RECEIVER(Context2d.GetLineJoin);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  const char *join;
  switch (cairo_get_line_join(context->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());
}
/*
 * Set line join.
 */
NAN_SETTER(Context2d::SetLineJoin) {
  CHECK_RECEIVER(Context2d.SetLineJoin);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  Nan::Utf8String type(Nan::To<String>(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);
  }
}
/*
 * Get line cap.
 */
NAN_GETTER(Context2d::GetLineCap) {
  CHECK_RECEIVER(Context2d.GetLineCap);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  const char *cap;
  switch (cairo_get_line_cap(context->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());
}
/*
 * Set line cap.
 */
NAN_SETTER(Context2d::SetLineCap) {
  CHECK_RECEIVER(Context2d.SetLineCap);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  Nan::Utf8String type(Nan::To<String>(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);
  }
}
/*
 * 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<Context2d>(info.This());
    cairo_t *ctx = context->context();
    double x = Nan::To<double>(info[0]).FromMaybe(0)
         , y = Nan::To<double>(info[1]).FromMaybe(0);
    context->setFillRule(info[2]);
    info.GetReturnValue().Set(Nan::New<Boolean>(cairo_in_fill(ctx, x, y) || cairo_in_stroke(ctx, x, y)));
    return;
  }
  info.GetReturnValue().Set(Nan::False());
}
/*
 * Set shadow color.
 */
NAN_SETTER(Context2d::SetShadowColor) {
  CHECK_RECEIVER(Context2d.SetShadowColor);
  short ok;
  Nan::Utf8String str(Nan::To<String>(value).ToLocalChecked());
  uint32_t rgba = rgba_from_string(*str, &ok);
  if (ok) {
    Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
    context->state->shadow = rgba_create(rgba);
  }
}
/*
 * Get shadow color.
 */
NAN_GETTER(Context2d::GetShadowColor) {
  CHECK_RECEIVER(Context2d.GetShadowColor);
  char buf[64];
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  rgba_to_string(context->state->shadow, buf, sizeof(buf));
  info.GetReturnValue().Set(Nan::New<String>(buf).ToLocalChecked());
}
/*
 * Set fill color, used internally for fillStyle=
 */
void Context2d::_setFillColor(Local<Value> arg) {
  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);
}
/*
 * Get fill color.
 */
Local<Value> Context2d::_getFillColor() {
  char buf[64];
  rgba_to_string(state->fill, buf, sizeof(buf));
  return Nan::New<String>(buf).ToLocalChecked();
}
/*
 * Set stroke color, used internally for strokeStyle=
 */
void Context2d::_setStrokeColor(Local<Value> arg) {
  short ok;
  Nan::Utf8String str(arg);
  uint32_t rgba = rgba_from_string(*str, &ok);
  if (!ok) return;
  state->strokePattern = state->strokeGradient = NULL;
  state->stroke = rgba_create(rgba);
}
/*
 * Get stroke color.
 */
Local<Value> Context2d::_getStrokeColor() {
  char buf[64];
  rgba_to_string(state->stroke, buf, sizeof(buf));
  return Nan::New<String>(buf).ToLocalChecked();
}
NAN_METHOD(Context2d::CreatePattern) {
  Local<Value> image = info[0];
  Local<Value> repetition = info[1];
  if (!Nan::To<bool>(repetition).FromMaybe(false))
    repetition = Nan::New("repeat").ToLocalChecked();
  const int argc = 2;
  Local<Value> argv[argc] = { image, repetition };
  Local<Function> ctor = Nan::GetFunction(Nan::New(Pattern::constructor)).ToLocalChecked();
  Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
  info.GetReturnValue().Set(instance);
}
NAN_METHOD(Context2d::CreateLinearGradient) {
  const int argc = 4;
  Local<Value> argv[argc] = { info[0], info[1], info[2], info[3] };
  Local<Function> ctor = Nan::GetFunction(Nan::New(Gradient::constructor)).ToLocalChecked();
  Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
  info.GetReturnValue().Set(instance);
}
NAN_METHOD(Context2d::CreateRadialGradient) {
  const int argc = 6;
  Local<Value> argv[argc] = { info[0], info[1], info[2], info[3], info[4], info[5] };
  Local<Function> ctor = Nan::GetFunction(Nan::New(Gradient::constructor)).ToLocalChecked();
  Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();
  info.GetReturnValue().Set(instance);
}
/*
 * Bezier curve.
 */
NAN_METHOD(Context2d::BezierCurveTo) {
  double args[6];
  if(!checkArgs(info, args, 6))
    return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_curve_to(context->context()
    , args[0]
    , args[1]
    , args[2]
    , args[3]
    , args[4]
    , args[5]);
}
/*
 * Quadratic curve approximation from libsvg-cairo.
 */
NAN_METHOD(Context2d::QuadraticCurveTo) {
  double args[4];
  if(!checkArgs(info, args, 4))
    return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  double x, y
    , x1 = args[0]
    , y1 = args[1]
    , x2 = args[2]
    , y2 = args[3];
  cairo_get_current_point(ctx, &x, &y);
  if (0 == x && 0 == y) {
    x = x1;
    y = y1;
  }
  cairo_curve_to(ctx
    , x  + 2.0 / 3.0 * (x1 - x),  y  + 2.0 / 3.0 * (y1 - y)
    , x2 + 2.0 / 3.0 * (x1 - x2), y2 + 2.0 / 3.0 * (y1 - y2)
    , x2
    , y2);
}
/*
 * Save state.
 */
NAN_METHOD(Context2d::Save) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->save();
}
/*
 * Restore state.
 */
NAN_METHOD(Context2d::Restore) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->restore();
}
/*
 * Creates a new subpath.
 */
NAN_METHOD(Context2d::BeginPath) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_new_path(context->context());
}
/*
 * Marks the subpath as closed.
 */
NAN_METHOD(Context2d::ClosePath) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_close_path(context->context());
}
/*
 * Rotate transformation.
 */
NAN_METHOD(Context2d::Rotate) {
  double args[1];
  if(!checkArgs(info, args, 1))
    return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_rotate(context->context(), args[0]);
}
/*
 * Modify the CTM.
 */
NAN_METHOD(Context2d::Transform) {
  double args[6];
  if(!checkArgs(info, args, 6))
    return;
  cairo_matrix_t matrix;
  cairo_matrix_init(&matrix
    , args[0]
    , args[1]
    , args[2]
    , args[3]
    , args[4]
    , args[5]);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_transform(context->context(), &matrix);
}
/*
 * Get the CTM
 */
NAN_METHOD(Context2d::GetTransform) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  Local<Object> instance = get_current_transform(context);
  info.GetReturnValue().Set(instance);
}
/*
 * Reset the CTM, used internally by setTransform().
 */
NAN_METHOD(Context2d::ResetTransform) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_identity_matrix(context->context());
}
/*
 * Reset transform matrix to identity, then apply the given args.
 */
NAN_METHOD(Context2d::SetTransform) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  if (info.Length() == 1) {
    Local<Object> mat = Nan::To<Object>(info[0]).ToLocalChecked();
    #if NODE_MAJOR_VERSION >= 8
      Local<Context> ctx = Nan::GetCurrentContext();
      if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) {
        return Nan::ThrowTypeError("Expected DOMMatrix");
      }
    #endif
    cairo_matrix_t matrix;
    parse_matrix_from_object(matrix, mat);
    cairo_set_matrix(context->context(), &matrix);
  } else {
    cairo_identity_matrix(context->context());
    Context2d::Transform(info);
  }
}
/*
 * Translate transformation.
 */
NAN_METHOD(Context2d::Translate) {
  double args[2];
  if(!checkArgs(info, args, 2))
    return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_translate(context->context(), args[0], args[1]);
}
/*
 * Scale transformation.
 */
NAN_METHOD(Context2d::Scale) {
  double args[2];
  if(!checkArgs(info, args, 2))
    return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_scale(context->context(), args[0], args[1]);
}
/*
 * Use path as clipping region.
 */
NAN_METHOD(Context2d::Clip) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->setFillRule(info[0]);
  cairo_t *ctx = context->context();
  cairo_clip_preserve(ctx);
}
/*
 * Fill the path.
 */
NAN_METHOD(Context2d::Fill) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->setFillRule(info[0]);
  context->fill(true);
}
/*
 * Stroke the path.
 */
NAN_METHOD(Context2d::Stroke) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->stroke(true);
}
/*
 * Helper for fillText/strokeText
 */
double
get_text_scale(PangoLayout *layout, double maxWidth) {
  PangoRectangle logical_rect;
  pango_layout_get_pixel_extents(layout, NULL, &logical_rect);
  if (logical_rect.width > maxWidth) {
    return maxWidth / logical_rect.width;
  } else {
    return 1.0;
  }
}
void
paintText(const Nan::FunctionCallbackInfo<Value> &info, bool stroke) {
  int argsNum = info.Length() >= 4 ? 3 : 2;
  if (argsNum == 3 && info[3]->IsUndefined())
    argsNum = 2;
  double args[3];
  if(!checkArgs(info, args, argsNum, 1))
    return;
  Nan::Utf8String str(Nan::To<String>(info[0]).ToLocalChecked());
  double x = args[0];
  double y = args[1];
  double scaled_by = 1;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  PangoLayout *layout = context->layout();
  pango_layout_set_text(layout, *str, -1);
  pango_cairo_update_layout(context->context(), layout);
  if (argsNum == 3) {
    scaled_by = get_text_scale(layout, args[2]);
    cairo_save(context->context());
    cairo_scale(context->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(); }
  }
  context->restorePath();
  if (argsNum == 3) {
    cairo_restore(context->context());
  }
}
/*
 * Fill text at (x, y).
 */
NAN_METHOD(Context2d::FillText) {
  paintText(info, false);
}
/*
 * Stroke text at (x ,y).
 */
NAN_METHOD(Context2d::StrokeText) {
  paintText(info, true);
}
/*
 * Gets the baseline adjustment in device pixels
 */
inline double getBaselineAdjustment(PangoLayout* layout, short baseline) {
  PangoRectangle logical_rect;
  pango_layout_line_get_extents(pango_layout_get_line(layout, 0), NULL, &logical_rect);
  double scale = 1.0 / PANGO_SCALE;
  double ascent = scale * pango_layout_get_baseline(layout);
  double descent = scale * logical_rect.height - ascent;
  switch (baseline) {
  case TEXT_BASELINE_ALPHABETIC:
    return ascent;
  case TEXT_BASELINE_MIDDLE:
    return (ascent + descent) / 2.0;
  case TEXT_BASELINE_BOTTOM:
    return ascent + descent;
  default:
    return 0;
  }
}
/*
 * Set text path for the string in the layout at (x, y).
 * This function is called by paintText and won't behave correctly
 * if is not called from there.
 * it needs pango_layout_set_text and pango_cairo_update_layout to be called before
 */
void
Context2d::setTextPath(double x, double y) {
  PangoRectangle logical_rect;
  switch (state->textAlignment) {
    case TEXT_ALIGNMENT_CENTER:
      pango_layout_get_pixel_extents(_layout, NULL, &logical_rect);
      x -= logical_rect.width / 2;
      break;
    case TEXT_ALIGNMENT_END:
    case TEXT_ALIGNMENT_RIGHT:
      pango_layout_get_pixel_extents(_layout, NULL, &logical_rect);
      x -= logical_rect.width;
      break;
  }
  y -= getBaselineAdjustment(_layout, state->textBaseline);
  cairo_move_to(_context, x, y);
  if (state->textDrawingMode == TEXT_DRAW_PATHS) {
    pango_cairo_layout_path(_context, _layout);
  } else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) {
    pango_cairo_show_layout(_context, _layout);
  }
}
/*
 * Adds a point to the current subpath.
 */
NAN_METHOD(Context2d::LineTo) {
  double args[2];
  if(!checkArgs(info, args, 2))
    return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_line_to(context->context(), args[0], args[1]);
}
/*
 * Creates a new subpath at the given point.
 */
NAN_METHOD(Context2d::MoveTo) {
  double args[2];
  if(!checkArgs(info, args, 2))
    return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_move_to(context->context(), args[0], args[1]);
}
/*
 * Get font.
 */
NAN_GETTER(Context2d::GetFont) {
  CHECK_RECEIVER(Context2d.GetFont);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  info.GetReturnValue().Set(Nan::New(context->state->font).ToLocalChecked());
}
/*
 * Set font:
 *   - weight
 *   - style
 *   - size
 *   - unit
 *   - family
 */
NAN_SETTER(Context2d::SetFont) {
  CHECK_RECEIVER(Context2d.SetFont);
  if (!value->IsString()) return;
  Isolate *iso = Isolate::GetCurrent();
  Local<Context> ctx = Nan::GetCurrentContext();
  Local<String> str = Nan::To<String>(value).ToLocalChecked();
  if (!str->Length()) return;
  const int argc = 1;
  Local<Value> argv[argc] = { value };
  Local<Value> mparsed = Nan::Call(_parseFont.Get(iso), ctx->Global(), argc, argv).ToLocalChecked();
  // parseFont returns undefined for invalid CSS font strings
  if (mparsed->IsUndefined()) return;
  Local<Object> font = Nan::To<Object>(mparsed).ToLocalChecked();
  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<double>(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());
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  PangoFontDescription *desc = pango_font_description_copy(context->state->fontDescription);
  pango_font_description_free(context->state->fontDescription);
  pango_font_description_set_style(desc, Canvas::GetStyleFromCSSString(*style));
  pango_font_description_set_weight(desc, Canvas::GetWeightFromCSSString(*weight));
  if (strlen(*family) > 0) {
    // See #1643 - Pango understands "sans" whereas CSS uses "sans-serif"
    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);
    }
  }
  PangoFontDescription *sys_desc = Canvas::ResolveFontDescription(desc);
  pango_font_description_free(desc);
  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);
  context->state->font = *Nan::Utf8String(value);
}
/*
 * Get text baseline.
 */
NAN_GETTER(Context2d::GetTextBaseline) {
  CHECK_RECEIVER(Context2d.GetTextBaseline);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  const char* baseline;
  switch (context->state->textBaseline) {
    default:
    case TEXT_BASELINE_ALPHABETIC: baseline = "alphabetic"; break;
    case TEXT_BASELINE_TOP: baseline = "top"; break;
    case TEXT_BASELINE_BOTTOM: baseline = "bottom"; break;
    case TEXT_BASELINE_MIDDLE: baseline = "middle"; break;
    case TEXT_BASELINE_IDEOGRAPHIC: baseline = "ideographic"; break;
    case TEXT_BASELINE_HANGING: baseline = "hanging"; break;
  }
  info.GetReturnValue().Set(Nan::New(baseline).ToLocalChecked());
}
/*
 * Set text baseline.
 */
NAN_SETTER(Context2d::SetTextBaseline) {
  CHECK_RECEIVER(Context2d.SetTextBaseline);
  if (!value->IsString()) return;
  Nan::Utf8String opStr(Nan::To<String>(value).ToLocalChecked());
  const std::map<std::string, text_baseline_t> modes = {
    {"alphabetic", TEXT_BASELINE_ALPHABETIC},
    {"top", TEXT_BASELINE_TOP},
    {"bottom", TEXT_BASELINE_BOTTOM},
    {"middle", TEXT_BASELINE_MIDDLE},
    {"ideographic", TEXT_BASELINE_IDEOGRAPHIC},
    {"hanging", TEXT_BASELINE_HANGING}
  };
  auto op = modes.find(*opStr);
  if (op == modes.end()) return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->state->textBaseline = op->second;
}
/*
 * Get text align.
 */
NAN_GETTER(Context2d::GetTextAlign) {
  CHECK_RECEIVER(Context2d.GetTextAlign);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  const char* align;
  switch (context->state->textAlignment) {
    default:
    // TODO the default is supposed to be "start"
    case TEXT_ALIGNMENT_LEFT: align = "left"; break;
    case TEXT_ALIGNMENT_START: align = "start"; break;
    case TEXT_ALIGNMENT_CENTER: align = "center"; break;
    case TEXT_ALIGNMENT_RIGHT: align = "right"; break;
    case TEXT_ALIGNMENT_END: align = "end"; break;
  }
  info.GetReturnValue().Set(Nan::New(align).ToLocalChecked());
}
/*
 * Set text align.
 */
NAN_SETTER(Context2d::SetTextAlign) {
  CHECK_RECEIVER(Context2d.SetTextAlign);
  if (!value->IsString()) return;
  Nan::Utf8String opStr(Nan::To<String>(value).ToLocalChecked());
  const std::map<std::string, text_align_t> modes = {
    {"center", TEXT_ALIGNMENT_CENTER},
    {"left", TEXT_ALIGNMENT_LEFT},
    {"start", TEXT_ALIGNMENT_START},
    {"right", TEXT_ALIGNMENT_RIGHT},
    {"end", TEXT_ALIGNMENT_END}
  };
  auto op = modes.find(*opStr);
  if (op == modes.end()) return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  context->state->textAlignment = op->second;
}
/*
 * Return the given text extents.
 * TODO: Support for:
 * hangingBaseline, ideographicBaseline,
 * fontBoundingBoxAscent, fontBoundingBoxDescent
 */
NAN_METHOD(Context2d::MeasureText) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  Nan::Utf8String str(Nan::To<String>(info[0]).ToLocalChecked());
  Local<Object> obj = Nan::New<Object>();
  PangoRectangle _ink_rect, _logical_rect;
  float_rectangle ink_rect, logical_rect;
  PangoFontMetrics *metrics;
  PangoLayout *layout = context->layout();
  pango_layout_set_text(layout, *str, -1);
  pango_cairo_update_layout(ctx, layout);
  // Normally you could use pango_layout_get_pixel_extents and be done, or use
  // pango_extents_to_pixels, but both of those round the pixels, so we have to
  // divide by PANGO_SCALE manually
  pango_layout_get_extents(layout, &_ink_rect, &_logical_rect);
  float inverse_pango_scale = 1. / PANGO_SCALE;
  logical_rect.x = _logical_rect.x * inverse_pango_scale;
  logical_rect.y = _logical_rect.y * inverse_pango_scale;
  logical_rect.width = _logical_rect.width * inverse_pango_scale;
  logical_rect.height = _logical_rect.height * inverse_pango_scale;
  ink_rect.x = _ink_rect.x * inverse_pango_scale;
  ink_rect.y = _ink_rect.y * inverse_pango_scale;
  ink_rect.width = _ink_rect.width * inverse_pango_scale;
  ink_rect.height = _ink_rect.height * inverse_pango_scale;
  metrics = PANGO_LAYOUT_GET_METRICS(layout);
  double x_offset;
  switch (context->state->textAlignment) {
    case TEXT_ALIGNMENT_CENTER:
      x_offset = logical_rect.width / 2.;
      break;
    case TEXT_ALIGNMENT_END:
    case TEXT_ALIGNMENT_RIGHT:
      x_offset = logical_rect.width;
      break;
    case TEXT_ALIGNMENT_START:
    case TEXT_ALIGNMENT_LEFT:
    default:
      x_offset = 0.0;
  }
  cairo_matrix_t matrix;
  cairo_get_matrix(ctx, &matrix);
  double y_offset = getBaselineAdjustment(layout, context->state->textBaseline);
  Nan::Set(obj,
           Nan::New<String>("width").ToLocalChecked(),
           Nan::New<Number>(logical_rect.width)).Check();
  Nan::Set(obj,
           Nan::New<String>("actualBoundingBoxLeft").ToLocalChecked(),
           Nan::New<Number>(PANGO_LBEARING(ink_rect) + x_offset)).Check();
  Nan::Set(obj,
           Nan::New<String>("actualBoundingBoxRight").ToLocalChecked(),
           Nan::New<Number>(PANGO_RBEARING(ink_rect) - x_offset)).Check();
  Nan::Set(obj,
           Nan::New<String>("actualBoundingBoxAscent").ToLocalChecked(),
           Nan::New<Number>(y_offset + PANGO_ASCENT(ink_rect))).Check();
  Nan::Set(obj,
           Nan::New<String>("actualBoundingBoxDescent").ToLocalChecked(),
           Nan::New<Number>(PANGO_DESCENT(ink_rect) - y_offset)).Check();
  Nan::Set(obj,
           Nan::New<String>("emHeightAscent").ToLocalChecked(),
           Nan::New<Number>(-(PANGO_ASCENT(logical_rect) - y_offset))).Check();
  Nan::Set(obj,
           Nan::New<String>("emHeightDescent").ToLocalChecked(),
           Nan::New<Number>(PANGO_DESCENT(logical_rect) - y_offset)).Check();
  Nan::Set(obj,
           Nan::New<String>("alphabeticBaseline").ToLocalChecked(),
           Nan::New<Number>(-(pango_font_metrics_get_ascent(metrics) * inverse_pango_scale - y_offset))).Check();
  pango_font_metrics_unref(metrics);
  info.GetReturnValue().Set(obj);
}
/*
 * Set line dash
 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
 */
NAN_METHOD(Context2d::SetLineDash) {
  if (!info[0]->IsArray()) return;
  Local<Array> dash = Local<Array>::Cast(info[0]);
  uint32_t dashes = dash->Length() & 1 ? dash->Length() * 2 : dash->Length();
  uint32_t zero_dashes = 0;
  std::vector<double> a(dashes);
  for (uint32_t i=0; i<dashes; i++) {
    Local<Value> d = Nan::Get(dash, i % dash->Length()).ToLocalChecked();
    if (!d->IsNumber()) return;
    a[i] = Nan::To<double>(d).FromMaybe(0);
    if (a[i] == 0) zero_dashes++;
    if (a[i] < 0 || !std::isfinite(a[i])) return;
  }
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  double offset;
  cairo_get_dash(ctx, NULL, &offset);
  if (zero_dashes == dashes) {
    std::vector<double> b(0);
    cairo_set_dash(ctx, b.data(), 0, offset);
  } else {
    cairo_set_dash(ctx, a.data(), dashes, offset);
  }
}
/*
 * Get line dash
 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
 */
NAN_METHOD(Context2d::GetLineDash) {
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  int dashes = cairo_get_dash_count(ctx);
  std::vector<double> a(dashes);
  cairo_get_dash(ctx, a.data(), NULL);
  Local<Array> dash = Nan::New<Array>(dashes);
  for (int i=0; i<dashes; i++) {
    Nan::Set(dash, Nan::New<Number>(i), Nan::New<Number>(a[i])).Check();
  }
  info.GetReturnValue().Set(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<double>(value).FromMaybe(0);
  if (!std::isfinite(offset)) return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  int dashes = cairo_get_dash_count(ctx);
  std::vector<double> a(dashes);
  cairo_get_dash(ctx, a.data(), NULL);
  cairo_set_dash(ctx, a.data(), dashes, offset);
}
/*
 * 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<Context2d>(info.This());
  cairo_t *ctx = context->context();
  double offset;
  cairo_get_dash(ctx, NULL, &offset);
  info.GetReturnValue().Set(Nan::New<Number>(offset));
}
/*
 * Fill the rectangle defined by x, y, width and height.
 */
NAN_METHOD(Context2d::FillRect) {
  RECT_ARGS;
  if (0 == width || 0 == height) return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  context->savePath();
  cairo_rectangle(ctx, x, y, width, height);
  context->fill();
  context->restorePath();
}
/*
 * Stroke the rectangle defined by x, y, width and height.
 */
NAN_METHOD(Context2d::StrokeRect) {
  RECT_ARGS;
  if (0 == width && 0 == height) return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  context->savePath();
  cairo_rectangle(ctx, x, y, width, height);
  context->stroke();
  context->restorePath();
}
/*
 * Clears all pixels defined by x, y, width and height.
 */
NAN_METHOD(Context2d::ClearRect) {
  RECT_ARGS;
  if (0 == width || 0 == height) return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  cairo_save(ctx);
  context->savePath();
  cairo_rectangle(ctx, x, y, width, height);
  cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR);
  cairo_fill(ctx);
  context->restorePath();
  cairo_restore(ctx);
}
/*
 * Adds a rectangle subpath.
 */
NAN_METHOD(Context2d::Rect) {
  RECT_ARGS;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  if (width == 0) {
    cairo_move_to(ctx, x, y);
    cairo_line_to(ctx, x, y + height);
  } else if (height == 0) {
    cairo_move_to(ctx, x, y);
    cairo_line_to(ctx, x + width, y);
  } else {
    cairo_rectangle(ctx, x, y, width, height);
  }
}
// Draws an arc with two potentially different radii.
inline static
void elli_arc(cairo_t* ctx, double xc, double yc, double rx, double ry, double a1, double a2, bool clockwise=true) {
  if (rx == 0. || ry == 0.) {
    cairo_line_to(ctx, xc + rx, yc + ry);
  } else {
    cairo_save(ctx);
    cairo_translate(ctx, xc, yc);
    cairo_scale(ctx, rx, ry);
    if (clockwise)
      cairo_arc(ctx, 0., 0., 1., a1, a2);
    else
      cairo_arc_negative(ctx, 0., 0., 1., a2, a1);
    cairo_restore(ctx);
  }
}
inline static
bool getRadius(Point<double>& p, const Local<Value>& v) {
  if (v->IsObject()) { // 5.1 DOMPointInit
    auto rx = Nan::Get(v.As<Object>(), Nan::New("x").ToLocalChecked()).ToLocalChecked();
    auto ry = Nan::Get(v.As<Object>(), Nan::New("y").ToLocalChecked()).ToLocalChecked();
    if (rx->IsNumber() && ry->IsNumber()) {
      auto rxv = Nan::To<double>(rx).FromJust();
      auto ryv = Nan::To<double>(ry).FromJust();
      if (!std::isfinite(rxv) || !std::isfinite(ryv))
        return true;
      if (rxv < 0 || ryv < 0) {
        Nan::ThrowRangeError("radii must be positive.");
        return true;
      }
      p.x = rxv;
      p.y = ryv;
      return false;
    }
  } else if (v->IsNumber()) { // 5.2 unrestricted double
    auto rv = Nan::To<double>(v).FromJust();
    if (!std::isfinite(rv))
      return true;
    if (rv < 0) {
      Nan::ThrowRangeError("radii must be positive.");
      return true;
    }
    p.x = p.y = rv;
    return false;
  }
  return true;
}
/**
 * https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
 * x, y, w, h, [radius|[radii]]
 */
NAN_METHOD(Context2d::RoundRect) {
  RECT_ARGS;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  // 4. Let normalizedRadii be an empty list
  Point<double> normalizedRadii[4];
  size_t nRadii = 4;
  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<v8::Array>();
    nRadii = radiiList->Length();
    if (!(nRadii >= 1 && nRadii <= 4)) {
      Nan::ThrowRangeError("radii must be a list of one, two, three or four radii.");
      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))
        return;
    }
  } else {
    // 2. If radii is a double, then set radii to <<radii>>
    if (getRadius(normalizedRadii[0], info[4]))
      return;
    for (size_t i = 1; i < 4; i++) {
      normalizedRadii[i].x = normalizedRadii[0].x;
      normalizedRadii[i].y = normalizedRadii[0].y;
    }
  }
  Point<double> upperLeft, upperRight, lowerRight, lowerLeft;
  if (nRadii == 4) {
    upperLeft = normalizedRadii[0];
    upperRight = normalizedRadii[1];
    lowerRight = normalizedRadii[2];
    lowerLeft = normalizedRadii[3];
  } else if (nRadii == 3) {
    upperLeft = normalizedRadii[0];
    upperRight = normalizedRadii[1];
    lowerLeft = normalizedRadii[1];
    lowerRight = normalizedRadii[2];
  } else if (nRadii == 2) {
    upperLeft = normalizedRadii[0];
    lowerRight = normalizedRadii[0];
    upperRight = normalizedRadii[1];
    lowerLeft = normalizedRadii[1];
  } else {
    upperLeft = normalizedRadii[0];
    upperRight = normalizedRadii[0];
    lowerRight = normalizedRadii[0];
    lowerLeft = normalizedRadii[0];
  }
  bool clockwise = true;
  if (width < 0) {
    clockwise = false;
    x += width;
    width = -width;
    std::swap(upperLeft, upperRight);
    std::swap(lowerLeft, lowerRight);
  }
  if (height < 0) {
    clockwise = !clockwise;
    y += height;
    height = -height;
    std::swap(upperLeft, lowerLeft);
    std::swap(upperRight, lowerRight);
  }
  // 11. Corner curves must not overlap. Scale radii to prevent this.
  {
    auto top = upperLeft.x + upperRight.x;
    auto right = upperRight.y + lowerRight.y;
    auto bottom = lowerRight.x + lowerLeft.x;
    auto left = upperLeft.y + lowerLeft.y;
    auto scale = std::min({ width / top, height / right, width / bottom, height / left });
    if (scale < 1.) {
      upperLeft.x *= scale;
      upperLeft.y *= scale;
      upperRight.x *= scale;
      upperRight.x *= scale;
      lowerLeft.y *= scale;
      lowerLeft.y *= scale;
      lowerRight.y *= scale;
      lowerRight.y *= scale;
    }
  }
  // 12. Draw
  cairo_move_to(ctx, x + upperLeft.x, y);
  if (clockwise) {
    cairo_line_to(ctx, x + width - upperRight.x, y);
    elli_arc(ctx, x + width - upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 3. * M_PI / 2., 0.);
    cairo_line_to(ctx, x + width, y + height - lowerRight.y);
    elli_arc(ctx, x + width - lowerRight.x, y + height - lowerRight.y, lowerRight.x, lowerRight.y, 0, M_PI / 2.);
    cairo_line_to(ctx, x + lowerLeft.x, y + height);
    elli_arc(ctx, x + lowerLeft.x, y + height - lowerLeft.y, lowerLeft.x, lowerLeft.y, M_PI / 2., M_PI);
    cairo_line_to(ctx, x, y + upperLeft.y);
    elli_arc(ctx, x + upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, M_PI, 3. * M_PI / 2.);
  } else {
    elli_arc(ctx, x + upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, M_PI, 3. * M_PI / 2., false);
    cairo_line_to(ctx, x, y + upperLeft.y);
    elli_arc(ctx, x + lowerLeft.x, y + height - lowerLeft.y, lowerLeft.x, lowerLeft.y, M_PI / 2., M_PI, false);
    cairo_line_to(ctx, x + lowerLeft.x, y + height);
    elli_arc(ctx, x + width - lowerRight.x, y + height - lowerRight.y, lowerRight.x, lowerRight.y, 0, M_PI / 2., false);
    cairo_line_to(ctx, x + width, y + height - lowerRight.y);
    elli_arc(ctx, x + width - upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 3. * M_PI / 2., 0., false);
    cairo_line_to(ctx, x + width - upperRight.x, y);
  }
  cairo_close_path(ctx);
}
// Adapted from https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/modules/canvas2d/CanvasPathMethods.cpp
static void canonicalizeAngle(double& startAngle, double& endAngle) {
  // Make 0 <= startAngle < 2*PI
  double newStartAngle = std::fmod(startAngle, twoPi);
  if (newStartAngle < 0) {
      newStartAngle += twoPi;
      // Check for possible catastrophic cancellation in cases where
      // newStartAngle was a tiny negative number (c.f. crbug.com/503422)
      if (newStartAngle >= twoPi)
          newStartAngle -= twoPi;
  }
  double delta = newStartAngle - startAngle;
  startAngle = newStartAngle;
  endAngle = endAngle + delta;
}
// Adapted from https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/modules/canvas2d/CanvasPathMethods.cpp
static double adjustEndAngle(double startAngle, double endAngle, bool counterclockwise) {
  double newEndAngle = endAngle;
  /* http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-arc
  * If the counterclockwise argument is false and endAngle-startAngle is equal to or greater than 2pi, or,
  * if the counterclockwise argument is true and startAngle-endAngle is equal to or greater than 2pi,
  * then the arc is the whole circumference of this ellipse, and the point at startAngle along this circle's circumference,
  * measured in radians clockwise from the ellipse's semi-major axis, acts as both the start point and the end point.
  */
  if (!counterclockwise && endAngle - startAngle >= twoPi)
    newEndAngle = startAngle + twoPi;
  else if (counterclockwise && startAngle - endAngle >= twoPi)
    newEndAngle = startAngle - twoPi;
  /*
  * Otherwise, the arc is the path along the circumference of this ellipse from the start point to the end point,
  * going anti-clockwise if the counterclockwise argument is true, and clockwise otherwise.
  * Since the points are on the ellipse, as opposed to being simply angles from zero,
  * the arc can never cover an angle greater than 2pi radians.
  */
  /* NOTE: When startAngle = 0, endAngle = 2Pi and counterclockwise = true, the spec does not indicate clearly.
  * We draw the entire circle, because some web sites use arc(x, y, r, 0, 2*Math.PI, true) to draw circle.
  * We preserve backward-compatibility.
  */
  else if (!counterclockwise && startAngle > endAngle)
    newEndAngle = startAngle + (twoPi - std::fmod(startAngle - endAngle, twoPi));
  else if (counterclockwise && startAngle < endAngle)
    newEndAngle = startAngle - (twoPi - std::fmod(endAngle - startAngle, twoPi));
  return newEndAngle;
}
/*
 * Adds an arc at x, y with the given radii and start/end angles.
 */
NAN_METHOD(Context2d::Arc) {
  double args[5];
  if(!checkArgs(info, args, 5))
    return;
  auto x = args[0];
  auto y = args[1];
  auto radius = args[2];
  auto startAngle = args[3];
  auto endAngle = args[4];
  if (radius < 0) {
    Nan::ThrowRangeError("The radius provided is negative.");
    return;
  }
  bool counterclockwise = Nan::To<bool>(info[5]).FromMaybe(false);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  canonicalizeAngle(startAngle, endAngle);
  endAngle = adjustEndAngle(startAngle, endAngle, counterclockwise);
  if (counterclockwise) {
    cairo_arc_negative(ctx, x, y, radius, startAngle, endAngle);
  } else {
    cairo_arc(ctx, x, y, radius, startAngle, endAngle);
  }
}
/*
 * Adds an arcTo point (x0,y0) to (x1,y1) with the given radius.
 *
 * Implementation influenced by WebKit.
 */
NAN_METHOD(Context2d::ArcTo) {
  double args[5];
  if(!checkArgs(info, args, 5))
    return;
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  // Current path point
  double x, y;
  cairo_get_current_point(ctx, &x, &y);
  Point<float> p0(x, y);
  // Point (x0,y0)
  Point<float> p1(args[0], args[1]);
  // Point (x1,y1)
  Point<float> p2(args[2], args[3]);
  float radius = args[4];
  if ((p1.x == p0.x && p1.y == p0.y)
    || (p1.x == p2.x && p1.y == p2.y)
    || radius == 0.f) {
    cairo_line_to(ctx, p1.x, p1.y);
    return;
  }
  Point<float> p1p0((p0.x - p1.x),(p0.y - p1.y));
  Point<float> p1p2((p2.x - p1.x),(p2.y - p1.y));
  float p1p0_length = sqrtf(p1p0.x * p1p0.x + p1p0.y * p1p0.y);
  float p1p2_length = sqrtf(p1p2.x * p1p2.x + p1p2.y * p1p2.y);
  double cos_phi = (p1p0.x * p1p2.x + p1p0.y * p1p2.y) / (p1p0_length * p1p2_length);
  // all points on a line logic
  if (-1 == cos_phi) {
    cairo_line_to(ctx, p1.x, p1.y);
    return;
  }
  if (1 == cos_phi) {
    // add infinite far away point
    unsigned int max_length = 65535;
    double factor_max = max_length / p1p0_length;
    Point<float> ep((p0.x + factor_max * p1p0.x), (p0.y + factor_max * p1p0.y));
    cairo_line_to(ctx, ep.x, ep.y);
    return;
  }
  float tangent = radius / tan(acos(cos_phi) / 2);
  float factor_p1p0 = tangent / p1p0_length;
  Point<float> t_p1p0((p1.x + factor_p1p0 * p1p0.x), (p1.y + factor_p1p0 * p1p0.y));
  Point<float> orth_p1p0(p1p0.y, -p1p0.x);
  float orth_p1p0_length = sqrt(orth_p1p0.x * orth_p1p0.x + orth_p1p0.y * orth_p1p0.y);
  float factor_ra = radius / orth_p1p0_length;
  double cos_alpha = (orth_p1p0.x * p1p2.x + orth_p1p0.y * p1p2.y) / (orth_p1p0_length * p1p2_length);
  if (cos_alpha < 0.f)
      orth_p1p0 = Point<float>(-orth_p1p0.x, -orth_p1p0.y);
  Point<float> p((t_p1p0.x + factor_ra * orth_p1p0.x), (t_p1p0.y + factor_ra * orth_p1p0.y));
  orth_p1p0 = Point<float>(-orth_p1p0.x, -orth_p1p0.y);
  float sa = acos(orth_p1p0.x / orth_p1p0_length);
  if (orth_p1p0.y < 0.f)
      sa = 2 * M_PI - sa;
  bool anticlockwise = false;
  float factor_p1p2 = tangent / p1p2_length;
  Point<float> t_p1p2((p1.x + factor_p1p2 * p1p2.x), (p1.y + factor_p1p2 * p1p2.y));
  Point<float> orth_p1p2((t_p1p2.x - p.x),(t_p1p2.y - p.y));
  float orth_p1p2_length = sqrtf(orth_p1p2.x * orth_p1p2.x + orth_p1p2.y * orth_p1p2.y);
  float ea = acos(orth_p1p2.x / orth_p1p2_length);
  if (orth_p1p2.y < 0) ea = 2 * M_PI - ea;
  if ((sa > ea) && ((sa - ea) < M_PI)) anticlockwise = true;
  if ((sa < ea) && ((ea - sa) > M_PI)) anticlockwise = true;
  cairo_line_to(ctx, t_p1p0.x, t_p1p0.y);
  if (anticlockwise && M_PI * 2 != radius) {
    cairo_arc_negative(ctx
      , p.x
      , p.y
      , radius
      , sa
      , ea);
  } else {
    cairo_arc(ctx
      , p.x
      , p.y
      , radius
      , sa
      , ea);
  }
}
/*
 * Adds an ellipse to the path which is centered at (x, y) position with the
 * radii radiusX and radiusY starting at startAngle and ending at endAngle
 * going in the given direction by anticlockwise (defaulting to clockwise).
 */
NAN_METHOD(Context2d::Ellipse) {
  double args[7];
  if(!checkArgs(info, args, 7))
    return;
  double radiusX = args[2];
  double radiusY = args[3];
  if (radiusX == 0 || radiusY == 0) return;
  double x = args[0];
  double y = args[1];
  double rotation = args[4];
  double startAngle = args[5];
  double endAngle = args[6];
  bool anticlockwise = Nan::To<bool>(info[7]).FromMaybe(false);
  Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
  cairo_t *ctx = context->context();
  // See https://www.cairographics.org/cookbook/ellipses/
  double xRatio = radiusX / radiusY;
  cairo_matrix_t save_matrix;
  cairo_get_matrix(ctx, &save_matrix);
  cairo_translate(ctx, x, y);
  cairo_rotate(ctx, rotation);
  cairo_scale(ctx, xRatio, 1.0);
  cairo_translate(ctx, -x, -y);
  if (anticlockwise && M_PI * 2 != args[4]) {
    cairo_arc_negative(ctx,
      x,
      y,
      radiusY,
      startAngle,
      endAngle);
  } else {
    cairo_arc(ctx,
      x,
      y,
      radiusY,
      startAngle,
      endAngle);
  }
  cairo_set_matrix(ctx, &save_matrix);
}
#undef CHECK_RECEIVER
node_modules/canvas/src/CanvasRenderingContext2d.cc.orig
New file
@@ -0,0 +1,3381 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "CanvasRenderingContext2d.h"
#include <algorithm>
#include "backend/ImageBackend.h"
#include <cairo-pdf.h>
#include "Canvas.h"
#include "CanvasGradient.h"
#include "CanvasPattern.h"
#include <cmath>
#include <cstdlib>
#include "Image.h"
#include "ImageData.h"
#include <limits>
#include <map>
#include "Point.h"
#include <string>
#include "Util.h"
#include <vector>
using namespace Napi;
Napi::FunctionReference Context2d::constructor;
/*
 * Rectangle arg assertions.
 */
#define RECT_ARGS \
  double args[4]; \
  if(!checkArgs(info, args, 4)) \
    return; \
  double x = args[0]; \
  double y = args[1]; \
  double width = args[2]; \
  double height = args[3];
#define CHECK_RECEIVER(prop) \
  if (!Context2d::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { \
    Napi::TypeError::New(env, "Method " #prop " called on incompatible receiver").ThrowAsJavaScriptException();
 \
    return; \
  }
constexpr double twoPi = M_PI * 2.;
/*
 * Simple helper macro for a rather verbose function call.
 */
#define PANGO_LAYOUT_GET_METRICS(LAYOUT) pango_context_get_metrics( \
   pango_layout_get_context(LAYOUT), \
   pango_layout_get_font_description(LAYOUT), \
   pango_context_get_language(pango_layout_get_context(LAYOUT)))
inline static bool checkArgs(const Napi::CallbackInfo&info, double *args, int argsNum, int offset = 0){
  int argsEnd = offset + argsNum;
  bool areArgsValid = true;
  for (int i = offset; i < argsEnd; i++) {
    double val = info[i].As<Napi::Number>().DoubleValue().FromMaybe(0);
    if (areArgsValid) {
      if (!std::isfinite(val)) {
        // We should continue the loop instead of returning immediately
        // See https://html.spec.whatwg.org/multipage/canvas.html
        areArgsValid = false;
        continue;
      }
      args[i - offset] = val;
    }
  }
  return areArgsValid;
}
Napi::FunctionReference Context2d::_DOMMatrix;
Napi::FunctionReference Context2d::_parseFont;
/*
 * Initialize Context2d.
 */
void
Context2d::Initialize(Napi::Env& env, Napi::Object& target) {
  Napi::HandleScope scope(env);
  // Constructor
  Napi::FunctionReference ctor = Napi::Function::New(env, Context2d::New);
  constructor.Reset(ctor);
  ctor->SetClassName(Napi::String::New(env, "CanvasRenderingContext2D"));
  // Prototype
  Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
  InstanceMethod("drawImage", &DrawImage),
  InstanceMethod("putImageData", &PutImageData),
  InstanceMethod("getImageData", &GetImageData),
  InstanceMethod("createImageData", &CreateImageData),
  InstanceMethod("addPage", &AddPage),
  InstanceMethod("save", &Save),
  InstanceMethod("restore", &Restore),
  InstanceMethod("rotate", &Rotate),
  InstanceMethod("translate", &Translate),
  InstanceMethod("transform", &Transform),
  InstanceMethod("getTransform", &GetTransform),
  InstanceMethod("resetTransform", &ResetTransform),
  InstanceMethod("setTransform", &SetTransform),
  InstanceMethod("isPointInPath", &IsPointInPath),
  InstanceMethod("scale", &Scale),
  InstanceMethod("clip", &Clip),
  InstanceMethod("fill", &Fill),
  InstanceMethod("stroke", &Stroke),
  InstanceMethod("fillText", &FillText),
  InstanceMethod("strokeText", &StrokeText),
  InstanceMethod("fillRect", &FillRect),
  InstanceMethod("strokeRect", &StrokeRect),
  InstanceMethod("clearRect", &ClearRect),
  InstanceMethod("rect", &Rect),
  InstanceMethod("roundRect", &RoundRect),
  InstanceMethod("measureText", &MeasureText),
  InstanceMethod("moveTo", &MoveTo),
  InstanceMethod("lineTo", &LineTo),
  InstanceMethod("bezierCurveTo", &BezierCurveTo),
  InstanceMethod("quadraticCurveTo", &QuadraticCurveTo),
  InstanceMethod("beginPath", &BeginPath),
  InstanceMethod("closePath", &ClosePath),
  InstanceMethod("arc", &Arc),
  InstanceMethod("arcTo", &ArcTo),
  InstanceMethod("ellipse", &Ellipse),
  InstanceMethod("setLineDash", &SetLineDash),
  InstanceMethod("getLineDash", &GetLineDash),
  InstanceMethod("createPattern", &CreatePattern),
  InstanceMethod("createLinearGradient", &CreateLinearGradient),
  InstanceMethod("createRadialGradient", &CreateRadialGradient),
  Napi::SetAccessor(proto, Napi::String::New(env, "pixelFormat"), GetFormat);
  Napi::SetAccessor(proto, Napi::String::New(env, "patternQuality"), GetPatternQuality, SetPatternQuality);
  Napi::SetAccessor(proto, Napi::String::New(env, "imageSmoothingEnabled"), GetImageSmoothingEnabled, SetImageSmoothingEnabled);
  Napi::SetAccessor(proto, Napi::String::New(env, "globalCompositeOperation"), GetGlobalCompositeOperation, SetGlobalCompositeOperation);
  Napi::SetAccessor(proto, Napi::String::New(env, "globalAlpha"), GetGlobalAlpha, SetGlobalAlpha);
  Napi::SetAccessor(proto, Napi::String::New(env, "shadowColor"), GetShadowColor, SetShadowColor);
  Napi::SetAccessor(proto, Napi::String::New(env, "miterLimit"), GetMiterLimit, SetMiterLimit);
  Napi::SetAccessor(proto, Napi::String::New(env, "lineWidth"), GetLineWidth, SetLineWidth);
  Napi::SetAccessor(proto, Napi::String::New(env, "lineCap"), GetLineCap, SetLineCap);
  Napi::SetAccessor(proto, Napi::String::New(env, "lineJoin"), GetLineJoin, SetLineJoin);
  Napi::SetAccessor(proto, Napi::String::New(env, "lineDashOffset"), GetLineDashOffset, SetLineDashOffset);
  Napi::SetAccessor(proto, Napi::String::New(env, "shadowOffsetX"), GetShadowOffsetX, SetShadowOffsetX);
  Napi::SetAccessor(proto, Napi::String::New(env, "shadowOffsetY"), GetShadowOffsetY, SetShadowOffsetY);
  Napi::SetAccessor(proto, Napi::String::New(env, "shadowBlur"), GetShadowBlur, SetShadowBlur);
  Napi::SetAccessor(proto, Napi::String::New(env, "antialias"), GetAntiAlias, SetAntiAlias);
  Napi::SetAccessor(proto, Napi::String::New(env, "textDrawingMode"), GetTextDrawingMode, SetTextDrawingMode);
  Napi::SetAccessor(proto, Napi::String::New(env, "quality"), GetQuality, SetQuality);
  Napi::SetAccessor(proto, Napi::String::New(env, "currentTransform"), GetCurrentTransform, SetCurrentTransform);
  Napi::SetAccessor(proto, Napi::String::New(env, "fillStyle"), GetFillStyle, SetFillStyle);
  Napi::SetAccessor(proto, Napi::String::New(env, "strokeStyle"), GetStrokeStyle, SetStrokeStyle);
  Napi::SetAccessor(proto, Napi::String::New(env, "font"), GetFont, SetFont);
  Napi::SetAccessor(proto, Napi::String::New(env, "textBaseline"), GetTextBaseline, SetTextBaseline);
  Napi::SetAccessor(proto, Napi::String::New(env, "textAlign"), GetTextAlign, SetTextAlign);
  Local<Context> ctx = Napi::GetCurrentContext();
  (target).Set(Napi::String::New(env, "CanvasRenderingContext2d"), ctor->GetFunction(ctx));
  (target).Set(Napi::String::New(env, "CanvasRenderingContext2dInit"), Napi::Function::New(env, SaveExternalModules));
}
/*
 * Create a cairo context.
 */
Context2d::Context2d(Canvas *canvas) {
  _canvas = canvas;
  _context = canvas->createCairoContext();
  _layout = pango_cairo_create_layout(_context);
  // As of January 2023, Pango rounds glyph positions which renders text wider
  // or narrower than the browser. See #2184 for more information
#if PANGO_VERSION_CHECK(1, 44, 0)
  pango_context_set_round_glyph_positions(pango_layout_get_context(_layout), FALSE);
#endif
  states.emplace();
  state = &states.top();
  pango_layout_set_font_description(_layout, state->fontDescription);
}
/*
 * Destroy cairo context.
 */
Context2d::~Context2d() {
  g_object_unref(_layout);
  cairo_destroy(_context);
  _resetPersistentHandles();
}
/*
 * Reset canvas state.
 */
void Context2d::resetState() {
  states.pop();
  states.emplace();
  pango_layout_set_font_description(_layout, state->fontDescription);
  _resetPersistentHandles();
}
void Context2d::_resetPersistentHandles() {
  _fillStyle.Reset();
  _strokeStyle.Reset();
}
/*
 * Save cairo / canvas state.
 */
void
Context2d::save() {
  cairo_save(_context);
  states.emplace(states.top());
  state = &states.top();
}
/*
 * Restore cairo / canvas state.
 */
void
Context2d::restore() {
  if (states.size() > 1) {
    cairo_restore(_context);
    states.pop();
    state = &states.top();
    pango_layout_set_font_description(_layout, state->fontDescription);
  }
}
/*
 * Save flat path.
 */
void
Context2d::savePath() {
  _path = cairo_copy_path_flat(_context);
  cairo_new_path(_context);
}
/*
 * Restore flat path.
 */
void
Context2d::restorePath() {
  cairo_new_path(_context);
  cairo_append_path(_context, _path);
  cairo_path_destroy(_path);
}
/*
 * Create temporary surface for gradient or pattern transparency
 */
cairo_pattern_t*
create_transparent_gradient(cairo_pattern_t *source, float alpha) {
  double x0;
  double y0;
  double x1;
  double y1;
  double r0;
  double r1;
  int count;
  int i;
  double offset;
  double r;
  double g;
  double b;
  double a;
  cairo_pattern_t *newGradient;
  cairo_pattern_type_t type = cairo_pattern_get_type(source);
  cairo_pattern_get_color_stop_count(source, &count);
  if (type == CAIRO_PATTERN_TYPE_LINEAR) {
    cairo_pattern_get_linear_points (source, &x0, &y0, &x1, &y1);
    newGradient = cairo_pattern_create_linear(x0, y0, x1, y1);
  } else if (type == CAIRO_PATTERN_TYPE_RADIAL) {
    cairo_pattern_get_radial_circles(source, &x0, &y0, &r0, &x1, &y1, &r1);
    newGradient = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1);
  } else {
    Napi::Error::New(env, "Unexpected gradient type").ThrowAsJavaScriptException();
    return NULL;
  }
  for ( i = 0; i < count; i++ ) {
    cairo_pattern_get_color_stop_rgba(source, i, &offset, &r, &g, &b, &a);
    cairo_pattern_add_color_stop_rgba(newGradient, offset, r, g, b, a * alpha);
  }
  return newGradient;
}
cairo_pattern_t*
create_transparent_pattern(cairo_pattern_t *source, float alpha) {
  cairo_surface_t *surface;
  cairo_pattern_get_surface(source, &surface);
  int width = cairo_image_surface_get_width(surface);
  int height = cairo_image_surface_get_height(surface);
  cairo_surface_t *mask_surface = cairo_image_surface_create(
    CAIRO_FORMAT_ARGB32,
    width,
    height);
  cairo_t *mask_context = cairo_create(mask_surface);
  if (cairo_status(mask_context) != CAIRO_STATUS_SUCCESS) {
    Napi::Error::New(env, "Failed to initialize context").ThrowAsJavaScriptException();
    return NULL;
  }
  cairo_set_source(mask_context, source);
  cairo_paint_with_alpha(mask_context, alpha);
  cairo_destroy(mask_context);
  cairo_pattern_t* newPattern = cairo_pattern_create_for_surface(mask_surface);
  cairo_surface_destroy(mask_surface);
  return newPattern;
}
/*
 * Fill and apply shadow.
 */
void
Context2d::setFillRule(Napi::Value value) {
  Napi::Env env = value.Env();
  cairo_fill_rule_t rule = CAIRO_FILL_RULE_WINDING;
  if (value.IsString()) {
    std::string str = value.As<Napi::String>();
    if (std::strcmp(*str, "evenodd") == 0) {
      rule = CAIRO_FILL_RULE_EVEN_ODD;
    }
  }
  cairo_set_fill_rule(_context, rule);
}
void
Context2d::fill(bool preserve) {
  cairo_pattern_t *new_pattern;
  bool needsRestore = false;
  if (state->fillPattern) {
    if (state->globalAlpha < 1) {
      new_pattern = create_transparent_pattern(state->fillPattern, state->globalAlpha);
      if (new_pattern == NULL) {
        // failed to allocate; Napi::ThrowError has already been called, so return from this fn.
        return;
      }
      cairo_set_source(_context, new_pattern);
      cairo_pattern_destroy(new_pattern);
    } else {
      cairo_pattern_set_filter(state->fillPattern, state->patternQuality);
      cairo_set_source(_context, state->fillPattern);
    }
    repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->fillPattern);
    if (repeat == NO_REPEAT) {
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE);
    } else if (repeat == REPEAT) {
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
    } else {
      cairo_save(_context);
      cairo_path_t *savedPath = cairo_copy_path(_context);
      cairo_surface_t *patternSurface = nullptr;
      cairo_pattern_get_surface(cairo_get_source(_context), &patternSurface);
      double width, height;
      if (repeat == REPEAT_X) {
        double x1, x2;
        cairo_path_extents(_context, &x1, nullptr, &x2, nullptr);
        width = x2 - x1;
        height = cairo_image_surface_get_height(patternSurface);
      } else {
        double y1, y2;
        cairo_path_extents(_context, nullptr, &y1, nullptr, &y2);
        width = cairo_image_surface_get_width(patternSurface);
        height = y2 - y1;
      }
      cairo_new_path(_context);
      cairo_rectangle(_context, 0, 0, width, height);
      cairo_clip(_context);
      cairo_append_path(_context, savedPath);
      cairo_path_destroy(savedPath);
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
      needsRestore = true;
    }
  } else if (state->fillGradient) {
    if (state->globalAlpha < 1) {
      new_pattern = create_transparent_gradient(state->fillGradient, state->globalAlpha);
      if (new_pattern == NULL) {
        // failed to recognize gradient; Napi::ThrowError has already been called, so return from this fn.
        return;
      }
      cairo_pattern_set_filter(new_pattern, state->patternQuality);
      cairo_set_source(_context, new_pattern);
      cairo_pattern_destroy(new_pattern);
    } else {
      cairo_pattern_set_filter(state->fillGradient, state->patternQuality);
      cairo_set_source(_context, state->fillGradient);
    }
  } else {
    setSourceRGBA(state->fill);
  }
  if (preserve) {
    hasShadow()
      ? shadow(cairo_fill_preserve)
      : cairo_fill_preserve(_context);
  } else {
    hasShadow()
      ? shadow(cairo_fill)
      : cairo_fill(_context);
  }
  if (needsRestore) {
    cairo_restore(_context);
  }
}
/*
 * Stroke and apply shadow.
 */
void
Context2d::stroke(bool preserve) {
  cairo_pattern_t *new_pattern;
  if (state->strokePattern) {
    if (state->globalAlpha < 1) {
      new_pattern = create_transparent_pattern(state->strokePattern, state->globalAlpha);
      if (new_pattern == NULL) {
        // failed to allocate; Napi::ThrowError has already been called, so return from this fn.
        return;
      }
      cairo_set_source(_context, new_pattern);
      cairo_pattern_destroy(new_pattern);
    } else {
      cairo_pattern_set_filter(state->strokePattern, state->patternQuality);
      cairo_set_source(_context, state->strokePattern);
    }
    repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->strokePattern);
    if (NO_REPEAT == repeat) {
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE);
    } else {
      cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT);
    }
  } else if (state->strokeGradient) {
    if (state->globalAlpha < 1) {
      new_pattern = create_transparent_gradient(state->strokeGradient, state->globalAlpha);
      if (new_pattern == NULL) {
        // failed to recognize gradient; Napi::ThrowError has already been called, so return from this fn.
        return;
      }
      cairo_pattern_set_filter(new_pattern, state->patternQuality);
      cairo_set_source(_context, new_pattern);
      cairo_pattern_destroy(new_pattern);
    } else {
      cairo_pattern_set_filter(state->strokeGradient, state->patternQuality);
      cairo_set_source(_context, state->strokeGradient);
    }
  } else {
    setSourceRGBA(state->stroke);
  }
  if (preserve) {
    hasShadow()
      ? shadow(cairo_stroke_preserve)
      : cairo_stroke_preserve(_context);
  } else {
    hasShadow()
      ? shadow(cairo_stroke)
      : cairo_stroke(_context);
  }
}
/*
 * Apply shadow with the given draw fn.
 */
void
Context2d::shadow(void (fn)(cairo_t *cr)) {
  cairo_path_t *path = cairo_copy_path_flat(_context);
  cairo_save(_context);
  // shadowOffset is unaffected by current transform
  cairo_matrix_t path_matrix;
  cairo_get_matrix(_context, &path_matrix);
  cairo_identity_matrix(_context);
  // Apply shadow
  cairo_push_group(_context);
  // No need to invoke blur if shadowBlur is 0
  if (state->shadowBlur) {
    // find out extent of path
    double x1, y1, x2, y2;
    if (fn == cairo_fill || fn == cairo_fill_preserve) {
      cairo_fill_extents(_context, &x1, &y1, &x2, &y2);
    } else {
      cairo_stroke_extents(_context, &x1, &y1, &x2, &y2);
    }
    // create new image surface that size + padding for blurring
    double dx = x2-x1, dy = y2-y1;
    cairo_user_to_device_distance(_context, &dx, &dy);
    int pad = state->shadowBlur * 2;
    cairo_surface_t *shadow_surface = cairo_image_surface_create(
      CAIRO_FORMAT_ARGB32,
      dx + 2 * pad,
      dy + 2 * pad);
    cairo_t *shadow_context = cairo_create(shadow_surface);
    // transform path to the right place
    cairo_translate(shadow_context, pad-x1, pad-y1);
    cairo_transform(shadow_context, &path_matrix);
    // set lineCap lineJoin lineDash
    cairo_set_line_cap(shadow_context, cairo_get_line_cap(_context));
    cairo_set_line_join(shadow_context, cairo_get_line_join(_context));
    double offset;
    int dashes = cairo_get_dash_count(_context);
    std::vector<double> a(dashes);
    cairo_get_dash(_context, a.data(), &offset);
    cairo_set_dash(shadow_context, a.data(), dashes, offset);
    // draw the path and blur
    cairo_set_line_width(shadow_context, cairo_get_line_width(_context));
    cairo_new_path(shadow_context);
    cairo_append_path(shadow_context, path);
    setSourceRGBA(shadow_context, state->shadow);
    fn(shadow_context);
    blur(shadow_surface, state->shadowBlur);
    // paint to original context
    cairo_set_source_surface(_context, shadow_surface,
      x1 - pad + state->shadowOffsetX + 1,
      y1 - pad + state->shadowOffsetY + 1);
    cairo_paint(_context);
    cairo_destroy(shadow_context);
    cairo_surface_destroy(shadow_surface);
  } else {
    // Offset first, then apply path's transform
    cairo_translate(
        _context
      , state->shadowOffsetX
      , state->shadowOffsetY);
    cairo_transform(_context, &path_matrix);
    // Apply shadow
    cairo_new_path(_context);
    cairo_append_path(_context, path);
    setSourceRGBA(state->shadow);
    fn(_context);
  }
  // Paint the shadow
  cairo_pop_group_to_source(_context);
  cairo_paint(_context);
  // Restore state
  cairo_restore(_context);
  cairo_new_path(_context);
  cairo_append_path(_context, path);
  fn(_context);
  cairo_path_destroy(path);
}
/*
 * Set source RGBA for the current context
 */
void
Context2d::setSourceRGBA(rgba_t color) {
  setSourceRGBA(_context, color);
}
/*
 * Set source RGBA
 */
void
Context2d::setSourceRGBA(cairo_t *ctx, rgba_t color) {
  cairo_set_source_rgba(
      ctx
    , color.r
    , color.g
    , color.b
    , color.a * state->globalAlpha);
}
/*
 * Check if the context has a drawable shadow.
 */
bool
Context2d::hasShadow() {
  return state->shadow.a
    && (state->shadowBlur || state->shadowOffsetX || state->shadowOffsetY);
}
/*
 * Blur the given surface with the given radius.
 */
void
Context2d::blur(cairo_surface_t *surface, int radius) {
  // Steve Hanov, 2009
  // Released into the public domain.
  radius = radius * 0.57735f + 0.5f;
  // get width, height
  int width = cairo_image_surface_get_width( surface );
  int height = cairo_image_surface_get_height( surface );
  unsigned* precalc =
      (unsigned*)malloc(width*height*sizeof(unsigned));
  cairo_surface_flush( surface );
  unsigned char* src = cairo_image_surface_get_data( surface );
  double mul=1.f/((radius*2)*(radius*2));
  int channel;
  // The number of times to perform the averaging. According to wikipedia,
  // three iterations is good enough to pass for a gaussian.
  const int MAX_ITERATIONS = 3;
  int iteration;
  for ( iteration = 0; iteration < MAX_ITERATIONS; iteration++ ) {
      for( channel = 0; channel < 4; channel++ ) {
          int x,y;
          // precomputation step.
          unsigned char* pix = src;
          unsigned* pre = precalc;
          pix += channel;
          for (y=0;y<height;y++) {
              for (x=0;x<width;x++) {
                  int tot=pix[0];
                  if (x>0) tot+=pre[-1];
                  if (y>0) tot+=pre[-width];
                  if (x>0 && y>0) tot-=pre[-width-1];
                  *pre++=tot;
                  pix += 4;
              }
          }
          // blur step.
          pix = src + (int)radius * width * 4 + (int)radius * 4 + channel;
          for (y=radius;y<height-radius;y++) {
              for (x=radius;x<width-radius;x++) {
                  int l = x < radius ? 0 : x - radius;
                  int t = y < radius ? 0 : y - radius;
                  int r = x + radius >= width ? width - 1 : x + radius;
                  int b = y + radius >= height ? height - 1 : y + radius;
                  int tot = precalc[r+b*width] + precalc[l+t*width] -
                      precalc[l+b*width] - precalc[r+t*width];
                  *pix=(unsigned char)(tot*mul);
                  pix += 4;
              }
              pix += (int)radius * 2 * 4;
          }
      }
  }
  cairo_surface_mark_dirty(surface);
  free(precalc);
}
/*
 * Initialize a new Context2d with the given canvas.
 */
Napi::Value Context2d::New(const Napi::CallbackInfo& info) {
  if (!info.IsConstructCall()) {
    Napi::TypeError::New(env, "Class constructors cannot be invoked without 'new'").ThrowAsJavaScriptException();
    return env.Null();
  }
  if (!info[0].IsObject())
    Napi::TypeError::New(env, "Canvas expected").ThrowAsJavaScriptException();
    return env.Null();
  Napi::Object obj = info[0].To<Napi::Object>();
  if (!Napi::New(env, Canvas::constructor)->HasInstance(obj))
    Napi::TypeError::New(env, "Canvas expected").ThrowAsJavaScriptException();
    return env.Null();
  Canvas *canvas = obj.Unwrap<Canvas>();
  bool isImageBackend = canvas->backend()->getName() == "image";
  if (isImageBackend) {
    cairo_format_t format = ImageBackend::DEFAULT_FORMAT;
    if (info[1].IsObject()) {
      Napi::Object ctxAttributes = info[1].To<Napi::Object>();
      Napi::Value pixelFormat = (ctxAttributes).Get(Napi::String::New(env, "pixelFormat"));
      if (pixelFormat.IsString()) {
        std::string utf8PixelFormat = pixelFormat.As<Napi::String>();
        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
      Napi::Value alpha = (ctxAttributes).Get(Napi::String::New(env, "alpha"));
      if (alpha->IsBoolean() && !alpha.As<Napi::Boolean>().Value().FromMaybe(false)) {
        format = CAIRO_FORMAT_RGB24;
      }
    }
    static_cast<ImageBackend*>(canvas->backend())->setFormat(format);
  }
  Context2d *context = new Context2d(canvas);
  context->Wrap(info.This());
  return info.This();
}
/*
 * Save some external modules as private references.
 */
Napi::Value Context2d::SaveExternalModules(const Napi::CallbackInfo& info) {
  _DOMMatrix.Reset(info[0].To<Napi::Function>());
  _parseFont.Reset(info[1].To<Napi::Function>());
}
/*
* Get format (string).
*/
Napi::Value Context2d::GetFormat(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetFormat);
  Context2d *context = info.This().Unwrap<Context2d>();
  std::string pixelFormatString;
  switch (context->canvas()->backend()->getFormat()) {
  case CAIRO_FORMAT_ARGB32: pixelFormatString = "RGBA32"; break;
  case CAIRO_FORMAT_RGB24: pixelFormatString = "RGB24"; break;
  case CAIRO_FORMAT_A8: pixelFormatString = "A8"; break;
  case CAIRO_FORMAT_A1: pixelFormatString = "A1"; break;
  case CAIRO_FORMAT_RGB16_565: pixelFormatString = "RGB16_565"; break;
#ifdef CAIRO_FORMAT_RGB30
  case CAIRO_FORMAT_RGB30: pixelFormatString = "RGB30"; break;
#endif
  default: return info.GetReturnValue().SetNull();
  }
  return Napi::String::New(env, pixelFormatString);
}
/*
 * Create a new page.
 */
Napi::Value Context2d::AddPage(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  if (context->canvas()->backend()->getName() != "pdf") {
    Napi::Error::New(env, "only PDF canvases support .addPage()").ThrowAsJavaScriptException();
    return env.Null();
  }
  cairo_show_page(context->context());
  int width = info[0].As<Napi::Number>().Int32Value().FromMaybe(0);
  int height = info[1].As<Napi::Number>().Int32Value().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;
}
/*
 * Put image data.
 *
 *  - imageData, dx, dy
 *  - imageData, dx, dy, sx, sy, sw, sh
 *
 */
Napi::Value Context2d::PutImageData(const Napi::CallbackInfo& info) {
  if (!info[0].IsObject())
    Napi::TypeError::New(env, "ImageData expected").ThrowAsJavaScriptException();
    return env.Null();
  Napi::Object obj = info[0].To<Napi::Object>();
  if (!Napi::New(env, ImageData::constructor)->HasInstance(obj))
    Napi::TypeError::New(env, "ImageData expected").ThrowAsJavaScriptException();
    return env.Null();
  Context2d *context = info.This().Unwrap<Context2d>();
  ImageData *imageData = obj.Unwrap<ImageData>();
  uint8_t *src = imageData->data();
  uint8_t *dst = context->canvas()->data();
  int dstStride = context->canvas()->stride();
  int Bpp = dstStride / context->canvas()->getWidth();
  int srcStride = Bpp * imageData->width();
  int sx = 0
    , sy = 0
    , sw = 0
    , sh = 0
    , dx = info[1].As<Napi::Number>().Int32Value().FromMaybe(0)
    , dy = info[2].As<Napi::Number>().Int32Value().FromMaybe(0)
    , rows
    , cols;
  switch (info.Length()) {
    // imageData, dx, dy
    case 3:
      sw = imageData->width();
      sh = imageData->height();
      break;
    // imageData, dx, dy, sx, sy, sw, sh
    case 7:
      sx = info[3].As<Napi::Number>().Int32Value().FromMaybe(0);
      sy = info[4].As<Napi::Number>().Int32Value().FromMaybe(0);
      sw = info[5].As<Napi::Number>().Int32Value().FromMaybe(0);
      sh = info[6].As<Napi::Number>().Int32Value().FromMaybe(0);
      // fix up negative height, width
      if (sw < 0) sx += sw, sw = -sw;
      if (sh < 0) sy += sh, sh = -sh;
      // clamp the left edge
      if (sx < 0) sw += sx, sx = 0;
      if (sy < 0) sh += sy, sy = 0;
      // clamp the right edge
      if (sx + sw > imageData->width()) sw = imageData->width() - sx;
      if (sy + sh > imageData->height()) sh = imageData->height() - sy;
      // start destination at source offset
      dx += sx;
      dy += sy;
      break;
    default:
      Napi::Error::New(env, "invalid arguments").ThrowAsJavaScriptException();
      return env.Null();
  }
  // chop off outlying source data
  if (dx < 0) sw += dx, sx -= dx, dx = 0;
  if (dy < 0) sh += dy, sy -= dy, dy = 0;
  // 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);
  if (cols <= 0 || rows <= 0) return;
  switch (context->canvas()->backend()->getFormat()) {
  case CAIRO_FORMAT_ARGB32: {
    src += sy * srcStride + sx * 4;
    dst += dstStride * dy + 4 * dx;
    for (int y = 0; y < rows; ++y) {
      uint8_t *dstRow = dst;
      uint8_t *srcRow = src;
      for (int x = 0; x < cols; ++x) {
        // rgba
        uint8_t r = *srcRow++;
        uint8_t g = *srcRow++;
        uint8_t b = *srcRow++;
        uint8_t a = *srcRow++;
        // argb
        // performance optimization: fully transparent/opaque pixels can be
        // processed more efficiently.
        if (a == 0) {
          *dstRow++ = 0;
          *dstRow++ = 0;
          *dstRow++ = 0;
          *dstRow++ = 0;
        } else if (a == 255) {
          *dstRow++ = b;
          *dstRow++ = g;
          *dstRow++ = r;
          *dstRow++ = a;
        } else {
          float alpha = (float)a / 255;
          *dstRow++ = b * alpha;
          *dstRow++ = g * alpha;
          *dstRow++ = r * alpha;
          *dstRow++ = a;
        }
      }
      dst += dstStride;
      src += srcStride;
    }
    break;
  }
  case CAIRO_FORMAT_RGB24: {
    src += sy * srcStride + sx * 4;
    dst += dstStride * dy + 4 * dx;
    for (int y = 0; y < rows; ++y) {
      uint8_t *dstRow = dst;
      uint8_t *srcRow = src;
      for (int x = 0; x < cols; ++x) {
        // rgba
        uint8_t r = *srcRow++;
        uint8_t g = *srcRow++;
        uint8_t b = *srcRow++;
        srcRow++;
        // argb
        *dstRow++ = b;
        *dstRow++ = g;
        *dstRow++ = r;
        *dstRow++ = 255;
      }
      dst += dstStride;
      src += srcStride;
    }
    break;
  }
  case CAIRO_FORMAT_A8: {
    src += sy * srcStride + sx;
    dst += dstStride * dy + dx;
    if (srcStride == dstStride && cols == dstStride) {
      // fast path: strides are the same and doing a full-width put
      memcpy(dst, src, cols * rows);
    } else {
      for (int y = 0; y < rows; ++y) {
        memcpy(dst, src, cols);
        dst += dstStride;
        src += srcStride;
      }
    }
    break;
  }
  case CAIRO_FORMAT_A1: {
    // TODO Should this be totally packed, or maintain a stride divisible by 4?
    Napi::Error::New(env, "putImageData for CANVAS_FORMAT_A1 is not yet implemented").ThrowAsJavaScriptException();
    break;
  }
  case CAIRO_FORMAT_RGB16_565: {
    src += sy * srcStride + sx * 2;
    dst += dstStride * dy + 2 * dx;
    for (int y = 0; y < rows; ++y) {
      memcpy(dst, src, cols * 2);
      dst += dstStride;
      src += srcStride;
    }
    break;
  }
#ifdef CAIRO_FORMAT_RGB30
  case CAIRO_FORMAT_RGB30: {
    // TODO
    Napi::Error::New(env, "putImageData for CANVAS_FORMAT_RGB30 is not yet implemented").ThrowAsJavaScriptException();
    break;
  }
#endif
  default: {
    Napi::Error::New(env, "Invalid pixel format or not an image canvas").ThrowAsJavaScriptException();
    return env.Null();
  }
  }
  cairo_surface_mark_dirty_rectangle(
      context->canvas()->surface()
    , dx
    , dy
    , cols
    , rows);
}
/*
 * Get image data.
 *
 *  - sx, sy, sw, sh
 *
 */
Napi::Value Context2d::GetImageData(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  Canvas *canvas = context->canvas();
  int sx = info[0].As<Napi::Number>().Int32Value().FromMaybe(0);
  int sy = info[1].As<Napi::Number>().Int32Value().FromMaybe(0);
  int sw = info[2].As<Napi::Number>().Int32Value().FromMaybe(0);
  int sh = info[3].As<Napi::Number>().Int32Value().FromMaybe(0);
  if (!sw)
    Napi::Error::New(env, "IndexSizeError: The source width is 0.").ThrowAsJavaScriptException();
    return env.Null();
  if (!sh)
    Napi::Error::New(env, "IndexSizeError: The source height is 0.").ThrowAsJavaScriptException();
    return env.Null();
  int width = canvas->getWidth();
  int height = canvas->getHeight();
  if (!width)
    Napi::TypeError::New(env, "Canvas width is 0").ThrowAsJavaScriptException();
    return env.Null();
  if (!height)
    Napi::TypeError::New(env, "Canvas height is 0").ThrowAsJavaScriptException();
    return env.Null();
  // WebKit and Firefox have this behavior:
  // Flip the coordinates so the origin is top/left-most:
  if (sw < 0) {
    sx += sw;
    sw = -sw;
  }
  if (sh < 0) {
    sy += sh;
    sh = -sh;
  }
  if (sx + sw > width) sw = width - sx;
  if (sy + sh > height) sh = height - sy;
  // WebKit/moz functionality. node-canvas used to return in either case.
  if (sw <= 0) sw = 1;
  if (sh <= 0) sh = 1;
  // Non-compliant. "Pixels outside the canvas must be returned as transparent
  // black." This instead clips the returned array to the canvas area.
  if (sx < 0) {
    sw += sx;
    sx = 0;
  }
  if (sy < 0) {
    sh += sy;
    sy = 0;
  }
  int srcStride = canvas->stride();
  int bpp = srcStride / width;
  int size = sw * sh * bpp;
  int dstStride = sw * bpp;
  uint8_t *src = canvas->data();
  Local<ArrayBuffer> buffer = ArrayBuffer::New(Isolate::GetCurrent(), size);
  Local<TypedArray> dataArray;
  if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565) {
    dataArray = Uint16Array::New(buffer, 0, size >> 1);
  } else {
    dataArray = Uint8ClampedArray::New(buffer, 0, size);
  }
  Napi::TypedArrayContents<uint8_t> typedArrayContents(dataArray);
  uint8_t* dst = *typedArrayContents;
  switch (canvas->backend()->getFormat()) {
  case CAIRO_FORMAT_ARGB32: {
    // Rearrange alpha (argb -> rgba), undo alpha pre-multiplication,
    // and store in big-endian format
    for (int y = 0; y < sh; ++y) {
      uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
      for (int x = 0; x < sw; ++x) {
        int bx = x * 4;
        uint32_t *pixel = row + x + sx;
        uint8_t a = *pixel >> 24;
        uint8_t r = *pixel >> 16;
        uint8_t g = *pixel >> 8;
        uint8_t b = *pixel;
        dst[bx + 3] = a;
        // Performance optimization: fully transparent/opaque pixels can be
        // processed more efficiently.
        if (a == 0 || a == 255) {
          dst[bx + 0] = r;
          dst[bx + 1] = g;
          dst[bx + 2] = b;
        } else {
          // Undo alpha pre-multiplication
          float alphaR = (float)255 / a;
          dst[bx + 0] = (int)((float)r * alphaR);
          dst[bx + 1] = (int)((float)g * alphaR);
          dst[bx + 2] = (int)((float)b * alphaR);
        }
      }
      dst += dstStride;
    }
    break;
  }
  case CAIRO_FORMAT_RGB24: {
  // Rearrange alpha (argb -> rgba) and store in big-endian format
    for (int y = 0; y < sh; ++y) {
    uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
    for (int x = 0; x < sw; ++x) {
      int bx = x * 4;
      uint32_t *pixel = row + x + sx;
      uint8_t r = *pixel >> 16;
      uint8_t g = *pixel >> 8;
      uint8_t b = *pixel;
      dst[bx + 0] = r;
      dst[bx + 1] = g;
      dst[bx + 2] = b;
      dst[bx + 3] = 255;
    }
    dst += dstStride;
    }
    break;
  }
  case CAIRO_FORMAT_A8: {
    for (int y = 0; y < sh; ++y) {
      uint8_t *row = (uint8_t *)(src + srcStride * (y + sy));
      memcpy(dst, row + sx, dstStride);
      dst += dstStride;
    }
    break;
  }
  case CAIRO_FORMAT_A1: {
    // TODO Should this be totally packed, or maintain a stride divisible by 4?
    Napi::Error::New(env, "getImageData for CANVAS_FORMAT_A1 is not yet implemented").ThrowAsJavaScriptException();
    break;
  }
  case CAIRO_FORMAT_RGB16_565: {
    for (int y = 0; y < sh; ++y) {
      uint16_t *row = (uint16_t *)(src + srcStride * (y + sy));
      memcpy(dst, row + sx, dstStride);
      dst += dstStride;
    }
    break;
  }
#ifdef CAIRO_FORMAT_RGB30
  case CAIRO_FORMAT_RGB30: {
    // TODO
    Napi::Error::New(env, "getImageData for CANVAS_FORMAT_RGB30 is not yet implemented").ThrowAsJavaScriptException();
    break;
  }
#endif
  default: {
    // Unlikely
    Napi::Error::New(env, "Invalid pixel format or not an image canvas").ThrowAsJavaScriptException();
    return env.Null();
  }
  }
  const int argc = 3;
  Local<Int32> swHandle = Napi::New(env, sw);
  Local<Int32> shHandle = Napi::New(env, sh);
  Napi::Value argv[argc] = { dataArray, swHandle, shHandle };
  Napi::Function ctor = Napi::GetFunction(Napi::New(env, ImageData::constructor));
  Napi::Object instance = Napi::NewInstance(ctor, argc, argv);
  return instance;
}
/**
 * Create `ImageData` with the given dimensions or
 * `ImageData` instance for dimensions.
 */
Napi::Value Context2d::CreateImageData(const Napi::CallbackInfo& info){
  Isolate *iso = Isolate::GetCurrent();
  Context2d *context = info.This().Unwrap<Context2d>();
  Canvas *canvas = context->canvas();
  int32_t width, height;
  if (info[0].IsObject()) {
    Napi::Object obj = info[0].To<Napi::Object>();
    width = (obj).Get(Napi::String::New(env, "width".As<Napi::Number>().Int32Value())).FromMaybe(0);
    height = (obj).Get(Napi::String::New(env, "height".As<Napi::Number>().Int32Value())).FromMaybe(0);
  } else {
    width = info[0].As<Napi::Number>().Int32Value().FromMaybe(0);
    height = info[1].As<Napi::Number>().Int32Value().FromMaybe(0);
  }
  int stride = canvas->stride();
  double Bpp = static_cast<double>(stride) / canvas->getWidth();
  int nBytes = static_cast<int>(Bpp * width * height + .5);
  Local<ArrayBuffer> ab = ArrayBuffer::New(iso, nBytes);
  Napi::Object arr;
  if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565)
    arr = Uint16Array::New(ab, 0, nBytes / 2);
  else
    arr = Uint8ClampedArray::New(ab, 0, nBytes);
  const int argc = 3;
  Napi::Value argv[argc] = { arr, Napi::New(env, width), Napi::New(env, height) };
  Napi::Function ctor = Napi::GetFunction(Napi::New(env, ImageData::constructor));
  Napi::Object instance = Napi::NewInstance(ctor, argc, argv);
  return instance;
}
/*
 * Take a transform matrix and return its components
 * 0: angle, 1: scaleX, 2: scaleY, 3: skewX, 4: translateX, 5: translateY
 */
void decompose_matrix(cairo_matrix_t matrix, double *destination) {
  double denom = pow(matrix.xx, 2) + pow(matrix.yx, 2);
  destination[0] = atan2(matrix.yx, matrix.xx);
  destination[1] = sqrt(denom);
  destination[2] = (matrix.xx * matrix.yy - matrix.xy * matrix.yx) / destination[1];
  destination[3] = atan2(matrix.xx * matrix.xy + matrix.yx * matrix.yy, denom);
  destination[4] = matrix.x0;
  destination[5] = matrix.y0;
}
/*
 * Draw image src image to the destination (context).
 *
 *  - dx, dy
 *  - dx, dy, dw, dh
 *  - sx, sy, sw, sh, dx, dy, dw, dh
 *
 */
Napi::Value Context2d::DrawImage(const Napi::CallbackInfo& info) {
  int infoLen = info.Length();
  if (infoLen != 3 && infoLen != 5 && infoLen != 9)
    Napi::TypeError::New(env, "Invalid arguments").ThrowAsJavaScriptException();
    return env.Null();
  if (!info[0].IsObject())
    Napi::TypeError::New(env, "The first argument must be an object").ThrowAsJavaScriptException();
    return env.Null();
  double args[8];
  if(!checkArgs(info, args, infoLen - 1, 1))
    return;
  double sx = 0
    , sy = 0
    , sw = 0
    , sh = 0
    , dx = 0
    , dy = 0
    , dw = 0
    , dh = 0
    , source_w = 0
    , source_h = 0;
  cairo_surface_t *surface;
  Napi::Object obj = info[0].To<Napi::Object>();
  // Image
  if (Napi::New(env, Image::constructor)->HasInstance(obj)) {
    Image *img = obj.Unwrap<Image>();
    if (!img->isComplete()) {
      Napi::Error::New(env, "Image given has not completed loading").ThrowAsJavaScriptException();
      return env.Null();
    }
    source_w = sw = img->width;
    source_h = sh = img->height;
    surface = img->surface();
  // Canvas
  } else if (Napi::New(env, Canvas::constructor)->HasInstance(obj)) {
    Canvas *canvas = obj.Unwrap<Canvas>();
    source_w = sw = canvas->getWidth();
    source_h = sh = canvas->getHeight();
    surface = canvas->surface();
  // Invalid
  } else {
    Napi::TypeError::New(env, "Image or Canvas expected").ThrowAsJavaScriptException();
    return env.Null();
  }
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  // Arguments
  switch (infoLen) {
    // img, sx, sy, sw, sh, dx, dy, dw, dh
    case 9:
      sx = args[0];
      sy = args[1];
      sw = args[2];
      sh = args[3];
      dx = args[4];
      dy = args[5];
      dw = args[6];
      dh = args[7];
      break;
    // img, dx, dy, dw, dh
    case 5:
      dx = args[0];
      dy = args[1];
      dw = args[2];
      dh = args[3];
      break;
    // img, dx, dy
    case 3:
      dx = args[0];
      dy = args[1];
      dw = sw;
      dh = sh;
      break;
  }
  if (!(sw && sh && dw && dh))
    return;
  // Start draw
  cairo_save(ctx);
  cairo_matrix_t matrix;
  double transforms[6];
  cairo_get_matrix(context->context(), &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.
  double current_scale_x = std::abs(transforms[1]);
  double current_scale_y = std::abs(transforms[2]);
  double extra_dx = 0;
  double extra_dy = 0;
  double fx = dw / sw * current_scale_x; // transforms[1] is scale on X
  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 needsExtraSurface = sameCanvas || needCut || needScale;
  cairo_surface_t *surfTemp = NULL;
  cairo_t *ctxTemp = NULL;
  if (needsExtraSurface) {
    // we want to create the extra surface as small as possible.
    // fx and fy are the total scaling we need to apply to sw, sh.
    // from sw and sh we want to remove the part that is outside the source_w and soruce_h
    double real_w = sw;
    double real_h = sh;
    double translate_x = 0;
    double translate_y = 0;
    // if sx or sy are negative, a part of the area represented by sw and sh is empty
    // because there are empty pixels, so we cut it out.
    // On the other hand if sx or sy are positive, but sw and sh extend outside the real
    // source pixels, we cut the area in that case too.
    if (sx < 0) {
      extra_dx = -sx * fx;
      real_w = sw + sx;
    } else if (sx + sw > source_w) {
      real_w = sw - (sx + sw - source_w);
    }
    if (sy < 0) {
      extra_dy = -sy * fy;
      real_h = sh + sy;
    } else if (sy + sh > source_h) {
      real_h = sh - (sy + sh - source_h);
    }
    // if after cutting we are still bigger than source pixels, we restrict again
    if (real_w > source_w) {
      real_w = source_w;
    }
    if (real_h > source_h) {
      real_h = source_h;
    }
    // TODO: find a way to limit the surfTemp to real_w and real_h if fx and fy are bigger than 1.
    // there are no more pixel than the one available in the source, no need to create a bigger surface.
    surfTemp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, round(real_w * fx), round(real_h * fy));
    ctxTemp = cairo_create(surfTemp);
    cairo_scale(ctxTemp, fx, fy);
    if (sx > 0) {
      translate_x = sx;
    }
    if (sy > 0) {
      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_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) {
      // we need to create a new surface in order to blur
      int pad = context->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);
      cairo_mask_surface(shadow_context, surface, pad, pad);
      context->blur(shadow_surface, context->state->shadowBlur);
      // paint
      // @note: ShadowBlur looks different in each browser. This implementation matches chrome as close as possible.
      //        The 1.4 offset comes from visual tests with Chrome. I have read the spec and part of the shadowBlur
      //        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);
      cairo_paint(ctx);
      // cleanup
      cairo_destroy(shadow_context);
      cairo_surface_destroy(shadow_surface);
    } else {
      context->setSourceRGBA(context->state->shadow);
      cairo_mask_surface(ctx, surface,
        dx + (context->state->shadowOffsetX),
        dy + (context->state->shadowOffsetY));
    }
  }
  double scaled_dx = dx;
  double scaled_dy = dy;
  if (needsExtraSurface && (current_scale_x != 1 || current_scale_y != 1)) {
    // in this case our surface contains already current_scale_x, we need to scale back
    cairo_scale(ctx, 1 / current_scale_x, 1 / current_scale_y);
    scaled_dx *= current_scale_x;
    scaled_dy *= current_scale_y;
  }
  // 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_extend(cairo_get_source(ctx), CAIRO_EXTEND_NONE);
  cairo_paint_with_alpha(ctx, context->state->globalAlpha);
  cairo_restore(ctx);
  if (needsExtraSurface) {
    cairo_destroy(ctxTemp);
    cairo_surface_destroy(surfTemp);
  }
}
/*
 * Get global alpha.
 */
Napi::Value Context2d::GetGlobalAlpha(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetGlobalAlpha);
  Context2d *context = info.This().Unwrap<Context2d>();
  return Napi::Number::New(env, context->state->globalAlpha);
}
/*
 * Set global alpha.
 */
void Context2d::SetGlobalAlpha(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetGlobalAlpha);
  double n = value.As<Napi::Number>().DoubleValue().FromMaybe(0);
  if (n >= 0 && n <= 1) {
    Context2d *context = info.This().Unwrap<Context2d>();
    context->state->globalAlpha = n;
  }
}
/*
 * Get global composite operation.
 */
Napi::Value Context2d::GetGlobalCompositeOperation(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetGlobalCompositeOperation);
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  const char *op = "source-over";
  switch (cairo_get_operator(ctx)) {
    // composite modes:
    case CAIRO_OPERATOR_CLEAR: op = "clear"; break;
    case CAIRO_OPERATOR_SOURCE: op = "copy"; break;
    case CAIRO_OPERATOR_DEST: op = "destination"; break;
    case CAIRO_OPERATOR_OVER: op = "source-over"; break;
    case CAIRO_OPERATOR_DEST_OVER: op = "destination-over"; break;
    case CAIRO_OPERATOR_IN: op = "source-in"; break;
    case CAIRO_OPERATOR_DEST_IN: op = "destination-in"; break;
    case CAIRO_OPERATOR_OUT: op = "source-out"; break;
    case CAIRO_OPERATOR_DEST_OUT: op = "destination-out"; break;
    case CAIRO_OPERATOR_ATOP: op = "source-atop"; break;
    case CAIRO_OPERATOR_DEST_ATOP: op = "destination-atop"; break;
    case CAIRO_OPERATOR_XOR: op = "xor"; break;
    case CAIRO_OPERATOR_ADD: op = "lighter"; break;
    // blend modes:
    // Note: "source-over" and "normal" are synonyms. Chrome and FF both report
    // "source-over" after setting gCO to "normal".
    // case CAIRO_OPERATOR_OVER: op = "normal";
    case CAIRO_OPERATOR_MULTIPLY: op = "multiply"; break;
    case CAIRO_OPERATOR_SCREEN: op = "screen"; break;
    case CAIRO_OPERATOR_OVERLAY: op = "overlay"; break;
    case CAIRO_OPERATOR_DARKEN: op = "darken"; break;
    case CAIRO_OPERATOR_LIGHTEN: op = "lighten"; break;
    case CAIRO_OPERATOR_COLOR_DODGE: op = "color-dodge"; break;
    case CAIRO_OPERATOR_COLOR_BURN: op = "color-burn"; break;
    case CAIRO_OPERATOR_HARD_LIGHT: op = "hard-light"; break;
    case CAIRO_OPERATOR_SOFT_LIGHT: op = "soft-light"; break;
    case CAIRO_OPERATOR_DIFFERENCE: op = "difference"; break;
    case CAIRO_OPERATOR_EXCLUSION: op = "exclusion"; break;
    case CAIRO_OPERATOR_HSL_HUE: op = "hue"; break;
    case CAIRO_OPERATOR_HSL_SATURATION: op = "saturation"; break;
    case CAIRO_OPERATOR_HSL_COLOR: op = "color"; break;
    case CAIRO_OPERATOR_HSL_LUMINOSITY: op = "luminosity"; break;
    // non-standard:
    case CAIRO_OPERATOR_SATURATE: op = "saturate"; break;
  }
  return Napi::New(env, op);
}
/*
 * Set pattern quality.
 */
void Context2d::SetPatternQuality(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetPatternQuality);
  Context2d *context = info.This().Unwrap<Context2d>();
  std::string quality = value.As<Napi::String>(.To<Napi::String>());
  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;
  }
}
/*
 * Get pattern quality.
 */
Napi::Value Context2d::GetPatternQuality(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetPatternQuality);
  Context2d *context = info.This().Unwrap<Context2d>();
  const char *quality;
  switch (context->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";
  }
  return Napi::New(env, quality);
}
/*
 * Set ImageSmoothingEnabled value.
 */
void Context2d::SetImageSmoothingEnabled(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetImageSmoothingEnabled);
  Context2d *context = info.This().Unwrap<Context2d>();
  context->state->imageSmoothingEnabled = value.As<Napi::Boolean>().Value().FromMaybe(false);
}
/*
 * Get pattern quality.
 */
Napi::Value Context2d::GetImageSmoothingEnabled(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetImageSmoothingEnabled);
  Context2d *context = info.This().Unwrap<Context2d>();
  return Napi::Boolean::New(env, context->state->imageSmoothingEnabled);
}
/*
 * Set global composite operation.
 */
void Context2d::SetGlobalCompositeOperation(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetGlobalCompositeOperation);
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  std::string opStr = value.As<Napi::String>(.To<Napi::String>()); // Unlike CSS colors, this *is* case-sensitive
  const std::map<std::string, cairo_operator_t> 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);
}
/*
 * Get shadow offset x.
 */
Napi::Value Context2d::GetShadowOffsetX(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetShadowOffsetX);
  Context2d *context = info.This().Unwrap<Context2d>();
  return Napi::Number::New(env, context->state->shadowOffsetX);
}
/*
 * Set shadow offset x.
 */
void Context2d::SetShadowOffsetX(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetShadowOffsetX);
  Context2d *context = info.This().Unwrap<Context2d>();
  context->state->shadowOffsetX = value.As<Napi::Number>().DoubleValue().FromMaybe(0);
}
/*
 * Get shadow offset y.
 */
Napi::Value Context2d::GetShadowOffsetY(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetShadowOffsetY);
  Context2d *context = info.This().Unwrap<Context2d>();
  return Napi::Number::New(env, context->state->shadowOffsetY);
}
/*
 * Set shadow offset y.
 */
void Context2d::SetShadowOffsetY(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetShadowOffsetY);
  Context2d *context = info.This().Unwrap<Context2d>();
  context->state->shadowOffsetY = value.As<Napi::Number>().DoubleValue().FromMaybe(0);
}
/*
 * Get shadow blur.
 */
Napi::Value Context2d::GetShadowBlur(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetShadowBlur);
  Context2d *context = info.This().Unwrap<Context2d>();
  return Napi::Number::New(env, context->state->shadowBlur);
}
/*
 * Set shadow blur.
 */
void Context2d::SetShadowBlur(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetShadowBlur);
  int n = value.As<Napi::Number>().DoubleValue().FromMaybe(0);
  if (n >= 0) {
    Context2d *context = info.This().Unwrap<Context2d>();
    context->state->shadowBlur = n;
  }
}
/*
 * Get current antialiasing setting.
 */
Napi::Value Context2d::GetAntiAlias(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetAntiAlias);
  Context2d *context = info.This().Unwrap<Context2d>();
  const char *aa;
  switch (cairo_get_antialias(context->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";
  }
  return Napi::New(env, aa);
}
/*
 * Set antialiasing.
 */
void Context2d::SetAntiAlias(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetAntiAlias);
  std::string str = value.As<Napi::String>(.To<Napi::String>());
  Context2d *context = info.This().Unwrap<Context2d>();
  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);
  }
  cairo_set_antialias(ctx, a);
}
/*
 * Get text drawing mode.
 */
Napi::Value Context2d::GetTextDrawingMode(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetTextDrawingMode);
  Context2d *context = info.This().Unwrap<Context2d>();
  const char *mode;
  if (context->state->textDrawingMode == TEXT_DRAW_PATHS) {
    mode = "path";
  } else if (context->state->textDrawingMode == TEXT_DRAW_GLYPHS) {
    mode = "glyph";
  } else {
    mode = "unknown";
  }
  return Napi::New(env, mode);
}
/*
 * Set text drawing mode.
 */
void Context2d::SetTextDrawingMode(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetTextDrawingMode);
  std::string str = value.As<Napi::String>(.To<Napi::String>());
  Context2d *context = info.This().Unwrap<Context2d>();
  if (0 == strcmp("path", *str)) {
    context->state->textDrawingMode = TEXT_DRAW_PATHS;
  } else if (0 == strcmp("glyph", *str)) {
    context->state->textDrawingMode = TEXT_DRAW_GLYPHS;
  }
}
/*
 * Get filter.
 */
Napi::Value Context2d::GetQuality(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetQuality);
  Context2d *context = info.This().Unwrap<Context2d>();
  const char *filter;
  switch (cairo_pattern_get_filter(cairo_get_source(context->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";
  }
  return Napi::New(env, filter);
}
/*
 * Set filter.
 */
void Context2d::SetQuality(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetQuality);
  std::string str = value.As<Napi::String>(.To<Napi::String>());
  Context2d *context = info.This().Unwrap<Context2d>();
  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;
  }
  cairo_pattern_set_filter(cairo_get_source(context->context()), filter);
}
/*
 * Helper for get current transform matrix
 */
Napi::Object
get_current_transform(Context2d *context) {
  Isolate *iso = Isolate::GetCurrent();
  Local<Float64Array> arr = Float64Array::New(ArrayBuffer::New(iso, 48), 0, 6);
  Napi::TypedArrayContents<double> dest(arr);
  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;
  Napi::Value argv[argc] = { arr };
  return Napi::NewInstance(context->_DOMMatrix.Get(iso), argc, argv);
}
/*
 * Helper for get/set transform.
 */
void parse_matrix_from_object(cairo_matrix_t &matrix, Napi::Object mat) {
  cairo_matrix_init(&matrix,
    (mat).Get(Napi::String::New(env, "a".As<Napi::Number>().DoubleValue())).FromMaybe(0),
    (mat).Get(Napi::String::New(env, "b".As<Napi::Number>().DoubleValue())).FromMaybe(0),
    (mat).Get(Napi::String::New(env, "c".As<Napi::Number>().DoubleValue())).FromMaybe(0),
    (mat).Get(Napi::String::New(env, "d".As<Napi::Number>().DoubleValue())).FromMaybe(0),
    (mat).Get(Napi::String::New(env, "e".As<Napi::Number>().DoubleValue())).FromMaybe(0),
    (mat).Get(Napi::String::New(env, "f".As<Napi::Number>().DoubleValue())).FromMaybe(0)
  );
}
/*
 * Get current transform.
 */
Napi::Value Context2d::GetCurrentTransform(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetCurrentTransform);
  Context2d *context = info.This().Unwrap<Context2d>();
  Napi::Object instance = get_current_transform(context);
  return instance;
}
/*
 * Set current transform.
 */
void Context2d::SetCurrentTransform(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetCurrentTransform);
  Context2d *context = info.This().Unwrap<Context2d>();
  Local<Context> ctx = Napi::GetCurrentContext();
  Napi::Object mat = value.To<Napi::Object>();
#if NODE_MAJOR_VERSION >= 8
  if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) {
    Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException();
    return env.Null();
  }
#endif
  cairo_matrix_t matrix;
  parse_matrix_from_object(matrix, mat);
  cairo_transform(context->context(), &matrix);
}
/*
 * Get current fill style.
 */
Napi::Value Context2d::GetFillStyle(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetFillStyle);
  Context2d *context = info.This().Unwrap<Context2d>();
  Isolate *iso = Isolate::GetCurrent();
  Napi::Value style;
  if (context->_fillStyle.IsEmpty())
    style = context->_getFillColor();
  else
    style = context->_fillStyle.Get(iso);
  return style;
}
/*
 * Set current fill style.
 */
void Context2d::SetFillStyle(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetFillStyle);
  Context2d *context = info.This().Unwrap<Context2d>();
  if (value.IsString()) {
    MaybeNapi::String mstr = value.To<Napi::String>();
    if (mstr.IsEmpty()) return;
    Napi::String str = mstr;
    context->_fillStyle.Reset();
    context->_setFillColor(str);
  } else if (value.IsObject()) {
    Napi::Object obj = value.To<Napi::Object>();
    if (Napi::New(env, Gradient::constructor)->HasInstance(obj)) {
      context->_fillStyle.Reset(value);
      Gradient *grad = obj.Unwrap<Gradient>();
      context->state->fillGradient = grad->pattern();
    } else if (Napi::New(env, Pattern::constructor)->HasInstance(obj)) {
      context->_fillStyle.Reset(value);
      Pattern *pattern = obj.Unwrap<Pattern>();
      context->state->fillPattern = pattern->pattern();
    }
  }
}
/*
 * Get current stroke style.
 */
Napi::Value Context2d::GetStrokeStyle(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetStrokeStyle);
  Context2d *context = info.This().Unwrap<Context2d>();
  Napi::Value style;
  if (context->_strokeStyle.IsEmpty())
    style = context->_getStrokeColor();
  else
    style = context->_strokeStyle.Get(Isolate::GetCurrent());
  return style;
}
/*
 * Set current stroke style.
 */
void Context2d::SetStrokeStyle(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetStrokeStyle);
  Context2d *context = info.This().Unwrap<Context2d>();
  if (value.IsString()) {
    MaybeNapi::String mstr = value.To<Napi::String>();
    if (mstr.IsEmpty()) return;
    Napi::String str = mstr;
    context->_strokeStyle.Reset();
    context->_setStrokeColor(str);
  } else if (value.IsObject()) {
    Napi::Object obj = value.To<Napi::Object>();
    if (Napi::New(env, Gradient::constructor)->HasInstance(obj)) {
      context->_strokeStyle.Reset(value);
      Gradient *grad = obj.Unwrap<Gradient>();
      context->state->strokeGradient = grad->pattern();
    } else if (Napi::New(env, Pattern::constructor)->HasInstance(obj)) {
      context->_strokeStyle.Reset(value);
      Pattern *pattern = obj.Unwrap<Pattern>();
      context->state->strokePattern = pattern->pattern();
    }
  }
}
/*
 * Get miter limit.
 */
Napi::Value Context2d::GetMiterLimit(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetMiterLimit);
  Context2d *context = info.This().Unwrap<Context2d>();
  return Napi::Number::New(env, cairo_get_miter_limit(context->context()));
}
/*
 * Set miter limit.
 */
void Context2d::SetMiterLimit(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetMiterLimit);
  double n = value.As<Napi::Number>().DoubleValue().FromMaybe(0);
  if (n > 0) {
    Context2d *context = info.This().Unwrap<Context2d>();
    cairo_set_miter_limit(context->context(), n);
  }
}
/*
 * Get line width.
 */
Napi::Value Context2d::GetLineWidth(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetLineWidth);
  Context2d *context = info.This().Unwrap<Context2d>();
  return Napi::Number::New(env, cairo_get_line_width(context->context()));
}
/*
 * Set line width.
 */
void Context2d::SetLineWidth(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetLineWidth);
  double n = value.As<Napi::Number>().DoubleValue().FromMaybe(0);
  if (n > 0 && n != std::numeric_limits<double>::infinity()) {
    Context2d *context = info.This().Unwrap<Context2d>();
    cairo_set_line_width(context->context(), n);
  }
}
/*
 * Get line join.
 */
Napi::Value Context2d::GetLineJoin(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetLineJoin);
  Context2d *context = info.This().Unwrap<Context2d>();
  const char *join;
  switch (cairo_get_line_join(context->context())) {
    case CAIRO_LINE_JOIN_BEVEL: join = "bevel"; break;
    case CAIRO_LINE_JOIN_ROUND: join = "round"; break;
    default: join = "miter";
  }
  return Napi::New(env, join);
}
/*
 * Set line join.
 */
void Context2d::SetLineJoin(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetLineJoin);
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  std::string type = value.As<Napi::String>(.To<Napi::String>());
  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);
  }
}
/*
 * Get line cap.
 */
Napi::Value Context2d::GetLineCap(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetLineCap);
  Context2d *context = info.This().Unwrap<Context2d>();
  const char *cap;
  switch (cairo_get_line_cap(context->context())) {
    case CAIRO_LINE_CAP_ROUND: cap = "round"; break;
    case CAIRO_LINE_CAP_SQUARE: cap = "square"; break;
    default: cap = "butt";
  }
  return Napi::New(env, cap);
}
/*
 * Set line cap.
 */
void Context2d::SetLineCap(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetLineCap);
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  std::string type = value.As<Napi::String>(.To<Napi::String>());
  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);
  }
}
/*
 * Check if the given point is within the current path.
 */
Napi::Value Context2d::IsPointInPath(const Napi::CallbackInfo& info) {
  if (info[0].IsNumber() && info[1].IsNumber()) {
    Context2d *context = info.This().Unwrap<Context2d>();
    cairo_t *ctx = context->context();
    double x = info[0].As<Napi::Number>().DoubleValue().FromMaybe(0)
         , y = info[1].As<Napi::Number>().DoubleValue().FromMaybe(0);
    context->setFillRule(info[2]);
    return Napi::Boolean::New(env, cairo_in_fill(ctx, x, y) || cairo_in_stroke(ctx, x, y));
    return;
  }
  return env.False();
}
/*
 * Set shadow color.
 */
void Context2d::SetShadowColor(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetShadowColor);
  short ok;
  std::string str = value.As<Napi::String>(.To<Napi::String>());
  uint32_t rgba = rgba_from_string(*str, &ok);
  if (ok) {
    Context2d *context = info.This().Unwrap<Context2d>();
    context->state->shadow = rgba_create(rgba);
  }
}
/*
 * Get shadow color.
 */
Napi::Value Context2d::GetShadowColor(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetShadowColor);
  char buf[64];
  Context2d *context = info.This().Unwrap<Context2d>();
  rgba_to_string(context->state->shadow, buf, sizeof(buf));
  return Napi::String::New(env, buf);
}
/*
 * Set fill color, used internally for fillStyle=
 */
void Context2d::_setFillColor(Napi::Value arg) {
  Napi::Env env = arg.Env();
  short ok;
  std::string str = arg.As<Napi::String>();
  uint32_t rgba = rgba_from_string(*str, &ok);
  if (!ok) return;
  state->fillPattern = state->fillGradient = NULL;
  state->fill = rgba_create(rgba);
}
/*
 * Get fill color.
 */
Napi::Value Context2d::_getFillColor() {
  char buf[64];
  rgba_to_string(state->fill, buf, sizeof(buf));
  return Napi::String::New(env, buf);
}
/*
 * Set stroke color, used internally for strokeStyle=
 */
void Context2d::_setStrokeColor(Napi::Value arg) {
  Napi::Env env = arg.Env();
  short ok;
  std::string str = arg.As<Napi::String>();
  uint32_t rgba = rgba_from_string(*str, &ok);
  if (!ok) return;
  state->strokePattern = state->strokeGradient = NULL;
  state->stroke = rgba_create(rgba);
}
/*
 * Get stroke color.
 */
Napi::Value Context2d::_getStrokeColor() {
  char buf[64];
  rgba_to_string(state->stroke, buf, sizeof(buf));
  return Napi::String::New(env, buf);
}
Napi::Value Context2d::CreatePattern(const Napi::CallbackInfo& info) {
  Napi::Value image = info[0];
  Napi::Value repetition = info[1];
  if (!repetition.As<Napi::Boolean>().Value().FromMaybe(false))
    repetition = Napi::String::New(env, "repeat");
  const int argc = 2;
  Napi::Value argv[argc] = { image, repetition };
  Napi::Function ctor = Napi::GetFunction(Napi::New(env, Pattern::constructor));
  Napi::Object instance = Napi::NewInstance(ctor, argc, argv);
  return instance;
}
Napi::Value Context2d::CreateLinearGradient(const Napi::CallbackInfo& info) {
  const int argc = 4;
  Napi::Value argv[argc] = { info[0], info[1], info[2], info[3] };
  Napi::Function ctor = Napi::GetFunction(Napi::New(env, Gradient::constructor));
  Napi::Object instance = Napi::NewInstance(ctor, argc, argv);
  return instance;
}
Napi::Value Context2d::CreateRadialGradient(const Napi::CallbackInfo& info) {
  const int argc = 6;
  Napi::Value argv[argc] = { info[0], info[1], info[2], info[3], info[4], info[5] };
  Napi::Function ctor = Napi::GetFunction(Napi::New(env, Gradient::constructor));
  Napi::Object instance = Napi::NewInstance(ctor, argc, argv);
  return instance;
}
/*
 * Bezier curve.
 */
Napi::Value Context2d::BezierCurveTo(const Napi::CallbackInfo& info) {
  double args[6];
  if(!checkArgs(info, args, 6))
    return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_curve_to(context->context()
    , args[0]
    , args[1]
    , args[2]
    , args[3]
    , args[4]
    , args[5]);
}
/*
 * Quadratic curve approximation from libsvg-cairo.
 */
Napi::Value Context2d::QuadraticCurveTo(const Napi::CallbackInfo& info) {
  double args[4];
  if(!checkArgs(info, args, 4))
    return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  double x, y
    , x1 = args[0]
    , y1 = args[1]
    , x2 = args[2]
    , y2 = args[3];
  cairo_get_current_point(ctx, &x, &y);
  if (0 == x && 0 == y) {
    x = x1;
    y = y1;
  }
  cairo_curve_to(ctx
    , x  + 2.0 / 3.0 * (x1 - x),  y  + 2.0 / 3.0 * (y1 - y)
    , x2 + 2.0 / 3.0 * (x1 - x2), y2 + 2.0 / 3.0 * (y1 - y2)
    , x2
    , y2);
}
/*
 * Save state.
 */
Napi::Value Context2d::Save(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  context->save();
}
/*
 * Restore state.
 */
Napi::Value Context2d::Restore(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  context->restore();
}
/*
 * Creates a new subpath.
 */
Napi::Value Context2d::BeginPath(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_new_path(context->context());
}
/*
 * Marks the subpath as closed.
 */
Napi::Value Context2d::ClosePath(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_close_path(context->context());
}
/*
 * Rotate transformation.
 */
Napi::Value Context2d::Rotate(const Napi::CallbackInfo& info) {
  double args[1];
  if(!checkArgs(info, args, 1))
    return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_rotate(context->context(), args[0]);
}
/*
 * Modify the CTM.
 */
Napi::Value Context2d::Transform(const Napi::CallbackInfo& info) {
  double args[6];
  if(!checkArgs(info, args, 6))
    return;
  cairo_matrix_t matrix;
  cairo_matrix_init(&matrix
    , args[0]
    , args[1]
    , args[2]
    , args[3]
    , args[4]
    , args[5]);
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_transform(context->context(), &matrix);
}
/*
 * Get the CTM
 */
Napi::Value Context2d::GetTransform(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  Napi::Object instance = get_current_transform(context);
  return instance;
}
/*
 * Reset the CTM, used internally by setTransform().
 */
Napi::Value Context2d::ResetTransform(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_identity_matrix(context->context());
}
/*
 * Reset transform matrix to identity, then apply the given args.
 */
Napi::Value Context2d::SetTransform(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  if (info.Length() == 1) {
    Napi::Object mat = info[0].To<Napi::Object>();
    #if NODE_MAJOR_VERSION >= 8
      Local<Context> ctx = Napi::GetCurrentContext();
      if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) {
        Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException();
        return env.Null();
      }
    #endif
    cairo_matrix_t matrix;
    parse_matrix_from_object(matrix, mat);
    cairo_set_matrix(context->context(), &matrix);
  } else {
    cairo_identity_matrix(context->context());
    Context2d::Transform(info);
  }
}
/*
 * Translate transformation.
 */
Napi::Value Context2d::Translate(const Napi::CallbackInfo& info) {
  double args[2];
  if(!checkArgs(info, args, 2))
    return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_translate(context->context(), args[0], args[1]);
}
/*
 * Scale transformation.
 */
Napi::Value Context2d::Scale(const Napi::CallbackInfo& info) {
  double args[2];
  if(!checkArgs(info, args, 2))
    return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_scale(context->context(), args[0], args[1]);
}
/*
 * Use path as clipping region.
 */
Napi::Value Context2d::Clip(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  context->setFillRule(info[0]);
  cairo_t *ctx = context->context();
  cairo_clip_preserve(ctx);
}
/*
 * Fill the path.
 */
Napi::Value Context2d::Fill(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  context->setFillRule(info[0]);
  context->fill(true);
}
/*
 * Stroke the path.
 */
Napi::Value Context2d::Stroke(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  context->stroke(true);
}
/*
 * Helper for fillText/strokeText
 */
double
get_text_scale(PangoLayout *layout, double maxWidth) {
  PangoRectangle logical_rect;
  pango_layout_get_pixel_extents(layout, NULL, &logical_rect);
  if (logical_rect.width > maxWidth) {
    return maxWidth / logical_rect.width;
  } else {
    return 1.0;
  }
}
void
paintText(const Napi::CallbackInfo&info, bool stroke) {
  int argsNum = info.Length() >= 4 ? 3 : 2;
  if (argsNum == 3 && info[3].IsUndefined())
    argsNum = 2;
  double args[3];
  if(!checkArgs(info, args, argsNum, 1))
    return;
  std::string str = info[0].As<Napi::String>(.To<Napi::String>());
  double x = args[0];
  double y = args[1];
  double scaled_by = 1;
  Context2d *context = info.This().Unwrap<Context2d>();
  PangoLayout *layout = context->layout();
  pango_layout_set_text(layout, *str, -1);
  pango_cairo_update_layout(context->context(), layout);
  if (argsNum == 3) {
    scaled_by = get_text_scale(layout, args[2]);
    cairo_save(context->context());
    cairo_scale(context->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(); }
  }
  context->restorePath();
  if (argsNum == 3) {
    cairo_restore(context->context());
  }
}
/*
 * Fill text at (x, y).
 */
Napi::Value Context2d::FillText(const Napi::CallbackInfo& info) {
  paintText(info, false);
}
/*
 * Stroke text at (x ,y).
 */
Napi::Value Context2d::StrokeText(const Napi::CallbackInfo& info) {
  paintText(info, true);
}
/*
 * Gets the baseline adjustment in device pixels
 */
inline double getBaselineAdjustment(PangoLayout* layout, short baseline) {
  PangoRectangle logical_rect;
  pango_layout_line_get_extents(pango_layout_get_line(layout, 0), NULL, &logical_rect);
  double scale = 1.0 / PANGO_SCALE;
  double ascent = scale * pango_layout_get_baseline(layout);
  double descent = scale * logical_rect.height - ascent;
  switch (baseline) {
  case TEXT_BASELINE_ALPHABETIC:
    return ascent;
  case TEXT_BASELINE_MIDDLE:
    return (ascent + descent) / 2.0;
  case TEXT_BASELINE_BOTTOM:
    return ascent + descent;
  default:
    return 0;
  }
}
/*
 * Set text path for the string in the layout at (x, y).
 * This function is called by paintText and won't behave correctly
 * if is not called from there.
 * it needs pango_layout_set_text and pango_cairo_update_layout to be called before
 */
void
Context2d::setTextPath(double x, double y) {
  PangoRectangle logical_rect;
  switch (state->textAlignment) {
    case TEXT_ALIGNMENT_CENTER:
      pango_layout_get_pixel_extents(_layout, NULL, &logical_rect);
      x -= logical_rect.width / 2;
      break;
    case TEXT_ALIGNMENT_END:
    case TEXT_ALIGNMENT_RIGHT:
      pango_layout_get_pixel_extents(_layout, NULL, &logical_rect);
      x -= logical_rect.width;
      break;
  }
  y -= getBaselineAdjustment(_layout, state->textBaseline);
  cairo_move_to(_context, x, y);
  if (state->textDrawingMode == TEXT_DRAW_PATHS) {
    pango_cairo_layout_path(_context, _layout);
  } else if (state->textDrawingMode == TEXT_DRAW_GLYPHS) {
    pango_cairo_show_layout(_context, _layout);
  }
}
/*
 * Adds a point to the current subpath.
 */
Napi::Value Context2d::LineTo(const Napi::CallbackInfo& info) {
  double args[2];
  if(!checkArgs(info, args, 2))
    return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_line_to(context->context(), args[0], args[1]);
}
/*
 * Creates a new subpath at the given point.
 */
Napi::Value Context2d::MoveTo(const Napi::CallbackInfo& info) {
  double args[2];
  if(!checkArgs(info, args, 2))
    return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_move_to(context->context(), args[0], args[1]);
}
/*
 * Get font.
 */
Napi::Value Context2d::GetFont(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetFont);
  Context2d *context = info.This().Unwrap<Context2d>();
  return Napi::New(env, context->state->font);
}
/*
 * Set font:
 *   - weight
 *   - style
 *   - size
 *   - unit
 *   - family
 */
void Context2d::SetFont(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetFont);
  if (!value.IsString()) return;
  Isolate *iso = Isolate::GetCurrent();
  Local<Context> ctx = Napi::GetCurrentContext();
  Napi::String str = value.To<Napi::String>();
  if (!str->Length()) return;
  const int argc = 1;
  Napi::Value argv[argc] = { value };
  Napi::Value mparsed = Napi::Call(_parseFont.Get(iso), ctx->Global(), argc, argv);
  // parseFont returns undefined for invalid CSS font strings
  if (mparsed->IsUndefined()) return;
  Napi::Object font = mparsed.To<Napi::Object>();
  std::string weight = (font).Get(Napi::New(env, "weight".As<Napi::String>()));
  std::string style = (font).Get(Napi::New(env, "style".As<Napi::String>()));
  double size = (font).Get(Napi::String::New(env, "size".As<Napi::Number>().DoubleValue())).FromMaybe(0);
  std::string unit = (font).Get(Napi::New(env, "unit".As<Napi::String>()));
  std::string family = (font).Get(Napi::New(env, "family".As<Napi::String>()));
  Context2d *context = info.This().Unwrap<Context2d>();
  PangoFontDescription *desc = pango_font_description_copy(context->state->fontDescription);
  pango_font_description_free(context->state->fontDescription);
  pango_font_description_set_style(desc, Canvas::GetStyleFromCSSString(*style));
  pango_font_description_set_weight(desc, Canvas::GetWeightFromCSSString(*weight));
  if (strlen(*family) > 0) {
    // See #1643 - Pango understands "sans" whereas CSS uses "sans-serif"
    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);
    }
  }
  PangoFontDescription *sys_desc = Canvas::ResolveFontDescription(desc);
  pango_font_description_free(desc);
  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);
  context->state->font = value->As<Napi::String>().Utf8Value().c_str();
}
/*
 * Get text baseline.
 */
Napi::Value Context2d::GetTextBaseline(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetTextBaseline);
  Context2d *context = info.This().Unwrap<Context2d>();
  const char* baseline;
  switch (context->state->textBaseline) {
    default:
    case TEXT_BASELINE_ALPHABETIC: baseline = "alphabetic"; break;
    case TEXT_BASELINE_TOP: baseline = "top"; break;
    case TEXT_BASELINE_BOTTOM: baseline = "bottom"; break;
    case TEXT_BASELINE_MIDDLE: baseline = "middle"; break;
    case TEXT_BASELINE_IDEOGRAPHIC: baseline = "ideographic"; break;
    case TEXT_BASELINE_HANGING: baseline = "hanging"; break;
  }
  return Napi::New(env, baseline);
}
/*
 * Set text baseline.
 */
void Context2d::SetTextBaseline(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetTextBaseline);
  if (!value.IsString()) return;
  std::string opStr = value.As<Napi::String>(.To<Napi::String>());
  const std::map<std::string, text_baseline_t> modes = {
    {"alphabetic", TEXT_BASELINE_ALPHABETIC},
    {"top", TEXT_BASELINE_TOP},
    {"bottom", TEXT_BASELINE_BOTTOM},
    {"middle", TEXT_BASELINE_MIDDLE},
    {"ideographic", TEXT_BASELINE_IDEOGRAPHIC},
    {"hanging", TEXT_BASELINE_HANGING}
  };
  auto op = modes.find(*opStr);
  if (op == modes.end()) return;
  Context2d *context = info.This().Unwrap<Context2d>();
  context->state->textBaseline = op->second;
}
/*
 * Get text align.
 */
Napi::Value Context2d::GetTextAlign(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetTextAlign);
  Context2d *context = info.This().Unwrap<Context2d>();
  const char* align;
  switch (context->state->textAlignment) {
    default:
    // TODO the default is supposed to be "start"
    case TEXT_ALIGNMENT_LEFT: align = "left"; break;
    case TEXT_ALIGNMENT_START: align = "start"; break;
    case TEXT_ALIGNMENT_CENTER: align = "center"; break;
    case TEXT_ALIGNMENT_RIGHT: align = "right"; break;
    case TEXT_ALIGNMENT_END: align = "end"; break;
  }
  return Napi::New(env, align);
}
/*
 * Set text align.
 */
void Context2d::SetTextAlign(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetTextAlign);
  if (!value.IsString()) return;
  std::string opStr = value.As<Napi::String>(.To<Napi::String>());
  const std::map<std::string, text_align_t> modes = {
    {"center", TEXT_ALIGNMENT_CENTER},
    {"left", TEXT_ALIGNMENT_LEFT},
    {"start", TEXT_ALIGNMENT_START},
    {"right", TEXT_ALIGNMENT_RIGHT},
    {"end", TEXT_ALIGNMENT_END}
  };
  auto op = modes.find(*opStr);
  if (op == modes.end()) return;
  Context2d *context = info.This().Unwrap<Context2d>();
  context->state->textAlignment = op->second;
}
/*
 * Return the given text extents.
 * TODO: Support for:
 * hangingBaseline, ideographicBaseline,
 * fontBoundingBoxAscent, fontBoundingBoxDescent
 */
Napi::Value Context2d::MeasureText(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  std::string str = info[0].As<Napi::String>(.To<Napi::String>());
  Napi::Object obj = Napi::Object::New(env);
  PangoRectangle _ink_rect, _logical_rect;
  float_rectangle ink_rect, logical_rect;
  PangoFontMetrics *metrics;
  PangoLayout *layout = context->layout();
  pango_layout_set_text(layout, *str, -1);
  pango_cairo_update_layout(ctx, layout);
  // Normally you could use pango_layout_get_pixel_extents and be done, or use
  // pango_extents_to_pixels, but both of those round the pixels, so we have to
  // divide by PANGO_SCALE manually
  pango_layout_get_extents(layout, &_ink_rect, &_logical_rect);
  float inverse_pango_scale = 1. / PANGO_SCALE;
  logical_rect.x = _logical_rect.x * inverse_pango_scale;
  logical_rect.y = _logical_rect.y * inverse_pango_scale;
  logical_rect.width = _logical_rect.width * inverse_pango_scale;
  logical_rect.height = _logical_rect.height * inverse_pango_scale;
  ink_rect.x = _ink_rect.x * inverse_pango_scale;
  ink_rect.y = _ink_rect.y * inverse_pango_scale;
  ink_rect.width = _ink_rect.width * inverse_pango_scale;
  ink_rect.height = _ink_rect.height * inverse_pango_scale;
  metrics = PANGO_LAYOUT_GET_METRICS(layout);
  double x_offset;
  switch (context->state->textAlignment) {
    case TEXT_ALIGNMENT_CENTER:
      x_offset = logical_rect.width / 2.;
      break;
    case TEXT_ALIGNMENT_END:
    case TEXT_ALIGNMENT_RIGHT:
      x_offset = logical_rect.width;
      break;
    case TEXT_ALIGNMENT_START:
    case TEXT_ALIGNMENT_LEFT:
    default:
      x_offset = 0.0;
  }
  cairo_matrix_t matrix;
  cairo_get_matrix(ctx, &matrix);
  double y_offset = getBaselineAdjustment(layout, context->state->textBaseline);
  (obj).Set(Napi::String::New(env, "width"),
           Napi::Number::New(env, logical_rect.width)).Check();
  (obj).Set(Napi::String::New(env, "actualBoundingBoxLeft"),
           Napi::Number::New(env, PANGO_LBEARING(ink_rect) + x_offset)).Check();
  (obj).Set(Napi::String::New(env, "actualBoundingBoxRight"),
           Napi::Number::New(env, PANGO_RBEARING(ink_rect) - x_offset)).Check();
  (obj).Set(Napi::String::New(env, "actualBoundingBoxAscent"),
           Napi::Number::New(env, y_offset + PANGO_ASCENT(ink_rect))).Check();
  (obj).Set(Napi::String::New(env, "actualBoundingBoxDescent"),
           Napi::Number::New(env, PANGO_DESCENT(ink_rect) - y_offset)).Check();
  (obj).Set(Napi::String::New(env, "emHeightAscent"),
           Napi::Number::New(env, -(PANGO_ASCENT(logical_rect) - y_offset))).Check();
  (obj).Set(Napi::String::New(env, "emHeightDescent"),
           Napi::Number::New(env, PANGO_DESCENT(logical_rect) - y_offset)).Check();
  (obj).Set(Napi::String::New(env, "alphabeticBaseline"),
           Napi::Number::New(env, -(pango_font_metrics_get_ascent(metrics) * inverse_pango_scale - y_offset))).Check();
  pango_font_metrics_unref(metrics);
  return obj;
}
/*
 * Set line dash
 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
 */
Napi::Value Context2d::SetLineDash(const Napi::CallbackInfo& info) {
  if (!info[0].IsArray()) return;
  Napi::Array dash = info[0].As<Napi::Array>();
  uint32_t dashes = dash->Length() & 1 ? dash->Length() * 2 : dash->Length();
  uint32_t zero_dashes = 0;
  std::vector<double> a(dashes);
  for (uint32_t i=0; i<dashes; i++) {
    Napi::Value d = (dash).Get(i % dash->Length());
    if (!d.IsNumber()) return;
    a[i] = d.As<Napi::Number>().DoubleValue().FromMaybe(0);
    if (a[i] == 0) zero_dashes++;
    if (a[i] < 0 || !std::isfinite(a[i])) return;
  }
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  double offset;
  cairo_get_dash(ctx, NULL, &offset);
  if (zero_dashes == dashes) {
    std::vector<double> b(0);
    cairo_set_dash(ctx, b.data(), 0, offset);
  } else {
    cairo_set_dash(ctx, a.data(), dashes, offset);
  }
}
/*
 * Get line dash
 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
 */
Napi::Value Context2d::GetLineDash(const Napi::CallbackInfo& info) {
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  int dashes = cairo_get_dash_count(ctx);
  std::vector<double> a(dashes);
  cairo_get_dash(ctx, a.data(), NULL);
  Napi::Array dash = Napi::Array::New(env, dashes);
  for (int i=0; i<dashes; i++) {
    (dash).Set(Napi::Number::New(env, i), Napi::Number::New(env, a[i])).Check();
  }
  return dash;
}
/*
 * Set line dash offset
 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
 */
void Context2d::SetLineDashOffset(const Napi::CallbackInfo& info, const Napi::Value& value) {
  CHECK_RECEIVER(Context2d.SetLineDashOffset);
  double offset = value.As<Napi::Number>().DoubleValue().FromMaybe(0);
  if (!std::isfinite(offset)) return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  int dashes = cairo_get_dash_count(ctx);
  std::vector<double> a(dashes);
  cairo_get_dash(ctx, a.data(), NULL);
  cairo_set_dash(ctx, a.data(), dashes, offset);
}
/*
 * Get line dash offset
 * ref: http://www.w3.org/TR/2dcontext/#dom-context-2d-setlinedash
 */
Napi::Value Context2d::GetLineDashOffset(const Napi::CallbackInfo& info) {
  CHECK_RECEIVER(Context2d.GetLineDashOffset);
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  double offset;
  cairo_get_dash(ctx, NULL, &offset);
  return Napi::Number::New(env, offset);
}
/*
 * Fill the rectangle defined by x, y, width and height.
 */
Napi::Value Context2d::FillRect(const Napi::CallbackInfo& info) {
  RECT_ARGS;
  if (0 == width || 0 == height) return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  context->savePath();
  cairo_rectangle(ctx, x, y, width, height);
  context->fill();
  context->restorePath();
}
/*
 * Stroke the rectangle defined by x, y, width and height.
 */
Napi::Value Context2d::StrokeRect(const Napi::CallbackInfo& info) {
  RECT_ARGS;
  if (0 == width && 0 == height) return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  context->savePath();
  cairo_rectangle(ctx, x, y, width, height);
  context->stroke();
  context->restorePath();
}
/*
 * Clears all pixels defined by x, y, width and height.
 */
Napi::Value Context2d::ClearRect(const Napi::CallbackInfo& info) {
  RECT_ARGS;
  if (0 == width || 0 == height) return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  cairo_save(ctx);
  context->savePath();
  cairo_rectangle(ctx, x, y, width, height);
  cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR);
  cairo_fill(ctx);
  context->restorePath();
  cairo_restore(ctx);
}
/*
 * Adds a rectangle subpath.
 */
Napi::Value Context2d::Rect(const Napi::CallbackInfo& info) {
  RECT_ARGS;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  if (width == 0) {
    cairo_move_to(ctx, x, y);
    cairo_line_to(ctx, x, y + height);
  } else if (height == 0) {
    cairo_move_to(ctx, x, y);
    cairo_line_to(ctx, x + width, y);
  } else {
    cairo_rectangle(ctx, x, y, width, height);
  }
}
// Draws an arc with two potentially different radii.
inline static
void elli_arc(cairo_t* ctx, double xc, double yc, double rx, double ry, double a1, double a2, bool clockwise=true) {
  if (rx == 0. || ry == 0.) {
    cairo_line_to(ctx, xc + rx, yc + ry);
  } else {
    cairo_save(ctx);
    cairo_translate(ctx, xc, yc);
    cairo_scale(ctx, rx, ry);
    if (clockwise)
      cairo_arc(ctx, 0., 0., 1., a1, a2);
    else
      cairo_arc_negative(ctx, 0., 0., 1., a2, a1);
    cairo_restore(ctx);
  }
}
inline static
bool getRadius(Point<double>& p, const Napi::Value& v) {
  if (v.IsObject()) { // 5.1 DOMPointInit
    auto rx = (v.As<Napi::Object>()).Get(Napi::String::New(env, "x"));
    auto ry = (v.As<Napi::Object>()).Get(Napi::String::New(env, "y"));
    if (rx.IsNumber() && ry.IsNumber()) {
      auto rxv = rx.As<Napi::Number>().DoubleValue();
      auto ryv = ry.As<Napi::Number>().DoubleValue();
      if (!std::isfinite(rxv) || !std::isfinite(ryv))
        return true;
      if (rxv < 0 || ryv < 0) {
        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 = v.As<Napi::Number>().DoubleValue();
    if (!std::isfinite(rv))
      return true;
    if (rv < 0) {
      Napi::RangeError::New(env, "radii must be positive.").ThrowAsJavaScriptException();
      return true;
    }
    p.x = p.y = rv;
    return false;
  }
  return true;
}
/**
 * https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
 * x, y, w, h, [radius|[radii]]
 */
Napi::Value Context2d::RoundRect(const Napi::CallbackInfo& info) {
  RECT_ARGS;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  // 4. Let normalizedRadii be an empty list
  Point<double> normalizedRadii[4];
  size_t nRadii = 4;
  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<Napi::Array>();
    nRadii = radiiList->Length();
    if (!(nRadii >= 1 && nRadii <= 4)) {
      Napi::RangeError::New(env, "radii must be a list of one, two, three or four radii.").ThrowAsJavaScriptException();
      return env.Null();
    }
    // 5. For each radius of radii
    for (size_t i = 0; i < nRadii; i++) {
      auto r = (radiiList).Get(i);
      if (getRadius(normalizedRadii[i], r))
        return;
    }
  } else {
    // 2. If radii is a double, then set radii to <<radii>>
    if (getRadius(normalizedRadii[0], info[4]))
      return;
    for (size_t i = 1; i < 4; i++) {
      normalizedRadii[i].x = normalizedRadii[0].x;
      normalizedRadii[i].y = normalizedRadii[0].y;
    }
  }
  Point<double> upperLeft, upperRight, lowerRight, lowerLeft;
  if (nRadii == 4) {
    upperLeft = normalizedRadii[0];
    upperRight = normalizedRadii[1];
    lowerRight = normalizedRadii[2];
    lowerLeft = normalizedRadii[3];
  } else if (nRadii == 3) {
    upperLeft = normalizedRadii[0];
    upperRight = normalizedRadii[1];
    lowerLeft = normalizedRadii[1];
    lowerRight = normalizedRadii[2];
  } else if (nRadii == 2) {
    upperLeft = normalizedRadii[0];
    lowerRight = normalizedRadii[0];
    upperRight = normalizedRadii[1];
    lowerLeft = normalizedRadii[1];
  } else {
    upperLeft = normalizedRadii[0];
    upperRight = normalizedRadii[0];
    lowerRight = normalizedRadii[0];
    lowerLeft = normalizedRadii[0];
  }
  bool clockwise = true;
  if (width < 0) {
    clockwise = false;
    x += width;
    width = -width;
    std::swap(upperLeft, upperRight);
    std::swap(lowerLeft, lowerRight);
  }
  if (height < 0) {
    clockwise = !clockwise;
    y += height;
    height = -height;
    std::swap(upperLeft, lowerLeft);
    std::swap(upperRight, lowerRight);
  }
  // 11. Corner curves must not overlap. Scale radii to prevent this.
  {
    auto top = upperLeft.x + upperRight.x;
    auto right = upperRight.y + lowerRight.y;
    auto bottom = lowerRight.x + lowerLeft.x;
    auto left = upperLeft.y + lowerLeft.y;
    auto scale = std::min({ width / top, height / right, width / bottom, height / left });
    if (scale < 1.) {
      upperLeft.x *= scale;
      upperLeft.y *= scale;
      upperRight.x *= scale;
      upperRight.x *= scale;
      lowerLeft.y *= scale;
      lowerLeft.y *= scale;
      lowerRight.y *= scale;
      lowerRight.y *= scale;
    }
  }
  // 12. Draw
  cairo_move_to(ctx, x + upperLeft.x, y);
  if (clockwise) {
    cairo_line_to(ctx, x + width - upperRight.x, y);
    elli_arc(ctx, x + width - upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 3. * M_PI / 2., 0.);
    cairo_line_to(ctx, x + width, y + height - lowerRight.y);
    elli_arc(ctx, x + width - lowerRight.x, y + height - lowerRight.y, lowerRight.x, lowerRight.y, 0, M_PI / 2.);
    cairo_line_to(ctx, x + lowerLeft.x, y + height);
    elli_arc(ctx, x + lowerLeft.x, y + height - lowerLeft.y, lowerLeft.x, lowerLeft.y, M_PI / 2., M_PI);
    cairo_line_to(ctx, x, y + upperLeft.y);
    elli_arc(ctx, x + upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, M_PI, 3. * M_PI / 2.);
  } else {
    elli_arc(ctx, x + upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, M_PI, 3. * M_PI / 2., false);
    cairo_line_to(ctx, x, y + upperLeft.y);
    elli_arc(ctx, x + lowerLeft.x, y + height - lowerLeft.y, lowerLeft.x, lowerLeft.y, M_PI / 2., M_PI, false);
    cairo_line_to(ctx, x + lowerLeft.x, y + height);
    elli_arc(ctx, x + width - lowerRight.x, y + height - lowerRight.y, lowerRight.x, lowerRight.y, 0, M_PI / 2., false);
    cairo_line_to(ctx, x + width, y + height - lowerRight.y);
    elli_arc(ctx, x + width - upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 3. * M_PI / 2., 0., false);
    cairo_line_to(ctx, x + width - upperRight.x, y);
  }
  cairo_close_path(ctx);
}
// Adapted from https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/modules/canvas2d/CanvasPathMethods.cpp
static void canonicalizeAngle(double& startAngle, double& endAngle) {
  // Make 0 <= startAngle < 2*PI
  double newStartAngle = std::fmod(startAngle, twoPi);
  if (newStartAngle < 0) {
      newStartAngle += twoPi;
      // Check for possible catastrophic cancellation in cases where
      // newStartAngle was a tiny negative number (c.f. crbug.com/503422)
      if (newStartAngle >= twoPi)
          newStartAngle -= twoPi;
  }
  double delta = newStartAngle - startAngle;
  startAngle = newStartAngle;
  endAngle = endAngle + delta;
}
// Adapted from https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/modules/canvas2d/CanvasPathMethods.cpp
static double adjustEndAngle(double startAngle, double endAngle, bool counterclockwise) {
  double newEndAngle = endAngle;
  /* http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-arc
  * If the counterclockwise argument is false and endAngle-startAngle is equal to or greater than 2pi, or,
  * if the counterclockwise argument is true and startAngle-endAngle is equal to or greater than 2pi,
  * then the arc is the whole circumference of this ellipse, and the point at startAngle along this circle's circumference,
  * measured in radians clockwise from the ellipse's semi-major axis, acts as both the start point and the end point.
  */
  if (!counterclockwise && endAngle - startAngle >= twoPi)
    newEndAngle = startAngle + twoPi;
  else if (counterclockwise && startAngle - endAngle >= twoPi)
    newEndAngle = startAngle - twoPi;
  /*
  * Otherwise, the arc is the path along the circumference of this ellipse from the start point to the end point,
  * going anti-clockwise if the counterclockwise argument is true, and clockwise otherwise.
  * Since the points are on the ellipse, as opposed to being simply angles from zero,
  * the arc can never cover an angle greater than 2pi radians.
  */
  /* NOTE: When startAngle = 0, endAngle = 2Pi and counterclockwise = true, the spec does not indicate clearly.
  * We draw the entire circle, because some web sites use arc(x, y, r, 0, 2*Math.PI, true) to draw circle.
  * We preserve backward-compatibility.
  */
  else if (!counterclockwise && startAngle > endAngle)
    newEndAngle = startAngle + (twoPi - std::fmod(startAngle - endAngle, twoPi));
  else if (counterclockwise && startAngle < endAngle)
    newEndAngle = startAngle - (twoPi - std::fmod(endAngle - startAngle, twoPi));
  return newEndAngle;
}
/*
 * Adds an arc at x, y with the given radii and start/end angles.
 */
Napi::Value Context2d::Arc(const Napi::CallbackInfo& info) {
  double args[5];
  if(!checkArgs(info, args, 5))
    return;
  auto x = args[0];
  auto y = args[1];
  auto radius = args[2];
  auto startAngle = args[3];
  auto endAngle = args[4];
  if (radius < 0) {
    Napi::RangeError::New(env, "The radius provided is negative.").ThrowAsJavaScriptException();
    return env.Null();
  }
  bool counterclockwise = info[5].As<Napi::Boolean>().Value().FromMaybe(false);
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  canonicalizeAngle(startAngle, endAngle);
  endAngle = adjustEndAngle(startAngle, endAngle, counterclockwise);
  if (counterclockwise) {
    cairo_arc_negative(ctx, x, y, radius, startAngle, endAngle);
  } else {
    cairo_arc(ctx, x, y, radius, startAngle, endAngle);
  }
}
/*
 * Adds an arcTo point (x0,y0) to (x1,y1) with the given radius.
 *
 * Implementation influenced by WebKit.
 */
Napi::Value Context2d::ArcTo(const Napi::CallbackInfo& info) {
  double args[5];
  if(!checkArgs(info, args, 5))
    return;
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  // Current path point
  double x, y;
  cairo_get_current_point(ctx, &x, &y);
  Point<float> p0(x, y);
  // Point (x0,y0)
  Point<float> p1(args[0], args[1]);
  // Point (x1,y1)
  Point<float> p2(args[2], args[3]);
  float radius = args[4];
  if ((p1.x == p0.x && p1.y == p0.y)
    || (p1.x == p2.x && p1.y == p2.y)
    || radius == 0.f) {
    cairo_line_to(ctx, p1.x, p1.y);
    return;
  }
  Point<float> p1p0((p0.x - p1.x),(p0.y - p1.y));
  Point<float> p1p2((p2.x - p1.x),(p2.y - p1.y));
  float p1p0_length = sqrtf(p1p0.x * p1p0.x + p1p0.y * p1p0.y);
  float p1p2_length = sqrtf(p1p2.x * p1p2.x + p1p2.y * p1p2.y);
  double cos_phi = (p1p0.x * p1p2.x + p1p0.y * p1p2.y) / (p1p0_length * p1p2_length);
  // all points on a line logic
  if (-1 == cos_phi) {
    cairo_line_to(ctx, p1.x, p1.y);
    return;
  }
  if (1 == cos_phi) {
    // add infinite far away point
    unsigned int max_length = 65535;
    double factor_max = max_length / p1p0_length;
    Point<float> ep((p0.x + factor_max * p1p0.x), (p0.y + factor_max * p1p0.y));
    cairo_line_to(ctx, ep.x, ep.y);
    return;
  }
  float tangent = radius / tan(acos(cos_phi) / 2);
  float factor_p1p0 = tangent / p1p0_length;
  Point<float> t_p1p0((p1.x + factor_p1p0 * p1p0.x), (p1.y + factor_p1p0 * p1p0.y));
  Point<float> orth_p1p0(p1p0.y, -p1p0.x);
  float orth_p1p0_length = sqrt(orth_p1p0.x * orth_p1p0.x + orth_p1p0.y * orth_p1p0.y);
  float factor_ra = radius / orth_p1p0_length;
  double cos_alpha = (orth_p1p0.x * p1p2.x + orth_p1p0.y * p1p2.y) / (orth_p1p0_length * p1p2_length);
  if (cos_alpha < 0.f)
      orth_p1p0 = Point<float>(-orth_p1p0.x, -orth_p1p0.y);
  Point<float> p((t_p1p0.x + factor_ra * orth_p1p0.x), (t_p1p0.y + factor_ra * orth_p1p0.y));
  orth_p1p0 = Point<float>(-orth_p1p0.x, -orth_p1p0.y);
  float sa = acos(orth_p1p0.x / orth_p1p0_length);
  if (orth_p1p0.y < 0.f)
      sa = 2 * M_PI - sa;
  bool anticlockwise = false;
  float factor_p1p2 = tangent / p1p2_length;
  Point<float> t_p1p2((p1.x + factor_p1p2 * p1p2.x), (p1.y + factor_p1p2 * p1p2.y));
  Point<float> orth_p1p2((t_p1p2.x - p.x),(t_p1p2.y - p.y));
  float orth_p1p2_length = sqrtf(orth_p1p2.x * orth_p1p2.x + orth_p1p2.y * orth_p1p2.y);
  float ea = acos(orth_p1p2.x / orth_p1p2_length);
  if (orth_p1p2.y < 0) ea = 2 * M_PI - ea;
  if ((sa > ea) && ((sa - ea) < M_PI)) anticlockwise = true;
  if ((sa < ea) && ((ea - sa) > M_PI)) anticlockwise = true;
  cairo_line_to(ctx, t_p1p0.x, t_p1p0.y);
  if (anticlockwise && M_PI * 2 != radius) {
    cairo_arc_negative(ctx
      , p.x
      , p.y
      , radius
      , sa
      , ea);
  } else {
    cairo_arc(ctx
      , p.x
      , p.y
      , radius
      , sa
      , ea);
  }
}
/*
 * Adds an ellipse to the path which is centered at (x, y) position with the
 * radii radiusX and radiusY starting at startAngle and ending at endAngle
 * going in the given direction by anticlockwise (defaulting to clockwise).
 */
Napi::Value Context2d::Ellipse(const Napi::CallbackInfo& info) {
  double args[7];
  if(!checkArgs(info, args, 7))
    return;
  double radiusX = args[2];
  double radiusY = args[3];
  if (radiusX == 0 || radiusY == 0) return;
  double x = args[0];
  double y = args[1];
  double rotation = args[4];
  double startAngle = args[5];
  double endAngle = args[6];
  bool anticlockwise = info[7].As<Napi::Boolean>().Value().FromMaybe(false);
  Context2d *context = info.This().Unwrap<Context2d>();
  cairo_t *ctx = context->context();
  // See https://www.cairographics.org/cookbook/ellipses/
  double xRatio = radiusX / radiusY;
  cairo_matrix_t save_matrix;
  cairo_get_matrix(ctx, &save_matrix);
  cairo_translate(ctx, x, y);
  cairo_rotate(ctx, rotation);
  cairo_scale(ctx, xRatio, 1.0);
  cairo_translate(ctx, -x, -y);
  if (anticlockwise && M_PI * 2 != args[4]) {
    cairo_arc_negative(ctx,
      x,
      y,
      radiusY,
      startAngle,
      endAngle);
  } else {
    cairo_arc(ctx,
      x,
      y,
      radiusY,
      startAngle,
      endAngle);
  }
  cairo_set_matrix(ctx, &save_matrix);
}
#undef CHECK_RECEIVER
node_modules/canvas/src/CanvasRenderingContext2d.h
New file
@@ -0,0 +1,225 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include "cairo.h"
#include "Canvas.h"
#include "color.h"
#include "nan.h"
#include <pango/pangocairo.h>
#include <stack>
/*
 * State struct.
 *
 * Used in conjunction with Save() / Restore() since
 * cairo's gstate maintains only a single source pattern at a time.
 */
struct canvas_state_t {
  rgba_t fill = { 0, 0, 0, 1 };
  rgba_t stroke = { 0, 0, 0, 1 };
  rgba_t shadow = { 0, 0, 0, 0 };
  double shadowOffsetX = 0.;
  double shadowOffsetY = 0.;
  cairo_pattern_t* fillPattern = nullptr;
  cairo_pattern_t* strokePattern = nullptr;
  cairo_pattern_t* fillGradient = nullptr;
  cairo_pattern_t* strokeGradient = nullptr;
  PangoFontDescription* fontDescription = nullptr;
  std::string font = "10px sans-serif";
  cairo_filter_t patternQuality = CAIRO_FILTER_GOOD;
  float globalAlpha = 1.f;
  int shadowBlur = 0;
  text_align_t textAlignment = TEXT_ALIGNMENT_LEFT; // TODO default is supposed to be START
  text_baseline_t textBaseline = TEXT_BASELINE_ALPHABETIC;
  canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS;
  bool imageSmoothingEnabled = true;
  canvas_state_t() {
    fontDescription = pango_font_description_from_string("sans");
    pango_font_description_set_absolute_size(fontDescription, 10 * PANGO_SCALE);
  }
  canvas_state_t(const canvas_state_t& other) {
    fill = other.fill;
    stroke = other.stroke;
    patternQuality = other.patternQuality;
    fillPattern = other.fillPattern;
    strokePattern = other.strokePattern;
    fillGradient = other.fillGradient;
    strokeGradient = other.strokeGradient;
    globalAlpha = other.globalAlpha;
    textAlignment = other.textAlignment;
    textBaseline = other.textBaseline;
    shadow = other.shadow;
    shadowBlur = other.shadowBlur;
    shadowOffsetX = other.shadowOffsetX;
    shadowOffsetY = other.shadowOffsetY;
    textDrawingMode = other.textDrawingMode;
    fontDescription = pango_font_description_copy(other.fontDescription);
    font = other.font;
    imageSmoothingEnabled = other.imageSmoothingEnabled;
  }
  ~canvas_state_t() {
    pango_font_description_free(fontDescription);
  }
};
/*
 * Equivalent to a PangoRectangle but holds floats instead of ints
 * (software pixels are stored here instead of pango units)
 *
 * Should be compatible with PANGO_ASCENT, PANGO_LBEARING, etc.
 */
typedef struct {
  float x;
  float y;
  float width;
  float height;
} float_rectangle;
class Context2d : public Nan::ObjectWrap {
  public:
    std::stack<canvas_state_t> states;
    canvas_state_t *state;
    Context2d(Canvas *canvas);
    static Nan::Persistent<v8::Function> _DOMMatrix;
    static Nan::Persistent<v8::Function> _parseFont;
    static Nan::Persistent<v8::FunctionTemplate> 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);
    inline void setContext(cairo_t *ctx) { _context = ctx; }
    inline cairo_t *context(){ return _context; }
    inline Canvas *canvas(){ return _canvas; }
    inline bool hasShadow();
    void inline setSourceRGBA(rgba_t color);
    void inline setSourceRGBA(cairo_t *ctx, rgba_t color);
    void setTextPath(double x, double y);
    void blur(cairo_surface_t *surface, int radius);
    void shadow(void (fn)(cairo_t *cr));
    void shadowStart();
    void shadowApply();
    void savePath();
    void restorePath();
    void saveState();
    void restoreState();
    void inline setFillRule(v8::Local<v8::Value> value);
    void fill(bool preserve = false);
    void stroke(bool preserve = false);
    void save();
    void restore();
    void setFontFromState();
    void resetState();
    inline PangoLayout *layout(){ return _layout; }
  private:
    ~Context2d();
    void _resetPersistentHandles();
    v8::Local<v8::Value> _getFillColor();
    v8::Local<v8::Value> _getStrokeColor();
    void _setFillColor(v8::Local<v8::Value> arg);
    void _setFillPattern(v8::Local<v8::Value> arg);
    void _setStrokeColor(v8::Local<v8::Value> arg);
    void _setStrokePattern(v8::Local<v8::Value> arg);
    Nan::Persistent<v8::Value> _fillStyle;
    Nan::Persistent<v8::Value> _strokeStyle;
    Canvas *_canvas;
    cairo_t *_context;
    cairo_path_t *_path;
    PangoLayout *_layout;
};
node_modules/canvas/src/CanvasRenderingContext2d.h.orig
New file
@@ -0,0 +1,226 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include "cairo.h"
#include "Canvas.h"
#include "color.h"
#include "napi.h"
#include "uv.h"
#include <pango/pangocairo.h>
#include <stack>
/*
 * State struct.
 *
 * Used in conjunction with Save() / Restore() since
 * cairo's gstate maintains only a single source pattern at a time.
 */
struct canvas_state_t {
  rgba_t fill = { 0, 0, 0, 1 };
  rgba_t stroke = { 0, 0, 0, 1 };
  rgba_t shadow = { 0, 0, 0, 0 };
  double shadowOffsetX = 0.;
  double shadowOffsetY = 0.;
  cairo_pattern_t* fillPattern = nullptr;
  cairo_pattern_t* strokePattern = nullptr;
  cairo_pattern_t* fillGradient = nullptr;
  cairo_pattern_t* strokeGradient = nullptr;
  PangoFontDescription* fontDescription = nullptr;
  std::string font = "10px sans-serif";
  cairo_filter_t patternQuality = CAIRO_FILTER_GOOD;
  float globalAlpha = 1.f;
  int shadowBlur = 0;
  text_align_t textAlignment = TEXT_ALIGNMENT_LEFT; // TODO default is supposed to be START
  text_baseline_t textBaseline = TEXT_BASELINE_ALPHABETIC;
  canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS;
  bool imageSmoothingEnabled = true;
  canvas_state_t() {
    fontDescription = pango_font_description_from_string("sans");
    pango_font_description_set_absolute_size(fontDescription, 10 * PANGO_SCALE);
  }
  canvas_state_t(const canvas_state_t& other) {
    fill = other.fill;
    stroke = other.stroke;
    patternQuality = other.patternQuality;
    fillPattern = other.fillPattern;
    strokePattern = other.strokePattern;
    fillGradient = other.fillGradient;
    strokeGradient = other.strokeGradient;
    globalAlpha = other.globalAlpha;
    textAlignment = other.textAlignment;
    textBaseline = other.textBaseline;
    shadow = other.shadow;
    shadowBlur = other.shadowBlur;
    shadowOffsetX = other.shadowOffsetX;
    shadowOffsetY = other.shadowOffsetY;
    textDrawingMode = other.textDrawingMode;
    fontDescription = pango_font_description_copy(other.fontDescription);
    font = other.font;
    imageSmoothingEnabled = other.imageSmoothingEnabled;
  }
  ~canvas_state_t() {
    pango_font_description_free(fontDescription);
  }
};
/*
 * Equivalent to a PangoRectangle but holds floats instead of ints
 * (software pixels are stored here instead of pango units)
 *
 * Should be compatible with PANGO_ASCENT, PANGO_LBEARING, etc.
 */
typedef struct {
  float x;
  float y;
  float width;
  float height;
} float_rectangle;
class Context2d : public Napi::ObjectWrap<Context2d> {
  public:
    std::stack<canvas_state_t> states;
    canvas_state_t *state;
    Context2d(Canvas *canvas);
    static Napi::FunctionReference _DOMMatrix;
    static Napi::FunctionReference _parseFont;
    static Napi::FunctionReference constructor;
    static void Initialize(Napi::Env& env, Napi::Object& target);
    static Napi::Value New(const Napi::CallbackInfo& info);
    static Napi::Value SaveExternalModules(const Napi::CallbackInfo& info);
    static Napi::Value DrawImage(const Napi::CallbackInfo& info);
    static Napi::Value PutImageData(const Napi::CallbackInfo& info);
    static Napi::Value Save(const Napi::CallbackInfo& info);
    static Napi::Value Restore(const Napi::CallbackInfo& info);
    static Napi::Value Rotate(const Napi::CallbackInfo& info);
    static Napi::Value Translate(const Napi::CallbackInfo& info);
    static Napi::Value Scale(const Napi::CallbackInfo& info);
    static Napi::Value Transform(const Napi::CallbackInfo& info);
    static Napi::Value GetTransform(const Napi::CallbackInfo& info);
    static Napi::Value ResetTransform(const Napi::CallbackInfo& info);
    static Napi::Value SetTransform(const Napi::CallbackInfo& info);
    static Napi::Value IsPointInPath(const Napi::CallbackInfo& info);
    static Napi::Value BeginPath(const Napi::CallbackInfo& info);
    static Napi::Value ClosePath(const Napi::CallbackInfo& info);
    static Napi::Value AddPage(const Napi::CallbackInfo& info);
    static Napi::Value Clip(const Napi::CallbackInfo& info);
    static Napi::Value Fill(const Napi::CallbackInfo& info);
    static Napi::Value Stroke(const Napi::CallbackInfo& info);
    static Napi::Value FillText(const Napi::CallbackInfo& info);
    static Napi::Value 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);
    static Napi::Value SetLineDash(const Napi::CallbackInfo& info);
    static Napi::Value GetLineDash(const Napi::CallbackInfo& info);
    static Napi::Value MeasureText(const Napi::CallbackInfo& info);
    static Napi::Value BezierCurveTo(const Napi::CallbackInfo& info);
    static Napi::Value QuadraticCurveTo(const Napi::CallbackInfo& info);
    static Napi::Value LineTo(const Napi::CallbackInfo& info);
    static Napi::Value MoveTo(const Napi::CallbackInfo& info);
    static Napi::Value FillRect(const Napi::CallbackInfo& info);
    static Napi::Value StrokeRect(const Napi::CallbackInfo& info);
    static Napi::Value ClearRect(const Napi::CallbackInfo& info);
    static Napi::Value Rect(const Napi::CallbackInfo& info);
    static Napi::Value RoundRect(const Napi::CallbackInfo& info);
    static Napi::Value Arc(const Napi::CallbackInfo& info);
    static Napi::Value ArcTo(const Napi::CallbackInfo& info);
    static Napi::Value Ellipse(const Napi::CallbackInfo& info);
    static Napi::Value GetImageData(const Napi::CallbackInfo& info);
    static Napi::Value CreateImageData(const Napi::CallbackInfo& info);
    static Napi::Value GetStrokeColor(const Napi::CallbackInfo& info);
    static Napi::Value CreatePattern(const Napi::CallbackInfo& info);
    static Napi::Value CreateLinearGradient(const Napi::CallbackInfo& info);
    static 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; }
    inline bool hasShadow();
    void inline setSourceRGBA(rgba_t color);
    void inline setSourceRGBA(cairo_t *ctx, rgba_t color);
    void setTextPath(double x, double y);
    void blur(cairo_surface_t *surface, int radius);
    void shadow(void (fn)(cairo_t *cr));
    void shadowStart();
    void shadowApply();
    void savePath();
    void restorePath();
    void saveState();
    void restoreState();
    void inline setFillRule(Napi::Value value);
    void fill(bool preserve = false);
    void stroke(bool preserve = false);
    void save();
    void restore();
    void setFontFromState();
    void resetState();
    inline PangoLayout *layout(){ return _layout; }
  private:
    ~Context2d();
    void _resetPersistentHandles();
    Napi::Value _getFillColor();
    Napi::Value _getStrokeColor();
    void _setFillColor(Napi::Value arg);
    void _setFillPattern(Napi::Value arg);
    void _setStrokeColor(Napi::Value arg);
    void _setStrokePattern(Napi::Value arg);
    Napi::Persistent<v8::Value> _fillStyle;
    Napi::Persistent<v8::Value> _strokeStyle;
    Canvas *_canvas;
    cairo_t *_context;
    cairo_path_t *_path;
    PangoLayout *_layout;
};
node_modules/canvas/src/Image.cc
New file
@@ -0,0 +1,1434 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "Image.h"
#include "bmp/BMPParser.h"
#include "Canvas.h"
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <node_buffer.h>
/* Cairo limit:
  * https://lists.cairographics.org/archives/cairo/2010-December/021422.html
  */
static constexpr int canvas_max_side = (1 << 15) - 1;
#ifdef HAVE_GIF
typedef struct {
  uint8_t *buf;
  unsigned len;
  unsigned pos;
} gif_data_t;
#endif
#ifdef HAVE_JPEG
#include <csetjmp>
struct canvas_jpeg_error_mgr: jpeg_error_mgr {
    Image* image;
    jmp_buf setjmp_buffer;
};
#endif
/*
 * Read closure used by loadFromBuffer.
 */
typedef struct {
  unsigned len;
  uint8_t *buf;
} read_closure_t;
using namespace v8;
Nan::Persistent<FunctionTemplate> Image::constructor;
/*
 * Initialize Image.
 */
void
Image::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
  Nan::HandleScope scope;
  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Image::New);
  constructor.Reset(ctor);
  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(Nan::New("Image").ToLocalChecked());
  // Prototype
  Local<ObjectTemplate> 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<Number>(DATA_IMAGE));
  ctor->Set(Nan::New("MODE_MIME").ToLocalChecked(), Nan::New<Number>(DATA_MIME));
  Local<Context> ctx = Nan::GetCurrentContext();
  Nan::Set(target, Nan::New("Image").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked());
  // Used internally in lib/image.js
  NAN_EXPORT(target, GetSource);
  NAN_EXPORT(target, SetSource);
}
/*
 * 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());
}
/*
 * Get complete boolean.
 */
NAN_GETTER(Image::GetComplete) {
  info.GetReturnValue().Set(Nan::New<Boolean>(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<Image>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(img->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<Image>(info.This());
    int mode = Nan::To<uint32_t>(value).FromMaybe(0);
    img->data_mode = (data_mode_t) mode;
  }
}
/*
 * 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<Image>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(img->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<Image>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(img->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<Image>(info.This());
    img->width = Nan::To<uint32_t>(value).FromMaybe(0);
  }
}
/*
 * 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<Image>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(img->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<Image>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(img->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<Image>(info.This());
    img->height = Nan::To<uint32_t>(value).FromMaybe(0);
  }
}
/*
 * 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<Image>(info.This());
  info.GetReturnValue().Set(Nan::New<String>(img->filename ? img->filename : "").ToLocalChecked());
}
/*
 * Clean up assets and variables.
 */
void
Image::clearData() {
  if (_surface) {
    cairo_surface_destroy(_surface);
    Nan::AdjustExternalMemory(-_data_len);
    _data_len = 0;
    _surface = NULL;
  }
  delete[] _data;
  _data = nullptr;
  free(filename);
  filename = NULL;
#ifdef HAVE_RSVG
  if (_rsvg != NULL) {
    g_object_unref(_rsvg);
    _rsvg = NULL;
  }
#endif
  width = height = 0;
  naturalWidth = naturalHeight = 0;
  state = DEFAULT;
}
/*
 * 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<Image>(info.This());
  cairo_status_t status = CAIRO_STATUS_READ_ERROR;
  Local<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 (img->filename) free(img->filename);
    img->filename = strdup(*src);
    status = img->load();
  // Buffer
  } else if (node::Buffer::HasInstance(value)) {
    uint8_t *buf = (uint8_t *) node::Buffer::Data(Nan::To<Object>(value).ToLocalChecked());
    unsigned len = node::Buffer::Length(Nan::To<Object>(value).ToLocalChecked());
    status = img->loadFromBuffer(buf, len);
  }
  if (status) {
    Local<Value> onerrorFn = Nan::Get(info.This(), Nan::New("onerror").ToLocalChecked()).ToLocalChecked();
    if (onerrorFn->IsFunction()) {
      Local<Value> 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());
      } else {
        argv[0] = Nan::Error(Nan::New(cairo_status_to_string(status)).ToLocalChecked());
      }
      Local<Context> ctx = Nan::GetCurrentContext();
      Nan::Call(onerrorFn.As<Function>(), ctx->Global(), 1, argv);
    }
  } else {
    img->loaded();
    Local<Value> onloadFn = Nan::Get(info.This(), Nan::New("onload").ToLocalChecked()).ToLocalChecked();
    if (onloadFn->IsFunction()) {
      Local<Context> ctx = Nan::GetCurrentContext();
      Nan::Call(onloadFn.As<Function>(), ctx->Global(), 0, NULL);
    }
  }
}
/*
 * Load image data from `buf` by sniffing
 * the bytes to determine format.
 */
cairo_status_t
Image::loadFromBuffer(uint8_t *buf, unsigned len) {
  uint8_t data[4] = {0};
  memcpy(data, buf, (len < 4 ? len : 4) * sizeof(uint8_t));
  if (isPNG(data)) return loadPNGFromBuffer(buf);
  if (isGIF(data)) {
#ifdef HAVE_GIF
    return loadGIFFromBuffer(buf, len);
#else
    this->errorInfo.set("node-canvas was built without GIF support");
    return CAIRO_STATUS_READ_ERROR;
#endif
  }
  if (isJPEG(data)) {
#ifdef HAVE_JPEG
    if (DATA_IMAGE == data_mode) return loadJPEGFromBuffer(buf, len);
    if (DATA_MIME == data_mode) return decodeJPEGBufferIntoMimeSurface(buf, len);
    if ((DATA_IMAGE | DATA_MIME) == data_mode) {
      cairo_status_t status;
      status = loadJPEGFromBuffer(buf, len);
      if (status) return status;
      return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
    }
#else // HAVE_JPEG
    this->errorInfo.set("node-canvas was built without JPEG support");
    return CAIRO_STATUS_READ_ERROR;
#endif
  }
  // confirm svg using first 1000 chars
  // if a very long comment precedes the root <svg> tag, isSVG returns false
  unsigned head_len = (len < 1000 ? len : 1000);
  if (isSVG(buf, head_len)) {
#ifdef HAVE_RSVG
    return loadSVGFromBuffer(buf, len);
#else
    this->errorInfo.set("node-canvas was built without SVG support");
    return CAIRO_STATUS_READ_ERROR;
#endif
  }
  if (isBMP(buf, len))
    return loadBMPFromBuffer(buf, len);
  this->errorInfo.set("Unsupported image type");
  return CAIRO_STATUS_READ_ERROR;
}
/*
 * Load PNG data from `buf`.
 */
cairo_status_t
Image::loadPNGFromBuffer(uint8_t *buf) {
  read_closure_t closure;
  closure.len = 0;
  closure.buf = buf;
  _surface = cairo_image_surface_create_from_png_stream(readPNG, &closure);
  cairo_status_t status = cairo_surface_status(_surface);
  if (status) return status;
  return CAIRO_STATUS_SUCCESS;
}
/*
 * Read PNG data.
 */
cairo_status_t
Image::readPNG(void *c, uint8_t *data, unsigned int len) {
  read_closure_t *closure = (read_closure_t *) c;
  memcpy(data, closure->buf + closure->len, len);
  closure->len += 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.
 */
Image::~Image() {
  clearData();
}
/*
 * Initiate image loading.
 */
cairo_status_t
Image::load() {
  if (LOADING != state) {
    state = LOADING;
    return loadSurface();
  }
  return CAIRO_STATUS_READ_ERROR;
}
/*
 * Set state, assign dimensions.
 */
void
Image::loaded() {
  Nan::HandleScope scope;
  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);
}
/*
 * Returns this image's surface.
 */
cairo_surface_t *Image::surface() {
#ifdef HAVE_RSVG
  if (_is_svg && (_svg_last_width != width || _svg_last_height != height)) {
    if (_surface != NULL) {
      cairo_surface_destroy(_surface);
      _surface = NULL;
    }
    cairo_status_t status = renderSVGToSurface();
    if (status != CAIRO_STATUS_SUCCESS) {
      g_object_unref(_rsvg);
      Nan::ThrowError(Canvas::Error(status));
      return NULL;
    }
  }
#endif
  return _surface;
}
/*
 * Load cairo surface from the image src.
 *
 * TODO: support more formats
 * TODO: use node IO or at least thread pool
 */
cairo_status_t
Image::loadSurface() {
  FILE *stream = fopen(filename, "rb");
  if (!stream) {
    this->errorInfo.set(NULL, "fopen", errno, filename);
    return CAIRO_STATUS_READ_ERROR;
  }
  uint8_t buf[5];
  if (1 != fread(&buf, 5, 1, stream)) {
    fclose(stream);
    return CAIRO_STATUS_READ_ERROR;
  }
  rewind(stream);
  // png
  if (isPNG(buf)) {
    fclose(stream);
    return loadPNG();
  }
  if (isGIF(buf)) {
#ifdef HAVE_GIF
    return loadGIF(stream);
#else
    this->errorInfo.set("node-canvas was built without GIF support");
    return CAIRO_STATUS_READ_ERROR;
#endif
  }
  if (isJPEG(buf)) {
#ifdef HAVE_JPEG
    return loadJPEG(stream);
#else
    this->errorInfo.set("node-canvas was built without JPEG support");
    return CAIRO_STATUS_READ_ERROR;
#endif
  }
  // confirm svg using first 1000 chars
  // if a very long comment precedes the root <svg> tag, isSVG returns false
  uint8_t head[1000] = {0};
  fseek(stream, 0 , SEEK_END);
  long len = ftell(stream);
  unsigned head_len = (len < 1000 ? len : 1000);
  unsigned head_size = head_len * sizeof(uint8_t);
  rewind(stream);
  if (head_size != fread(&head, 1, head_size, stream)) {
    fclose(stream);
    return CAIRO_STATUS_READ_ERROR;
  }
  rewind(stream);
  if (isSVG(head, head_len)) {
#ifdef HAVE_RSVG
    return loadSVG(stream);
#else
    this->errorInfo.set("node-canvas was built without SVG support");
    return CAIRO_STATUS_READ_ERROR;
#endif
  }
  if (isBMP(buf, 2))
    return loadBMP(stream);
  fclose(stream);
  this->errorInfo.set("Unsupported image type");
  return CAIRO_STATUS_READ_ERROR;
}
/*
 * Load PNG.
 */
cairo_status_t
Image::loadPNG() {
  _surface = cairo_image_surface_create_from_png(filename);
  return cairo_surface_status(_surface);
}
// GIF support
#ifdef HAVE_GIF
/*
 * Return the alpha color for `gif` at `frame`, or -1.
 */
int
get_gif_transparent_color(GifFileType *gif, int frame) {
  ExtensionBlock *ext = gif->SavedImages[frame].ExtensionBlocks;
  int len = gif->SavedImages[frame].ExtensionBlockCount;
  for (int x = 0; x < len; ++x, ++ext) {
    if ((ext->Function == GRAPHICS_EXT_FUNC_CODE) && (ext->Bytes[0] & 1)) {
      return ext->Bytes[3] == 0 ? 0 : (uint8_t) ext->Bytes[3];
    }
  }
  return -1;
}
/*
 * Memory GIF reader callback.
 */
int
read_gif_from_memory(GifFileType *gif, GifByteType *buf, int len) {
  gif_data_t *data = (gif_data_t *) gif->UserData;
  if ((data->pos + len) > data->len) len = data->len - data->pos;
  memcpy(buf, data->pos + data->buf, len);
  data->pos += len;
  return len;
}
/*
 * Load GIF.
 */
cairo_status_t
Image::loadGIF(FILE *stream) {
  struct stat s;
  int fd = fileno(stream);
  // stat
  if (fstat(fd, &s) < 0) {
    fclose(stream);
    return CAIRO_STATUS_READ_ERROR;
  }
  uint8_t *buf = (uint8_t *) malloc(s.st_size);
  if (!buf) {
    fclose(stream);
    this->errorInfo.set(NULL, "malloc", errno);
    return CAIRO_STATUS_NO_MEMORY;
  }
  size_t read = fread(buf, s.st_size, 1, stream);
  fclose(stream);
  cairo_status_t result = CAIRO_STATUS_READ_ERROR;
  if (1 == read) result = loadGIFFromBuffer(buf, s.st_size);
  free(buf);
  return result;
}
/*
 * Load give from `buf` and the given `len`.
 */
cairo_status_t
Image::loadGIFFromBuffer(uint8_t *buf, unsigned len) {
  int i = 0;
  GifFileType* gif;
  gif_data_t gifd = { buf, len, 0 };
#if GIFLIB_MAJOR >= 5
  int errorcode;
  if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory, &errorcode)) == NULL)
    return CAIRO_STATUS_READ_ERROR;
#else
  if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory)) == NULL)
    return CAIRO_STATUS_READ_ERROR;
#endif
  if (GIF_OK != DGifSlurp(gif)) {
    GIF_CLOSE_FILE(gif);
    return CAIRO_STATUS_READ_ERROR;
  }
  if (gif->SWidth > canvas_max_side || gif->SHeight > canvas_max_side) {
    GIF_CLOSE_FILE(gif);
    return CAIRO_STATUS_INVALID_SIZE;
  }
  width = naturalWidth = gif->SWidth;
  height = naturalHeight = gif->SHeight;
  uint8_t *data = new uint8_t[naturalWidth * naturalHeight * 4];
  if (!data) {
    GIF_CLOSE_FILE(gif);
    this->errorInfo.set(NULL, "malloc", errno);
    return CAIRO_STATUS_NO_MEMORY;
  }
  GifImageDesc *img = &gif->SavedImages[i].ImageDesc;
  // local colormap takes precedence over global
  ColorMapObject *colormap = img->ColorMap
    ? img->ColorMap
    : gif->SColorMap;
  if (colormap == nullptr) {
    GIF_CLOSE_FILE(gif);
    return CAIRO_STATUS_READ_ERROR;
  }
  int bgColor = 0;
  int alphaColor = get_gif_transparent_color(gif, i);
  if (gif->SColorMap) bgColor = (uint8_t) gif->SBackGroundColor;
  else if(alphaColor >= 0) bgColor = alphaColor;
  uint8_t *src_data = (uint8_t*) gif->SavedImages[i].RasterBits;
  uint32_t *dst_data = (uint32_t*) data;
  if (!gif->Image.Interlace) {
    if (naturalWidth == img->Width && naturalHeight == img->Height) {
      for (int y = 0; y < naturalHeight; ++y) {
        for (int x = 0; x < naturalWidth; ++x) {
          *dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24
            | colormap->Colors[*src_data].Red << 16
            | colormap->Colors[*src_data].Green << 8
            | colormap->Colors[*src_data].Blue;
          dst_data++;
          src_data++;
        }
      }
    } else {
      // Image does not take up whole "screen" so we need to fill-in the background
      int bottom = img->Top + img->Height;
      int right = img->Left + img->Width;
      uint32_t bgPixel =
        ((bgColor == alphaColor) ? 0 : 255) << 24
        | colormap->Colors[bgColor].Red << 16
        | colormap->Colors[bgColor].Green << 8
        | colormap->Colors[bgColor].Blue;
      for (int y = 0; y < naturalHeight; ++y) {
        for (int x = 0; x < naturalWidth; ++x) {
          if (y < img->Top || y >= bottom || x < img->Left || x >= right) {
            *dst_data = bgPixel;
            dst_data++;
          } else {
            *dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24
              | colormap->Colors[*src_data].Red << 16
              | colormap->Colors[*src_data].Green << 8
              | colormap->Colors[*src_data].Blue;
            dst_data++;
            src_data++;
          }
        }
      }
    }
  } else {
    // Image is interlaced so that it streams nice over 14.4k and 28.8k modems :)
    // We first load in 1/8 of the image, followed by another 1/8, followed by
    // 1/4 and finally the remaining 1/2.
    int ioffs[] = { 0, 4, 2, 1 };
    int ijumps[] = { 8, 8, 4, 2 };
    uint8_t *src_ptr = src_data;
    uint32_t *dst_ptr;
    for(int z = 0; z < 4; z++) {
      for(int y = ioffs[z]; y < naturalHeight; y += ijumps[z]) {
        dst_ptr = dst_data + naturalWidth * y;
        for(int x = 0; x < naturalWidth; ++x) {
          *dst_ptr = ((*src_ptr == alphaColor) ? 0 : 255) << 24
            | (colormap->Colors[*src_ptr].Red) << 16
            | (colormap->Colors[*src_ptr].Green) << 8
            | (colormap->Colors[*src_ptr].Blue);
          dst_ptr++;
          src_ptr++;
        }
      }
    }
  }
  GIF_CLOSE_FILE(gif);
  // New image surface
  _surface = cairo_image_surface_create_for_data(
      data
    , CAIRO_FORMAT_ARGB32
    , naturalWidth
    , naturalHeight
    , cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, naturalWidth));
  cairo_status_t status = cairo_surface_status(_surface);
  if (status) {
    delete[] data;
    return status;
  }
  _data = data;
  return CAIRO_STATUS_SUCCESS;
}
#endif /* HAVE_GIF */
// JPEG support
#ifdef HAVE_JPEG
// libjpeg 6.2 does not have jpeg_mem_src; define it ourselves here unless
// libjpeg 8 is installed.
#if JPEG_LIB_VERSION < 80 && !defined(MEM_SRCDST_SUPPORTED)
/* Read JPEG image from a memory segment */
static void
init_source(j_decompress_ptr cinfo) {}
static boolean
fill_input_buffer(j_decompress_ptr cinfo) {
  ERREXIT(cinfo, JERR_INPUT_EMPTY);
  return TRUE;
}
static void
skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
  struct jpeg_source_mgr* src = (struct jpeg_source_mgr*) cinfo->src;
  if (num_bytes > 0) {
    src->next_input_byte += (size_t) num_bytes;
    src->bytes_in_buffer -= (size_t) num_bytes;
  }
}
static void term_source (j_decompress_ptr cinfo) {}
static void jpeg_mem_src (j_decompress_ptr cinfo, void* buffer, long nbytes) {
  struct jpeg_source_mgr* src;
  if (cinfo->src == NULL) {
    cinfo->src = (struct jpeg_source_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
                                  sizeof(struct jpeg_source_mgr));
  }
  src = (struct jpeg_source_mgr*) cinfo->src;
  src->init_source = init_source;
  src->fill_input_buffer = fill_input_buffer;
  src->skip_input_data = skip_input_data;
  src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
  src->term_source = term_source;
  src->bytes_in_buffer = nbytes;
  src->next_input_byte = (JOCTET*)buffer;
}
#endif
void Image::jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode) {
  int stride = naturalWidth * 4;
  for (int y = 0; y < naturalHeight; ++y) {
    jpeg_read_scanlines(args, &src, 1);
    uint32_t *row = (uint32_t*)(data + stride * y);
    for (int x = 0; x < naturalWidth; ++x) {
      int bx = args->output_components * x;
      row[x] = decode(src + bx);
    }
  }
}
/*
 * Takes an initialised jpeg_decompress_struct and decodes the
 * data into _surface.
 */
cairo_status_t
Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args) {
  cairo_status_t status = CAIRO_STATUS_SUCCESS;
  uint8_t *data = new uint8_t[naturalWidth * naturalHeight * 4];
  if (!data) {
    jpeg_abort_decompress(args);
    jpeg_destroy_decompress(args);
    this->errorInfo.set(NULL, "malloc", errno);
    return CAIRO_STATUS_NO_MEMORY;
  }
  uint8_t *src = new uint8_t[naturalWidth * args->output_components];
  if (!src) {
    free(data);
    jpeg_abort_decompress(args);
    jpeg_destroy_decompress(args);
    this->errorInfo.set(NULL, "malloc", errno);
    return CAIRO_STATUS_NO_MEMORY;
  }
  // These are the three main cases to handle. libjpeg converts YCCK to CMYK
  // and YCbCr to RGB by default.
  switch (args->out_color_space) {
    case JCS_CMYK:
      jpegToARGB(args, data, src, [](uint8_t const* src) {
        uint16_t k = static_cast<uint16_t>(src[3]);
        uint8_t r = k * src[0] / 255;
        uint8_t g = k * src[1] / 255;
        uint8_t b = k * src[2] / 255;
        return 255 << 24 | r << 16 | g << 8 | b;
      });
      break;
    case JCS_RGB:
      jpegToARGB(args, data, src, [](uint8_t const* src) {
        uint8_t r = src[0], g = src[1], b = src[2];
        return 255 << 24 | r << 16 | g << 8 | b;
      });
      break;
    case JCS_GRAYSCALE:
      jpegToARGB(args, data, src, [](uint8_t const* src) {
        uint8_t v = src[0];
        return 255 << 24 | v << 16 | v << 8 | v;
      });
      break;
    default:
      this->errorInfo.set("Unsupported JPEG encoding");
      status = CAIRO_STATUS_READ_ERROR;
      break;
  }
  if (!status) {
    _surface = cairo_image_surface_create_for_data(
        data
      , CAIRO_FORMAT_ARGB32
      , naturalWidth
      , naturalHeight
      , cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, naturalWidth));
  }
  jpeg_finish_decompress(args);
  jpeg_destroy_decompress(args);
  status = cairo_surface_status(_surface);
  delete[] src;
  if (status) {
    delete[] data;
    return status;
  }
  _data = data;
  return CAIRO_STATUS_SUCCESS;
}
/*
 * Callback to recover from jpeg errors
 */
static void canvas_jpeg_error_exit(j_common_ptr cinfo) {
  canvas_jpeg_error_mgr *cjerr = static_cast<canvas_jpeg_error_mgr*>(cinfo->err);
  cjerr->output_message(cinfo);
  // Return control to the setjmp point
  longjmp(cjerr->setjmp_buffer, 1);
}
// Capture libjpeg errors instead of writing stdout
static void canvas_jpeg_output_message(j_common_ptr cinfo) {
  canvas_jpeg_error_mgr *cjerr = static_cast<canvas_jpeg_error_mgr*>(cinfo->err);
  char buff[JMSG_LENGTH_MAX];
  cjerr->format_message(cinfo, buff);
  // (Only the last message will be returned to JS land.)
  cjerr->image->errorInfo.set(buff);
}
/*
 * Takes a jpeg data buffer and assigns it as mime data to a
 * dummy surface
 */
cairo_status_t
Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) {
  // TODO: remove this duplicate logic
  // JPEG setup
  struct jpeg_decompress_struct args;
  struct canvas_jpeg_error_mgr err;
  err.image = this;
  args.err = jpeg_std_error(&err);
  args.err->error_exit = canvas_jpeg_error_exit;
  args.err->output_message = canvas_jpeg_output_message;
  // Establish the setjmp return context for canvas_jpeg_error_exit to use
  if (setjmp(err.setjmp_buffer)) {
    // If we get here, the JPEG code has signaled an error.
    // We need to clean up the JPEG object, close the input file, and return.
    jpeg_destroy_decompress(&args);
    return CAIRO_STATUS_READ_ERROR;
  }
  jpeg_create_decompress(&args);
  jpeg_mem_src(&args, buf, len);
  jpeg_read_header(&args, 1);
  jpeg_start_decompress(&args);
  width = naturalWidth = args.output_width;
  height = naturalHeight = args.output_height;
  // Data alloc
  // 8 pixels per byte using Alpha Channel format to reduce memory requirement.
  int buf_size = naturalHeight * cairo_format_stride_for_width(CAIRO_FORMAT_A1, naturalWidth);
  uint8_t *data = new uint8_t[buf_size];
  if (!data) {
    this->errorInfo.set(NULL, "malloc", errno);
    return CAIRO_STATUS_NO_MEMORY;
  }
  // New image surface
  _surface = cairo_image_surface_create_for_data(
      data
    , CAIRO_FORMAT_A1
    , naturalWidth
    , naturalHeight
    , cairo_format_stride_for_width(CAIRO_FORMAT_A1, naturalWidth));
  // Cleanup
  jpeg_abort_decompress(&args);
  jpeg_destroy_decompress(&args);
  cairo_status_t status = cairo_surface_status(_surface);
  if (status) {
    delete[] data;
    return status;
  }
  _data = data;
  return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
}
/*
 * Helper function for disposing of a mime data closure.
 */
void
clearMimeData(void *closure) {
  Nan::AdjustExternalMemory(
      -static_cast<int>((static_cast<read_closure_t *>(closure)->len)));
  free(static_cast<read_closure_t *>(closure)->buf);
  free(closure);
}
/*
 * Assign a given buffer as mime data against the surface.
 * The provided buffer will be copied, and the copy will
 * be automatically freed when the surface is destroyed.
 */
cairo_status_t
Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) {
  uint8_t *mime_data = (uint8_t *) malloc(len);
  if (!mime_data) {
    this->errorInfo.set(NULL, "malloc", errno);
    return CAIRO_STATUS_NO_MEMORY;
  }
  read_closure_t *mime_closure = (read_closure_t *) malloc(sizeof(read_closure_t));
  if (!mime_closure) {
    free(mime_data);
    this->errorInfo.set(NULL, "malloc", errno);
    return CAIRO_STATUS_NO_MEMORY;
  }
  memcpy(mime_data, data, len);
  mime_closure->buf = mime_data;
  mime_closure->len = len;
  Nan::AdjustExternalMemory(len);
  return cairo_surface_set_mime_data(_surface
    , mime_type
    , mime_data
    , len
    , clearMimeData
    , mime_closure);
}
/*
 * Load jpeg from buffer.
 */
cairo_status_t
Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) {
  // TODO: remove this duplicate logic
  // JPEG setup
  struct jpeg_decompress_struct args;
  struct canvas_jpeg_error_mgr err;
  err.image = this;
  args.err = jpeg_std_error(&err);
  args.err->error_exit = canvas_jpeg_error_exit;
  args.err->output_message = canvas_jpeg_output_message;
  // Establish the setjmp return context for canvas_jpeg_error_exit to use
  if (setjmp(err.setjmp_buffer)) {
    // If we get here, the JPEG code has signaled an error.
    // We need to clean up the JPEG object, close the input file, and return.
    jpeg_destroy_decompress(&args);
    return CAIRO_STATUS_READ_ERROR;
  }
  jpeg_create_decompress(&args);
  jpeg_mem_src(&args, buf, len);
  jpeg_read_header(&args, 1);
  jpeg_start_decompress(&args);
  width = naturalWidth = args.output_width;
  height = naturalHeight = args.output_height;
  return decodeJPEGIntoSurface(&args);
}
/*
 * Load JPEG, convert RGB to ARGB.
 */
cairo_status_t
Image::loadJPEG(FILE *stream) {
  cairo_status_t status;
#if defined(_MSC_VER)
  if (false) { // Force using loadJPEGFromBuffer
#else
  if (data_mode == DATA_IMAGE) { // Can lazily read in the JPEG.
#endif
    // JPEG setup
    struct jpeg_decompress_struct args;
    struct canvas_jpeg_error_mgr err;
    err.image = this;
    args.err = jpeg_std_error(&err);
    args.err->error_exit = canvas_jpeg_error_exit;
    args.err->output_message = canvas_jpeg_output_message;
    // Establish the setjmp return context for canvas_jpeg_error_exit to use
    if (setjmp(err.setjmp_buffer)) {
      // If we get here, the JPEG code has signaled an error.
      // We need to clean up the JPEG object, close the input file, and return.
      jpeg_destroy_decompress(&args);
      return CAIRO_STATUS_READ_ERROR;
    }
    jpeg_create_decompress(&args);
    jpeg_stdio_src(&args, stream);
    jpeg_read_header(&args, 1);
    jpeg_start_decompress(&args);
    if (args.output_width > canvas_max_side || args.output_height > canvas_max_side) {
      jpeg_destroy_decompress(&args);
      return CAIRO_STATUS_INVALID_SIZE;
    }
    width = naturalWidth = args.output_width;
    height = naturalHeight = args.output_height;
    status = decodeJPEGIntoSurface(&args);
    fclose(stream);
  } else { // We'll need the actual source jpeg data, so read fully.
    uint8_t *buf;
    unsigned len;
    fseek(stream, 0, SEEK_END);
    len = ftell(stream);
    fseek(stream, 0, SEEK_SET);
    buf = (uint8_t *) malloc(len);
    if (!buf) {
      this->errorInfo.set(NULL, "malloc", errno);
      return CAIRO_STATUS_NO_MEMORY;
    }
    if (fread(buf, len, 1, stream) != 1) {
      status = CAIRO_STATUS_READ_ERROR;
    } else if ((DATA_IMAGE | DATA_MIME) == data_mode) {
      status = loadJPEGFromBuffer(buf, len);
      if (!status) status = assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
    } else if (DATA_MIME == data_mode) {
      status = decodeJPEGBufferIntoMimeSurface(buf, len);
    }
#if defined(_MSC_VER)
    else if (DATA_IMAGE == data_mode) {
      status = loadJPEGFromBuffer(buf, len);
    }
#endif
    else {
      status = CAIRO_STATUS_READ_ERROR;
    }
    fclose(stream);
    free(buf);
  }
  return status;
}
#endif /* HAVE_JPEG */
#ifdef HAVE_RSVG
/*
 * Load SVG from buffer
 */
cairo_status_t
Image::loadSVGFromBuffer(uint8_t *buf, unsigned len) {
  _is_svg = true;
  cairo_status_t status;
  GError *gerr = NULL;
  if (NULL == (_rsvg = rsvg_handle_new_from_data(buf, len, &gerr))) {
    return CAIRO_STATUS_READ_ERROR;
  }
  RsvgDimensionData *dims = new RsvgDimensionData();
  rsvg_handle_get_dimensions(_rsvg, dims);
  width = naturalWidth = dims->width;
  height = naturalHeight = dims->height;
  status = renderSVGToSurface();
  if (status != CAIRO_STATUS_SUCCESS) {
    g_object_unref(_rsvg);
    return status;
  }
  return CAIRO_STATUS_SUCCESS;
}
/*
 * Renders the Rsvg handle to this image's surface
 */
cairo_status_t
Image::renderSVGToSurface() {
  cairo_status_t status;
  _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
  status = cairo_surface_status(_surface);
  if (status != CAIRO_STATUS_SUCCESS) {
    g_object_unref(_rsvg);
    return status;
  }
  cairo_t *cr = cairo_create(_surface);
  cairo_scale(cr,
    (double)width / (double)naturalWidth,
    (double)height / (double)naturalHeight);
  status = cairo_status(cr);
  if (status != CAIRO_STATUS_SUCCESS) {
    g_object_unref(_rsvg);
    return status;
  }
  gboolean render_ok = rsvg_handle_render_cairo(_rsvg, cr);
  if (!render_ok) {
    g_object_unref(_rsvg);
    cairo_destroy(cr);
    return CAIRO_STATUS_READ_ERROR; // or WRITE?
  }
  cairo_destroy(cr);
  _svg_last_width = width;
  _svg_last_height = height;
  return status;
}
/*
 * Load SVG
 */
cairo_status_t
Image::loadSVG(FILE *stream) {
  _is_svg = true;
  struct stat s;
  int fd = fileno(stream);
  // stat
  if (fstat(fd, &s) < 0) {
    fclose(stream);
    return CAIRO_STATUS_READ_ERROR;
  }
  uint8_t *buf = (uint8_t *) malloc(s.st_size);
  if (!buf) {
    fclose(stream);
    return CAIRO_STATUS_NO_MEMORY;
  }
  size_t read = fread(buf, s.st_size, 1, stream);
  fclose(stream);
  cairo_status_t result = CAIRO_STATUS_READ_ERROR;
  if (1 == read) result = loadSVGFromBuffer(buf, s.st_size);
  free(buf);
  return result;
}
#endif /* HAVE_RSVG */
/*
 * Load BMP from buffer.
 */
cairo_status_t Image::loadBMPFromBuffer(uint8_t *buf, unsigned len){
  BMPParser::Parser parser;
  // Reversed ARGB32 with pre-multiplied alpha
  uint8_t pixFmt[5] = {2, 1, 0, 3, 1};
  parser.parse(buf, len, pixFmt);
  if (parser.getStatus() != BMPParser::Status::OK) {
    errorInfo.reset();
    errorInfo.message = parser.getErrMsg();
    return CAIRO_STATUS_READ_ERROR;
  }
  width = naturalWidth = parser.getWidth();
  height = naturalHeight = parser.getHeight();
  uint8_t *data = parser.getImgd();
  _surface = cairo_image_surface_create_for_data(
    data,
    CAIRO_FORMAT_ARGB32,
    width,
    height,
    cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width)
  );
  // No need to delete the data
  cairo_status_t status = cairo_surface_status(_surface);
  if (status) return status;
  _data = data;
  parser.clearImgd();
  return CAIRO_STATUS_SUCCESS;
}
/*
 * Load BMP.
 */
cairo_status_t Image::loadBMP(FILE *stream){
  struct stat s;
  int fd = fileno(stream);
  // Stat
  if (fstat(fd, &s) < 0) {
    fclose(stream);
    return CAIRO_STATUS_READ_ERROR;
  }
  uint8_t *buf = new uint8_t[s.st_size];
  if (!buf) {
    fclose(stream);
    errorInfo.set(NULL, "malloc", errno);
    return CAIRO_STATUS_NO_MEMORY;
  }
  size_t read = fread(buf, s.st_size, 1, stream);
  fclose(stream);
  cairo_status_t result = CAIRO_STATUS_READ_ERROR;
  if (read == 1) result = loadBMPFromBuffer(buf, s.st_size);
  delete[] buf;
  return result;
}
/*
 * Return UNKNOWN, SVG, GIF, JPEG, or PNG based on the filename.
 */
Image::type
Image::extension(const char *filename) {
  size_t len = strlen(filename);
  filename += len;
  if (len >= 5 && 0 == strcmp(".jpeg", filename - 5)) return Image::JPEG;
  if (len >= 4 && 0 == strcmp(".gif", filename - 4)) return Image::GIF;
  if (len >= 4 && 0 == strcmp(".jpg", filename - 4)) return Image::JPEG;
  if (len >= 4 && 0 == strcmp(".png", filename - 4)) return Image::PNG;
  if (len >= 4 && 0 == strcmp(".svg", filename - 4)) return Image::SVG;
  return Image::UNKNOWN;
}
/*
 * Sniff bytes 0..1 for JPEG's magic number ff d8.
 */
int
Image::isJPEG(uint8_t *data) {
  return 0xff == data[0] && 0xd8 == data[1];
}
/*
 * Sniff bytes 0..2 for "GIF".
 */
int
Image::isGIF(uint8_t *data) {
  return 'G' == data[0] && 'I' == data[1] && 'F' == data[2];
}
/*
 * Sniff bytes 1..3 for "PNG".
 */
int
Image::isPNG(uint8_t *data) {
  return 'P' == data[1] && 'N' == data[2] && 'G' == data[3];
}
/*
 * Skip "<?" and "<!" tags to test if root tag starts "<svg"
 */
int
Image::isSVG(uint8_t *data, unsigned len) {
  for (unsigned i = 3; i < len; i++) {
    if ('<' == data[i-3]) {
      switch (data[i-2]) {
        case '?':
        case '!':
          break;
        case 's':
          return ('v' == data[i-1] && 'g' == data[i]);
        default:
          return false;
      }
    }
  }
  return false;
}
/*
 * Check for valid BMP signatures
 */
int Image::isBMP(uint8_t *data, unsigned len) {
  if(len < 2) return false;
  std::string sig = std::string(1, (char)data[0]) + (char)data[1];
  return sig == "BM" ||
         sig == "BA" ||
         sig == "CI" ||
         sig == "CP" ||
         sig == "IC" ||
         sig == "PT";
}
node_modules/canvas/src/Image.h
New file
@@ -0,0 +1,127 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include <cairo.h>
#include "CanvasError.h"
#include <functional>
#include <nan.h>
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <v8.h>
#ifdef HAVE_JPEG
#include <jpeglib.h>
#include <jerror.h>
#endif
#ifdef HAVE_GIF
#include <gif_lib.h>
  #if GIFLIB_MAJOR > 5 || GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1
    #define GIF_CLOSE_FILE(gif) DGifCloseFile(gif, NULL)
  #else
    #define GIF_CLOSE_FILE(gif) DGifCloseFile(gif)
  #endif
#endif
#ifdef HAVE_RSVG
#include <librsvg/rsvg.h>
  // librsvg <= 2.36.1, identified by undefined macro, needs an extra include
  #ifndef LIBRSVG_CHECK_VERSION
  #include <librsvg/rsvg-cairo.h>
  #endif
#endif
using JPEGDecodeL = std::function<uint32_t (uint8_t* const src)>;
class Image: public Nan::ObjectWrap {
  public:
    char *filename;
    int width, height;
    int naturalWidth, naturalHeight;
    static Nan::Persistent<v8::FunctionTemplate> 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);
    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);
    static int isJPEG(uint8_t *data);
    static int isGIF(uint8_t *data);
    static int isSVG(uint8_t *data, unsigned len);
    static int isBMP(uint8_t *data, unsigned len);
    static cairo_status_t readPNG(void *closure, unsigned char *data, unsigned len);
    inline int isComplete(){ return COMPLETE == state; }
    cairo_surface_t *surface();
    cairo_status_t loadSurface();
    cairo_status_t loadFromBuffer(uint8_t *buf, unsigned len);
    cairo_status_t loadPNGFromBuffer(uint8_t *buf);
    cairo_status_t loadPNG();
    void clearData();
#ifdef HAVE_RSVG
    cairo_status_t loadSVGFromBuffer(uint8_t *buf, unsigned len);
    cairo_status_t loadSVG(FILE *stream);
    cairo_status_t renderSVGToSurface();
#endif
#ifdef HAVE_GIF
    cairo_status_t loadGIFFromBuffer(uint8_t *buf, unsigned len);
    cairo_status_t loadGIF(FILE *stream);
#endif
#ifdef HAVE_JPEG
    cairo_status_t loadJPEGFromBuffer(uint8_t *buf, unsigned len);
    cairo_status_t loadJPEG(FILE *stream);
    void jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode);
    cairo_status_t decodeJPEGIntoSurface(jpeg_decompress_struct *info);
    cairo_status_t decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len);
    cairo_status_t assignDataAsMime(uint8_t *data, int len, const char *mime_type);
#endif
    cairo_status_t loadBMPFromBuffer(uint8_t *buf, unsigned len);
    cairo_status_t loadBMP(FILE *stream);
    CanvasError errorInfo;
    void loaded();
    cairo_status_t load();
    Image();
    enum {
        DEFAULT
      , LOADING
      , COMPLETE
    } state;
    enum data_mode_t {
        DATA_IMAGE = 1
      , DATA_MIME = 2
    } data_mode;
    typedef enum {
        UNKNOWN
      , GIF
      , JPEG
      , PNG
      , SVG
    } type;
    static type extension(const char *filename);
  private:
    cairo_surface_t *_surface;
    uint8_t *_data = nullptr;
    int _data_len;
#ifdef HAVE_RSVG
    RsvgHandle *_rsvg;
    bool _is_svg;
    int _svg_last_width;
    int _svg_last_height;
#endif
    ~Image();
};
node_modules/canvas/src/ImageData.cc
New file
@@ -0,0 +1,146 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "ImageData.h"
using namespace v8;
Nan::Persistent<FunctionTemplate> ImageData::constructor;
/*
 * Initialize ImageData.
 */
void
ImageData::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
  Nan::HandleScope scope;
  // Constructor
  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(ImageData::New);
  constructor.Reset(ctor);
  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(Nan::New("ImageData").ToLocalChecked());
  // Prototype
  Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
  Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth);
  Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight);
  Local<Context> ctx = Nan::GetCurrentContext();
  Nan::Set(target, Nan::New("ImageData").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked());
}
/*
 * Initialize a new ImageData object.
 */
NAN_METHOD(ImageData::New) {
  if (!info.IsConstructCall()) {
    return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
  }
  Local<TypedArray> dataArray;
  uint32_t width;
  uint32_t height;
  int length;
  if (info[0]->IsUint32() && info[1]->IsUint32()) {
    width = Nan::To<uint32_t>(info[0]).FromMaybe(0);
    if (width == 0) {
      Nan::ThrowRangeError("The source width is zero.");
      return;
    }
    height = Nan::To<uint32_t>(info[1]).FromMaybe(0);
    if (height == 0) {
      Nan::ThrowRangeError("The source height is zero.");
      return;
    }
    length = width * height * 4; // ImageData(w, h) constructor assumes 4 BPP; documented.
    dataArray = Uint8ClampedArray::New(ArrayBuffer::New(Isolate::GetCurrent(), length), 0, length);
  } else if (info[0]->IsUint8ClampedArray() && info[1]->IsUint32()) {
    dataArray = info[0].As<Uint8ClampedArray>();
    length = dataArray->Length();
    if (length == 0) {
      Nan::ThrowRangeError("The input data has a zero byte length.");
      return;
    }
    // Don't assert that the ImageData length is a multiple of four because some
    // data formats are not 4 BPP.
    width = Nan::To<uint32_t>(info[1]).FromMaybe(0);
    if (width == 0) {
      Nan::ThrowRangeError("The source width is zero.");
      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<uint32_t>(info[2]).FromMaybe(0);
    } else { // Calculate height assuming 4 BPP
      int size = length / 4;
      height = size / width;
    }
  } else if (info[0]->IsUint16Array() && info[1]->IsUint32()) { // Intended for RGB16_565 format
  dataArray = info[0].As<Uint16Array>();
  length = dataArray->Length();
  if (length == 0) {
    Nan::ThrowRangeError("The input data has a zero byte length.");
    return;
  }
  width = Nan::To<uint32_t>(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<uint32_t>(info[2]).FromMaybe(0);
  } 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)");
    return;
  }
  Nan::TypedArrayContents<uint8_t> dataPtr(dataArray);
  ImageData *imageData = new ImageData(reinterpret_cast<uint8_t*>(*dataPtr), width, height);
  imageData->Wrap(info.This());
  Nan::Set(info.This(), Nan::New("data").ToLocalChecked(), dataArray).Check();
  info.GetReturnValue().Set(info.This());
}
/*
 * 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<ImageData>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(imageData->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<ImageData>(info.This());
  info.GetReturnValue().Set(Nan::New<Number>(imageData->height()));
}
node_modules/canvas/src/ImageData.h
New file
@@ -0,0 +1,27 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include <nan.h>
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <v8.h>
class ImageData: public Nan::ObjectWrap {
  public:
    static Nan::Persistent<v8::FunctionTemplate> constructor;
    static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
    static NAN_METHOD(New);
    static NAN_GETTER(GetWidth);
    static NAN_GETTER(GetHeight);
    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) {}
  private:
    int _width;
    int _height;
    uint8_t *_data;
};
node_modules/canvas/src/InstanceData.h
New file
@@ -0,0 +1,8 @@
#include <napi.h>
struct InstanceData {
  Napi::FunctionReference ImageBackendCtor;
  Napi::FunctionReference PdfBackendCtor;
  Napi::FunctionReference SvgBackendCtor;
  Napi::FunctionReference CanvasCtor;
};
node_modules/canvas/src/JPEGStream.h
New file
@@ -0,0 +1,167 @@
#pragma once
#include "closure.h"
#include <jpeglib.h>
#include <jerror.h>
/*
 * Expanded data destination object for closure output,
 * inspired by IJG's jdatadst.c
 */
struct closure_destination_mgr {
  jpeg_destination_mgr pub;
  JpegClosure* closure;
  JOCTET *buffer;
  int bufsize;
};
void
init_closure_destination(j_compress_ptr cinfo){
  // we really don't have to do anything here
}
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;
  v8::Local<v8::Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize).ToLocalChecked();
  // emit "data"
  v8::Local<v8::Value> argv[2] = {
      Nan::Null()
    , buf
  };
  dest->closure->cb.Call(sizeof argv / sizeof *argv, argv, &async);
  dest->buffer = (JOCTET *)malloc(dest->bufsize);
  cinfo->dest->next_output_byte = dest->buffer;
  cinfo->dest->free_in_buffer = dest->bufsize;
  return true;
}
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;
  /* emit remaining data */
  v8::Local<v8::Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize - dest->pub.free_in_buffer).ToLocalChecked();
  v8::Local<v8::Value> data_argv[2] = {
      Nan::Null()
    , buf
  };
  dest->closure->cb.Call(sizeof data_argv / sizeof *data_argv, data_argv, &async);
  // emit "end"
  v8::Local<v8::Value> end_argv[2] = {
      Nan::Null()
    , Nan::Null()
  };
  dest->closure->cb.Call(sizeof end_argv / sizeof *end_argv, end_argv, &async);
}
void
jpeg_closure_dest(j_compress_ptr cinfo, JpegClosure* closure, int bufsize){
  closure_destination_mgr * dest;
  /* The destination object is made permanent so that multiple JPEG images
   * can be written to the same buffer without re-executing jpeg_mem_dest.
   */
  if (cinfo->dest == NULL) {  /* first time for this JPEG object? */
    cinfo->dest = (struct jpeg_destination_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
         sizeof(closure_destination_mgr));
  }
  dest = (closure_destination_mgr *) cinfo->dest;
  cinfo->dest->init_destination = &init_closure_destination;
  cinfo->dest->empty_output_buffer = &empty_closure_output_buffer;
  cinfo->dest->term_destination = &term_closure_destination;
  dest->closure = closure;
  dest->bufsize = bufsize;
  dest->buffer = (JOCTET *)malloc(bufsize);
  cinfo->dest->next_output_byte = dest->buffer;
  cinfo->dest->free_in_buffer = dest->bufsize;
}
void encode_jpeg(jpeg_compress_struct cinfo, cairo_surface_t *surface, int quality, bool progressive, int chromaHSampFactor, int chromaVSampFactor) {
  int w = cairo_image_surface_get_width(surface);
  int h = cairo_image_surface_get_height(surface);
  cinfo.in_color_space = JCS_RGB;
  cinfo.input_components = 3;
  cinfo.image_width = w;
  cinfo.image_height = h;
  jpeg_set_defaults(&cinfo);
  if (progressive)
    jpeg_simple_progression(&cinfo);
  jpeg_set_quality(&cinfo, quality, (quality < 25) ? 0 : 1);
  cinfo.comp_info[0].h_samp_factor = chromaHSampFactor;
  cinfo.comp_info[0].v_samp_factor = chromaVSampFactor;
  JSAMPROW slr;
  jpeg_start_compress(&cinfo, TRUE);
  unsigned char *dst;
  unsigned int *src = (unsigned int *)cairo_image_surface_get_data(surface);
  int sl = 0;
  dst = (unsigned char *)malloc(w * 3);
  while (sl < h) {
    unsigned char *dp = dst;
    int x = 0;
    while (x < w) {
      dp[0] = (*src >> 16) & 255;
      dp[1] = (*src >> 8) & 255;
      dp[2] = *src & 255;
      src++;
      dp += 3;
      x++;
    }
    slr = dst;
    jpeg_write_scanlines(&cinfo, &slr, 1);
    sl++;
  }
  free(dst);
  jpeg_finish_compress(&cinfo);
  jpeg_destroy_compress(&cinfo);
}
void
write_to_jpeg_stream(cairo_surface_t *surface, int bufsize, JpegClosure* closure) {
  jpeg_compress_struct cinfo;
  jpeg_error_mgr jerr;
  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_compress(&cinfo);
  jpeg_closure_dest(&cinfo, closure, bufsize);
  encode_jpeg(
    cinfo,
    surface,
    closure->quality,
    closure->progressive,
    closure->chromaSubsampling,
    closure->chromaSubsampling);
}
void
write_to_jpeg_buffer(cairo_surface_t* surface, JpegClosure* closure) {
  jpeg_compress_struct cinfo;
  jpeg_error_mgr jerr;
  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_compress(&cinfo);
  cinfo.client_data = closure;
  cinfo.dest = closure->jpeg_dest_mgr;
  encode_jpeg(
    cinfo,
    surface,
    closure->quality,
    closure->progressive,
    closure->chromaSubsampling,
    closure->chromaSubsampling);
}
node_modules/canvas/src/PNG.h
New file
@@ -0,0 +1,292 @@
#pragma once
#include <cairo.h>
#include "closure.h"
#include <cmath> // round
#include <cstdlib>
#include <cstring>
#include <png.h>
#include <pngconf.h>
#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__)
#define likely(expr) (__builtin_expect (!!(expr), 1))
#define unlikely(expr) (__builtin_expect (!!(expr), 0))
#else
#define likely(expr) (expr)
#define unlikely(expr) (expr)
#endif
static void canvas_png_flush(png_structp png_ptr) {
    /* Do nothing; fflush() is said to be just a waste of energy. */
    (void) png_ptr;   /* Stifle compiler warning */
}
/* Converts native endian xRGB => RGBx bytes */
static void canvas_convert_data_to_bytes(png_structp png, png_row_infop row_info, png_bytep data) {
    unsigned int i;
    for (i = 0; i < row_info->rowbytes; i += 4) {
        uint8_t *b = &data[i];
        uint32_t pixel;
        memcpy(&pixel, b, sizeof (uint32_t));
        b[0] = (pixel & 0xff0000) >> 16;
        b[1] = (pixel & 0x00ff00) >>  8;
        b[2] = (pixel & 0x0000ff) >>  0;
        b[3] = 0;
    }
}
/* Unpremultiplies data and converts native endian ARGB => RGBA bytes */
static void canvas_unpremultiply_data(png_structp png, png_row_infop row_info, png_bytep data) {
    unsigned int i;
    for (i = 0; i < row_info->rowbytes; i += 4) {
        uint8_t *b = &data[i];
        uint32_t pixel;
        uint8_t  alpha;
        memcpy(&pixel, b, sizeof (uint32_t));
        alpha = (pixel & 0xff000000) >> 24;
        if (alpha == 0) {
            b[0] = b[1] = b[2] = b[3] = 0;
        } else {
            b[0] = (((pixel & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
            b[1] = (((pixel & 0x00ff00) >>  8) * 255 + alpha / 2) / alpha;
            b[2] = (((pixel & 0x0000ff) >>  0) * 255 + alpha / 2) / alpha;
            b[3] = alpha;
        }
    }
}
/* Converts RGB16_565 format data to RGBA32 */
static void canvas_convert_565_to_888(png_structp png, png_row_infop row_info, png_bytep data) {
  // Loop in reverse to unpack in-place.
  for (ptrdiff_t col = row_info->width - 1; col >= 0; col--) {
    uint8_t* src = &data[col * sizeof(uint16_t)];
    uint8_t* dst = &data[col * 3];
    uint16_t pixel;
    memcpy(&pixel, src, sizeof(uint16_t));
    // Convert and rescale to the full 0-255 range
    // See http://stackoverflow.com/a/29326693
    const uint8_t red5 = (pixel & 0xF800) >> 11;
    const uint8_t green6 = (pixel & 0x7E0) >> 5;
    const uint8_t blue5 = (pixel & 0x001F);
    dst[0] = ((red5 * 255 + 15) / 31);
    dst[1] = ((green6 * 255 + 31) / 63);
    dst[2] = ((blue5 * 255 + 15) / 31);
  }
}
struct canvas_png_write_closure_t {
    cairo_write_func_t write_func;
    PngClosure* closure;
};
#ifdef PNG_SETJMP_SUPPORTED
bool setjmp_wrapper(png_structp png) {
    return setjmp(png_jmpbuf(png));
}
#endif
static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr write_func, canvas_png_write_closure_t *closure) {
    unsigned int i;
    cairo_status_t status = CAIRO_STATUS_SUCCESS;
    uint8_t *data;
    png_structp png;
    png_infop info;
    png_bytep *volatile rows = NULL;
    png_color_16 white;
    int png_color_type;
    int bpc;
    unsigned int width = cairo_image_surface_get_width(surface);
    unsigned int height = cairo_image_surface_get_height(surface);
    data = cairo_image_surface_get_data(surface);
    if (data == NULL) {
        status = CAIRO_STATUS_SURFACE_TYPE_MISMATCH;
        return status;
    }
    cairo_surface_flush(surface);
    if (width == 0 || height == 0) {
        status = CAIRO_STATUS_WRITE_ERROR;
        return status;
    }
    rows = (png_bytep *) malloc(height * sizeof (png_byte*));
    if (unlikely(rows == NULL)) {
        status = CAIRO_STATUS_NO_MEMORY;
        return status;
    }
    int stride = cairo_image_surface_get_stride(surface);
    for (i = 0; i < height; i++) {
        rows[i] = (png_byte *) data + i * stride;
    }
#ifdef PNG_USER_MEM_SUPPORTED
     png = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL, NULL, NULL);
#else
    png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
#endif
    if (unlikely(png == NULL)) {
        status = CAIRO_STATUS_NO_MEMORY;
        free(rows);
        return status;
    }
    info = png_create_info_struct (png);
    if (unlikely(info == NULL)) {
        status = CAIRO_STATUS_NO_MEMORY;
        png_destroy_write_struct(&png, &info);
        free(rows);
        return status;
    }
#ifdef PNG_SETJMP_SUPPORTED
    if (setjmp_wrapper(png)) {
        png_destroy_write_struct(&png, &info);
        free(rows);
        return status;
    }
#endif
    png_set_write_fn(png, closure, write_func, canvas_png_flush);
    png_set_compression_level(png, closure->closure->compressionLevel);
    png_set_filter(png, 0, closure->closure->filters);
    if (closure->closure->resolution != 0) {
        uint32_t res = static_cast<uint32_t>(round(static_cast<double>(closure->closure->resolution) * 39.3701));
        png_set_pHYs(png, info, res, res, PNG_RESOLUTION_METER);
    }
    cairo_format_t format = cairo_image_surface_get_format(surface);
    switch (format) {
    case CAIRO_FORMAT_ARGB32:
        bpc = 8;
        png_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
        break;
#ifdef CAIRO_FORMAT_RGB30
    case CAIRO_FORMAT_RGB30:
        bpc = 10;
        png_color_type = PNG_COLOR_TYPE_RGB;
        break;
#endif
    case CAIRO_FORMAT_RGB24:
        bpc = 8;
        png_color_type = PNG_COLOR_TYPE_RGB;
        break;
    case CAIRO_FORMAT_A8:
        bpc = 8;
        png_color_type = PNG_COLOR_TYPE_GRAY;
        break;
    case CAIRO_FORMAT_A1:
        bpc = 1;
        png_color_type = PNG_COLOR_TYPE_GRAY;
#ifndef WORDS_BIGENDIAN
        png_set_packswap(png);
#endif
        break;
    case CAIRO_FORMAT_RGB16_565:
        bpc = 8; // 565 gets upconverted to 888
        png_color_type = PNG_COLOR_TYPE_RGB;
        break;
    case CAIRO_FORMAT_INVALID:
    default:
        status = CAIRO_STATUS_INVALID_FORMAT;
        png_destroy_write_struct(&png, &info);
        free(rows);
        return status;
    }
    if ((format == CAIRO_FORMAT_A8 || format == CAIRO_FORMAT_A1) &&
      closure->closure->palette != NULL) {
      png_color_type = PNG_COLOR_TYPE_PALETTE;
    }
    png_set_IHDR(png, info, width, height, bpc, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
    if (png_color_type == PNG_COLOR_TYPE_PALETTE) {
      size_t nColors = closure->closure->nPaletteColors;
      uint8_t* colors = closure->closure->palette;
      uint8_t backgroundIndex = closure->closure->backgroundIndex;
      png_colorp pngPalette = (png_colorp)png_malloc(png, nColors * sizeof(png_colorp));
      png_bytep transparency = (png_bytep)png_malloc(png, nColors * sizeof(png_bytep));
      for (i = 0; i < nColors; i++) {
        pngPalette[i].red = colors[4 * i];
        pngPalette[i].green = colors[4 * i + 1];
        pngPalette[i].blue = colors[4 * i + 2];
        transparency[i] = colors[4 * i + 3];
      }
      png_set_PLTE(png, info, pngPalette, nColors);
      png_set_tRNS(png, info, transparency, nColors, NULL);
      png_set_packing(png); // pack pixels
      // have libpng free palette and trans:
      png_data_freer(png, info, PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_PLTE | PNG_FREE_TRNS);
      png_color_16 bkg;
      bkg.index = backgroundIndex;
      png_set_bKGD(png, info, &bkg);
    }
    if (png_color_type != PNG_COLOR_TYPE_PALETTE) {
      white.gray = (1 << bpc) - 1;
      white.red = white.blue = white.green = white.gray;
      png_set_bKGD(png, info, &white);
    }
    /* We have to call png_write_info() before setting up the write
     * transformation, since it stores data internally in 'png'
     * that is needed for the write transformation functions to work.
     */
    png_write_info(png, info);
    if (png_color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
        png_set_write_user_transform_fn(png, canvas_unpremultiply_data);
    } else if (format == CAIRO_FORMAT_RGB16_565) {
        png_set_write_user_transform_fn(png, canvas_convert_565_to_888);
    } else if (png_color_type == PNG_COLOR_TYPE_RGB) {
        png_set_write_user_transform_fn(png, canvas_convert_data_to_bytes);
        png_set_filler(png, 0, PNG_FILLER_AFTER);
    }
    png_write_image(png, rows);
    png_write_end(png, info);
    png_destroy_write_struct(&png, &info);
    free(rows);
    return status;
}
static void canvas_stream_write_func(png_structp png, png_bytep data, png_size_t size) {
    cairo_status_t status;
    struct canvas_png_write_closure_t *png_closure;
    png_closure = (struct canvas_png_write_closure_t *) png_get_io_ptr(png);
    status = png_closure->write_func(png_closure->closure, data, size);
    if (unlikely(status)) {
        cairo_status_t *error = (cairo_status_t *) png_get_error_ptr(png);
        if (*error == CAIRO_STATUS_SUCCESS) {
            *error = status;
        }
        png_error(png, NULL);
    }
}
static cairo_status_t canvas_write_to_png_stream(cairo_surface_t *surface, cairo_write_func_t write_func, PngClosure* closure) {
    struct canvas_png_write_closure_t png_closure;
    if (cairo_surface_status(surface)) {
        return cairo_surface_status(surface);
    }
    png_closure.write_func = write_func;
    png_closure.closure = closure;
    return canvas_write_png(surface, canvas_stream_write_func, &png_closure);
}
node_modules/canvas/src/Point.h
New file
@@ -0,0 +1,11 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
template <typename T>
class Point {
  public:
    T x, y;
    Point(T x=0, T y=0): x(x), y(y) {}
    Point(const Point&) = default;
    Point& operator=(const Point&) = default;
};
node_modules/canvas/src/Util.h
New file
@@ -0,0 +1,9 @@
#pragma once
#include <cctype>
inline bool streq_casein(std::string& str1, std::string& str2) {
  return str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), [](char& c1, char& c2) {
    return c1 == c2 || std::toupper(c1) == std::toupper(c2);
  });
}
node_modules/canvas/src/backend/Backend.cc
New file
@@ -0,0 +1,112 @@
#include "Backend.h"
#include <string>
Backend::Backend(std::string name, int width, int height)
  : name(name)
  , width(width)
  , height(height)
{}
Backend::~Backend()
{
  this->destroySurface();
}
void Backend::init(const Nan::FunctionCallbackInfo<v8::Value> &info) {
  int width  = 0;
  int height = 0;
  if (info[0]->IsNumber()) width  = Nan::To<uint32_t>(info[0]).FromMaybe(0);
  if (info[1]->IsNumber()) height = Nan::To<uint32_t>(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;
}
cairo_surface_t* Backend::recreateSurface()
{
  this->destroySurface();
  return this->createSurface();
}
DLL_PUBLIC cairo_surface_t* Backend::getSurface() {
  if (!surface) createSurface();
  return surface;
}
void Backend::destroySurface()
{
  if(this->surface)
  {
    cairo_surface_destroy(this->surface);
    this->surface = NULL;
  }
}
std::string Backend::getName()
{
  return name;
}
int Backend::getWidth()
{
  return this->width;
}
void Backend::setWidth(int width_)
{
  this->width = width_;
  this->recreateSurface();
}
int Backend::getHeight()
{
  return this->height;
}
void Backend::setHeight(int height_)
{
  this->height = height_;
  this->recreateSurface();
}
bool Backend::isSurfaceValid(){
  bool hadSurface = surface != NULL;
  bool isValid = true;
  cairo_status_t status = cairo_surface_status(getSurface());
  if (status != CAIRO_STATUS_SUCCESS) {
    error = cairo_status_to_string(status);
    isValid = false;
  }
  if (!hadSurface)
    destroySurface();
  return isValid;
}
BackendOperationNotAvailable::BackendOperationNotAvailable(Backend* backend,
  std::string operation_name)
  : backend(backend)
  , operation_name(operation_name)
{
  msg = "operation " + operation_name +
    " not supported by backend " + backend->getName();
};
BackendOperationNotAvailable::~BackendOperationNotAvailable() throw() {};
const char* BackendOperationNotAvailable::what() const throw()
{
  return msg.c_str();
};
node_modules/canvas/src/backend/Backend.h
New file
@@ -0,0 +1,69 @@
#pragma once
#include <cairo.h>
#include "../dll_visibility.h"
#include <exception>
#include <nan.h>
#include <string>
#include <v8.h>
class Canvas;
class Backend : public Nan::ObjectWrap
{
  private:
    const std::string name;
    const char* error = NULL;
  protected:
    int width;
    int height;
    cairo_surface_t* surface = nullptr;
    Canvas* canvas = nullptr;
    Backend(std::string name, int width, int height);
    static void init(const Nan::FunctionCallbackInfo<v8::Value> &info);
    static Backend *construct(int width, int height){ return nullptr; }
  public:
    virtual ~Backend();
    void setCanvas(Canvas* canvas);
    virtual cairo_surface_t* createSurface() = 0;
    virtual cairo_surface_t* recreateSurface();
    DLL_PUBLIC cairo_surface_t* getSurface();
    virtual void destroySurface();
    DLL_PUBLIC std::string getName();
    DLL_PUBLIC int getWidth();
    virtual void setWidth(int width);
    DLL_PUBLIC int getHeight();
    virtual void setHeight(int height);
    // Overridden by ImageBackend. SVG and PDF thus always return INVALID.
    virtual cairo_format_t getFormat() {
      return CAIRO_FORMAT_INVALID;
    }
    bool isSurfaceValid();
    inline const char* getError(){ return error; }
};
class BackendOperationNotAvailable: public std::exception
{
  private:
    Backend* backend;
    std::string operation_name;
    std::string msg;
  public:
    BackendOperationNotAvailable(Backend* backend, std::string operation_name);
    ~BackendOperationNotAvailable() throw();
    const char* what() const throw();
};
node_modules/canvas/src/backend/ImageBackend.cc
New file
@@ -0,0 +1,74 @@
#include "ImageBackend.h"
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);
}
// This returns an approximate value only, suitable for Nan::AdjustExternalMemory.
// The formats that don't map to intrinsic types (RGB30, A1) round up.
int32_t ImageBackend::approxBytesPerPixel() {
  switch (format) {
  case CAIRO_FORMAT_ARGB32:
  case CAIRO_FORMAT_RGB24:
    return 4;
#ifdef CAIRO_FORMAT_RGB30
  case CAIRO_FORMAT_RGB30:
    return 3;
#endif
  case CAIRO_FORMAT_RGB16_565:
    return 2;
  case CAIRO_FORMAT_A8:
  case CAIRO_FORMAT_A1:
    return 1;
  default:
    return 0;
  }
}
cairo_surface_t* ImageBackend::createSurface() {
  assert(!surface);
  surface = cairo_image_surface_create(format, width, height);
  assert(surface);
  Nan::AdjustExternalMemory(approxBytesPerPixel() * width * height);
  return surface;
}
void ImageBackend::destroySurface() {
  if (surface) {
    cairo_surface_destroy(surface);
    surface = nullptr;
    Nan::AdjustExternalMemory(-approxBytesPerPixel() * width * height);
  }
}
cairo_format_t ImageBackend::getFormat() {
  return format;
}
void ImageBackend::setFormat(cairo_format_t _format) {
    this->format = _format;
}
Nan::Persistent<FunctionTemplate> ImageBackend::constructor;
void ImageBackend::Initialize(Local<Object> target) {
    Nan::HandleScope scope;
    Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(ImageBackend::New);
    ImageBackend::constructor.Reset(ctor);
    ctor->InstanceTemplate()->SetInternalFieldCount(1);
    ctor->SetClassName(Nan::New<String>("ImageBackend").ToLocalChecked());
  Nan::Set(target,
           Nan::New<String>("ImageBackend").ToLocalChecked(),
           Nan::GetFunction(ctor).ToLocalChecked()).Check();
}
NAN_METHOD(ImageBackend::New) {
  init(info);
}
node_modules/canvas/src/backend/ImageBackend.h
New file
@@ -0,0 +1,26 @@
#pragma once
#include "Backend.h"
#include <v8.h>
class ImageBackend : public Backend
{
  private:
    cairo_surface_t* createSurface();
    void destroySurface();
    cairo_format_t format = DEFAULT_FORMAT;
  public:
    ImageBackend(int width, int height);
    static Backend *construct(int width, int height);
    cairo_format_t getFormat();
    void setFormat(cairo_format_t format);
    int32_t approxBytesPerPixel();
    static Nan::Persistent<v8::FunctionTemplate> constructor;
    static void Initialize(v8::Local<v8::Object> target);
    static NAN_METHOD(New);
    const static cairo_format_t DEFAULT_FORMAT = CAIRO_FORMAT_ARGB32;
};
node_modules/canvas/src/backend/PdfBackend.cc
New file
@@ -0,0 +1,53 @@
#include "PdfBackend.h"
#include <cairo-pdf.h>
#include "../Canvas.h"
#include "../closure.h"
using namespace v8;
PdfBackend::PdfBackend(int width, int height)
  : Backend("pdf", width, height) {
  createSurface();
}
PdfBackend::~PdfBackend() {
  cairo_surface_finish(surface);
  if (_closure) delete _closure;
  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);
  return surface;
}
cairo_surface_t* PdfBackend::recreateSurface() {
  cairo_pdf_surface_set_size(surface, width, height);
  return surface;
}
Nan::Persistent<FunctionTemplate> PdfBackend::constructor;
void PdfBackend::Initialize(Local<Object> target) {
  Nan::HandleScope scope;
  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(PdfBackend::New);
  PdfBackend::constructor.Reset(ctor);
  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(Nan::New<String>("PdfBackend").ToLocalChecked());
  Nan::Set(target,
           Nan::New<String>("PdfBackend").ToLocalChecked(),
           Nan::GetFunction(ctor).ToLocalChecked()).Check();
}
NAN_METHOD(PdfBackend::New) {
  init(info);
}
node_modules/canvas/src/backend/PdfBackend.h
New file
@@ -0,0 +1,24 @@
#pragma once
#include "Backend.h"
#include "../closure.h"
#include <v8.h>
class PdfBackend : public Backend
{
  private:
    cairo_surface_t* createSurface();
    cairo_surface_t* recreateSurface();
  public:
    PdfSvgClosure* _closure = NULL;
    inline PdfSvgClosure* closure() { return _closure; }
    PdfBackend(int width, int height);
    ~PdfBackend();
    static Backend *construct(int width, int height);
    static Nan::Persistent<v8::FunctionTemplate> constructor;
    static void Initialize(v8::Local<v8::Object> target);
    static NAN_METHOD(New);
};
node_modules/canvas/src/backend/SvgBackend.cc
New file
@@ -0,0 +1,61 @@
#include "SvgBackend.h"
#include <cairo-svg.h>
#include "../Canvas.h"
#include "../closure.h"
#include <cassert>
using namespace v8;
SvgBackend::SvgBackend(int width, int height)
  : Backend("svg", width, height) {
  createSurface();
}
SvgBackend::~SvgBackend() {
  cairo_surface_finish(surface);
  if (_closure) {
    delete _closure;
    _closure = nullptr;
  }
  destroySurface();
}
Backend *SvgBackend::construct(int width, int height){
  return new SvgBackend(width, height);
}
cairo_surface_t* SvgBackend::createSurface() {
  assert(!_closure);
  _closure = new PdfSvgClosure(canvas);
  surface = cairo_svg_surface_create_for_stream(PdfSvgClosure::writeVec, _closure, width, height);
  return surface;
}
cairo_surface_t* SvgBackend::recreateSurface() {
  cairo_surface_finish(surface);
  delete _closure;
  _closure = nullptr;
  cairo_surface_destroy(surface);
  return createSurface();
 }
Nan::Persistent<FunctionTemplate> SvgBackend::constructor;
void SvgBackend::Initialize(Local<Object> target) {
  Nan::HandleScope scope;
  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(SvgBackend::New);
  SvgBackend::constructor.Reset(ctor);
  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(Nan::New<String>("SvgBackend").ToLocalChecked());
  Nan::Set(target,
           Nan::New<String>("SvgBackend").ToLocalChecked(),
           Nan::GetFunction(ctor).ToLocalChecked()).Check();
}
NAN_METHOD(SvgBackend::New) {
  init(info);
}
node_modules/canvas/src/backend/SvgBackend.h
New file
@@ -0,0 +1,24 @@
#pragma once
#include "Backend.h"
#include "../closure.h"
#include <v8.h>
class SvgBackend : public Backend
{
  private:
    cairo_surface_t* createSurface();
    cairo_surface_t* recreateSurface();
  public:
    PdfSvgClosure* _closure = NULL;
    inline PdfSvgClosure* closure() { return _closure; }
    SvgBackend(int width, int height);
    ~SvgBackend();
    static Backend *construct(int width, int height);
    static Nan::Persistent<v8::FunctionTemplate> constructor;
    static void Initialize(v8::Local<v8::Object> target);
    static NAN_METHOD(New);
};
node_modules/canvas/src/bmp/BMPParser.cc
New file
@@ -0,0 +1,457 @@
#include "BMPParser.h"
#include <cassert>
using namespace std;
using namespace BMPParser;
#define MAX_IMG_SIZE 10000
#define E(cond, msg) if(cond) return setErr(msg)
#define EU(cond, msg) if(cond) return setErrUnsupported(msg)
#define EX(cond, msg) if(cond) return setErrUnknown(msg)
#define I1() get<char>()
#define U1() get<uint8_t>()
#define I2() get<int16_t>()
#define U2() get<uint16_t>()
#define I4() get<int32_t>()
#define U4() get<uint32_t>()
#define I1UC() get<char, false>()
#define U1UC() get<uint8_t, false>()
#define I2UC() get<int16_t, false>()
#define U2UC() get<uint16_t, false>()
#define I4UC() get<int32_t, false>()
#define U4UC() get<uint32_t, false>()
#define CHECK_OVERRUN(ptr, size, type) \
  if((ptr) + (size) - data > len){ \
    setErr("unexpected end of file"); \
    return type(); \
  }
Parser::~Parser(){
  data = nullptr;
  ptr = nullptr;
  if(imgd){
    delete[] imgd;
    imgd = nullptr;
  }
}
void Parser::parse(uint8_t *buf, int bufSize, uint8_t *format){
  assert(status == Status::EMPTY);
  data = ptr = buf;
  len = bufSize;
  // Start parsing file header
  setOp("file header");
  // File header signature
  string fhSig = getStr(2);
  string temp = "file header signature";
  EU(fhSig == "BA", temp + " \"BA\"");
  EU(fhSig == "CI", temp + " \"CI\"");
  EU(fhSig == "CP", temp + " \"CP\"");
  EU(fhSig == "IC", temp + " \"IC\"");
  EU(fhSig == "PT", temp + " \"PT\"");
  EX(fhSig != "BM", temp); // BM
  // Length of the file should not be larger than `len`
  E(U4() > static_cast<uint32_t>(len), "inconsistent file size");
  // Skip unused values
  skip(4);
  // Offset where the pixel array (bitmap data) can be found
  auto imgdOffset = U4();
  // Start parsing DIB header
  setOp("DIB header");
  // Prepare some variables in case they are needed
  uint32_t compr = 0;
  uint32_t redShift = 0, greenShift = 0, blueShift = 0, alphaShift = 0;
  uint32_t redMask = 0, greenMask = 0, blueMask = 0, alphaMask = 0;
  double redMultp = 0, greenMultp = 0, blueMultp = 0, alphaMultp = 0;
  /**
   * Type of the DIB (device-independent bitmap) header
   * is determined by its size. Most BMP files use BITMAPINFOHEADER.
   */
  auto dibSize = U4();
  temp = "DIB header";
  EU(dibSize == 64, temp + " \"OS22XBITMAPHEADER\"");
  EU(dibSize == 16, temp + " \"OS22XBITMAPHEADER\"");
  uint32_t infoHeader = dibSize == 40 ? 1 :
                        dibSize == 52 ? 2 :
                        dibSize == 56 ? 3 :
                        dibSize == 108 ? 4 :
                        dibSize == 124 ? 5 : 0;
  // BITMAPCOREHEADER, BITMAP*INFOHEADER, BITMAP*HEADER
  auto isDibValid = dibSize == 12 || infoHeader;
  EX(!isDibValid, temp);
  // Image width
  w = dibSize == 12 ? U2() : I4();
  E(!w, "image width is 0");
  E(w < 0, "negative image width");
  E(w > MAX_IMG_SIZE, "too large image width");
  // Image height (specification allows negative values)
  h = dibSize == 12 ? U2() : I4();
  E(!h, "image height is 0");
  E(h > MAX_IMG_SIZE, "too large image height");
  bool isHeightNegative = h < 0;
  if(isHeightNegative) h = -h;
  // Number of color planes (must be 1)
  E(U2() != 1, "number of color planes must be 1");
  // Bits per pixel (color depth)
  auto bpp = U2();
  auto isBppValid = bpp == 1 || bpp == 4 || bpp == 8 || bpp == 16 || bpp == 24 || bpp == 32;
  EU(!isBppValid, "color depth");
  // Calculate image data size and padding
  uint32_t expectedImgdSize = (((w * bpp + 31) >> 5) << 2) * h;
  uint32_t rowPadding = (-w * bpp & 31) >> 3;
  uint32_t imgdSize = 0;
  // Color palette data
  uint8_t* paletteStart = nullptr;
  uint32_t palColNum = 0;
  if(infoHeader){
    // Compression type
    compr = U4();
    temp = "compression type";
    EU(compr == 1, temp + " \"BI_RLE8\"");
    EU(compr == 2, temp + " \"BI_RLE4\"");
    EU(compr == 4, temp + " \"BI_JPEG\"");
    EU(compr == 5, temp + " \"BI_PNG\"");
    EU(compr == 6, temp + " \"BI_ALPHABITFIELDS\"");
    EU(compr == 11, temp + " \"BI_CMYK\"");
    EU(compr == 12, temp + " \"BI_CMYKRLE8\"");
    EU(compr == 13, temp + " \"BI_CMYKRLE4\"");
    // BI_RGB and BI_BITFIELDS
    auto isComprValid = compr == 0 || compr == 3;
    EX(!isComprValid, temp);
    // Ensure that BI_BITFIELDS appears only with 16-bit or 32-bit color
    E(compr == 3 && !(bpp == 16 || bpp == 32), "compression BI_BITFIELDS can be used only with 16-bit and 32-bit color depth");
    // Size of the image data
    imgdSize = U4();
    // Horizontal and vertical resolution (ignored)
    skip(8);
    // Number of colors in the palette or 0 if no palette is present
    palColNum = U4();
    EU(palColNum && bpp > 8, "color palette and bit depth combination");
    if(palColNum) paletteStart = data + dibSize + 14;
    // Number of important colors used or 0 if all colors are important (generally ignored)
    skip(4);
    if(infoHeader >= 2){
      // If BI_BITFIELDS are used, calculate masks, otherwise ignore them
      if(compr == 3){
        calcMaskShift(redShift, redMask, redMultp);
        calcMaskShift(greenShift, greenMask, greenMultp);
        calcMaskShift(blueShift, blueMask, blueMultp);
        if(infoHeader >= 3) calcMaskShift(alphaShift, alphaMask, alphaMultp);
        if(status == Status::ERROR) return;
      }else{
        skip(16);
      }
      // Ensure that the color space is LCS_WINDOWS_COLOR_SPACE or sRGB
      if(infoHeader >= 4 && !palColNum){
        string colSpace = getStr(4, 1);
        EU(colSpace != "Win " && colSpace != "sRGB", "color space \"" + colSpace + "\"");
      }
    }
  }
  // Skip to the image data (there may be other chunks between, but they are optional)
  E(ptr - data > imgdOffset, "image data overlaps with another structure");
  ptr = data + imgdOffset;
  // Start parsing image data
  setOp("image data");
  if(!imgdSize){
    // Value 0 is allowed only for BI_RGB compression type
    E(compr != 0, "missing image data size");
    imgdSize = expectedImgdSize;
  }else{
    E(imgdSize < expectedImgdSize, "invalid image data size");
  }
  // Ensure that all image data is present
  E(ptr - data + imgdSize > len, "not enough image data");
  // Direction of reading rows
  int yStart = h - 1;
  int yEnd = -1;
  int dy = isHeightNegative ? 1 : -1;
  // In case of negative height, read rows backward
  if(isHeightNegative){
    yStart = 0;
    yEnd = h;
  }
  // Allocate output image data array
  int buffLen = w * h << 2;
  imgd = new (nothrow) uint8_t[buffLen];
  E(!imgd, "unable to allocate memory");
  // Prepare color values
  uint8_t color[4] = {0};
  uint8_t &red = color[0];
  uint8_t &green = color[1];
  uint8_t &blue = color[2];
  uint8_t &alpha = color[3];
  // Check if pre-multiplied alpha is used
  bool premul = format ? format[4] : 0;
  // Main loop
  for(int y = yStart; y != yEnd; y += dy){
    // Use in-byte offset for bpp < 8
    uint8_t colOffset = 0;
    uint8_t cval = 0;
    uint32_t val = 0;
    for(int x = 0; x != w; x++){
      // Index in the output image data
      int i = (x + y * w) << 2;
      switch(compr){
        case 0: // BI_RGB
          switch(bpp){
            case 1:
              if(colOffset) ptr--;
              cval = (U1UC() >> (7 - colOffset)) & 1;
              if(palColNum){
                uint8_t* entry = paletteStart + (cval << 2);
                blue = get<uint8_t>(entry);
                green = get<uint8_t>(entry + 1);
                red = get<uint8_t>(entry + 2);
                if(status == Status::ERROR) return;
              }else{
                red = green = blue = cval ? 255 : 0;
              }
              alpha = 255;
              colOffset = (colOffset + 1) & 7;
              break;
            case 4:
              if(colOffset) ptr--;
              cval = (U1UC() >> (4 - colOffset)) & 15;
              if(palColNum){
                uint8_t* entry = paletteStart + (cval << 2);
                blue = get<uint8_t>(entry);
                green = get<uint8_t>(entry + 1);
                red = get<uint8_t>(entry + 2);
                if(status == Status::ERROR) return;
              }else{
                red = green = blue = cval << 4;
              }
              alpha = 255;
              colOffset = (colOffset + 4) & 7;
              break;
            case 8:
              cval = U1UC();
              if(palColNum){
                uint8_t* entry = paletteStart + (cval << 2);
                blue = get<uint8_t>(entry);
                green = get<uint8_t>(entry + 1);
                red = get<uint8_t>(entry + 2);
                if(status == Status::ERROR) return;
              }else{
                red = green = blue = cval;
              }
              alpha = 255;
              break;
            case 16:
              // RGB555
              val = U1UC();
              val |= U1UC() << 8;
              red = (val >> 10) << 3;
              green = (val >> 5) << 3;
              blue = val << 3;
              alpha = 255;
              break;
            case 24:
              blue = U1UC();
              green = U1UC();
              red = U1UC();
              alpha = 255;
              break;
            case 32:
              blue = U1UC();
              green = U1UC();
              red = U1UC();
              if(infoHeader >= 3){
                alpha = U1UC();
              }else{
                alpha = 255;
                skip(1);
              }
              break;
          }
          break;
        case 3: // BI_BITFIELDS
          uint32_t col = bpp == 16 ? U2UC() : U4UC();
          red = ((col >> redShift) & redMask) * redMultp + .5;
          green = ((col >> greenShift) & greenMask) * greenMultp + .5;
          blue = ((col >> blueShift) & blueMask) * blueMultp + .5;
          alpha = alphaMask ? ((col >> alphaShift) & alphaMask) * alphaMultp + .5 : 255;
          break;
      }
      /**
       * Pixel format:
       *  red,
       *  green,
       *  blue,
       *  alpha,
       *  is alpha pre-multiplied
       * Default is [0, 1, 2, 3, 0]
       */
      if(premul && alpha != 255){
        double a = alpha / 255.;
        red = static_cast<uint8_t>(red * a + .5);
        green = static_cast<uint8_t>(green * a + .5);
        blue = static_cast<uint8_t>(blue * a + .5);
      }
      if(format){
        imgd[i] = color[format[0]];
        imgd[i + 1] = color[format[1]];
        imgd[i + 2] = color[format[2]];
        imgd[i + 3] = color[format[3]];
      }else{
        imgd[i] = red;
        imgd[i + 1] = green;
        imgd[i + 2] = blue;
        imgd[i + 3] = alpha;
      }
    }
    // Skip unused bytes in the current row
    skip(rowPadding);
  }
  if(status == Status::ERROR) return;
  status = Status::OK;
};
void Parser::clearImgd(){ imgd = nullptr; }
int32_t Parser::getWidth() const{ return w; }
int32_t Parser::getHeight() const{ return h; }
uint8_t *Parser::getImgd() const{ return imgd; }
Status Parser::getStatus() const{ return status; }
string Parser::getErrMsg() const{
  return "Error while processing " + getOp() + " - " + err;
}
template <typename T, bool check> inline T Parser::get(){
  if(check)
    CHECK_OVERRUN(ptr, sizeof(T), T);
  T val = *(T*)ptr;
  ptr += sizeof(T);
  return val;
}
template <typename T, bool check> inline T Parser::get(uint8_t* pointer){
  if(check)
    CHECK_OVERRUN(pointer, sizeof(T), T);
  T val = *(T*)pointer;
  return val;
}
string Parser::getStr(int size, bool reverse){
  CHECK_OVERRUN(ptr, size, string);
  string val = "";
  while(size--){
    if(reverse) val = string(1, static_cast<char>(*ptr++)) + val;
    else val += static_cast<char>(*ptr++);
  }
  return val;
}
inline void Parser::skip(int size){
  CHECK_OVERRUN(ptr, size, void);
  ptr += size;
}
void Parser::calcMaskShift(uint32_t& shift, uint32_t& mask, double& multp){
  mask = U4();
  shift = 0;
  if(mask == 0) return;
  while(~mask & 1){
    mask >>= 1;
    shift++;
  }
  E(mask & (mask + 1), "invalid color mask");
  multp = 255. / mask;
}
void Parser::setOp(string val){
  if(status != Status::EMPTY) return;
  op = val;
}
string Parser::getOp() const{
  return op;
}
void Parser::setErrUnsupported(string msg){
  setErr("unsupported " + msg);
}
void Parser::setErrUnknown(string msg){
  setErr("unknown " + msg);
}
void Parser::setErr(string msg){
  if(status != Status::EMPTY) return;
  err = msg;
  status = Status::ERROR;
}
string Parser::getErr() const{
  return err;
}
node_modules/canvas/src/bmp/BMPParser.h
New file
@@ -0,0 +1,60 @@
#pragma once
#ifdef ERROR
#define ERROR_ ERROR
#undef ERROR
#endif
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <string>
namespace BMPParser{
  enum Status{
    EMPTY,
    OK,
    ERROR,
  };
  class Parser{
  public:
    Parser()=default;
    ~Parser();
    void parse(uint8_t *buf, int bufSize, uint8_t *format=nullptr);
    void clearImgd();
    int32_t getWidth() const;
    int32_t getHeight() const;
    uint8_t *getImgd() const;
    Status getStatus() const;
    std::string getErrMsg() const;
  private:
    Status status = Status::EMPTY;
    uint8_t *data = nullptr;
    uint8_t *ptr = nullptr;
    int len = 0;
    int32_t w = 0;
    int32_t h = 0;
    uint8_t *imgd = nullptr;
    std::string err = "";
    std::string op = "";
    template <typename T, bool check=true> inline T get();
    template <typename T, bool check=true> inline T get(uint8_t* pointer);
    std::string getStr(int len, bool reverse=false);
    inline void skip(int len);
    void calcMaskShift(uint32_t& shift, uint32_t& mask, double& multp);
    void setOp(std::string val);
    std::string getOp() const;
    void setErrUnsupported(std::string msg);
    void setErrUnknown(std::string msg);
    void setErr(std::string msg);
    std::string getErr() const;
  };
}
#ifdef ERROR_
#define ERROR ERROR_
#undef ERROR_
#endif
node_modules/canvas/src/bmp/LICENSE.md
New file
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>
node_modules/canvas/src/closure.cc
New file
@@ -0,0 +1,26 @@
#include "closure.h"
#ifdef HAVE_JPEG
void JpegClosure::init_destination(j_compress_ptr cinfo) {
  JpegClosure* closure = (JpegClosure*)cinfo->client_data;
  closure->vec.resize(PAGE_SIZE);
  closure->jpeg_dest_mgr->next_output_byte = &closure->vec[0];
  closure->jpeg_dest_mgr->free_in_buffer = closure->vec.size();
}
boolean JpegClosure::empty_output_buffer(j_compress_ptr cinfo) {
  JpegClosure* closure = (JpegClosure*)cinfo->client_data;
  size_t currentSize = closure->vec.size();
  closure->vec.resize(currentSize * 1.5);
  closure->jpeg_dest_mgr->next_output_byte = &closure->vec[currentSize];
  closure->jpeg_dest_mgr->free_in_buffer = closure->vec.size() - currentSize;
  return true;
}
void JpegClosure::term_destination(j_compress_ptr cinfo) {
  JpegClosure* closure = (JpegClosure*)cinfo->client_data;
  size_t finalSize = closure->vec.size() - closure->jpeg_dest_mgr->free_in_buffer;
  closure->vec.resize(finalSize);
}
#endif
node_modules/canvas/src/closure.h
New file
@@ -0,0 +1,81 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include "Canvas.h"
#ifdef HAVE_JPEG
#include <jpeglib.h>
#endif
#include <nan.h>
#include <png.h>
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <vector>
#ifndef PAGE_SIZE
  #define PAGE_SIZE 4096
#endif
/*
 * Image encoding closures.
 */
struct Closure {
  std::vector<uint8_t> vec;
  Nan::Callback cb;
  Canvas* canvas = nullptr;
  cairo_status_t status = CAIRO_STATUS_SUCCESS;
  static cairo_status_t writeVec(void *c, const uint8_t *odata, unsigned len) {
    Closure* closure = static_cast<Closure*>(c);
    try {
      closure->vec.insert(closure->vec.end(), odata, odata + len);
    } catch (const std::bad_alloc &) {
      return CAIRO_STATUS_NO_MEMORY;
    }
    return CAIRO_STATUS_SUCCESS;
  }
  Closure(Canvas* canvas) : canvas(canvas) {};
};
struct PdfSvgClosure : Closure {
  PdfSvgClosure(Canvas* canvas) : Closure(canvas) {};
};
struct PngClosure : Closure {
  uint32_t compressionLevel = 6;
  uint32_t filters = PNG_ALL_FILTERS;
  uint32_t resolution = 0; // 0 = unspecified
  // Indexed PNGs:
  uint32_t nPaletteColors = 0;
  uint8_t* palette = nullptr;
  uint8_t backgroundIndex = 0;
  PngClosure(Canvas* canvas) : Closure(canvas) {};
};
#ifdef HAVE_JPEG
struct JpegClosure : Closure {
  uint32_t quality = 75;
  uint32_t chromaSubsampling = 2;
  bool progressive = false;
  jpeg_destination_mgr* jpeg_dest_mgr = nullptr;
  static void init_destination(j_compress_ptr cinfo);
  static boolean empty_output_buffer(j_compress_ptr cinfo);
  static void term_destination(j_compress_ptr cinfo);
  JpegClosure(Canvas* canvas) : Closure(canvas) {
    jpeg_dest_mgr = new jpeg_destination_mgr;
    jpeg_dest_mgr->init_destination = init_destination;
    jpeg_dest_mgr->empty_output_buffer = empty_output_buffer;
    jpeg_dest_mgr->term_destination = term_destination;
  };
  ~JpegClosure() {
    delete jpeg_dest_mgr;
  }
};
#endif
node_modules/canvas/src/color.cc
New file
@@ -0,0 +1,779 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "color.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <map>
#include <string>
// Compatibility with Visual Studio versions prior to VS2015
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif
/*
 * Parse integer value
 */
template <typename parsed_t>
static bool
parse_integer(const char** pStr, parsed_t *pParsed) {
  parsed_t& c = *pParsed;
  const char*& str = *pStr;
  int8_t sign=1;
  c = 0;
  if (*str == '-') {
    sign=-1;
    ++str;
  }
  else if (*str == '+')
    ++str;
  if (*str >= '0' && *str <= '9') {
     do {
       c *= 10;
       c += *str++ - '0';
     } while (*str >= '0' && *str <= '9');
   } else {
     return false;
   }
   if (sign<0)
    c=-c;
   return true;
}
/*
 * Parse CSS <number> value
 * Adapted from http://crackprogramming.blogspot.co.il/2012/10/implement-atof.html
 */
template <typename parsed_t>
static bool
parse_css_number(const char** pStr, parsed_t *pParsed) {
   parsed_t &parsed = *pParsed;
   const char*& str = *pStr;
   const char* startStr = str;
   if (!str || !*str)
       return false;
   parsed_t integerPart = 0;
   parsed_t fractionPart = 0;
   int divisorForFraction = 1;
   int sign = 1;
   int exponent = 0;
   int digits = 0;
   bool inFraction = false;
   if (*str == '-') {
       ++str;
       sign = -1;
   }
   else if (*str == '+')
       ++str;
   while (*str != '\0') {
       if (*str >= '0' && *str <= '9') {
          if (digits>=std::numeric_limits<parsed_t>::digits10) {
            if (!inFraction)
              return false;
          }
          else {
            ++digits;
            if (inFraction) {
                fractionPart = fractionPart*10 + (*str - '0');
                divisorForFraction *= 10;
            }
            else {
                integerPart = integerPart*10 + (*str - '0');
            }
          }
       }
       else if (*str == '.') {
           if (inFraction)
               break;
           else
               inFraction = true;
       }
       else if (*str == 'e') {
          ++str;
          if (!parse_integer(&str, &exponent))
            return false;
          break;
       }
       else
          break;
       ++str;
   }
   if (str != startStr) {
      parsed = sign * (integerPart + fractionPart/divisorForFraction);
      for (;exponent>0;--exponent)
        parsed *= 10;
      for (;exponent<0;++exponent)
        parsed /= 10;
      return true;
   }
   return false;
}
/*
 * Clip value to the range [minValue, maxValue]
 */
template <typename T>
static T
clip(T value, T minValue, T maxValue) {
  if (value > maxValue)
      value = maxValue;
  if (value < minValue)
      value = minValue;
  return value;
}
/*
 * Wrap value to the range [0, limit]
 */
template <typename T>
static T
wrap_float(T value, T limit) {
  return fmod(fmod(value, limit) + limit, limit);
}
/*
 * Wrap value to the range [0, limit] - currently-unused integer version of wrap_float
 */
// template <typename T>
// static T wrap_int(T value, T limit) {
//   return (value % limit + limit) % limit;
// }
/*
 * Parse color channel value
 */
static bool
parse_rgb_channel(const char** pStr, uint8_t *pChannel) {
  int channel;
  if (parse_integer(pStr, &channel)) {
    *pChannel = clip(channel, 0, 255);
    return true;
  }
  return false;
}
/*
 * Parse a value in degrees
 */
static bool
parse_degrees(const char** pStr, float *pDegrees) {
  float degrees;
  if (parse_css_number(pStr, &degrees)) {
    *pDegrees = wrap_float(degrees, 360.0f);
    return true;
  }
  return false;
}
/*
 * Parse and clip a percentage value. Returns a float in the range [0, 1].
 */
static bool
parse_clipped_percentage(const char** pStr, float *pFraction) {
  float percentage;
  bool result = parse_css_number(pStr,&percentage);
  const char*& str = *pStr;
  if (result) {
    if (*str == '%') {
      ++str;
      *pFraction = clip(percentage, 0.0f, 100.0f) / 100.0f;
      return result;
    }
  }
  return false;
}
/*
 * Macros to help with parsing inside rgba_from_*_string
 */
#define WHITESPACE \
  while (' ' == *str) ++str;
#define WHITESPACE_OR_COMMA \
  while (' ' == *str || ',' == *str) ++str;
#define CHANNEL(NAME) \
   if (!parse_rgb_channel(&str, &NAME)) \
    return 0; \
#define HUE(NAME) \
   if (!parse_degrees(&str, &NAME)) \
    return 0;
#define SATURATION(NAME) \
   if (!parse_clipped_percentage(&str, &NAME)) \
    return 0;
#define LIGHTNESS(NAME) SATURATION(NAME)
#define ALPHA(NAME) \
    if (*str >= '1' && *str <= '9') { \
      NAME = 1; \
    } else { \
      if ('0' == *str) { \
        NAME = 0; \
        ++str; \
      } \
      if ('.' == *str) { \
        ++str; \
        NAME = 0; \
        float n = .1f; \
        while (*str >= '0' && *str <= '9') { \
          NAME += (*str++ - '0') * n; \
          n *= .1f; \
        } \
      } \
    } \
    do {} while (0) // require trailing semicolon
/*
 * Named colors.
 */
static const std::map<std::string, uint32_t> named_colors = {
    { "transparent", 0xFFFFFF00}
  , { "aliceblue", 0xF0F8FFFF }
  , { "antiquewhite", 0xFAEBD7FF }
  , { "aqua", 0x00FFFFFF }
  , { "aquamarine", 0x7FFFD4FF }
  , { "azure", 0xF0FFFFFF }
  , { "beige", 0xF5F5DCFF }
  , { "bisque", 0xFFE4C4FF }
  , { "black", 0x000000FF }
  , { "blanchedalmond", 0xFFEBCDFF }
  , { "blue", 0x0000FFFF }
  , { "blueviolet", 0x8A2BE2FF }
  , { "brown", 0xA52A2AFF }
  , { "burlywood", 0xDEB887FF }
  , { "cadetblue", 0x5F9EA0FF }
  , { "chartreuse", 0x7FFF00FF }
  , { "chocolate", 0xD2691EFF }
  , { "coral", 0xFF7F50FF }
  , { "cornflowerblue", 0x6495EDFF }
  , { "cornsilk", 0xFFF8DCFF }
  , { "crimson", 0xDC143CFF }
  , { "cyan", 0x00FFFFFF }
  , { "darkblue", 0x00008BFF }
  , { "darkcyan", 0x008B8BFF }
  , { "darkgoldenrod", 0xB8860BFF }
  , { "darkgray", 0xA9A9A9FF }
  , { "darkgreen", 0x006400FF }
  , { "darkgrey", 0xA9A9A9FF }
  , { "darkkhaki", 0xBDB76BFF }
  , { "darkmagenta", 0x8B008BFF }
  , { "darkolivegreen", 0x556B2FFF }
  , { "darkorange", 0xFF8C00FF }
  , { "darkorchid", 0x9932CCFF }
  , { "darkred", 0x8B0000FF }
  , { "darksalmon", 0xE9967AFF }
  , { "darkseagreen", 0x8FBC8FFF }
  , { "darkslateblue", 0x483D8BFF }
  , { "darkslategray", 0x2F4F4FFF }
  , { "darkslategrey", 0x2F4F4FFF }
  , { "darkturquoise", 0x00CED1FF }
  , { "darkviolet", 0x9400D3FF }
  , { "deeppink", 0xFF1493FF }
  , { "deepskyblue", 0x00BFFFFF }
  , { "dimgray", 0x696969FF }
  , { "dimgrey", 0x696969FF }
  , { "dodgerblue", 0x1E90FFFF }
  , { "firebrick", 0xB22222FF }
  , { "floralwhite", 0xFFFAF0FF }
  , { "forestgreen", 0x228B22FF }
  , { "fuchsia", 0xFF00FFFF }
  , { "gainsboro", 0xDCDCDCFF }
  , { "ghostwhite", 0xF8F8FFFF }
  , { "gold", 0xFFD700FF }
  , { "goldenrod", 0xDAA520FF }
  , { "gray", 0x808080FF }
  , { "green", 0x008000FF }
  , { "greenyellow", 0xADFF2FFF }
  , { "grey", 0x808080FF }
  , { "honeydew", 0xF0FFF0FF }
  , { "hotpink", 0xFF69B4FF }
  , { "indianred", 0xCD5C5CFF }
  , { "indigo", 0x4B0082FF }
  , { "ivory", 0xFFFFF0FF }
  , { "khaki", 0xF0E68CFF }
  , { "lavender", 0xE6E6FAFF }
  , { "lavenderblush", 0xFFF0F5FF }
  , { "lawngreen", 0x7CFC00FF }
  , { "lemonchiffon", 0xFFFACDFF }
  , { "lightblue", 0xADD8E6FF }
  , { "lightcoral", 0xF08080FF }
  , { "lightcyan", 0xE0FFFFFF }
  , { "lightgoldenrodyellow", 0xFAFAD2FF }
  , { "lightgray", 0xD3D3D3FF }
  , { "lightgreen", 0x90EE90FF }
  , { "lightgrey", 0xD3D3D3FF }
  , { "lightpink", 0xFFB6C1FF }
  , { "lightsalmon", 0xFFA07AFF }
  , { "lightseagreen", 0x20B2AAFF }
  , { "lightskyblue", 0x87CEFAFF }
  , { "lightslategray", 0x778899FF }
  , { "lightslategrey", 0x778899FF }
  , { "lightsteelblue", 0xB0C4DEFF }
  , { "lightyellow", 0xFFFFE0FF }
  , { "lime", 0x00FF00FF }
  , { "limegreen", 0x32CD32FF }
  , { "linen", 0xFAF0E6FF }
  , { "magenta", 0xFF00FFFF }
  , { "maroon", 0x800000FF }
  , { "mediumaquamarine", 0x66CDAAFF }
  , { "mediumblue", 0x0000CDFF }
  , { "mediumorchid", 0xBA55D3FF }
  , { "mediumpurple", 0x9370DBFF }
  , { "mediumseagreen", 0x3CB371FF }
  , { "mediumslateblue", 0x7B68EEFF }
  , { "mediumspringgreen", 0x00FA9AFF }
  , { "mediumturquoise", 0x48D1CCFF }
  , { "mediumvioletred", 0xC71585FF }
  , { "midnightblue", 0x191970FF }
  , { "mintcream", 0xF5FFFAFF }
  , { "mistyrose", 0xFFE4E1FF }
  , { "moccasin", 0xFFE4B5FF }
  , { "navajowhite", 0xFFDEADFF }
  , { "navy", 0x000080FF }
  , { "oldlace", 0xFDF5E6FF }
  , { "olive", 0x808000FF }
  , { "olivedrab", 0x6B8E23FF }
  , { "orange", 0xFFA500FF }
  , { "orangered", 0xFF4500FF }
  , { "orchid", 0xDA70D6FF }
  , { "palegoldenrod", 0xEEE8AAFF }
  , { "palegreen", 0x98FB98FF }
  , { "paleturquoise", 0xAFEEEEFF }
  , { "palevioletred", 0xDB7093FF }
  , { "papayawhip", 0xFFEFD5FF }
  , { "peachpuff", 0xFFDAB9FF }
  , { "peru", 0xCD853FFF }
  , { "pink", 0xFFC0CBFF }
  , { "plum", 0xDDA0DDFF }
  , { "powderblue", 0xB0E0E6FF }
  , { "purple", 0x800080FF }
  , { "rebeccapurple", 0x663399FF } // Source: CSS Color Level 4 draft
  , { "red", 0xFF0000FF }
  , { "rosybrown", 0xBC8F8FFF }
  , { "royalblue", 0x4169E1FF }
  , { "saddlebrown", 0x8B4513FF }
  , { "salmon", 0xFA8072FF }
  , { "sandybrown", 0xF4A460FF }
  , { "seagreen", 0x2E8B57FF }
  , { "seashell", 0xFFF5EEFF }
  , { "sienna", 0xA0522DFF }
  , { "silver", 0xC0C0C0FF }
  , { "skyblue", 0x87CEEBFF }
  , { "slateblue", 0x6A5ACDFF }
  , { "slategray", 0x708090FF }
  , { "slategrey", 0x708090FF }
  , { "snow", 0xFFFAFAFF }
  , { "springgreen", 0x00FF7FFF }
  , { "steelblue", 0x4682B4FF }
  , { "tan", 0xD2B48CFF }
  , { "teal", 0x008080FF }
  , { "thistle", 0xD8BFD8FF }
  , { "tomato", 0xFF6347FF }
  , { "turquoise", 0x40E0D0FF }
  , { "violet", 0xEE82EEFF }
  , { "wheat", 0xF5DEB3FF }
  , { "white", 0xFFFFFFFF }
  , { "whitesmoke", 0xF5F5F5FF }
  , { "yellow", 0xFFFF00FF }
  , { "yellowgreen", 0x9ACD32FF }
};
/*
 * Hex digit int val.
 */
static int
h(char c) {
  switch (c) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      return c - '0';
    case 'a':
    case 'b':
    case 'c':
    case 'd':
    case 'e':
    case 'f':
      return (c - 'a') + 10;
    case 'A':
    case 'B':
    case 'C':
    case 'D':
    case 'E':
    case 'F':
      return (c - 'A') + 10;
  }
  return 0;
}
/*
 * Return rgba_t from rgba.
 */
rgba_t
rgba_create(uint32_t rgba) {
  rgba_t color;
  color.r = (double) (rgba >> 24) / 255;
  color.g = (double) (rgba >> 16 & 0xff) / 255;
  color.b = (double) (rgba >> 8 & 0xff) / 255;
  color.a = (double) (rgba & 0xff) / 255;
  return color;
}
/*
 * Return a string representation of the color.
 */
void
rgba_to_string(rgba_t rgba, char *buf, size_t len) {
  if (1 == rgba.a) {
    snprintf(buf, len, "#%.2x%.2x%.2x",
      static_cast<int>(round(rgba.r * 255)),
      static_cast<int>(round(rgba.g * 255)),
      static_cast<int>(round(rgba.b * 255)));
  } else {
    snprintf(buf, len, "rgba(%d, %d, %d, %.2f)",
      static_cast<int>(round(rgba.r * 255)),
      static_cast<int>(round(rgba.g * 255)),
      static_cast<int>(round(rgba.b * 255)),
      rgba.a);
  }
}
/*
 * Return rgba from (r,g,b,a).
 */
static inline int32_t
rgba_from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
  return
      r << 24
    | g << 16
    | b << 8
    | a;
}
/*
 * Helper function used in rgba_from_hsla().
 * Based on http://dev.w3.org/csswg/css-color-4/#hsl-to-rgb
 */
static float
hue_to_rgb(float t1, float t2, float hue) {
  if (hue < 0)
    hue += 6;
  if (hue >= 6)
    hue -= 6;
  if (hue < 1)
    return (t2 - t1) * hue + t1;
  else if (hue < 3)
    return t2;
  else if (hue < 4)
    return (t2 - t1) * (4 - hue) + t1;
  else
    return t1;
}
/*
 * Return rgba from (h,s,l,a).
 * Expects h values in the range [0, 360), and s, l, a in the range [0, 1].
 * Adapted from http://dev.w3.org/csswg/css-color-4/#hsl-to-rgb
 */
static inline int32_t
rgba_from_hsla(float h_deg, float s, float l, float a) {
  uint8_t r, g, b;
  float h = (6 * h_deg) / 360.0f, m1, m2;
  if (l<=0.5)
    m2=l*(s+1);
  else
    m2=l+s-l*s;
  m1 = l*2 - m2;
  // Scale and round the RGB components
  r = (uint8_t)floor(hue_to_rgb(m1, m2, h + 2) * 255 + 0.5);
  g = (uint8_t)floor(hue_to_rgb(m1, m2, h    ) * 255 + 0.5);
  b = (uint8_t)floor(hue_to_rgb(m1, m2, h - 2) * 255 + 0.5);
  return rgba_from_rgba(r, g, b, (uint8_t) (a * 255));
}
/*
 * Return rgba from (h,s,l).
 * Expects h values in the range [0, 360), and s, l in the range [0, 1].
 */
static inline int32_t
rgba_from_hsl(float h_deg, float s, float l) {
  return rgba_from_hsla(h_deg, s, l, 1.0);
}
/*
 * Return rgba from (r,g,b).
 */
static int32_t
rgba_from_rgb(uint8_t r, uint8_t g, uint8_t b) {
  return rgba_from_rgba(r, g, b, 255);
}
/*
 * Return rgba from #RRGGBBAA
 */
static int32_t
rgba_from_hex8_string(const char *str) {
  return rgba_from_rgba(
    (h(str[0]) << 4) + h(str[1]),
    (h(str[2]) << 4) + h(str[3]),
    (h(str[4]) << 4) + h(str[5]),
    (h(str[6]) << 4) + h(str[7])
  );
}
/*
 * Return rgb from "#RRGGBB".
 */
static int32_t
rgba_from_hex6_string(const char *str) {
  return rgba_from_rgb(
      (h(str[0]) << 4) + h(str[1])
    , (h(str[2]) << 4) + h(str[3])
    , (h(str[4]) << 4) + h(str[5])
    );
}
/*
* Return rgba from #RGBA
*/
static int32_t
rgba_from_hex4_string(const char *str) {
  return rgba_from_rgba(
    (h(str[0]) << 4) + h(str[0]),
    (h(str[1]) << 4) + h(str[1]),
    (h(str[2]) << 4) + h(str[2]),
    (h(str[3]) << 4) + h(str[3])
  );
}
/*
 * Return rgb from "#RGB"
 */
static int32_t
rgba_from_hex3_string(const char *str) {
  return rgba_from_rgb(
      (h(str[0]) << 4) + h(str[0])
    , (h(str[1]) << 4) + h(str[1])
    , (h(str[2]) << 4) + h(str[2])
    );
}
/*
 * Return rgb from "rgb()"
 */
static int32_t
rgba_from_rgb_string(const char *str, short *ok) {
  if (str == strstr(str, "rgb(")) {
    str += 4;
    WHITESPACE;
    uint8_t r = 0, g = 0, b = 0;
    CHANNEL(r);
    WHITESPACE_OR_COMMA;
    CHANNEL(g);
    WHITESPACE_OR_COMMA;
    CHANNEL(b);
    WHITESPACE;
    return *ok = 1, rgba_from_rgb(r, g, b);
  }
  return *ok = 0;
}
/*
 * Return rgb from "rgba()"
 */
static int32_t
rgba_from_rgba_string(const char *str, short *ok) {
  if (str == strstr(str, "rgba(")) {
    str += 5;
    WHITESPACE;
    uint8_t r = 0, g = 0, b = 0;
    float a = 1.f;
    CHANNEL(r);
    WHITESPACE_OR_COMMA;
    CHANNEL(g);
    WHITESPACE_OR_COMMA;
    CHANNEL(b);
    WHITESPACE_OR_COMMA;
    ALPHA(a);
    WHITESPACE;
    return *ok = 1, rgba_from_rgba(r, g, b, (int) (a * 255));
  }
  return *ok = 0;
}
/*
 * Return rgb from "hsla()"
 */
static int32_t
rgba_from_hsla_string(const char *str, short *ok) {
  if (str == strstr(str, "hsla(")) {
    str += 5;
    WHITESPACE;
    float h_deg = 0;
    float s = 0, l = 0;
    float a = 0;
    HUE(h_deg);
    WHITESPACE_OR_COMMA;
    SATURATION(s);
    WHITESPACE_OR_COMMA;
    LIGHTNESS(l);
    WHITESPACE_OR_COMMA;
    ALPHA(a);
    WHITESPACE;
    return *ok = 1, rgba_from_hsla(h_deg, s, l, a);
  }
  return *ok = 0;
}
/*
 * Return rgb from "hsl()"
 */
static int32_t
rgba_from_hsl_string(const char *str, short *ok) {
  if (str == strstr(str, "hsl(")) {
    str += 4;
    WHITESPACE;
    float h_deg = 0;
    float s = 0, l = 0;
    HUE(h_deg);
    WHITESPACE_OR_COMMA;
    SATURATION(s);
    WHITESPACE_OR_COMMA;
    LIGHTNESS(l);
    WHITESPACE;
    return *ok = 1, rgba_from_hsl(h_deg, s, l);
  }
  return *ok = 0;
}
/*
 * Return rgb from:
 *
 *  - "#RGB"
 *  - "#RGBA"
 *  - "#RRGGBB"
 *  - "#RRGGBBAA"
 *
 */
static int32_t
rgba_from_hex_string(const char *str, short *ok) {
  size_t len = strlen(str);
  *ok = 1;
  switch (len) {
    case 8: return rgba_from_hex8_string(str);
    case 6: return rgba_from_hex6_string(str);
    case 4: return rgba_from_hex4_string(str);
    case 3: return rgba_from_hex3_string(str);
  }
  return *ok = 0;
}
/*
 * Return named color value.
 */
static int32_t
rgba_from_name_string(const char *str, short *ok) {
  std::string lowered(str);
  std::transform(lowered.begin(), lowered.end(), lowered.begin(), tolower);
  auto color = named_colors.find(lowered);
  if (color != named_colors.end()) {
    return *ok = 1, color->second;
  }
  return *ok = 0;
}
/*
 * Return rgb from:
 *
 *  - #RGB
 *  - #RGBA
 *  - #RRGGBB
 *  - #RRGGBBAA
 *  - rgb(r,g,b)
 *  - rgba(r,g,b,a)
 *  - hsl(h,s,l)
 *  - hsla(h,s,l,a)
 *  - name
 *
 */
int32_t
rgba_from_string(const char *str, short *ok) {
  if ('#' == str[0])
    return rgba_from_hex_string(++str, ok);
  if (str == strstr(str, "rgba"))
    return rgba_from_rgba_string(str, ok);
  if (str == strstr(str, "rgb"))
    return rgba_from_rgb_string(str, ok);
  if (str == strstr(str, "hsla"))
    return rgba_from_hsla_string(str, ok);
  if (str == strstr(str, "hsl"))
    return rgba_from_hsl_string(str, ok);
  return rgba_from_name_string(str, ok);
}
/*
 * Inspect the given rgba color.
 */
void
rgba_inspect(int32_t rgba) {
  printf("rgba(%d,%d,%d,%d)\n"
    , rgba >> 24 & 0xff
    , rgba >> 16 & 0xff
    , rgba >> 8 & 0xff
    , rgba & 0xff
    );
}
node_modules/canvas/src/color.h
New file
@@ -0,0 +1,30 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <cstdlib>
/*
 * RGBA struct.
 */
typedef struct {
  double r, g, b, a;
} rgba_t;
/*
 * Prototypes.
 */
rgba_t
rgba_create(uint32_t rgba);
int32_t
rgba_from_string(const char *str, short *ok);
void
rgba_to_string(rgba_t rgba, char *buf, size_t len);
void
rgba_inspect(int32_t rgba);
node_modules/canvas/src/dll_visibility.h
New file
@@ -0,0 +1,20 @@
#ifndef DLL_PUBLIC
#if defined _WIN32
  #ifdef __GNUC__
    #define DLL_PUBLIC __attribute__ ((dllexport))
  #else
    #define DLL_PUBLIC __declspec(dllexport)
  #endif
  #define DLL_LOCAL
#else
  #if __GNUC__ >= 4
    #define DLL_PUBLIC __attribute__ ((visibility ("default")))
    #define DLL_LOCAL  __attribute__ ((visibility ("hidden")))
  #else
    #define DLL_PUBLIC
    #define DLL_LOCAL
  #endif
#endif
#endif
node_modules/canvas/src/init.cc
New file
@@ -0,0 +1,94 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include <cstdio>
#include <pango/pango.h>
#include <cairo.h>
#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0)
// CAIRO_FORMAT_RGB16_565: undeprecated in v1.10.0
// CAIRO_STATUS_INVALID_SIZE: v1.10.0
// CAIRO_FORMAT_INVALID: v1.10.0
// Lots of the compositing operators: v1.10.0
// JPEG MIME tracking: v1.10.0
// Note: CAIRO_FORMAT_RGB30 is v1.12.0 and still optional
#error("cairo v1.10.0 or later is required")
#endif
#include "Backends.h"
#include "Canvas.h"
#include "CanvasGradient.h"
#include "CanvasPattern.h"
#include "CanvasRenderingContext2d.h"
#include "Image.h"
#include "ImageData.h"
#include <ft2build.h>
#include FT_FREETYPE_H
using namespace v8;
// 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);
  Nan::Set(target, Nan::New<String>("cairoVersion").ToLocalChecked(), Nan::New<String>(cairo_version_string()).ToLocalChecked()).Check();
#ifdef HAVE_JPEG
#ifndef JPEG_LIB_VERSION_MAJOR
#ifdef JPEG_LIB_VERSION
#define JPEG_LIB_VERSION_MAJOR (JPEG_LIB_VERSION / 10)
#else
#define JPEG_LIB_VERSION_MAJOR 0
#endif
#endif
#ifndef JPEG_LIB_VERSION_MINOR
#ifdef JPEG_LIB_VERSION
#define JPEG_LIB_VERSION_MINOR (JPEG_LIB_VERSION % 10)
#else
#define JPEG_LIB_VERSION_MINOR 0
#endif
#endif
  char jpeg_version[10];
  static bool minor_gt_0 = JPEG_LIB_VERSION_MINOR > 0;
  if (minor_gt_0) {
    snprintf(jpeg_version, 10, "%d%c", JPEG_LIB_VERSION_MAJOR, JPEG_LIB_VERSION_MINOR + 'a' - 1);
  } else {
    snprintf(jpeg_version, 10, "%d", JPEG_LIB_VERSION_MAJOR);
  }
  Nan::Set(target, Nan::New<String>("jpegVersion").ToLocalChecked(), Nan::New<String>(jpeg_version).ToLocalChecked()).Check();
#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<String>("gifVersion").ToLocalChecked(), Nan::New<String>(gif_version).ToLocalChecked()).Check();
#else
  Nan::Set(target, Nan::New<String>("gifVersion").ToLocalChecked(), Nan::New<String>(GIF_LIB_VERSION).ToLocalChecked()).Check();
#endif
#endif
#ifdef HAVE_RSVG
  Nan::Set(target, Nan::New<String>("rsvgVersion").ToLocalChecked(), Nan::New<String>(LIBRSVG_VERSION).ToLocalChecked()).Check();
#endif
  Nan::Set(target, Nan::New<String>("pangoVersion").ToLocalChecked(), Nan::New<String>(PANGO_VERSION_STRING).ToLocalChecked()).Check();
  char freetype_version[10];
  snprintf(freetype_version, 10, "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
  Nan::Set(target, Nan::New<String>("freetypeVersion").ToLocalChecked(), Nan::New<String>(freetype_version).ToLocalChecked()).Check();
}
NODE_MODULE(canvas, init);
node_modules/canvas/src/register_font.cc
New file
@@ -0,0 +1,408 @@
#include "register_font.h"
#include <string>
#include <pango/pangocairo.h>
#include <pango/pango-fontmap.h>
#include <pango/pango.h>
#ifdef __APPLE__
#include <CoreText/CoreText.h>
#elif defined(_WIN32)
#include <windows.h>
#include <memory>
#else
#include <fontconfig/fontconfig.h>
#include <pango/pangofc-fontmap.h>
#endif
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_TRUETYPE_TABLES_H
#include FT_SFNT_NAMES_H
#include FT_TRUETYPE_IDS_H
#ifndef FT_SFNT_OS2
#define FT_SFNT_OS2 ft_sfnt_os2
#endif
// OSX seems to read the strings in MacRoman encoding and ignore Unicode entries.
// You can verify this by opening a TTF with both Unicode and Macroman on OSX.
// It uses the MacRoman name, while Fontconfig and Windows use Unicode
#ifdef __APPLE__
#define PREFERRED_PLATFORM_ID TT_PLATFORM_MACINTOSH
#define PREFERRED_ENCODING_ID TT_MAC_ID_ROMAN
#else
#define PREFERRED_PLATFORM_ID TT_PLATFORM_MICROSOFT
#define PREFERRED_ENCODING_ID TT_MS_ID_UNICODE_CS
#endif
// With PangoFcFontMaps (the pango font module on Linux) we're able to add a
// hook that lets us get perfect matching. Tie the conditions for enabling that
// feature to one variable
#if !defined(__APPLE__) && !defined(_WIN32) && PANGO_VERSION_CHECK(1, 47, 0)
#define PERFECT_MATCHES_ENABLED
#endif
#define IS_PREFERRED_ENC(X) \
  X.platform_id == PREFERRED_PLATFORM_ID && X.encoding_id == PREFERRED_ENCODING_ID
#ifdef PERFECT_MATCHES_ENABLED
// On Linux-like OSes using FontConfig, the PostScript name ranks higher than
// preferred family and family name since we'll use it to get perfect font
// matching (see fc_font_map_substitute_hook)
#define GET_NAME_RANK(X) \
  ((IS_PREFERRED_ENC(X) ? 1 : 0) << 2) | \
  ((X.name_id == TT_NAME_ID_PS_NAME ? 1 : 0) << 1) | \
  (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0)
#else
#define GET_NAME_RANK(X) \
  ((IS_PREFERRED_ENC(X) ? 1 : 0) << 1) | \
  (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0)
#endif
/*
 * Return a UTF-8 encoded string given a TrueType name buf+len
 * and its platform and encoding
 */
char *
to_utf8(FT_Byte* buf, FT_UInt len, FT_UShort pid, FT_UShort eid) {
  size_t ret_len = len * 4; // max chars in a utf8 string
  char *ret = (char*)malloc(ret_len + 1); // utf8 string + null
  if (!ret) return NULL;
  // In my testing of hundreds of fonts from the Google Font repo, the two types
  // of fonts are TT_PLATFORM_MICROSOFT with TT_MS_ID_UNICODE_CS encoding, or
  // TT_PLATFORM_MACINTOSH with TT_MAC_ID_ROMAN encoding. Usually both, never neither
  char const *fromcode;
  if (pid == TT_PLATFORM_MACINTOSH && eid == TT_MAC_ID_ROMAN) {
    fromcode = "MAC";
  } else if (pid == TT_PLATFORM_MICROSOFT && eid == TT_MS_ID_UNICODE_CS) {
    fromcode = "UTF-16BE";
  } else {
    free(ret);
    return NULL;
  }
  GIConv cd = g_iconv_open("UTF-8", fromcode);
  if (cd == (GIConv)-1) {
    free(ret);
    return NULL;
  }
  size_t inbytesleft = len;
  size_t outbytesleft = ret_len;
  size_t n_converted = g_iconv(cd, (char**)&buf, &inbytesleft, &ret, &outbytesleft);
  ret -= ret_len - outbytesleft; // rewind the pointers to their
  buf -= len - inbytesleft;      // original starting positions
  if (n_converted == (size_t)-1) {
    free(ret);
    return NULL;
  } else {
    ret[ret_len - outbytesleft] = '\0';
    return ret;
  }
}
/*
 * Find a family name in the face's name table, preferring the one the
 * system, fall back to the other
 */
char *
get_family_name(FT_Face face) {
  FT_SfntName name;
  int best_rank = -1;
  char* best_buf = NULL;
  for (unsigned i = 0; i < FT_Get_Sfnt_Name_Count(face); ++i) {
    FT_Get_Sfnt_Name(face, i, &name);
    if (
      name.name_id == TT_NAME_ID_FONT_FAMILY ||
#ifdef PERFECT_MATCHES_ENABLED
      name.name_id == TT_NAME_ID_PS_NAME ||
#endif
      name.name_id == TT_NAME_ID_PREFERRED_FAMILY
    ) {
      int rank = GET_NAME_RANK(name);
      if (rank > best_rank) {
        char *buf = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id);
        if (buf) {
          best_rank = rank;
          if (best_buf) free(best_buf);
          best_buf = buf;
#ifdef PERFECT_MATCHES_ENABLED
          // Prepend an '@' to the postscript name
          if (name.name_id == TT_NAME_ID_PS_NAME) {
            std::string best_buf_modified = "@";
            best_buf_modified += best_buf;
            free(best_buf);
            best_buf = strdup(best_buf_modified.c_str());
          }
#endif
        } else {
          free(buf);
        }
      }
    }
  }
  return best_buf;
}
PangoWeight
get_pango_weight(FT_UShort weight) {
  switch (weight) {
    case 100: return PANGO_WEIGHT_THIN;
    case 200: return PANGO_WEIGHT_ULTRALIGHT;
    case 300: return PANGO_WEIGHT_LIGHT;
    #if PANGO_VERSION >= PANGO_VERSION_ENCODE(1, 36, 7)
    case 350: return PANGO_WEIGHT_SEMILIGHT;
    #endif
    case 380: return PANGO_WEIGHT_BOOK;
    case 400: return PANGO_WEIGHT_NORMAL;
    case 500: return PANGO_WEIGHT_MEDIUM;
    case 600: return PANGO_WEIGHT_SEMIBOLD;
    case 700: return PANGO_WEIGHT_BOLD;
    case 800: return PANGO_WEIGHT_ULTRABOLD;
    case 900: return PANGO_WEIGHT_HEAVY;
    case 1000: return PANGO_WEIGHT_ULTRAHEAVY;
    default: return PANGO_WEIGHT_NORMAL;
  }
}
PangoStretch
get_pango_stretch(FT_UShort width) {
  switch (width) {
    case 1: return PANGO_STRETCH_ULTRA_CONDENSED;
    case 2: return PANGO_STRETCH_EXTRA_CONDENSED;
    case 3: return PANGO_STRETCH_CONDENSED;
    case 4: return PANGO_STRETCH_SEMI_CONDENSED;
    case 5: return PANGO_STRETCH_NORMAL;
    case 6: return PANGO_STRETCH_SEMI_EXPANDED;
    case 7: return PANGO_STRETCH_EXPANDED;
    case 8: return PANGO_STRETCH_EXTRA_EXPANDED;
    case 9: return PANGO_STRETCH_ULTRA_EXPANDED;
    default: return PANGO_STRETCH_NORMAL;
  }
}
PangoStyle
get_pango_style(FT_Long flags) {
  if (flags & FT_STYLE_FLAG_ITALIC) {
    return PANGO_STYLE_ITALIC;
  } else {
    return PANGO_STYLE_NORMAL;
  }
}
#ifdef _WIN32
std::unique_ptr<wchar_t[]>
u8ToWide(const char* str) {
  int iBufferSize = MultiByteToWideChar(CP_UTF8, 0, str, -1, (wchar_t*)NULL, 0);
  if(!iBufferSize){
    return nullptr;
  }
  std::unique_ptr<wchar_t[]> wpBufWString = std::unique_ptr<wchar_t[]>{ new wchar_t[static_cast<size_t>(iBufferSize)] };
  if(!MultiByteToWideChar(CP_UTF8, 0, str, -1, wpBufWString.get(), iBufferSize)){
    return nullptr;
  }
  return wpBufWString;
}
static unsigned long
stream_read_func(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count){
  HANDLE hFile = reinterpret_cast<HANDLE>(stream->descriptor.pointer);
  DWORD numberOfBytesRead;
  OVERLAPPED overlapped;
  overlapped.Offset = offset;
  overlapped.OffsetHigh = 0;
  overlapped.hEvent = NULL;
  if(!ReadFile(hFile, buffer, count, &numberOfBytesRead, &overlapped)){
    return 0;
  }
  return numberOfBytesRead;
};
static void
stream_close_func(FT_Stream stream){
  HANDLE hFile = reinterpret_cast<HANDLE>(stream->descriptor.pointer);
  CloseHandle(hFile);
}
#endif
/*
 * Return a PangoFontDescription that will resolve to the font file
 */
PangoFontDescription *
get_pango_font_description(unsigned char* filepath) {
  FT_Library library;
  FT_Face face;
  PangoFontDescription *desc = pango_font_description_new();
#ifdef _WIN32
  // FT_New_Face use fopen.
  // Unable to find the file when supplied the multibyte string path on the Windows platform and throw error "Could not parse font file".
  // This workaround fixes this by reading the font file uses win32 wide character API.
  std::unique_ptr<wchar_t[]> wFilepath = u8ToWide((char*)filepath);
  if(!wFilepath){
    return NULL;
  }
  HANDLE hFile = CreateFileW(
        wFilepath.get(),
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
  );
  if(!hFile){
    return NULL;
  }
  LARGE_INTEGER liSize;
  if(!GetFileSizeEx(hFile, &liSize)) {
    CloseHandle(hFile);
    return NULL;
  }
  FT_Open_Args args;
  args.flags = FT_OPEN_STREAM;
  FT_StreamRec stream;
  stream.base = NULL;
  stream.size = liSize.QuadPart;
  stream.pos = 0;
  stream.descriptor.pointer = hFile;
  stream.read = stream_read_func;
  stream.close = stream_close_func;
  args.stream = &stream;
  if (
    !FT_Init_FreeType(&library) &&
    !FT_Open_Face(library, &args, 0, &face)) {
#else
  if (!FT_Init_FreeType(&library) && !FT_New_Face(library, (const char*)filepath, 0, &face)) {
#endif
    TT_OS2 *table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
    if (table) {
      char *family = get_family_name(face);
      if (!family) {
        pango_font_description_free(desc);
        FT_Done_Face(face);
        FT_Done_FreeType(library);
        return NULL;
      }
      pango_font_description_set_family_static(desc, family);
      pango_font_description_set_weight(desc, get_pango_weight(table->usWeightClass));
      pango_font_description_set_stretch(desc, get_pango_stretch(table->usWidthClass));
      pango_font_description_set_style(desc, get_pango_style(face->style_flags));
      FT_Done_Face(face);
      FT_Done_FreeType(library);
      return desc;
    }
  }
  pango_font_description_free(desc);
  return NULL;
}
#ifdef PERFECT_MATCHES_ENABLED
static void
fc_font_map_substitute_hook(FcPattern *pat, gpointer data) {
  FcChar8 *family;
  for (int i = 0; FcPatternGetString(pat, FC_FAMILY, i, &family) == FcResultMatch; i++) {
    if (family[0] == '@') {
      FcPatternAddString(pat, FC_POSTSCRIPT_NAME, (FcChar8 *)family + 1);
      FcPatternRemove(pat, FC_FAMILY, i);
      i -= 1;
    }
  }
}
#endif
/*
 * Register font with the OS
 */
bool
register_font(unsigned char *filepath) {
  bool success;
  #ifdef __APPLE__
  CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false);
  success = CTFontManagerRegisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL);
  #elif defined(_WIN32)
  std::unique_ptr<wchar_t[]> wFilepath = u8ToWide((char*)filepath);
  if(wFilepath){
    success = AddFontResourceExW(wFilepath.get(), FR_PRIVATE, 0) != 0;
  }else{
    success = false;
  }
  #else
  success = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(filepath));
  #endif
  if (!success) return false;
  // Tell Pango to throw away the current FontMap and create a new one. This
  // has the effect of registering the new font in Pango by re-looking up all
  // font families.
  pango_cairo_font_map_set_default(NULL);
#ifdef PERFECT_MATCHES_ENABLED
  PangoFontMap* map = pango_cairo_font_map_get_default();
  PangoFcFontMap* fc_map = PANGO_FC_FONT_MAP(map);
  pango_fc_font_map_set_default_substitute(fc_map, fc_font_map_substitute_hook, NULL, NULL);
#endif
  return true;
}
/*
 * Deregister font from the OS
 * Note that Linux (FontConfig) can only dereregister ALL fonts at once.
 */
bool
deregister_font(unsigned char *filepath) {
  bool success;
  #ifdef __APPLE__
  CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false);
  success = CTFontManagerUnregisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL);
  #elif defined(_WIN32)
  std::unique_ptr<wchar_t[]> wFilepath = u8ToWide((char*)filepath);
  if(wFilepath){
    success = RemoveFontResourceExW(wFilepath.get(), FR_PRIVATE, 0) != 0;
  }else{
    success = false;
  }
  #else
  FcConfigAppFontClear(FcConfigGetCurrent());
  success = true;
  #endif
  if (!success) return false;
  // Tell Pango to throw away the current FontMap and create a new one. This
  // has the effect of deregistering the font in Pango by re-looking up all
  // font families.
  pango_cairo_font_map_set_default(NULL);
  return true;
}
node_modules/canvas/src/register_font.h
New file
@@ -0,0 +1,7 @@
#pragma once
#include <pango/pango.h>
PangoFontDescription *get_pango_font_description(unsigned char *filepath);
bool register_font(unsigned char *filepath);
bool deregister_font(unsigned char *filepath);
node_modules/canvas/types/Readme.md
New file
@@ -0,0 +1,3 @@
Notes:
* `"unified-signatures": false` because of https://github.com/Microsoft/dtslint/issues/183
node_modules/canvas/types/index.d.ts
New file
@@ -0,0 +1,484 @@
// TypeScript Version: 3.0
import { Readable } from 'stream'
export interface PngConfig {
    /** Specifies the ZLIB compression level. Defaults to 6. */
    compressionLevel?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
    /**
     * Any bitwise combination of `PNG_FILTER_NONE`, `PNG_FILTER_SUB`,
     * `PNG_FILTER_UP`, `PNG_FILTER_AVG` and `PNG_FILTER_PATETH`; or one of
     * `PNG_ALL_FILTERS` or `PNG_NO_FILTERS` (all are properties of the canvas
     * instance). These specify which filters *may* be used by libpng. During
     * encoding, libpng will select the best filter from this list of allowed
     * filters. Defaults to `canvas.PNG_ALL_FILTERS`.
     */
    filters?: number
    /**
     * _For creating indexed PNGs._ The palette of colors. Entries should be in
     * RGBA order.
     */
    palette?: Uint8ClampedArray
    /**
     * _For creating indexed PNGs._ The index of the background color. Defaults
     * to 0.
     */
    backgroundIndex?: number
    /** pixels per inch */
    resolution?: number
}
export interface JpegConfig {
    /** Specifies the quality, between 0 and 1. Defaults to 0.75. */
    quality?: number
    /** Enables progressive encoding. Defaults to `false`. */
    progressive?: boolean
    /** Enables 2x2 chroma subsampling. Defaults to `true`. */
    chromaSubsampling?: boolean
}
export interface PdfConfig {
    title?: string
    author?: string
    subject?: string
    keywords?: string
    creator?: string
    creationDate?: Date
    modDate?: Date
}
export interface NodeCanvasRenderingContext2DSettings {
    alpha?: boolean
    pixelFormat?: 'RGBA32' | 'RGB24' | 'A8' | 'RGB16_565' | 'A1' | 'RGB30'
}
export class Canvas {
    width: number
    height: number
    /** _Non standard._ The type of the canvas. */
    readonly type: 'image'|'pdf'|'svg'
    /** _Non standard._ Getter. The stride used by the canvas. */
    readonly stride: number;
    /** Constant used in PNG encoding methods. */
    readonly PNG_NO_FILTERS: number
    /** Constant used in PNG encoding methods. */
    readonly PNG_ALL_FILTERS: number
    /** Constant used in PNG encoding methods. */
    readonly PNG_FILTER_NONE: number
    /** Constant used in PNG encoding methods. */
    readonly PNG_FILTER_SUB: number
    /** Constant used in PNG encoding methods. */
    readonly PNG_FILTER_UP: number
    /** Constant used in PNG encoding methods. */
    readonly PNG_FILTER_AVG: number
    /** Constant used in PNG encoding methods. */
    readonly PNG_FILTER_PAETH: number
    constructor(width: number, height: number, type?: 'image'|'pdf'|'svg')
    getContext(contextId: '2d', contextAttributes?: NodeCanvasRenderingContext2DSettings): CanvasRenderingContext2D
    /**
     * For image canvases, encodes the canvas as a PNG. For PDF canvases,
     * encodes the canvas as a PDF. For SVG canvases, encodes the canvas as an
     * SVG.
     */
    toBuffer(cb: (err: Error|null, result: Buffer) => void): void
    toBuffer(cb: (err: Error|null, result: Buffer) => void, mimeType: 'image/png', config?: PngConfig): void
    toBuffer(cb: (err: Error|null, result: Buffer) => void, mimeType: 'image/jpeg', config?: JpegConfig): void
    /**
     * For image canvases, encodes the canvas as a PNG. For PDF canvases,
     * encodes the canvas as a PDF. For SVG canvases, encodes the canvas as an
     * SVG.
     */
    toBuffer(): Buffer
    toBuffer(mimeType: 'image/png', config?: PngConfig): Buffer
    toBuffer(mimeType: 'image/jpeg', config?: JpegConfig): Buffer
    toBuffer(mimeType: 'application/pdf', config?: PdfConfig): Buffer
    /**
     * Returns the unencoded pixel data, top-to-bottom. On little-endian (most)
     * systems, the array will be ordered BGRA; on big-endian systems, it will
     * be ARGB.
     */
    toBuffer(mimeType: 'raw'): Buffer
    createPNGStream(config?: PngConfig): PNGStream
    createJPEGStream(config?: JpegConfig): JPEGStream
    createPDFStream(config?: PdfConfig): PDFStream
    /** Defaults to PNG image. */
    toDataURL(): string
    toDataURL(mimeType: 'image/png'): string
    toDataURL(mimeType: 'image/jpeg', quality?: number): string
    /** _Non-standard._ Defaults to PNG image. */
    toDataURL(cb: (err: Error|null, result: string) => void): void
    /** _Non-standard._ */
    toDataURL(mimeType: 'image/png', cb: (err: Error|null, result: string) => void): void
    /** _Non-standard._ */
    toDataURL(mimeType: 'image/jpeg', cb: (err: Error|null, result: string) => void): void
    /** _Non-standard._ */
    toDataURL(mimeType: 'image/jpeg', config: JpegConfig, cb: (err: Error|null, result: string) => void): void
    /** _Non-standard._ */
    toDataURL(mimeType: 'image/jpeg', quality: number, cb: (err: Error|null, result: string) => void): void
}
export interface TextMetrics {
    readonly actualBoundingBoxAscent: number;
    readonly actualBoundingBoxDescent: number;
    readonly actualBoundingBoxLeft: number;
    readonly actualBoundingBoxRight: number;
    readonly fontBoundingBoxAscent: number;
    readonly fontBoundingBoxDescent: number;
    readonly width: number;
}
export type CanvasFillRule = 'evenodd' | 'nonzero';
export type GlobalCompositeOperation =
    | 'clear'
    | 'copy'
    | 'destination'
    | 'source-over'
    | 'destination-over'
    | 'source-in'
    | 'destination-in'
    | 'source-out'
    | 'destination-out'
    | 'source-atop'
    | 'destination-atop'
    | 'xor'
    | 'lighter'
    | 'normal'
    | 'multiply'
    | 'screen'
    | 'overlay'
    | 'darken'
    | 'lighten'
    | 'color-dodge'
    | 'color-burn'
    | 'hard-light'
    | 'soft-light'
    | 'difference'
    | 'exclusion'
    | 'hue'
    | 'saturation'
    | 'color'
    | 'luminosity'
    | 'saturate';
export type CanvasLineCap = 'butt' | 'round' | 'square';
export type CanvasLineJoin = 'bevel' | 'miter' | 'round';
export type CanvasTextBaseline = 'alphabetic' | 'bottom' | 'hanging' | 'ideographic' | 'middle' | 'top';
export type CanvasTextAlign = 'center' | 'end' | 'left' | 'right' | 'start';
export class CanvasRenderingContext2D {
    drawImage(image: Canvas|Image, dx: number, dy: number): void
    drawImage(image: Canvas|Image, dx: number, dy: number, dw: number, dh: number): void
    drawImage(image: Canvas|Image, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void
    putImageData(imagedata: ImageData, dx: number, dy: number): void;
    putImageData(imagedata: ImageData, dx: number, dy: number, dirtyX: number, dirtyY: number, dirtyWidth: number, dirtyHeight: number): void;
    getImageData(sx: number, sy: number, sw: number, sh: number): ImageData;
    createImageData(sw: number, sh: number): ImageData;
    createImageData(imagedata: ImageData): ImageData;
    /**
     * For PDF canvases, adds another page. If width and/or height are omitted,
     * the canvas's initial size is used.
     */
    addPage(width?: number, height?: number): void
    save(): void;
    restore(): void;
    rotate(angle: number): void;
    translate(x: number, y: number): void;
    transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
    getTransform(): DOMMatrix;
    resetTransform(): void;
    setTransform(transform?: DOMMatrix): void;
    isPointInPath(x: number, y: number, fillRule?: CanvasFillRule): boolean;
    scale(x: number, y: number): void;
    clip(fillRule?: CanvasFillRule): void;
    fill(fillRule?: CanvasFillRule): void;
    stroke(): void;
    fillText(text: string, x: number, y: number, maxWidth?: number): void;
    strokeText(text: string, x: number, y: number, maxWidth?: number): void;
    fillRect(x: number, y: number, w: number, h: number): void;
    strokeRect(x: number, y: number, w: number, h: number): void;
    clearRect(x: number, y: number, w: number, h: number): void;
    rect(x: number, y: number, w: number, h: number): void;
    roundRect(x: number, y: number, w: number, h: number, radii?: number | number[]): void;
    measureText(text: string): TextMetrics;
    moveTo(x: number, y: number): void;
    lineTo(x: number, y: number): void;
    bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void;
    quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void;
    beginPath(): void;
    closePath(): void;
    arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void;
    arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void;
    ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void;
    setLineDash(segments: number[]): void;
    getLineDash(): number[];
    createPattern(image: Canvas|Image, repetition: 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat' | '' | null): CanvasPattern
    createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient;
    createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient;
    /**
     * _Non-standard_. Defaults to 'good'. Affects pattern (gradient, image,
     * etc.) rendering quality.
     */
    patternQuality: 'fast' | 'good' | 'best' | 'nearest' | 'bilinear'
    imageSmoothingEnabled: boolean;
    globalCompositeOperation: GlobalCompositeOperation;
    globalAlpha: number;
    shadowColor: string;
    miterLimit: number;
    lineWidth: number;
    lineCap: CanvasLineCap;
    lineJoin: CanvasLineJoin;
    lineDashOffset: number;
    shadowOffsetX: number;
    shadowOffsetY: number;
    shadowBlur: number;
    /** _Non-standard_. Sets the antialiasing mode. */
    antialias: 'default' | 'gray' | 'none' | 'subpixel'
    /**
     * Defaults to 'path'. The effect depends on the canvas type:
     *
     * * **Standard (image)** `'glyph'` and `'path'` both result in rasterized
     *   text. Glyph mode is faster than path, but may result in lower-quality
     *   text, especially when rotated or translated.
     *
     * * **PDF** `'glyph'` will embed text instead of paths into the PDF. This
     *   is faster to encode, faster to open with PDF viewers, yields a smaller
     *   file size and makes the text selectable. The subset of the font needed
     *   to render the glyphs will be embedded in the PDF. This is usually the
     *   mode you want to use with PDF canvases.
     *
     * * **SVG** glyph does not cause `<text>` elements to be produced as one
     *   might expect ([cairo bug](https://gitlab.freedesktop.org/cairo/cairo/issues/253)).
     *   Rather, glyph will create a `<defs>` section with a `<symbol>` for each
     *   glyph, then those glyphs be reused via `<use>` elements. `'path'` mode
     *   creates a `<path>` element for each text string. glyph mode is faster
     *   and yields a smaller file size.
     *
     * In glyph mode, `ctx.strokeText()` and `ctx.fillText()` behave the same
     * (aside from using the stroke and fill style, respectively).
     */
    textDrawingMode: 'path' | 'glyph'
    /**
     * _Non-standard_. Defaults to 'good'. Like `patternQuality`, but applies to
     * transformations affecting more than just patterns.
     */
    quality: 'fast' | 'good' | 'best' | 'nearest' | 'bilinear'
    /** Returns or sets a `DOMMatrix` for the current transformation matrix. */
    currentTransform: DOMMatrix
    fillStyle: string | CanvasGradient | CanvasPattern;
    strokeStyle: string | CanvasGradient | CanvasPattern;
    font: string;
    textBaseline: CanvasTextBaseline;
    textAlign: CanvasTextAlign;
    canvas: Canvas;
}
export class CanvasGradient {
    addColorStop(offset: number, color: string): void;
}
export class CanvasPattern {
    setTransform(transform?: DOMMatrix): void;
}
// This does not extend HTMLImageElement because there are dozens of inherited
// methods and properties that we do not provide.
export class Image {
    /** Track image data */
    static readonly MODE_IMAGE: number
    /** Track MIME data */
    static readonly MODE_MIME: number
    /**
     * The URL, `data:` URI or local file path of the image to be loaded, or a
     * Buffer instance containing an encoded image.
     */
    src: string | Buffer
    /** Retrieves whether the object is fully loaded. */
    readonly complete: boolean
    /** Sets or retrieves the height of the image. */
    height: number
    /** Sets or retrieves the width of the image. */
    width: number
    /** The original height of the image resource before sizing. */
    readonly naturalHeight: number
    /** The original width of the image resource before sizing. */
    readonly naturalWidth: number
    /**
     * Applies to JPEG images drawn to PDF canvases only. Setting
     * `img.dataMode = Image.MODE_MIME` or `Image.MODE_MIME|Image.MODE_IMAGE`
     * enables image MIME data tracking. When MIME data is tracked, PDF canvases
     * can embed JPEGs directly into the output, rather than re-encoding into
     * PNG. This can drastically reduce filesize and speed up rendering.
     */
    dataMode: number
    onload: (() => void) | null;
    onerror: ((err: Error) => void) | null;
}
/**
 * Creates a Canvas instance. This function works in both Node.js and Web
 * browsers, where there is no Canvas constructor.
 * @param type Optionally specify to create a PDF or SVG canvas. Defaults to an
 * image canvas.
 */
export function createCanvas(width: number, height: number, type?: 'pdf'|'svg'): Canvas
/**
 * Creates an ImageData instance. This function works in both Node.js and Web
 * browsers.
 * @param data An array containing the pixel representation of the image.
 * @param height If omitted, the height is calculated based on the array's size
 * and `width`.
 */
export function createImageData(data: Uint8ClampedArray, width: number, height?: number): ImageData
/**
 * _Non-standard._ Creates an ImageData instance for an alternative pixel
 * format, such as RGB16_565
 * @param data An array containing the pixel representation of the image.
 * @param height If omitted, the height is calculated based on the array's size
 * and `width`.
 */
export function createImageData(data: Uint16Array, width: number, height?: number): ImageData
/**
 * Creates an ImageData instance. This function works in both Node.js and Web
 * browsers.
 */
export function createImageData(width: number, height: number): ImageData
/**
 * Convenience function for loading an image with a Promise interface. This
 * function works in both Node.js and Web browsers; however, the `src` must be
 * a string in Web browsers (it can only be a Buffer in Node.js).
 * @param src URL, `data: ` URI or (Node.js only) a local file path or Buffer
 * instance.
 */
export function loadImage(src: string|Buffer, options?: any): Promise<Image>
/**
 * Registers a font that is not installed as a system font. This must be used
 * before creating Canvas instances.
 * @param path Path to local font file.
 * @param fontFace Description of the font face, corresponding to CSS properties
 * used in `@font-face` rules.
 */
export function registerFont(path: string, fontFace: {family: string, weight?: string, style?: string}): void
/**
 * Unloads all fonts
 */
export function deregisterAllFonts(): void;
/** This class must not be constructed directly; use `canvas.createPNGStream()`. */
export class PNGStream extends Readable {}
/** This class must not be constructed directly; use `canvas.createJPEGStream()`. */
export class JPEGStream extends Readable {}
/** This class must not be constructed directly; use `canvas.createPDFStream()`. */
export class PDFStream extends Readable {}
export class DOMPoint {
    w: number;
    x: number;
    y: number;
    z: number;
}
export class DOMMatrix {
    constructor(init: string | number[]);
    toString(): string;
    multiply(other?: DOMMatrix): DOMMatrix;
    multiplySelf(other?: DOMMatrix): DOMMatrix;
    preMultiplySelf(other?: DOMMatrix): DOMMatrix;
    translate(tx?: number, ty?: number, tz?: number): DOMMatrix;
    translateSelf(tx?: number, ty?: number, tz?: number): DOMMatrix;
    scale(scaleX?: number, scaleY?: number, scaleZ?: number, originX?: number, originY?: number, originZ?: number): DOMMatrix;
    scale3d(scale?: number, originX?: number, originY?: number, originZ?: number): DOMMatrix;
    scale3dSelf(scale?: number, originX?: number, originY?: number, originZ?: number): DOMMatrix;
    scaleSelf(scaleX?: number, scaleY?: number, scaleZ?: number, originX?: number, originY?: number, originZ?: number): DOMMatrix;
    rotateFromVector(x?: number, y?: number): DOMMatrix;
    rotateFromVectorSelf(x?: number, y?: number): DOMMatrix;
    rotate(rotX?: number, rotY?: number, rotZ?: number): DOMMatrix;
    rotateSelf(rotX?: number, rotY?: number, rotZ?: number): DOMMatrix;
    rotateAxisAngle(x?: number, y?: number, z?: number, angle?: number): DOMMatrix;
    rotateAxisAngleSelf(x?: number, y?: number, z?: number, angle?: number): DOMMatrix;
    skewX(sx?: number): DOMMatrix;
    skewXSelf(sx?: number): DOMMatrix;
    skewY(sy?: number): DOMMatrix;
    skewYSelf(sy?: number): DOMMatrix;
    flipX(): DOMMatrix;
    flipY(): DOMMatrix;
    inverse(): DOMMatrix;
    invertSelf(): DOMMatrix;
    setMatrixValue(transformList: string): DOMMatrix;
    transformPoint(point?: DOMPoint): DOMPoint;
    toFloat32Array(): Float32Array;
    toFloat64Array(): Float64Array;
    readonly is2D: boolean;
    readonly isIdentity: boolean;
    a: number;
    b: number;
    c: number;
    d: number;
    e: number;
    f: number;
    m11: number;
    m12: number;
    m13: number;
    m14: number;
    m21: number;
    m22: number;
    m23: number;
    m24: number;
    m31: number;
    m32: number;
    m33: number;
    m34: number;
    m41: number;
    m42: number;
    m43: number;
    m44: number;
    static fromMatrix(other: DOMMatrix): DOMMatrix;
    static fromFloat32Array(a: Float32Array): DOMMatrix;
    static fromFloat64Array(a: Float64Array): DOMMatrix;
}
export class ImageData {
    constructor(sw: number, sh: number);
    constructor(data: Uint8ClampedArray, sw: number, sh?: number);
    readonly data: Uint8ClampedArray;
    readonly height: number;
    readonly width: number;
}
// This is marked private, but is exported...
// export function parseFont(description: string): object
// Not documented: backends
/** Library version. */
export const version: string
/** Cairo version. */
export const cairoVersion: string
/** jpeglib version, if built with JPEG support. */
export const jpegVersion: string | undefined
/** giflib version, if built with GIF support. */
export const gifVersion: string | undefined
/** freetype version. */
export const freetypeVersion: string
/** rsvg version. */
export const rsvgVersion: string | undefined
node_modules/canvas/util/has_lib.js
New file
@@ -0,0 +1,119 @@
const query = process.argv[2]
const fs = require('fs')
const childProcess = require('child_process')
const SYSTEM_PATHS = [
  '/lib',
  '/usr/lib',
  '/usr/lib64',
  '/usr/local/lib',
  '/opt/local/lib',
  '/opt/homebrew/lib',
  '/usr/lib/x86_64-linux-gnu',
  '/usr/lib/i386-linux-gnu',
  '/usr/lib/arm-linux-gnueabihf',
  '/usr/lib/arm-linux-gnueabi',
  '/usr/lib/aarch64-linux-gnu'
]
/**
 * Checks for lib using ldconfig if present, or searching SYSTEM_PATHS
 * otherwise.
 * @param {string} lib - library name, e.g. 'jpeg' in 'libjpeg64.so' (see first line)
 * @return {boolean} exists
 */
function hasSystemLib (lib) {
  const libName = 'lib' + lib + '.+(so|dylib)'
  const libNameRegex = new RegExp(libName)
  // Try using ldconfig on linux systems
  if (hasLdconfig()) {
    try {
      if (childProcess.execSync('ldconfig -p 2>/dev/null | grep -E "' + libName + '"').length) {
        return true
      }
    } catch (err) {
      // noop -- proceed to other search methods
    }
  }
  // Try checking common library locations
  return SYSTEM_PATHS.some(function (systemPath) {
    try {
      const dirListing = fs.readdirSync(systemPath)
      return dirListing.some(function (file) {
        return libNameRegex.test(file)
      })
    } catch (err) {
      return false
    }
  })
}
/**
 * Checks for ldconfig on the path and /sbin
 * @return {boolean} exists
 */
function hasLdconfig () {
  try {
    // Add /sbin to path as ldconfig is located there on some systems -- e.g.
    // Debian (and it can still be used by unprivileged users):
    childProcess.execSync('export PATH="$PATH:/sbin"')
    process.env.PATH = '...'
    // execSync throws on nonzero exit
    childProcess.execSync('hash ldconfig 2>/dev/null')
    return true
  } catch (err) {
    return false
  }
}
/**
 * Checks for freetype2 with --cflags-only-I
 * @return Boolean exists
 */
function hasFreetype () {
  try {
    if (childProcess.execSync('pkg-config cairo --cflags-only-I 2>/dev/null | grep freetype2').length) {
      return true
    }
  } catch (err) {
    // noop
  }
  return false
}
/**
 * Checks for lib using pkg-config.
 * @param {string} lib - library name
 * @return {boolean} exists
 */
function hasPkgconfigLib (lib) {
  try {
    // execSync throws on nonzero exit
    childProcess.execSync('pkg-config --exists "' + lib + '" 2>/dev/null')
    return true
  } catch (err) {
    return false
  }
}
function main (query) {
  switch (query) {
    case 'gif':
    case 'cairo':
      return hasSystemLib(query)
    case 'pango':
      return hasPkgconfigLib(query)
    case 'freetype':
      return hasFreetype()
    case 'jpeg':
      return hasPkgconfigLib('libjpeg')
    case 'rsvg':
      return hasPkgconfigLib('librsvg-2.0')
    default:
      throw new Error('Unknown library: ' + query)
  }
}
process.stdout.write(main(query).toString())
node_modules/canvas/util/win_jpeg_lookup.js
New file
@@ -0,0 +1,21 @@
const fs = require('fs')
const paths = ['C:/libjpeg-turbo']
if (process.arch === 'x64') {
  paths.unshift('C:/libjpeg-turbo64')
}
paths.forEach(function (path) {
  if (exists(path)) {
    process.stdout.write(path)
    process.exit()
  }
})
function exists (path) {
  try {
    return fs.lstatSync(path).isDirectory()
  } catch (e) {
    return false
  }
}
node_modules/crypto-js/CONTRIBUTING.md
New file
@@ -0,0 +1,28 @@
# Contribution
# Git Flow
The crypto-js project uses [git flow](https://github.com/nvie/gitflow) to manage branches.
Do your changes on the `develop` or even better on a `feature/*` branch. Don't do any changes on the `master` branch.
# Pull request
Target your pull request on `develop` branch. Other pull request won't be accepted.
# How to build
1. Clone
2. Run
    ```sh
    npm install
    ```
3. Run
    ```sh
    npm run build
    ```
4. Check `build` folder
node_modules/crypto-js/LICENSE
New file
@@ -0,0 +1,24 @@
# License
[The MIT License (MIT)](http://opensource.org/licenses/MIT)
Copyright (c) 2009-2013 Jeff Mott
Copyright (c) 2013-2016 Evan Vosberg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
node_modules/crypto-js/README.md
New file
@@ -0,0 +1,275 @@
# crypto-js
JavaScript library of crypto standards.
## Discontinued
Active development of CryptoJS has been discontinued. This library is no longer maintained.
Nowadays, NodeJS and modern browsers have a native `Crypto` module. The latest version of CryptoJS already uses the native Crypto module for random number generation, since `Math.random()` is not crypto-safe. Further development of CryptoJS would result in it only being a wrapper of native Crypto. Therefore, development and maintenance has been discontinued, it is time to go for the native `crypto` module.
## Node.js (Install)
Requirements:
- Node.js
- npm (Node.js package manager)
```bash
npm install crypto-js
```
### Usage
ES6 import for typical API call signing use case:
```javascript
import sha256 from 'crypto-js/sha256';
import hmacSHA512 from 'crypto-js/hmac-sha512';
import Base64 from 'crypto-js/enc-base64';
const message, nonce, path, privateKey; // ...
const hashDigest = sha256(nonce + message);
const hmacDigest = Base64.stringify(hmacSHA512(path + hashDigest, privateKey));
```
Modular include:
```javascript
var AES = require("crypto-js/aes");
var SHA256 = require("crypto-js/sha256");
...
console.log(SHA256("Message"));
```
Including all libraries, for access to extra methods:
```javascript
var CryptoJS = require("crypto-js");
console.log(CryptoJS.HmacSHA1("Message", "Key"));
```
## Client (browser)
Requirements:
- Node.js
- Bower (package manager for frontend)
```bash
bower install crypto-js
```
### Usage
Modular include:
```javascript
require.config({
    packages: [
        {
            name: 'crypto-js',
            location: 'path-to/bower_components/crypto-js',
            main: 'index'
        }
    ]
});
require(["crypto-js/aes", "crypto-js/sha256"], function (AES, SHA256) {
    console.log(SHA256("Message"));
});
```
Including all libraries, for access to extra methods:
```javascript
// Above-mentioned will work or use this simple form
require.config({
    paths: {
        'crypto-js': 'path-to/bower_components/crypto-js/crypto-js'
    }
});
require(["crypto-js"], function (CryptoJS) {
    console.log(CryptoJS.HmacSHA1("Message", "Key"));
});
```
### Usage without RequireJS
```html
<script type="text/javascript" src="path-to/bower_components/crypto-js/crypto-js.js"></script>
<script type="text/javascript">
    var encrypted = CryptoJS.AES(...);
    var encrypted = CryptoJS.SHA256(...);
</script>
```
## API
See: https://cryptojs.gitbook.io/docs/
### AES Encryption
#### Plain text encryption
```javascript
var CryptoJS = require("crypto-js");
// Encrypt
var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString();
// Decrypt
var bytes  = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
var originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log(originalText); // 'my message'
```
#### Object encryption
```javascript
var CryptoJS = require("crypto-js");
var data = [{id: 1}, {id: 2}]
// Encrypt
var ciphertext = CryptoJS.AES.encrypt(JSON.stringify(data), 'secret key 123').toString();
// Decrypt
var bytes  = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
var decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
console.log(decryptedData); // [{id: 1}, {id: 2}]
```
### List of modules
- ```crypto-js/core```
- ```crypto-js/x64-core```
- ```crypto-js/lib-typedarrays```
---
- ```crypto-js/md5```
- ```crypto-js/sha1```
- ```crypto-js/sha256```
- ```crypto-js/sha224```
- ```crypto-js/sha512```
- ```crypto-js/sha384```
- ```crypto-js/sha3```
- ```crypto-js/ripemd160```
---
- ```crypto-js/hmac-md5```
- ```crypto-js/hmac-sha1```
- ```crypto-js/hmac-sha256```
- ```crypto-js/hmac-sha224```
- ```crypto-js/hmac-sha512```
- ```crypto-js/hmac-sha384```
- ```crypto-js/hmac-sha3```
- ```crypto-js/hmac-ripemd160```
---
- ```crypto-js/pbkdf2```
---
- ```crypto-js/aes```
- ```crypto-js/tripledes```
- ```crypto-js/rc4```
- ```crypto-js/rabbit```
- ```crypto-js/rabbit-legacy```
- ```crypto-js/evpkdf```
---
- ```crypto-js/format-openssl```
- ```crypto-js/format-hex```
---
- ```crypto-js/enc-latin1```
- ```crypto-js/enc-utf8```
- ```crypto-js/enc-hex```
- ```crypto-js/enc-utf16```
- ```crypto-js/enc-base64```
---
- ```crypto-js/mode-cfb```
- ```crypto-js/mode-ctr```
- ```crypto-js/mode-ctr-gladman```
- ```crypto-js/mode-ofb```
- ```crypto-js/mode-ecb```
---
- ```crypto-js/pad-pkcs7```
- ```crypto-js/pad-ansix923```
- ```crypto-js/pad-iso10126```
- ```crypto-js/pad-iso97971```
- ```crypto-js/pad-zeropadding```
- ```crypto-js/pad-nopadding```
## Release notes
### 4.2.0
Change default hash algorithm and iteration's for PBKDF2 to prevent weak security by using the default configuration.
Custom KDF Hasher
Blowfish support
### 4.1.1
Fix module order in bundled release.
Include the browser field in the released package.json.
### 4.1.0
Added url safe variant of base64 encoding. [357](https://github.com/brix/crypto-js/pull/357)
Avoid webpack to add crypto-browser package. [364](https://github.com/brix/crypto-js/pull/364)
### 4.0.0
This is an update including breaking changes for some environments.
In this version `Math.random()` has been replaced by the random methods of the native crypto module.
For this reason CryptoJS might not run in some JavaScript environments without native crypto module. Such as IE 10 or before or React Native.
### 3.3.0
Rollback, `3.3.0` is the same as `3.1.9-1`.
The move of using native secure crypto module will be shifted to a new `4.x.x` version. As it is a breaking change the impact is too big for a minor release.
### 3.2.1
The usage of the native crypto module has been fixed. The import and access of the native crypto module has been improved.
### 3.2.0
In this version `Math.random()` has been replaced by the random methods of the native crypto module.
For this reason CryptoJS might does not run in some JavaScript environments without native crypto module. Such as IE 10 or before.
If it's absolute required to run CryptoJS in such an environment, stay with `3.1.x` version. Encrypting and decrypting stays compatible. But keep in mind `3.1.x` versions still use `Math.random()` which is cryptographically not secure, as it's not random enough.
This version came along with `CRITICAL` `BUG`.
DO NOT USE THIS VERSION! Please, go for a newer version!
### 3.1.x
The `3.1.x` are based on the original CryptoJS, wrapped in CommonJS modules.
node_modules/crypto-js/aes.js
New file
@@ -0,0 +1,234 @@
;(function (root, factory, undef) {
    if (typeof exports === "object") {
        // CommonJS
        module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core"));
    }
    else if (typeof define === "function" && define.amd) {
        // AMD
        define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory);
    }
    else {
        // Global (browser)
        factory(root.CryptoJS);
    }
}(this, function (CryptoJS) {
    (function () {
        // Shortcuts
        var C = CryptoJS;
        var C_lib = C.lib;
        var BlockCipher = C_lib.BlockCipher;
        var C_algo = C.algo;
        // Lookup tables
        var SBOX = [];
        var INV_SBOX = [];
        var SUB_MIX_0 = [];
        var SUB_MIX_1 = [];
        var SUB_MIX_2 = [];
        var SUB_MIX_3 = [];
        var INV_SUB_MIX_0 = [];
        var INV_SUB_MIX_1 = [];
        var INV_SUB_MIX_2 = [];
        var INV_SUB_MIX_3 = [];
        // Compute lookup tables
        (function () {
            // Compute double table
            var d = [];
            for (var i = 0; i < 256; i++) {
                if (i < 128) {
                    d[i] = i << 1;
                } else {
                    d[i] = (i << 1) ^ 0x11b;
                }
            }
            // Walk GF(2^8)
            var x = 0;
            var xi = 0;
            for (var i = 0; i < 256; i++) {
                // Compute sbox
                var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4);
                sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63;
                SBOX[x] = sx;
                INV_SBOX[sx] = x;
                // Compute multiplication
                var x2 = d[x];
                var x4 = d[x2];
                var x8 = d[x4];
                // Compute sub bytes, mix columns tables
                var t = (d[sx] * 0x101) ^ (sx * 0x1010100);
                SUB_MIX_0[x] = (t << 24) | (t >>> 8);
                SUB_MIX_1[x] = (t << 16) | (t >>> 16);
                SUB_MIX_2[x] = (t << 8)  | (t >>> 24);
                SUB_MIX_3[x] = t;
                // Compute inv sub bytes, inv mix columns tables
                var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100);
                INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8);
                INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16);
                INV_SUB_MIX_2[sx] = (t << 8)  | (t >>> 24);
                INV_SUB_MIX_3[sx] = t;
                // Compute next counter
                if (!x) {
                    x = xi = 1;
                } else {
                    x = x2 ^ d[d[d[x8 ^ x2]]];
                    xi ^= d[d[xi]];
                }
            }
        }());
        // Precomputed Rcon lookup
        var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
        /**
         * AES block cipher algorithm.
         */
        var AES = C_algo.AES = BlockCipher.extend({
            _doReset: function () {
                var t;
                // Skip reset of nRounds has been set before and key did not change
                if (this._nRounds && this._keyPriorReset === this._key) {
                    return;
                }
                // Shortcuts
                var key = this._keyPriorReset = this._key;
                var keyWords = key.words;
                var keySize = key.sigBytes / 4;
                // Compute number of rounds
                var nRounds = this._nRounds = keySize + 6;
                // Compute number of key schedule rows
                var ksRows = (nRounds + 1) * 4;
                // Compute key schedule
                var keySchedule = this._keySchedule = [];
                for (var ksRow = 0; ksRow < ksRows; ksRow++) {
                    if (ksRow < keySize) {
                        keySchedule[ksRow] = keyWords[ksRow];
                    } else {
                        t = keySchedule[ksRow - 1];
                        if (!(ksRow % keySize)) {
                            // Rot word
                            t = (t << 8) | (t >>> 24);
                            // Sub word
                            t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff];
                            // Mix Rcon
                            t ^= RCON[(ksRow / keySize) | 0] << 24;
                        } else if (keySize > 6 && ksRow % keySize == 4) {
                            // Sub word
                            t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff];
                        }
                        keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t;
                    }
                }
                // Compute inv key schedule
                var invKeySchedule = this._invKeySchedule = [];
                for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) {
                    var ksRow = ksRows - invKsRow;
                    if (invKsRow % 4) {
                        var t = keySchedule[ksRow];
                    } else {
                        var t = keySchedule[ksRow - 4];
                    }
                    if (invKsRow < 4 || ksRow <= 4) {
                        invKeySchedule[invKsRow] = t;
                    } else {
                        invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^
                                                   INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]];
                    }
                }
            },
            encryptBlock: function (M, offset) {
                this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX);
            },
            decryptBlock: function (M, offset) {
                // Swap 2nd and 4th rows
                var t = M[offset + 1];
                M[offset + 1] = M[offset + 3];
                M[offset + 3] = t;
                this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX);
                // Inv swap 2nd and 4th rows
                var t = M[offset + 1];
                M[offset + 1] = M[offset + 3];
                M[offset + 3] = t;
            },
            _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) {
                // Shortcut
                var nRounds = this._nRounds;
                // Get input, add round key
                var s0 = M[offset]     ^ keySchedule[0];
                var s1 = M[offset + 1] ^ keySchedule[1];
                var s2 = M[offset + 2] ^ keySchedule[2];
                var s3 = M[offset + 3] ^ keySchedule[3];
                // Key schedule row counter
                var ksRow = 4;
                // Rounds
                for (var round = 1; round < nRounds; round++) {
                    // Shift rows, sub bytes, mix columns, add round key
                    var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++];
                    var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++];
                    var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++];
                    var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++];
                    // Update state
                    s0 = t0;
                    s1 = t1;
                    s2 = t2;
                    s3 = t3;
                }
                // Shift rows, sub bytes, add round key
                var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++];
                var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++];
                var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++];
                var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++];
                // Set output
                M[offset]     = t0;
                M[offset + 1] = t1;
                M[offset + 2] = t2;
                M[offset + 3] = t3;
            },
            keySize: 256/32
        });
        /**
         * Shortcut functions to the cipher's object interface.
         *
         * @example
         *
         *     var ciphertext = CryptoJS.AES.encrypt(message, key, cfg);
         *     var plaintext  = CryptoJS.AES.decrypt(ciphertext, key, cfg);
         */
        C.AES = BlockCipher._createHelper(AES);
    }());
    return CryptoJS.AES;
}));
node_modules/crypto-js/blowfish.js
New file
@@ -0,0 +1,471 @@
;(function (root, factory, undef) {
    if (typeof exports === "object") {
        // CommonJS
        module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core"));
    }
    else if (typeof define === "function" && define.amd) {
        // AMD
        define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory);
    }
    else {
        // Global (browser)
        factory(root.CryptoJS);
    }
}(this, function (CryptoJS) {
    (function () {
        // Shortcuts
        var C = CryptoJS;
        var C_lib = C.lib;
        var BlockCipher = C_lib.BlockCipher;
        var C_algo = C.algo;
        const N = 16;
        //Origin pbox and sbox, derived from PI
        const ORIG_P = [
            0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344,
            0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89,
            0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C,
            0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917,
            0x9216D5D9, 0x8979FB1B
        ];
        const ORIG_S = [
            [   0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7,
                0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99,
                0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16,
                0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E,
                0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE,
                0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013,
                0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF,
                0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E,
                0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60,
                0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440,
                0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE,
                0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A,
                0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E,
                0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677,
                0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193,
                0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032,
                0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88,
                0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239,
                0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E,
                0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0,
                0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3,
                0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98,
                0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88,
                0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE,
                0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6,
                0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D,
                0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B,
                0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7,
                0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA,
                0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463,
                0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F,
                0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09,
                0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3,
                0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB,
                0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279,
                0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8,
                0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB,
                0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82,
                0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB,
                0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573,
                0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0,
                0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B,
                0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790,
                0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8,
                0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4,
                0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0,
                0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7,
                0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C,
                0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD,
                0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1,
                0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299,
                0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9,
                0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477,
                0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF,
                0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49,
                0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF,
                0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA,
                0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5,
                0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41,
                0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915,
                0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400,
                0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915,
                0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664,
                0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A   ],
            [   0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623,
                0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266,
                0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1,
                0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E,
                0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6,
                0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1,
                0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E,
                0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1,
                0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737,
                0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8,
                0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF,
                0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD,
                0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701,
                0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7,
                0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41,
                0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331,
                0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF,
                0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF,
                0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E,
                0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87,
                0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C,
                0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2,
                0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16,
                0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD,
                0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B,
                0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509,
                0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E,
                0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3,
                0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F,
                0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A,
                0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4,
                0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960,
                0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66,
                0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28,
                0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802,
                0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84,
                0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510,
                0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF,
                0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14,
                0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E,
                0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50,
                0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7,
                0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8,
                0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281,
                0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99,
                0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696,
                0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128,
                0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73,
                0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0,
                0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0,
                0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105,
                0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250,
                0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3,
                0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285,
                0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00,
                0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061,
                0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB,
                0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E,
                0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735,
                0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC,
                0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9,
                0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340,
                0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20,
                0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7   ],
            [   0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934,
                0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068,
                0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF,
                0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840,
                0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45,
                0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504,
                0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A,
                0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB,
                0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE,
                0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6,
                0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42,
                0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B,
                0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2,
                0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB,
                0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527,
                0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B,
                0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33,
                0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C,
                0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3,
                0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC,
                0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17,
                0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564,
                0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B,
                0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115,
                0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922,
                0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728,
                0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0,
                0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E,
                0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37,
                0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D,
                0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804,
                0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B,
                0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3,
                0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB,
                0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D,
                0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C,
                0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350,
                0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9,
                0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A,
                0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE,
                0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D,
                0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC,
                0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F,
                0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61,
                0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2,
                0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9,
                0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2,
                0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C,
                0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E,
                0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633,
                0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10,
                0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169,
                0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52,
                0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027,
                0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5,
                0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62,
                0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634,
                0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76,
                0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24,
                0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC,
                0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4,
                0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C,
                0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837,
                0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0  ],
            [   0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B,
                0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE,
                0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B,
                0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4,
                0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8,
                0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6,
                0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304,
                0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22,
                0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4,
                0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6,
                0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9,
                0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59,
                0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593,
                0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51,
                0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28,
                0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C,
                0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B,
                0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28,
                0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C,
                0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD,
                0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A,
                0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319,
                0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB,
                0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F,
                0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991,
                0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32,
                0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680,
                0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166,
                0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE,
                0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB,
                0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5,
                0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47,
                0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370,
                0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D,
                0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84,
                0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048,
                0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8,
                0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD,
                0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9,
                0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7,
                0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38,
                0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F,
                0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C,
                0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525,
                0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1,
                0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442,
                0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964,
                0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E,
                0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8,
                0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D,
                0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F,
                0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299,
                0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02,
                0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC,
                0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614,
                0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A,
                0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6,
                0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B,
                0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0,
                0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060,
                0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E,
                0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9,
                0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F,
                0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6  ]
        ];
        var BLOWFISH_CTX = {
            pbox: [],
            sbox: []
        }
        function F(ctx, x){
            let a = (x >> 24) & 0xFF;
            let b = (x >> 16) & 0xFF;
            let c = (x >> 8) & 0xFF;
            let d = x & 0xFF;
            let y = ctx.sbox[0][a] + ctx.sbox[1][b];
            y = y ^ ctx.sbox[2][c];
            y = y + ctx.sbox[3][d];
            return y;
        }
        function BlowFish_Encrypt(ctx, left, right){
            let Xl = left;
            let Xr = right;
            let temp;
            for(let i = 0; i < N; ++i){
                Xl = Xl ^ ctx.pbox[i];
                Xr = F(ctx, Xl) ^ Xr;
                temp = Xl;
                Xl = Xr;
                Xr = temp;
            }
            temp = Xl;
            Xl = Xr;
            Xr = temp;
            Xr = Xr ^ ctx.pbox[N];
            Xl = Xl ^ ctx.pbox[N + 1];
            return {left: Xl, right: Xr};
        }
        function BlowFish_Decrypt(ctx, left, right){
            let Xl = left;
            let Xr = right;
            let temp;
            for(let i = N + 1; i > 1; --i){
                Xl = Xl ^ ctx.pbox[i];
                Xr = F(ctx, Xl) ^ Xr;
                temp = Xl;
                Xl = Xr;
                Xr = temp;
            }
            temp = Xl;
            Xl = Xr;
            Xr = temp;
            Xr = Xr ^ ctx.pbox[1];
            Xl = Xl ^ ctx.pbox[0];
            return {left: Xl, right: Xr};
        }
        /**
         * Initialization ctx's pbox and sbox.
         *
         * @param {Object} ctx The object has pbox and sbox.
         * @param {Array} key An array of 32-bit words.
         * @param {int} keysize The length of the key.
         *
         * @example
         *
         *     BlowFishInit(BLOWFISH_CTX, key, 128/32);
         */
        function BlowFishInit(ctx, key, keysize)
        {
            for(let Row = 0; Row < 4; Row++)
            {
                ctx.sbox[Row] = [];
                for(let Col = 0; Col < 256; Col++)
                {
                    ctx.sbox[Row][Col] = ORIG_S[Row][Col];
                }
            }
            let keyIndex = 0;
            for(let index = 0; index < N + 2; index++)
            {
                ctx.pbox[index] = ORIG_P[index] ^ key[keyIndex];
                keyIndex++;
                if(keyIndex >= keysize)
                {
                    keyIndex = 0;
                }
            }
            let Data1 = 0;
            let Data2 = 0;
            let res = 0;
            for(let i = 0; i < N + 2; i += 2)
            {
                res = BlowFish_Encrypt(ctx, Data1, Data2);
                Data1 = res.left;
                Data2 = res.right;
                ctx.pbox[i] = Data1;
                ctx.pbox[i + 1] = Data2;
            }
            for(let i = 0; i < 4; i++)
            {
                for(let j = 0; j < 256; j += 2)
                {
                    res = BlowFish_Encrypt(ctx, Data1, Data2);
                    Data1 = res.left;
                    Data2 = res.right;
                    ctx.sbox[i][j] = Data1;
                    ctx.sbox[i][j + 1] = Data2;
                }
            }
            return true;
        }
        /**
         * Blowfish block cipher algorithm.
         */
        var Blowfish = C_algo.Blowfish = BlockCipher.extend({
            _doReset: function () {
                // Skip reset of nRounds has been set before and key did not change
                if (this._keyPriorReset === this._key) {
                    return;
                }
                // Shortcuts
                var key = this._keyPriorReset = this._key;
                var keyWords = key.words;
                var keySize = key.sigBytes / 4;
                //Initialization pbox and sbox
                BlowFishInit(BLOWFISH_CTX, keyWords, keySize);
            },
            encryptBlock: function (M, offset) {
                var res = BlowFish_Encrypt(BLOWFISH_CTX, M[offset], M[offset + 1]);
                M[offset] = res.left;
                M[offset + 1] = res.right;
            },
            decryptBlock: function (M, offset) {
                var res = BlowFish_Decrypt(BLOWFISH_CTX, M[offset], M[offset + 1]);
                M[offset] = res.left;
                M[offset + 1] = res.right;
            },
            blockSize: 64/32,
            keySize: 128/32,
            ivSize: 64/32
        });
        /**
         * Shortcut functions to the cipher's object interface.
         *
         * @example
         *
         *     var ciphertext = CryptoJS.Blowfish.encrypt(message, key, cfg);
         *     var plaintext  = CryptoJS.Blowfish.decrypt(ciphertext, key, cfg);
         */
        C.Blowfish = BlockCipher._createHelper(Blowfish);
    }());
    return CryptoJS.Blowfish;
}));
node_modules/crypto-js/bower.json
New file
@@ -0,0 +1,39 @@
{
  "name": "crypto-js",
  "version": "4.2.0",
  "description": "JavaScript library of crypto standards.",
  "license": "MIT",
  "homepage": "http://github.com/brix/crypto-js",
  "repository": {
    "type": "git",
    "url": "http://github.com/brix/crypto-js.git"
  },
  "keywords": [
    "security",
    "crypto",
    "Hash",
    "MD5",
    "SHA1",
    "SHA-1",
    "SHA256",
    "SHA-256",
    "RC4",
    "Rabbit",
    "AES",
    "DES",
    "PBKDF2",
    "HMAC",
    "OFB",
    "CFB",
    "CTR",
    "CBC",
    "Base64",
    "Base64url"
  ],
  "main": "index.js",
  "dependencies": {},
  "browser": {
    "crypto": false
  },
  "ignore": []
}
node_modules/crypto-js/cipher-core.js
New file
@@ -0,0 +1,895 @@
;(function (root, factory, undef) {
    if (typeof exports === "object") {
        // CommonJS
        module.exports = exports = factory(require("./core"), require("./evpkdf"));
    }
    else if (typeof define === "function" && define.amd) {
        // AMD
        define(["./core", "./evpkdf"], factory);
    }
    else {
        // Global (browser)
        factory(root.CryptoJS);
    }
}(this, function (CryptoJS) {
    /**
     * Cipher core components.
     */
    CryptoJS.lib.Cipher || (function (undefined) {
        // Shortcuts
        var C = CryptoJS;
        var C_lib = C.lib;
        var Base = C_lib.Base;
        var WordArray = C_lib.WordArray;
        var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm;
        var C_enc = C.enc;
        var Utf8 = C_enc.Utf8;
        var Base64 = C_enc.Base64;
        var C_algo = C.algo;
        var EvpKDF = C_algo.EvpKDF;
        /**
         * Abstract base cipher template.
         *
         * @property {number} keySize This cipher's key size. Default: 4 (128 bits)
         * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits)
         * @property {number} _ENC_XFORM_MODE A constant representing encryption mode.
         * @property {number} _DEC_XFORM_MODE A constant representing decryption mode.
         */
        var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({
            /**
             * Configuration options.
             *
             * @property {WordArray} iv The IV to use for this operation.
             */
            cfg: Base.extend(),
            /**
             * Creates this cipher in encryption mode.
             *
             * @param {WordArray} key The key.
             * @param {Object} cfg (Optional) The configuration options to use for this operation.
             *
             * @return {Cipher} A cipher instance.
             *
             * @static
             *
             * @example
             *
             *     var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray });
             */
            createEncryptor: function (key, cfg) {
                return this.create(this._ENC_XFORM_MODE, key, cfg);
            },
            /**
             * Creates this cipher in decryption mode.
             *
             * @param {WordArray} key The key.
             * @param {Object} cfg (Optional) The configuration options to use for this operation.
             *
             * @return {Cipher} A cipher instance.
             *
             * @static
             *
             * @example
             *
             *     var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray });
             */
            createDecryptor: function (key, cfg) {
                return this.create(this._DEC_XFORM_MODE, key, cfg);
            },
            /**
             * Initializes a newly created cipher.
             *
             * @param {number} xformMode Either the encryption or decryption transormation mode constant.
             * @param {WordArray} key The key.
             * @param {Object} cfg (Optional) The configuration options to use for this operation.
             *
             * @example
             *
             *     var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray });
             */
            init: function (xformMode, key, cfg) {
                // Apply config defaults
                this.cfg = this.cfg.extend(cfg);
                // Store transform mode and key
                this._xformMode = xformMode;
                this._key = key;
                // Set initial values
                this.reset();
            },
            /**
             * Resets this cipher to its initial state.
             *
             * @example
             *
             *     cipher.reset();
             */
            reset: function () {
                // Reset data buffer
                BufferedBlockAlgorithm.reset.call(this);
                // Perform concrete-cipher logic
                this._doReset();
            },
            /**
             * Adds data to be encrypted or decrypted.
             *
             * @param {WordArray|string} dataUpdate The data to encrypt or decrypt.
             *
             * @return {WordArray} The data after processing.
             *
             * @example
             *
             *     var encrypted = cipher.process('data');
             *     var encrypted = cipher.process(wordArray);
             */
            process: function (dataUpdate) {
                // Append
                this._append(dataUpdate);
                // Process available blocks
                return this._process();
            },
            /**
             * Finalizes the encryption or decryption process.
             * Note that the finalize operation is effectively a destructive, read-once operation.
             *
             * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt.
             *
             * @return {WordArray} The data after final processing.
             *
             * @example
             *
             *     var encrypted = cipher.finalize();
             *     var encrypted = cipher.finalize('data');
             *     var encrypted = cipher.finalize(wordArray);
             */
            finalize: function (dataUpdate) {
                // Final data update
                if (dataUpdate) {
                    this._append(dataUpdate);
                }
                // Perform concrete-cipher logic
                var finalProcessedData = this._doFinalize();
                return finalProcessedData;
            },
            keySize: 128/32,
            ivSize: 128/32,
            _ENC_XFORM_MODE: 1,
            _DEC_XFORM_MODE: 2,
            /**
             * Creates shortcut functions to a cipher's object interface.
             *
             * @param {Cipher} cipher The cipher to create a helper for.
             *
             * @return {Object} An object with encrypt and decrypt shortcut functions.
             *
             * @static
             *
             * @example
             *
             *     var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES);
             */
            _createHelper: (function () {
                function selectCipherStrategy(key) {
                    if (typeof key == 'string') {
                        return PasswordBasedCipher;
                    } else {
                        return SerializableCipher;
                    }
                }
                return function (cipher) {
                    return {
                        encrypt: function (message, key, cfg) {
                            return selectCipherStrategy(key).encrypt(cipher, message, key, cfg);
                        },
                        decrypt: function (ciphertext, key, cfg) {
                            return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg);
                        }
                    };
                };
            }())
        });
        /**
         * Abstract base stream cipher template.
         *
         * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits)
         */
        var StreamCipher = C_lib.StreamCipher = Cipher.extend({
            _doFinalize: function () {
                // Process partial blocks
                var finalProcessedBlocks = this._process(!!'flush');
                return finalProcessedBlocks;
            },
            blockSize: 1
        });
        /**
         * Mode namespace.
         */
        var C_mode = C.mode = {};
        /**
         * Abstract base block cipher mode template.
         */
        var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({
            /**
             * Creates this mode for encryption.
             *
             * @param {Cipher} cipher A block cipher instance.
             * @param {Array} iv The IV words.
             *
             * @static
             *
             * @example
             *
             *     var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words);
             */
            createEncryptor: function (cipher, iv) {
                return this.Encryptor.create(cipher, iv);
            },
            /**
             * Creates this mode for decryption.
             *
             * @param {Cipher} cipher A block cipher instance.
             * @param {Array} iv The IV words.
             *
             * @static
             *
             * @example
             *
             *     var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words);
             */
            createDecryptor: function (cipher, iv) {
                return this.Decryptor.create(cipher, iv);
            },
            /**
             * Initializes a newly created mode.
             *
             * @param {Cipher} cipher A block cipher instance.
             * @param {Array} iv The IV words.
             *
             * @example
             *
             *     var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words);
             */
            init: function (cipher, iv) {
                this._cipher = cipher;
                this._iv = iv;
            }
        });
        /**
         * Cipher Block Chaining mode.
         */
        var CBC = C_mode.CBC = (function () {
            /**
             * Abstract base CBC mode.
             */
            var CBC = BlockCipherMode.extend();
            /**
             * CBC encryptor.
             */
            CBC.Encryptor = CBC.extend({
                /**
                 * Processes the data block at offset.
                 *
                 * @param {Array} words The data words to operate on.
                 * @param {number} offset The offset where the block starts.
                 *
                 * @example
                 *
                 *     mode.processBlock(data.words, offset);
                 */
                processBlock: function (words, offset) {
                    // Shortcuts
                    var cipher = this._cipher;
                    var blockSize = cipher.blockSize;
                    // XOR and encrypt
                    xorBlock.call(this, words, offset, blockSize);
                    cipher.encryptBlock(words, offset);
                    // Remember this block to use with next block
                    this._prevBlock = words.slice(offset, offset + blockSize);
                }
            });
            /**
             * CBC decryptor.
             */
            CBC.Decryptor = CBC.extend({
                /**
                 * Processes the data block at offset.
                 *
                 * @param {Array} words The data words to operate on.
                 * @param {number} offset The offset where the block starts.
                 *
                 * @example
                 *
                 *     mode.processBlock(data.words, offset);
                 */
                processBlock: function (words, offset) {
                    // Shortcuts
                    var cipher = this._cipher;
                    var blockSize = cipher.blockSize;
                    // Remember this block to use with next block
                    var thisBlock = words.slice(offset, offset + blockSize);
                    // Decrypt and XOR
                    cipher.decryptBlock(words, offset);
                    xorBlock.call(this, words, offset, blockSize);
                    // This block becomes the previous block
                    this._prevBlock = thisBlock;
                }
            });
            function xorBlock(words, offset, blockSize) {
                var block;
                // Shortcut
                var iv = this._iv;
                // Choose mixing block
                if (iv) {
                    block = iv;
                    // Remove IV for subsequent blocks
                    this._iv = undefined;
                } else {
                    block = this._prevBlock;
                }
                // XOR blocks
                for (var i = 0; i < blockSize; i++) {
                    words[offset + i] ^= block[i];
                }
            }
            return CBC;
        }());
        /**
         * Padding namespace.
         */
        var C_pad = C.pad = {};
        /**
         * PKCS #5/7 padding strategy.
         */
        var Pkcs7 = C_pad.Pkcs7 = {
            /**
             * Pads data using the algorithm defined in PKCS #5/7.
             *
             * @param {WordArray} data The data to pad.
             * @param {number} blockSize The multiple that the data should be padded to.
             *
             * @static
             *
             * @example
             *
             *     CryptoJS.pad.Pkcs7.pad(wordArray, 4);
             */
            pad: function (data, blockSize) {
                // Shortcut
                var blockSizeBytes = blockSize * 4;
                // Count padding bytes
                var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes;
                // Create padding word
                var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes;
                // Create padding
                var paddingWords = [];
                for (var i = 0; i < nPaddingBytes; i += 4) {
                    paddingWords.push(paddingWord);
                }
                var padding = WordArray.create(paddingWords, nPaddingBytes);
                // Add padding
                data.concat(padding);
            },
            /**
             * Unpads data that had been padded using the algorithm defined in PKCS #5/7.
             *
             * @param {WordArray} data The data to unpad.
             *
             * @static
             *
             * @example
             *
             *     CryptoJS.pad.Pkcs7.unpad(wordArray);
             */
            unpad: function (data) {
                // Get number of padding bytes from last byte
                var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff;
                // Remove padding
                data.sigBytes -= nPaddingBytes;
            }
        };
        /**
         * Abstract base block cipher template.
         *
         * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits)
         */
        var BlockCipher = C_lib.BlockCipher = Cipher.extend({
            /**
             * Configuration options.
             *
             * @property {Mode} mode The block mode to use. Default: CBC
             * @property {Padding} padding The padding strategy to use. Default: Pkcs7
             */
            cfg: Cipher.cfg.extend({
                mode: CBC,
                padding: Pkcs7
            }),
            reset: function () {
                var modeCreator;
                // Reset cipher
                Cipher.reset.call(this);
                // Shortcuts
                var cfg = this.cfg;
                var iv = cfg.iv;
                var mode = cfg.mode;
                // Reset block mode
                if (this._xformMode == this._ENC_XFORM_MODE) {
                    modeCreator = mode.createEncryptor;
                } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ {
                    modeCreator = mode.createDecryptor;
                    // Keep at least one block in the buffer for unpadding
                    this._minBufferSize = 1;
                }
                if (this._mode && this._mode.__creator == modeCreator) {
                    this._mode.init(this, iv && iv.words);
                } else {
                    this._mode = modeCreator.call(mode, this, iv && iv.words);
                    this._mode.__creator = modeCreator;
                }
            },
            _doProcessBlock: function (words, offset) {
                this._mode.processBlock(words, offset);
            },
            _doFinalize: function () {
                var finalProcessedBlocks;
                // Shortcut
                var padding = this.cfg.padding;
                // Finalize
                if (this._xformMode == this._ENC_XFORM_MODE) {
                    // Pad data
                    padding.pad(this._data, this.blockSize);
                    // Process final blocks
                    finalProcessedBlocks = this._process(!!'flush');
                } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ {
                    // Process final blocks
                    finalProcessedBlocks = this._process(!!'flush');
                    // Unpad data
                    padding.unpad(finalProcessedBlocks);
                }
                return finalProcessedBlocks;
            },
            blockSize: 128/32
        });
        /**
         * A collection of cipher parameters.
         *
         * @property {WordArray} ciphertext The raw ciphertext.
         * @property {WordArray} key The key to this ciphertext.
         * @property {WordArray} iv The IV used in the ciphering operation.
         * @property {WordArray} salt The salt used with a key derivation function.
         * @property {Cipher} algorithm The cipher algorithm.
         * @property {Mode} mode The block mode used in the ciphering operation.
         * @property {Padding} padding The padding scheme used in the ciphering operation.
         * @property {number} blockSize The block size of the cipher.
         * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string.
         */
        var CipherParams = C_lib.CipherParams = Base.extend({
            /**
             * Initializes a newly created cipher params object.
             *
             * @param {Object} cipherParams An object with any of the possible cipher parameters.
             *
             * @example
             *
             *     var cipherParams = CryptoJS.lib.CipherParams.create({
             *         ciphertext: ciphertextWordArray,
             *         key: keyWordArray,
             *         iv: ivWordArray,
             *         salt: saltWordArray,
             *         algorithm: CryptoJS.algo.AES,
             *         mode: CryptoJS.mode.CBC,
             *         padding: CryptoJS.pad.PKCS7,
             *         blockSize: 4,
             *         formatter: CryptoJS.format.OpenSSL
             *     });
             */
            init: function (cipherParams) {
                this.mixIn(cipherParams);
            },
            /**
             * Converts this cipher params object to a string.
             *
             * @param {Format} formatter (Optional) The formatting strategy to use.
             *
             * @return {string} The stringified cipher params.
             *
             * @throws Error If neither the formatter nor the default formatter is set.
             *
             * @example
             *
             *     var string = cipherParams + '';
             *     var string = cipherParams.toString();
             *     var string = cipherParams.toString(CryptoJS.format.OpenSSL);
             */
            toString: function (formatter) {
                return (formatter || this.formatter).stringify(this);
            }
        });
        /**
         * Format namespace.
         */
        var C_format = C.format = {};
        /**
         * OpenSSL formatting strategy.
         */
        var OpenSSLFormatter = C_format.OpenSSL = {
            /**
             * Converts a cipher params object to an OpenSSL-compatible string.
             *
             * @param {CipherParams} cipherParams The cipher params object.
             *
             * @return {string} The OpenSSL-compatible string.
             *
             * @static
             *
             * @example
             *
             *     var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams);
             */
            stringify: function (cipherParams) {
                var wordArray;
                // Shortcuts
                var ciphertext = cipherParams.ciphertext;
                var salt = cipherParams.salt;
                // Format
                if (salt) {
                    wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext);
                } else {
                    wordArray = ciphertext;
                }
                return wordArray.toString(Base64);
            },
            /**
             * Converts an OpenSSL-compatible string to a cipher params object.
             *
             * @param {string} openSSLStr The OpenSSL-compatible string.
             *
             * @return {CipherParams} The cipher params object.
             *
             * @static
             *
             * @example
             *
             *     var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString);
             */
            parse: function (openSSLStr) {
                var salt;
                // Parse base64
                var ciphertext = Base64.parse(openSSLStr);
                // Shortcut
                var ciphertextWords = ciphertext.words;
                // Test for salt
                if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) {
                    // Extract salt
                    salt = WordArray.create(ciphertextWords.slice(2, 4));
                    // Remove salt from ciphertext
                    ciphertextWords.splice(0, 4);
                    ciphertext.sigBytes -= 16;
                }
                return CipherParams.create({ ciphertext: ciphertext, salt: salt });
            }
        };
        /**
         * A cipher wrapper that returns ciphertext as a serializable cipher params object.
         */
        var SerializableCipher = C_lib.SerializableCipher = Base.extend({
            /**
             * Configuration options.
             *
             * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL
             */
            cfg: Base.extend({
                format: OpenSSLFormatter
            }),
            /**
             * Encrypts a message.
             *
             * @param {Cipher} cipher The cipher algorithm to use.
             * @param {WordArray|string} message The message to encrypt.
             * @param {WordArray} key The key.
             * @param {Object} cfg (Optional) The configuration options to use for this operation.
             *
             * @return {CipherParams} A cipher params object.
             *
             * @static
             *
             * @example
             *
             *     var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key);
             *     var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv });
             *     var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL });
             */
            encrypt: function (cipher, message, key, cfg) {
                // Apply config defaults
                cfg = this.cfg.extend(cfg);
                // Encrypt
                var encryptor = cipher.createEncryptor(key, cfg);
                var ciphertext = encryptor.finalize(message);
                // Shortcut
                var cipherCfg = encryptor.cfg;
                // Create and return serializable cipher params
                return CipherParams.create({
                    ciphertext: ciphertext,
                    key: key,
                    iv: cipherCfg.iv,
                    algorithm: cipher,
                    mode: cipherCfg.mode,
                    padding: cipherCfg.padding,
                    blockSize: cipher.blockSize,
                    formatter: cfg.format
                });
            },
            /**
             * Decrypts serialized ciphertext.
             *
             * @param {Cipher} cipher The cipher algorithm to use.
             * @param {CipherParams|string} ciphertext The ciphertext to decrypt.
             * @param {WordArray} key The key.
             * @param {Object} cfg (Optional) The configuration options to use for this operation.
             *
             * @return {WordArray} The plaintext.
             *
             * @static
             *
             * @example
             *
             *     var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL });
             *     var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL });
             */
            decrypt: function (cipher, ciphertext, key, cfg) {
                // Apply config defaults
                cfg = this.cfg.extend(cfg);
                // Convert string to CipherParams
                ciphertext = this._parse(ciphertext, cfg.format);
                // Decrypt
                var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext);
                return plaintext;
            },
            /**
             * Converts serialized ciphertext to CipherParams,
             * else assumed CipherParams already and returns ciphertext unchanged.
             *
             * @param {CipherParams|string} ciphertext The ciphertext.
             * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext.
             *
             * @return {CipherParams} The unserialized ciphertext.
             *
             * @static
             *
             * @example
             *
             *     var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format);
             */
            _parse: function (ciphertext, format) {
                if (typeof ciphertext == 'string') {
                    return format.parse(ciphertext, this);
                } else {
                    return ciphertext;
                }
            }
        });
        /**
         * Key derivation function namespace.
         */
        var C_kdf = C.kdf = {};
        /**
         * OpenSSL key derivation function.
         */
        var OpenSSLKdf = C_kdf.OpenSSL = {
            /**
             * Derives a key and IV from a password.
             *
             * @param {string} password The password to derive from.
             * @param {number} keySize The size in words of the key to generate.
             * @param {number} ivSize The size in words of the IV to generate.
             * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly.
             *
             * @return {CipherParams} A cipher params object with the key, IV, and salt.
             *
             * @static
             *
             * @example
             *
             *     var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32);
             *     var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt');
             */
            execute: function (password, keySize, ivSize, salt, hasher) {
                // Generate random salt
                if (!salt) {
                    salt = WordArray.random(64/8);
                }
                // Derive key and IV
                if (!hasher) {
                    var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt);
                } else {
                    var key = EvpKDF.create({ keySize: keySize + ivSize, hasher: hasher }).compute(password, salt);
                }
                // Separate key and IV
                var iv = WordArray.create(key.words.slice(keySize), ivSize * 4);
                key.sigBytes = keySize * 4;
                // Return params
                return CipherParams.create({ key: key, iv: iv, salt: salt });
            }
        };
        /**
         * A serializable cipher wrapper that derives the key from a password,
         * and returns ciphertext as a serializable cipher params object.
         */
        var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({
            /**
             * Configuration options.
             *
             * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL
             */
            cfg: SerializableCipher.cfg.extend({
                kdf: OpenSSLKdf
            }),
            /**
             * Encrypts a message using a password.
             *
             * @param {Cipher} cipher The cipher algorithm to use.
             * @param {WordArray|string} message The message to encrypt.
             * @param {string} password The password.
             * @param {Object} cfg (Optional) The configuration options to use for this operation.
             *
             * @return {CipherParams} A cipher params object.
             *
             * @static
             *
             * @example
             *
             *     var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password');
             *     var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL });
             */
            encrypt: function (cipher, message, password, cfg) {
                // Apply config defaults
                cfg = this.cfg.extend(cfg);
                // Derive key and other params
                var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, cfg.salt, cfg.hasher);
                // Add IV to config
                cfg.iv = derivedParams.iv;
                // Encrypt
                var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg);
                // Mix in derived params
                ciphertext.mixIn(derivedParams);
                return ciphertext;
            },
            /**
             * Decrypts serialized ciphertext using a password.
             *
             * @param {Cipher} cipher The cipher algorithm to use.
             * @param {CipherParams|string} ciphertext The ciphertext to decrypt.
             * @param {string} password The password.
             * @param {Object} cfg (Optional) The configuration options to use for this operation.
             *
             * @return {WordArray} The plaintext.
             *
             * @static
             *
             * @example
             *
             *     var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL });
             *     var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL });
             */
            decrypt: function (cipher, ciphertext, password, cfg) {
                // Apply config defaults
                cfg = this.cfg.extend(cfg);
                // Convert string to CipherParams
                ciphertext = this._parse(ciphertext, cfg.format);
                // Derive key and other params
                var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt, cfg.hasher);
                // Add IV to config
                cfg.iv = derivedParams.iv;
                // Decrypt
                var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg);
                return plaintext;
            }
        });
    }());
}));
node_modules/crypto-js/core.js
New file
@@ -0,0 +1,807 @@
;(function (root, factory) {
    if (typeof exports === "object") {
        // CommonJS
        module.exports = exports = factory();
    }
    else if (typeof define === "function" && define.amd) {
        // AMD
        define([], factory);
    }
    else {
        // Global (browser)
        root.CryptoJS = factory();
    }
}(this, function () {
    /*globals window, global, require*/
    /**
     * CryptoJS core components.
     */
    var CryptoJS = CryptoJS || (function (Math, undefined) {
        var crypto;
        // Native crypto from window (Browser)
        if (typeof window !== 'undefined' && window.crypto) {
            crypto = window.crypto;
        }
        // Native crypto in web worker (Browser)
        if (typeof self !== 'undefined' && self.crypto) {
            crypto = self.crypto;
        }
        // Native crypto from worker
        if (typeof globalThis !== 'undefined' && globalThis.crypto) {
            crypto = globalThis.crypto;
        }
        // Native (experimental IE 11) crypto from window (Browser)
        if (!crypto && typeof window !== 'undefined' && window.msCrypto) {
            crypto = window.msCrypto;
        }
        // Native crypto from global (NodeJS)
        if (!crypto && typeof global !== 'undefined' && global.crypto) {
            crypto = global.crypto;
        }
        // Native crypto import via require (NodeJS)
        if (!crypto && typeof require === 'function') {
            try {
                crypto = require('crypto');
            } catch (err) {}
        }
        /*
         * Cryptographically secure pseudorandom number generator
         *
         * As Math.random() is cryptographically not safe to use
         */
        var cryptoSecureRandomInt = function () {
            if (crypto) {
                // Use getRandomValues method (Browser)
                if (typeof crypto.getRandomValues === 'function') {
                    try {
                        return crypto.getRandomValues(new Uint32Array(1))[0];
                    } catch (err) {}
                }
                // Use randomBytes method (NodeJS)
                if (typeof crypto.randomBytes === 'function') {
                    try {
                        return crypto.randomBytes(4).readInt32LE();
                    } catch (err) {}
                }
            }
            throw new Error('Native crypto module could not be used to get secure random number.');
        };
        /*
         * Local polyfill of Object.create
         */
        var create = Object.create || (function () {
            function F() {}
            return function (obj) {
                var subtype;
                F.prototype = obj;
                subtype = new F();
                F.prototype = null;
                return subtype;
            };
        }());
        /**
         * CryptoJS namespace.
         */
        var C = {};
        /**
         * Library namespace.
         */
        var C_lib = C.lib = {};
        /**
         * Base object for prototypal inheritance.
         */
        var Base = C_lib.Base = (function () {
            return {
                /**
                 * Creates a new object that inherits from this object.
                 *
                 * @param {Object} overrides Properties to copy into the new object.
                 *
                 * @return {Object} The new object.
                 *
                 * @static
                 *
                 * @example
                 *
                 *     var MyType = CryptoJS.lib.Base.extend({
                 *         field: 'value',
                 *
                 *         method: function () {
                 *         }
                 *     });
                 */
                extend: function (overrides) {
                    // Spawn
                    var subtype = create(this);
                    // Augment
                    if (overrides) {
                        subtype.mixIn(overrides);
                    }
                    // Create default initializer
                    if (!subtype.hasOwnProperty('init') || this.init === subtype.init) {
                        subtype.init = function () {
                            subtype.$super.init.apply(this, arguments);
                        };
                    }
                    // Initializer's prototype is the subtype object
                    subtype.init.prototype = subtype;
                    // Reference supertype
                    subtype.$super = this;
                    return subtype;
                },
                /**
                 * Extends this object and runs the init method.
                 * Arguments to create() will be passed to init().
                 *
                 * @return {Object} The new object.
                 *
                 * @static
                 *
                 * @example
                 *
                 *     var instance = MyType.create();
                 */
                create: function () {
                    var instance = this.extend();
                    instance.init.apply(instance, arguments);
                    return instance;
                },
                /**
                 * Initializes a newly created object.
                 * Override this method to add some logic when your objects are created.
                 *
                 * @example
                 *
                 *     var MyType = CryptoJS.lib.Base.extend({
                 *         init: function () {
                 *             // ...
                 *         }
                 *     });
                 */
                init: function () {
                },
                /**
                 * Copies properties into this object.
                 *
                 * @param {Object} properties The properties to mix in.
                 *
                 * @example
                 *
                 *     MyType.mixIn({
                 *         field: 'value'
                 *     });
                 */
                mixIn: function (properties) {
                    for (var propertyName in properties) {
                        if (properties.hasOwnProperty(propertyName)) {
                            this[propertyName] = properties[propertyName];
                        }
                    }
                    // IE won't copy toString using the loop above
                    if (properties.hasOwnProperty('toString')) {
                        this.toString = properties.toString;
                    }
                },
                /**
                 * Creates a copy of this object.
                 *
                 * @return {Object} The clone.
                 *
                 * @example
                 *
                 *     var clone = instance.clone();
                 */
                clone: function () {
                    return this.init.prototype.extend(this);
                }
            };
        }());
        /**
         * An array of 32-bit words.
         *
         * @property {Array} words The array of 32-bit words.
         * @property {number} sigBytes The number of significant bytes in this word array.
         */
        var WordArray = C_lib.WordArray = Base.extend({
            /**
             * Initializes a newly created word array.
             *
             * @param {Array} words (Optional) An array of 32-bit words.
             * @param {number} sigBytes (Optional) The number of significant bytes in the words.
             *
             * @example
             *
             *     var wordArray = CryptoJS.lib.WordArray.create();
             *     var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]);
             *     var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6);
             */
            init: function (words, sigBytes) {
                words = this.words = words || [];
                if (sigBytes != undefined) {
                    this.sigBytes = sigBytes;
                } else {
                    this.sigBytes = words.length * 4;
                }
            },
            /**
             * Converts this word array to a string.
             *
             * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex
             *
             * @return {string} The stringified word array.
             *
             * @example
             *
             *     var string = wordArray + '';
             *     var string = wordArray.toString();
             *     var string = wordArray.toString(CryptoJS.enc.Utf8);
             */
            toString: function (encoder) {
                return (encoder || Hex).stringify(this);
            },
            /**
             * Concatenates a word array to this word array.
             *
             * @param {WordArray} wordArray The word array to append.
             *
             * @return {WordArray} This word array.
             *
             * @example
             *
             *     wordArray1.concat(wordArray2);
             */
            concat: function (wordArray) {
                // Shortcuts
                var thisWords = this.words;
                var thatWords = wordArray.words;
                var thisSigBytes = this.sigBytes;
                var thatSigBytes = wordArray.sigBytes;
                // Clamp excess bits
                this.clamp();
                // Concat
                if (thisSigBytes % 4) {
                    // Copy one byte at a time
                    for (var i = 0; i < thatSigBytes; i++) {
                        var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                        thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8);
                    }
                } else {
                    // Copy one word at a time
                    for (var j = 0; j < thatSigBytes; j += 4) {
                        thisWords[(thisSigBytes + j) >>> 2] = thatWords[j >>> 2];
                    }
                }
                this.sigBytes += thatSigBytes;
                // Chainable
                return this;
            },
            /**
             * Removes insignificant bits.
             *
             * @example
             *
             *     wordArray.clamp();
             */
            clamp: function () {
                // Shortcuts
                var words = this.words;
                var sigBytes = this.sigBytes;
                // Clamp
                words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8);
                words.length = Math.ceil(sigBytes / 4);
            },
            /**
             * Creates a copy of this word array.
             *
             * @return {WordArray} The clone.
             *
             * @example
             *
             *     var clone = wordArray.clone();
             */
            clone: function () {
                var clone = Base.clone.call(this);
                clone.words = this.words.slice(0);
                return clone;
            },
            /**
             * Creates a word array filled with random bytes.
             *
             * @param {number} nBytes The number of random bytes to generate.
             *
             * @return {WordArray} The random word array.
             *
             * @static
             *
             * @example
             *
             *     var wordArray = CryptoJS.lib.WordArray.random(16);
             */
            random: function (nBytes) {
                var words = [];
                for (var i = 0; i < nBytes; i += 4) {
                    words.push(cryptoSecureRandomInt());
                }
                return new WordArray.init(words, nBytes);
            }
        });
        /**
         * Encoder namespace.
         */
        var C_enc = C.enc = {};
        /**
         * Hex encoding strategy.
         */
        var Hex = C_enc.Hex = {
            /**
             * Converts a word array to a hex string.
             *
             * @param {WordArray} wordArray The word array.
             *
             * @return {string} The hex string.
             *
             * @static
             *
             * @example
             *
             *     var hexString = CryptoJS.enc.Hex.stringify(wordArray);
             */
            stringify: function (wordArray) {
                // Shortcuts
                var words = wordArray.words;
                var sigBytes = wordArray.sigBytes;
                // Convert
                var hexChars = [];
                for (var i = 0; i < sigBytes; i++) {
                    var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                    hexChars.push((bite >>> 4).toString(16));
                    hexChars.push((bite & 0x0f).toString(16));
                }
                return hexChars.join('');
            },
            /**
             * Converts a hex string to a word array.
             *
             * @param {string} hexStr The hex string.
             *
             * @return {WordArray} The word array.
             *
             * @static
             *
             * @example
             *
             *     var wordArray = CryptoJS.enc.Hex.parse(hexString);
             */
            parse: function (hexStr) {
                // Shortcut
                var hexStrLength = hexStr.length;
                // Convert
                var words = [];
                for (var i = 0; i < hexStrLength; i += 2) {
                    words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
                }
                return new WordArray.init(words, hexStrLength / 2);
            }
        };
        /**
         * Latin1 encoding strategy.
         */
        var Latin1 = C_enc.Latin1 = {
            /**
             * Converts a word array to a Latin1 string.
             *
             * @param {WordArray} wordArray The word array.
             *
             * @return {string} The Latin1 string.
             *
             * @static
             *
             * @example
             *
             *     var latin1String = CryptoJS.enc.Latin1.stringify(wordArray);
             */
            stringify: function (wordArray) {
                // Shortcuts
                var words = wordArray.words;
                var sigBytes = wordArray.sigBytes;
                // Convert
                var latin1Chars = [];
                for (var i = 0; i < sigBytes; i++) {
                    var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                    latin1Chars.push(String.fromCharCode(bite));
                }
                return latin1Chars.join('');
            },
            /**
             * Converts a Latin1 string to a word array.
             *
             * @param {string} latin1Str The Latin1 string.
             *
             * @return {WordArray} The word array.
             *
             * @static
             *
             * @example
             *
             *     var wordArray = CryptoJS.enc.Latin1.parse(latin1String);
             */
            parse: function (latin1Str) {
                // Shortcut
                var latin1StrLength = latin1Str.length;
                // Convert
                var words = [];
                for (var i = 0; i < latin1StrLength; i++) {
                    words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
                }
                return new WordArray.init(words, latin1StrLength);
            }
        };
        /**
         * UTF-8 encoding strategy.
         */
        var Utf8 = C_enc.Utf8 = {
            /**
             * Converts a word array to a UTF-8 string.
             *
             * @param {WordArray} wordArray The word array.
             *
             * @return {string} The UTF-8 string.
             *
             * @static
             *
             * @example
             *
             *     var utf8String = CryptoJS.enc.Utf8.stringify(wordArray);
             */
            stringify: function (wordArray) {
                try {
                    return decodeURIComponent(escape(Latin1.stringify(wordArray)));
                } catch (e) {
                    throw new Error('Malformed UTF-8 data');
                }
            },
            /**
             * Converts a UTF-8 string to a word array.
             *
             * @param {string} utf8Str The UTF-8 string.
             *
             * @return {WordArray} The word array.
             *
             * @static
             *
             * @example
             *
             *     var wordArray = CryptoJS.enc.Utf8.parse(utf8String);
             */
            parse: function (utf8Str) {
                return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
            }
        };
        /**
         * Abstract buffered block algorithm template.
         *
         * The property blockSize must be implemented in a concrete subtype.
         *
         * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0
         */
        var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({
            /**
             * Resets this block algorithm's data buffer to its initial state.
             *
             * @example
             *
             *     bufferedBlockAlgorithm.reset();
             */
            reset: function () {
                // Initial values
                this._data = new WordArray.init();
                this._nDataBytes = 0;
            },
            /**
             * Adds new data to this block algorithm's buffer.
             *
             * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8.
             *
             * @example
             *
             *     bufferedBlockAlgorithm._append('data');
             *     bufferedBlockAlgorithm._append(wordArray);
             */
            _append: function (data) {
                // Convert string to WordArray, else assume WordArray already
                if (typeof data == 'string') {
                    data = Utf8.parse(data);
                }
                // Append
                this._data.concat(data);
                this._nDataBytes += data.sigBytes;
            },
            /**
             * Processes available data blocks.
             *
             * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype.
             *
             * @param {boolean} doFlush Whether all blocks and partial blocks should be processed.
             *
             * @return {WordArray} The processed data.
             *
             * @example
             *
             *     var processedData = bufferedBlockAlgorithm._process();
             *     var processedData = bufferedBlockAlgorithm._process(!!'flush');
             */
            _process: function (doFlush) {
                var processedWords;
                // Shortcuts
                var data = this._data;
                var dataWords = data.words;
                var dataSigBytes = data.sigBytes;
                var blockSize = this.blockSize;
                var blockSizeBytes = blockSize * 4;
                // Count blocks ready
                var nBlocksReady = dataSigBytes / blockSizeBytes;
                if (doFlush) {
                    // Round up to include partial blocks
                    nBlocksReady = Math.ceil(nBlocksReady);
                } else {
                    // Round down to include only full blocks,
                    // less the number of blocks that must remain in the buffer
                    nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
                }
                // Count words ready
                var nWordsReady = nBlocksReady * blockSize;
                // Count bytes ready
                var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);
                // Process blocks
                if (nWordsReady) {
                    for (var offset = 0; offset < nWordsReady; offset += blockSize) {
                        // Perform concrete-algorithm logic
                        this._doProcessBlock(dataWords, offset);
                    }
                    // Remove processed words
                    processedWords = dataWords.splice(0, nWordsReady);
                    data.sigBytes -= nBytesReady;
                }
                // Return processed words
                return new WordArray.init(processedWords, nBytesReady);
            },
            /**
             * Creates a copy of this object.
             *
             * @return {Object} The clone.
             *
             * @example
             *
             *     var clone = bufferedBlockAlgorithm.clone();
             */
            clone: function () {
                var clone = Base.clone.call(this);
                clone._data = this._data.clone();
                return clone;
            },
            _minBufferSize: 0
        });
        /**
         * Abstract hasher template.
         *
         * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits)
         */
        var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({
            /**
             * Configuration options.
             */
            cfg: Base.extend(),
            /**
             * Initializes a newly created hasher.
             *
             * @param {Object} cfg (Optional) The configuration options to use for this hash computation.
             *
             * @example
             *
             *     var hasher = CryptoJS.algo.SHA256.create();
             */
            init: function (cfg) {
                // Apply config defaults
                this.cfg = this.cfg.extend(cfg);
                // Set initial values
                this.reset();
            },
            /**
             * Resets this hasher to its initial state.
             *
             * @example
             *
             *     hasher.reset();
             */
            reset: function () {
                // Reset data buffer
                BufferedBlockAlgorithm.reset.call(this);
                // Perform concrete-hasher logic
                this._doReset();
            },
            /**
             * Updates this hasher with a message.
             *
             * @param {WordArray|string} messageUpdate The message to append.
             *
             * @return {Hasher} This hasher.
             *
             * @example
             *
             *     hasher.update('message');
             *     hasher.update(wordArray);
             */
            update: function (messageUpdate) {
                // Append
                this._append(messageUpdate);
                // Update the hash
                this._process();
                // Chainable
                return this;
            },
            /**
             * Finalizes the hash computation.
             * Note that the finalize operation is effectively a destructive, read-once operation.
             *
             * @param {WordArray|string} messageUpdate (Optional) A final message update.
             *
             * @return {WordArray} The hash.
             *
             * @example
             *
             *     var hash = hasher.finalize();
             *     var hash = hasher.finalize('message');
             *     var hash = hasher.finalize(wordArray);
             */
            finalize: function (messageUpdate) {
                // Final message update
                if (messageUpdate) {
                    this._append(messageUpdate);
                }
                // Perform concrete-hasher logic
                var hash = this._doFinalize();
                return hash;
            },
            blockSize: 512/32,
            /**
             * Creates a shortcut function to a hasher's object interface.
             *
             * @param {Hasher} hasher The hasher to create a helper for.
             *
             * @return {Function} The shortcut function.
             *
             * @static
             *
             * @example
             *
             *     var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256);
             */
            _createHelper: function (hasher) {
                return function (message, cfg) {
                    return new hasher.init(cfg).finalize(message);
                };
            },
            /**
             * Creates a shortcut function to the HMAC's object interface.
             *
             * @param {Hasher} hasher The hasher to use in this HMAC helper.
             *
             * @return {Function} The shortcut function.
             *
             * @static
             *
             * @example
             *
             *     var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256);
             */
            _createHmacHelper: function (hasher) {
                return function (message, key) {
                    return new C_algo.HMAC.init(hasher, key).finalize(message);
                };
            }
        });
        /**
         * Algorithm namespace.
         */
        var C_algo = C.algo = {};
        return C;
    }(Math));
    return CryptoJS;
}));
node_modules/crypto-js/crypto-js.js
New file
Diff too large
node_modules/crypto-js/docs/QuickStartGuide.wiki
New file
@@ -0,0 +1,470 @@
<wiki:toc/>
----
= Quick-start Guide =
== Hashers ==
=== The Hasher Algorithms ===
==== MD5 ====
MD5 is a widely used hash function. It's been used in a variety of security applications and is also commonly used to check the integrity of files. Though, MD5 is not collision resistant, and it isn't suitable for applications like SSL certificates or digital signatures that rely on this property.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/md5.js"></script>
<script>
    var hash = CryptoJS.MD5("Message");
</script>
}}}
==== SHA-1 ====
The SHA hash functions were designed by the National Security Agency (NSA). SHA-1 is the most established of the existing SHA hash functions, and it's used in a variety of security applications and protocols. Though, SHA-1's collision resistance has been weakening as new attacks are discovered or improved.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/sha1.js"></script>
<script>
    var hash = CryptoJS.SHA1("Message");
</script>
}}}
==== SHA-2 ====
SHA-256 is one of the four variants in the SHA-2 set. It isn't as widely used as SHA-1, though it appears to provide much better security.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/sha256.js"></script>
<script>
    var hash = CryptoJS.SHA256("Message");
</script>
}}}
SHA-512 is largely identical to SHA-256 but operates on 64-bit words rather than 32.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/sha512.js"></script>
<script>
    var hash = CryptoJS.SHA512("Message");
</script>
}}}
CryptoJS also supports SHA-224 and SHA-384, which are largely identical but truncated versions of SHA-256 and SHA-512 respectively.
==== SHA-3 ====
SHA-3 is the winner of a five-year competition to select a new cryptographic hash algorithm where 64 competing designs were evaluated.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/sha3.js"></script>
<script>
    var hash = CryptoJS.SHA3("Message");
</script>
}}}
SHA-3 can be configured to output hash lengths of one of 224, 256, 384, or 512 bits. The default is 512 bits.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/sha3.js"></script>
<script>
    var hash = CryptoJS.SHA3("Message", { outputLength: 512 });
    var hash = CryptoJS.SHA3("Message", { outputLength: 384 });
    var hash = CryptoJS.SHA3("Message", { outputLength: 256 });
    var hash = CryptoJS.SHA3("Message", { outputLength: 224 });
</script>
}}}
==== RIPEMD-160 ====
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/ripemd160.js"></script>
<script>
    var hash = CryptoJS.RIPEMD160("Message");
</script>
}}}
=== The Hasher Input ===
The hash algorithms accept either strings or instances of CryptoJS.lib.WordArray. A WordArray object represents an array of 32-bit words. When you pass a string, it's automatically converted to a WordArray encoded as UTF-8.
=== The Hasher Output ===
The hash you get back isn't a string yet. It's a WordArray object. When you use a WordArray object in a string context, it's automatically converted to a hex string.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/sha256.js"></script>
<script>
    var hash = CryptoJS.SHA256("Message");
    alert(typeof hash); // object
    alert(hash); // 2f77668a9dfbf8d5848b9eeb4a7145ca94c6ed9236e4a773f6dcafa5132b2f91
</script>
}}}
You can convert a WordArray object to other formats by explicitly calling the toString method and passing an encoder.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/sha256.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/components/enc-base64-min.js"></script>
<script>
    var hash = CryptoJS.SHA256("Message");
    alert(hash.toString(CryptoJS.enc.Base64)); // L3dmip37+NWEi57rSnFFypTG7ZI25Kdz9tyvpRMrL5E=
    alert(hash.toString(CryptoJS.enc.Latin1)); // /wf��ûøÕ���ëJqEÊ�Æí�6ä§söܯ¥+/�
    alert(hash.toString(CryptoJS.enc.Hex)); // 2f77668a9dfbf8d5848b9eeb4a7145ca94c6ed9236e4a773f6dcafa5132b2f91
</script>
}}}
=== Progressive Hashing ===
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/sha256.js"></script>
<script>
    var sha256 = CryptoJS.algo.SHA256.create();
    sha256.update("Message Part 1");
    sha256.update("Message Part 2");
    sha256.update("Message Part 3");
    var hash = sha256.finalize();
</script>
}}}
== HMAC ==
Keyed-hash message authentication codes (HMAC) is a mechanism for message authentication using cryptographic hash functions.
HMAC can be used in combination with any iterated cryptographic hash function.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/hmac-md5.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/hmac-sha1.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/hmac-sha256.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/hmac-sha512.js"></script>
<script>
    var hash = CryptoJS.HmacMD5("Message", "Secret Passphrase");
    var hash = CryptoJS.HmacSHA1("Message", "Secret Passphrase");
    var hash = CryptoJS.HmacSHA256("Message", "Secret Passphrase");
    var hash = CryptoJS.HmacSHA512("Message", "Secret Passphrase");
</script>
}}}
=== Progressive HMAC Hashing ===
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/hmac-sha256.js"></script>
<script>
    var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, "Secret Passphrase");
    hmac.update("Message Part 1");
    hmac.update("Message Part 2");
    hmac.update("Message Part 3");
    var hash = hmac.finalize();
</script>
}}}
== PBKDF2 ==
PBKDF2 is a password-based key derivation function. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.
A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/pbkdf2.js"></script>
<script>
    var salt = CryptoJS.lib.WordArray.random(128/8);
    var key128Bits = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32 });
    var key256Bits = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 256/32 });
    var key512Bits = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 512/32 });
    var key512Bits1000Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 512/32, iterations: 1000 });
</script>
}}}
== Ciphers ==
=== The Cipher Algorithms ===
==== AES ====
The Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/aes.js"></script>
<script>
    var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
    var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");
</script>
}}}
CryptoJS supports AES-128, AES-192, and AES-256. It will pick the variant by the size of the key you pass in. If you use a passphrase, then it will generate a 256-bit key.
==== DES, Triple DES ====
DES is a previously dominant algorithm for encryption, and was published as an official Federal Information Processing Standard (FIPS). DES is now considered to be insecure due to the small key size.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/tripledes.js"></script>
<script>
    var encrypted = CryptoJS.DES.encrypt("Message", "Secret Passphrase");
    var decrypted = CryptoJS.DES.decrypt(encrypted, "Secret Passphrase");
</script>
}}}
Triple DES applies DES three times to each block to increase the key size. The algorithm is believed to be secure in this form.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/tripledes.js"></script>
<script>
    var encrypted = CryptoJS.TripleDES.encrypt("Message", "Secret Passphrase");
    var decrypted = CryptoJS.TripleDES.decrypt(encrypted, "Secret Passphrase");
</script>
}}}
==== Rabbit ====
Rabbit is a high-performance stream cipher and a finalist in the eSTREAM Portfolio. It is one of the four designs selected after a 3 1/2-year process where 22 designs were evaluated.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/rabbit.js"></script>
<script>
    var encrypted = CryptoJS.Rabbit.encrypt("Message", "Secret Passphrase");
    var decrypted = CryptoJS.Rabbit.decrypt(encrypted, "Secret Passphrase");
</script>
}}}
==== RC4, RC4Drop ====
RC4 is a widely-used stream cipher. It's used in popular protocols such as SSL and WEP. Although remarkable for its simplicity and speed, the algorithm's history doesn't inspire confidence in its security.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/rc4.js"></script>
<script>
    var encrypted = CryptoJS.RC4.encrypt("Message", "Secret Passphrase");
    var decrypted = CryptoJS.RC4.decrypt(encrypted, "Secret Passphrase");
</script>
}}}
It was discovered that the first few bytes of keystream are strongly non-random and leak information about the key. We can defend against this attack by discarding the initial portion of the keystream. This modified algorithm is traditionally called RC4-drop.
By default, 192 words (768 bytes) are dropped, but you can configure the algorithm to drop any number of words.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/rc4.js"></script>
<script>
    var encrypted = CryptoJS.RC4Drop.encrypt("Message", "Secret Passphrase");
    var encrypted = CryptoJS.RC4Drop.encrypt("Message", "Secret Passphrase", { drop: 3072/4 });
    var decrypted = CryptoJS.RC4Drop.decrypt(encrypted, "Secret Passphrase", { drop: 3072/4 });
</script>
}}}
=== Custom Key and IV ===
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/aes.js"></script>
<script>
    var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f');
    var iv  = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
    var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });
</script>
}}}
=== Block Modes and Padding ===
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/aes.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/components/mode-cfb-min.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/components/pad-ansix923-min.js"></script>
<script>
    var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase", { mode: CryptoJS.mode.CFB, padding: CryptoJS.pad.AnsiX923 });
</script>
}}}
CryptoJS supports the following modes:
  * CBC (the default)
  * CFB
  * CTR
  * OFB
  * ECB
And CryptoJS supports the following padding schemes:
  * Pkcs7 (the default)
  * Iso97971
  * AnsiX923
  * Iso10126
  * ZeroPadding
  * NoPadding
=== The Cipher Input ===
For the plaintext message, the cipher algorithms accept either strings or instances of CryptoJS.lib.WordArray.
For the key, when you pass a string, it's treated as a passphrase and used to derive an actual key and IV. Or you can pass a WordArray that represents the actual key. If you pass the actual key, you must also pass the actual IV.
For the ciphertext, the cipher algorithms accept either strings or instances of CryptoJS.lib.CipherParams. A CipherParams object represents a collection of parameters such as the IV, a salt, and the raw ciphertext itself. When you pass a string, it's automatically converted to a CipherParams object according to a configurable format strategy.
=== The Cipher Output ===
The plaintext you get back after decryption is a WordArray object. See Hashers' Output for more detail.
The ciphertext you get back after encryption isn't a string yet. It's a CipherParams object. A CipherParams object gives you access to all the parameters used during encryption. When you use a CipherParams object in a string context, it's automatically converted to a string according to a format strategy. The default is an OpenSSL-compatible format.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/aes.js"></script>
<script>
    var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
    alert(encrypted.key);        // 74eb593087a982e2a6f5dded54ecd96d1fd0f3d44a58728cdcd40c55227522223
    alert(encrypted.iv);         // 7781157e2629b094f0e3dd48c4d786115
    alert(encrypted.salt);       // 7a25f9132ec6a8b34
    alert(encrypted.ciphertext); // 73e54154a15d1beeb509d9e12f1e462a0
    alert(encrypted);            // U2FsdGVkX1+iX5Ey7GqLND5UFUoV0b7rUJ2eEvHkYqA=
</script>
}}}
You can define your own formats in order to be compatible with other crypto implementations. A format is an object with two methods—stringify and parse—that converts between CipherParams objects and ciphertext strings.
Here's how you might write a JSON formatter:
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/aes.js"></script>
<script>
    var JsonFormatter = {
        stringify: function (cipherParams) {
            // create json object with ciphertext
            var jsonObj = {
                ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64)
            };
            // optionally add iv and salt
            if (cipherParams.iv) {
                jsonObj.iv = cipherParams.iv.toString();
            }
            if (cipherParams.salt) {
                jsonObj.s = cipherParams.salt.toString();
            }
            // stringify json object
            return JSON.stringify(jsonObj);
        },
        parse: function (jsonStr) {
            // parse json string
            var jsonObj = JSON.parse(jsonStr);
            // extract ciphertext from json object, and create cipher params object
            var cipherParams = CryptoJS.lib.CipherParams.create({
                ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct)
            });
            // optionally extract iv and salt
            if (jsonObj.iv) {
                cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv)
            }
            if (jsonObj.s) {
                cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s)
            }
            return cipherParams;
        }
    };
    var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase", { format: JsonFormatter });
    alert(encrypted); // {"ct":"tZ4MsEnfbcDOwqau68aOrQ==","iv":"8a8c8fd8fe33743d3638737ea4a00698","s":"ba06373c8f57179c"}
    var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase", { format: JsonFormatter });
    alert(decrypted.toString(CryptoJS.enc.Utf8)); // Message
</script>
}}}
=== Progressive Ciphering ===
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/aes.js"></script>
<script>
    var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f');
    var iv  = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
    var aesEncryptor = CryptoJS.algo.AES.createEncryptor(key, { iv: iv });
    var ciphertextPart1 = aesEncryptor.process("Message Part 1");
    var ciphertextPart2 = aesEncryptor.process("Message Part 2");
    var ciphertextPart3 = aesEncryptor.process("Message Part 3");
    var ciphertextPart4 = aesEncryptor.finalize();
    var aesDecryptor = CryptoJS.algo.AES.createDecryptor(key, { iv: iv });
    var plaintextPart1 = aesDecryptor.process(ciphertextPart1);
    var plaintextPart2 = aesDecryptor.process(ciphertextPart2);
    var plaintextPart3 = aesDecryptor.process(ciphertextPart3);
    var plaintextPart4 = aesDecryptor.process(ciphertextPart4);
    var plaintextPart5 = aesDecryptor.finalize();
</script>
}}}
=== Interoperability ===
==== With OpenSSL ====
Encrypt with OpenSSL:
{{{
openssl enc -aes-256-cbc -in infile -out outfile -pass pass:"Secret Passphrase" -e -base64
}}}
Decrypt with CryptoJS:
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/rollups/aes.js"></script>
<script>
    var decrypted = CryptoJS.AES.decrypt(openSSLEncrypted, "Secret Passphrase");
</script>
}}}
== Encoders ==
CryptoJS can convert from encoding formats such as Base64, Latin1 or Hex to WordArray objects and vica versa.
{{{
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/components/core-min.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/components/enc-utf16-min.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/components/enc-base64-min.js"></script>
<script>
    var words  = CryptoJS.enc.Base64.parse('SGVsbG8sIFdvcmxkIQ==');
    var base64 = CryptoJS.enc.Base64.stringify(words);
    var words  = CryptoJS.enc.Latin1.parse('Hello, World!');
    var latin1 = CryptoJS.enc.Latin1.stringify(words);
    var words = CryptoJS.enc.Hex.parse('48656c6c6f2c20576f726c6421');
    var hex   = CryptoJS.enc.Hex.stringify(words);
    var words = CryptoJS.enc.Utf8.parse('𤭢');
    var utf8  = CryptoJS.enc.Utf8.stringify(words);
    var words = CryptoJS.enc.Utf16.parse('Hello, World!');
    var utf16 = CryptoJS.enc.Utf16.stringify(words);
    var words = CryptoJS.enc.Utf16LE.parse('Hello, World!');
    var utf16 = CryptoJS.enc.Utf16LE.stringify(words);
</script>
}}}
Diff truncated after the above file
node_modules/crypto-js/enc-base64.js node_modules/crypto-js/enc-base64url.js node_modules/crypto-js/enc-hex.js node_modules/crypto-js/enc-latin1.js node_modules/crypto-js/enc-utf16.js node_modules/crypto-js/enc-utf8.js node_modules/crypto-js/evpkdf.js node_modules/crypto-js/format-hex.js node_modules/crypto-js/format-openssl.js node_modules/crypto-js/hmac-md5.js node_modules/crypto-js/hmac-ripemd160.js node_modules/crypto-js/hmac-sha1.js node_modules/crypto-js/hmac-sha224.js node_modules/crypto-js/hmac-sha256.js node_modules/crypto-js/hmac-sha3.js node_modules/crypto-js/hmac-sha384.js node_modules/crypto-js/hmac-sha512.js node_modules/crypto-js/hmac.js node_modules/crypto-js/index.js node_modules/crypto-js/lib-typedarrays.js node_modules/crypto-js/md5.js node_modules/crypto-js/mode-cfb.js node_modules/crypto-js/mode-ctr-gladman.js node_modules/crypto-js/mode-ctr.js node_modules/crypto-js/mode-ecb.js node_modules/crypto-js/mode-ofb.js node_modules/crypto-js/package.json node_modules/crypto-js/pad-ansix923.js node_modules/crypto-js/pad-iso10126.js node_modules/crypto-js/pad-iso97971.js node_modules/crypto-js/pad-nopadding.js node_modules/crypto-js/pad-pkcs7.js node_modules/crypto-js/pad-zeropadding.js node_modules/crypto-js/pbkdf2.js node_modules/crypto-js/rabbit-legacy.js node_modules/crypto-js/rabbit.js node_modules/crypto-js/rc4.js node_modules/crypto-js/ripemd160.js node_modules/crypto-js/sha1.js node_modules/crypto-js/sha224.js node_modules/crypto-js/sha256.js node_modules/crypto-js/sha3.js node_modules/crypto-js/sha384.js node_modules/crypto-js/sha512.js node_modules/crypto-js/tripledes.js node_modules/crypto-js/x64-core.js node_modules/pdfh5/LICENSE node_modules/pdfh5/README.md node_modules/pdfh5/css/pdfh5.css node_modules/pdfh5/css/style.css node_modules/pdfh5/git.pdf node_modules/pdfh5/index.html node_modules/pdfh5/js/jquery-3.6.0.min.js node_modules/pdfh5/js/jquery.qrcode.min.js node_modules/pdfh5/js/pdf-obfuscated.js node_modules/pdfh5/js/pdf.js node_modules/pdfh5/js/pdf.worker.js node_modules/pdfh5/js/pdfh5.js node_modules/pdfh5/js/pdfh5.worker.js node_modules/pdfh5/js/pinch-zoom.js node_modules/pdfh5/js/pinch-zoom.min.js node_modules/pdfh5/package.json node_modules/pdfh5/pdf.html node_modules/pdfh5/pdfh5.png node_modules/pdfh5/test.pdf node_modules/pdfh5/vconsole.min.js pages/addAddr/addAddr.vue pages/addBank/addBank.vue pages/address/address.vue pages/agree/agree.vue pages/bank/bank.vue pages/carts/carts.vue pages/cates/cates.vue pages/catesList/catesList.vue pages/collect/collect.vue pages/customer/customer.vue pages/edu/edu.vue pages/entrust/entrust.vue pages/esign/esign.vue pages/face/face.vue pages/findpass/findpass.vue pages/ginfo/ginfo.vue pages/idcard/idcard.vue pages/index/index.vue pages/job/job.vue pages/login/login.vue pages/logs/logs.vue pages/my/my.vue pages/order/order.vue pages/orderinfo/orderinfo.vue pages/pays/pays.vue pages/profile/profile.vue pages/regist/regist.vue pages/search/search.vue pages/wallet/wallet.vue pages/web/index.vue