From 7f59355b0a008750a247195aeb5e8b4b87927b61 Mon Sep 17 00:00:00 2001 From: k1w1 Date: Mon, 22 Aug 2022 16:53:26 -0700 Subject: [PATCH 01/12] Support independent paths --- README.md | 15 ++ context.js | 420 +++++++++-------------------------------- index.js | 4 +- path2d.js | 341 +++++++++++++++++++++++++++++++++ test/index.js | 4 +- test/rendering.test.js | 4 +- test/tests/path2D.js | 18 ++ utils.js | 12 +- 8 files changed, 485 insertions(+), 333 deletions(-) create mode 100644 path2d.js create mode 100644 test/tests/path2D.js diff --git a/README.md b/README.md index c8e82d2..b26448c 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,21 @@ dom.wrapper; // a div with the svg as a child dom.svg; // the inline svg element ``` +Working with paths directly: + +```javascript +import { Context, Path2D } from "svgcanvas"; + +const ctx = new Context(500, 500); + +// Create a path: +const path = new Path2D(ctx, "M 230 80 L 275 80 Z"); // or ctx.createPath("M 230 80 L 275 80 Z"); +ctx.stroke(path); + +// serialize your SVG +const mySerializedSVG = ctx.getSerializedSvg(); +``` + ## Tests https://zenozeng.github.io/p5.js-svg/test/ diff --git a/context.js b/context.js index 8b22aca..e512322 100644 --- a/context.js +++ b/context.js @@ -14,22 +14,15 @@ */ import * as utils from './utils'; +import { format } from './utils'; import imageUtils from './image'; +import Path2D from './path2d'; export default (function () { "use strict"; var STYLES, Context, CanvasGradient, CanvasPattern, namedEntities; - //helper function to format a string - function format(str, args) { - var keys = Object.keys(args), i; - for (i=0; i -1) { - this.__addPathCommand(format("L {x} {y}", { - x: this.__matrixTransform(x, y).x, - y: this.__matrixTransform(x, y).y - })); - } else { - this.__addPathCommand(format("M {x} {y}", { - x: this.__matrixTransform(x, y).x, - y: this.__matrixTransform(x, y).y - })); + Context.prototype.rect = function (x, y, width, height) { + if (!this.__currentDefaultPath) { + this.beginPath(); } - }; + this.__currentDefaultPath.rect(x, y, width, height); + } - /** - * Add a bezier command - */ Context.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { - this.__currentPosition = {x: x, y: y}; - this.__addPathCommand(format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", - { - cp1x: this.__matrixTransform(cp1x, cp1y).x, - cp1y: this.__matrixTransform(cp1x, cp1y).y, - cp2x: this.__matrixTransform(cp2x, cp2y).x, - cp2y: this.__matrixTransform(cp2x, cp2y).y, - x: this.__matrixTransform(x, y).x, - y: this.__matrixTransform(x, y).y - })); + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); }; - /** - * Adds a quadratic curve to command - */ Context.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { - this.__currentPosition = {x: x, y: y}; - this.__addPathCommand(format("Q {cpx} {cpy} {x} {y}", { - cpx: this.__matrixTransform(cpx, cpy).x, - cpy: this.__matrixTransform(cpx, cpy).y, - x: this.__matrixTransform(x, y).x, - y: this.__matrixTransform(x, y).y - })); + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.quadraticCurveTo(cpx, cpy, x, y); }; - - /** - * Return a new normalized vector of given vector - */ - var normalize = function (vector) { - var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); - return [vector[0] / len, vector[1] / len]; + Context.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.arc(x, y, radius, startAngle, endAngle, counterClockwise); }; - /** - * Adds the arcTo to the current path - * - * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto - */ Context.prototype.arcTo = function (x1, y1, x2, y2, radius) { - // Let the point (x0, y0) be the last point in the subpath. - var x0 = this.__currentPosition && this.__currentPosition.x; - var y0 = this.__currentPosition && this.__currentPosition.y; - - // First ensure there is a subpath for (x1, y1). - if (typeof x0 == "undefined" || typeof y0 == "undefined") { - return; - } - - // Negative values for radius must cause the implementation to throw an IndexSizeError exception. - if (radius < 0) { - throw new Error("IndexSizeError: The radius provided (" + radius + ") is negative."); - } - - // If the point (x0, y0) is equal to the point (x1, y1), - // or if the point (x1, y1) is equal to the point (x2, y2), - // or if the radius radius is zero, - // then the method must add the point (x1, y1) to the subpath, - // and connect that point to the previous point (x0, y0) by a straight line. - if (((x0 === x1) && (y0 === y1)) - || ((x1 === x2) && (y1 === y2)) - || (radius === 0)) { - this.lineTo(x1, y1); - return; + if (!this.__currentDefaultPath) { + this.beginPath(); } + this.__currentDefaultPath.arcTo(x1, y1, x2, y2, radius); + }; - // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line, - // then the method must add the point (x1, y1) to the subpath, - // and connect that point to the previous point (x0, y0) by a straight line. - var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); - var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); - if (unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === unit_vec_p1_p0[1] * unit_vec_p1_p2[0]) { - this.lineTo(x1, y1); - return; + Context.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterClockwise) { + if (!this.__currentDefaultPath) { + this.beginPath(); } - - // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius, - // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1), - // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2). - // The points at which this circle touches these two lines are called the start and end tangent points respectively. - - // note that both vectors are unit vectors, so the length is 1 - var cos = (unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1]); - var theta = Math.acos(Math.abs(cos)); - - // Calculate origin - var unit_vec_p1_origin = normalize([ - unit_vec_p1_p0[0] + unit_vec_p1_p2[0], - unit_vec_p1_p0[1] + unit_vec_p1_p2[1] - ]); - var len_p1_origin = radius / Math.sin(theta / 2); - var x = x1 + len_p1_origin * unit_vec_p1_origin[0]; - var y = y1 + len_p1_origin * unit_vec_p1_origin[1]; - - // Calculate start angle and end angle - // rotate 90deg clockwise (note that y axis points to its down) - var unit_vec_origin_start_tangent = [ - -unit_vec_p1_p0[1], - unit_vec_p1_p0[0] - ]; - // rotate 90deg counter clockwise (note that y axis points to its down) - var unit_vec_origin_end_tangent = [ - unit_vec_p1_p2[1], - -unit_vec_p1_p2[0] - ]; - var getAngle = function (vector) { - // get angle (clockwise) between vector and (1, 0) - var x = vector[0]; - var y = vector[1]; - if (y >= 0) { // note that y axis points to its down - return Math.acos(x); - } else { - return -Math.acos(x); - } - }; - var startAngle = getAngle(unit_vec_origin_start_tangent); - var endAngle = getAngle(unit_vec_origin_end_tangent); - - // Connect the point (x0, y0) to the start tangent point by a straight line - this.lineTo(x + unit_vec_origin_start_tangent[0] * radius, - y + unit_vec_origin_start_tangent[1] * radius); - - // Connect the start tangent point to the end tangent point by arc - // and adding the end tangent point to the subpath. - this.arc(x, y, radius, startAngle, endAngle); + this.__currentDefaultPath.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterClockwise); }; /** - * Sets the stroke property on the current element + * Helper function to apply currentDefaultPath to current path element + * @private */ - Context.prototype.stroke = function () { - if (this.__currentElement.nodeName === "path") { - this.__currentElement.setAttribute("paint-order", "fill stroke markers"); + Context.prototype.__applyCurrentDefaultPath = function (path) { + var currentElement = this.__currentElement; + if (currentElement.nodeName === "path") { + currentElement.setAttribute("d", this.__currentDefaultPath.__pathString); + } else { + console.error("Attempted to apply path command to node", currentElement.nodeName); } - this.__applyCurrentDefaultPath(); - this.__applyStyleToCurrentElement("stroke"); }; /** - * Sets fill properties on the current element + * Sets the stroke property on the current element */ - Context.prototype.fill = function () { - if (this.__currentElement.nodeName === "path") { - this.__currentElement.setAttribute("paint-order", "stroke fill markers"); + Context.prototype.stroke = function (path2d) { + if (path2d) { + var path = this.__createPathElement(); + this.__applyStyleToElement(path, "stroke"); + path.setAttribute("paint-order", "fill stroke markers"); + path.setAttribute("d", path2d.__pathString); + } else { + if (this.__currentElement.nodeName === "path") { + this.__currentElement.setAttribute("paint-order", "fill stroke markers"); + } + this.__applyCurrentDefaultPath(); + this.__applyStyleToElement(this.__currentElement, "stroke"); } - this.__applyCurrentDefaultPath(); - this.__applyStyleToCurrentElement("fill"); }; /** - * Adds a rectangle to the path. + * Sets fill properties on the current element */ - Context.prototype.rect = function (x, y, width, height) { - if (this.__currentElement.nodeName !== "path") { - this.beginPath(); + Context.prototype.fill = function (path2d) { + if (path2d) { + var path = this.__createPathElement(); + this.__applyStyleToElement(path, "fill"); + path.setAttribute("paint-order", "fill stroke markers"); + path.setAttribute("d", path2d.__pathString); + } else { + if (this.__currentElement.nodeName === "path") { + this.__currentElement.setAttribute("paint-order", "stroke fill markers"); + } + this.__applyCurrentDefaultPath(); + this.__applyStyleToElement(this.__currentElement, "fill"); } - this.moveTo(x, y); - this.lineTo(x+width, y); - this.lineTo(x+width, y+height); - this.lineTo(x, y+height); - this.lineTo(x, y); - this.closePath(); }; - /** * adds a rectangle element */ @@ -811,7 +687,7 @@ export default (function () { parent.appendChild(rect); this.__currentElement = rect; this.__applyTransformation(rect); - this.__applyStyleToCurrentElement("fill"); + this.__applyStyleToElement(this.__currentElement, "fill"); }; /** @@ -833,7 +709,7 @@ export default (function () { parent.appendChild(rect); this.__currentElement = rect; this.__applyTransformation(rect); - this.__applyStyleToCurrentElement("stroke"); + this.__applyStyleToElement(this.__currentElement, "stroke"); }; @@ -942,7 +818,7 @@ export default (function () { textElement.appendChild(this.__document.createTextNode(text)); this.__currentElement = textElement; this.__applyTransformation(textElement); - this.__applyStyleToCurrentElement(action); + this.__applyStyleToElement(this.__currentElement, action); if (this.__fontHref) { var a = this.__createElement("a"); @@ -985,120 +861,6 @@ export default (function () { return this.__ctx.measureText(text); }; - /** - * Arc command! - */ - Context.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) { - // in canvas no circle is drawn if no angle is provided. - if (startAngle === endAngle) { - return; - } - startAngle = startAngle % (2*Math.PI); - endAngle = endAngle % (2*Math.PI); - if (startAngle === endAngle) { - //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle) - endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI); - } - var endX = x+radius*Math.cos(endAngle), - endY = y+radius*Math.sin(endAngle), - startX = x+radius*Math.cos(startAngle), - startY = y+radius*Math.sin(startAngle), - sweepFlag = counterClockwise ? 0 : 1, - largeArcFlag = 0, - diff = endAngle - startAngle; - - // https://github.com/gliffy/canvas2svg/issues/4 - if (diff < 0) { - diff += 2*Math.PI; - } - - if (counterClockwise) { - largeArcFlag = diff > Math.PI ? 0 : 1; - } else { - largeArcFlag = diff > Math.PI ? 1 : 0; - } - - var scaleX = Math.hypot(this.__transformMatrix.a, this.__transformMatrix.b); - var scaleY = Math.hypot(this.__transformMatrix.c, this.__transformMatrix.d); - - this.lineTo(startX, startY); - this.__addPathCommand(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", - { - rx:radius * scaleX, - ry:radius * scaleY, - xAxisRotation:0, - largeArcFlag:largeArcFlag, - sweepFlag:sweepFlag, - endX: this.__matrixTransform(endX, endY).x, - endY: this.__matrixTransform(endX, endY).y - })); - - this.__currentPosition = {x: endX, y: endY}; - }; - - /** - * Ellipse command! - */ - Context.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterClockwise) { - if (startAngle === endAngle) { - return; - } - - var transformedCenter = this.__matrixTransform(x, y); - x = transformedCenter.x; - y = transformedCenter.y; - var scale = this.__getTransformScale(); - radiusX = radiusX * scale.x; - radiusY = radiusY * scale.y; - rotation = rotation + this.__getTransformRotation() - - startAngle = startAngle % (2*Math.PI); - endAngle = endAngle % (2*Math.PI); - if(startAngle === endAngle) { - endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI); - } - var endX = x + Math.cos(-rotation) * radiusX * Math.cos(endAngle) - + Math.sin(-rotation) * radiusY * Math.sin(endAngle), - endY = y - Math.sin(-rotation) * radiusX * Math.cos(endAngle) - + Math.cos(-rotation) * radiusY * Math.sin(endAngle), - startX = x + Math.cos(-rotation) * radiusX * Math.cos(startAngle) - + Math.sin(-rotation) * radiusY * Math.sin(startAngle), - startY = y - Math.sin(-rotation) * radiusX * Math.cos(startAngle) - + Math.cos(-rotation) * radiusY * Math.sin(startAngle), - sweepFlag = counterClockwise ? 0 : 1, - largeArcFlag = 0, - diff = endAngle - startAngle; - - if(diff < 0) { - diff += 2*Math.PI; - } - - if(counterClockwise) { - largeArcFlag = diff > Math.PI ? 0 : 1; - } else { - largeArcFlag = diff > Math.PI ? 1 : 0; - } - - // Transform is already applied, so temporarily remove since lineTo - // will apply it again. - var currentTransform = this.__transformMatrix; - this.resetTransform(); - this.lineTo(startX, startY); - this.__transformMatrix = currentTransform; - - this.__addPathCommand(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", - { - rx:radiusX, - ry:radiusY, - xAxisRotation:rotation*(180/Math.PI), - largeArcFlag:largeArcFlag, - sweepFlag:sweepFlag, - endX:endX, - endY:endY - })); - - this.__currentPosition = {x: endX, y: endY}; - }; /** * Generates a ClipPath from the clip command. diff --git a/index.js b/index.js index b945055..81ded5c 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,7 @@ import Context from './context'; import Element from './element'; +import Path2D from './path2d'; export {Context}; -export {Element}; \ No newline at end of file +export {Element}; +export {Path2D}; \ No newline at end of file diff --git a/path2d.js b/path2d.js new file mode 100644 index 0000000..a59e018 --- /dev/null +++ b/path2d.js @@ -0,0 +1,341 @@ +import { format } from './utils'; + +export default (function () { + "use strict"; + + var Path2D; + + Path2D = function (ctx, arg) { + if (!ctx) { + console.error("Path2D must be passed the context"); + } + if (typeof arg === 'string') { + // Initialize from string path. + this.__pathString = arg; + } else if (typeof arg === 'object') { + // Initialize by copying another path. + this.__pathString = arg.__pathString; + } else { + // Initialize a new path. + this.__pathString = ""; + } + + this.ctx = ctx; + this.__currentPosition = {x: undefined, y: undefined}; + } + + Path2D.prototype.__matrixTransform = function(x, y) { + return this.ctx.__matrixTransform(x, y); + } + + Path2D.prototype.addPath = function(path, transform) { + if (transform) console.error("transform argument to addPath is not supported"); + + this.__pathString = this.__pathString + " " + path; + } + + /** + * Closes the current path + */ + Path2D.prototype.closePath = function () { + this.addPath("Z"); + }; + + /** + * Adds the move command to the current path element, + * if the currentPathElement is not empty create a new path element + */ + Path2D.prototype.moveTo = function (x,y) { + // creates a new subpath with the given point + this.__currentPosition = {x: x, y: y}; + this.addPath(format("M {x} {y}", { + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + }; + + + /** + * Adds a line to command + */ + Path2D.prototype.lineTo = function (x, y) { + this.__currentPosition = {x: x, y: y}; + if (this.__pathString.indexOf('M') > -1) { + this.addPath(format("L {x} {y}", { + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + } else { + this.addPath(format("M {x} {y}", { + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + } + }; + + /** + * Adds a rectangle to the path. + */ + Path2D.prototype.rect = function (x, y, width, height) { + if (this.__currentElement.nodeName !== "path") { + this.beginPath(); + } + this.moveTo(x, y); + this.lineTo(x+width, y); + this.lineTo(x+width, y+height); + this.lineTo(x, y+height); + this.lineTo(x, y); + this.closePath(); + }; + + /** + * Add a bezier command + */ + Path2D.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { + this.__currentPosition = {x: x, y: y}; + this.addPath(format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", + { + cp1x: this.__matrixTransform(cp1x, cp1y).x, + cp1y: this.__matrixTransform(cp1x, cp1y).y, + cp2x: this.__matrixTransform(cp2x, cp2y).x, + cp2y: this.__matrixTransform(cp2x, cp2y).y, + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + }; + + /** + * Adds a quadratic curve to command + */ + Path2D.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { + this.__currentPosition = {x: x, y: y}; + this.addPath(format("Q {cpx} {cpy} {x} {y}", { + cpx: this.__matrixTransform(cpx, cpy).x, + cpy: this.__matrixTransform(cpx, cpy).y, + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y + })); + }; + + + + /** + * Arc command! + */ + Path2D.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) { + // in canvas no circle is drawn if no angle is provided. + if (startAngle === endAngle) { + return; + } + startAngle = startAngle % (2*Math.PI); + endAngle = endAngle % (2*Math.PI); + if (startAngle === endAngle) { + //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle) + endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI); + } + var endX = x+radius*Math.cos(endAngle), + endY = y+radius*Math.sin(endAngle), + startX = x+radius*Math.cos(startAngle), + startY = y+radius*Math.sin(startAngle), + sweepFlag = counterClockwise ? 0 : 1, + largeArcFlag = 0, + diff = endAngle - startAngle; + + // https://github.com/gliffy/canvas2svg/issues/4 + if (diff < 0) { + diff += 2*Math.PI; + } + + if (counterClockwise) { + largeArcFlag = diff > Math.PI ? 0 : 1; + } else { + largeArcFlag = diff > Math.PI ? 1 : 0; + } + + var scaleX = Math.hypot(this.ctx.__transformMatrix.a, this.ctx.__transformMatrix.b); + var scaleY = Math.hypot(this.ctx.__transformMatrix.c, this.ctx.__transformMatrix.d); + + this.lineTo(startX, startY); + this.addPath(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", + { + rx:radius * scaleX, + ry:radius * scaleY, + xAxisRotation:0, + largeArcFlag:largeArcFlag, + sweepFlag:sweepFlag, + endX: this.__matrixTransform(endX, endY).x, + endY: this.__matrixTransform(endX, endY).y + })); + + this.__currentPosition = {x: endX, y: endY}; + }; + + + /** + * Return a new normalized vector of given vector + */ + var normalize = function (vector) { + var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); + return [vector[0] / len, vector[1] / len]; + }; + + /** + * Adds the arcTo to the current path + * + * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto + */ + Path2D.prototype.arcTo = function (x1, y1, x2, y2, radius) { + // Let the point (x0, y0) be the last point in the subpath. + var x0 = this.__currentPosition && this.__currentPosition.x; + var y0 = this.__currentPosition && this.__currentPosition.y; + + // First ensure there is a subpath for (x1, y1). + if (typeof x0 == "undefined" || typeof y0 == "undefined") { + return; + } + + // Negative values for radius must cause the implementation to throw an IndexSizeError exception. + if (radius < 0) { + throw new Error("IndexSizeError: The radius provided (" + radius + ") is negative."); + } + + // If the point (x0, y0) is equal to the point (x1, y1), + // or if the point (x1, y1) is equal to the point (x2, y2), + // or if the radius radius is zero, + // then the method must add the point (x1, y1) to the subpath, + // and connect that point to the previous point (x0, y0) by a straight line. + if (((x0 === x1) && (y0 === y1)) + || ((x1 === x2) && (y1 === y2)) + || (radius === 0)) { + this.lineTo(x1, y1); + return; + } + + // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line, + // then the method must add the point (x1, y1) to the subpath, + // and connect that point to the previous point (x0, y0) by a straight line. + var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); + var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); + if (unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === unit_vec_p1_p0[1] * unit_vec_p1_p2[0]) { + this.lineTo(x1, y1); + return; + } + + // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius, + // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1), + // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2). + // The points at which this circle touches these two lines are called the start and end tangent points respectively. + + // note that both vectors are unit vectors, so the length is 1 + var cos = (unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1]); + var theta = Math.acos(Math.abs(cos)); + + // Calculate origin + var unit_vec_p1_origin = normalize([ + unit_vec_p1_p0[0] + unit_vec_p1_p2[0], + unit_vec_p1_p0[1] + unit_vec_p1_p2[1] + ]); + var len_p1_origin = radius / Math.sin(theta / 2); + var x = x1 + len_p1_origin * unit_vec_p1_origin[0]; + var y = y1 + len_p1_origin * unit_vec_p1_origin[1]; + + // Calculate start angle and end angle + // rotate 90deg clockwise (note that y axis points to its down) + var unit_vec_origin_start_tangent = [ + -unit_vec_p1_p0[1], + unit_vec_p1_p0[0] + ]; + // rotate 90deg counter clockwise (note that y axis points to its down) + var unit_vec_origin_end_tangent = [ + unit_vec_p1_p2[1], + -unit_vec_p1_p2[0] + ]; + var getAngle = function (vector) { + // get angle (clockwise) between vector and (1, 0) + var x = vector[0]; + var y = vector[1]; + if (y >= 0) { // note that y axis points to its down + return Math.acos(x); + } else { + return -Math.acos(x); + } + }; + var startAngle = getAngle(unit_vec_origin_start_tangent); + var endAngle = getAngle(unit_vec_origin_end_tangent); + + // Connect the point (x0, y0) to the start tangent point by a straight line + this.lineTo(x + unit_vec_origin_start_tangent[0] * radius, + y + unit_vec_origin_start_tangent[1] * radius); + + // Connect the start tangent point to the end tangent point by arc + // and adding the end tangent point to the subpath. + this.arc(x, y, radius, startAngle, endAngle); + }; + + + /** + * Ellipse command! + */ + Path2D.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterClockwise) { + if (startAngle === endAngle) { + return; + } + + var transformedCenter = this.__matrixTransform(x, y); + x = transformedCenter.x; + y = transformedCenter.y; + var scale = this.ctx.__getTransformScale(); + radiusX = radiusX * scale.x; + radiusY = radiusY * scale.y; + rotation = rotation + this.ctx.__getTransformRotation() + + startAngle = startAngle % (2*Math.PI); + endAngle = endAngle % (2*Math.PI); + if(startAngle === endAngle) { + endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI); + } + var endX = x + Math.cos(-rotation) * radiusX * Math.cos(endAngle) + + Math.sin(-rotation) * radiusY * Math.sin(endAngle), + endY = y - Math.sin(-rotation) * radiusX * Math.cos(endAngle) + + Math.cos(-rotation) * radiusY * Math.sin(endAngle), + startX = x + Math.cos(-rotation) * radiusX * Math.cos(startAngle) + + Math.sin(-rotation) * radiusY * Math.sin(startAngle), + startY = y - Math.sin(-rotation) * radiusX * Math.cos(startAngle) + + Math.cos(-rotation) * radiusY * Math.sin(startAngle), + sweepFlag = counterClockwise ? 0 : 1, + largeArcFlag = 0, + diff = endAngle - startAngle; + + if(diff < 0) { + diff += 2*Math.PI; + } + + if(counterClockwise) { + largeArcFlag = diff > Math.PI ? 0 : 1; + } else { + largeArcFlag = diff > Math.PI ? 1 : 0; + } + + // Transform is already applied, so temporarily remove since lineTo + // will apply it again. + var currentTransform = this.ctx.__transformMatrix; + this.ctx.resetTransform(); + this.lineTo(startX, startY); + this.ctx.__transformMatrix = currentTransform; + + this.addPath(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", + { + rx:radiusX, + ry:radiusY, + xAxisRotation:rotation*(180/Math.PI), + largeArcFlag:largeArcFlag, + sweepFlag:sweepFlag, + endX:endX, + endY:endY + })); + + this.__currentPosition = {x: endX, y: endY}; + }; + + return Path2D; +}()); diff --git a/test/index.js b/test/index.js index a00bf38..db8695f 100644 --- a/test/index.js +++ b/test/index.js @@ -20,6 +20,7 @@ import text from './tests/text' import tiger from './tests/tiger' import transform from './tests/transform' import pattern from "./tests/pattern"; +import path2D from './tests/path2D'; const tests = [ tiger, @@ -42,7 +43,8 @@ const tests = [ setLineDash, text, transform, - pattern + pattern, + path2D ]; for (let fn of tests) { diff --git a/test/rendering.test.js b/test/rendering.test.js index 4d7e0ef..e1e83b3 100644 --- a/test/rendering.test.js +++ b/test/rendering.test.js @@ -21,6 +21,7 @@ import text from './tests/text' import tiger from './tests/tiger' import transform from './tests/transform' import pattern from "./tests/pattern"; +import path2D from './tests/path2D'; const tests = { tiger, @@ -43,7 +44,8 @@ const tests = { setLineDash, text, transform, - pattern + pattern, + path2D }; const config = { diff --git a/test/tests/path2D.js b/test/tests/path2D.js new file mode 100644 index 0000000..c93a1c5 --- /dev/null +++ b/test/tests/path2D.js @@ -0,0 +1,18 @@ +function makePath(ctx, arg) { + if (ctx.createPath) { + return ctx.createPath(arg); + } else { + return new Path2D(arg); + } +} + +export default function path2D(ctx) { + const path1 = makePath(ctx, `M 230 80 + A 45 45, 0, 1, 0, 275 125 + L 275 80 Z`); + + ctx.strokeStyle = 'red'; + ctx.stroke(path1); + ctx.fillStyle = 'grey'; + ctx.fill(path1); +}; \ No newline at end of file diff --git a/utils.js b/utils.js index 4384a5c..6363b09 100644 --- a/utils.js +++ b/utils.js @@ -16,4 +16,14 @@ function debug(...data) { } } -export {toString, debug}; \ No newline at end of file + +//helper function to format a string +function format(str, args) { + var keys = Object.keys(args), i; + for (i=0; i Date: Sun, 28 Aug 2022 10:19:31 -0700 Subject: [PATCH 02/12] Test for scale/translate/rotate. Currently failing --- test/tests/path2D.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/tests/path2D.js b/test/tests/path2D.js index c93a1c5..491d726 100644 --- a/test/tests/path2D.js +++ b/test/tests/path2D.js @@ -15,4 +15,15 @@ export default function path2D(ctx) { ctx.stroke(path1); ctx.fillStyle = 'grey'; ctx.fill(path1); -}; \ No newline at end of file + + ctx.translate(10, 25); + ctx.lineWidth = 10; + ctx.stroke(path1); + + ctx.rotate(Math.PI / 4); + ctx.scale(1.5, 1.5); + ctx.translate(10, 25); + + ctx.strokeStyle = 'blue'; + ctx.stroke(path1); + }; \ No newline at end of file From f43ff8ba15c2c9a7d674d1ecfda0ea420abb6c7e Mon Sep 17 00:00:00 2001 From: k1w1 Date: Mon, 5 Sep 2022 12:35:47 -0700 Subject: [PATCH 03/12] Apply transform to paths --- context.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/context.js b/context.js index e512322..c003d7a 100644 --- a/context.js +++ b/context.js @@ -633,35 +633,32 @@ export default (function () { * Sets the stroke property on the current element */ Context.prototype.stroke = function (path2d) { - if (path2d) { - var path = this.__createPathElement(); - this.__applyStyleToElement(path, "stroke"); - path.setAttribute("paint-order", "fill stroke markers"); - path.setAttribute("d", path2d.__pathString); - } else { - if (this.__currentElement.nodeName === "path") { - this.__currentElement.setAttribute("paint-order", "fill stroke markers"); - } - this.__applyCurrentDefaultPath(); - this.__applyStyleToElement(this.__currentElement, "stroke"); - } + this.__strokeOrFill(path2d, "stroke"); }; /** * Sets fill properties on the current element */ Context.prototype.fill = function (path2d) { + this.__strokeOrFill(path2d, "fill"); + }; + + Context.prototype.__strokeOrFill = function (path2d, action) { if (path2d) { var path = this.__createPathElement(); - this.__applyStyleToElement(path, "fill"); + this.__applyStyleToElement(path, action); path.setAttribute("paint-order", "fill stroke markers"); path.setAttribute("d", path2d.__pathString); + this.__applyTransformation(path); } else { if (this.__currentElement.nodeName === "path") { - this.__currentElement.setAttribute("paint-order", "stroke fill markers"); + this.__currentElement.setAttribute( + "paint-order", + "stroke fill markers" + ); } this.__applyCurrentDefaultPath(); - this.__applyStyleToElement(this.__currentElement, "fill"); + this.__applyStyleToElement(this.__currentElement, action); } }; From f3cac336d241f5e9a0214199d123a439fe5ad1f4 Mon Sep 17 00:00:00 2001 From: k1w1 Date: Sat, 24 Dec 2022 11:39:58 -0800 Subject: [PATCH 04/12] Built for npm --- .DS_Store | Bin 0 -> 6148 bytes package-lock.json | 3538 +-------------------------------------------- package.json | 4 +- 3 files changed, 15 insertions(+), 3527 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a2eb533abf551d042629249e6fe5fb378b3b5ee0 GIT binary patch literal 6148 zcmeH~u?oUK42Bc!Ah>jNyu}Cb4Gz&K=nFU~E>c0O^F6wMazU^xC+1b{XuyJ79K1T}(S!u1*@b}wNMJ-@TJzTK|1JE}{6A`8N&+PC zX9Tp_belC^D(=>|*R%RAs=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.17.2", - "@babel/parser": "^7.17.3", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", - "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.0", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", - "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "21.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.2.tgz", - "integrity": "sha512-d/OmjaLVO4j/aQX69bwpWPpbvI3TJkQuxoAk7BH8ew1PyoMBLTOuvJTjzG8oEoW7drIIqB0KCJtfFLu/2GClWg==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "commondir": "^1.0.1", - "estree-walker": "^2.0.1", - "glob": "^7.1.6", - "is-reference": "^1.2.1", - "magic-string": "^0.25.7", - "resolve": "^1.17.0" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^2.38.3" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz", - "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^2.42.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@socket.io/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "dev": true - }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "node_modules/@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true - }, - "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", - "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", - "dev": true - }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true, - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.0.tgz", - "integrity": "sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001313", - "electron-to-chromium": "^1.4.76", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/builtin-modules": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", - "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001314", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz", - "integrity": "sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==", - "dev": true - }, - "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/convert-source-map/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "dependencies": { - "node-fetch": "2.6.7" - } - }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true - }, - "node_modules/date-format": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.4.tgz", - "integrity": "sha512-/jyf4rhB17ge328HJuJjAcmRtCsGd+NDeAtahRBTaK6vSPR6MO5HlrAit3Nn7dVjaa6sowW0WXt8yQtLyZQFRg==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.969999", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.969999.tgz", - "integrity": "sha512-6GfzuDWU0OFAuOvBokXpXPLxjOJ5DZ157Ue3sGQQM3LgAamb8m0R0ruSfN0DDu+XG5XJgT50i6zZ/0o8RglreQ==", - "dev": true - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", - "dev": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.78", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.78.tgz", - "integrity": "sha512-o61+D/Lx7j/E0LIin/efOqeHpXhwi1TaQco9vUcRmr91m25SfZY6L5hWJDv/r+6kNjboFKgBw1LbfM0lbhuK6Q==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", - "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", - "dev": true, - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", - "dev": true, - "dependencies": { - "@socket.io/base64-arraybuffer": "~1.0.2" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/extract-zip/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", - "dev": true, - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/isbinaryfile": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", - "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", - "dev": true, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/istanbul-reports": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", - "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/karma": { - "version": "6.3.17", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz", - "integrity": "sha512-2TfjHwrRExC8yHoWlPBULyaLwAFmXmxQrcuFImt/JsAsSZu1uOWTZ1ZsWjqQtWpHLiatJOHL5jFjXSJIgCd01g==", - "dev": true, - "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.2.0", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/karma-chrome-launcher": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", - "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", - "dev": true, - "dependencies": { - "which": "^1.2.1" - } - }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/karma-coverage": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", - "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.1", - "istanbul-reports": "^3.0.5", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/karma-mocha": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", - "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.3" - } - }, - "node_modules/karma-mocha-reporter": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", - "integrity": "sha512-Hr6nhkIp0GIJJrvzY8JFeHpQZNseuIakGac4bpw8K1+5F0tLb6l7uvXRa8mt2Z+NVwYgCct4QAfp2R2QP6o00w==", - "dev": true, - "dependencies": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "strip-ansi": "^4.0.0" - }, - "peerDependencies": { - "karma": ">=0.13" - } - }, - "node_modules/karma-mocha-reporter/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha512-wFUFA5bg5dviipbQQ32yOQhl6gcJaJXiHE7dvR8VYPG97+J/GNC5FKGepKdEDUFeXRzDxPF1X/Btc8L+v7oqIQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/karma-mocha-reporter/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/karma-mocha-reporter/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/karma-mocha-reporter/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-sourcemap-loader": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz", - "integrity": "sha512-zorxyAakYZuBcHRJE+vbrK2o2JXLFWK8VVjiT/6P+ltLBUGUvqTEkUiQ119MGdOrK7mrmxXHZF1/pfT6GgIZ6g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/log4js": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.2.tgz", - "integrity": "sha512-k80cggS2sZQLBwllpT1p06GtfvzMmSdUCkW96f0Hj83rKGJDAu2vZjt9B9ag2vx8Zz1IXzxoLgqvRJCdMKybGg==", - "dev": true, - "dependencies": { - "date-format": "^4.0.4", - "debug": "^4.3.3", - "flatted": "^3.2.5", - "rfdc": "^1.3.0", - "streamroller": "^3.0.4" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/log4js/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/log4js/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, - "dependencies": { - "mime-db": "1.51.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "node_modules/mocha": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", - "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.2.0", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/puppeteer": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.5.0.tgz", - "integrity": "sha512-raPr2YZ3RZLboGwt7jJgusJTBRDaVEUiPOSOWWFLV1oj07xqT5UqqbjmNXFXlMlkhF/NwmcEInW64VhvyllVdw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "cross-fetch": "3.1.5", - "debug": "4.3.3", - "devtools-protocol": "0.0.969999", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.5.0" - }, - "engines": { - "node": ">=10.18.1" - } - }, - "node_modules/puppeteer/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/puppeteer/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/puppeteer/node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.8.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rollup": { - "version": "2.67.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.67.0.tgz", - "integrity": "sha512-W83AaERwvDiHwHEF/dfAfS3z1Be5wf7n+pO3ZAO5IQadCT2lBTr7WQ2MwZZe+nodbD+n3HtC4OCOAdsOPPcKZQ==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/socket.io": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", - "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.1.0", - "socket.io-adapter": "~2.3.3", - "socket.io-parser": "~4.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", - "dev": true - }, - "node_modules/socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", - "dev": true, - "dependencies": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/streamroller": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.4.tgz", - "integrity": "sha512-GI9NzeD+D88UFuIlJkKNDH/IsuR+qIN7Qh8EsmhoRZr9bQoehTraRgwtLUkZbpcAw+hLPfHOypmppz8YyGK68w==", - "dev": true, - "dependencies": { - "date-format": "^4.0.4", - "debug": "^4.3.3", - "fs-extra": "^10.0.1" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/streamroller/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/streamroller/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - } - } - }, "dependencies": { "@ampproject/remapping": { "version": "2.1.2", @@ -5698,8 +2188,7 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "requires": {} + "dev": true } } }, @@ -5953,15 +2442,6 @@ } } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -5973,6 +2453,15 @@ "strip-ansi": "^6.0.1" } }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6190,8 +2679,7 @@ "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "requires": {} + "dev": true }, "y18n": { "version": "5.0.8", diff --git a/package.json b/package.json index c045ca2..7d2fd6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "svgcanvas", - "version": "2.5.0", + "name": "@aha-app/svgcanvas", + "version": "2.5.0-a6", "description": "svgcanvas", "main": "dist/svgcanvas.js", "scripts": { From fd024674c0f3c52f436757ea737cfd02d855aa9f Mon Sep 17 00:00:00 2001 From: k1w1 Date: Sat, 24 Dec 2022 14:15:14 -0800 Subject: [PATCH 05/12] Added clipping test --- test/index.js | 130 ++++++++++++++++++++++--------------------- test/tests/clip.js | 38 +++++++++++++ test/tests/path2D.js | 59 +++++++++++++------- 3 files changed, 143 insertions(+), 84 deletions(-) create mode 100644 test/tests/clip.js diff --git a/test/index.js b/test/index.js index db8695f..b04704d 100644 --- a/test/index.js +++ b/test/index.js @@ -1,71 +1,73 @@ -import {Element} from '../index' -import arc from './tests/arc' -import arcTo from './tests/arcTo' -import arcTo2 from './tests/arcTo2' -import arcToScaled from './tests/arcToScaled' -import emptyArc from './tests/emptyArc' -import ellipse from './tests/ellipse' -import ellipse2 from './tests/ellipse2' -import fillstyle from './tests/fillstyle' -import globalAlpha from './tests/globalalpha' -import gradient from './tests/gradient' -import linecap from './tests/linecap' -import linewidth from './tests/linewidth' -import scaledLine from './tests/scaledLine' -import rgba from './tests/rgba' -import rotate from './tests/rotate' -import saveandrestore from './tests/saveandrestore' -import setLineDash from './tests/setLineDash' -import text from './tests/text' -import tiger from './tests/tiger' -import transform from './tests/transform' +import { Element } from "../index"; +import arc from "./tests/arc"; +import arcTo from "./tests/arcTo"; +import arcTo2 from "./tests/arcTo2"; +import arcToScaled from "./tests/arcToScaled"; +import emptyArc from "./tests/emptyArc"; +import ellipse from "./tests/ellipse"; +import ellipse2 from "./tests/ellipse2"; +import fillstyle from "./tests/fillstyle"; +import globalAlpha from "./tests/globalalpha"; +import gradient from "./tests/gradient"; +import linecap from "./tests/linecap"; +import linewidth from "./tests/linewidth"; +import scaledLine from "./tests/scaledLine"; +import rgba from "./tests/rgba"; +import rotate from "./tests/rotate"; +import saveandrestore from "./tests/saveandrestore"; +import setLineDash from "./tests/setLineDash"; +import text from "./tests/text"; +import tiger from "./tests/tiger"; +import transform from "./tests/transform"; import pattern from "./tests/pattern"; -import path2D from './tests/path2D'; +import path2D from "./tests/path2D"; +import clip from "./tests/clip"; const tests = [ - tiger, - arc, - arcTo, - arcTo2, - arcToScaled, - emptyArc, - ellipse, - ellipse2, - fillstyle, - globalAlpha, - gradient, - linecap, - linewidth, - scaledLine, - rgba, - rotate, - saveandrestore, - setLineDash, - text, - transform, - pattern, - path2D + tiger, + arc, + arcTo, + arcTo2, + arcToScaled, + clip, + emptyArc, + ellipse, + ellipse2, + fillstyle, + globalAlpha, + gradient, + linecap, + linewidth, + scaledLine, + rgba, + rotate, + saveandrestore, + setLineDash, + text, + transform, + pattern, + path2D, ]; for (let fn of tests) { - let name = fn.name; - // Container - const container = document.createElement('div'); - container.className = 'example'; - container.id = 'example-' + name; - container.innerHTML = `

