diff --git a/dist/editor/extensions/ext-server_moinsave.js.map b/dist/editor/extensions/ext-server_moinsave.js.map index 10276e58..6d08e722 100644 --- a/dist/editor/extensions/ext-server_moinsave.js.map +++ b/dist/editor/extensions/ext-server_moinsave.js.map @@ -1 +1 @@ -{"version":3,"file":"ext-server_moinsave.js","sources":["../../../src/editor/canvg/rgbcolor.js","../../../src/external/stackblur-canvas/dist/stackblur-es.js","../../../src/editor/canvg/canvg.js","../../../src/editor/extensions/ext-server_moinsave.js"],"sourcesContent":["/**\n * For parsing color values.\n * @module RGBColor\n * @author Stoyan Stefanov \n * @see https://www.phpied.com/rgb-color-parser-in-javascript/\n * @license MIT\n*/\nconst simpleColors = {\n aliceblue: 'f0f8ff',\n antiquewhite: 'faebd7',\n aqua: '00ffff',\n aquamarine: '7fffd4',\n azure: 'f0ffff',\n beige: 'f5f5dc',\n bisque: 'ffe4c4',\n black: '000000',\n blanchedalmond: 'ffebcd',\n blue: '0000ff',\n blueviolet: '8a2be2',\n brown: 'a52a2a',\n burlywood: 'deb887',\n cadetblue: '5f9ea0',\n chartreuse: '7fff00',\n chocolate: 'd2691e',\n coral: 'ff7f50',\n cornflowerblue: '6495ed',\n cornsilk: 'fff8dc',\n crimson: 'dc143c',\n cyan: '00ffff',\n darkblue: '00008b',\n darkcyan: '008b8b',\n darkgoldenrod: 'b8860b',\n darkgray: 'a9a9a9',\n darkgreen: '006400',\n darkkhaki: 'bdb76b',\n darkmagenta: '8b008b',\n darkolivegreen: '556b2f',\n darkorange: 'ff8c00',\n darkorchid: '9932cc',\n darkred: '8b0000',\n darksalmon: 'e9967a',\n darkseagreen: '8fbc8f',\n darkslateblue: '483d8b',\n darkslategray: '2f4f4f',\n darkturquoise: '00ced1',\n darkviolet: '9400d3',\n deeppink: 'ff1493',\n deepskyblue: '00bfff',\n dimgray: '696969',\n dodgerblue: '1e90ff',\n feldspar: 'd19275',\n firebrick: 'b22222',\n floralwhite: 'fffaf0',\n forestgreen: '228b22',\n fuchsia: 'ff00ff',\n gainsboro: 'dcdcdc',\n ghostwhite: 'f8f8ff',\n gold: 'ffd700',\n goldenrod: 'daa520',\n gray: '808080',\n green: '008000',\n greenyellow: 'adff2f',\n honeydew: 'f0fff0',\n hotpink: 'ff69b4',\n indianred: 'cd5c5c',\n indigo: '4b0082',\n ivory: 'fffff0',\n khaki: 'f0e68c',\n lavender: 'e6e6fa',\n lavenderblush: 'fff0f5',\n lawngreen: '7cfc00',\n lemonchiffon: 'fffacd',\n lightblue: 'add8e6',\n lightcoral: 'f08080',\n lightcyan: 'e0ffff',\n lightgoldenrodyellow: 'fafad2',\n lightgrey: 'd3d3d3',\n lightgreen: '90ee90',\n lightpink: 'ffb6c1',\n lightsalmon: 'ffa07a',\n lightseagreen: '20b2aa',\n lightskyblue: '87cefa',\n lightslateblue: '8470ff',\n lightslategray: '778899',\n lightsteelblue: 'b0c4de',\n lightyellow: 'ffffe0',\n lime: '00ff00',\n limegreen: '32cd32',\n linen: 'faf0e6',\n magenta: 'ff00ff',\n maroon: '800000',\n mediumaquamarine: '66cdaa',\n mediumblue: '0000cd',\n mediumorchid: 'ba55d3',\n mediumpurple: '9370d8',\n mediumseagreen: '3cb371',\n mediumslateblue: '7b68ee',\n mediumspringgreen: '00fa9a',\n mediumturquoise: '48d1cc',\n mediumvioletred: 'c71585',\n midnightblue: '191970',\n mintcream: 'f5fffa',\n mistyrose: 'ffe4e1',\n moccasin: 'ffe4b5',\n navajowhite: 'ffdead',\n navy: '000080',\n oldlace: 'fdf5e6',\n olive: '808000',\n olivedrab: '6b8e23',\n orange: 'ffa500',\n orangered: 'ff4500',\n orchid: 'da70d6',\n palegoldenrod: 'eee8aa',\n palegreen: '98fb98',\n paleturquoise: 'afeeee',\n palevioletred: 'd87093',\n papayawhip: 'ffefd5',\n peachpuff: 'ffdab9',\n peru: 'cd853f',\n pink: 'ffc0cb',\n plum: 'dda0dd',\n powderblue: 'b0e0e6',\n purple: '800080',\n red: 'ff0000',\n rosybrown: 'bc8f8f',\n royalblue: '4169e1',\n saddlebrown: '8b4513',\n salmon: 'fa8072',\n sandybrown: 'f4a460',\n seagreen: '2e8b57',\n seashell: 'fff5ee',\n sienna: 'a0522d',\n silver: 'c0c0c0',\n skyblue: '87ceeb',\n slateblue: '6a5acd',\n slategray: '708090',\n snow: 'fffafa',\n springgreen: '00ff7f',\n steelblue: '4682b4',\n tan: 'd2b48c',\n teal: '008080',\n thistle: 'd8bfd8',\n tomato: 'ff6347',\n turquoise: '40e0d0',\n violet: 'ee82ee',\n violetred: 'd02090',\n wheat: 'f5deb3',\n white: 'ffffff',\n whitesmoke: 'f5f5f5',\n yellow: 'ffff00',\n yellowgreen: '9acd32'\n};\n\n// array of color definition objects\nconst colorDefs = [\n {\n re: /^rgb\\((\\d{1,3}),\\s*(\\d{1,3}),\\s*(\\d{1,3})\\)$/,\n // re: /^rgb\\((?\\d{1,3}),\\s*(?\\d{1,3}),\\s*(?\\d{1,3})\\)$/,\n example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b));\n }\n },\n {\n re: /^(\\w{2})(\\w{2})(\\w{2})$/,\n // re: /^(?\\w{2})(?\\w{2})(?\\w{2})$/,\n example: ['#00ff00', '336699'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b, 16));\n }\n },\n {\n re: /^(\\w)(\\w)(\\w)$/,\n // re: /^(?\\w{1})(?\\w{1})(?\\w{1})$/,\n example: ['#fb0', 'f0f'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b + b, 16));\n }\n }\n];\n\n/**\n * A class to parse color values.\n */\nexport default class RGBColor {\n /**\n * @param {string} colorString\n */\n constructor (colorString) {\n this.ok = false;\n\n // strip any leading #\n if (colorString.charAt(0) === '#') { // remove # if any\n colorString = colorString.substr(1, 6);\n }\n\n colorString = colorString.replace(/ /g, '');\n colorString = colorString.toLowerCase();\n\n // before getting into regexps, try simple matches\n // and overwrite the input\n if (colorString in simpleColors) {\n colorString = simpleColors[colorString];\n }\n // end of simple type-in colors\n\n // search through the definitions to find a match\n\n colorDefs.forEach(({re, process: processor}) => {\n const bits = re.exec(colorString);\n if (bits) {\n const [r, g, b] = processor(...bits);\n Object.assign(this, {r, g, b});\n this.ok = true;\n }\n });\n\n // validate/cleanup values\n this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);\n this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);\n this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);\n }\n\n // some getters\n /**\n * @returns {string}\n */\n toRGB () {\n return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';\n }\n\n /**\n * @returns {string}\n */\n toHex () {\n let r = this.r.toString(16);\n let g = this.g.toString(16);\n let b = this.b.toString(16);\n if (r.length === 1) { r = '0' + r; }\n if (g.length === 1) { g = '0' + g; }\n if (b.length === 1) { b = '0' + b; }\n return '#' + r + g + b;\n }\n\n /**\n * Offers a bulleted list of help.\n * @returns {HTMLUListElement}\n */\n static getHelpXML () {\n const examples = [\n // add regexps\n ...colorDefs.flatMap(({example}) => {\n return example;\n }),\n // add type-in colors\n ...Object.keys(simpleColors)\n ];\n\n const xml = document.createElement('ul');\n xml.setAttribute('id', 'rgbcolor-examples');\n\n xml.append(...examples.map((example) => {\n try {\n const listItem = document.createElement('li');\n const listColor = new RGBColor(example);\n const exampleDiv = document.createElement('div');\n exampleDiv.style.cssText = `\n margin: 3px;\n border: 1px solid black;\n background: ${listColor.toHex()};\n color: ${listColor.toHex()};`;\n exampleDiv.append('test');\n const listItemValue = ` ${example} -> ${listColor.toRGB()} -> ${listColor.toHex()}`;\n listItem.append(exampleDiv, listItemValue);\n return listItem;\n } catch (e) {\n return '';\n }\n }));\n return xml;\n }\n}\n","function _typeof(obj) {\n \"@babel/helpers - typeof\";\n\n if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") {\n _typeof = function (obj) {\n return typeof obj;\n };\n } else {\n _typeof = function (obj) {\n return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj;\n };\n }\n\n return _typeof(obj);\n}\n\nfunction _classCallCheck(instance, Constructor) {\n if (!(instance instanceof Constructor)) {\n throw new TypeError(\"Cannot call a class as a function\");\n }\n}\n\n/* eslint-disable no-bitwise, unicorn/prefer-query-selector */\n\n/**\n* StackBlur - a fast almost Gaussian Blur For Canvas\n*\n* In case you find this class useful - especially in commercial projects -\n* I am not totally unhappy for a small donation to my PayPal account\n* mario@quasimondo.de\n*\n* Or support me on flattr:\n* {@link https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript}.\n*\n* @module StackBlur\n* @author Mario Klingemann\n* Contact: mario@quasimondo.com\n* Website: {@link http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html}\n* Twitter: @quasimondo\n*\n* @copyright (c) 2010 Mario Klingemann\n*\n* Permission is hereby granted, free of charge, to any person\n* obtaining a copy of this software and associated documentation\n* files (the \"Software\"), to deal in the Software without\n* restriction, including without limitation the rights to use,\n* copy, modify, merge, publish, distribute, sublicense, and/or sell\n* copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following\n* conditions:\n*\n* The above copyright notice and this permission notice shall be\n* included in all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n* OTHER DEALINGS IN THE SOFTWARE.\n*/\n\n/* eslint-disable max-len */\nvar mulTable = [512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259];\nvar shgTable = [9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24];\n/* eslint-enable max-len */\n\n/**\n * @param {string|HTMLImageElement} img\n * @param {string|HTMLCanvasElement} canvas\n * @param {Float} radius\n * @param {boolean} blurAlphaChannel\n * @returns {undefined}\n */\n\nfunction processImage(img, canvas, radius, blurAlphaChannel) {\n if (typeof img === 'string') {\n img = document.getElementById(img);\n }\n\n if (!img || !('naturalWidth' in img)) {\n return;\n }\n\n var w = img.naturalWidth;\n var h = img.naturalHeight;\n\n if (typeof canvas === 'string') {\n canvas = document.getElementById(canvas);\n }\n\n if (!canvas || !('getContext' in canvas)) {\n return;\n }\n\n canvas.style.width = w + 'px';\n canvas.style.height = h + 'px';\n canvas.width = w;\n canvas.height = h;\n var context = canvas.getContext('2d');\n context.clearRect(0, 0, w, h);\n context.drawImage(img, 0, 0);\n\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n if (blurAlphaChannel) {\n processCanvasRGBA(canvas, 0, 0, w, h, radius);\n } else {\n processCanvasRGB(canvas, 0, 0, w, h, radius);\n }\n}\n/**\n * @param {string|HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @throws {Error|TypeError}\n * @returns {ImageData} See {@link https://html.spec.whatwg.org/multipage/canvas.html#imagedata}\n */\n\n\nfunction getImageDataFromCanvas(canvas, topX, topY, width, height) {\n if (typeof canvas === 'string') {\n canvas = document.getElementById(canvas);\n }\n\n if (!canvas || _typeof(canvas) !== 'object' || !('getContext' in canvas)) {\n throw new TypeError('Expecting canvas with `getContext` method ' + 'in processCanvasRGB(A) calls!');\n }\n\n var context = canvas.getContext('2d');\n\n try {\n return context.getImageData(topX, topY, width, height);\n } catch (e) {\n throw new Error('unable to access image data: ' + e);\n }\n}\n/**\n * @param {HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {undefined}\n */\n\n\nfunction processCanvasRGBA(canvas, topX, topY, width, height, radius) {\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n radius |= 0;\n var imageData = getImageDataFromCanvas(canvas, topX, topY, width, height);\n imageData = processImageDataRGBA(imageData, topX, topY, width, height, radius);\n canvas.getContext('2d').putImageData(imageData, topX, topY);\n}\n/**\n * @param {ImageData} imageData\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {ImageData}\n */\n\n\nfunction processImageDataRGBA(imageData, topX, topY, width, height, radius) {\n var pixels = imageData.data;\n var x, y, i, p, yp, yi, yw, rSum, gSum, bSum, aSum, rOutSum, gOutSum, bOutSum, aOutSum, rInSum, gInSum, bInSum, aInSum, pr, pg, pb, pa, rbs;\n var div = 2 * radius + 1; // const w4 = width << 2;\n\n var widthMinus1 = width - 1;\n var heightMinus1 = height - 1;\n var radiusPlus1 = radius + 1;\n var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;\n var stackStart = new BlurStack();\n var stack = stackStart;\n var stackEnd;\n\n for (i = 1; i < div; i++) {\n stack = stack.next = new BlurStack();\n\n if (i === radiusPlus1) {\n stackEnd = stack;\n }\n }\n\n stack.next = stackStart;\n var stackIn = null;\n var stackOut = null;\n yw = yi = 0;\n var mulSum = mulTable[radius];\n var shgSum = shgTable[radius];\n\n for (y = 0; y < height; y++) {\n rInSum = gInSum = bInSum = aInSum = rSum = gSum = bSum = aSum = 0;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n aOutSum = radiusPlus1 * (pa = pixels[yi + 3]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n aSum += sumFactor * pa;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack.a = pa;\n stack = stack.next;\n }\n\n for (i = 1; i < radiusPlus1; i++) {\n p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);\n rSum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[p + 1]) * rbs;\n bSum += (stack.b = pb = pixels[p + 2]) * rbs;\n aSum += (stack.a = pa = pixels[p + 3]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n aInSum += pa;\n stack = stack.next;\n }\n\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (x = 0; x < width; x++) {\n pixels[yi + 3] = pa = aSum * mulSum >> shgSum;\n\n if (pa !== 0) {\n pa = 255 / pa;\n pixels[yi] = (rSum * mulSum >> shgSum) * pa;\n pixels[yi + 1] = (gSum * mulSum >> shgSum) * pa;\n pixels[yi + 2] = (bSum * mulSum >> shgSum) * pa;\n } else {\n pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0;\n }\n\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n aSum -= aOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n aOutSum -= stackIn.a;\n p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2;\n rInSum += stackIn.r = pixels[p];\n gInSum += stackIn.g = pixels[p + 1];\n bInSum += stackIn.b = pixels[p + 2];\n aInSum += stackIn.a = pixels[p + 3];\n rSum += rInSum;\n gSum += gInSum;\n bSum += bInSum;\n aSum += aInSum;\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n aOutSum += pa = stackOut.a;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n aInSum -= pa;\n stackOut = stackOut.next;\n yi += 4;\n }\n\n yw += width;\n }\n\n for (x = 0; x < width; x++) {\n gInSum = bInSum = aInSum = rInSum = gSum = bSum = aSum = rSum = 0;\n yi = x << 2;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n aOutSum = radiusPlus1 * (pa = pixels[yi + 3]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n aSum += sumFactor * pa;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack.a = pa;\n stack = stack.next;\n }\n\n yp = width;\n\n for (i = 1; i <= radius; i++) {\n yi = yp + x << 2;\n rSum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[yi + 1]) * rbs;\n bSum += (stack.b = pb = pixels[yi + 2]) * rbs;\n aSum += (stack.a = pa = pixels[yi + 3]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n aInSum += pa;\n stack = stack.next;\n\n if (i < heightMinus1) {\n yp += width;\n }\n }\n\n yi = x;\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (y = 0; y < height; y++) {\n p = yi << 2;\n pixels[p + 3] = pa = aSum * mulSum >> shgSum;\n\n if (pa > 0) {\n pa = 255 / pa;\n pixels[p] = (rSum * mulSum >> shgSum) * pa;\n pixels[p + 1] = (gSum * mulSum >> shgSum) * pa;\n pixels[p + 2] = (bSum * mulSum >> shgSum) * pa;\n } else {\n pixels[p] = pixels[p + 1] = pixels[p + 2] = 0;\n }\n\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n aSum -= aOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n aOutSum -= stackIn.a;\n p = x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << 2;\n rSum += rInSum += stackIn.r = pixels[p];\n gSum += gInSum += stackIn.g = pixels[p + 1];\n bSum += bInSum += stackIn.b = pixels[p + 2];\n aSum += aInSum += stackIn.a = pixels[p + 3];\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n aOutSum += pa = stackOut.a;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n aInSum -= pa;\n stackOut = stackOut.next;\n yi += width;\n }\n }\n\n return imageData;\n}\n/**\n * @param {HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {undefined}\n */\n\n\nfunction processCanvasRGB(canvas, topX, topY, width, height, radius) {\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n radius |= 0;\n var imageData = getImageDataFromCanvas(canvas, topX, topY, width, height);\n imageData = processImageDataRGB(imageData, topX, topY, width, height, radius);\n canvas.getContext('2d').putImageData(imageData, topX, topY);\n}\n/**\n * @param {ImageData} imageData\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {ImageData}\n */\n\n\nfunction processImageDataRGB(imageData, topX, topY, width, height, radius) {\n var pixels = imageData.data;\n var x, y, i, p, yp, yi, yw, rSum, gSum, bSum, rOutSum, gOutSum, bOutSum, rInSum, gInSum, bInSum, pr, pg, pb, rbs;\n var div = 2 * radius + 1; // const w4 = width << 2;\n\n var widthMinus1 = width - 1;\n var heightMinus1 = height - 1;\n var radiusPlus1 = radius + 1;\n var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;\n var stackStart = new BlurStack();\n var stack = stackStart;\n var stackEnd;\n\n for (i = 1; i < div; i++) {\n stack = stack.next = new BlurStack();\n\n if (i === radiusPlus1) {\n stackEnd = stack;\n }\n }\n\n stack.next = stackStart;\n var stackIn = null;\n var stackOut = null;\n yw = yi = 0;\n var mulSum = mulTable[radius];\n var shgSum = shgTable[radius];\n\n for (y = 0; y < height; y++) {\n rInSum = gInSum = bInSum = rSum = gSum = bSum = 0;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack = stack.next;\n }\n\n for (i = 1; i < radiusPlus1; i++) {\n p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);\n rSum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[p + 1]) * rbs;\n bSum += (stack.b = pb = pixels[p + 2]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n stack = stack.next;\n }\n\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (x = 0; x < width; x++) {\n pixels[yi] = rSum * mulSum >> shgSum;\n pixels[yi + 1] = gSum * mulSum >> shgSum;\n pixels[yi + 2] = bSum * mulSum >> shgSum;\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2;\n rInSum += stackIn.r = pixels[p];\n gInSum += stackIn.g = pixels[p + 1];\n bInSum += stackIn.b = pixels[p + 2];\n rSum += rInSum;\n gSum += gInSum;\n bSum += bInSum;\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n stackOut = stackOut.next;\n yi += 4;\n }\n\n yw += width;\n }\n\n for (x = 0; x < width; x++) {\n gInSum = bInSum = rInSum = gSum = bSum = rSum = 0;\n yi = x << 2;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack = stack.next;\n }\n\n yp = width;\n\n for (i = 1; i <= radius; i++) {\n yi = yp + x << 2;\n rSum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[yi + 1]) * rbs;\n bSum += (stack.b = pb = pixels[yi + 2]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n stack = stack.next;\n\n if (i < heightMinus1) {\n yp += width;\n }\n }\n\n yi = x;\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (y = 0; y < height; y++) {\n p = yi << 2;\n pixels[p] = rSum * mulSum >> shgSum;\n pixels[p + 1] = gSum * mulSum >> shgSum;\n pixels[p + 2] = bSum * mulSum >> shgSum;\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n p = x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << 2;\n rSum += rInSum += stackIn.r = pixels[p];\n gSum += gInSum += stackIn.g = pixels[p + 1];\n bSum += bInSum += stackIn.b = pixels[p + 2];\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n stackOut = stackOut.next;\n yi += width;\n }\n }\n\n return imageData;\n}\n/**\n *\n */\n\n\nvar BlurStack =\n/**\n * Set properties.\n */\nfunction BlurStack() {\n _classCallCheck(this, BlurStack);\n\n this.r = 0;\n this.g = 0;\n this.b = 0;\n this.a = 0;\n this.next = null;\n};\n\nexport { BlurStack, processCanvasRGB as canvasRGB, processCanvasRGBA as canvasRGBA, processImage as image, processImageDataRGB as imageDataRGB, processImageDataRGBA as imageDataRGBA };\n","/* eslint-disable new-cap, class-methods-use-this, jsdoc/require-jsdoc */\n// Todo: Compare with latest canvg (add any improvements of ours) and add full JSDocs (denoting links to standard APIs and which are custom): https://github.com/canvg/canvg\n/**\n * Javascript SVG parser and renderer on Canvas.\n * @file canvg.js\n * @module canvg\n * @license MIT\n * @author Gabe Lerner \n * @see https://github.com/canvg/canvg\n */\n\nimport RGBColor from './rgbcolor.js';\nimport {canvasRGBA} from '../../external/stackblur-canvas/dist/stackblur-es.js';\n\n/**\n * Whether a value is `null` or `undefined`.\n * @param {any} val\n * @returns {boolean}\n */\nconst isNullish = (val) => {\n return val === null || val === undefined;\n};\n\n/**\n* @callback module:canvg.ForceRedraw\n* @returns {boolean}\n*/\n\n/**\n* @typedef {PlainObject} module:canvg.CanvgOptions\n* @property {boolean} ignoreMouse true => ignore mouse events\n* @property {boolean} ignoreAnimation true => ignore animations\n* @property {boolean} ignoreDimensions true => does not try to resize canvas\n* @property {boolean} ignoreClear true => does not clear canvas\n* @property {Integer} offsetX int => draws at a x offset\n* @property {Integer} offsetY int => draws at a y offset\n* @property {Integer} scaleWidth int => scales horizontally to width\n* @property {Integer} scaleHeight int => scales vertically to height\n* @property {module:canvg.ForceRedraw} forceRedraw function => will call the function on every frame, if it returns true, will redraw\n* @property {boolean} log Adds log function\n* @property {boolean} useCORS Whether to set CORS `crossOrigin` for the image to `Anonymous`\n*/\n\n/**\n* If called with no arguments, it will replace all `` elements on the page\n* with `` elements.\n* @function module:canvg.canvg\n* @param {HTMLCanvasElement|string} target canvas element or the id of a canvas element\n* @param {string|XMLDocument} s - svg string, url to svg file, or xml document\n* @param {module:canvg.CanvgOptions} [opts] Optional hash of options\n* @returns {Promise} All the function after the first render is completed with dom\n*/\nexport const canvg = function (target, s, opts) {\n // no parameters\n if (isNullish(target) && isNullish(s) && isNullish(opts)) {\n const svgTags = document.querySelectorAll('svg');\n return Promise.all([...svgTags].map((svgTag) => {\n const c = document.createElement('canvas');\n c.width = svgTag.clientWidth;\n c.height = svgTag.clientHeight;\n svgTag.before(c);\n svgTag.remove();\n const div = document.createElement('div');\n div.append(svgTag);\n return canvg(c, div.innerHTML);\n }));\n }\n\n if (typeof target === 'string') {\n target = document.getElementById(target);\n }\n\n // store class on canvas\n if (!isNullish(target.svg)) target.svg.stop();\n const svg = build(opts || {});\n // on i.e. 8 for flash canvas, we can't assign the property so check for it\n if (!(target.childNodes.length === 1 && target.childNodes[0].nodeName === 'OBJECT')) {\n target.svg = svg;\n }\n\n const ctx = target.getContext('2d');\n if (typeof s.documentElement !== 'undefined') {\n // load from xml doc\n return svg.loadXmlDoc(ctx, s);\n }\n if (s.substr(0, 1) === '<') {\n // load from xml string\n return svg.loadXml(ctx, s);\n }\n // load from url\n return svg.load(ctx, s);\n};\n\n/* eslint-disable jsdoc/check-types */\n/**\n* @param {module:canvg.CanvgOptions} opts\n* @returns {object}\n* @todo Flesh out exactly what object is returned here (after updating to latest and reincluding our changes here and those of StackBlur)\n*/\nfunction build (opts) {\n /* eslint-enable jsdoc/check-types */\n const svg = {opts};\n\n svg.FRAMERATE = 30;\n svg.MAX_VIRTUAL_PIXELS = 30000;\n\n svg.log = function (msg) { /* */ };\n if (svg.opts.log === true && typeof console !== 'undefined') {\n svg.log = function (msg) { console.log(msg); }; // eslint-disable-line no-console\n }\n\n // globals\n svg.init = function (ctx) {\n let uniqueId = 0;\n svg.UniqueId = function () {\n uniqueId++;\n return 'canvg' + uniqueId;\n };\n svg.Definitions = {};\n svg.Styles = {};\n svg.Animations = [];\n svg.Images = [];\n svg.ctx = ctx;\n svg.ViewPort = {\n viewPorts: [],\n Clear () { this.viewPorts = []; },\n SetCurrent (width, height) { this.viewPorts.push({width, height}); },\n RemoveCurrent () { this.viewPorts.pop(); },\n Current () { return this.viewPorts[this.viewPorts.length - 1]; },\n width () { return this.Current().width; },\n height () { return this.Current().height; },\n ComputeSize (d) {\n if (!isNullish(d) && typeof d === 'number') return d;\n if (d === 'x') return this.width();\n if (d === 'y') return this.height();\n return Math.sqrt(\n (this.width() ** 2) + (this.height() ** 2)\n ) / Math.sqrt(2);\n }\n };\n };\n svg.init();\n\n // images loaded\n svg.ImagesLoaded = function () {\n return svg.Images.every((img) => img.loaded);\n };\n\n // trim\n svg.trim = function (s) {\n return s.replace(/^\\s+|\\s+$/g, '');\n };\n\n // compress spaces\n svg.compressSpaces = function (s) {\n return s.replace(/\\s+/gm, ' ');\n };\n\n // ajax\n // Todo: Replace with `fetch` and polyfill\n svg.ajax = function (url, asynch) {\n const AJAX = window.XMLHttpRequest\n ? new XMLHttpRequest()\n : new window.ActiveXObject('Microsoft.XMLHTTP');\n if (asynch) {\n return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new\n const req = AJAX.open('GET', url, true);\n req.addEventListener('load', () => {\n resolve(AJAX.responseText);\n });\n AJAX.send(null);\n });\n }\n\n AJAX.open('GET', url, false);\n AJAX.send(null);\n return AJAX.responseText;\n };\n\n // parse xml\n svg.parseXml = function (xml) {\n if (window.DOMParser) {\n const parser = new DOMParser();\n return parser.parseFromString(xml, 'text/xml');\n }\n xml = xml.replace(/]*>/, '');\n const xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');\n xmlDoc.async = 'false';\n xmlDoc.loadXML(xml);\n return xmlDoc;\n };\n\n // text extensions\n // get the text baseline\n const textBaselineMapping = {\n baseline: 'alphabetic',\n 'before-edge': 'top',\n 'text-before-edge': 'top',\n middle: 'middle',\n central: 'middle',\n 'after-edge': 'bottom',\n 'text-after-edge': 'bottom',\n ideographic: 'ideographic',\n alphabetic: 'alphabetic',\n hanging: 'hanging',\n mathematical: 'alphabetic'\n };\n\n svg.Property = class Property {\n constructor (name, value) {\n this.name = name;\n this.value = value;\n }\n\n getValue () {\n return this.value;\n }\n\n hasValue () {\n return (!isNullish(this.value) && this.value !== '');\n }\n\n // return the numerical value of the property\n numValue () {\n if (!this.hasValue()) return 0;\n\n let n = Number.parseFloat(this.value);\n if (String(this.value).endsWith('%')) {\n n /= 100.0;\n }\n return n;\n }\n\n valueOrDefault (def) {\n if (this.hasValue()) return this.value;\n return def;\n }\n\n numValueOrDefault (def) {\n if (this.hasValue()) return this.numValue();\n return def;\n }\n\n // color extensions\n // augment the current color value with the opacity\n addOpacity (opacityProp) {\n let newValue = this.value;\n if (!isNullish(opacityProp.value) && opacityProp.value !== '' && typeof this.value === 'string') { // can only add opacity to colors, not patterns\n const color = new RGBColor(this.value);\n if (color.ok) {\n newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacityProp.numValue() + ')';\n }\n }\n return new svg.Property(this.name, newValue);\n }\n\n // definition extensions\n // get the definition from the definitions table\n getDefinition () {\n let name = this.value.match(/#([^)'\"]+)/);\n if (name) { name = name[1]; }\n if (!name) { name = this.value; }\n return svg.Definitions[name];\n }\n\n isUrlDefinition () {\n return this.value.startsWith('url(');\n }\n\n getFillStyleDefinition (e, opacityProp) {\n let def = this.getDefinition();\n\n // gradient\n if (!isNullish(def) && def.createGradient) {\n return def.createGradient(svg.ctx, e, opacityProp);\n }\n\n // pattern\n if (!isNullish(def) && def.createPattern) {\n if (def.getHrefAttribute().hasValue()) {\n const pt = def.attribute('patternTransform');\n def = def.getHrefAttribute().getDefinition();\n if (pt.hasValue()) { def.attribute('patternTransform', true).value = pt.value; }\n }\n return def.createPattern(svg.ctx, e);\n }\n\n return null;\n }\n\n // length extensions\n getDPI (viewPort) {\n return 96.0; // TODO: compute?\n }\n\n getEM (viewPort) {\n let em = 12;\n\n const fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);\n if (fontSize.hasValue()) em = fontSize.toPixels(viewPort);\n\n return em;\n }\n\n getUnits () {\n return String(this.value).replace(/[\\d.-]/g, '');\n }\n\n // get the length as pixels\n toPixels (viewPort, processPercent) {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('em')) return this.numValue() * this.getEM(viewPort);\n if (s.endsWith('ex')) return this.numValue() * this.getEM(viewPort) / 2.0;\n if (s.endsWith('px')) return this.numValue();\n if (s.endsWith('pt')) return this.numValue() * this.getDPI(viewPort) * (1.0 / 72.0);\n if (s.endsWith('pc')) return this.numValue() * 15;\n if (s.endsWith('cm')) return this.numValue() * this.getDPI(viewPort) / 2.54;\n if (s.endsWith('mm')) return this.numValue() * this.getDPI(viewPort) / 25.4;\n if (s.endsWith('in')) return this.numValue() * this.getDPI(viewPort);\n if (s.endsWith('%')) return this.numValue() * svg.ViewPort.ComputeSize(viewPort);\n const n = this.numValue();\n if (processPercent && n < 1.0) return n * svg.ViewPort.ComputeSize(viewPort);\n return n;\n }\n\n // time extensions\n // get the time as milliseconds\n toMilliseconds () {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('ms')) return this.numValue();\n if (s.endsWith('s')) return this.numValue() * 1000;\n return this.numValue();\n }\n\n // angle extensions\n // get the angle as radians\n toRadians () {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('deg')) return this.numValue() * (Math.PI / 180.0);\n if (s.endsWith('grad')) return this.numValue() * (Math.PI / 200.0);\n if (s.endsWith('rad')) return this.numValue();\n return this.numValue() * (Math.PI / 180.0);\n }\n\n toTextBaseline () {\n if (!this.hasValue()) return null;\n return textBaselineMapping[this.value];\n }\n };\n\n // fonts\n svg.Font = {\n Styles: 'normal|italic|oblique|inherit',\n Variants: 'normal|small-caps|inherit',\n Weights: 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit',\n\n CreateFont (fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {\n const f = !isNullish(inherit) ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);\n return {\n fontFamily: fontFamily || f.fontFamily,\n fontSize: fontSize || f.fontSize,\n fontStyle: fontStyle || f.fontStyle,\n fontWeight: fontWeight || f.fontWeight,\n fontVariant: fontVariant || f.fontVariant,\n toString () {\n return [\n this.fontStyle, this.fontVariant, this.fontWeight,\n this.fontSize, this.fontFamily\n ].join(' ');\n }\n };\n },\n\n Parse (s) {\n const f = {};\n const ds = svg.trim(svg.compressSpaces(s || '')).split(' ');\n const set = {\n fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false\n };\n let ff = '';\n ds.forEach((d) => {\n if (!set.fontStyle && this.Styles.includes(d)) {\n if (d !== 'inherit') {\n f.fontStyle = d;\n }\n set.fontStyle = true;\n } else if (!set.fontVariant && this.Variants.includes(d)) {\n if (d !== 'inherit') {\n f.fontVariant = d;\n }\n set.fontStyle = set.fontVariant = true;\n } else if (!set.fontWeight && this.Weights.includes(d)) {\n if (d !== 'inherit') {\n f.fontWeight = d;\n }\n set.fontStyle = set.fontVariant = set.fontWeight = true;\n } else if (!set.fontSize) {\n if (d !== 'inherit') {\n f.fontSize = d.split('/')[0];\n }\n set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true;\n } else if (d !== 'inherit') {\n ff += d;\n }\n });\n if (ff !== '') { f.fontFamily = ff; }\n return f;\n }\n };\n\n // points and paths\n svg.ToNumberArray = function (s) {\n const a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');\n return a.map((_a) => Number.parseFloat(_a));\n };\n svg.Point = class {\n constructor (x, y) {\n this.x = x;\n this.y = y;\n }\n\n angleTo (p) {\n return Math.atan2(p.y - this.y, p.x - this.x);\n }\n\n applyTransform (v) {\n const xp = this.x * v[0] + this.y * v[2] + v[4];\n const yp = this.x * v[1] + this.y * v[3] + v[5];\n this.x = xp;\n this.y = yp;\n }\n };\n\n svg.CreatePoint = function (s) {\n const a = svg.ToNumberArray(s);\n return new svg.Point(a[0], a[1]);\n };\n svg.CreatePath = function (s) {\n const a = svg.ToNumberArray(s);\n const path = [];\n for (let i = 0; i < a.length; i += 2) {\n path.push(new svg.Point(a[i], a[i + 1]));\n }\n return path;\n };\n\n // bounding box\n svg.BoundingBox = class {\n constructor (x1, y1, x2, y2) { // pass in initial points if you want\n this.x1 = Number.NaN;\n this.y1 = Number.NaN;\n this.x2 = Number.NaN;\n this.y2 = Number.NaN;\n this.addPoint(x1, y1);\n this.addPoint(x2, y2);\n }\n\n x () { return this.x1; }\n y () { return this.y1; }\n width () { return this.x2 - this.x1; }\n height () { return this.y2 - this.y1; }\n\n addPoint (x, y) {\n if (!isNullish(x)) {\n if (isNaN(this.x1) || isNaN(this.x2)) {\n this.x1 = x;\n this.x2 = x;\n }\n if (x < this.x1) this.x1 = x;\n if (x > this.x2) this.x2 = x;\n }\n\n if (!isNullish(y)) {\n if (isNaN(this.y1) || isNaN(this.y2)) {\n this.y1 = y;\n this.y2 = y;\n }\n if (y < this.y1) this.y1 = y;\n if (y > this.y2) this.y2 = y;\n }\n }\n addX (x) { this.addPoint(x, null); }\n addY (y) { this.addPoint(null, y); }\n\n addBoundingBox (bb) {\n this.addPoint(bb.x1, bb.y1);\n this.addPoint(bb.x2, bb.y2);\n }\n\n addQuadraticCurve (p0x, p0y, p1x, p1y, p2x, p2y) {\n const cp1x = p0x + 2 / 3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)\n const cp1y = p0y + 2 / 3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)\n const cp2x = cp1x + 1 / 3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)\n const cp2y = cp1y + 1 / 3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)\n this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);\n }\n\n addBezierCurve (p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {\n // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html\n const p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];\n this.addPoint(p0[0], p0[1]);\n this.addPoint(p3[0], p3[1]);\n\n for (let i = 0; i <= 1; i++) {\n const f = function (t) {\n return ((1 - t) ** 3) * p0[i] +\n 3 * ((1 - t) ** 2) * t * p1[i] +\n 3 * (1 - t) * (t ** 2) * p2[i] +\n (t ** 3) * p3[i];\n };\n\n const b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];\n const a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];\n const c = 3 * p1[i] - 3 * p0[i];\n\n if (a === 0) {\n if (b === 0) continue;\n const t = -c / b;\n if (t > 0 && t < 1) {\n if (i === 0) this.addX(f(t));\n if (i === 1) this.addY(f(t));\n }\n continue;\n }\n\n const b2ac = (b ** 2) - 4 * c * a;\n if (b2ac < 0) continue;\n const t1 = (-b + Math.sqrt(b2ac)) / (2 * a);\n if (t1 > 0 && t1 < 1) {\n if (i === 0) this.addX(f(t1));\n if (i === 1) this.addY(f(t1));\n }\n const t2 = (-b - Math.sqrt(b2ac)) / (2 * a);\n if (t2 > 0 && t2 < 1) {\n if (i === 0) this.addX(f(t2));\n if (i === 1) this.addY(f(t2));\n }\n }\n }\n\n isPointInBox (x, y) {\n return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);\n }\n };\n\n // transforms\n svg.Transform = class {\n constructor (v) {\n this.Type = {\n translate: class {\n constructor (s) {\n this.p = svg.CreatePoint(s);\n this.apply = function (ctx) {\n ctx.translate(this.p.x || 0.0, this.p.y || 0.0);\n };\n this.unapply = function (ctx) {\n ctx.translate(-1.0 * this.p.x || 0.0, -1.0 * this.p.y || 0.0);\n };\n this.applyToPoint = function (p) {\n p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n };\n }\n },\n rotate: class {\n constructor (s) {\n const a = svg.ToNumberArray(s);\n this.angle = new svg.Property('angle', a[0]);\n this.cx = a[1] || 0;\n this.cy = a[2] || 0;\n this.apply = function (ctx) {\n ctx.translate(this.cx, this.cy);\n ctx.rotate(this.angle.toRadians());\n ctx.translate(-this.cx, -this.cy);\n };\n this.unapply = function (ctx) {\n ctx.translate(this.cx, this.cy);\n ctx.rotate(-1.0 * this.angle.toRadians());\n ctx.translate(-this.cx, -this.cy);\n };\n this.applyToPoint = function (p) {\n const _a = this.angle.toRadians();\n p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n p.applyTransform([Math.cos(_a), Math.sin(_a), -Math.sin(_a), Math.cos(_a), 0, 0]);\n p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);\n };\n }\n },\n scale: class {\n constructor (s) {\n this.p = svg.CreatePoint(s);\n this.apply = function (ctx) {\n ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);\n };\n this.unapply = function (ctx) {\n ctx.scale(1.0 / this.p.x || 1.0, 1.0 / this.p.y || this.p.x || 1.0);\n };\n this.applyToPoint = function (p) {\n p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);\n };\n }\n },\n matrix: class {\n constructor (s) {\n this.m = svg.ToNumberArray(s);\n this.apply = function (ctx) {\n ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);\n };\n this.applyToPoint = function (p) {\n p.applyTransform(this.m);\n };\n }\n }\n };\n Object.assign(this.Type, {\n SkewBase: class extends this.Type.matrix {\n constructor (s) {\n super(s);\n this.angle = new svg.Property('angle', s);\n }\n }\n });\n Object.assign(this.Type, {\n skewX: class extends this.Type.SkewBase {\n constructor (s) {\n super(s);\n this.m = [1, 0, Math.tan(this.angle.toRadians()), 1, 0, 0];\n }\n },\n skewY: class extends this.Type.SkewBase {\n constructor (s) {\n super(s);\n this.m = [1, Math.tan(this.angle.toRadians()), 0, 1, 0, 0];\n }\n }\n });\n\n const data = svg.trim(svg.compressSpaces(v)).replace(\n /\\)([a-zA-Z])/g, ') $1'\n ).replace(/\\)(\\s?,\\s?)/g, ') ').split(/\\s(?=[a-z])/);\n this.transforms = data.map((d) => {\n const type = svg.trim(d.split('(')[0]);\n const s = d.split('(')[1].replace(')', '');\n const transform = new this.Type[type](s);\n transform.type = type;\n return transform;\n });\n }\n\n apply (ctx) {\n this.transforms.forEach((transform) => {\n transform.apply(ctx);\n });\n }\n\n unapply (ctx) {\n for (let i = this.transforms.length - 1; i >= 0; i--) {\n this.transforms[i].unapply(ctx);\n }\n }\n\n applyToPoint (p) {\n this.transforms.forEach((transform) => {\n transform.applyToPoint(p);\n });\n }\n };\n\n // aspect ratio\n svg.AspectRatio = function (ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {\n // aspect ratio - https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute\n aspectRatio = svg.compressSpaces(aspectRatio);\n aspectRatio = aspectRatio.replace(/^defer\\s/, ''); // ignore defer\n const align = aspectRatio.split(' ')[0] || 'xMidYMid';\n const meetOrSlice = aspectRatio.split(' ')[1] || 'meet';\n\n // calculate scale\n const scaleX = width / desiredWidth;\n const scaleY = height / desiredHeight;\n const scaleMin = Math.min(scaleX, scaleY);\n const scaleMax = Math.max(scaleX, scaleY);\n if (meetOrSlice === 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }\n if (meetOrSlice === 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }\n\n refX = new svg.Property('refX', refX);\n refY = new svg.Property('refY', refY);\n if (refX.hasValue() && refY.hasValue()) {\n ctx.translate(-scaleMin * refX.toPixels('x'), -scaleMin * refY.toPixels('y'));\n } else {\n // align\n if (align.startsWith('xMid') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleY) || (meetOrSlice === 'slice' && scaleMax === scaleY))) {\n ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);\n }\n if (align.endsWith('YMid') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleX) || (meetOrSlice === 'slice' && scaleMax === scaleX))) {\n ctx.translate(0, height / 2.0 - desiredHeight / 2.0);\n }\n if (align.startsWith('xMax') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleY) || (meetOrSlice === 'slice' && scaleMax === scaleY))) {\n ctx.translate(width - desiredWidth, 0);\n }\n if (align.endsWith('YMax') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleX) ||\n (meetOrSlice === 'slice' && scaleMax === scaleX)\n )\n ) {\n ctx.translate(0, height - desiredHeight);\n }\n }\n\n // scale\n if (align === 'none') ctx.scale(scaleX, scaleY);\n else if (meetOrSlice === 'meet') ctx.scale(scaleMin, scaleMin);\n else if (meetOrSlice === 'slice') ctx.scale(scaleMax, scaleMax);\n\n // translate\n ctx.translate(isNullish(minX) ? 0 : -minX, isNullish(minY) ? 0 : -minY);\n };\n\n // elements\n svg.Element = {};\n\n svg.EmptyProperty = new svg.Property('EMPTY', '');\n\n svg.Element.ElementBase = class {\n constructor (node) {\n // Argument from inheriting class\n this.captureTextNodes = arguments[1]; // eslint-disable-line prefer-rest-params\n this.attributes = {};\n this.styles = {};\n this.children = [];\n if (!isNullish(node) && node.nodeType === 1) { // ELEMENT_NODE\n // add children\n [...node.childNodes].forEach((childNode) => {\n if (childNode.nodeType === 1) {\n this.addChild(childNode, true); // ELEMENT_NODE\n }\n if (this.captureTextNodes && (\n childNode.nodeType === 3 || childNode.nodeType === 4\n )) {\n const text = childNode.nodeValue || childNode.text || '';\n if (svg.trim(svg.compressSpaces(text)) !== '') {\n this.addChild(new svg.Element.tspan(childNode), false); // TEXT_NODE\n }\n }\n });\n\n // add attributes\n [...node.attributes].forEach(({nodeName, nodeValue}) => {\n this.attributes[nodeName] = new svg.Property(\n nodeName,\n nodeValue\n );\n });\n // add tag styles\n let styles = svg.Styles[node.nodeName];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n\n // add class styles\n if (this.attribute('class').hasValue()) {\n const classes = svg.compressSpaces(this.attribute('class').value).split(' ');\n classes.forEach((clss) => {\n styles = svg.Styles['.' + clss];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n styles = svg.Styles[node.nodeName + '.' + clss];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n });\n }\n\n // add id styles\n if (this.attribute('id').hasValue()) {\n const _styles = svg.Styles['#' + this.attribute('id').value];\n if (!isNullish(_styles)) {\n Object.entries(_styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n }\n\n // add inline styles\n if (this.attribute('style').hasValue()) {\n const _styles = this.attribute('style').value.split(';');\n _styles.forEach((style) => {\n if (svg.trim(style) !== '') {\n let {name, value} = style.split(':');\n name = svg.trim(name);\n value = svg.trim(value);\n this.styles[name] = new svg.Property(name, value);\n }\n });\n }\n\n // add id\n if (this.attribute('id').hasValue()) {\n if (isNullish(svg.Definitions[this.attribute('id').value])) {\n svg.Definitions[this.attribute('id').value] = this;\n }\n }\n }\n }\n\n // get or create attribute\n attribute (name, createIfNotExists) {\n let a = this.attributes[name];\n if (!isNullish(a)) return a;\n\n if (createIfNotExists === true) { a = new svg.Property(name, ''); this.attributes[name] = a; }\n return a || svg.EmptyProperty;\n }\n\n getHrefAttribute () {\n for (const a in this.attributes) {\n if (a.endsWith(':href')) {\n return this.attributes[a];\n }\n }\n return svg.EmptyProperty;\n }\n\n // get or create style, crawls up node tree\n style (name, createIfNotExists, skipAncestors) {\n let s = this.styles[name];\n if (!isNullish(s)) return s;\n\n const a = this.attribute(name);\n if (!isNullish(a) && a.hasValue()) {\n this.styles[name] = a; // move up to me to cache\n return a;\n }\n\n if (skipAncestors !== true) {\n const p = this.parent;\n if (!isNullish(p)) {\n const ps = p.style(name);\n if (!isNullish(ps) && ps.hasValue()) {\n return ps;\n }\n }\n }\n\n if (createIfNotExists === true) { s = new svg.Property(name, ''); this.styles[name] = s; }\n return s || svg.EmptyProperty;\n }\n\n // base render\n render (ctx) {\n // don't render display=none\n if (this.style('display').value === 'none') return;\n\n // don't render visibility=hidden\n if (this.style('visibility').value === 'hidden') return;\n\n ctx.save();\n if (this.attribute('mask').hasValue()) { // mask\n const mask = this.attribute('mask').getDefinition();\n if (!isNullish(mask)) mask.apply(ctx, this);\n } else if (this.style('filter').hasValue()) { // filter\n const filter = this.style('filter').getDefinition();\n if (!isNullish(filter)) filter.apply(ctx, this);\n } else {\n this.setContext(ctx);\n this.renderChildren(ctx);\n this.clearContext(ctx);\n }\n ctx.restore();\n }\n\n // base set context\n setContext (ctx) {\n // OVERRIDE ME!\n }\n\n // base clear context\n clearContext (ctx) {\n // OVERRIDE ME!\n }\n\n // base render children\n renderChildren (ctx) {\n this.children.forEach((child) => {\n child.render(ctx);\n });\n }\n\n addChild (childNode, create) {\n const child = create\n ? svg.CreateElement(childNode)\n : childNode;\n child.parent = this;\n if (child.type !== 'title') { this.children.push(child); }\n }\n };\n\n svg.Element.RenderedElementBase = class extends svg.Element.ElementBase {\n setContext (ctx) {\n // fill\n if (this.style('fill').isUrlDefinition()) {\n const fs = this.style('fill').getFillStyleDefinition(this, this.style('fill-opacity'));\n if (!isNullish(fs)) ctx.fillStyle = fs;\n } else if (this.style('fill').hasValue()) {\n const fillStyle = this.style('fill');\n if (fillStyle.value === 'currentColor') fillStyle.value = this.style('color').value;\n ctx.fillStyle = (fillStyle.value === 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);\n }\n if (this.style('fill-opacity').hasValue()) {\n let fillStyle = new svg.Property('fill', ctx.fillStyle);\n fillStyle = fillStyle.addOpacity(this.style('fill-opacity'));\n ctx.fillStyle = fillStyle.value;\n }\n\n // stroke\n if (this.style('stroke').isUrlDefinition()) {\n const fs = this.style('stroke').getFillStyleDefinition(this, this.style('stroke-opacity'));\n if (!isNullish(fs)) ctx.strokeStyle = fs;\n } else if (this.style('stroke').hasValue()) {\n const strokeStyle = this.style('stroke');\n if (strokeStyle.value === 'currentColor') strokeStyle.value = this.style('color').value;\n ctx.strokeStyle = (strokeStyle.value === 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);\n }\n if (this.style('stroke-opacity').hasValue()) {\n let strokeStyle = new svg.Property('stroke', ctx.strokeStyle);\n strokeStyle = strokeStyle.addOpacity(this.style('stroke-opacity'));\n ctx.strokeStyle = strokeStyle.value;\n }\n if (this.style('stroke-width').hasValue()) {\n const newLineWidth = this.style('stroke-width').toPixels();\n ctx.lineWidth = newLineWidth === 0 ? 0.001 : newLineWidth; // browsers don't respect 0\n }\n if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;\n if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;\n if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;\n if (this.style('stroke-dasharray').hasValue() && this.style('stroke-dasharray').value !== 'none') {\n const gaps = svg.ToNumberArray(this.style('stroke-dasharray').value);\n if (typeof ctx.setLineDash !== 'undefined') {\n ctx.setLineDash(gaps);\n } else if (typeof ctx.webkitLineDash !== 'undefined') {\n ctx.webkitLineDash = gaps;\n } else if (typeof ctx.mozDash !== 'undefined' && !(gaps.length === 1 && gaps[0] === 0)) {\n ctx.mozDash = gaps;\n }\n\n const offset = this.style('stroke-dashoffset').numValueOrDefault(1);\n if (typeof ctx.lineDashOffset !== 'undefined') {\n ctx.lineDashOffset = offset;\n } else if (typeof ctx.webkitLineDashOffset !== 'undefined') {\n ctx.webkitLineDashOffset = offset;\n } else if (typeof ctx.mozDashOffset !== 'undefined') {\n ctx.mozDashOffset = offset;\n }\n }\n\n // font\n if (typeof ctx.font !== 'undefined') {\n ctx.font = svg.Font.CreateFont(\n this.style('font-style').value,\n this.style('font-variant').value,\n this.style('font-weight').value,\n this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '',\n this.style('font-family').value\n ).toString();\n }\n\n // transform\n if (this.attribute('transform').hasValue()) {\n const transform = new svg.Transform(this.attribute('transform').value);\n transform.apply(ctx);\n }\n\n // clip\n if (this.style('clip-path', false, true).hasValue()) {\n const clip = this.style('clip-path', false, true).getDefinition();\n if (!isNullish(clip)) clip.apply(ctx);\n }\n\n // opacity\n if (this.style('opacity').hasValue()) {\n ctx.globalAlpha = this.style('opacity').numValue();\n }\n }\n };\n\n svg.Element.PathElementBase = class extends svg.Element.RenderedElementBase {\n path (ctx) {\n if (!isNullish(ctx)) ctx.beginPath();\n return new svg.BoundingBox();\n }\n\n renderChildren (ctx) {\n this.path(ctx);\n svg.Mouse.checkPath(this, ctx);\n if (ctx.fillStyle !== '') {\n if (this.style('fill-rule').valueOrDefault('inherit') !== 'inherit') {\n ctx.fill(this.style('fill-rule').value);\n } else {\n ctx.fill();\n }\n }\n if (ctx.strokeStyle !== '') ctx.stroke();\n\n const markers = this.getMarkers();\n if (!isNullish(markers)) {\n if (this.style('marker-start').isUrlDefinition()) {\n const marker = this.style('marker-start').getDefinition();\n marker.render(ctx, markers[0][0], markers[0][1]);\n }\n if (this.style('marker-mid').isUrlDefinition()) {\n const marker = this.style('marker-mid').getDefinition();\n for (let i = 1; i < markers.length - 1; i++) {\n marker.render(ctx, markers[i][0], markers[i][1]);\n }\n }\n if (this.style('marker-end').isUrlDefinition()) {\n const marker = this.style('marker-end').getDefinition();\n marker.render(ctx, markers[markers.length - 1][0], markers[markers.length - 1][1]);\n }\n }\n }\n\n getBoundingBox () {\n return this.path();\n }\n\n getMarkers () {\n return null;\n }\n };\n\n // svg element\n svg.Element.svg = class extends svg.Element.RenderedElementBase {\n clearContext (ctx) {\n super.clearContext(ctx);\n svg.ViewPort.RemoveCurrent();\n }\n\n setContext (ctx) {\n // initial values and defaults\n ctx.strokeStyle = 'rgba(0,0,0,0)';\n ctx.lineCap = 'butt';\n ctx.lineJoin = 'miter';\n ctx.miterLimit = 4;\n if (typeof ctx.font !== 'undefined' && typeof window.getComputedStyle !== 'undefined') {\n ctx.font = window.getComputedStyle(ctx.canvas).getPropertyValue('font');\n }\n\n super.setContext(ctx);\n\n // create new view port\n if (!this.attribute('x').hasValue()) this.attribute('x', true).value = 0;\n if (!this.attribute('y').hasValue()) this.attribute('y', true).value = 0;\n ctx.translate(this.attribute('x').toPixels('x'), this.attribute('y').toPixels('y'));\n\n let width = svg.ViewPort.width();\n let height = svg.ViewPort.height();\n\n if (!this.attribute('width').hasValue()) this.attribute('width', true).value = '100%';\n if (!this.attribute('height').hasValue()) this.attribute('height', true).value = '100%';\n if (typeof this.root === 'undefined') {\n width = this.attribute('width').toPixels('x');\n height = this.attribute('height').toPixels('y');\n\n let x = 0;\n let y = 0;\n if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {\n x = -this.attribute('refX').toPixels('x');\n y = -this.attribute('refY').toPixels('y');\n }\n\n if (this.attribute('overflow').valueOrDefault('hidden') !== 'visible') {\n ctx.beginPath();\n ctx.moveTo(x, y);\n ctx.lineTo(width, y);\n ctx.lineTo(width, height);\n ctx.lineTo(x, height);\n ctx.closePath();\n ctx.clip();\n }\n }\n svg.ViewPort.SetCurrent(width, height);\n\n // viewbox\n if (this.attribute('viewBox').hasValue()) {\n const viewBox = svg.ToNumberArray(this.attribute('viewBox').value);\n const minX = viewBox[0];\n const minY = viewBox[1];\n width = viewBox[2];\n height = viewBox[3];\n\n svg.AspectRatio(\n ctx,\n this.attribute('preserveAspectRatio').value,\n svg.ViewPort.width(),\n width,\n svg.ViewPort.height(),\n height,\n minX,\n minY,\n this.attribute('refX').value,\n this.attribute('refY').value\n );\n\n svg.ViewPort.RemoveCurrent();\n svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);\n }\n }\n };\n\n // rect element\n svg.Element.rect = class extends svg.Element.PathElementBase {\n path (ctx) {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n let rx = this.attribute('rx').toPixels('x');\n let ry = this.attribute('ry').toPixels('y');\n if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;\n if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;\n rx = Math.min(rx, width / 2.0);\n ry = Math.min(ry, height / 2.0);\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(x + rx, y);\n ctx.lineTo(x + width - rx, y);\n ctx.quadraticCurveTo(x + width, y, x + width, y + ry);\n ctx.lineTo(x + width, y + height - ry);\n ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);\n ctx.lineTo(x + rx, y + height);\n ctx.quadraticCurveTo(x, y + height, x, y + height - ry);\n ctx.lineTo(x, y + ry);\n ctx.quadraticCurveTo(x, y, x + rx, y);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(x, y, x + width, y + height);\n }\n };\n\n // circle element\n svg.Element.circle = class extends svg.Element.PathElementBase {\n path (ctx) {\n const cx = this.attribute('cx').toPixels('x');\n const cy = this.attribute('cy').toPixels('y');\n const r = this.attribute('r').toPixels();\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2, true);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);\n }\n };\n\n // ellipse element\n const KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);\n svg.Element.ellipse = class extends svg.Element.PathElementBase {\n path (ctx) {\n const rx = this.attribute('rx').toPixels('x');\n const ry = this.attribute('ry').toPixels('y');\n const cx = this.attribute('cx').toPixels('x');\n const cy = this.attribute('cy').toPixels('y');\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(cx, cy - ry);\n ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);\n ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);\n ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);\n ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);\n }\n };\n\n // line element\n svg.Element.line = class extends svg.Element.PathElementBase {\n getPoints () {\n return [\n new svg.Point(this.attribute('x1').toPixels('x'), this.attribute('y1').toPixels('y')),\n new svg.Point(this.attribute('x2').toPixels('x'), this.attribute('y2').toPixels('y'))\n ];\n }\n\n path (ctx) {\n const points = this.getPoints();\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(points[0].x, points[0].y);\n ctx.lineTo(points[1].x, points[1].y);\n }\n\n return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);\n }\n\n getMarkers () {\n const points = this.getPoints();\n const a = points[0].angleTo(points[1]);\n return [[points[0], a], [points[1], a]];\n }\n };\n\n // polyline element\n svg.Element.polyline = class extends svg.Element.PathElementBase {\n constructor (node) {\n super(node);\n\n this.points = svg.CreatePath(this.attribute('points').value);\n }\n path (ctx) {\n const {x, y} = this.points[0];\n const bb = new svg.BoundingBox(x, y);\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(x, y);\n }\n for (let i = 1; i < this.points.length; i++) {\n const {x: _x, y: _y} = this.points[i];\n bb.addPoint(_x, _y);\n if (!isNullish(ctx)) ctx.lineTo(_x, _y);\n }\n return bb;\n }\n\n getMarkers () {\n const markers = [];\n for (let i = 0; i < this.points.length - 1; i++) {\n markers.push([this.points[i], this.points[i].angleTo(this.points[i + 1])]);\n }\n markers.push([this.points[this.points.length - 1], markers[markers.length - 1][1]]);\n return markers;\n }\n };\n\n // polygon element\n svg.Element.polygon = class extends svg.Element.polyline {\n path (ctx) {\n const bb = super.path(ctx);\n if (!isNullish(ctx)) {\n ctx.lineTo(this.points[0].x, this.points[0].y);\n ctx.closePath();\n }\n return bb;\n }\n };\n\n // path element\n svg.Element.path = class extends svg.Element.PathElementBase {\n constructor (node) {\n super(node);\n\n let d = this.attribute('d').value\n // TODO: convert to real lexer based on https://www.w3.org/TR/SVG11/paths.html#PathDataBNF\n .replace(/,/gm, ' ') // get rid of all commas\n .replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from commands\n .replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from commands\n .replace(/([MmZzLlHhVvCcSsQqTtAa])(\\S)/gm, '$1 $2') // separate commands from points\n .replace(/(\\S)([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from points\n .replace(/(\\d)([+-])/gm, '$1 $2') // separate digits when no comma\n .replace(/(\\.\\d*)(\\.)/gm, '$1 $2') // separate digits when no comma\n .replace(/([Aa](\\s+\\d+)(\\s+\\d+)(\\s+\\d+))\\s+([01])\\s*([01])/gm, '$1 $5 $6 '); // shorthand elliptical arc path syntax\n d = svg.compressSpaces(d); // compress multiple spaces\n d = svg.trim(d);\n this.PathParser = {\n tokens: d.split(' '),\n\n reset () {\n this.i = -1;\n this.command = '';\n this.previousCommand = '';\n this.start = new svg.Point(0, 0);\n this.control = new svg.Point(0, 0);\n this.current = new svg.Point(0, 0);\n this.points = [];\n this.angles = [];\n },\n\n isEnd () {\n return this.i >= this.tokens.length - 1;\n },\n\n isCommandOrEnd () {\n if (this.isEnd()) return true;\n return !isNullish(this.tokens[this.i + 1].match(/^[A-Za-z]$/));\n },\n\n isRelativeCommand () {\n switch (this.command) {\n case 'm':\n case 'l':\n case 'h':\n case 'v':\n case 'c':\n case 's':\n case 'q':\n case 't':\n case 'a':\n case 'z':\n return true;\n }\n return false;\n },\n\n getToken () {\n this.i++;\n return this.tokens[this.i];\n },\n\n getScalar () {\n return Number.parseFloat(this.getToken());\n },\n\n nextCommand () {\n this.previousCommand = this.command;\n this.command = this.getToken();\n },\n\n getPoint () {\n const p = new svg.Point(this.getScalar(), this.getScalar());\n return this.makeAbsolute(p);\n },\n\n getAsControlPoint () {\n const p = this.getPoint();\n this.control = p;\n return p;\n },\n\n getAsCurrentPoint () {\n const p = this.getPoint();\n this.current = p;\n return p;\n },\n\n getReflectedControlPoint () {\n if (this.previousCommand.toLowerCase() !== 'c' &&\n this.previousCommand.toLowerCase() !== 's' &&\n this.previousCommand.toLowerCase() !== 'q' &&\n this.previousCommand.toLowerCase() !== 't') {\n return this.current;\n }\n\n // reflect point\n const p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);\n return p;\n },\n\n makeAbsolute (p) {\n if (this.isRelativeCommand()) {\n p.x += this.current.x;\n p.y += this.current.y;\n }\n return p;\n },\n\n addMarker (p, from, priorTo) {\n // if the last angle isn't filled in because we didn't have this point yet ...\n if (!isNullish(priorTo) && this.angles.length > 0 && isNullish(this.angles[this.angles.length - 1])) {\n this.angles[this.angles.length - 1] = this.points[this.points.length - 1].angleTo(priorTo);\n }\n this.addMarkerAngle(p, isNullish(from) ? null : from.angleTo(p));\n },\n\n addMarkerAngle (p, a) {\n this.points.push(p);\n this.angles.push(a);\n },\n\n getMarkerPoints () { return this.points; },\n getMarkerAngles () {\n for (let i = 0; i < this.angles.length; i++) {\n if (isNullish(this.angles[i])) {\n for (let j = i + 1; j < this.angles.length; j++) {\n if (!isNullish(this.angles[j])) {\n this.angles[i] = this.angles[j];\n break;\n }\n }\n }\n }\n return this.angles;\n }\n };\n }\n\n path (ctx) {\n const pp = this.PathParser;\n pp.reset();\n\n const bb = new svg.BoundingBox();\n if (!isNullish(ctx)) ctx.beginPath();\n while (!pp.isEnd()) {\n pp.nextCommand();\n switch (pp.command) {\n case 'M':\n case 'm': {\n const p = pp.getAsCurrentPoint();\n pp.addMarker(p);\n bb.addPoint(p.x, p.y);\n if (!isNullish(ctx)) ctx.moveTo(p.x, p.y);\n pp.start = pp.current;\n while (!pp.isCommandOrEnd()) {\n const _p = pp.getAsCurrentPoint();\n pp.addMarker(_p, pp.start);\n bb.addPoint(_p.x, _p.y);\n if (!isNullish(ctx)) ctx.lineTo(_p.x, _p.y);\n }\n break;\n } case 'L':\n case 'l':\n while (!pp.isCommandOrEnd()) {\n const c = pp.current;\n const p = pp.getAsCurrentPoint();\n pp.addMarker(p, c);\n bb.addPoint(p.x, p.y);\n if (!isNullish(ctx)) ctx.lineTo(p.x, p.y);\n }\n break;\n case 'H':\n case 'h':\n while (!pp.isCommandOrEnd()) {\n const newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);\n pp.addMarker(newP, pp.current);\n pp.current = newP;\n bb.addPoint(pp.current.x, pp.current.y);\n if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);\n }\n break;\n case 'V':\n case 'v':\n while (!pp.isCommandOrEnd()) {\n const newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());\n pp.addMarker(newP, pp.current);\n pp.current = newP;\n bb.addPoint(pp.current.x, pp.current.y);\n if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);\n }\n break;\n case 'C':\n case 'c':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const p1 = pp.getPoint();\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, p1);\n bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'S':\n case 's':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const p1 = pp.getReflectedControlPoint();\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, p1);\n bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'Q':\n case 'q':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, cntrl);\n bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'T':\n case 't':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const cntrl = pp.getReflectedControlPoint();\n pp.control = cntrl;\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, cntrl);\n bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'A':\n case 'a':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n let rx = pp.getScalar();\n let ry = pp.getScalar();\n const xAxisRotation = pp.getScalar() * (Math.PI / 180.0);\n const largeArcFlag = pp.getScalar();\n const sweepFlag = pp.getScalar();\n const cp = pp.getAsCurrentPoint();\n\n // Conversion from endpoint to center parameterization\n // https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter\n\n // x1', y1'\n const currp = new svg.Point(\n Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,\n -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0\n );\n // adjust radii\n const l = (currp.x ** 2) / (rx ** 2) + (currp.y ** 2) / (ry ** 2);\n if (l > 1) {\n rx *= Math.sqrt(l);\n ry *= Math.sqrt(l);\n }\n // cx', cy'\n let s = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(\n (((rx ** 2) * (ry ** 2)) - ((rx ** 2) * (currp.y ** 2)) - ((ry ** 2) * (currp.x ** 2))) /\n ((rx ** 2) * (currp.y ** 2) + (ry ** 2) * (currp.x ** 2))\n );\n if (isNaN(s)) s = 0;\n const cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);\n // cx, cy\n const centp = new svg.Point(\n (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,\n (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y\n );\n // vector magnitude\n const m = function (v) {\n return Math.sqrt((v[0] ** 2) + (v[1] ** 2));\n };\n // ratio between two vectors\n const r = function (u, v) {\n return (u[0] * v[0] + u[1] * v[1]) / (m(u) * m(v));\n };\n // angle between two vectors\n const a = function (u, v) {\n return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(r(u, v));\n };\n // initial angle\n const a1 = a([1, 0], [(currp.x - cpp.x) / rx, (currp.y - cpp.y) / ry]);\n // angle delta\n const u = [(currp.x - cpp.x) / rx, (currp.y - cpp.y) / ry];\n const v = [(-currp.x - cpp.x) / rx, (-currp.y - cpp.y) / ry];\n let ad = a(u, v);\n if (r(u, v) <= -1) ad = Math.PI;\n if (r(u, v) >= 1) ad = 0;\n\n // for markers\n const dir = 1 - sweepFlag ? 1.0 : -1.0;\n const ah = a1 + dir * (ad / 2.0);\n const halfWay = new svg.Point(\n centp.x + rx * Math.cos(ah),\n centp.y + ry * Math.sin(ah)\n );\n pp.addMarkerAngle(halfWay, ah - dir * Math.PI / 2);\n pp.addMarkerAngle(cp, ah - dir * Math.PI);\n\n bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better\n if (!isNullish(ctx)) {\n const _r = rx > ry ? rx : ry;\n const sx = rx > ry ? 1 : rx / ry;\n const sy = rx > ry ? ry / rx : 1;\n\n ctx.translate(centp.x, centp.y);\n ctx.rotate(xAxisRotation);\n ctx.scale(sx, sy);\n ctx.arc(0, 0, _r, a1, a1 + ad, 1 - sweepFlag);\n ctx.scale(1 / sx, 1 / sy);\n ctx.rotate(-xAxisRotation);\n ctx.translate(-centp.x, -centp.y);\n }\n }\n break;\n case 'Z':\n case 'z':\n if (!isNullish(ctx)) ctx.closePath();\n pp.current = pp.start;\n }\n }\n\n return bb;\n }\n\n getMarkers () {\n const points = this.PathParser.getMarkerPoints();\n const angles = this.PathParser.getMarkerAngles();\n\n const markers = points.map((point, i) => {\n return [point, angles[i]];\n });\n return markers;\n }\n };\n\n // pattern element\n svg.Element.pattern = class extends svg.Element.ElementBase {\n createPattern (ctx, element) {\n const width = this.attribute('width').toPixels('x', true);\n const height = this.attribute('height').toPixels('y', true);\n\n // render me using a temporary svg element\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.viewBox = new svg.Property('viewBox', this.attribute('viewBox').value);\n tempSvg.attributes.width = new svg.Property('width', width + 'px');\n tempSvg.attributes.height = new svg.Property('height', height + 'px');\n tempSvg.attributes.transform = new svg.Property('transform', this.attribute('patternTransform').value);\n tempSvg.children = this.children;\n\n const c = document.createElement('canvas');\n c.width = width;\n c.height = height;\n const cctx = c.getContext('2d');\n if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {\n cctx.translate(this.attribute('x').toPixels('x', true), this.attribute('y').toPixels('y', true));\n }\n // render 3x3 grid so when we transform there's no white space on edges\n for (let x = -1; x <= 1; x++) {\n for (let y = -1; y <= 1; y++) {\n cctx.save();\n cctx.translate(x * c.width, y * c.height);\n tempSvg.render(cctx);\n cctx.restore();\n }\n }\n const pattern = ctx.createPattern(c, 'repeat');\n return pattern;\n }\n };\n\n // marker element\n svg.Element.marker = class extends svg.Element.ElementBase {\n render (ctx, point, angle) {\n ctx.translate(point.x, point.y);\n if (this.attribute('orient').valueOrDefault('auto') === 'auto') ctx.rotate(angle);\n if (this.attribute('markerUnits').valueOrDefault('strokeWidth') === 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);\n ctx.save();\n\n // render me using a temporary svg element\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.viewBox = new svg.Property(\n 'viewBox', this.attribute('viewBox').value\n );\n tempSvg.attributes.refX = new svg.Property(\n 'refX', this.attribute('refX').value\n );\n tempSvg.attributes.refY = new svg.Property(\n 'refY', this.attribute('refY').value\n );\n tempSvg.attributes.width = new svg.Property(\n 'width', this.attribute('markerWidth').value\n );\n tempSvg.attributes.height = new svg.Property(\n 'height', this.attribute('markerHeight').value\n );\n tempSvg.attributes.fill = new svg.Property(\n 'fill', this.attribute('fill').valueOrDefault('black')\n );\n tempSvg.attributes.stroke = new svg.Property(\n 'stroke', this.attribute('stroke').valueOrDefault('none')\n );\n tempSvg.children = this.children;\n tempSvg.render(ctx);\n\n ctx.restore();\n if (this.attribute('markerUnits').valueOrDefault('strokeWidth') ===\n 'strokeWidth'\n ) ctx.scale(1 / ctx.lineWidth, 1 / ctx.lineWidth);\n if (this.attribute('orient').valueOrDefault('auto') === 'auto') {\n ctx.rotate(-angle);\n }\n ctx.translate(-point.x, -point.y);\n }\n };\n\n // definitions element\n svg.Element.defs = class extends svg.Element.ElementBase {\n render (ctx) {\n // NOOP\n }\n };\n\n // base for gradients\n svg.Element.GradientBase = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');\n\n this.stops = [];\n this.children.forEach((child) => {\n if (child.type === 'stop') {\n this.stops.push(child);\n }\n });\n }\n\n getGradient () {\n // OVERRIDE ME!\n }\n\n createGradient (ctx, element, parentOpacityProp) {\n const stopsContainer = this.getHrefAttribute().hasValue()\n ? this.getHrefAttribute().getDefinition()\n : this;\n\n const addParentOpacity = function (color) {\n if (parentOpacityProp.hasValue()) {\n const p = new svg.Property('color', color);\n return p.addOpacity(parentOpacityProp).value;\n }\n return color;\n };\n\n const g = this.getGradient(ctx, element);\n if (isNullish(g)) return addParentOpacity(stopsContainer.stops[stopsContainer.stops.length - 1].color);\n stopsContainer.stops.forEach(({offset, color}) => {\n g.addColorStop(offset, addParentOpacity(color));\n });\n\n if (this.attribute('gradientTransform').hasValue()) {\n // render as transformed pattern on temporary canvas\n const rootView = svg.ViewPort.viewPorts[0];\n\n const rect = new svg.Element.rect();\n rect.attributes.x = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS / 3.0);\n rect.attributes.y = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS / 3.0);\n rect.attributes.width = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);\n rect.attributes.height = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);\n\n const group = new svg.Element.g();\n group.attributes.transform = new svg.Property('transform', this.attribute('gradientTransform').value);\n group.children = [rect];\n\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.x = new svg.Property('x', 0);\n tempSvg.attributes.y = new svg.Property('y', 0);\n tempSvg.attributes.width = new svg.Property('width', rootView.width);\n tempSvg.attributes.height = new svg.Property('height', rootView.height);\n tempSvg.children = [group];\n\n const c = document.createElement('canvas');\n c.width = rootView.width;\n c.height = rootView.height;\n const tempCtx = c.getContext('2d');\n tempCtx.fillStyle = g;\n tempSvg.render(tempCtx);\n return tempCtx.createPattern(c, 'no-repeat');\n }\n\n return g;\n }\n };\n\n // linear gradient element\n svg.Element.linearGradient = class extends svg.Element.GradientBase {\n getGradient (ctx, element) {\n const useBB = this.gradientUnits === 'objectBoundingBox' && element.getBoundingBox;\n const bb = useBB\n ? element.getBoundingBox()\n : null;\n\n if (!this.attribute('x1').hasValue() &&\n !this.attribute('y1').hasValue() &&\n !this.attribute('x2').hasValue() &&\n !this.attribute('y2').hasValue()\n ) {\n this.attribute('x1', true).value = 0;\n this.attribute('y1', true).value = 0;\n this.attribute('x2', true).value = 1;\n this.attribute('y2', true).value = 0;\n }\n\n const x1 = (useBB\n ? bb.x() + bb.width() * this.attribute('x1').numValue()\n : this.attribute('x1').toPixels('x'));\n const y1 = (useBB\n ? bb.y() + bb.height() * this.attribute('y1').numValue()\n : this.attribute('y1').toPixels('y'));\n const x2 = (useBB\n ? bb.x() + bb.width() * this.attribute('x2').numValue()\n : this.attribute('x2').toPixels('x'));\n const y2 = (useBB\n ? bb.y() + bb.height() * this.attribute('y2').numValue()\n : this.attribute('y2').toPixels('y'));\n\n if (x1 === x2 && y1 === y2) return null;\n return ctx.createLinearGradient(x1, y1, x2, y2);\n }\n };\n\n // radial gradient element\n svg.Element.radialGradient = class extends svg.Element.GradientBase {\n getGradient (ctx, element) {\n const useBB = this.gradientUnits === 'objectBoundingBox' && element.getBoundingBox;\n const bb = useBB ? element.getBoundingBox() : null;\n\n if (!this.attribute('cx').hasValue()) this.attribute('cx', true).value = '50%';\n if (!this.attribute('cy').hasValue()) this.attribute('cy', true).value = '50%';\n if (!this.attribute('r').hasValue()) this.attribute('r', true).value = '50%';\n\n const cx = (useBB\n ? bb.x() + bb.width() * this.attribute('cx').numValue()\n : this.attribute('cx').toPixels('x'));\n const cy = (useBB\n ? bb.y() + bb.height() * this.attribute('cy').numValue()\n : this.attribute('cy').toPixels('y'));\n\n let fx = cx;\n let fy = cy;\n if (this.attribute('fx').hasValue()) {\n fx = (useBB\n ? bb.x() + bb.width() * this.attribute('fx').numValue()\n : this.attribute('fx').toPixels('x'));\n }\n if (this.attribute('fy').hasValue()) {\n fy = (useBB\n ? bb.y() + bb.height() * this.attribute('fy').numValue()\n : this.attribute('fy').toPixels('y'));\n }\n\n const r = (useBB\n ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()\n : this.attribute('r').toPixels());\n\n return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);\n }\n };\n\n // gradient stop element\n svg.Element.stop = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.offset = this.attribute('offset').numValue();\n if (this.offset < 0) this.offset = 0;\n if (this.offset > 1) this.offset = 1;\n\n let stopColor = this.style('stop-color');\n if (this.style('stop-opacity').hasValue()) {\n stopColor = stopColor.addOpacity(this.style('stop-opacity'));\n }\n this.color = stopColor.value;\n }\n };\n\n // animation base element\n svg.Element.AnimateBase = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n svg.Animations.push(this);\n\n this.duration = 0.0;\n this.begin = this.attribute('begin').toMilliseconds();\n this.maxDuration = this.begin + this.attribute('dur').toMilliseconds();\n\n this.initialValue = null;\n this.initialUnits = '';\n this.removed = false;\n\n this.from = this.attribute('from');\n this.to = this.attribute('to');\n this.values = this.attribute('values');\n if (this.values.hasValue()) this.values.value = this.values.value.split(';');\n }\n\n getProperty () {\n const attributeType = this.attribute('attributeType').value;\n const attributeName = this.attribute('attributeName').value;\n\n if (attributeType === 'CSS') {\n return this.parent.style(attributeName, true);\n }\n return this.parent.attribute(attributeName, true);\n }\n\n calcValue () {\n // OVERRIDE ME!\n return '';\n }\n\n update (delta) {\n // set initial value\n if (isNullish(this.initialValue)) {\n this.initialValue = this.getProperty().value;\n this.initialUnits = this.getProperty().getUnits();\n }\n\n // if we're past the end time\n if (this.duration > this.maxDuration) {\n // loop for indefinitely repeating animations\n if (this.attribute('repeatCount').value === 'indefinite' ||\n this.attribute('repeatDur').value === 'indefinite') {\n this.duration = 0.0;\n } else if (this.attribute('fill').valueOrDefault('remove') === 'freeze' && !this.frozen) {\n this.frozen = true;\n this.parent.animationFrozen = true;\n this.parent.animationFrozenValue = this.getProperty().value;\n } else if (this.attribute('fill').valueOrDefault('remove') === 'remove' && !this.removed) {\n this.removed = true;\n this.getProperty().value = this.parent.animationFrozen ? this.parent.animationFrozenValue : this.initialValue;\n return true;\n }\n return false;\n }\n this.duration += delta;\n\n // if we're past the begin time\n let updated = false;\n if (this.begin < this.duration) {\n let newValue = this.calcValue(); // tween\n\n if (this.attribute('type').hasValue()) {\n // for transform, etc.\n const type = this.attribute('type').value;\n newValue = type + '(' + newValue + ')';\n }\n\n this.getProperty().value = newValue;\n updated = true;\n }\n\n return updated;\n }\n\n // fraction of duration we've covered\n progress () {\n const ret = {progress: (this.duration - this.begin) / (this.maxDuration - this.begin)};\n if (this.values.hasValue()) {\n const p = ret.progress * (this.values.value.length - 1);\n const lb = Math.floor(p), ub = Math.ceil(p);\n ret.from = new svg.Property('from', Number.parseFloat(this.values.value[lb]));\n ret.to = new svg.Property('to', Number.parseFloat(this.values.value[ub]));\n ret.progress = (p - lb) / (ub - lb);\n } else {\n ret.from = this.from;\n ret.to = this.to;\n }\n return ret;\n }\n };\n\n // animate element\n svg.Element.animate = class extends svg.Element.AnimateBase {\n calcValue () {\n const p = this.progress();\n\n // tween value linearly\n const newValue = p.from.numValue() + (p.to.numValue() - p.from.numValue()) * p.progress;\n return newValue + this.initialUnits;\n }\n };\n\n // animate color element\n svg.Element.animateColor = class extends svg.Element.AnimateBase {\n calcValue () {\n const p = this.progress();\n const from = new RGBColor(p.from.value);\n const to = new RGBColor(p.to.value);\n\n if (from.ok && to.ok) {\n // tween color linearly\n const r = from.r + (to.r - from.r) * p.progress;\n const g = from.g + (to.g - from.g) * p.progress;\n const b = from.b + (to.b - from.b) * p.progress;\n return 'rgb(' + Number.parseInt(r) + ',' + Number.parseInt(g) + ',' + Number.parseInt(b) + ')';\n }\n return this.attribute('from').value;\n }\n };\n\n // animate transform element\n svg.Element.animateTransform = class extends svg.Element.animate {\n calcValue () {\n const p = this.progress();\n\n // tween value linearly\n const from = svg.ToNumberArray(p.from.value);\n const to = svg.ToNumberArray(p.to.value);\n let newValue = '';\n from.forEach((fr, i) => {\n newValue += fr + (to[i] - fr) * p.progress + ' ';\n });\n return newValue;\n }\n };\n\n // font element\n svg.Element.font = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.horizAdvX = this.attribute('horiz-adv-x').numValue();\n\n this.isRTL = false;\n this.isArabic = false;\n this.fontFace = null;\n this.missingGlyph = null;\n this.glyphs = [];\n this.children.forEach((child) => {\n if (child.type === 'font-face') {\n this.fontFace = child;\n if (child.style('font-family').hasValue()) {\n svg.Definitions[child.style('font-family').value] = this;\n }\n } else if (child.type === 'missing-glyph') {\n this.missingGlyph = child;\n } else if (child.type === 'glyph') {\n if (child.arabicForm !== '') {\n this.isRTL = true;\n this.isArabic = true;\n if (typeof this.glyphs[child.unicode] === 'undefined') {\n this.glyphs[child.unicode] = [];\n }\n this.glyphs[child.unicode][child.arabicForm] = child;\n } else {\n this.glyphs[child.unicode] = child;\n }\n }\n });\n }\n };\n\n // font-face element\n svg.Element.fontface = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.ascent = this.attribute('ascent').value;\n this.descent = this.attribute('descent').value;\n this.unitsPerEm = this.attribute('units-per-em').numValue();\n }\n };\n\n // missing-glyph element\n svg.Element.missingglyph = class extends svg.Element.path {\n constructor (node) {\n super(node);\n\n this.horizAdvX = 0;\n }\n };\n\n // glyph element\n svg.Element.glyph = class extends svg.Element.path {\n constructor (node) {\n super(node);\n\n this.horizAdvX = this.attribute('horiz-adv-x').numValue();\n this.unicode = this.attribute('unicode').value;\n this.arabicForm = this.attribute('arabic-form').value;\n }\n };\n\n // text element\n svg.Element.text = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node, true);\n }\n\n setContext (ctx) {\n super.setContext(ctx);\n\n let textBaseline = this.style('dominant-baseline').toTextBaseline();\n if (isNullish(textBaseline)) textBaseline = this.style('alignment-baseline').toTextBaseline();\n if (!isNullish(textBaseline)) ctx.textBaseline = textBaseline;\n }\n\n getBoundingBox () {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n return new svg.BoundingBox(x, y - fontSize, x + Math.floor(fontSize * 2.0 / 3.0) * this.children[0].getText().length, y);\n }\n\n renderChildren (ctx) {\n this.x = this.attribute('x').toPixels('x');\n this.y = this.attribute('y').toPixels('y');\n this.x += this.getAnchorDelta(ctx, this, 0);\n this.children.forEach((child, i) => {\n this.renderChild(ctx, this, i);\n });\n }\n\n getAnchorDelta (ctx, parent, startI) {\n const textAnchor = this.style('text-anchor').valueOrDefault('start');\n if (textAnchor !== 'start') {\n let width = 0;\n for (let i = startI; i < parent.children.length; i++) {\n const child = parent.children[i];\n if (i > startI && child.attribute('x').hasValue()) break; // new group\n width += child.measureTextRecursive(ctx);\n }\n return -1 * (textAnchor === 'end' ? width : width / 2.0);\n }\n return 0;\n }\n\n renderChild (ctx, parent, i) {\n const child = parent.children[i];\n if (child.attribute('x').hasValue()) {\n child.x = child.attribute('x').toPixels('x') + this.getAnchorDelta(ctx, parent, i);\n if (child.attribute('dx').hasValue()) child.x += child.attribute('dx').toPixels('x');\n } else {\n if (this.attribute('dx').hasValue()) this.x += this.attribute('dx').toPixels('x');\n if (child.attribute('dx').hasValue()) this.x += child.attribute('dx').toPixels('x');\n child.x = this.x;\n }\n this.x = child.x + child.measureText(ctx);\n\n if (child.attribute('y').hasValue()) {\n child.y = child.attribute('y').toPixels('y');\n if (child.attribute('dy').hasValue()) child.y += child.attribute('dy').toPixels('y');\n } else {\n if (this.attribute('dy').hasValue()) this.y += this.attribute('dy').toPixels('y');\n if (child.attribute('dy').hasValue()) this.y += child.attribute('dy').toPixels('y');\n child.y = this.y;\n }\n this.y = child.y;\n\n child.render(ctx);\n\n for (let j = 0; j < child.children.length; j++) {\n this.renderChild(ctx, child, j);\n }\n }\n };\n\n // text base\n svg.Element.TextElementBase = class extends svg.Element.RenderedElementBase {\n getGlyph (font, text, i) {\n const c = text[i];\n let glyph = null;\n if (font.isArabic) {\n let arabicForm = 'isolated';\n if ((i === 0 || text[i - 1] === ' ') && i < text.length - 2 && text[i + 1] !== ' ') arabicForm = 'terminal';\n if (i > 0 && text[i - 1] !== ' ' && i < text.length - 2 && text[i + 1] !== ' ') arabicForm = 'medial';\n if (i > 0 && text[i - 1] !== ' ' && (i === text.length - 1 || text[i + 1] === ' ')) arabicForm = 'initial';\n if (typeof font.glyphs[c] !== 'undefined') {\n glyph = font.glyphs[c][arabicForm];\n if (isNullish(glyph) && font.glyphs[c].type === 'glyph') glyph = font.glyphs[c];\n }\n } else {\n glyph = font.glyphs[c];\n }\n if (isNullish(glyph)) glyph = font.missingGlyph;\n return glyph;\n }\n\n renderChildren (ctx) {\n const customFont = this.parent.style('font-family').getDefinition();\n if (!isNullish(customFont)) {\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n const fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);\n let text = this.getText();\n if (customFont.isRTL) text = text.split('').reverse().join('');\n\n const dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n for (let i = 0; i < text.length; i++) {\n const glyph = this.getGlyph(customFont, text, i);\n const scale = fontSize / customFont.fontFace.unitsPerEm;\n ctx.translate(this.x, this.y);\n ctx.scale(scale, -scale);\n const lw = ctx.lineWidth;\n ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;\n if (fontStyle === 'italic') ctx.transform(1, 0, 0.4, 1, 0, 0);\n glyph.render(ctx);\n if (fontStyle === 'italic') ctx.transform(1, 0, -0.4, 1, 0, 0);\n ctx.lineWidth = lw;\n ctx.scale(1 / scale, -1 / scale);\n ctx.translate(-this.x, -this.y);\n\n this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;\n if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {\n this.x += dx[i];\n }\n }\n return;\n }\n\n if (ctx.fillStyle !== '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);\n if (ctx.strokeStyle !== '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);\n }\n\n getText () {\n // OVERRIDE ME\n }\n\n measureTextRecursive (ctx) {\n let width = this.measureText(ctx);\n this.children.forEach((child) => {\n width += child.measureTextRecursive(ctx);\n });\n return width;\n }\n\n measureText (ctx) {\n const customFont = this.parent.style('font-family').getDefinition();\n if (!isNullish(customFont)) {\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n let measure = 0;\n let text = this.getText();\n if (customFont.isRTL) text = text.split('').reverse().join('');\n const dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n for (let i = 0; i < text.length; i++) {\n const glyph = this.getGlyph(customFont, text, i);\n measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;\n if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {\n measure += dx[i];\n }\n }\n return measure;\n }\n\n const textToMeasure = svg.compressSpaces(this.getText());\n if (!ctx.measureText) return textToMeasure.length * 10;\n\n ctx.save();\n this.setContext(ctx);\n const {width} = ctx.measureText(textToMeasure);\n ctx.restore();\n return width;\n }\n };\n\n // tspan\n svg.Element.tspan = class extends svg.Element.TextElementBase {\n constructor (node) {\n super(node, true);\n\n this.text = node.nodeValue || node.text || '';\n }\n getText () {\n return this.text;\n }\n };\n\n // tref\n svg.Element.tref = class extends svg.Element.TextElementBase {\n getText () {\n const element = this.getHrefAttribute().getDefinition();\n if (!isNullish(element)) return element.children[0].getText();\n return undefined;\n }\n };\n\n // a element\n svg.Element.a = class extends svg.Element.TextElementBase {\n constructor (node) {\n super(node);\n\n this.hasText = true;\n [...node.childNodes].forEach((childNode) => {\n if (childNode.nodeType !== 3) {\n this.hasText = false;\n }\n });\n // this might contain text\n this.text = this.hasText ? node.childNodes[0].nodeValue : '';\n }\n\n getText () {\n return this.text;\n }\n\n renderChildren (ctx) {\n if (this.hasText) {\n // render as text element\n super.renderChildren(ctx);\n const fontSize = new svg.Property(\n 'fontSize', svg.Font.Parse(svg.ctx.font).fontSize\n );\n svg.Mouse.checkBoundingBox(\n this, new svg.BoundingBox(\n this.x,\n this.y - fontSize.toPixels('y'),\n this.x + this.measureText(ctx),\n this.y\n )\n );\n } else {\n // render as temporary group\n const g = new svg.Element.g();\n g.children = this.children;\n g.parent = this;\n g.render(ctx);\n }\n }\n\n onclick () {\n window.open(this.getHrefAttribute().value);\n }\n\n onmousemove () {\n svg.ctx.canvas.style.cursor = 'pointer';\n }\n };\n\n // image element\n svg.Element.image = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node);\n\n const href = this.getHrefAttribute().value;\n if (href === '') {\n return;\n }\n this._isSvg = href.match(/\\.svg$/);\n\n svg.Images.push(this);\n this.loaded = false;\n if (!this._isSvg) {\n this.img = document.createElement('img');\n if (svg.opts.useCORS === true) {\n this.img.crossOrigin = 'Anonymous';\n }\n this.img.addEventListener('load', () => {\n this.loaded = true;\n });\n this.img.addEventListener('error', () => {\n svg.log('ERROR: image \"' + href + '\" not found');\n this.loaded = true;\n });\n this.img.src = href;\n } else {\n svg.ajax(href, true).then((img) => { // eslint-disable-line promise/prefer-await-to-then, promise/always-return\n this.img = img;\n this.loaded = true;\n }).catch((err) => { // eslint-disable-line promise/prefer-await-to-callbacks\n this.erred = true;\n console.error('Ajax error for canvg', err); // eslint-disable-line no-console\n });\n }\n }\n renderChildren (ctx) {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n if (width === 0 || height === 0) return;\n\n ctx.save();\n if (this._isSvg) {\n ctx.drawSvg(this.img, x, y, width, height);\n } else {\n ctx.translate(x, y);\n svg.AspectRatio(\n ctx,\n this.attribute('preserveAspectRatio').value,\n width,\n this.img.width,\n height,\n this.img.height,\n 0,\n 0\n );\n ctx.drawImage(this.img, 0, 0);\n }\n ctx.restore();\n }\n\n getBoundingBox () {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n return new svg.BoundingBox(x, y, x + width, y + height);\n }\n };\n\n // group element\n svg.Element.g = class extends svg.Element.RenderedElementBase {\n getBoundingBox () {\n const bb = new svg.BoundingBox();\n this.children.forEach((child) => {\n bb.addBoundingBox(child.getBoundingBox());\n });\n return bb;\n }\n };\n\n // symbol element\n svg.Element.symbol = class extends svg.Element.RenderedElementBase {\n render (ctx) {\n // NO RENDER\n }\n };\n\n // style element\n svg.Element.style = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n // text, or spaces then CDATA\n let css = '';\n [...node.childNodes].forEach(({nodeValue}) => {\n css += nodeValue;\n });\n // remove comments\n css = css.replace(/(\\/\\*([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+\\/)|(^\\s*\\/\\/.*)/gm, ''); // eslint-disable-line unicorn/no-unsafe-regex\n // replace whitespace\n css = svg.compressSpaces(css);\n const cssDefs = css.split('}');\n cssDefs.forEach((cssDef) => {\n if (svg.trim(cssDef) !== '') {\n let [cssClasses, cssProps] = cssDef.split('{');\n cssClasses = cssClasses.split(',');\n cssProps = cssProps.split(';');\n cssClasses.forEach((cssClass) => {\n cssClass = svg.trim(cssClass);\n if (cssClass !== '') {\n const props = {};\n cssProps.forEach((cssProp) => {\n const prop = cssProp.indexOf(':');\n const name = cssProp.substr(0, prop);\n const value = cssProp.substr(prop + 1, cssProp.length - prop);\n if (!isNullish(name) && !isNullish(value)) {\n props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));\n }\n });\n svg.Styles[cssClass] = props;\n if (cssClass === '@font-face') {\n const fontFamily = props['font-family'].value.replace(/\"/g, '');\n const srcs = props.src.value.split(',');\n srcs.forEach((src) => {\n if (src.includes('format(\"svg\")')) {\n const urlStart = src.indexOf('url');\n const urlEnd = src.indexOf(')', urlStart);\n const url = src.substr(urlStart + 5, urlEnd - urlStart - 6);\n // Can this ajax safely be converted to async?\n const doc = svg.parseXml(svg.ajax(url));\n const fonts = doc.getElementsByTagName('font');\n [...fonts].forEach((font) => {\n font = svg.CreateElement(font);\n svg.Definitions[fontFamily] = font;\n });\n }\n });\n }\n }\n });\n }\n });\n }\n };\n\n // use element\n svg.Element.use = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node);\n\n this._el = this.getHrefAttribute().getDefinition();\n }\n\n setContext (ctx) {\n super.setContext(ctx);\n if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').toPixels('x'), 0);\n if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').toPixels('y'));\n }\n\n path (ctx) {\n const {_el: element} = this;\n if (!isNullish(element)) element.path(ctx);\n }\n\n getBoundingBox () {\n const {_el: element} = this;\n if (!isNullish(element)) return element.getBoundingBox();\n return undefined;\n }\n\n renderChildren (ctx) {\n const {_el: element} = this;\n if (!isNullish(element)) {\n let tempSvg = element;\n if (element.type === 'symbol') {\n // render me using a temporary svg element in symbol cases\n // (https://www.w3.org/TR/SVG/struct.html#UseElement)\n tempSvg = new svg.Element.svg();\n tempSvg.type = 'svg';\n tempSvg.attributes.viewBox = new svg.Property(\n 'viewBox', element.attribute('viewBox').value\n );\n tempSvg.attributes.preserveAspectRatio = new svg.Property(\n 'preserveAspectRatio', element.attribute('preserveAspectRatio').value\n );\n tempSvg.attributes.overflow = new svg.Property(\n 'overflow', element.attribute('overflow').value\n );\n tempSvg.children = element.children;\n }\n if (tempSvg.type === 'svg') {\n // if symbol or svg, inherit width/height from me\n if (this.attribute('width').hasValue()) {\n tempSvg.attributes.width = new svg.Property(\n 'width', this.attribute('width').value\n );\n }\n if (this.attribute('height').hasValue()) {\n tempSvg.attributes.height = new svg.Property(\n 'height', this.attribute('height').value\n );\n }\n }\n const oldParent = tempSvg.parent;\n tempSvg.parent = null;\n tempSvg.render(ctx);\n tempSvg.parent = oldParent;\n }\n }\n };\n\n // mask element\n svg.Element.mask = class extends svg.Element.ElementBase {\n apply (ctx, element) {\n // render as temp svg\n let x = this.attribute('x').toPixels('x');\n let y = this.attribute('y').toPixels('y');\n let width = this.attribute('width').toPixels('x');\n let height = this.attribute('height').toPixels('y');\n\n if (width === 0 && height === 0) {\n const bb = new svg.BoundingBox();\n this.children.forEach((child) => {\n bb.addBoundingBox(child.getBoundingBox());\n });\n x = Math.floor(bb.x1);\n y = Math.floor(bb.y1);\n width = Math.floor(bb.width());\n height = Math.floor(bb.height());\n }\n\n // temporarily remove mask to avoid recursion\n const mask = element.attribute('mask').value;\n element.attribute('mask').value = '';\n\n const cMask = document.createElement('canvas');\n cMask.width = x + width;\n cMask.height = y + height;\n const maskCtx = cMask.getContext('2d');\n this.renderChildren(maskCtx);\n\n const c = document.createElement('canvas');\n c.width = x + width;\n c.height = y + height;\n const tempCtx = c.getContext('2d');\n element.render(tempCtx);\n tempCtx.globalCompositeOperation = 'destination-in';\n tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');\n tempCtx.fillRect(0, 0, x + width, y + height);\n\n ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');\n ctx.fillRect(0, 0, x + width, y + height);\n\n // reassign mask\n element.attribute('mask').value = mask;\n }\n\n render (ctx) {\n // NO RENDER\n }\n };\n\n // clip element\n svg.Element.clipPath = class extends svg.Element.ElementBase {\n apply (ctx) {\n this.children.forEach((child) => {\n if (typeof child.path !== 'undefined') {\n let transform = null;\n if (child.attribute('transform').hasValue()) {\n transform = new svg.Transform(child.attribute('transform').value);\n transform.apply(ctx);\n }\n child.path(ctx);\n ctx.clip();\n if (transform) { transform.unapply(ctx); }\n }\n });\n }\n render (ctx) {\n // NO RENDER\n }\n };\n\n // filters\n svg.Element.filter = class extends svg.Element.ElementBase {\n apply (ctx, element) {\n // render as temp svg\n const bb = element.getBoundingBox();\n const x = Math.floor(bb.x1);\n const y = Math.floor(bb.y1);\n const width = Math.floor(bb.width());\n const height = Math.floor(bb.height());\n\n // temporarily remove filter to avoid recursion\n const filter = element.style('filter').value;\n element.style('filter').value = '';\n\n let px = 0, py = 0;\n this.children.forEach((child) => {\n const efd = child.extraFilterDistance || 0;\n px = Math.max(px, efd);\n py = Math.max(py, efd);\n });\n\n const c = document.createElement('canvas');\n c.width = width + 2 * px;\n c.height = height + 2 * py;\n const tempCtx = c.getContext('2d');\n tempCtx.translate(-x + px, -y + py);\n element.render(tempCtx);\n\n // apply filters\n this.children.forEach((child) => {\n child.apply(tempCtx, 0, 0, width + 2 * px, height + 2 * py);\n });\n\n // render on me\n ctx.drawImage(c, 0, 0, width + 2 * px, height + 2 * py, x - px, y - py, width + 2 * px, height + 2 * py);\n\n // reassign filter\n element.style('filter', true).value = filter;\n }\n\n render (ctx) {\n // NO RENDER\n }\n };\n\n svg.Element.feMorphology = class extends svg.Element.ElementBase {\n apply (ctx, x, y, width, height) {\n // TODO: implement\n }\n };\n\n svg.Element.feComposite = class extends svg.Element.ElementBase {\n apply (ctx, x, y, width, height) {\n // TODO: implement\n }\n };\n\n /**\n * @param {Uint8ClampedArray} img\n * @param {Integer} x\n * @param {Integer} y\n * @param {Float} width\n * @param {Float} height\n * @param {Integer} rgba\n * @returns {Integer}\n */\n function imGet (img, x, y, width, height, rgba) {\n return img[y * width * 4 + x * 4 + rgba];\n }\n\n /**\n * @param {Uint8ClampedArray} img\n * @param {Integer} x\n * @param {Integer} y\n * @param {Float} width\n * @param {Float} height\n * @param {Integer} rgba\n * @param {Float} val\n * @returns {void}\n */\n function imSet (img, x, y, width, height, rgba, val) {\n img[y * width * 4 + x * 4 + rgba] = val;\n }\n\n svg.Element.feColorMatrix = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n let matrix = svg.ToNumberArray(this.attribute('values').value);\n switch (this.attribute('type').valueOrDefault('matrix')) { // https://www.w3.org/TR/SVG/filters.html#feColorMatrixElement\n case 'saturate': {\n const s = matrix[0];\n matrix = [\n 0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s, 0, 0,\n 0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s, 0, 0,\n 0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s, 0, 0,\n 0, 0, 0, 1, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n } case 'hueRotate': {\n const a = matrix[0] * Math.PI / 180.0;\n const c = function (m1, m2, m3) {\n return m1 + Math.cos(a) * m2 + Math.sin(a) * m3;\n };\n matrix = [\n c(0.213, 0.787, -0.213), c(0.715, -0.715, -0.715), c(0.072, -0.072, 0.928), 0, 0,\n c(0.213, -0.213, 0.143), c(0.715, 0.285, 0.140), c(0.072, -0.072, -0.283), 0, 0,\n c(0.213, -0.213, -0.787), c(0.715, -0.715, 0.715), c(0.072, 0.928, 0.072), 0, 0,\n 0, 0, 0, 1, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n } case 'luminanceToAlpha':\n matrix = [\n 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0,\n 0.2125, 0.7154, 0.0721, 0, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n }\n this.matrix = matrix;\n\n this._m = (i, v) => {\n const mi = matrix[i];\n return mi * (mi < 0 ? v - 255 : v);\n };\n }\n apply (ctx, x, y, width, height) {\n const {_m: m} = this;\n // assuming x==0 && y==0 for now\n const srcData = ctx.getImageData(0, 0, width, height);\n for (let _y = 0; _y < height; _y++) {\n for (let _x = 0; _x < width; _x++) {\n const r = imGet(srcData.data, _x, _y, width, height, 0);\n const g = imGet(srcData.data, _x, _y, width, height, 1);\n const b = imGet(srcData.data, _x, _y, width, height, 2);\n const a = imGet(srcData.data, _x, _y, width, height, 3);\n imSet(srcData.data, _x, _y, width, height, 0, m(0, r) + m(1, g) + m(2, b) + m(3, a) + m(4, 1));\n imSet(srcData.data, _x, _y, width, height, 1, m(5, r) + m(6, g) + m(7, b) + m(8, a) + m(9, 1));\n imSet(srcData.data, _x, _y, width, height, 2, m(10, r) + m(11, g) + m(12, b) + m(13, a) + m(14, 1));\n imSet(srcData.data, _x, _y, width, height, 3, m(15, r) + m(16, g) + m(17, b) + m(18, a) + m(19, 1));\n }\n }\n ctx.clearRect(0, 0, width, height);\n ctx.putImageData(srcData, 0, 0);\n }\n };\n\n svg.Element.feGaussianBlur = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.blurRadius = Math.floor(this.attribute('stdDeviation').numValue());\n this.extraFilterDistance = this.blurRadius;\n }\n\n apply (ctx, x, y, width, height) {\n // Todo: This might not be a problem anymore with out `instanceof` fix\n // StackBlur requires canvas be on document\n ctx.canvas.id = svg.UniqueId();\n ctx.canvas.style.display = 'none';\n document.body.append(ctx.canvas);\n canvasRGBA(ctx.canvas, x, y, width, height, this.blurRadius);\n ctx.canvas.remove();\n }\n };\n\n // title element, do nothing\n svg.Element.title = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n }\n };\n\n // desc element, do nothing\n svg.Element.desc = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n }\n };\n\n svg.Element.MISSING = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n svg.log('ERROR: Element \\'' + node.nodeName + '\\' not yet implemented.');\n }\n };\n\n // element factory\n svg.CreateElement = function (node) {\n const className = node.nodeName\n .replace(/^[^:]+:/, '') // remove namespace\n .replace(/-/g, ''); // remove dashes\n let e;\n if (typeof svg.Element[className] !== 'undefined') {\n e = new svg.Element[className](node);\n } else {\n e = new svg.Element.MISSING(node);\n }\n\n e.type = node.nodeName;\n return e;\n };\n\n // load from url\n svg.load = async function (ctx, url) {\n const dom = await svg.ajax(url, true);\n return svg.loadXml(ctx, dom);\n };\n\n // load from xml\n svg.loadXml = function (ctx, xml) {\n return svg.loadXmlDoc(ctx, svg.parseXml(xml));\n };\n\n svg.loadXmlDoc = function (ctx, dom) {\n let res;\n svg.init(ctx);\n\n const mapXY = function (p) {\n let e = ctx.canvas;\n while (e) {\n p.x -= e.offsetLeft;\n p.y -= e.offsetTop;\n e = e.offsetParent;\n }\n if (window.scrollX) p.x += window.scrollX;\n if (window.scrollY) p.y += window.scrollY;\n return p;\n };\n\n // bind mouse\n if (svg.opts.ignoreMouse !== true) {\n ctx.canvas.addEventListener('click', function (e) {\n const args = !isNullish(e)\n ? [e.clientX, e.clientY]\n : [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals\n const {x, y} = mapXY(new svg.Point(...args));\n svg.Mouse.onclick(x, y);\n });\n ctx.canvas.addEventListener('mousemove', function (e) {\n const args = !isNullish(e)\n ? [e.clientX, e.clientY]\n : [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals\n const {x, y} = mapXY(new svg.Point(...args));\n svg.Mouse.onmousemove(x, y);\n });\n }\n\n const e = svg.CreateElement(dom.documentElement);\n e.root = true;\n\n // render loop\n let isFirstRender = true;\n const draw = function (resolve) {\n svg.ViewPort.Clear();\n if (ctx.canvas.parentNode) {\n svg.ViewPort.SetCurrent(\n ctx.canvas.parentNode.clientWidth,\n ctx.canvas.parentNode.clientHeight\n );\n }\n\n if (svg.opts.ignoreDimensions !== true) {\n // set canvas size\n if (e.style('width').hasValue()) {\n ctx.canvas.width = e.style('width').toPixels('x');\n ctx.canvas.style.width = ctx.canvas.width + 'px';\n }\n if (e.style('height').hasValue()) {\n ctx.canvas.height = e.style('height').toPixels('y');\n ctx.canvas.style.height = ctx.canvas.height + 'px';\n }\n }\n let cWidth = ctx.canvas.clientWidth || ctx.canvas.width;\n let cHeight = ctx.canvas.clientHeight || ctx.canvas.height;\n if (svg.opts.ignoreDimensions === true &&\n e.style('width').hasValue() && e.style('height').hasValue()\n ) {\n cWidth = e.style('width').toPixels('x');\n cHeight = e.style('height').toPixels('y');\n }\n svg.ViewPort.SetCurrent(cWidth, cHeight);\n\n if (!isNullish(svg.opts.offsetX)) {\n e.attribute('x', true).value = svg.opts.offsetX;\n }\n if (!isNullish(svg.opts.offsetY)) {\n e.attribute('y', true).value = svg.opts.offsetY;\n }\n if (!isNullish(svg.opts.scaleWidth) || !isNullish(svg.opts.scaleHeight)) {\n const viewBox = svg.ToNumberArray(e.attribute('viewBox').value);\n let xRatio = null, yRatio = null;\n\n if (!isNullish(svg.opts.scaleWidth)) {\n if (e.attribute('width').hasValue()) {\n xRatio = e.attribute('width').toPixels('x') / svg.opts.scaleWidth;\n } else if (!isNaN(viewBox[2])) {\n xRatio = viewBox[2] / svg.opts.scaleWidth;\n }\n }\n\n if (!isNullish(svg.opts.scaleHeight)) {\n if (e.attribute('height').hasValue()) {\n yRatio = e.attribute('height').toPixels('y') / svg.opts.scaleHeight;\n } else if (!isNaN(viewBox[3])) {\n yRatio = viewBox[3] / svg.opts.scaleHeight;\n }\n }\n\n if (isNullish(xRatio)) { xRatio = yRatio; }\n if (isNullish(yRatio)) { yRatio = xRatio; }\n\n e.attribute('width', true).value = svg.opts.scaleWidth;\n e.attribute('height', true).value = svg.opts.scaleHeight;\n e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);\n e.attribute('preserveAspectRatio', true).value = 'none';\n }\n\n // clear and render\n if (svg.opts.ignoreClear !== true) {\n ctx.clearRect(0, 0, cWidth, cHeight);\n }\n e.render(ctx);\n if (isFirstRender) {\n isFirstRender = false;\n resolve(dom);\n }\n };\n\n let waitingForImages = true;\n svg.intervalID = setInterval(function () {\n let needUpdate = false;\n\n if (waitingForImages && svg.ImagesLoaded()) {\n waitingForImages = false;\n needUpdate = true;\n }\n\n // need update from mouse events?\n if (svg.opts.ignoreMouse !== true) {\n needUpdate = needUpdate || svg.Mouse.hasEvents();\n }\n\n // need update from animations?\n if (svg.opts.ignoreAnimation !== true) {\n svg.Animations.forEach((animation) => {\n const needAnimationUpdate = animation.update(1000 / svg.FRAMERATE);\n needUpdate = needUpdate || needAnimationUpdate;\n });\n }\n\n // need update from redraw?\n if (typeof svg.opts.forceRedraw === 'function') {\n if (svg.opts.forceRedraw() === true) {\n needUpdate = true;\n }\n }\n\n // render if needed\n if (needUpdate) {\n draw(res);\n svg.Mouse.runEvents(); // run and clear our events\n }\n }, 1000 / svg.FRAMERATE);\n // Todo: Replace with an image loading Promise utility?\n // eslint-disable-next-line promise/avoid-new\n return new Promise((resolve, reject) => {\n if (svg.ImagesLoaded()) {\n waitingForImages = false;\n draw(resolve);\n return;\n }\n res = resolve;\n });\n };\n\n svg.stop = () => {\n if (svg.intervalID) {\n clearInterval(svg.intervalID);\n }\n };\n\n svg.Mouse = {\n events: [],\n hasEvents () { return this.events.length !== 0; },\n\n onclick (x, y) {\n this.events.push({\n type: 'onclick', x, y,\n run (e) { if (e.onclick) e.onclick(); }\n });\n },\n\n onmousemove (x, y) {\n this.events.push({\n type: 'onmousemove', x, y,\n run (e) { if (e.onmousemove) e.onmousemove(); }\n });\n },\n\n eventElements: [],\n\n checkPath (element, ctx) {\n this.events.forEach(({x, y}, i) => {\n if (ctx.isPointInPath && ctx.isPointInPath(x, y)) {\n this.eventElements[i] = element;\n }\n });\n },\n\n checkBoundingBox (element, bb) {\n this.events.forEach(({x, y}, i) => {\n if (bb.isPointInBox(x, y)) {\n this.eventElements[i] = element;\n }\n });\n },\n\n runEvents () {\n svg.ctx.canvas.style.cursor = '';\n\n this.events.forEach((e, i) => {\n let element = this.eventElements[i];\n while (element) {\n e.run(element);\n element = element.parent;\n }\n });\n\n // done running, clear\n this.events = [];\n this.eventElements = [];\n }\n };\n\n return svg;\n}\n\nif (typeof CanvasRenderingContext2D !== 'undefined') {\n CanvasRenderingContext2D.prototype.drawSvg = function (s, dx, dy, dw, dh) {\n canvg(this.canvas, s, {\n ignoreMouse: true,\n ignoreAnimation: true,\n ignoreDimensions: true,\n ignoreClear: true,\n offsetX: dx,\n offsetY: dy,\n scaleWidth: dw,\n scaleHeight: dh\n });\n };\n}\n","/**\n * @file ext-server_moinsave.js\n *\n * @license (MIT OR GPL-2.0-or-later)\n *\n * @copyright 2010 Alexis Deveria, 2011 MoinMoin:ReimarBauer\n * adopted for moinmoins item storage. It sends in one post png and svg data\n * (I agree to dual license my work to additional GPLv2 or later)\n */\nimport {canvg} from '../canvg/canvg.js';\n\nexport default {\n name: 'server_moinsave',\n async init ({$, encode64, importLocale}) {\n const strings = await importLocale();\n const svgEditor = this;\n const svgCanvas = svgEditor.canvas;\n const saveSvgAction = '/+modify';\n\n // Create upload target (hidden iframe)\n // Hiding by size instead of display to avoid FF console errors\n // with `getBBox` in browser.js `supportsPathBBox_`)\n /* const target = */ $(\n `\\d{1,3})\\)$/,\n example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b));\n }\n },\n {\n re: /^(\\w{2})(\\w{2})(\\w{2})$/,\n // re: /^(?\\w{2})(?\\w{2})(?\\w{2})$/,\n example: ['#00ff00', '336699'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b, 16));\n }\n },\n {\n re: /^(\\w)(\\w)(\\w)$/,\n // re: /^(?\\w{1})(?\\w{1})(?\\w{1})$/,\n example: ['#fb0', 'f0f'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b + b, 16));\n }\n }\n];\n\n/**\n * A class to parse color values.\n */\nexport default class RGBColor {\n /**\n * @param {string} colorString\n */\n constructor (colorString) {\n this.ok = false;\n\n // strip any leading #\n if (colorString.charAt(0) === '#') { // remove # if any\n colorString = colorString.substr(1, 6);\n }\n\n colorString = colorString.replace(/ /g, '');\n colorString = colorString.toLowerCase();\n\n // before getting into regexps, try simple matches\n // and overwrite the input\n if (colorString in simpleColors) {\n colorString = simpleColors[colorString];\n }\n // end of simple type-in colors\n\n // search through the definitions to find a match\n\n colorDefs.forEach(({re, process: processor}) => {\n const bits = re.exec(colorString);\n if (bits) {\n const [r, g, b] = processor(...bits);\n Object.assign(this, {r, g, b});\n this.ok = true;\n }\n });\n\n // validate/cleanup values\n this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);\n this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);\n this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);\n }\n\n // some getters\n /**\n * @returns {string}\n */\n toRGB () {\n return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';\n }\n\n /**\n * @returns {string}\n */\n toHex () {\n let r = this.r.toString(16);\n let g = this.g.toString(16);\n let b = this.b.toString(16);\n if (r.length === 1) { r = '0' + r; }\n if (g.length === 1) { g = '0' + g; }\n if (b.length === 1) { b = '0' + b; }\n return '#' + r + g + b;\n }\n\n /**\n * Offers a bulleted list of help.\n * @returns {HTMLUListElement}\n */\n static getHelpXML () {\n const examples = [\n // add regexps\n ...colorDefs.flatMap(({example}) => {\n return example;\n }),\n // add type-in colors\n ...Object.keys(simpleColors)\n ];\n\n const xml = document.createElement('ul');\n xml.setAttribute('id', 'rgbcolor-examples');\n\n xml.append(...examples.map((example) => {\n try {\n const listItem = document.createElement('li');\n const listColor = new RGBColor(example);\n const exampleDiv = document.createElement('div');\n exampleDiv.style.cssText = `\n margin: 3px;\n border: 1px solid black;\n background: ${listColor.toHex()};\n color: ${listColor.toHex()};`;\n exampleDiv.append('test');\n const listItemValue = ` ${example} -> ${listColor.toRGB()} -> ${listColor.toHex()}`;\n listItem.append(exampleDiv, listItemValue);\n return listItem;\n } catch (e) {\n return '';\n }\n }));\n return xml;\n }\n}\n","function _typeof(obj) {\n \"@babel/helpers - typeof\";\n\n if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") {\n _typeof = function (obj) {\n return typeof obj;\n };\n } else {\n _typeof = function (obj) {\n return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj;\n };\n }\n\n return _typeof(obj);\n}\n\nfunction _classCallCheck(instance, Constructor) {\n if (!(instance instanceof Constructor)) {\n throw new TypeError(\"Cannot call a class as a function\");\n }\n}\n\n/* eslint-disable no-bitwise, unicorn/prefer-query-selector */\n\n/**\n* StackBlur - a fast almost Gaussian Blur For Canvas\n*\n* In case you find this class useful - especially in commercial projects -\n* I am not totally unhappy for a small donation to my PayPal account\n* mario@quasimondo.de\n*\n* Or support me on flattr:\n* {@link https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript}.\n*\n* @module StackBlur\n* @author Mario Klingemann\n* Contact: mario@quasimondo.com\n* Website: {@link http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html}\n* Twitter: @quasimondo\n*\n* @copyright (c) 2010 Mario Klingemann\n*\n* Permission is hereby granted, free of charge, to any person\n* obtaining a copy of this software and associated documentation\n* files (the \"Software\"), to deal in the Software without\n* restriction, including without limitation the rights to use,\n* copy, modify, merge, publish, distribute, sublicense, and/or sell\n* copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following\n* conditions:\n*\n* The above copyright notice and this permission notice shall be\n* included in all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n* OTHER DEALINGS IN THE SOFTWARE.\n*/\n\n/* eslint-disable max-len */\nvar mulTable = [512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259];\nvar shgTable = [9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24];\n/* eslint-enable max-len */\n\n/**\n * @param {string|HTMLImageElement} img\n * @param {string|HTMLCanvasElement} canvas\n * @param {Float} radius\n * @param {boolean} blurAlphaChannel\n * @returns {undefined}\n */\n\nfunction processImage(img, canvas, radius, blurAlphaChannel) {\n if (typeof img === 'string') {\n img = document.getElementById(img);\n }\n\n if (!img || !('naturalWidth' in img)) {\n return;\n }\n\n var w = img.naturalWidth;\n var h = img.naturalHeight;\n\n if (typeof canvas === 'string') {\n canvas = document.getElementById(canvas);\n }\n\n if (!canvas || !('getContext' in canvas)) {\n return;\n }\n\n canvas.style.width = w + 'px';\n canvas.style.height = h + 'px';\n canvas.width = w;\n canvas.height = h;\n var context = canvas.getContext('2d');\n context.clearRect(0, 0, w, h);\n context.drawImage(img, 0, 0);\n\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n if (blurAlphaChannel) {\n processCanvasRGBA(canvas, 0, 0, w, h, radius);\n } else {\n processCanvasRGB(canvas, 0, 0, w, h, radius);\n }\n}\n/**\n * @param {string|HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @throws {Error|TypeError}\n * @returns {ImageData} See {@link https://html.spec.whatwg.org/multipage/canvas.html#imagedata}\n */\n\n\nfunction getImageDataFromCanvas(canvas, topX, topY, width, height) {\n if (typeof canvas === 'string') {\n canvas = document.getElementById(canvas);\n }\n\n if (!canvas || _typeof(canvas) !== 'object' || !('getContext' in canvas)) {\n throw new TypeError('Expecting canvas with `getContext` method ' + 'in processCanvasRGB(A) calls!');\n }\n\n var context = canvas.getContext('2d');\n\n try {\n return context.getImageData(topX, topY, width, height);\n } catch (e) {\n throw new Error('unable to access image data: ' + e);\n }\n}\n/**\n * @param {HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {undefined}\n */\n\n\nfunction processCanvasRGBA(canvas, topX, topY, width, height, radius) {\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n radius |= 0;\n var imageData = getImageDataFromCanvas(canvas, topX, topY, width, height);\n imageData = processImageDataRGBA(imageData, topX, topY, width, height, radius);\n canvas.getContext('2d').putImageData(imageData, topX, topY);\n}\n/**\n * @param {ImageData} imageData\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {ImageData}\n */\n\n\nfunction processImageDataRGBA(imageData, topX, topY, width, height, radius) {\n var pixels = imageData.data;\n var x, y, i, p, yp, yi, yw, rSum, gSum, bSum, aSum, rOutSum, gOutSum, bOutSum, aOutSum, rInSum, gInSum, bInSum, aInSum, pr, pg, pb, pa, rbs;\n var div = 2 * radius + 1; // const w4 = width << 2;\n\n var widthMinus1 = width - 1;\n var heightMinus1 = height - 1;\n var radiusPlus1 = radius + 1;\n var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;\n var stackStart = new BlurStack();\n var stack = stackStart;\n var stackEnd;\n\n for (i = 1; i < div; i++) {\n stack = stack.next = new BlurStack();\n\n if (i === radiusPlus1) {\n stackEnd = stack;\n }\n }\n\n stack.next = stackStart;\n var stackIn = null;\n var stackOut = null;\n yw = yi = 0;\n var mulSum = mulTable[radius];\n var shgSum = shgTable[radius];\n\n for (y = 0; y < height; y++) {\n rInSum = gInSum = bInSum = aInSum = rSum = gSum = bSum = aSum = 0;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n aOutSum = radiusPlus1 * (pa = pixels[yi + 3]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n aSum += sumFactor * pa;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack.a = pa;\n stack = stack.next;\n }\n\n for (i = 1; i < radiusPlus1; i++) {\n p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);\n rSum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[p + 1]) * rbs;\n bSum += (stack.b = pb = pixels[p + 2]) * rbs;\n aSum += (stack.a = pa = pixels[p + 3]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n aInSum += pa;\n stack = stack.next;\n }\n\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (x = 0; x < width; x++) {\n pixels[yi + 3] = pa = aSum * mulSum >> shgSum;\n\n if (pa !== 0) {\n pa = 255 / pa;\n pixels[yi] = (rSum * mulSum >> shgSum) * pa;\n pixels[yi + 1] = (gSum * mulSum >> shgSum) * pa;\n pixels[yi + 2] = (bSum * mulSum >> shgSum) * pa;\n } else {\n pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0;\n }\n\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n aSum -= aOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n aOutSum -= stackIn.a;\n p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2;\n rInSum += stackIn.r = pixels[p];\n gInSum += stackIn.g = pixels[p + 1];\n bInSum += stackIn.b = pixels[p + 2];\n aInSum += stackIn.a = pixels[p + 3];\n rSum += rInSum;\n gSum += gInSum;\n bSum += bInSum;\n aSum += aInSum;\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n aOutSum += pa = stackOut.a;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n aInSum -= pa;\n stackOut = stackOut.next;\n yi += 4;\n }\n\n yw += width;\n }\n\n for (x = 0; x < width; x++) {\n gInSum = bInSum = aInSum = rInSum = gSum = bSum = aSum = rSum = 0;\n yi = x << 2;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n aOutSum = radiusPlus1 * (pa = pixels[yi + 3]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n aSum += sumFactor * pa;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack.a = pa;\n stack = stack.next;\n }\n\n yp = width;\n\n for (i = 1; i <= radius; i++) {\n yi = yp + x << 2;\n rSum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[yi + 1]) * rbs;\n bSum += (stack.b = pb = pixels[yi + 2]) * rbs;\n aSum += (stack.a = pa = pixels[yi + 3]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n aInSum += pa;\n stack = stack.next;\n\n if (i < heightMinus1) {\n yp += width;\n }\n }\n\n yi = x;\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (y = 0; y < height; y++) {\n p = yi << 2;\n pixels[p + 3] = pa = aSum * mulSum >> shgSum;\n\n if (pa > 0) {\n pa = 255 / pa;\n pixels[p] = (rSum * mulSum >> shgSum) * pa;\n pixels[p + 1] = (gSum * mulSum >> shgSum) * pa;\n pixels[p + 2] = (bSum * mulSum >> shgSum) * pa;\n } else {\n pixels[p] = pixels[p + 1] = pixels[p + 2] = 0;\n }\n\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n aSum -= aOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n aOutSum -= stackIn.a;\n p = x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << 2;\n rSum += rInSum += stackIn.r = pixels[p];\n gSum += gInSum += stackIn.g = pixels[p + 1];\n bSum += bInSum += stackIn.b = pixels[p + 2];\n aSum += aInSum += stackIn.a = pixels[p + 3];\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n aOutSum += pa = stackOut.a;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n aInSum -= pa;\n stackOut = stackOut.next;\n yi += width;\n }\n }\n\n return imageData;\n}\n/**\n * @param {HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {undefined}\n */\n\n\nfunction processCanvasRGB(canvas, topX, topY, width, height, radius) {\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n radius |= 0;\n var imageData = getImageDataFromCanvas(canvas, topX, topY, width, height);\n imageData = processImageDataRGB(imageData, topX, topY, width, height, radius);\n canvas.getContext('2d').putImageData(imageData, topX, topY);\n}\n/**\n * @param {ImageData} imageData\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {ImageData}\n */\n\n\nfunction processImageDataRGB(imageData, topX, topY, width, height, radius) {\n var pixels = imageData.data;\n var x, y, i, p, yp, yi, yw, rSum, gSum, bSum, rOutSum, gOutSum, bOutSum, rInSum, gInSum, bInSum, pr, pg, pb, rbs;\n var div = 2 * radius + 1; // const w4 = width << 2;\n\n var widthMinus1 = width - 1;\n var heightMinus1 = height - 1;\n var radiusPlus1 = radius + 1;\n var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;\n var stackStart = new BlurStack();\n var stack = stackStart;\n var stackEnd;\n\n for (i = 1; i < div; i++) {\n stack = stack.next = new BlurStack();\n\n if (i === radiusPlus1) {\n stackEnd = stack;\n }\n }\n\n stack.next = stackStart;\n var stackIn = null;\n var stackOut = null;\n yw = yi = 0;\n var mulSum = mulTable[radius];\n var shgSum = shgTable[radius];\n\n for (y = 0; y < height; y++) {\n rInSum = gInSum = bInSum = rSum = gSum = bSum = 0;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack = stack.next;\n }\n\n for (i = 1; i < radiusPlus1; i++) {\n p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);\n rSum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[p + 1]) * rbs;\n bSum += (stack.b = pb = pixels[p + 2]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n stack = stack.next;\n }\n\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (x = 0; x < width; x++) {\n pixels[yi] = rSum * mulSum >> shgSum;\n pixels[yi + 1] = gSum * mulSum >> shgSum;\n pixels[yi + 2] = bSum * mulSum >> shgSum;\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2;\n rInSum += stackIn.r = pixels[p];\n gInSum += stackIn.g = pixels[p + 1];\n bInSum += stackIn.b = pixels[p + 2];\n rSum += rInSum;\n gSum += gInSum;\n bSum += bInSum;\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n stackOut = stackOut.next;\n yi += 4;\n }\n\n yw += width;\n }\n\n for (x = 0; x < width; x++) {\n gInSum = bInSum = rInSum = gSum = bSum = rSum = 0;\n yi = x << 2;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack = stack.next;\n }\n\n yp = width;\n\n for (i = 1; i <= radius; i++) {\n yi = yp + x << 2;\n rSum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[yi + 1]) * rbs;\n bSum += (stack.b = pb = pixels[yi + 2]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n stack = stack.next;\n\n if (i < heightMinus1) {\n yp += width;\n }\n }\n\n yi = x;\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (y = 0; y < height; y++) {\n p = yi << 2;\n pixels[p] = rSum * mulSum >> shgSum;\n pixels[p + 1] = gSum * mulSum >> shgSum;\n pixels[p + 2] = bSum * mulSum >> shgSum;\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n p = x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << 2;\n rSum += rInSum += stackIn.r = pixels[p];\n gSum += gInSum += stackIn.g = pixels[p + 1];\n bSum += bInSum += stackIn.b = pixels[p + 2];\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n stackOut = stackOut.next;\n yi += width;\n }\n }\n\n return imageData;\n}\n/**\n *\n */\n\n\nvar BlurStack =\n/**\n * Set properties.\n */\nfunction BlurStack() {\n _classCallCheck(this, BlurStack);\n\n this.r = 0;\n this.g = 0;\n this.b = 0;\n this.a = 0;\n this.next = null;\n};\n\nexport { BlurStack, processCanvasRGB as canvasRGB, processCanvasRGBA as canvasRGBA, processImage as image, processImageDataRGB as imageDataRGB, processImageDataRGBA as imageDataRGBA };\n","/* eslint-disable new-cap, class-methods-use-this, jsdoc/require-jsdoc */\n// Todo: Compare with latest canvg (add any improvements of ours) and add full JSDocs (denoting links to standard APIs and which are custom): https://github.com/canvg/canvg\n/**\n * Javascript SVG parser and renderer on Canvas.\n * @file canvg.js\n * @module canvg\n * @license MIT\n * @author Gabe Lerner \n * @see https://github.com/canvg/canvg\n */\n\nimport RGBColor from './rgbcolor.js';\nimport {canvasRGBA} from '../../external/stackblur-canvas/dist/stackblur-es.js';\n\n/**\n * Whether a value is `null` or `undefined`.\n * @param {any} val\n * @returns {boolean}\n */\nconst isNullish = (val) => {\n return val === null || val === undefined;\n};\n\n/**\n* @callback module:canvg.ForceRedraw\n* @returns {boolean}\n*/\n\n/**\n* @typedef {PlainObject} module:canvg.CanvgOptions\n* @property {boolean} ignoreMouse true => ignore mouse events\n* @property {boolean} ignoreAnimation true => ignore animations\n* @property {boolean} ignoreDimensions true => does not try to resize canvas\n* @property {boolean} ignoreClear true => does not clear canvas\n* @property {Integer} offsetX int => draws at a x offset\n* @property {Integer} offsetY int => draws at a y offset\n* @property {Integer} scaleWidth int => scales horizontally to width\n* @property {Integer} scaleHeight int => scales vertically to height\n* @property {module:canvg.ForceRedraw} forceRedraw function => will call the function on every frame, if it returns true, will redraw\n* @property {boolean} log Adds log function\n* @property {boolean} useCORS Whether to set CORS `crossOrigin` for the image to `Anonymous`\n*/\n\n/**\n* If called with no arguments, it will replace all `` elements on the page\n* with `` elements.\n* @function module:canvg.canvg\n* @param {HTMLCanvasElement|string} target canvas element or the id of a canvas element\n* @param {string|XMLDocument} s - svg string, url to svg file, or xml document\n* @param {module:canvg.CanvgOptions} [opts] Optional hash of options\n* @returns {Promise} All the function after the first render is completed with dom\n*/\nexport const canvg = function (target, s, opts) {\n // no parameters\n if (isNullish(target) && isNullish(s) && isNullish(opts)) {\n const svgTags = document.querySelectorAll('svg');\n return Promise.all([...svgTags].map((svgTag) => {\n const c = document.createElement('canvas');\n c.width = svgTag.clientWidth;\n c.height = svgTag.clientHeight;\n svgTag.before(c);\n svgTag.remove();\n const div = document.createElement('div');\n div.append(svgTag);\n return canvg(c, div.innerHTML);\n }));\n }\n\n if (typeof target === 'string') {\n target = document.getElementById(target);\n }\n\n // store class on canvas\n if (!isNullish(target.svg)) target.svg.stop();\n const svg = build(opts || {});\n // on i.e. 8 for flash canvas, we can't assign the property so check for it\n if (!(target.childNodes.length === 1 && target.childNodes[0].nodeName === 'OBJECT')) {\n target.svg = svg;\n }\n\n const ctx = target.getContext('2d');\n if (typeof s.documentElement !== 'undefined') {\n // load from xml doc\n return svg.loadXmlDoc(ctx, s);\n }\n if (s.substr(0, 1) === '<') {\n // load from xml string\n return svg.loadXml(ctx, s);\n }\n // load from url\n return svg.load(ctx, s);\n};\n\n/* eslint-disable jsdoc/check-types */\n/**\n* @param {module:canvg.CanvgOptions} opts\n* @returns {object}\n* @todo Flesh out exactly what object is returned here (after updating to latest and reincluding our changes here and those of StackBlur)\n*/\nfunction build (opts) {\n /* eslint-enable jsdoc/check-types */\n const svg = {opts};\n\n svg.FRAMERATE = 30;\n svg.MAX_VIRTUAL_PIXELS = 30000;\n\n svg.log = function (msg) { /* */ };\n if (svg.opts.log === true && typeof console !== 'undefined') {\n svg.log = function (msg) { console.log(msg); }; // eslint-disable-line no-console\n }\n\n // globals\n svg.init = function (ctx) {\n let uniqueId = 0;\n svg.UniqueId = function () {\n uniqueId++;\n return 'canvg' + uniqueId;\n };\n svg.Definitions = {};\n svg.Styles = {};\n svg.Animations = [];\n svg.Images = [];\n svg.ctx = ctx;\n svg.ViewPort = {\n viewPorts: [],\n Clear () { this.viewPorts = []; },\n SetCurrent (width, height) { this.viewPorts.push({width, height}); },\n RemoveCurrent () { this.viewPorts.pop(); },\n Current () { return this.viewPorts[this.viewPorts.length - 1]; },\n width () { return this.Current().width; },\n height () { return this.Current().height; },\n ComputeSize (d) {\n if (!isNullish(d) && typeof d === 'number') return d;\n if (d === 'x') return this.width();\n if (d === 'y') return this.height();\n return Math.sqrt(\n (this.width() ** 2) + (this.height() ** 2)\n ) / Math.sqrt(2);\n }\n };\n };\n svg.init();\n\n // images loaded\n svg.ImagesLoaded = function () {\n return svg.Images.every((img) => img.loaded);\n };\n\n // trim\n svg.trim = function (s) {\n return s.replace(/^\\s+|\\s+$/g, '');\n };\n\n // compress spaces\n svg.compressSpaces = function (s) {\n return s.replace(/\\s+/gm, ' ');\n };\n\n // ajax\n // Todo: Replace with `fetch` and polyfill\n svg.ajax = function (url, asynch) {\n const AJAX = window.XMLHttpRequest\n ? new XMLHttpRequest()\n : new window.ActiveXObject('Microsoft.XMLHTTP');\n if (asynch) {\n return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new\n const req = AJAX.open('GET', url, true);\n req.addEventListener('load', () => {\n resolve(AJAX.responseText);\n });\n AJAX.send(null);\n });\n }\n\n AJAX.open('GET', url, false);\n AJAX.send(null);\n return AJAX.responseText;\n };\n\n // parse xml\n svg.parseXml = function (xml) {\n if (window.DOMParser) {\n const parser = new DOMParser();\n return parser.parseFromString(xml, 'text/xml');\n }\n xml = xml.replace(/]*>/, '');\n const xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');\n xmlDoc.async = 'false';\n xmlDoc.loadXML(xml);\n return xmlDoc;\n };\n\n // text extensions\n // get the text baseline\n const textBaselineMapping = {\n baseline: 'alphabetic',\n 'before-edge': 'top',\n 'text-before-edge': 'top',\n middle: 'middle',\n central: 'middle',\n 'after-edge': 'bottom',\n 'text-after-edge': 'bottom',\n ideographic: 'ideographic',\n alphabetic: 'alphabetic',\n hanging: 'hanging',\n mathematical: 'alphabetic'\n };\n\n svg.Property = class Property {\n constructor (name, value) {\n this.name = name;\n this.value = value;\n }\n\n getValue () {\n return this.value;\n }\n\n hasValue () {\n return (!isNullish(this.value) && this.value !== '');\n }\n\n // return the numerical value of the property\n numValue () {\n if (!this.hasValue()) return 0;\n\n let n = Number.parseFloat(this.value);\n if (String(this.value).endsWith('%')) {\n n /= 100.0;\n }\n return n;\n }\n\n valueOrDefault (def) {\n if (this.hasValue()) return this.value;\n return def;\n }\n\n numValueOrDefault (def) {\n if (this.hasValue()) return this.numValue();\n return def;\n }\n\n // color extensions\n // augment the current color value with the opacity\n addOpacity (opacityProp) {\n let newValue = this.value;\n if (!isNullish(opacityProp.value) && opacityProp.value !== '' && typeof this.value === 'string') { // can only add opacity to colors, not patterns\n const color = new RGBColor(this.value);\n if (color.ok) {\n newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacityProp.numValue() + ')';\n }\n }\n return new svg.Property(this.name, newValue);\n }\n\n // definition extensions\n // get the definition from the definitions table\n getDefinition () {\n let name = this.value.match(/#([^)'\"]+)/);\n if (name) { name = name[1]; }\n if (!name) { name = this.value; }\n return svg.Definitions[name];\n }\n\n isUrlDefinition () {\n return this.value.startsWith('url(');\n }\n\n getFillStyleDefinition (e, opacityProp) {\n let def = this.getDefinition();\n\n // gradient\n if (!isNullish(def) && def.createGradient) {\n return def.createGradient(svg.ctx, e, opacityProp);\n }\n\n // pattern\n if (!isNullish(def) && def.createPattern) {\n if (def.getHrefAttribute().hasValue()) {\n const pt = def.attribute('patternTransform');\n def = def.getHrefAttribute().getDefinition();\n if (pt.hasValue()) { def.attribute('patternTransform', true).value = pt.value; }\n }\n return def.createPattern(svg.ctx, e);\n }\n\n return null;\n }\n\n // length extensions\n getDPI (viewPort) {\n return 96.0; // TODO: compute?\n }\n\n getEM (viewPort) {\n let em = 12;\n\n const fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);\n if (fontSize.hasValue()) em = fontSize.toPixels(viewPort);\n\n return em;\n }\n\n getUnits () {\n return String(this.value).replace(/[\\d.-]/g, '');\n }\n\n // get the length as pixels\n toPixels (viewPort, processPercent) {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('em')) return this.numValue() * this.getEM(viewPort);\n if (s.endsWith('ex')) return this.numValue() * this.getEM(viewPort) / 2.0;\n if (s.endsWith('px')) return this.numValue();\n if (s.endsWith('pt')) return this.numValue() * this.getDPI(viewPort) * (1.0 / 72.0);\n if (s.endsWith('pc')) return this.numValue() * 15;\n if (s.endsWith('cm')) return this.numValue() * this.getDPI(viewPort) / 2.54;\n if (s.endsWith('mm')) return this.numValue() * this.getDPI(viewPort) / 25.4;\n if (s.endsWith('in')) return this.numValue() * this.getDPI(viewPort);\n if (s.endsWith('%')) return this.numValue() * svg.ViewPort.ComputeSize(viewPort);\n const n = this.numValue();\n if (processPercent && n < 1.0) return n * svg.ViewPort.ComputeSize(viewPort);\n return n;\n }\n\n // time extensions\n // get the time as milliseconds\n toMilliseconds () {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('ms')) return this.numValue();\n if (s.endsWith('s')) return this.numValue() * 1000;\n return this.numValue();\n }\n\n // angle extensions\n // get the angle as radians\n toRadians () {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('deg')) return this.numValue() * (Math.PI / 180.0);\n if (s.endsWith('grad')) return this.numValue() * (Math.PI / 200.0);\n if (s.endsWith('rad')) return this.numValue();\n return this.numValue() * (Math.PI / 180.0);\n }\n\n toTextBaseline () {\n if (!this.hasValue()) return null;\n return textBaselineMapping[this.value];\n }\n };\n\n // fonts\n svg.Font = {\n Styles: 'normal|italic|oblique|inherit',\n Variants: 'normal|small-caps|inherit',\n Weights: 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit',\n\n CreateFont (fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {\n const f = !isNullish(inherit) ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);\n return {\n fontFamily: fontFamily || f.fontFamily,\n fontSize: fontSize || f.fontSize,\n fontStyle: fontStyle || f.fontStyle,\n fontWeight: fontWeight || f.fontWeight,\n fontVariant: fontVariant || f.fontVariant,\n toString () {\n return [\n this.fontStyle, this.fontVariant, this.fontWeight,\n this.fontSize, this.fontFamily\n ].join(' ');\n }\n };\n },\n\n Parse (s) {\n const f = {};\n const ds = svg.trim(svg.compressSpaces(s || '')).split(' ');\n const set = {\n fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false\n };\n let ff = '';\n ds.forEach((d) => {\n if (!set.fontStyle && this.Styles.includes(d)) {\n if (d !== 'inherit') {\n f.fontStyle = d;\n }\n set.fontStyle = true;\n } else if (!set.fontVariant && this.Variants.includes(d)) {\n if (d !== 'inherit') {\n f.fontVariant = d;\n }\n set.fontStyle = set.fontVariant = true;\n } else if (!set.fontWeight && this.Weights.includes(d)) {\n if (d !== 'inherit') {\n f.fontWeight = d;\n }\n set.fontStyle = set.fontVariant = set.fontWeight = true;\n } else if (!set.fontSize) {\n if (d !== 'inherit') {\n f.fontSize = d.split('/')[0];\n }\n set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true;\n } else if (d !== 'inherit') {\n ff += d;\n }\n });\n if (ff !== '') { f.fontFamily = ff; }\n return f;\n }\n };\n\n // points and paths\n svg.ToNumberArray = function (s) {\n const a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');\n return a.map((_a) => Number.parseFloat(_a));\n };\n svg.Point = class {\n constructor (x, y) {\n this.x = x;\n this.y = y;\n }\n\n angleTo (p) {\n return Math.atan2(p.y - this.y, p.x - this.x);\n }\n\n applyTransform (v) {\n const xp = this.x * v[0] + this.y * v[2] + v[4];\n const yp = this.x * v[1] + this.y * v[3] + v[5];\n this.x = xp;\n this.y = yp;\n }\n };\n\n svg.CreatePoint = function (s) {\n const a = svg.ToNumberArray(s);\n return new svg.Point(a[0], a[1]);\n };\n svg.CreatePath = function (s) {\n const a = svg.ToNumberArray(s);\n const path = [];\n for (let i = 0; i < a.length; i += 2) {\n path.push(new svg.Point(a[i], a[i + 1]));\n }\n return path;\n };\n\n // bounding box\n svg.BoundingBox = class {\n constructor (x1, y1, x2, y2) { // pass in initial points if you want\n this.x1 = Number.NaN;\n this.y1 = Number.NaN;\n this.x2 = Number.NaN;\n this.y2 = Number.NaN;\n this.addPoint(x1, y1);\n this.addPoint(x2, y2);\n }\n\n x () { return this.x1; }\n y () { return this.y1; }\n width () { return this.x2 - this.x1; }\n height () { return this.y2 - this.y1; }\n\n addPoint (x, y) {\n if (!isNullish(x)) {\n if (isNaN(this.x1) || isNaN(this.x2)) {\n this.x1 = x;\n this.x2 = x;\n }\n if (x < this.x1) this.x1 = x;\n if (x > this.x2) this.x2 = x;\n }\n\n if (!isNullish(y)) {\n if (isNaN(this.y1) || isNaN(this.y2)) {\n this.y1 = y;\n this.y2 = y;\n }\n if (y < this.y1) this.y1 = y;\n if (y > this.y2) this.y2 = y;\n }\n }\n addX (x) { this.addPoint(x, null); }\n addY (y) { this.addPoint(null, y); }\n\n addBoundingBox (bb) {\n this.addPoint(bb.x1, bb.y1);\n this.addPoint(bb.x2, bb.y2);\n }\n\n addQuadraticCurve (p0x, p0y, p1x, p1y, p2x, p2y) {\n const cp1x = p0x + 2 / 3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)\n const cp1y = p0y + 2 / 3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)\n const cp2x = cp1x + 1 / 3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)\n const cp2y = cp1y + 1 / 3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)\n this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);\n }\n\n addBezierCurve (p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {\n // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html\n const p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];\n this.addPoint(p0[0], p0[1]);\n this.addPoint(p3[0], p3[1]);\n\n for (let i = 0; i <= 1; i++) {\n const f = function (t) {\n return ((1 - t) ** 3) * p0[i] +\n 3 * ((1 - t) ** 2) * t * p1[i] +\n 3 * (1 - t) * (t ** 2) * p2[i] +\n (t ** 3) * p3[i];\n };\n\n const b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];\n const a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];\n const c = 3 * p1[i] - 3 * p0[i];\n\n if (a === 0) {\n if (b === 0) continue;\n const t = -c / b;\n if (t > 0 && t < 1) {\n if (i === 0) this.addX(f(t));\n if (i === 1) this.addY(f(t));\n }\n continue;\n }\n\n const b2ac = (b ** 2) - 4 * c * a;\n if (b2ac < 0) continue;\n const t1 = (-b + Math.sqrt(b2ac)) / (2 * a);\n if (t1 > 0 && t1 < 1) {\n if (i === 0) this.addX(f(t1));\n if (i === 1) this.addY(f(t1));\n }\n const t2 = (-b - Math.sqrt(b2ac)) / (2 * a);\n if (t2 > 0 && t2 < 1) {\n if (i === 0) this.addX(f(t2));\n if (i === 1) this.addY(f(t2));\n }\n }\n }\n\n isPointInBox (x, y) {\n return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);\n }\n };\n\n // transforms\n svg.Transform = class {\n constructor (v) {\n this.Type = {\n translate: class {\n constructor (s) {\n this.p = svg.CreatePoint(s);\n this.apply = function (ctx) {\n ctx.translate(this.p.x || 0.0, this.p.y || 0.0);\n };\n this.unapply = function (ctx) {\n ctx.translate(-1.0 * this.p.x || 0.0, -1.0 * this.p.y || 0.0);\n };\n this.applyToPoint = function (p) {\n p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n };\n }\n },\n rotate: class {\n constructor (s) {\n const a = svg.ToNumberArray(s);\n this.angle = new svg.Property('angle', a[0]);\n this.cx = a[1] || 0;\n this.cy = a[2] || 0;\n this.apply = function (ctx) {\n ctx.translate(this.cx, this.cy);\n ctx.rotate(this.angle.toRadians());\n ctx.translate(-this.cx, -this.cy);\n };\n this.unapply = function (ctx) {\n ctx.translate(this.cx, this.cy);\n ctx.rotate(-1.0 * this.angle.toRadians());\n ctx.translate(-this.cx, -this.cy);\n };\n this.applyToPoint = function (p) {\n const _a = this.angle.toRadians();\n p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n p.applyTransform([Math.cos(_a), Math.sin(_a), -Math.sin(_a), Math.cos(_a), 0, 0]);\n p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);\n };\n }\n },\n scale: class {\n constructor (s) {\n this.p = svg.CreatePoint(s);\n this.apply = function (ctx) {\n ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);\n };\n this.unapply = function (ctx) {\n ctx.scale(1.0 / this.p.x || 1.0, 1.0 / this.p.y || this.p.x || 1.0);\n };\n this.applyToPoint = function (p) {\n p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);\n };\n }\n },\n matrix: class {\n constructor (s) {\n this.m = svg.ToNumberArray(s);\n this.apply = function (ctx) {\n ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);\n };\n this.applyToPoint = function (p) {\n p.applyTransform(this.m);\n };\n }\n }\n };\n Object.assign(this.Type, {\n SkewBase: class extends this.Type.matrix {\n constructor (s) {\n super(s);\n this.angle = new svg.Property('angle', s);\n }\n }\n });\n Object.assign(this.Type, {\n skewX: class extends this.Type.SkewBase {\n constructor (s) {\n super(s);\n this.m = [1, 0, Math.tan(this.angle.toRadians()), 1, 0, 0];\n }\n },\n skewY: class extends this.Type.SkewBase {\n constructor (s) {\n super(s);\n this.m = [1, Math.tan(this.angle.toRadians()), 0, 1, 0, 0];\n }\n }\n });\n\n const data = svg.trim(svg.compressSpaces(v)).replace(\n /\\)([a-zA-Z])/g, ') $1'\n ).replace(/\\)(\\s?,\\s?)/g, ') ').split(/\\s(?=[a-z])/);\n this.transforms = data.map((d) => {\n const type = svg.trim(d.split('(')[0]);\n const s = d.split('(')[1].replace(')', '');\n const transform = new this.Type[type](s);\n transform.type = type;\n return transform;\n });\n }\n\n apply (ctx) {\n this.transforms.forEach((transform) => {\n transform.apply(ctx);\n });\n }\n\n unapply (ctx) {\n for (let i = this.transforms.length - 1; i >= 0; i--) {\n this.transforms[i].unapply(ctx);\n }\n }\n\n applyToPoint (p) {\n this.transforms.forEach((transform) => {\n transform.applyToPoint(p);\n });\n }\n };\n\n // aspect ratio\n svg.AspectRatio = function (ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {\n // aspect ratio - https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute\n aspectRatio = svg.compressSpaces(aspectRatio);\n aspectRatio = aspectRatio.replace(/^defer\\s/, ''); // ignore defer\n const align = aspectRatio.split(' ')[0] || 'xMidYMid';\n const meetOrSlice = aspectRatio.split(' ')[1] || 'meet';\n\n // calculate scale\n const scaleX = width / desiredWidth;\n const scaleY = height / desiredHeight;\n const scaleMin = Math.min(scaleX, scaleY);\n const scaleMax = Math.max(scaleX, scaleY);\n if (meetOrSlice === 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }\n if (meetOrSlice === 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }\n\n refX = new svg.Property('refX', refX);\n refY = new svg.Property('refY', refY);\n if (refX.hasValue() && refY.hasValue()) {\n ctx.translate(-scaleMin * refX.toPixels('x'), -scaleMin * refY.toPixels('y'));\n } else {\n // align\n if (align.startsWith('xMid') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleY) || (meetOrSlice === 'slice' && scaleMax === scaleY))) {\n ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);\n }\n if (align.endsWith('YMid') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleX) || (meetOrSlice === 'slice' && scaleMax === scaleX))) {\n ctx.translate(0, height / 2.0 - desiredHeight / 2.0);\n }\n if (align.startsWith('xMax') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleY) || (meetOrSlice === 'slice' && scaleMax === scaleY))) {\n ctx.translate(width - desiredWidth, 0);\n }\n if (align.endsWith('YMax') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleX) ||\n (meetOrSlice === 'slice' && scaleMax === scaleX)\n )\n ) {\n ctx.translate(0, height - desiredHeight);\n }\n }\n\n // scale\n if (align === 'none') ctx.scale(scaleX, scaleY);\n else if (meetOrSlice === 'meet') ctx.scale(scaleMin, scaleMin);\n else if (meetOrSlice === 'slice') ctx.scale(scaleMax, scaleMax);\n\n // translate\n ctx.translate(isNullish(minX) ? 0 : -minX, isNullish(minY) ? 0 : -minY);\n };\n\n // elements\n svg.Element = {};\n\n svg.EmptyProperty = new svg.Property('EMPTY', '');\n\n svg.Element.ElementBase = class {\n constructor (node) {\n // Argument from inheriting class\n this.captureTextNodes = arguments[1]; // eslint-disable-line prefer-rest-params\n this.attributes = {};\n this.styles = {};\n this.children = [];\n if (!isNullish(node) && node.nodeType === 1) { // ELEMENT_NODE\n // add children\n [...node.childNodes].forEach((childNode) => {\n if (childNode.nodeType === 1) {\n this.addChild(childNode, true); // ELEMENT_NODE\n }\n if (this.captureTextNodes && (\n childNode.nodeType === 3 || childNode.nodeType === 4\n )) {\n const text = childNode.nodeValue || childNode.text || '';\n if (svg.trim(svg.compressSpaces(text)) !== '') {\n this.addChild(new svg.Element.tspan(childNode), false); // TEXT_NODE\n }\n }\n });\n\n // add attributes\n [...node.attributes].forEach(({nodeName, nodeValue}) => {\n this.attributes[nodeName] = new svg.Property(\n nodeName,\n nodeValue\n );\n });\n // add tag styles\n let styles = svg.Styles[node.nodeName];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n\n // add class styles\n if (this.attribute('class').hasValue()) {\n const classes = svg.compressSpaces(this.attribute('class').value).split(' ');\n classes.forEach((clss) => {\n styles = svg.Styles['.' + clss];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n styles = svg.Styles[node.nodeName + '.' + clss];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n });\n }\n\n // add id styles\n if (this.attribute('id').hasValue()) {\n const _styles = svg.Styles['#' + this.attribute('id').value];\n if (!isNullish(_styles)) {\n Object.entries(_styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n }\n\n // add inline styles\n if (this.attribute('style').hasValue()) {\n const _styles = this.attribute('style').value.split(';');\n _styles.forEach((style) => {\n if (svg.trim(style) !== '') {\n let {name, value} = style.split(':');\n name = svg.trim(name);\n value = svg.trim(value);\n this.styles[name] = new svg.Property(name, value);\n }\n });\n }\n\n // add id\n if (this.attribute('id').hasValue()) {\n if (isNullish(svg.Definitions[this.attribute('id').value])) {\n svg.Definitions[this.attribute('id').value] = this;\n }\n }\n }\n }\n\n // get or create attribute\n attribute (name, createIfNotExists) {\n let a = this.attributes[name];\n if (!isNullish(a)) return a;\n\n if (createIfNotExists === true) { a = new svg.Property(name, ''); this.attributes[name] = a; }\n return a || svg.EmptyProperty;\n }\n\n getHrefAttribute () {\n for (const a in this.attributes) {\n if (a.endsWith(':href')) {\n return this.attributes[a];\n }\n }\n return svg.EmptyProperty;\n }\n\n // get or create style, crawls up node tree\n style (name, createIfNotExists, skipAncestors) {\n let s = this.styles[name];\n if (!isNullish(s)) return s;\n\n const a = this.attribute(name);\n if (!isNullish(a) && a.hasValue()) {\n this.styles[name] = a; // move up to me to cache\n return a;\n }\n\n if (skipAncestors !== true) {\n const p = this.parent;\n if (!isNullish(p)) {\n const ps = p.style(name);\n if (!isNullish(ps) && ps.hasValue()) {\n return ps;\n }\n }\n }\n\n if (createIfNotExists === true) { s = new svg.Property(name, ''); this.styles[name] = s; }\n return s || svg.EmptyProperty;\n }\n\n // base render\n render (ctx) {\n // don't render display=none\n if (this.style('display').value === 'none') return;\n\n // don't render visibility=hidden\n if (this.style('visibility').value === 'hidden') return;\n\n ctx.save();\n if (this.attribute('mask').hasValue()) { // mask\n const mask = this.attribute('mask').getDefinition();\n if (!isNullish(mask)) mask.apply(ctx, this);\n } else if (this.style('filter').hasValue()) { // filter\n const filter = this.style('filter').getDefinition();\n if (!isNullish(filter)) filter.apply(ctx, this);\n } else {\n this.setContext(ctx);\n this.renderChildren(ctx);\n this.clearContext(ctx);\n }\n ctx.restore();\n }\n\n // base set context\n setContext (ctx) {\n // OVERRIDE ME!\n }\n\n // base clear context\n clearContext (ctx) {\n // OVERRIDE ME!\n }\n\n // base render children\n renderChildren (ctx) {\n this.children.forEach((child) => {\n child.render(ctx);\n });\n }\n\n addChild (childNode, create) {\n const child = create\n ? svg.CreateElement(childNode)\n : childNode;\n child.parent = this;\n if (child.type !== 'title') { this.children.push(child); }\n }\n };\n\n svg.Element.RenderedElementBase = class extends svg.Element.ElementBase {\n setContext (ctx) {\n // fill\n if (this.style('fill').isUrlDefinition()) {\n const fs = this.style('fill').getFillStyleDefinition(this, this.style('fill-opacity'));\n if (!isNullish(fs)) ctx.fillStyle = fs;\n } else if (this.style('fill').hasValue()) {\n const fillStyle = this.style('fill');\n if (fillStyle.value === 'currentColor') fillStyle.value = this.style('color').value;\n ctx.fillStyle = (fillStyle.value === 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);\n }\n if (this.style('fill-opacity').hasValue()) {\n let fillStyle = new svg.Property('fill', ctx.fillStyle);\n fillStyle = fillStyle.addOpacity(this.style('fill-opacity'));\n ctx.fillStyle = fillStyle.value;\n }\n\n // stroke\n if (this.style('stroke').isUrlDefinition()) {\n const fs = this.style('stroke').getFillStyleDefinition(this, this.style('stroke-opacity'));\n if (!isNullish(fs)) ctx.strokeStyle = fs;\n } else if (this.style('stroke').hasValue()) {\n const strokeStyle = this.style('stroke');\n if (strokeStyle.value === 'currentColor') strokeStyle.value = this.style('color').value;\n ctx.strokeStyle = (strokeStyle.value === 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);\n }\n if (this.style('stroke-opacity').hasValue()) {\n let strokeStyle = new svg.Property('stroke', ctx.strokeStyle);\n strokeStyle = strokeStyle.addOpacity(this.style('stroke-opacity'));\n ctx.strokeStyle = strokeStyle.value;\n }\n if (this.style('stroke-width').hasValue()) {\n const newLineWidth = this.style('stroke-width').toPixels();\n ctx.lineWidth = newLineWidth === 0 ? 0.001 : newLineWidth; // browsers don't respect 0\n }\n if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;\n if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;\n if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;\n if (this.style('stroke-dasharray').hasValue() && this.style('stroke-dasharray').value !== 'none') {\n const gaps = svg.ToNumberArray(this.style('stroke-dasharray').value);\n if (typeof ctx.setLineDash !== 'undefined') {\n ctx.setLineDash(gaps);\n } else if (typeof ctx.webkitLineDash !== 'undefined') {\n ctx.webkitLineDash = gaps;\n } else if (typeof ctx.mozDash !== 'undefined' && !(gaps.length === 1 && gaps[0] === 0)) {\n ctx.mozDash = gaps;\n }\n\n const offset = this.style('stroke-dashoffset').numValueOrDefault(1);\n if (typeof ctx.lineDashOffset !== 'undefined') {\n ctx.lineDashOffset = offset;\n } else if (typeof ctx.webkitLineDashOffset !== 'undefined') {\n ctx.webkitLineDashOffset = offset;\n } else if (typeof ctx.mozDashOffset !== 'undefined') {\n ctx.mozDashOffset = offset;\n }\n }\n\n // font\n if (typeof ctx.font !== 'undefined') {\n ctx.font = svg.Font.CreateFont(\n this.style('font-style').value,\n this.style('font-variant').value,\n this.style('font-weight').value,\n this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '',\n this.style('font-family').value\n ).toString();\n }\n\n // transform\n if (this.attribute('transform').hasValue()) {\n const transform = new svg.Transform(this.attribute('transform').value);\n transform.apply(ctx);\n }\n\n // clip\n if (this.style('clip-path', false, true).hasValue()) {\n const clip = this.style('clip-path', false, true).getDefinition();\n if (!isNullish(clip)) clip.apply(ctx);\n }\n\n // opacity\n if (this.style('opacity').hasValue()) {\n ctx.globalAlpha = this.style('opacity').numValue();\n }\n }\n };\n\n svg.Element.PathElementBase = class extends svg.Element.RenderedElementBase {\n path (ctx) {\n if (!isNullish(ctx)) ctx.beginPath();\n return new svg.BoundingBox();\n }\n\n renderChildren (ctx) {\n this.path(ctx);\n svg.Mouse.checkPath(this, ctx);\n if (ctx.fillStyle !== '') {\n if (this.style('fill-rule').valueOrDefault('inherit') !== 'inherit') {\n ctx.fill(this.style('fill-rule').value);\n } else {\n ctx.fill();\n }\n }\n if (ctx.strokeStyle !== '') ctx.stroke();\n\n const markers = this.getMarkers();\n if (!isNullish(markers)) {\n if (this.style('marker-start').isUrlDefinition()) {\n const marker = this.style('marker-start').getDefinition();\n marker.render(ctx, markers[0][0], markers[0][1]);\n }\n if (this.style('marker-mid').isUrlDefinition()) {\n const marker = this.style('marker-mid').getDefinition();\n for (let i = 1; i < markers.length - 1; i++) {\n marker.render(ctx, markers[i][0], markers[i][1]);\n }\n }\n if (this.style('marker-end').isUrlDefinition()) {\n const marker = this.style('marker-end').getDefinition();\n marker.render(ctx, markers[markers.length - 1][0], markers[markers.length - 1][1]);\n }\n }\n }\n\n getBoundingBox () {\n return this.path();\n }\n\n getMarkers () {\n return null;\n }\n };\n\n // svg element\n svg.Element.svg = class extends svg.Element.RenderedElementBase {\n clearContext (ctx) {\n super.clearContext(ctx);\n svg.ViewPort.RemoveCurrent();\n }\n\n setContext (ctx) {\n // initial values and defaults\n ctx.strokeStyle = 'rgba(0,0,0,0)';\n ctx.lineCap = 'butt';\n ctx.lineJoin = 'miter';\n ctx.miterLimit = 4;\n if (typeof ctx.font !== 'undefined' && typeof window.getComputedStyle !== 'undefined') {\n ctx.font = window.getComputedStyle(ctx.canvas).getPropertyValue('font');\n }\n\n super.setContext(ctx);\n\n // create new view port\n if (!this.attribute('x').hasValue()) this.attribute('x', true).value = 0;\n if (!this.attribute('y').hasValue()) this.attribute('y', true).value = 0;\n ctx.translate(this.attribute('x').toPixels('x'), this.attribute('y').toPixels('y'));\n\n let width = svg.ViewPort.width();\n let height = svg.ViewPort.height();\n\n if (!this.attribute('width').hasValue()) this.attribute('width', true).value = '100%';\n if (!this.attribute('height').hasValue()) this.attribute('height', true).value = '100%';\n if (typeof this.root === 'undefined') {\n width = this.attribute('width').toPixels('x');\n height = this.attribute('height').toPixels('y');\n\n let x = 0;\n let y = 0;\n if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {\n x = -this.attribute('refX').toPixels('x');\n y = -this.attribute('refY').toPixels('y');\n }\n\n if (this.attribute('overflow').valueOrDefault('hidden') !== 'visible') {\n ctx.beginPath();\n ctx.moveTo(x, y);\n ctx.lineTo(width, y);\n ctx.lineTo(width, height);\n ctx.lineTo(x, height);\n ctx.closePath();\n ctx.clip();\n }\n }\n svg.ViewPort.SetCurrent(width, height);\n\n // viewbox\n if (this.attribute('viewBox').hasValue()) {\n const viewBox = svg.ToNumberArray(this.attribute('viewBox').value);\n const minX = viewBox[0];\n const minY = viewBox[1];\n width = viewBox[2];\n height = viewBox[3];\n\n svg.AspectRatio(\n ctx,\n this.attribute('preserveAspectRatio').value,\n svg.ViewPort.width(),\n width,\n svg.ViewPort.height(),\n height,\n minX,\n minY,\n this.attribute('refX').value,\n this.attribute('refY').value\n );\n\n svg.ViewPort.RemoveCurrent();\n svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);\n }\n }\n };\n\n // rect element\n svg.Element.rect = class extends svg.Element.PathElementBase {\n path (ctx) {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n let rx = this.attribute('rx').toPixels('x');\n let ry = this.attribute('ry').toPixels('y');\n if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;\n if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;\n rx = Math.min(rx, width / 2.0);\n ry = Math.min(ry, height / 2.0);\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(x + rx, y);\n ctx.lineTo(x + width - rx, y);\n ctx.quadraticCurveTo(x + width, y, x + width, y + ry);\n ctx.lineTo(x + width, y + height - ry);\n ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);\n ctx.lineTo(x + rx, y + height);\n ctx.quadraticCurveTo(x, y + height, x, y + height - ry);\n ctx.lineTo(x, y + ry);\n ctx.quadraticCurveTo(x, y, x + rx, y);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(x, y, x + width, y + height);\n }\n };\n\n // circle element\n svg.Element.circle = class extends svg.Element.PathElementBase {\n path (ctx) {\n const cx = this.attribute('cx').toPixels('x');\n const cy = this.attribute('cy').toPixels('y');\n const r = this.attribute('r').toPixels();\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2, true);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);\n }\n };\n\n // ellipse element\n const KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);\n svg.Element.ellipse = class extends svg.Element.PathElementBase {\n path (ctx) {\n const rx = this.attribute('rx').toPixels('x');\n const ry = this.attribute('ry').toPixels('y');\n const cx = this.attribute('cx').toPixels('x');\n const cy = this.attribute('cy').toPixels('y');\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(cx, cy - ry);\n ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);\n ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);\n ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);\n ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);\n }\n };\n\n // line element\n svg.Element.line = class extends svg.Element.PathElementBase {\n getPoints () {\n return [\n new svg.Point(this.attribute('x1').toPixels('x'), this.attribute('y1').toPixels('y')),\n new svg.Point(this.attribute('x2').toPixels('x'), this.attribute('y2').toPixels('y'))\n ];\n }\n\n path (ctx) {\n const points = this.getPoints();\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(points[0].x, points[0].y);\n ctx.lineTo(points[1].x, points[1].y);\n }\n\n return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);\n }\n\n getMarkers () {\n const points = this.getPoints();\n const a = points[0].angleTo(points[1]);\n return [[points[0], a], [points[1], a]];\n }\n };\n\n // polyline element\n svg.Element.polyline = class extends svg.Element.PathElementBase {\n constructor (node) {\n super(node);\n\n this.points = svg.CreatePath(this.attribute('points').value);\n }\n path (ctx) {\n const {x, y} = this.points[0];\n const bb = new svg.BoundingBox(x, y);\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(x, y);\n }\n for (let i = 1; i < this.points.length; i++) {\n const {x: _x, y: _y} = this.points[i];\n bb.addPoint(_x, _y);\n if (!isNullish(ctx)) ctx.lineTo(_x, _y);\n }\n return bb;\n }\n\n getMarkers () {\n const markers = [];\n for (let i = 0; i < this.points.length - 1; i++) {\n markers.push([this.points[i], this.points[i].angleTo(this.points[i + 1])]);\n }\n markers.push([this.points[this.points.length - 1], markers[markers.length - 1][1]]);\n return markers;\n }\n };\n\n // polygon element\n svg.Element.polygon = class extends svg.Element.polyline {\n path (ctx) {\n const bb = super.path(ctx);\n if (!isNullish(ctx)) {\n ctx.lineTo(this.points[0].x, this.points[0].y);\n ctx.closePath();\n }\n return bb;\n }\n };\n\n // path element\n svg.Element.path = class extends svg.Element.PathElementBase {\n constructor (node) {\n super(node);\n\n let d = this.attribute('d').value\n // TODO: convert to real lexer based on https://www.w3.org/TR/SVG11/paths.html#PathDataBNF\n .replace(/,/gm, ' ') // get rid of all commas\n .replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from commands\n .replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from commands\n .replace(/([MmZzLlHhVvCcSsQqTtAa])(\\S)/gm, '$1 $2') // separate commands from points\n .replace(/(\\S)([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from points\n .replace(/(\\d)([+-])/gm, '$1 $2') // separate digits when no comma\n .replace(/(\\.\\d*)(\\.)/gm, '$1 $2') // separate digits when no comma\n .replace(/([Aa](\\s+\\d+)(\\s+\\d+)(\\s+\\d+))\\s+([01])\\s*([01])/gm, '$1 $5 $6 '); // shorthand elliptical arc path syntax\n d = svg.compressSpaces(d); // compress multiple spaces\n d = svg.trim(d);\n this.PathParser = {\n tokens: d.split(' '),\n\n reset () {\n this.i = -1;\n this.command = '';\n this.previousCommand = '';\n this.start = new svg.Point(0, 0);\n this.control = new svg.Point(0, 0);\n this.current = new svg.Point(0, 0);\n this.points = [];\n this.angles = [];\n },\n\n isEnd () {\n return this.i >= this.tokens.length - 1;\n },\n\n isCommandOrEnd () {\n if (this.isEnd()) return true;\n return !isNullish(this.tokens[this.i + 1].match(/^[A-Za-z]$/));\n },\n\n isRelativeCommand () {\n switch (this.command) {\n case 'm':\n case 'l':\n case 'h':\n case 'v':\n case 'c':\n case 's':\n case 'q':\n case 't':\n case 'a':\n case 'z':\n return true;\n }\n return false;\n },\n\n getToken () {\n this.i++;\n return this.tokens[this.i];\n },\n\n getScalar () {\n return Number.parseFloat(this.getToken());\n },\n\n nextCommand () {\n this.previousCommand = this.command;\n this.command = this.getToken();\n },\n\n getPoint () {\n const p = new svg.Point(this.getScalar(), this.getScalar());\n return this.makeAbsolute(p);\n },\n\n getAsControlPoint () {\n const p = this.getPoint();\n this.control = p;\n return p;\n },\n\n getAsCurrentPoint () {\n const p = this.getPoint();\n this.current = p;\n return p;\n },\n\n getReflectedControlPoint () {\n if (this.previousCommand.toLowerCase() !== 'c' &&\n this.previousCommand.toLowerCase() !== 's' &&\n this.previousCommand.toLowerCase() !== 'q' &&\n this.previousCommand.toLowerCase() !== 't') {\n return this.current;\n }\n\n // reflect point\n const p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);\n return p;\n },\n\n makeAbsolute (p) {\n if (this.isRelativeCommand()) {\n p.x += this.current.x;\n p.y += this.current.y;\n }\n return p;\n },\n\n addMarker (p, from, priorTo) {\n // if the last angle isn't filled in because we didn't have this point yet ...\n if (!isNullish(priorTo) && this.angles.length > 0 && isNullish(this.angles[this.angles.length - 1])) {\n this.angles[this.angles.length - 1] = this.points[this.points.length - 1].angleTo(priorTo);\n }\n this.addMarkerAngle(p, isNullish(from) ? null : from.angleTo(p));\n },\n\n addMarkerAngle (p, a) {\n this.points.push(p);\n this.angles.push(a);\n },\n\n getMarkerPoints () { return this.points; },\n getMarkerAngles () {\n for (let i = 0; i < this.angles.length; i++) {\n if (isNullish(this.angles[i])) {\n for (let j = i + 1; j < this.angles.length; j++) {\n if (!isNullish(this.angles[j])) {\n this.angles[i] = this.angles[j];\n break;\n }\n }\n }\n }\n return this.angles;\n }\n };\n }\n\n path (ctx) {\n const pp = this.PathParser;\n pp.reset();\n\n const bb = new svg.BoundingBox();\n if (!isNullish(ctx)) ctx.beginPath();\n while (!pp.isEnd()) {\n pp.nextCommand();\n switch (pp.command) {\n case 'M':\n case 'm': {\n const p = pp.getAsCurrentPoint();\n pp.addMarker(p);\n bb.addPoint(p.x, p.y);\n if (!isNullish(ctx)) ctx.moveTo(p.x, p.y);\n pp.start = pp.current;\n while (!pp.isCommandOrEnd()) {\n const _p = pp.getAsCurrentPoint();\n pp.addMarker(_p, pp.start);\n bb.addPoint(_p.x, _p.y);\n if (!isNullish(ctx)) ctx.lineTo(_p.x, _p.y);\n }\n break;\n } case 'L':\n case 'l':\n while (!pp.isCommandOrEnd()) {\n const c = pp.current;\n const p = pp.getAsCurrentPoint();\n pp.addMarker(p, c);\n bb.addPoint(p.x, p.y);\n if (!isNullish(ctx)) ctx.lineTo(p.x, p.y);\n }\n break;\n case 'H':\n case 'h':\n while (!pp.isCommandOrEnd()) {\n const newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);\n pp.addMarker(newP, pp.current);\n pp.current = newP;\n bb.addPoint(pp.current.x, pp.current.y);\n if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);\n }\n break;\n case 'V':\n case 'v':\n while (!pp.isCommandOrEnd()) {\n const newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());\n pp.addMarker(newP, pp.current);\n pp.current = newP;\n bb.addPoint(pp.current.x, pp.current.y);\n if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);\n }\n break;\n case 'C':\n case 'c':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const p1 = pp.getPoint();\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, p1);\n bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'S':\n case 's':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const p1 = pp.getReflectedControlPoint();\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, p1);\n bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'Q':\n case 'q':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, cntrl);\n bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'T':\n case 't':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const cntrl = pp.getReflectedControlPoint();\n pp.control = cntrl;\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, cntrl);\n bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'A':\n case 'a':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n let rx = pp.getScalar();\n let ry = pp.getScalar();\n const xAxisRotation = pp.getScalar() * (Math.PI / 180.0);\n const largeArcFlag = pp.getScalar();\n const sweepFlag = pp.getScalar();\n const cp = pp.getAsCurrentPoint();\n\n // Conversion from endpoint to center parameterization\n // https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter\n\n // x1', y1'\n const currp = new svg.Point(\n Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,\n -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0\n );\n // adjust radii\n const l = (currp.x ** 2) / (rx ** 2) + (currp.y ** 2) / (ry ** 2);\n if (l > 1) {\n rx *= Math.sqrt(l);\n ry *= Math.sqrt(l);\n }\n // cx', cy'\n let s = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(\n (((rx ** 2) * (ry ** 2)) - ((rx ** 2) * (currp.y ** 2)) - ((ry ** 2) * (currp.x ** 2))) /\n ((rx ** 2) * (currp.y ** 2) + (ry ** 2) * (currp.x ** 2))\n );\n if (isNaN(s)) s = 0;\n const cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);\n // cx, cy\n const centp = new svg.Point(\n (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,\n (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y\n );\n // vector magnitude\n const m = function (v) {\n return Math.sqrt((v[0] ** 2) + (v[1] ** 2));\n };\n // ratio between two vectors\n const r = function (u, v) {\n return (u[0] * v[0] + u[1] * v[1]) / (m(u) * m(v));\n };\n // angle between two vectors\n const a = function (u, v) {\n return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(r(u, v));\n };\n // initial angle\n const a1 = a([1, 0], [(currp.x - cpp.x) / rx, (currp.y - cpp.y) / ry]);\n // angle delta\n const u = [(currp.x - cpp.x) / rx, (currp.y - cpp.y) / ry];\n const v = [(-currp.x - cpp.x) / rx, (-currp.y - cpp.y) / ry];\n let ad = a(u, v);\n if (r(u, v) <= -1) ad = Math.PI;\n if (r(u, v) >= 1) ad = 0;\n\n // for markers\n const dir = 1 - sweepFlag ? 1.0 : -1.0;\n const ah = a1 + dir * (ad / 2.0);\n const halfWay = new svg.Point(\n centp.x + rx * Math.cos(ah),\n centp.y + ry * Math.sin(ah)\n );\n pp.addMarkerAngle(halfWay, ah - dir * Math.PI / 2);\n pp.addMarkerAngle(cp, ah - dir * Math.PI);\n\n bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better\n if (!isNullish(ctx)) {\n const _r = rx > ry ? rx : ry;\n const sx = rx > ry ? 1 : rx / ry;\n const sy = rx > ry ? ry / rx : 1;\n\n ctx.translate(centp.x, centp.y);\n ctx.rotate(xAxisRotation);\n ctx.scale(sx, sy);\n ctx.arc(0, 0, _r, a1, a1 + ad, 1 - sweepFlag);\n ctx.scale(1 / sx, 1 / sy);\n ctx.rotate(-xAxisRotation);\n ctx.translate(-centp.x, -centp.y);\n }\n }\n break;\n case 'Z':\n case 'z':\n if (!isNullish(ctx)) ctx.closePath();\n pp.current = pp.start;\n }\n }\n\n return bb;\n }\n\n getMarkers () {\n const points = this.PathParser.getMarkerPoints();\n const angles = this.PathParser.getMarkerAngles();\n\n const markers = points.map((point, i) => {\n return [point, angles[i]];\n });\n return markers;\n }\n };\n\n // pattern element\n svg.Element.pattern = class extends svg.Element.ElementBase {\n createPattern (ctx, element) {\n const width = this.attribute('width').toPixels('x', true);\n const height = this.attribute('height').toPixels('y', true);\n\n // render me using a temporary svg element\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.viewBox = new svg.Property('viewBox', this.attribute('viewBox').value);\n tempSvg.attributes.width = new svg.Property('width', width + 'px');\n tempSvg.attributes.height = new svg.Property('height', height + 'px');\n tempSvg.attributes.transform = new svg.Property('transform', this.attribute('patternTransform').value);\n tempSvg.children = this.children;\n\n const c = document.createElement('canvas');\n c.width = width;\n c.height = height;\n const cctx = c.getContext('2d');\n if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {\n cctx.translate(this.attribute('x').toPixels('x', true), this.attribute('y').toPixels('y', true));\n }\n // render 3x3 grid so when we transform there's no white space on edges\n for (let x = -1; x <= 1; x++) {\n for (let y = -1; y <= 1; y++) {\n cctx.save();\n cctx.translate(x * c.width, y * c.height);\n tempSvg.render(cctx);\n cctx.restore();\n }\n }\n const pattern = ctx.createPattern(c, 'repeat');\n return pattern;\n }\n };\n\n // marker element\n svg.Element.marker = class extends svg.Element.ElementBase {\n render (ctx, point, angle) {\n ctx.translate(point.x, point.y);\n if (this.attribute('orient').valueOrDefault('auto') === 'auto') ctx.rotate(angle);\n if (this.attribute('markerUnits').valueOrDefault('strokeWidth') === 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);\n ctx.save();\n\n // render me using a temporary svg element\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.viewBox = new svg.Property(\n 'viewBox', this.attribute('viewBox').value\n );\n tempSvg.attributes.refX = new svg.Property(\n 'refX', this.attribute('refX').value\n );\n tempSvg.attributes.refY = new svg.Property(\n 'refY', this.attribute('refY').value\n );\n tempSvg.attributes.width = new svg.Property(\n 'width', this.attribute('markerWidth').value\n );\n tempSvg.attributes.height = new svg.Property(\n 'height', this.attribute('markerHeight').value\n );\n tempSvg.attributes.fill = new svg.Property(\n 'fill', this.attribute('fill').valueOrDefault('black')\n );\n tempSvg.attributes.stroke = new svg.Property(\n 'stroke', this.attribute('stroke').valueOrDefault('none')\n );\n tempSvg.children = this.children;\n tempSvg.render(ctx);\n\n ctx.restore();\n if (this.attribute('markerUnits').valueOrDefault('strokeWidth') ===\n 'strokeWidth'\n ) ctx.scale(1 / ctx.lineWidth, 1 / ctx.lineWidth);\n if (this.attribute('orient').valueOrDefault('auto') === 'auto') {\n ctx.rotate(-angle);\n }\n ctx.translate(-point.x, -point.y);\n }\n };\n\n // definitions element\n svg.Element.defs = class extends svg.Element.ElementBase {\n render (ctx) {\n // NOOP\n }\n };\n\n // base for gradients\n svg.Element.GradientBase = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');\n\n this.stops = [];\n this.children.forEach((child) => {\n if (child.type === 'stop') {\n this.stops.push(child);\n }\n });\n }\n\n getGradient () {\n // OVERRIDE ME!\n }\n\n createGradient (ctx, element, parentOpacityProp) {\n const stopsContainer = this.getHrefAttribute().hasValue()\n ? this.getHrefAttribute().getDefinition()\n : this;\n\n const addParentOpacity = function (color) {\n if (parentOpacityProp.hasValue()) {\n const p = new svg.Property('color', color);\n return p.addOpacity(parentOpacityProp).value;\n }\n return color;\n };\n\n const g = this.getGradient(ctx, element);\n if (isNullish(g)) return addParentOpacity(stopsContainer.stops[stopsContainer.stops.length - 1].color);\n stopsContainer.stops.forEach(({offset, color}) => {\n g.addColorStop(offset, addParentOpacity(color));\n });\n\n if (this.attribute('gradientTransform').hasValue()) {\n // render as transformed pattern on temporary canvas\n const rootView = svg.ViewPort.viewPorts[0];\n\n const rect = new svg.Element.rect();\n rect.attributes.x = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS / 3.0);\n rect.attributes.y = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS / 3.0);\n rect.attributes.width = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);\n rect.attributes.height = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);\n\n const group = new svg.Element.g();\n group.attributes.transform = new svg.Property('transform', this.attribute('gradientTransform').value);\n group.children = [rect];\n\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.x = new svg.Property('x', 0);\n tempSvg.attributes.y = new svg.Property('y', 0);\n tempSvg.attributes.width = new svg.Property('width', rootView.width);\n tempSvg.attributes.height = new svg.Property('height', rootView.height);\n tempSvg.children = [group];\n\n const c = document.createElement('canvas');\n c.width = rootView.width;\n c.height = rootView.height;\n const tempCtx = c.getContext('2d');\n tempCtx.fillStyle = g;\n tempSvg.render(tempCtx);\n return tempCtx.createPattern(c, 'no-repeat');\n }\n\n return g;\n }\n };\n\n // linear gradient element\n svg.Element.linearGradient = class extends svg.Element.GradientBase {\n getGradient (ctx, element) {\n const useBB = this.gradientUnits === 'objectBoundingBox' && element.getBoundingBox;\n const bb = useBB\n ? element.getBoundingBox()\n : null;\n\n if (!this.attribute('x1').hasValue() &&\n !this.attribute('y1').hasValue() &&\n !this.attribute('x2').hasValue() &&\n !this.attribute('y2').hasValue()\n ) {\n this.attribute('x1', true).value = 0;\n this.attribute('y1', true).value = 0;\n this.attribute('x2', true).value = 1;\n this.attribute('y2', true).value = 0;\n }\n\n const x1 = (useBB\n ? bb.x() + bb.width() * this.attribute('x1').numValue()\n : this.attribute('x1').toPixels('x'));\n const y1 = (useBB\n ? bb.y() + bb.height() * this.attribute('y1').numValue()\n : this.attribute('y1').toPixels('y'));\n const x2 = (useBB\n ? bb.x() + bb.width() * this.attribute('x2').numValue()\n : this.attribute('x2').toPixels('x'));\n const y2 = (useBB\n ? bb.y() + bb.height() * this.attribute('y2').numValue()\n : this.attribute('y2').toPixels('y'));\n\n if (x1 === x2 && y1 === y2) return null;\n return ctx.createLinearGradient(x1, y1, x2, y2);\n }\n };\n\n // radial gradient element\n svg.Element.radialGradient = class extends svg.Element.GradientBase {\n getGradient (ctx, element) {\n const useBB = this.gradientUnits === 'objectBoundingBox' && element.getBoundingBox;\n const bb = useBB ? element.getBoundingBox() : null;\n\n if (!this.attribute('cx').hasValue()) this.attribute('cx', true).value = '50%';\n if (!this.attribute('cy').hasValue()) this.attribute('cy', true).value = '50%';\n if (!this.attribute('r').hasValue()) this.attribute('r', true).value = '50%';\n\n const cx = (useBB\n ? bb.x() + bb.width() * this.attribute('cx').numValue()\n : this.attribute('cx').toPixels('x'));\n const cy = (useBB\n ? bb.y() + bb.height() * this.attribute('cy').numValue()\n : this.attribute('cy').toPixels('y'));\n\n let fx = cx;\n let fy = cy;\n if (this.attribute('fx').hasValue()) {\n fx = (useBB\n ? bb.x() + bb.width() * this.attribute('fx').numValue()\n : this.attribute('fx').toPixels('x'));\n }\n if (this.attribute('fy').hasValue()) {\n fy = (useBB\n ? bb.y() + bb.height() * this.attribute('fy').numValue()\n : this.attribute('fy').toPixels('y'));\n }\n\n const r = (useBB\n ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()\n : this.attribute('r').toPixels());\n\n return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);\n }\n };\n\n // gradient stop element\n svg.Element.stop = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.offset = this.attribute('offset').numValue();\n if (this.offset < 0) this.offset = 0;\n if (this.offset > 1) this.offset = 1;\n\n let stopColor = this.style('stop-color');\n if (this.style('stop-opacity').hasValue()) {\n stopColor = stopColor.addOpacity(this.style('stop-opacity'));\n }\n this.color = stopColor.value;\n }\n };\n\n // animation base element\n svg.Element.AnimateBase = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n svg.Animations.push(this);\n\n this.duration = 0.0;\n this.begin = this.attribute('begin').toMilliseconds();\n this.maxDuration = this.begin + this.attribute('dur').toMilliseconds();\n\n this.initialValue = null;\n this.initialUnits = '';\n this.removed = false;\n\n this.from = this.attribute('from');\n this.to = this.attribute('to');\n this.values = this.attribute('values');\n if (this.values.hasValue()) this.values.value = this.values.value.split(';');\n }\n\n getProperty () {\n const attributeType = this.attribute('attributeType').value;\n const attributeName = this.attribute('attributeName').value;\n\n if (attributeType === 'CSS') {\n return this.parent.style(attributeName, true);\n }\n return this.parent.attribute(attributeName, true);\n }\n\n calcValue () {\n // OVERRIDE ME!\n return '';\n }\n\n update (delta) {\n // set initial value\n if (isNullish(this.initialValue)) {\n this.initialValue = this.getProperty().value;\n this.initialUnits = this.getProperty().getUnits();\n }\n\n // if we're past the end time\n if (this.duration > this.maxDuration) {\n // loop for indefinitely repeating animations\n if (this.attribute('repeatCount').value === 'indefinite' ||\n this.attribute('repeatDur').value === 'indefinite') {\n this.duration = 0.0;\n } else if (this.attribute('fill').valueOrDefault('remove') === 'freeze' && !this.frozen) {\n this.frozen = true;\n this.parent.animationFrozen = true;\n this.parent.animationFrozenValue = this.getProperty().value;\n } else if (this.attribute('fill').valueOrDefault('remove') === 'remove' && !this.removed) {\n this.removed = true;\n this.getProperty().value = this.parent.animationFrozen ? this.parent.animationFrozenValue : this.initialValue;\n return true;\n }\n return false;\n }\n this.duration += delta;\n\n // if we're past the begin time\n let updated = false;\n if (this.begin < this.duration) {\n let newValue = this.calcValue(); // tween\n\n if (this.attribute('type').hasValue()) {\n // for transform, etc.\n const type = this.attribute('type').value;\n newValue = type + '(' + newValue + ')';\n }\n\n this.getProperty().value = newValue;\n updated = true;\n }\n\n return updated;\n }\n\n // fraction of duration we've covered\n progress () {\n const ret = {progress: (this.duration - this.begin) / (this.maxDuration - this.begin)};\n if (this.values.hasValue()) {\n const p = ret.progress * (this.values.value.length - 1);\n const lb = Math.floor(p), ub = Math.ceil(p);\n ret.from = new svg.Property('from', Number.parseFloat(this.values.value[lb]));\n ret.to = new svg.Property('to', Number.parseFloat(this.values.value[ub]));\n ret.progress = (p - lb) / (ub - lb);\n } else {\n ret.from = this.from;\n ret.to = this.to;\n }\n return ret;\n }\n };\n\n // animate element\n svg.Element.animate = class extends svg.Element.AnimateBase {\n calcValue () {\n const p = this.progress();\n\n // tween value linearly\n const newValue = p.from.numValue() + (p.to.numValue() - p.from.numValue()) * p.progress;\n return newValue + this.initialUnits;\n }\n };\n\n // animate color element\n svg.Element.animateColor = class extends svg.Element.AnimateBase {\n calcValue () {\n const p = this.progress();\n const from = new RGBColor(p.from.value);\n const to = new RGBColor(p.to.value);\n\n if (from.ok && to.ok) {\n // tween color linearly\n const r = from.r + (to.r - from.r) * p.progress;\n const g = from.g + (to.g - from.g) * p.progress;\n const b = from.b + (to.b - from.b) * p.progress;\n return 'rgb(' + Number.parseInt(r) + ',' + Number.parseInt(g) + ',' + Number.parseInt(b) + ')';\n }\n return this.attribute('from').value;\n }\n };\n\n // animate transform element\n svg.Element.animateTransform = class extends svg.Element.animate {\n calcValue () {\n const p = this.progress();\n\n // tween value linearly\n const from = svg.ToNumberArray(p.from.value);\n const to = svg.ToNumberArray(p.to.value);\n let newValue = '';\n from.forEach((fr, i) => {\n newValue += fr + (to[i] - fr) * p.progress + ' ';\n });\n return newValue;\n }\n };\n\n // font element\n svg.Element.font = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.horizAdvX = this.attribute('horiz-adv-x').numValue();\n\n this.isRTL = false;\n this.isArabic = false;\n this.fontFace = null;\n this.missingGlyph = null;\n this.glyphs = [];\n this.children.forEach((child) => {\n if (child.type === 'font-face') {\n this.fontFace = child;\n if (child.style('font-family').hasValue()) {\n svg.Definitions[child.style('font-family').value] = this;\n }\n } else if (child.type === 'missing-glyph') {\n this.missingGlyph = child;\n } else if (child.type === 'glyph') {\n if (child.arabicForm !== '') {\n this.isRTL = true;\n this.isArabic = true;\n if (typeof this.glyphs[child.unicode] === 'undefined') {\n this.glyphs[child.unicode] = [];\n }\n this.glyphs[child.unicode][child.arabicForm] = child;\n } else {\n this.glyphs[child.unicode] = child;\n }\n }\n });\n }\n };\n\n // font-face element\n svg.Element.fontface = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.ascent = this.attribute('ascent').value;\n this.descent = this.attribute('descent').value;\n this.unitsPerEm = this.attribute('units-per-em').numValue();\n }\n };\n\n // missing-glyph element\n svg.Element.missingglyph = class extends svg.Element.path {\n constructor (node) {\n super(node);\n\n this.horizAdvX = 0;\n }\n };\n\n // glyph element\n svg.Element.glyph = class extends svg.Element.path {\n constructor (node) {\n super(node);\n\n this.horizAdvX = this.attribute('horiz-adv-x').numValue();\n this.unicode = this.attribute('unicode').value;\n this.arabicForm = this.attribute('arabic-form').value;\n }\n };\n\n // text element\n svg.Element.text = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node, true);\n }\n\n setContext (ctx) {\n super.setContext(ctx);\n\n let textBaseline = this.style('dominant-baseline').toTextBaseline();\n if (isNullish(textBaseline)) textBaseline = this.style('alignment-baseline').toTextBaseline();\n if (!isNullish(textBaseline)) ctx.textBaseline = textBaseline;\n }\n\n getBoundingBox () {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n return new svg.BoundingBox(x, y - fontSize, x + Math.floor(fontSize * 2.0 / 3.0) * this.children[0].getText().length, y);\n }\n\n renderChildren (ctx) {\n this.x = this.attribute('x').toPixels('x');\n this.y = this.attribute('y').toPixels('y');\n this.x += this.getAnchorDelta(ctx, this, 0);\n this.children.forEach((child, i) => {\n this.renderChild(ctx, this, i);\n });\n }\n\n getAnchorDelta (ctx, parent, startI) {\n const textAnchor = this.style('text-anchor').valueOrDefault('start');\n if (textAnchor !== 'start') {\n let width = 0;\n for (let i = startI; i < parent.children.length; i++) {\n const child = parent.children[i];\n if (i > startI && child.attribute('x').hasValue()) break; // new group\n width += child.measureTextRecursive(ctx);\n }\n return -1 * (textAnchor === 'end' ? width : width / 2.0);\n }\n return 0;\n }\n\n renderChild (ctx, parent, i) {\n const child = parent.children[i];\n if (child.attribute('x').hasValue()) {\n child.x = child.attribute('x').toPixels('x') + this.getAnchorDelta(ctx, parent, i);\n if (child.attribute('dx').hasValue()) child.x += child.attribute('dx').toPixels('x');\n } else {\n if (this.attribute('dx').hasValue()) this.x += this.attribute('dx').toPixels('x');\n if (child.attribute('dx').hasValue()) this.x += child.attribute('dx').toPixels('x');\n child.x = this.x;\n }\n this.x = child.x + child.measureText(ctx);\n\n if (child.attribute('y').hasValue()) {\n child.y = child.attribute('y').toPixels('y');\n if (child.attribute('dy').hasValue()) child.y += child.attribute('dy').toPixels('y');\n } else {\n if (this.attribute('dy').hasValue()) this.y += this.attribute('dy').toPixels('y');\n if (child.attribute('dy').hasValue()) this.y += child.attribute('dy').toPixels('y');\n child.y = this.y;\n }\n this.y = child.y;\n\n child.render(ctx);\n\n for (let j = 0; j < child.children.length; j++) {\n this.renderChild(ctx, child, j);\n }\n }\n };\n\n // text base\n svg.Element.TextElementBase = class extends svg.Element.RenderedElementBase {\n getGlyph (font, text, i) {\n const c = text[i];\n let glyph = null;\n if (font.isArabic) {\n let arabicForm = 'isolated';\n if ((i === 0 || text[i - 1] === ' ') && i < text.length - 2 && text[i + 1] !== ' ') arabicForm = 'terminal';\n if (i > 0 && text[i - 1] !== ' ' && i < text.length - 2 && text[i + 1] !== ' ') arabicForm = 'medial';\n if (i > 0 && text[i - 1] !== ' ' && (i === text.length - 1 || text[i + 1] === ' ')) arabicForm = 'initial';\n if (typeof font.glyphs[c] !== 'undefined') {\n glyph = font.glyphs[c][arabicForm];\n if (isNullish(glyph) && font.glyphs[c].type === 'glyph') glyph = font.glyphs[c];\n }\n } else {\n glyph = font.glyphs[c];\n }\n if (isNullish(glyph)) glyph = font.missingGlyph;\n return glyph;\n }\n\n renderChildren (ctx) {\n const customFont = this.parent.style('font-family').getDefinition();\n if (!isNullish(customFont)) {\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n const fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);\n let text = this.getText();\n if (customFont.isRTL) text = text.split('').reverse().join('');\n\n const dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n for (let i = 0; i < text.length; i++) {\n const glyph = this.getGlyph(customFont, text, i);\n const scale = fontSize / customFont.fontFace.unitsPerEm;\n ctx.translate(this.x, this.y);\n ctx.scale(scale, -scale);\n const lw = ctx.lineWidth;\n ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;\n if (fontStyle === 'italic') ctx.transform(1, 0, 0.4, 1, 0, 0);\n glyph.render(ctx);\n if (fontStyle === 'italic') ctx.transform(1, 0, -0.4, 1, 0, 0);\n ctx.lineWidth = lw;\n ctx.scale(1 / scale, -1 / scale);\n ctx.translate(-this.x, -this.y);\n\n this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;\n if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {\n this.x += dx[i];\n }\n }\n return;\n }\n\n if (ctx.fillStyle !== '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);\n if (ctx.strokeStyle !== '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);\n }\n\n getText () {\n // OVERRIDE ME\n }\n\n measureTextRecursive (ctx) {\n let width = this.measureText(ctx);\n this.children.forEach((child) => {\n width += child.measureTextRecursive(ctx);\n });\n return width;\n }\n\n measureText (ctx) {\n const customFont = this.parent.style('font-family').getDefinition();\n if (!isNullish(customFont)) {\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n let measure = 0;\n let text = this.getText();\n if (customFont.isRTL) text = text.split('').reverse().join('');\n const dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n for (let i = 0; i < text.length; i++) {\n const glyph = this.getGlyph(customFont, text, i);\n measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;\n if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {\n measure += dx[i];\n }\n }\n return measure;\n }\n\n const textToMeasure = svg.compressSpaces(this.getText());\n if (!ctx.measureText) return textToMeasure.length * 10;\n\n ctx.save();\n this.setContext(ctx);\n const {width} = ctx.measureText(textToMeasure);\n ctx.restore();\n return width;\n }\n };\n\n // tspan\n svg.Element.tspan = class extends svg.Element.TextElementBase {\n constructor (node) {\n super(node, true);\n\n this.text = node.nodeValue || node.text || '';\n }\n getText () {\n return this.text;\n }\n };\n\n // tref\n svg.Element.tref = class extends svg.Element.TextElementBase {\n getText () {\n const element = this.getHrefAttribute().getDefinition();\n if (!isNullish(element)) return element.children[0].getText();\n return undefined;\n }\n };\n\n // a element\n svg.Element.a = class extends svg.Element.TextElementBase {\n constructor (node) {\n super(node);\n\n this.hasText = true;\n [...node.childNodes].forEach((childNode) => {\n if (childNode.nodeType !== 3) {\n this.hasText = false;\n }\n });\n // this might contain text\n this.text = this.hasText ? node.childNodes[0].nodeValue : '';\n }\n\n getText () {\n return this.text;\n }\n\n renderChildren (ctx) {\n if (this.hasText) {\n // render as text element\n super.renderChildren(ctx);\n const fontSize = new svg.Property(\n 'fontSize', svg.Font.Parse(svg.ctx.font).fontSize\n );\n svg.Mouse.checkBoundingBox(\n this, new svg.BoundingBox(\n this.x,\n this.y - fontSize.toPixels('y'),\n this.x + this.measureText(ctx),\n this.y\n )\n );\n } else {\n // render as temporary group\n const g = new svg.Element.g();\n g.children = this.children;\n g.parent = this;\n g.render(ctx);\n }\n }\n\n onclick () {\n window.open(this.getHrefAttribute().value);\n }\n\n onmousemove () {\n svg.ctx.canvas.style.cursor = 'pointer';\n }\n };\n\n // image element\n svg.Element.image = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node);\n\n const href = this.getHrefAttribute().value;\n if (href === '') {\n return;\n }\n this._isSvg = href.match(/\\.svg$/);\n\n svg.Images.push(this);\n this.loaded = false;\n if (!this._isSvg) {\n this.img = document.createElement('img');\n if (svg.opts.useCORS === true) {\n this.img.crossOrigin = 'Anonymous';\n }\n this.img.addEventListener('load', () => {\n this.loaded = true;\n });\n this.img.addEventListener('error', () => {\n svg.log('ERROR: image \"' + href + '\" not found');\n this.loaded = true;\n });\n this.img.src = href;\n } else {\n svg.ajax(href, true).then((img) => { // eslint-disable-line promise/prefer-await-to-then, promise/always-return\n this.img = img;\n this.loaded = true;\n }).catch((err) => { // eslint-disable-line promise/prefer-await-to-callbacks\n this.erred = true;\n console.error('Ajax error for canvg', err); // eslint-disable-line no-console\n });\n }\n }\n renderChildren (ctx) {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n if (width === 0 || height === 0) return;\n\n ctx.save();\n if (this._isSvg) {\n ctx.drawSvg(this.img, x, y, width, height);\n } else {\n ctx.translate(x, y);\n svg.AspectRatio(\n ctx,\n this.attribute('preserveAspectRatio').value,\n width,\n this.img.width,\n height,\n this.img.height,\n 0,\n 0\n );\n ctx.drawImage(this.img, 0, 0);\n }\n ctx.restore();\n }\n\n getBoundingBox () {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n return new svg.BoundingBox(x, y, x + width, y + height);\n }\n };\n\n // group element\n svg.Element.g = class extends svg.Element.RenderedElementBase {\n getBoundingBox () {\n const bb = new svg.BoundingBox();\n this.children.forEach((child) => {\n bb.addBoundingBox(child.getBoundingBox());\n });\n return bb;\n }\n };\n\n // symbol element\n svg.Element.symbol = class extends svg.Element.RenderedElementBase {\n render (ctx) {\n // NO RENDER\n }\n };\n\n // style element\n svg.Element.style = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n // text, or spaces then CDATA\n let css = '';\n [...node.childNodes].forEach(({nodeValue}) => {\n css += nodeValue;\n });\n // remove comments\n css = css.replace(/(\\/\\*([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+\\/)|(^\\s*\\/\\/.*)/gm, ''); // eslint-disable-line unicorn/no-unsafe-regex\n // replace whitespace\n css = svg.compressSpaces(css);\n const cssDefs = css.split('}');\n cssDefs.forEach((cssDef) => {\n if (svg.trim(cssDef) !== '') {\n let [cssClasses, cssProps] = cssDef.split('{');\n cssClasses = cssClasses.split(',');\n cssProps = cssProps.split(';');\n cssClasses.forEach((cssClass) => {\n cssClass = svg.trim(cssClass);\n if (cssClass !== '') {\n const props = {};\n cssProps.forEach((cssProp) => {\n const prop = cssProp.indexOf(':');\n const name = cssProp.substr(0, prop);\n const value = cssProp.substr(prop + 1, cssProp.length - prop);\n if (!isNullish(name) && !isNullish(value)) {\n props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));\n }\n });\n svg.Styles[cssClass] = props;\n if (cssClass === '@font-face') {\n const fontFamily = props['font-family'].value.replace(/\"/g, '');\n const srcs = props.src.value.split(',');\n srcs.forEach((src) => {\n if (src.includes('format(\"svg\")')) {\n const urlStart = src.indexOf('url');\n const urlEnd = src.indexOf(')', urlStart);\n const url = src.substr(urlStart + 5, urlEnd - urlStart - 6);\n // Can this ajax safely be converted to async?\n const doc = svg.parseXml(svg.ajax(url));\n const fonts = doc.getElementsByTagName('font');\n [...fonts].forEach((font) => {\n font = svg.CreateElement(font);\n svg.Definitions[fontFamily] = font;\n });\n }\n });\n }\n }\n });\n }\n });\n }\n };\n\n // use element\n svg.Element.use = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node);\n\n this._el = this.getHrefAttribute().getDefinition();\n }\n\n setContext (ctx) {\n super.setContext(ctx);\n if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').toPixels('x'), 0);\n if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').toPixels('y'));\n }\n\n path (ctx) {\n const {_el: element} = this;\n if (!isNullish(element)) element.path(ctx);\n }\n\n getBoundingBox () {\n const {_el: element} = this;\n if (!isNullish(element)) return element.getBoundingBox();\n return undefined;\n }\n\n renderChildren (ctx) {\n const {_el: element} = this;\n if (!isNullish(element)) {\n let tempSvg = element;\n if (element.type === 'symbol') {\n // render me using a temporary svg element in symbol cases\n // (https://www.w3.org/TR/SVG/struct.html#UseElement)\n tempSvg = new svg.Element.svg();\n tempSvg.type = 'svg';\n tempSvg.attributes.viewBox = new svg.Property(\n 'viewBox', element.attribute('viewBox').value\n );\n tempSvg.attributes.preserveAspectRatio = new svg.Property(\n 'preserveAspectRatio', element.attribute('preserveAspectRatio').value\n );\n tempSvg.attributes.overflow = new svg.Property(\n 'overflow', element.attribute('overflow').value\n );\n tempSvg.children = element.children;\n }\n if (tempSvg.type === 'svg') {\n // if symbol or svg, inherit width/height from me\n if (this.attribute('width').hasValue()) {\n tempSvg.attributes.width = new svg.Property(\n 'width', this.attribute('width').value\n );\n }\n if (this.attribute('height').hasValue()) {\n tempSvg.attributes.height = new svg.Property(\n 'height', this.attribute('height').value\n );\n }\n }\n const oldParent = tempSvg.parent;\n tempSvg.parent = null;\n tempSvg.render(ctx);\n tempSvg.parent = oldParent;\n }\n }\n };\n\n // mask element\n svg.Element.mask = class extends svg.Element.ElementBase {\n apply (ctx, element) {\n // render as temp svg\n let x = this.attribute('x').toPixels('x');\n let y = this.attribute('y').toPixels('y');\n let width = this.attribute('width').toPixels('x');\n let height = this.attribute('height').toPixels('y');\n\n if (width === 0 && height === 0) {\n const bb = new svg.BoundingBox();\n this.children.forEach((child) => {\n bb.addBoundingBox(child.getBoundingBox());\n });\n x = Math.floor(bb.x1);\n y = Math.floor(bb.y1);\n width = Math.floor(bb.width());\n height = Math.floor(bb.height());\n }\n\n // temporarily remove mask to avoid recursion\n const mask = element.attribute('mask').value;\n element.attribute('mask').value = '';\n\n const cMask = document.createElement('canvas');\n cMask.width = x + width;\n cMask.height = y + height;\n const maskCtx = cMask.getContext('2d');\n this.renderChildren(maskCtx);\n\n const c = document.createElement('canvas');\n c.width = x + width;\n c.height = y + height;\n const tempCtx = c.getContext('2d');\n element.render(tempCtx);\n tempCtx.globalCompositeOperation = 'destination-in';\n tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');\n tempCtx.fillRect(0, 0, x + width, y + height);\n\n ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');\n ctx.fillRect(0, 0, x + width, y + height);\n\n // reassign mask\n element.attribute('mask').value = mask;\n }\n\n render (ctx) {\n // NO RENDER\n }\n };\n\n // clip element\n svg.Element.clipPath = class extends svg.Element.ElementBase {\n apply (ctx) {\n this.children.forEach((child) => {\n if (typeof child.path !== 'undefined') {\n let transform = null;\n if (child.attribute('transform').hasValue()) {\n transform = new svg.Transform(child.attribute('transform').value);\n transform.apply(ctx);\n }\n child.path(ctx);\n ctx.clip();\n if (transform) { transform.unapply(ctx); }\n }\n });\n }\n render (ctx) {\n // NO RENDER\n }\n };\n\n // filters\n svg.Element.filter = class extends svg.Element.ElementBase {\n apply (ctx, element) {\n // render as temp svg\n const bb = element.getBoundingBox();\n const x = Math.floor(bb.x1);\n const y = Math.floor(bb.y1);\n const width = Math.floor(bb.width());\n const height = Math.floor(bb.height());\n\n // temporarily remove filter to avoid recursion\n const filter = element.style('filter').value;\n element.style('filter').value = '';\n\n let px = 0, py = 0;\n this.children.forEach((child) => {\n const efd = child.extraFilterDistance || 0;\n px = Math.max(px, efd);\n py = Math.max(py, efd);\n });\n\n const c = document.createElement('canvas');\n c.width = width + 2 * px;\n c.height = height + 2 * py;\n const tempCtx = c.getContext('2d');\n tempCtx.translate(-x + px, -y + py);\n element.render(tempCtx);\n\n // apply filters\n this.children.forEach((child) => {\n child.apply(tempCtx, 0, 0, width + 2 * px, height + 2 * py);\n });\n\n // render on me\n ctx.drawImage(c, 0, 0, width + 2 * px, height + 2 * py, x - px, y - py, width + 2 * px, height + 2 * py);\n\n // reassign filter\n element.style('filter', true).value = filter;\n }\n\n render (ctx) {\n // NO RENDER\n }\n };\n\n svg.Element.feMorphology = class extends svg.Element.ElementBase {\n apply (ctx, x, y, width, height) {\n // TODO: implement\n }\n };\n\n svg.Element.feComposite = class extends svg.Element.ElementBase {\n apply (ctx, x, y, width, height) {\n // TODO: implement\n }\n };\n\n /**\n * @param {Uint8ClampedArray} img\n * @param {Integer} x\n * @param {Integer} y\n * @param {Float} width\n * @param {Float} height\n * @param {Integer} rgba\n * @returns {Integer}\n */\n function imGet (img, x, y, width, height, rgba) {\n return img[y * width * 4 + x * 4 + rgba];\n }\n\n /**\n * @param {Uint8ClampedArray} img\n * @param {Integer} x\n * @param {Integer} y\n * @param {Float} width\n * @param {Float} height\n * @param {Integer} rgba\n * @param {Float} val\n * @returns {void}\n */\n function imSet (img, x, y, width, height, rgba, val) {\n img[y * width * 4 + x * 4 + rgba] = val;\n }\n\n svg.Element.feColorMatrix = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n let matrix = svg.ToNumberArray(this.attribute('values').value);\n switch (this.attribute('type').valueOrDefault('matrix')) { // https://www.w3.org/TR/SVG/filters.html#feColorMatrixElement\n case 'saturate': {\n const s = matrix[0];\n matrix = [\n 0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s, 0, 0,\n 0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s, 0, 0,\n 0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s, 0, 0,\n 0, 0, 0, 1, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n } case 'hueRotate': {\n const a = matrix[0] * Math.PI / 180.0;\n const c = function (m1, m2, m3) {\n return m1 + Math.cos(a) * m2 + Math.sin(a) * m3;\n };\n matrix = [\n c(0.213, 0.787, -0.213), c(0.715, -0.715, -0.715), c(0.072, -0.072, 0.928), 0, 0,\n c(0.213, -0.213, 0.143), c(0.715, 0.285, 0.140), c(0.072, -0.072, -0.283), 0, 0,\n c(0.213, -0.213, -0.787), c(0.715, -0.715, 0.715), c(0.072, 0.928, 0.072), 0, 0,\n 0, 0, 0, 1, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n } case 'luminanceToAlpha':\n matrix = [\n 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0,\n 0.2125, 0.7154, 0.0721, 0, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n }\n this.matrix = matrix;\n\n this._m = (i, v) => {\n const mi = matrix[i];\n return mi * (mi < 0 ? v - 255 : v);\n };\n }\n apply (ctx, x, y, width, height) {\n const {_m: m} = this;\n // assuming x==0 && y==0 for now\n const srcData = ctx.getImageData(0, 0, width, height);\n for (let _y = 0; _y < height; _y++) {\n for (let _x = 0; _x < width; _x++) {\n const r = imGet(srcData.data, _x, _y, width, height, 0);\n const g = imGet(srcData.data, _x, _y, width, height, 1);\n const b = imGet(srcData.data, _x, _y, width, height, 2);\n const a = imGet(srcData.data, _x, _y, width, height, 3);\n imSet(srcData.data, _x, _y, width, height, 0, m(0, r) + m(1, g) + m(2, b) + m(3, a) + m(4, 1));\n imSet(srcData.data, _x, _y, width, height, 1, m(5, r) + m(6, g) + m(7, b) + m(8, a) + m(9, 1));\n imSet(srcData.data, _x, _y, width, height, 2, m(10, r) + m(11, g) + m(12, b) + m(13, a) + m(14, 1));\n imSet(srcData.data, _x, _y, width, height, 3, m(15, r) + m(16, g) + m(17, b) + m(18, a) + m(19, 1));\n }\n }\n ctx.clearRect(0, 0, width, height);\n ctx.putImageData(srcData, 0, 0);\n }\n };\n\n svg.Element.feGaussianBlur = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.blurRadius = Math.floor(this.attribute('stdDeviation').numValue());\n this.extraFilterDistance = this.blurRadius;\n }\n\n apply (ctx, x, y, width, height) {\n // Todo: This might not be a problem anymore with out `instanceof` fix\n // StackBlur requires canvas be on document\n ctx.canvas.id = svg.UniqueId();\n ctx.canvas.style.display = 'none';\n document.body.append(ctx.canvas);\n canvasRGBA(ctx.canvas, x, y, width, height, this.blurRadius);\n ctx.canvas.remove();\n }\n };\n\n // title element, do nothing\n svg.Element.title = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n }\n };\n\n // desc element, do nothing\n svg.Element.desc = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n }\n };\n\n svg.Element.MISSING = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n svg.log('ERROR: Element \\'' + node.nodeName + '\\' not yet implemented.');\n }\n };\n\n // element factory\n svg.CreateElement = function (node) {\n const className = node.nodeName\n .replace(/^[^:]+:/, '') // remove namespace\n .replace(/-/g, ''); // remove dashes\n let e;\n if (typeof svg.Element[className] !== 'undefined') {\n e = new svg.Element[className](node);\n } else {\n e = new svg.Element.MISSING(node);\n }\n\n e.type = node.nodeName;\n return e;\n };\n\n // load from url\n svg.load = async function (ctx, url) {\n const dom = await svg.ajax(url, true);\n return svg.loadXml(ctx, dom);\n };\n\n // load from xml\n svg.loadXml = function (ctx, xml) {\n return svg.loadXmlDoc(ctx, svg.parseXml(xml));\n };\n\n svg.loadXmlDoc = function (ctx, dom) {\n let res;\n svg.init(ctx);\n\n const mapXY = function (p) {\n let e = ctx.canvas;\n while (e) {\n p.x -= e.offsetLeft;\n p.y -= e.offsetTop;\n e = e.offsetParent;\n }\n if (window.scrollX) p.x += window.scrollX;\n if (window.scrollY) p.y += window.scrollY;\n return p;\n };\n\n // bind mouse\n if (svg.opts.ignoreMouse !== true) {\n ctx.canvas.addEventListener('click', function (e) {\n const args = !isNullish(e)\n ? [e.clientX, e.clientY]\n : [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals\n const {x, y} = mapXY(new svg.Point(...args));\n svg.Mouse.onclick(x, y);\n });\n ctx.canvas.addEventListener('mousemove', function (e) {\n const args = !isNullish(e)\n ? [e.clientX, e.clientY]\n : [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals\n const {x, y} = mapXY(new svg.Point(...args));\n svg.Mouse.onmousemove(x, y);\n });\n }\n\n const e = svg.CreateElement(dom.documentElement);\n e.root = true;\n\n // render loop\n let isFirstRender = true;\n const draw = function (resolve) {\n svg.ViewPort.Clear();\n if (ctx.canvas.parentNode) {\n svg.ViewPort.SetCurrent(\n ctx.canvas.parentNode.clientWidth,\n ctx.canvas.parentNode.clientHeight\n );\n }\n\n if (svg.opts.ignoreDimensions !== true) {\n // set canvas size\n if (e.style('width').hasValue()) {\n ctx.canvas.width = e.style('width').toPixels('x');\n ctx.canvas.style.width = ctx.canvas.width + 'px';\n }\n if (e.style('height').hasValue()) {\n ctx.canvas.height = e.style('height').toPixels('y');\n ctx.canvas.style.height = ctx.canvas.height + 'px';\n }\n }\n let cWidth = ctx.canvas.clientWidth || ctx.canvas.width;\n let cHeight = ctx.canvas.clientHeight || ctx.canvas.height;\n if (svg.opts.ignoreDimensions === true &&\n e.style('width').hasValue() && e.style('height').hasValue()\n ) {\n cWidth = e.style('width').toPixels('x');\n cHeight = e.style('height').toPixels('y');\n }\n svg.ViewPort.SetCurrent(cWidth, cHeight);\n\n if (!isNullish(svg.opts.offsetX)) {\n e.attribute('x', true).value = svg.opts.offsetX;\n }\n if (!isNullish(svg.opts.offsetY)) {\n e.attribute('y', true).value = svg.opts.offsetY;\n }\n if (!isNullish(svg.opts.scaleWidth) || !isNullish(svg.opts.scaleHeight)) {\n const viewBox = svg.ToNumberArray(e.attribute('viewBox').value);\n let xRatio = null, yRatio = null;\n\n if (!isNullish(svg.opts.scaleWidth)) {\n if (e.attribute('width').hasValue()) {\n xRatio = e.attribute('width').toPixels('x') / svg.opts.scaleWidth;\n } else if (!isNaN(viewBox[2])) {\n xRatio = viewBox[2] / svg.opts.scaleWidth;\n }\n }\n\n if (!isNullish(svg.opts.scaleHeight)) {\n if (e.attribute('height').hasValue()) {\n yRatio = e.attribute('height').toPixels('y') / svg.opts.scaleHeight;\n } else if (!isNaN(viewBox[3])) {\n yRatio = viewBox[3] / svg.opts.scaleHeight;\n }\n }\n\n if (isNullish(xRatio)) { xRatio = yRatio; }\n if (isNullish(yRatio)) { yRatio = xRatio; }\n\n e.attribute('width', true).value = svg.opts.scaleWidth;\n e.attribute('height', true).value = svg.opts.scaleHeight;\n e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);\n e.attribute('preserveAspectRatio', true).value = 'none';\n }\n\n // clear and render\n if (svg.opts.ignoreClear !== true) {\n ctx.clearRect(0, 0, cWidth, cHeight);\n }\n e.render(ctx);\n if (isFirstRender) {\n isFirstRender = false;\n resolve(dom);\n }\n };\n\n let waitingForImages = true;\n svg.intervalID = setInterval(function () {\n let needUpdate = false;\n\n if (waitingForImages && svg.ImagesLoaded()) {\n waitingForImages = false;\n needUpdate = true;\n }\n\n // need update from mouse events?\n if (svg.opts.ignoreMouse !== true) {\n needUpdate = needUpdate || svg.Mouse.hasEvents();\n }\n\n // need update from animations?\n if (svg.opts.ignoreAnimation !== true) {\n svg.Animations.forEach((animation) => {\n const needAnimationUpdate = animation.update(1000 / svg.FRAMERATE);\n needUpdate = needUpdate || needAnimationUpdate;\n });\n }\n\n // need update from redraw?\n if (typeof svg.opts.forceRedraw === 'function') {\n if (svg.opts.forceRedraw() === true) {\n needUpdate = true;\n }\n }\n\n // render if needed\n if (needUpdate) {\n draw(res);\n svg.Mouse.runEvents(); // run and clear our events\n }\n }, 1000 / svg.FRAMERATE);\n // Todo: Replace with an image loading Promise utility?\n // eslint-disable-next-line promise/avoid-new\n return new Promise((resolve, reject) => {\n if (svg.ImagesLoaded()) {\n waitingForImages = false;\n draw(resolve);\n return;\n }\n res = resolve;\n });\n };\n\n svg.stop = () => {\n if (svg.intervalID) {\n clearInterval(svg.intervalID);\n }\n };\n\n svg.Mouse = {\n events: [],\n hasEvents () { return this.events.length !== 0; },\n\n onclick (x, y) {\n this.events.push({\n type: 'onclick', x, y,\n run (e) { if (e.onclick) e.onclick(); }\n });\n },\n\n onmousemove (x, y) {\n this.events.push({\n type: 'onmousemove', x, y,\n run (e) { if (e.onmousemove) e.onmousemove(); }\n });\n },\n\n eventElements: [],\n\n checkPath (element, ctx) {\n this.events.forEach(({x, y}, i) => {\n if (ctx.isPointInPath && ctx.isPointInPath(x, y)) {\n this.eventElements[i] = element;\n }\n });\n },\n\n checkBoundingBox (element, bb) {\n this.events.forEach(({x, y}, i) => {\n if (bb.isPointInBox(x, y)) {\n this.eventElements[i] = element;\n }\n });\n },\n\n runEvents () {\n svg.ctx.canvas.style.cursor = '';\n\n this.events.forEach((e, i) => {\n let element = this.eventElements[i];\n while (element) {\n e.run(element);\n element = element.parent;\n }\n });\n\n // done running, clear\n this.events = [];\n this.eventElements = [];\n }\n };\n\n return svg;\n}\n\nif (typeof CanvasRenderingContext2D !== 'undefined') {\n CanvasRenderingContext2D.prototype.drawSvg = function (s, dx, dy, dw, dh) {\n canvg(this.canvas, s, {\n ignoreMouse: true,\n ignoreAnimation: true,\n ignoreDimensions: true,\n ignoreClear: true,\n offsetX: dx,\n offsetY: dy,\n scaleWidth: dw,\n scaleHeight: dh\n });\n };\n}\n","/**\n * @file ext-server_moinsave.js\n *\n * @license (MIT OR GPL-2.0-or-later)\n *\n * @copyright 2010 Alexis Deveria, 2011 MoinMoin:ReimarBauer\n * adopted for moinmoins item storage. It sends in one post png and svg data\n * (I agree to dual license my work to additional GPLv2 or later)\n */\nimport {canvg} from '../../external/canvg/canvg.js';\n\nexport default {\n name: 'server_moinsave',\n async init ({$, encode64, importLocale}) {\n const strings = await importLocale();\n const svgEditor = this;\n const svgCanvas = svgEditor.canvas;\n const saveSvgAction = '/+modify';\n\n // Create upload target (hidden iframe)\n // Hiding by size instead of display to avoid FF console errors\n // with `getBBox` in browser.js `supportsPathBBox_`)\n /* const target = */ $(\n `\\d{1,3})\\)$/,\n example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b));\n }\n },\n {\n re: /^(\\w{2})(\\w{2})(\\w{2})$/,\n // re: /^(?\\w{2})(?\\w{2})(?\\w{2})$/,\n example: ['#00ff00', '336699'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b, 16));\n }\n },\n {\n re: /^(\\w)(\\w)(\\w)$/,\n // re: /^(?\\w{1})(?\\w{1})(?\\w{1})$/,\n example: ['#fb0', 'f0f'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b + b, 16));\n }\n }\n];\n\n/**\n * A class to parse color values.\n */\nexport default class RGBColor {\n /**\n * @param {string} colorString\n */\n constructor (colorString) {\n this.ok = false;\n\n // strip any leading #\n if (colorString.charAt(0) === '#') { // remove # if any\n colorString = colorString.substr(1, 6);\n }\n\n colorString = colorString.replace(/ /g, '');\n colorString = colorString.toLowerCase();\n\n // before getting into regexps, try simple matches\n // and overwrite the input\n if (colorString in simpleColors) {\n colorString = simpleColors[colorString];\n }\n // end of simple type-in colors\n\n // search through the definitions to find a match\n\n colorDefs.forEach(({re, process: processor}) => {\n const bits = re.exec(colorString);\n if (bits) {\n const [r, g, b] = processor(...bits);\n Object.assign(this, {r, g, b});\n this.ok = true;\n }\n });\n\n // validate/cleanup values\n this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);\n this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);\n this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);\n }\n\n // some getters\n /**\n * @returns {string}\n */\n toRGB () {\n return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';\n }\n\n /**\n * @returns {string}\n */\n toHex () {\n let r = this.r.toString(16);\n let g = this.g.toString(16);\n let b = this.b.toString(16);\n if (r.length === 1) { r = '0' + r; }\n if (g.length === 1) { g = '0' + g; }\n if (b.length === 1) { b = '0' + b; }\n return '#' + r + g + b;\n }\n\n /**\n * Offers a bulleted list of help.\n * @returns {HTMLUListElement}\n */\n static getHelpXML () {\n const examples = [\n // add regexps\n ...colorDefs.flatMap(({example}) => {\n return example;\n }),\n // add type-in colors\n ...Object.keys(simpleColors)\n ];\n\n const xml = document.createElement('ul');\n xml.setAttribute('id', 'rgbcolor-examples');\n\n xml.append(...examples.map((example) => {\n try {\n const listItem = document.createElement('li');\n const listColor = new RGBColor(example);\n const exampleDiv = document.createElement('div');\n exampleDiv.style.cssText = `\n margin: 3px;\n border: 1px solid black;\n background: ${listColor.toHex()};\n color: ${listColor.toHex()};`;\n exampleDiv.append('test');\n const listItemValue = ` ${example} -> ${listColor.toRGB()} -> ${listColor.toHex()}`;\n listItem.append(exampleDiv, listItemValue);\n return listItem;\n } catch (e) {\n return '';\n }\n }));\n return xml;\n }\n}\n","function _typeof(obj) {\n \"@babel/helpers - typeof\";\n\n if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") {\n _typeof = function (obj) {\n return typeof obj;\n };\n } else {\n _typeof = function (obj) {\n return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj;\n };\n }\n\n return _typeof(obj);\n}\n\nfunction _classCallCheck(instance, Constructor) {\n if (!(instance instanceof Constructor)) {\n throw new TypeError(\"Cannot call a class as a function\");\n }\n}\n\n/* eslint-disable no-bitwise, unicorn/prefer-query-selector */\n\n/**\n* StackBlur - a fast almost Gaussian Blur For Canvas\n*\n* In case you find this class useful - especially in commercial projects -\n* I am not totally unhappy for a small donation to my PayPal account\n* mario@quasimondo.de\n*\n* Or support me on flattr:\n* {@link https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript}.\n*\n* @module StackBlur\n* @author Mario Klingemann\n* Contact: mario@quasimondo.com\n* Website: {@link http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html}\n* Twitter: @quasimondo\n*\n* @copyright (c) 2010 Mario Klingemann\n*\n* Permission is hereby granted, free of charge, to any person\n* obtaining a copy of this software and associated documentation\n* files (the \"Software\"), to deal in the Software without\n* restriction, including without limitation the rights to use,\n* copy, modify, merge, publish, distribute, sublicense, and/or sell\n* copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following\n* conditions:\n*\n* The above copyright notice and this permission notice shall be\n* included in all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n* OTHER DEALINGS IN THE SOFTWARE.\n*/\n\n/* eslint-disable max-len */\nvar mulTable = [512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259];\nvar shgTable = [9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24];\n/* eslint-enable max-len */\n\n/**\n * @param {string|HTMLImageElement} img\n * @param {string|HTMLCanvasElement} canvas\n * @param {Float} radius\n * @param {boolean} blurAlphaChannel\n * @returns {undefined}\n */\n\nfunction processImage(img, canvas, radius, blurAlphaChannel) {\n if (typeof img === 'string') {\n img = document.getElementById(img);\n }\n\n if (!img || !('naturalWidth' in img)) {\n return;\n }\n\n var w = img.naturalWidth;\n var h = img.naturalHeight;\n\n if (typeof canvas === 'string') {\n canvas = document.getElementById(canvas);\n }\n\n if (!canvas || !('getContext' in canvas)) {\n return;\n }\n\n canvas.style.width = w + 'px';\n canvas.style.height = h + 'px';\n canvas.width = w;\n canvas.height = h;\n var context = canvas.getContext('2d');\n context.clearRect(0, 0, w, h);\n context.drawImage(img, 0, 0);\n\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n if (blurAlphaChannel) {\n processCanvasRGBA(canvas, 0, 0, w, h, radius);\n } else {\n processCanvasRGB(canvas, 0, 0, w, h, radius);\n }\n}\n/**\n * @param {string|HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @throws {Error|TypeError}\n * @returns {ImageData} See {@link https://html.spec.whatwg.org/multipage/canvas.html#imagedata}\n */\n\n\nfunction getImageDataFromCanvas(canvas, topX, topY, width, height) {\n if (typeof canvas === 'string') {\n canvas = document.getElementById(canvas);\n }\n\n if (!canvas || _typeof(canvas) !== 'object' || !('getContext' in canvas)) {\n throw new TypeError('Expecting canvas with `getContext` method ' + 'in processCanvasRGB(A) calls!');\n }\n\n var context = canvas.getContext('2d');\n\n try {\n return context.getImageData(topX, topY, width, height);\n } catch (e) {\n throw new Error('unable to access image data: ' + e);\n }\n}\n/**\n * @param {HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {undefined}\n */\n\n\nfunction processCanvasRGBA(canvas, topX, topY, width, height, radius) {\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n radius |= 0;\n var imageData = getImageDataFromCanvas(canvas, topX, topY, width, height);\n imageData = processImageDataRGBA(imageData, topX, topY, width, height, radius);\n canvas.getContext('2d').putImageData(imageData, topX, topY);\n}\n/**\n * @param {ImageData} imageData\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {ImageData}\n */\n\n\nfunction processImageDataRGBA(imageData, topX, topY, width, height, radius) {\n var pixels = imageData.data;\n var x, y, i, p, yp, yi, yw, rSum, gSum, bSum, aSum, rOutSum, gOutSum, bOutSum, aOutSum, rInSum, gInSum, bInSum, aInSum, pr, pg, pb, pa, rbs;\n var div = 2 * radius + 1; // const w4 = width << 2;\n\n var widthMinus1 = width - 1;\n var heightMinus1 = height - 1;\n var radiusPlus1 = radius + 1;\n var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;\n var stackStart = new BlurStack();\n var stack = stackStart;\n var stackEnd;\n\n for (i = 1; i < div; i++) {\n stack = stack.next = new BlurStack();\n\n if (i === radiusPlus1) {\n stackEnd = stack;\n }\n }\n\n stack.next = stackStart;\n var stackIn = null;\n var stackOut = null;\n yw = yi = 0;\n var mulSum = mulTable[radius];\n var shgSum = shgTable[radius];\n\n for (y = 0; y < height; y++) {\n rInSum = gInSum = bInSum = aInSum = rSum = gSum = bSum = aSum = 0;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n aOutSum = radiusPlus1 * (pa = pixels[yi + 3]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n aSum += sumFactor * pa;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack.a = pa;\n stack = stack.next;\n }\n\n for (i = 1; i < radiusPlus1; i++) {\n p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);\n rSum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[p + 1]) * rbs;\n bSum += (stack.b = pb = pixels[p + 2]) * rbs;\n aSum += (stack.a = pa = pixels[p + 3]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n aInSum += pa;\n stack = stack.next;\n }\n\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (x = 0; x < width; x++) {\n pixels[yi + 3] = pa = aSum * mulSum >> shgSum;\n\n if (pa !== 0) {\n pa = 255 / pa;\n pixels[yi] = (rSum * mulSum >> shgSum) * pa;\n pixels[yi + 1] = (gSum * mulSum >> shgSum) * pa;\n pixels[yi + 2] = (bSum * mulSum >> shgSum) * pa;\n } else {\n pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0;\n }\n\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n aSum -= aOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n aOutSum -= stackIn.a;\n p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2;\n rInSum += stackIn.r = pixels[p];\n gInSum += stackIn.g = pixels[p + 1];\n bInSum += stackIn.b = pixels[p + 2];\n aInSum += stackIn.a = pixels[p + 3];\n rSum += rInSum;\n gSum += gInSum;\n bSum += bInSum;\n aSum += aInSum;\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n aOutSum += pa = stackOut.a;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n aInSum -= pa;\n stackOut = stackOut.next;\n yi += 4;\n }\n\n yw += width;\n }\n\n for (x = 0; x < width; x++) {\n gInSum = bInSum = aInSum = rInSum = gSum = bSum = aSum = rSum = 0;\n yi = x << 2;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n aOutSum = radiusPlus1 * (pa = pixels[yi + 3]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n aSum += sumFactor * pa;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack.a = pa;\n stack = stack.next;\n }\n\n yp = width;\n\n for (i = 1; i <= radius; i++) {\n yi = yp + x << 2;\n rSum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[yi + 1]) * rbs;\n bSum += (stack.b = pb = pixels[yi + 2]) * rbs;\n aSum += (stack.a = pa = pixels[yi + 3]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n aInSum += pa;\n stack = stack.next;\n\n if (i < heightMinus1) {\n yp += width;\n }\n }\n\n yi = x;\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (y = 0; y < height; y++) {\n p = yi << 2;\n pixels[p + 3] = pa = aSum * mulSum >> shgSum;\n\n if (pa > 0) {\n pa = 255 / pa;\n pixels[p] = (rSum * mulSum >> shgSum) * pa;\n pixels[p + 1] = (gSum * mulSum >> shgSum) * pa;\n pixels[p + 2] = (bSum * mulSum >> shgSum) * pa;\n } else {\n pixels[p] = pixels[p + 1] = pixels[p + 2] = 0;\n }\n\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n aSum -= aOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n aOutSum -= stackIn.a;\n p = x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << 2;\n rSum += rInSum += stackIn.r = pixels[p];\n gSum += gInSum += stackIn.g = pixels[p + 1];\n bSum += bInSum += stackIn.b = pixels[p + 2];\n aSum += aInSum += stackIn.a = pixels[p + 3];\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n aOutSum += pa = stackOut.a;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n aInSum -= pa;\n stackOut = stackOut.next;\n yi += width;\n }\n }\n\n return imageData;\n}\n/**\n * @param {HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {undefined}\n */\n\n\nfunction processCanvasRGB(canvas, topX, topY, width, height, radius) {\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n radius |= 0;\n var imageData = getImageDataFromCanvas(canvas, topX, topY, width, height);\n imageData = processImageDataRGB(imageData, topX, topY, width, height, radius);\n canvas.getContext('2d').putImageData(imageData, topX, topY);\n}\n/**\n * @param {ImageData} imageData\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {ImageData}\n */\n\n\nfunction processImageDataRGB(imageData, topX, topY, width, height, radius) {\n var pixels = imageData.data;\n var x, y, i, p, yp, yi, yw, rSum, gSum, bSum, rOutSum, gOutSum, bOutSum, rInSum, gInSum, bInSum, pr, pg, pb, rbs;\n var div = 2 * radius + 1; // const w4 = width << 2;\n\n var widthMinus1 = width - 1;\n var heightMinus1 = height - 1;\n var radiusPlus1 = radius + 1;\n var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;\n var stackStart = new BlurStack();\n var stack = stackStart;\n var stackEnd;\n\n for (i = 1; i < div; i++) {\n stack = stack.next = new BlurStack();\n\n if (i === radiusPlus1) {\n stackEnd = stack;\n }\n }\n\n stack.next = stackStart;\n var stackIn = null;\n var stackOut = null;\n yw = yi = 0;\n var mulSum = mulTable[radius];\n var shgSum = shgTable[radius];\n\n for (y = 0; y < height; y++) {\n rInSum = gInSum = bInSum = rSum = gSum = bSum = 0;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack = stack.next;\n }\n\n for (i = 1; i < radiusPlus1; i++) {\n p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);\n rSum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[p + 1]) * rbs;\n bSum += (stack.b = pb = pixels[p + 2]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n stack = stack.next;\n }\n\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (x = 0; x < width; x++) {\n pixels[yi] = rSum * mulSum >> shgSum;\n pixels[yi + 1] = gSum * mulSum >> shgSum;\n pixels[yi + 2] = bSum * mulSum >> shgSum;\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2;\n rInSum += stackIn.r = pixels[p];\n gInSum += stackIn.g = pixels[p + 1];\n bInSum += stackIn.b = pixels[p + 2];\n rSum += rInSum;\n gSum += gInSum;\n bSum += bInSum;\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n stackOut = stackOut.next;\n yi += 4;\n }\n\n yw += width;\n }\n\n for (x = 0; x < width; x++) {\n gInSum = bInSum = rInSum = gSum = bSum = rSum = 0;\n yi = x << 2;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack = stack.next;\n }\n\n yp = width;\n\n for (i = 1; i <= radius; i++) {\n yi = yp + x << 2;\n rSum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[yi + 1]) * rbs;\n bSum += (stack.b = pb = pixels[yi + 2]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n stack = stack.next;\n\n if (i < heightMinus1) {\n yp += width;\n }\n }\n\n yi = x;\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (y = 0; y < height; y++) {\n p = yi << 2;\n pixels[p] = rSum * mulSum >> shgSum;\n pixels[p + 1] = gSum * mulSum >> shgSum;\n pixels[p + 2] = bSum * mulSum >> shgSum;\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n p = x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << 2;\n rSum += rInSum += stackIn.r = pixels[p];\n gSum += gInSum += stackIn.g = pixels[p + 1];\n bSum += bInSum += stackIn.b = pixels[p + 2];\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n stackOut = stackOut.next;\n yi += width;\n }\n }\n\n return imageData;\n}\n/**\n *\n */\n\n\nvar BlurStack =\n/**\n * Set properties.\n */\nfunction BlurStack() {\n _classCallCheck(this, BlurStack);\n\n this.r = 0;\n this.g = 0;\n this.b = 0;\n this.a = 0;\n this.next = null;\n};\n\nexport { BlurStack, processCanvasRGB as canvasRGB, processCanvasRGBA as canvasRGBA, processImage as image, processImageDataRGB as imageDataRGB, processImageDataRGBA as imageDataRGBA };\n","/* eslint-disable new-cap, class-methods-use-this, jsdoc/require-jsdoc */\n// Todo: Compare with latest canvg (add any improvements of ours) and add full JSDocs (denoting links to standard APIs and which are custom): https://github.com/canvg/canvg\n/**\n * Javascript SVG parser and renderer on Canvas.\n * @file canvg.js\n * @module canvg\n * @license MIT\n * @author Gabe Lerner \n * @see https://github.com/canvg/canvg\n */\n\nimport RGBColor from './rgbcolor.js';\nimport {canvasRGBA} from '../../external/stackblur-canvas/dist/stackblur-es.js';\n\n/**\n * Whether a value is `null` or `undefined`.\n * @param {any} val\n * @returns {boolean}\n */\nconst isNullish = (val) => {\n return val === null || val === undefined;\n};\n\n/**\n* @callback module:canvg.ForceRedraw\n* @returns {boolean}\n*/\n\n/**\n* @typedef {PlainObject} module:canvg.CanvgOptions\n* @property {boolean} ignoreMouse true => ignore mouse events\n* @property {boolean} ignoreAnimation true => ignore animations\n* @property {boolean} ignoreDimensions true => does not try to resize canvas\n* @property {boolean} ignoreClear true => does not clear canvas\n* @property {Integer} offsetX int => draws at a x offset\n* @property {Integer} offsetY int => draws at a y offset\n* @property {Integer} scaleWidth int => scales horizontally to width\n* @property {Integer} scaleHeight int => scales vertically to height\n* @property {module:canvg.ForceRedraw} forceRedraw function => will call the function on every frame, if it returns true, will redraw\n* @property {boolean} log Adds log function\n* @property {boolean} useCORS Whether to set CORS `crossOrigin` for the image to `Anonymous`\n*/\n\n/**\n* If called with no arguments, it will replace all `` elements on the page\n* with `` elements.\n* @function module:canvg.canvg\n* @param {HTMLCanvasElement|string} target canvas element or the id of a canvas element\n* @param {string|XMLDocument} s - svg string, url to svg file, or xml document\n* @param {module:canvg.CanvgOptions} [opts] Optional hash of options\n* @returns {Promise} All the function after the first render is completed with dom\n*/\nexport const canvg = function (target, s, opts) {\n // no parameters\n if (isNullish(target) && isNullish(s) && isNullish(opts)) {\n const svgTags = document.querySelectorAll('svg');\n return Promise.all([...svgTags].map((svgTag) => {\n const c = document.createElement('canvas');\n c.width = svgTag.clientWidth;\n c.height = svgTag.clientHeight;\n svgTag.before(c);\n svgTag.remove();\n const div = document.createElement('div');\n div.append(svgTag);\n return canvg(c, div.innerHTML);\n }));\n }\n\n if (typeof target === 'string') {\n target = document.getElementById(target);\n }\n\n // store class on canvas\n if (!isNullish(target.svg)) target.svg.stop();\n const svg = build(opts || {});\n // on i.e. 8 for flash canvas, we can't assign the property so check for it\n if (!(target.childNodes.length === 1 && target.childNodes[0].nodeName === 'OBJECT')) {\n target.svg = svg;\n }\n\n const ctx = target.getContext('2d');\n if (typeof s.documentElement !== 'undefined') {\n // load from xml doc\n return svg.loadXmlDoc(ctx, s);\n }\n if (s.substr(0, 1) === '<') {\n // load from xml string\n return svg.loadXml(ctx, s);\n }\n // load from url\n return svg.load(ctx, s);\n};\n\n/* eslint-disable jsdoc/check-types */\n/**\n* @param {module:canvg.CanvgOptions} opts\n* @returns {object}\n* @todo Flesh out exactly what object is returned here (after updating to latest and reincluding our changes here and those of StackBlur)\n*/\nfunction build (opts) {\n /* eslint-enable jsdoc/check-types */\n const svg = {opts};\n\n svg.FRAMERATE = 30;\n svg.MAX_VIRTUAL_PIXELS = 30000;\n\n svg.log = function (msg) { /* */ };\n if (svg.opts.log === true && typeof console !== 'undefined') {\n svg.log = function (msg) { console.log(msg); }; // eslint-disable-line no-console\n }\n\n // globals\n svg.init = function (ctx) {\n let uniqueId = 0;\n svg.UniqueId = function () {\n uniqueId++;\n return 'canvg' + uniqueId;\n };\n svg.Definitions = {};\n svg.Styles = {};\n svg.Animations = [];\n svg.Images = [];\n svg.ctx = ctx;\n svg.ViewPort = {\n viewPorts: [],\n Clear () { this.viewPorts = []; },\n SetCurrent (width, height) { this.viewPorts.push({width, height}); },\n RemoveCurrent () { this.viewPorts.pop(); },\n Current () { return this.viewPorts[this.viewPorts.length - 1]; },\n width () { return this.Current().width; },\n height () { return this.Current().height; },\n ComputeSize (d) {\n if (!isNullish(d) && typeof d === 'number') return d;\n if (d === 'x') return this.width();\n if (d === 'y') return this.height();\n return Math.sqrt(\n (this.width() ** 2) + (this.height() ** 2)\n ) / Math.sqrt(2);\n }\n };\n };\n svg.init();\n\n // images loaded\n svg.ImagesLoaded = function () {\n return svg.Images.every((img) => img.loaded);\n };\n\n // trim\n svg.trim = function (s) {\n return s.replace(/^\\s+|\\s+$/g, '');\n };\n\n // compress spaces\n svg.compressSpaces = function (s) {\n return s.replace(/\\s+/gm, ' ');\n };\n\n // ajax\n // Todo: Replace with `fetch` and polyfill\n svg.ajax = function (url, asynch) {\n const AJAX = window.XMLHttpRequest\n ? new XMLHttpRequest()\n : new window.ActiveXObject('Microsoft.XMLHTTP');\n if (asynch) {\n return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new\n const req = AJAX.open('GET', url, true);\n req.addEventListener('load', () => {\n resolve(AJAX.responseText);\n });\n AJAX.send(null);\n });\n }\n\n AJAX.open('GET', url, false);\n AJAX.send(null);\n return AJAX.responseText;\n };\n\n // parse xml\n svg.parseXml = function (xml) {\n if (window.DOMParser) {\n const parser = new DOMParser();\n return parser.parseFromString(xml, 'text/xml');\n }\n xml = xml.replace(/]*>/, '');\n const xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');\n xmlDoc.async = 'false';\n xmlDoc.loadXML(xml);\n return xmlDoc;\n };\n\n // text extensions\n // get the text baseline\n const textBaselineMapping = {\n baseline: 'alphabetic',\n 'before-edge': 'top',\n 'text-before-edge': 'top',\n middle: 'middle',\n central: 'middle',\n 'after-edge': 'bottom',\n 'text-after-edge': 'bottom',\n ideographic: 'ideographic',\n alphabetic: 'alphabetic',\n hanging: 'hanging',\n mathematical: 'alphabetic'\n };\n\n svg.Property = class Property {\n constructor (name, value) {\n this.name = name;\n this.value = value;\n }\n\n getValue () {\n return this.value;\n }\n\n hasValue () {\n return (!isNullish(this.value) && this.value !== '');\n }\n\n // return the numerical value of the property\n numValue () {\n if (!this.hasValue()) return 0;\n\n let n = Number.parseFloat(this.value);\n if (String(this.value).endsWith('%')) {\n n /= 100.0;\n }\n return n;\n }\n\n valueOrDefault (def) {\n if (this.hasValue()) return this.value;\n return def;\n }\n\n numValueOrDefault (def) {\n if (this.hasValue()) return this.numValue();\n return def;\n }\n\n // color extensions\n // augment the current color value with the opacity\n addOpacity (opacityProp) {\n let newValue = this.value;\n if (!isNullish(opacityProp.value) && opacityProp.value !== '' && typeof this.value === 'string') { // can only add opacity to colors, not patterns\n const color = new RGBColor(this.value);\n if (color.ok) {\n newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacityProp.numValue() + ')';\n }\n }\n return new svg.Property(this.name, newValue);\n }\n\n // definition extensions\n // get the definition from the definitions table\n getDefinition () {\n let name = this.value.match(/#([^)'\"]+)/);\n if (name) { name = name[1]; }\n if (!name) { name = this.value; }\n return svg.Definitions[name];\n }\n\n isUrlDefinition () {\n return this.value.startsWith('url(');\n }\n\n getFillStyleDefinition (e, opacityProp) {\n let def = this.getDefinition();\n\n // gradient\n if (!isNullish(def) && def.createGradient) {\n return def.createGradient(svg.ctx, e, opacityProp);\n }\n\n // pattern\n if (!isNullish(def) && def.createPattern) {\n if (def.getHrefAttribute().hasValue()) {\n const pt = def.attribute('patternTransform');\n def = def.getHrefAttribute().getDefinition();\n if (pt.hasValue()) { def.attribute('patternTransform', true).value = pt.value; }\n }\n return def.createPattern(svg.ctx, e);\n }\n\n return null;\n }\n\n // length extensions\n getDPI (viewPort) {\n return 96.0; // TODO: compute?\n }\n\n getEM (viewPort) {\n let em = 12;\n\n const fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);\n if (fontSize.hasValue()) em = fontSize.toPixels(viewPort);\n\n return em;\n }\n\n getUnits () {\n return String(this.value).replace(/[\\d.-]/g, '');\n }\n\n // get the length as pixels\n toPixels (viewPort, processPercent) {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('em')) return this.numValue() * this.getEM(viewPort);\n if (s.endsWith('ex')) return this.numValue() * this.getEM(viewPort) / 2.0;\n if (s.endsWith('px')) return this.numValue();\n if (s.endsWith('pt')) return this.numValue() * this.getDPI(viewPort) * (1.0 / 72.0);\n if (s.endsWith('pc')) return this.numValue() * 15;\n if (s.endsWith('cm')) return this.numValue() * this.getDPI(viewPort) / 2.54;\n if (s.endsWith('mm')) return this.numValue() * this.getDPI(viewPort) / 25.4;\n if (s.endsWith('in')) return this.numValue() * this.getDPI(viewPort);\n if (s.endsWith('%')) return this.numValue() * svg.ViewPort.ComputeSize(viewPort);\n const n = this.numValue();\n if (processPercent && n < 1.0) return n * svg.ViewPort.ComputeSize(viewPort);\n return n;\n }\n\n // time extensions\n // get the time as milliseconds\n toMilliseconds () {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('ms')) return this.numValue();\n if (s.endsWith('s')) return this.numValue() * 1000;\n return this.numValue();\n }\n\n // angle extensions\n // get the angle as radians\n toRadians () {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('deg')) return this.numValue() * (Math.PI / 180.0);\n if (s.endsWith('grad')) return this.numValue() * (Math.PI / 200.0);\n if (s.endsWith('rad')) return this.numValue();\n return this.numValue() * (Math.PI / 180.0);\n }\n\n toTextBaseline () {\n if (!this.hasValue()) return null;\n return textBaselineMapping[this.value];\n }\n };\n\n // fonts\n svg.Font = {\n Styles: 'normal|italic|oblique|inherit',\n Variants: 'normal|small-caps|inherit',\n Weights: 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit',\n\n CreateFont (fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {\n const f = !isNullish(inherit) ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);\n return {\n fontFamily: fontFamily || f.fontFamily,\n fontSize: fontSize || f.fontSize,\n fontStyle: fontStyle || f.fontStyle,\n fontWeight: fontWeight || f.fontWeight,\n fontVariant: fontVariant || f.fontVariant,\n toString () {\n return [\n this.fontStyle, this.fontVariant, this.fontWeight,\n this.fontSize, this.fontFamily\n ].join(' ');\n }\n };\n },\n\n Parse (s) {\n const f = {};\n const ds = svg.trim(svg.compressSpaces(s || '')).split(' ');\n const set = {\n fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false\n };\n let ff = '';\n ds.forEach((d) => {\n if (!set.fontStyle && this.Styles.includes(d)) {\n if (d !== 'inherit') {\n f.fontStyle = d;\n }\n set.fontStyle = true;\n } else if (!set.fontVariant && this.Variants.includes(d)) {\n if (d !== 'inherit') {\n f.fontVariant = d;\n }\n set.fontStyle = set.fontVariant = true;\n } else if (!set.fontWeight && this.Weights.includes(d)) {\n if (d !== 'inherit') {\n f.fontWeight = d;\n }\n set.fontStyle = set.fontVariant = set.fontWeight = true;\n } else if (!set.fontSize) {\n if (d !== 'inherit') {\n f.fontSize = d.split('/')[0];\n }\n set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true;\n } else if (d !== 'inherit') {\n ff += d;\n }\n });\n if (ff !== '') { f.fontFamily = ff; }\n return f;\n }\n };\n\n // points and paths\n svg.ToNumberArray = function (s) {\n const a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');\n return a.map((_a) => Number.parseFloat(_a));\n };\n svg.Point = class {\n constructor (x, y) {\n this.x = x;\n this.y = y;\n }\n\n angleTo (p) {\n return Math.atan2(p.y - this.y, p.x - this.x);\n }\n\n applyTransform (v) {\n const xp = this.x * v[0] + this.y * v[2] + v[4];\n const yp = this.x * v[1] + this.y * v[3] + v[5];\n this.x = xp;\n this.y = yp;\n }\n };\n\n svg.CreatePoint = function (s) {\n const a = svg.ToNumberArray(s);\n return new svg.Point(a[0], a[1]);\n };\n svg.CreatePath = function (s) {\n const a = svg.ToNumberArray(s);\n const path = [];\n for (let i = 0; i < a.length; i += 2) {\n path.push(new svg.Point(a[i], a[i + 1]));\n }\n return path;\n };\n\n // bounding box\n svg.BoundingBox = class {\n constructor (x1, y1, x2, y2) { // pass in initial points if you want\n this.x1 = Number.NaN;\n this.y1 = Number.NaN;\n this.x2 = Number.NaN;\n this.y2 = Number.NaN;\n this.addPoint(x1, y1);\n this.addPoint(x2, y2);\n }\n\n x () { return this.x1; }\n y () { return this.y1; }\n width () { return this.x2 - this.x1; }\n height () { return this.y2 - this.y1; }\n\n addPoint (x, y) {\n if (!isNullish(x)) {\n if (isNaN(this.x1) || isNaN(this.x2)) {\n this.x1 = x;\n this.x2 = x;\n }\n if (x < this.x1) this.x1 = x;\n if (x > this.x2) this.x2 = x;\n }\n\n if (!isNullish(y)) {\n if (isNaN(this.y1) || isNaN(this.y2)) {\n this.y1 = y;\n this.y2 = y;\n }\n if (y < this.y1) this.y1 = y;\n if (y > this.y2) this.y2 = y;\n }\n }\n addX (x) { this.addPoint(x, null); }\n addY (y) { this.addPoint(null, y); }\n\n addBoundingBox (bb) {\n this.addPoint(bb.x1, bb.y1);\n this.addPoint(bb.x2, bb.y2);\n }\n\n addQuadraticCurve (p0x, p0y, p1x, p1y, p2x, p2y) {\n const cp1x = p0x + 2 / 3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)\n const cp1y = p0y + 2 / 3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)\n const cp2x = cp1x + 1 / 3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)\n const cp2y = cp1y + 1 / 3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)\n this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);\n }\n\n addBezierCurve (p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {\n // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html\n const p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];\n this.addPoint(p0[0], p0[1]);\n this.addPoint(p3[0], p3[1]);\n\n for (let i = 0; i <= 1; i++) {\n const f = function (t) {\n return ((1 - t) ** 3) * p0[i] +\n 3 * ((1 - t) ** 2) * t * p1[i] +\n 3 * (1 - t) * (t ** 2) * p2[i] +\n (t ** 3) * p3[i];\n };\n\n const b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];\n const a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];\n const c = 3 * p1[i] - 3 * p0[i];\n\n if (a === 0) {\n if (b === 0) continue;\n const t = -c / b;\n if (t > 0 && t < 1) {\n if (i === 0) this.addX(f(t));\n if (i === 1) this.addY(f(t));\n }\n continue;\n }\n\n const b2ac = (b ** 2) - 4 * c * a;\n if (b2ac < 0) continue;\n const t1 = (-b + Math.sqrt(b2ac)) / (2 * a);\n if (t1 > 0 && t1 < 1) {\n if (i === 0) this.addX(f(t1));\n if (i === 1) this.addY(f(t1));\n }\n const t2 = (-b - Math.sqrt(b2ac)) / (2 * a);\n if (t2 > 0 && t2 < 1) {\n if (i === 0) this.addX(f(t2));\n if (i === 1) this.addY(f(t2));\n }\n }\n }\n\n isPointInBox (x, y) {\n return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);\n }\n };\n\n // transforms\n svg.Transform = class {\n constructor (v) {\n this.Type = {\n translate: class {\n constructor (s) {\n this.p = svg.CreatePoint(s);\n this.apply = function (ctx) {\n ctx.translate(this.p.x || 0.0, this.p.y || 0.0);\n };\n this.unapply = function (ctx) {\n ctx.translate(-1.0 * this.p.x || 0.0, -1.0 * this.p.y || 0.0);\n };\n this.applyToPoint = function (p) {\n p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n };\n }\n },\n rotate: class {\n constructor (s) {\n const a = svg.ToNumberArray(s);\n this.angle = new svg.Property('angle', a[0]);\n this.cx = a[1] || 0;\n this.cy = a[2] || 0;\n this.apply = function (ctx) {\n ctx.translate(this.cx, this.cy);\n ctx.rotate(this.angle.toRadians());\n ctx.translate(-this.cx, -this.cy);\n };\n this.unapply = function (ctx) {\n ctx.translate(this.cx, this.cy);\n ctx.rotate(-1.0 * this.angle.toRadians());\n ctx.translate(-this.cx, -this.cy);\n };\n this.applyToPoint = function (p) {\n const _a = this.angle.toRadians();\n p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n p.applyTransform([Math.cos(_a), Math.sin(_a), -Math.sin(_a), Math.cos(_a), 0, 0]);\n p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);\n };\n }\n },\n scale: class {\n constructor (s) {\n this.p = svg.CreatePoint(s);\n this.apply = function (ctx) {\n ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);\n };\n this.unapply = function (ctx) {\n ctx.scale(1.0 / this.p.x || 1.0, 1.0 / this.p.y || this.p.x || 1.0);\n };\n this.applyToPoint = function (p) {\n p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);\n };\n }\n },\n matrix: class {\n constructor (s) {\n this.m = svg.ToNumberArray(s);\n this.apply = function (ctx) {\n ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);\n };\n this.applyToPoint = function (p) {\n p.applyTransform(this.m);\n };\n }\n }\n };\n Object.assign(this.Type, {\n SkewBase: class extends this.Type.matrix {\n constructor (s) {\n super(s);\n this.angle = new svg.Property('angle', s);\n }\n }\n });\n Object.assign(this.Type, {\n skewX: class extends this.Type.SkewBase {\n constructor (s) {\n super(s);\n this.m = [1, 0, Math.tan(this.angle.toRadians()), 1, 0, 0];\n }\n },\n skewY: class extends this.Type.SkewBase {\n constructor (s) {\n super(s);\n this.m = [1, Math.tan(this.angle.toRadians()), 0, 1, 0, 0];\n }\n }\n });\n\n const data = svg.trim(svg.compressSpaces(v)).replace(\n /\\)([a-zA-Z])/g, ') $1'\n ).replace(/\\)(\\s?,\\s?)/g, ') ').split(/\\s(?=[a-z])/);\n this.transforms = data.map((d) => {\n const type = svg.trim(d.split('(')[0]);\n const s = d.split('(')[1].replace(')', '');\n const transform = new this.Type[type](s);\n transform.type = type;\n return transform;\n });\n }\n\n apply (ctx) {\n this.transforms.forEach((transform) => {\n transform.apply(ctx);\n });\n }\n\n unapply (ctx) {\n for (let i = this.transforms.length - 1; i >= 0; i--) {\n this.transforms[i].unapply(ctx);\n }\n }\n\n applyToPoint (p) {\n this.transforms.forEach((transform) => {\n transform.applyToPoint(p);\n });\n }\n };\n\n // aspect ratio\n svg.AspectRatio = function (ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {\n // aspect ratio - https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute\n aspectRatio = svg.compressSpaces(aspectRatio);\n aspectRatio = aspectRatio.replace(/^defer\\s/, ''); // ignore defer\n const align = aspectRatio.split(' ')[0] || 'xMidYMid';\n const meetOrSlice = aspectRatio.split(' ')[1] || 'meet';\n\n // calculate scale\n const scaleX = width / desiredWidth;\n const scaleY = height / desiredHeight;\n const scaleMin = Math.min(scaleX, scaleY);\n const scaleMax = Math.max(scaleX, scaleY);\n if (meetOrSlice === 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }\n if (meetOrSlice === 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }\n\n refX = new svg.Property('refX', refX);\n refY = new svg.Property('refY', refY);\n if (refX.hasValue() && refY.hasValue()) {\n ctx.translate(-scaleMin * refX.toPixels('x'), -scaleMin * refY.toPixels('y'));\n } else {\n // align\n if (align.startsWith('xMid') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleY) || (meetOrSlice === 'slice' && scaleMax === scaleY))) {\n ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);\n }\n if (align.endsWith('YMid') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleX) || (meetOrSlice === 'slice' && scaleMax === scaleX))) {\n ctx.translate(0, height / 2.0 - desiredHeight / 2.0);\n }\n if (align.startsWith('xMax') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleY) || (meetOrSlice === 'slice' && scaleMax === scaleY))) {\n ctx.translate(width - desiredWidth, 0);\n }\n if (align.endsWith('YMax') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleX) ||\n (meetOrSlice === 'slice' && scaleMax === scaleX)\n )\n ) {\n ctx.translate(0, height - desiredHeight);\n }\n }\n\n // scale\n if (align === 'none') ctx.scale(scaleX, scaleY);\n else if (meetOrSlice === 'meet') ctx.scale(scaleMin, scaleMin);\n else if (meetOrSlice === 'slice') ctx.scale(scaleMax, scaleMax);\n\n // translate\n ctx.translate(isNullish(minX) ? 0 : -minX, isNullish(minY) ? 0 : -minY);\n };\n\n // elements\n svg.Element = {};\n\n svg.EmptyProperty = new svg.Property('EMPTY', '');\n\n svg.Element.ElementBase = class {\n constructor (node) {\n // Argument from inheriting class\n this.captureTextNodes = arguments[1]; // eslint-disable-line prefer-rest-params\n this.attributes = {};\n this.styles = {};\n this.children = [];\n if (!isNullish(node) && node.nodeType === 1) { // ELEMENT_NODE\n // add children\n [...node.childNodes].forEach((childNode) => {\n if (childNode.nodeType === 1) {\n this.addChild(childNode, true); // ELEMENT_NODE\n }\n if (this.captureTextNodes && (\n childNode.nodeType === 3 || childNode.nodeType === 4\n )) {\n const text = childNode.nodeValue || childNode.text || '';\n if (svg.trim(svg.compressSpaces(text)) !== '') {\n this.addChild(new svg.Element.tspan(childNode), false); // TEXT_NODE\n }\n }\n });\n\n // add attributes\n [...node.attributes].forEach(({nodeName, nodeValue}) => {\n this.attributes[nodeName] = new svg.Property(\n nodeName,\n nodeValue\n );\n });\n // add tag styles\n let styles = svg.Styles[node.nodeName];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n\n // add class styles\n if (this.attribute('class').hasValue()) {\n const classes = svg.compressSpaces(this.attribute('class').value).split(' ');\n classes.forEach((clss) => {\n styles = svg.Styles['.' + clss];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n styles = svg.Styles[node.nodeName + '.' + clss];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n });\n }\n\n // add id styles\n if (this.attribute('id').hasValue()) {\n const _styles = svg.Styles['#' + this.attribute('id').value];\n if (!isNullish(_styles)) {\n Object.entries(_styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n }\n\n // add inline styles\n if (this.attribute('style').hasValue()) {\n const _styles = this.attribute('style').value.split(';');\n _styles.forEach((style) => {\n if (svg.trim(style) !== '') {\n let {name, value} = style.split(':');\n name = svg.trim(name);\n value = svg.trim(value);\n this.styles[name] = new svg.Property(name, value);\n }\n });\n }\n\n // add id\n if (this.attribute('id').hasValue()) {\n if (isNullish(svg.Definitions[this.attribute('id').value])) {\n svg.Definitions[this.attribute('id').value] = this;\n }\n }\n }\n }\n\n // get or create attribute\n attribute (name, createIfNotExists) {\n let a = this.attributes[name];\n if (!isNullish(a)) return a;\n\n if (createIfNotExists === true) { a = new svg.Property(name, ''); this.attributes[name] = a; }\n return a || svg.EmptyProperty;\n }\n\n getHrefAttribute () {\n for (const a in this.attributes) {\n if (a.endsWith(':href')) {\n return this.attributes[a];\n }\n }\n return svg.EmptyProperty;\n }\n\n // get or create style, crawls up node tree\n style (name, createIfNotExists, skipAncestors) {\n let s = this.styles[name];\n if (!isNullish(s)) return s;\n\n const a = this.attribute(name);\n if (!isNullish(a) && a.hasValue()) {\n this.styles[name] = a; // move up to me to cache\n return a;\n }\n\n if (skipAncestors !== true) {\n const p = this.parent;\n if (!isNullish(p)) {\n const ps = p.style(name);\n if (!isNullish(ps) && ps.hasValue()) {\n return ps;\n }\n }\n }\n\n if (createIfNotExists === true) { s = new svg.Property(name, ''); this.styles[name] = s; }\n return s || svg.EmptyProperty;\n }\n\n // base render\n render (ctx) {\n // don't render display=none\n if (this.style('display').value === 'none') return;\n\n // don't render visibility=hidden\n if (this.style('visibility').value === 'hidden') return;\n\n ctx.save();\n if (this.attribute('mask').hasValue()) { // mask\n const mask = this.attribute('mask').getDefinition();\n if (!isNullish(mask)) mask.apply(ctx, this);\n } else if (this.style('filter').hasValue()) { // filter\n const filter = this.style('filter').getDefinition();\n if (!isNullish(filter)) filter.apply(ctx, this);\n } else {\n this.setContext(ctx);\n this.renderChildren(ctx);\n this.clearContext(ctx);\n }\n ctx.restore();\n }\n\n // base set context\n setContext (ctx) {\n // OVERRIDE ME!\n }\n\n // base clear context\n clearContext (ctx) {\n // OVERRIDE ME!\n }\n\n // base render children\n renderChildren (ctx) {\n this.children.forEach((child) => {\n child.render(ctx);\n });\n }\n\n addChild (childNode, create) {\n const child = create\n ? svg.CreateElement(childNode)\n : childNode;\n child.parent = this;\n if (child.type !== 'title') { this.children.push(child); }\n }\n };\n\n svg.Element.RenderedElementBase = class extends svg.Element.ElementBase {\n setContext (ctx) {\n // fill\n if (this.style('fill').isUrlDefinition()) {\n const fs = this.style('fill').getFillStyleDefinition(this, this.style('fill-opacity'));\n if (!isNullish(fs)) ctx.fillStyle = fs;\n } else if (this.style('fill').hasValue()) {\n const fillStyle = this.style('fill');\n if (fillStyle.value === 'currentColor') fillStyle.value = this.style('color').value;\n ctx.fillStyle = (fillStyle.value === 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);\n }\n if (this.style('fill-opacity').hasValue()) {\n let fillStyle = new svg.Property('fill', ctx.fillStyle);\n fillStyle = fillStyle.addOpacity(this.style('fill-opacity'));\n ctx.fillStyle = fillStyle.value;\n }\n\n // stroke\n if (this.style('stroke').isUrlDefinition()) {\n const fs = this.style('stroke').getFillStyleDefinition(this, this.style('stroke-opacity'));\n if (!isNullish(fs)) ctx.strokeStyle = fs;\n } else if (this.style('stroke').hasValue()) {\n const strokeStyle = this.style('stroke');\n if (strokeStyle.value === 'currentColor') strokeStyle.value = this.style('color').value;\n ctx.strokeStyle = (strokeStyle.value === 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);\n }\n if (this.style('stroke-opacity').hasValue()) {\n let strokeStyle = new svg.Property('stroke', ctx.strokeStyle);\n strokeStyle = strokeStyle.addOpacity(this.style('stroke-opacity'));\n ctx.strokeStyle = strokeStyle.value;\n }\n if (this.style('stroke-width').hasValue()) {\n const newLineWidth = this.style('stroke-width').toPixels();\n ctx.lineWidth = newLineWidth === 0 ? 0.001 : newLineWidth; // browsers don't respect 0\n }\n if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;\n if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;\n if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;\n if (this.style('stroke-dasharray').hasValue() && this.style('stroke-dasharray').value !== 'none') {\n const gaps = svg.ToNumberArray(this.style('stroke-dasharray').value);\n if (typeof ctx.setLineDash !== 'undefined') {\n ctx.setLineDash(gaps);\n } else if (typeof ctx.webkitLineDash !== 'undefined') {\n ctx.webkitLineDash = gaps;\n } else if (typeof ctx.mozDash !== 'undefined' && !(gaps.length === 1 && gaps[0] === 0)) {\n ctx.mozDash = gaps;\n }\n\n const offset = this.style('stroke-dashoffset').numValueOrDefault(1);\n if (typeof ctx.lineDashOffset !== 'undefined') {\n ctx.lineDashOffset = offset;\n } else if (typeof ctx.webkitLineDashOffset !== 'undefined') {\n ctx.webkitLineDashOffset = offset;\n } else if (typeof ctx.mozDashOffset !== 'undefined') {\n ctx.mozDashOffset = offset;\n }\n }\n\n // font\n if (typeof ctx.font !== 'undefined') {\n ctx.font = svg.Font.CreateFont(\n this.style('font-style').value,\n this.style('font-variant').value,\n this.style('font-weight').value,\n this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '',\n this.style('font-family').value\n ).toString();\n }\n\n // transform\n if (this.attribute('transform').hasValue()) {\n const transform = new svg.Transform(this.attribute('transform').value);\n transform.apply(ctx);\n }\n\n // clip\n if (this.style('clip-path', false, true).hasValue()) {\n const clip = this.style('clip-path', false, true).getDefinition();\n if (!isNullish(clip)) clip.apply(ctx);\n }\n\n // opacity\n if (this.style('opacity').hasValue()) {\n ctx.globalAlpha = this.style('opacity').numValue();\n }\n }\n };\n\n svg.Element.PathElementBase = class extends svg.Element.RenderedElementBase {\n path (ctx) {\n if (!isNullish(ctx)) ctx.beginPath();\n return new svg.BoundingBox();\n }\n\n renderChildren (ctx) {\n this.path(ctx);\n svg.Mouse.checkPath(this, ctx);\n if (ctx.fillStyle !== '') {\n if (this.style('fill-rule').valueOrDefault('inherit') !== 'inherit') {\n ctx.fill(this.style('fill-rule').value);\n } else {\n ctx.fill();\n }\n }\n if (ctx.strokeStyle !== '') ctx.stroke();\n\n const markers = this.getMarkers();\n if (!isNullish(markers)) {\n if (this.style('marker-start').isUrlDefinition()) {\n const marker = this.style('marker-start').getDefinition();\n marker.render(ctx, markers[0][0], markers[0][1]);\n }\n if (this.style('marker-mid').isUrlDefinition()) {\n const marker = this.style('marker-mid').getDefinition();\n for (let i = 1; i < markers.length - 1; i++) {\n marker.render(ctx, markers[i][0], markers[i][1]);\n }\n }\n if (this.style('marker-end').isUrlDefinition()) {\n const marker = this.style('marker-end').getDefinition();\n marker.render(ctx, markers[markers.length - 1][0], markers[markers.length - 1][1]);\n }\n }\n }\n\n getBoundingBox () {\n return this.path();\n }\n\n getMarkers () {\n return null;\n }\n };\n\n // svg element\n svg.Element.svg = class extends svg.Element.RenderedElementBase {\n clearContext (ctx) {\n super.clearContext(ctx);\n svg.ViewPort.RemoveCurrent();\n }\n\n setContext (ctx) {\n // initial values and defaults\n ctx.strokeStyle = 'rgba(0,0,0,0)';\n ctx.lineCap = 'butt';\n ctx.lineJoin = 'miter';\n ctx.miterLimit = 4;\n if (typeof ctx.font !== 'undefined' && typeof window.getComputedStyle !== 'undefined') {\n ctx.font = window.getComputedStyle(ctx.canvas).getPropertyValue('font');\n }\n\n super.setContext(ctx);\n\n // create new view port\n if (!this.attribute('x').hasValue()) this.attribute('x', true).value = 0;\n if (!this.attribute('y').hasValue()) this.attribute('y', true).value = 0;\n ctx.translate(this.attribute('x').toPixels('x'), this.attribute('y').toPixels('y'));\n\n let width = svg.ViewPort.width();\n let height = svg.ViewPort.height();\n\n if (!this.attribute('width').hasValue()) this.attribute('width', true).value = '100%';\n if (!this.attribute('height').hasValue()) this.attribute('height', true).value = '100%';\n if (typeof this.root === 'undefined') {\n width = this.attribute('width').toPixels('x');\n height = this.attribute('height').toPixels('y');\n\n let x = 0;\n let y = 0;\n if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {\n x = -this.attribute('refX').toPixels('x');\n y = -this.attribute('refY').toPixels('y');\n }\n\n if (this.attribute('overflow').valueOrDefault('hidden') !== 'visible') {\n ctx.beginPath();\n ctx.moveTo(x, y);\n ctx.lineTo(width, y);\n ctx.lineTo(width, height);\n ctx.lineTo(x, height);\n ctx.closePath();\n ctx.clip();\n }\n }\n svg.ViewPort.SetCurrent(width, height);\n\n // viewbox\n if (this.attribute('viewBox').hasValue()) {\n const viewBox = svg.ToNumberArray(this.attribute('viewBox').value);\n const minX = viewBox[0];\n const minY = viewBox[1];\n width = viewBox[2];\n height = viewBox[3];\n\n svg.AspectRatio(\n ctx,\n this.attribute('preserveAspectRatio').value,\n svg.ViewPort.width(),\n width,\n svg.ViewPort.height(),\n height,\n minX,\n minY,\n this.attribute('refX').value,\n this.attribute('refY').value\n );\n\n svg.ViewPort.RemoveCurrent();\n svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);\n }\n }\n };\n\n // rect element\n svg.Element.rect = class extends svg.Element.PathElementBase {\n path (ctx) {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n let rx = this.attribute('rx').toPixels('x');\n let ry = this.attribute('ry').toPixels('y');\n if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;\n if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;\n rx = Math.min(rx, width / 2.0);\n ry = Math.min(ry, height / 2.0);\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(x + rx, y);\n ctx.lineTo(x + width - rx, y);\n ctx.quadraticCurveTo(x + width, y, x + width, y + ry);\n ctx.lineTo(x + width, y + height - ry);\n ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);\n ctx.lineTo(x + rx, y + height);\n ctx.quadraticCurveTo(x, y + height, x, y + height - ry);\n ctx.lineTo(x, y + ry);\n ctx.quadraticCurveTo(x, y, x + rx, y);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(x, y, x + width, y + height);\n }\n };\n\n // circle element\n svg.Element.circle = class extends svg.Element.PathElementBase {\n path (ctx) {\n const cx = this.attribute('cx').toPixels('x');\n const cy = this.attribute('cy').toPixels('y');\n const r = this.attribute('r').toPixels();\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2, true);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);\n }\n };\n\n // ellipse element\n const KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);\n svg.Element.ellipse = class extends svg.Element.PathElementBase {\n path (ctx) {\n const rx = this.attribute('rx').toPixels('x');\n const ry = this.attribute('ry').toPixels('y');\n const cx = this.attribute('cx').toPixels('x');\n const cy = this.attribute('cy').toPixels('y');\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(cx, cy - ry);\n ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);\n ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);\n ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);\n ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);\n }\n };\n\n // line element\n svg.Element.line = class extends svg.Element.PathElementBase {\n getPoints () {\n return [\n new svg.Point(this.attribute('x1').toPixels('x'), this.attribute('y1').toPixels('y')),\n new svg.Point(this.attribute('x2').toPixels('x'), this.attribute('y2').toPixels('y'))\n ];\n }\n\n path (ctx) {\n const points = this.getPoints();\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(points[0].x, points[0].y);\n ctx.lineTo(points[1].x, points[1].y);\n }\n\n return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);\n }\n\n getMarkers () {\n const points = this.getPoints();\n const a = points[0].angleTo(points[1]);\n return [[points[0], a], [points[1], a]];\n }\n };\n\n // polyline element\n svg.Element.polyline = class extends svg.Element.PathElementBase {\n constructor (node) {\n super(node);\n\n this.points = svg.CreatePath(this.attribute('points').value);\n }\n path (ctx) {\n const {x, y} = this.points[0];\n const bb = new svg.BoundingBox(x, y);\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(x, y);\n }\n for (let i = 1; i < this.points.length; i++) {\n const {x: _x, y: _y} = this.points[i];\n bb.addPoint(_x, _y);\n if (!isNullish(ctx)) ctx.lineTo(_x, _y);\n }\n return bb;\n }\n\n getMarkers () {\n const markers = [];\n for (let i = 0; i < this.points.length - 1; i++) {\n markers.push([this.points[i], this.points[i].angleTo(this.points[i + 1])]);\n }\n markers.push([this.points[this.points.length - 1], markers[markers.length - 1][1]]);\n return markers;\n }\n };\n\n // polygon element\n svg.Element.polygon = class extends svg.Element.polyline {\n path (ctx) {\n const bb = super.path(ctx);\n if (!isNullish(ctx)) {\n ctx.lineTo(this.points[0].x, this.points[0].y);\n ctx.closePath();\n }\n return bb;\n }\n };\n\n // path element\n svg.Element.path = class extends svg.Element.PathElementBase {\n constructor (node) {\n super(node);\n\n let d = this.attribute('d').value\n // TODO: convert to real lexer based on https://www.w3.org/TR/SVG11/paths.html#PathDataBNF\n .replace(/,/gm, ' ') // get rid of all commas\n .replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from commands\n .replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from commands\n .replace(/([MmZzLlHhVvCcSsQqTtAa])(\\S)/gm, '$1 $2') // separate commands from points\n .replace(/(\\S)([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from points\n .replace(/(\\d)([+-])/gm, '$1 $2') // separate digits when no comma\n .replace(/(\\.\\d*)(\\.)/gm, '$1 $2') // separate digits when no comma\n .replace(/([Aa](\\s+\\d+)(\\s+\\d+)(\\s+\\d+))\\s+([01])\\s*([01])/gm, '$1 $5 $6 '); // shorthand elliptical arc path syntax\n d = svg.compressSpaces(d); // compress multiple spaces\n d = svg.trim(d);\n this.PathParser = {\n tokens: d.split(' '),\n\n reset () {\n this.i = -1;\n this.command = '';\n this.previousCommand = '';\n this.start = new svg.Point(0, 0);\n this.control = new svg.Point(0, 0);\n this.current = new svg.Point(0, 0);\n this.points = [];\n this.angles = [];\n },\n\n isEnd () {\n return this.i >= this.tokens.length - 1;\n },\n\n isCommandOrEnd () {\n if (this.isEnd()) return true;\n return !isNullish(this.tokens[this.i + 1].match(/^[A-Za-z]$/));\n },\n\n isRelativeCommand () {\n switch (this.command) {\n case 'm':\n case 'l':\n case 'h':\n case 'v':\n case 'c':\n case 's':\n case 'q':\n case 't':\n case 'a':\n case 'z':\n return true;\n }\n return false;\n },\n\n getToken () {\n this.i++;\n return this.tokens[this.i];\n },\n\n getScalar () {\n return Number.parseFloat(this.getToken());\n },\n\n nextCommand () {\n this.previousCommand = this.command;\n this.command = this.getToken();\n },\n\n getPoint () {\n const p = new svg.Point(this.getScalar(), this.getScalar());\n return this.makeAbsolute(p);\n },\n\n getAsControlPoint () {\n const p = this.getPoint();\n this.control = p;\n return p;\n },\n\n getAsCurrentPoint () {\n const p = this.getPoint();\n this.current = p;\n return p;\n },\n\n getReflectedControlPoint () {\n if (this.previousCommand.toLowerCase() !== 'c' &&\n this.previousCommand.toLowerCase() !== 's' &&\n this.previousCommand.toLowerCase() !== 'q' &&\n this.previousCommand.toLowerCase() !== 't') {\n return this.current;\n }\n\n // reflect point\n const p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);\n return p;\n },\n\n makeAbsolute (p) {\n if (this.isRelativeCommand()) {\n p.x += this.current.x;\n p.y += this.current.y;\n }\n return p;\n },\n\n addMarker (p, from, priorTo) {\n // if the last angle isn't filled in because we didn't have this point yet ...\n if (!isNullish(priorTo) && this.angles.length > 0 && isNullish(this.angles[this.angles.length - 1])) {\n this.angles[this.angles.length - 1] = this.points[this.points.length - 1].angleTo(priorTo);\n }\n this.addMarkerAngle(p, isNullish(from) ? null : from.angleTo(p));\n },\n\n addMarkerAngle (p, a) {\n this.points.push(p);\n this.angles.push(a);\n },\n\n getMarkerPoints () { return this.points; },\n getMarkerAngles () {\n for (let i = 0; i < this.angles.length; i++) {\n if (isNullish(this.angles[i])) {\n for (let j = i + 1; j < this.angles.length; j++) {\n if (!isNullish(this.angles[j])) {\n this.angles[i] = this.angles[j];\n break;\n }\n }\n }\n }\n return this.angles;\n }\n };\n }\n\n path (ctx) {\n const pp = this.PathParser;\n pp.reset();\n\n const bb = new svg.BoundingBox();\n if (!isNullish(ctx)) ctx.beginPath();\n while (!pp.isEnd()) {\n pp.nextCommand();\n switch (pp.command) {\n case 'M':\n case 'm': {\n const p = pp.getAsCurrentPoint();\n pp.addMarker(p);\n bb.addPoint(p.x, p.y);\n if (!isNullish(ctx)) ctx.moveTo(p.x, p.y);\n pp.start = pp.current;\n while (!pp.isCommandOrEnd()) {\n const _p = pp.getAsCurrentPoint();\n pp.addMarker(_p, pp.start);\n bb.addPoint(_p.x, _p.y);\n if (!isNullish(ctx)) ctx.lineTo(_p.x, _p.y);\n }\n break;\n } case 'L':\n case 'l':\n while (!pp.isCommandOrEnd()) {\n const c = pp.current;\n const p = pp.getAsCurrentPoint();\n pp.addMarker(p, c);\n bb.addPoint(p.x, p.y);\n if (!isNullish(ctx)) ctx.lineTo(p.x, p.y);\n }\n break;\n case 'H':\n case 'h':\n while (!pp.isCommandOrEnd()) {\n const newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);\n pp.addMarker(newP, pp.current);\n pp.current = newP;\n bb.addPoint(pp.current.x, pp.current.y);\n if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);\n }\n break;\n case 'V':\n case 'v':\n while (!pp.isCommandOrEnd()) {\n const newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());\n pp.addMarker(newP, pp.current);\n pp.current = newP;\n bb.addPoint(pp.current.x, pp.current.y);\n if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);\n }\n break;\n case 'C':\n case 'c':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const p1 = pp.getPoint();\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, p1);\n bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'S':\n case 's':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const p1 = pp.getReflectedControlPoint();\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, p1);\n bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'Q':\n case 'q':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, cntrl);\n bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'T':\n case 't':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const cntrl = pp.getReflectedControlPoint();\n pp.control = cntrl;\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, cntrl);\n bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'A':\n case 'a':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n let rx = pp.getScalar();\n let ry = pp.getScalar();\n const xAxisRotation = pp.getScalar() * (Math.PI / 180.0);\n const largeArcFlag = pp.getScalar();\n const sweepFlag = pp.getScalar();\n const cp = pp.getAsCurrentPoint();\n\n // Conversion from endpoint to center parameterization\n // https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter\n\n // x1', y1'\n const currp = new svg.Point(\n Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,\n -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0\n );\n // adjust radii\n const l = (currp.x ** 2) / (rx ** 2) + (currp.y ** 2) / (ry ** 2);\n if (l > 1) {\n rx *= Math.sqrt(l);\n ry *= Math.sqrt(l);\n }\n // cx', cy'\n let s = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(\n (((rx ** 2) * (ry ** 2)) - ((rx ** 2) * (currp.y ** 2)) - ((ry ** 2) * (currp.x ** 2))) /\n ((rx ** 2) * (currp.y ** 2) + (ry ** 2) * (currp.x ** 2))\n );\n if (isNaN(s)) s = 0;\n const cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);\n // cx, cy\n const centp = new svg.Point(\n (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,\n (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y\n );\n // vector magnitude\n const m = function (v) {\n return Math.sqrt((v[0] ** 2) + (v[1] ** 2));\n };\n // ratio between two vectors\n const r = function (u, v) {\n return (u[0] * v[0] + u[1] * v[1]) / (m(u) * m(v));\n };\n // angle between two vectors\n const a = function (u, v) {\n return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(r(u, v));\n };\n // initial angle\n const a1 = a([1, 0], [(currp.x - cpp.x) / rx, (currp.y - cpp.y) / ry]);\n // angle delta\n const u = [(currp.x - cpp.x) / rx, (currp.y - cpp.y) / ry];\n const v = [(-currp.x - cpp.x) / rx, (-currp.y - cpp.y) / ry];\n let ad = a(u, v);\n if (r(u, v) <= -1) ad = Math.PI;\n if (r(u, v) >= 1) ad = 0;\n\n // for markers\n const dir = 1 - sweepFlag ? 1.0 : -1.0;\n const ah = a1 + dir * (ad / 2.0);\n const halfWay = new svg.Point(\n centp.x + rx * Math.cos(ah),\n centp.y + ry * Math.sin(ah)\n );\n pp.addMarkerAngle(halfWay, ah - dir * Math.PI / 2);\n pp.addMarkerAngle(cp, ah - dir * Math.PI);\n\n bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better\n if (!isNullish(ctx)) {\n const _r = rx > ry ? rx : ry;\n const sx = rx > ry ? 1 : rx / ry;\n const sy = rx > ry ? ry / rx : 1;\n\n ctx.translate(centp.x, centp.y);\n ctx.rotate(xAxisRotation);\n ctx.scale(sx, sy);\n ctx.arc(0, 0, _r, a1, a1 + ad, 1 - sweepFlag);\n ctx.scale(1 / sx, 1 / sy);\n ctx.rotate(-xAxisRotation);\n ctx.translate(-centp.x, -centp.y);\n }\n }\n break;\n case 'Z':\n case 'z':\n if (!isNullish(ctx)) ctx.closePath();\n pp.current = pp.start;\n }\n }\n\n return bb;\n }\n\n getMarkers () {\n const points = this.PathParser.getMarkerPoints();\n const angles = this.PathParser.getMarkerAngles();\n\n const markers = points.map((point, i) => {\n return [point, angles[i]];\n });\n return markers;\n }\n };\n\n // pattern element\n svg.Element.pattern = class extends svg.Element.ElementBase {\n createPattern (ctx, element) {\n const width = this.attribute('width').toPixels('x', true);\n const height = this.attribute('height').toPixels('y', true);\n\n // render me using a temporary svg element\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.viewBox = new svg.Property('viewBox', this.attribute('viewBox').value);\n tempSvg.attributes.width = new svg.Property('width', width + 'px');\n tempSvg.attributes.height = new svg.Property('height', height + 'px');\n tempSvg.attributes.transform = new svg.Property('transform', this.attribute('patternTransform').value);\n tempSvg.children = this.children;\n\n const c = document.createElement('canvas');\n c.width = width;\n c.height = height;\n const cctx = c.getContext('2d');\n if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {\n cctx.translate(this.attribute('x').toPixels('x', true), this.attribute('y').toPixels('y', true));\n }\n // render 3x3 grid so when we transform there's no white space on edges\n for (let x = -1; x <= 1; x++) {\n for (let y = -1; y <= 1; y++) {\n cctx.save();\n cctx.translate(x * c.width, y * c.height);\n tempSvg.render(cctx);\n cctx.restore();\n }\n }\n const pattern = ctx.createPattern(c, 'repeat');\n return pattern;\n }\n };\n\n // marker element\n svg.Element.marker = class extends svg.Element.ElementBase {\n render (ctx, point, angle) {\n ctx.translate(point.x, point.y);\n if (this.attribute('orient').valueOrDefault('auto') === 'auto') ctx.rotate(angle);\n if (this.attribute('markerUnits').valueOrDefault('strokeWidth') === 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);\n ctx.save();\n\n // render me using a temporary svg element\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.viewBox = new svg.Property(\n 'viewBox', this.attribute('viewBox').value\n );\n tempSvg.attributes.refX = new svg.Property(\n 'refX', this.attribute('refX').value\n );\n tempSvg.attributes.refY = new svg.Property(\n 'refY', this.attribute('refY').value\n );\n tempSvg.attributes.width = new svg.Property(\n 'width', this.attribute('markerWidth').value\n );\n tempSvg.attributes.height = new svg.Property(\n 'height', this.attribute('markerHeight').value\n );\n tempSvg.attributes.fill = new svg.Property(\n 'fill', this.attribute('fill').valueOrDefault('black')\n );\n tempSvg.attributes.stroke = new svg.Property(\n 'stroke', this.attribute('stroke').valueOrDefault('none')\n );\n tempSvg.children = this.children;\n tempSvg.render(ctx);\n\n ctx.restore();\n if (this.attribute('markerUnits').valueOrDefault('strokeWidth') ===\n 'strokeWidth'\n ) ctx.scale(1 / ctx.lineWidth, 1 / ctx.lineWidth);\n if (this.attribute('orient').valueOrDefault('auto') === 'auto') {\n ctx.rotate(-angle);\n }\n ctx.translate(-point.x, -point.y);\n }\n };\n\n // definitions element\n svg.Element.defs = class extends svg.Element.ElementBase {\n render (ctx) {\n // NOOP\n }\n };\n\n // base for gradients\n svg.Element.GradientBase = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');\n\n this.stops = [];\n this.children.forEach((child) => {\n if (child.type === 'stop') {\n this.stops.push(child);\n }\n });\n }\n\n getGradient () {\n // OVERRIDE ME!\n }\n\n createGradient (ctx, element, parentOpacityProp) {\n const stopsContainer = this.getHrefAttribute().hasValue()\n ? this.getHrefAttribute().getDefinition()\n : this;\n\n const addParentOpacity = function (color) {\n if (parentOpacityProp.hasValue()) {\n const p = new svg.Property('color', color);\n return p.addOpacity(parentOpacityProp).value;\n }\n return color;\n };\n\n const g = this.getGradient(ctx, element);\n if (isNullish(g)) return addParentOpacity(stopsContainer.stops[stopsContainer.stops.length - 1].color);\n stopsContainer.stops.forEach(({offset, color}) => {\n g.addColorStop(offset, addParentOpacity(color));\n });\n\n if (this.attribute('gradientTransform').hasValue()) {\n // render as transformed pattern on temporary canvas\n const rootView = svg.ViewPort.viewPorts[0];\n\n const rect = new svg.Element.rect();\n rect.attributes.x = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS / 3.0);\n rect.attributes.y = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS / 3.0);\n rect.attributes.width = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);\n rect.attributes.height = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);\n\n const group = new svg.Element.g();\n group.attributes.transform = new svg.Property('transform', this.attribute('gradientTransform').value);\n group.children = [rect];\n\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.x = new svg.Property('x', 0);\n tempSvg.attributes.y = new svg.Property('y', 0);\n tempSvg.attributes.width = new svg.Property('width', rootView.width);\n tempSvg.attributes.height = new svg.Property('height', rootView.height);\n tempSvg.children = [group];\n\n const c = document.createElement('canvas');\n c.width = rootView.width;\n c.height = rootView.height;\n const tempCtx = c.getContext('2d');\n tempCtx.fillStyle = g;\n tempSvg.render(tempCtx);\n return tempCtx.createPattern(c, 'no-repeat');\n }\n\n return g;\n }\n };\n\n // linear gradient element\n svg.Element.linearGradient = class extends svg.Element.GradientBase {\n getGradient (ctx, element) {\n const useBB = this.gradientUnits === 'objectBoundingBox' && element.getBoundingBox;\n const bb = useBB\n ? element.getBoundingBox()\n : null;\n\n if (!this.attribute('x1').hasValue() &&\n !this.attribute('y1').hasValue() &&\n !this.attribute('x2').hasValue() &&\n !this.attribute('y2').hasValue()\n ) {\n this.attribute('x1', true).value = 0;\n this.attribute('y1', true).value = 0;\n this.attribute('x2', true).value = 1;\n this.attribute('y2', true).value = 0;\n }\n\n const x1 = (useBB\n ? bb.x() + bb.width() * this.attribute('x1').numValue()\n : this.attribute('x1').toPixels('x'));\n const y1 = (useBB\n ? bb.y() + bb.height() * this.attribute('y1').numValue()\n : this.attribute('y1').toPixels('y'));\n const x2 = (useBB\n ? bb.x() + bb.width() * this.attribute('x2').numValue()\n : this.attribute('x2').toPixels('x'));\n const y2 = (useBB\n ? bb.y() + bb.height() * this.attribute('y2').numValue()\n : this.attribute('y2').toPixels('y'));\n\n if (x1 === x2 && y1 === y2) return null;\n return ctx.createLinearGradient(x1, y1, x2, y2);\n }\n };\n\n // radial gradient element\n svg.Element.radialGradient = class extends svg.Element.GradientBase {\n getGradient (ctx, element) {\n const useBB = this.gradientUnits === 'objectBoundingBox' && element.getBoundingBox;\n const bb = useBB ? element.getBoundingBox() : null;\n\n if (!this.attribute('cx').hasValue()) this.attribute('cx', true).value = '50%';\n if (!this.attribute('cy').hasValue()) this.attribute('cy', true).value = '50%';\n if (!this.attribute('r').hasValue()) this.attribute('r', true).value = '50%';\n\n const cx = (useBB\n ? bb.x() + bb.width() * this.attribute('cx').numValue()\n : this.attribute('cx').toPixels('x'));\n const cy = (useBB\n ? bb.y() + bb.height() * this.attribute('cy').numValue()\n : this.attribute('cy').toPixels('y'));\n\n let fx = cx;\n let fy = cy;\n if (this.attribute('fx').hasValue()) {\n fx = (useBB\n ? bb.x() + bb.width() * this.attribute('fx').numValue()\n : this.attribute('fx').toPixels('x'));\n }\n if (this.attribute('fy').hasValue()) {\n fy = (useBB\n ? bb.y() + bb.height() * this.attribute('fy').numValue()\n : this.attribute('fy').toPixels('y'));\n }\n\n const r = (useBB\n ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()\n : this.attribute('r').toPixels());\n\n return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);\n }\n };\n\n // gradient stop element\n svg.Element.stop = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.offset = this.attribute('offset').numValue();\n if (this.offset < 0) this.offset = 0;\n if (this.offset > 1) this.offset = 1;\n\n let stopColor = this.style('stop-color');\n if (this.style('stop-opacity').hasValue()) {\n stopColor = stopColor.addOpacity(this.style('stop-opacity'));\n }\n this.color = stopColor.value;\n }\n };\n\n // animation base element\n svg.Element.AnimateBase = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n svg.Animations.push(this);\n\n this.duration = 0.0;\n this.begin = this.attribute('begin').toMilliseconds();\n this.maxDuration = this.begin + this.attribute('dur').toMilliseconds();\n\n this.initialValue = null;\n this.initialUnits = '';\n this.removed = false;\n\n this.from = this.attribute('from');\n this.to = this.attribute('to');\n this.values = this.attribute('values');\n if (this.values.hasValue()) this.values.value = this.values.value.split(';');\n }\n\n getProperty () {\n const attributeType = this.attribute('attributeType').value;\n const attributeName = this.attribute('attributeName').value;\n\n if (attributeType === 'CSS') {\n return this.parent.style(attributeName, true);\n }\n return this.parent.attribute(attributeName, true);\n }\n\n calcValue () {\n // OVERRIDE ME!\n return '';\n }\n\n update (delta) {\n // set initial value\n if (isNullish(this.initialValue)) {\n this.initialValue = this.getProperty().value;\n this.initialUnits = this.getProperty().getUnits();\n }\n\n // if we're past the end time\n if (this.duration > this.maxDuration) {\n // loop for indefinitely repeating animations\n if (this.attribute('repeatCount').value === 'indefinite' ||\n this.attribute('repeatDur').value === 'indefinite') {\n this.duration = 0.0;\n } else if (this.attribute('fill').valueOrDefault('remove') === 'freeze' && !this.frozen) {\n this.frozen = true;\n this.parent.animationFrozen = true;\n this.parent.animationFrozenValue = this.getProperty().value;\n } else if (this.attribute('fill').valueOrDefault('remove') === 'remove' && !this.removed) {\n this.removed = true;\n this.getProperty().value = this.parent.animationFrozen ? this.parent.animationFrozenValue : this.initialValue;\n return true;\n }\n return false;\n }\n this.duration += delta;\n\n // if we're past the begin time\n let updated = false;\n if (this.begin < this.duration) {\n let newValue = this.calcValue(); // tween\n\n if (this.attribute('type').hasValue()) {\n // for transform, etc.\n const type = this.attribute('type').value;\n newValue = type + '(' + newValue + ')';\n }\n\n this.getProperty().value = newValue;\n updated = true;\n }\n\n return updated;\n }\n\n // fraction of duration we've covered\n progress () {\n const ret = {progress: (this.duration - this.begin) / (this.maxDuration - this.begin)};\n if (this.values.hasValue()) {\n const p = ret.progress * (this.values.value.length - 1);\n const lb = Math.floor(p), ub = Math.ceil(p);\n ret.from = new svg.Property('from', Number.parseFloat(this.values.value[lb]));\n ret.to = new svg.Property('to', Number.parseFloat(this.values.value[ub]));\n ret.progress = (p - lb) / (ub - lb);\n } else {\n ret.from = this.from;\n ret.to = this.to;\n }\n return ret;\n }\n };\n\n // animate element\n svg.Element.animate = class extends svg.Element.AnimateBase {\n calcValue () {\n const p = this.progress();\n\n // tween value linearly\n const newValue = p.from.numValue() + (p.to.numValue() - p.from.numValue()) * p.progress;\n return newValue + this.initialUnits;\n }\n };\n\n // animate color element\n svg.Element.animateColor = class extends svg.Element.AnimateBase {\n calcValue () {\n const p = this.progress();\n const from = new RGBColor(p.from.value);\n const to = new RGBColor(p.to.value);\n\n if (from.ok && to.ok) {\n // tween color linearly\n const r = from.r + (to.r - from.r) * p.progress;\n const g = from.g + (to.g - from.g) * p.progress;\n const b = from.b + (to.b - from.b) * p.progress;\n return 'rgb(' + Number.parseInt(r) + ',' + Number.parseInt(g) + ',' + Number.parseInt(b) + ')';\n }\n return this.attribute('from').value;\n }\n };\n\n // animate transform element\n svg.Element.animateTransform = class extends svg.Element.animate {\n calcValue () {\n const p = this.progress();\n\n // tween value linearly\n const from = svg.ToNumberArray(p.from.value);\n const to = svg.ToNumberArray(p.to.value);\n let newValue = '';\n from.forEach((fr, i) => {\n newValue += fr + (to[i] - fr) * p.progress + ' ';\n });\n return newValue;\n }\n };\n\n // font element\n svg.Element.font = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.horizAdvX = this.attribute('horiz-adv-x').numValue();\n\n this.isRTL = false;\n this.isArabic = false;\n this.fontFace = null;\n this.missingGlyph = null;\n this.glyphs = [];\n this.children.forEach((child) => {\n if (child.type === 'font-face') {\n this.fontFace = child;\n if (child.style('font-family').hasValue()) {\n svg.Definitions[child.style('font-family').value] = this;\n }\n } else if (child.type === 'missing-glyph') {\n this.missingGlyph = child;\n } else if (child.type === 'glyph') {\n if (child.arabicForm !== '') {\n this.isRTL = true;\n this.isArabic = true;\n if (typeof this.glyphs[child.unicode] === 'undefined') {\n this.glyphs[child.unicode] = [];\n }\n this.glyphs[child.unicode][child.arabicForm] = child;\n } else {\n this.glyphs[child.unicode] = child;\n }\n }\n });\n }\n };\n\n // font-face element\n svg.Element.fontface = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.ascent = this.attribute('ascent').value;\n this.descent = this.attribute('descent').value;\n this.unitsPerEm = this.attribute('units-per-em').numValue();\n }\n };\n\n // missing-glyph element\n svg.Element.missingglyph = class extends svg.Element.path {\n constructor (node) {\n super(node);\n\n this.horizAdvX = 0;\n }\n };\n\n // glyph element\n svg.Element.glyph = class extends svg.Element.path {\n constructor (node) {\n super(node);\n\n this.horizAdvX = this.attribute('horiz-adv-x').numValue();\n this.unicode = this.attribute('unicode').value;\n this.arabicForm = this.attribute('arabic-form').value;\n }\n };\n\n // text element\n svg.Element.text = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node, true);\n }\n\n setContext (ctx) {\n super.setContext(ctx);\n\n let textBaseline = this.style('dominant-baseline').toTextBaseline();\n if (isNullish(textBaseline)) textBaseline = this.style('alignment-baseline').toTextBaseline();\n if (!isNullish(textBaseline)) ctx.textBaseline = textBaseline;\n }\n\n getBoundingBox () {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n return new svg.BoundingBox(x, y - fontSize, x + Math.floor(fontSize * 2.0 / 3.0) * this.children[0].getText().length, y);\n }\n\n renderChildren (ctx) {\n this.x = this.attribute('x').toPixels('x');\n this.y = this.attribute('y').toPixels('y');\n this.x += this.getAnchorDelta(ctx, this, 0);\n this.children.forEach((child, i) => {\n this.renderChild(ctx, this, i);\n });\n }\n\n getAnchorDelta (ctx, parent, startI) {\n const textAnchor = this.style('text-anchor').valueOrDefault('start');\n if (textAnchor !== 'start') {\n let width = 0;\n for (let i = startI; i < parent.children.length; i++) {\n const child = parent.children[i];\n if (i > startI && child.attribute('x').hasValue()) break; // new group\n width += child.measureTextRecursive(ctx);\n }\n return -1 * (textAnchor === 'end' ? width : width / 2.0);\n }\n return 0;\n }\n\n renderChild (ctx, parent, i) {\n const child = parent.children[i];\n if (child.attribute('x').hasValue()) {\n child.x = child.attribute('x').toPixels('x') + this.getAnchorDelta(ctx, parent, i);\n if (child.attribute('dx').hasValue()) child.x += child.attribute('dx').toPixels('x');\n } else {\n if (this.attribute('dx').hasValue()) this.x += this.attribute('dx').toPixels('x');\n if (child.attribute('dx').hasValue()) this.x += child.attribute('dx').toPixels('x');\n child.x = this.x;\n }\n this.x = child.x + child.measureText(ctx);\n\n if (child.attribute('y').hasValue()) {\n child.y = child.attribute('y').toPixels('y');\n if (child.attribute('dy').hasValue()) child.y += child.attribute('dy').toPixels('y');\n } else {\n if (this.attribute('dy').hasValue()) this.y += this.attribute('dy').toPixels('y');\n if (child.attribute('dy').hasValue()) this.y += child.attribute('dy').toPixels('y');\n child.y = this.y;\n }\n this.y = child.y;\n\n child.render(ctx);\n\n for (let j = 0; j < child.children.length; j++) {\n this.renderChild(ctx, child, j);\n }\n }\n };\n\n // text base\n svg.Element.TextElementBase = class extends svg.Element.RenderedElementBase {\n getGlyph (font, text, i) {\n const c = text[i];\n let glyph = null;\n if (font.isArabic) {\n let arabicForm = 'isolated';\n if ((i === 0 || text[i - 1] === ' ') && i < text.length - 2 && text[i + 1] !== ' ') arabicForm = 'terminal';\n if (i > 0 && text[i - 1] !== ' ' && i < text.length - 2 && text[i + 1] !== ' ') arabicForm = 'medial';\n if (i > 0 && text[i - 1] !== ' ' && (i === text.length - 1 || text[i + 1] === ' ')) arabicForm = 'initial';\n if (typeof font.glyphs[c] !== 'undefined') {\n glyph = font.glyphs[c][arabicForm];\n if (isNullish(glyph) && font.glyphs[c].type === 'glyph') glyph = font.glyphs[c];\n }\n } else {\n glyph = font.glyphs[c];\n }\n if (isNullish(glyph)) glyph = font.missingGlyph;\n return glyph;\n }\n\n renderChildren (ctx) {\n const customFont = this.parent.style('font-family').getDefinition();\n if (!isNullish(customFont)) {\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n const fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);\n let text = this.getText();\n if (customFont.isRTL) text = text.split('').reverse().join('');\n\n const dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n for (let i = 0; i < text.length; i++) {\n const glyph = this.getGlyph(customFont, text, i);\n const scale = fontSize / customFont.fontFace.unitsPerEm;\n ctx.translate(this.x, this.y);\n ctx.scale(scale, -scale);\n const lw = ctx.lineWidth;\n ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;\n if (fontStyle === 'italic') ctx.transform(1, 0, 0.4, 1, 0, 0);\n glyph.render(ctx);\n if (fontStyle === 'italic') ctx.transform(1, 0, -0.4, 1, 0, 0);\n ctx.lineWidth = lw;\n ctx.scale(1 / scale, -1 / scale);\n ctx.translate(-this.x, -this.y);\n\n this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;\n if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {\n this.x += dx[i];\n }\n }\n return;\n }\n\n if (ctx.fillStyle !== '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);\n if (ctx.strokeStyle !== '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);\n }\n\n getText () {\n // OVERRIDE ME\n }\n\n measureTextRecursive (ctx) {\n let width = this.measureText(ctx);\n this.children.forEach((child) => {\n width += child.measureTextRecursive(ctx);\n });\n return width;\n }\n\n measureText (ctx) {\n const customFont = this.parent.style('font-family').getDefinition();\n if (!isNullish(customFont)) {\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n let measure = 0;\n let text = this.getText();\n if (customFont.isRTL) text = text.split('').reverse().join('');\n const dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n for (let i = 0; i < text.length; i++) {\n const glyph = this.getGlyph(customFont, text, i);\n measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;\n if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {\n measure += dx[i];\n }\n }\n return measure;\n }\n\n const textToMeasure = svg.compressSpaces(this.getText());\n if (!ctx.measureText) return textToMeasure.length * 10;\n\n ctx.save();\n this.setContext(ctx);\n const {width} = ctx.measureText(textToMeasure);\n ctx.restore();\n return width;\n }\n };\n\n // tspan\n svg.Element.tspan = class extends svg.Element.TextElementBase {\n constructor (node) {\n super(node, true);\n\n this.text = node.nodeValue || node.text || '';\n }\n getText () {\n return this.text;\n }\n };\n\n // tref\n svg.Element.tref = class extends svg.Element.TextElementBase {\n getText () {\n const element = this.getHrefAttribute().getDefinition();\n if (!isNullish(element)) return element.children[0].getText();\n return undefined;\n }\n };\n\n // a element\n svg.Element.a = class extends svg.Element.TextElementBase {\n constructor (node) {\n super(node);\n\n this.hasText = true;\n [...node.childNodes].forEach((childNode) => {\n if (childNode.nodeType !== 3) {\n this.hasText = false;\n }\n });\n // this might contain text\n this.text = this.hasText ? node.childNodes[0].nodeValue : '';\n }\n\n getText () {\n return this.text;\n }\n\n renderChildren (ctx) {\n if (this.hasText) {\n // render as text element\n super.renderChildren(ctx);\n const fontSize = new svg.Property(\n 'fontSize', svg.Font.Parse(svg.ctx.font).fontSize\n );\n svg.Mouse.checkBoundingBox(\n this, new svg.BoundingBox(\n this.x,\n this.y - fontSize.toPixels('y'),\n this.x + this.measureText(ctx),\n this.y\n )\n );\n } else {\n // render as temporary group\n const g = new svg.Element.g();\n g.children = this.children;\n g.parent = this;\n g.render(ctx);\n }\n }\n\n onclick () {\n window.open(this.getHrefAttribute().value);\n }\n\n onmousemove () {\n svg.ctx.canvas.style.cursor = 'pointer';\n }\n };\n\n // image element\n svg.Element.image = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node);\n\n const href = this.getHrefAttribute().value;\n if (href === '') {\n return;\n }\n this._isSvg = href.match(/\\.svg$/);\n\n svg.Images.push(this);\n this.loaded = false;\n if (!this._isSvg) {\n this.img = document.createElement('img');\n if (svg.opts.useCORS === true) {\n this.img.crossOrigin = 'Anonymous';\n }\n this.img.addEventListener('load', () => {\n this.loaded = true;\n });\n this.img.addEventListener('error', () => {\n svg.log('ERROR: image \"' + href + '\" not found');\n this.loaded = true;\n });\n this.img.src = href;\n } else {\n svg.ajax(href, true).then((img) => { // eslint-disable-line promise/prefer-await-to-then, promise/always-return\n this.img = img;\n this.loaded = true;\n }).catch((err) => { // eslint-disable-line promise/prefer-await-to-callbacks\n this.erred = true;\n console.error('Ajax error for canvg', err); // eslint-disable-line no-console\n });\n }\n }\n renderChildren (ctx) {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n if (width === 0 || height === 0) return;\n\n ctx.save();\n if (this._isSvg) {\n ctx.drawSvg(this.img, x, y, width, height);\n } else {\n ctx.translate(x, y);\n svg.AspectRatio(\n ctx,\n this.attribute('preserveAspectRatio').value,\n width,\n this.img.width,\n height,\n this.img.height,\n 0,\n 0\n );\n ctx.drawImage(this.img, 0, 0);\n }\n ctx.restore();\n }\n\n getBoundingBox () {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n return new svg.BoundingBox(x, y, x + width, y + height);\n }\n };\n\n // group element\n svg.Element.g = class extends svg.Element.RenderedElementBase {\n getBoundingBox () {\n const bb = new svg.BoundingBox();\n this.children.forEach((child) => {\n bb.addBoundingBox(child.getBoundingBox());\n });\n return bb;\n }\n };\n\n // symbol element\n svg.Element.symbol = class extends svg.Element.RenderedElementBase {\n render (ctx) {\n // NO RENDER\n }\n };\n\n // style element\n svg.Element.style = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n // text, or spaces then CDATA\n let css = '';\n [...node.childNodes].forEach(({nodeValue}) => {\n css += nodeValue;\n });\n // remove comments\n css = css.replace(/(\\/\\*([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+\\/)|(^\\s*\\/\\/.*)/gm, ''); // eslint-disable-line unicorn/no-unsafe-regex\n // replace whitespace\n css = svg.compressSpaces(css);\n const cssDefs = css.split('}');\n cssDefs.forEach((cssDef) => {\n if (svg.trim(cssDef) !== '') {\n let [cssClasses, cssProps] = cssDef.split('{');\n cssClasses = cssClasses.split(',');\n cssProps = cssProps.split(';');\n cssClasses.forEach((cssClass) => {\n cssClass = svg.trim(cssClass);\n if (cssClass !== '') {\n const props = {};\n cssProps.forEach((cssProp) => {\n const prop = cssProp.indexOf(':');\n const name = cssProp.substr(0, prop);\n const value = cssProp.substr(prop + 1, cssProp.length - prop);\n if (!isNullish(name) && !isNullish(value)) {\n props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));\n }\n });\n svg.Styles[cssClass] = props;\n if (cssClass === '@font-face') {\n const fontFamily = props['font-family'].value.replace(/\"/g, '');\n const srcs = props.src.value.split(',');\n srcs.forEach((src) => {\n if (src.includes('format(\"svg\")')) {\n const urlStart = src.indexOf('url');\n const urlEnd = src.indexOf(')', urlStart);\n const url = src.substr(urlStart + 5, urlEnd - urlStart - 6);\n // Can this ajax safely be converted to async?\n const doc = svg.parseXml(svg.ajax(url));\n const fonts = doc.getElementsByTagName('font');\n [...fonts].forEach((font) => {\n font = svg.CreateElement(font);\n svg.Definitions[fontFamily] = font;\n });\n }\n });\n }\n }\n });\n }\n });\n }\n };\n\n // use element\n svg.Element.use = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node);\n\n this._el = this.getHrefAttribute().getDefinition();\n }\n\n setContext (ctx) {\n super.setContext(ctx);\n if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').toPixels('x'), 0);\n if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').toPixels('y'));\n }\n\n path (ctx) {\n const {_el: element} = this;\n if (!isNullish(element)) element.path(ctx);\n }\n\n getBoundingBox () {\n const {_el: element} = this;\n if (!isNullish(element)) return element.getBoundingBox();\n return undefined;\n }\n\n renderChildren (ctx) {\n const {_el: element} = this;\n if (!isNullish(element)) {\n let tempSvg = element;\n if (element.type === 'symbol') {\n // render me using a temporary svg element in symbol cases\n // (https://www.w3.org/TR/SVG/struct.html#UseElement)\n tempSvg = new svg.Element.svg();\n tempSvg.type = 'svg';\n tempSvg.attributes.viewBox = new svg.Property(\n 'viewBox', element.attribute('viewBox').value\n );\n tempSvg.attributes.preserveAspectRatio = new svg.Property(\n 'preserveAspectRatio', element.attribute('preserveAspectRatio').value\n );\n tempSvg.attributes.overflow = new svg.Property(\n 'overflow', element.attribute('overflow').value\n );\n tempSvg.children = element.children;\n }\n if (tempSvg.type === 'svg') {\n // if symbol or svg, inherit width/height from me\n if (this.attribute('width').hasValue()) {\n tempSvg.attributes.width = new svg.Property(\n 'width', this.attribute('width').value\n );\n }\n if (this.attribute('height').hasValue()) {\n tempSvg.attributes.height = new svg.Property(\n 'height', this.attribute('height').value\n );\n }\n }\n const oldParent = tempSvg.parent;\n tempSvg.parent = null;\n tempSvg.render(ctx);\n tempSvg.parent = oldParent;\n }\n }\n };\n\n // mask element\n svg.Element.mask = class extends svg.Element.ElementBase {\n apply (ctx, element) {\n // render as temp svg\n let x = this.attribute('x').toPixels('x');\n let y = this.attribute('y').toPixels('y');\n let width = this.attribute('width').toPixels('x');\n let height = this.attribute('height').toPixels('y');\n\n if (width === 0 && height === 0) {\n const bb = new svg.BoundingBox();\n this.children.forEach((child) => {\n bb.addBoundingBox(child.getBoundingBox());\n });\n x = Math.floor(bb.x1);\n y = Math.floor(bb.y1);\n width = Math.floor(bb.width());\n height = Math.floor(bb.height());\n }\n\n // temporarily remove mask to avoid recursion\n const mask = element.attribute('mask').value;\n element.attribute('mask').value = '';\n\n const cMask = document.createElement('canvas');\n cMask.width = x + width;\n cMask.height = y + height;\n const maskCtx = cMask.getContext('2d');\n this.renderChildren(maskCtx);\n\n const c = document.createElement('canvas');\n c.width = x + width;\n c.height = y + height;\n const tempCtx = c.getContext('2d');\n element.render(tempCtx);\n tempCtx.globalCompositeOperation = 'destination-in';\n tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');\n tempCtx.fillRect(0, 0, x + width, y + height);\n\n ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');\n ctx.fillRect(0, 0, x + width, y + height);\n\n // reassign mask\n element.attribute('mask').value = mask;\n }\n\n render (ctx) {\n // NO RENDER\n }\n };\n\n // clip element\n svg.Element.clipPath = class extends svg.Element.ElementBase {\n apply (ctx) {\n this.children.forEach((child) => {\n if (typeof child.path !== 'undefined') {\n let transform = null;\n if (child.attribute('transform').hasValue()) {\n transform = new svg.Transform(child.attribute('transform').value);\n transform.apply(ctx);\n }\n child.path(ctx);\n ctx.clip();\n if (transform) { transform.unapply(ctx); }\n }\n });\n }\n render (ctx) {\n // NO RENDER\n }\n };\n\n // filters\n svg.Element.filter = class extends svg.Element.ElementBase {\n apply (ctx, element) {\n // render as temp svg\n const bb = element.getBoundingBox();\n const x = Math.floor(bb.x1);\n const y = Math.floor(bb.y1);\n const width = Math.floor(bb.width());\n const height = Math.floor(bb.height());\n\n // temporarily remove filter to avoid recursion\n const filter = element.style('filter').value;\n element.style('filter').value = '';\n\n let px = 0, py = 0;\n this.children.forEach((child) => {\n const efd = child.extraFilterDistance || 0;\n px = Math.max(px, efd);\n py = Math.max(py, efd);\n });\n\n const c = document.createElement('canvas');\n c.width = width + 2 * px;\n c.height = height + 2 * py;\n const tempCtx = c.getContext('2d');\n tempCtx.translate(-x + px, -y + py);\n element.render(tempCtx);\n\n // apply filters\n this.children.forEach((child) => {\n child.apply(tempCtx, 0, 0, width + 2 * px, height + 2 * py);\n });\n\n // render on me\n ctx.drawImage(c, 0, 0, width + 2 * px, height + 2 * py, x - px, y - py, width + 2 * px, height + 2 * py);\n\n // reassign filter\n element.style('filter', true).value = filter;\n }\n\n render (ctx) {\n // NO RENDER\n }\n };\n\n svg.Element.feMorphology = class extends svg.Element.ElementBase {\n apply (ctx, x, y, width, height) {\n // TODO: implement\n }\n };\n\n svg.Element.feComposite = class extends svg.Element.ElementBase {\n apply (ctx, x, y, width, height) {\n // TODO: implement\n }\n };\n\n /**\n * @param {Uint8ClampedArray} img\n * @param {Integer} x\n * @param {Integer} y\n * @param {Float} width\n * @param {Float} height\n * @param {Integer} rgba\n * @returns {Integer}\n */\n function imGet (img, x, y, width, height, rgba) {\n return img[y * width * 4 + x * 4 + rgba];\n }\n\n /**\n * @param {Uint8ClampedArray} img\n * @param {Integer} x\n * @param {Integer} y\n * @param {Float} width\n * @param {Float} height\n * @param {Integer} rgba\n * @param {Float} val\n * @returns {void}\n */\n function imSet (img, x, y, width, height, rgba, val) {\n img[y * width * 4 + x * 4 + rgba] = val;\n }\n\n svg.Element.feColorMatrix = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n let matrix = svg.ToNumberArray(this.attribute('values').value);\n switch (this.attribute('type').valueOrDefault('matrix')) { // https://www.w3.org/TR/SVG/filters.html#feColorMatrixElement\n case 'saturate': {\n const s = matrix[0];\n matrix = [\n 0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s, 0, 0,\n 0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s, 0, 0,\n 0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s, 0, 0,\n 0, 0, 0, 1, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n } case 'hueRotate': {\n const a = matrix[0] * Math.PI / 180.0;\n const c = function (m1, m2, m3) {\n return m1 + Math.cos(a) * m2 + Math.sin(a) * m3;\n };\n matrix = [\n c(0.213, 0.787, -0.213), c(0.715, -0.715, -0.715), c(0.072, -0.072, 0.928), 0, 0,\n c(0.213, -0.213, 0.143), c(0.715, 0.285, 0.140), c(0.072, -0.072, -0.283), 0, 0,\n c(0.213, -0.213, -0.787), c(0.715, -0.715, 0.715), c(0.072, 0.928, 0.072), 0, 0,\n 0, 0, 0, 1, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n } case 'luminanceToAlpha':\n matrix = [\n 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0,\n 0.2125, 0.7154, 0.0721, 0, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n }\n this.matrix = matrix;\n\n this._m = (i, v) => {\n const mi = matrix[i];\n return mi * (mi < 0 ? v - 255 : v);\n };\n }\n apply (ctx, x, y, width, height) {\n const {_m: m} = this;\n // assuming x==0 && y==0 for now\n const srcData = ctx.getImageData(0, 0, width, height);\n for (let _y = 0; _y < height; _y++) {\n for (let _x = 0; _x < width; _x++) {\n const r = imGet(srcData.data, _x, _y, width, height, 0);\n const g = imGet(srcData.data, _x, _y, width, height, 1);\n const b = imGet(srcData.data, _x, _y, width, height, 2);\n const a = imGet(srcData.data, _x, _y, width, height, 3);\n imSet(srcData.data, _x, _y, width, height, 0, m(0, r) + m(1, g) + m(2, b) + m(3, a) + m(4, 1));\n imSet(srcData.data, _x, _y, width, height, 1, m(5, r) + m(6, g) + m(7, b) + m(8, a) + m(9, 1));\n imSet(srcData.data, _x, _y, width, height, 2, m(10, r) + m(11, g) + m(12, b) + m(13, a) + m(14, 1));\n imSet(srcData.data, _x, _y, width, height, 3, m(15, r) + m(16, g) + m(17, b) + m(18, a) + m(19, 1));\n }\n }\n ctx.clearRect(0, 0, width, height);\n ctx.putImageData(srcData, 0, 0);\n }\n };\n\n svg.Element.feGaussianBlur = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.blurRadius = Math.floor(this.attribute('stdDeviation').numValue());\n this.extraFilterDistance = this.blurRadius;\n }\n\n apply (ctx, x, y, width, height) {\n // Todo: This might not be a problem anymore with out `instanceof` fix\n // StackBlur requires canvas be on document\n ctx.canvas.id = svg.UniqueId();\n ctx.canvas.style.display = 'none';\n document.body.append(ctx.canvas);\n canvasRGBA(ctx.canvas, x, y, width, height, this.blurRadius);\n ctx.canvas.remove();\n }\n };\n\n // title element, do nothing\n svg.Element.title = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n }\n };\n\n // desc element, do nothing\n svg.Element.desc = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n }\n };\n\n svg.Element.MISSING = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n svg.log('ERROR: Element \\'' + node.nodeName + '\\' not yet implemented.');\n }\n };\n\n // element factory\n svg.CreateElement = function (node) {\n const className = node.nodeName\n .replace(/^[^:]+:/, '') // remove namespace\n .replace(/-/g, ''); // remove dashes\n let e;\n if (typeof svg.Element[className] !== 'undefined') {\n e = new svg.Element[className](node);\n } else {\n e = new svg.Element.MISSING(node);\n }\n\n e.type = node.nodeName;\n return e;\n };\n\n // load from url\n svg.load = async function (ctx, url) {\n const dom = await svg.ajax(url, true);\n return svg.loadXml(ctx, dom);\n };\n\n // load from xml\n svg.loadXml = function (ctx, xml) {\n return svg.loadXmlDoc(ctx, svg.parseXml(xml));\n };\n\n svg.loadXmlDoc = function (ctx, dom) {\n let res;\n svg.init(ctx);\n\n const mapXY = function (p) {\n let e = ctx.canvas;\n while (e) {\n p.x -= e.offsetLeft;\n p.y -= e.offsetTop;\n e = e.offsetParent;\n }\n if (window.scrollX) p.x += window.scrollX;\n if (window.scrollY) p.y += window.scrollY;\n return p;\n };\n\n // bind mouse\n if (svg.opts.ignoreMouse !== true) {\n ctx.canvas.addEventListener('click', function (e) {\n const args = !isNullish(e)\n ? [e.clientX, e.clientY]\n : [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals\n const {x, y} = mapXY(new svg.Point(...args));\n svg.Mouse.onclick(x, y);\n });\n ctx.canvas.addEventListener('mousemove', function (e) {\n const args = !isNullish(e)\n ? [e.clientX, e.clientY]\n : [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals\n const {x, y} = mapXY(new svg.Point(...args));\n svg.Mouse.onmousemove(x, y);\n });\n }\n\n const e = svg.CreateElement(dom.documentElement);\n e.root = true;\n\n // render loop\n let isFirstRender = true;\n const draw = function (resolve) {\n svg.ViewPort.Clear();\n if (ctx.canvas.parentNode) {\n svg.ViewPort.SetCurrent(\n ctx.canvas.parentNode.clientWidth,\n ctx.canvas.parentNode.clientHeight\n );\n }\n\n if (svg.opts.ignoreDimensions !== true) {\n // set canvas size\n if (e.style('width').hasValue()) {\n ctx.canvas.width = e.style('width').toPixels('x');\n ctx.canvas.style.width = ctx.canvas.width + 'px';\n }\n if (e.style('height').hasValue()) {\n ctx.canvas.height = e.style('height').toPixels('y');\n ctx.canvas.style.height = ctx.canvas.height + 'px';\n }\n }\n let cWidth = ctx.canvas.clientWidth || ctx.canvas.width;\n let cHeight = ctx.canvas.clientHeight || ctx.canvas.height;\n if (svg.opts.ignoreDimensions === true &&\n e.style('width').hasValue() && e.style('height').hasValue()\n ) {\n cWidth = e.style('width').toPixels('x');\n cHeight = e.style('height').toPixels('y');\n }\n svg.ViewPort.SetCurrent(cWidth, cHeight);\n\n if (!isNullish(svg.opts.offsetX)) {\n e.attribute('x', true).value = svg.opts.offsetX;\n }\n if (!isNullish(svg.opts.offsetY)) {\n e.attribute('y', true).value = svg.opts.offsetY;\n }\n if (!isNullish(svg.opts.scaleWidth) || !isNullish(svg.opts.scaleHeight)) {\n const viewBox = svg.ToNumberArray(e.attribute('viewBox').value);\n let xRatio = null, yRatio = null;\n\n if (!isNullish(svg.opts.scaleWidth)) {\n if (e.attribute('width').hasValue()) {\n xRatio = e.attribute('width').toPixels('x') / svg.opts.scaleWidth;\n } else if (!isNaN(viewBox[2])) {\n xRatio = viewBox[2] / svg.opts.scaleWidth;\n }\n }\n\n if (!isNullish(svg.opts.scaleHeight)) {\n if (e.attribute('height').hasValue()) {\n yRatio = e.attribute('height').toPixels('y') / svg.opts.scaleHeight;\n } else if (!isNaN(viewBox[3])) {\n yRatio = viewBox[3] / svg.opts.scaleHeight;\n }\n }\n\n if (isNullish(xRatio)) { xRatio = yRatio; }\n if (isNullish(yRatio)) { yRatio = xRatio; }\n\n e.attribute('width', true).value = svg.opts.scaleWidth;\n e.attribute('height', true).value = svg.opts.scaleHeight;\n e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);\n e.attribute('preserveAspectRatio', true).value = 'none';\n }\n\n // clear and render\n if (svg.opts.ignoreClear !== true) {\n ctx.clearRect(0, 0, cWidth, cHeight);\n }\n e.render(ctx);\n if (isFirstRender) {\n isFirstRender = false;\n resolve(dom);\n }\n };\n\n let waitingForImages = true;\n svg.intervalID = setInterval(function () {\n let needUpdate = false;\n\n if (waitingForImages && svg.ImagesLoaded()) {\n waitingForImages = false;\n needUpdate = true;\n }\n\n // need update from mouse events?\n if (svg.opts.ignoreMouse !== true) {\n needUpdate = needUpdate || svg.Mouse.hasEvents();\n }\n\n // need update from animations?\n if (svg.opts.ignoreAnimation !== true) {\n svg.Animations.forEach((animation) => {\n const needAnimationUpdate = animation.update(1000 / svg.FRAMERATE);\n needUpdate = needUpdate || needAnimationUpdate;\n });\n }\n\n // need update from redraw?\n if (typeof svg.opts.forceRedraw === 'function') {\n if (svg.opts.forceRedraw() === true) {\n needUpdate = true;\n }\n }\n\n // render if needed\n if (needUpdate) {\n draw(res);\n svg.Mouse.runEvents(); // run and clear our events\n }\n }, 1000 / svg.FRAMERATE);\n // Todo: Replace with an image loading Promise utility?\n // eslint-disable-next-line promise/avoid-new\n return new Promise((resolve, reject) => {\n if (svg.ImagesLoaded()) {\n waitingForImages = false;\n draw(resolve);\n return;\n }\n res = resolve;\n });\n };\n\n svg.stop = () => {\n if (svg.intervalID) {\n clearInterval(svg.intervalID);\n }\n };\n\n svg.Mouse = {\n events: [],\n hasEvents () { return this.events.length !== 0; },\n\n onclick (x, y) {\n this.events.push({\n type: 'onclick', x, y,\n run (e) { if (e.onclick) e.onclick(); }\n });\n },\n\n onmousemove (x, y) {\n this.events.push({\n type: 'onmousemove', x, y,\n run (e) { if (e.onmousemove) e.onmousemove(); }\n });\n },\n\n eventElements: [],\n\n checkPath (element, ctx) {\n this.events.forEach(({x, y}, i) => {\n if (ctx.isPointInPath && ctx.isPointInPath(x, y)) {\n this.eventElements[i] = element;\n }\n });\n },\n\n checkBoundingBox (element, bb) {\n this.events.forEach(({x, y}, i) => {\n if (bb.isPointInBox(x, y)) {\n this.eventElements[i] = element;\n }\n });\n },\n\n runEvents () {\n svg.ctx.canvas.style.cursor = '';\n\n this.events.forEach((e, i) => {\n let element = this.eventElements[i];\n while (element) {\n e.run(element);\n element = element.parent;\n }\n });\n\n // done running, clear\n this.events = [];\n this.eventElements = [];\n }\n };\n\n return svg;\n}\n\nif (typeof CanvasRenderingContext2D !== 'undefined') {\n CanvasRenderingContext2D.prototype.drawSvg = function (s, dx, dy, dw, dh) {\n canvg(this.canvas, s, {\n ignoreMouse: true,\n ignoreAnimation: true,\n ignoreDimensions: true,\n ignoreClear: true,\n offsetX: dx,\n offsetY: dy,\n scaleWidth: dw,\n scaleHeight: dh\n });\n };\n}\n","/**\n * @file ext-server_opensave.js\n *\n * @license MIT\n *\n * @copyright 2010 Alexis Deveria\n *\n */\nimport {canvg} from '../canvg/canvg.js';\n\nexport default {\n name: 'server_opensave',\n async init ({$, decode64, encode64, importLocale}) {\n const strings = await importLocale();\n const svgEditor = this;\n const {\n curConfig: {\n extPath,\n avoidClientSide, // Deprecated\n avoidClientSideDownload, avoidClientSideOpen\n },\n canvas: svgCanvas\n } = svgEditor;\n\n /**\n *\n * @returns {string}\n */\n function getFileNameFromTitle () {\n const title = svgCanvas.getDocumentTitle();\n // We convert (to underscore) only those disallowed Win7 file name characters\n return title.trim().replace(/[/\\\\:*?\"<>|]/g, '_');\n }\n /**\n * Escapes XML predefined entities for quoted attributes.\n * @param {string} str\n * @returns {string}\n */\n function xhtmlEscape (str) {\n return str.replace(/&(?!amp;)/g, '&').replace(/\"/g, '"').replace(/')[0].download === '';\n let a;\n if (support) {\n a = $('hidden').attr({\n download: (filename || 'image') + suffix,\n href: uri\n }).css('display', 'none').appendTo('body');\n a[0].click();\n return true;\n }\n return false;\n }\n const\n saveSvgAction = extPath + 'filesave.php',\n saveImgAction = extPath + 'filesave.php';\n // Create upload target (hidden iframe)\n\n let cancelled = false;\n\n // Hiding by size instead of display to avoid FF console errors\n // with `getBBox` in browser.js `supportsPathBBox_`)\n $(\n `\\d{1,3})\\)$/,\n example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b));\n }\n },\n {\n re: /^(\\w{2})(\\w{2})(\\w{2})$/,\n // re: /^(?\\w{2})(?\\w{2})(?\\w{2})$/,\n example: ['#00ff00', '336699'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b, 16));\n }\n },\n {\n re: /^(\\w)(\\w)(\\w)$/,\n // re: /^(?\\w{1})(?\\w{1})(?\\w{1})$/,\n example: ['#fb0', 'f0f'],\n process (_, ...bits) {\n return bits.map((b) => Number.parseInt(b + b, 16));\n }\n }\n];\n\n/**\n * A class to parse color values.\n */\nexport default class RGBColor {\n /**\n * @param {string} colorString\n */\n constructor (colorString) {\n this.ok = false;\n\n // strip any leading #\n if (colorString.charAt(0) === '#') { // remove # if any\n colorString = colorString.substr(1, 6);\n }\n\n colorString = colorString.replace(/ /g, '');\n colorString = colorString.toLowerCase();\n\n // before getting into regexps, try simple matches\n // and overwrite the input\n if (colorString in simpleColors) {\n colorString = simpleColors[colorString];\n }\n // end of simple type-in colors\n\n // search through the definitions to find a match\n\n colorDefs.forEach(({re, process: processor}) => {\n const bits = re.exec(colorString);\n if (bits) {\n const [r, g, b] = processor(...bits);\n Object.assign(this, {r, g, b});\n this.ok = true;\n }\n });\n\n // validate/cleanup values\n this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);\n this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);\n this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);\n }\n\n // some getters\n /**\n * @returns {string}\n */\n toRGB () {\n return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';\n }\n\n /**\n * @returns {string}\n */\n toHex () {\n let r = this.r.toString(16);\n let g = this.g.toString(16);\n let b = this.b.toString(16);\n if (r.length === 1) { r = '0' + r; }\n if (g.length === 1) { g = '0' + g; }\n if (b.length === 1) { b = '0' + b; }\n return '#' + r + g + b;\n }\n\n /**\n * Offers a bulleted list of help.\n * @returns {HTMLUListElement}\n */\n static getHelpXML () {\n const examples = [\n // add regexps\n ...colorDefs.flatMap(({example}) => {\n return example;\n }),\n // add type-in colors\n ...Object.keys(simpleColors)\n ];\n\n const xml = document.createElement('ul');\n xml.setAttribute('id', 'rgbcolor-examples');\n\n xml.append(...examples.map((example) => {\n try {\n const listItem = document.createElement('li');\n const listColor = new RGBColor(example);\n const exampleDiv = document.createElement('div');\n exampleDiv.style.cssText = `\n margin: 3px;\n border: 1px solid black;\n background: ${listColor.toHex()};\n color: ${listColor.toHex()};`;\n exampleDiv.append('test');\n const listItemValue = ` ${example} -> ${listColor.toRGB()} -> ${listColor.toHex()}`;\n listItem.append(exampleDiv, listItemValue);\n return listItem;\n } catch (e) {\n return '';\n }\n }));\n return xml;\n }\n}\n","function _typeof(obj) {\n \"@babel/helpers - typeof\";\n\n if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") {\n _typeof = function (obj) {\n return typeof obj;\n };\n } else {\n _typeof = function (obj) {\n return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj;\n };\n }\n\n return _typeof(obj);\n}\n\nfunction _classCallCheck(instance, Constructor) {\n if (!(instance instanceof Constructor)) {\n throw new TypeError(\"Cannot call a class as a function\");\n }\n}\n\n/* eslint-disable no-bitwise, unicorn/prefer-query-selector */\n\n/**\n* StackBlur - a fast almost Gaussian Blur For Canvas\n*\n* In case you find this class useful - especially in commercial projects -\n* I am not totally unhappy for a small donation to my PayPal account\n* mario@quasimondo.de\n*\n* Or support me on flattr:\n* {@link https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript}.\n*\n* @module StackBlur\n* @author Mario Klingemann\n* Contact: mario@quasimondo.com\n* Website: {@link http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html}\n* Twitter: @quasimondo\n*\n* @copyright (c) 2010 Mario Klingemann\n*\n* Permission is hereby granted, free of charge, to any person\n* obtaining a copy of this software and associated documentation\n* files (the \"Software\"), to deal in the Software without\n* restriction, including without limitation the rights to use,\n* copy, modify, merge, publish, distribute, sublicense, and/or sell\n* copies of the Software, and to permit persons to whom the\n* Software is furnished to do so, subject to the following\n* conditions:\n*\n* The above copyright notice and this permission notice shall be\n* included in all copies or substantial portions of the Software.\n*\n* THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n* OTHER DEALINGS IN THE SOFTWARE.\n*/\n\n/* eslint-disable max-len */\nvar mulTable = [512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259];\nvar shgTable = [9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24];\n/* eslint-enable max-len */\n\n/**\n * @param {string|HTMLImageElement} img\n * @param {string|HTMLCanvasElement} canvas\n * @param {Float} radius\n * @param {boolean} blurAlphaChannel\n * @returns {undefined}\n */\n\nfunction processImage(img, canvas, radius, blurAlphaChannel) {\n if (typeof img === 'string') {\n img = document.getElementById(img);\n }\n\n if (!img || !('naturalWidth' in img)) {\n return;\n }\n\n var w = img.naturalWidth;\n var h = img.naturalHeight;\n\n if (typeof canvas === 'string') {\n canvas = document.getElementById(canvas);\n }\n\n if (!canvas || !('getContext' in canvas)) {\n return;\n }\n\n canvas.style.width = w + 'px';\n canvas.style.height = h + 'px';\n canvas.width = w;\n canvas.height = h;\n var context = canvas.getContext('2d');\n context.clearRect(0, 0, w, h);\n context.drawImage(img, 0, 0);\n\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n if (blurAlphaChannel) {\n processCanvasRGBA(canvas, 0, 0, w, h, radius);\n } else {\n processCanvasRGB(canvas, 0, 0, w, h, radius);\n }\n}\n/**\n * @param {string|HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @throws {Error|TypeError}\n * @returns {ImageData} See {@link https://html.spec.whatwg.org/multipage/canvas.html#imagedata}\n */\n\n\nfunction getImageDataFromCanvas(canvas, topX, topY, width, height) {\n if (typeof canvas === 'string') {\n canvas = document.getElementById(canvas);\n }\n\n if (!canvas || _typeof(canvas) !== 'object' || !('getContext' in canvas)) {\n throw new TypeError('Expecting canvas with `getContext` method ' + 'in processCanvasRGB(A) calls!');\n }\n\n var context = canvas.getContext('2d');\n\n try {\n return context.getImageData(topX, topY, width, height);\n } catch (e) {\n throw new Error('unable to access image data: ' + e);\n }\n}\n/**\n * @param {HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {undefined}\n */\n\n\nfunction processCanvasRGBA(canvas, topX, topY, width, height, radius) {\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n radius |= 0;\n var imageData = getImageDataFromCanvas(canvas, topX, topY, width, height);\n imageData = processImageDataRGBA(imageData, topX, topY, width, height, radius);\n canvas.getContext('2d').putImageData(imageData, topX, topY);\n}\n/**\n * @param {ImageData} imageData\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {ImageData}\n */\n\n\nfunction processImageDataRGBA(imageData, topX, topY, width, height, radius) {\n var pixels = imageData.data;\n var x, y, i, p, yp, yi, yw, rSum, gSum, bSum, aSum, rOutSum, gOutSum, bOutSum, aOutSum, rInSum, gInSum, bInSum, aInSum, pr, pg, pb, pa, rbs;\n var div = 2 * radius + 1; // const w4 = width << 2;\n\n var widthMinus1 = width - 1;\n var heightMinus1 = height - 1;\n var radiusPlus1 = radius + 1;\n var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;\n var stackStart = new BlurStack();\n var stack = stackStart;\n var stackEnd;\n\n for (i = 1; i < div; i++) {\n stack = stack.next = new BlurStack();\n\n if (i === radiusPlus1) {\n stackEnd = stack;\n }\n }\n\n stack.next = stackStart;\n var stackIn = null;\n var stackOut = null;\n yw = yi = 0;\n var mulSum = mulTable[radius];\n var shgSum = shgTable[radius];\n\n for (y = 0; y < height; y++) {\n rInSum = gInSum = bInSum = aInSum = rSum = gSum = bSum = aSum = 0;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n aOutSum = radiusPlus1 * (pa = pixels[yi + 3]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n aSum += sumFactor * pa;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack.a = pa;\n stack = stack.next;\n }\n\n for (i = 1; i < radiusPlus1; i++) {\n p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);\n rSum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[p + 1]) * rbs;\n bSum += (stack.b = pb = pixels[p + 2]) * rbs;\n aSum += (stack.a = pa = pixels[p + 3]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n aInSum += pa;\n stack = stack.next;\n }\n\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (x = 0; x < width; x++) {\n pixels[yi + 3] = pa = aSum * mulSum >> shgSum;\n\n if (pa !== 0) {\n pa = 255 / pa;\n pixels[yi] = (rSum * mulSum >> shgSum) * pa;\n pixels[yi + 1] = (gSum * mulSum >> shgSum) * pa;\n pixels[yi + 2] = (bSum * mulSum >> shgSum) * pa;\n } else {\n pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0;\n }\n\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n aSum -= aOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n aOutSum -= stackIn.a;\n p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2;\n rInSum += stackIn.r = pixels[p];\n gInSum += stackIn.g = pixels[p + 1];\n bInSum += stackIn.b = pixels[p + 2];\n aInSum += stackIn.a = pixels[p + 3];\n rSum += rInSum;\n gSum += gInSum;\n bSum += bInSum;\n aSum += aInSum;\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n aOutSum += pa = stackOut.a;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n aInSum -= pa;\n stackOut = stackOut.next;\n yi += 4;\n }\n\n yw += width;\n }\n\n for (x = 0; x < width; x++) {\n gInSum = bInSum = aInSum = rInSum = gSum = bSum = aSum = rSum = 0;\n yi = x << 2;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n aOutSum = radiusPlus1 * (pa = pixels[yi + 3]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n aSum += sumFactor * pa;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack.a = pa;\n stack = stack.next;\n }\n\n yp = width;\n\n for (i = 1; i <= radius; i++) {\n yi = yp + x << 2;\n rSum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[yi + 1]) * rbs;\n bSum += (stack.b = pb = pixels[yi + 2]) * rbs;\n aSum += (stack.a = pa = pixels[yi + 3]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n aInSum += pa;\n stack = stack.next;\n\n if (i < heightMinus1) {\n yp += width;\n }\n }\n\n yi = x;\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (y = 0; y < height; y++) {\n p = yi << 2;\n pixels[p + 3] = pa = aSum * mulSum >> shgSum;\n\n if (pa > 0) {\n pa = 255 / pa;\n pixels[p] = (rSum * mulSum >> shgSum) * pa;\n pixels[p + 1] = (gSum * mulSum >> shgSum) * pa;\n pixels[p + 2] = (bSum * mulSum >> shgSum) * pa;\n } else {\n pixels[p] = pixels[p + 1] = pixels[p + 2] = 0;\n }\n\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n aSum -= aOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n aOutSum -= stackIn.a;\n p = x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << 2;\n rSum += rInSum += stackIn.r = pixels[p];\n gSum += gInSum += stackIn.g = pixels[p + 1];\n bSum += bInSum += stackIn.b = pixels[p + 2];\n aSum += aInSum += stackIn.a = pixels[p + 3];\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n aOutSum += pa = stackOut.a;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n aInSum -= pa;\n stackOut = stackOut.next;\n yi += width;\n }\n }\n\n return imageData;\n}\n/**\n * @param {HTMLCanvasElement} canvas\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {undefined}\n */\n\n\nfunction processCanvasRGB(canvas, topX, topY, width, height, radius) {\n if (isNaN(radius) || radius < 1) {\n return;\n }\n\n radius |= 0;\n var imageData = getImageDataFromCanvas(canvas, topX, topY, width, height);\n imageData = processImageDataRGB(imageData, topX, topY, width, height, radius);\n canvas.getContext('2d').putImageData(imageData, topX, topY);\n}\n/**\n * @param {ImageData} imageData\n * @param {Integer} topX\n * @param {Integer} topY\n * @param {Integer} width\n * @param {Integer} height\n * @param {Float} radius\n * @returns {ImageData}\n */\n\n\nfunction processImageDataRGB(imageData, topX, topY, width, height, radius) {\n var pixels = imageData.data;\n var x, y, i, p, yp, yi, yw, rSum, gSum, bSum, rOutSum, gOutSum, bOutSum, rInSum, gInSum, bInSum, pr, pg, pb, rbs;\n var div = 2 * radius + 1; // const w4 = width << 2;\n\n var widthMinus1 = width - 1;\n var heightMinus1 = height - 1;\n var radiusPlus1 = radius + 1;\n var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;\n var stackStart = new BlurStack();\n var stack = stackStart;\n var stackEnd;\n\n for (i = 1; i < div; i++) {\n stack = stack.next = new BlurStack();\n\n if (i === radiusPlus1) {\n stackEnd = stack;\n }\n }\n\n stack.next = stackStart;\n var stackIn = null;\n var stackOut = null;\n yw = yi = 0;\n var mulSum = mulTable[radius];\n var shgSum = shgTable[radius];\n\n for (y = 0; y < height; y++) {\n rInSum = gInSum = bInSum = rSum = gSum = bSum = 0;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack = stack.next;\n }\n\n for (i = 1; i < radiusPlus1; i++) {\n p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);\n rSum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[p + 1]) * rbs;\n bSum += (stack.b = pb = pixels[p + 2]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n stack = stack.next;\n }\n\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (x = 0; x < width; x++) {\n pixels[yi] = rSum * mulSum >> shgSum;\n pixels[yi + 1] = gSum * mulSum >> shgSum;\n pixels[yi + 2] = bSum * mulSum >> shgSum;\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n p = yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1) << 2;\n rInSum += stackIn.r = pixels[p];\n gInSum += stackIn.g = pixels[p + 1];\n bInSum += stackIn.b = pixels[p + 2];\n rSum += rInSum;\n gSum += gInSum;\n bSum += bInSum;\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n stackOut = stackOut.next;\n yi += 4;\n }\n\n yw += width;\n }\n\n for (x = 0; x < width; x++) {\n gInSum = bInSum = rInSum = gSum = bSum = rSum = 0;\n yi = x << 2;\n rOutSum = radiusPlus1 * (pr = pixels[yi]);\n gOutSum = radiusPlus1 * (pg = pixels[yi + 1]);\n bOutSum = radiusPlus1 * (pb = pixels[yi + 2]);\n rSum += sumFactor * pr;\n gSum += sumFactor * pg;\n bSum += sumFactor * pb;\n stack = stackStart;\n\n for (i = 0; i < radiusPlus1; i++) {\n stack.r = pr;\n stack.g = pg;\n stack.b = pb;\n stack = stack.next;\n }\n\n yp = width;\n\n for (i = 1; i <= radius; i++) {\n yi = yp + x << 2;\n rSum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);\n gSum += (stack.g = pg = pixels[yi + 1]) * rbs;\n bSum += (stack.b = pb = pixels[yi + 2]) * rbs;\n rInSum += pr;\n gInSum += pg;\n bInSum += pb;\n stack = stack.next;\n\n if (i < heightMinus1) {\n yp += width;\n }\n }\n\n yi = x;\n stackIn = stackStart;\n stackOut = stackEnd;\n\n for (y = 0; y < height; y++) {\n p = yi << 2;\n pixels[p] = rSum * mulSum >> shgSum;\n pixels[p + 1] = gSum * mulSum >> shgSum;\n pixels[p + 2] = bSum * mulSum >> shgSum;\n rSum -= rOutSum;\n gSum -= gOutSum;\n bSum -= bOutSum;\n rOutSum -= stackIn.r;\n gOutSum -= stackIn.g;\n bOutSum -= stackIn.b;\n p = x + ((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width << 2;\n rSum += rInSum += stackIn.r = pixels[p];\n gSum += gInSum += stackIn.g = pixels[p + 1];\n bSum += bInSum += stackIn.b = pixels[p + 2];\n stackIn = stackIn.next;\n rOutSum += pr = stackOut.r;\n gOutSum += pg = stackOut.g;\n bOutSum += pb = stackOut.b;\n rInSum -= pr;\n gInSum -= pg;\n bInSum -= pb;\n stackOut = stackOut.next;\n yi += width;\n }\n }\n\n return imageData;\n}\n/**\n *\n */\n\n\nvar BlurStack =\n/**\n * Set properties.\n */\nfunction BlurStack() {\n _classCallCheck(this, BlurStack);\n\n this.r = 0;\n this.g = 0;\n this.b = 0;\n this.a = 0;\n this.next = null;\n};\n\nexport { BlurStack, processCanvasRGB as canvasRGB, processCanvasRGBA as canvasRGBA, processImage as image, processImageDataRGB as imageDataRGB, processImageDataRGBA as imageDataRGBA };\n","/* eslint-disable new-cap, class-methods-use-this, jsdoc/require-jsdoc */\n// Todo: Compare with latest canvg (add any improvements of ours) and add full JSDocs (denoting links to standard APIs and which are custom): https://github.com/canvg/canvg\n/**\n * Javascript SVG parser and renderer on Canvas.\n * @file canvg.js\n * @module canvg\n * @license MIT\n * @author Gabe Lerner \n * @see https://github.com/canvg/canvg\n */\n\nimport RGBColor from './rgbcolor.js';\nimport {canvasRGBA} from '../../external/stackblur-canvas/dist/stackblur-es.js';\n\n/**\n * Whether a value is `null` or `undefined`.\n * @param {any} val\n * @returns {boolean}\n */\nconst isNullish = (val) => {\n return val === null || val === undefined;\n};\n\n/**\n* @callback module:canvg.ForceRedraw\n* @returns {boolean}\n*/\n\n/**\n* @typedef {PlainObject} module:canvg.CanvgOptions\n* @property {boolean} ignoreMouse true => ignore mouse events\n* @property {boolean} ignoreAnimation true => ignore animations\n* @property {boolean} ignoreDimensions true => does not try to resize canvas\n* @property {boolean} ignoreClear true => does not clear canvas\n* @property {Integer} offsetX int => draws at a x offset\n* @property {Integer} offsetY int => draws at a y offset\n* @property {Integer} scaleWidth int => scales horizontally to width\n* @property {Integer} scaleHeight int => scales vertically to height\n* @property {module:canvg.ForceRedraw} forceRedraw function => will call the function on every frame, if it returns true, will redraw\n* @property {boolean} log Adds log function\n* @property {boolean} useCORS Whether to set CORS `crossOrigin` for the image to `Anonymous`\n*/\n\n/**\n* If called with no arguments, it will replace all `` elements on the page\n* with `` elements.\n* @function module:canvg.canvg\n* @param {HTMLCanvasElement|string} target canvas element or the id of a canvas element\n* @param {string|XMLDocument} s - svg string, url to svg file, or xml document\n* @param {module:canvg.CanvgOptions} [opts] Optional hash of options\n* @returns {Promise} All the function after the first render is completed with dom\n*/\nexport const canvg = function (target, s, opts) {\n // no parameters\n if (isNullish(target) && isNullish(s) && isNullish(opts)) {\n const svgTags = document.querySelectorAll('svg');\n return Promise.all([...svgTags].map((svgTag) => {\n const c = document.createElement('canvas');\n c.width = svgTag.clientWidth;\n c.height = svgTag.clientHeight;\n svgTag.before(c);\n svgTag.remove();\n const div = document.createElement('div');\n div.append(svgTag);\n return canvg(c, div.innerHTML);\n }));\n }\n\n if (typeof target === 'string') {\n target = document.getElementById(target);\n }\n\n // store class on canvas\n if (!isNullish(target.svg)) target.svg.stop();\n const svg = build(opts || {});\n // on i.e. 8 for flash canvas, we can't assign the property so check for it\n if (!(target.childNodes.length === 1 && target.childNodes[0].nodeName === 'OBJECT')) {\n target.svg = svg;\n }\n\n const ctx = target.getContext('2d');\n if (typeof s.documentElement !== 'undefined') {\n // load from xml doc\n return svg.loadXmlDoc(ctx, s);\n }\n if (s.substr(0, 1) === '<') {\n // load from xml string\n return svg.loadXml(ctx, s);\n }\n // load from url\n return svg.load(ctx, s);\n};\n\n/* eslint-disable jsdoc/check-types */\n/**\n* @param {module:canvg.CanvgOptions} opts\n* @returns {object}\n* @todo Flesh out exactly what object is returned here (after updating to latest and reincluding our changes here and those of StackBlur)\n*/\nfunction build (opts) {\n /* eslint-enable jsdoc/check-types */\n const svg = {opts};\n\n svg.FRAMERATE = 30;\n svg.MAX_VIRTUAL_PIXELS = 30000;\n\n svg.log = function (msg) { /* */ };\n if (svg.opts.log === true && typeof console !== 'undefined') {\n svg.log = function (msg) { console.log(msg); }; // eslint-disable-line no-console\n }\n\n // globals\n svg.init = function (ctx) {\n let uniqueId = 0;\n svg.UniqueId = function () {\n uniqueId++;\n return 'canvg' + uniqueId;\n };\n svg.Definitions = {};\n svg.Styles = {};\n svg.Animations = [];\n svg.Images = [];\n svg.ctx = ctx;\n svg.ViewPort = {\n viewPorts: [],\n Clear () { this.viewPorts = []; },\n SetCurrent (width, height) { this.viewPorts.push({width, height}); },\n RemoveCurrent () { this.viewPorts.pop(); },\n Current () { return this.viewPorts[this.viewPorts.length - 1]; },\n width () { return this.Current().width; },\n height () { return this.Current().height; },\n ComputeSize (d) {\n if (!isNullish(d) && typeof d === 'number') return d;\n if (d === 'x') return this.width();\n if (d === 'y') return this.height();\n return Math.sqrt(\n (this.width() ** 2) + (this.height() ** 2)\n ) / Math.sqrt(2);\n }\n };\n };\n svg.init();\n\n // images loaded\n svg.ImagesLoaded = function () {\n return svg.Images.every((img) => img.loaded);\n };\n\n // trim\n svg.trim = function (s) {\n return s.replace(/^\\s+|\\s+$/g, '');\n };\n\n // compress spaces\n svg.compressSpaces = function (s) {\n return s.replace(/\\s+/gm, ' ');\n };\n\n // ajax\n // Todo: Replace with `fetch` and polyfill\n svg.ajax = function (url, asynch) {\n const AJAX = window.XMLHttpRequest\n ? new XMLHttpRequest()\n : new window.ActiveXObject('Microsoft.XMLHTTP');\n if (asynch) {\n return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new\n const req = AJAX.open('GET', url, true);\n req.addEventListener('load', () => {\n resolve(AJAX.responseText);\n });\n AJAX.send(null);\n });\n }\n\n AJAX.open('GET', url, false);\n AJAX.send(null);\n return AJAX.responseText;\n };\n\n // parse xml\n svg.parseXml = function (xml) {\n if (window.DOMParser) {\n const parser = new DOMParser();\n return parser.parseFromString(xml, 'text/xml');\n }\n xml = xml.replace(/]*>/, '');\n const xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');\n xmlDoc.async = 'false';\n xmlDoc.loadXML(xml);\n return xmlDoc;\n };\n\n // text extensions\n // get the text baseline\n const textBaselineMapping = {\n baseline: 'alphabetic',\n 'before-edge': 'top',\n 'text-before-edge': 'top',\n middle: 'middle',\n central: 'middle',\n 'after-edge': 'bottom',\n 'text-after-edge': 'bottom',\n ideographic: 'ideographic',\n alphabetic: 'alphabetic',\n hanging: 'hanging',\n mathematical: 'alphabetic'\n };\n\n svg.Property = class Property {\n constructor (name, value) {\n this.name = name;\n this.value = value;\n }\n\n getValue () {\n return this.value;\n }\n\n hasValue () {\n return (!isNullish(this.value) && this.value !== '');\n }\n\n // return the numerical value of the property\n numValue () {\n if (!this.hasValue()) return 0;\n\n let n = Number.parseFloat(this.value);\n if (String(this.value).endsWith('%')) {\n n /= 100.0;\n }\n return n;\n }\n\n valueOrDefault (def) {\n if (this.hasValue()) return this.value;\n return def;\n }\n\n numValueOrDefault (def) {\n if (this.hasValue()) return this.numValue();\n return def;\n }\n\n // color extensions\n // augment the current color value with the opacity\n addOpacity (opacityProp) {\n let newValue = this.value;\n if (!isNullish(opacityProp.value) && opacityProp.value !== '' && typeof this.value === 'string') { // can only add opacity to colors, not patterns\n const color = new RGBColor(this.value);\n if (color.ok) {\n newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacityProp.numValue() + ')';\n }\n }\n return new svg.Property(this.name, newValue);\n }\n\n // definition extensions\n // get the definition from the definitions table\n getDefinition () {\n let name = this.value.match(/#([^)'\"]+)/);\n if (name) { name = name[1]; }\n if (!name) { name = this.value; }\n return svg.Definitions[name];\n }\n\n isUrlDefinition () {\n return this.value.startsWith('url(');\n }\n\n getFillStyleDefinition (e, opacityProp) {\n let def = this.getDefinition();\n\n // gradient\n if (!isNullish(def) && def.createGradient) {\n return def.createGradient(svg.ctx, e, opacityProp);\n }\n\n // pattern\n if (!isNullish(def) && def.createPattern) {\n if (def.getHrefAttribute().hasValue()) {\n const pt = def.attribute('patternTransform');\n def = def.getHrefAttribute().getDefinition();\n if (pt.hasValue()) { def.attribute('patternTransform', true).value = pt.value; }\n }\n return def.createPattern(svg.ctx, e);\n }\n\n return null;\n }\n\n // length extensions\n getDPI (viewPort) {\n return 96.0; // TODO: compute?\n }\n\n getEM (viewPort) {\n let em = 12;\n\n const fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);\n if (fontSize.hasValue()) em = fontSize.toPixels(viewPort);\n\n return em;\n }\n\n getUnits () {\n return String(this.value).replace(/[\\d.-]/g, '');\n }\n\n // get the length as pixels\n toPixels (viewPort, processPercent) {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('em')) return this.numValue() * this.getEM(viewPort);\n if (s.endsWith('ex')) return this.numValue() * this.getEM(viewPort) / 2.0;\n if (s.endsWith('px')) return this.numValue();\n if (s.endsWith('pt')) return this.numValue() * this.getDPI(viewPort) * (1.0 / 72.0);\n if (s.endsWith('pc')) return this.numValue() * 15;\n if (s.endsWith('cm')) return this.numValue() * this.getDPI(viewPort) / 2.54;\n if (s.endsWith('mm')) return this.numValue() * this.getDPI(viewPort) / 25.4;\n if (s.endsWith('in')) return this.numValue() * this.getDPI(viewPort);\n if (s.endsWith('%')) return this.numValue() * svg.ViewPort.ComputeSize(viewPort);\n const n = this.numValue();\n if (processPercent && n < 1.0) return n * svg.ViewPort.ComputeSize(viewPort);\n return n;\n }\n\n // time extensions\n // get the time as milliseconds\n toMilliseconds () {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('ms')) return this.numValue();\n if (s.endsWith('s')) return this.numValue() * 1000;\n return this.numValue();\n }\n\n // angle extensions\n // get the angle as radians\n toRadians () {\n if (!this.hasValue()) return 0;\n const s = String(this.value);\n if (s.endsWith('deg')) return this.numValue() * (Math.PI / 180.0);\n if (s.endsWith('grad')) return this.numValue() * (Math.PI / 200.0);\n if (s.endsWith('rad')) return this.numValue();\n return this.numValue() * (Math.PI / 180.0);\n }\n\n toTextBaseline () {\n if (!this.hasValue()) return null;\n return textBaselineMapping[this.value];\n }\n };\n\n // fonts\n svg.Font = {\n Styles: 'normal|italic|oblique|inherit',\n Variants: 'normal|small-caps|inherit',\n Weights: 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit',\n\n CreateFont (fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {\n const f = !isNullish(inherit) ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);\n return {\n fontFamily: fontFamily || f.fontFamily,\n fontSize: fontSize || f.fontSize,\n fontStyle: fontStyle || f.fontStyle,\n fontWeight: fontWeight || f.fontWeight,\n fontVariant: fontVariant || f.fontVariant,\n toString () {\n return [\n this.fontStyle, this.fontVariant, this.fontWeight,\n this.fontSize, this.fontFamily\n ].join(' ');\n }\n };\n },\n\n Parse (s) {\n const f = {};\n const ds = svg.trim(svg.compressSpaces(s || '')).split(' ');\n const set = {\n fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false\n };\n let ff = '';\n ds.forEach((d) => {\n if (!set.fontStyle && this.Styles.includes(d)) {\n if (d !== 'inherit') {\n f.fontStyle = d;\n }\n set.fontStyle = true;\n } else if (!set.fontVariant && this.Variants.includes(d)) {\n if (d !== 'inherit') {\n f.fontVariant = d;\n }\n set.fontStyle = set.fontVariant = true;\n } else if (!set.fontWeight && this.Weights.includes(d)) {\n if (d !== 'inherit') {\n f.fontWeight = d;\n }\n set.fontStyle = set.fontVariant = set.fontWeight = true;\n } else if (!set.fontSize) {\n if (d !== 'inherit') {\n f.fontSize = d.split('/')[0];\n }\n set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true;\n } else if (d !== 'inherit') {\n ff += d;\n }\n });\n if (ff !== '') { f.fontFamily = ff; }\n return f;\n }\n };\n\n // points and paths\n svg.ToNumberArray = function (s) {\n const a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');\n return a.map((_a) => Number.parseFloat(_a));\n };\n svg.Point = class {\n constructor (x, y) {\n this.x = x;\n this.y = y;\n }\n\n angleTo (p) {\n return Math.atan2(p.y - this.y, p.x - this.x);\n }\n\n applyTransform (v) {\n const xp = this.x * v[0] + this.y * v[2] + v[4];\n const yp = this.x * v[1] + this.y * v[3] + v[5];\n this.x = xp;\n this.y = yp;\n }\n };\n\n svg.CreatePoint = function (s) {\n const a = svg.ToNumberArray(s);\n return new svg.Point(a[0], a[1]);\n };\n svg.CreatePath = function (s) {\n const a = svg.ToNumberArray(s);\n const path = [];\n for (let i = 0; i < a.length; i += 2) {\n path.push(new svg.Point(a[i], a[i + 1]));\n }\n return path;\n };\n\n // bounding box\n svg.BoundingBox = class {\n constructor (x1, y1, x2, y2) { // pass in initial points if you want\n this.x1 = Number.NaN;\n this.y1 = Number.NaN;\n this.x2 = Number.NaN;\n this.y2 = Number.NaN;\n this.addPoint(x1, y1);\n this.addPoint(x2, y2);\n }\n\n x () { return this.x1; }\n y () { return this.y1; }\n width () { return this.x2 - this.x1; }\n height () { return this.y2 - this.y1; }\n\n addPoint (x, y) {\n if (!isNullish(x)) {\n if (isNaN(this.x1) || isNaN(this.x2)) {\n this.x1 = x;\n this.x2 = x;\n }\n if (x < this.x1) this.x1 = x;\n if (x > this.x2) this.x2 = x;\n }\n\n if (!isNullish(y)) {\n if (isNaN(this.y1) || isNaN(this.y2)) {\n this.y1 = y;\n this.y2 = y;\n }\n if (y < this.y1) this.y1 = y;\n if (y > this.y2) this.y2 = y;\n }\n }\n addX (x) { this.addPoint(x, null); }\n addY (y) { this.addPoint(null, y); }\n\n addBoundingBox (bb) {\n this.addPoint(bb.x1, bb.y1);\n this.addPoint(bb.x2, bb.y2);\n }\n\n addQuadraticCurve (p0x, p0y, p1x, p1y, p2x, p2y) {\n const cp1x = p0x + 2 / 3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)\n const cp1y = p0y + 2 / 3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)\n const cp2x = cp1x + 1 / 3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)\n const cp2y = cp1y + 1 / 3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)\n this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);\n }\n\n addBezierCurve (p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {\n // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html\n const p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];\n this.addPoint(p0[0], p0[1]);\n this.addPoint(p3[0], p3[1]);\n\n for (let i = 0; i <= 1; i++) {\n const f = function (t) {\n return ((1 - t) ** 3) * p0[i] +\n 3 * ((1 - t) ** 2) * t * p1[i] +\n 3 * (1 - t) * (t ** 2) * p2[i] +\n (t ** 3) * p3[i];\n };\n\n const b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];\n const a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];\n const c = 3 * p1[i] - 3 * p0[i];\n\n if (a === 0) {\n if (b === 0) continue;\n const t = -c / b;\n if (t > 0 && t < 1) {\n if (i === 0) this.addX(f(t));\n if (i === 1) this.addY(f(t));\n }\n continue;\n }\n\n const b2ac = (b ** 2) - 4 * c * a;\n if (b2ac < 0) continue;\n const t1 = (-b + Math.sqrt(b2ac)) / (2 * a);\n if (t1 > 0 && t1 < 1) {\n if (i === 0) this.addX(f(t1));\n if (i === 1) this.addY(f(t1));\n }\n const t2 = (-b - Math.sqrt(b2ac)) / (2 * a);\n if (t2 > 0 && t2 < 1) {\n if (i === 0) this.addX(f(t2));\n if (i === 1) this.addY(f(t2));\n }\n }\n }\n\n isPointInBox (x, y) {\n return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);\n }\n };\n\n // transforms\n svg.Transform = class {\n constructor (v) {\n this.Type = {\n translate: class {\n constructor (s) {\n this.p = svg.CreatePoint(s);\n this.apply = function (ctx) {\n ctx.translate(this.p.x || 0.0, this.p.y || 0.0);\n };\n this.unapply = function (ctx) {\n ctx.translate(-1.0 * this.p.x || 0.0, -1.0 * this.p.y || 0.0);\n };\n this.applyToPoint = function (p) {\n p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n };\n }\n },\n rotate: class {\n constructor (s) {\n const a = svg.ToNumberArray(s);\n this.angle = new svg.Property('angle', a[0]);\n this.cx = a[1] || 0;\n this.cy = a[2] || 0;\n this.apply = function (ctx) {\n ctx.translate(this.cx, this.cy);\n ctx.rotate(this.angle.toRadians());\n ctx.translate(-this.cx, -this.cy);\n };\n this.unapply = function (ctx) {\n ctx.translate(this.cx, this.cy);\n ctx.rotate(-1.0 * this.angle.toRadians());\n ctx.translate(-this.cx, -this.cy);\n };\n this.applyToPoint = function (p) {\n const _a = this.angle.toRadians();\n p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n p.applyTransform([Math.cos(_a), Math.sin(_a), -Math.sin(_a), Math.cos(_a), 0, 0]);\n p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);\n };\n }\n },\n scale: class {\n constructor (s) {\n this.p = svg.CreatePoint(s);\n this.apply = function (ctx) {\n ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);\n };\n this.unapply = function (ctx) {\n ctx.scale(1.0 / this.p.x || 1.0, 1.0 / this.p.y || this.p.x || 1.0);\n };\n this.applyToPoint = function (p) {\n p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);\n };\n }\n },\n matrix: class {\n constructor (s) {\n this.m = svg.ToNumberArray(s);\n this.apply = function (ctx) {\n ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);\n };\n this.applyToPoint = function (p) {\n p.applyTransform(this.m);\n };\n }\n }\n };\n Object.assign(this.Type, {\n SkewBase: class extends this.Type.matrix {\n constructor (s) {\n super(s);\n this.angle = new svg.Property('angle', s);\n }\n }\n });\n Object.assign(this.Type, {\n skewX: class extends this.Type.SkewBase {\n constructor (s) {\n super(s);\n this.m = [1, 0, Math.tan(this.angle.toRadians()), 1, 0, 0];\n }\n },\n skewY: class extends this.Type.SkewBase {\n constructor (s) {\n super(s);\n this.m = [1, Math.tan(this.angle.toRadians()), 0, 1, 0, 0];\n }\n }\n });\n\n const data = svg.trim(svg.compressSpaces(v)).replace(\n /\\)([a-zA-Z])/g, ') $1'\n ).replace(/\\)(\\s?,\\s?)/g, ') ').split(/\\s(?=[a-z])/);\n this.transforms = data.map((d) => {\n const type = svg.trim(d.split('(')[0]);\n const s = d.split('(')[1].replace(')', '');\n const transform = new this.Type[type](s);\n transform.type = type;\n return transform;\n });\n }\n\n apply (ctx) {\n this.transforms.forEach((transform) => {\n transform.apply(ctx);\n });\n }\n\n unapply (ctx) {\n for (let i = this.transforms.length - 1; i >= 0; i--) {\n this.transforms[i].unapply(ctx);\n }\n }\n\n applyToPoint (p) {\n this.transforms.forEach((transform) => {\n transform.applyToPoint(p);\n });\n }\n };\n\n // aspect ratio\n svg.AspectRatio = function (ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {\n // aspect ratio - https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute\n aspectRatio = svg.compressSpaces(aspectRatio);\n aspectRatio = aspectRatio.replace(/^defer\\s/, ''); // ignore defer\n const align = aspectRatio.split(' ')[0] || 'xMidYMid';\n const meetOrSlice = aspectRatio.split(' ')[1] || 'meet';\n\n // calculate scale\n const scaleX = width / desiredWidth;\n const scaleY = height / desiredHeight;\n const scaleMin = Math.min(scaleX, scaleY);\n const scaleMax = Math.max(scaleX, scaleY);\n if (meetOrSlice === 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }\n if (meetOrSlice === 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }\n\n refX = new svg.Property('refX', refX);\n refY = new svg.Property('refY', refY);\n if (refX.hasValue() && refY.hasValue()) {\n ctx.translate(-scaleMin * refX.toPixels('x'), -scaleMin * refY.toPixels('y'));\n } else {\n // align\n if (align.startsWith('xMid') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleY) || (meetOrSlice === 'slice' && scaleMax === scaleY))) {\n ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);\n }\n if (align.endsWith('YMid') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleX) || (meetOrSlice === 'slice' && scaleMax === scaleX))) {\n ctx.translate(0, height / 2.0 - desiredHeight / 2.0);\n }\n if (align.startsWith('xMax') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleY) || (meetOrSlice === 'slice' && scaleMax === scaleY))) {\n ctx.translate(width - desiredWidth, 0);\n }\n if (align.endsWith('YMax') &&\n ((meetOrSlice === 'meet' && scaleMin === scaleX) ||\n (meetOrSlice === 'slice' && scaleMax === scaleX)\n )\n ) {\n ctx.translate(0, height - desiredHeight);\n }\n }\n\n // scale\n if (align === 'none') ctx.scale(scaleX, scaleY);\n else if (meetOrSlice === 'meet') ctx.scale(scaleMin, scaleMin);\n else if (meetOrSlice === 'slice') ctx.scale(scaleMax, scaleMax);\n\n // translate\n ctx.translate(isNullish(minX) ? 0 : -minX, isNullish(minY) ? 0 : -minY);\n };\n\n // elements\n svg.Element = {};\n\n svg.EmptyProperty = new svg.Property('EMPTY', '');\n\n svg.Element.ElementBase = class {\n constructor (node) {\n // Argument from inheriting class\n this.captureTextNodes = arguments[1]; // eslint-disable-line prefer-rest-params\n this.attributes = {};\n this.styles = {};\n this.children = [];\n if (!isNullish(node) && node.nodeType === 1) { // ELEMENT_NODE\n // add children\n [...node.childNodes].forEach((childNode) => {\n if (childNode.nodeType === 1) {\n this.addChild(childNode, true); // ELEMENT_NODE\n }\n if (this.captureTextNodes && (\n childNode.nodeType === 3 || childNode.nodeType === 4\n )) {\n const text = childNode.nodeValue || childNode.text || '';\n if (svg.trim(svg.compressSpaces(text)) !== '') {\n this.addChild(new svg.Element.tspan(childNode), false); // TEXT_NODE\n }\n }\n });\n\n // add attributes\n [...node.attributes].forEach(({nodeName, nodeValue}) => {\n this.attributes[nodeName] = new svg.Property(\n nodeName,\n nodeValue\n );\n });\n // add tag styles\n let styles = svg.Styles[node.nodeName];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n\n // add class styles\n if (this.attribute('class').hasValue()) {\n const classes = svg.compressSpaces(this.attribute('class').value).split(' ');\n classes.forEach((clss) => {\n styles = svg.Styles['.' + clss];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n styles = svg.Styles[node.nodeName + '.' + clss];\n if (!isNullish(styles)) {\n Object.entries(styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n });\n }\n\n // add id styles\n if (this.attribute('id').hasValue()) {\n const _styles = svg.Styles['#' + this.attribute('id').value];\n if (!isNullish(_styles)) {\n Object.entries(_styles).forEach(([name, styleValue]) => {\n this.styles[name] = styleValue;\n });\n }\n }\n\n // add inline styles\n if (this.attribute('style').hasValue()) {\n const _styles = this.attribute('style').value.split(';');\n _styles.forEach((style) => {\n if (svg.trim(style) !== '') {\n let {name, value} = style.split(':');\n name = svg.trim(name);\n value = svg.trim(value);\n this.styles[name] = new svg.Property(name, value);\n }\n });\n }\n\n // add id\n if (this.attribute('id').hasValue()) {\n if (isNullish(svg.Definitions[this.attribute('id').value])) {\n svg.Definitions[this.attribute('id').value] = this;\n }\n }\n }\n }\n\n // get or create attribute\n attribute (name, createIfNotExists) {\n let a = this.attributes[name];\n if (!isNullish(a)) return a;\n\n if (createIfNotExists === true) { a = new svg.Property(name, ''); this.attributes[name] = a; }\n return a || svg.EmptyProperty;\n }\n\n getHrefAttribute () {\n for (const a in this.attributes) {\n if (a.endsWith(':href')) {\n return this.attributes[a];\n }\n }\n return svg.EmptyProperty;\n }\n\n // get or create style, crawls up node tree\n style (name, createIfNotExists, skipAncestors) {\n let s = this.styles[name];\n if (!isNullish(s)) return s;\n\n const a = this.attribute(name);\n if (!isNullish(a) && a.hasValue()) {\n this.styles[name] = a; // move up to me to cache\n return a;\n }\n\n if (skipAncestors !== true) {\n const p = this.parent;\n if (!isNullish(p)) {\n const ps = p.style(name);\n if (!isNullish(ps) && ps.hasValue()) {\n return ps;\n }\n }\n }\n\n if (createIfNotExists === true) { s = new svg.Property(name, ''); this.styles[name] = s; }\n return s || svg.EmptyProperty;\n }\n\n // base render\n render (ctx) {\n // don't render display=none\n if (this.style('display').value === 'none') return;\n\n // don't render visibility=hidden\n if (this.style('visibility').value === 'hidden') return;\n\n ctx.save();\n if (this.attribute('mask').hasValue()) { // mask\n const mask = this.attribute('mask').getDefinition();\n if (!isNullish(mask)) mask.apply(ctx, this);\n } else if (this.style('filter').hasValue()) { // filter\n const filter = this.style('filter').getDefinition();\n if (!isNullish(filter)) filter.apply(ctx, this);\n } else {\n this.setContext(ctx);\n this.renderChildren(ctx);\n this.clearContext(ctx);\n }\n ctx.restore();\n }\n\n // base set context\n setContext (ctx) {\n // OVERRIDE ME!\n }\n\n // base clear context\n clearContext (ctx) {\n // OVERRIDE ME!\n }\n\n // base render children\n renderChildren (ctx) {\n this.children.forEach((child) => {\n child.render(ctx);\n });\n }\n\n addChild (childNode, create) {\n const child = create\n ? svg.CreateElement(childNode)\n : childNode;\n child.parent = this;\n if (child.type !== 'title') { this.children.push(child); }\n }\n };\n\n svg.Element.RenderedElementBase = class extends svg.Element.ElementBase {\n setContext (ctx) {\n // fill\n if (this.style('fill').isUrlDefinition()) {\n const fs = this.style('fill').getFillStyleDefinition(this, this.style('fill-opacity'));\n if (!isNullish(fs)) ctx.fillStyle = fs;\n } else if (this.style('fill').hasValue()) {\n const fillStyle = this.style('fill');\n if (fillStyle.value === 'currentColor') fillStyle.value = this.style('color').value;\n ctx.fillStyle = (fillStyle.value === 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);\n }\n if (this.style('fill-opacity').hasValue()) {\n let fillStyle = new svg.Property('fill', ctx.fillStyle);\n fillStyle = fillStyle.addOpacity(this.style('fill-opacity'));\n ctx.fillStyle = fillStyle.value;\n }\n\n // stroke\n if (this.style('stroke').isUrlDefinition()) {\n const fs = this.style('stroke').getFillStyleDefinition(this, this.style('stroke-opacity'));\n if (!isNullish(fs)) ctx.strokeStyle = fs;\n } else if (this.style('stroke').hasValue()) {\n const strokeStyle = this.style('stroke');\n if (strokeStyle.value === 'currentColor') strokeStyle.value = this.style('color').value;\n ctx.strokeStyle = (strokeStyle.value === 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);\n }\n if (this.style('stroke-opacity').hasValue()) {\n let strokeStyle = new svg.Property('stroke', ctx.strokeStyle);\n strokeStyle = strokeStyle.addOpacity(this.style('stroke-opacity'));\n ctx.strokeStyle = strokeStyle.value;\n }\n if (this.style('stroke-width').hasValue()) {\n const newLineWidth = this.style('stroke-width').toPixels();\n ctx.lineWidth = newLineWidth === 0 ? 0.001 : newLineWidth; // browsers don't respect 0\n }\n if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;\n if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;\n if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;\n if (this.style('stroke-dasharray').hasValue() && this.style('stroke-dasharray').value !== 'none') {\n const gaps = svg.ToNumberArray(this.style('stroke-dasharray').value);\n if (typeof ctx.setLineDash !== 'undefined') {\n ctx.setLineDash(gaps);\n } else if (typeof ctx.webkitLineDash !== 'undefined') {\n ctx.webkitLineDash = gaps;\n } else if (typeof ctx.mozDash !== 'undefined' && !(gaps.length === 1 && gaps[0] === 0)) {\n ctx.mozDash = gaps;\n }\n\n const offset = this.style('stroke-dashoffset').numValueOrDefault(1);\n if (typeof ctx.lineDashOffset !== 'undefined') {\n ctx.lineDashOffset = offset;\n } else if (typeof ctx.webkitLineDashOffset !== 'undefined') {\n ctx.webkitLineDashOffset = offset;\n } else if (typeof ctx.mozDashOffset !== 'undefined') {\n ctx.mozDashOffset = offset;\n }\n }\n\n // font\n if (typeof ctx.font !== 'undefined') {\n ctx.font = svg.Font.CreateFont(\n this.style('font-style').value,\n this.style('font-variant').value,\n this.style('font-weight').value,\n this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '',\n this.style('font-family').value\n ).toString();\n }\n\n // transform\n if (this.attribute('transform').hasValue()) {\n const transform = new svg.Transform(this.attribute('transform').value);\n transform.apply(ctx);\n }\n\n // clip\n if (this.style('clip-path', false, true).hasValue()) {\n const clip = this.style('clip-path', false, true).getDefinition();\n if (!isNullish(clip)) clip.apply(ctx);\n }\n\n // opacity\n if (this.style('opacity').hasValue()) {\n ctx.globalAlpha = this.style('opacity').numValue();\n }\n }\n };\n\n svg.Element.PathElementBase = class extends svg.Element.RenderedElementBase {\n path (ctx) {\n if (!isNullish(ctx)) ctx.beginPath();\n return new svg.BoundingBox();\n }\n\n renderChildren (ctx) {\n this.path(ctx);\n svg.Mouse.checkPath(this, ctx);\n if (ctx.fillStyle !== '') {\n if (this.style('fill-rule').valueOrDefault('inherit') !== 'inherit') {\n ctx.fill(this.style('fill-rule').value);\n } else {\n ctx.fill();\n }\n }\n if (ctx.strokeStyle !== '') ctx.stroke();\n\n const markers = this.getMarkers();\n if (!isNullish(markers)) {\n if (this.style('marker-start').isUrlDefinition()) {\n const marker = this.style('marker-start').getDefinition();\n marker.render(ctx, markers[0][0], markers[0][1]);\n }\n if (this.style('marker-mid').isUrlDefinition()) {\n const marker = this.style('marker-mid').getDefinition();\n for (let i = 1; i < markers.length - 1; i++) {\n marker.render(ctx, markers[i][0], markers[i][1]);\n }\n }\n if (this.style('marker-end').isUrlDefinition()) {\n const marker = this.style('marker-end').getDefinition();\n marker.render(ctx, markers[markers.length - 1][0], markers[markers.length - 1][1]);\n }\n }\n }\n\n getBoundingBox () {\n return this.path();\n }\n\n getMarkers () {\n return null;\n }\n };\n\n // svg element\n svg.Element.svg = class extends svg.Element.RenderedElementBase {\n clearContext (ctx) {\n super.clearContext(ctx);\n svg.ViewPort.RemoveCurrent();\n }\n\n setContext (ctx) {\n // initial values and defaults\n ctx.strokeStyle = 'rgba(0,0,0,0)';\n ctx.lineCap = 'butt';\n ctx.lineJoin = 'miter';\n ctx.miterLimit = 4;\n if (typeof ctx.font !== 'undefined' && typeof window.getComputedStyle !== 'undefined') {\n ctx.font = window.getComputedStyle(ctx.canvas).getPropertyValue('font');\n }\n\n super.setContext(ctx);\n\n // create new view port\n if (!this.attribute('x').hasValue()) this.attribute('x', true).value = 0;\n if (!this.attribute('y').hasValue()) this.attribute('y', true).value = 0;\n ctx.translate(this.attribute('x').toPixels('x'), this.attribute('y').toPixels('y'));\n\n let width = svg.ViewPort.width();\n let height = svg.ViewPort.height();\n\n if (!this.attribute('width').hasValue()) this.attribute('width', true).value = '100%';\n if (!this.attribute('height').hasValue()) this.attribute('height', true).value = '100%';\n if (typeof this.root === 'undefined') {\n width = this.attribute('width').toPixels('x');\n height = this.attribute('height').toPixels('y');\n\n let x = 0;\n let y = 0;\n if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {\n x = -this.attribute('refX').toPixels('x');\n y = -this.attribute('refY').toPixels('y');\n }\n\n if (this.attribute('overflow').valueOrDefault('hidden') !== 'visible') {\n ctx.beginPath();\n ctx.moveTo(x, y);\n ctx.lineTo(width, y);\n ctx.lineTo(width, height);\n ctx.lineTo(x, height);\n ctx.closePath();\n ctx.clip();\n }\n }\n svg.ViewPort.SetCurrent(width, height);\n\n // viewbox\n if (this.attribute('viewBox').hasValue()) {\n const viewBox = svg.ToNumberArray(this.attribute('viewBox').value);\n const minX = viewBox[0];\n const minY = viewBox[1];\n width = viewBox[2];\n height = viewBox[3];\n\n svg.AspectRatio(\n ctx,\n this.attribute('preserveAspectRatio').value,\n svg.ViewPort.width(),\n width,\n svg.ViewPort.height(),\n height,\n minX,\n minY,\n this.attribute('refX').value,\n this.attribute('refY').value\n );\n\n svg.ViewPort.RemoveCurrent();\n svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);\n }\n }\n };\n\n // rect element\n svg.Element.rect = class extends svg.Element.PathElementBase {\n path (ctx) {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n let rx = this.attribute('rx').toPixels('x');\n let ry = this.attribute('ry').toPixels('y');\n if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;\n if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;\n rx = Math.min(rx, width / 2.0);\n ry = Math.min(ry, height / 2.0);\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(x + rx, y);\n ctx.lineTo(x + width - rx, y);\n ctx.quadraticCurveTo(x + width, y, x + width, y + ry);\n ctx.lineTo(x + width, y + height - ry);\n ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);\n ctx.lineTo(x + rx, y + height);\n ctx.quadraticCurveTo(x, y + height, x, y + height - ry);\n ctx.lineTo(x, y + ry);\n ctx.quadraticCurveTo(x, y, x + rx, y);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(x, y, x + width, y + height);\n }\n };\n\n // circle element\n svg.Element.circle = class extends svg.Element.PathElementBase {\n path (ctx) {\n const cx = this.attribute('cx').toPixels('x');\n const cy = this.attribute('cy').toPixels('y');\n const r = this.attribute('r').toPixels();\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2, true);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);\n }\n };\n\n // ellipse element\n const KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);\n svg.Element.ellipse = class extends svg.Element.PathElementBase {\n path (ctx) {\n const rx = this.attribute('rx').toPixels('x');\n const ry = this.attribute('ry').toPixels('y');\n const cx = this.attribute('cx').toPixels('x');\n const cy = this.attribute('cy').toPixels('y');\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(cx, cy - ry);\n ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);\n ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);\n ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);\n ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);\n ctx.closePath();\n }\n\n return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);\n }\n };\n\n // line element\n svg.Element.line = class extends svg.Element.PathElementBase {\n getPoints () {\n return [\n new svg.Point(this.attribute('x1').toPixels('x'), this.attribute('y1').toPixels('y')),\n new svg.Point(this.attribute('x2').toPixels('x'), this.attribute('y2').toPixels('y'))\n ];\n }\n\n path (ctx) {\n const points = this.getPoints();\n\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(points[0].x, points[0].y);\n ctx.lineTo(points[1].x, points[1].y);\n }\n\n return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);\n }\n\n getMarkers () {\n const points = this.getPoints();\n const a = points[0].angleTo(points[1]);\n return [[points[0], a], [points[1], a]];\n }\n };\n\n // polyline element\n svg.Element.polyline = class extends svg.Element.PathElementBase {\n constructor (node) {\n super(node);\n\n this.points = svg.CreatePath(this.attribute('points').value);\n }\n path (ctx) {\n const {x, y} = this.points[0];\n const bb = new svg.BoundingBox(x, y);\n if (!isNullish(ctx)) {\n ctx.beginPath();\n ctx.moveTo(x, y);\n }\n for (let i = 1; i < this.points.length; i++) {\n const {x: _x, y: _y} = this.points[i];\n bb.addPoint(_x, _y);\n if (!isNullish(ctx)) ctx.lineTo(_x, _y);\n }\n return bb;\n }\n\n getMarkers () {\n const markers = [];\n for (let i = 0; i < this.points.length - 1; i++) {\n markers.push([this.points[i], this.points[i].angleTo(this.points[i + 1])]);\n }\n markers.push([this.points[this.points.length - 1], markers[markers.length - 1][1]]);\n return markers;\n }\n };\n\n // polygon element\n svg.Element.polygon = class extends svg.Element.polyline {\n path (ctx) {\n const bb = super.path(ctx);\n if (!isNullish(ctx)) {\n ctx.lineTo(this.points[0].x, this.points[0].y);\n ctx.closePath();\n }\n return bb;\n }\n };\n\n // path element\n svg.Element.path = class extends svg.Element.PathElementBase {\n constructor (node) {\n super(node);\n\n let d = this.attribute('d').value\n // TODO: convert to real lexer based on https://www.w3.org/TR/SVG11/paths.html#PathDataBNF\n .replace(/,/gm, ' ') // get rid of all commas\n .replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from commands\n .replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from commands\n .replace(/([MmZzLlHhVvCcSsQqTtAa])(\\S)/gm, '$1 $2') // separate commands from points\n .replace(/(\\S)([MmZzLlHhVvCcSsQqTtAa])/gm, '$1 $2') // separate commands from points\n .replace(/(\\d)([+-])/gm, '$1 $2') // separate digits when no comma\n .replace(/(\\.\\d*)(\\.)/gm, '$1 $2') // separate digits when no comma\n .replace(/([Aa](\\s+\\d+)(\\s+\\d+)(\\s+\\d+))\\s+([01])\\s*([01])/gm, '$1 $5 $6 '); // shorthand elliptical arc path syntax\n d = svg.compressSpaces(d); // compress multiple spaces\n d = svg.trim(d);\n this.PathParser = {\n tokens: d.split(' '),\n\n reset () {\n this.i = -1;\n this.command = '';\n this.previousCommand = '';\n this.start = new svg.Point(0, 0);\n this.control = new svg.Point(0, 0);\n this.current = new svg.Point(0, 0);\n this.points = [];\n this.angles = [];\n },\n\n isEnd () {\n return this.i >= this.tokens.length - 1;\n },\n\n isCommandOrEnd () {\n if (this.isEnd()) return true;\n return !isNullish(this.tokens[this.i + 1].match(/^[A-Za-z]$/));\n },\n\n isRelativeCommand () {\n switch (this.command) {\n case 'm':\n case 'l':\n case 'h':\n case 'v':\n case 'c':\n case 's':\n case 'q':\n case 't':\n case 'a':\n case 'z':\n return true;\n }\n return false;\n },\n\n getToken () {\n this.i++;\n return this.tokens[this.i];\n },\n\n getScalar () {\n return Number.parseFloat(this.getToken());\n },\n\n nextCommand () {\n this.previousCommand = this.command;\n this.command = this.getToken();\n },\n\n getPoint () {\n const p = new svg.Point(this.getScalar(), this.getScalar());\n return this.makeAbsolute(p);\n },\n\n getAsControlPoint () {\n const p = this.getPoint();\n this.control = p;\n return p;\n },\n\n getAsCurrentPoint () {\n const p = this.getPoint();\n this.current = p;\n return p;\n },\n\n getReflectedControlPoint () {\n if (this.previousCommand.toLowerCase() !== 'c' &&\n this.previousCommand.toLowerCase() !== 's' &&\n this.previousCommand.toLowerCase() !== 'q' &&\n this.previousCommand.toLowerCase() !== 't') {\n return this.current;\n }\n\n // reflect point\n const p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);\n return p;\n },\n\n makeAbsolute (p) {\n if (this.isRelativeCommand()) {\n p.x += this.current.x;\n p.y += this.current.y;\n }\n return p;\n },\n\n addMarker (p, from, priorTo) {\n // if the last angle isn't filled in because we didn't have this point yet ...\n if (!isNullish(priorTo) && this.angles.length > 0 && isNullish(this.angles[this.angles.length - 1])) {\n this.angles[this.angles.length - 1] = this.points[this.points.length - 1].angleTo(priorTo);\n }\n this.addMarkerAngle(p, isNullish(from) ? null : from.angleTo(p));\n },\n\n addMarkerAngle (p, a) {\n this.points.push(p);\n this.angles.push(a);\n },\n\n getMarkerPoints () { return this.points; },\n getMarkerAngles () {\n for (let i = 0; i < this.angles.length; i++) {\n if (isNullish(this.angles[i])) {\n for (let j = i + 1; j < this.angles.length; j++) {\n if (!isNullish(this.angles[j])) {\n this.angles[i] = this.angles[j];\n break;\n }\n }\n }\n }\n return this.angles;\n }\n };\n }\n\n path (ctx) {\n const pp = this.PathParser;\n pp.reset();\n\n const bb = new svg.BoundingBox();\n if (!isNullish(ctx)) ctx.beginPath();\n while (!pp.isEnd()) {\n pp.nextCommand();\n switch (pp.command) {\n case 'M':\n case 'm': {\n const p = pp.getAsCurrentPoint();\n pp.addMarker(p);\n bb.addPoint(p.x, p.y);\n if (!isNullish(ctx)) ctx.moveTo(p.x, p.y);\n pp.start = pp.current;\n while (!pp.isCommandOrEnd()) {\n const _p = pp.getAsCurrentPoint();\n pp.addMarker(_p, pp.start);\n bb.addPoint(_p.x, _p.y);\n if (!isNullish(ctx)) ctx.lineTo(_p.x, _p.y);\n }\n break;\n } case 'L':\n case 'l':\n while (!pp.isCommandOrEnd()) {\n const c = pp.current;\n const p = pp.getAsCurrentPoint();\n pp.addMarker(p, c);\n bb.addPoint(p.x, p.y);\n if (!isNullish(ctx)) ctx.lineTo(p.x, p.y);\n }\n break;\n case 'H':\n case 'h':\n while (!pp.isCommandOrEnd()) {\n const newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);\n pp.addMarker(newP, pp.current);\n pp.current = newP;\n bb.addPoint(pp.current.x, pp.current.y);\n if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);\n }\n break;\n case 'V':\n case 'v':\n while (!pp.isCommandOrEnd()) {\n const newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());\n pp.addMarker(newP, pp.current);\n pp.current = newP;\n bb.addPoint(pp.current.x, pp.current.y);\n if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);\n }\n break;\n case 'C':\n case 'c':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const p1 = pp.getPoint();\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, p1);\n bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'S':\n case 's':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const p1 = pp.getReflectedControlPoint();\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, p1);\n bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'Q':\n case 'q':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const cntrl = pp.getAsControlPoint();\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, cntrl);\n bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'T':\n case 't':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n const cntrl = pp.getReflectedControlPoint();\n pp.control = cntrl;\n const cp = pp.getAsCurrentPoint();\n pp.addMarker(cp, cntrl, cntrl);\n bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n }\n break;\n case 'A':\n case 'a':\n while (!pp.isCommandOrEnd()) {\n const curr = pp.current;\n let rx = pp.getScalar();\n let ry = pp.getScalar();\n const xAxisRotation = pp.getScalar() * (Math.PI / 180.0);\n const largeArcFlag = pp.getScalar();\n const sweepFlag = pp.getScalar();\n const cp = pp.getAsCurrentPoint();\n\n // Conversion from endpoint to center parameterization\n // https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter\n\n // x1', y1'\n const currp = new svg.Point(\n Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,\n -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0\n );\n // adjust radii\n const l = (currp.x ** 2) / (rx ** 2) + (currp.y ** 2) / (ry ** 2);\n if (l > 1) {\n rx *= Math.sqrt(l);\n ry *= Math.sqrt(l);\n }\n // cx', cy'\n let s = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(\n (((rx ** 2) * (ry ** 2)) - ((rx ** 2) * (currp.y ** 2)) - ((ry ** 2) * (currp.x ** 2))) /\n ((rx ** 2) * (currp.y ** 2) + (ry ** 2) * (currp.x ** 2))\n );\n if (isNaN(s)) s = 0;\n const cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);\n // cx, cy\n const centp = new svg.Point(\n (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,\n (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y\n );\n // vector magnitude\n const m = function (v) {\n return Math.sqrt((v[0] ** 2) + (v[1] ** 2));\n };\n // ratio between two vectors\n const r = function (u, v) {\n return (u[0] * v[0] + u[1] * v[1]) / (m(u) * m(v));\n };\n // angle between two vectors\n const a = function (u, v) {\n return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(r(u, v));\n };\n // initial angle\n const a1 = a([1, 0], [(currp.x - cpp.x) / rx, (currp.y - cpp.y) / ry]);\n // angle delta\n const u = [(currp.x - cpp.x) / rx, (currp.y - cpp.y) / ry];\n const v = [(-currp.x - cpp.x) / rx, (-currp.y - cpp.y) / ry];\n let ad = a(u, v);\n if (r(u, v) <= -1) ad = Math.PI;\n if (r(u, v) >= 1) ad = 0;\n\n // for markers\n const dir = 1 - sweepFlag ? 1.0 : -1.0;\n const ah = a1 + dir * (ad / 2.0);\n const halfWay = new svg.Point(\n centp.x + rx * Math.cos(ah),\n centp.y + ry * Math.sin(ah)\n );\n pp.addMarkerAngle(halfWay, ah - dir * Math.PI / 2);\n pp.addMarkerAngle(cp, ah - dir * Math.PI);\n\n bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better\n if (!isNullish(ctx)) {\n const _r = rx > ry ? rx : ry;\n const sx = rx > ry ? 1 : rx / ry;\n const sy = rx > ry ? ry / rx : 1;\n\n ctx.translate(centp.x, centp.y);\n ctx.rotate(xAxisRotation);\n ctx.scale(sx, sy);\n ctx.arc(0, 0, _r, a1, a1 + ad, 1 - sweepFlag);\n ctx.scale(1 / sx, 1 / sy);\n ctx.rotate(-xAxisRotation);\n ctx.translate(-centp.x, -centp.y);\n }\n }\n break;\n case 'Z':\n case 'z':\n if (!isNullish(ctx)) ctx.closePath();\n pp.current = pp.start;\n }\n }\n\n return bb;\n }\n\n getMarkers () {\n const points = this.PathParser.getMarkerPoints();\n const angles = this.PathParser.getMarkerAngles();\n\n const markers = points.map((point, i) => {\n return [point, angles[i]];\n });\n return markers;\n }\n };\n\n // pattern element\n svg.Element.pattern = class extends svg.Element.ElementBase {\n createPattern (ctx, element) {\n const width = this.attribute('width').toPixels('x', true);\n const height = this.attribute('height').toPixels('y', true);\n\n // render me using a temporary svg element\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.viewBox = new svg.Property('viewBox', this.attribute('viewBox').value);\n tempSvg.attributes.width = new svg.Property('width', width + 'px');\n tempSvg.attributes.height = new svg.Property('height', height + 'px');\n tempSvg.attributes.transform = new svg.Property('transform', this.attribute('patternTransform').value);\n tempSvg.children = this.children;\n\n const c = document.createElement('canvas');\n c.width = width;\n c.height = height;\n const cctx = c.getContext('2d');\n if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {\n cctx.translate(this.attribute('x').toPixels('x', true), this.attribute('y').toPixels('y', true));\n }\n // render 3x3 grid so when we transform there's no white space on edges\n for (let x = -1; x <= 1; x++) {\n for (let y = -1; y <= 1; y++) {\n cctx.save();\n cctx.translate(x * c.width, y * c.height);\n tempSvg.render(cctx);\n cctx.restore();\n }\n }\n const pattern = ctx.createPattern(c, 'repeat');\n return pattern;\n }\n };\n\n // marker element\n svg.Element.marker = class extends svg.Element.ElementBase {\n render (ctx, point, angle) {\n ctx.translate(point.x, point.y);\n if (this.attribute('orient').valueOrDefault('auto') === 'auto') ctx.rotate(angle);\n if (this.attribute('markerUnits').valueOrDefault('strokeWidth') === 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);\n ctx.save();\n\n // render me using a temporary svg element\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.viewBox = new svg.Property(\n 'viewBox', this.attribute('viewBox').value\n );\n tempSvg.attributes.refX = new svg.Property(\n 'refX', this.attribute('refX').value\n );\n tempSvg.attributes.refY = new svg.Property(\n 'refY', this.attribute('refY').value\n );\n tempSvg.attributes.width = new svg.Property(\n 'width', this.attribute('markerWidth').value\n );\n tempSvg.attributes.height = new svg.Property(\n 'height', this.attribute('markerHeight').value\n );\n tempSvg.attributes.fill = new svg.Property(\n 'fill', this.attribute('fill').valueOrDefault('black')\n );\n tempSvg.attributes.stroke = new svg.Property(\n 'stroke', this.attribute('stroke').valueOrDefault('none')\n );\n tempSvg.children = this.children;\n tempSvg.render(ctx);\n\n ctx.restore();\n if (this.attribute('markerUnits').valueOrDefault('strokeWidth') ===\n 'strokeWidth'\n ) ctx.scale(1 / ctx.lineWidth, 1 / ctx.lineWidth);\n if (this.attribute('orient').valueOrDefault('auto') === 'auto') {\n ctx.rotate(-angle);\n }\n ctx.translate(-point.x, -point.y);\n }\n };\n\n // definitions element\n svg.Element.defs = class extends svg.Element.ElementBase {\n render (ctx) {\n // NOOP\n }\n };\n\n // base for gradients\n svg.Element.GradientBase = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');\n\n this.stops = [];\n this.children.forEach((child) => {\n if (child.type === 'stop') {\n this.stops.push(child);\n }\n });\n }\n\n getGradient () {\n // OVERRIDE ME!\n }\n\n createGradient (ctx, element, parentOpacityProp) {\n const stopsContainer = this.getHrefAttribute().hasValue()\n ? this.getHrefAttribute().getDefinition()\n : this;\n\n const addParentOpacity = function (color) {\n if (parentOpacityProp.hasValue()) {\n const p = new svg.Property('color', color);\n return p.addOpacity(parentOpacityProp).value;\n }\n return color;\n };\n\n const g = this.getGradient(ctx, element);\n if (isNullish(g)) return addParentOpacity(stopsContainer.stops[stopsContainer.stops.length - 1].color);\n stopsContainer.stops.forEach(({offset, color}) => {\n g.addColorStop(offset, addParentOpacity(color));\n });\n\n if (this.attribute('gradientTransform').hasValue()) {\n // render as transformed pattern on temporary canvas\n const rootView = svg.ViewPort.viewPorts[0];\n\n const rect = new svg.Element.rect();\n rect.attributes.x = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS / 3.0);\n rect.attributes.y = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS / 3.0);\n rect.attributes.width = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);\n rect.attributes.height = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);\n\n const group = new svg.Element.g();\n group.attributes.transform = new svg.Property('transform', this.attribute('gradientTransform').value);\n group.children = [rect];\n\n const tempSvg = new svg.Element.svg();\n tempSvg.attributes.x = new svg.Property('x', 0);\n tempSvg.attributes.y = new svg.Property('y', 0);\n tempSvg.attributes.width = new svg.Property('width', rootView.width);\n tempSvg.attributes.height = new svg.Property('height', rootView.height);\n tempSvg.children = [group];\n\n const c = document.createElement('canvas');\n c.width = rootView.width;\n c.height = rootView.height;\n const tempCtx = c.getContext('2d');\n tempCtx.fillStyle = g;\n tempSvg.render(tempCtx);\n return tempCtx.createPattern(c, 'no-repeat');\n }\n\n return g;\n }\n };\n\n // linear gradient element\n svg.Element.linearGradient = class extends svg.Element.GradientBase {\n getGradient (ctx, element) {\n const useBB = this.gradientUnits === 'objectBoundingBox' && element.getBoundingBox;\n const bb = useBB\n ? element.getBoundingBox()\n : null;\n\n if (!this.attribute('x1').hasValue() &&\n !this.attribute('y1').hasValue() &&\n !this.attribute('x2').hasValue() &&\n !this.attribute('y2').hasValue()\n ) {\n this.attribute('x1', true).value = 0;\n this.attribute('y1', true).value = 0;\n this.attribute('x2', true).value = 1;\n this.attribute('y2', true).value = 0;\n }\n\n const x1 = (useBB\n ? bb.x() + bb.width() * this.attribute('x1').numValue()\n : this.attribute('x1').toPixels('x'));\n const y1 = (useBB\n ? bb.y() + bb.height() * this.attribute('y1').numValue()\n : this.attribute('y1').toPixels('y'));\n const x2 = (useBB\n ? bb.x() + bb.width() * this.attribute('x2').numValue()\n : this.attribute('x2').toPixels('x'));\n const y2 = (useBB\n ? bb.y() + bb.height() * this.attribute('y2').numValue()\n : this.attribute('y2').toPixels('y'));\n\n if (x1 === x2 && y1 === y2) return null;\n return ctx.createLinearGradient(x1, y1, x2, y2);\n }\n };\n\n // radial gradient element\n svg.Element.radialGradient = class extends svg.Element.GradientBase {\n getGradient (ctx, element) {\n const useBB = this.gradientUnits === 'objectBoundingBox' && element.getBoundingBox;\n const bb = useBB ? element.getBoundingBox() : null;\n\n if (!this.attribute('cx').hasValue()) this.attribute('cx', true).value = '50%';\n if (!this.attribute('cy').hasValue()) this.attribute('cy', true).value = '50%';\n if (!this.attribute('r').hasValue()) this.attribute('r', true).value = '50%';\n\n const cx = (useBB\n ? bb.x() + bb.width() * this.attribute('cx').numValue()\n : this.attribute('cx').toPixels('x'));\n const cy = (useBB\n ? bb.y() + bb.height() * this.attribute('cy').numValue()\n : this.attribute('cy').toPixels('y'));\n\n let fx = cx;\n let fy = cy;\n if (this.attribute('fx').hasValue()) {\n fx = (useBB\n ? bb.x() + bb.width() * this.attribute('fx').numValue()\n : this.attribute('fx').toPixels('x'));\n }\n if (this.attribute('fy').hasValue()) {\n fy = (useBB\n ? bb.y() + bb.height() * this.attribute('fy').numValue()\n : this.attribute('fy').toPixels('y'));\n }\n\n const r = (useBB\n ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()\n : this.attribute('r').toPixels());\n\n return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);\n }\n };\n\n // gradient stop element\n svg.Element.stop = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.offset = this.attribute('offset').numValue();\n if (this.offset < 0) this.offset = 0;\n if (this.offset > 1) this.offset = 1;\n\n let stopColor = this.style('stop-color');\n if (this.style('stop-opacity').hasValue()) {\n stopColor = stopColor.addOpacity(this.style('stop-opacity'));\n }\n this.color = stopColor.value;\n }\n };\n\n // animation base element\n svg.Element.AnimateBase = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n svg.Animations.push(this);\n\n this.duration = 0.0;\n this.begin = this.attribute('begin').toMilliseconds();\n this.maxDuration = this.begin + this.attribute('dur').toMilliseconds();\n\n this.initialValue = null;\n this.initialUnits = '';\n this.removed = false;\n\n this.from = this.attribute('from');\n this.to = this.attribute('to');\n this.values = this.attribute('values');\n if (this.values.hasValue()) this.values.value = this.values.value.split(';');\n }\n\n getProperty () {\n const attributeType = this.attribute('attributeType').value;\n const attributeName = this.attribute('attributeName').value;\n\n if (attributeType === 'CSS') {\n return this.parent.style(attributeName, true);\n }\n return this.parent.attribute(attributeName, true);\n }\n\n calcValue () {\n // OVERRIDE ME!\n return '';\n }\n\n update (delta) {\n // set initial value\n if (isNullish(this.initialValue)) {\n this.initialValue = this.getProperty().value;\n this.initialUnits = this.getProperty().getUnits();\n }\n\n // if we're past the end time\n if (this.duration > this.maxDuration) {\n // loop for indefinitely repeating animations\n if (this.attribute('repeatCount').value === 'indefinite' ||\n this.attribute('repeatDur').value === 'indefinite') {\n this.duration = 0.0;\n } else if (this.attribute('fill').valueOrDefault('remove') === 'freeze' && !this.frozen) {\n this.frozen = true;\n this.parent.animationFrozen = true;\n this.parent.animationFrozenValue = this.getProperty().value;\n } else if (this.attribute('fill').valueOrDefault('remove') === 'remove' && !this.removed) {\n this.removed = true;\n this.getProperty().value = this.parent.animationFrozen ? this.parent.animationFrozenValue : this.initialValue;\n return true;\n }\n return false;\n }\n this.duration += delta;\n\n // if we're past the begin time\n let updated = false;\n if (this.begin < this.duration) {\n let newValue = this.calcValue(); // tween\n\n if (this.attribute('type').hasValue()) {\n // for transform, etc.\n const type = this.attribute('type').value;\n newValue = type + '(' + newValue + ')';\n }\n\n this.getProperty().value = newValue;\n updated = true;\n }\n\n return updated;\n }\n\n // fraction of duration we've covered\n progress () {\n const ret = {progress: (this.duration - this.begin) / (this.maxDuration - this.begin)};\n if (this.values.hasValue()) {\n const p = ret.progress * (this.values.value.length - 1);\n const lb = Math.floor(p), ub = Math.ceil(p);\n ret.from = new svg.Property('from', Number.parseFloat(this.values.value[lb]));\n ret.to = new svg.Property('to', Number.parseFloat(this.values.value[ub]));\n ret.progress = (p - lb) / (ub - lb);\n } else {\n ret.from = this.from;\n ret.to = this.to;\n }\n return ret;\n }\n };\n\n // animate element\n svg.Element.animate = class extends svg.Element.AnimateBase {\n calcValue () {\n const p = this.progress();\n\n // tween value linearly\n const newValue = p.from.numValue() + (p.to.numValue() - p.from.numValue()) * p.progress;\n return newValue + this.initialUnits;\n }\n };\n\n // animate color element\n svg.Element.animateColor = class extends svg.Element.AnimateBase {\n calcValue () {\n const p = this.progress();\n const from = new RGBColor(p.from.value);\n const to = new RGBColor(p.to.value);\n\n if (from.ok && to.ok) {\n // tween color linearly\n const r = from.r + (to.r - from.r) * p.progress;\n const g = from.g + (to.g - from.g) * p.progress;\n const b = from.b + (to.b - from.b) * p.progress;\n return 'rgb(' + Number.parseInt(r) + ',' + Number.parseInt(g) + ',' + Number.parseInt(b) + ')';\n }\n return this.attribute('from').value;\n }\n };\n\n // animate transform element\n svg.Element.animateTransform = class extends svg.Element.animate {\n calcValue () {\n const p = this.progress();\n\n // tween value linearly\n const from = svg.ToNumberArray(p.from.value);\n const to = svg.ToNumberArray(p.to.value);\n let newValue = '';\n from.forEach((fr, i) => {\n newValue += fr + (to[i] - fr) * p.progress + ' ';\n });\n return newValue;\n }\n };\n\n // font element\n svg.Element.font = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.horizAdvX = this.attribute('horiz-adv-x').numValue();\n\n this.isRTL = false;\n this.isArabic = false;\n this.fontFace = null;\n this.missingGlyph = null;\n this.glyphs = [];\n this.children.forEach((child) => {\n if (child.type === 'font-face') {\n this.fontFace = child;\n if (child.style('font-family').hasValue()) {\n svg.Definitions[child.style('font-family').value] = this;\n }\n } else if (child.type === 'missing-glyph') {\n this.missingGlyph = child;\n } else if (child.type === 'glyph') {\n if (child.arabicForm !== '') {\n this.isRTL = true;\n this.isArabic = true;\n if (typeof this.glyphs[child.unicode] === 'undefined') {\n this.glyphs[child.unicode] = [];\n }\n this.glyphs[child.unicode][child.arabicForm] = child;\n } else {\n this.glyphs[child.unicode] = child;\n }\n }\n });\n }\n };\n\n // font-face element\n svg.Element.fontface = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.ascent = this.attribute('ascent').value;\n this.descent = this.attribute('descent').value;\n this.unitsPerEm = this.attribute('units-per-em').numValue();\n }\n };\n\n // missing-glyph element\n svg.Element.missingglyph = class extends svg.Element.path {\n constructor (node) {\n super(node);\n\n this.horizAdvX = 0;\n }\n };\n\n // glyph element\n svg.Element.glyph = class extends svg.Element.path {\n constructor (node) {\n super(node);\n\n this.horizAdvX = this.attribute('horiz-adv-x').numValue();\n this.unicode = this.attribute('unicode').value;\n this.arabicForm = this.attribute('arabic-form').value;\n }\n };\n\n // text element\n svg.Element.text = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node, true);\n }\n\n setContext (ctx) {\n super.setContext(ctx);\n\n let textBaseline = this.style('dominant-baseline').toTextBaseline();\n if (isNullish(textBaseline)) textBaseline = this.style('alignment-baseline').toTextBaseline();\n if (!isNullish(textBaseline)) ctx.textBaseline = textBaseline;\n }\n\n getBoundingBox () {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n return new svg.BoundingBox(x, y - fontSize, x + Math.floor(fontSize * 2.0 / 3.0) * this.children[0].getText().length, y);\n }\n\n renderChildren (ctx) {\n this.x = this.attribute('x').toPixels('x');\n this.y = this.attribute('y').toPixels('y');\n this.x += this.getAnchorDelta(ctx, this, 0);\n this.children.forEach((child, i) => {\n this.renderChild(ctx, this, i);\n });\n }\n\n getAnchorDelta (ctx, parent, startI) {\n const textAnchor = this.style('text-anchor').valueOrDefault('start');\n if (textAnchor !== 'start') {\n let width = 0;\n for (let i = startI; i < parent.children.length; i++) {\n const child = parent.children[i];\n if (i > startI && child.attribute('x').hasValue()) break; // new group\n width += child.measureTextRecursive(ctx);\n }\n return -1 * (textAnchor === 'end' ? width : width / 2.0);\n }\n return 0;\n }\n\n renderChild (ctx, parent, i) {\n const child = parent.children[i];\n if (child.attribute('x').hasValue()) {\n child.x = child.attribute('x').toPixels('x') + this.getAnchorDelta(ctx, parent, i);\n if (child.attribute('dx').hasValue()) child.x += child.attribute('dx').toPixels('x');\n } else {\n if (this.attribute('dx').hasValue()) this.x += this.attribute('dx').toPixels('x');\n if (child.attribute('dx').hasValue()) this.x += child.attribute('dx').toPixels('x');\n child.x = this.x;\n }\n this.x = child.x + child.measureText(ctx);\n\n if (child.attribute('y').hasValue()) {\n child.y = child.attribute('y').toPixels('y');\n if (child.attribute('dy').hasValue()) child.y += child.attribute('dy').toPixels('y');\n } else {\n if (this.attribute('dy').hasValue()) this.y += this.attribute('dy').toPixels('y');\n if (child.attribute('dy').hasValue()) this.y += child.attribute('dy').toPixels('y');\n child.y = this.y;\n }\n this.y = child.y;\n\n child.render(ctx);\n\n for (let j = 0; j < child.children.length; j++) {\n this.renderChild(ctx, child, j);\n }\n }\n };\n\n // text base\n svg.Element.TextElementBase = class extends svg.Element.RenderedElementBase {\n getGlyph (font, text, i) {\n const c = text[i];\n let glyph = null;\n if (font.isArabic) {\n let arabicForm = 'isolated';\n if ((i === 0 || text[i - 1] === ' ') && i < text.length - 2 && text[i + 1] !== ' ') arabicForm = 'terminal';\n if (i > 0 && text[i - 1] !== ' ' && i < text.length - 2 && text[i + 1] !== ' ') arabicForm = 'medial';\n if (i > 0 && text[i - 1] !== ' ' && (i === text.length - 1 || text[i + 1] === ' ')) arabicForm = 'initial';\n if (typeof font.glyphs[c] !== 'undefined') {\n glyph = font.glyphs[c][arabicForm];\n if (isNullish(glyph) && font.glyphs[c].type === 'glyph') glyph = font.glyphs[c];\n }\n } else {\n glyph = font.glyphs[c];\n }\n if (isNullish(glyph)) glyph = font.missingGlyph;\n return glyph;\n }\n\n renderChildren (ctx) {\n const customFont = this.parent.style('font-family').getDefinition();\n if (!isNullish(customFont)) {\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n const fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);\n let text = this.getText();\n if (customFont.isRTL) text = text.split('').reverse().join('');\n\n const dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n for (let i = 0; i < text.length; i++) {\n const glyph = this.getGlyph(customFont, text, i);\n const scale = fontSize / customFont.fontFace.unitsPerEm;\n ctx.translate(this.x, this.y);\n ctx.scale(scale, -scale);\n const lw = ctx.lineWidth;\n ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;\n if (fontStyle === 'italic') ctx.transform(1, 0, 0.4, 1, 0, 0);\n glyph.render(ctx);\n if (fontStyle === 'italic') ctx.transform(1, 0, -0.4, 1, 0, 0);\n ctx.lineWidth = lw;\n ctx.scale(1 / scale, -1 / scale);\n ctx.translate(-this.x, -this.y);\n\n this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;\n if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {\n this.x += dx[i];\n }\n }\n return;\n }\n\n if (ctx.fillStyle !== '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);\n if (ctx.strokeStyle !== '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);\n }\n\n getText () {\n // OVERRIDE ME\n }\n\n measureTextRecursive (ctx) {\n let width = this.measureText(ctx);\n this.children.forEach((child) => {\n width += child.measureTextRecursive(ctx);\n });\n return width;\n }\n\n measureText (ctx) {\n const customFont = this.parent.style('font-family').getDefinition();\n if (!isNullish(customFont)) {\n const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n let measure = 0;\n let text = this.getText();\n if (customFont.isRTL) text = text.split('').reverse().join('');\n const dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n for (let i = 0; i < text.length; i++) {\n const glyph = this.getGlyph(customFont, text, i);\n measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;\n if (typeof dx[i] !== 'undefined' && !isNaN(dx[i])) {\n measure += dx[i];\n }\n }\n return measure;\n }\n\n const textToMeasure = svg.compressSpaces(this.getText());\n if (!ctx.measureText) return textToMeasure.length * 10;\n\n ctx.save();\n this.setContext(ctx);\n const {width} = ctx.measureText(textToMeasure);\n ctx.restore();\n return width;\n }\n };\n\n // tspan\n svg.Element.tspan = class extends svg.Element.TextElementBase {\n constructor (node) {\n super(node, true);\n\n this.text = node.nodeValue || node.text || '';\n }\n getText () {\n return this.text;\n }\n };\n\n // tref\n svg.Element.tref = class extends svg.Element.TextElementBase {\n getText () {\n const element = this.getHrefAttribute().getDefinition();\n if (!isNullish(element)) return element.children[0].getText();\n return undefined;\n }\n };\n\n // a element\n svg.Element.a = class extends svg.Element.TextElementBase {\n constructor (node) {\n super(node);\n\n this.hasText = true;\n [...node.childNodes].forEach((childNode) => {\n if (childNode.nodeType !== 3) {\n this.hasText = false;\n }\n });\n // this might contain text\n this.text = this.hasText ? node.childNodes[0].nodeValue : '';\n }\n\n getText () {\n return this.text;\n }\n\n renderChildren (ctx) {\n if (this.hasText) {\n // render as text element\n super.renderChildren(ctx);\n const fontSize = new svg.Property(\n 'fontSize', svg.Font.Parse(svg.ctx.font).fontSize\n );\n svg.Mouse.checkBoundingBox(\n this, new svg.BoundingBox(\n this.x,\n this.y - fontSize.toPixels('y'),\n this.x + this.measureText(ctx),\n this.y\n )\n );\n } else {\n // render as temporary group\n const g = new svg.Element.g();\n g.children = this.children;\n g.parent = this;\n g.render(ctx);\n }\n }\n\n onclick () {\n window.open(this.getHrefAttribute().value);\n }\n\n onmousemove () {\n svg.ctx.canvas.style.cursor = 'pointer';\n }\n };\n\n // image element\n svg.Element.image = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node);\n\n const href = this.getHrefAttribute().value;\n if (href === '') {\n return;\n }\n this._isSvg = href.match(/\\.svg$/);\n\n svg.Images.push(this);\n this.loaded = false;\n if (!this._isSvg) {\n this.img = document.createElement('img');\n if (svg.opts.useCORS === true) {\n this.img.crossOrigin = 'Anonymous';\n }\n this.img.addEventListener('load', () => {\n this.loaded = true;\n });\n this.img.addEventListener('error', () => {\n svg.log('ERROR: image \"' + href + '\" not found');\n this.loaded = true;\n });\n this.img.src = href;\n } else {\n svg.ajax(href, true).then((img) => { // eslint-disable-line promise/prefer-await-to-then, promise/always-return\n this.img = img;\n this.loaded = true;\n }).catch((err) => { // eslint-disable-line promise/prefer-await-to-callbacks\n this.erred = true;\n console.error('Ajax error for canvg', err); // eslint-disable-line no-console\n });\n }\n }\n renderChildren (ctx) {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n if (width === 0 || height === 0) return;\n\n ctx.save();\n if (this._isSvg) {\n ctx.drawSvg(this.img, x, y, width, height);\n } else {\n ctx.translate(x, y);\n svg.AspectRatio(\n ctx,\n this.attribute('preserveAspectRatio').value,\n width,\n this.img.width,\n height,\n this.img.height,\n 0,\n 0\n );\n ctx.drawImage(this.img, 0, 0);\n }\n ctx.restore();\n }\n\n getBoundingBox () {\n const x = this.attribute('x').toPixels('x');\n const y = this.attribute('y').toPixels('y');\n const width = this.attribute('width').toPixels('x');\n const height = this.attribute('height').toPixels('y');\n return new svg.BoundingBox(x, y, x + width, y + height);\n }\n };\n\n // group element\n svg.Element.g = class extends svg.Element.RenderedElementBase {\n getBoundingBox () {\n const bb = new svg.BoundingBox();\n this.children.forEach((child) => {\n bb.addBoundingBox(child.getBoundingBox());\n });\n return bb;\n }\n };\n\n // symbol element\n svg.Element.symbol = class extends svg.Element.RenderedElementBase {\n render (ctx) {\n // NO RENDER\n }\n };\n\n // style element\n svg.Element.style = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n // text, or spaces then CDATA\n let css = '';\n [...node.childNodes].forEach(({nodeValue}) => {\n css += nodeValue;\n });\n // remove comments\n css = css.replace(/(\\/\\*([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+\\/)|(^\\s*\\/\\/.*)/gm, ''); // eslint-disable-line unicorn/no-unsafe-regex\n // replace whitespace\n css = svg.compressSpaces(css);\n const cssDefs = css.split('}');\n cssDefs.forEach((cssDef) => {\n if (svg.trim(cssDef) !== '') {\n let [cssClasses, cssProps] = cssDef.split('{');\n cssClasses = cssClasses.split(',');\n cssProps = cssProps.split(';');\n cssClasses.forEach((cssClass) => {\n cssClass = svg.trim(cssClass);\n if (cssClass !== '') {\n const props = {};\n cssProps.forEach((cssProp) => {\n const prop = cssProp.indexOf(':');\n const name = cssProp.substr(0, prop);\n const value = cssProp.substr(prop + 1, cssProp.length - prop);\n if (!isNullish(name) && !isNullish(value)) {\n props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));\n }\n });\n svg.Styles[cssClass] = props;\n if (cssClass === '@font-face') {\n const fontFamily = props['font-family'].value.replace(/\"/g, '');\n const srcs = props.src.value.split(',');\n srcs.forEach((src) => {\n if (src.includes('format(\"svg\")')) {\n const urlStart = src.indexOf('url');\n const urlEnd = src.indexOf(')', urlStart);\n const url = src.substr(urlStart + 5, urlEnd - urlStart - 6);\n // Can this ajax safely be converted to async?\n const doc = svg.parseXml(svg.ajax(url));\n const fonts = doc.getElementsByTagName('font');\n [...fonts].forEach((font) => {\n font = svg.CreateElement(font);\n svg.Definitions[fontFamily] = font;\n });\n }\n });\n }\n }\n });\n }\n });\n }\n };\n\n // use element\n svg.Element.use = class extends svg.Element.RenderedElementBase {\n constructor (node) {\n super(node);\n\n this._el = this.getHrefAttribute().getDefinition();\n }\n\n setContext (ctx) {\n super.setContext(ctx);\n if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').toPixels('x'), 0);\n if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').toPixels('y'));\n }\n\n path (ctx) {\n const {_el: element} = this;\n if (!isNullish(element)) element.path(ctx);\n }\n\n getBoundingBox () {\n const {_el: element} = this;\n if (!isNullish(element)) return element.getBoundingBox();\n return undefined;\n }\n\n renderChildren (ctx) {\n const {_el: element} = this;\n if (!isNullish(element)) {\n let tempSvg = element;\n if (element.type === 'symbol') {\n // render me using a temporary svg element in symbol cases\n // (https://www.w3.org/TR/SVG/struct.html#UseElement)\n tempSvg = new svg.Element.svg();\n tempSvg.type = 'svg';\n tempSvg.attributes.viewBox = new svg.Property(\n 'viewBox', element.attribute('viewBox').value\n );\n tempSvg.attributes.preserveAspectRatio = new svg.Property(\n 'preserveAspectRatio', element.attribute('preserveAspectRatio').value\n );\n tempSvg.attributes.overflow = new svg.Property(\n 'overflow', element.attribute('overflow').value\n );\n tempSvg.children = element.children;\n }\n if (tempSvg.type === 'svg') {\n // if symbol or svg, inherit width/height from me\n if (this.attribute('width').hasValue()) {\n tempSvg.attributes.width = new svg.Property(\n 'width', this.attribute('width').value\n );\n }\n if (this.attribute('height').hasValue()) {\n tempSvg.attributes.height = new svg.Property(\n 'height', this.attribute('height').value\n );\n }\n }\n const oldParent = tempSvg.parent;\n tempSvg.parent = null;\n tempSvg.render(ctx);\n tempSvg.parent = oldParent;\n }\n }\n };\n\n // mask element\n svg.Element.mask = class extends svg.Element.ElementBase {\n apply (ctx, element) {\n // render as temp svg\n let x = this.attribute('x').toPixels('x');\n let y = this.attribute('y').toPixels('y');\n let width = this.attribute('width').toPixels('x');\n let height = this.attribute('height').toPixels('y');\n\n if (width === 0 && height === 0) {\n const bb = new svg.BoundingBox();\n this.children.forEach((child) => {\n bb.addBoundingBox(child.getBoundingBox());\n });\n x = Math.floor(bb.x1);\n y = Math.floor(bb.y1);\n width = Math.floor(bb.width());\n height = Math.floor(bb.height());\n }\n\n // temporarily remove mask to avoid recursion\n const mask = element.attribute('mask').value;\n element.attribute('mask').value = '';\n\n const cMask = document.createElement('canvas');\n cMask.width = x + width;\n cMask.height = y + height;\n const maskCtx = cMask.getContext('2d');\n this.renderChildren(maskCtx);\n\n const c = document.createElement('canvas');\n c.width = x + width;\n c.height = y + height;\n const tempCtx = c.getContext('2d');\n element.render(tempCtx);\n tempCtx.globalCompositeOperation = 'destination-in';\n tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');\n tempCtx.fillRect(0, 0, x + width, y + height);\n\n ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');\n ctx.fillRect(0, 0, x + width, y + height);\n\n // reassign mask\n element.attribute('mask').value = mask;\n }\n\n render (ctx) {\n // NO RENDER\n }\n };\n\n // clip element\n svg.Element.clipPath = class extends svg.Element.ElementBase {\n apply (ctx) {\n this.children.forEach((child) => {\n if (typeof child.path !== 'undefined') {\n let transform = null;\n if (child.attribute('transform').hasValue()) {\n transform = new svg.Transform(child.attribute('transform').value);\n transform.apply(ctx);\n }\n child.path(ctx);\n ctx.clip();\n if (transform) { transform.unapply(ctx); }\n }\n });\n }\n render (ctx) {\n // NO RENDER\n }\n };\n\n // filters\n svg.Element.filter = class extends svg.Element.ElementBase {\n apply (ctx, element) {\n // render as temp svg\n const bb = element.getBoundingBox();\n const x = Math.floor(bb.x1);\n const y = Math.floor(bb.y1);\n const width = Math.floor(bb.width());\n const height = Math.floor(bb.height());\n\n // temporarily remove filter to avoid recursion\n const filter = element.style('filter').value;\n element.style('filter').value = '';\n\n let px = 0, py = 0;\n this.children.forEach((child) => {\n const efd = child.extraFilterDistance || 0;\n px = Math.max(px, efd);\n py = Math.max(py, efd);\n });\n\n const c = document.createElement('canvas');\n c.width = width + 2 * px;\n c.height = height + 2 * py;\n const tempCtx = c.getContext('2d');\n tempCtx.translate(-x + px, -y + py);\n element.render(tempCtx);\n\n // apply filters\n this.children.forEach((child) => {\n child.apply(tempCtx, 0, 0, width + 2 * px, height + 2 * py);\n });\n\n // render on me\n ctx.drawImage(c, 0, 0, width + 2 * px, height + 2 * py, x - px, y - py, width + 2 * px, height + 2 * py);\n\n // reassign filter\n element.style('filter', true).value = filter;\n }\n\n render (ctx) {\n // NO RENDER\n }\n };\n\n svg.Element.feMorphology = class extends svg.Element.ElementBase {\n apply (ctx, x, y, width, height) {\n // TODO: implement\n }\n };\n\n svg.Element.feComposite = class extends svg.Element.ElementBase {\n apply (ctx, x, y, width, height) {\n // TODO: implement\n }\n };\n\n /**\n * @param {Uint8ClampedArray} img\n * @param {Integer} x\n * @param {Integer} y\n * @param {Float} width\n * @param {Float} height\n * @param {Integer} rgba\n * @returns {Integer}\n */\n function imGet (img, x, y, width, height, rgba) {\n return img[y * width * 4 + x * 4 + rgba];\n }\n\n /**\n * @param {Uint8ClampedArray} img\n * @param {Integer} x\n * @param {Integer} y\n * @param {Float} width\n * @param {Float} height\n * @param {Integer} rgba\n * @param {Float} val\n * @returns {void}\n */\n function imSet (img, x, y, width, height, rgba, val) {\n img[y * width * 4 + x * 4 + rgba] = val;\n }\n\n svg.Element.feColorMatrix = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n let matrix = svg.ToNumberArray(this.attribute('values').value);\n switch (this.attribute('type').valueOrDefault('matrix')) { // https://www.w3.org/TR/SVG/filters.html#feColorMatrixElement\n case 'saturate': {\n const s = matrix[0];\n matrix = [\n 0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s, 0, 0,\n 0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s, 0, 0,\n 0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s, 0, 0,\n 0, 0, 0, 1, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n } case 'hueRotate': {\n const a = matrix[0] * Math.PI / 180.0;\n const c = function (m1, m2, m3) {\n return m1 + Math.cos(a) * m2 + Math.sin(a) * m3;\n };\n matrix = [\n c(0.213, 0.787, -0.213), c(0.715, -0.715, -0.715), c(0.072, -0.072, 0.928), 0, 0,\n c(0.213, -0.213, 0.143), c(0.715, 0.285, 0.140), c(0.072, -0.072, -0.283), 0, 0,\n c(0.213, -0.213, -0.787), c(0.715, -0.715, 0.715), c(0.072, 0.928, 0.072), 0, 0,\n 0, 0, 0, 1, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n } case 'luminanceToAlpha':\n matrix = [\n 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0,\n 0.2125, 0.7154, 0.0721, 0, 0,\n 0, 0, 0, 0, 1\n ];\n break;\n }\n this.matrix = matrix;\n\n this._m = (i, v) => {\n const mi = matrix[i];\n return mi * (mi < 0 ? v - 255 : v);\n };\n }\n apply (ctx, x, y, width, height) {\n const {_m: m} = this;\n // assuming x==0 && y==0 for now\n const srcData = ctx.getImageData(0, 0, width, height);\n for (let _y = 0; _y < height; _y++) {\n for (let _x = 0; _x < width; _x++) {\n const r = imGet(srcData.data, _x, _y, width, height, 0);\n const g = imGet(srcData.data, _x, _y, width, height, 1);\n const b = imGet(srcData.data, _x, _y, width, height, 2);\n const a = imGet(srcData.data, _x, _y, width, height, 3);\n imSet(srcData.data, _x, _y, width, height, 0, m(0, r) + m(1, g) + m(2, b) + m(3, a) + m(4, 1));\n imSet(srcData.data, _x, _y, width, height, 1, m(5, r) + m(6, g) + m(7, b) + m(8, a) + m(9, 1));\n imSet(srcData.data, _x, _y, width, height, 2, m(10, r) + m(11, g) + m(12, b) + m(13, a) + m(14, 1));\n imSet(srcData.data, _x, _y, width, height, 3, m(15, r) + m(16, g) + m(17, b) + m(18, a) + m(19, 1));\n }\n }\n ctx.clearRect(0, 0, width, height);\n ctx.putImageData(srcData, 0, 0);\n }\n };\n\n svg.Element.feGaussianBlur = class extends svg.Element.ElementBase {\n constructor (node) {\n super(node);\n\n this.blurRadius = Math.floor(this.attribute('stdDeviation').numValue());\n this.extraFilterDistance = this.blurRadius;\n }\n\n apply (ctx, x, y, width, height) {\n // Todo: This might not be a problem anymore with out `instanceof` fix\n // StackBlur requires canvas be on document\n ctx.canvas.id = svg.UniqueId();\n ctx.canvas.style.display = 'none';\n document.body.append(ctx.canvas);\n canvasRGBA(ctx.canvas, x, y, width, height, this.blurRadius);\n ctx.canvas.remove();\n }\n };\n\n // title element, do nothing\n svg.Element.title = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n }\n };\n\n // desc element, do nothing\n svg.Element.desc = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n }\n };\n\n svg.Element.MISSING = class extends svg.Element.ElementBase {\n constructor (node) {\n super();\n svg.log('ERROR: Element \\'' + node.nodeName + '\\' not yet implemented.');\n }\n };\n\n // element factory\n svg.CreateElement = function (node) {\n const className = node.nodeName\n .replace(/^[^:]+:/, '') // remove namespace\n .replace(/-/g, ''); // remove dashes\n let e;\n if (typeof svg.Element[className] !== 'undefined') {\n e = new svg.Element[className](node);\n } else {\n e = new svg.Element.MISSING(node);\n }\n\n e.type = node.nodeName;\n return e;\n };\n\n // load from url\n svg.load = async function (ctx, url) {\n const dom = await svg.ajax(url, true);\n return svg.loadXml(ctx, dom);\n };\n\n // load from xml\n svg.loadXml = function (ctx, xml) {\n return svg.loadXmlDoc(ctx, svg.parseXml(xml));\n };\n\n svg.loadXmlDoc = function (ctx, dom) {\n let res;\n svg.init(ctx);\n\n const mapXY = function (p) {\n let e = ctx.canvas;\n while (e) {\n p.x -= e.offsetLeft;\n p.y -= e.offsetTop;\n e = e.offsetParent;\n }\n if (window.scrollX) p.x += window.scrollX;\n if (window.scrollY) p.y += window.scrollY;\n return p;\n };\n\n // bind mouse\n if (svg.opts.ignoreMouse !== true) {\n ctx.canvas.addEventListener('click', function (e) {\n const args = !isNullish(e)\n ? [e.clientX, e.clientY]\n : [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals\n const {x, y} = mapXY(new svg.Point(...args));\n svg.Mouse.onclick(x, y);\n });\n ctx.canvas.addEventListener('mousemove', function (e) {\n const args = !isNullish(e)\n ? [e.clientX, e.clientY]\n : [event.clientX, event.clientY]; // eslint-disable-line no-restricted-globals\n const {x, y} = mapXY(new svg.Point(...args));\n svg.Mouse.onmousemove(x, y);\n });\n }\n\n const e = svg.CreateElement(dom.documentElement);\n e.root = true;\n\n // render loop\n let isFirstRender = true;\n const draw = function (resolve) {\n svg.ViewPort.Clear();\n if (ctx.canvas.parentNode) {\n svg.ViewPort.SetCurrent(\n ctx.canvas.parentNode.clientWidth,\n ctx.canvas.parentNode.clientHeight\n );\n }\n\n if (svg.opts.ignoreDimensions !== true) {\n // set canvas size\n if (e.style('width').hasValue()) {\n ctx.canvas.width = e.style('width').toPixels('x');\n ctx.canvas.style.width = ctx.canvas.width + 'px';\n }\n if (e.style('height').hasValue()) {\n ctx.canvas.height = e.style('height').toPixels('y');\n ctx.canvas.style.height = ctx.canvas.height + 'px';\n }\n }\n let cWidth = ctx.canvas.clientWidth || ctx.canvas.width;\n let cHeight = ctx.canvas.clientHeight || ctx.canvas.height;\n if (svg.opts.ignoreDimensions === true &&\n e.style('width').hasValue() && e.style('height').hasValue()\n ) {\n cWidth = e.style('width').toPixels('x');\n cHeight = e.style('height').toPixels('y');\n }\n svg.ViewPort.SetCurrent(cWidth, cHeight);\n\n if (!isNullish(svg.opts.offsetX)) {\n e.attribute('x', true).value = svg.opts.offsetX;\n }\n if (!isNullish(svg.opts.offsetY)) {\n e.attribute('y', true).value = svg.opts.offsetY;\n }\n if (!isNullish(svg.opts.scaleWidth) || !isNullish(svg.opts.scaleHeight)) {\n const viewBox = svg.ToNumberArray(e.attribute('viewBox').value);\n let xRatio = null, yRatio = null;\n\n if (!isNullish(svg.opts.scaleWidth)) {\n if (e.attribute('width').hasValue()) {\n xRatio = e.attribute('width').toPixels('x') / svg.opts.scaleWidth;\n } else if (!isNaN(viewBox[2])) {\n xRatio = viewBox[2] / svg.opts.scaleWidth;\n }\n }\n\n if (!isNullish(svg.opts.scaleHeight)) {\n if (e.attribute('height').hasValue()) {\n yRatio = e.attribute('height').toPixels('y') / svg.opts.scaleHeight;\n } else if (!isNaN(viewBox[3])) {\n yRatio = viewBox[3] / svg.opts.scaleHeight;\n }\n }\n\n if (isNullish(xRatio)) { xRatio = yRatio; }\n if (isNullish(yRatio)) { yRatio = xRatio; }\n\n e.attribute('width', true).value = svg.opts.scaleWidth;\n e.attribute('height', true).value = svg.opts.scaleHeight;\n e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);\n e.attribute('preserveAspectRatio', true).value = 'none';\n }\n\n // clear and render\n if (svg.opts.ignoreClear !== true) {\n ctx.clearRect(0, 0, cWidth, cHeight);\n }\n e.render(ctx);\n if (isFirstRender) {\n isFirstRender = false;\n resolve(dom);\n }\n };\n\n let waitingForImages = true;\n svg.intervalID = setInterval(function () {\n let needUpdate = false;\n\n if (waitingForImages && svg.ImagesLoaded()) {\n waitingForImages = false;\n needUpdate = true;\n }\n\n // need update from mouse events?\n if (svg.opts.ignoreMouse !== true) {\n needUpdate = needUpdate || svg.Mouse.hasEvents();\n }\n\n // need update from animations?\n if (svg.opts.ignoreAnimation !== true) {\n svg.Animations.forEach((animation) => {\n const needAnimationUpdate = animation.update(1000 / svg.FRAMERATE);\n needUpdate = needUpdate || needAnimationUpdate;\n });\n }\n\n // need update from redraw?\n if (typeof svg.opts.forceRedraw === 'function') {\n if (svg.opts.forceRedraw() === true) {\n needUpdate = true;\n }\n }\n\n // render if needed\n if (needUpdate) {\n draw(res);\n svg.Mouse.runEvents(); // run and clear our events\n }\n }, 1000 / svg.FRAMERATE);\n // Todo: Replace with an image loading Promise utility?\n // eslint-disable-next-line promise/avoid-new\n return new Promise((resolve, reject) => {\n if (svg.ImagesLoaded()) {\n waitingForImages = false;\n draw(resolve);\n return;\n }\n res = resolve;\n });\n };\n\n svg.stop = () => {\n if (svg.intervalID) {\n clearInterval(svg.intervalID);\n }\n };\n\n svg.Mouse = {\n events: [],\n hasEvents () { return this.events.length !== 0; },\n\n onclick (x, y) {\n this.events.push({\n type: 'onclick', x, y,\n run (e) { if (e.onclick) e.onclick(); }\n });\n },\n\n onmousemove (x, y) {\n this.events.push({\n type: 'onmousemove', x, y,\n run (e) { if (e.onmousemove) e.onmousemove(); }\n });\n },\n\n eventElements: [],\n\n checkPath (element, ctx) {\n this.events.forEach(({x, y}, i) => {\n if (ctx.isPointInPath && ctx.isPointInPath(x, y)) {\n this.eventElements[i] = element;\n }\n });\n },\n\n checkBoundingBox (element, bb) {\n this.events.forEach(({x, y}, i) => {\n if (bb.isPointInBox(x, y)) {\n this.eventElements[i] = element;\n }\n });\n },\n\n runEvents () {\n svg.ctx.canvas.style.cursor = '';\n\n this.events.forEach((e, i) => {\n let element = this.eventElements[i];\n while (element) {\n e.run(element);\n element = element.parent;\n }\n });\n\n // done running, clear\n this.events = [];\n this.eventElements = [];\n }\n };\n\n return svg;\n}\n\nif (typeof CanvasRenderingContext2D !== 'undefined') {\n CanvasRenderingContext2D.prototype.drawSvg = function (s, dx, dy, dw, dh) {\n canvg(this.canvas, s, {\n ignoreMouse: true,\n ignoreAnimation: true,\n ignoreDimensions: true,\n ignoreClear: true,\n offsetX: dx,\n offsetY: dy,\n scaleWidth: dw,\n scaleHeight: dh\n });\n };\n}\n","/**\n * @file ext-server_opensave.js\n *\n * @license MIT\n *\n * @copyright 2010 Alexis Deveria\n *\n */\nimport {canvg} from '../../external/canvg/canvg.js';\n\nexport default {\n name: 'server_opensave',\n async init ({$, decode64, encode64, importLocale}) {\n const strings = await importLocale();\n const svgEditor = this;\n const {\n curConfig: {\n extPath,\n avoidClientSide, // Deprecated\n avoidClientSideDownload, avoidClientSideOpen\n },\n canvas: svgCanvas\n } = svgEditor;\n\n /**\n *\n * @returns {string}\n */\n function getFileNameFromTitle () {\n const title = svgCanvas.getDocumentTitle();\n // We convert (to underscore) only those disallowed Win7 file name characters\n return title.trim().replace(/[/\\\\:*?\"<>|]/g, '_');\n }\n /**\n * Escapes XML predefined entities for quoted attributes.\n * @param {string} str\n * @returns {string}\n */\n function xhtmlEscape (str) {\n return str.replace(/&(?!amp;)/g, '&').replace(/\"/g, '"').replace(/')[0].download === '';\n let a;\n if (support) {\n a = $('hidden').attr({\n download: (filename || 'image') + suffix,\n href: uri\n }).css('display', 'none').appendTo('body');\n a[0].click();\n return true;\n }\n return false;\n }\n const\n saveSvgAction = extPath + 'filesave.php',\n saveImgAction = extPath + 'filesave.php';\n // Create upload target (hidden iframe)\n\n let cancelled = false;\n\n // Hiding by size instead of display to avoid FF console errors\n // with `getBBox` in browser.js `supportsPathBBox_`)\n $(\n `