diff --git a/package.json b/package.json index f68f20e..b5f5a49 100755 --- a/package.json +++ b/package.json @@ -35,8 +35,7 @@ "eslint-plugin-import": "^2.27.5", "jest": "^29.4.3", "rollup": "^3.15.0", - "rollup-plugin-cleanup": "^3.2.1", - "typescript": "^4.9.5" + "rollup-plugin-cleanup": "^3.2.1" }, "jest": { "collectCoverage": true diff --git a/rollup.config.js b/rollup.config.js index 973b293..76fbc4e 100755 --- a/rollup.config.js +++ b/rollup.config.js @@ -2,7 +2,7 @@ import cleanup from "rollup-plugin-cleanup"; import terser from "@rollup/plugin-terser"; const input = "src/index.js"; -const name = "svg"; +const name = "SVG"; const banner = "/* svg (c) Kraft, MIT License */"; export default [{ @@ -12,9 +12,14 @@ export default [{ file: "svg.js", format: "umd", banner, + compact: true, + generatedCode: { + constBindings: true, + objectShorthand: true, + }, }, - // plugins: [cleanup(), terser()], - plugins: [cleanup()], + plugins: [cleanup(), terser()], + // plugins: [cleanup()], }, { input, output: { @@ -22,6 +27,10 @@ export default [{ file: "svg.module.js", format: "es", banner, + generatedCode: { + constBindings: true, + objectShorthand: true, + }, }, plugins: [cleanup()], }, { @@ -37,14 +46,4 @@ export default [{ objectShorthand: true, }, }, - plugins: [cleanup()], -}, { - input, - output: { - name, - file: "svg.module.comments.js", - format: "es", - banner, - }, - plugins: [], }]; diff --git a/src/arguments/index.js b/src/arguments/index.js deleted file mode 100644 index 1679da5..0000000 --- a/src/arguments/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * SVG (c) Kraft - */ -import * as transformCase from "./transformCase.js"; -import makeCoordinates from "./makeCoordinates.js"; -import makeUUID from "./makeUUID.js"; -import makeViewBox from "./makeViewBox.js"; - -export default { - ...transformCase, - makeCoordinates, - makeUUID, - makeViewBox, -}; diff --git a/src/colors/convert.js b/src/colors/convert.js new file mode 100644 index 0000000..5aba438 --- /dev/null +++ b/src/colors/convert.js @@ -0,0 +1,68 @@ +/** + * Rabbit Ear (c) Kraft + */ +/** + * @description Convert hue-saturation-lightness values into + * three RGB values, each between 0 and 1 (not 0-255). + * @param {number} hue value between 0 and 360 + * @param {number} saturation value between 0 and 100 + * @param {number} lightness value between 0 and 100 + * @returns {number[]} three values between 0 and 255 + * @linkcode Origami ./src/convert/svgParsers/colors/hexToRGB.js 10 + */ +export const hslToRgb = (hue, saturation, lightness) => { + const s = saturation / 100; + const l = lightness / 100; + const k = n => (n + hue / 30) % 12; + const a = s * Math.min(l, 1 - l); + const f = n => ( + l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))) + ); + return [f(0) * 255, f(8) * 255, f(4) * 255]; +}; +/** + * + */ +const mapHexNumbers = (numbers, map) => { + // ensure a minimum number of characters (fill 0 if needed) + const chars = Array.from(Array(map.length)) + .map((_, i) => numbers[i] || "0"); + // handle abbreviated hex codes: #fb4 or #fb48 (with alpha) + return numbers.length <= 4 + ? map.map(i => chars[i]).join("") + : chars.join(""); +}; +/** + * @description Convert a hex string into an array of + * three numbers, the rgb values (between 0 and 1). + * This ignores any alpha values. + * @param {string} value a hex color code as a string + * @returns {number[]} three values between 0 and 255 + * @linkcode Origami ./src/convert/svgParsers/colors/hexToRGB.js 10 + */ +export const hexToRgb = (string) => { + const numbers = string.replace(/#(?=\S)/g, ""); + const hasAlpha = numbers.length === 4 || numbers.length === 8; + const hexString = hasAlpha + ? mapHexNumbers(numbers, [0, 0, 1, 1, 2, 2, 3, 3]) + : mapHexNumbers(numbers, [0, 0, 1, 1, 2, 2]); + const c = parseInt(hexString, 16); + return hasAlpha + ? [(c >> 24) & 255, (c >> 16) & 255, (c >> 8) & 255, c & 255] + : [(c >> 16) & 255, (c >> 8) & 255, c & 255]; +}; +/** + * @param {number} red the red component from 0 to 255 + * @param {number} green the green component from 0 to 255 + * @param {number} blue the blue component from 0 to 255 + * @param {number | undefined} alpha the alpha component from 0 to 1 + * @returns {string} hex string, with our without alpha. + */ +export const rgbToHex = (red, green, blue, alpha) => { + const to16 = n => `00${Math.max(0, Math.min(Math.round(n), 255)).toString(16)}` + .slice(-2); + const hex = `#${[red, green, blue].map(to16).join("")}`; + return alpha === undefined + ? hex + : `${hex}${to16(alpha * 255)}`; +}; diff --git a/src/colors/cssColors.js b/src/colors/cssColors.js new file mode 100644 index 0000000..1949bd5 --- /dev/null +++ b/src/colors/cssColors.js @@ -0,0 +1,149 @@ +export default { + black: "#000000", + silver: "#c0c0c0", + gray: "#808080", + white: "#ffffff", + maroon: "#800000", + red: "#ff0000", + purple: "#800080", + fuchsia: "#ff00ff", + green: "#008000", + lime: "#00ff00", + olive: "#808000", + yellow: "#ffff00", + navy: "#000080", + blue: "#0000ff", + teal: "#008080", + aqua: "#00ffff", + orange: "#ffa500", + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + blanchedalmond: "#ffebcd", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkgrey: "#a9a9a9", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + greenyellow: "#adff2f", + grey: "#808080", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgreen: "#90ee90", + lightgrey: "#d3d3d3", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370db", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + oldlace: "#fdf5e6", + olivedrab: "#6b8e23", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#db7093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + whitesmoke: "#f5f5f5", + yellowgreen: "#9acd32", +}; diff --git a/src/colors/index.js b/src/colors/index.js new file mode 100644 index 0000000..26d71a1 --- /dev/null +++ b/src/colors/index.js @@ -0,0 +1,12 @@ +/** + * SVG (c) Kraft + */ +import cssColors from "./cssColors.js"; +import * as convert from "./convert.js"; +import * as parseColor from "./parseColor.js"; + +export default { + cssColors, + ...convert, + ...parseColor, +}; diff --git a/src/colors/parseColor.js b/src/colors/parseColor.js new file mode 100644 index 0000000..e499da6 --- /dev/null +++ b/src/colors/parseColor.js @@ -0,0 +1,76 @@ +/** + * Rabbit Ear (c) Kraft + */ +import cssColors from "./cssColors.js"; +import { + hexToRgb, + hslToRgb, + rgbToHex, +} from "./convert.js"; +/** + * + */ +const getParenNumbers = str => { + const match = str.match(/\(([^\)]+)\)/g); + if (match == null || !match.length) { return []; } + return match[0] + .substring(1, match[0].length - 1) + .split(/[\s,]+/) + .map(parseFloat); +}; +/** + * @description input a color as a string and get back the RGB + * values as three numbers in an array. This supports CSS/SVG + * color strings like named colors, hex colors, rgb(), hsl(). + * @returns {number[] | undefined} red green blue values between 0 and 255, + * with possible 4th value between 0 and 1. + */ +export const parseColorToRgb = (string) => { + if (cssColors[string]) { return hexToRgb(cssColors[string]); } + if (string[0] === "#") { return hexToRgb(string); } + if (string.substring(0, 4) === "rgba" + || string.substring(0, 3) === "rgb") { + const values = getParenNumbers(string); + [0, 1, 2] + .filter(i => values[i] === undefined) + .forEach(i => { values[i] = 0; }); + return values; + } + if (string.substring(0, 4) === "hsla" + || string.substring(0, 3) === "hsl") { + const values = getParenNumbers(string); + [0, 1, 2] + .filter(i => values[i] === undefined) + .forEach(i => { values[i] = 0; }); + const rgb = hslToRgb(...values); + if (values.length === 4) { rgb.push(values[3]); } + return rgb; + } + return undefined; +}; +/** + * @description input a color as a string and return the + * same color as a hex value string. This supports CSS/SVG + * color strings like named colors, hex colors, rgb(), hsl(). + */ +export const parseColorToHex = (string) => { + if (cssColors[string]) { return cssColors[string].toUpperCase(); } + // convert back and forth, this converts 3 or 4 digit hex to 6 or 8. + if (string[0] === "#") { return rgbToHex(...hexToRgb(string)); } + if (string.substring(0, 4) === "rgba" + || string.substring(0, 3) === "rgb") { + return rgbToHex(...getParenNumbers(string)); + } + if (string.substring(0, 4) === "hsla" + || string.substring(0, 3) === "hsl") { + const values = getParenNumbers(string); + [0, 1, 2] + .filter(i => values[i] === undefined) + .forEach(i => { values[i] = 0; }); + const rgb = hslToRgb(...values); + if (values.length === 4) { rgb.push(values[3]); } + [0, 1, 2].forEach(i => { rgb[i] *= 255; }); + rgbToHex(...rgb); + } + return undefined; +}; diff --git a/src/constructor/extensions/arc/index.js b/src/constructor/extensions/arc/index.js new file mode 100755 index 0000000..74361f7 --- /dev/null +++ b/src/constructor/extensions/arc/index.js @@ -0,0 +1,20 @@ +/** + * SVG (c) Kraft + */ +import makeArcPath from "../shared/makeArcPath.js"; +import { str_path } from "../../../environment/strings.js"; +import TransformMethods from "../shared/transforms.js"; + +const arcArguments = (a, b, c, d, e) => [makeArcPath(a, b, c, d, e, false)]; + +export default { + arc: { + nodeName: str_path, + attributes: ["d"], + args: arcArguments, + methods: { + setArc: (el, ...args) => el.setAttribute("d", arcArguments(...args)), + ...TransformMethods, + }, + }, +}; diff --git a/src/constructor/extensions/arrow/index.js b/src/constructor/extensions/arrow/index.js new file mode 100755 index 0000000..7465521 --- /dev/null +++ b/src/constructor/extensions/arrow/index.js @@ -0,0 +1,15 @@ +/** + * SVG (c) Kraft + */ +import ArrowMethods from "./methods.js"; +import init from "./init.js"; + +export default { + arrow: { + nodeName: "g", + attributes: [], + args: () => [], // one function + methods: ArrowMethods, // object of functions + init, + }, +}; diff --git a/src/constructor/extensions/arrow/init.js b/src/constructor/extensions/arrow/init.js new file mode 100755 index 0000000..bb47f7b --- /dev/null +++ b/src/constructor/extensions/arrow/init.js @@ -0,0 +1,60 @@ +/** + * SVG (c) Kraft + */ +import { + str_object, + str_arrow, + str_class, + str_path, + str_tail, + str_head, + str_style, + str_stroke, + str_none, +} from "../../../environment/strings.js"; +import NS from "../../../spec/namespace.js"; +import window from "../../../environment/window.js"; +import ArrowMethods from "./methods.js"; +import { makeArrowOptions } from "./options.js"; +// import Library from "../../../library.js"; + +const arrowKeys = Object.keys(makeArrowOptions()); + +const matchingOptions = (...args) => { + for (let a = 0; a < args.length; a += 1) { + if (typeof args[a] !== str_object) { continue; } + const keys = Object.keys(args[a]); + for (let i = 0; i < keys.length; i += 1) { + if (arrowKeys.includes(keys[i])) { + return args[a]; + } + } + } + return undefined; +}; + +const init = function (element, ...args) { + element.classList.add(str_arrow); + // element.setAttribute(str_class, str_arrow); + const paths = ["line", str_tail, str_head].map(key => { + const path = window().document.createElementNS(NS, str_path); + path.className = `${str_arrow}-${key}`; + element.appendChild(path); + return path; + }); + // .map(key => Library.path().addClass(`${str_arrow}-${key}`).appendTo(element)); + paths[0].setAttribute(str_style, "fill:none;"); + paths[1].setAttribute(str_stroke, str_none); + paths[2].setAttribute(str_stroke, str_none); + element.options = makeArrowOptions(); + ArrowMethods.setPoints(element, ...args); + const options = matchingOptions(...args); + if (options) { + Object.keys(options) + .filter(key => ArrowMethods[key]) + .forEach(key => ArrowMethods[key](element, options[key])); + } + return element; +}; + +export default init; diff --git a/src/constructor/extensions/arrow/makeArrowPaths.js b/src/constructor/extensions/arrow/makeArrowPaths.js new file mode 100644 index 0000000..c987070 --- /dev/null +++ b/src/constructor/extensions/arrow/makeArrowPaths.js @@ -0,0 +1,75 @@ +/** + * SVG (c) Kraft + */ +import * as S from "../../../environment/strings.js"; +import { + svg_add2, + svg_sub2, + svg_scale2, + svg_magnitude2, +} from "../../../general/algebra.js"; + +const ends = [S.str_tail, S.str_head]; +const stringifyPoint = p => p.join(","); +const pointsToPath = (points) => "M" + points.map(pt => pt.join(",")).join("L") + "Z"; + +const makeArrowPaths = function (options) { + // throughout, tail is 0, head is 1 + let pts = [[0,1], [2,3]].map(pt => pt.map(i => options.points[i] || 0)); + let vector = svg_sub2(pts[1], pts[0]); + let midpoint = svg_add2(pts[0], svg_scale2(vector, 0.5)); + // make sure arrow isn't too small + const len = svg_magnitude2(vector); + const minLength = ends + .map(s => (options[s].visible + ? (1 + options[s].padding) * options[s].height * 2.5 + : 0)) + .reduce((a, b) => a + b, 0); + if (len < minLength) { + // check len === exactly 0. don't compare to epsilon here + const minVec = len === 0 ? [minLength, 0] : svg_scale2(vector, minLength / len); + pts = [svg_sub2, svg_add2].map(f => f(midpoint, svg_scale2(minVec, 0.5))); + vector = svg_sub2(pts[1], pts[0]); + } else { + // allow padding to be able to be applied. but still cap it at minLength + // if (options.padding) {} + } + let perpendicular = [vector[1], -vector[0]]; + let bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, options.bend)); + const bezs = pts.map(pt => svg_sub2(bezPoint, pt)); + const bezsLen = bezs.map(v => svg_magnitude2(v)); + const bezsNorm = bezs.map((bez, i) => bezsLen[i] === 0 + ? bez + : svg_scale2(bez, 1 / bezsLen[i])); + const vectors = bezsNorm.map(norm => svg_scale2(norm, -1)); + const normals = vectors.map(vec => [vec[1], -vec[0]]); + // get padding from either head/tail options or root of options + const pad = ends.map((s, i) => options[s].padding + ? options[s].padding + : (options.padding ? options.padding : 0.0)); + const scales = ends + .map((s, i) => options[s].height * (options[s].visible ? 1 : 0)) + .map((n, i) => n + pad[i]); + // .map((s, i) => options[s].height * ((options[s].visible ? 1 : 0) + pad[i])); + const arcs = pts.map((pt, i) => svg_add2(pt, svg_scale2(bezsNorm[i], scales[i]))); + // readjust bezier curve now that the arrow heads push inwards + vector = svg_sub2(arcs[1], arcs[0]); + perpendicular = [vector[1], -vector[0]]; + midpoint = svg_add2(arcs[0], svg_scale2(vector, 0.5)); + bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, options.bend)); + // done adjust + const controls = arcs + .map((arc, i) => svg_add2(arc, svg_scale2(svg_sub2(bezPoint, arc), options.pinch))) + const polyPoints = ends.map((s, i) => [ + svg_add2(arcs[i], svg_scale2(vectors[i], options[s].height)), + svg_add2(arcs[i], svg_scale2(normals[i], options[s].width / 2)), + svg_add2(arcs[i], svg_scale2(normals[i], -options[s].width / 2)), + ]); + return { + line: `M${stringifyPoint(arcs[0])}C${stringifyPoint(controls[0])},${stringifyPoint(controls[1])},${stringifyPoint(arcs[1])}`, + tail: pointsToPath(polyPoints[0]), + head: pointsToPath(polyPoints[1]), + }; +}; + +export default makeArrowPaths; diff --git a/src/constructor/extensions/arrow/methods.js b/src/constructor/extensions/arrow/methods.js new file mode 100755 index 0000000..ca7fb48 --- /dev/null +++ b/src/constructor/extensions/arrow/methods.js @@ -0,0 +1,111 @@ +/** + * SVG (c) Kraft + */ +import * as S from "../../../environment/strings.js"; +import { toCamel } from "../../../general/transformCase.js"; +import semiFlatten from "../../../arguments/semiFlattenArrays.js"; +import makeCoordinates from "../../../arguments/makeCoordinates.js"; +import makeArrowPaths from "./makeArrowPaths.js"; +import TransformMethods from "../shared/transforms.js"; + +// end is "head" or "tail" +const setArrowheadOptions = (element, options, which) => { + if (typeof options === S.str_boolean) { + element.options[which].visible = options; + } else if (typeof options === S.str_object) { + Object.assign(element.options[which], options); + if (options.visible == null) { + element.options[which].visible = true; + } + } else if (options == null) { + element.options[which].visible = true; + } +}; + +const setArrowStyle = (element, options = {}, which = S.str_head) => { + const path = element.getElementsByClassName(`${S.str_arrow}-${which}`)[0]; + // find options which translate to object methods (el.stroke("red")) + Object.keys(options) + .map(key => ({ key, fn: path[toCamel(key)] })) + .filter(el => typeof el.fn === S.str_function && el.key !== "class") + .forEach(el => el.fn(options[el.key])); + // find options which don't work as methods, set as attributes + // Object.keys(options) + // .map(key => ({ key, fn: path[toCamel(key)] })) + // .filter(el => typeof el.fn !== S.str_function && el.key !== "class") + // .forEach(el => path.setAttribute(el.key, options[el.key])); + // + // apply a class attribute (add, don't overwrite existing classes) + Object.keys(options) + .filter(key => key === "class") + .forEach(key => path.classList.add(options[key])); +}; + +const redraw = (element) => { + const paths = makeArrowPaths(element.options); + Object.keys(paths) + .map(path => ({ + path, + element: element.getElementsByClassName(`${S.str_arrow}-${path}`)[0], + })) + .filter(el => el.element) + .map(el => { el.element.setAttribute("d", paths[el.path]); return el; }) + .filter(el => element.options[el.path]) + .forEach(el => el.element.setAttribute( + "visibility", + element.options[el.path].visible + ? "visible" + : "hidden", + )); + return element; +}; + +const setPoints = (element, ...args) => { + element.options.points = makeCoordinates(...semiFlatten(...args)).slice(0, 4); + return redraw(element); +}; + +const bend = (element, amount) => { + element.options.bend = amount; + return redraw(element); +}; + +const pinch = (element, amount) => { + element.options.pinch = amount; + return redraw(element); +}; + +const padding = (element, amount) => { + element.options.padding = amount; + return redraw(element); +}; + +const head = (element, options) => { + setArrowheadOptions(element, options, S.str_head); + setArrowStyle(element, options, S.str_head); + return redraw(element); +}; + +const tail = (element, options) => { + setArrowheadOptions(element, options, S.str_tail); + setArrowStyle(element, options, S.str_tail); + return redraw(element); +}; + +const getLine = element => element.getElementsByClassName(`${S.str_arrow}-line`)[0]; +const getHead = element => element.getElementsByClassName(`${S.str_arrow}-${S.str_head}`)[0]; +const getTail = element => element.getElementsByClassName(`${S.str_arrow}-${S.str_tail}`)[0]; + +export default { + setPoints, + points: setPoints, + bend, + pinch, + padding, + head, + tail, + getLine, + getHead, + getTail, + ...TransformMethods, +}; diff --git a/src/constructor/extensions/arrow/options.js b/src/constructor/extensions/arrow/options.js new file mode 100644 index 0000000..102a222 --- /dev/null +++ b/src/constructor/extensions/arrow/options.js @@ -0,0 +1,22 @@ +/** + * SVG (c) Kraft + */ +const endOptions = () => ({ + visible: false, + width: 8, + height: 10, + padding: 0.0, +}); + +const makeArrowOptions = () => ({ + head: endOptions(), + tail: endOptions(), + bend: 0.0, + padding: 0.0, + pinch: 0.618, + points: [], +}); + +export { + makeArrowOptions, +}; diff --git a/src/constructor/extensions/arrow/template.js b/src/constructor/extensions/arrow/template.js new file mode 100755 index 0000000..fa0f0b2 --- /dev/null +++ b/src/constructor/extensions/arrow/template.js @@ -0,0 +1,29 @@ +/** + * SVG (c) Kraft + */ +const arrow = function (...args) { + const shape = window.document.createElementNS(svgNS, "g"); + const tailPoly = window.document.createElementNS(svgNS, "polygon"); + const headPoly = window.document.createElementNS(svgNS, "polygon"); + const arrowPath = window.document.createElementNS(svgNS, "path"); + tailPoly.setAttributeNS(null, "class", "svg-arrow-tail"); + headPoly.setAttributeNS(null, "class", "svg-arrow-head"); + arrowPath.setAttributeNS(null, "class", "svg-arrow-path"); + tailPoly.setAttributeNS(null, "style", "stroke: none; pointer-events: none;"); + headPoly.setAttributeNS(null, "style", "stroke: none; pointer-events: none;"); + arrowPath.setAttributeNS(null, "style", "fill: none;"); + shape.appendChild(arrowPath); + shape.appendChild(tailPoly); + shape.appendChild(headPoly); + shape.options = { + head: { width: 0.5, height: 2, visible: false, padding: 0.0 }, + tail: { width: 0.5, height: 2, visible: false, padding: 0.0 }, + curve: 0.0, + pinch: 0.618, + points: [], + }; + setArrowPoints(shape, ...args); + prepare("arrow", shape); + shape.setPoints = (...a) => setArrowPoints(shape, ...a); + return shape; +}; diff --git a/src/constructor/extensions/circle.js b/src/constructor/extensions/circle.js index b25575b..c4edf52 100755 --- a/src/constructor/extensions/circle.js +++ b/src/constructor/extensions/circle.js @@ -3,9 +3,10 @@ */ import makeCoordinates from "../../arguments/makeCoordinates.js"; import nodes_attributes from "../../spec/nodes_attributes.js"; -import { svg_distance2 } from "../../methods/algebra.js"; +import { svg_distance2 } from "../../general/algebra.js"; import TransformMethods from "./shared/transforms.js"; import URLMethods from "./shared/urls.js"; +import * as DOM from "./shared/dom.js"; const setRadius = (el, r) => { el.setAttribute(nodes_attributes.circle[2], r); @@ -52,6 +53,7 @@ export default { setPosition: setOrigin, ...TransformMethods, ...URLMethods, + ...DOM, }, }, }; diff --git a/src/constructor/extensions/curve/arguments.js b/src/constructor/extensions/curve/arguments.js new file mode 100755 index 0000000..80d6356 --- /dev/null +++ b/src/constructor/extensions/curve/arguments.js @@ -0,0 +1,11 @@ +/** + * SVG (c) Kraft + */ +import makeCoordinates from "../../../arguments/makeCoordinates.js"; +import makeCurvePath from "./makeCurvePath.js"; + +const curveArguments = (...args) => [ + makeCurvePath(makeCoordinates(...args.flat())), +]; + +export default curveArguments; diff --git a/src/constructor/extensions/curve/getCurveEndpoints.js b/src/constructor/extensions/curve/getCurveEndpoints.js new file mode 100644 index 0000000..65d2493 --- /dev/null +++ b/src/constructor/extensions/curve/getCurveEndpoints.js @@ -0,0 +1,31 @@ +/** + * SVG (c) Kraft + */ +const getNumbersFromPathCommand = str => str + .slice(1) + .split(/[, ]+/) + .map(s => parseFloat(s)); + +// this gets the parameter numbers, in an array +const getCurveTos = d => d + .match(/[Cc][(0-9), .-]+/) + .map(curve => getNumbersFromPathCommand(curve)); + +const getMoveTos = d => d + .match(/[Mm][(0-9), .-]+/) + .map(curve => getNumbersFromPathCommand(curve)); + +const getCurveEndpoints = (d) => { + // get only the first Move and Curve commands + const move = getMoveTos(d).shift(); + const curve = getCurveTos(d).shift(); + const start = move + ? [move[move.length - 2], move[move.length - 1]] + : [0, 0]; + const end = curve + ? [curve[curve.length - 2], curve[curve.length - 1]] + : [0, 0]; + return [...start, ...end]; +}; + +export default getCurveEndpoints; diff --git a/src/constructor/extensions/curve/index.js b/src/constructor/extensions/curve/index.js new file mode 100755 index 0000000..27c7966 --- /dev/null +++ b/src/constructor/extensions/curve/index.js @@ -0,0 +1,15 @@ +/** + * SVG (c) Kraft + */ +import args from "./arguments.js"; +import curve_methods from "./methods.js"; +import { str_path } from "../../../environment/strings.js"; + +export default { + curve: { + nodeName: str_path, + attributes: ["d"], + args, // one function + methods: curve_methods, // object of functions + }, +}; diff --git a/src/constructor/extensions/curve/makeCurvePath.js b/src/constructor/extensions/curve/makeCurvePath.js new file mode 100644 index 0000000..8635364 --- /dev/null +++ b/src/constructor/extensions/curve/makeCurvePath.js @@ -0,0 +1,19 @@ +/** + * SVG (c) Kraft + */ +import { svg_add2, svg_sub2, svg_scale2 } from "../../../general/algebra.js"; + +// endpoints is an array of 4 numbers +const makeCurvePath = (endpoints = [], bend = 0, pinch = 0.5) => { + const tailPt = [endpoints[0] || 0, endpoints[1] || 0]; + const headPt = [endpoints[2] || 0, endpoints[3] || 0]; + const vector = svg_sub2(headPt, tailPt); + const midpoint = svg_add2(tailPt, svg_scale2(vector, 0.5)); + const perpendicular = [vector[1], -vector[0]]; + const bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, bend)); + const tailControl = svg_add2(tailPt, svg_scale2(svg_sub2(bezPoint, tailPt), pinch)); + const headControl = svg_add2(headPt, svg_scale2(svg_sub2(bezPoint, headPt), pinch)); + return `M${tailPt[0]},${tailPt[1]}C${tailControl[0]},${tailControl[1]} ${headControl[0]},${headControl[1]} ${headPt[0]},${headPt[1]}`; +}; + +export default makeCurvePath; diff --git a/src/constructor/extensions/curve/methods.js b/src/constructor/extensions/curve/methods.js new file mode 100755 index 0000000..04c6bda --- /dev/null +++ b/src/constructor/extensions/curve/methods.js @@ -0,0 +1,30 @@ +/** + * SVG (c) Kraft + */ +import makeCoordinates from "../../../arguments/makeCoordinates.js"; +import makeCurvePath from "./makeCurvePath.js"; +import getCurveEndpoints from "./getCurveEndpoints.js"; +import TransformMethods from "../shared/transforms.js"; + +const setPoints = (element, ...args) => { + const coords = makeCoordinates(...args.flat()).slice(0, 4); + element.setAttribute("d", makeCurvePath(coords, element._bend, element._pinch)); + return element; +}; + +const bend = (element, amount) => { + element._bend = amount; + return setPoints(element, ...getCurveEndpoints(element.getAttribute("d"))); +}; + +const pinch = (element, amount) => { + element._pinch = amount; + return setPoints(element, ...getCurveEndpoints(element.getAttribute("d"))); +}; + +export default { + setPoints, + bend, + pinch, + ...TransformMethods, +}; diff --git a/src/constructor/extensions/ellipse.js b/src/constructor/extensions/ellipse.js index 4c727c6..728fa3f 100755 --- a/src/constructor/extensions/ellipse.js +++ b/src/constructor/extensions/ellipse.js @@ -5,6 +5,7 @@ import makeCoordinates from "../../arguments/makeCoordinates.js"; import nodes_attributes from "../../spec/nodes_attributes.js"; import TransformMethods from "./shared/transforms.js"; import URLMethods from "./shared/urls.js"; +import * as DOM from "./shared/dom.js"; // const setRadii = (el, rx, ry) => [,,rx,ry] // .forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value)); @@ -39,6 +40,7 @@ export default { setPosition: setOrigin, ...TransformMethods, ...URLMethods, + ...DOM, }, }, }; diff --git a/src/constructor/extensions/g.js b/src/constructor/extensions/g.js index 21a10f9..0877f0d 100755 --- a/src/constructor/extensions/g.js +++ b/src/constructor/extensions/g.js @@ -6,12 +6,11 @@ import window from "../../environment/window.js"; // import * as S from "../../environment/strings.js"; // import { sync } from "../../file/load.js"; // import { moveChildren } from "../../methods/dom.js"; -import { removeChildren } from "./shared/dom.js"; import TransformMethods from "./shared/transforms.js"; import URLMethods from "./shared/urls.js"; +import * as DOM from "./shared/dom.js"; -const loadGroup = (...sources) => { - const group = window().document.createElementNS(svgNS, "g"); +const loadGroup = (group, ...sources) => { // const elements = sources.map(source => sync(source)) // .filter(a => a !== undefined); // elements.filter(element => element.tagName === S.str_svg) @@ -21,14 +20,19 @@ const loadGroup = (...sources) => { return group; }; +const init = (...sources) => { + const group = window().document.createElementNS(svgNS, "g"); + return loadGroup(group, ...sources); +}; + export default { g: { - init: loadGroup, + // init, methods: { - load: loadGroup, - removeChildren, + // load: loadGroup, ...TransformMethods, ...URLMethods, + ...DOM, }, }, }; diff --git a/src/constructor/extensions/index.js b/src/constructor/extensions/index.js index c00041c..51c0977 100755 --- a/src/constructor/extensions/index.js +++ b/src/constructor/extensions/index.js @@ -13,6 +13,12 @@ import textDef from "./text.js"; // multiple nodes in one import maskTypes from "./maskTypes.js"; import polyDefs from "./polys.js"; +// extensions +import arcDef from "./arc/index.js"; +import arrowDef from "./arrow/index.js"; +import curveDef from "./curve/index.js"; +import wedgeDef from "./wedge/index.js"; +import origamiDef from "./origami/index.js"; /** * in each of these instances, arguments maps the arguments to attributes * as the attributes are listed in the "attributes" folder. @@ -35,4 +41,10 @@ export default { // multiple ...maskTypes, ...polyDefs, + // extensions + ...arcDef, + ...arrowDef, + ...curveDef, + ...wedgeDef, + ...origamiDef, }; diff --git a/src/constructor/extensions/line.js b/src/constructor/extensions/line.js index b544f4f..7d62aaa 100755 --- a/src/constructor/extensions/line.js +++ b/src/constructor/extensions/line.js @@ -6,6 +6,7 @@ import makeCoordinates from "../../arguments/makeCoordinates.js"; import nodes_attributes from "../../spec/nodes_attributes.js"; import TransformMethods from "./shared/transforms.js"; import URLMethods from "./shared/urls.js"; +import * as DOM from "./shared/dom.js"; const Args = (...args) => makeCoordinates(...semiFlattenArrays(...args)).slice(0, 4); @@ -26,6 +27,7 @@ export default { setPoints, ...TransformMethods, ...URLMethods, + ...DOM, }, }, }; diff --git a/src/constructor/extensions/maskTypes.js b/src/constructor/extensions/maskTypes.js index 1c768a4..e26d815 100755 --- a/src/constructor/extensions/maskTypes.js +++ b/src/constructor/extensions/maskTypes.js @@ -2,11 +2,11 @@ * SVG (c) Kraft */ import * as S from "../../environment/strings.js"; -import makeUUID from "../../arguments/makeUUID.js"; -import { setViewBox } from "../../methods/viewBox.js"; -import { removeChildren } from "./shared/dom.js"; +import makeUUID from "../../general/makeUUID.js"; +import { setViewBox } from "../../general/viewBox.js"; import TransformMethods from "./shared/transforms.js"; import URLMethods from "./shared/urls.js"; +import * as DOM from "./shared/dom.js"; const makeIDString = function () { return Array.from(arguments) @@ -20,25 +20,25 @@ export default { mask: { args: maskArgs, methods: { - removeChildren, ...TransformMethods, ...URLMethods, + ...DOM, }, }, clipPath: { args: maskArgs, methods: { - removeChildren, ...TransformMethods, ...URLMethods, + ...DOM, }, }, symbol: { args: maskArgs, methods: { - removeChildren, ...TransformMethods, ...URLMethods, + ...DOM, }, }, marker: { @@ -46,9 +46,9 @@ export default { methods: { size: setViewBox, setViewBox: setViewBox, - removeChildren, ...TransformMethods, ...URLMethods, + ...DOM, }, }, }; diff --git a/src/constructor/extensions/origami/index.js b/src/constructor/extensions/origami/index.js new file mode 100644 index 0000000..e0e22f5 --- /dev/null +++ b/src/constructor/extensions/origami/index.js @@ -0,0 +1,14 @@ +/** + * SVG (c) Kraft + */ +import init from "./init.js"; +import methods from "./methods.js"; + +export default { + origami: { + nodeName: "g", + init, + args: () => [], + methods, + }, +}; diff --git a/src/constructor/extensions/origami/init.js b/src/constructor/extensions/origami/init.js new file mode 100644 index 0000000..e819547 --- /dev/null +++ b/src/constructor/extensions/origami/init.js @@ -0,0 +1,14 @@ +/** + * SVG (c) Kraft + */ +import NS from "../../../spec/namespace.js"; +import window from "../../../environment/window.js"; +import lib from "../../../environment/lib.js"; + +const init = (graph, ...args) => { + const g = window().document.createElementNS(NS, "g"); + lib.ear.convert.foldToSvg.render(graph, g, ...args); + return g; +}; + +export default init; diff --git a/src/constructor/extensions/origami/methods.js b/src/constructor/extensions/origami/methods.js new file mode 100644 index 0000000..c680d55 --- /dev/null +++ b/src/constructor/extensions/origami/methods.js @@ -0,0 +1,41 @@ +/** + * SVG (c) Kraft + */ +// import window from "../../../environment/window.js"; +// import NS from "../../../spec/namespace.js"; +// import lib from "../../../environment/lib.js"; +import TransformMethods from "../shared/transforms.js"; +import URLMethods from "../shared/urls.js"; +import * as DOM from "../shared/dom.js"; + +// const clearSVG = (element) => { +// Array.from(element.attributes) +// .filter(attr => attr.name !== "xmlns" && attr.name !== "version") +// .forEach(attr => element.removeAttribute(attr.name)); +// return DOM.removeChildren(element); +// }; + +// const vertices = (...args) => { +// lib.ear.convert.foldToSvg.vertices(...args); +// const g = window().document.createElementNS(NS, "g"); +// lib.ear.convert.foldToSvg.drawInto(g, ...args); +// return g; +// }; + +// const edges = (...args) => { +// console.log("edges"); +// }; + +// const faces = (...args) => { +// console.log("faces"); +// }; + +// these will end up as methods on the nodes +export default { + // vertices, + // edges, + // faces, + ...TransformMethods, + ...URLMethods, + ...DOM, +}; diff --git a/src/constructor/extensions/path.js b/src/constructor/extensions/path.js index bc01017..fd98bb3 100755 --- a/src/constructor/extensions/path.js +++ b/src/constructor/extensions/path.js @@ -4,9 +4,10 @@ import { pathCommandNames, parsePathCommands, -} from "../../methods/path.js"; +} from "../../general/path.js"; import TransformMethods from "./shared/transforms.js"; import URLMethods from "./shared/urls.js"; +import * as DOM from "./shared/dom.js"; /** * @param {SVGElement} one svg element, intended to be a element * @returns {string} the "d" attribute, or if unset, returns an empty string "". @@ -85,6 +86,7 @@ const path_methods = { // add: noClearSet, ...TransformMethods, ...URLMethods, + ...DOM, }; Object.keys(pathCommandNames).forEach((key) => { diff --git a/src/constructor/extensions/polys.js b/src/constructor/extensions/polys.js index bd6fd00..924849e 100755 --- a/src/constructor/extensions/polys.js +++ b/src/constructor/extensions/polys.js @@ -6,6 +6,7 @@ import semiFlattenArrays from "../../arguments/semiFlattenArrays.js"; import makeCoordinates from "../../arguments/makeCoordinates.js"; import TransformMethods from "./shared/transforms.js"; import URLMethods from "./shared/urls.js"; +import * as DOM from "./shared/dom.js"; const getPoints = (el) => { const attr = el.getAttribute(S.str_points); @@ -53,6 +54,7 @@ export default { addPoint, ...TransformMethods, ...URLMethods, + ...DOM, }, }, polygon: { @@ -62,6 +64,7 @@ export default { addPoint, ...TransformMethods, ...URLMethods, + ...DOM, }, }, }; diff --git a/src/constructor/extensions/rect.js b/src/constructor/extensions/rect.js index e76223d..da66daf 100755 --- a/src/constructor/extensions/rect.js +++ b/src/constructor/extensions/rect.js @@ -5,6 +5,7 @@ import makeCoordinates from "../../arguments/makeCoordinates.js"; import nodes_attributes from "../../spec/nodes_attributes.js"; import TransformMethods from "./shared/transforms.js"; import URLMethods from "./shared/urls.js"; +import * as DOM from "./shared/dom.js"; const setRectSize = (el, rx, ry) => { [, , rx, ry] @@ -58,6 +59,7 @@ export default { setSize: setRectSize, ...TransformMethods, ...URLMethods, + ...DOM, }, }, }; diff --git a/src/constructor/extensions/shared/dom.js b/src/constructor/extensions/shared/dom.js index cf749f6..c07d527 100644 --- a/src/constructor/extensions/shared/dom.js +++ b/src/constructor/extensions/shared/dom.js @@ -1,7 +1,7 @@ /** * SVG (c) Kraft */ -import { toKebab } from "../../../arguments/transformCase.js"; +import { toKebab } from "../../../general/transformCase.js"; export const removeChildren = (element) => { while (element.lastChild) { @@ -22,10 +22,3 @@ export const setAttributes = (element, attrs) => { .forEach(key => element.setAttribute(toKebab(key), attrs[key])); return element; }; - -export const clearSVG = (element) => { - Array.from(element.attributes) - .filter(attr => attr.name !== "xmlns" && attr.name !== "version") - .forEach(attr => element.removeAttribute(attr.name)); - return removeChildren(element); -}; diff --git a/src/constructor/extensions/shared/makeArcPath.js b/src/constructor/extensions/shared/makeArcPath.js new file mode 100755 index 0000000..0ac9e25 --- /dev/null +++ b/src/constructor/extensions/shared/makeArcPath.js @@ -0,0 +1,22 @@ +/** + * SVG (c) Kraft + */ +import { svg_polar_to_cart } from "../../../general/algebra.js"; + +const arcPath = (x, y, radius, startAngle, endAngle, includeCenter = false) => { + if (endAngle == null) { return ""; } + const start = svg_polar_to_cart(startAngle, radius); + const end = svg_polar_to_cart(endAngle, radius); + const arcVec = [end[0] - start[0], end[1] - start[1]]; + const py = start[0] * end[1] - start[1] * end[0]; + const px = start[0] * end[0] + start[1] * end[1]; + const arcdir = (Math.atan2(py, px) > 0 ? 0 : 1); + let d = (includeCenter + ? `M ${x},${y} l ${start[0]},${start[1]} ` + : `M ${x + start[0]},${y + start[1]} `); + d += ["a ", radius, radius, 0, arcdir, 1, arcVec[0], arcVec[1]].join(" "); + if (includeCenter) { d += " Z"; } + return d; +}; + +export default arcPath; diff --git a/src/constructor/extensions/shared/urls.js b/src/constructor/extensions/shared/urls.js index 63d077f..b6c76ac 100644 --- a/src/constructor/extensions/shared/urls.js +++ b/src/constructor/extensions/shared/urls.js @@ -5,7 +5,7 @@ import { str_string, str_id, } from "../../../environment/strings.js"; -import { toCamel } from "../../../arguments/transformCase.js"; +import { toCamel } from "../../../general/transformCase.js"; // for the clip-path and mask values. looks for the ID as a "url(#id-name)" string const findIdURL = function (arg) { @@ -33,7 +33,10 @@ const methods = {}; "marker-mid", "marker-start", ].forEach(attr => { - methods[toCamel(attr)] = (element, parent) => element.setAttribute(attr, findIdURL(parent)); + methods[toCamel(attr)] = (element, parent) => { + element.setAttribute(attr, findIdURL(parent)); + return element; + }; }); export default methods; diff --git a/src/constructor/extensions/style.js b/src/constructor/extensions/style.js index 6eeb314..0443742 100755 --- a/src/constructor/extensions/style.js +++ b/src/constructor/extensions/style.js @@ -3,7 +3,7 @@ */ import svgNS from "../../spec/namespace.js"; import window from "../../environment/window.js"; -import makeCDATASection from "../../methods/makeCDATASection.js"; +import makeCDATASection from "../../general/makeCDATASection.js"; export default { style: { diff --git a/src/constructor/extensions/svg/animation.js b/src/constructor/extensions/svg/animation.js index 3e0a47c..6fe5b17 100644 --- a/src/constructor/extensions/svg/animation.js +++ b/src/constructor/extensions/svg/animation.js @@ -2,55 +2,40 @@ * SVG (c) Kraft */ import window from "../../../environment/window.js"; -import UUID from "../../../arguments/makeUUID.js"; +import UUID from "../../../general/makeUUID.js"; const Animation = function (element) { - // let fps; // todo: bring this back - let start; - const handlers = {}; let frame = 0; let requestId; + const handlers = {}; - const removeHandlers = () => { + const stop = () => { if (window().cancelAnimationFrame) { window().cancelAnimationFrame(requestId); } - Object.keys(handlers) - .forEach(uuid => delete handlers[uuid]); - start = undefined; - frame = 0; + Object.keys(handlers).forEach(uuid => delete handlers[uuid]); }; - Object.defineProperty(element, "play", { - set: (handler) => { - removeHandlers(); - if (handler == null) { return; } - const uuid = UUID(); - const handlerFunc = (e) => { - if (!start) { - start = e; - frame = 0; - } - const progress = (e - start) * 0.001; - handler({ time: progress, frame }); - // prepare next frame - frame += 1; - if (handlers[uuid]) { - requestId = window().requestAnimationFrame(handlers[uuid]); - } - }; - handlers[uuid] = handlerFunc; - // node.js doesn't have requestAnimationFrame - // we don't need to duplicate this if statement above, because it won't - // ever be called if this one is prevented. - if (window().requestAnimationFrame) { + const play = (handler) => { + stop(); + if (!handler || !(window().requestAnimationFrame)) { return; } + start = performance.now(); + frame = 0; + const uuid = UUID(); + handlers[uuid] = (now) => { + const time = (now - start) * 1e-3; + handler({ time, frame }); + frame += 1; + if (handlers[uuid]) { requestId = window().requestAnimationFrame(handlers[uuid]); } - }, - enumerable: true, - }); - Object.defineProperty(element, "stop", { value: removeHandlers, enumerable: true }); + }; + requestId = window().requestAnimationFrame(handlers[uuid]); + }; + + Object.defineProperty(element, "play", { set: play, enumerable: true }); + Object.defineProperty(element, "stop", { value: stop, enumerable: true }); }; export default Animation; diff --git a/src/constructor/extensions/svg/controls.js b/src/constructor/extensions/svg/controls.js index eb25804..6230918 100644 --- a/src/constructor/extensions/svg/controls.js +++ b/src/constructor/extensions/svg/controls.js @@ -3,7 +3,7 @@ */ import * as S from "../../../environment/strings.js"; import makeCoordinates from "../../../arguments/makeCoordinates.js"; -import { svg_distanceSq2 } from "../../../methods/algebra.js"; +import { svg_distanceSq2 } from "../../../general/algebra.js"; const attachToParent = (parent, svg) => (svg && svg.parentNode == null ? parent.appendChild(svg) diff --git a/src/constructor/extensions/svg/getSVGFrame.js b/src/constructor/extensions/svg/getSVGFrame.js index a9acebe..25b5a1a 100644 --- a/src/constructor/extensions/svg/getSVGFrame.js +++ b/src/constructor/extensions/svg/getSVGFrame.js @@ -1,7 +1,7 @@ import { str_function, } from "../../../environment/strings.js"; -import { getViewBox } from "../../../methods/viewBox.js"; +import { getViewBox } from "../../../general/viewBox.js"; const getSVGFrame = function (element) { const viewBox = getViewBox(element); diff --git a/src/constructor/extensions/svg/index.js b/src/constructor/extensions/svg/index.js index 411f716..f9458d4 100755 --- a/src/constructor/extensions/svg/index.js +++ b/src/constructor/extensions/svg/index.js @@ -3,9 +3,6 @@ */ import svgNS from "../../../spec/namespace.js"; import window from "../../../environment/window.js"; -import { - str_string, -} from "../../../environment/strings.js"; import makeViewBox from "../../../arguments/makeViewBox.js"; import makeCoordinates from "../../../arguments/makeCoordinates.js"; import methods from "./methods.js"; diff --git a/src/constructor/extensions/svg/methods.js b/src/constructor/extensions/svg/methods.js index b0232fa..5ee7805 100755 --- a/src/constructor/extensions/svg/methods.js +++ b/src/constructor/extensions/svg/methods.js @@ -7,18 +7,14 @@ import { // str_function, } from "../../../environment/strings.js"; import NS from "../../../spec/namespace.js"; -import makeCDATASection from "../../../methods/makeCDATASection.js"; -import { - clearSVG, - removeChildren, -} from "../shared/dom.js"; -// import { clearSVG, assignSVG } from "../../../methods/dom.js"; +import makeCDATASection from "../../../general/makeCDATASection.js"; // import Load from "../../../file/load.js"; // import Save from "../../../file/save.js"; -import { getViewBox, setViewBox } from "../../../methods/viewBox.js"; +import { getViewBox, setViewBox } from "../../../general/viewBox.js"; import makeBackground from "./makeBackground.js"; import getSVGFrame from "./getSVGFrame.js"; import TransformMethods from "../shared/transforms.js"; +import * as DOM from "../shared/dom.js"; // check if the loader is running synchronously or asynchronously // export const loadSVG = (target, data) => { @@ -64,10 +60,16 @@ const stylesheet = function (element, textContent) { return styleSection; }; +const clearSVG = (element) => { + Array.from(element.attributes) + .filter(attr => attr.name !== "xmlns" && attr.name !== "version") + .forEach(attr => element.removeAttribute(attr.name)); + return DOM.removeChildren(element); +}; + // these will end up as methods on the nodes export default { clear: clearSVG, - removeChildren, size: setViewBox, setViewBox, getViewBox, @@ -80,6 +82,7 @@ export default { // load: loadSVG, // save: Save, ...TransformMethods, + ...DOM, }; // svg.load = function (element, data, callback) { diff --git a/src/constructor/extensions/svg/touch.js b/src/constructor/extensions/svg/touch.js index 9e70ba9..8c5f110 100644 --- a/src/constructor/extensions/svg/touch.js +++ b/src/constructor/extensions/svg/touch.js @@ -1,108 +1,68 @@ /** * SVG (c) Kraft */ -import { capitalized } from "../../../arguments/transformCase.js"; -import { convertToViewBox } from "../../../methods/viewBox.js"; +import { capitalized } from "../../../general/transformCase.js"; +import { convertToViewBox } from "../../../general/viewBox.js"; -const categories = { +const eventNameCategories = { move: ["mousemove", "touchmove"], press: ["mousedown", "touchstart"], // "mouseover", release: ["mouseup", "touchend"], leave: ["mouseleave", "touchcancel"], }; -const handlerNames = Object.values(categories) - .reduce((a, b) => a.concat(b), []); - -const off = (element, handlers) => handlerNames.forEach((handlerName) => { - handlers[handlerName].forEach(func => element.removeEventListener(handlerName, func)); - handlers[handlerName] = []; -}); - -const defineGetter = (obj, prop, value) => Object.defineProperty(obj, prop, { - get: () => value, - enumerable: true, - configurable: true, -}); +const off = (el, handlers) => Object.values(eventNameCategories) + .flat() + .forEach((handlerName) => { + handlers[handlerName].forEach(func => el + .removeEventListener(handlerName, func)); + handlers[handlerName] = []; + }); -const assignPress = (e, startPoint) => { - ["pressX", "pressY"].filter(prop => !Object.prototype.hasOwnProperty.call(e, prop)) - .forEach((prop, i) => defineGetter(e, prop, startPoint[i])); - if (!Object.prototype.hasOwnProperty.call(e, "press")) { - // defineGetter(e, "press", Libraries.math.vector(...startPoint)); - defineGetter(e, "press", [...startPoint]); - } -}; +const defineGetter = (obj, prop, value) => Object + .defineProperty(obj, prop, { + get: () => value, + enumerable: true, + configurable: true, + }); +// todo, more pointers for multiple screen touches const TouchEvents = function (element) { - // todo, more pointers for multiple screen touches - - let startPoint = []; - // hold onto all handlers. to be able to turn them off + // hold onto all handlers to be able to turn them off const handlers = []; - Object.keys(categories).forEach((key) => { - categories[key].forEach((handler) => { + Object.keys(eventNameCategories).forEach((key) => { + eventNameCategories[key].forEach((handler) => { handlers[handler] = []; }); }); - const removeHandler = category => categories[category] + const removeHandler = category => eventNameCategories[category] .forEach(handlerName => handlers[handlerName] .forEach(func => element.removeEventListener(handlerName, func))); - // add more properties depending on the type of handler - const categoryUpdate = { - press: (e, viewPoint) => { - startPoint = viewPoint; - assignPress(e, startPoint); - }, - release: () => {}, - leave: () => {}, - move: (e, viewPoint) => { - if (e.buttons > 0 && startPoint[0] === undefined) { - startPoint = viewPoint; - } else if (e.buttons === 0 && startPoint[0] !== undefined) { - startPoint = []; - } - assignPress(e, startPoint); - }, - }; - - // assign handlers for onMove, onPress, onRelease - Object.keys(categories).forEach((category) => { - const propName = `on${capitalized(category)}`; - Object.defineProperty(element, propName, { + // assign handlers for onMove, onPress, onRelease, onLeave + Object.keys(eventNameCategories).forEach((category) => { + Object.defineProperty(element, `on${capitalized(category)}`, { set: (handler) => { + if (!element.addEventListener) { return; } if (handler == null) { removeHandler(category); return; } - categories[category].forEach((handlerName) => { + eventNameCategories[category].forEach((handlerName) => { const handlerFunc = (e) => { - // const pointer = (e.touches != null && e.touches.length - const pointer = (e.touches != null - ? e.touches[0] - : e); - // onRelease events don't have a pointer + const pointer = (e.touches != null ? e.touches[0] : e); + // for onRelease, pointer will be undefined if (pointer !== undefined) { - const viewPoint = convertToViewBox(element, pointer.clientX, pointer.clientY) - .map(n => (Number.isNaN(n) ? undefined : n)); // e.target - ["x", "y"] - .filter(prop => !Object.prototype.hasOwnProperty.call(e, prop)) - .forEach((prop, i) => defineGetter(e, prop, viewPoint[i])); - if (!Object.prototype.hasOwnProperty.call(e, "position")) { - // defineGetter(e, "position", Libraries.math.vector(...viewPoint)); - defineGetter(e, "position", [...viewPoint]); - } - categoryUpdate[category](e, viewPoint); + const { clientX, clientY } = pointer; + const [x, y] = convertToViewBox(element, clientX, clientY); + defineGetter(e, "x", x); + defineGetter(e, "y", y); } handler(e); }; - // node.js doesn't have addEventListener - if (element.addEventListener) { - handlers[handlerName].push(handlerFunc); - element.addEventListener(handlerName, handlerFunc); - } + handlers[handlerName].push(handlerFunc); + element.addEventListener(handlerName, handlerFunc); }); }, enumerable: true, diff --git a/src/constructor/extensions/text.js b/src/constructor/extensions/text.js index 2263b32..c8e2508 100755 --- a/src/constructor/extensions/text.js +++ b/src/constructor/extensions/text.js @@ -7,6 +7,7 @@ import makeCoordinates from "../../arguments/makeCoordinates.js"; import { str_string } from "../../environment/strings.js"; import TransformMethods from "./shared/transforms.js"; import URLMethods from "./shared/urls.js"; +import { appendTo, setAttributes } from "./shared/dom.js"; /** * @description SVG text element * @memberof SVG @@ -25,6 +26,8 @@ export default { methods: { ...TransformMethods, ...URLMethods, + appendTo, + setAttributes, }, }, }; diff --git a/src/constructor/extensions/wedge/index.js b/src/constructor/extensions/wedge/index.js new file mode 100755 index 0000000..d9f743b --- /dev/null +++ b/src/constructor/extensions/wedge/index.js @@ -0,0 +1,20 @@ +/** + * SVG (c) Kraft + */ +import makeArcPath from "../shared/makeArcPath.js"; +import { str_path } from "../../../environment/strings.js"; +import TransformMethods from "../shared/transforms.js"; + +const wedgeArguments = (a, b, c, d, e) => [makeArcPath(a, b, c, d, e, true)]; + +export default { + wedge: { + nodeName: str_path, + args: wedgeArguments, + attributes: ["d"], + methods: { + setArc: (el, ...args) => el.setAttribute("d", wedgeArguments(...args)), + ...TransformMethods, + }, + }, +}; diff --git a/src/constructor/index.js b/src/constructor/index.js index 2e29f24..addea38 100644 --- a/src/constructor/index.js +++ b/src/constructor/index.js @@ -5,17 +5,21 @@ import window from "../environment/window.js"; import svgNS from "../spec/namespace.js"; import nodes_children from "../spec/nodes_children.js"; import nodes_attributes from "../spec/nodes_attributes.js"; -import { toCamel } from "../arguments/transformCase.js"; +import { toCamel } from "../general/transformCase.js"; import extensions from "./extensions/index.js"; const passthroughArgs = (...args) => args; /** - * @description a constructor to build a "smart" SVG element. this element - * will be assigned methods which do things like set an attribute value or - * create a child of this object. This can create all elements from the SVG - * spec, some of which are programmed to be highly customized under a system - * which supports custom elements and the ability to extend the lookup - * for this constructor to include your custom element. + * @description This is the main constructor for the library which generates + * SVGElements (DOM elements) using createElementNS in the svg namespace. + * Additionally, this element will be bound with methods to operate on the + * element itself, which do things like set an attribute value or + * create a child of this object. + * Using this constructor, this library has full support for all elements + * in the SVG spec (I think so, double check me on this), additionally, + * some custom elements, for example "arrow" which makes a few shapes under + * a single group. So this library is highly extendable, you can write + * your own "arrow" objects, see more inside this directory's subdirectories. * @param {string} name the name of the element, although, slightly abstracted * from the actual element name, like "line" for because it supports * custom elements, "arrow", which in turn will create a or etc.. @@ -27,7 +31,7 @@ const Constructor = (name, parent, ...initArgs) => { const nodeName = extensions[name] && extensions[name].nodeName ? extensions[name].nodeName : name; - const { init, args, methods } = extensions[nodeName] || {}; + const { init, args, methods } = extensions[name] || {}; const attributes = nodes_attributes[nodeName] || []; const children = nodes_children[nodeName] || []; diff --git a/src/environment/lib.js b/src/environment/lib.js new file mode 100644 index 0000000..03ee8f0 --- /dev/null +++ b/src/environment/lib.js @@ -0,0 +1,6 @@ +/** + * SVG (c) Kraft + */ +const lib = {}; + +export default lib; diff --git a/src/methods/algebra.js b/src/general/algebra.js similarity index 68% rename from src/methods/algebra.js rename to src/general/algebra.js index 6ba7fe8..90e313b 100755 --- a/src/methods/algebra.js +++ b/src/general/algebra.js @@ -9,3 +9,11 @@ export const svg_magnitude2 = (a) => Math.sqrt(svg_magnitudeSq2(a)); export const svg_distanceSq2 = (a, b) => svg_magnitudeSq2(svg_sub2(a, b)); export const svg_distance2 = (a, b) => Math.sqrt(svg_distanceSq2(a, b)); export const svg_polar_to_cart = (a, d) => [Math.cos(a) * d, Math.sin(a) * d]; +export const svg_multiplyMatrices2 = (m1, m2) => [ + m1[0] * m2[0] + m1[2] * m2[1], + m1[1] * m2[0] + m1[3] * m2[1], + m1[0] * m2[2] + m1[2] * m2[3], + m1[1] * m2[2] + m1[3] * m2[3], + m1[0] * m2[4] + m1[2] * m2[5] + m1[4], + m1[1] * m2[4] + m1[3] * m2[5] + m1[5], +]; diff --git a/src/general/dom.js b/src/general/dom.js new file mode 100644 index 0000000..1c6afbc --- /dev/null +++ b/src/general/dom.js @@ -0,0 +1,134 @@ +/** + * SVG (c) Kraft + */ +import window from "../environment/window.js"; +// import nodes_attributes from "../svg/spec/nodes_attributes.js"; +import { transformStringToMatrix } from "./transforms.js"; +import { svg_multiplyMatrices2 } from "./algebra.js"; +/** + * @param {string} input an SVG as a string + * @param {string} mimeType default to XML, for SVG use "image/svg+xml". + */ +export const xmlStringToElement = (input, mimeType = "text/xml") => { + const result = (new (window().DOMParser)()).parseFromString(input, mimeType); + return result ? result.documentElement : null; +}; +/** + * @description Get the furthest root parent up the DOM tree + */ +export const getRootParent = (el) => { + let parent = el; + while (parent.parentNode != null) { + parent = parent.parentNode; + } + return parent; +}; +/** + * @description search up the parent-chain until we find the first + * with the nodeName matching the parameter, + * return undefined if none exists. + * Note: there is no protection against a dependency cycle. + * @param {Element} element a DOM element + * @param {string} nodeName the name of the element, like "svg" or "div" + * @returns {Element|undefined} the element if it exists + */ +export const findElementTypeInParents = (element, nodeName) => { + if ((element.nodeName || "") === nodeName) { + return element; + } + return element.parentNode + ? findElementTypeInParents(element.parentNode, nodeName) + : undefined; +}; + +const polyfillClassListAdd = (el, ...classes) => { + const hash = {}; + const getClass = el.getAttribute("class"); + const classArray = getClass ? getClass.split(" ") : []; + classArray.push(...classes); + classArray.forEach(str => { hash[str] = true; }); + const classString = Object.keys(hash).join(" "); + el.setAttribute("class", classString); +}; +/** + * @description Add classes to an Element, essentially classList.add(), but + * it will call a polyfill if classList doesn't exist (as in @xmldom/xmldom) + * @param {Element} element a DOM element + * @param {...string} classes a list of class strings to be added to the element + */ +export const addClass = (el, ...classes) => { + if (!el || !classes.length) { return undefined; } + return el.classList + ? el.classList.add(...classes) + : polyfillClassListAdd(el, ...classes); +}; +/** + * @description Recurse through a DOM element and flatten all elements + * into one array. This ignores all style attributes, including + * "transform" which by its absense really makes this function useful + * for treating all elements on an individual bases, and not a reliable + * reflection of where the element will end up, globally speaking. + */ +export const flattenDomTree = (el) => ( + el.childNodes == null || !el.childNodes.length + ? [el] + : Array.from(el.childNodes).flatMap(child => flattenDomTree(child)) +); + +const nodeSpecificAttrs = { + svg: ["viewBox", "xmlns", "version"], + line: ["x1", "y1", "x2", "y2"], + rect: ["x", "y", "width", "height"], + circle: ["cx", "cy", "r"], + ellipse: ["cx", "cy", "rx", "ry"], + polygon: ["points"], + polyline: ["points"], + path: ["d"], +}; + +const getAttributes = element => { + const attributeValue = element.attributes; + if (attributeValue == null) { return []; } + const attributes = Array.from(attributeValue); + return nodeSpecificAttrs[element.nodeName] + ? attributes + .filter(a => !nodeSpecificAttrs[element.nodeName].includes(a.name)) + : attributes; +}; + +const objectifyAttributes = (list) => { + const obj = {}; + list.forEach((a) => { obj[a.nodeName] = a.value; }); + return obj; +}; +/** + * @param {object} the parent element's attribute object + * @param {Element} the current element + */ +const attrAssign = (parentAttrs, element) => { + const attrs = objectifyAttributes(getAttributes(element)); + if (!attrs.transform && !parentAttrs.transform) { + return { ...parentAttrs, ...attrs }; + } + const elemTransform = attrs.transform || ""; + const parentTransform = parentAttrs.transform || ""; + const elemMatrix = transformStringToMatrix(elemTransform); + const parentMatrix = transformStringToMatrix(parentTransform); + const matrix = svg_multiplyMatrices2(parentMatrix, elemMatrix); + const transform = `matrix(${matrix.join(", ")})`; + return { ...parentAttrs, ...attrs, transform }; +}; +/** + * @description Recurse through a DOM element and flatten all elements + * into one array, where each element also has a style object which + * contains a flat object of all attributes from the parents down + * to the element itself, the closer to the element gets priority, and + * the parent attributes will be overwritten, except in the case of + * "transform", where the parent-child values are computed and merged. + */ +export const flattenDomTreeWithStyle = (element, attributes = {}) => ( + element.childNodes == null || !element.childNodes.length + ? [{ element, attributes }] + : Array.from(element.childNodes) + .flatMap(child => flattenDomTreeWithStyle(child, attrAssign(attributes, child))) +); diff --git a/src/methods/index.js b/src/general/index.js similarity index 73% rename from src/methods/index.js rename to src/general/index.js index 8452d8f..b59c4ca 100644 --- a/src/methods/index.js +++ b/src/general/index.js @@ -2,13 +2,17 @@ * SVG (c) Kraft */ import * as svgMath from "./algebra.js"; +import * as dom from "./dom.js"; import makeCDATASection from "./makeCDATASection.js"; import * as pathMethods from "./path.js"; +import * as transforms from "./transforms.js"; import * as viewBox from "./viewBox.js"; export default { ...svgMath, + ...dom, makeCDATASection, ...pathMethods, + ...transforms, ...viewBox, }; diff --git a/src/methods/makeCDATASection.js b/src/general/makeCDATASection.js similarity index 100% rename from src/methods/makeCDATASection.js rename to src/general/makeCDATASection.js diff --git a/src/arguments/makeUUID.js b/src/general/makeUUID.js similarity index 100% rename from src/arguments/makeUUID.js rename to src/general/makeUUID.js diff --git a/src/methods/path.js b/src/general/path.js similarity index 86% rename from src/methods/path.js rename to src/general/path.js index 540ed5d..5fc8b8f 100644 --- a/src/methods/path.js +++ b/src/general/path.js @@ -27,12 +27,16 @@ Object.keys(pathCommandNames).forEach((key) => { const add2path = (a, b) => [a[0] + (b[0] || 0), a[1] + (b[1] || 0)]; const getEndpoint = (command, values, offset = [0, 0]) => { const upper = command.toUpperCase(); - const origin = command === upper ? [0, 0] : offset; + let origin = command === upper ? [0, 0] : offset; + // H and V (uppercase) absolutely position themselves along their + // horiz or vert axis, but they should carry over the other component + if (command === "V") { origin = [offset[0], 0]; } + if (command === "H") { origin = [0, offset[1]]; } switch (upper) { + case "V": return add2path(origin, [0, values[0]]); + case "H": return add2path(origin, [values[0], 0]); case "M": case "L": - case "V": - case "H": case "T": return add2path(origin, values); case "A": return add2path(origin, [values[5], values[6]]); case "C": return add2path(origin, [values[4], values[5]]); diff --git a/src/methods/points.js b/src/general/points.js similarity index 100% rename from src/methods/points.js rename to src/general/points.js diff --git a/src/arguments/transformCase.js b/src/general/transformCase.js similarity index 100% rename from src/arguments/transformCase.js rename to src/general/transformCase.js diff --git a/src/general/transforms.js b/src/general/transforms.js new file mode 100644 index 0000000..1c20c27 --- /dev/null +++ b/src/general/transforms.js @@ -0,0 +1,81 @@ +/** + * SVG (c) Kraft + */ +import { svg_multiplyMatrices2 } from "./algebra.js"; +/** SVG transforms are in DEGREES ! */ +/** + * parse the value of a SVG transform attribute + * @param {string} transform, like "translate(20 30) rotate(30) skewY(10)" + * @returns {object[]} array of objects, {transform:__, parameters:__} + */ +const parseTransform = function (transform) { + const parsed = transform.match(/(\w+\((\-?\d+\.?\d*e?\-?\d*,?\s*)+\))+/g); + if (!parsed) { return []; } + const listForm = parsed.map(a => a.match(/[\w\.\-]+/g)); + return listForm.map(a => ({ + transform: a.shift(), + parameters: a.map(p => parseFloat(p)), + })); +}; + +/** + * convert the arguments of each SVG affine transform type into matrix form + */ +const matrixFormTranslate = function (params) { + switch (params.length) { + case 1: return [1, 0, 0, 1, params[0], 0]; + case 2: return [1, 0, 0, 1, params[0], params[1]]; + default: console.warn(`improper translate, ${params}`); + } + return undefined; +}; + +const matrixFormRotate = function (params) { + const cos_p = Math.cos(params[0] / (180 * Math.PI)); + const sin_p = Math.sin(params[0] / (180 * Math.PI)); + switch (params.length) { + case 1: return [cos_p, sin_p, -sin_p, cos_p, 0, 0]; + case 3: return [cos_p, sin_p, -sin_p, cos_p, + -params[1] * cos_p + params[2] * sin_p + params[1], + -params[1] * sin_p - params[2] * cos_p + params[2]]; + default: console.warn(`improper rotate, ${params}`); + } + return undefined; +}; + +const matrixFormScale = function (params) { + switch (params.length) { + case 1: return [params[0], 0, 0, params[0], 0, 0]; + case 2: return [params[0], 0, 0, params[1], 0, 0]; + default: console.warn(`improper scale, ${params}`); + } + return undefined; +}; + +const matrixFormSkewX = function (params) { + return [1, 0, Math.tan(params[0] / (180 * Math.PI)), 1, 0, 0]; +}; + +const matrixFormSkewY = function (params) { + return [1, Math.tan(params[0] / (180 * Math.PI)), 0, 1, 0, 0]; +}; + +const matrixForm = function (transformType, params) { + switch (transformType) { + case "translate": return matrixFormTranslate(params); + case "rotate": return matrixFormRotate(params); + case "scale": return matrixFormScale(params); + case "skewX": return matrixFormSkewX(params); + case "skewY": return matrixFormSkewY(params); + case "matrix": return params; + default: console.warn(`unknown transform type ${transformType}`); + } + return undefined; +}; + +export const transformStringToMatrix = function (string) { + return parseTransform(string) + .map(el => matrixForm(el.transform, el.parameters)) + .filter(a => a !== undefined) + .reduce((a, b) => svg_multiplyMatrices2(a, b), [1, 0, 0, 1, 0, 0]); +}; diff --git a/src/methods/viewBox.js b/src/general/viewBox.js similarity index 100% rename from src/methods/viewBox.js rename to src/general/viewBox.js diff --git a/src/index.js b/src/index.js index 94605fe..e1b0ecc 100755 --- a/src/index.js +++ b/src/index.js @@ -1,39 +1,56 @@ /** * SVG (c) Kraft */ -import { setSVGWindow } from "./environment/window.js"; +import { + str_svg, + str_function, +} from "./environment/strings.js"; +import window, { setSVGWindow } from "./environment/window.js"; import NS from "./spec/namespace.js"; -// import argumentMethods from "./arguments/index.js"; -// import methods from "./methods/index.js"; -import extensions from "./constructor/extensions/index.js"; -import constructor from "./constructor/index.js"; -import nodes from "./spec/nodes.js"; import nodes_attributes from "./spec/nodes_attributes.js"; import nodes_children from "./spec/nodes_children.js"; +import nodes from "./spec/nodes.js"; +import colors from "./colors/index.js"; +import general from "./general/index.js"; +import extensions from "./constructor/extensions/index.js"; +import Constructor from "./constructor/index.js"; // the top level container object is also an constructor -const svg = (...args) => constructor("svg", null, ...args); +// const SVG = (...args) => Constructor("svg", null, ...args); +const SVG = (...args) => { + const svg = Constructor(str_svg, null, ...args); + const initialize = () => args + .filter(arg => typeof arg === str_function) + .forEach(func => func.call(svg, svg)); + // call initialize as soon as possible. check if page has loaded + if (window().document.readyState === "loading") { + window().document.addEventListener("DOMContentLoaded", initialize); + } else { + initialize(); + } + return svg; +}; // constants and methods at the top level of the library -Object.assign(svg, { +Object.assign(SVG, { NS, nodes_attributes, nodes_children, extensions, - // ...argumentMethods, - // ...methods, + ...colors, + ...general, }); nodes.forEach(nodeName => { - svg[nodeName] = (...args) => constructor(nodeName, null, ...args); + SVG[nodeName] = (...args) => Constructor(nodeName, null, ...args); }); // the window object, from which the document is used to createElement. // if using a browser, no need to interact with this, // if using node.js, set this to the library @xmldom/xmldom. -Object.defineProperty(svg, "window", { +Object.defineProperty(SVG, "window", { enumerable: false, - set: value => { setSVGWindow(value); }, + set: setSVGWindow, }); -export default svg; +export default SVG; diff --git a/src/spec/classes_nodes.js b/src/spec/classes_nodes.js index cc0b3d8..8a8975f 100644 --- a/src/spec/classes_nodes.js +++ b/src/spec/classes_nodes.js @@ -1,3 +1,6 @@ +/** + * SVG (c) Kraft + */ const classes_nodes = { svg: [ "svg", @@ -29,6 +32,15 @@ const classes_nodes = { "polygon", "polyline", "rect", + + // extensions to the SVG spec + "arc", + "arrow", + "curve", + "parabola", + "roundRect", + "wedge", + "origami", ], text: [ "text", diff --git a/src/spec/nodes.js b/src/spec/nodes.js index edc5c02..cf9ff0c 100644 --- a/src/spec/nodes.js +++ b/src/spec/nodes.js @@ -1,3 +1,6 @@ +/** + * SVG (c) Kraft + */ import classes_nodes from "./classes_nodes.js"; const nodeNames = Object.values(classes_nodes).flat(); diff --git a/svg.js b/svg.js index 109d979..76ce036 100644 --- a/svg.js +++ b/svg.js @@ -1,1259 +1 @@ -/* svg (c) Kraft, MIT License */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.svg = factory()); -})(this, (function () { 'use strict'; - - const str_class = "class"; - const str_function = "function"; - const str_undefined = "undefined"; - const str_number = "number"; - const str_string = "string"; - const str_object = "object"; - const str_svg = "svg"; - const str_id = "id"; - const str_style = "style"; - const str_viewBox = "viewBox"; - const str_transform = "transform"; - const str_points = "points"; - const str_stroke = "stroke"; - const str_fill = "fill"; - const str_none = "none"; - - const isBrowser = typeof window !== str_undefined - && typeof window.document !== str_undefined; - typeof process !== str_undefined - && process.versions != null - && process.versions.node != null; - - var Messages = { - window: "window not set; svg.window = @xmldom/xmldom", - }; - - const svgWindowContainer = { window: undefined }; - const buildHTMLDocument = (newWindow) => new newWindow.DOMParser() - .parseFromString(".", "text/html"); - const setSVGWindow = (newWindow) => { - if (!newWindow.document) { newWindow.document = buildHTMLDocument(newWindow); } - svgWindowContainer.window = newWindow; - return svgWindowContainer.window; - }; - if (isBrowser) { svgWindowContainer.window = window; } - const SVGWindow = () => { - if (svgWindowContainer.window === undefined) { - throw Messages.window; - } - return svgWindowContainer.window; - }; - - var NS = "http://www.w3.org/2000/svg"; - - const makeCoordinates = (...args) => args - .filter(a => typeof a === str_number) - .concat(args - .filter(a => typeof a === str_object && a !== null) - .map((el) => { - if (typeof el.x === str_number) { return [el.x, el.y]; } - if (typeof el[0] === str_number) { return [el[0], el[1]]; } - return undefined; - }).filter(a => a !== undefined) - .reduce((a, b) => a.concat(b), [])); - - const viewBoxValuesToString = function (x, y, width, height, padding = 0) { - const scale = 1.0; - const d = (width / scale) - width; - const X = (x - d) - padding; - const Y = (y - d) - padding; - const W = (width + d * 2) + padding * 2; - const H = (height + d * 2) + padding * 2; - return [X, Y, W, H].join(" "); - }; - const makeViewBox = (...args) => { - const numbers = makeCoordinates(...args.flat()); - if (numbers.length === 2) { numbers.unshift(0, 0); } - return numbers.length === 4 ? viewBoxValuesToString(...numbers) : undefined; - }; - - const makeCDATASection = (text) => (new (SVGWindow()).DOMParser()) - .parseFromString("", "text/xml") - .createCDATASection(text); - - const toCamel = (s) => s - .replace(/([-_][a-z])/ig, $1 => $1 - .toUpperCase() - .replace("-", "") - .replace("_", "")); - const capitalized = (s) => s - .charAt(0).toUpperCase() + s.slice(1); - - const removeChildren = (element) => { - while (element.lastChild) { - element.removeChild(element.lastChild); - } - return element; - }; - const clearSVG = (element) => { - Array.from(element.attributes) - .filter(attr => attr.name !== "xmlns" && attr.name !== "version") - .forEach(attr => element.removeAttribute(attr.name)); - return removeChildren(element); - }; - - const setViewBox = (element, ...args) => { - const viewBox = args.length === 1 && typeof args[0] === str_string - ? args[0] - : makeViewBox(...args); - if (viewBox) { - element.setAttribute(str_viewBox, viewBox); - } - return element; - }; - const getViewBox = function (element) { - const vb = element.getAttribute(str_viewBox); - return (vb == null - ? undefined - : vb.split(" ").map(n => parseFloat(n))); - }; - const convertToViewBox = function (svg, x, y) { - const pt = svg.createSVGPoint(); - pt.x = x; - pt.y = y; - const svgPoint = pt.matrixTransform(svg.getScreenCTM().inverse()); - return [svgPoint.x, svgPoint.y]; - }; - - const classes_attributes = { - presentation: [ - "color", - "color-interpolation", - "cursor", - "direction", - "display", - "fill", - "fill-opacity", - "fill-rule", - "font-family", - "font-size", - "font-size-adjust", - "font-stretch", - "font-style", - "font-variant", - "font-weight", - "image-rendering", - "letter-spacing", - "opacity", - "overflow", - "paint-order", - "pointer-events", - "preserveAspectRatio", - "shape-rendering", - "stroke", - "stroke-dasharray", - "stroke-dashoffset", - "stroke-linecap", - "stroke-linejoin", - "stroke-miterlimit", - "stroke-opacity", - "stroke-width", - "tabindex", - "transform-origin", - "user-select", - "vector-effect", - "visibility", - ], - animation: [ - "accumulate", - "additive", - "attributeName", - "begin", - "by", - "calcMode", - "dur", - "end", - "from", - "keyPoints", - "keySplines", - "keyTimes", - "max", - "min", - "repeatCount", - "repeatDur", - "restart", - "to", - "values", - ], - effects: [ - "azimuth", - "baseFrequency", - "bias", - "color-interpolation-filters", - "diffuseConstant", - "divisor", - "edgeMode", - "elevation", - "exponent", - "filter", - "filterRes", - "filterUnits", - "flood-color", - "flood-opacity", - "in", - "in2", - "intercept", - "k1", - "k2", - "k3", - "k4", - "kernelMatrix", - "lighting-color", - "limitingConeAngle", - "mode", - "numOctaves", - "operator", - "order", - "pointsAtX", - "pointsAtY", - "pointsAtZ", - "preserveAlpha", - "primitiveUnits", - "radius", - "result", - "seed", - "specularConstant", - "specularExponent", - "stdDeviation", - "stitchTiles", - "surfaceScale", - "targetX", - "targetY", - "type", - "xChannelSelector", - "yChannelSelector", - ], - text: [ - "dx", - "dy", - "alignment-baseline", - "baseline-shift", - "dominant-baseline", - "lengthAdjust", - "method", - "overline-position", - "overline-thickness", - "rotate", - "spacing", - "startOffset", - "strikethrough-position", - "strikethrough-thickness", - "text-anchor", - "text-decoration", - "text-rendering", - "textLength", - "underline-position", - "underline-thickness", - "word-spacing", - "writing-mode", - ], - gradient: [ - "gradientTransform", - "gradientUnits", - "spreadMethod", - ], - }; - - const classes_nodes = { - svg: [ - "svg", - ], - defs: [ - "defs", - ], - header: [ - "desc", - "filter", - "metadata", - "style", - "script", - "title", - "view", - ], - cdata: [ - "cdata", - ], - group: [ - "g", - ], - visible: [ - "circle", - "ellipse", - "line", - "path", - "polygon", - "polyline", - "rect", - ], - text: [ - "text", - ], - invisible: [ - "marker", - "symbol", - "clipPath", - "mask", - ], - patterns: [ - "linearGradient", - "radialGradient", - "pattern", - ], - childrenOfText: [ - "textPath", - "tspan", - ], - gradients: [ - "stop", - ], - filter: [ - "feBlend", - "feColorMatrix", - "feComponentTransfer", - "feComposite", - "feConvolveMatrix", - "feDiffuseLighting", - "feDisplacementMap", - "feDistantLight", - "feDropShadow", - "feFlood", - "feFuncA", - "feFuncB", - "feFuncG", - "feFuncR", - "feGaussianBlur", - "feImage", - "feMerge", - "feMergeNode", - "feMorphology", - "feOffset", - "fePointLight", - "feSpecularLighting", - "feSpotLight", - "feTile", - "feTurbulence", - ], - }; - - const nodes_attributes = { - svg: [str_viewBox], - line: ["x1", "y1", "x2", "y2"], - rect: ["x", "y", "width", "height"], - circle: ["cx", "cy", "r"], - ellipse: ["cx", "cy", "rx", "ry"], - polygon: [str_points], - polyline: [str_points], - path: ["d"], - text: ["x", "y"], - mask: [str_id], - symbol: [str_id], - clipPath: [str_id, "clip-rule"], - marker: [ - str_id, - "markerHeight", - "markerUnits", - "markerWidth", - "orient", - "refX", - "refY", - ], - linearGradient: ["x1", "x2", "y1", "y2"], - radialGradient: ["cx", "cy", "r", "fr", "fx", "fy"], - stop: ["offset", "stop-color", "stop-opacity"], - pattern: ["patternContentUnits", "patternTransform", "patternUnits"], - }; - const additionalNodeAttributes = [{ - nodes: [str_svg, "defs", "g"].concat(classes_nodes.visible, classes_nodes.text), - attr: classes_attributes.presentation, - }, { - nodes: ["filter"], - attr: classes_attributes.effects, - }, { - nodes: classes_nodes.childrenOfText.concat("text"), - attr: classes_attributes.text, - }, { - nodes: classes_nodes.filter, - attr: classes_attributes.effects, - }, { - nodes: classes_nodes.gradients, - attr: classes_attributes.gradient, - }]; - additionalNodeAttributes - .forEach(el => el.nodes - .forEach(nodeName => { - if (!nodes_attributes[nodeName]) { nodes_attributes[nodeName] = []; } - nodes_attributes[nodeName].push(...el.attr); - })); - - const getSVGFrame = function (element) { - const viewBox = getViewBox(element); - if (viewBox !== undefined) { - return viewBox; - } - if (typeof element.getBoundingClientRect === str_function) { - const rr = element.getBoundingClientRect(); - return [rr.x, rr.y, rr.width, rr.height]; - } - return []; - }; - - const bgClass = "svg-background-rectangle"; - const makeBackground = function (element, color) { - let backRect = Array.from(element.childNodes) - .filter(child => child.getAttribute(str_class) === bgClass) - .shift(); - if (backRect == null) { - backRect = SVGWindow().document.createElementNS(NS, "rect"); - getSVGFrame(element).forEach((n, i) => backRect.setAttribute(nodes_attributes.rect[i], n)); - backRect.setAttribute(str_class, bgClass); - backRect.setAttribute(str_stroke, str_none); - element.insertBefore(backRect, element.firstChild); - } - backRect.setAttribute(str_fill, color); - return element; - }; - - const getAttr = (element) => { - const t = element.getAttribute(str_transform); - return (t == null || t === "") ? undefined : t; - }; - const TransformMethods = { - clearTransform: (el) => { el.removeAttribute(str_transform); return el; }, - }; - ["translate", "rotate", "scale", "matrix"].forEach(key => { - TransformMethods[key] = (element, ...args) => { - element.setAttribute( - str_transform, - [getAttr(element), `${key}(${args.join(" ")})`] - .filter(a => a !== undefined) - .join(" "), - ); - return element; - }; - }); - - const setPadding = function (element, padding) { - const viewBox = getViewBox(element); - if (viewBox !== undefined) { - setViewBox(element, ...[-padding, -padding, padding * 2, padding * 2] - .map((nudge, i) => viewBox[i] + nudge)); - } - return element; - }; - const findOneElement = function (element, nodeName) { - const styles = element.getElementsByTagName(nodeName); - return styles.length ? styles[0] : null; - }; - const stylesheet = function (element, textContent) { - let styleSection = findOneElement(element, str_style); - if (styleSection == null) { - styleSection = SVGWindow().document.createElementNS(NS, str_style); - styleSection.setTextContent = (text) => { - styleSection.textContent = ""; - styleSection.appendChild(makeCDATASection(text)); - return styleSection; - }; - element.insertBefore(styleSection, element.firstChild); - } - styleSection.textContent = ""; - styleSection.appendChild(makeCDATASection(textContent)); - return styleSection; - }; - var methods$1 = { - clear: clearSVG, - removeChildren, - size: setViewBox, - setViewBox, - getViewBox, - padding: setPadding, - background: makeBackground, - getWidth: el => getSVGFrame(el)[2], - getHeight: el => getSVGFrame(el)[3], - stylesheet: function (el, text) { return stylesheet.call(this, el, text); }, - ...TransformMethods, - }; - - const categories = { - move: ["mousemove", "touchmove"], - press: ["mousedown", "touchstart"], - release: ["mouseup", "touchend"], - leave: ["mouseleave", "touchcancel"], - }; - const handlerNames = Object.values(categories) - .reduce((a, b) => a.concat(b), []); - const off = (element, handlers) => handlerNames.forEach((handlerName) => { - handlers[handlerName].forEach(func => element.removeEventListener(handlerName, func)); - handlers[handlerName] = []; - }); - const defineGetter = (obj, prop, value) => Object.defineProperty(obj, prop, { - get: () => value, - enumerable: true, - configurable: true, - }); - const assignPress = (e, startPoint) => { - ["pressX", "pressY"].filter(prop => !Object.prototype.hasOwnProperty.call(e, prop)) - .forEach((prop, i) => defineGetter(e, prop, startPoint[i])); - if (!Object.prototype.hasOwnProperty.call(e, "press")) { - defineGetter(e, "press", [...startPoint]); - } - }; - const TouchEvents = function (element) { - let startPoint = []; - const handlers = []; - Object.keys(categories).forEach((key) => { - categories[key].forEach((handler) => { - handlers[handler] = []; - }); - }); - const removeHandler = category => categories[category] - .forEach(handlerName => handlers[handlerName] - .forEach(func => element.removeEventListener(handlerName, func))); - const categoryUpdate = { - press: (e, viewPoint) => { - startPoint = viewPoint; - assignPress(e, startPoint); - }, - release: () => {}, - leave: () => {}, - move: (e, viewPoint) => { - if (e.buttons > 0 && startPoint[0] === undefined) { - startPoint = viewPoint; - } else if (e.buttons === 0 && startPoint[0] !== undefined) { - startPoint = []; - } - assignPress(e, startPoint); - }, - }; - Object.keys(categories).forEach((category) => { - const propName = `on${capitalized(category)}`; - Object.defineProperty(element, propName, { - set: (handler) => { - if (handler == null) { - removeHandler(category); - return; - } - categories[category].forEach((handlerName) => { - const handlerFunc = (e) => { - const pointer = (e.touches != null - ? e.touches[0] - : e); - if (pointer !== undefined) { - const viewPoint = convertToViewBox(element, pointer.clientX, pointer.clientY) - .map(n => (Number.isNaN(n) ? undefined : n)); - ["x", "y"] - .filter(prop => !Object.prototype.hasOwnProperty.call(e, prop)) - .forEach((prop, i) => defineGetter(e, prop, viewPoint[i])); - if (!Object.prototype.hasOwnProperty.call(e, "position")) { - defineGetter(e, "position", [...viewPoint]); - } - categoryUpdate[category](e, viewPoint); - } - handler(e); - }; - if (element.addEventListener) { - handlers[handlerName].push(handlerFunc); - element.addEventListener(handlerName, handlerFunc); - } - }); - }, - enumerable: true, - }); - }); - Object.defineProperty(element, "off", { value: () => off(element, handlers) }); - }; - - const makeUUID = () => Math.random() - .toString(36) - .replace(/[^a-z]+/g, "") - .concat("aaaaa") - .substr(0, 5); - - const Animation = function (element) { - let start; - const handlers = {}; - let frame = 0; - let requestId; - const removeHandlers = () => { - if (SVGWindow().cancelAnimationFrame) { - SVGWindow().cancelAnimationFrame(requestId); - } - Object.keys(handlers) - .forEach(uuid => delete handlers[uuid]); - start = undefined; - frame = 0; - }; - Object.defineProperty(element, "play", { - set: (handler) => { - removeHandlers(); - if (handler == null) { return; } - const uuid = makeUUID(); - const handlerFunc = (e) => { - if (!start) { - start = e; - frame = 0; - } - const progress = (e - start) * 0.001; - handler({ time: progress, frame }); - frame += 1; - if (handlers[uuid]) { - requestId = SVGWindow().requestAnimationFrame(handlers[uuid]); - } - }; - handlers[uuid] = handlerFunc; - if (SVGWindow().requestAnimationFrame) { - requestId = SVGWindow().requestAnimationFrame(handlers[uuid]); - } - }, - enumerable: true, - }); - Object.defineProperty(element, "stop", { value: removeHandlers, enumerable: true }); - }; - - const svg_sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]]; - const svg_magnitudeSq2 = (a) => (a[0] ** 2) + (a[1] ** 2); - const svg_distanceSq2 = (a, b) => svg_magnitudeSq2(svg_sub2(a, b)); - const svg_distance2 = (a, b) => Math.sqrt(svg_distanceSq2(a, b)); - - const removeFromParent = svg => (svg && svg.parentNode - ? svg.parentNode.removeChild(svg) - : undefined); - const possiblePositionAttributes = [["cx", "cy"], ["x", "y"]]; - const controlPoint = function (parent, options = {}) { - const position = [0, 0]; - const cp = { - selected: false, - svg: undefined, - updatePosition: input => input, - }; - const updateSVG = () => { - if (!cp.svg) { return; } - if (!cp.svg.parentNode) { - parent.appendChild(cp.svg); - } - possiblePositionAttributes - .filter(coords => cp.svg[coords[0]] != null) - .forEach(coords => coords.forEach((attr, i) => { - cp.svg.setAttribute(attr, position[i]); - })); - }; - const proxy = new Proxy(position, { - set: (target, property, value) => { - target[property] = value; - updateSVG(); - return true; - }, - }); - const setPosition = function (...args) { - makeCoordinates(...args.flat()) - .forEach((n, i) => { position[i] = n; }); - updateSVG(); - if (typeof position.delegate === str_function) { - position.delegate.apply(position.pointsContainer, [proxy, position.pointsContainer]); - } - }; - position.delegate = undefined; - position.setPosition = setPosition; - position.onMouseMove = mouse => (cp.selected - ? setPosition(cp.updatePosition(mouse)) - : undefined); - position.onMouseUp = () => { cp.selected = false; }; - position.distance = mouse => Math.sqrt(svg_distanceSq2(mouse, position)); - ["x", "y"].forEach((prop, i) => Object.defineProperty(position, prop, { - get: () => position[i], - set: (v) => { position[i] = v; } - })); - [str_svg, "updatePosition", "selected"].forEach(key => Object - .defineProperty(position, key, { - get: () => cp[key], - set: (v) => { cp[key] = v; }, - })); - Object.defineProperty(position, "remove", { - value: () => { - removeFromParent(cp.svg); - position.delegate = undefined; - }, - }); - return proxy; - }; - const controls = function (svg, number, options) { - let selected; - let delegate; - const points = Array.from(Array(number)) - .map(() => controlPoint(svg, options)); - const protocol = point => (typeof delegate === str_function - ? delegate.call(points, point, selected, points) - : undefined); - points.forEach((p) => { - p.delegate = protocol; - p.pointsContainer = points; - }); - const mousePressedHandler = function (mouse) { - if (!(points.length > 0)) { return; } - selected = points - .map((p, i) => ({ i, d: svg_distanceSq2(p, [mouse.x, mouse.y]) })) - .sort((a, b) => a.d - b.d) - .shift() - .i; - points[selected].selected = true; - }; - const mouseMovedHandler = function (mouse) { - points.forEach(p => p.onMouseMove(mouse)); - }; - const mouseReleasedHandler = function () { - points.forEach(p => p.onMouseUp()); - selected = undefined; - }; - svg.onPress = mousePressedHandler; - svg.onMove = mouseMovedHandler; - svg.onRelease = mouseReleasedHandler; - Object.defineProperty(points, "selectedIndex", { get: () => selected }); - Object.defineProperty(points, "selected", { get: () => points[selected] }); - Object.defineProperty(points, "add", { - value: (opt) => { - points.push(controlPoint(svg, opt)); - }, - }); - points.removeAll = () => { - while (points.length > 0) { - points.pop().remove(); - } - }; - const functionalMethods = { - onChange: (func, runOnceAtStart) => { - delegate = func; - if (runOnceAtStart === true) { - const index = points.length - 1; - func.call(points, points[index], index, points); - } - }, - position: func => points.forEach((p, i) => p.setPosition(func.call(points, p, i, points))), - svg: func => points.forEach((p, i) => { p.svg = func.call(points, p, i, points); }), - }; - Object.keys(functionalMethods).forEach((key) => { - points[key] = function () { - if (typeof arguments[0] === str_function) { - functionalMethods[key](...arguments); - } - return points; - }; - }); - points.parent = function (parent) { - if (parent != null && parent.appendChild != null) { - points.forEach((p) => { parent.appendChild(p.svg); }); - } - return points; - }; - return points; - }; - const applyControlsToSVG = (svg) => { - svg.controls = (...args) => controls.call(svg, svg, ...args); - }; - - var svgDef = { - svg: { - args: (...args) => [makeViewBox(makeCoordinates(...args))].filter(a => a != null), - methods: methods$1, - init: (...args) => { - const element = SVGWindow().document.createElementNS(NS, "svg"); - element.setAttribute("version", "1.1"); - element.setAttribute("xmlns", NS); - args.filter(a => a != null) - .filter(el => el.appendChild) - .forEach(parent => parent.appendChild(element)); - TouchEvents(element); - Animation(element); - applyControlsToSVG(element); - return element; - }, - }, - }; - - const findIdURL = function (arg) { - if (arg == null) { return ""; } - if (typeof arg === str_string) { - return arg.slice(0, 3) === "url" - ? arg - : `url(#${arg})`; - } - if (arg.getAttribute != null) { - const idString = arg.getAttribute(str_id); - return `url(#${idString})`; - } - return ""; - }; - const methods = {}; - ["clip-path", - "mask", - "symbol", - "marker-end", - "marker-mid", - "marker-start", - ].forEach(attr => { - methods[toCamel(attr)] = (element, parent) => element.setAttribute(attr, findIdURL(parent)); - }); - - const loadGroup = (...sources) => { - const group = SVGWindow().document.createElementNS(NS, "g"); - return group; - }; - var gDef = { - g: { - init: loadGroup, - methods: { - load: loadGroup, - removeChildren, - ...TransformMethods, - ...methods, - }, - }, - }; - - const setRadius = (el, r) => { - el.setAttribute(nodes_attributes.circle[2], r); - return el; - }; - const setOrigin$1 = (el, a, b) => { - [...makeCoordinates(...[a, b].flat()).slice(0, 2)] - .forEach((value, i) => el.setAttribute(nodes_attributes.circle[i], value)); - return el; - }; - const fromPoints = (a, b, c, d) => [a, b, svg_distance2([a, b], [c, d])]; - var circleDef = { - circle: { - args: (a, b, c, d) => { - const coords = makeCoordinates(...[a, b, c, d].flat()); - switch (coords.length) { - case 0: case 1: return [, , ...coords]; - case 2: case 3: return coords; - default: return fromPoints(...coords); - } - }, - methods: { - radius: setRadius, - setRadius, - origin: setOrigin$1, - setOrigin: setOrigin$1, - center: setOrigin$1, - setCenter: setOrigin$1, - position: setOrigin$1, - setPosition: setOrigin$1, - ...TransformMethods, - ...methods, - }, - }, - }; - - const setRadii = (el, rx, ry) => { - [, , rx, ry].forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value)); - return el; - }; - const setOrigin = (el, a, b) => { - [...makeCoordinates(...[a, b].flat()).slice(0, 2)] - .forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value)); - return el; - }; - var ellipseDef = { - ellipse: { - args: (a, b, c, d) => { - const coords = makeCoordinates(...[a, b, c, d].flat()).slice(0, 4); - switch (coords.length) { - case 0: case 1: case 2: return [, , ...coords]; - default: return coords; - } - }, - methods: { - radius: setRadii, - setRadius: setRadii, - origin: setOrigin, - setOrigin, - center: setOrigin, - setCenter: setOrigin, - position: setOrigin, - setPosition: setOrigin, - ...TransformMethods, - ...methods, - }, - }, - }; - - const svgIsIterable = (obj) => obj != null - && typeof obj[Symbol.iterator] === str_function; - const svgSemiFlattenArrays = function () { - switch (arguments.length) { - case 0: return Array.from(arguments); - case 1: return svgIsIterable(arguments[0]) && typeof arguments[0] !== str_string - ? svgSemiFlattenArrays(...arguments[0]) - : [arguments[0]]; - default: - return Array.from(arguments).map(a => (svgIsIterable(a) - ? [...svgSemiFlattenArrays(a)] - : a)); - } - }; - - const Args$1 = (...args) => makeCoordinates(...svgSemiFlattenArrays(...args)).slice(0, 4); - const setPoints$1 = (element, ...args) => { - Args$1(...args).forEach((value, i) => element.setAttribute(nodes_attributes.line[i], value)); - return element; - }; - var lineDef = { - line: { - args: Args$1, - methods: { - setPoints: setPoints$1, - ...TransformMethods, - ...methods, - }, - }, - }; - - const markerRegEx = /[MmLlSsQqLlHhVvCcSsQqTtAaZz]/g; - const digitRegEx = /-?[0-9]*\.?\d+/g; - const pathCommandNames = { - m: "move", - l: "line", - v: "vertical", - h: "horizontal", - a: "ellipse", - c: "curve", - s: "smoothCurve", - q: "quadCurve", - t: "smoothQuadCurve", - z: "close", - }; - Object.keys(pathCommandNames).forEach((key) => { - const s = pathCommandNames[key]; - pathCommandNames[key.toUpperCase()] = s.charAt(0).toUpperCase() + s.slice(1); - }); - const parsePathCommands = (d) => { - const results = []; - let match; - while ((match = markerRegEx.exec(d)) !== null) { - results.push(match); - } - return results - .map((result, i, arr) => [ - result[0], - result.index, - i === arr.length - 1 - ? d.length - 1 - : arr[(i + 1) % arr.length].index - 1, - ]) - .map(el => { - const command = el[0]; - const valueString = d.substring(el[1] + 1, el[2] + 1); - const strings = valueString.match(digitRegEx); - const values = strings ? strings.map(parseFloat) : []; - return { command, values }; - }); - }; - - const getD = (el) => { - const attr = el.getAttribute("d"); - return (attr == null) ? "" : attr; - }; - const clear = element => { - element.removeAttribute("d"); - return element; - }; - const appendPathCommand = (el, command, ...args) => { - el.setAttribute("d", `${getD(el)}${command}${args.flat().join(" ")}`); - return el; - }; - const getCommands = element => parsePathCommands(getD(element)); - const path_methods = { - addCommand: appendPathCommand, - appendCommand: appendPathCommand, - clear, - getCommands: getCommands, - get: getCommands, - getD: el => el.getAttribute("d"), - ...TransformMethods, - ...methods, - }; - Object.keys(pathCommandNames).forEach((key) => { - path_methods[pathCommandNames[key]] = (el, ...args) => appendPathCommand(el, key, ...args); - }); - var pathDef = { - path: { - methods: path_methods, - }, - }; - - const setRectSize = (el, rx, ry) => { - [, , rx, ry] - .forEach((value, i) => el.setAttribute(nodes_attributes.rect[i], value)); - return el; - }; - const setRectOrigin = (el, a, b) => { - [...makeCoordinates(...[a, b].flat()).slice(0, 2)] - .forEach((value, i) => el.setAttribute(nodes_attributes.rect[i], value)); - return el; - }; - const fixNegatives = function (arr) { - [0, 1].forEach(i => { - if (arr[2 + i] < 0) { - if (arr[0 + i] === undefined) { arr[0 + i] = 0; } - arr[0 + i] += arr[2 + i]; - arr[2 + i] = -arr[2 + i]; - } - }); - return arr; - }; - var rectDef = { - rect: { - args: (a, b, c, d) => { - const coords = makeCoordinates(...[a, b, c, d].flat()).slice(0, 4); - switch (coords.length) { - case 0: case 1: case 2: case 3: return fixNegatives([, , ...coords]); - default: return fixNegatives(coords); - } - }, - methods: { - origin: setRectOrigin, - setOrigin: setRectOrigin, - center: setRectOrigin, - setCenter: setRectOrigin, - size: setRectSize, - setSize: setRectSize, - ...TransformMethods, - ...methods, - }, - }, - }; - - var styleDef = { - style: { - init: (text) => { - const el = SVGWindow().document.createElementNS(NS, "style"); - el.setAttribute("type", "text/css"); - el.textContent = ""; - el.appendChild(makeCDATASection(text)); - return el; - }, - methods: { - setTextContent: (el, text) => { - el.textContent = ""; - el.appendChild(makeCDATASection(text)); - return el; - }, - }, - }, - }; - - var textDef = { - text: { - args: (a, b, c) => makeCoordinates(...[a, b, c].flat()).slice(0, 2), - init: (a, b, c, d) => { - const element = SVGWindow().document.createElementNS(NS, "text"); - const text = [a, b, c, d].filter(el => typeof el === str_string).shift(); - element.appendChild(SVGWindow().document.createTextNode(text || "")); - return element; - }, - methods: { - ...TransformMethods, - ...methods, - }, - }, - }; - - const makeIDString = function () { - return Array.from(arguments) - .filter(a => typeof a === str_string || a instanceof String) - .shift() || makeUUID(); - }; - const maskArgs = (...args) => [makeIDString(...args)]; - var maskTypes = { - mask: { - args: maskArgs, - methods: { - removeChildren, - ...TransformMethods, - ...methods, - }, - }, - clipPath: { - args: maskArgs, - methods: { - removeChildren, - ...TransformMethods, - ...methods, - }, - }, - symbol: { - args: maskArgs, - methods: { - removeChildren, - ...TransformMethods, - ...methods, - }, - }, - marker: { - args: maskArgs, - methods: { - size: setViewBox, - setViewBox: setViewBox, - removeChildren, - ...TransformMethods, - ...methods, - }, - }, - }; - - const getPoints = (el) => { - const attr = el.getAttribute(str_points); - return (attr == null) ? "" : attr; - }; - const polyString = function () { - return Array - .from(Array(Math.floor(arguments.length / 2))) - .map((_, i) => `${arguments[i * 2 + 0]},${arguments[i * 2 + 1]}`) - .join(" "); - }; - const stringifyArgs = (...args) => [ - polyString(...makeCoordinates(...svgSemiFlattenArrays(...args))), - ]; - const setPoints = (element, ...args) => { - element.setAttribute(str_points, stringifyArgs(...args)[0]); - return element; - }; - const addPoint = (element, ...args) => { - element.setAttribute(str_points, [getPoints(element), stringifyArgs(...args)[0]] - .filter(a => a !== "") - .join(" ")); - return element; - }; - const Args = function (...args) { - return args.length === 1 && typeof args[0] === str_string - ? [args[0]] - : stringifyArgs(...args); - }; - var polyDefs = { - polyline: { - args: Args, - methods: { - setPoints, - addPoint, - ...TransformMethods, - ...methods, - }, - }, - polygon: { - args: Args, - methods: { - setPoints, - addPoint, - ...TransformMethods, - ...methods, - }, - }, - }; - - var extensions = { - ...svgDef, - ...gDef, - ...circleDef, - ...ellipseDef, - ...lineDef, - ...pathDef, - ...rectDef, - ...styleDef, - ...textDef, - ...maskTypes, - ...polyDefs, - }; - - const headerStuff = [ - classes_nodes.header, - classes_nodes.invisible, - classes_nodes.patterns, - ].flat(); - const drawingShapes = [ - classes_nodes.group, - classes_nodes.visible, - classes_nodes.text, - ].flat(); - const nodes_children = { - svg: [["svg", "defs"], headerStuff, drawingShapes].flat(), - defs: headerStuff, - filter: classes_nodes.filter, - g: drawingShapes, - text: classes_nodes.childrenOfText, - marker: drawingShapes, - symbol: drawingShapes, - clipPath: drawingShapes, - mask: drawingShapes, - linearGradient: classes_nodes.gradients, - radialGradient: classes_nodes.gradients, - }; - - const passthroughArgs = (...args) => args; - const Constructor = (name, parent, ...initArgs) => { - const nodeName = extensions[name] && extensions[name].nodeName - ? extensions[name].nodeName - : name; - const { init, args, methods } = extensions[nodeName] || {}; - const attributes = nodes_attributes[nodeName] || []; - const children = nodes_children[nodeName] || []; - const element = init - ? init(...initArgs) - : SVGWindow().document.createElementNS(NS, nodeName); - if (parent) { parent.appendChild(element); } - const processArgs = args || passthroughArgs; - processArgs(...initArgs).forEach((v, i) => { - element.setAttribute(nodes_attributes[nodeName][i], v); - }); - if (methods) { - Object.keys(methods) - .forEach(methodName => Object.defineProperty(element, methodName, { - value: function () { - return methods[methodName](element, ...arguments); - }, - })); - } - attributes.forEach((attribute) => { - const attrNameCamel = toCamel(attribute); - if (element[attrNameCamel]) { return; } - Object.defineProperty(element, attrNameCamel, { - value: function () { - element.setAttribute(attribute, ...arguments); - return element; - }, - }); - }); - children.forEach((childNode) => { - if (element[childNode]) { return; } - const value = function () { return Constructor(childNode, element, ...arguments); }; - Object.defineProperty(element, childNode, { value }); - }); - return element; - }; - - const nodeNames = Object.values(classes_nodes).flat(); - - const svg = (...args) => Constructor("svg", null, ...args); - Object.assign(svg, { - NS, - nodes_attributes, - nodes_children, - extensions, - }); - nodeNames.forEach(nodeName => { - svg[nodeName] = (...args) => Constructor(nodeName, null, ...args); - }); - Object.defineProperty(svg, "window", { - enumerable: false, - set: value => { setSVGWindow(value); }, - }); - - return svg; - -})); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).SVG=t()}(this,(function(){"use strict";const e="class",t="function",r="undefined",n="number",a="string",o="object",i="svg",s="path",l="id",c="style",f="viewBox",d="transform",u="points",p="stroke",h="none",m="arrow",g="head",b="tail",y=typeof window!==r&&typeof window.document!==r;typeof process!==r&&null!=process.versions&&process.versions.node;const v="window not set; svg.window = @xmldom/xmldom",w={window:void 0};y&&(w.window=window);const x=()=>{if(void 0===w.window)throw v;return w.window},k="http://www.w3.org/2000/svg",A={presentation:["color","color-interpolation","cursor","direction","display","fill","fill-opacity","fill-rule","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","image-rendering","letter-spacing","opacity","overflow","paint-order","pointer-events","preserveAspectRatio","shape-rendering","stroke","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","tabindex","transform-origin","user-select","vector-effect","visibility"],animation:["accumulate","additive","attributeName","begin","by","calcMode","dur","end","from","keyPoints","keySplines","keyTimes","max","min","repeatCount","repeatDur","restart","to","values"],effects:["azimuth","baseFrequency","bias","color-interpolation-filters","diffuseConstant","divisor","edgeMode","elevation","exponent","filter","filterRes","filterUnits","flood-color","flood-opacity","in","in2","intercept","k1","k2","k3","k4","kernelMatrix","lighting-color","limitingConeAngle","mode","numOctaves","operator","order","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","primitiveUnits","radius","result","seed","specularConstant","specularExponent","stdDeviation","stitchTiles","surfaceScale","targetX","targetY","type","xChannelSelector","yChannelSelector"],text:["dx","dy","alignment-baseline","baseline-shift","dominant-baseline","lengthAdjust","method","overline-position","overline-thickness","rotate","spacing","startOffset","strikethrough-position","strikethrough-thickness","text-anchor","text-decoration","text-rendering","textLength","underline-position","underline-thickness","word-spacing","writing-mode"],gradient:["gradientTransform","gradientUnits","spreadMethod"]},C={svg:["svg"],defs:["defs"],header:["desc","filter","metadata","style","script","title","view"],cdata:["cdata"],group:["g"],visible:["circle","ellipse","line","path","polygon","polyline","rect","arc","arrow","curve","parabola","roundRect","wedge","origami"],text:["text"],invisible:["marker","symbol","clipPath","mask"],patterns:["linearGradient","radialGradient","pattern"],childrenOfText:["textPath","tspan"],gradients:["stop"],filter:["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]},E={svg:[f],line:["x1","y1","x2","y2"],rect:["x","y","width","height"],circle:["cx","cy","r"],ellipse:["cx","cy","rx","ry"],polygon:[u],polyline:[u],path:["d"],text:["x","y"],mask:[l],symbol:[l],clipPath:[l,"clip-rule"],marker:[l,"markerHeight","markerUnits","markerWidth","orient","refX","refY"],linearGradient:["x1","x2","y1","y2"],radialGradient:["cx","cy","r","fr","fx","fy"],stop:["offset","stop-color","stop-opacity"],pattern:["patternContentUnits","patternTransform","patternUnits"]};[{nodes:[i,"defs","g"].concat(C.visible,C.text),attr:A.presentation},{nodes:["filter"],attr:A.effects},{nodes:C.childrenOfText.concat("text"),attr:A.text},{nodes:C.filter,attr:A.effects},{nodes:C.gradients,attr:A.gradient}].forEach((e=>e.nodes.forEach((t=>{E[t]||(E[t]=[]),E[t].push(...e.attr)}))));const j=[C.header,C.invisible,C.patterns].flat(),M=[C.group,C.visible,C.text].flat(),O={svg:[["svg","defs"],j,M].flat(),defs:j,filter:C.filter,g:M,text:C.childrenOfText,marker:M,symbol:M,clipPath:M,mask:M,linearGradient:C.gradients,radialGradient:C.gradients},P=Object.values(C).flat(),$={black:"#000000",silver:"#c0c0c0",gray:"#808080",white:"#ffffff",maroon:"#800000",red:"#ff0000",purple:"#800080",fuchsia:"#ff00ff",green:"#008000",lime:"#00ff00",olive:"#808000",yellow:"#ffff00",navy:"#000080",blue:"#0000ff",teal:"#008080",aqua:"#00ffff",orange:"#ffa500",aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",blanchedalmond:"#ffebcd",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",oldlace:"#fdf5e6",olivedrab:"#6b8e23",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",whitesmoke:"#f5f5f5",yellowgreen:"#9acd32"},_=(e,t,r)=>{const n=r/100,a=t=>(t+e/30)%12,o=t/100*Math.min(n,1-n),i=e=>n-o*Math.max(-1,Math.min(a(e)-3,Math.min(9-a(e),1)));return[255*i(0),255*i(8),255*i(4)]},N=(e,t)=>{const r=Array.from(Array(t.length)).map(((t,r)=>e[r]||"0"));return e.length<=4?t.map((e=>r[e])).join(""):r.join("")},S=e=>{const t=e.replace(/#(?=\S)/g,""),r=4===t.length||8===t.length,n=N(t,r?[0,0,1,1,2,2,3,3]:[0,0,1,1,2,2]),a=parseInt(n,16);return r?[a>>24&255,a>>16&255,a>>8&255,255&a]:[a>>16&255,a>>8&255,255&a]},T=(e,t,r,n)=>{const a=e=>`00${Math.max(0,Math.min(Math.round(e),255)).toString(16)}`.slice(-2),o=`#${[e,t,r].map(a).join("")}`;return void 0===n?o:`${o}${a(255*n)}`},z=Object.freeze({__proto__:null,hexToRgb:S,hslToRgb:_,rgbToHex:T}),q=e=>{const t=e.match(/\(([^\)]+)\)/g);return null!=t&&t.length?t[0].substring(1,t[0].length-1).split(/[\s,]+/).map(parseFloat):[]},L=Object.freeze({__proto__:null,parseColorToHex:e=>{if($[e])return $[e].toUpperCase();if("#"===e[0])return T(...S(e));if("rgba"===e.substring(0,4)||"rgb"===e.substring(0,3))return T(...q(e));if("hsla"===e.substring(0,4)||"hsl"===e.substring(0,3)){const t=q(e);[0,1,2].filter((e=>void 0===t[e])).forEach((e=>{t[e]=0}));const r=_(...t);4===t.length&&r.push(t[3]),[0,1,2].forEach((e=>{r[e]*=255})),T(...r)}},parseColorToRgb:e=>{if($[e])return S($[e]);if("#"===e[0])return S(e);if("rgba"===e.substring(0,4)||"rgb"===e.substring(0,3)){const t=q(e);return[0,1,2].filter((e=>void 0===t[e])).forEach((e=>{t[e]=0})),t}if("hsla"===e.substring(0,4)||"hsl"===e.substring(0,3)){const t=q(e);[0,1,2].filter((e=>void 0===t[e])).forEach((e=>{t[e]=0}));const r=_(...t);return 4===t.length&&r.push(t[3]),r}}}),B={cssColors:$,...z,...L},F=(e,t)=>[e[0]+t[0],e[1]+t[1]],U=(e,t)=>[e[0]-t[0],e[1]-t[1]],D=(e,t)=>[e[0]*t,e[1]*t],R=e=>e[0]**2+e[1]**2,V=e=>Math.sqrt(R(e)),G=(e,t)=>R(U(e,t)),Z=(e,t)=>Math.sqrt(G(e,t)),H=(e,t)=>[Math.cos(e)*t,Math.sin(e)*t],I=(e,t)=>[e[0]*t[0]+e[2]*t[1],e[1]*t[0]+e[3]*t[1],e[0]*t[2]+e[2]*t[3],e[1]*t[2]+e[3]*t[3],e[0]*t[4]+e[2]*t[5]+e[4],e[1]*t[4]+e[3]*t[5]+e[5]],Y=Object.freeze({__proto__:null,svg_add2:F,svg_distance2:Z,svg_distanceSq2:G,svg_magnitude2:V,svg_magnitudeSq2:R,svg_multiplyMatrices2:I,svg_polar_to_cart:H,svg_scale2:D,svg_sub2:U}),X=function(e,t){switch(e){case"translate":return function(e){switch(e.length){case 1:return[1,0,0,1,e[0],0];case 2:return[1,0,0,1,e[0],e[1]];default:console.warn(`improper translate, ${e}`)}}(t);case"rotate":return function(e){const t=Math.cos(e[0]/(180*Math.PI)),r=Math.sin(e[0]/(180*Math.PI));switch(e.length){case 1:return[t,r,-r,t,0,0];case 3:return[t,r,-r,t,-e[1]*t+e[2]*r+e[1],-e[1]*r-e[2]*t+e[2]];default:console.warn(`improper rotate, ${e}`)}}(t);case"scale":return function(e){switch(e.length){case 1:return[e[0],0,0,e[0],0,0];case 2:return[e[0],0,0,e[1],0,0];default:console.warn(`improper scale, ${e}`)}}(t);case"skewX":return function(e){return[1,0,Math.tan(e[0]/(180*Math.PI)),1,0,0]}(t);case"skewY":return function(e){return[1,Math.tan(e[0]/(180*Math.PI)),0,1,0,0]}(t);case"matrix":return t;default:console.warn(`unknown transform type ${e}`)}},Q=function(e){return function(e){const t=e.match(/(\w+\((\-?\d+\.?\d*e?\-?\d*,?\s*)+\))+/g);return t?t.map((e=>e.match(/[\w\.\-]+/g))).map((e=>({transform:e.shift(),parameters:e.map((e=>parseFloat(e)))}))):[]}(e).map((e=>X(e.transform,e.parameters))).filter((e=>void 0!==e)).reduce(((e,t)=>I(e,t)),[1,0,0,1,0,0])},W=Object.freeze({__proto__:null,transformStringToMatrix:Q}),J=(e,t)=>(e.nodeName||"")===t?e:e.parentNode?J(e.parentNode,t):void 0,K=e=>null!=e.childNodes&&e.childNodes.length?Array.from(e.childNodes).flatMap((e=>K(e))):[e],ee={svg:["viewBox","xmlns","version"],line:["x1","y1","x2","y2"],rect:["x","y","width","height"],circle:["cx","cy","r"],ellipse:["cx","cy","rx","ry"],polygon:["points"],polyline:["points"],path:["d"]},te=(e,t)=>{const r=(e=>{const t={};return e.forEach((e=>{t[e.nodeName]=e.value})),t})((e=>{const t=e.attributes;if(null==t)return[];const r=Array.from(t);return ee[e.nodeName]?r.filter((t=>!ee[e.nodeName].includes(t.name))):r})(t));if(!r.transform&&!e.transform)return{...e,...r};const n=r.transform||"",a=e.transform||"",o=Q(n),i=Q(a),s=`matrix(${I(i,o).join(", ")})`;return{...e,...r,transform:s}},re=(e,t={})=>null!=e.childNodes&&e.childNodes.length?Array.from(e.childNodes).flatMap((e=>re(e,te(t,e)))):[{element:e,attributes:t}],ne=Object.freeze({__proto__:null,addClass:(e,...t)=>{if(e&&t.length)return e.classList?e.classList.add(...t):((e,...t)=>{const r={},n=e.getAttribute("class"),a=n?n.split(" "):[];a.push(...t),a.forEach((e=>{r[e]=!0}));const o=Object.keys(r).join(" ");e.setAttribute("class",o)})(e,...t)},findElementTypeInParents:J,flattenDomTree:K,flattenDomTreeWithStyle:re,getRootParent:e=>{let t=e;for(;null!=t.parentNode;)t=t.parentNode;return t},xmlStringToElement:(e,t="text/xml")=>{const r=(new(x().DOMParser)).parseFromString(e,t);return r?r.documentElement:null}}),ae=e=>(new(x().DOMParser)).parseFromString("","text/xml").createCDATASection(e),oe=/[MmLlSsQqLlHhVvCcSsQqTtAaZz]/g,ie=/-?[0-9]*\.?\d+/g,se={m:"move",l:"line",v:"vertical",h:"horizontal",a:"ellipse",c:"curve",s:"smoothCurve",q:"quadCurve",t:"smoothQuadCurve",z:"close"};Object.keys(se).forEach((e=>{const t=se[e];se[e.toUpperCase()]=t.charAt(0).toUpperCase()+t.slice(1)}));const le=(e,t)=>[e[0]+(t[0]||0),e[1]+(t[1]||0)],ce=e=>{const t=[];let r;for(;null!==(r=oe.exec(e));)t.push(r);return t.map(((t,r,n)=>[t[0],t.index,r===n.length-1?e.length-1:n[(r+1)%n.length].index-1])).map((t=>{const r=t[0],n=e.substring(t[1]+1,t[2]+1).match(ie);return{command:r,values:n?n.map(parseFloat):[]}}))},fe=(...e)=>e.filter((e=>typeof e===n)).concat(e.filter((e=>typeof e===o&&null!==e)).map((e=>typeof e.x===n?[e.x,e.y]:typeof e[0]===n?[e[0],e[1]]:void 0)).filter((e=>void 0!==e)).reduce(((e,t)=>e.concat(t)),[])),de=(...e)=>{const t=fe(...e.flat());return 2===t.length&&t.unshift(0,0),4===t.length?function(e,t,r,n,a=0){const o=r/1-r;return[e-o-a,t-o-a,r+2*o+2*a,n+2*o+2*a].join(" ")}(...t):void 0},ue=(e,...t)=>{const r=1===t.length&&typeof t[0]===a?t[0]:de(...t);return r&&e.setAttribute(f,r),e},pe=function(e){const t=e.getAttribute(f);return null==t?void 0:t.split(" ").map((e=>parseFloat(e)))},he=function(e,t,r){const n=e.createSVGPoint();n.x=t,n.y=r;const a=n.matrixTransform(e.getScreenCTM().inverse());return[a.x,a.y]},me={...Y,...ne,makeCDATASection:ae,...Object.freeze({__proto__:null,parsePathCommands:ce,parsePathCommandsWithEndpoints:e=>{let t=[0,0];const r=ce(e);if(!r.length)return r;r.forEach(((e,n)=>{r[n].end=((e,t,r=[0,0])=>{const n=e.toUpperCase();let a=e===n?[0,0]:r;switch("V"===e&&(a=[r[0],0]),"H"===e&&(a=[0,r[1]]),n){case"V":return le(a,[0,t[0]]);case"H":return le(a,[t[0],0]);case"M":case"L":case"T":return le(a,t);case"A":return le(a,[t[5],t[6]]);case"C":return le(a,[t[4],t[5]]);case"S":case"Q":return le(a,[t[2],t[3]]);case"Z":return;default:return a}})(e.command,e.values,t),r[n].start=0===n?t:r[n-1].end,t=r[n].end}));const n=r[r.length-1],a=r.filter((e=>"M"!==e.command.toUpperCase()&&"Z"!==e.command.toUpperCase())).shift();return"Z"===n.command.toUpperCase()&&(n.end=[...a.start]),r},pathCommandNames:se}),...W,...Object.freeze({__proto__:null,convertToViewBox:he,getViewBox:pe,setViewBox:ue})},ge=function(e){const r=pe(e);if(void 0!==r)return r;if(typeof e.getBoundingClientRect===t){const t=e.getBoundingClientRect();return[t.x,t.y,t.width,t.height]}return[]},be="svg-background-rectangle",ye=e=>{const t=e.getAttribute(d);return null==t||""===t?void 0:t},ve={clearTransform:e=>(e.removeAttribute(d),e)};["translate","rotate","scale","matrix"].forEach((e=>{ve[e]=(t,...r)=>(t.setAttribute(d,[ye(t),`${e}(${r.join(" ")})`].filter((e=>void 0!==e)).join(" ")),t)}));const we=e=>e.replace(/([-_][a-z])/gi,(e=>e.toUpperCase().replace("-","").replace("_",""))),xe=e=>{for(;e.lastChild;)e.removeChild(e.lastChild);return e},ke=(e,t)=>(t&&t.appendChild&&t.appendChild(e),e),Ae=(e,t)=>(Object.keys(t).forEach((r=>e.setAttribute(r.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/([A-Z])([A-Z])(?=[a-z])/g,"$1-$2").toLowerCase(),t[r]))),e),Ce=Object.freeze({__proto__:null,appendTo:ke,removeChildren:xe,setAttributes:Ae}),Ee=function(e,t){let r=function(e,t){const r=e.getElementsByTagName(t);return r.length?r[0]:null}(e,c);return null==r&&(r=x().document.createElementNS(k,c),r.setTextContent=e=>(r.textContent="",r.appendChild(ae(e)),r),e.insertBefore(r,e.firstChild)),r.textContent="",r.appendChild(ae(t)),r},je={clear:e=>(Array.from(e.attributes).filter((e=>"xmlns"!==e.name&&"version"!==e.name)).forEach((t=>e.removeAttribute(t.name))),xe(e)),size:ue,setViewBox:ue,getViewBox:pe,padding:function(e,t){const r=pe(e);return void 0!==r&&ue(e,...[-t,-t,2*t,2*t].map(((e,t)=>r[t]+e))),e},background:function(t,r){let n=Array.from(t.childNodes).filter((t=>t.getAttribute(e)===be)).shift();return null==n&&(n=x().document.createElementNS(k,"rect"),ge(t).forEach(((e,t)=>n.setAttribute(E.rect[t],e))),n.setAttribute(e,be),n.setAttribute(p,h),t.insertBefore(n,t.firstChild)),n.setAttribute("fill",r),t},getWidth:e=>ge(e)[2],getHeight:e=>ge(e)[3],stylesheet:function(e,t){return Ee.call(this,e,t)},...ve,...Ce},Me={move:["mousemove","touchmove"],press:["mousedown","touchstart"],release:["mouseup","touchend"],leave:["mouseleave","touchcancel"]},Oe=(e,t,r)=>Object.defineProperty(e,t,{get:()=>r,enumerable:!0,configurable:!0}),Pe=function(e){const t=[];Object.keys(Me).forEach((e=>{Me[e].forEach((e=>{t[e]=[]}))}));Object.keys(Me).forEach((r=>{var n;Object.defineProperty(e,`on${n=r,n.charAt(0).toUpperCase()+n.slice(1)}`,{set:n=>{e.addEventListener&&(null!=n?Me[r].forEach((r=>{const a=t=>{const r=null!=t.touches?t.touches[0]:t;if(void 0!==r){const{clientX:n,clientY:a}=r,[o,i]=he(e,n,a);Oe(t,"x",o),Oe(t,"y",i)}n(t)};t[r].push(a),e.addEventListener(r,a)})):(r=>{Me[r].forEach((r=>t[r].forEach((t=>e.removeEventListener(r,t)))))})(r))},enumerable:!0})})),Object.defineProperty(e,"off",{value:()=>((e,t)=>Object.values(Me).flat().forEach((r=>{t[r].forEach((t=>e.removeEventListener(r,t))),t[r]=[]})))(e,t)})},$e=()=>Math.random().toString(36).replace(/[^a-z]+/g,"").concat("aaaaa").substr(0,5),_e=[["cx","cy"],["x","y"]],Ne=function(e,r={}){const n=[0,0],a={selected:!1,svg:void 0,updatePosition:e=>e},o=()=>{a.svg&&(a.svg.parentNode||e.appendChild(a.svg),_e.filter((e=>null!=a.svg[e[0]])).forEach((e=>e.forEach(((e,t)=>{a.svg.setAttribute(e,n[t])})))))},s=new Proxy(n,{set:(e,t,r)=>(e[t]=r,o(),!0)}),l=function(...e){fe(...e.flat()).forEach(((e,t)=>{n[t]=e})),o(),typeof n.delegate===t&&n.delegate.apply(n.pointsContainer,[s,n.pointsContainer])};return n.delegate=void 0,n.setPosition=l,n.onMouseMove=e=>a.selected?l(a.updatePosition(e)):void 0,n.onMouseUp=()=>{a.selected=!1},n.distance=e=>Math.sqrt(G(e,n)),["x","y"].forEach(((e,t)=>Object.defineProperty(n,e,{get:()=>n[t],set:e=>{n[t]=e}}))),[i,"updatePosition","selected"].forEach((e=>Object.defineProperty(n,e,{get:()=>a[e],set:t=>{a[e]=t}}))),Object.defineProperty(n,"remove",{value:()=>{var e;(e=a.svg)&&e.parentNode&&e.parentNode.removeChild(e),n.delegate=void 0}}),s},Se=function(e,r,n){let a,o;const i=Array.from(Array(r)).map((()=>Ne(e,n))),s=e=>typeof o===t?o.call(i,e,a,i):void 0;i.forEach((e=>{e.delegate=s,e.pointsContainer=i}));e.onPress=function(e){i.length>0&&(a=i.map(((t,r)=>({i:r,d:G(t,[e.x,e.y])}))).sort(((e,t)=>e.d-t.d)).shift().i,i[a].selected=!0)},e.onMove=function(e){i.forEach((t=>t.onMouseMove(e)))},e.onRelease=function(){i.forEach((e=>e.onMouseUp())),a=void 0},Object.defineProperty(i,"selectedIndex",{get:()=>a}),Object.defineProperty(i,"selected",{get:()=>i[a]}),Object.defineProperty(i,"add",{value:t=>{i.push(Ne(e,t))}}),i.removeAll=()=>{for(;i.length>0;)i.pop().remove()};const l={onChange:(e,t)=>{if(o=e,!0===t){const t=i.length-1;e.call(i,i[t],t,i)}},position:e=>i.forEach(((t,r)=>t.setPosition(e.call(i,t,r,i)))),svg:e=>i.forEach(((t,r)=>{t.svg=e.call(i,t,r,i)}))};return Object.keys(l).forEach((e=>{i[e]=function(){return typeof arguments[0]===t&&l[e](...arguments),i}})),i.parent=function(e){return null!=e&&null!=e.appendChild&&i.forEach((t=>{e.appendChild(t.svg)})),i},i},Te=e=>{e.controls=(...t)=>Se.call(e,e,...t)},ze={svg:{args:(...e)=>[de(fe(...e))].filter((e=>null!=e)),methods:je,init:(...e)=>{const t=x().document.createElementNS(k,"svg");return t.setAttribute("version","1.1"),t.setAttribute("xmlns",k),e.filter((e=>null!=e)).filter((e=>e.appendChild)).forEach((e=>e.appendChild(t))),Pe(t),function(e){let t,r,n=0;const a={},o=()=>{x().cancelAnimationFrame&&x().cancelAnimationFrame(r),Object.keys(a).forEach((e=>delete a[e]))};Object.defineProperty(e,"play",{set:e=>{if(o(),!e||!x().requestAnimationFrame)return;t=performance.now(),n=0;const i=$e();a[i]=o=>{e({time:.001*(o-t),frame:n}),n+=1,a[i]&&(r=x().requestAnimationFrame(a[i]))},r=x().requestAnimationFrame(a[i])},enumerable:!0}),Object.defineProperty(e,"stop",{value:o,enumerable:!0})}(t),Te(t),t}}},qe={};["clip-path","mask","symbol","marker-end","marker-mid","marker-start"].forEach((e=>{qe[we(e)]=(t,r)=>(t.setAttribute(e,function(e){if(null==e)return"";if(typeof e===a)return"url"===e.slice(0,3)?e:`url(#${e})`;if(null!=e.getAttribute)return`url(#${e.getAttribute(l)})`;return""}(r)),t)}));const Le={g:{methods:{...ve,...qe,...Ce}}},Be=(e,t)=>(e.setAttribute(E.circle[2],t),e),Fe=(e,t,r)=>([...fe(...[t,r].flat()).slice(0,2)].forEach(((t,r)=>e.setAttribute(E.circle[r],t))),e),Ue={circle:{args:(e,t,r,n)=>{const a=fe(...[e,t,r,n].flat());switch(a.length){case 0:case 1:return[,,...a];case 2:case 3:return a;default:return((e,t,r,n)=>[e,t,Z([e,t],[r,n])])(...a)}},methods:{radius:Be,setRadius:Be,origin:Fe,setOrigin:Fe,center:Fe,setCenter:Fe,position:Fe,setPosition:Fe,...ve,...qe,...Ce}}},De=(e,t,r)=>([,,t,r].forEach(((t,r)=>e.setAttribute(E.ellipse[r],t))),e),Re=(e,t,r)=>([...fe(...[t,r].flat()).slice(0,2)].forEach(((t,r)=>e.setAttribute(E.ellipse[r],t))),e),Ve={ellipse:{args:(e,t,r,n)=>{const a=fe(...[e,t,r,n].flat()).slice(0,4);switch(a.length){case 0:case 1:case 2:return[,,...a];default:return a}},methods:{radius:De,setRadius:De,origin:Re,setOrigin:Re,center:Re,setCenter:Re,position:Re,setPosition:Re,...ve,...qe,...Ce}}},Ge=e=>null!=e&&typeof e[Symbol.iterator]===t,Ze=function(){switch(arguments.length){case 0:return Array.from(arguments);case 1:return Ge(arguments[0])&&typeof arguments[0]!==a?Ze(...arguments[0]):[arguments[0]];default:return Array.from(arguments).map((e=>Ge(e)?[...Ze(e)]:e))}},He=(...e)=>fe(...Ze(...e)).slice(0,4),Ie={line:{args:He,methods:{setPoints:(e,...t)=>(He(...t).forEach(((t,r)=>e.setAttribute(E.line[r],t))),e),...ve,...qe,...Ce}}},Ye=e=>{const t=e.getAttribute("d");return null==t?"":t},Xe=(e,t,...r)=>(e.setAttribute("d",`${Ye(e)}${t}${r.flat().join(" ")}`),e),Qe=e=>ce(Ye(e)),We={addCommand:Xe,appendCommand:Xe,clear:e=>(e.removeAttribute("d"),e),getCommands:Qe,get:Qe,getD:e=>e.getAttribute("d"),...ve,...qe,...Ce};Object.keys(se).forEach((e=>{We[se[e]]=(t,...r)=>Xe(t,e,...r)}));const Je={path:{methods:We}},Ke=(e,t,r)=>([,,t,r].forEach(((t,r)=>e.setAttribute(E.rect[r],t))),e),et=(e,t,r)=>([...fe(...[t,r].flat()).slice(0,2)].forEach(((t,r)=>e.setAttribute(E.rect[r],t))),e),tt=function(e){return[0,1].forEach((t=>{e[2+t]<0&&(void 0===e[0+t]&&(e[0+t]=0),e[0+t]+=e[2+t],e[2+t]=-e[2+t])})),e},rt={rect:{args:(e,t,r,n)=>{const a=fe(...[e,t,r,n].flat()).slice(0,4);switch(a.length){case 0:case 1:case 2:case 3:return tt([,,...a]);default:return tt(a)}},methods:{origin:et,setOrigin:et,center:et,setCenter:et,size:Ke,setSize:Ke,...ve,...qe,...Ce}}},nt={style:{init:e=>{const t=x().document.createElementNS(k,"style");return t.setAttribute("type","text/css"),t.textContent="",t.appendChild(ae(e)),t},methods:{setTextContent:(e,t)=>(e.textContent="",e.appendChild(ae(t)),e)}}},at={text:{args:(e,t,r)=>fe(...[e,t,r].flat()).slice(0,2),init:(e,t,r,n)=>{const o=x().document.createElementNS(k,"text"),i=[e,t,r,n].filter((e=>typeof e===a)).shift();return o.appendChild(x().document.createTextNode(i||"")),o},methods:{...ve,...qe,appendTo:ke,setAttributes:Ae}}},ot=function(){return Array.from(arguments).filter((e=>typeof e===a||e instanceof String)).shift()||$e()},it=(...e)=>[ot(...e)],st={mask:{args:it,methods:{...ve,...qe,...Ce}},clipPath:{args:it,methods:{...ve,...qe,...Ce}},symbol:{args:it,methods:{...ve,...qe,...Ce}},marker:{args:it,methods:{size:ue,setViewBox:ue,...ve,...qe,...Ce}}},lt=e=>{const t=e.getAttribute(u);return null==t?"":t},ct=function(){return Array.from(Array(Math.floor(arguments.length/2))).map(((e,t)=>`${arguments[2*t+0]},${arguments[2*t+1]}`)).join(" ")},ft=(...e)=>[ct(...fe(...Ze(...e)))],dt=(e,...t)=>(e.setAttribute(u,ft(...t)[0]),e),ut=(e,...t)=>(e.setAttribute(u,[lt(e),ft(...t)[0]].filter((e=>""!==e)).join(" ")),e),pt=function(...e){return 1===e.length&&typeof e[0]===a?[e[0]]:ft(...e)},ht={polyline:{args:pt,methods:{setPoints:dt,addPoint:ut,...ve,...qe,...Ce}},polygon:{args:pt,methods:{setPoints:dt,addPoint:ut,...ve,...qe,...Ce}}},mt=(e,t,r,n,a,o=!1)=>{if(null==a)return"";const i=H(n,r),s=H(a,r),l=[s[0]-i[0],s[1]-i[1]],c=i[0]*s[1]-i[1]*s[0],f=i[0]*s[0]+i[1]*s[1],d=Math.atan2(c,f)>0?0:1;let u=o?`M ${e},${t} l ${i[0]},${i[1]} `:`M ${e+i[0]},${t+i[1]} `;return u+=["a ",r,r,0,d,1,l[0],l[1]].join(" "),o&&(u+=" Z"),u},gt=(e,t,r,n,a)=>[mt(e,t,r,n,a,!1)],bt={arc:{nodeName:s,attributes:["d"],args:gt,methods:{setArc:(e,...t)=>e.setAttribute("d",gt(...t)),...ve}}},yt=[b,g],vt=e=>e.join(","),wt=e=>"M"+e.map((e=>e.join(","))).join("L")+"Z",xt=(e,t,r)=>{"boolean"==typeof t?e.options[r].visible=t:typeof t===o?(Object.assign(e.options[r],t),null==t.visible&&(e.options[r].visible=!0)):null==t&&(e.options[r].visible=!0)},kt=(e,r={},n=g)=>{const a=e.getElementsByClassName(`${m}-${n}`)[0];Object.keys(r).map((e=>({key:e,fn:a[we(e)]}))).filter((e=>typeof e.fn===t&&"class"!==e.key)).forEach((e=>e.fn(r[e.key]))),Object.keys(r).filter((e=>"class"===e)).forEach((e=>a.classList.add(r[e])))},At=e=>{const t=function(e){let t=[[0,1],[2,3]].map((t=>t.map((t=>e.points[t]||0)))),r=U(t[1],t[0]),n=F(t[0],D(r,.5));const a=V(r),o=yt.map((t=>e[t].visible?(1+e[t].padding)*e[t].height*2.5:0)).reduce(((e,t)=>e+t),0);if(at(n,D(e,.5)))),r=U(t[1],t[0])}let i=[r[1],-r[0]],s=F(n,D(i,e.bend));const l=t.map((e=>U(s,e))),c=l.map((e=>V(e))),f=l.map(((e,t)=>0===c[t]?e:D(e,1/c[t]))),d=f.map((e=>D(e,-1))),u=d.map((e=>[e[1],-e[0]])),p=yt.map(((t,r)=>e[t].padding?e[t].padding:e.padding?e.padding:0)),h=yt.map(((t,r)=>e[t].height*(e[t].visible?1:0))).map(((e,t)=>e+p[t])),m=t.map(((e,t)=>F(e,D(f[t],h[t]))));r=U(m[1],m[0]),i=[r[1],-r[0]],n=F(m[0],D(r,.5)),s=F(n,D(i,e.bend));const g=m.map(((t,r)=>F(t,D(U(s,t),e.pinch)))),b=yt.map(((t,r)=>[F(m[r],D(d[r],e[t].height)),F(m[r],D(u[r],e[t].width/2)),F(m[r],D(u[r],-e[t].width/2))]));return{line:`M${vt(m[0])}C${vt(g[0])},${vt(g[1])},${vt(m[1])}`,tail:wt(b[0]),head:wt(b[1])}}(e.options);return Object.keys(t).map((t=>({path:t,element:e.getElementsByClassName(`${m}-${t}`)[0]}))).filter((e=>e.element)).map((e=>(e.element.setAttribute("d",t[e.path]),e))).filter((t=>e.options[t.path])).forEach((t=>t.element.setAttribute("visibility",e.options[t.path].visible?"visible":"hidden"))),e},Ct=(e,...t)=>(e.options.points=fe(...Ze(...t)).slice(0,4),At(e)),Et={setPoints:Ct,points:Ct,bend:(e,t)=>(e.options.bend=t,At(e)),pinch:(e,t)=>(e.options.pinch=t,At(e)),padding:(e,t)=>(e.options.padding=t,At(e)),head:(e,t)=>(xt(e,t,g),kt(e,t,g),At(e)),tail:(e,t)=>(xt(e,t,b),kt(e,t,b),At(e)),getLine:e=>e.getElementsByClassName(`${m}-line`)[0],getHead:e=>e.getElementsByClassName(`${m}-${g}`)[0],getTail:e=>e.getElementsByClassName(`${m}-${b}`)[0],...ve},jt=Object.keys({head:{visible:!1,width:8,height:10,padding:0},tail:{visible:!1,width:8,height:10,padding:0},bend:0,padding:0,pinch:.618,points:[]}),Mt={arrow:{nodeName:"g",attributes:[],args:()=>[],methods:Et,init:function(e,...t){e.classList.add(m);const r=["line",b,g].map((t=>{const r=x().document.createElementNS(k,s);return r.className=`${m}-${t}`,e.appendChild(r),r}));r[0].setAttribute(c,"fill:none;"),r[1].setAttribute(p,h),r[2].setAttribute(p,h),e.options={head:{visible:!1,width:8,height:10,padding:0},tail:{visible:!1,width:8,height:10,padding:0},bend:0,padding:0,pinch:.618,points:[]},Et.setPoints(e,...t);const n=((...e)=>{for(let t=0;tEt[e])).forEach((t=>Et[t](e,n[t]))),e}}},Ot=(e=[],t=0,r=.5)=>{const n=[e[0]||0,e[1]||0],a=[e[2]||0,e[3]||0],o=U(a,n),i=F(n,D(o,.5)),s=[o[1],-o[0]],l=F(i,D(s,t)),c=F(n,D(U(l,n),r)),f=F(a,D(U(l,a),r));return`M${n[0]},${n[1]}C${c[0]},${c[1]} ${f[0]},${f[1]} ${a[0]},${a[1]}`},Pt=e=>e.slice(1).split(/[, ]+/).map((e=>parseFloat(e))),$t=e=>{const t=(e=>e.match(/[Mm][(0-9), .-]+/).map((e=>Pt(e))))(e).shift(),r=(e=>e.match(/[Cc][(0-9), .-]+/).map((e=>Pt(e))))(e).shift();return[...t?[t[t.length-2],t[t.length-1]]:[0,0],...r?[r[r.length-2],r[r.length-1]]:[0,0]]},_t=(e,...t)=>{const r=fe(...t.flat()).slice(0,4);return e.setAttribute("d",Ot(r,e._bend,e._pinch)),e},Nt={setPoints:_t,bend:(e,t)=>(e._bend=t,_t(e,...$t(e.getAttribute("d")))),pinch:(e,t)=>(e._pinch=t,_t(e,...$t(e.getAttribute("d")))),...ve},St={curve:{nodeName:s,attributes:["d"],args:(...e)=>[Ot(fe(...e.flat()))],methods:Nt}},Tt=(e,t,r,n,a)=>[mt(e,t,r,n,a,!0)],zt={wedge:{nodeName:s,args:Tt,attributes:["d"],methods:{setArc:(e,...t)=>e.setAttribute("d",Tt(...t)),...ve}}},qt={},Lt={...ze,...Le,...Ue,...Ve,...Ie,...Je,...rt,...nt,...at,...st,...ht,...bt,...Mt,...St,...zt,...{origami:{nodeName:"g",init:(e,...t)=>{const r=x().document.createElementNS(k,"g");return qt.ear.convert.foldToSvg.render(e,r,...t),r},args:()=>[],methods:{...ve,...qe,...Ce}}}},Bt=(...e)=>e,Ft=(e,t,...r)=>{const n=Lt[e]&&Lt[e].nodeName?Lt[e].nodeName:e,{init:a,args:o,methods:i}=Lt[e]||{},s=E[n]||[],l=O[n]||[],c=a?a(...r):x().document.createElementNS(k,n);t&&t.appendChild(c);return(o||Bt)(...r).forEach(((e,t)=>{c.setAttribute(E[n][t],e)})),i&&Object.keys(i).forEach((e=>Object.defineProperty(c,e,{value:function(){return i[e](c,...arguments)}}))),s.forEach((e=>{const t=we(e);c[t]||Object.defineProperty(c,t,{value:function(){return c.setAttribute(e,...arguments),c}})})),l.forEach((e=>{if(c[e])return;Object.defineProperty(c,e,{value:function(){return Ft(e,c,...arguments)}})})),c},Ut=(...e)=>{const r=Ft(i,null,...e),n=()=>e.filter((e=>typeof e===t)).forEach((e=>e.call(r,r)));return"loading"===x().document.readyState?x().document.addEventListener("DOMContentLoaded",n):n(),r};return Object.assign(Ut,{NS:k,nodes_attributes:E,nodes_children:O,extensions:Lt,...B,...me}),P.forEach((e=>{Ut[e]=(...t)=>Ft(e,null,...t)})),Object.defineProperty(Ut,"window",{enumerable:!1,set:e=>(e.document||(e.document=(e=>(new e.DOMParser).parseFromString(".","text/html"))(e)),w.window=e,w.window)}),Ut})); \ No newline at end of file diff --git a/svg.module.js b/svg.module.js new file mode 100644 index 0000000..4435282 --- /dev/null +++ b/svg.module.js @@ -0,0 +1,2100 @@ +/* svg (c) Kraft, MIT License */ +const str_class = "class"; +const str_function = "function"; +const str_undefined = "undefined"; +const str_boolean = "boolean"; +const str_number = "number"; +const str_string = "string"; +const str_object = "object"; +const str_svg = "svg"; +const str_path = "path"; +const str_id = "id"; +const str_style = "style"; +const str_viewBox = "viewBox"; +const str_transform = "transform"; +const str_points = "points"; +const str_stroke = "stroke"; +const str_fill = "fill"; +const str_none = "none"; +const str_arrow = "arrow"; +const str_head = "head"; +const str_tail = "tail"; + +const isBrowser = typeof window !== str_undefined + && typeof window.document !== str_undefined; +typeof process !== str_undefined + && process.versions != null + && process.versions.node != null; + +const Messages = { + window: "window not set; svg.window = @xmldom/xmldom", +}; + +const svgWindowContainer = { window: undefined }; +const buildHTMLDocument = (newWindow) => new newWindow.DOMParser() + .parseFromString(".", "text/html"); +const setSVGWindow = (newWindow) => { + if (!newWindow.document) { newWindow.document = buildHTMLDocument(newWindow); } + svgWindowContainer.window = newWindow; + return svgWindowContainer.window; +}; +if (isBrowser) { svgWindowContainer.window = window; } +const SVGWindow = () => { + if (svgWindowContainer.window === undefined) { + throw Messages.window; + } + return svgWindowContainer.window; +}; + +const NS = "http://www.w3.org/2000/svg"; + +const classes_attributes = { + presentation: [ + "color", + "color-interpolation", + "cursor", + "direction", + "display", + "fill", + "fill-opacity", + "fill-rule", + "font-family", + "font-size", + "font-size-adjust", + "font-stretch", + "font-style", + "font-variant", + "font-weight", + "image-rendering", + "letter-spacing", + "opacity", + "overflow", + "paint-order", + "pointer-events", + "preserveAspectRatio", + "shape-rendering", + "stroke", + "stroke-dasharray", + "stroke-dashoffset", + "stroke-linecap", + "stroke-linejoin", + "stroke-miterlimit", + "stroke-opacity", + "stroke-width", + "tabindex", + "transform-origin", + "user-select", + "vector-effect", + "visibility", + ], + animation: [ + "accumulate", + "additive", + "attributeName", + "begin", + "by", + "calcMode", + "dur", + "end", + "from", + "keyPoints", + "keySplines", + "keyTimes", + "max", + "min", + "repeatCount", + "repeatDur", + "restart", + "to", + "values", + ], + effects: [ + "azimuth", + "baseFrequency", + "bias", + "color-interpolation-filters", + "diffuseConstant", + "divisor", + "edgeMode", + "elevation", + "exponent", + "filter", + "filterRes", + "filterUnits", + "flood-color", + "flood-opacity", + "in", + "in2", + "intercept", + "k1", + "k2", + "k3", + "k4", + "kernelMatrix", + "lighting-color", + "limitingConeAngle", + "mode", + "numOctaves", + "operator", + "order", + "pointsAtX", + "pointsAtY", + "pointsAtZ", + "preserveAlpha", + "primitiveUnits", + "radius", + "result", + "seed", + "specularConstant", + "specularExponent", + "stdDeviation", + "stitchTiles", + "surfaceScale", + "targetX", + "targetY", + "type", + "xChannelSelector", + "yChannelSelector", + ], + text: [ + "dx", + "dy", + "alignment-baseline", + "baseline-shift", + "dominant-baseline", + "lengthAdjust", + "method", + "overline-position", + "overline-thickness", + "rotate", + "spacing", + "startOffset", + "strikethrough-position", + "strikethrough-thickness", + "text-anchor", + "text-decoration", + "text-rendering", + "textLength", + "underline-position", + "underline-thickness", + "word-spacing", + "writing-mode", + ], + gradient: [ + "gradientTransform", + "gradientUnits", + "spreadMethod", + ], +}; + +const classes_nodes = { + svg: [ + "svg", + ], + defs: [ + "defs", + ], + header: [ + "desc", + "filter", + "metadata", + "style", + "script", + "title", + "view", + ], + cdata: [ + "cdata", + ], + group: [ + "g", + ], + visible: [ + "circle", + "ellipse", + "line", + "path", + "polygon", + "polyline", + "rect", + "arc", + "arrow", + "curve", + "parabola", + "roundRect", + "wedge", + "origami", + ], + text: [ + "text", + ], + invisible: [ + "marker", + "symbol", + "clipPath", + "mask", + ], + patterns: [ + "linearGradient", + "radialGradient", + "pattern", + ], + childrenOfText: [ + "textPath", + "tspan", + ], + gradients: [ + "stop", + ], + filter: [ + "feBlend", + "feColorMatrix", + "feComponentTransfer", + "feComposite", + "feConvolveMatrix", + "feDiffuseLighting", + "feDisplacementMap", + "feDistantLight", + "feDropShadow", + "feFlood", + "feFuncA", + "feFuncB", + "feFuncG", + "feFuncR", + "feGaussianBlur", + "feImage", + "feMerge", + "feMergeNode", + "feMorphology", + "feOffset", + "fePointLight", + "feSpecularLighting", + "feSpotLight", + "feTile", + "feTurbulence", + ], +}; + +const nodes_attributes = { + svg: [str_viewBox], + line: ["x1", "y1", "x2", "y2"], + rect: ["x", "y", "width", "height"], + circle: ["cx", "cy", "r"], + ellipse: ["cx", "cy", "rx", "ry"], + polygon: [str_points], + polyline: [str_points], + path: ["d"], + text: ["x", "y"], + mask: [str_id], + symbol: [str_id], + clipPath: [str_id, "clip-rule"], + marker: [ + str_id, + "markerHeight", + "markerUnits", + "markerWidth", + "orient", + "refX", + "refY", + ], + linearGradient: ["x1", "x2", "y1", "y2"], + radialGradient: ["cx", "cy", "r", "fr", "fx", "fy"], + stop: ["offset", "stop-color", "stop-opacity"], + pattern: ["patternContentUnits", "patternTransform", "patternUnits"], +}; +const additionalNodeAttributes = [{ + nodes: [str_svg, "defs", "g"].concat(classes_nodes.visible, classes_nodes.text), + attr: classes_attributes.presentation, +}, { + nodes: ["filter"], + attr: classes_attributes.effects, +}, { + nodes: classes_nodes.childrenOfText.concat("text"), + attr: classes_attributes.text, +}, { + nodes: classes_nodes.filter, + attr: classes_attributes.effects, +}, { + nodes: classes_nodes.gradients, + attr: classes_attributes.gradient, +}]; +additionalNodeAttributes + .forEach(el => el.nodes + .forEach(nodeName => { + if (!nodes_attributes[nodeName]) { nodes_attributes[nodeName] = []; } + nodes_attributes[nodeName].push(...el.attr); + })); + +const headerStuff = [ + classes_nodes.header, + classes_nodes.invisible, + classes_nodes.patterns, +].flat(); +const drawingShapes = [ + classes_nodes.group, + classes_nodes.visible, + classes_nodes.text, +].flat(); +const nodes_children = { + svg: [["svg", "defs"], headerStuff, drawingShapes].flat(), + defs: headerStuff, + filter: classes_nodes.filter, + g: drawingShapes, + text: classes_nodes.childrenOfText, + marker: drawingShapes, + symbol: drawingShapes, + clipPath: drawingShapes, + mask: drawingShapes, + linearGradient: classes_nodes.gradients, + radialGradient: classes_nodes.gradients, +}; + +const nodeNames = Object.values(classes_nodes).flat(); + +const cssColors = { + black: "#000000", + silver: "#c0c0c0", + gray: "#808080", + white: "#ffffff", + maroon: "#800000", + red: "#ff0000", + purple: "#800080", + fuchsia: "#ff00ff", + green: "#008000", + lime: "#00ff00", + olive: "#808000", + yellow: "#ffff00", + navy: "#000080", + blue: "#0000ff", + teal: "#008080", + aqua: "#00ffff", + orange: "#ffa500", + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + blanchedalmond: "#ffebcd", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkgrey: "#a9a9a9", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + greenyellow: "#adff2f", + grey: "#808080", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgreen: "#90ee90", + lightgrey: "#d3d3d3", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370db", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + oldlace: "#fdf5e6", + olivedrab: "#6b8e23", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#db7093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + whitesmoke: "#f5f5f5", + yellowgreen: "#9acd32", +}; + +const hslToRgb = (hue, saturation, lightness) => { + const s = saturation / 100; + const l = lightness / 100; + const k = n => (n + hue / 30) % 12; + const a = s * Math.min(l, 1 - l); + const f = n => ( + l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))) + ); + return [f(0) * 255, f(8) * 255, f(4) * 255]; +}; +const mapHexNumbers = (numbers, map) => { + const chars = Array.from(Array(map.length)) + .map((_, i) => numbers[i] || "0"); + return numbers.length <= 4 + ? map.map(i => chars[i]).join("") + : chars.join(""); +}; +const hexToRgb = (string) => { + const numbers = string.replace(/#(?=\S)/g, ""); + const hasAlpha = numbers.length === 4 || numbers.length === 8; + const hexString = hasAlpha + ? mapHexNumbers(numbers, [0, 0, 1, 1, 2, 2, 3, 3]) + : mapHexNumbers(numbers, [0, 0, 1, 1, 2, 2]); + const c = parseInt(hexString, 16); + return hasAlpha + ? [(c >> 24) & 255, (c >> 16) & 255, (c >> 8) & 255, c & 255] + : [(c >> 16) & 255, (c >> 8) & 255, c & 255]; +}; +const rgbToHex = (red, green, blue, alpha) => { + const to16 = n => `00${Math.max(0, Math.min(Math.round(n), 255)).toString(16)}` + .slice(-2); + const hex = `#${[red, green, blue].map(to16).join("")}`; + return alpha === undefined + ? hex + : `${hex}${to16(alpha * 255)}`; +}; + +const convert = /*#__PURE__*/Object.freeze({ + __proto__: null, + hexToRgb, + hslToRgb, + rgbToHex +}); + +const getParenNumbers = str => { + const match = str.match(/\(([^\)]+)\)/g); + if (match == null || !match.length) { return []; } + return match[0] + .substring(1, match[0].length - 1) + .split(/[\s,]+/) + .map(parseFloat); +}; +const parseColorToRgb = (string) => { + if (cssColors[string]) { return hexToRgb(cssColors[string]); } + if (string[0] === "#") { return hexToRgb(string); } + if (string.substring(0, 4) === "rgba" + || string.substring(0, 3) === "rgb") { + const values = getParenNumbers(string); + [0, 1, 2] + .filter(i => values[i] === undefined) + .forEach(i => { values[i] = 0; }); + return values; + } + if (string.substring(0, 4) === "hsla" + || string.substring(0, 3) === "hsl") { + const values = getParenNumbers(string); + [0, 1, 2] + .filter(i => values[i] === undefined) + .forEach(i => { values[i] = 0; }); + const rgb = hslToRgb(...values); + if (values.length === 4) { rgb.push(values[3]); } + return rgb; + } + return undefined; +}; +const parseColorToHex = (string) => { + if (cssColors[string]) { return cssColors[string].toUpperCase(); } + if (string[0] === "#") { return rgbToHex(...hexToRgb(string)); } + if (string.substring(0, 4) === "rgba" + || string.substring(0, 3) === "rgb") { + return rgbToHex(...getParenNumbers(string)); + } + if (string.substring(0, 4) === "hsla" + || string.substring(0, 3) === "hsl") { + const values = getParenNumbers(string); + [0, 1, 2] + .filter(i => values[i] === undefined) + .forEach(i => { values[i] = 0; }); + const rgb = hslToRgb(...values); + if (values.length === 4) { rgb.push(values[3]); } + [0, 1, 2].forEach(i => { rgb[i] *= 255; }); + rgbToHex(...rgb); + } + return undefined; +}; + +const parseColor = /*#__PURE__*/Object.freeze({ + __proto__: null, + parseColorToHex, + parseColorToRgb +}); + +const colors = { + cssColors, + ...convert, + ...parseColor, +}; + +const svg_add2 = (a, b) => [a[0] + b[0], a[1] + b[1]]; +const svg_sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]]; +const svg_scale2 = (a, s) => [a[0] * s, a[1] * s]; +const svg_magnitudeSq2 = (a) => (a[0] ** 2) + (a[1] ** 2); +const svg_magnitude2 = (a) => Math.sqrt(svg_magnitudeSq2(a)); +const svg_distanceSq2 = (a, b) => svg_magnitudeSq2(svg_sub2(a, b)); +const svg_distance2 = (a, b) => Math.sqrt(svg_distanceSq2(a, b)); +const svg_polar_to_cart = (a, d) => [Math.cos(a) * d, Math.sin(a) * d]; +const svg_multiplyMatrices2 = (m1, m2) => [ + m1[0] * m2[0] + m1[2] * m2[1], + m1[1] * m2[0] + m1[3] * m2[1], + m1[0] * m2[2] + m1[2] * m2[3], + m1[1] * m2[2] + m1[3] * m2[3], + m1[0] * m2[4] + m1[2] * m2[5] + m1[4], + m1[1] * m2[4] + m1[3] * m2[5] + m1[5], +]; + +const svgMath = /*#__PURE__*/Object.freeze({ + __proto__: null, + svg_add2, + svg_distance2, + svg_distanceSq2, + svg_magnitude2, + svg_magnitudeSq2, + svg_multiplyMatrices2, + svg_polar_to_cart, + svg_scale2, + svg_sub2 +}); + +const parseTransform = function (transform) { + const parsed = transform.match(/(\w+\((\-?\d+\.?\d*e?\-?\d*,?\s*)+\))+/g); + if (!parsed) { return []; } + const listForm = parsed.map(a => a.match(/[\w\.\-]+/g)); + return listForm.map(a => ({ + transform: a.shift(), + parameters: a.map(p => parseFloat(p)), + })); +}; +const matrixFormTranslate = function (params) { + switch (params.length) { + case 1: return [1, 0, 0, 1, params[0], 0]; + case 2: return [1, 0, 0, 1, params[0], params[1]]; + default: console.warn(`improper translate, ${params}`); + } + return undefined; +}; +const matrixFormRotate = function (params) { + const cos_p = Math.cos(params[0] / (180 * Math.PI)); + const sin_p = Math.sin(params[0] / (180 * Math.PI)); + switch (params.length) { + case 1: return [cos_p, sin_p, -sin_p, cos_p, 0, 0]; + case 3: return [cos_p, sin_p, -sin_p, cos_p, + -params[1] * cos_p + params[2] * sin_p + params[1], + -params[1] * sin_p - params[2] * cos_p + params[2]]; + default: console.warn(`improper rotate, ${params}`); + } + return undefined; +}; +const matrixFormScale = function (params) { + switch (params.length) { + case 1: return [params[0], 0, 0, params[0], 0, 0]; + case 2: return [params[0], 0, 0, params[1], 0, 0]; + default: console.warn(`improper scale, ${params}`); + } + return undefined; +}; +const matrixFormSkewX = function (params) { + return [1, 0, Math.tan(params[0] / (180 * Math.PI)), 1, 0, 0]; +}; +const matrixFormSkewY = function (params) { + return [1, Math.tan(params[0] / (180 * Math.PI)), 0, 1, 0, 0]; +}; +const matrixForm = function (transformType, params) { + switch (transformType) { + case "translate": return matrixFormTranslate(params); + case "rotate": return matrixFormRotate(params); + case "scale": return matrixFormScale(params); + case "skewX": return matrixFormSkewX(params); + case "skewY": return matrixFormSkewY(params); + case "matrix": return params; + default: console.warn(`unknown transform type ${transformType}`); + } + return undefined; +}; +const transformStringToMatrix = function (string) { + return parseTransform(string) + .map(el => matrixForm(el.transform, el.parameters)) + .filter(a => a !== undefined) + .reduce((a, b) => svg_multiplyMatrices2(a, b), [1, 0, 0, 1, 0, 0]); +}; + +const transforms = /*#__PURE__*/Object.freeze({ + __proto__: null, + transformStringToMatrix +}); + +const xmlStringToElement = (input, mimeType = "text/xml") => { + const result = (new (SVGWindow().DOMParser)()).parseFromString(input, mimeType); + return result ? result.documentElement : null; +}; +const getRootParent = (el) => { + let parent = el; + while (parent.parentNode != null) { + parent = parent.parentNode; + } + return parent; +}; +const findElementTypeInParents = (element, nodeName) => { + if ((element.nodeName || "") === nodeName) { + return element; + } + return element.parentNode + ? findElementTypeInParents(element.parentNode, nodeName) + : undefined; +}; +const polyfillClassListAdd = (el, ...classes) => { + const hash = {}; + const getClass = el.getAttribute("class"); + const classArray = getClass ? getClass.split(" ") : []; + classArray.push(...classes); + classArray.forEach(str => { hash[str] = true; }); + const classString = Object.keys(hash).join(" "); + el.setAttribute("class", classString); +}; +const addClass = (el, ...classes) => { + if (!el || !classes.length) { return undefined; } + return el.classList + ? el.classList.add(...classes) + : polyfillClassListAdd(el, ...classes); +}; +const flattenDomTree = (el) => ( + el.childNodes == null || !el.childNodes.length + ? [el] + : Array.from(el.childNodes).flatMap(child => flattenDomTree(child)) +); +const nodeSpecificAttrs = { + svg: ["viewBox", "xmlns", "version"], + line: ["x1", "y1", "x2", "y2"], + rect: ["x", "y", "width", "height"], + circle: ["cx", "cy", "r"], + ellipse: ["cx", "cy", "rx", "ry"], + polygon: ["points"], + polyline: ["points"], + path: ["d"], +}; +const getAttributes = element => { + const attributeValue = element.attributes; + if (attributeValue == null) { return []; } + const attributes = Array.from(attributeValue); + return nodeSpecificAttrs[element.nodeName] + ? attributes + .filter(a => !nodeSpecificAttrs[element.nodeName].includes(a.name)) + : attributes; +}; +const objectifyAttributes = (list) => { + const obj = {}; + list.forEach((a) => { obj[a.nodeName] = a.value; }); + return obj; +}; +const attrAssign = (parentAttrs, element) => { + const attrs = objectifyAttributes(getAttributes(element)); + if (!attrs.transform && !parentAttrs.transform) { + return { ...parentAttrs, ...attrs }; + } + const elemTransform = attrs.transform || ""; + const parentTransform = parentAttrs.transform || ""; + const elemMatrix = transformStringToMatrix(elemTransform); + const parentMatrix = transformStringToMatrix(parentTransform); + const matrix = svg_multiplyMatrices2(parentMatrix, elemMatrix); + const transform = `matrix(${matrix.join(", ")})`; + return { ...parentAttrs, ...attrs, transform }; +}; +const flattenDomTreeWithStyle = (element, attributes = {}) => ( + element.childNodes == null || !element.childNodes.length + ? [{ element, attributes }] + : Array.from(element.childNodes) + .flatMap(child => flattenDomTreeWithStyle(child, attrAssign(attributes, child))) +); + +const dom = /*#__PURE__*/Object.freeze({ + __proto__: null, + addClass, + findElementTypeInParents, + flattenDomTree, + flattenDomTreeWithStyle, + getRootParent, + xmlStringToElement +}); + +const makeCDATASection = (text) => (new (SVGWindow()).DOMParser()) + .parseFromString("", "text/xml") + .createCDATASection(text); + +const markerRegEx = /[MmLlSsQqLlHhVvCcSsQqTtAaZz]/g; +const digitRegEx = /-?[0-9]*\.?\d+/g; +const pathCommandNames = { + m: "move", + l: "line", + v: "vertical", + h: "horizontal", + a: "ellipse", + c: "curve", + s: "smoothCurve", + q: "quadCurve", + t: "smoothQuadCurve", + z: "close", +}; +Object.keys(pathCommandNames).forEach((key) => { + const s = pathCommandNames[key]; + pathCommandNames[key.toUpperCase()] = s.charAt(0).toUpperCase() + s.slice(1); +}); +const add2path = (a, b) => [a[0] + (b[0] || 0), a[1] + (b[1] || 0)]; +const getEndpoint = (command, values, offset = [0, 0]) => { + const upper = command.toUpperCase(); + let origin = command === upper ? [0, 0] : offset; + if (command === "V") { origin = [offset[0], 0]; } + if (command === "H") { origin = [0, offset[1]]; } + switch (upper) { + case "V": return add2path(origin, [0, values[0]]); + case "H": return add2path(origin, [values[0], 0]); + case "M": + case "L": + case "T": return add2path(origin, values); + case "A": return add2path(origin, [values[5], values[6]]); + case "C": return add2path(origin, [values[4], values[5]]); + case "S": + case "Q": return add2path(origin, [values[2], values[3]]); + case "Z": return undefined; + default: return origin; + } +}; +const parsePathCommands = (d) => { + const results = []; + let match; + while ((match = markerRegEx.exec(d)) !== null) { + results.push(match); + } + return results + .map((result, i, arr) => [ + result[0], + result.index, + i === arr.length - 1 + ? d.length - 1 + : arr[(i + 1) % arr.length].index - 1, + ]) + .map(el => { + const command = el[0]; + const valueString = d.substring(el[1] + 1, el[2] + 1); + const strings = valueString.match(digitRegEx); + const values = strings ? strings.map(parseFloat) : []; + return { command, values }; + }); +}; +const parsePathCommandsWithEndpoints = (d) => { + let pen = [0, 0]; + const commands = parsePathCommands(d); + if (!commands.length) { return commands; } + commands.forEach((command, i) => { + commands[i].end = getEndpoint(command.command, command.values, pen); + commands[i].start = i === 0 ? pen : commands[i - 1].end; + pen = commands[i].end; + }); + const last = commands[commands.length - 1]; + const firstDrawCommand = commands + .filter(el => el.command.toUpperCase() !== "M" + && el.command.toUpperCase() !== "Z") + .shift(); + if (last.command.toUpperCase() === "Z") { + last.end = [...firstDrawCommand.start]; + } + return commands; +}; + +const pathMethods = /*#__PURE__*/Object.freeze({ + __proto__: null, + parsePathCommands, + parsePathCommandsWithEndpoints, + pathCommandNames +}); + +const makeCoordinates = (...args) => args + .filter(a => typeof a === str_number) + .concat(args + .filter(a => typeof a === str_object && a !== null) + .map((el) => { + if (typeof el.x === str_number) { return [el.x, el.y]; } + if (typeof el[0] === str_number) { return [el[0], el[1]]; } + return undefined; + }).filter(a => a !== undefined) + .reduce((a, b) => a.concat(b), [])); + +const viewBoxValuesToString = function (x, y, width, height, padding = 0) { + const scale = 1.0; + const d = (width / scale) - width; + const X = (x - d) - padding; + const Y = (y - d) - padding; + const W = (width + d * 2) + padding * 2; + const H = (height + d * 2) + padding * 2; + return [X, Y, W, H].join(" "); +}; +const makeViewBox = (...args) => { + const numbers = makeCoordinates(...args.flat()); + if (numbers.length === 2) { numbers.unshift(0, 0); } + return numbers.length === 4 ? viewBoxValuesToString(...numbers) : undefined; +}; + +const setViewBox = (element, ...args) => { + const viewBox = args.length === 1 && typeof args[0] === str_string + ? args[0] + : makeViewBox(...args); + if (viewBox) { + element.setAttribute(str_viewBox, viewBox); + } + return element; +}; +const getViewBox = function (element) { + const vb = element.getAttribute(str_viewBox); + return (vb == null + ? undefined + : vb.split(" ").map(n => parseFloat(n))); +}; +const convertToViewBox = function (svg, x, y) { + const pt = svg.createSVGPoint(); + pt.x = x; + pt.y = y; + const svgPoint = pt.matrixTransform(svg.getScreenCTM().inverse()); + return [svgPoint.x, svgPoint.y]; +}; + +const viewBox = /*#__PURE__*/Object.freeze({ + __proto__: null, + convertToViewBox, + getViewBox, + setViewBox +}); + +const general = { + ...svgMath, + ...dom, + makeCDATASection, + ...pathMethods, + ...transforms, + ...viewBox, +}; + +const getSVGFrame = function (element) { + const viewBox = getViewBox(element); + if (viewBox !== undefined) { + return viewBox; + } + if (typeof element.getBoundingClientRect === str_function) { + const rr = element.getBoundingClientRect(); + return [rr.x, rr.y, rr.width, rr.height]; + } + return []; +}; + +const bgClass = "svg-background-rectangle"; +const makeBackground = function (element, color) { + let backRect = Array.from(element.childNodes) + .filter(child => child.getAttribute(str_class) === bgClass) + .shift(); + if (backRect == null) { + backRect = SVGWindow().document.createElementNS(NS, "rect"); + getSVGFrame(element).forEach((n, i) => backRect.setAttribute(nodes_attributes.rect[i], n)); + backRect.setAttribute(str_class, bgClass); + backRect.setAttribute(str_stroke, str_none); + element.insertBefore(backRect, element.firstChild); + } + backRect.setAttribute(str_fill, color); + return element; +}; + +const getAttr = (element) => { + const t = element.getAttribute(str_transform); + return (t == null || t === "") ? undefined : t; +}; +const TransformMethods = { + clearTransform: (el) => { el.removeAttribute(str_transform); return el; }, +}; +["translate", "rotate", "scale", "matrix"].forEach(key => { + TransformMethods[key] = (element, ...args) => { + element.setAttribute( + str_transform, + [getAttr(element), `${key}(${args.join(" ")})`] + .filter(a => a !== undefined) + .join(" "), + ); + return element; + }; +}); + +const toCamel = (s) => s + .replace(/([-_][a-z])/ig, $1 => $1 + .toUpperCase() + .replace("-", "") + .replace("_", "")); +const toKebab = (s) => s + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") + .replace(/([A-Z])([A-Z])(?=[a-z])/g, "$1-$2") + .toLowerCase(); +const capitalized = (s) => s + .charAt(0).toUpperCase() + s.slice(1); + +const removeChildren = (element) => { + while (element.lastChild) { + element.removeChild(element.lastChild); + } + return element; +}; +const appendTo = (element, parent) => { + if (parent && parent.appendChild) { + parent.appendChild(element); + } + return element; +}; +const setAttributes = (element, attrs) => { + Object.keys(attrs) + .forEach(key => element.setAttribute(toKebab(key), attrs[key])); + return element; +}; + +const DOM = /*#__PURE__*/Object.freeze({ + __proto__: null, + appendTo, + removeChildren, + setAttributes +}); + +const setPadding = function (element, padding) { + const viewBox = getViewBox(element); + if (viewBox !== undefined) { + setViewBox(element, ...[-padding, -padding, padding * 2, padding * 2] + .map((nudge, i) => viewBox[i] + nudge)); + } + return element; +}; +const findOneElement = function (element, nodeName) { + const styles = element.getElementsByTagName(nodeName); + return styles.length ? styles[0] : null; +}; +const stylesheet = function (element, textContent) { + let styleSection = findOneElement(element, str_style); + if (styleSection == null) { + styleSection = SVGWindow().document.createElementNS(NS, str_style); + styleSection.setTextContent = (text) => { + styleSection.textContent = ""; + styleSection.appendChild(makeCDATASection(text)); + return styleSection; + }; + element.insertBefore(styleSection, element.firstChild); + } + styleSection.textContent = ""; + styleSection.appendChild(makeCDATASection(textContent)); + return styleSection; +}; +const clearSVG = (element) => { + Array.from(element.attributes) + .filter(attr => attr.name !== "xmlns" && attr.name !== "version") + .forEach(attr => element.removeAttribute(attr.name)); + return removeChildren(element); +}; +const methods$2 = { + clear: clearSVG, + size: setViewBox, + setViewBox, + getViewBox, + padding: setPadding, + background: makeBackground, + getWidth: el => getSVGFrame(el)[2], + getHeight: el => getSVGFrame(el)[3], + stylesheet: function (el, text) { return stylesheet.call(this, el, text); }, + ...TransformMethods, + ...DOM, +}; + +const eventNameCategories = { + move: ["mousemove", "touchmove"], + press: ["mousedown", "touchstart"], + release: ["mouseup", "touchend"], + leave: ["mouseleave", "touchcancel"], +}; +const off = (el, handlers) => Object.values(eventNameCategories) + .flat() + .forEach((handlerName) => { + handlers[handlerName].forEach(func => el + .removeEventListener(handlerName, func)); + handlers[handlerName] = []; + }); +const defineGetter = (obj, prop, value) => Object + .defineProperty(obj, prop, { + get: () => value, + enumerable: true, + configurable: true, + }); +const TouchEvents = function (element) { + const handlers = []; + Object.keys(eventNameCategories).forEach((key) => { + eventNameCategories[key].forEach((handler) => { + handlers[handler] = []; + }); + }); + const removeHandler = category => eventNameCategories[category] + .forEach(handlerName => handlers[handlerName] + .forEach(func => element.removeEventListener(handlerName, func))); + Object.keys(eventNameCategories).forEach((category) => { + Object.defineProperty(element, `on${capitalized(category)}`, { + set: (handler) => { + if (!element.addEventListener) { return; } + if (handler == null) { + removeHandler(category); + return; + } + eventNameCategories[category].forEach((handlerName) => { + const handlerFunc = (e) => { + const pointer = (e.touches != null ? e.touches[0] : e); + if (pointer !== undefined) { + const { clientX, clientY } = pointer; + const [x, y] = convertToViewBox(element, clientX, clientY); + defineGetter(e, "x", x); + defineGetter(e, "y", y); + } + handler(e); + }; + handlers[handlerName].push(handlerFunc); + element.addEventListener(handlerName, handlerFunc); + }); + }, + enumerable: true, + }); + }); + Object.defineProperty(element, "off", { value: () => off(element, handlers) }); +}; + +const makeUUID = () => Math.random() + .toString(36) + .replace(/[^a-z]+/g, "") + .concat("aaaaa") + .substr(0, 5); + +const Animation = function (element) { + let start; + let frame = 0; + let requestId; + const handlers = {}; + const stop = () => { + if (SVGWindow().cancelAnimationFrame) { + SVGWindow().cancelAnimationFrame(requestId); + } + Object.keys(handlers).forEach(uuid => delete handlers[uuid]); + }; + const play = (handler) => { + stop(); + if (!handler || !(SVGWindow().requestAnimationFrame)) { return; } + start = performance.now(); + frame = 0; + const uuid = makeUUID(); + handlers[uuid] = (now) => { + const time = (now - start) * 1e-3; + handler({ time, frame }); + frame += 1; + if (handlers[uuid]) { + requestId = SVGWindow().requestAnimationFrame(handlers[uuid]); + } + }; + requestId = SVGWindow().requestAnimationFrame(handlers[uuid]); + }; + Object.defineProperty(element, "play", { set: play, enumerable: true }); + Object.defineProperty(element, "stop", { value: stop, enumerable: true }); +}; + +const removeFromParent = svg => (svg && svg.parentNode + ? svg.parentNode.removeChild(svg) + : undefined); +const possiblePositionAttributes = [["cx", "cy"], ["x", "y"]]; +const controlPoint = function (parent, options = {}) { + const position = [0, 0]; + const cp = { + selected: false, + svg: undefined, + updatePosition: input => input, + }; + const updateSVG = () => { + if (!cp.svg) { return; } + if (!cp.svg.parentNode) { + parent.appendChild(cp.svg); + } + possiblePositionAttributes + .filter(coords => cp.svg[coords[0]] != null) + .forEach(coords => coords.forEach((attr, i) => { + cp.svg.setAttribute(attr, position[i]); + })); + }; + const proxy = new Proxy(position, { + set: (target, property, value) => { + target[property] = value; + updateSVG(); + return true; + }, + }); + const setPosition = function (...args) { + makeCoordinates(...args.flat()) + .forEach((n, i) => { position[i] = n; }); + updateSVG(); + if (typeof position.delegate === str_function) { + position.delegate.apply(position.pointsContainer, [proxy, position.pointsContainer]); + } + }; + position.delegate = undefined; + position.setPosition = setPosition; + position.onMouseMove = mouse => (cp.selected + ? setPosition(cp.updatePosition(mouse)) + : undefined); + position.onMouseUp = () => { cp.selected = false; }; + position.distance = mouse => Math.sqrt(svg_distanceSq2(mouse, position)); + ["x", "y"].forEach((prop, i) => Object.defineProperty(position, prop, { + get: () => position[i], + set: (v) => { position[i] = v; } + })); + [str_svg, "updatePosition", "selected"].forEach(key => Object + .defineProperty(position, key, { + get: () => cp[key], + set: (v) => { cp[key] = v; }, + })); + Object.defineProperty(position, "remove", { + value: () => { + removeFromParent(cp.svg); + position.delegate = undefined; + }, + }); + return proxy; +}; +const controls = function (svg, number, options) { + let selected; + let delegate; + const points = Array.from(Array(number)) + .map(() => controlPoint(svg, options)); + const protocol = point => (typeof delegate === str_function + ? delegate.call(points, point, selected, points) + : undefined); + points.forEach((p) => { + p.delegate = protocol; + p.pointsContainer = points; + }); + const mousePressedHandler = function (mouse) { + if (!(points.length > 0)) { return; } + selected = points + .map((p, i) => ({ i, d: svg_distanceSq2(p, [mouse.x, mouse.y]) })) + .sort((a, b) => a.d - b.d) + .shift() + .i; + points[selected].selected = true; + }; + const mouseMovedHandler = function (mouse) { + points.forEach(p => p.onMouseMove(mouse)); + }; + const mouseReleasedHandler = function () { + points.forEach(p => p.onMouseUp()); + selected = undefined; + }; + svg.onPress = mousePressedHandler; + svg.onMove = mouseMovedHandler; + svg.onRelease = mouseReleasedHandler; + Object.defineProperty(points, "selectedIndex", { get: () => selected }); + Object.defineProperty(points, "selected", { get: () => points[selected] }); + Object.defineProperty(points, "add", { + value: (opt) => { + points.push(controlPoint(svg, opt)); + }, + }); + points.removeAll = () => { + while (points.length > 0) { + points.pop().remove(); + } + }; + const functionalMethods = { + onChange: (func, runOnceAtStart) => { + delegate = func; + if (runOnceAtStart === true) { + const index = points.length - 1; + func.call(points, points[index], index, points); + } + }, + position: func => points.forEach((p, i) => p.setPosition(func.call(points, p, i, points))), + svg: func => points.forEach((p, i) => { p.svg = func.call(points, p, i, points); }), + }; + Object.keys(functionalMethods).forEach((key) => { + points[key] = function () { + if (typeof arguments[0] === str_function) { + functionalMethods[key](...arguments); + } + return points; + }; + }); + points.parent = function (parent) { + if (parent != null && parent.appendChild != null) { + points.forEach((p) => { parent.appendChild(p.svg); }); + } + return points; + }; + return points; +}; +const applyControlsToSVG = (svg) => { + svg.controls = (...args) => controls.call(svg, svg, ...args); +}; + +const svgDef = { + svg: { + args: (...args) => [makeViewBox(makeCoordinates(...args))].filter(a => a != null), + methods: methods$2, + init: (...args) => { + const element = SVGWindow().document.createElementNS(NS, "svg"); + element.setAttribute("version", "1.1"); + element.setAttribute("xmlns", NS); + args.filter(a => a != null) + .filter(el => el.appendChild) + .forEach(parent => parent.appendChild(element)); + TouchEvents(element); + Animation(element); + applyControlsToSVG(element); + return element; + }, + }, +}; + +const findIdURL = function (arg) { + if (arg == null) { return ""; } + if (typeof arg === str_string) { + return arg.slice(0, 3) === "url" + ? arg + : `url(#${arg})`; + } + if (arg.getAttribute != null) { + const idString = arg.getAttribute(str_id); + return `url(#${idString})`; + } + return ""; +}; +const methods$1 = {}; +["clip-path", + "mask", + "symbol", + "marker-end", + "marker-mid", + "marker-start", +].forEach(attr => { + methods$1[toCamel(attr)] = (element, parent) => { + element.setAttribute(attr, findIdURL(parent)); + return element; + }; +}); + +const gDef = { + g: { + methods: { + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, +}; + +const setRadius = (el, r) => { + el.setAttribute(nodes_attributes.circle[2], r); + return el; +}; +const setOrigin$1 = (el, a, b) => { + [...makeCoordinates(...[a, b].flat()).slice(0, 2)] + .forEach((value, i) => el.setAttribute(nodes_attributes.circle[i], value)); + return el; +}; +const fromPoints = (a, b, c, d) => [a, b, svg_distance2([a, b], [c, d])]; +const circleDef = { + circle: { + args: (a, b, c, d) => { + const coords = makeCoordinates(...[a, b, c, d].flat()); + switch (coords.length) { + case 0: case 1: return [, , ...coords]; + case 2: case 3: return coords; + default: return fromPoints(...coords); + } + }, + methods: { + radius: setRadius, + setRadius, + origin: setOrigin$1, + setOrigin: setOrigin$1, + center: setOrigin$1, + setCenter: setOrigin$1, + position: setOrigin$1, + setPosition: setOrigin$1, + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, +}; + +const setRadii = (el, rx, ry) => { + [, , rx, ry].forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value)); + return el; +}; +const setOrigin = (el, a, b) => { + [...makeCoordinates(...[a, b].flat()).slice(0, 2)] + .forEach((value, i) => el.setAttribute(nodes_attributes.ellipse[i], value)); + return el; +}; +const ellipseDef = { + ellipse: { + args: (a, b, c, d) => { + const coords = makeCoordinates(...[a, b, c, d].flat()).slice(0, 4); + switch (coords.length) { + case 0: case 1: case 2: return [, , ...coords]; + default: return coords; + } + }, + methods: { + radius: setRadii, + setRadius: setRadii, + origin: setOrigin, + setOrigin, + center: setOrigin, + setCenter: setOrigin, + position: setOrigin, + setPosition: setOrigin, + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, +}; + +const svgIsIterable = (obj) => obj != null + && typeof obj[Symbol.iterator] === str_function; +const svgSemiFlattenArrays = function () { + switch (arguments.length) { + case 0: return Array.from(arguments); + case 1: return svgIsIterable(arguments[0]) && typeof arguments[0] !== str_string + ? svgSemiFlattenArrays(...arguments[0]) + : [arguments[0]]; + default: + return Array.from(arguments).map(a => (svgIsIterable(a) + ? [...svgSemiFlattenArrays(a)] + : a)); + } +}; + +const Args$1 = (...args) => makeCoordinates(...svgSemiFlattenArrays(...args)).slice(0, 4); +const setPoints$3 = (element, ...args) => { + Args$1(...args).forEach((value, i) => element.setAttribute(nodes_attributes.line[i], value)); + return element; +}; +const lineDef = { + line: { + args: Args$1, + methods: { + setPoints: setPoints$3, + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, +}; + +const getD = (el) => { + const attr = el.getAttribute("d"); + return (attr == null) ? "" : attr; +}; +const clear = element => { + element.removeAttribute("d"); + return element; +}; +const appendPathCommand = (el, command, ...args) => { + el.setAttribute("d", `${getD(el)}${command}${args.flat().join(" ")}`); + return el; +}; +const getCommands = element => parsePathCommands(getD(element)); +const path_methods = { + addCommand: appendPathCommand, + appendCommand: appendPathCommand, + clear, + getCommands: getCommands, + get: getCommands, + getD: el => el.getAttribute("d"), + ...TransformMethods, + ...methods$1, + ...DOM, +}; +Object.keys(pathCommandNames).forEach((key) => { + path_methods[pathCommandNames[key]] = (el, ...args) => appendPathCommand(el, key, ...args); +}); +const pathDef = { + path: { + methods: path_methods, + }, +}; + +const setRectSize = (el, rx, ry) => { + [, , rx, ry] + .forEach((value, i) => el.setAttribute(nodes_attributes.rect[i], value)); + return el; +}; +const setRectOrigin = (el, a, b) => { + [...makeCoordinates(...[a, b].flat()).slice(0, 2)] + .forEach((value, i) => el.setAttribute(nodes_attributes.rect[i], value)); + return el; +}; +const fixNegatives = function (arr) { + [0, 1].forEach(i => { + if (arr[2 + i] < 0) { + if (arr[0 + i] === undefined) { arr[0 + i] = 0; } + arr[0 + i] += arr[2 + i]; + arr[2 + i] = -arr[2 + i]; + } + }); + return arr; +}; +const rectDef = { + rect: { + args: (a, b, c, d) => { + const coords = makeCoordinates(...[a, b, c, d].flat()).slice(0, 4); + switch (coords.length) { + case 0: case 1: case 2: case 3: return fixNegatives([, , ...coords]); + default: return fixNegatives(coords); + } + }, + methods: { + origin: setRectOrigin, + setOrigin: setRectOrigin, + center: setRectOrigin, + setCenter: setRectOrigin, + size: setRectSize, + setSize: setRectSize, + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, +}; + +const styleDef = { + style: { + init: (text) => { + const el = SVGWindow().document.createElementNS(NS, "style"); + el.setAttribute("type", "text/css"); + el.textContent = ""; + el.appendChild(makeCDATASection(text)); + return el; + }, + methods: { + setTextContent: (el, text) => { + el.textContent = ""; + el.appendChild(makeCDATASection(text)); + return el; + }, + }, + }, +}; + +const textDef = { + text: { + args: (a, b, c) => makeCoordinates(...[a, b, c].flat()).slice(0, 2), + init: (a, b, c, d) => { + const element = SVGWindow().document.createElementNS(NS, "text"); + const text = [a, b, c, d].filter(el => typeof el === str_string).shift(); + element.appendChild(SVGWindow().document.createTextNode(text || "")); + return element; + }, + methods: { + ...TransformMethods, + ...methods$1, + appendTo, + setAttributes, + }, + }, +}; + +const makeIDString = function () { + return Array.from(arguments) + .filter(a => typeof a === str_string || a instanceof String) + .shift() || makeUUID(); +}; +const maskArgs = (...args) => [makeIDString(...args)]; +const maskTypes = { + mask: { + args: maskArgs, + methods: { + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, + clipPath: { + args: maskArgs, + methods: { + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, + symbol: { + args: maskArgs, + methods: { + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, + marker: { + args: maskArgs, + methods: { + size: setViewBox, + setViewBox: setViewBox, + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, +}; + +const getPoints = (el) => { + const attr = el.getAttribute(str_points); + return (attr == null) ? "" : attr; +}; +const polyString = function () { + return Array + .from(Array(Math.floor(arguments.length / 2))) + .map((_, i) => `${arguments[i * 2 + 0]},${arguments[i * 2 + 1]}`) + .join(" "); +}; +const stringifyArgs = (...args) => [ + polyString(...makeCoordinates(...svgSemiFlattenArrays(...args))), +]; +const setPoints$2 = (element, ...args) => { + element.setAttribute(str_points, stringifyArgs(...args)[0]); + return element; +}; +const addPoint = (element, ...args) => { + element.setAttribute(str_points, [getPoints(element), stringifyArgs(...args)[0]] + .filter(a => a !== "") + .join(" ")); + return element; +}; +const Args = function (...args) { + return args.length === 1 && typeof args[0] === str_string + ? [args[0]] + : stringifyArgs(...args); +}; +const polyDefs = { + polyline: { + args: Args, + methods: { + setPoints: setPoints$2, + addPoint, + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, + polygon: { + args: Args, + methods: { + setPoints: setPoints$2, + addPoint, + ...TransformMethods, + ...methods$1, + ...DOM, + }, + }, +}; + +const arcPath = (x, y, radius, startAngle, endAngle, includeCenter = false) => { + if (endAngle == null) { return ""; } + const start = svg_polar_to_cart(startAngle, radius); + const end = svg_polar_to_cart(endAngle, radius); + const arcVec = [end[0] - start[0], end[1] - start[1]]; + const py = start[0] * end[1] - start[1] * end[0]; + const px = start[0] * end[0] + start[1] * end[1]; + const arcdir = (Math.atan2(py, px) > 0 ? 0 : 1); + let d = (includeCenter + ? `M ${x},${y} l ${start[0]},${start[1]} ` + : `M ${x + start[0]},${y + start[1]} `); + d += ["a ", radius, radius, 0, arcdir, 1, arcVec[0], arcVec[1]].join(" "); + if (includeCenter) { d += " Z"; } + return d; +}; + +const arcArguments = (a, b, c, d, e) => [arcPath(a, b, c, d, e, false)]; +const arcDef = { + arc: { + nodeName: str_path, + attributes: ["d"], + args: arcArguments, + methods: { + setArc: (el, ...args) => el.setAttribute("d", arcArguments(...args)), + ...TransformMethods, + }, + }, +}; + +const ends = [str_tail, str_head]; +const stringifyPoint = p => p.join(","); +const pointsToPath = (points) => "M" + points.map(pt => pt.join(",")).join("L") + "Z"; +const makeArrowPaths = function (options) { + let pts = [[0,1], [2,3]].map(pt => pt.map(i => options.points[i] || 0)); + let vector = svg_sub2(pts[1], pts[0]); + let midpoint = svg_add2(pts[0], svg_scale2(vector, 0.5)); + const len = svg_magnitude2(vector); + const minLength = ends + .map(s => (options[s].visible + ? (1 + options[s].padding) * options[s].height * 2.5 + : 0)) + .reduce((a, b) => a + b, 0); + if (len < minLength) { + const minVec = len === 0 ? [minLength, 0] : svg_scale2(vector, minLength / len); + pts = [svg_sub2, svg_add2].map(f => f(midpoint, svg_scale2(minVec, 0.5))); + vector = svg_sub2(pts[1], pts[0]); + } + let perpendicular = [vector[1], -vector[0]]; + let bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, options.bend)); + const bezs = pts.map(pt => svg_sub2(bezPoint, pt)); + const bezsLen = bezs.map(v => svg_magnitude2(v)); + const bezsNorm = bezs.map((bez, i) => bezsLen[i] === 0 + ? bez + : svg_scale2(bez, 1 / bezsLen[i])); + const vectors = bezsNorm.map(norm => svg_scale2(norm, -1)); + const normals = vectors.map(vec => [vec[1], -vec[0]]); + const pad = ends.map((s, i) => options[s].padding + ? options[s].padding + : (options.padding ? options.padding : 0.0)); + const scales = ends + .map((s, i) => options[s].height * (options[s].visible ? 1 : 0)) + .map((n, i) => n + pad[i]); + const arcs = pts.map((pt, i) => svg_add2(pt, svg_scale2(bezsNorm[i], scales[i]))); + vector = svg_sub2(arcs[1], arcs[0]); + perpendicular = [vector[1], -vector[0]]; + midpoint = svg_add2(arcs[0], svg_scale2(vector, 0.5)); + bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, options.bend)); + const controls = arcs + .map((arc, i) => svg_add2(arc, svg_scale2(svg_sub2(bezPoint, arc), options.pinch))); + const polyPoints = ends.map((s, i) => [ + svg_add2(arcs[i], svg_scale2(vectors[i], options[s].height)), + svg_add2(arcs[i], svg_scale2(normals[i], options[s].width / 2)), + svg_add2(arcs[i], svg_scale2(normals[i], -options[s].width / 2)), + ]); + return { + line: `M${stringifyPoint(arcs[0])}C${stringifyPoint(controls[0])},${stringifyPoint(controls[1])},${stringifyPoint(arcs[1])}`, + tail: pointsToPath(polyPoints[0]), + head: pointsToPath(polyPoints[1]), + }; +}; + +const setArrowheadOptions = (element, options, which) => { + if (typeof options === str_boolean) { + element.options[which].visible = options; + } else if (typeof options === str_object) { + Object.assign(element.options[which], options); + if (options.visible == null) { + element.options[which].visible = true; + } + } else if (options == null) { + element.options[which].visible = true; + } +}; +const setArrowStyle = (element, options = {}, which = str_head) => { + const path = element.getElementsByClassName(`${str_arrow}-${which}`)[0]; + Object.keys(options) + .map(key => ({ key, fn: path[toCamel(key)] })) + .filter(el => typeof el.fn === str_function && el.key !== "class") + .forEach(el => el.fn(options[el.key])); + Object.keys(options) + .filter(key => key === "class") + .forEach(key => path.classList.add(options[key])); +}; +const redraw = (element) => { + const paths = makeArrowPaths(element.options); + Object.keys(paths) + .map(path => ({ + path, + element: element.getElementsByClassName(`${str_arrow}-${path}`)[0], + })) + .filter(el => el.element) + .map(el => { el.element.setAttribute("d", paths[el.path]); return el; }) + .filter(el => element.options[el.path]) + .forEach(el => el.element.setAttribute( + "visibility", + element.options[el.path].visible + ? "visible" + : "hidden", + )); + return element; +}; +const setPoints$1 = (element, ...args) => { + element.options.points = makeCoordinates(...svgSemiFlattenArrays(...args)).slice(0, 4); + return redraw(element); +}; +const bend$1 = (element, amount) => { + element.options.bend = amount; + return redraw(element); +}; +const pinch$1 = (element, amount) => { + element.options.pinch = amount; + return redraw(element); +}; +const padding = (element, amount) => { + element.options.padding = amount; + return redraw(element); +}; +const head = (element, options) => { + setArrowheadOptions(element, options, str_head); + setArrowStyle(element, options, str_head); + return redraw(element); +}; +const tail = (element, options) => { + setArrowheadOptions(element, options, str_tail); + setArrowStyle(element, options, str_tail); + return redraw(element); +}; +const getLine = element => element.getElementsByClassName(`${str_arrow}-line`)[0]; +const getHead = element => element.getElementsByClassName(`${str_arrow}-${str_head}`)[0]; +const getTail = element => element.getElementsByClassName(`${str_arrow}-${str_tail}`)[0]; +const ArrowMethods = { + setPoints: setPoints$1, + points: setPoints$1, + bend: bend$1, + pinch: pinch$1, + padding, + head, + tail, + getLine, + getHead, + getTail, + ...TransformMethods, +}; + +const endOptions = () => ({ + visible: false, + width: 8, + height: 10, + padding: 0.0, +}); +const makeArrowOptions = () => ({ + head: endOptions(), + tail: endOptions(), + bend: 0.0, + padding: 0.0, + pinch: 0.618, + points: [], +}); + +const arrowKeys = Object.keys(makeArrowOptions()); +const matchingOptions = (...args) => { + for (let a = 0; a < args.length; a += 1) { + if (typeof args[a] !== str_object) { continue; } + const keys = Object.keys(args[a]); + for (let i = 0; i < keys.length; i += 1) { + if (arrowKeys.includes(keys[i])) { + return args[a]; + } + } + } + return undefined; +}; +const init$1 = function (element, ...args) { + element.classList.add(str_arrow); + const paths = ["line", str_tail, str_head].map(key => { + const path = SVGWindow().document.createElementNS(NS, str_path); + path.className = `${str_arrow}-${key}`; + element.appendChild(path); + return path; + }); + paths[0].setAttribute(str_style, "fill:none;"); + paths[1].setAttribute(str_stroke, str_none); + paths[2].setAttribute(str_stroke, str_none); + element.options = makeArrowOptions(); + ArrowMethods.setPoints(element, ...args); + const options = matchingOptions(...args); + if (options) { + Object.keys(options) + .filter(key => ArrowMethods[key]) + .forEach(key => ArrowMethods[key](element, options[key])); + } + return element; +}; + +const arrowDef = { + arrow: { + nodeName: "g", + attributes: [], + args: () => [], + methods: ArrowMethods, + init: init$1, + }, +}; + +const makeCurvePath = (endpoints = [], bend = 0, pinch = 0.5) => { + const tailPt = [endpoints[0] || 0, endpoints[1] || 0]; + const headPt = [endpoints[2] || 0, endpoints[3] || 0]; + const vector = svg_sub2(headPt, tailPt); + const midpoint = svg_add2(tailPt, svg_scale2(vector, 0.5)); + const perpendicular = [vector[1], -vector[0]]; + const bezPoint = svg_add2(midpoint, svg_scale2(perpendicular, bend)); + const tailControl = svg_add2(tailPt, svg_scale2(svg_sub2(bezPoint, tailPt), pinch)); + const headControl = svg_add2(headPt, svg_scale2(svg_sub2(bezPoint, headPt), pinch)); + return `M${tailPt[0]},${tailPt[1]}C${tailControl[0]},${tailControl[1]} ${headControl[0]},${headControl[1]} ${headPt[0]},${headPt[1]}`; +}; + +const curveArguments = (...args) => [ + makeCurvePath(makeCoordinates(...args.flat())), +]; + +const getNumbersFromPathCommand = str => str + .slice(1) + .split(/[, ]+/) + .map(s => parseFloat(s)); +const getCurveTos = d => d + .match(/[Cc][(0-9), .-]+/) + .map(curve => getNumbersFromPathCommand(curve)); +const getMoveTos = d => d + .match(/[Mm][(0-9), .-]+/) + .map(curve => getNumbersFromPathCommand(curve)); +const getCurveEndpoints = (d) => { + const move = getMoveTos(d).shift(); + const curve = getCurveTos(d).shift(); + const start = move + ? [move[move.length - 2], move[move.length - 1]] + : [0, 0]; + const end = curve + ? [curve[curve.length - 2], curve[curve.length - 1]] + : [0, 0]; + return [...start, ...end]; +}; + +const setPoints = (element, ...args) => { + const coords = makeCoordinates(...args.flat()).slice(0, 4); + element.setAttribute("d", makeCurvePath(coords, element._bend, element._pinch)); + return element; +}; +const bend = (element, amount) => { + element._bend = amount; + return setPoints(element, ...getCurveEndpoints(element.getAttribute("d"))); +}; +const pinch = (element, amount) => { + element._pinch = amount; + return setPoints(element, ...getCurveEndpoints(element.getAttribute("d"))); +}; +const curve_methods = { + setPoints, + bend, + pinch, + ...TransformMethods, +}; + +const curveDef = { + curve: { + nodeName: str_path, + attributes: ["d"], + args: curveArguments, + methods: curve_methods, + }, +}; + +const wedgeArguments = (a, b, c, d, e) => [arcPath(a, b, c, d, e, true)]; +const wedgeDef = { + wedge: { + nodeName: str_path, + args: wedgeArguments, + attributes: ["d"], + methods: { + setArc: (el, ...args) => el.setAttribute("d", wedgeArguments(...args)), + ...TransformMethods, + }, + }, +}; + +const lib = {}; + +const init = (graph, ...args) => { + const g = SVGWindow().document.createElementNS(NS, "g"); + lib.ear.convert.foldToSvg.render(graph, g, ...args); + return g; +}; + +const methods = { + ...TransformMethods, + ...methods$1, + ...DOM, +}; + +const origamiDef = { + origami: { + nodeName: "g", + init, + args: () => [], + methods, + }, +}; + +const extensions = { + ...svgDef, + ...gDef, + ...circleDef, + ...ellipseDef, + ...lineDef, + ...pathDef, + ...rectDef, + ...styleDef, + ...textDef, + ...maskTypes, + ...polyDefs, + ...arcDef, + ...arrowDef, + ...curveDef, + ...wedgeDef, + ...origamiDef, +}; + +const passthroughArgs = (...args) => args; +const Constructor = (name, parent, ...initArgs) => { + const nodeName = extensions[name] && extensions[name].nodeName + ? extensions[name].nodeName + : name; + const { init, args, methods } = extensions[name] || {}; + const attributes = nodes_attributes[nodeName] || []; + const children = nodes_children[nodeName] || []; + const element = init + ? init(...initArgs) + : SVGWindow().document.createElementNS(NS, nodeName); + if (parent) { parent.appendChild(element); } + const processArgs = args || passthroughArgs; + processArgs(...initArgs).forEach((v, i) => { + element.setAttribute(nodes_attributes[nodeName][i], v); + }); + if (methods) { + Object.keys(methods) + .forEach(methodName => Object.defineProperty(element, methodName, { + value: function () { + return methods[methodName](element, ...arguments); + }, + })); + } + attributes.forEach((attribute) => { + const attrNameCamel = toCamel(attribute); + if (element[attrNameCamel]) { return; } + Object.defineProperty(element, attrNameCamel, { + value: function () { + element.setAttribute(attribute, ...arguments); + return element; + }, + }); + }); + children.forEach((childNode) => { + if (element[childNode]) { return; } + const value = function () { return Constructor(childNode, element, ...arguments); }; + Object.defineProperty(element, childNode, { value }); + }); + return element; +}; + +const SVG = (...args) => { + const svg = Constructor(str_svg, null, ...args); + const initialize = () => args + .filter(arg => typeof arg === str_function) + .forEach(func => func.call(svg, svg)); + if (SVGWindow().document.readyState === "loading") { + SVGWindow().document.addEventListener("DOMContentLoaded", initialize); + } else { + initialize(); + } + return svg; +}; +Object.assign(SVG, { + NS, + nodes_attributes, + nodes_children, + extensions, + ...colors, + ...general, +}); +nodeNames.forEach(nodeName => { + SVG[nodeName] = (...args) => Constructor(nodeName, null, ...args); +}); +Object.defineProperty(SVG, "window", { + enumerable: false, + set: setSVGWindow, +}); + +export { SVG as default }; diff --git a/tests/colors.test.js b/tests/colors.test.js new file mode 100644 index 0000000..0688bb0 --- /dev/null +++ b/tests/colors.test.js @@ -0,0 +1,56 @@ +const { test, expect } = require("@jest/globals"); +const SVG = require("../svg.js"); + +test("colors hexToRgb", () => { + const rgb1 = SVG.hexToRgb("#158"); + const rgb2 = SVG.hexToRgb("#115588"); + expect(JSON.stringify(rgb1)).toBe(JSON.stringify(rgb2)); + [17, 85, 136] + .forEach((value, i) => expect(value).toBeCloseTo(rgb1[i])); +}); + +test("colors hslToRgb", () => { + const rgb1 = SVG.hslToRgb(0, 0, 100); + const rgb2 = SVG.hslToRgb(0, 100, 100); + expect(JSON.stringify(rgb1)).toBe(JSON.stringify(rgb2)); +}); + +test("colors hslToRgb 2", () => { + const colorR = SVG.hslToRgb(0, 100, 50); + [255, 0, 0].forEach((value, i) => expect(value).toBeCloseTo(colorR[i])); + const colorG = SVG.hslToRgb(120, 100, 50); + [0, 255, 0].forEach((value, i) => expect(value).toBeCloseTo(colorG[i])); + const colorB = SVG.hslToRgb(240, 100, 50); + [0, 0, 255].forEach((value, i) => expect(value).toBeCloseTo(colorB[i])); +}); + +test("parseColorToRgb", () => { + const color1 = SVG.parseColorToRgb("red"); + const color2 = SVG.parseColorToRgb("#f00"); + const color3 = SVG.parseColorToRgb("#ff0000"); + const color4 = SVG.parseColorToRgb("rgb(255, 0, 0)"); + const color5 = SVG.parseColorToRgb("hsl(0, 100%, 50%)"); + const colorAlpha1 = SVG.parseColorToRgb("rgba(255, 0, 0, 1)"); + const colorAlpha2 = SVG.parseColorToRgb("hsla(0, 100%, 50%, 1)"); + expect(JSON.stringify(color1)).toBe(JSON.stringify(color2)); + expect(JSON.stringify(color1)).toBe(JSON.stringify(color3)); + expect(JSON.stringify(color1)).toBe(JSON.stringify(color4)); + expect(JSON.stringify(color1)).toBe(JSON.stringify(color5)); + expect(JSON.stringify(colorAlpha1)).toBe(JSON.stringify(colorAlpha2)); + expect(JSON.stringify(color1)).not.toBe(JSON.stringify(colorAlpha1)); +}); + +test("empty parseColorToRgb", () => { + const color1 = SVG.parseColorToRgb("rgb()"); + const color2 = SVG.parseColorToRgb("hsl()"); + const colorAlpha1 = SVG.parseColorToRgb("rgba()"); + const colorAlpha2 = SVG.parseColorToRgb("hsla()"); + expect(JSON.stringify(color1)).toBe(JSON.stringify(color2)); + expect(JSON.stringify(color1)).toBe(JSON.stringify(colorAlpha1)); + expect(JSON.stringify(color1)).toBe(JSON.stringify(colorAlpha2)); +}); + +test("invalid parseColorToRgb", () => { + const notacolor = SVG.parseColorToRgb("notacolor"); + expect(notacolor).toBe(undefined); +}); diff --git a/tests/index.html b/tests/index.html index 09360cd..e103bc8 100755 --- a/tests/index.html +++ b/tests/index.html @@ -1,10 +1,8 @@ - + - \ No newline at end of file diff --git a/tests/window.test.js b/tests/window.test.js new file mode 100644 index 0000000..a063e0d --- /dev/null +++ b/tests/window.test.js @@ -0,0 +1,9 @@ +const { test, expect } = require("@jest/globals"); +const xmldom = require("@xmldom/xmldom"); +const SVG = require("../svg.js"); + +SVG.window = xmldom; + +test("environment", () => { + expect(true).toBe(true); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ec513c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "include": ["src/**/*"], + "exclude": ["src/primitives/**/*", "src/sketches/**/*"], + "compilerOptions": { + // Tells TypeScript to read JS files, as + // normally they are ignored as source files + "allowJs": true, + "checkJS": true, + // Generate d.ts files + "declaration": true, + // This compiler run should + // only output d.ts files + "emitDeclarationOnly": true, + // Types should go into this directory. + // Removing this would place the .d.ts files + // next to the .js files + "outDir": "types", + // go to js file when using IDE functions like + // "Go to Definition" in VSCode + // "declarationMap": true + } +} \ No newline at end of file