all these changes

This commit is contained in:
Jake Kasper
2026-04-09 13:19:47 -05:00
parent e83a51a051
commit 65315f36d1
39102 changed files with 7932979 additions and 567 deletions

90
frontend/node_modules/svg-pathdata/src/SVGPathData.ts generated vendored Normal file
View File

@@ -0,0 +1,90 @@
import { encodeSVGPath } from "./SVGPathDataEncoder";
import { SVGPathDataParser } from "./SVGPathDataParser";
import { SVGPathDataTransformer } from "./SVGPathDataTransformer";
import { TransformableSVG } from "./TransformableSVG";
import { SVGCommand } from "./types";
export class SVGPathData extends TransformableSVG {
commands: SVGCommand[];
constructor(content: string | SVGCommand[]) {
super();
if ("string" === typeof content) {
this.commands = SVGPathData.parse(content);
} else {
this.commands = content;
}
}
encode() {
return SVGPathData.encode(this.commands);
}
getBounds() {
const boundsTransform = SVGPathDataTransformer.CALCULATE_BOUNDS();
this.transform(boundsTransform);
return boundsTransform;
}
transform(
transformFunction: (input: SVGCommand) => SVGCommand | SVGCommand[],
) {
const newCommands = [];
for (const command of this.commands) {
const transformedCommand = transformFunction(command);
if (Array.isArray(transformedCommand)) {
newCommands.push(...transformedCommand);
} else {
newCommands.push(transformedCommand);
}
}
this.commands = newCommands;
return this;
}
static encode(commands: SVGCommand[]) {
return encodeSVGPath(commands);
}
static parse(path: string) {
const parser = new SVGPathDataParser();
const commands: SVGCommand[] = [];
parser.parse(path, commands);
parser.finish(commands);
return commands;
}
static readonly CLOSE_PATH: 1 = 1;
static readonly MOVE_TO: 2 = 2;
static readonly HORIZ_LINE_TO: 4 = 4;
static readonly VERT_LINE_TO: 8 = 8;
static readonly LINE_TO: 16 = 16;
static readonly CURVE_TO: 32 = 32;
static readonly SMOOTH_CURVE_TO: 64 = 64;
static readonly QUAD_TO: 128 = 128;
static readonly SMOOTH_QUAD_TO: 256 = 256;
static readonly ARC: 512 = 512;
static readonly LINE_COMMANDS = SVGPathData.LINE_TO | SVGPathData.HORIZ_LINE_TO | SVGPathData.VERT_LINE_TO;
static readonly DRAWING_COMMANDS = SVGPathData.HORIZ_LINE_TO | SVGPathData.VERT_LINE_TO | SVGPathData.LINE_TO |
SVGPathData.CURVE_TO | SVGPathData.SMOOTH_CURVE_TO | SVGPathData.QUAD_TO |
SVGPathData.SMOOTH_QUAD_TO | SVGPathData.ARC;
}
export const COMMAND_ARG_COUNTS = {
[SVGPathData.MOVE_TO]: 2,
[SVGPathData.LINE_TO]: 2,
[SVGPathData.HORIZ_LINE_TO]: 1,
[SVGPathData.VERT_LINE_TO]: 1,
[SVGPathData.CLOSE_PATH]: 0,
[SVGPathData.QUAD_TO]: 4,
[SVGPathData.SMOOTH_QUAD_TO]: 2,
[SVGPathData.CURVE_TO]: 6,
[SVGPathData.SMOOTH_CURVE_TO]: 4,
[SVGPathData.ARC]: 7,
};
export {encodeSVGPath} from "./SVGPathDataEncoder";
export {SVGPathDataParser} from "./SVGPathDataParser";
export {SVGPathDataTransformer} from "./SVGPathDataTransformer";

View File

@@ -0,0 +1,62 @@
import { SVGPathData } from "./SVGPathData";
import { SVGCommand } from "./types";
// Encode SVG PathData
// http://www.w3.org/TR/SVG/paths.html#PathDataBNF
// Private consts : Char groups
const WSP = " ";
export function encodeSVGPath(commands: SVGCommand | SVGCommand[]) {
let str = "";
if (!Array.isArray(commands)) {
commands = [commands];
}
for (let i = 0; i < commands.length; i++) {
const command = commands[i];
if (command.type === SVGPathData.CLOSE_PATH) {
str += "z";
} else if (command.type === SVGPathData.HORIZ_LINE_TO) {
str += (command.relative ? "h" : "H") +
command.x;
} else if (command.type === SVGPathData.VERT_LINE_TO) {
str += (command.relative ? "v" : "V") +
command.y;
} else if (command.type === SVGPathData.MOVE_TO) {
str += (command.relative ? "m" : "M") +
command.x + WSP + command.y;
} else if (command.type === SVGPathData.LINE_TO) {
str += (command.relative ? "l" : "L") +
command.x + WSP + command.y;
} else if (command.type === SVGPathData.CURVE_TO) {
str += (command.relative ? "c" : "C") +
command.x1 + WSP + command.y1 +
WSP + command.x2 + WSP + command.y2 +
WSP + command.x + WSP + command.y;
} else if (command.type === SVGPathData.SMOOTH_CURVE_TO) {
str += (command.relative ? "s" : "S") +
command.x2 + WSP + command.y2 +
WSP + command.x + WSP + command.y;
} else if (command.type === SVGPathData.QUAD_TO) {
str += (command.relative ? "q" : "Q") +
command.x1 + WSP + command.y1 +
WSP + command.x + WSP + command.y;
} else if (command.type === SVGPathData.SMOOTH_QUAD_TO) {
str += (command.relative ? "t" : "T") +
command.x + WSP + command.y;
} else if (command.type === SVGPathData.ARC) {
str += (command.relative ? "a" : "A") +
command.rX + WSP + command.rY +
WSP + command.xRot +
WSP + (+command.lArcFlag) + WSP + (+command.sweepFlag) +
WSP + command.x + WSP + command.y;
} else {
// Unknown command
throw new Error(
`Unexpected command type "${ (command as any).type}" at index ${i}.`);
}
}
return str;
}

View File