${name}

` - // Canvas - const canvas = document.createElement('canvas'); - container.querySelector('.canvas').appendChild(canvas); - // SVGCanvas - const svgcanvas = new Element(); - container.querySelector('.svg').appendChild(svgcanvas.getElement()); - document.querySelector('body').appendChild(container); - // Render - for (let c of [canvas, svgcanvas]) { - c.width = 500; - c.height = 500; - const ctx = c.getContext('2d'); - fn(ctx); - } + let name = fn.name; + // Container + const container = document.createElement("div"); + container.className = "example"; + container.id = "example-" + name; + container.innerHTML = `

${name}

`; + // Canvas + const canvas = document.createElement("canvas"); + container.querySelector(".canvas").appendChild(canvas); + // SVGCanvas + const svgcanvas = new Element(); + container.querySelector(".svg").appendChild(svgcanvas.getElement()); + document.querySelector("body").appendChild(container); + // Render + for (let c of [canvas, svgcanvas]) { + c.width = 500; + c.height = 500; + const ctx = c.getContext("2d"); + fn(ctx); + } } diff --git a/test/tests/clip.js b/test/tests/clip.js new file mode 100644 index 0000000..086b38c --- /dev/null +++ b/test/tests/clip.js @@ -0,0 +1,38 @@ +export default function clip(ctx) { + // Draw a line with clipped areas removed. + var scaleX = 1.5, + scaleY = 1.2; + + ctx.rotate(Math.PI / 10); + ctx.scale(scaleX, scaleY); + ctx.translate(200, 25); + + // Draw unclipped line + ctx.beginPath(); + ctx.moveTo(5, 10); + ctx.lineTo(195, 200); + ctx.stroke(); + + ctx.save(); + + // Remove clipped areas + ctx.beginPath(); + ctx.rect(20, 30, 30, 10); + ctx.rect(0, 0, 300, 300); + // ctx.stroke(); // Uncomment for debugging clip. + ctx.clip("evenodd"); + + // Draw line. + ctx.beginPath(); + ctx.moveTo(10, 10); + ctx.lineTo(200, 200); + ctx.stroke(); + + ctx.restore(); + + // Draw unclipped line + ctx.beginPath(); + ctx.moveTo(15, 10); + ctx.lineTo(205, 200); + ctx.stroke(); +} diff --git a/test/tests/path2D.js b/test/tests/path2D.js index 491d726..6a80b59 100644 --- a/test/tests/path2D.js +++ b/test/tests/path2D.js @@ -1,29 +1,48 @@ function makePath(ctx, arg) { - if (ctx.createPath) { - return ctx.createPath(arg); - } else { - return new Path2D(arg); - } + if (ctx.createPath) { + return ctx.createPath(arg); + } else { + return new Path2D(arg); + } } export default function path2D(ctx) { - const path1 = makePath(ctx, `M 230 80 + const path1 = makePath( + ctx, + `M 230 80 A 45 45, 0, 1, 0, 275 125 - L 275 80 Z`); + L 275 80 Z` + ); - ctx.strokeStyle = 'red'; - ctx.stroke(path1); - ctx.fillStyle = 'grey'; - ctx.fill(path1); + ctx.save(); + ctx.strokeStyle = "red"; + ctx.stroke(path1); + ctx.fillStyle = "grey"; + ctx.fill(path1); - ctx.translate(10, 25); - ctx.lineWidth = 10; - ctx.stroke(path1); + ctx.translate(10, 25); + ctx.lineWidth = 10; + ctx.stroke(path1); - ctx.rotate(Math.PI / 4); - ctx.scale(1.5, 1.5); - ctx.translate(10, 25); + ctx.rotate(Math.PI / 4); + ctx.scale(1.5, 1.5); + ctx.translate(10, 25); - ctx.strokeStyle = 'blue'; - ctx.stroke(path1); - }; \ No newline at end of file + ctx.strokeStyle = "blue"; + ctx.stroke(path1); + ctx.restore(); + + ctx.save(); + // Stroke and fill the same path. + ctx.beginPath(); + ctx.rect(10, 10, 40, 20); + ctx.save(); + ctx.strokeStyle = "red"; + ctx.stroke(); + ctx.restore(); + ctx.save(); + ctx.fillStyle = "green"; + ctx.fill(); + ctx.restore(); + ctx.restore(); +} From 4f7bcfec4c13c1ae7f99fb1ec41cc1ca8dbff5bb Mon Sep 17 00:00:00 2001 From: k1w1 Date: Sat, 24 Dec 2022 14:49:29 -0800 Subject: [PATCH 06/12] Fixed clipping behavior and handling of save/restore around stroke and fill. --- context.js | 2390 ++++++++++++++++++++++++-------------------- package-lock.json | 4 +- package.json | 2 +- path2d.js | 651 ++++++------ test/tests/clip.js | 2 +- 5 files changed, 1650 insertions(+), 1399 deletions(-) diff --git a/context.js b/context.js index c003d7a..92f91ea 100644 --- a/context.js +++ b/context.js @@ -13,1136 +13,1342 @@ * Copyright (c) 2021 Zeno Zeng */ -import * as utils from './utils'; -import { format } from './utils'; -import imageUtils from './image'; -import Path2D from './path2d'; +import * as utils from "./utils"; +import { format } from "./utils"; +import imageUtils from "./image"; +import Path2D from "./path2d"; export default (function () { - "use strict"; + "use strict"; - var STYLES, Context, CanvasGradient, CanvasPattern, namedEntities; + var STYLES, Context, CanvasGradient, CanvasPattern, namedEntities; - //helper function that generates a random string - function randomString(holder) { - var chars, randomstring, i; - if (!holder) { - throw new Error("cannot create a random attribute name for an undefined object"); - } - chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; - randomstring = ""; - do { - randomstring = ""; - for (i = 0; i < 12; i++) { - randomstring += chars[Math.floor(Math.random() * chars.length)]; - } - } while (holder[randomstring]); - return randomstring; - } - - //helper function to map named to numbered entities - function createNamedToNumberedLookup(items, radix) { - var i, entity, lookup = {}, base10, base16; - items = items.split(','); - radix = radix || 10; - // Map from named to numbered entities. - for (i = 0; i < items.length; i += 2) { - entity = '&' + items[i + 1] + ';'; - base10 = parseInt(items[i], radix); - lookup[entity] = '&#'+base10+';'; - } - //FF and IE need to create a regex from hex values ie   == \xa0 - lookup["\\xa0"] = ' '; - return lookup; - } - - //helper function to map canvas-textAlign to svg-textAnchor - function getTextAnchor(textAlign) { - //TODO: support rtl languages - var mapping = {"left":"start", "right":"end", "center":"middle", "start":"start", "end":"end"}; - return mapping[textAlign] || mapping.start; + //helper function that generates a random string + function randomString(holder) { + var chars, randomstring, i; + if (!holder) { + throw new Error( + "cannot create a random attribute name for an undefined object" + ); } - - //helper function to map canvas-textBaseline to svg-dominantBaseline - function getDominantBaseline(textBaseline) { - //INFO: not supported in all browsers - var mapping = {"alphabetic": "alphabetic", "hanging": "hanging", "top":"text-before-edge", "bottom":"text-after-edge", "middle":"central"}; - return mapping[textBaseline] || mapping.alphabetic; + chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + randomstring = ""; + do { + randomstring = ""; + for (i = 0; i < 12; i++) { + randomstring += chars[Math.floor(Math.random() * chars.length)]; + } + } while (holder[randomstring]); + return randomstring; + } + + //helper function to map named to numbered entities + function createNamedToNumberedLookup(items, radix) { + var i, + entity, + lookup = {}, + base10, + base16; + items = items.split(","); + radix = radix || 10; + // Map from named to numbered entities. + for (i = 0; i < items.length; i += 2) { + entity = "&" + items[i + 1] + ";"; + base10 = parseInt(items[i], radix); + lookup[entity] = "&#" + base10 + ";"; } - - // Unpack entities lookup where the numbers are in radix 32 to reduce the size - // entity mapping courtesy of tinymce - namedEntities = createNamedToNumberedLookup( - '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + - '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + - '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + - '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + - '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + - '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + - '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + - '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + - '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + - '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + - 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + - 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + - 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + - 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + - 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + - '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + - '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + - '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + - '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + - '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + - 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + - 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + - 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + - '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + - '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); - - - //Some basic mappings for attributes and default values. - STYLES = { - "strokeStyle":{ - svgAttr : "stroke", //corresponding svg attribute - canvas : "#000000", //canvas default - svg : "none", //svg default - apply : "stroke" //apply on stroke() or fill() - }, - "fillStyle":{ - svgAttr : "fill", - canvas : "#000000", - svg : null, //svg default is black, but we need to special case this to handle canvas stroke without fill - apply : "fill" - }, - "lineCap":{ - svgAttr : "stroke-linecap", - canvas : "butt", - svg : "butt", - apply : "stroke" - }, - "lineJoin":{ - svgAttr : "stroke-linejoin", - canvas : "miter", - svg : "miter", - apply : "stroke" - }, - "miterLimit":{ - svgAttr : "stroke-miterlimit", - canvas : 10, - svg : 4, - apply : "stroke" - }, - "lineWidth":{ - svgAttr : "stroke-width", - canvas : 1, - svg : 1, - apply : "stroke" - }, - "globalAlpha": { - svgAttr : "opacity", - canvas : 1, - svg : 1, - apply : "fill stroke" - }, - "font":{ - //font converts to multiple svg attributes, there is custom logic for this - canvas : "10px sans-serif" - }, - "shadowColor":{ - canvas : "#000000" - }, - "shadowOffsetX":{ - canvas : 0 - }, - "shadowOffsetY":{ - canvas : 0 - }, - "shadowBlur":{ - canvas : 0 - }, - "textAlign":{ - canvas : "start" - }, - "textBaseline":{ - canvas : "alphabetic" - }, - "lineDash" : { - svgAttr : "stroke-dasharray", - canvas : [], - svg : null, - apply : "stroke" - } + //FF and IE need to create a regex from hex values ie   == \xa0 + lookup["\\xa0"] = " "; + return lookup; + } + + //helper function to map canvas-textAlign to svg-textAnchor + function getTextAnchor(textAlign) { + //TODO: support rtl languages + var mapping = { + left: "start", + right: "end", + center: "middle", + start: "start", + end: "end", }; - - /** - * - * @param gradientNode - reference to the gradient - * @constructor - */ - CanvasGradient = function (gradientNode, ctx) { - this.__root = gradientNode; - this.__ctx = ctx; + return mapping[textAlign] || mapping.start; + } + + //helper function to map canvas-textBaseline to svg-dominantBaseline + function getDominantBaseline(textBaseline) { + //INFO: not supported in all browsers + var mapping = { + alphabetic: "alphabetic", + hanging: "hanging", + top: "text-before-edge", + bottom: "text-after-edge", + middle: "central", }; - - /** - * Adds a color stop to the gradient root - */ - CanvasGradient.prototype.addColorStop = function (offset, color) { - var stop = this.__ctx.__createElement("stop"), regex, matches; - stop.setAttribute("offset", offset); - if (utils.toString(color).indexOf("rgba") !== -1) { - //separate alpha value, since webkit can't handle it - regex = /rgba\(\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi; - matches = regex.exec(color); - stop.setAttribute("stop-color", format("rgb({r},{g},{b})", {r:matches[1], g:matches[2], b:matches[3]})); - stop.setAttribute("stop-opacity", matches[4]); - } else { - stop.setAttribute("stop-color", utils.toString(color)); - } - this.__root.appendChild(stop); - }; - - CanvasPattern = function (pattern, ctx) { - this.__root = pattern; - this.__ctx = ctx; - }; - - /** - * The mock canvas context - * @param o - options include: - * ctx - existing Context2D to wrap around - * width - width of your canvas (defaults to 500) - * height - height of your canvas (defaults to 500) - * enableMirroring - enables canvas mirroring (get image data) (defaults to false) - * document - the document object (defaults to the current document) - */ - Context = function (o) { - - var defaultOptions = { width:500, height:500, enableMirroring : false}, options; - - // keep support for this way of calling Context: new Context(width, height) - if (arguments.length > 1) { - options = defaultOptions; - options.width = arguments[0]; - options.height = arguments[1]; - } else if ( !o ) { - options = defaultOptions; - } else { - options = o; - } - - if (!(this instanceof Context)) { - //did someone call this without new? - return new Context(options); - } - - //setup options - this.width = options.width || defaultOptions.width; - this.height = options.height || defaultOptions.height; - this.enableMirroring = options.enableMirroring !== undefined ? options.enableMirroring : defaultOptions.enableMirroring; - - this.canvas = this; ///point back to this instance! - this.__document = options.document || document; - - // allow passing in an existing context to wrap around - // if a context is passed in, we know a canvas already exist - if (options.ctx) { - this.__ctx = options.ctx; - } else { - this.__canvas = this.__document.createElement("canvas"); - this.__ctx = this.__canvas.getContext("2d"); - } - - this.__setDefaultStyles(); - this.__styleStack = [this.__getStyleState()]; - this.__groupStack = []; - - //the root svg element - this.__root = this.__document.createElementNS("http://www.w3.org/2000/svg", "svg"); - this.__root.setAttribute("version", 1.1); - this.__root.setAttribute("xmlns", "http://www.w3.org/2000/svg"); - this.__root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink"); - this.__root.setAttribute("width", this.width); - this.__root.setAttribute("height", this.height); - - //make sure we don't generate the same ids in defs - this.__ids = {}; - - //defs tag - this.__defs = this.__document.createElementNS("http://www.w3.org/2000/svg", "defs"); - this.__root.appendChild(this.__defs); - - //also add a group child. the svg element can't use the transform attribute - this.__currentElement = this.__document.createElementNS("http://www.w3.org/2000/svg", "g"); - this.__root.appendChild(this.__currentElement); - - // init transformation matrix - this.resetTransform(); - - this.__options = options; - this.__id = Math.random().toString(16).substring(2, 8); - this.__debug(`new`, o); - }; - - /** - * Log - * - * @private - */ - Context.prototype.__debug = function(...data) { - if (!this.__options.debug) { - return - } - console.debug(`svgcanvas#${this.__id}:`, ...data) + return mapping[textBaseline] || mapping.alphabetic; + } + + // Unpack entities lookup where the numbers are in radix 32 to reduce the size + // entity mapping courtesy of tinymce + namedEntities = createNamedToNumberedLookup( + "50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy," + + "5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute," + + "5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34," + + "5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil," + + "68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde," + + "6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute," + + "6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml," + + "75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc," + + "7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash," + + "7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta," + + "sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu," + + "st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi," + + "t9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota," + + "tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau," + + "u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip," + + "81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym," + + "8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr," + + "8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod," + + "8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup," + + "8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4," + + "nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob," + + "rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0," + + "Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm," + + "80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger," + + "811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro", + 32 + ); + + //Some basic mappings for attributes and default values. + STYLES = { + strokeStyle: { + svgAttr: "stroke", //corresponding svg attribute + canvas: "#000000", //canvas default + svg: "none", //svg default + apply: "stroke", //apply on stroke() or fill() + }, + fillStyle: { + svgAttr: "fill", + canvas: "#000000", + svg: null, //svg default is black, but we need to special case this to handle canvas stroke without fill + apply: "fill", + }, + lineCap: { + svgAttr: "stroke-linecap", + canvas: "butt", + svg: "butt", + apply: "stroke", + }, + lineJoin: { + svgAttr: "stroke-linejoin", + canvas: "miter", + svg: "miter", + apply: "stroke", + }, + miterLimit: { + svgAttr: "stroke-miterlimit", + canvas: 10, + svg: 4, + apply: "stroke", + }, + lineWidth: { + svgAttr: "stroke-width", + canvas: 1, + svg: 1, + apply: "stroke", + }, + globalAlpha: { + svgAttr: "opacity", + canvas: 1, + svg: 1, + apply: "fill stroke", + }, + font: { + //font converts to multiple svg attributes, there is custom logic for this + canvas: "10px sans-serif", + }, + shadowColor: { + canvas: "#000000", + }, + shadowOffsetX: { + canvas: 0, + }, + shadowOffsetY: { + canvas: 0, + }, + shadowBlur: { + canvas: 0, + }, + textAlign: { + canvas: "start", + }, + textBaseline: { + canvas: "alphabetic", + }, + lineDash: { + svgAttr: "stroke-dasharray", + canvas: [], + svg: null, + apply: "stroke", + }, + }; + + /** + * + * @param gradientNode - reference to the gradient + * @constructor + */ + CanvasGradient = function (gradientNode, ctx) { + this.__root = gradientNode; + this.__ctx = ctx; + }; + + /** + * Adds a color stop to the gradient root + */ + CanvasGradient.prototype.addColorStop = function (offset, color) { + var stop = this.__ctx.__createElement("stop"), + regex, + matches; + stop.setAttribute("offset", offset); + if (utils.toString(color).indexOf("rgba") !== -1) { + //separate alpha value, since webkit can't handle it + regex = + /rgba\(\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi; + matches = regex.exec(color); + stop.setAttribute( + "stop-color", + format("rgb({r},{g},{b})", { + r: matches[1], + g: matches[2], + b: matches[3], + }) + ); + stop.setAttribute("stop-opacity", matches[4]); + } else { + stop.setAttribute("stop-color", utils.toString(color)); } - - /** - * Creates the specified svg element - * @private - */ - Context.prototype.__createElement = function (elementName, properties, resetFill) { - if (typeof properties === "undefined") { - properties = {}; - } - - var element = this.__document.createElementNS("http://www.w3.org/2000/svg", elementName), - keys = Object.keys(properties), i, key; - if (resetFill) { - //if fill or stroke is not specified, the svg element should not display. By default SVG's fill is black. - element.setAttribute("fill", "none"); - element.setAttribute("stroke", "none"); - } - for (i=0; i 1) { + options = defaultOptions; + options.width = arguments[0]; + options.height = arguments[1]; + } else if (!o) { + options = defaultOptions; + } else { + options = o; } - /** - * Apples the current styles to the current SVG element. On "ctx.fill" or "ctx.stroke" - * @param type - * @private - */ - Context.prototype.__applyStyleToElement = function (element, type) { - var currentElement = element; - var currentStyleGroup = this.__currentElementsToStyle; - if (currentStyleGroup) { - currentElement.setAttribute(type, ""); - currentElement = currentStyleGroup.element; - currentStyleGroup.children.forEach(function (node) { - node.setAttribute(type, ""); - }) - } - - var keys = Object.keys(STYLES), i, style, value, regex, matches, id, nodeIndex, node; - for (i = 0; i < keys.length; i++) { - style = STYLES[keys[i]]; - value = this[keys[i]]; - if (style.apply) { - //is this a gradient or pattern? - if (value instanceof CanvasPattern) { - //pattern - if (value.__ctx) { - //copy over defs - for(nodeIndex = 0; nodeIndex < value.__ctx.__defs.childNodes.length; nodeIndex++){ - node = value.__ctx.__defs.childNodes[nodeIndex]; - id = node.getAttribute("id"); - this.__ids[id] = id; - this.__defs.appendChild(node); - } - } - currentElement.setAttribute(style.apply, format("url(#{id})", {id:value.__root.getAttribute("id")})); - } - else if (value instanceof CanvasGradient) { - //gradient - currentElement.setAttribute(style.apply, format("url(#{id})", {id:value.__root.getAttribute("id")})); - } else if (style.apply.indexOf(type)!==-1 && style.svg !== value) { - if ((style.svgAttr === "stroke" || style.svgAttr === "fill") && value.indexOf("rgba") !== -1) { - //separate alpha value, since illustrator can't handle it - regex = /rgba\(\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi; - matches = regex.exec(value); - currentElement.setAttribute(style.svgAttr, format("rgb({r},{g},{b})", {r:matches[1], g:matches[2], b:matches[3]})); - //should take globalAlpha here - var opacity = matches[4]; - var globalAlpha = this.globalAlpha; - if (globalAlpha != null) { - opacity *= globalAlpha; - } - currentElement.setAttribute(style.svgAttr+"-opacity", opacity); - } else { - var attr = style.svgAttr; - if (keys[i] === 'globalAlpha') { - attr = type+'-'+style.svgAttr; - if (currentElement.getAttribute(attr)) { - //fill-opacity or stroke-opacity has already been set by stroke or fill. - continue; - } - } else if (keys[i] === 'lineWidth') { - var scale = this.__getTransformScale(); - value = value * Math.max(scale.x, scale.y); - } - //otherwise only update attribute if right type, and not svg default - currentElement.setAttribute(attr, value); - } - } - } - } - }; - - /** - * Will return the closest group or svg node. May return the current element. - * @private - */ - Context.prototype.__closestGroupOrSvg = function (node) { - node = node || this.__currentElement; - if (node.nodeName === "g" || node.nodeName === "svg") { - return node; - } else { - return this.__closestGroupOrSvg(node.parentNode); - } - }; - - /** - * Returns the serialized value of the svg so far - * @param fixNamedEntities - Standalone SVG doesn't support named entities, which document.createTextNode encodes. - * If true, we attempt to find all named entities and encode it as a numeric entity. - * @return serialized svg - */ - Context.prototype.getSerializedSvg = function (fixNamedEntities) { - var serialized = new XMLSerializer().serializeToString(this.__root), - keys, i, key, value, regexp, xmlns; - - //IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly - xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi; - if (xmlns.test(serialized)) { - serialized = serialized.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink'); - } - - if (fixNamedEntities) { - keys = Object.keys(namedEntities); - //loop over each named entity and replace with the proper equivalent. - for (i=0; i 0) { - this.setTransform(this.__transformMatrixStack.pop()) - } - - }; - - Context.prototype.__createPathElement = function () { - var path = this.__createElement("path", {}, true); - var parent = this.__closestGroupOrSvg(); - parent.appendChild(path); - return path; + //setup options + this.width = options.width || defaultOptions.width; + this.height = options.height || defaultOptions.height; + this.enableMirroring = + options.enableMirroring !== undefined + ? options.enableMirroring + : defaultOptions.enableMirroring; + + this.canvas = this; ///point back to this instance! + this.__document = options.document || document; + + // allow passing in an existing context to wrap around + // if a context is passed in, we know a canvas already exist + if (options.ctx) { + this.__ctx = options.ctx; + } else { + this.__canvas = this.__document.createElement("canvas"); + this.__ctx = this.__canvas.getContext("2d"); } - /** - * Create a new Path Element - */ - Context.prototype.beginPath = function () { - // Note that there is only one current default path, it is not part of the drawing state. - // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path - this.__currentDefaultPath = new Path2D(this); - this.__currentPosition = {}; - - var path = this.__createPathElement(); - this.__currentElement = path; - }; - - Context.prototype.closePath = function () { - if (!this.__currentDefaultPath) { - this.beginPath(); - } - this.__currentDefaultPath.closePath(); + this.__setDefaultStyles(); + this.__styleStack = [this.__getStyleState()]; + this.__groupStack = []; + + //the root svg element + this.__root = this.__document.createElementNS( + "http://www.w3.org/2000/svg", + "svg" + ); + this.__root.setAttribute("version", 1.1); + this.__root.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + this.__root.setAttributeNS( + "http://www.w3.org/2000/xmlns/", + "xmlns:xlink", + "http://www.w3.org/1999/xlink" + ); + this.__root.setAttribute("width", this.width); + this.__root.setAttribute("height", this.height); + + //make sure we don't generate the same ids in defs + this.__ids = {}; + + //defs tag + this.__defs = this.__document.createElementNS( + "http://www.w3.org/2000/svg", + "defs" + ); + this.__root.appendChild(this.__defs); + + //also add a group child. the svg element can't use the transform attribute + this.__currentElement = this.__document.createElementNS( + "http://www.w3.org/2000/svg", + "g" + ); + this.__root.appendChild(this.__currentElement); + + // init transformation matrix + this.resetTransform(); + + this.__options = options; + this.__id = Math.random().toString(16).substring(2, 8); + this.__debug(`new`, o); + }; + + /** + * Log + * + * @private + */ + Context.prototype.__debug = function (...data) { + if (!this.__options.debug) { + return; } - - Context.prototype.moveTo = function (x, y) { - if (!this.__currentDefaultPath) { - this.beginPath(); - } - this.__currentDefaultPath.moveTo(x, y); - }; - - Context.prototype.lineTo = function (x, y) { - if (!this.__currentDefaultPath) { - this.moveTo(x, y); - } - this.__currentDefaultPath.lineTo(x, y); - }; - - Context.prototype.rect = function (x, y, width, height) { - if (!this.__currentDefaultPath) { - this.beginPath(); - } - this.__currentDefaultPath.rect(x, y, width, height); + console.debug(`svgcanvas#${this.__id}:`, ...data); + }; + + /** + * Creates the specified svg element + * @private + */ + Context.prototype.__createElement = function ( + elementName, + properties, + resetFill + ) { + if (typeof properties === "undefined") { + properties = {}; } - Context.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { - if (!this.__currentDefaultPath) { - this.beginPath(); - } - this.__currentDefaultPath.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); - }; - - Context.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { - if (!this.__currentDefaultPath) { - this.beginPath(); - } - this.__currentDefaultPath.quadraticCurveTo(cpx, cpy, x, y); - }; - - Context.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) { - if (!this.__currentDefaultPath) { - this.beginPath(); - } - this.__currentDefaultPath.arc(x, y, radius, startAngle, endAngle, counterClockwise); - }; - - Context.prototype.arcTo = function (x1, y1, x2, y2, radius) { - if (!this.__currentDefaultPath) { - this.beginPath(); - } - this.__currentDefaultPath.arcTo(x1, y1, x2, y2, radius); - }; - - Context.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterClockwise) { - if (!this.__currentDefaultPath) { - this.beginPath(); - } - this.__currentDefaultPath.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterClockwise); - }; - - /** - * Helper function to apply currentDefaultPath to current path element - * @private - */ - Context.prototype.__applyCurrentDefaultPath = function (path) { - var currentElement = this.__currentElement; - if (currentElement.nodeName === "path") { - currentElement.setAttribute("d", this.__currentDefaultPath.__pathString); - } else { - console.error("Attempted to apply path command to node", currentElement.nodeName); - } - }; - - /** - * Sets the stroke property on the current element - */ - Context.prototype.stroke = function (path2d) { - this.__strokeOrFill(path2d, "stroke"); - }; - - /** - * Sets fill properties on the current element - */ - Context.prototype.fill = function (path2d) { - this.__strokeOrFill(path2d, "fill"); - }; + var element = this.__document.createElementNS( + "http://www.w3.org/2000/svg", + elementName + ), + keys = Object.keys(properties), + i, + key; + if (resetFill) { + //if fill or stroke is not specified, the svg element should not display. By default SVG's fill is black. + element.setAttribute("fill", "none"); + element.setAttribute("stroke", "none"); + } + for (i = 0; i < keys.length; i++) { + key = keys[i]; + element.setAttribute(key, properties[key]); + } + return element; + }; + + /** + * Applies default canvas styles to the context + * @private + */ + Context.prototype.__setDefaultStyles = function () { + //default 2d canvas context properties see:http://www.w3.org/TR/2dcontext/ + var keys = Object.keys(STYLES), + i, + key; + for (i = 0; i < keys.length; i++) { + key = keys[i]; + this[key] = STYLES[key].canvas; + } + }; + + /** + * Applies styles on restore + * @param styleState + * @private + */ + Context.prototype.__applyStyleState = function (styleState) { + var keys = Object.keys(styleState), + i, + key; + for (i = 0; i < keys.length; i++) { + key = keys[i]; + this[key] = styleState[key]; + } + }; + + /** + * Gets the current style state + * @return {Object} + * @private + */ + Context.prototype.__getStyleState = function () { + var i, + styleState = {}, + keys = Object.keys(STYLES), + key; + for (i = 0; i < keys.length; i++) { + key = keys[i]; + styleState[key] = this[key]; + } + return styleState; + }; + + /** + * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform + */ + Context.prototype.__applyTransformation = function (element, matrix) { + const { a, b, c, d, e, f } = matrix || this.getTransform(); + element.setAttribute("transform", `matrix(${a} ${b} ${c} ${d} ${e} ${f})`); + }; + + /** + * Apples the current styles to the current SVG element. On "ctx.fill" or "ctx.stroke" + * @param type + * @private + */ + Context.prototype.__applyStyleToElement = function (element, type) { + var currentElement = element; + var currentStyleGroup = this.__currentElementsToStyle; + if (currentStyleGroup) { + currentElement.setAttribute(type, ""); + currentElement = currentStyleGroup.element; + currentStyleGroup.children.forEach(function (node) { + node.setAttribute(type, ""); + }); + } - Context.prototype.__strokeOrFill = function (path2d, action) { - if (path2d) { - var path = this.__createPathElement(); - this.__applyStyleToElement(path, action); - path.setAttribute("paint-order", "fill stroke markers"); - path.setAttribute("d", path2d.__pathString); - this.__applyTransformation(path); - } else { - if (this.__currentElement.nodeName === "path") { - this.__currentElement.setAttribute( - "paint-order", - "stroke fill markers" - ); + var keys = Object.keys(STYLES), + i, + style, + value, + regex, + matches, + id, + nodeIndex, + node; + for (i = 0; i < keys.length; i++) { + style = STYLES[keys[i]]; + value = this[keys[i]]; + if (style.apply) { + //is this a gradient or pattern? + if (value instanceof CanvasPattern) { + //pattern + if (value.__ctx) { + //copy over defs + for ( + nodeIndex = 0; + nodeIndex < value.__ctx.__defs.childNodes.length; + nodeIndex++ + ) { + node = value.__ctx.__defs.childNodes[nodeIndex]; + id = node.getAttribute("id"); + this.__ids[id] = id; + this.__defs.appendChild(node); } - this.__applyCurrentDefaultPath(); - this.__applyStyleToElement(this.__currentElement, action); - } - }; - - /** - * adds a rectangle element - */ - Context.prototype.fillRect = function (x, y, width, height) { - let {a, b, c, d, e, f} = this.getTransform(); - if (JSON.stringify([a, b, c, d, e, f]) === JSON.stringify([1, 0, 0, 1, 0, 0])) { - //clear entire canvas - if (x === 0 && y === 0 && width === this.width && height === this.height) { - this.__clearCanvas(); + } + currentElement.setAttribute( + style.apply, + format("url(#{id})", { id: value.__root.getAttribute("id") }) + ); + } else if (value instanceof CanvasGradient) { + //gradient + currentElement.setAttribute( + style.apply, + format("url(#{id})", { id: value.__root.getAttribute("id") }) + ); + } else if (style.apply.indexOf(type) !== -1 && style.svg !== value) { + if ( + (style.svgAttr === "stroke" || style.svgAttr === "fill") && + value.indexOf("rgba") !== -1 + ) { + //separate alpha value, since illustrator can't handle it + regex = + /rgba\(\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\s*,\s*(\d?\.?\d*)\s*\)/gi; + matches = regex.exec(value); + currentElement.setAttribute( + style.svgAttr, + format("rgb({r},{g},{b})", { + r: matches[1], + g: matches[2], + b: matches[3], + }) + ); + //should take globalAlpha here + var opacity = matches[4]; + var globalAlpha = this.globalAlpha; + if (globalAlpha != null) { + opacity *= globalAlpha; } - } - var rect, parent; - rect = this.__createElement("rect", { - x : x, - y : y, - width : width, - height : height - }, true); - parent = this.__closestGroupOrSvg(); - parent.appendChild(rect); - this.__currentElement = rect; - this.__applyTransformation(rect); - this.__applyStyleToElement(this.__currentElement, "fill"); - }; - - /** - * Draws a rectangle with no fill - * @param x - * @param y - * @param width - * @param height - */ - Context.prototype.strokeRect = function (x, y, width, height) { - var rect, parent; - rect = this.__createElement("rect", { - x : x, - y : y, - width : width, - height : height - }, true); - parent = this.__closestGroupOrSvg(); - parent.appendChild(rect); - this.__currentElement = rect; - this.__applyTransformation(rect); - this.__applyStyleToElement(this.__currentElement, "stroke"); - }; - - - /** - * Clear entire canvas: - * 1. save current transforms - * 2. remove all the childNodes of the root g element - */ - Context.prototype.__clearCanvas = function () { - var rootGroup = this.__root.childNodes[1]; - this.__root.removeChild(rootGroup); - this.__currentElement = this.__document.createElementNS("http://www.w3.org/2000/svg", "g"); - this.__root.appendChild(this.__currentElement); - //reset __groupStack as all the child group nodes are all removed. - this.__groupStack = []; - }; - - /** - * "Clears" a canvas by just drawing a white rectangle in the current group. - */ - Context.prototype.clearRect = function (x, y, width, height) { - let {a, b, c, d, e, f} = this.getTransform(); - if (JSON.stringify([a, b, c, d, e, f]) === JSON.stringify([1, 0, 0, 1, 0, 0])) { - //clear entire canvas - if (x === 0 && y === 0 && width === this.width && height === this.height) { - this.__clearCanvas(); - return; + currentElement.setAttribute(style.svgAttr + "-opacity", opacity); + } else { + var attr = style.svgAttr; + if (keys[i] === "globalAlpha") { + attr = type + "-" + style.svgAttr; + if (currentElement.getAttribute(attr)) { + //fill-opacity or stroke-opacity has already been set by stroke or fill. + continue; + } + } else if (keys[i] === "lineWidth") { + var scale = this.__getTransformScale(); + value = value * Math.max(scale.x, scale.y); } + //otherwise only update attribute if right type, and not svg default + currentElement.setAttribute(attr, value); + } } - var rect, parent = this.__closestGroupOrSvg(); - rect = this.__createElement("rect", { - x : x, - y : y, - width : width, - height : height, - fill : "#FFFFFF" - }, true); - this.__applyTransformation(rect) - parent.appendChild(rect); - }; - - /** - * Adds a linear gradient to a defs tag. - * Returns a canvas gradient object that has a reference to it's parent def - */ - Context.prototype.createLinearGradient = function (x1, y1, x2, y2) { - var grad = this.__createElement("linearGradient", { - id : randomString(this.__ids), - x1 : x1+"px", - x2 : x2+"px", - y1 : y1+"px", - y2 : y2+"px", - "gradientUnits" : "userSpaceOnUse" - }, false); - this.__defs.appendChild(grad); - return new CanvasGradient(grad, this); - }; - - /** - * Adds a radial gradient to a defs tag. - * Returns a canvas gradient object that has a reference to it's parent def - */ - Context.prototype.createRadialGradient = function (x0, y0, r0, x1, y1, r1) { - var grad = this.__createElement("radialGradient", { - id : randomString(this.__ids), - cx : x1+"px", - cy : y1+"px", - r : r1+"px", - fx : x0+"px", - fy : y0+"px", - "gradientUnits" : "userSpaceOnUse" - }, false); - this.__defs.appendChild(grad); - return new CanvasGradient(grad, this); - - }; + } + } + }; + + /** + * Will return the closest group or svg node. May return the current element. + * @private + */ + Context.prototype.__closestGroupOrSvg = function (node) { + node = node || this.__currentElement; + if (node.nodeName === "g" || node.nodeName === "svg") { + return node; + } else { + return this.__closestGroupOrSvg(node.parentNode); + } + }; + + /** + * Returns the serialized value of the svg so far + * @param fixNamedEntities - Standalone SVG doesn't support named entities, which document.createTextNode encodes. + * If true, we attempt to find all named entities and encode it as a numeric entity. + * @return serialized svg + */ + Context.prototype.getSerializedSvg = function (fixNamedEntities) { + var serialized = new XMLSerializer().serializeToString(this.__root), + keys, + i, + key, + value, + regexp, + xmlns; + + //IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly + xmlns = + /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi; + if (xmlns.test(serialized)) { + serialized = serialized.replace( + 'xmlns="http://www.w3.org/2000/svg', + 'xmlns:xlink="http://www.w3.org/1999/xlink' + ); + } - /** - * Fills or strokes text - * @param text - * @param x - * @param y - * @param action - stroke or fill - * @private - */ - Context.prototype.__applyText = function (text, x, y, action) { - var el = document.createElement("span"); - el.setAttribute("style", 'font:' + this.font); - - var style = el.style, // CSSStyleDeclaration object - parent = this.__closestGroupOrSvg(), - textElement = this.__createElement("text", { - "font-family": style.fontFamily, - "font-size": style.fontSize, - "font-style": style.fontStyle, - "font-weight": style.fontWeight, - - // canvas doesn't support underline natively, but we do :) - "text-decoration": this.__fontUnderline, - "x": x, - "y": y, - "text-anchor": getTextAnchor(this.textAlign), - "dominant-baseline": getDominantBaseline(this.textBaseline) - }, true); - - textElement.appendChild(this.__document.createTextNode(text)); - this.__currentElement = textElement; - this.__applyTransformation(textElement); - this.__applyStyleToElement(this.__currentElement, action); - - if (this.__fontHref) { - var a = this.__createElement("a"); - // canvas doesn't natively support linking, but we do :) - a.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", this.__fontHref); - a.appendChild(textElement); - textElement = a; + if (fixNamedEntities) { + keys = Object.keys(namedEntities); + //loop over each named entity and replace with the proper equivalent. + for (i = 0; i < keys.length; i++) { + key = keys[i]; + value = namedEntities[key]; + regexp = new RegExp(key, "gi"); + if (regexp.test(serialized)) { + serialized = serialized.replace(regexp, value); } + } + } - parent.appendChild(textElement); - }; - - /** - * Creates a text element - * @param text - * @param x - * @param y - */ - Context.prototype.fillText = function (text, x, y) { - this.__applyText(text, x, y, "fill"); - }; - - /** - * Strokes text - * @param text - * @param x - * @param y - */ - Context.prototype.strokeText = function (text, x, y) { - this.__applyText(text, x, y, "stroke"); - }; - - /** - * No need to implement this for svg. - * @param text - * @return {TextMetrics} - */ - Context.prototype.measureText = function (text) { - this.__ctx.font = this.font; - return this.__ctx.measureText(text); - }; - - - /** - * Generates a ClipPath from the clip command. - */ - Context.prototype.clip = function () { - var group = this.__closestGroupOrSvg(), - clipPath = this.__createElement("clipPath"), - id = randomString(this.__ids), - newGroup = this.__createElement("g"); - - this.__applyCurrentDefaultPath(); - group.removeChild(this.__currentElement); - clipPath.setAttribute("id", id); - clipPath.appendChild(this.__currentElement); - - this.__defs.appendChild(clipPath); - - //set the clip path to this group - group.setAttribute("clip-path", format("url(#{id})", {id:id})); - - //clip paths can be scaled and transformed, we need to add another wrapper group to avoid later transformations - // to this path - group.appendChild(newGroup); - - this.__currentElement = newGroup; + return serialized; + }; + + Context.prototype.createPath = function (arg) { + return new Path2D(this, arg); + }; + + /** + * Returns the root svg + * @return + */ + Context.prototype.getSvg = function () { + return this.__root; + }; + + /** + * Will generate a group tag. + */ + Context.prototype.save = function () { + var group = this.__createElement("g"); + var parent = this.__closestGroupOrSvg(); + this.__groupStack.push(parent); + parent.appendChild(group); + this.__currentElement = group; + const style = this.__getStyleState(); + + this.__debug("save style", style); + this.__styleStack.push(style); + if (!this.__transformMatrixStack) { + this.__transformMatrixStack = []; + } + this.__transformMatrixStack.push(this.getTransform()); + }; + + /** + * Sets current element to parent, or just root if already root + */ + Context.prototype.restore = function () { + this.__currentElement = this.__groupStack.pop(); + this.__currentElementsToStyle = null; + //Clearing canvas will make the poped group invalid, currentElement is set to the root group node. + if (!this.__currentElement) { + this.__currentElement = this.__root.childNodes[1]; + } + var state = this.__styleStack.pop(); + this.__debug("restore style", state); + this.__applyStyleState(state); + if (this.__transformMatrixStack && this.__transformMatrixStack.length > 0) { + this.setTransform(this.__transformMatrixStack.pop()); + } + }; + + Context.prototype.__createPathElement = function () { + var path = this.__createElement("path", {}, true); + var parent = this.__closestGroupOrSvg(); + parent.appendChild(path); + return path; + }; + + /** + * Create a new Path Element + */ + Context.prototype.beginPath = function () { + // Note that there is only one current default path, it is not part of the drawing state. + // See also: https://html.spec.whatwg.org/multipage/scripting.html#current-default-path + this.__currentDefaultPath = new Path2D(this); + this.__currentPosition = {}; + }; + + Context.prototype.closePath = function () { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.closePath(); + }; - }; + Context.prototype.moveTo = function (x, y) { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.moveTo(x, y); + }; - /** - * Draws a canvas, image or mock context to this canvas. - * Note that all svg dom manipulation uses node.childNodes rather than node.children for IE support. - * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage - */ - Context.prototype.drawImage = function () { - //convert arguments to a real array - var args = Array.prototype.slice.call(arguments), - image=args[0], - dx, dy, dw, dh, sx=0, sy=0, sw, sh, parent, svg, defs, group, - currentElement, svgImage, canvas, context, id; - - if (args.length === 3) { - dx = args[1]; - dy = args[2]; - sw = image.width; - sh = image.height; - dw = sw; - dh = sh; - } else if (args.length === 5) { - dx = args[1]; - dy = args[2]; - dw = args[3]; - dh = args[4]; - sw = image.width; - sh = image.height; - } else if (args.length === 9) { - sx = args[1]; - sy = args[2]; - sw = args[3]; - sh = args[4]; - dx = args[5]; - dy = args[6]; - dw = args[7]; - dh = args[8]; - } else { - throw new Error("Invalid number of arguments passed to drawImage: " + arguments.length); - } + Context.prototype.lineTo = function (x, y) { + if (!this.__currentDefaultPath) { + this.moveTo(x, y); + } + this.__currentDefaultPath.lineTo(x, y); + }; - parent = this.__closestGroupOrSvg(); - currentElement = this.__currentElement; - const matrix = this.getTransform().translate(dx, dy); - if (image instanceof Context) { - //canvas2svg mock canvas context. In the future we may want to clone nodes instead. - //also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context. - svg = image.getSvg().cloneNode(true); - if (svg.childNodes && svg.childNodes.length > 1) { - defs = svg.childNodes[0]; - while(defs.childNodes.length) { - id = defs.childNodes[0].getAttribute("id"); - this.__ids[id] = id; - this.__defs.appendChild(defs.childNodes[0]); - } - group = svg.childNodes[1]; - if (group) { - this.__applyTransformation(group, matrix); - parent.appendChild(group); - } - } - } else if (image.nodeName === "CANVAS" || image.nodeName === "IMG") { - //canvas or image - svgImage = this.__createElement("image"); - svgImage.setAttribute("width", dw); - svgImage.setAttribute("height", dh); - svgImage.setAttribute("preserveAspectRatio", "none"); - - if (sx || sy || sw !== image.width || sh !== image.height) { - //crop the image using a temporary canvas - canvas = this.__document.createElement("canvas"); - canvas.width = dw; - canvas.height = dh; - context = canvas.getContext("2d"); - context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh); - image = canvas; - } - this.__applyTransformation(svgImage, matrix); - svgImage.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", - image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src")); - parent.appendChild(svgImage); - } - }; + Context.prototype.rect = function (x, y, width, height) { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.rect(x, y, width, height); + }; - /** - * Generates a pattern tag - */ - Context.prototype.createPattern = function (image, repetition) { - var pattern = this.__document.createElementNS("http://www.w3.org/2000/svg", "pattern"), id = randomString(this.__ids), - img; - pattern.setAttribute("id", id); - pattern.setAttribute("width", image.width); - pattern.setAttribute("height", image.height); - // We want the pattern sizing to be absolute, and not relative - // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Patterns - // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternUnits - pattern.setAttribute("patternUnits", "userSpaceOnUse"); - - if (image.nodeName === "CANVAS" || image.nodeName === "IMG") { - img = this.__document.createElementNS("http://www.w3.org/2000/svg", "image"); - img.setAttribute("width", image.width); - img.setAttribute("height", image.height); - img.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", - image.nodeName === "CANVAS" ? image.toDataURL() : image.getAttribute("src")); - pattern.appendChild(img); - this.__defs.appendChild(pattern); - } else if (image instanceof Context) { - pattern.appendChild(image.__root.childNodes[1]); - this.__defs.appendChild(pattern); - } - return new CanvasPattern(pattern, this); - }; + Context.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); + }; - Context.prototype.setLineDash = function (dashArray) { - if (dashArray && dashArray.length > 0) { - this.lineDash = dashArray.join(","); - } else { - this.lineDash = null; - } - }; + Context.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.quadraticCurveTo(cpx, cpy, x, y); + }; + + Context.prototype.arc = function ( + x, + y, + radius, + startAngle, + endAngle, + counterClockwise + ) { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.arc( + x, + y, + radius, + startAngle, + endAngle, + counterClockwise + ); + }; + + Context.prototype.arcTo = function (x1, y1, x2, y2, radius) { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.arcTo(x1, y1, x2, y2, radius); + }; + + Context.prototype.ellipse = function ( + x, + y, + radiusX, + radiusY, + rotation, + startAngle, + endAngle, + counterClockwise + ) { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.ellipse( + x, + y, + radiusX, + radiusY, + rotation, + startAngle, + endAngle, + counterClockwise + ); + }; + + /** + * Sets the stroke property on the current element + */ + Context.prototype.stroke = function (path2d) { + this.__strokeOrFill(path2d, "stroke"); + }; + + /** + * Sets fill properties on the current element + */ + Context.prototype.fill = function (path2d) { + this.__strokeOrFill(path2d, "fill"); + }; + + Context.prototype.__strokeOrFill = function (path2d, action) { + var path; + + if (path2d) { + path = path2d; + } else { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + path = this.__currentDefaultPath; + } - /** - * SetTransform changes the current transformation matrix to - * the matrix given by the arguments as described below. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform - */ - Context.prototype.setTransform = function (a, b, c, d, e, f) { - if (a instanceof DOMMatrix) { - this.__transformMatrix = new DOMMatrix([a.a, a.b, a.c, a.d, a.e, a.f]); - } else { - this.__transformMatrix = new DOMMatrix([a, b, c, d, e, f]); - } - }; + var pathElement = this.__createPathElement(); + this.__applyStyleToElement(pathElement, action); + pathElement.setAttribute("paint-order", "fill stroke markers"); + pathElement.setAttribute("d", path.__pathString); + if (path2d) { + this.__applyTransformation(pathElement); + } + }; + + /** + * adds a rectangle element + */ + Context.prototype.fillRect = function (x, y, width, height) { + let { a, b, c, d, e, f } = this.getTransform(); + if ( + JSON.stringify([a, b, c, d, e, f]) === JSON.stringify([1, 0, 0, 1, 0, 0]) + ) { + //clear entire canvas + if ( + x === 0 && + y === 0 && + width === this.width && + height === this.height + ) { + this.__clearCanvas(); + } + } + var rect, parent; + rect = this.__createElement( + "rect", + { + x: x, + y: y, + width: width, + height: height, + }, + true + ); + parent = this.__closestGroupOrSvg(); + parent.appendChild(rect); + this.__currentElement = rect; + this.__applyTransformation(rect); + this.__applyStyleToElement(this.__currentElement, "fill"); + }; + + /** + * Draws a rectangle with no fill + * @param x + * @param y + * @param width + * @param height + */ + Context.prototype.strokeRect = function (x, y, width, height) { + var rect, parent; + rect = this.__createElement( + "rect", + { + x: x, + y: y, + width: width, + height: height, + }, + true + ); + parent = this.__closestGroupOrSvg(); + parent.appendChild(rect); + this.__currentElement = rect; + this.__applyTransformation(rect); + this.__applyStyleToElement(this.__currentElement, "stroke"); + }; + + /** + * Clear entire canvas: + * 1. save current transforms + * 2. remove all the childNodes of the root g element + */ + Context.prototype.__clearCanvas = function () { + var rootGroup = this.__root.childNodes[1]; + this.__root.removeChild(rootGroup); + this.__currentElement = this.__document.createElementNS( + "http://www.w3.org/2000/svg", + "g" + ); + this.__root.appendChild(this.__currentElement); + //reset __groupStack as all the child group nodes are all removed. + this.__groupStack = []; + }; + + /** + * "Clears" a canvas by just drawing a white rectangle in the current group. + */ + Context.prototype.clearRect = function (x, y, width, height) { + let { a, b, c, d, e, f } = this.getTransform(); + if ( + JSON.stringify([a, b, c, d, e, f]) === JSON.stringify([1, 0, 0, 1, 0, 0]) + ) { + //clear entire canvas + if ( + x === 0 && + y === 0 && + width === this.width && + height === this.height + ) { + this.__clearCanvas(); + return; + } + } + var rect, + parent = this.__closestGroupOrSvg(); + rect = this.__createElement( + "rect", + { + x: x, + y: y, + width: width, + height: height, + fill: "#FFFFFF", + }, + true + ); + this.__applyTransformation(rect); + parent.appendChild(rect); + }; + + /** + * Adds a linear gradient to a defs tag. + * Returns a canvas gradient object that has a reference to it's parent def + */ + Context.prototype.createLinearGradient = function (x1, y1, x2, y2) { + var grad = this.__createElement( + "linearGradient", + { + id: randomString(this.__ids), + x1: x1 + "px", + x2: x2 + "px", + y1: y1 + "px", + y2: y2 + "px", + gradientUnits: "userSpaceOnUse", + }, + false + ); + this.__defs.appendChild(grad); + return new CanvasGradient(grad, this); + }; + + /** + * Adds a radial gradient to a defs tag. + * Returns a canvas gradient object that has a reference to it's parent def + */ + Context.prototype.createRadialGradient = function (x0, y0, r0, x1, y1, r1) { + var grad = this.__createElement( + "radialGradient", + { + id: randomString(this.__ids), + cx: x1 + "px", + cy: y1 + "px", + r: r1 + "px", + fx: x0 + "px", + fy: y0 + "px", + gradientUnits: "userSpaceOnUse", + }, + false + ); + this.__defs.appendChild(grad); + return new CanvasGradient(grad, this); + }; + + /** + * Fills or strokes text + * @param text + * @param x + * @param y + * @param action - stroke or fill + * @private + */ + Context.prototype.__applyText = function (text, x, y, action) { + var el = document.createElement("span"); + el.setAttribute("style", "font:" + this.font); + + var style = el.style, // CSSStyleDeclaration object + parent = this.__closestGroupOrSvg(), + textElement = this.__createElement( + "text", + { + "font-family": style.fontFamily, + "font-size": style.fontSize, + "font-style": style.fontStyle, + "font-weight": style.fontWeight, + + // canvas doesn't support underline natively, but we do :) + "text-decoration": this.__fontUnderline, + x: x, + y: y, + "text-anchor": getTextAnchor(this.textAlign), + "dominant-baseline": getDominantBaseline(this.textBaseline), + }, + true + ); + + textElement.appendChild(this.__document.createTextNode(text)); + this.__currentElement = textElement; + this.__applyTransformation(textElement); + this.__applyStyleToElement(this.__currentElement, action); + + if (this.__fontHref) { + var a = this.__createElement("a"); + // canvas doesn't natively support linking, but we do :) + a.setAttributeNS( + "http://www.w3.org/1999/xlink", + "xlink:href", + this.__fontHref + ); + a.appendChild(textElement); + textElement = a; + } - /** - * GetTransform Returns a copy of the current transformation matrix, - * as a newly created DOMMAtrix Object - * - * @returns A DOMMatrix Object - */ - Context.prototype.getTransform = function () { - let {a, b, c, d, e, f} = this.__transformMatrix; - return new DOMMatrix([a, b, c, d, e, f]); - }; + parent.appendChild(textElement); + }; + + /** + * Creates a text element + * @param text + * @param x + * @param y + */ + Context.prototype.fillText = function (text, x, y) { + this.__applyText(text, x, y, "fill"); + }; + + /** + * Strokes text + * @param text + * @param x + * @param y + */ + Context.prototype.strokeText = function (text, x, y) { + this.__applyText(text, x, y, "stroke"); + }; + + /** + * No need to implement this for svg. + * @param text + * @return {TextMetrics} + */ + Context.prototype.measureText = function (text) { + this.__ctx.font = this.font; + return this.__ctx.measureText(text); + }; + + /** + * Generates a ClipPath from the clip command. + */ + Context.prototype.clip = function (fillRule) { + var group = this.__closestGroupOrSvg(), + clipPath = this.__createElement("clipPath"), + id = randomString(this.__ids); + + var pathElement = this.__createPathElement(); + pathElement.setAttribute("d", this.__currentDefaultPath.__pathString); + // this.__applyTransformation(pathElement); + + clipPath.setAttribute("id", id); + + if (typeof fillRule === "string") { + clipPath.setAttribute("clip-rule", fillRule); + } - /** - * ResetTransform resets the current transformation matrix to the identity matrix - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/resetTransform - */ - Context.prototype.resetTransform = function () { - this.setTransform(1, 0, 0, 1, 0, 0); - }; + clipPath.appendChild(pathElement); + + this.__defs.appendChild(clipPath); + + //set the clip path to this group + group.setAttribute("clip-path", format("url(#{id})", { id: id })); + + this.__currentElement = group; + }; + + /** + * Draws a canvas, image or mock context to this canvas. + * Note that all svg dom manipulation uses node.childNodes rather than node.children for IE support. + * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-drawimage + */ + Context.prototype.drawImage = function () { + //convert arguments to a real array + var args = Array.prototype.slice.call(arguments), + image = args[0], + dx, + dy, + dw, + dh, + sx = 0, + sy = 0, + sw, + sh, + parent, + svg, + defs, + group, + currentElement, + svgImage, + canvas, + context, + id; + + if (args.length === 3) { + dx = args[1]; + dy = args[2]; + sw = image.width; + sh = image.height; + dw = sw; + dh = sh; + } else if (args.length === 5) { + dx = args[1]; + dy = args[2]; + dw = args[3]; + dh = args[4]; + sw = image.width; + sh = image.height; + } else if (args.length === 9) { + sx = args[1]; + sy = args[2]; + sw = args[3]; + sh = args[4]; + dx = args[5]; + dy = args[6]; + dw = args[7]; + dh = args[8]; + } else { + throw new Error( + "Invalid number of arguments passed to drawImage: " + arguments.length + ); + } - /** - * Add the scaling transformation described by the arguments to the current transformation matrix. - * - * @param x The x argument represents the scale factor in the horizontal direction - * @param y The y argument represents the scale factor in the vertical direction. - * @see https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-scale - */ - Context.prototype.scale = function (x, y) { - if (y === undefined) { - y = x; + parent = this.__closestGroupOrSvg(); + currentElement = this.__currentElement; + const matrix = this.getTransform().translate(dx, dy); + if (image instanceof Context) { + //canvas2svg mock canvas context. In the future we may want to clone nodes instead. + //also I'm currently ignoring dw, dh, sw, sh, sx, sy for a mock context. + svg = image.getSvg().cloneNode(true); + if (svg.childNodes && svg.childNodes.length > 1) { + defs = svg.childNodes[0]; + while (defs.childNodes.length) { + id = defs.childNodes[0].getAttribute("id"); + this.__ids[id] = id; + this.__defs.appendChild(defs.childNodes[0]); } - // If either of the arguments are infinite or NaN, then return. - if (isNaN(x) || isNaN(y) || !isFinite(x) || !isFinite(y)) { - return + group = svg.childNodes[1]; + if (group) { + this.__applyTransformation(group, matrix); + parent.appendChild(group); } - let matrix = this.getTransform().scale(x, y); - this.setTransform(matrix); - }; - - /** - * Rotate adds a rotation to the transformation matrix. - * - * @param angle The rotation angle, clockwise in radians. You can use degree * Math.PI / 180 to calculate a radian from a degree. - * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate - * @see https://www.w3.org/TR/css-transforms-1 - */ - Context.prototype.rotate = function (angle) { - let matrix = this.getTransform().multiply(new DOMMatrix([ - Math.cos(angle), - Math.sin(angle), - -Math.sin(angle), - Math.cos(angle), - 0, - 0 - ])) - this.setTransform(matrix); - }; - - /** - * Translate adds a translation transformation to the current matrix. - * - * @param x Distance to move in the horizontal direction. Positive values are to the right, and negative to the left. - * @param y Distance to move in the vertical direction. Positive values are down, and negative are up. - * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate - */ - Context.prototype.translate = function (x, y) { - const matrix = this.getTransform().translate(x, y); - this.setTransform(matrix); - }; - - /** - * Transform multiplies the current transformation with the matrix described by the arguments of this method. - * This lets you scale, rotate, translate (move), and skew the context. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform - */ - Context.prototype.transform = function (a, b, c, d, e, f) { - const matrix = this.getTransform().multiply(new DOMMatrix([a, b, c, d, e, f])); - this.setTransform(matrix); - }; - - Context.prototype.__matrixTransform = function(x, y) { - return new DOMPoint(x, y).matrixTransform(this.__transformMatrix) + } + } else if (image.nodeName === "CANVAS" || image.nodeName === "IMG") { + //canvas or image + svgImage = this.__createElement("image"); + svgImage.setAttribute("width", dw); + svgImage.setAttribute("height", dh); + svgImage.setAttribute("preserveAspectRatio", "none"); + + if (sx || sy || sw !== image.width || sh !== image.height) { + //crop the image using a temporary canvas + canvas = this.__document.createElement("canvas"); + canvas.width = dw; + canvas.height = dh; + context = canvas.getContext("2d"); + context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh); + image = canvas; + } + this.__applyTransformation(svgImage, matrix); + svgImage.setAttributeNS( + "http://www.w3.org/1999/xlink", + "xlink:href", + image.nodeName === "CANVAS" + ? image.toDataURL() + : image.getAttribute("src") + ); + parent.appendChild(svgImage); } - - /** - * - * @returns The scale component of the transform matrix as {x,y}. - */ - Context.prototype.__getTransformScale = function() { - return { - x: Math.hypot(this.__transformMatrix.a, this.__transformMatrix.b), - y: Math.hypot(this.__transformMatrix.c, this.__transformMatrix.d) - }; + }; + + /** + * Generates a pattern tag + */ + Context.prototype.createPattern = function (image, repetition) { + var pattern = this.__document.createElementNS( + "http://www.w3.org/2000/svg", + "pattern" + ), + id = randomString(this.__ids), + img; + pattern.setAttribute("id", id); + pattern.setAttribute("width", image.width); + pattern.setAttribute("height", image.height); + // We want the pattern sizing to be absolute, and not relative + // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Patterns + // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternUnits + pattern.setAttribute("patternUnits", "userSpaceOnUse"); + + if (image.nodeName === "CANVAS" || image.nodeName === "IMG") { + img = this.__document.createElementNS( + "http://www.w3.org/2000/svg", + "image" + ); + img.setAttribute("width", image.width); + img.setAttribute("height", image.height); + img.setAttributeNS( + "http://www.w3.org/1999/xlink", + "xlink:href", + image.nodeName === "CANVAS" + ? image.toDataURL() + : image.getAttribute("src") + ); + pattern.appendChild(img); + this.__defs.appendChild(pattern); + } else if (image instanceof Context) { + pattern.appendChild(image.__root.childNodes[1]); + this.__defs.appendChild(pattern); } - - /** - * - * @returns The rotation component of the transform matrix in radians. - */ - Context.prototype.__getTransformRotation = function() { - return Math.atan2(this.__transformMatrix.b, this.__transformMatrix.a); + return new CanvasPattern(pattern, this); + }; + + Context.prototype.setLineDash = function (dashArray) { + if (dashArray && dashArray.length > 0) { + this.lineDash = dashArray.join(","); + } else { + this.lineDash = null; } - - /** - * - * @param {*} sx The x-axis coordinate of the top-left corner of the rectangle from which the ImageData will be extracted. - * @param {*} sy The y-axis coordinate of the top-left corner of the rectangle from which the ImageData will be extracted. - * @param {*} sw The width of the rectangle from which the ImageData will be extracted. Positive values are to the right, and negative to the left. - * @param {*} sh The height of the rectangle from which the ImageData will be extracted. Positive values are down, and negative are up. - * @param {Boolean} options.async Will return a Promise if true, must be set to true - * @returns An ImageData object containing the image data for the rectangle of the canvas specified. The coordinates of the rectangle's top-left corner are (sx, sy), while the coordinates of the bottom corner are (sx + sw, sy + sh). - */ - Context.prototype.getImageData = function(sx, sy, sw, sh, options) { - return imageUtils.getImageData(this.getSvg(), this.width, this.height, sx, sy, sw, sh, options); + }; + + /** + * SetTransform changes the current transformation matrix to + * the matrix given by the arguments as described below. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform + */ + Context.prototype.setTransform = function (a, b, c, d, e, f) { + if (a instanceof DOMMatrix) { + this.__transformMatrix = new DOMMatrix([a.a, a.b, a.c, a.d, a.e, a.f]); + } else { + this.__transformMatrix = new DOMMatrix([a, b, c, d, e, f]); + } + }; + + /** + * GetTransform Returns a copy of the current transformation matrix, + * as a newly created DOMMAtrix Object + * + * @returns A DOMMatrix Object + */ + Context.prototype.getTransform = function () { + let { a, b, c, d, e, f } = this.__transformMatrix; + return new DOMMatrix([a, b, c, d, e, f]); + }; + + /** + * ResetTransform resets the current transformation matrix to the identity matrix + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/resetTransform + */ + Context.prototype.resetTransform = function () { + this.setTransform(1, 0, 0, 1, 0, 0); + }; + + /** + * Add the scaling transformation described by the arguments to the current transformation matrix. + * + * @param x The x argument represents the scale factor in the horizontal direction + * @param y The y argument represents the scale factor in the vertical direction. + * @see https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-scale + */ + Context.prototype.scale = function (x, y) { + if (y === undefined) { + y = x; + } + // If either of the arguments are infinite or NaN, then return. + if (isNaN(x) || isNaN(y) || !isFinite(x) || !isFinite(y)) { + return; + } + let matrix = this.getTransform().scale(x, y); + this.setTransform(matrix); + }; + + /** + * Rotate adds a rotation to the transformation matrix. + * + * @param angle The rotation angle, clockwise in radians. You can use degree * Math.PI / 180 to calculate a radian from a degree. + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate + * @see https://www.w3.org/TR/css-transforms-1 + */ + Context.prototype.rotate = function (angle) { + let matrix = this.getTransform().multiply( + new DOMMatrix([ + Math.cos(angle), + Math.sin(angle), + -Math.sin(angle), + Math.cos(angle), + 0, + 0, + ]) + ); + this.setTransform(matrix); + }; + + /** + * Translate adds a translation transformation to the current matrix. + * + * @param x Distance to move in the horizontal direction. Positive values are to the right, and negative to the left. + * @param y Distance to move in the vertical direction. Positive values are down, and negative are up. + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/translate + */ + Context.prototype.translate = function (x, y) { + const matrix = this.getTransform().translate(x, y); + this.setTransform(matrix); + }; + + /** + * Transform multiplies the current transformation with the matrix described by the arguments of this method. + * This lets you scale, rotate, translate (move), and skew the context. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform + */ + Context.prototype.transform = function (a, b, c, d, e, f) { + const matrix = this.getTransform().multiply( + new DOMMatrix([a, b, c, d, e, f]) + ); + this.setTransform(matrix); + }; + + Context.prototype.__matrixTransform = function (x, y) { + return new DOMPoint(x, y).matrixTransform(this.__transformMatrix); + }; + + /** + * + * @returns The scale component of the transform matrix as {x,y}. + */ + Context.prototype.__getTransformScale = function () { + return { + x: Math.hypot(this.__transformMatrix.a, this.__transformMatrix.b), + y: Math.hypot(this.__transformMatrix.c, this.__transformMatrix.d), }; - - /** - * Not yet implemented - */ - Context.prototype.drawFocusRing = function () {}; - Context.prototype.createImageData = function () {}; - Context.prototype.putImageData = function () {}; - Context.prototype.globalCompositeOperation = function () {}; - - return Context; -}()); + }; + + /** + * + * @returns The rotation component of the transform matrix in radians. + */ + Context.prototype.__getTransformRotation = function () { + return Math.atan2(this.__transformMatrix.b, this.__transformMatrix.a); + }; + + /** + * + * @param {*} sx The x-axis coordinate of the top-left corner of the rectangle from which the ImageData will be extracted. + * @param {*} sy The y-axis coordinate of the top-left corner of the rectangle from which the ImageData will be extracted. + * @param {*} sw The width of the rectangle from which the ImageData will be extracted. Positive values are to the right, and negative to the left. + * @param {*} sh The height of the rectangle from which the ImageData will be extracted. Positive values are down, and negative are up. + * @param {Boolean} options.async Will return a Promise if true, must be set to true + * @returns An ImageData object containing the image data for the rectangle of the canvas specified. The coordinates of the rectangle's top-left corner are (sx, sy), while the coordinates of the bottom corner are (sx + sw, sy + sh). + */ + Context.prototype.getImageData = function (sx, sy, sw, sh, options) { + return imageUtils.getImageData( + this.getSvg(), + this.width, + this.height, + sx, + sy, + sw, + sh, + options + ); + }; + + /** + * Not yet implemented + */ + Context.prototype.drawFocusRing = function () {}; + Context.prototype.createImageData = function () {}; + Context.prototype.putImageData = function () {}; + Context.prototype.globalCompositeOperation = function () {}; + + return Context; +})(); diff --git a/package-lock.json b/package-lock.json index 84b9794..4a43335 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "svgcanvas", - "version": "2.5.0", + "name": "@aha-app/svgcanvas", + "version": "2.5.0-a11", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7d2fd6e..0526c8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aha-app/svgcanvas", - "version": "2.5.0-a6", + "version": "2.5.0-a11", "description": "svgcanvas", "main": "dist/svgcanvas.js", "scripts": { diff --git a/path2d.js b/path2d.js index a59e018..7885202 100644 --- a/path2d.js +++ b/path2d.js @@ -1,341 +1,386 @@ -import { format } from './utils'; +import { format } from "./utils"; export default (function () { - "use strict"; + "use strict"; - var Path2D; + var Path2D; - Path2D = function (ctx, arg) { - if (!ctx) { - console.error("Path2D must be passed the context"); - } - if (typeof arg === 'string') { - // Initialize from string path. - this.__pathString = arg; - } else if (typeof arg === 'object') { - // Initialize by copying another path. - this.__pathString = arg.__pathString; - } else { - // Initialize a new path. - this.__pathString = ""; - } - - this.ctx = ctx; - this.__currentPosition = {x: undefined, y: undefined}; + Path2D = function (ctx, arg) { + if (!ctx) { + console.error("Path2D must be passed the context"); } - - Path2D.prototype.__matrixTransform = function(x, y) { - return this.ctx.__matrixTransform(x, y); + if (typeof arg === "string") { + // Initialize from string path. + this.__pathString = arg; + } else if (typeof arg === "object") { + // Initialize by copying another path. + this.__pathString = arg.__pathString; + } else { + // Initialize a new path. + this.__pathString = ""; } - Path2D.prototype.addPath = function(path, transform) { - if (transform) console.error("transform argument to addPath is not supported"); + this.ctx = ctx; + this.__currentPosition = { x: undefined, y: undefined }; + }; - this.__pathString = this.__pathString + " " + path; - } + Path2D.prototype.__matrixTransform = function (x, y) { + return this.ctx.__matrixTransform(x, y); + }; - /** - * Closes the current path - */ - Path2D.prototype.closePath = function () { - this.addPath("Z"); - }; + Path2D.prototype.addPath = function (path, transform) { + if (transform) + console.error("transform argument to addPath is not supported"); - /** - * Adds the move command to the current path element, - * if the currentPathElement is not empty create a new path element - */ - Path2D.prototype.moveTo = function (x,y) { - // creates a new subpath with the given point - this.__currentPosition = {x: x, y: y}; - this.addPath(format("M {x} {y}", { - x: this.__matrixTransform(x, y).x, - y: this.__matrixTransform(x, y).y - })); - }; + this.__pathString = this.__pathString + " " + path; + }; + /** + * Closes the current path + */ + Path2D.prototype.closePath = function () { + this.addPath("Z"); + }; - /** - * Adds a line to command - */ - Path2D.prototype.lineTo = function (x, y) { - this.__currentPosition = {x: x, y: y}; - if (this.__pathString.indexOf('M') > -1) { - this.addPath(format("L {x} {y}", { - x: this.__matrixTransform(x, y).x, - y: this.__matrixTransform(x, y).y - })); - } else { - this.addPath(format("M {x} {y}", { - x: this.__matrixTransform(x, y).x, - y: this.__matrixTransform(x, y).y - })); - } - }; + /** + * Adds the move command to the current path element, + * if the currentPathElement is not empty create a new path element + */ + Path2D.prototype.moveTo = function (x, y) { + // creates a new subpath with the given point + this.__currentPosition = { x: x, y: y }; + this.addPath( + format("M {x} {y}", { + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y, + }) + ); + }; - /** - * Adds a rectangle to the path. - */ - Path2D.prototype.rect = function (x, y, width, height) { - if (this.__currentElement.nodeName !== "path") { - this.beginPath(); - } - this.moveTo(x, y); - this.lineTo(x+width, y); - this.lineTo(x+width, y+height); - this.lineTo(x, y+height); - this.lineTo(x, y); - this.closePath(); - }; + /** + * Adds a line to command + */ + Path2D.prototype.lineTo = function (x, y) { + this.__currentPosition = { x: x, y: y }; + if (this.__pathString.indexOf("M") > -1) { + this.addPath( + format("L {x} {y}", { + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y, + }) + ); + } else { + this.addPath( + format("M {x} {y}", { + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y, + }) + ); + } + }; - /** - * Add a bezier command - */ - Path2D.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { - this.__currentPosition = {x: x, y: y}; - this.addPath(format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", - { - cp1x: this.__matrixTransform(cp1x, cp1y).x, - cp1y: this.__matrixTransform(cp1x, cp1y).y, - cp2x: this.__matrixTransform(cp2x, cp2y).x, - cp2y: this.__matrixTransform(cp2x, cp2y).y, - x: this.__matrixTransform(x, y).x, - y: this.__matrixTransform(x, y).y - })); - }; + /** + * Adds a rectangle to the path. + */ + Path2D.prototype.rect = function (x, y, width, height) { + this.moveTo(x, y); + this.lineTo(x + width, y); + this.lineTo(x + width, y + height); + this.lineTo(x, y + height); + this.lineTo(x, y); + }; - /** - * Adds a quadratic curve to command - */ - Path2D.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { - this.__currentPosition = {x: x, y: y}; - this.addPath(format("Q {cpx} {cpy} {x} {y}", { - cpx: this.__matrixTransform(cpx, cpy).x, - cpy: this.__matrixTransform(cpx, cpy).y, - x: this.__matrixTransform(x, y).x, - y: this.__matrixTransform(x, y).y - })); - }; + /** + * Add a bezier command + */ + Path2D.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { + this.__currentPosition = { x: x, y: y }; + this.addPath( + format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", { + cp1x: this.__matrixTransform(cp1x, cp1y).x, + cp1y: this.__matrixTransform(cp1x, cp1y).y, + cp2x: this.__matrixTransform(cp2x, cp2y).x, + cp2y: this.__matrixTransform(cp2x, cp2y).y, + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y, + }) + ); + }; + /** + * Adds a quadratic curve to command + */ + Path2D.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { + this.__currentPosition = { x: x, y: y }; + this.addPath( + format("Q {cpx} {cpy} {x} {y}", { + cpx: this.__matrixTransform(cpx, cpy).x, + cpy: this.__matrixTransform(cpx, cpy).y, + x: this.__matrixTransform(x, y).x, + y: this.__matrixTransform(x, y).y, + }) + ); + }; + /** + * Arc command! + */ + Path2D.prototype.arc = function ( + x, + y, + radius, + startAngle, + endAngle, + counterClockwise + ) { + // in canvas no circle is drawn if no angle is provided. + if (startAngle === endAngle) { + return; + } + startAngle = startAngle % (2 * Math.PI); + endAngle = endAngle % (2 * Math.PI); + if (startAngle === endAngle) { + //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle) + endAngle = + (endAngle + 2 * Math.PI - 0.001 * (counterClockwise ? -1 : 1)) % + (2 * Math.PI); + } + var endX = x + radius * Math.cos(endAngle), + endY = y + radius * Math.sin(endAngle), + startX = x + radius * Math.cos(startAngle), + startY = y + radius * Math.sin(startAngle), + sweepFlag = counterClockwise ? 0 : 1, + largeArcFlag = 0, + diff = endAngle - startAngle; + + // https://github.com/gliffy/canvas2svg/issues/4 + if (diff < 0) { + diff += 2 * Math.PI; + } - /** - * Arc command! - */ - Path2D.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) { - // in canvas no circle is drawn if no angle is provided. - if (startAngle === endAngle) { - return; - } - startAngle = startAngle % (2*Math.PI); - endAngle = endAngle % (2*Math.PI); - if (startAngle === endAngle) { - //circle time! subtract some of the angle so svg is happy (svg elliptical arc can't draw a full circle) - endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI); - } - var endX = x+radius*Math.cos(endAngle), - endY = y+radius*Math.sin(endAngle), - startX = x+radius*Math.cos(startAngle), - startY = y+radius*Math.sin(startAngle), - sweepFlag = counterClockwise ? 0 : 1, - largeArcFlag = 0, - diff = endAngle - startAngle; - - // https://github.com/gliffy/canvas2svg/issues/4 - if (diff < 0) { - diff += 2*Math.PI; - } + if (counterClockwise) { + largeArcFlag = diff > Math.PI ? 0 : 1; + } else { + largeArcFlag = diff > Math.PI ? 1 : 0; + } - if (counterClockwise) { - largeArcFlag = diff > Math.PI ? 0 : 1; - } else { - largeArcFlag = diff > Math.PI ? 1 : 0; - } + var scaleX = Math.hypot( + this.ctx.__transformMatrix.a, + this.ctx.__transformMatrix.b + ); + var scaleY = Math.hypot( + this.ctx.__transformMatrix.c, + this.ctx.__transformMatrix.d + ); + + this.lineTo(startX, startY); + this.addPath( + format( + "A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", + { + rx: radius * scaleX, + ry: radius * scaleY, + xAxisRotation: 0, + largeArcFlag: largeArcFlag, + sweepFlag: sweepFlag, + endX: this.__matrixTransform(endX, endY).x, + endY: this.__matrixTransform(endX, endY).y, + } + ) + ); - var scaleX = Math.hypot(this.ctx.__transformMatrix.a, this.ctx.__transformMatrix.b); - var scaleY = Math.hypot(this.ctx.__transformMatrix.c, this.ctx.__transformMatrix.d); - - this.lineTo(startX, startY); - this.addPath(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", - { - rx:radius * scaleX, - ry:radius * scaleY, - xAxisRotation:0, - largeArcFlag:largeArcFlag, - sweepFlag:sweepFlag, - endX: this.__matrixTransform(endX, endY).x, - endY: this.__matrixTransform(endX, endY).y - })); - - this.__currentPosition = {x: endX, y: endY}; - }; + this.__currentPosition = { x: endX, y: endY }; + }; + /** + * Return a new normalized vector of given vector + */ + var normalize = function (vector) { + var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); + return [vector[0] / len, vector[1] / len]; + }; - /** - * Return a new normalized vector of given vector - */ - var normalize = function (vector) { - var len = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); - return [vector[0] / len, vector[1] / len]; - }; + /** + * Adds the arcTo to the current path + * + * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto + */ + Path2D.prototype.arcTo = function (x1, y1, x2, y2, radius) { + // Let the point (x0, y0) be the last point in the subpath. + var x0 = this.__currentPosition && this.__currentPosition.x; + var y0 = this.__currentPosition && this.__currentPosition.y; + + // First ensure there is a subpath for (x1, y1). + if (typeof x0 == "undefined" || typeof y0 == "undefined") { + return; + } - /** - * Adds the arcTo to the current path - * - * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto - */ - Path2D.prototype.arcTo = function (x1, y1, x2, y2, radius) { - // Let the point (x0, y0) be the last point in the subpath. - var x0 = this.__currentPosition && this.__currentPosition.x; - var y0 = this.__currentPosition && this.__currentPosition.y; - - // First ensure there is a subpath for (x1, y1). - if (typeof x0 == "undefined" || typeof y0 == "undefined") { - return; - } + // Negative values for radius must cause the implementation to throw an IndexSizeError exception. + if (radius < 0) { + throw new Error( + "IndexSizeError: The radius provided (" + radius + ") is negative." + ); + } - // Negative values for radius must cause the implementation to throw an IndexSizeError exception. - if (radius < 0) { - throw new Error("IndexSizeError: The radius provided (" + radius + ") is negative."); - } + // If the point (x0, y0) is equal to the point (x1, y1), + // or if the point (x1, y1) is equal to the point (x2, y2), + // or if the radius radius is zero, + // then the method must add the point (x1, y1) to the subpath, + // and connect that point to the previous point (x0, y0) by a straight line. + if ((x0 === x1 && y0 === y1) || (x1 === x2 && y1 === y2) || radius === 0) { + this.lineTo(x1, y1); + return; + } - // If the point (x0, y0) is equal to the point (x1, y1), - // or if the point (x1, y1) is equal to the point (x2, y2), - // or if the radius radius is zero, - // then the method must add the point (x1, y1) to the subpath, - // and connect that point to the previous point (x0, y0) by a straight line. - if (((x0 === x1) && (y0 === y1)) - || ((x1 === x2) && (y1 === y2)) - || (radius === 0)) { - this.lineTo(x1, y1); - return; - } + // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line, + // then the method must add the point (x1, y1) to the subpath, + // and connect that point to the previous point (x0, y0) by a straight line. + var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); + var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); + if ( + unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === + unit_vec_p1_p0[1] * unit_vec_p1_p2[0] + ) { + this.lineTo(x1, y1); + return; + } - // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line, - // then the method must add the point (x1, y1) to the subpath, - // and connect that point to the previous point (x0, y0) by a straight line. - var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); - var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); - if (unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === unit_vec_p1_p0[1] * unit_vec_p1_p2[0]) { - this.lineTo(x1, y1); - return; + // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius, + // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1), + // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2). + // The points at which this circle touches these two lines are called the start and end tangent points respectively. + + // note that both vectors are unit vectors, so the length is 1 + var cos = + unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + + unit_vec_p1_p0[1] * unit_vec_p1_p2[1]; + var theta = Math.acos(Math.abs(cos)); + + // Calculate origin + var unit_vec_p1_origin = normalize([ + unit_vec_p1_p0[0] + unit_vec_p1_p2[0], + unit_vec_p1_p0[1] + unit_vec_p1_p2[1], + ]); + var len_p1_origin = radius / Math.sin(theta / 2); + var x = x1 + len_p1_origin * unit_vec_p1_origin[0]; + var y = y1 + len_p1_origin * unit_vec_p1_origin[1]; + + // Calculate start angle and end angle + // rotate 90deg clockwise (note that y axis points to its down) + var unit_vec_origin_start_tangent = [-unit_vec_p1_p0[1], unit_vec_p1_p0[0]]; + // rotate 90deg counter clockwise (note that y axis points to its down) + var unit_vec_origin_end_tangent = [unit_vec_p1_p2[1], -unit_vec_p1_p2[0]]; + var getAngle = function (vector) { + // get angle (clockwise) between vector and (1, 0) + var x = vector[0]; + var y = vector[1]; + if (y >= 0) { + // note that y axis points to its down + return Math.acos(x); + } else { + return -Math.acos(x); } - - // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius, - // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1), - // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2). - // The points at which this circle touches these two lines are called the start and end tangent points respectively. - - // note that both vectors are unit vectors, so the length is 1 - var cos = (unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + unit_vec_p1_p0[1] * unit_vec_p1_p2[1]); - var theta = Math.acos(Math.abs(cos)); - - // Calculate origin - var unit_vec_p1_origin = normalize([ - unit_vec_p1_p0[0] + unit_vec_p1_p2[0], - unit_vec_p1_p0[1] + unit_vec_p1_p2[1] - ]); - var len_p1_origin = radius / Math.sin(theta / 2); - var x = x1 + len_p1_origin * unit_vec_p1_origin[0]; - var y = y1 + len_p1_origin * unit_vec_p1_origin[1]; - - // Calculate start angle and end angle - // rotate 90deg clockwise (note that y axis points to its down) - var unit_vec_origin_start_tangent = [ - -unit_vec_p1_p0[1], - unit_vec_p1_p0[0] - ]; - // rotate 90deg counter clockwise (note that y axis points to its down) - var unit_vec_origin_end_tangent = [ - unit_vec_p1_p2[1], - -unit_vec_p1_p2[0] - ]; - var getAngle = function (vector) { - // get angle (clockwise) between vector and (1, 0) - var x = vector[0]; - var y = vector[1]; - if (y >= 0) { // note that y axis points to its down - return Math.acos(x); - } else { - return -Math.acos(x); - } - }; - var startAngle = getAngle(unit_vec_origin_start_tangent); - var endAngle = getAngle(unit_vec_origin_end_tangent); - - // Connect the point (x0, y0) to the start tangent point by a straight line - this.lineTo(x + unit_vec_origin_start_tangent[0] * radius, - y + unit_vec_origin_start_tangent[1] * radius); - - // Connect the start tangent point to the end tangent point by arc - // and adding the end tangent point to the subpath. - this.arc(x, y, radius, startAngle, endAngle); + }; + var startAngle = getAngle(unit_vec_origin_start_tangent); + var endAngle = getAngle(unit_vec_origin_end_tangent); + + // Connect the point (x0, y0) to the start tangent point by a straight line + this.lineTo( + x + unit_vec_origin_start_tangent[0] * radius, + y + unit_vec_origin_start_tangent[1] * radius + ); + + // Connect the start tangent point to the end tangent point by arc + // and adding the end tangent point to the subpath. + this.arc(x, y, radius, startAngle, endAngle); }; - /** * Ellipse command! */ - Path2D.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterClockwise) { - if (startAngle === endAngle) { - return; - } + Path2D.prototype.ellipse = function ( + x, + y, + radiusX, + radiusY, + rotation, + startAngle, + endAngle, + counterClockwise + ) { + if (startAngle === endAngle) { + return; + } - var transformedCenter = this.__matrixTransform(x, y); - x = transformedCenter.x; - y = transformedCenter.y; - var scale = this.ctx.__getTransformScale(); - radiusX = radiusX * scale.x; - radiusY = radiusY * scale.y; - rotation = rotation + this.ctx.__getTransformRotation() - - startAngle = startAngle % (2*Math.PI); - endAngle = endAngle % (2*Math.PI); - if(startAngle === endAngle) { - endAngle = ((endAngle + (2*Math.PI)) - 0.001 * (counterClockwise ? -1 : 1)) % (2*Math.PI); - } - var endX = x + Math.cos(-rotation) * radiusX * Math.cos(endAngle) - + Math.sin(-rotation) * radiusY * Math.sin(endAngle), - endY = y - Math.sin(-rotation) * radiusX * Math.cos(endAngle) - + Math.cos(-rotation) * radiusY * Math.sin(endAngle), - startX = x + Math.cos(-rotation) * radiusX * Math.cos(startAngle) - + Math.sin(-rotation) * radiusY * Math.sin(startAngle), - startY = y - Math.sin(-rotation) * radiusX * Math.cos(startAngle) - + Math.cos(-rotation) * radiusY * Math.sin(startAngle), - sweepFlag = counterClockwise ? 0 : 1, - largeArcFlag = 0, - diff = endAngle - startAngle; - - if(diff < 0) { - diff += 2*Math.PI; - } + var transformedCenter = this.__matrixTransform(x, y); + x = transformedCenter.x; + y = transformedCenter.y; + var scale = this.ctx.__getTransformScale(); + radiusX = radiusX * scale.x; + radiusY = radiusY * scale.y; + rotation = rotation + this.ctx.__getTransformRotation(); + + startAngle = startAngle % (2 * Math.PI); + endAngle = endAngle % (2 * Math.PI); + if (startAngle === endAngle) { + endAngle = + (endAngle + 2 * Math.PI - 0.001 * (counterClockwise ? -1 : 1)) % + (2 * Math.PI); + } + var endX = + x + + Math.cos(-rotation) * radiusX * Math.cos(endAngle) + + Math.sin(-rotation) * radiusY * Math.sin(endAngle), + endY = + y - + Math.sin(-rotation) * radiusX * Math.cos(endAngle) + + Math.cos(-rotation) * radiusY * Math.sin(endAngle), + startX = + x + + Math.cos(-rotation) * radiusX * Math.cos(startAngle) + + Math.sin(-rotation) * radiusY * Math.sin(startAngle), + startY = + y - + Math.sin(-rotation) * radiusX * Math.cos(startAngle) + + Math.cos(-rotation) * radiusY * Math.sin(startAngle), + sweepFlag = counterClockwise ? 0 : 1, + largeArcFlag = 0, + diff = endAngle - startAngle; + + if (diff < 0) { + diff += 2 * Math.PI; + } - if(counterClockwise) { - largeArcFlag = diff > Math.PI ? 0 : 1; - } else { - largeArcFlag = diff > Math.PI ? 1 : 0; - } + if (counterClockwise) { + largeArcFlag = diff > Math.PI ? 0 : 1; + } else { + largeArcFlag = diff > Math.PI ? 1 : 0; + } + + // Transform is already applied, so temporarily remove since lineTo + // will apply it again. + var currentTransform = this.ctx.__transformMatrix; + this.ctx.resetTransform(); + this.lineTo(startX, startY); + this.ctx.__transformMatrix = currentTransform; + + this.addPath( + format( + "A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", + { + rx: radiusX, + ry: radiusY, + xAxisRotation: rotation * (180 / Math.PI), + largeArcFlag: largeArcFlag, + sweepFlag: sweepFlag, + endX: endX, + endY: endY, + } + ) + ); - // Transform is already applied, so temporarily remove since lineTo - // will apply it again. - var currentTransform = this.ctx.__transformMatrix; - this.ctx.resetTransform(); - this.lineTo(startX, startY); - this.ctx.__transformMatrix = currentTransform; - - this.addPath(format("A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", - { - rx:radiusX, - ry:radiusY, - xAxisRotation:rotation*(180/Math.PI), - largeArcFlag:largeArcFlag, - sweepFlag:sweepFlag, - endX:endX, - endY:endY - })); - - this.__currentPosition = {x: endX, y: endY}; + this.__currentPosition = { x: endX, y: endY }; }; return Path2D; -}()); +})(); diff --git a/test/tests/clip.js b/test/tests/clip.js index 086b38c..aa9a3f5 100644 --- a/test/tests/clip.js +++ b/test/tests/clip.js @@ -19,7 +19,7 @@ export default function clip(ctx) { ctx.beginPath(); ctx.rect(20, 30, 30, 10); ctx.rect(0, 0, 300, 300); - // ctx.stroke(); // Uncomment for debugging clip. + ctx.stroke(); ctx.clip("evenodd"); // Draw line. From 7fe0aeaaeadfd49fe3cee3a81bd571985f308295 Mon Sep 17 00:00:00 2001 From: k1w1 Date: Thu, 29 Dec 2022 15:30:59 -0800 Subject: [PATCH 07/12] Force all images to be inline bitmaps. Otherwise the SVGs we generate can't be opened by editing tools like Figma. --- context.js | 17 ++++++++--------- package.json | 2 +- test/index.js | 2 ++ test/tests/image.js | 18 ++++++++++++++++++ 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 test/tests/image.js diff --git a/context.js b/context.js index 92f91ea..7215ac2 100644 --- a/context.js +++ b/context.js @@ -1128,15 +1128,14 @@ export default (function () { svgImage.setAttribute("height", dh); svgImage.setAttribute("preserveAspectRatio", "none"); - if (sx || sy || sw !== image.width || sh !== image.height) { - //crop the image using a temporary canvas - canvas = this.__document.createElement("canvas"); - canvas.width = dw; - canvas.height = dh; - context = canvas.getContext("2d"); - context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh); - image = canvas; - } + // Crop the image using a temporary canvas and convert to a bitmap + canvas = this.__document.createElement("canvas"); + canvas.width = dw; + canvas.height = dh; + context = canvas.getContext("2d"); + context.drawImage(image, sx, sy, sw, sh, 0, 0, dw, dh); + image = canvas; + this.__applyTransformation(svgImage, matrix); svgImage.setAttributeNS( "http://www.w3.org/1999/xlink", diff --git a/package.json b/package.json index 0526c8f..7d44f05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aha-app/svgcanvas", - "version": "2.5.0-a11", + "version": "2.5.0-a12", "description": "svgcanvas", "main": "dist/svgcanvas.js", "scripts": { diff --git a/test/index.js b/test/index.js index b04704d..8e63def 100644 --- a/test/index.js +++ b/test/index.js @@ -9,6 +9,7 @@ import ellipse2 from "./tests/ellipse2"; import fillstyle from "./tests/fillstyle"; import globalAlpha from "./tests/globalalpha"; import gradient from "./tests/gradient"; +import image from "./tests/image"; import linecap from "./tests/linecap"; import linewidth from "./tests/linewidth"; import scaledLine from "./tests/scaledLine"; @@ -36,6 +37,7 @@ const tests = [ fillstyle, globalAlpha, gradient, + image, linecap, linewidth, scaledLine, diff --git a/test/tests/image.js b/test/tests/image.js new file mode 100644 index 0000000..9276804 --- /dev/null +++ b/test/tests/image.js @@ -0,0 +1,18 @@ +export default function image(ctx) { + // Create an image based on a URL + var img = new Image(); + img.src = + "https://dl.dropboxusercontent.com/s/m76annmw0nzz5fx/Unknown.jpg?dl=1"; + img.crossOrigin = "anonymous"; + img.onload = function () { + ctx.drawImage(img, 20, 20, 30, 30); + }; + + // Create an image based on a foreignObject block. + var img2 = new Image(); + img2.src = + "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22164%22%20height%3D%2260%22%20data-reactroot%3D%22%22%3E%3Cstyle%3E%0A%20%20%20%20%20%20%3Aroot%20%7B%0A%20%20%20%20%20%20%20%20--theme-link-text%3A%20rgb(0%2C%20115%2C%20207)%3B%0A%20%20%20%20%20%20%20%20--theme-primary-border%3A%20rgb(205%2C%20205%2C%20205)%3B%0A%20%20%20%20%20%20%20%20--theme-light-border%3A%20rgb(241%2C%20241%2C%20241)%3B%0A%20%20%20%20%20%20%20%20--aha-yellow-400%3A%20rgb(255%2C%20228%2C%20142)%3B%0A%20%20%20%20%20%20%20%20--aha-gray-700%3A%20rgb(150%2C%20153%2C%20153)%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20--aha-orange-600%3A%20rgb(249%2C%20147%2C%2025)%3B%0A%20%20%20%20%20%20%20%20--aha-orange-800%3A%20rgb(199%2C%20109%2C%200)%3B%0A%20%20%20%20%20%20%20%20--aha-red-600%3A%20rgb(235%2C%20121%2C%2087)%3B%0A%20%20%20%20%20%20%20%20--aha-blue-500%3A%20rgb(0%2C%20115%2C%20207)%3B%0A%20%20%20%20%20%20%20%20--theme-green-text%3A%20rgb(79%2C%20143%2C%2014)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%3C!--%20--%3E._editorContainer_19k9u_1%20%7B%0A%20%20display%3A%20block%3B%0A%7D%0A%0A._editor_19k9u_1%20%7B%0A%20%20display%3A%20flex%3B%0A%20%20flex-direction%3A%20column%3B%0A%20%20width%3A%20min-content%3B%0A%20%20overflow%3A%20hidden%3B%0A%20%20white-space%3A%20pre-wrap%3B%0A%20%20overflow-wrap%3A%20break-word%3B%0A%0A%20%20%2F*%20Default%20font%20*%2F%0A%20%20font-family%3A%20sans-serif%3B%0A%20%20font-size%3A%2014px%3B%0A%20%20line-height%3A%201.4%3B%0A%7D%0A._editor_19k9u_1._autoGrow_19k9u_18%2C%0A._editor_19k9u_1._autoGrow_19k9u_18%20.aha-editor-body%20%7B%0A%20%20white-space%3A%20pre%20!important%3B%0A%7D%0A._editor_19k9u_1._editing_19k9u_22%20%7B%0A%20%20overflow%3A%20visible%3B%0A%7D%0A%0A._editor_19k9u_1%20._content_19k9u_26%20%7B%0A%20%20margin%3A%20auto%3B%0A%20%20width%3A%20100%25%3B%0A%7D%0A%0A._editor_19k9u_1%20.aha-editor__pre-container%20%7B%0A%20%20position%3A%20relative%3B%0A%7D%0A%0A._editor_19k9u_1%20.aha-editor-context-toolbar-btn%20%7B%0A%20%20position%3A%20absolute%3B%0A%20%20right%3A%20-5px%3B%0A%20%20top%3A%203px%3B%0A%20%20z-index%3A%209999%3B%0A%20%20height%3A%2016px%3B%0A%7D%0A%0A._editor_19k9u_1%0A%20%20.aha-editor-context-toolbar-btn%3Ahover%0A%20%20.aha-editor-toolbar-btn%20%7B%0A%20%20background-color%3A%20var(--theme-editor-button-hovered)%20!important%3B%0A%20%20color%3A%20var(--aha-gray-900)%20!important%3B%0A%20%20cursor%3A%20pointer%3B%0A%7D%0A%0A._editor_19k9u_1%20.aha-editor-context-toolbar-btn%20button%20%7B%0A%20%20border%3A%20none%3B%0A%20%20border-radius%3A%204px%3B%0A%20%20padding%3A%200%3B%0A%20%20background-color%3A%20var(--theme-tertiary-background)%3B%0A%7D%0A%0A._editor_19k9u_1%20.aha-editor-context-toolbar-btn%20i%20%7B%0A%20%20margin-right%3A%205px%3B%0A%7D%0A%0A%2F*%0A%20*%20Text%20styles.%0A%20*%2F%0A._editor_19k9u_1%20p%2C%0A._editor_19k9u_1%20pre%20%7B%0A%20%20margin%3A%200%3B%0A%7D%0A%0A._editor_19k9u_1%20.align-left%20%7B%0A%20%20text-align%3A%20left%3B%0A%7D%0A%0A._editor_19k9u_1%20.align-center%20%7B%0A%20%20text-align%3A%20center%3B%0A%7D%0A%0A._editor_19k9u_1%20.align-right%20%7B%0A%20%20text-align%3A%20right%3B%0A%7D%0A%0A._editor_19k9u_1%20.align-justify%20%7B%0A%20%20text-align%3A%20justify%3B%0A%7D%0A%0A._editor_19k9u_1%20.inline-highlight%20%7B%0A%20%20background-color%3A%20var(--aha-yellow-400)%3B%0A%7D%0A%0A._editor_19k9u_1%20a%20%7B%0A%20%20word-wrap%3A%20break-word%3B%0A%7D%0A%0A._editor_19k9u_1%20code%20%7B%0A%20%20font-size%3A%20inherit%3B%0A%7D%0A%0A._editor_19k9u_1%20ul%2C%0A._editor_19k9u_1%20ol%20%7B%0A%20%20padding%3A%200%3B%0A%20%20list-style-position%3A%20inside%3B%0A%7D%0A._editor_19k9u_1%20ul%20%7B%0A%20%20margin%3A%200%200%200%201em%3B%0A%7D%0A._editor_19k9u_1%20ol%20%7B%0A%20%20margin%3A%200%200%200%202em%3B%20%2F*%20More%20space%20for%202%20digit%20numbers%20*%2F%0A%7D%0A._editor_19k9u_1%20ul%20p%2C%0A._editor_19k9u_1%20ol%20p%20%7B%0A%20%20text-align%3A%20left%20!important%3B%0A%7D%0A%0A._editor_19k9u_1%20ul%20%7B%0A%20%20list-style-type%3A%20disc%3B%0A%7D%0A%0A._editor_19k9u_1%20ul.checklist%20%7B%0A%20%20list-style-type%3A%20none%3B%0A%7D%0A%0A._editor_19k9u_1%20li.checklist-item--checked%2C%0A._editor_19k9u_1%20li.checklist-item--unchecked%20%7B%0A%20%20list-style-type%3A%20none%3B%0A%7D%0A%0A._editor_19k9u_1%20li%20%7B%0A%20%20box-sizing%3A%20border-box%3B%0A%20%20line-height%3A%201.4%3B%20%2F*%20Must%20be%20same%20as%20%26lt%3Bp%26gt%3B%20line-height.%20*%2F%0A%20%20list-style-position%3A%20outside%3B%0A%20%20font-style%3A%20normal%3B%0A%20%20font-weight%3A%20normal%3B%0A%20%20padding%3A%200%3B%0A%20%20margin%3A%200%3B%0A%7D%0A%0A._editor_19k9u_1%20li.checkbox%20%7B%0A%20%20padding-left%3A%200%3B%0A%20%20min-height%3A%20auto%3B%0A%7D%0A%0A._editor_19k9u_1%20li.checkbox%20input%5Btype%3D%26%23x27%3Bcheckbox%26%23x27%3B%5D%20%7B%0A%20%20margin%3A%203px%200%200%20-18px%3B%0A%20%20position%3A%20absolute%3B%0A%20%20margin-top%3A%200%3B%0A%20%20margin-left%3A%20-15px%3B%0A%7D%0A%0A._editor_19k9u_1%20li%20.checklist-item__indicator%20%7B%0A%20%20margin%3A%200%3B%0A%20%20position%3A%20absolute%3B%0A%20%20margin-left%3A%20-1.5ch%3B%0A%7D%0A%0A._editor_19k9u_1%20code%20%7B%0A%20%20white-space%3A%20inherit%3B%0A%7D%0A%0A._editor_19k9u_1%20table%20%7B%0A%20%20width%3A%20100%25%3B%0A%20%20word-wrap%3A%20break-word%3B%0A%20%20table-layout%3A%20fixed%3B%0A%7D%0A%0A._editor_19k9u_1%20td%20%7B%0A%20%20position%3A%20relative%3B%0A%20%20border%3A%201px%20solid%20var(--theme-primary-border)%3B%0A%20%20padding%3A%208px%3B%0A%20%20vertical-align%3A%20top%3B%0A%7D%0A%0A._editor_19k9u_1%20blockquote%20%7B%0A%20%20padding%3A%203px%200%203px%2010px%3B%0A%20%20border-left%3A%204px%20solid%20var(--theme-light-border)%3B%0A%7D%0A%0A._editor_19k9u_1%20a%20%7B%0A%20%20color%3A%20var(--theme-link-text)%3B%0A%20%20text-decoration%3A%20underline%3B%0A%7D%0A%0A._editor_19k9u_1%20code%20%7B%0A%20%20padding%3A%202px%204px%3B%0A%20%20color%3A%20%23d14%3B%0A%20%20background-color%3A%20%23f7f7f9%3B%0A%20%20border%3A%201px%20solid%20%23e1e1e8%3B%0A%20%20border-radius%3A%203px%3B%0A%7D%0A%0A._editor_19k9u_1%20pre%20%7B%0A%20%20padding%3A%202px%204px%3B%0A%20%20margin%3A%200%3B%0A%20%20display%3A%20block%3B%0A%20%20background-color%3A%20%23f7f7f9%3B%0A%20%20border%3A%201px%20solid%20%23e1e1e8%3B%0A%20%20border-radius%3A%203px%3B%0A%20%20word-break%3A%20break-all%3B%0A%20%20white-space%3A%20break-spaces%3B%0A%7D%0A._editor_19k9u_1%20pre%20code%20%7B%0A%20%20padding%3A%200%3B%0A%20%20color%3A%20inherit%3B%0A%20%20background-color%3A%20transparent%3B%0A%20%20border%3A%200%3B%0A%7D%0A%0A%2F*%20prism.js%20styles%20*%2F%0A._editor_19k9u_1%20.token.comment%2C%0A._editor_19k9u_1%20.token.prolog%2C%0A._editor_19k9u_1%20.token.doctype%2C%0A._editor_19k9u_1%20.token.cdata%20%7B%0A%20%20color%3A%20slategray%3B%0A%7D%0A%0A._editor_19k9u_1%20.token.punctuation%20%7B%0A%20%20color%3A%20%23999%3B%0A%7D%0A%0A._editor_19k9u_1%20.token.namespace%20%7B%0A%20%20opacity%3A%200.7%3B%0A%7D%0A%0A._editor_19k9u_1%20.token.property%2C%0A._editor_19k9u_1%20.token.tag%2C%0A._editor_19k9u_1%20token.boolean%2C%0A._editor_19k9u_1%20.token.number%2C%0A._editor_19k9u_1%20.token.constant%2C%0A._editor_19k9u_1%20.token.symbol%2C%0A._editor_19k9u_1%20.token.deleted%20%7B%0A%20%20color%3A%20%23905%3B%0A%7D%0A%0A._editor_19k9u_1%20.token.selector%2C%0A._editor_19k9u_1%20.token.attr-name%2C%0A._editor_19k9u_1%20.token.string%2C%0A._editor_19k9u_1%20.token.char%2C%0A._editor_19k9u_1%20.token.builtin%2C%0A._editor_19k9u_1%20.token.inserted%20%7B%0A%20%20color%3A%20var(--theme-green-text)%3B%0A%7D%0A%0A._editor_19k9u_1%20.token.operator%2C%0A._editor_19k9u_1%20.token.entity%2C%0A._editor_19k9u_1%20.token.url%2C%0A._editor_19k9u_1%20.language-css%20.token.string%2C%0A._editor_19k9u_1%20.style%20.token.string%20%7B%0A%20%20color%3A%20var(--aha-orange-800)%3B%0A%20%20%2F*%20This%20background%20color%20was%20intended%20by%20the%20author%20of%20this%20theme.%20*%2F%0A%20%20background%3A%20hsla(0%2C%200%25%2C%20100%25%2C%200.5)%3B%0A%7D%0A%0A._editor_19k9u_1%20.token.atrule%2C%0A._editor_19k9u_1%20.token.attr-value%2C%0A._editor_19k9u_1%20.token.keyword%20%7B%0A%20%20color%3A%20var(--aha-blue-500)%3B%0A%7D%0A%0A._editor_19k9u_1%20.token.function%2C%0A._editor_19k9u_1%20.token.class-name%20%7B%0A%20%20color%3A%20var(--aha-red-600)%3B%0A%7D%0A%0A._editor_19k9u_1%20.token.regex%2C%0A._editor_19k9u_1%20.token.important%2C%0A._editor_19k9u_1%20.token.variable%20%7B%0A%20%20color%3A%20var(--aha-orange-600)%3B%0A%7D%0A%0A._editor_19k9u_1%20.token.important%2C%0A._editor_19k9u_1%20.token.bold%20%7B%0A%20%20font-weight%3A%20bold%3B%0A%7D%0A._editor_19k9u_1%20.token.italic%20%7B%0A%20%20font-style%3A%20italic%3B%0A%7D%0A%0A._editor_19k9u_1%20.frameText%20%7B%0A%20%20color%3A%20var(--aha-gray-700)%3B%0A%7D%0A%3C%2Fstyle%3E%3CforeignObject%20width%3D%22100%25%22%20height%3D%22100%25%22%3E%3Cdiv%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%20style%3D%22min-height%3A30px%3Bwidth%3A82px%3Bfont-size%3A14px%3Bbox-sizing%3Aborder-box%3Bpadding%3A5px%205px%205px%205px%3Btransform-origin%3Atop%20left%3Btransform%3Ascale(2)%22%20class%3D%22_editor_19k9u_1%20_autoGrow_19k9u_18%22%3E%3Cdiv%20class%3D%22_content_19k9u_26%22%3E%3Cp%20style%3D%22text-align%3Acenter%22%3EHello%20World%3C%2Fp%3E%3C%2Fdiv%3E%3C%2Fdiv%3E%3C%2FforeignObject%3E%3C%2Fsvg%3E"; + img2.onload = function () { + ctx.drawImage(img2, 20, 100, 60, 30); + }; +} From 9d7c7bae3e5911b0dbd5982095774b3fd4e19d71 Mon Sep 17 00:00:00 2001 From: k1w1 Date: Thu, 9 Mar 2023 17:15:52 -0800 Subject: [PATCH 08/12] Fixed arcTo implementation. Previously it only worked in the orientation in the test, but not for lines curving in other directions. --- README.md | 13 +++++ package-lock.json | 2 +- package.json | 2 +- path2d.js | 114 +++++++++++++++++++++---------------------- test/tests/arcTo.js | 22 +++++++-- test/tests/arcTo2.js | 13 +++++ 6 files changed, 103 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index b26448c..f73456c 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,19 @@ const mySerializedSVG = ctx.getSerializedSvg(); https://zenozeng.github.io/p5.js-svg/test/ +To run the testsuite: + +``` +npm run test +``` + +To debug tests in a browser: + +``` +open test/index.html +npm run watch +``` + ## License This library is licensed under the MIT license. diff --git a/package-lock.json b/package-lock.json index 4a43335..23ede9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aha-app/svgcanvas", - "version": "2.5.0-a11", + "version": "2.5.0-a12", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7d44f05..a71d22b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aha-app/svgcanvas", - "version": "2.5.0-a12", + "version": "2.5.0-a13", "description": "svgcanvas", "main": "dist/svgcanvas.js", "scripts": { diff --git a/path2d.js b/path2d.js index 7885202..f09bb19 100644 --- a/path2d.js +++ b/path2d.js @@ -201,7 +201,8 @@ export default (function () { }; /** - * Adds the arcTo to the current path + * Adds the arcTo to the current path. Based on Webkit implementation from + * https://github.com/WebKit/webkit/blob/main/Source/WebCore/platform/graphics/cairo/PathCairo.cpp * * @see http://www.w3.org/TR/2015/WD-2dcontext-20150514/#dom-context-2d-arcto */ @@ -232,67 +233,66 @@ export default (function () { return; } - // Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line, - // then the method must add the point (x1, y1) to the subpath, - // and connect that point to the previous point (x0, y0) by a straight line. - var unit_vec_p1_p0 = normalize([x0 - x1, y0 - y1]); - var unit_vec_p1_p2 = normalize([x2 - x1, y2 - y1]); - if ( - unit_vec_p1_p0[0] * unit_vec_p1_p2[1] === - unit_vec_p1_p0[1] * unit_vec_p1_p2[0] - ) { + const p1p0 = [x0 - x1, y0 - y1]; + const p1p2 = [x2 - x1, y2 - y1]; + const p1p0_length = Math.hypot(p1p0[0], p1p0[1]); + const p1p2_length = Math.hypot(p1p2[0], p1p2[1]); + const cos_phi = (p1p0[0] * p1p2[0] + p1p0[1] * p1p2[1]) / (p1p0_length * p1p2_length); + // all points on a line logic + if (cos_phi == -1) { this.lineTo(x1, y1); return; } + if (cos_phi == 1) { + // add infinite far away point + const max_length = 65535; + const factor_max = max_length / p1p0_length; + const ep = [xp0 + factor_max * p1p0[0], y0 + factor_max * p1p0[1]]; + this.lineTo(ep[0], ep[1]); + return; + } - // Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius, - // and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1), - // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1), and crosses the point (x2, y2). - // The points at which this circle touches these two lines are called the start and end tangent points respectively. - - // note that both vectors are unit vectors, so the length is 1 - var cos = - unit_vec_p1_p0[0] * unit_vec_p1_p2[0] + - unit_vec_p1_p0[1] * unit_vec_p1_p2[1]; - var theta = Math.acos(Math.abs(cos)); - - // Calculate origin - var unit_vec_p1_origin = normalize([ - unit_vec_p1_p0[0] + unit_vec_p1_p2[0], - unit_vec_p1_p0[1] + unit_vec_p1_p2[1], - ]); - var len_p1_origin = radius / Math.sin(theta / 2); - var x = x1 + len_p1_origin * unit_vec_p1_origin[0]; - var y = y1 + len_p1_origin * unit_vec_p1_origin[1]; - - // Calculate start angle and end angle - // rotate 90deg clockwise (note that y axis points to its down) - var unit_vec_origin_start_tangent = [-unit_vec_p1_p0[1], unit_vec_p1_p0[0]]; - // rotate 90deg counter clockwise (note that y axis points to its down) - var unit_vec_origin_end_tangent = [unit_vec_p1_p2[1], -unit_vec_p1_p2[0]]; - var getAngle = function (vector) { - // get angle (clockwise) between vector and (1, 0) - var x = vector[0]; - var y = vector[1]; - if (y >= 0) { - // note that y axis points to its down - return Math.acos(x); - } else { - return -Math.acos(x); - } - }; - var startAngle = getAngle(unit_vec_origin_start_tangent); - var endAngle = getAngle(unit_vec_origin_end_tangent); - - // Connect the point (x0, y0) to the start tangent point by a straight line - this.lineTo( - x + unit_vec_origin_start_tangent[0] * radius, - y + unit_vec_origin_start_tangent[1] * radius - ); + const tangent = radius / Math.tan(Math.acos(cos_phi) / 2); + const factor_p1p0 = tangent / p1p0_length; + const t_p1p0 = [x1 + factor_p1p0 * p1p0[0], y1 + factor_p1p0 * p1p0[1]]; + + let orth_p1p0 = [p1p0[1], -p1p0[0]]; + const orth_p1p0_length = Math.hypot(orth_p1p0[0], orth_p1p0[1]); + const factor_ra = radius / orth_p1p0_length; + + // angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0 + const cos_alpha = (orth_p1p0[0] * p1p2[0] + orth_p1p0[1] * p1p2[1]) / (orth_p1p0_length * p1p2_length); + if (cos_alpha < 0) { + orth_p1p0 = [-orth_p1p0[0], -orth_p1p0[1]]; + } + + const p = [t_p1p0[0] + factor_ra * orth_p1p0[0], t_p1p0[1] + factor_ra * orth_p1p0[1]]; + + // calculate angles for addArc + orth_p1p0 = [-orth_p1p0[0], -orth_p1p0[1]]; + let sa = Math.acos(orth_p1p0[0] / orth_p1p0_length); + if (orth_p1p0[1] < 0) { + sa = 2 * Math.PI - sa; + } + + // anticlockwise logic + let anticlockwise = false; + + const factor_p1p2 = tangent / p1p2_length; + const t_p1p2 = [x1 + factor_p1p2 * p1p2[0], y1 + factor_p1p2 * p1p2[1]]; + const orth_p1p2 = [t_p1p2[0] - p[0], t_p1p2[1] - p[1]]; + const orth_p1p2_length = Math.hypot(orth_p1p2[0], orth_p1p2[1]); + let ea = Math.acos(orth_p1p2[0] / orth_p1p2_length); + if (orth_p1p2[1] < 0) { + ea = 2 * Math.PI - ea; + } + if (sa > ea && sa - ea < Math.PI) + anticlockwise = true; + if (sa < ea && ea - sa > Math.PI) + anticlockwise = true; - // Connect the start tangent point to the end tangent point by arc - // and adding the end tangent point to the subpath. - this.arc(x, y, radius, startAngle, endAngle); + this.lineTo(t_p1p0[0], t_p1p0[1]) + this.arc(p[0], p[1], radius, sa, ea, anticlockwise) }; /** diff --git a/test/tests/arcTo.js b/test/tests/arcTo.js index 7f06e61..6ad0b8b 100644 --- a/test/tests/arcTo.js +++ b/test/tests/arcTo.js @@ -1,16 +1,30 @@ export default function arcTo(ctx) { ctx.beginPath(); ctx.moveTo(150, 20); - ctx.arcTo(150, 100, 50, 20, 30); + ctx.arcTo(150, 100, 250, 20, 20); + ctx.stroke(); + + ctx.beginPath(); + ctx.arc(450, 100, 20, 180/180*Math.PI, 45/180*Math.PI, true); ctx.stroke(); ctx.fillStyle = 'blue'; // base point - ctx.fillRect(150, 20, 10, 10); + ctx.fillRect(150, 20, 2, 2); ctx.fillStyle = 'red'; // control point one - ctx.fillRect(150, 100, 10, 10); + ctx.fillRect(150, 100, 2, 2); // control point two - ctx.fillRect(50, 20, 10, 10); + ctx.fillRect(250, 20, 2, 2); + + ctx.beginPath(); + ctx.moveTo(150, 200); + ctx.arcTo(250, 200, 250, 250, 20); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(150, 400); + ctx.arcTo(50, 400, 20, 450, 20); + ctx.stroke(); }; \ No newline at end of file diff --git a/test/tests/arcTo2.js b/test/tests/arcTo2.js index 28765eb..7fb9321 100644 --- a/test/tests/arcTo2.js +++ b/test/tests/arcTo2.js @@ -4,4 +4,17 @@ export default function arcTo(ctx) { ctx.arcTo(300, 25, 500, 225, 75); // P1, P2 and the radius ctx.lineTo(500, 225); // P2 ctx.stroke(); + + + const path = [[50, 50], [50, 150], [100, 150], [100, 150], [200, 150], [200, 50], [300, 50], [300, 150]]; + ctx.beginPath(); + let fromPoint = path[0]; + ctx.moveTo(fromPoint[0], fromPoint[1]); + for (let i = 1; i < path.length; i++) { + const point = path[i]; + ctx.arcTo(fromPoint[0], fromPoint[1], point[0], point[1], 20); // P1, P2 and the radius + fromPoint = point; + } + ctx.lineTo(300, 100) + ctx.stroke(); }; \ No newline at end of file From 130fe48f8e5e7431145cdc1877a263b5dac3f3c5 Mon Sep 17 00:00:00 2001 From: k1w1 Date: Fri, 22 Dec 2023 19:54:13 -0800 Subject: [PATCH 09/12] Added roundRect support --- context.js | 7 ++ package.json | 2 +- path2d.js | 16 ++- roundRect.js | 232 ++++++++++++++++++++++++++++++++++++++++ test/index.js | 2 + test/rendering.test.js | 4 +- test/tests/roundRect.js | 29 +++++ 7 files changed, 285 insertions(+), 7 deletions(-) create mode 100644 roundRect.js create mode 100644 test/tests/roundRect.js diff --git a/context.js b/context.js index 7215ac2..29e41d0 100644 --- a/context.js +++ b/context.js @@ -675,6 +675,13 @@ export default (function () { this.__currentDefaultPath.rect(x, y, width, height); }; + Context.prototype.roundRect = function (x, y, width, height, radii) { + if (!this.__currentDefaultPath) { + this.beginPath(); + } + this.__currentDefaultPath.roundRect(x, y, width, height, radii); + }; + Context.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { if (!this.__currentDefaultPath) { this.beginPath(); diff --git a/package.json b/package.json index a71d22b..a1a559b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aha-app/svgcanvas", - "version": "2.5.0-a13", + "version": "2.5.0-a14", "description": "svgcanvas", "main": "dist/svgcanvas.js", "scripts": { diff --git a/path2d.js b/path2d.js index f09bb19..487049c 100644 --- a/path2d.js +++ b/path2d.js @@ -1,4 +1,5 @@ import { format } from "./utils"; +import roundRectPolyfill from "./roundRect"; export default (function () { "use strict"; @@ -90,6 +91,11 @@ export default (function () { this.lineTo(x, y); }; + /** + * Adds a rounded rectangle to the path. + */ + Path2D.prototype.roundRect = roundRectPolyfill; + /** * Add a bezier command */ @@ -287,9 +293,9 @@ export default (function () { ea = 2 * Math.PI - ea; } if (sa > ea && sa - ea < Math.PI) - anticlockwise = true; + anticlockwise = true; if (sa < ea && ea - sa > Math.PI) - anticlockwise = true; + anticlockwise = true; this.lineTo(t_p1p0[0], t_p1p0[1]) this.arc(p[0], p[1], radius, sa, ea, anticlockwise) @@ -328,9 +334,9 @@ export default (function () { (2 * Math.PI); } var endX = - x + - Math.cos(-rotation) * radiusX * Math.cos(endAngle) + - Math.sin(-rotation) * radiusY * Math.sin(endAngle), + x + + Math.cos(-rotation) * radiusX * Math.cos(endAngle) + + Math.sin(-rotation) * radiusY * Math.sin(endAngle), endY = y - Math.sin(-rotation) * radiusX * Math.cos(endAngle) + diff --git a/roundRect.js b/roundRect.js new file mode 100644 index 0000000..b04cd1e --- /dev/null +++ b/roundRect.js @@ -0,0 +1,232 @@ +/* + * Implements the .roundRect() method of the CanvasPath mixin + * as introduced by https://github.com/whatwg/html/pull/6765 + * + * From https://github.com/Kaiido/roundRect + */ +export default function roundRect(x, y, w, h, radii) { + if (!([x, y, w, h].every((input) => Number.isFinite(input)))) { + return; + } + + radii = parseRadiiArgument(radii); + + let upperLeft, upperRight, lowerRight, lowerLeft; + + if (radii.length === 4) { + + upperLeft = toCornerPoint(radii[0]); + upperRight = toCornerPoint(radii[1]); + lowerRight = toCornerPoint(radii[2]); + lowerLeft = toCornerPoint(radii[3]); + + } else if (radii.length === 3) { + + upperLeft = toCornerPoint(radii[0]); + upperRight = toCornerPoint(radii[1]); + lowerLeft = toCornerPoint(radii[1]); + lowerRight = toCornerPoint(radii[2]); + + } else if (radii.length === 2) { + + upperLeft = toCornerPoint(radii[0]); + lowerRight = toCornerPoint(radii[0]); + upperRight = toCornerPoint(radii[1]); + lowerLeft = toCornerPoint(radii[1]); + + } else if (radii.length === 1) { + + upperLeft = toCornerPoint(radii[0]); + upperRight = toCornerPoint(radii[0]); + lowerRight = toCornerPoint(radii[0]); + lowerLeft = toCornerPoint(radii[0]); + + } else { + + throw new RangeError(`${getErrorMessageHeader(this)} ${radii.length} is not a valid size for radii sequence.`); + + } + + const corners = [upperLeft, upperRight, lowerRight, lowerLeft]; + const negativeCorner = corners.find(({ x, y }) => x < 0 || y < 0); + const negativeValue = negativeCorner?.x < 0 ? negativeCorner.x : negativeCorner?.y + + if (corners.some(({ x, y }) => !Number.isFinite(x) || !Number.isFinite(y))) { + + return; + + } + + if (negativeCorner) { + + throw new RangeError(`${getErrorMessageHeader(this)} Radius value ${negativeCorner} is negative.`); + + } + + fixOverlappingCorners(corners); + + if (w < 0 && h < 0) { + + this.moveTo(x - upperLeft.x, y); + this.ellipse(x + w + upperRight.x, y - upperRight.y, upperRight.x, upperRight.y, 0, -Math.PI * 1.5, -Math.PI); + this.ellipse(x + w + lowerRight.x, y + h + lowerRight.y, lowerRight.x, lowerRight.y, 0, -Math.PI, -Math.PI / 2); + this.ellipse(x - lowerLeft.x, y + h + lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, -Math.PI / 2, 0); + this.ellipse(x - upperLeft.x, y - upperLeft.y, upperLeft.x, upperLeft.y, 0, 0, -Math.PI / 2); + + } else if (w < 0) { + + this.moveTo(x - upperLeft.x, y); + this.ellipse(x + w + upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 0, -Math.PI / 2, -Math.PI, 1); + this.ellipse(x + w + lowerRight.x, y + h - lowerRight.y, lowerRight.x, lowerRight.y, 0, -Math.PI, -Math.PI * 1.5, 1); + this.ellipse(x - lowerLeft.x, y + h - lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, Math.PI / 2, 0, 1); + this.ellipse(x - upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, 0, 0, -Math.PI / 2, 1); + + } else if (h < 0) { + + this.moveTo(x + upperLeft.x, y); + this.ellipse(x + w - upperRight.x, y - upperRight.y, upperRight.x, upperRight.y, 0, Math.PI / 2, 0, 1); + this.ellipse(x + w - lowerRight.x, y + h + lowerRight.y, lowerRight.x, lowerRight.y, 0, 0, -Math.PI / 2, 1); + this.ellipse(x + lowerLeft.x, y + h + lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, -Math.PI / 2, -Math.PI, 1); + this.ellipse(x + upperLeft.x, y - upperLeft.y, upperLeft.x, upperLeft.y, 0, -Math.PI, -Math.PI * 1.5, 1); + + } else { + + this.moveTo(x + upperLeft.x, y); + this.ellipse(x + w - upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 0, -Math.PI / 2, 0); + this.ellipse(x + w - lowerRight.x, y + h - lowerRight.y, lowerRight.x, lowerRight.y, 0, 0, Math.PI / 2); + this.ellipse(x + lowerLeft.x, y + h - lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, Math.PI / 2, Math.PI); + this.ellipse(x + upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, 0, Math.PI, Math.PI * 1.5); + + } + + this.closePath(); + this.moveTo(x, y); + + function toDOMPointInit(value) { + + const { x, y, z, w } = value; + return { x, y, z, w }; + + } + + function parseRadiiArgument(value) { + + // https://webidl.spec.whatwg.org/#es-union + // with 'optional (unrestricted double or DOMPointInit + // or sequence<(unrestricted double or DOMPointInit)>) radii = 0' + const type = typeof value; + + if (type === "undefined" || value === null) { + + return [0]; + + } + if (type === "function") { + + return [NaN]; + + } + if (type === "object") { + + if (typeof value[Symbol.iterator] === "function") { + + return [...value].map((elem) => { + // https://webidl.spec.whatwg.org/#es-union + // with '(unrestricted double or DOMPointInit)' + const elemType = typeof elem; + if (elemType === "undefined" || elem === null) { + return 0; + } + if (elemType === "function") { + return NaN; + } + if (elemType === "object") { + return toDOMPointInit(elem); + } + return toUnrestrictedNumber(elem); + }); + + } + + return [toDOMPointInit(value)]; + + } + + return [toUnrestrictedNumber(value)]; + + } + + function toUnrestrictedNumber(value) { + + return +value; + + } + + function toCornerPoint(value) { + + const asNumber = toUnrestrictedNumber(value); + if (Number.isFinite(asNumber)) { + + return { + x: asNumber, + y: asNumber + }; + + } + if (Object(value) === value) { + + return { + x: toUnrestrictedNumber(value.x ?? 0), + y: toUnrestrictedNumber(value.y ?? 0) + }; + + } + + return { + x: NaN, + y: NaN + }; + + } + + function fixOverlappingCorners(corners) { + + const [upperLeft, upperRight, lowerRight, lowerLeft] = corners; + const factors = [ + Math.abs(w) / (upperLeft.x + upperRight.x), + Math.abs(h) / (upperRight.y + lowerRight.y), + Math.abs(w) / (lowerRight.x + lowerLeft.x), + Math.abs(h) / (upperLeft.y + lowerLeft.y) + ]; + const minFactor = Math.min(...factors); + if (minFactor <= 1) { + + for (const radii of corners) { + + radii.x *= minFactor; + radii.y *= minFactor; + + } + + } + + } + +} + +function getErrorMessageHeader(instance) { + + return `Failed to execute 'roundRect' on '${getConstructorName(instance)}':`; + +} + +function getConstructorName(instance) { + + return Object(instance) === instance && + instance instanceof Path2D ? "Path2D" : + instance instanceof globalThis?.CanvasRenderingContext2D ? "CanvasRenderingContext2D" : + instance instanceof globalThis?.OffscreenCanvasRenderingContext2D ? "OffscreenCanvasRenderingContext2D" : + instance?.constructor.name || + instance; + +} diff --git a/test/index.js b/test/index.js index 8e63def..795b738 100644 --- a/test/index.js +++ b/test/index.js @@ -23,6 +23,7 @@ import transform from "./tests/transform"; import pattern from "./tests/pattern"; import path2D from "./tests/path2D"; import clip from "./tests/clip"; +import roundRect from "./tests/roundRect"; const tests = [ tiger, @@ -49,6 +50,7 @@ const tests = [ transform, pattern, path2D, + roundRect, ]; for (let fn of tests) { diff --git a/test/rendering.test.js b/test/rendering.test.js index e1e83b3..877e07e 100644 --- a/test/rendering.test.js +++ b/test/rendering.test.js @@ -22,6 +22,7 @@ import tiger from './tests/tiger' import transform from './tests/transform' import pattern from "./tests/pattern"; import path2D from './tests/path2D'; +import roundRect from './tests/roundRect' const tests = { tiger, @@ -45,7 +46,8 @@ const tests = { text, transform, pattern, - path2D + path2D, + roundRect, }; const config = { diff --git a/test/tests/roundRect.js b/test/tests/roundRect.js new file mode 100644 index 0000000..a2165c0 --- /dev/null +++ b/test/tests/roundRect.js @@ -0,0 +1,29 @@ +export default function roundRect(ctx) { + ctx.beginPath(); + ctx.roundRect(150, 20, 100, 50, 10); + ctx.stroke(); + + ctx.beginPath(); + ctx.roundRect(150, 150, 100, 50, 50); + ctx.stroke(); + + ctx.beginPath(); + ctx.roundRect(150, 300, 50, 50, 50); + ctx.stroke(); + + ctx.beginPath(); + ctx.roundRect(300, 20, 100, 50, 10); + ctx.fill(); + + ctx.beginPath(); + ctx.roundRect(300, 150, 100, 50, [10, 20, 30, 50]); + ctx.stroke(); + + ctx.beginPath(); + ctx.roundRect(300, 300, 50, 50, [10, 30]); + ctx.stroke(); + + ctx.beginPath(); + ctx.roundRect(300, 400, 50, 50, [30]); + ctx.stroke(); +}; \ No newline at end of file From cc6973fa1c67d833c8a1c39d4a2807fc8b9aa3a9 Mon Sep 17 00:00:00 2001 From: k1w1 Date: Fri, 29 Dec 2023 08:33:08 -0800 Subject: [PATCH 10/12] Added note about active branch --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f73456c..e2c6928 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # SVGCanvas +** Aha! note: the current branch for all builds is `issue-fill-path` ** + Draw on SVG using Canvas's 2D Context API. A maintained fork of [gliffy's canvas2svg](https://github.com/gliffy/canvas2svg). From a891b0ad58429ac2104e70a7ce6a40135c7e7eff Mon Sep 17 00:00:00 2001 From: k1w1 Date: Thu, 18 Apr 2024 13:26:13 -0700 Subject: [PATCH 11/12] Support `addPath` properly. However this is not correctly handling the transform. To do it correctly we need to store the sub-paths in the path in their original form (not a string) and apply the transforms at render time. --- context.js | 92 +++++++++++++++++++++++++------------------- path2d.js | 26 +++++++------ test/tests/path2D.js | 46 ++++++++++++++++++++++ 3 files changed, 113 insertions(+), 51 deletions(-) diff --git a/context.js b/context.js index 29e41d0..004c1d1 100644 --- a/context.js +++ b/context.js @@ -92,30 +92,30 @@ export default (function () { // entity mapping courtesy of tinymce namedEntities = createNamedToNumberedLookup( "50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy," + - "5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute," + - "5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34," + - "5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil," + - "68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde," + - "6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute," + - "6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml," + - "75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc," + - "7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash," + - "7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta," + - "sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu," + - "st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi," + - "t9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota," + - "tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau," + - "u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip," + - "81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym," + - "8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr," + - "8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod," + - "8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup," + - "8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4," + - "nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob," + - "rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0," + - "Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm," + - "80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger," + - "811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro", + "5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute," + + "5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34," + + "5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil," + + "68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde," + + "6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute," + + "6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml," + + "75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc," + + "7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash," + + "7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta," + + "sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu," + + "st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi," + + "t9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota," + + "tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau," + + "u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip," + + "81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym," + + "8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr," + + "8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod," + + "8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup," + + "8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4," + + "nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob," + + "rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0," + + "Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm," + + "80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger," + + "811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro", 32 ); @@ -355,9 +355,9 @@ export default (function () { } var element = this.__document.createElementNS( - "http://www.w3.org/2000/svg", - elementName - ), + "http://www.w3.org/2000/svg", + elementName + ), keys = Object.keys(properties), i, key; @@ -775,12 +775,26 @@ export default (function () { path = this.__currentDefaultPath; } - var pathElement = this.__createPathElement(); - this.__applyStyleToElement(pathElement, action); - pathElement.setAttribute("paint-order", "fill stroke markers"); - pathElement.setAttribute("d", path.__pathString); + if (path.__pathString.length > 0) { + var pathElement = this.__createPathElement(); + this.__applyStyleToElement(pathElement, action); + pathElement.setAttribute("paint-order", "fill stroke markers"); + pathElement.setAttribute("d", path.__pathString); + if (path2d) { + this.__applyTransformation(pathElement); + } + } + if (path2d) { - this.__applyTransformation(pathElement); + path2d.__subPaths.forEach((subPath) => { + var pathElement = this.__createPathElement(); + this.__applyStyleToElement(pathElement, action); + pathElement.setAttribute("paint-order", "fill stroke markers"); + pathElement.setAttribute("d", subPath.path.__pathString); + if (subPath.transform) { + this.__applyTransformation(pathElement, this.getTransform().multiply(subPath.transform)); + } + }); } }; @@ -1160,9 +1174,9 @@ export default (function () { */ Context.prototype.createPattern = function (image, repetition) { var pattern = this.__document.createElementNS( - "http://www.w3.org/2000/svg", - "pattern" - ), + "http://www.w3.org/2000/svg", + "pattern" + ), id = randomString(this.__ids), img; pattern.setAttribute("id", id); @@ -1351,10 +1365,10 @@ export default (function () { /** * Not yet implemented */ - Context.prototype.drawFocusRing = function () {}; - Context.prototype.createImageData = function () {}; - Context.prototype.putImageData = function () {}; - Context.prototype.globalCompositeOperation = function () {}; + Context.prototype.drawFocusRing = function () { }; + Context.prototype.createImageData = function () { }; + Context.prototype.putImageData = function () { }; + Context.prototype.globalCompositeOperation = function () { }; return Context; })(); diff --git a/path2d.js b/path2d.js index 487049c..40c66d9 100644 --- a/path2d.js +++ b/path2d.js @@ -22,6 +22,7 @@ export default (function () { } this.ctx = ctx; + this.__subPaths = []; // Array of path string/transform pairs. this.__currentPosition = { x: undefined, y: undefined }; }; @@ -29,18 +30,19 @@ export default (function () { return this.ctx.__matrixTransform(x, y); }; - Path2D.prototype.addPath = function (path, transform) { - if (transform) - console.error("transform argument to addPath is not supported"); + Path2D.prototype.addPath = function (path, transform = undefined) { + this.__subPaths.push({ path: path, transform: transform }); + }; + Path2D.prototype.appendPath = function (path) { this.__pathString = this.__pathString + " " + path; - }; + } /** * Closes the current path */ Path2D.prototype.closePath = function () { - this.addPath("Z"); + this.appendPath("Z"); }; /** @@ -50,7 +52,7 @@ export default (function () { Path2D.prototype.moveTo = function (x, y) { // creates a new subpath with the given point this.__currentPosition = { x: x, y: y }; - this.addPath( + this.appendPath( format("M {x} {y}", { x: this.__matrixTransform(x, y).x, y: this.__matrixTransform(x, y).y, @@ -64,14 +66,14 @@ export default (function () { Path2D.prototype.lineTo = function (x, y) { this.__currentPosition = { x: x, y: y }; if (this.__pathString.indexOf("M") > -1) { - this.addPath( + this.appendPath( format("L {x} {y}", { x: this.__matrixTransform(x, y).x, y: this.__matrixTransform(x, y).y, }) ); } else { - this.addPath( + this.appendPath( format("M {x} {y}", { x: this.__matrixTransform(x, y).x, y: this.__matrixTransform(x, y).y, @@ -101,7 +103,7 @@ export default (function () { */ Path2D.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { this.__currentPosition = { x: x, y: y }; - this.addPath( + this.appendPath( format("C {cp1x} {cp1y} {cp2x} {cp2y} {x} {y}", { cp1x: this.__matrixTransform(cp1x, cp1y).x, cp1y: this.__matrixTransform(cp1x, cp1y).y, @@ -118,7 +120,7 @@ export default (function () { */ Path2D.prototype.quadraticCurveTo = function (cpx, cpy, x, y) { this.__currentPosition = { x: x, y: y }; - this.addPath( + this.appendPath( format("Q {cpx} {cpy} {x} {y}", { cpx: this.__matrixTransform(cpx, cpy).x, cpy: this.__matrixTransform(cpx, cpy).y, @@ -180,7 +182,7 @@ export default (function () { ); this.lineTo(startX, startY); - this.addPath( + this.appendPath( format( "A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", { @@ -370,7 +372,7 @@ export default (function () { this.lineTo(startX, startY); this.ctx.__transformMatrix = currentTransform; - this.addPath( + this.appendPath( format( "A {rx} {ry} {xAxisRotation} {largeArcFlag} {sweepFlag} {endX} {endY}", { diff --git a/test/tests/path2D.js b/test/tests/path2D.js index 6a80b59..09d1aa9 100644 --- a/test/tests/path2D.js +++ b/test/tests/path2D.js @@ -45,4 +45,50 @@ export default function path2D(ctx) { ctx.fill(); ctx.restore(); ctx.restore(); + + // Scaling path versus scaling the context. + const path2 = makePath( + ctx, + `M -10 -10 + L 10 -10 + L 10 10 + L -10 10 + Z` + ); + + + ctx.save(); + ctx.translate(25, 100); + ctx.scale(2, 1); + ctx.strokeStyle = "red"; + ctx.moveTo(-10, -10); + ctx.lineTo(10, -10); + ctx.lineTo(10, 10); + ctx.lineTo(-10, 10); + ctx.closePath(); + ctx.fillStyle = "grey"; + ctx.fill(); + ctx.scale(1 / 2, 1); // Reset scale so that stroke is not scaled. + ctx.stroke(); + ctx.restore(); + + ctx.save(); + ctx.translate(100, 100); + ctx.scale(2, 1); + ctx.fillStyle = "grey"; + ctx.fill(path2); + ctx.strokeStyle = "red"; + + let pNext = makePath(ctx); + // add first path, transform path, twice size, move 100,10 + pNext.addPath(path2, new DOMMatrix([ + 2, 0, + 0, 1, + 0, + 0, + ])); + + ctx.scale(1 / 2, 1); // Reset scale so that stroke is not scaled. + ctx.stroke(pNext); + ctx.restore(); } From 2e5bd80cdbb9ce6f36355e1c34b8c8a64411cd68 Mon Sep 17 00:00:00 2001 From: k1w1 Date: Thu, 18 Apr 2024 17:02:55 -0700 Subject: [PATCH 12/12] Add hack to prevent incorrectly scaled stroke --- context.js | 3 +++ package.json | 2 +- test/tests/path2D.js | 7 +++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/context.js b/context.js index 004c1d1..e9433f0 100644 --- a/context.js +++ b/context.js @@ -779,6 +779,8 @@ export default (function () { var pathElement = this.__createPathElement(); this.__applyStyleToElement(pathElement, action); pathElement.setAttribute("paint-order", "fill stroke markers"); + // This is a hack to replicate the behavior of Fabric. + pathElement.setAttribute("vector-effect", "non-scaling-stroke"); pathElement.setAttribute("d", path.__pathString); if (path2d) { this.__applyTransformation(pathElement); @@ -790,6 +792,7 @@ export default (function () { var pathElement = this.__createPathElement(); this.__applyStyleToElement(pathElement, action); pathElement.setAttribute("paint-order", "fill stroke markers"); + pathElement.setAttribute("vector-effect", "non-scaling-stroke"); pathElement.setAttribute("d", subPath.path.__pathString); if (subPath.transform) { this.__applyTransformation(pathElement, this.getTransform().multiply(subPath.transform)); diff --git a/package.json b/package.json index a1a559b..82cb56b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aha-app/svgcanvas", - "version": "2.5.0-a14", + "version": "2.5.0-a15", "description": "svgcanvas", "main": "dist/svgcanvas.js", "scripts": { diff --git a/test/tests/path2D.js b/test/tests/path2D.js index 09d1aa9..8852b99 100644 --- a/test/tests/path2D.js +++ b/test/tests/path2D.js @@ -79,9 +79,8 @@ export default function path2D(ctx) { ctx.fill(path2); ctx.strokeStyle = "red"; - let pNext = makePath(ctx); - // add first path, transform path, twice size, move 100,10 - pNext.addPath(path2, new DOMMatrix([ + let scaledPath = makePath(ctx); + scaledPath.addPath(path2, new DOMMatrix([ 2, 0, 0, 1, 0, @@ -89,6 +88,6 @@ export default function path2D(ctx) { ])); ctx.scale(1 / 2, 1); // Reset scale so that stroke is not scaled. - ctx.stroke(pNext); + ctx.stroke(scaledPath); ctx.restore(); }