"use strict"; import fs from "fs"; import { EventEmitter } from "events"; import { inspect } from "util"; import { sync as glob, hasMagic } from "glob"; import get from "simple-get"; import geometry from "./geometry"; import css from "./css"; import io from "./io"; const REPR = inspect.custom; // const fs = require("fs"), // { EventEmitter } = require("events"), // { inspect } = require("util"), // { sync: glob, hasMagic } = require("glob"), // get = require("simple-get"), // geometry = require("./geometry"), // css = require("./css"), // io = require("./io"), // REPR = inspect.custom; // // Neon <-> Node interface // const ø = Symbol.for("📦"), // the attr containing the boxed struct core = obj => (obj || {})[ø], // dereference the boxed struct wrap = (type, struct) => { // create new instance for struct let obj = internal(Object.create(type.prototype), ø, struct); return struct && internal(obj, "native", neon[type.name]); }, neon = Object.entries(require("./v6")).reduce((api, [name, fn]) => { let [_, struct, getset, attr] = name.match(/(.*?)_(?:([sg]et)_)?(.*)/), cls = api[struct] || (api[struct] = {}), slot = getset ? cls[attr] || (cls[attr] = {}) : cls; slot[getset || attr] = fn; return api; }, {}); class RustClass { constructor(type) { internal(this, "native", neon[type.name]); } alloc(...args) { return this.init("new", ...args); } init(fn, ...args) { return internal(this, ø, this.native[fn](null, ...args)); } ref(key, val) { return arguments.length > 1 ? (this[Symbol.for(key)] = val) : this[Symbol.for(key)]; } prop(attr, val) { let getset = arguments.length > 1 ? "set" : "get"; return this.native[attr][getset](this[ø], val); } ƒ(fn, ...args) { try { return this.native[fn](this[ø], ...args); } catch (error) { Error.captureStackTrace(error, this.ƒ); throw error; } } } // shorthands for attaching read-only attributes const readOnly = (obj, attr, value) => Object.defineProperty(obj, attr, { value, writable: false, enumerable: true }); const internal = (obj, attr, value) => Object.defineProperty(obj, attr, { value, writable: false, enumerable: false }); // convert arguments list to a string of type abbreviations function signature(args) { return args .map(v => Array.isArray(v) ? "a" : { string: "s", number: "n", object: "o" }[typeof v] || "x" ) .join(""); } const toString = val => typeof val == "string" ? val : new String(val).toString(); // // Helpers to reconcile Skia and DOMMatrix’s disagreement about row/col orientation // function toSkMatrix(jsMatrix) { if (Array.isArray(jsMatrix) && jsMatrix.length == 6) { var [a, b, c, d, e, f, m14, m24, m44] = jsMatrix.concat(0, 0, 1); } else if (jsMatrix instanceof geometry.DOMMatrix) { var { a, b, c, d, e, f, m14, m24, m44 } = jsMatrix; } return [a, c, e, b, d, f, m14, m24, m44]; } function fromSkMatrix(skMatrix) { let [a, b, c, d, e, f, p0, p1, p2] = skMatrix; return new geometry.DOMMatrix([ a, d, 0, p0, b, e, 0, p1, 0, 0, 1, 0, c, f, 0, p2 ]); } // // The Canvas API // class Canvas extends RustClass { static parent = new WeakMap(); static contexts = new WeakMap(); constructor(width, height) { super(Canvas).alloc(); Canvas.contexts.set(this, []); Object.assign(this, { width, height }); } getContext(kind) { return kind == "2d" ? Canvas.contexts.get(this)[0] || this.newPage() : null; } get width() { return this.prop("width"); } set width(w) { this.prop( "width", typeof w == "number" && !Number.isNaN(w) && w >= 0 ? w : 300 ); if (Canvas.contexts.get(this)[0]) this.getContext("2d").ƒ("resetSize", core(this)); } get height() { return this.prop("height"); } set height(h) { this.prop( "height", (h = typeof h == "number" && !Number.isNaN(h) && h >= 0 ? h : 150) ); if (Canvas.contexts.get(this)[0]) this.getContext("2d").ƒ("resetSize", core(this)); } newPage(width, height) { let ctx = new CanvasRenderingContext2D(core(this)); Canvas.parent.set(ctx, this); Canvas.contexts.get(this).unshift(ctx); if (arguments.length == 2) { Object.assign(this, { width, height }); } return ctx; } get pages() { return Canvas.contexts .get(this) .slice() .reverse(); } get png() { return this.toBuffer("png"); } get jpg() { return this.toBuffer("jpg"); } get pdf() { return this.toBuffer("pdf"); } get svg() { return this.toBuffer("svg"); } get async() { return this.prop("async"); } set async(flag) { if (!flag) { process.emitWarning( "Use the saveAsSync, toBufferSync, and toDataURLSync methods instead of setting the Canvas `async` property to false", "DeprecationWarning" ); } this.prop("async", flag); } saveAs(filename, opts = {}) { if (!this.async) return this.saveAsSync(...arguments); // support while deprecated opts = typeof opts == "number" ? { quality: opts } : opts; let { format, quality, pages, padding, pattern, density, outline, matte } = io.options(this.pages, { filename, ...opts }), args = [ pages.map(core), pattern, padding, format, quality, density, outline, matte ], worker = new EventEmitter(); this.ƒ("save", (result, msg) => worker.emit(result, msg), ...args); return new Promise((res, rej) => worker.once("ok", res).once("err", msg => rej(new Error(msg))) ); } saveAsSync(filename, opts = {}) { opts = typeof opts == "number" ? { quality: opts } : opts; let { format, quality, pages, padding, pattern, density, outline, matte } = io.options(this.pages, { filename, ...opts }); this.ƒ( "saveSync", pages.map(core), pattern, padding, format, quality, density, outline, matte ); } toBuffer(extension = "png", opts = {}) { if (!this.async) return this.toBufferSync(...arguments); // support while deprecated opts = typeof opts == "number" ? { quality: opts } : opts; let { format, quality, pages, density, outline, matte } = io.options( this.pages, { extension, ...opts } ), args = [pages.map(core), format, quality, density, outline, matte], worker = new EventEmitter(); this.ƒ("toBuffer", (result, msg) => worker.emit(result, msg), ...args); return new Promise((res, rej) => worker.once("ok", res).once("err", msg => rej(new Error(msg))) ); } toBufferSync(extension = "png", opts = {}) { opts = typeof opts == "number" ? { quality: opts } : opts; let { format, quality, pages, density, outline, matte } = io.options( this.pages, { extension, ...opts } ); return this.ƒ( "toBufferSync", pages.map(core), format, quality, density, outline, matte ); } toDataURL(extension = "png", opts = {}) { if (!this.async) return this.toDataURLSync(...arguments); // support while deprecated opts = typeof opts == "number" ? { quality: opts } : opts; let { mime } = io.options(this.pages, { extension, ...opts }), buffer = this.toBuffer(extension, opts); return buffer.then( data => `data:${mime};base64,${data.toString("base64")}` ); } toDataURLSync(extension = "png", opts = {}) { opts = typeof opts == "number" ? { quality: opts } : opts; let { mime } = io.options(this.pages, { extension, ...opts }), buffer = this.toBufferSync(extension, opts); return `data:${mime};base64,${buffer.toString("base64")}`; } [REPR](depth, options) { let { width, height, async, pages } = this; return `Canvas ${inspect({ width, height, async, pages }, options)}`; } } class CanvasGradient extends RustClass { constructor(style, ...coords) { super(CanvasGradient); style = (style || "").toLowerCase(); if (["linear", "radial", "conic"].includes(style)) this.init(style, ...coords); else throw new Error( `Function is not a constructor (use CanvasRenderingContext2D's "createConicGradient", "createLinearGradient", and "createRadialGradient" methods instead)` ); } addColorStop(offset, color) { if (offset >= 0 && offset <= 1) this.ƒ("addColorStop", offset, color); else throw new Error("Color stop offsets must be between 0.0 and 1.0"); } [REPR](depth, options) { return `CanvasGradient (${this.ƒ("repr")})`; } } class CanvasPattern extends RustClass { constructor(src, repeat) { super(CanvasPattern); if (src instanceof Image) { this.init("from_image", core(src), repeat); } else if (src instanceof Canvas) { let ctx = src.getContext("2d"); this.init("from_canvas", core(ctx), repeat); } else { throw new Error("CanvasPatterns require a source Image or a Canvas"); } } setTransform(matrix) { if (arguments.length > 1) matrix = [...arguments]; this.ƒ("setTransform", toSkMatrix(matrix)); } [REPR](depth, options) { return `CanvasPattern (${this.ƒ("repr")})`; } } class CanvasTexture extends RustClass { constructor(spacing, { path, line, color, angle, offset = 0 } = {}) { super(CanvasTexture); let [x, y] = typeof offset == "number" ? [offset, offset] : offset.slice(0, 2); let [h, v] = typeof spacing == "number" ? [spacing, spacing] : spacing.slice(0, 2); path = core(path); line = line != null ? line : path ? 0 : 1; angle = angle != null ? angle : path ? 0 : -Math.PI / 4; this.alloc(path, color, line, angle, h, v, x, y); } [REPR](depth, options) { return `CanvasTexture (${this.ƒ("repr")})`; } } class CanvasRenderingContext2D extends RustClass { constructor(canvas) { try { super(CanvasRenderingContext2D).alloc(canvas); } catch (e) { throw new TypeError( `Function is not a constructor (use Canvas's "getContext" method instead)` ); } } get canvas() { return Canvas.parent.get(this); } // -- grid state ------------------------------------------------------------ save() { this.ƒ("save"); } restore() { this.ƒ("restore"); } get currentTransform() { return fromSkMatrix(this.prop("currentTransform")); } set currentTransform(matrix) { this.prop("currentTransform", toSkMatrix(matrix)); } resetTransform() { this.ƒ("resetTransform"); } getTransform() { return this.currentTransform; } setTransform(matrix) { this.currentTransform = arguments.length > 1 ? [...arguments] : matrix; } transform(a, b, c, d, e, f) { this.ƒ("transform", ...arguments); } translate(x, y) { this.ƒ("translate", ...arguments); } scale(x, y) { this.ƒ("scale", ...arguments); } rotate(angle) { this.ƒ("rotate", ...arguments); } createProjection(quad, basis) { return fromSkMatrix( this.ƒ("createProjection", [quad].flat(), [basis].flat()) ); } // -- bézier paths ---------------------------------------------------------- beginPath() { this.ƒ("beginPath"); } rect(x, y, width, height) { this.ƒ("rect", ...arguments); } arc(x, y, radius, startAngle, endAngle, isCCW) { this.ƒ("arc", ...arguments); } ellipse(x, y, xRadius, yRadius, rotation, startAngle, endAngle, isCCW) { this.ƒ("ellipse", ...arguments); } moveTo(x, y) { this.ƒ("moveTo", ...arguments); } lineTo(x, y) { this.ƒ("lineTo", ...arguments); } arcTo(x1, y1, x2, y2, radius) { this.ƒ("arcTo", ...arguments); } bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) { this.ƒ("bezierCurveTo", ...arguments); } quadraticCurveTo(cpx, cpy, x, y) { this.ƒ("quadraticCurveTo", ...arguments); } conicCurveTo(cpx, cpy, x, y, weight) { this.ƒ("conicCurveTo", ...arguments); } closePath() { this.ƒ("closePath"); } isPointInPath(x, y) { return this.ƒ("isPointInPath", ...arguments); } isPointInStroke(x, y) { return this.ƒ("isPointInStroke", ...arguments); } // -- using paths ----------------------------------------------------------- fill(path, rule) { if (path instanceof Path2D) this.ƒ("fill", core(path), rule); else this.ƒ("fill", path); // 'path' is the optional winding-rule } stroke(path, rule) { if (path instanceof Path2D) this.ƒ("stroke", core(path), rule); else this.ƒ("stroke", path); // 'path' is the optional winding-rule } clip(path, rule) { if (path instanceof Path2D) this.ƒ("clip", core(path), rule); else this.ƒ("clip", path); // 'path' is the optional winding-rule } // -- shaders --------------------------------------------------------------- createPattern(image, repetition) { return new CanvasPattern(...arguments); } createLinearGradient(x0, y0, x1, y1) { return new CanvasGradient("Linear", ...arguments); } createRadialGradient(x0, y0, r0, x1, y1, r1) { return new CanvasGradient("Radial", ...arguments); } createConicGradient(startAngle, x, y) { return new CanvasGradient("Conic", ...arguments); } createTexture(spacing, options) { return new CanvasTexture(spacing, options); } // -- fill & stroke --------------------------------------------------------- fillRect(x, y, width, height) { this.ƒ("fillRect", ...arguments); } strokeRect(x, y, width, height) { this.ƒ("strokeRect", ...arguments); } clearRect(x, y, width, height) { this.ƒ("clearRect", ...arguments); } set fillStyle(style) { let isShader = style instanceof CanvasPattern || style instanceof CanvasGradient || style instanceof CanvasTexture, [ref, val] = isShader ? [style, core(style)] : [null, style]; this.ref("fill", ref); this.prop("fillStyle", val); } get fillStyle() { let style = this.prop("fillStyle"); return style === null ? this.ref("fill") : style; } set strokeStyle(style) { let isShader = style instanceof CanvasPattern || style instanceof CanvasGradient || style instanceof CanvasTexture, [ref, val] = isShader ? [style, core(style)] : [null, style]; this.ref("stroke", ref); this.prop("strokeStyle", val); } get strokeStyle() { let style = this.prop("strokeStyle"); return style === null ? this.ref("stroke") : style; } // -- line style ------------------------------------------------------------ getLineDash() { return this.ƒ("getLineDash"); } setLineDash(segments) { this.ƒ("setLineDash", segments); } get lineCap() { return this.prop("lineCap"); } set lineCap(style) { this.prop("lineCap", style); } get lineDashFit() { return this.prop("lineDashFit"); } set lineDashFit(style) { this.prop("lineDashFit", style); } get lineDashMarker() { return wrap(Path2D, this.prop("lineDashMarker")); } set lineDashMarker(path) { this.prop("lineDashMarker", path instanceof Path2D ? core(path) : path); } get lineDashOffset() { return this.prop("lineDashOffset"); } set lineDashOffset(offset) { this.prop("lineDashOffset", offset); } get lineJoin() { return this.prop("lineJoin"); } set lineJoin(style) { this.prop("lineJoin", style); } get lineWidth() { return this.prop("lineWidth"); } set lineWidth(width) { this.prop("lineWidth", width); } get miterLimit() { return this.prop("miterLimit"); } set miterLimit(limit) { this.prop("miterLimit", limit); } // -- imagery --------------------------------------------------------------- get imageSmoothingEnabled() { return this.prop("imageSmoothingEnabled"); } set imageSmoothingEnabled(flag) { this.prop("imageSmoothingEnabled", !!flag); } get imageSmoothingQuality() { return this.prop("imageSmoothingQuality"); } set imageSmoothingQuality(level) { this.prop("imageSmoothingQuality", level); } putImageData(imageData, ...coords) { this.ƒ("putImageData", imageData, ...coords); } createImageData(width, height) { return new ImageData(width, height); } getImageData(x, y, width, height) { let w = Math.floor(width), h = Math.floor(height), buffer = this.ƒ("getImageData", x, y, w, h); return new ImageData(buffer, w, h); } drawImage(image, ...coords) { if (image instanceof Canvas) { this.ƒ("drawImage", core(image.getContext("2d")), ...coords); } else if (image instanceof Image) { this.ƒ("drawImage", core(image), ...coords); } else { throw new Error("Expected an Image or a Canvas argument"); } } drawCanvas(image, ...coords) { if (image instanceof Canvas) { this.ƒ("drawCanvas", core(image.getContext("2d")), ...coords); } else { this.drawImage(image, ...coords); } } // -- typography ------------------------------------------------------------ get font() { return this.prop("font"); } set font(str) { this.prop("font", css.font(str)); } get textAlign() { return this.prop("textAlign"); } set textAlign(mode) { this.prop("textAlign", mode); } get textBaseline() { return this.prop("textBaseline"); } set textBaseline(mode) { this.prop("textBaseline", mode); } get direction() { return this.prop("direction"); } set direction(mode) { this.prop("direction", mode); } measureText(text, maxWidth) { text = this.textWrap ? text : text + "\u200b"; // include trailing whitespace by default let [metrics, ...lines] = this.ƒ("measureText", toString(text), maxWidth); return new TextMetrics(metrics, lines); } fillText(text, x, y, maxWidth) { this.ƒ("fillText", toString(text), x, y, maxWidth); } strokeText(text, x, y, maxWidth) { this.ƒ("strokeText", toString(text), x, y, maxWidth); } outlineText(text) { let path = this.ƒ("outlineText", toString(text)); return path ? wrap(Path2D, path) : null; } // -- non-standard typography extensions -------------------------------------------- get fontVariant() { return this.prop("fontVariant"); } set fontVariant(str) { this.prop("fontVariant", css.variant(str)); } get textTracking() { return this.prop("textTracking"); } set textTracking(ems) { this.prop("textTracking", ems); } get textWrap() { return this.prop("textWrap"); } set textWrap(flag) { this.prop("textWrap", !!flag); } // -- effects --------------------------------------------------------------- get globalCompositeOperation() { return this.prop("globalCompositeOperation"); } set globalCompositeOperation(blend) { this.prop("globalCompositeOperation", blend); } get globalAlpha() { return this.prop("globalAlpha"); } set globalAlpha(alpha) { this.prop("globalAlpha", alpha); } get shadowBlur() { return this.prop("shadowBlur"); } set shadowBlur(level) { this.prop("shadowBlur", level); } get shadowColor() { return this.prop("shadowColor"); } set shadowColor(color) { this.prop("shadowColor", color); } get shadowOffsetX() { return this.prop("shadowOffsetX"); } set shadowOffsetX(x) { this.prop("shadowOffsetX", x); } get shadowOffsetY() { return this.prop("shadowOffsetY"); } set shadowOffsetY(y) { this.prop("shadowOffsetY", y); } get filter() { return this.prop("filter"); } set filter(str) { this.prop("filter", css.filter(str)); } [REPR](depth, options) { let props = [ "canvas", "currentTransform", "fillStyle", "strokeStyle", "font", "fontVariant", "direction", "textAlign", "textBaseline", "textTracking", "textWrap", "globalAlpha", "globalCompositeOperation", "imageSmoothingEnabled", "imageSmoothingQuality", "filter", "shadowBlur", "shadowColor", "shadowOffsetX", "shadowOffsetY", "lineCap", "lineDashOffset", "lineJoin", "lineWidth", "miterLimit" ]; let info = {}; if (depth > 0) { for (var prop of props) { try { info[prop] = this[prop]; } catch { info[prop] = undefined; } } } return `CanvasRenderingContext2D ${inspect(info, options)}`; } } const _expand = paths => [paths] .flat(2) .map(pth => (hasMagic(pth) ? glob(pth) : pth)) .flat(); class FontLibrary extends RustClass { constructor() { super(FontLibrary); } get families() { return this.prop("families"); } has(familyName) { return this.ƒ("has", familyName); } family(name) { return this.ƒ("family", name); } use(...args) { let sig = signature(args); if (sig == "o") { let results = {}; for (let [alias, paths] of Object.entries(args.shift())) { results[alias] = this.ƒ("addFamily", alias, _expand(paths)); } return results; } else if (sig.match(/^s?[as]$/)) { let fonts = _expand(args.pop()); let alias = args.shift(); return this.ƒ("addFamily", alias, fonts); } else { throw new Error( "Expected an array of file paths or an object mapping family names to font files" ); } } } class Image extends RustClass { constructor() { super(Image).alloc(); } get complete() { return this.prop("complete"); } get height() { return this.prop("height"); } get width() { return this.prop("width"); } get src() { return this.prop("src"); } set src(src) { var noop = () => {}, onload = img => fetch.emit("ok", img), onerror = err => fetch.emit("err", err), passthrough = fn => arg => { (fn || noop)(arg); delete this._fetch; }, data; if (this._fetch) this._fetch.removeAllListeners(); let fetch = (this._fetch = new EventEmitter() .once("ok", passthrough(this.onload)) .once("err", passthrough(this.onerror))); if (Buffer.isBuffer(src)) { [data, src] = [src, ""]; } else if (typeof src != "string") { return; } else if (/^\s*data:/.test(src)) { // data URI let split = src.indexOf(","), enc = src.lastIndexOf("base64", split) !== -1 ? "base64" : "utf8", content = src.slice(split + 1); data = Buffer.from(content, enc); } else if (/^\s*https?:\/\//.test(src)) { // remote URL get.concat(src, (err, res, data) => { let code = (res || {}).statusCode; if (err) onerror(err); else if (code < 200 || code >= 300) { onerror( new Error(`Failed to load image from "${src}" (error ${code})`) ); } else { if (this.prop("data", data)) onload(this); else onerror(new Error("Could not decode image data")); } }); } else { // local file path data = fs.readFileSync(src); } this.prop("src", src); if (data) { if (this.prop("data", data)) onload(this); else onerror(new Error("Could not decode image data")); } } decode() { return this._fetch ? new Promise((res, rej) => this._fetch.once("ok", res).once("err", rej)) : this.complete ? Promise.resolve(this) : Promise.reject(new Error("Missing Source URL")); } [REPR](depth, options) { let { width, height, complete, src } = this; options.maxStringLength = src.match(/^data:/) ? 128 : Infinity; return `Image ${inspect({ width, height, complete, src }, options)}`; } } class ImageData { constructor(...args) { if (args[0] instanceof ImageData) { var { data, width, height } = args[0]; } else if ( args[0] instanceof Uint8ClampedArray || args[0] instanceof Buffer ) { var [data, width, height] = args; height = height || data.length / width / 4; if (data.length / 4 != width * height) { throw new Error("ImageData dimensions must match buffer length"); } } else { var [width, height] = args; } if ( !Number.isInteger(width) || !Number.isInteger(height) || width < 0 || height < 0 ) { throw new Error("ImageData dimensions must be positive integers"); } readOnly(this, "width", width); readOnly(this, "height", height); readOnly( this, "data", new Uint8ClampedArray((data && data.buffer) || width * height * 4) ); } [REPR](depth, options) { let { width, height, data } = this; return `ImageData ${inspect({ width, height, data }, options)}`; } } class Path2D extends RustClass { static op(operation, path, other) { return wrap(Path2D, path.ƒ("op", core(other), operation)); } static interpolate(path, other, weight) { return wrap(Path2D, path.ƒ("interpolate", core(other), weight)); } static effect(effect, path, ...args) { return wrap(Path2D, path.ƒ(effect, ...args)); } constructor(source) { super(Path2D); if (source instanceof Path2D) this.init("from_path", core(source)); else if (typeof source == "string") this.init("from_svg", source); else this.alloc(); } // dimensions & contents get bounds() { return this.ƒ("bounds"); } get edges() { return this.ƒ("edges"); } get d() { return this.prop("d"); } set d(svg) { return this.prop("d", svg); } contains(x, y) { return this.ƒ("contains", x, y); } points(step = 1) { return this.jitter(step, 0) .edges.map(([verb, ...pts]) => pts.slice(-2)) .filter(pt => pt.length); } // concatenation addPath(path, matrix) { if (!(path instanceof Path2D)) throw new Error("Expected a Path2D object"); if (matrix) matrix = toSkMatrix(matrix); this.ƒ("addPath", core(path), matrix); } // line segments moveTo(x, y) { this.ƒ("moveTo", ...arguments); } lineTo(x, y) { this.ƒ("lineTo", ...arguments); } closePath() { this.ƒ("closePath"); } arcTo(x1, y1, x2, y2, radius) { this.ƒ("arcTo", ...arguments); } bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) { this.ƒ("bezierCurveTo", ...arguments); } quadraticCurveTo(cpx, cpy, x, y) { this.ƒ("quadraticCurveTo", ...arguments); } conicCurveTo(cpx, cpy, x, y, weight) { this.ƒ("conicCurveTo", ...arguments); } // shape primitives ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, isCCW) { this.ƒ("ellipse", ...arguments); } rect(x, y, width, height) { this.ƒ("rect", ...arguments); } arc(x, y, radius, startAngle, endAngle) { this.ƒ("arc", ...arguments); } // tween similar paths interpolate(path, weight) { return Path2D.interpolate(this, path, weight); } // boolean operations complement(path) { return Path2D.op("complement", this, path); } difference(path) { return Path2D.op("difference", this, path); } intersect(path) { return Path2D.op("intersect", this, path); } union(path) { return Path2D.op("union", this, path); } xor(path) { return Path2D.op("xor", this, path); } // path effects jitter(len, amt, seed) { return Path2D.effect("jitter", this, ...arguments); } simplify(rule) { return Path2D.effect("simplify", this, rule); } unwind() { return Path2D.effect("unwind", this); } round(radius) { return Path2D.effect("round", this, radius); } offset(dx, dy) { return Path2D.effect("offset", this, dx, dy); } transform(matrix) { let terms = arguments.length > 1 ? [...arguments] : matrix; return Path2D.effect("transform", this, toSkMatrix(terms)); } trim(...rng) { if (typeof rng[1] != "number") { if (rng[0] > 0) rng.unshift(0); else if (rng[0] < 0) rng.splice(1, 0, 1); } if (rng[0] < 0) rng[0] = Math.max(-1, rng[0]) + 1; if (rng[1] < 0) rng[1] = Math.max(-1, rng[1]) + 1; return Path2D.effect("trim", this, ...rng); } [REPR](depth, options) { let { d, bounds, edges } = this; return `Path2D ${inspect({ d, bounds, edges }, options)}`; } } class TextMetrics { constructor( [ width, left, right, ascent, descent, fontAscent, fontDescent, emAscent, emDescent, hanging, alphabetic, ideographic ], lines ) { readOnly(this, "width", width); readOnly(this, "actualBoundingBoxLeft", left); readOnly(this, "actualBoundingBoxRight", right); readOnly(this, "actualBoundingBoxAscent", ascent); readOnly(this, "actualBoundingBoxDescent", descent); readOnly(this, "fontBoundingBoxAscent", fontAscent); readOnly(this, "fontBoundingBoxDescent", fontDescent); readOnly(this, "emHeightAscent", emAscent); readOnly(this, "emHeightDescent", emDescent); readOnly(this, "hangingBaseline", hanging); readOnly(this, "alphabeticBaseline", alphabetic); readOnly(this, "ideographicBaseline", ideographic); readOnly( this, "lines", lines.map(([x, y, width, height, baseline, startIndex, endIndex]) => ({ x, y, width, height, baseline, startIndex, endIndex })) ); } } const loadImage = src => Object.assign(new Image(), { src }).decode(); // module.exports = { // Canvas, // CanvasGradient, // CanvasPattern, // CanvasRenderingContext2D, // CanvasTexture, // TextMetrics, // Image, // ImageData, // Path2D, // loadImage, // ...geometry, // FontLibrary: new FontLibrary() // }; const obj = { Canvas, CanvasGradient, CanvasPattern, CanvasRenderingContext2D, CanvasTexture, TextMetrics, Image, ImageData, Path2D, loadImage, ...geometry, FontLibrary: new FontLibrary() }; export default obj;