@@ -0,0 +1,290 @@
// Parse SVG PathData
// http://www.w3.org/TR/SVG/paths.html#PathDataBNF
import { COMMAND_ARG_COUNTS, SVGPathData } from "./SVGPathData";
import { TransformableSVG } from "./TransformableSVG";
import { SVGCommand, TransformFunction } from "./types";
// Private consts : Char groups
const isWhiteSpace = (c: string) =>
" " === c || "\t" === c || "\r" === c || "\n" === c;
const isDigit = (c: string) =>
"0".charCodeAt(0) <= c.charCodeAt(0) && c.charCodeAt(0) <= "9".charCodeAt(0);
const COMMANDS = "mMzZlLhHvVcCsSqQtTaA";
export class SVGPathDataParser extends TransformableSVG {
private curNumber: string = "";
private curCommandType: SVGCommand["type"] | -1 = -1;
private curCommandRelative = false;
private canParseCommandOrComma = true;
private curNumberHasExp = false;
private curNumberHasExpDigits = false;
private curNumberHasDecimal = false;
private curArgs: number[] = [];
constructor() {
super();
}
finish(commands: SVGCommand[] = []) {
this.parse(" ", commands);
// Adding residual command
if (0 !== this.curArgs.length || !this.canParseCommandOrComma) {
throw new SyntaxError("Unterminated command at the path end.");
}
return commands;
}
parse(str: string, commands: SVGCommand[] = []) {
const finishCommand = (command: SVGCommand) => {
commands.push(command);
this.curArgs.length = 0;
this.canParseCommandOrComma = true;
};
for (let i = 0; i < str.length; i++) {
const c = str[i];
// White spaces parsing
const isAArcFlag = this.curCommandType === SVGPathData.ARC &&
(this.curArgs.length === 3 || this.curArgs.length === 4) &&
this.curNumber.length === 1 &&
(this.curNumber === "0" || this.curNumber === "1");
const isEndingDigit = isDigit(c) && (
(this.curNumber === "0" && c === "0") ||
isAArcFlag
);
if (
isDigit(c) &&
!isEndingDigit
) {
this.curNumber += c;
this.curNumberHasExpDigits = this.curNumberHasExp;
continue;
}
if ("e" === c || "E" === c) {
this.curNumber += c;
this.curNumberHasExp = true;
continue;
}
if (
("-" === c || "+" === c) &&
this.curNumberHasExp &&
!this.curNumberHasExpDigits
) {
this.curNumber += c;
continue;
}
// if we already have a ".", it means we are starting a new number
if ("." === c && !this.curNumberHasExp && !this.curNumberHasDecimal && !isAArcFlag) {
this.curNumber += c;
this.curNumberHasDecimal = true;
continue;
}
// New number
if (this.curNumber && -1 !== this.curCommandType) {
const val = Number(this.curNumber);
if (isNaN(val)) {
throw new SyntaxError(`Invalid number ending at ${i}`);
}
if (this.curCommandType === SVGPathData.ARC) {
if (0 === this.curArgs.length || 1 === this.curArgs.length) {
if (0 > val) {
throw new SyntaxError(
`Expected positive number, got "${val}" at index "${i}"`,
);
}
} else if (3 === this.curArgs.length || 4 === this.curArgs.length) {
if ("0" !== this.curNumber && "1" !== this.curNumber) {
throw new SyntaxError(
`Expected a flag, got "${this.curNumber}" at index "${i}"`,
);
}
}
}
this.curArgs.push(val);
if (this.curArgs.length === COMMAND_ARG_COUNTS[this.curCommandType]) {
if (SVGPathData.HORIZ_LINE_TO === this.curCommandType) {
finishCommand({
type: SVGPathData.HORIZ_LINE_TO,
relative: this.curCommandRelative,
x: val,
});
} else if (SVGPathData.VERT_LINE_TO === this.curCommandType) {
finishCommand({
type: SVGPathData.VERT_LINE_TO,
relative: this.curCommandRelative,
y: val,
});
// Move to / line to / smooth quadratic curve to commands (x, y)
} else if (
this.curCommandType === SVGPathData.MOVE_TO ||
this.curCommandType === SVGPathData.LINE_TO ||
this.curCommandType === SVGPathData.SMOOTH_QUAD_TO
) {
finishCommand({
type: this.curCommandType,
relative: this.curCommandRelative,
x: this.curArgs[0],
y: this.curArgs[1],
} as SVGCommand);
// Switch to line to state
if (SVGPathData.MOVE_TO === this.curCommandType) {
this.curCommandType = SVGPathData.LINE_TO;
}
} else if (this.curCommandType === SVGPathData.CURVE_TO) {
finishCommand({
type: SVGPathData.CURVE_TO,
relative: this.curCommandRelative,
x1: this.curArgs[0],
y1: this.curArgs[1],
x2: this.curArgs[2],
y2: this.curArgs[3],
x: this.curArgs[4],
y: this.curArgs[5],
});
} else if (this.curCommandType === SVGPathData.SMOOTH_CURVE_TO) {
finishCommand({
type: SVGPathData.SMOOTH_CURVE_TO,
relative: this.curCommandRelative,
x2: this.curArgs[0],
y2: this.curArgs[1],
x: this.curArgs[2],
y: this.curArgs[3],
});
} else if (this.curCommandType === SVGPathData.QUAD_TO) {
finishCommand({
type: SVGPathData.QUAD_TO,
relative: this.curCommandRelative,
x1: this.curArgs[0],
y1: this.curArgs[1],
x: this.curArgs[2],
y: this.curArgs[3],
});
} else if (this.curCommandType === SVGPathData.ARC) {
finishCommand({
type: SVGPathData.ARC,
relative: this.curCommandRelative,
rX: this.curArgs[0],
rY: this.curArgs[1],
xRot: this.curArgs[2],
lArcFlag: this.curArgs[3] as 0 | 1,
sweepFlag: this.curArgs[4] as 0 | 1,
x: this.curArgs[5],
y: this.curArgs[6],
});
}
}
this.curNumber = "";
this.curNumberHasExpDigits = false;
this.curNumberHasExp = false;
this.curNumberHasDecimal = false;
this.canParseCommandOrComma = true;
}
// Continue if a white space or a comma was detected
if (isWhiteSpace(c)) {
continue;
}
if ("," === c && this.canParseCommandOrComma) {
// L 0,0, H is not valid:
this.canParseCommandOrComma = false;
continue;
}
// if a sign is detected, then parse the new number
if ("+" === c || "-" === c || "." === c) {
this.curNumber = c;
this.curNumberHasDecimal = "." === c;
continue;
}
// if a 0 is detected, then parse the new number
if (isEndingDigit) {
this.curNumber = c;
this.curNumberHasDecimal = false;
continue;
}
// Adding residual command
if (0 !== this.curArgs.length) {
throw new SyntaxError(`Unterminated command at index ${i}.`);
}
if (!this.canParseCommandOrComma) {
throw new SyntaxError(
`Unexpected character "${c}" at index ${i}. Command cannot follow comma`,
);
}
this.canParseCommandOrComma = false;
// Detecting the next command
if ("z" === c || "Z" === c) {
commands.push({
type: SVGPathData.CLOSE_PATH,
});
this.canParseCommandOrComma = true;
this.curCommandType = -1;
continue;
// Horizontal move to command
} else if ("h" === c || "H" === c) {
this.curCommandType = SVGPathData.HORIZ_LINE_TO;
this.curCommandRelative = "h" === c;
// Vertical move to command
} else if ("v" === c || "V" === c) {
this.curCommandType = SVGPathData.VERT_LINE_TO;
this.curCommandRelative = "v" === c;
// Move to command
} else if ("m" === c || "M" === c) {
this.curCommandType = SVGPathData.MOVE_TO;
this.curCommandRelative = "m" === c;
// Line to command
} else if ("l" === c || "L" === c) {
this.curCommandType = SVGPathData.LINE_TO;
this.curCommandRelative = "l" === c;
// Curve to command
} else if ("c" === c || "C" === c) {
this.curCommandType = SVGPathData.CURVE_TO;
this.curCommandRelative = "c" === c;
// Smooth curve to command
} else if ("s" === c || "S" === c) {
this.curCommandType = SVGPathData.SMOOTH_CURVE_TO;
this.curCommandRelative = "s" === c;
// Quadratic bezier curve to command
} else if ("q" === c || "Q" === c) {
this.curCommandType = SVGPathData.QUAD_TO;
this.curCommandRelative = "q" === c;
// Smooth quadratic bezier curve to command
} else if ("t" === c || "T" === c) {
this.curCommandType = SVGPathData.SMOOTH_QUAD_TO;
this.curCommandRelative = "t" === c;
// Elliptic arc command
} else if ("a" === c || "A" === c) {
this.curCommandType = SVGPathData.ARC;
this.curCommandRelative = "a" === c;
} else {
throw new SyntaxError(`Unexpected character "${c}" at index ${i}.`);
}
}
return commands;
}
/**
* Return a wrapper around this parser which applies the transformation on parsed commands.
*/
transform(transform: TransformFunction) {
const result = Object.create(this, {
parse: {
value(chunk: string, commands: SVGCommand[] = []) {
const parsedCommands = Object.getPrototypeOf(this).parse.call(
this,
chunk,
);
for (const c of parsedCommands) {
const cT = transform(c);
if (Array.isArray(cT)) {
commands.push(...cT);
} else {
commands.push(cT);
}
}
return commands;
},
},
});
return result as this;
}
}

