three-stdlib
Version:
stand-alone library of threejs examples
1,340 lines (1,339 loc) • 69.5 kB
JavaScript
import { Loader, FileLoader, Matrix3, Vector2, Vector3, ShapeUtils, Box2, Shape, Path, BufferGeometry, Float32BufferAttribute, ShapePath } from "three";
const COLOR_SPACE_SVG = "srgb";
const SVGLoader = /* @__PURE__ */ (() => {
class SVGLoader2 extends Loader {
constructor(manager) {
super(manager);
this.defaultDPI = 90;
this.defaultUnit = "px";
}
load(url, onLoad, onProgress, onError) {
const scope = this;
const loader = new FileLoader(scope.manager);
loader.setPath(scope.path);
loader.setRequestHeader(scope.requestHeader);
loader.setWithCredentials(scope.withCredentials);
loader.load(
url,
function(text) {
try {
onLoad(scope.parse(text));
} catch (e) {
if (onError) {
onError(e);
} else {
console.error(e);
}
scope.manager.itemError(url);
}
},
onProgress,
onError
);
}
parse(text) {
const scope = this;
function parseNode(node, style) {
if (node.nodeType !== 1)
return;
const transform = getNodeTransform(node);
let isDefsNode = false;
let path = null;
switch (node.nodeName) {
case "svg":
style = parseStyle(node, style);
break;
case "style":
parseCSSStylesheet(node);
break;
case "g":
style = parseStyle(node, style);
break;
case "path":
style = parseStyle(node, style);
if (node.hasAttribute("d"))
path = parsePathNode(node);
break;
case "rect":
style = parseStyle(node, style);
path = parseRectNode(node);
break;
case "polygon":
style = parseStyle(node, style);
path = parsePolygonNode(node);
break;
case "polyline":
style = parseStyle(node, style);
path = parsePolylineNode(node);
break;
case "circle":
style = parseStyle(node, style);
path = parseCircleNode(node);
break;
case "ellipse":
style = parseStyle(node, style);
path = parseEllipseNode(node);
break;
case "line":
style = parseStyle(node, style);
path = parseLineNode(node);
break;
case "defs":
isDefsNode = true;
break;
case "use":
style = parseStyle(node, style);
const href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href") || "";
const usedNodeId = href.substring(1);
const usedNode = node.viewportElement.getElementById(usedNodeId);
if (usedNode) {
parseNode(usedNode, style);
} else {
console.warn("SVGLoader: 'use node' references non-existent node id: " + usedNodeId);
}
break;
}
if (path) {
if (style.fill !== void 0 && style.fill !== "none") {
path.color.setStyle(style.fill, COLOR_SPACE_SVG);
}
transformPath(path, currentTransform);
paths.push(path);
path.userData = { node, style };
}
const childNodes = node.childNodes;
for (let i = 0; i < childNodes.length; i++) {
const node2 = childNodes[i];
if (isDefsNode && node2.nodeName !== "style" && node2.nodeName !== "defs") {
continue;
}
parseNode(node2, style);
}
if (transform) {
transformStack.pop();
if (transformStack.length > 0) {
currentTransform.copy(transformStack[transformStack.length - 1]);
} else {
currentTransform.identity();
}
}
}
function parsePathNode(node) {
const path = new ShapePath();
const point = new Vector2();
const control = new Vector2();
const firstPoint = new Vector2();
let isFirstPoint = true;
let doSetFirstPoint = false;
const d = node.getAttribute("d");
if (d === "" || d === "none")
return null;
const commands = d.match(/[a-df-z][^a-df-z]*/gi);
for (let i = 0, l = commands.length; i < l; i++) {
const command = commands[i];
const type = command.charAt(0);
const data2 = command.slice(1).trim();
if (isFirstPoint === true) {
doSetFirstPoint = true;
isFirstPoint = false;
}
let numbers;
switch (type) {
case "M":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 2) {
point.x = numbers[j + 0];
point.y = numbers[j + 1];
control.x = point.x;
control.y = point.y;
if (j === 0) {
path.moveTo(point.x, point.y);
} else {
path.lineTo(point.x, point.y);
}
if (j === 0)
firstPoint.copy(point);
}
break;
case "H":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j++) {
point.x = numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "V":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j++) {
point.y = numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "L":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 2) {
point.x = numbers[j + 0];
point.y = numbers[j + 1];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "C":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 6) {
path.bezierCurveTo(
numbers[j + 0],
numbers[j + 1],
numbers[j + 2],
numbers[j + 3],
numbers[j + 4],
numbers[j + 5]
);
control.x = numbers[j + 2];
control.y = numbers[j + 3];
point.x = numbers[j + 4];
point.y = numbers[j + 5];
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "S":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 4) {
path.bezierCurveTo(
getReflection(point.x, control.x),
getReflection(point.y, control.y),
numbers[j + 0],
numbers[j + 1],
numbers[j + 2],
numbers[j + 3]
);
control.x = numbers[j + 0];
control.y = numbers[j + 1];
point.x = numbers[j + 2];
point.y = numbers[j + 3];
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "Q":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 4) {
path.quadraticCurveTo(numbers[j + 0], numbers[j + 1], numbers[j + 2], numbers[j + 3]);
control.x = numbers[j + 0];
control.y = numbers[j + 1];
point.x = numbers[j + 2];
point.y = numbers[j + 3];
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "T":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 2) {
const rx = getReflection(point.x, control.x);
const ry = getReflection(point.y, control.y);
path.quadraticCurveTo(rx, ry, numbers[j + 0], numbers[j + 1]);
control.x = rx;
control.y = ry;
point.x = numbers[j + 0];
point.y = numbers[j + 1];
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "A":
numbers = parseFloats(data2, [3, 4], 7);
for (let j = 0, jl = numbers.length; j < jl; j += 7) {
if (numbers[j + 5] == point.x && numbers[j + 6] == point.y)
continue;
const start = point.clone();
point.x = numbers[j + 5];
point.y = numbers[j + 6];
control.x = point.x;
control.y = point.y;
parseArcCommand(
path,
numbers[j],
numbers[j + 1],
numbers[j + 2],
numbers[j + 3],
numbers[j + 4],
start,
point
);
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "m":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 2) {
point.x += numbers[j + 0];
point.y += numbers[j + 1];
control.x = point.x;
control.y = point.y;
if (j === 0) {
path.moveTo(point.x, point.y);
} else {
path.lineTo(point.x, point.y);
}
if (j === 0)
firstPoint.copy(point);
}
break;
case "h":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j++) {
point.x += numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "v":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j++) {
point.y += numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "l":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 2) {
point.x += numbers[j + 0];
point.y += numbers[j + 1];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "c":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 6) {
path.bezierCurveTo(
point.x + numbers[j + 0],
point.y + numbers[j + 1],
point.x + numbers[j + 2],
point.y + numbers[j + 3],
point.x + numbers[j + 4],
point.y + numbers[j + 5]
);
control.x = point.x + numbers[j + 2];
control.y = point.y + numbers[j + 3];
point.x += numbers[j + 4];
point.y += numbers[j + 5];
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "s":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 4) {
path.bezierCurveTo(
getReflection(point.x, control.x),
getReflection(point.y, control.y),
point.x + numbers[j + 0],
point.y + numbers[j + 1],
point.x + numbers[j + 2],
point.y + numbers[j + 3]
);
control.x = point.x + numbers[j + 0];
control.y = point.y + numbers[j + 1];
point.x += numbers[j + 2];
point.y += numbers[j + 3];
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "q":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 4) {
path.quadraticCurveTo(
point.x + numbers[j + 0],
point.y + numbers[j + 1],
point.x + numbers[j + 2],
point.y + numbers[j + 3]
);
control.x = point.x + numbers[j + 0];
control.y = point.y + numbers[j + 1];
point.x += numbers[j + 2];
point.y += numbers[j + 3];
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "t":
numbers = parseFloats(data2);
for (let j = 0, jl = numbers.length; j < jl; j += 2) {
const rx = getReflection(point.x, control.x);
const ry = getReflection(point.y, control.y);
path.quadraticCurveTo(rx, ry, point.x + numbers[j + 0], point.y + numbers[j + 1]);
control.x = rx;
control.y = ry;
point.x = point.x + numbers[j + 0];
point.y = point.y + numbers[j + 1];
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "a":
numbers = parseFloats(data2, [3, 4], 7);
for (let j = 0, jl = numbers.length; j < jl; j += 7) {
if (numbers[j + 5] == 0 && numbers[j + 6] == 0)
continue;
const start = point.clone();
point.x += numbers[j + 5];
point.y += numbers[j + 6];
control.x = point.x;
control.y = point.y;
parseArcCommand(
path,
numbers[j],
numbers[j + 1],
numbers[j + 2],
numbers[j + 3],
numbers[j + 4],
start,
point
);
if (j === 0 && doSetFirstPoint === true)
firstPoint.copy(point);
}
break;
case "Z":
case "z":
path.currentPath.autoClose = true;
if (path.currentPath.curves.length > 0) {
point.copy(firstPoint);
path.currentPath.currentPoint.copy(point);
isFirstPoint = true;
}
break;
default:
console.warn(command);
}
doSetFirstPoint = false;
}
return path;
}
function parseCSSStylesheet(node) {
if (!node.sheet || !node.sheet.cssRules || !node.sheet.cssRules.length)
return;
for (let i = 0; i < node.sheet.cssRules.length; i++) {
const stylesheet = node.sheet.cssRules[i];
if (stylesheet.type !== 1)
continue;
const selectorList = stylesheet.selectorText.split(/,/gm).filter(Boolean).map((i2) => i2.trim());
for (let j = 0; j < selectorList.length; j++) {
const definitions = Object.fromEntries(Object.entries(stylesheet.style).filter(([, v]) => v !== ""));
stylesheets[selectorList[j]] = Object.assign(stylesheets[selectorList[j]] || {}, definitions);
}
}
}
function parseArcCommand(path, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, start, end) {
if (rx == 0 || ry == 0) {
path.lineTo(end.x, end.y);
return;
}
x_axis_rotation = x_axis_rotation * Math.PI / 180;
rx = Math.abs(rx);
ry = Math.abs(ry);
const dx2 = (start.x - end.x) / 2;
const dy2 = (start.y - end.y) / 2;
const x1p = Math.cos(x_axis_rotation) * dx2 + Math.sin(x_axis_rotation) * dy2;
const y1p = -Math.sin(x_axis_rotation) * dx2 + Math.cos(x_axis_rotation) * dy2;
let rxs = rx * rx;
let rys = ry * ry;
const x1ps = x1p * x1p;
const y1ps = y1p * y1p;
const cr = x1ps / rxs + y1ps / rys;
if (cr > 1) {
const s = Math.sqrt(cr);
rx = s * rx;
ry = s * ry;
rxs = rx * rx;
rys = ry * ry;
}
const dq = rxs * y1ps + rys * x1ps;
const pq = (rxs * rys - dq) / dq;
let q = Math.sqrt(Math.max(0, pq));
if (large_arc_flag === sweep_flag)
q = -q;
const cxp = q * rx * y1p / ry;
const cyp = -q * ry * x1p / rx;
const cx = Math.cos(x_axis_rotation) * cxp - Math.sin(x_axis_rotation) * cyp + (start.x + end.x) / 2;
const cy = Math.sin(x_axis_rotation) * cxp + Math.cos(x_axis_rotation) * cyp + (start.y + end.y) / 2;
const theta = svgAngle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
const delta = svgAngle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry) % (Math.PI * 2);
path.currentPath.absellipse(cx, cy, rx, ry, theta, theta + delta, sweep_flag === 0, x_axis_rotation);
}
function svgAngle(ux, uy, vx, vy) {
const dot = ux * vx + uy * vy;
const len = Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy);
let ang = Math.acos(Math.max(-1, Math.min(1, dot / len)));
if (ux * vy - uy * vx < 0)
ang = -ang;
return ang;
}
function parseRectNode(node) {
const x = parseFloatWithUnits(node.getAttribute("x") || 0);
const y = parseFloatWithUnits(node.getAttribute("y") || 0);
const rx = parseFloatWithUnits(node.getAttribute("rx") || node.getAttribute("ry") || 0);
const ry = parseFloatWithUnits(node.getAttribute("ry") || node.getAttribute("rx") || 0);
const w = parseFloatWithUnits(node.getAttribute("width"));
const h = parseFloatWithUnits(node.getAttribute("height"));
const bci = 1 - 0.551915024494;
const path = new ShapePath();
path.moveTo(x + rx, y);
path.lineTo(x + w - rx, y);
if (rx !== 0 || ry !== 0) {
path.bezierCurveTo(x + w - rx * bci, y, x + w, y + ry * bci, x + w, y + ry);
}
path.lineTo(x + w, y + h - ry);
if (rx !== 0 || ry !== 0) {
path.bezierCurveTo(x + w, y + h - ry * bci, x + w - rx * bci, y + h, x + w - rx, y + h);
}
path.lineTo(x + rx, y + h);
if (rx !== 0 || ry !== 0) {
path.bezierCurveTo(x + rx * bci, y + h, x, y + h - ry * bci, x, y + h - ry);
}
path.lineTo(x, y + ry);
if (rx !== 0 || ry !== 0) {
path.bezierCurveTo(x, y + ry * bci, x + rx * bci, y, x + rx, y);
}
return path;
}
function parsePolygonNode(node) {
function iterator(match, a, b) {
const x = parseFloatWithUnits(a);
const y = parseFloatWithUnits(b);
if (index === 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
index++;
}
const regex = /([+-]?\d*\.?\d+(?:e[+-]?\d+)?)(?:,|\s)([+-]?\d*\.?\d+(?:e[+-]?\d+)?)/g;
const path = new ShapePath();
let index = 0;
node.getAttribute("points").replace(regex, iterator);
path.currentPath.autoClose = true;
return path;
}
function parsePolylineNode(node) {
function iterator(match, a, b) {
const x = parseFloatWithUnits(a);
const y = parseFloatWithUnits(b);
if (index === 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
index++;
}
const regex = /([+-]?\d*\.?\d+(?:e[+-]?\d+)?)(?:,|\s)([+-]?\d*\.?\d+(?:e[+-]?\d+)?)/g;
const path = new ShapePath();
let index = 0;
node.getAttribute("points").replace(regex, iterator);
path.currentPath.autoClose = false;
return path;
}
function parseCircleNode(node) {
const x = parseFloatWithUnits(node.getAttribute("cx") || 0);
const y = parseFloatWithUnits(node.getAttribute("cy") || 0);
const r = parseFloatWithUnits(node.getAttribute("r") || 0);
const subpath = new Path();
subpath.absarc(x, y, r, 0, Math.PI * 2);
const path = new ShapePath();
path.subPaths.push(subpath);
return path;
}
function parseEllipseNode(node) {
const x = parseFloatWithUnits(node.getAttribute("cx") || 0);
const y = parseFloatWithUnits(node.getAttribute("cy") || 0);
const rx = parseFloatWithUnits(node.getAttribute("rx") || 0);
const ry = parseFloatWithUnits(node.getAttribute("ry") || 0);
const subpath = new Path();
subpath.absellipse(x, y, rx, ry, 0, Math.PI * 2);
const path = new ShapePath();
path.subPaths.push(subpath);
return path;
}
function parseLineNode(node) {
const x1 = parseFloatWithUnits(node.getAttribute("x1") || 0);
const y1 = parseFloatWithUnits(node.getAttribute("y1") || 0);
const x2 = parseFloatWithUnits(node.getAttribute("x2") || 0);
const y2 = parseFloatWithUnits(node.getAttribute("y2") || 0);
const path = new ShapePath();
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.currentPath.autoClose = false;
return path;
}
function parseStyle(node, style) {
style = Object.assign({}, style);
let stylesheetStyles = {};
if (node.hasAttribute("class")) {
const classSelectors = node.getAttribute("class").split(/\s/).filter(Boolean).map((i) => i.trim());
for (let i = 0; i < classSelectors.length; i++) {
stylesheetStyles = Object.assign(stylesheetStyles, stylesheets["." + classSelectors[i]]);
}
}
if (node.hasAttribute("id")) {
stylesheetStyles = Object.assign(stylesheetStyles, stylesheets["#" + node.getAttribute("id")]);
}
function addStyle(svgName, jsName, adjustFunction) {
if (adjustFunction === void 0)
adjustFunction = function copy(v) {
if (v.startsWith("url"))
console.warn("SVGLoader: url access in attributes is not implemented.");
return v;
};
if (node.hasAttribute(svgName))
style[jsName] = adjustFunction(node.getAttribute(svgName));
if (stylesheetStyles[svgName])
style[jsName] = adjustFunction(stylesheetStyles[svgName]);
if (node.style && node.style[svgName] !== "")
style[jsName] = adjustFunction(node.style[svgName]);
}
function clamp(v) {
return Math.max(0, Math.min(1, parseFloatWithUnits(v)));
}
function positive(v) {
return Math.max(0, parseFloatWithUnits(v));
}
addStyle("fill", "fill");
addStyle("fill-opacity", "fillOpacity", clamp);
addStyle("fill-rule", "fillRule");
addStyle("opacity", "opacity", clamp);
addStyle("stroke", "stroke");
addStyle("stroke-opacity", "strokeOpacity", clamp);
addStyle("stroke-width", "strokeWidth", positive);
addStyle("stroke-linejoin", "strokeLineJoin");
addStyle("stroke-linecap", "strokeLineCap");
addStyle("stroke-miterlimit", "strokeMiterLimit", positive);
addStyle("visibility", "visibility");
return style;
}
function getReflection(a, b) {
return a - (b - a);
}
function parseFloats(input, flags, stride) {
if (typeof input !== "string") {
throw new TypeError("Invalid input: " + typeof input);
}
const RE = {
SEPARATOR: /[ \t\r\n\,.\-+]/,
WHITESPACE: /[ \t\r\n]/,
DIGIT: /[\d]/,
SIGN: /[-+]/,
POINT: /\./,
COMMA: /,/,
EXP: /e/i,
FLAGS: /[01]/
};
const SEP = 0;
const INT = 1;
const FLOAT = 2;
const EXP = 3;
let state = SEP;
let seenComma = true;
let number = "", exponent = "";
const result = [];
function throwSyntaxError(current2, i, partial) {
const error = new SyntaxError('Unexpected character "' + current2 + '" at index ' + i + ".");
error.partial = partial;
throw error;
}
function newNumber() {
if (number !== "") {
if (exponent === "")
result.push(Number(number));
else
result.push(Number(number) * Math.pow(10, Number(exponent)));
}
number = "";
exponent = "";
}
let current;
const length = input.length;
for (let i = 0; i < length; i++) {
current = input[i];
if (Array.isArray(flags) && flags.includes(result.length % stride) && RE.FLAGS.test(current)) {
state = INT;
number = current;
newNumber();
continue;
}
if (state === SEP) {
if (RE.WHITESPACE.test(current)) {
continue;
}
if (RE.DIGIT.test(current) || RE.SIGN.test(current)) {
state = INT;
number = current;
continue;
}
if (RE.POINT.test(current)) {
state = FLOAT;
number = current;
continue;
}
if (RE.COMMA.test(current)) {
if (seenComma) {
throwSyntaxError(current, i, result);
}
seenComma = true;
}
}
if (state === INT) {
if (RE.DIGIT.test(current)) {
number += current;
continue;
}
if (RE.POINT.test(current)) {
number += current;
state = FLOAT;
continue;
}
if (RE.EXP.test(current)) {
state = EXP;
continue;
}
if (RE.SIGN.test(current) && number.length === 1 && RE.SIGN.test(number[0])) {
throwSyntaxError(current, i, result);
}
}
if (state === FLOAT) {
if (RE.DIGIT.test(current)) {
number += current;
continue;
}
if (RE.EXP.test(current)) {
state = EXP;
continue;
}
if (RE.POINT.test(current) && number[number.length - 1] === ".") {
throwSyntaxError(current, i, result);
}
}
if (state === EXP) {
if (RE.DIGIT.test(current)) {
exponent += current;
continue;
}
if (RE.SIGN.test(current)) {
if (exponent === "") {
exponent += current;
continue;
}
if (exponent.length === 1 && RE.SIGN.test(exponent)) {
throwSyntaxError(current, i, result);
}
}
}
if (RE.WHITESPACE.test(current)) {
newNumber();
state = SEP;
seenComma = false;
} else if (RE.COMMA.test(current)) {
newNumber();
state = SEP;
seenComma = true;
} else if (RE.SIGN.test(current)) {
newNumber();
state = INT;
number = current;
} else if (RE.POINT.test(current)) {
newNumber();
state = FLOAT;
number = current;
} else {
throwSyntaxError(current, i, result);
}
}
newNumber();
return result;
}
const units = ["mm", "cm", "in", "pt", "pc", "px"];
const unitConversion = {
mm: {
mm: 1,
cm: 0.1,
in: 1 / 25.4,
pt: 72 / 25.4,
pc: 6 / 25.4,
px: -1
},
cm: {
mm: 10,
cm: 1,
in: 1 / 2.54,
pt: 72 / 2.54,
pc: 6 / 2.54,
px: -1
},
in: {
mm: 25.4,
cm: 2.54,
in: 1,
pt: 72,
pc: 6,
px: -1
},
pt: {
mm: 25.4 / 72,
cm: 2.54 / 72,
in: 1 / 72,
pt: 1,
pc: 6 / 72,
px: -1
},
pc: {
mm: 25.4 / 6,
cm: 2.54 / 6,
in: 1 / 6,
pt: 72 / 6,
pc: 1,
px: -1
},
px: {
px: 1
}
};
function parseFloatWithUnits(string) {
let theUnit = "px";
if (typeof string === "string" || string instanceof String) {
for (let i = 0, n = units.length; i < n; i++) {
const u = units[i];
if (string.endsWith(u)) {
theUnit = u;
string = string.substring(0, string.length - u.length);
break;
}
}
}
let scale = void 0;
if (theUnit === "px" && scope.defaultUnit !== "px") {
scale = unitConversion["in"][scope.defaultUnit] / scope.defaultDPI;
} else {
scale = unitConversion[theUnit][scope.defaultUnit];
if (scale < 0) {
scale = unitConversion[theUnit]["in"] * scope.defaultDPI;
}
}
return scale * parseFloat(string);
}
function getNodeTransform(node) {
if (!(node.hasAttribute("transform") || node.nodeName === "use" && (node.hasAttribute("x") || node.hasAttribute("y")))) {
return null;
}
const transform = parseNodeTransform(node);
if (transformStack.length > 0) {
transform.premultiply(transformStack[transformStack.length - 1]);
}
currentTransform.copy(transform);
transformStack.push(transform);
return transform;
}
function parseNodeTransform(node) {
const transform = new Matrix3();
const currentTransform2 = tempTransform0;
if (node.nodeName === "use" && (node.hasAttribute("x") || node.hasAttribute("y"))) {
const tx = parseFloatWithUnits(node.getAttribute("x"));
const ty = parseFloatWithUnits(node.getAttribute("y"));
transform.translate(tx, ty);
}
if (node.hasAttribute("transform")) {
const transformsTexts = node.getAttribute("transform").split(")");
for (let tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex--) {
const transformText = transformsTexts[tIndex].trim();
if (transformText === "")
continue;
const openParPos = transformText.indexOf("(");
const closeParPos = transformText.length;
if (openParPos > 0 && openParPos < closeParPos) {
const transformType = transformText.slice(0, openParPos);
const array = parseFloats(transformText.slice(openParPos + 1));
currentTransform2.identity();
switch (transformType) {
case "translate":
if (array.length >= 1) {
const tx = array[0];
let ty = 0;
if (array.length >= 2) {
ty = array[1];
}
currentTransform2.translate(tx, ty);
}
break;
case "rotate":
if (array.length >= 1) {
let angle = 0;
let cx = 0;
let cy = 0;
angle = array[0] * Math.PI / 180;
if (array.length >= 3) {
cx = array[1];
cy = array[2];
}
tempTransform1.makeTranslation(-cx, -cy);
tempTransform2.makeRotation(angle);
tempTransform3.multiplyMatrices(tempTransform2, tempTransform1);
tempTransform1.makeTranslation(cx, cy);
currentTransform2.multiplyMatrices(tempTransform1, tempTransform3);
}
break;
case "scale":
if (array.length >= 1) {
const scaleX = array[0];
let scaleY = scaleX;
if (array.length >= 2) {
scaleY = array[1];
}
currentTransform2.scale(scaleX, scaleY);
}
break;
case "skewX":
if (array.length === 1) {
currentTransform2.set(1, Math.tan(array[0] * Math.PI / 180), 0, 0, 1, 0, 0, 0, 1);
}
break;
case "skewY":
if (array.length === 1) {
currentTransform2.set(1, 0, 0, Math.tan(array[0] * Math.PI / 180), 1, 0, 0, 0, 1);
}
break;
case "matrix":
if (array.length === 6) {
currentTransform2.set(array[0], array[2], array[4], array[1], array[3], array[5], 0, 0, 1);
}
break;
}
}
transform.premultiply(currentTransform2);
}
}
return transform;
}
function transformPath(path, m) {
function transfVec2(v2) {
tempV3.set(v2.x, v2.y, 1).applyMatrix3(m);
v2.set(tempV3.x, tempV3.y);
}
function transfEllipseGeneric(curve) {
const a = curve.xRadius;
const b = curve.yRadius;
const cosTheta = Math.cos(curve.aRotation);
const sinTheta = Math.sin(curve.aRotation);
const v1 = new Vector3(a * cosTheta, a * sinTheta, 0);
const v2 = new Vector3(-b * sinTheta, b * cosTheta, 0);
const f1 = v1.applyMatrix3(m);
const f2 = v2.applyMatrix3(m);
const mF = tempTransform0.set(f1.x, f2.x, 0, f1.y, f2.y, 0, 0, 0, 1);
const mFInv = tempTransform1.copy(mF).invert();
const mFInvT = tempTransform2.copy(mFInv).transpose();
const mQ = mFInvT.multiply(mFInv);
const mQe = mQ.elements;
const ed = eigenDecomposition(mQe[0], mQe[1], mQe[4]);
const rt1sqrt = Math.sqrt(ed.rt1);
const rt2sqrt = Math.sqrt(ed.rt2);
curve.xRadius = 1 / rt1sqrt;
curve.yRadius = 1 / rt2sqrt;
curve.aRotation = Math.atan2(ed.sn, ed.cs);
const isFullEllipse = (curve.aEndAngle - curve.aStartAngle) % (2 * Math.PI) < Number.EPSILON;
if (!isFullEllipse) {
const mDsqrt = tempTransform1.set(rt1sqrt, 0, 0, 0, rt2sqrt, 0, 0, 0, 1);
const mRT = tempTransform2.set(ed.cs, ed.sn, 0, -ed.sn, ed.cs, 0, 0, 0, 1);
const mDRF = mDsqrt.multiply(mRT).multiply(mF);
const transformAngle = (phi) => {
const { x: cosR, y: sinR } = new Vector3(Math.cos(phi), Math.sin(phi), 0).applyMatrix3(mDRF);
return Math.atan2(sinR, cosR);
};
curve.aStartAngle = transformAngle(curve.aStartAngle);
curve.aEndAngle = transformAngle(curve.aEndAngle);
if (isTransformFlipped(m)) {
curve.aClockwise = !curve.aClockwise;
}
}
}
function transfEllipseNoSkew(curve) {
const sx = getTransformScaleX(m);
const sy = getTransformScaleY(m);
curve.xRadius *= sx;
curve.yRadius *= sy;
const theta = sx > Number.EPSILON ? Math.atan2(m.elements[1], m.elements[0]) : Math.atan2(-m.elements[3], m.elements[4]);
curve.aRotation += theta;
if (isTransformFlipped(m)) {
curve.aStartAngle *= -1;
curve.aEndAngle *= -1;
curve.aClockwise = !curve.aClockwise;
}
}
const subPaths = path.subPaths;
for (let i = 0, n = subPaths.length; i < n; i++) {
const subPath = subPaths[i];
const curves = subPath.curves;
for (let j = 0; j < curves.length; j++) {
const curve = curves[j];
if (curve.isLineCurve) {
transfVec2(curve.v1);
transfVec2(curve.v2);
} else if (curve.isCubicBezierCurve) {
transfVec2(curve.v0);
transfVec2(curve.v1);
transfVec2(curve.v2);
transfVec2(curve.v3);
} else if (curve.isQuadraticBezierCurve) {
transfVec2(curve.v0);
transfVec2(curve.v1);
transfVec2(curve.v2);
} else if (curve.isEllipseCurve) {
tempV2.set(curve.aX, curve.aY);
transfVec2(tempV2);
curve.aX = tempV2.x;
curve.aY = tempV2.y;
if (isTransformSkewed(m)) {
transfEllipseGeneric(curve);
} else {
transfEllipseNoSkew(curve);
}
}
}
}
}
function isTransformFlipped(m) {
const te = m.elements;
return te[0] * te[4] - te[1] * te[3] < 0;
}
function isTransformSkewed(m) {
const te = m.elements;
const basisDot = te[0] * te[3] + te[1] * te[4];
if (basisDot === 0)
return false;
const sx = getTransformScaleX(m);
const sy = getTransformScaleY(m);
return Math.abs(basisDot / (sx * sy)) > Number.EPSILON;
}
function getTransformScaleX(m) {
const te = m.elements;
return Math.sqrt(te[0] * te[0] + te[1] * te[1]);
}
function getTransformScaleY(m) {
const te = m.elements;
return Math.sqrt(te[3] * te[3] + te[4] * te[4]);
}
function eigenDecomposition(A, B, C) {
let rt1, rt2, cs, sn, t;
const sm = A + C;
const df = A - C;
const rt = Math.sqrt(df * df + 4 * B * B);
if (sm > 0) {
rt1 = 0.5 * (sm + rt);
t = 1 / rt1;
rt2 = A * t * C - B * t * B;
} else if (sm < 0) {
rt2 = 0.5 * (sm - rt);
} else {
rt1 = 0.5 * rt;
rt2 = -0.5 * rt;
}
if (df > 0) {
cs = df + rt;
} else {
cs = df - rt;
}
if (Math.abs(cs) > 2 * Math.abs(B)) {
t = -2 * B / cs;
sn = 1 / Math.sqrt(1 + t * t);
cs = t * sn;
} else if (Math.abs(B) === 0) {
cs = 1;
sn = 0;
} else {
t = -0.5 * cs / B;
cs = 1 / Math.sqrt(1 + t * t);
sn = t * cs;
}
if (df > 0) {
t = cs;
cs = -sn;
sn = t;
}
return { rt1, rt2, cs, sn };
}
const paths = [];
const stylesheets = {};
const transformStack = [];
const tempTransform0 = new Matrix3();
const tempTransform1 = new Matrix3();
const tempTransform2 = new Matrix3();
const tempTransform3 = new Matrix3();
const tempV2 = new Vector2();
const tempV3 = new Vector3();
const currentTransform = new Matrix3();
const xml = new DOMParser().parseFromString(text, "image/svg+xml");
parseNode(xml.documentElement, {
fill: "#000",
fillOpacity: 1,
strokeOpacity: 1,
strokeWidth: 1,
strokeLineJoin: "miter",
strokeLineCap: "butt",
strokeMiterLimit: 4
});
const data = { paths, xml: xml.documentElement };
return data;
}
static createShapes(shapePath) {
const BIGNUMBER = 999999999;
const IntersectionLocationType = {
ORIGIN: 0,
DESTINATION: 1,
BETWEEN: 2,
LEFT: 3,
RIGHT: 4,
BEHIND: 5,
BEYOND: 6
};
const classifyResult = {
loc: IntersectionLocationType.ORIGIN,
t: 0
};
function findEdgeIntersection(a0, a1, b0, b1) {
const x1 = a0.x;
const x2 = a1.x;
const x3 = b0.x;
const x4 = b1.x;
const y1 = a0.y;
const y2 = a1.y;
const y3 = b0.y;
const y4 = b1.y;
const nom1 = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
const nom2 = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
const t1 = nom1 / denom;
const t2 = nom2 / denom;
if (denom === 0 && nom1 !== 0 || t1 <= 0 || t1 >= 1 || t2 < 0 || t2 > 1) {
return null;
} else if (nom1 === 0 && denom === 0) {
for (let i = 0; i < 2; i++) {
classifyPoint(i === 0 ? b0 : b1, a0, a1);
if (classifyResult.loc == IntersectionLocationType.ORIGIN) {
const point = i === 0 ? b0 : b1;
return { x: point.x, y: point.y, t: classifyResult.t };
} else if (classifyResult.loc == IntersectionLocationType.BETWEEN) {
const x = +(x1 + classifyResult.t * (x2 - x1)).toPrecision(10);
const y = +(y1 + classifyResult.t * (y2 - y1)).toPrecision(10);
return { x, y, t: classifyResult.t };
}
}
return null;
} else {
for (let i = 0; i < 2; i++) {
classifyPoint(i === 0 ? b0 : b1, a0, a1);
if (classifyResult.loc == IntersectionLocationType.ORIGIN) {
const point = i === 0 ? b0 : b1;
return { x: point.x, y: point.y, t: classifyResult.t };
}
}
const x = +(x1 + t1 * (x2 - x1)).toPrecision(10);
const y = +(y1 + t1 * (y2 - y1)).toPrecision(10);
return { x, y, t: t1 };
}
}
function classifyPoint(p, edgeStart, edgeEnd) {
const ax = edgeEnd.x - edgeStart.x;
const ay = edgeEnd.y - edgeStart.y;
const bx = p.x - edgeStart.x;
const by = p.y - edgeStart.y;
const sa = ax * by - bx * ay;
if (p.x === edgeStart.x && p.y === edgeStart.y) {
classifyResult.loc = IntersectionLocationType.ORIGIN;
classifyResult.t = 0;
return;
}
if (p.x === edgeEnd.x && p.y === edgeEnd.y) {
classifyResult.loc = IntersectionLocationType.DESTINATION;
classifyResult.t = 1;
return;
}
if (sa < -Number.EPSILON) {
classifyResult.loc = IntersectionLocationType.LEFT;
return;
}
if (sa > Number.EPSILON) {
classifyResult.loc = IntersectionLocationType.RIGHT;
return;
}
if (ax * bx < 0 || ay * by < 0) {
classifyResult.loc = IntersectionLocationType.BEHIND;
return;
}
if (Math.sqrt(ax * ax + ay * ay) < Math.sqrt(bx * bx + by * by)) {
classifyResult.loc = IntersectionLocationType.BEYOND;
return;
}
let t;
if (ax !== 0) {
t = bx / ax;
} else {
t = by / ay;
}
classifyResult.loc = IntersectionLocationType.BETWEEN;
classifyResult.t = t;
}
function getIntersections(path1, path2) {
const intersectionsRaw = [];
const intersections = [];
for (let index = 1; index < path1.length; index++) {
const path1EdgeStart = path1[index - 1];
const path1EdgeEnd = path1[index];
for (let index2 = 1; index2 < path2.length; index2++) {
const path2EdgeStart = path2[index2 - 1];
const path2EdgeEnd = path2[index2];
const intersection = findEdgeIntersection(path1EdgeStart, path1EdgeEnd, path2EdgeStart, path2EdgeEnd);
if (intersection !== null && intersectionsRaw.find(
(i) => i.t <= intersection.t + Number.EPSILON && i.t >= intersection.t - Number.EPSILON
) === void 0) {
intersectionsRaw.push(intersection);
intersections.push(new Vector2(intersection.x, intersection.y));
}
}
}
return intersections;
}
function getScanlineIntersections(scanline, boundingBox, paths) {
const center = new Vector2();
boundingBox.getCenter(center);
const allIntersections = [];
paths.forEach((path) => {
if (path.boundingBox.containsPoint(center)) {
const intersections = getIntersections(scanline, path.points);
intersections.forEach((p) => {
allIntersections.push({ identifier: path.identifier, isCW: path.isCW, point: p });
});
}
});
allIntersections.sort((i1, i2) => {
return i1.point.x - i2.point.x;
});
return allIntersections;
}
function isHoleTo(simplePath, allPaths, scanlineMinX2, scanlineMaxX2, _fillRule) {
if (_fillRule === null || _fillRule === void 0 || _fillRule === "") {
_fillRule = "nonzero";
}
const centerBoundingBox = new Vector2();
simplePath.boundingBox.getCenter(centerBoundingBox);
const scanline = [
new Vector2(scanlineMinX2, centerBoundingBox.y),
new Vector2(scanlineMaxX2, centerBoundingBox.y)
];
const scanlineIntersections = getScanlineIntersections(scanline, simplePath.boundingBox, allPaths);
scanlineIntersections.sort((i1, i2) => {
return i1.point.x - i2.point.x;
});
const baseIntersections = [];
const otherIntersections = [];
scanlineIntersections.forEach((i2) => {
if (i2.identifier === simplePath.identifier) {
baseIntersections.push(i2);
} else {
otherIntersections.push(i2);
}
});
const firstXOfPath = baseIntersections[0].point.x;
const stack = [];
let i = 0;
while (i < otherIntersections.length && otherIntersections[i].point.x < firstXOfPath) {
if (stack.length > 0 && stack[stack.length - 1] === otherIntersections[i].identifier) {
stack.pop();
} else {
stack.push(otherIntersections[i].identifier);
}
i++;
}
stack.push(simplePath.identifier);
if (_fillRule === "evenodd") {
const isHole = stack.length % 2 === 0 ? true : false;
const isHoleFor = stack[stack.length - 2];
return { identifier: simplePath.identifier, isHole, for: isHoleFor };
} else if (_fillRule === "nonzero") {
let isHole = true;
let isHoleFor = null;
let lastCWValue = null;
for (let i2 = 0; i2 < stack.length; i2++) {
const identifier = stack[i2];
if (isHole) {
lastCWValue = allPaths[identifier].isCW;
isHole = false;
isHoleFor = identifier;
} else if (lastCWValue !== allPaths[identifier].isCW) {
lastCWValue = allPaths[identifier].isCW;
isHole = true;
}
}
return { identifier: simplePath.identifier, isHole, for: isHoleFor };
} else {
console.warn('fill-rule: "' + _fillRule + '" is currently not implemented.');
}
}
let scanlineMinX = BIGNUMBER;
let scanlineMaxX = -BIGNUMBER;
let simplePaths = shapePath.subPaths.map((p) => {
const points = p.getPoints();
let maxY = -BIGNUMBER;
let minY = BIGNUMBER;
let maxX = -BIGNUMBER;
let minX = BIGNUMBER;
for (let i = 0; i < points.length; i++) {
const p2 = points[i];
if (p2.y > maxY) {
maxY = p2.y;
}
if (p2.y < minY) {
minY = p2.y;
}
if (p2.x > maxX) {
maxX = p2.x;
}
if (p2.x < minX) {
minX = p2.x;
}
}
if (scanlineMaxX <= maxX) {
scanlineMaxX = maxX + 1;
}
if (scanlineMinX >= minX) {
scanlineMinX = minX - 1;
}
return {
curves: p.curves,
points,
isCW: ShapeUtils.isClockWise(points),
identifier: -1,
boundingBox: new Box2(new Vector2(minX, minY), new Vector2(maxX, maxY))
};
});