(function () { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } var DEBUG = false; var svgcanvas = {}; Object.defineProperty(svgcanvas, '__esModule', { value: true }); function toString(obj) { if (!obj) { return obj } if (typeof obj === 'string') { return obj } return obj + ''; } class ImageUtils { /** * Convert svg dataurl to canvas element * * @private */ async svg2canvas(svgDataURL, width, height) { const svgImage = await new Promise((resolve) => { var svgImage = new Image(); svgImage.onload = function() { resolve(svgImage); }; svgImage.src = svgDataURL; }); var canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx.drawImage(svgImage, 0, 0); return canvas; } toDataURL(svgNode, width, height, type, encoderOptions, options) { var xml = new XMLSerializer().serializeToString(svgNode); // documentMode is an IE-only property // http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx // http://stackoverflow.com/questions/10964966/detect-ie-version-prior-to-v9-in-javascript var isIE = document.documentMode; if (isIE) { // This is patch from canvas2svg // IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly var xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi; if(xmlns.test(xml)) { xml = xml.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink'); } } if (!options) { options = {}; } var SVGDataURL = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(xml); if (type === "image/svg+xml" || !type) { if (options.async) { return Promise.resolve(SVGDataURL) } return SVGDataURL; } if (type === "image/jpeg" || type === "image/png") { if (!options.async) { throw new Error('svgcanvas: options.async must be set to true if type is image/jpeg | image/png') } return (async () => { const canvas = await this.svg2canvas(SVGDataURL, width, height); const dataUrl = canvas.toDataURL(type, encoderOptions); canvas.remove(); return dataUrl; })() } throw new Error('svgcanvas: Unknown type for toDataURL, please use image/jpeg | image/png | image/svg+xml.'); } getImageData(svgNode, width, height, sx, sy, sw, sh, options) { if (!options) { options = {}; } if (!options.async) { throw new Error('svgcanvas: options.async must be set to true for getImageData') } const svgDataURL = this.toDataURL(svgNode, width, height, 'image/svg+xml'); return (async () => { const canvas = await this.svg2canvas(svgDataURL, width, height); const ctx = canvas.getContext('2d'); const imageData = ctx.getImageData(sx, sy, sw, sh); canvas.remove(); return imageData; })() } } const utils = new ImageUtils(); /*!! * SVGCanvas v2.0.3 * Draw on SVG using Canvas's 2D Context API. * * Licensed under the MIT license: * http://www.opensource.org/licenses/mit-license.php * * Author: * Kerry Liu * Zeno Zeng * * Copyright (c) 2014 Gliffy Inc. * Copyright (c) 2021 Zeno Zeng */ var Context = (function () { 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) { 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); }; /** * 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 0) { this.setTransform(this.__transformMatrixStack.pop()); } }; /** * Create a new Path Element */ Context.prototype.beginPath = function () { var path, parent; // 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 = ""; this.__currentPosition = {}; path = this.__createElement("path", {}, true); parent = this.__closestGroupOrSvg(); parent.appendChild(path); this.__currentElement = path; }; /** * Helper function to apply currentDefaultPath to current path element * @private */ Context.prototype.__applyCurrentDefaultPath = function () { var currentElement = this.__currentElement; if (currentElement.nodeName === "path") { currentElement.setAttribute("d", this.__currentDefaultPath); } else { console.error("Attempted to apply path command to node", currentElement.nodeName); } }; /** * Helper function to add path command * @private */ Context.prototype.__addPathCommand = function (command) { this.__currentDefaultPath += " "; this.__currentDefaultPath += command; }; /** * Adds the move command to the current path element, * if the currentPathElement is not empty create a new path element */ Context.prototype.moveTo = function (x,y) { if (this.__currentElement.nodeName !== "path") { this.beginPath(); } // creates a new subpath with the given point this.__currentPosition = {x: x, y: y}; this.__addPathCommand(format("M {x} {y}", { x: this.__matrixTransform(x, y).x, y: this.__matrixTransform(x, y).y })); }; /** * Closes the current path */ Context.prototype.closePath = function () { if (this.__currentDefaultPath) { this.__addPathCommand("Z"); } }; /** * Adds a line to command */ Context.prototype.lineTo = function (x, y) { this.__currentPosition = {x: x, y: y}; if (this.__currentDefaultPath.indexOf('M') > -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 })); } }; /** * 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 })); }; /** * 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 })); }; /** * 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 */ 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; } // 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); }; /** * Sets the stroke property on the current element */ Context.prototype.stroke = function () { if (this.__currentElement.nodeName === "path") { this.__currentElement.setAttribute("paint-order", "fill stroke markers"); } this.__applyCurrentDefaultPath(); this.__applyStyleToCurrentElement("stroke"); }; /** * Sets fill properties on the current element */ Context.prototype.fill = function () { if (this.__currentElement.nodeName === "path") { this.__currentElement.setAttribute("paint-order", "stroke fill markers"); } this.__applyCurrentDefaultPath(); this.__applyStyleToCurrentElement("fill"); }; /** * Adds a rectangle to the path. */ Context.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 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.__applyStyleToCurrentElement("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.__applyStyleToCurrentElement("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.__applyStyleToCurrentElement(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; } 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); }; /** * 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. */ 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; }; /** * 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, 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); } parent = this.__closestGroupOrSvg(); 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); } }; /** * 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.setLineDash = function (dashArray) { if (dashArray && dashArray.length > 0) { this.lineDash = dashArray.join(","); } else { this.lineDash = null; } }; /** * 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) }; }; /** * * @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 utils.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; }()); function SVGCanvasElement(options) { this.ctx = new Context(options); this.svg = this.ctx.__root; // sync attributes to svg var svg = this.svg; var _this = this; var wrapper = document.createElement('div'); wrapper.style.display = 'inline-block'; wrapper.appendChild(svg); this.wrapper = wrapper; Object.defineProperty(this, 'className', { get: function() { return wrapper.getAttribute('class') || ''; }, set: function(val) { return wrapper.setAttribute('class', val); } }); Object.defineProperty(this, 'tagName', { get: function() { return "CANVAS"; }, set: function() {} // no-op }); ["width", "height"].forEach(function(prop) { Object.defineProperty(_this, prop, { get: function() { return svg.getAttribute(prop) | 0; }, set: function(val) { if (isNaN(val) || (typeof val === "undefined")) { return; } _this.ctx[prop] = val; svg.setAttribute(prop, val); return wrapper[prop] = val; } }); }); ["style", "id"].forEach(function(prop) { Object.defineProperty(_this, prop, { get: function() { return wrapper[prop]; }, set: function(val) { if (typeof val !== "undefined") { return wrapper[prop] = val; } } }); }); ["getBoundingClientRect"].forEach(function(fn) { _this[fn] = function() { return svg[fn](); }; }); } SVGCanvasElement.prototype.getContext = function(type) { if (type !== '2d') { throw new Error('Unsupported type of context for SVGCanvas'); } return this.ctx; }; // you should always use URL.revokeObjectURL after your work done SVGCanvasElement.prototype.toObjectURL = function() { var data = new XMLSerializer().serializeToString(this.svg); var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'}); return URL.createObjectURL(svg); }; /** * toDataURL returns a data URI containing a representation of the image in the format specified by the type parameter. * * @param {String} type A DOMString indicating the image format. The default type is image/svg+xml; this image format will be also used if the specified type is not supported. * @param {Number} encoderOptions A Number between 0 and 1 indicating the image quality to be used when creating images using file formats that support lossy compression (such as image/jpeg or image/webp). A user agent will use its default quality value if this option is not specified, or if the number is outside the allowed range. * @param {Boolean} options.async Will return a Promise if true, must be set to true if type is not image/svg+xml */ SVGCanvasElement.prototype.toDataURL = function(type, encoderOptions, options) { return utils.toDataURL(this.svg, this.width, this.height, type, encoderOptions, options) }; SVGCanvasElement.prototype.addEventListener = function() { return this.svg.addEventListener.apply(this.svg, arguments); }; // will return wrapper element:
SVGCanvasElement.prototype.getElement = function() { return this.wrapper; }; SVGCanvasElement.prototype.getAttribute = function(prop) { return this.wrapper.getAttribute(prop); }; SVGCanvasElement.prototype.setAttribute = function(prop, val) { this.wrapper.setAttribute(prop, val); }; svgcanvas.Context = Context; var Element$1 = svgcanvas.Element = SVGCanvasElement; function RendererSVG (p5) { /** * @namespace RendererSVG * @constructor * @param elt canvas element to be replaced * @param pInst p5 Instance * @param isMainCanvas */ function RendererSVG(elt, pInst, isMainCanvas) { var svgCanvas = new Element$1({ debug: DEBUG }); var svg = svgCanvas.svg; // replace with and copy id, className var parent = elt.parentNode; var id = elt.id; var className = elt.className; parent.replaceChild(svgCanvas.getElement(), elt); svgCanvas.id = id; svgCanvas.className = className; elt = svgCanvas; // our fake elt.parentNode = { // fake parentNode.removeChild so that noCanvas will work removeChild: function (element) { if (element === elt) { var wrapper = svgCanvas.getElement(); wrapper.parentNode.removeChild(wrapper); } } }; var pInstProxy = new Proxy(pInst, { get: function (target, prop) { if (prop === '_pixelDensity') { // 1 is OK for SVG return 1; } return target[prop]; } }); Object.assign(this, p5.Renderer2D.call(this, elt, pInstProxy, isMainCanvas)); this.isSVG = true; this.svg = svg; return this; } RendererSVG.prototype = Object.create(p5.Renderer2D.prototype); RendererSVG.prototype._applyDefaults = function () { p5.Renderer2D.prototype._applyDefaults.call(this); this.drawingContext.lineWidth = 1; }; RendererSVG.prototype.resize = function (w, h) { if (!w || !h) { return; } if (this.width !== w || this.height !== h) { // canvas will be cleared if its size changed // so, we do same thing for SVG // note that at first this.width and this.height is undefined this.drawingContext.__clearCanvas(); } p5.Renderer2D.prototype.resize.call(this, w, h); // For scale, crop // see also: http://sarasoueidan.com/blog/svg-coordinate-systems/ this.svg.setAttribute('viewBox', [0, 0, w, h].join(' ')); }; RendererSVG.prototype.clear = function () { p5.Renderer2D.prototype.clear.call(this); this.drawingContext.__clearCanvas(); }; /** * Append a element to current SVG Graphics * * @function appendChild * @memberof RendererSVG.prototype * @param {SVGElement|Element} element */ RendererSVG.prototype.appendChild = function (element) { if (element && element.elt) { element = element.elt; } var g = this.drawingContext.__closestGroupOrSvg(); g.appendChild(element); }; /** * Draw an image or SVG to current SVG Graphics * * FIXME: sx, sy, sWidth, sHeight * * @function image * @memberof RendererSVG.prototype * @param {p5.Graphics|SVGGraphics|SVGElement|Element} image * @param {Number} x * @param {Number} y * @param {Number} width * @param {Number} height */ RendererSVG.prototype.image = function (img, sx, sy, sWidth, sHeight, x, y, w, h) { if (!img) { throw new Error('Invalid image: ' + img); } var elt = img._renderer && img._renderer.svg; // handle SVG Graphics elt = elt || (img.elt && img.elt.nodeName && (img.elt.nodeName.toLowerCase() === 'svg') && img.elt); // SVGElement elt = elt || (img.nodeName && (img.nodeName.toLowerCase() == 'svg') && img); // if (elt) { // it's element, let's handle it elt = elt.cloneNode(true); elt.setAttribute('width', w); elt.setAttribute('height', h); elt.setAttribute('x', x); elt.setAttribute('y', y); if (sx || sy || sWidth || sHeight) { sWidth /= this._pInst._pixelDensity; sHeight /= this._pInst._pixelDensity; elt.setAttribute('viewBox', [sx, sy, sWidth, sHeight].join(', ')); } var g = p5.SVGElement.create('g'); this.drawingContext.__applyTransformation(g.elt); g.elt.appendChild(elt); this.appendChild(g.elt); } else { p5.Renderer2D.prototype.image.apply(this, [img, sx, sy, sWidth, sHeight, x, y, w, h]); } }; /** * @method parent * @return {p5.Element} * * @see https://github.com/zenozeng/p5.js-svg/issues/187 */ RendererSVG.prototype.parent = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var $this = { elt: this.elt.getElement() }; return p5.Element.prototype.parent.apply($this, args); }; RendererSVG.prototype.loadPixels = function () { return __awaiter(this, void 0, void 0, function () { var pixelsState, pd, w, h, imageData; return __generator(this, function (_a) { switch (_a.label) { case 0: pixelsState = this._pixelsState // if called by p5.Image ; pd = pixelsState._pixelDensity; w = this.width * pd; h = this.height * pd; return [4 /*yield*/, this.drawingContext.getImageData(0, 0, w, h, { async: true })]; case 1: imageData = _a.sent(); pixelsState._setProperty('imageData', imageData); pixelsState._setProperty('pixels', imageData.data); return [2 /*return*/]; } }); }); }; p5.RendererSVG = RendererSVG; } function SVGFiters (p5) { /** * Generate discrete table values based on the given color map function * * @private * @param {Function} fn - Function to map channel values (val ∈ [0, 255]) * @see http://www.w3.org/TR/SVG/filters.html#feComponentTransferElement */ var _discreteTableValues = function (fn) { var table = []; for (var val = 0; val < 256; val++) { var newval = fn(val); table.push(newval / 255); // map to ∈ [0, 1] } return table; }; var generateID = function () { return Date.now().toString() + Math.random().toString().replace(/0\./, ''); }; var _blendOffset = function (inGraphics, resultGraphics, mode) { var elements = []; [ ['left', -1, 0], ['right', 1, 0], ['up', 0, -1], ['down', 0, 1] ].forEach(function (neighbor) { elements.push(p5.SVGElement.create('feOffset', { 'in': inGraphics, result: resultGraphics + '-' + neighbor[0], dx: neighbor[1], dy: neighbor[2] })); }); [ [null, inGraphics], [resultGraphics + '-left', resultGraphics + '-tmp-0'], [resultGraphics + '-right', resultGraphics + '-tmp-1'], [resultGraphics + '-up', resultGraphics + '-tmp-2'], [resultGraphics + '-down', resultGraphics + '-tmp-3'] ].forEach(function (layer, i, layers) { if (i === 0) { return; } elements.push(p5.SVGElement.create('feBlend', { 'in': layers[i - 1][1], in2: layer[0], result: layer[1], mode: mode })); }); return elements; }; p5.SVGFilters = { // @private // We have to build a filter for each element // the `filter: f1 f2` and svg param is not supported by many browsers // so we can just modify the filter def to do so apply: function (svgElement, func, arg, defs) { // get filters var filters = JSON.parse(svgElement.attribute('data-p5-svg-filters') || '[]'); if (func) { filters.push([func, arg]); } svgElement.attribute('data-p5-svg-filters', JSON.stringify(filters)); if (filters.length === 0) { svgElement.attribute('filter', null); return; } // generate filters chain var filtersElements = filters.map(function (filter, index) { var inGraphics = index === 0 ? 'SourceGraphic' : ('result-' + (index - 1)); var resultGraphics = 'result-' + index; var elements = p5.SVGFilters[filter[0]].call(null, inGraphics, resultGraphics, filter[1]); if (!Array.isArray(elements)) { return [elements]; } else { return elements; } }); // get filter id for this element or create one var filterid = svgElement.attribute('data-p5-svg-filter-id'); if (!filterid) { filterid = 'p5-svg-' + generateID(); svgElement.attribute('data-p5-svg-filter-id', filterid); } // Note that when filters is [], we will remove filter attr // So, here, we write this attr every time svgElement.attribute('filter', 'url(#' + filterid + ')'); // create var filter = p5.SVGElement.create('filter', { id: filterid }); filtersElements.forEach(function (elt) { elt.forEach(function (elt) { filter.append(elt); }); }); // get defs var oldfilter = defs.querySelectorAll('#' + filterid)[0]; if (!oldfilter) { defs.appendChild(filter.elt); } else { oldfilter.parentNode.replaceChild(filter.elt, oldfilter); } }, blur: function (inGraphics, resultGraphics, val) { return p5.SVGElement.create('feGaussianBlur', { stdDeviation: val + '', in: inGraphics, result: resultGraphics, 'color-interpolation-filters': 'sRGB' }); }, // See also: http://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement // See also: http://stackoverflow.com/questions/21977929/match-colors-in-fecolormatrix-filter colorMatrix: function (inGraphics, resultGraphics, matrix) { return p5.SVGElement.create('feColorMatrix', { type: 'matrix', values: matrix.join(' '), 'color-interpolation-filters': 'sRGB', in: inGraphics, result: resultGraphics }); }, // Here we use CIE luminance for RGB gray: function (inGraphics, resultGraphics) { var matrix = [ 0.2126, 0.7152, 0.0722, 0, 0, // R' 0.2126, 0.7152, 0.0722, 0, 0, // G' 0.2126, 0.7152, 0.0722, 0, 0, // B' 0, 0, 0, 1, 0 // A' ]; return p5.SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix); }, threshold: function (inGraphics, resultGraphics, val) { var elements = []; elements.push(p5.SVGFilters.gray(inGraphics, resultGraphics + '-tmp')); var componentTransfer = p5.SVGElement.create('feComponentTransfer', { 'in': resultGraphics + '-tmp', result: resultGraphics }); var thresh = Math.floor(val * 255); ['R', 'G', 'B'].forEach(function (channel) { // Note that original value is from 0 to 1 var func = p5.SVGElement.create('feFunc' + channel, { type: 'linear', slope: 255, // all non-zero * 255 intercept: (thresh - 1) * -1 }); componentTransfer.append(func); }); elements.push(componentTransfer); return elements; }, invert: function (inGraphics, resultGraphics) { var matrix = [ -1, 0, 0, 0, 1, 0, -1, 0, 0, 1, 0, 0, -1, 0, 1, 0, 0, 0, 1, 0 ]; return p5.SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix); }, opaque: function (inGraphics, resultGraphics) { var matrix = [ 1, 0, 0, 0, 0, // original R 0, 1, 0, 0, 0, // original G 0, 0, 1, 0, 0, // original B 0, 0, 0, 0, 1 // set A to 1 ]; return p5.SVGFilters.colorMatrix(inGraphics, resultGraphics, matrix); }, /** * Limits each channel of the image to the number of colors specified as * the parameter. The parameter can be set to values between 2 and 255, but * results are most noticeable in the lower ranges. * * Adapted from p5's Filters.posterize */ posterize: function (inGraphics, resultGraphics, level) { level = parseInt(level + '', 10); if ((level < 2) || (level > 255)) { throw new Error('Level must be greater than 2 and less than 255 for posterize'); } var tableValues = _discreteTableValues(function (val) { return (((val * level) >> 8) * 255) / (level - 1); }); var componentTransfer = p5.SVGElement.create('feComponentTransfer', { 'in': inGraphics, result: resultGraphics, 'color-interpolation-filters': 'sRGB' }); ['R', 'G', 'B'].forEach(function (channel) { var func = p5.SVGElement.create('feFunc' + channel, { type: 'discrete', tableValues: tableValues.join(' ') }); componentTransfer.append(func); }); return componentTransfer; }, /** * Increases the bright areas in an image * * Will create 4 offset layer and blend them using darken mode */ erode: function (inGraphics, resultGraphics) { return _blendOffset(inGraphics, resultGraphics, 'darken'); }, dilate: function (inGraphics, resultGraphics) { return _blendOffset(inGraphics, resultGraphics, 'lighten'); } }; } var constants = { SVG: 'svg' }; function Rendering (p5) { // patch p5.Graphics for SVG var _graphics = p5.Graphics; p5.Graphics = function (w, h, renderer, pInst) { var isSVG = renderer === constants.SVG; var pg = new _graphics(w, h, isSVG ? pInst.P2D : renderer, pInst); if (isSVG) { // replace renderer with SVG renderer var svgRenderer = new p5.RendererSVG(pg.elt, pg, false); pg._renderer = svgRenderer; pg.elt = svgRenderer.elt; // do default again pg._renderer.resize(w, h); pg._renderer._applyDefaults(); } return pg; }; p5.Graphics.prototype = _graphics.prototype; /** * Patched version of createCanvas * * use createCanvas(100, 100, SVG) to create SVG canvas. * * Creates a SVG element in the document, and sets its width and * height in pixels. This method should be called only once at * the start of setup. * @function createCanvas * @memberof p5.prototype * @param {Number} width - Width (in px) for SVG Element * @param {Number} height - Height (in px) for SVG Element * @return {Graphics} */ var _createCanvas = p5.prototype.createCanvas; p5.prototype.createCanvas = function (w, h, renderer) { var graphics = _createCanvas.apply(this, [w, h, renderer]); if (renderer === constants.SVG) { var c = graphics.canvas; this._setProperty('_renderer', new p5.RendererSVG(c, this, true)); this._isdefaultGraphics = true; this._renderer.resize(w, h); this._renderer._applyDefaults(); } return this._renderer; }; p5.prototype.createGraphics = function (w, h, renderer) { return new p5.Graphics(w, h, renderer, this); }; } function IO (p5) { /** * Convert SVG Element to jpeg / png data url * * @private * @param {SVGElement} svg SVG Element * @param {String} mine Mine * @param {Function} callback */ var svg2img = function (svg, mine, callback) { var svgText = (new XMLSerializer()).serializeToString(svg); svgText = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgText); if (mine == 'image/svg+xml') { callback(null, svgText); return; } var img = new Image(); var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); img.onload = function () { canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); var dataURL = canvas.toDataURL(mine); callback(null, dataURL); }; img.src = svgText; }; /** * Get SVG frame, and convert to target type * * @private * @param {Object} options * @param {SVGElement} options.svg SVG Element, defaults to current svg element * @param {String} options.filename * @param {String} options.ext Extension: 'svg' or 'jpg' or 'jpeg' or 'png' * @param {Function} options.callback */ p5.prototype._makeSVGFrame = function (options) { var filename = options.filename || 'untitled'; var ext = options.extension; ext = ext || this._checkFileExtension(filename, ext)[1]; var regexp = new RegExp('\\.' + ext + '$'); filename = filename.replace(regexp, ''); if (ext === '') { ext = 'svg'; } var mine = { png: 'image/png', jpeg: 'image/jpeg', jpg: 'image/jpeg', svg: 'image/svg+xml' }[ext]; if (!mine) { throw new Error('Fail to getFrame, invalid extension: ' + ext + ', please use png | jpeg | jpg | svg.'); } var svg = options.svg || this._renderer.svg; svg2img(svg, mine, function (err, dataURL) { var downloadMime = 'image/octet-stream'; dataURL = dataURL.replace(mine, downloadMime); options.callback(err, { imageData: dataURL, filename: filename, ext: ext }); }); }; /** * Save the current SVG as an image. In Safari, will open the * image in the window and the user must provide their own * filename on save-as. Other browsers will either save the * file immediately, or prompt the user with a dialogue window. * * @function saveSVG * @memberof p5.prototype * @param {Graphics|Element|SVGElement} [svg] Source to save * @param {String} [filename] * @param {String} [extension] Extension: 'svg' or 'jpg' or 'jpeg' or 'png' */ p5.prototype.saveSVG = function () { var _this = this; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } args = [args[0], args[1], args[2]]; var svg; if (args[0] instanceof p5.Graphics) { svg = args[0]._renderer.svg; args.shift(); } if (args[0] && args[0].elt) { svg = args[0].elt; args.shift(); } if (typeof args[0] == 'object') { svg = args[0]; args.shift(); } var filename = args[0]; var ext = args[1]; this._makeSVGFrame({ svg: svg, filename: filename, extension: ext, callback: function (err, frame) { if (err) { throw err; } _this.downloadFile(frame.imageData, frame.filename, frame.ext); } }); }; /** * Extends p5's saveFrames with SVG support * * @function saveFrames * @memberof p5.prototype * @param {String} filename filename * @param {String} extension Extension: 'svg' or 'jpg' or 'jpeg' or 'png' * @param {Number} duration duration * @param {Number} fps fps * @param {Function} callback callback */ var _saveFrames = p5.prototype.saveFrames; p5.prototype.saveFrames = function () { var _this = this; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var filename = args[0]; var extension = args[1]; var duration = args[2]; var fps = args[3]; var callback = args[4]; if (!this._renderer.svg) { return _saveFrames.apply(this, args); } duration = duration || 3; duration = p5.prototype.constrain(duration, 0, 15); duration = duration * 1000; fps = fps || 15; fps = p5.prototype.constrain(fps, 0, 22); var count = 0; var frames = []; var pending = 0; var frameFactory = setInterval(function () { (function (count) { pending++; _this._makeSVGFrame({ filename: filename + count, extension: extension, callback: function (err, frame) { if (err) { throw err; } frames[count] = frame; pending--; } }); })(count); count++; }, 1000 / fps); var done = function () { if (pending > 0) { setTimeout(function () { done(); }, 10); return; } if (callback) { callback(frames); } else { frames.forEach(function (f) { _this.downloadFile(f.imageData, f.filename, f.ext); }); } }; setTimeout(function () { clearInterval(frameFactory); done(); }, duration + 0.01); }; /** * Extends p5's save method with SVG support * * @function save * @memberof p5.prototype * @param {Graphics|Element|SVGElement} [source] Source to save * @param {String} [filename] filename */ var _save = p5.prototype.save; p5.prototype.save = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var svg; if (args[0] instanceof p5.Graphics) { var svgcanvas = args[0].elt; svg = svgcanvas.svg; args.shift(); } if (args[0] && args[0].elt) { svg = args[0].elt; args.shift(); } if (typeof args[0] == 'object') { svg = args[0]; args.shift(); } svg = svg || (this._renderer && this._renderer.svg); var filename = args[0]; var supportedExtensions = ['jpeg', 'png', 'jpg', 'svg', '']; var ext = this._checkFileExtension(filename, '')[1]; var useSVG = svg && svg.nodeName && svg.nodeName.toLowerCase() === 'svg' && supportedExtensions.indexOf(ext) > -1; if (useSVG) { this.saveSVG(svg, filename); } else { return _save.apply(this, args); } }; /** * Custom get in p5.svg (handles http and dataurl) * @private */ p5.prototype._svg_get = function (path, successCallback, failureCallback) { if (path.indexOf('data:') === 0) { if (path.indexOf(',') === -1) { failureCallback(new Error('Fail to parse dataurl: ' + path)); return; } var svg_1 = path.split(',').pop(); // force request to dataurl to be async // so that it won't make preload mess setTimeout(function () { if (path.indexOf(';base64,') > -1) { svg_1 = atob(svg_1); } else { svg_1 = decodeURIComponent(svg_1); } successCallback(svg_1); }, 1); return svg_1; } else { this.httpGet(path, successCallback); return null; } }; /** * loadSVG (like loadImage, but will return SVGElement) * * @function loadSVG * @memberof p5.prototype * @returns {p5.SVGElement} */ p5.prototype.loadSVG = function (path, successCallback, failureCallback) { var _this = this; var div = document.createElement('div'); var element = new p5.SVGElement(div); this._incrementPreload(); new Promise(function (resolve, reject) { _this._svg_get(path, function (svg) { div.innerHTML = svg; var svgEl = div.querySelector('svg'); if (!svgEl) { reject('Fail to create .'); return; } element.elt = svgEl; resolve(element); }, reject); }).then(function (v) { successCallback && successCallback(v); }).catch(function (e) { failureCallback && failureCallback(e); }).finally(function () { _this._decrementPreload(); }); return element; }; p5.prototype.getDataURL = function () { return this._renderer.elt.toDataURL('image/svg+xml'); }; } function Image$1 (p5) { p5.prototype.loadPixels = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } p5._validateParameters('loadPixels', args); return this._renderer.loadPixels(); }; } /** * https://github.com/processing/p5.js/blob/main/src/core/p5.Element.js */ var Element = (function (p5) { /** * Returns an Array of SVGElements of current SVG Graphics matching given selector * * @param selector CSS selector for query */ p5.prototype.querySVG = function (selector) { var svg = this._renderer && this._renderer.svg; if (!svg) { return null; } return p5.SVGElement.prototype.query.call({ elt: svg }, selector); }; p5.SVGElement = /** @class */ (function (_super) { __extends(SVGElement, _super); function SVGElement() { var _this = _super !== null && _super.apply(this, arguments) || this; /** * Apply different attribute operation based on arguments.length *
    *
  • setAttribute(name, value)
  • *
  • setAttributeNS(namespace, name, value)
  • *
  • getAttribute(name)
  • *
* */ _this.attribute = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } if (args.length === 3) { this.elt.setAttributeNS(args[0], args[1], args[2]); return this; } if (args.length === 2) { this.elt.setAttribute(args[0], args[1]); return this; } if (args.length === 1) { return this.elt.getAttribute(args[0]); } return this; }; return _this; } /** * Returns an Array of children of current SVG Element matching given selector * * @param selector CSS selector for query */ SVGElement.prototype.query = function (selector) { var elements = this.elt.querySelectorAll(selector); var objects = []; for (var i = 0; i < elements.length; i++) { objects[i] = new SVGElement(elements[i]); } return objects; }; /** * Append a new child to current element. * @param element * @returns */ SVGElement.prototype.append = function (element) { var elt = element.elt || element; this.elt.appendChild(elt); return this; }; /** * Create SVGElement * */ SVGElement.create = function (nodeName, attributes) { attributes = attributes || {}; var elt = document.createElementNS('http://www.w3.org/2000/svg', nodeName); Object.keys(attributes).forEach(function (k) { elt.setAttribute(k, attributes[k]); }); return new SVGElement(elt); }; /** * Get parentNode. * If selector not given, returns parentNode. * Otherwise, will look up all ancestors, * and return closest element matching given selector, * or return null if not found. * */ SVGElement.prototype.parentNode = function (selector) { if (!selector) { return new SVGElement(this.elt.parentNode); } var elt = this; while (elt) { elt = this.parentNode(); if (elt && elt.elt.matches(selector)) { return elt; } } return null; }; return SVGElement; }(p5.Element)); }); // SVG Filter function Filters (p5) { var _filter = p5.prototype.filter; /** * Register a custom SVG Filter * * @function registerSVGFilter * @memberof p5.prototype * @param {String} name Name for Custom SVG filter * @param {Function} filterFunction filterFunction(inGraphicsName, resultGraphicsName, value) * should return SVGElement or Array of SVGElement. * @example * registerSVGFilter('myblur', function(inGraphicsName, resultGraphicsName, value) { * return SVGElement.create('feGaussianBlur', { * stdDeviation: val, * in: inGraphics, * result: resultGraphics, * 'color-interpolation-filters': 'sRGB' * }); * }); * filter('myblur', 5); */ p5.prototype.registerSVGFilter = function (name, fn) { p5.SVGFilters[name] = fn; }; /** * Apply filter on current element. * If called multiple times, * these filters will be chained together and combine to a bigger SVG filter. * * @param operation BLUR, GRAY, INVERT, THRESHOLD, OPAQUE, ERODE, DILATE (defined in p5's constants) * @param value Argument for that filter */ p5.prototype.filter = function (operation, value) { var svg = this._renderer.svg; if (svg) { var ctx = this._renderer.drawingContext; var defs = ctx.__root.querySelectorAll('defs')[0]; var rootGroup = ctx.__root.childNodes[1]; // move nodes to a new var g = p5.SVGElement.create('g'); while (rootGroup.childNodes.length > 0) { g.elt.appendChild(rootGroup.childNodes[0]); } rootGroup.appendChild(g.elt); // apply filter p5.SVGFilters.apply(g, operation, value, defs); // create new so that new element won't be influenced by the filter g = p5.SVGElement.create('g'); rootGroup.appendChild(g.elt); ctx.__currentElement = g.elt; } else { _filter.apply(this, [operation, value]); } }; } function init(p5) { var p5svg = p5; RendererSVG(p5svg); SVGFiters(p5svg); Rendering(p5svg); IO(p5svg); Image$1(p5svg); Filters(p5svg); Element(p5svg); // attach constants to p5 instance p5svg.prototype['SVG'] = constants.SVG; return p5svg; } var global = window; if (typeof global.p5 !== 'undefined') { init(global.p5); } return init; })(); //# sourceMappingURL=p5.svg.js.map