View File

@@ -0,0 +1,620 @@
// Transform SVG PathData
// http://www.w3.org/TR/SVG/paths.html#PathDataBNF
import { a2c, annotateArcCommand, arcAt, assertNumbers, bezierAt, bezierRoot,
intersectionUnitCircleLine } from "./mathUtils";
import { SVGPathData } from "./SVGPathData";
import { SVGCommand, TransformFunction } from "./types";
export namespace SVGPathDataTransformer {
// Predefined transforming functions
// Rounds commands values
export function ROUND(roundVal = 1e13) {
assertNumbers(roundVal);
function rf(val: number) { return Math.round(val * roundVal) / roundVal; }
return function round(command: any) {
if ("undefined" !== typeof command.x1) {
command.x1 = rf(command.x1);
}
if ("undefined" !== typeof command.y1) {
command.y1 = rf(command.y1);
}
if ("undefined" !== typeof command.x2) {
command.x2 = rf(command.x2);
}
if ("undefined" !== typeof command.y2) {
command.y2 = rf(command.y2);
}
if ("undefined" !== typeof command.x) {
command.x = rf(command.x);
}
if ("undefined" !== typeof command.y) {
command.y = rf(command.y);
}
if ("undefined" !== typeof command.rX) {
command.rX = rf(command.rX);
}
if ("undefined" !== typeof command.rY) {
command.rY = rf(command.rY);
}
return command;
};
}
// Relative to absolute commands
export function TO_ABS() {
return INFO((command, prevX, prevY) => {
if (command.relative) {
// x1/y1 values
if ("undefined" !== typeof command.x1) {
command.x1 += prevX;
}
if ("undefined" !== typeof command.y1) {
command.y1 += prevY;
}
// x2/y2 values
if ("undefined" !== typeof command.x2) {
command.x2 += prevX;
}
if ("undefined" !== typeof command.y2) {
command.y2 += prevY;
}
// Finally x/y values
if ("undefined" !== typeof command.x) {
command.x += prevX;
}
if ("undefined" !== typeof command.y) {
command.y += prevY;
}
command.relative = false;
}
return command;
});
}
// Absolute to relative commands
export function TO_REL() {
return INFO((command, prevX, prevY) => {
if (!command.relative) {
// x1/y1 values
if ("undefined" !== typeof command.x1) {
command.x1 -= prevX;
}
if ("undefined" !== typeof command.y1) {
command.y1 -= prevY;
}
// x2/y2 values
if ("undefined" !== typeof command.x2) {
command.x2 -= prevX;
}
if ("undefined" !== typeof command.y2) {
command.y2 -= prevY;
}
// Finally x/y values
if ("undefined" !== typeof command.x) {
command.x -= prevX;
}
if ("undefined" !== typeof command.y) {
command.y -= prevY;
}
command.relative = true;
}
return command;
});
}
// Convert H, V, Z and A with rX = 0 to L
export function NORMALIZE_HVZ(normalizeZ = true, normalizeH = true, normalizeV = true) {
return INFO((command, prevX, prevY, pathStartX, pathStartY) => {
if (isNaN(pathStartX) && !(command.type & SVGPathData.MOVE_TO)) {
throw new Error("path must start with moveto");
}
if (normalizeH && command.type & SVGPathData.HORIZ_LINE_TO) {
command.type = SVGPathData.LINE_TO;
command.y = command.relative ? 0 : prevY;
}
if (normalizeV && command.type & SVGPathData.VERT_LINE_TO) {
command.type = SVGPathData.LINE_TO;
command.x = command.relative ? 0 : prevX;
}
if (normalizeZ && command.type & SVGPathData.CLOSE_PATH) {
command.type = SVGPathData.LINE_TO;
command.x = command.relative ? pathStartX - prevX : pathStartX;
command.y = command.relative ? pathStartY - prevY : pathStartY;
}
if (command.type & SVGPathData.ARC && (0 === command.rX || 0 === command.rY)) {
command.type = SVGPathData.LINE_TO;
delete command.rX;
delete command.rY;
delete command.xRot;
delete command.lArcFlag;
delete command.sweepFlag;
}
return command;
});
}
/*
* Transforms smooth curves and quads to normal curves and quads (SsTt to CcQq)
*/
export function NORMALIZE_ST() {
let prevCurveC2X = NaN;
let prevCurveC2Y = NaN;
let prevQuadCX = NaN;
let prevQuadCY = NaN;
return INFO((command, prevX, prevY) => {
if (command.type & SVGPathData.SMOOTH_CURVE_TO) {
command.type = SVGPathData.CURVE_TO;
prevCurveC2X = isNaN(prevCurveC2X) ? prevX : prevCurveC2X;
prevCurveC2Y = isNaN(prevCurveC2Y) ? prevY : prevCurveC2Y;
command.x1 = command.relative ? prevX - prevCurveC2X : 2 * prevX - prevCurveC2X;
command.y1 = command.relative ? prevY - prevCurveC2Y : 2 * prevY - prevCurveC2Y;
}
if (command.type & SVGPathData.CURVE_TO) {
prevCurveC2X = command.relative ? prevX + command.x2 : command.x2;
prevCurveC2Y = command.relative ? prevY + command.y2 : command.y2;
} else {
prevCurveC2X = NaN;
prevCurveC2Y = NaN;
}
if (command.type & SVGPathData.SMOOTH_QUAD_TO) {
command.type = SVGPathData.QUAD_TO;
prevQuadCX = isNaN(prevQuadCX) ? prevX : prevQuadCX;
prevQuadCY = isNaN(prevQuadCY) ? prevY : prevQuadCY;
command.x1 = command.relative ? prevX - prevQuadCX : 2 * prevX - prevQuadCX;
command.y1 = command.relative ? prevY - prevQuadCY : 2 * prevY - prevQuadCY;
}
if (command.type & SVGPathData.QUAD_TO) {
prevQuadCX = command.relative ? prevX + command.x1 : command.x1;
prevQuadCY = command.relative ? prevY + command.y1 : command.y1;
} else {
prevQuadCX = NaN;
prevQuadCY = NaN;
}
return command;
});
}
/*
* A quadratic bézier curve can be represented by a cubic bézier curve which has
* the same end points as the quadratic and both control points in place of the
* quadratic"s one.
*
* This transformer replaces QqTt commands with Cc commands respectively.
* This is useful for reading path data into a system which only has a
* representation for cubic curves.
*/
export function QT_TO_C() {
let prevQuadX1 = NaN;
let prevQuadY1 = NaN;
return INFO((command, prevX, prevY) => {
if (command.type & SVGPathData.SMOOTH_QUAD_TO) {
command.type = SVGPathData.QUAD_TO;
prevQuadX1 = isNaN(prevQuadX1) ? prevX : prevQuadX1;
prevQuadY1 = isNaN(prevQuadY1) ? prevY : prevQuadY1;
command.x1 = command.relative ? prevX - prevQuadX1 : 2 * prevX - prevQuadX1;
command.y1 = command.relative ? prevY - prevQuadY1 : 2 * prevY - prevQuadY1;
}
if (command.type & SVGPathData.QUAD_TO) {
prevQuadX1 = command.relative ? prevX + command.x1 : command.x1;
prevQuadY1 = command.relative ? prevY + command.y1 : command.y1;
const x1 = command.x1;
const y1 = command.y1;
command.type = SVGPathData.CURVE_TO;
command.x1 = ((command.relative ? 0 : prevX) + x1 * 2) / 3;
command.y1 = ((command.relative ? 0 : prevY) + y1 * 2) / 3;
command.x2 = (command.x + x1 * 2) / 3;
command.y2 = (command.y + y1 * 2) / 3;
} else {
prevQuadX1 = NaN;
prevQuadY1 = NaN;
}
return command;
});
}
export function INFO(
f: (command: any, prevXAbs: number, prevYAbs: number,
pathStartXAbs: number, pathStartYAbs: number) => any | any[]) {
let prevXAbs = 0;
let prevYAbs = 0;
let pathStartXAbs = NaN;
let pathStartYAbs = NaN;
return function transform(command: any) {
if (isNaN(pathStartXAbs) && !(command.type & SVGPathData.MOVE_TO)) {
throw new Error("path must start with moveto");
}
const result = f(command, prevXAbs, prevYAbs, pathStartXAbs, pathStartYAbs);
if (command.type & SVGPathData.CLOSE_PATH) {
prevXAbs = pathStartXAbs;
prevYAbs = pathStartYAbs;
}
if ("undefined" !== typeof command.x) {
prevXAbs = (command.relative ? prevXAbs + command.x : command.x);
}
if ("undefined" !== typeof command.y) {
prevYAbs = (command.relative ? prevYAbs + command.y : command.y);
}
if (command.type & SVGPathData.MOVE_TO) {
pathStartXAbs = prevXAbs;
pathStartYAbs = prevYAbs;
}
return result;
};
}
/*
* remove 0-length segments
*/
export function SANITIZE(EPS = 0) {
assertNumbers(EPS);
let prevCurveC2X = NaN;
let prevCurveC2Y = NaN;
let prevQuadCX = NaN;
let prevQuadCY = NaN;
return INFO((command, prevX, prevY, pathStartX, pathStartY) => {
const abs = Math.abs;
let skip = false;
let x1Rel = 0;
let y1Rel = 0;
if (command.type & SVGPathData.SMOOTH_CURVE_TO) {
x1Rel = isNaN(prevCurveC2X) ? 0 : prevX - prevCurveC2X;
y1Rel = isNaN(prevCurveC2Y) ? 0 : prevY - prevCurveC2Y;
}
if (command.type & (SVGPathData.CURVE_TO | SVGPathData.SMOOTH_CURVE_TO)) {
prevCurveC2X = command.relative ? prevX + command.x2 : command.x2;
prevCurveC2Y = command.relative ? prevY + command.y2 : command.y2;
} else {
prevCurveC2X = NaN;
prevCurveC2Y = NaN;
}
if (command.type & SVGPathData.SMOOTH_QUAD_TO) {
prevQuadCX = isNaN(prevQuadCX) ? prevX : 2 * prevX - prevQuadCX;
prevQuadCY = isNaN(prevQuadCY) ? prevY : 2 * prevY - prevQuadCY;
} else if (command.type & SVGPathData.QUAD_TO) {
prevQuadCX = command.relative ? prevX + command.x1 : command.x1;
prevQuadCY = command.relative ? prevY + command.y1 : command.y2;
} else {
prevQuadCX = NaN;
prevQuadCY = NaN;
}
if (command.type & SVGPathData.LINE_COMMANDS ||
command.type & SVGPathData.ARC && (0 === command.rX || 0 === command.rY || !command.lArcFlag) ||
command.type & SVGPathData.CURVE_TO || command.type & SVGPathData.SMOOTH_CURVE_TO ||
command.type & SVGPathData.QUAD_TO || command.type & SVGPathData.SMOOTH_QUAD_TO) {
const xRel = "undefined" === typeof command.x ? 0 :
(command.relative ? command.x : command.x - prevX);
const yRel = "undefined" === typeof command.y ? 0 :
(command.relative ? command.y : command.y - prevY);
x1Rel = !isNaN(prevQuadCX) ? prevQuadCX - prevX :
"undefined" === typeof command.x1 ? x1Rel :
command.relative ? command.x :
command.x1 - prevX;
y1Rel = !isNaN(prevQuadCY) ? prevQuadCY - prevY :
"undefined" === typeof command.y1 ? y1Rel :
command.relative ? command.y :
command.y1 - prevY;
const x2Rel = "undefined" === typeof command.x2 ? 0 :
(command.relative ? command.x : command.x2 - prevX);
const y2Rel = "undefined" === typeof command.y2 ? 0 :
(command.relative ? command.y : command.y2 - prevY);
if (abs(xRel) <= EPS && abs(yRel) <= EPS &&
abs(x1Rel) <= EPS && abs(y1Rel) <= EPS &&
abs(x2Rel) <= EPS && abs(y2Rel) <= EPS) {
skip = true;
}
}
if (command.type & SVGPathData.CLOSE_PATH) {
if (abs(prevX - pathStartX) <= EPS && abs(prevY - pathStartY) <= EPS) {
skip = true;
}
}
return skip ? [] : command;
});
}
// SVG Transforms : http://www.w3.org/TR/SVGTiny12/coords.html#TransformList
// Matrix : http://apike.ca/prog_svg_transform.html
// a c e
// b d f
export function MATRIX(a: number, b: number, c: number, d: number, e: number, f: number) {
assertNumbers(a, b, c, d, e, f);
return INFO((command, prevX, prevY, pathStartX) => {
const origX1 = command.x1;
const origX2 = command.x2;
// if isNaN(pathStartX), then this is the first command, which is ALWAYS an
// absolute MOVE_TO, regardless what the relative flag says
const comRel = command.relative && !isNaN(pathStartX);
const x = "undefined" !== typeof command.x ? command.x : (comRel ? 0 : prevX);
const y = "undefined" !== typeof command.y ? command.y : (comRel ? 0 : prevY);
if (command.type & SVGPathData.HORIZ_LINE_TO && 0 !== b) {
command.type = SVGPathData.LINE_TO;
command.y = command.relative ? 0 : prevY;
}
if (command.type & SVGPathData.VERT_LINE_TO && 0 !== c) {
command.type = SVGPathData.LINE_TO;
command.x = command.relative ? 0 : prevX;
}
if ("undefined" !== typeof command.x) {
command.x = (command.x * a) + (y * c) + (comRel ? 0 : e);
}
if ("undefined" !== typeof command.y) {
command.y = (x * b) + command.y * d + (comRel ? 0 : f);
}
if ("undefined" !== typeof command.x1) {
command.x1 = command.x1 * a + command.y1 * c + (comRel ? 0 : e);
}
if ("undefined" !== typeof command.y1) {
command.y1 = origX1 * b + command.y1 * d + (comRel ? 0 : f);
}
if ("undefined" !== typeof command.x2) {
command.x2 = command.x2 * a + command.y2 * c + (comRel ? 0 : e);
}
if ("undefined" !== typeof command.y2) {
command.y2 = origX2 * b + command.y2 * d + (comRel ? 0 : f);
}
function sqr(x: number) { return x * x; }
const det = a * d - b * c;
if ("undefined" !== typeof command.xRot) {
// Skip if this is a pure translation
if (1 !== a || 0 !== b || 0 !== c || 1 !== d) {
// Special case for singular matrix
if (0 === det) {
// In the singular case, the arc is compressed to a line. The actual geometric image of the original
// curve under this transform possibly extends beyond the starting and/or ending points of the segment, but
// for simplicity we ignore this detail and just replace this command with a single line segment.
delete command.rX;
delete command.rY;
delete command.xRot;
delete command.lArcFlag;
delete command.sweepFlag;
command.type = SVGPathData.LINE_TO;
} else {
// Convert to radians
const xRot = command.xRot * Math.PI / 180;
// Convert rotated ellipse to general conic form
// x0^2/rX^2 + y0^2/rY^2 - 1 = 0
// x0 = x*cos(xRot) + y*sin(xRot)
// y0 = -x*sin(xRot) + y*cos(xRot)
// --> A*x^2 + B*x*y + C*y^2 - 1 = 0, where
const sinRot = Math.sin(xRot);
const cosRot = Math.cos(xRot);
const xCurve = 1 / sqr(command.rX);
const yCurve = 1 / sqr(command.rY);
const A = sqr(cosRot) * xCurve + sqr(sinRot) * yCurve;
const B = 2 * sinRot * cosRot * (xCurve - yCurve);
const C = sqr(sinRot) * xCurve + sqr(cosRot) * yCurve;
// Apply matrix to A*x^2 + B*x*y + C*y^2 - 1 = 0
// x1 = a*x + c*y
// y1 = b*x + d*y
// (we can ignore e and f, since pure translations don"t affect the shape of the ellipse)
// --> A1*x1^2 + B1*x1*y1 + C1*y1^2 - det^2 = 0, where
const A1 = A * d * d - B * b * d + C * b * b;
const B1 = B * (a * d + b * c) - 2 * (A * c * d + C * a * b);
const C1 = A * c * c - B * a * c + C * a * a;
// Unapply newXRot to get back to axis-aligned ellipse equation
// x1 = x2*cos(newXRot) - y2*sin(newXRot)
// y1 = x2*sin(newXRot) + y2*cos(newXRot)
// A1*x1^2 + B1*x1*y1 + C1*y1^2 - det^2 =
// x2^2*(A1*cos(newXRot)^2 + B1*sin(newXRot)*cos(newXRot) + C1*sin(newXRot)^2)
// + x2*y2*(2*(C1 - A1)*sin(newXRot)*cos(newXRot) + B1*(cos(newXRot)^2 - sin(newXRot)^2))
// + y2^2*(A1*sin(newXRot)^2 - B1*sin(newXRot)*cos(newXRot) + C1*cos(newXRot)^2)
// (which must have the same zeroes as)
// x2^2/newRX^2 + y2^2/newRY^2 - 1
// (so we have)
// 2*(C1 - A1)*sin(newXRot)*cos(newXRot) + B1*(cos(newXRot)^2 - sin(newXRot)^2) = 0
// (A1 - C1)*sin(2*newXRot) = B1*cos(2*newXRot)
// 2*newXRot = atan2(B1, A1 - C1)
const newXRot = ((Math.atan2(B1, A1 - C1) + Math.PI) % Math.PI) / 2;
// For any integer n, (atan2(B1, A1 - C1) + n*pi)/2 is a solution to the above; incrementing n just swaps
// the x and y radii computed below (since that"s what rotating an ellipse by pi/2 does). Choosing the
// rotation between 0 and pi/2 eliminates the ambiguity and leads to more predictable output.
// Finally, we get newRX and newRY from the same-zeroes relationship that gave us newXRot
const newSinRot = Math.sin(newXRot);
const newCosRot = Math.cos(newXRot);
command.rX = Math.abs(det) /
Math.sqrt(A1 * sqr(newCosRot) + B1 * newSinRot * newCosRot + C1 * sqr(newSinRot));
command.rY = Math.abs(det) /
Math.sqrt(A1 * sqr(newSinRot) - B1 * newSinRot * newCosRot + C1 * sqr(newCosRot));
command.xRot = newXRot * 180 / Math.PI;
}
}
}
// sweepFlag needs to be inverted when mirroring shapes
// see http://www.itk.ilstu.edu/faculty/javila/SVG/SVG_drawing1/elliptical_curve.htm
// m 65,10 a 50,25 0 1 0 50,25
// M 65,60 A 50,25 0 1 1 115,35
if ("undefined" !== typeof command.sweepFlag && 0 > det) {
command.sweepFlag = +!command.sweepFlag;
}
return command;
});
}
export function ROTATE(a: number, x = 0, y = 0) {
assertNumbers(a, x, y);
const sin = Math.sin(a);
const cos = Math.cos(a);
return MATRIX(cos, sin, -sin, cos, x - x * cos + y * sin, y - x * sin - y * cos);
}
export function TRANSLATE(dX: number, dY = 0) {
assertNumbers(dX, dY);
return MATRIX(1, 0, 0, 1, dX, dY);
}
export function SCALE(dX: number, dY = dX) {
assertNumbers(dX, dY);
return MATRIX(dX, 0, 0, dY, 0, 0);
}
export function SKEW_X(a: number) {
assertNumbers(a);
return MATRIX(1, 0, Math.atan(a), 1, 0, 0);
}
export function SKEW_Y(a: number) {
assertNumbers(a);
return MATRIX(1, Math.atan(a), 0, 1, 0, 0);
}
export function X_AXIS_SYMMETRY(xOffset = 0) {
assertNumbers(xOffset);
return MATRIX(-1, 0, 0, 1, xOffset, 0);
}
export function Y_AXIS_SYMMETRY(yOffset = 0) {
assertNumbers(yOffset);
return MATRIX(1, 0, 0, -1, 0, yOffset);
}
// Convert arc commands to curve commands
export function A_TO_C() {
return INFO((command, prevX, prevY) => {
if (SVGPathData.ARC === command.type) {
return a2c(command, command.relative ? 0 : prevX, command.relative ? 0 : prevY);
}
return command;
});
}
// @see annotateArcCommand
export function ANNOTATE_ARCS() {
return INFO((c, x1, y1) => {
if (c.relative) {
x1 = 0;
y1 = 0;
}
if (SVGPathData.ARC === c.type) {
annotateArcCommand(c, x1, y1);
}
return c;
});
}
export function CLONE() {
return (c: SVGCommand) => {
const result = {} as SVGCommand;
// tslint:disable-next-line
for (const key in c) {
result[key as keyof SVGCommand] = c[key as keyof SVGCommand];
}
return result;
};
}
// @see annotateArcCommand
export function CALCULATE_BOUNDS() {
const clone = CLONE();
const toAbs = TO_ABS();
const qtToC = QT_TO_C();
const normST = NORMALIZE_ST();
const f: TransformFunction & {minX: number, maxX: number, minY: number, maxY: number} =
INFO((command, prevXAbs, prevYAbs) => {
const c = normST(qtToC(toAbs(clone(command))));
function fixX(absX: number) {
if (absX > f.maxX) { f.maxX = absX; }
if (absX < f.minX) { f.minX = absX; }
}
function fixY(absY: number) {
if (absY > f.maxY) { f.maxY = absY; }
if (absY < f.minY) { f.minY = absY; }
}
if (c.type & SVGPathData.DRAWING_COMMANDS) {
fixX(prevXAbs);
fixY(prevYAbs);
}
if (c.type & SVGPathData.HORIZ_LINE_TO) {
fixX(c.x);
}
if (c.type & SVGPathData.VERT_LINE_TO) {
fixY(c.y);
}
if (c.type & SVGPathData.LINE_TO) {
fixX(c.x);
fixY(c.y);
}
if (c.type & SVGPathData.CURVE_TO) {
// add start and end points
fixX(c.x);
fixY(c.y);
const xDerivRoots = bezierRoot(prevXAbs, c.x1, c.x2, c.x);
for (const derivRoot of xDerivRoots) {
if (0 < derivRoot && 1 > derivRoot) {
fixX(bezierAt(prevXAbs, c.x1, c.x2, c.x, derivRoot));
}
}
const yDerivRoots = bezierRoot(prevYAbs, c.y1, c.y2, c.y);
for (const derivRoot of yDerivRoots) {
if (0 < derivRoot && 1 > derivRoot) {
fixY(bezierAt(prevYAbs, c.y1, c.y2, c.y, derivRoot));
}
}
}
if (c.type & SVGPathData.ARC) {
// add start and end points
fixX(c.x);
fixY(c.y);
annotateArcCommand(c, prevXAbs, prevYAbs);
// p = cos(phi) * xv + sin(phi) * yv
// dp = -sin(phi) * xv + cos(phi) * yv = 0
const xRotRad = c.xRot / 180 * Math.PI;
// points on ellipse for phi = 0° and phi = 90°
const x0 = Math.cos(xRotRad) * c.rX;
const y0 = Math.sin(xRotRad) * c.rX;
const x90 = -Math.sin(xRotRad) * c.rY;
const y90 = Math.cos(xRotRad) * c.rY;
// annotateArcCommand returns phi1 and phi2 such that -180° < phi1 < 180° and phi2 is smaller or greater
// depending on the sweep flag. Calculate phiMin, phiMax such that -180° < phiMin < 180° and phiMin < phiMax
const [phiMin, phiMax] = c.phi1 < c.phi2 ?
[c.phi1, c.phi2] :
(-180 > c.phi2 ? [c.phi2 + 360, c.phi1 + 360] : [c.phi2, c.phi1]);
const normalizeXiEta = ([xi, eta]: [number, number]) => {
const phiRad = Math.atan2(eta, xi);
const phi = phiRad * 180 / Math.PI;
return phi < phiMin ? phi + 360 : phi;
};
// xi = cos(phi), eta = sin(phi)
const xDerivRoots = intersectionUnitCircleLine(x90, -x0, 0).map(normalizeXiEta);
for (const derivRoot of xDerivRoots) {
if (derivRoot > phiMin && derivRoot < phiMax) {
fixX(arcAt(c.cX, x0, x90, derivRoot));
}
}
const yDerivRoots = intersectionUnitCircleLine(y90, -y0, 0).map(normalizeXiEta);
for (const derivRoot of yDerivRoots) {
if (derivRoot > phiMin && derivRoot < phiMax) {
fixY(arcAt(c.cY, y0, y90, derivRoot));
}
}
}
return command;
}) as any;
f.minX = Infinity;
f.maxX = -Infinity;
f.minY = Infinity;
f.maxY = -Infinity;
return f;
}
}

View File

@@ -0,0 +1,74 @@
import { SVGPathDataTransformer } from "./SVGPathDataTransformer";
import { TransformFunction } from "./types";
export abstract class TransformableSVG {
round(x?: number) {
return this.transform(SVGPathDataTransformer.ROUND(x));
}
toAbs() {
return this.transform(SVGPathDataTransformer.TO_ABS());
}
toRel() {
return this.transform(SVGPathDataTransformer.TO_REL());
}
normalizeHVZ(a?: boolean, b?: boolean, c?: boolean) {
return this.transform(SVGPathDataTransformer.NORMALIZE_HVZ(a, b, c));
}
normalizeST() {
return this.transform(SVGPathDataTransformer.NORMALIZE_ST());
}
qtToC() {
return this.transform(SVGPathDataTransformer.QT_TO_C());
}
aToC() {
return this.transform(SVGPathDataTransformer.A_TO_C());
}
sanitize(eps?: number) {
return this.transform(SVGPathDataTransformer.SANITIZE(eps));
}
translate(x: number, y?: number) {
return this.transform(SVGPathDataTransformer.TRANSLATE(x, y));
}
scale(x: number, y?: number) {
return this.transform(SVGPathDataTransformer.SCALE(x, y));
}
rotate(a: number, x?: number, y?: number) {
return this.transform(SVGPathDataTransformer.ROTATE(a, x, y));
}
matrix(a: number, b: number, c: number, d: number, e: number, f: number) {
return this.transform(SVGPathDataTransformer.MATRIX(a, b, c, d, e, f));
}
skewX(a: number) {
return this.transform(SVGPathDataTransformer.SKEW_X(a));
}
skewY(a: number) {
return this.transform(SVGPathDataTransformer.SKEW_Y(a));
}
xSymmetry(xOffset?: number) {
return this.transform(SVGPathDataTransformer.X_AXIS_SYMMETRY(xOffset));
}
ySymmetry(yOffset?: number) {
return this.transform(SVGPathDataTransformer.Y_AXIS_SYMMETRY(yOffset));
}
annotateArcs() {
return this.transform(SVGPathDataTransformer.ANNOTATE_ARCS());
}
abstract transform(transformFunction: TransformFunction): this;
}

200
frontend/node_modules/svg-pathdata/src/mathUtils.ts generated vendored Normal file
View File

@@ -0,0 +1,200 @@
import { SVGPathData } from "./SVGPathData";
import { CommandA, CommandC } from "./types";
export function rotate([x, y]: [number, number], rad: number) {
return [
x * Math.cos(rad) - y * Math.sin(rad),
x * Math.sin(rad) + y * Math.cos(rad),
];
}
const DEBUG_CHECK_NUMBERS = true;
export function assertNumbers(...numbers: number[]) {
if (DEBUG_CHECK_NUMBERS) {
for (let i = 0; i < numbers.length; i++) {
if ("number" !== typeof numbers[i]) {
throw new Error(
`assertNumbers arguments[${i}] is not a number. ${typeof numbers[i]} == typeof ${numbers[i]}`);
}
}
}
return true;
}
const PI = Math.PI;
/**
* https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
* Fixes rX and rY.
* Ensures lArcFlag and sweepFlag are 0 or 1
* Adds center coordinates: command.cX, command.cY (relative or absolute, depending on command.relative)
* Adds start and end arc parameters (in degrees): command.phi1, command.phi2; phi1 < phi2 iff. c.sweepFlag == true
*/
export function annotateArcCommand(c: CommandA, x1: number, y1: number) {
c.lArcFlag = (0 === c.lArcFlag) ? 0 : 1;
c.sweepFlag = (0 === c.sweepFlag) ? 0 : 1;
// tslint:disable-next-line
let {rX, rY, x, y} = c;
rX = Math.abs(c.rX);
rY = Math.abs(c.rY);
const [x1_, y1_] = rotate([(x1 - x) / 2, (y1 - y) / 2], -c.xRot / 180 * PI);
const testValue = Math.pow(x1_, 2) / Math.pow(rX, 2) + Math.pow(y1_, 2) / Math.pow(rY, 2);
if (1 < testValue) {
rX *= Math.sqrt(testValue);
rY *= Math.sqrt(testValue);
}
c.rX = rX;
c.rY = rY;
const c_ScaleTemp = (Math.pow(rX, 2) * Math.pow(y1_, 2) + Math.pow(rY, 2) * Math.pow(x1_, 2));
const c_Scale = (c.lArcFlag !== c.sweepFlag ? 1 : -1) *
Math.sqrt(Math.max(0, (Math.pow(rX, 2) * Math.pow(rY, 2) - c_ScaleTemp) / c_ScaleTemp));
const cx_ = rX * y1_ / rY * c_Scale;
const cy_ = -rY * x1_ / rX * c_Scale;
const cRot = rotate([cx_, cy_], c.xRot / 180 * PI);
c.cX = cRot[0] + (x1 + x) / 2;
c.cY = cRot[1] + (y1 + y) / 2;
c.phi1 = Math.atan2((y1_ - cy_) / rY, (x1_ - cx_) / rX);
c.phi2 = Math.atan2((-y1_ - cy_) / rY, (-x1_ - cx_) / rX);
if (0 === c.sweepFlag && c.phi2 > c.phi1) {
c.phi2 -= 2 * PI;
}
if (1 === c.sweepFlag && c.phi2 < c.phi1) {
c.phi2 += 2 * PI;
}
c.phi1 *= 180 / PI;
c.phi2 *= 180 / PI;
}
/**
* Solves a quadratic system of equations of the form
* a * x + b * y = c
* x² + y² = 1
* This can be understood as the intersection of the unit circle with a line.
* => y = (c - a x) / b
* => x² + (c - a x)² / b² = 1
* => x² b² + c² - 2 c a x + a² x² = b²
* => (a² + b²) x² - 2 a c x + (c² - b²) = 0
*/
export function intersectionUnitCircleLine(a: number, b: number, c: number): [number, number][] {
assertNumbers(a, b, c);
// cf. pqFormula
const termSqr = a * a + b * b - c * c;
if (0 > termSqr) {
return [];
} else if (0 === termSqr) {
return [
[
(a * c) / (a * a + b * b),
(b * c) / (a * a + b * b)]];
}
const term = Math.sqrt(termSqr);
return [
[
(a * c + b * term) / (a * a + b * b),
(b * c - a * term) / (a * a + b * b)],
[
(a * c - b * term) / (a * a + b * b),
(b * c + a * term) / (a * a + b * b)]];
}
export const DEG = Math.PI / 180;
export function lerp(a: number, b: number, t: number) {
return (1 - t) * a + t * b;
}
export function arcAt(c: number, x1: number, x2: number, phiDeg: number) {
return c + Math.cos(phiDeg / 180 * PI) * x1 + Math.sin(phiDeg / 180 * PI) * x2;
}
export function bezierRoot(x0: number, x1: number, x2: number, x3: number) {
const EPS = 1e-6;
const x01 = x1 - x0;
const x12 = x2 - x1;
const x23 = x3 - x2;
const a = 3 * x01 + 3 * x23 - 6 * x12;
const b = (x12 - x01) * 6;
const c = 3 * x01;
// solve a * t² + b * t + c = 0
if (Math.abs(a) < EPS) {
// equivalent to b * t + c =>
return [-c / b];
}
return pqFormula(b / a, c / a, EPS);
}
export function bezierAt(x0: number, x1: number, x2: number, x3: number, t: number) {
// console.log(x0, y0, x1, y1, x2, y2, x3, y3, t)
const s = 1 - t;
const c0 = s * s * s;
const c1 = 3 * s * s * t;
const c2 = 3 * s * t * t;
const c3 = t * t * t;
return x0 * c0 + x1 * c1 + x2 * c2 + x3 * c3;
}
function pqFormula(p: number, q: number, PRECISION = 1e-6) {
// 4 times the discriminant:in
const discriminantX4 = p * p / 4 - q;
if (discriminantX4 < -PRECISION) {
return [];
} else if (discriminantX4 <= PRECISION) {
return [-p / 2];
}
const root = Math.sqrt(discriminantX4);
return [-(p / 2) - root, -(p / 2) + root];
}
export function a2c(arc: CommandA, x0: number, y0: number): CommandC[] {
if (!arc.cX) {
annotateArcCommand(arc, x0, y0);
}
const phiMin = Math.min(arc.phi1!, arc.phi2!), phiMax = Math.max(arc.phi1!, arc.phi2!), deltaPhi = phiMax - phiMin;
const partCount = Math.ceil(deltaPhi / 90 );
const result: CommandC[] = new Array(partCount);
let prevX = x0, prevY = y0;
for (let i = 0; i < partCount; i++) {
const phiStart = lerp(arc.phi1!, arc.phi2!, i / partCount);
const phiEnd = lerp(arc.phi1!, arc.phi2!, (i + 1) / partCount);
const deltaPhi = phiEnd - phiStart;
const f = 4 / 3 * Math.tan(deltaPhi * DEG / 4);
// x1/y1, x2/y2 and x/y coordinates on the unit circle for phiStart/phiEnd
const [x1, y1] = [
Math.cos(phiStart * DEG) - f * Math.sin(phiStart * DEG),
Math.sin(phiStart * DEG) + f * Math.cos(phiStart * DEG)];
const [x, y] = [Math.cos(phiEnd * DEG), Math.sin(phiEnd * DEG)];
const [x2, y2] = [x + f * Math.sin(phiEnd * DEG), y - f * Math.cos(phiEnd * DEG)];
result[i] = {relative: arc.relative, type: SVGPathData.CURVE_TO } as any;
const transform = (x: number, y: number) => {
const [xTemp, yTemp] = rotate([x * arc.rX, y * arc.rY], arc.xRot);
return [arc.cX! + xTemp, arc.cY! + yTemp];
};
[result[i].x1, result[i].y1] = transform(x1, y1);
[result[i].x2, result[i].y2] = transform(x2, y2);
[result[i].x, result[i].y] = transform(x, y);
if (arc.relative) {
result[i].x1 -= prevX;
result[i].y1 -= prevY;
result[i].x2 -= prevX;
result[i].y2 -= prevY;
result[i].x -= prevX;
result[i].y -= prevY;
}
[prevX, prevY] = [result[i].x, result[i].y];
}
return result;
}

41
frontend/node_modules/svg-pathdata/src/types.ts generated vendored Normal file
View File

@@ -0,0 +1,41 @@
import { SVGPathData } from "./SVGPathData";
export type CommandM = { relative: boolean, type: typeof SVGPathData.MOVE_TO, x: number, y: number };
export type CommandL = { relative: boolean, type: typeof SVGPathData.LINE_TO, x: number, y: number };
export type CommandH = { relative: boolean, type: typeof SVGPathData.HORIZ_LINE_TO, x: number };
export type CommandV = { relative: boolean, type: typeof SVGPathData.VERT_LINE_TO, y: number };
export type CommandZ = { type: typeof SVGPathData.CLOSE_PATH };
export type CommandQ = {
relative: boolean;
type: typeof SVGPathData.QUAD_TO;
x1: number;
y1: number;
x: number;
y: number;
};
export type CommandT = { relative: boolean, type: typeof SVGPathData.SMOOTH_QUAD_TO, x: number, y: number };
export type CommandC = {
relative: boolean,
type: typeof SVGPathData.CURVE_TO,
x1: number, y1: number,
x2: number, y2: number,
x: number, y: number };
export type CommandS = {
relative: boolean;
type: typeof SVGPathData.SMOOTH_CURVE_TO;
x2: number;
y2: number;
x: number;
y: number;
};
export type CommandA = {
relative: boolean,
type: typeof SVGPathData.ARC,
rX: number, rY: number,
xRot: number, sweepFlag: 0 | 1, lArcFlag: 0 | 1,
x: number, y: number
cX?: number, cY?: number, phi1?: number, phi2?: number};
export type SVGCommand = CommandM | CommandL | CommandH | CommandV | CommandZ | CommandQ |
CommandT | CommandC | CommandS | CommandA;
export type TransformFunction = (input: SVGCommand) => SVGCommand | SVGCommand[];