אין להתקין סקריפט זה ישירות. זוהי ספריה עבור סקריפטים אחרים // @require https://update.greasyfork.org/scripts/490146/1344867/path-data-parser-umd.js
(function (global, factory) {
global = typeof globalThis !== 'undefined' ? globalThis : global || self;
factory(global.pathDataParser = {});
})(this, (function (exports) { 'use strict';
const COMMAND = 0;
const NUMBER = 1;
const EOD = 2;
const PARAMS = {A: 7, a: 7, C: 6, c: 6, H: 1, h: 1, L: 2, l: 2, M: 2, m: 2, Q: 4, q: 4, S: 4, s: 4, T: 2, t: 2, V: 1, v: 1, Z: 0, z: 0};
function tokenize(d) {
const tokens = new Array();
while (d !== "") {
if (d.match(/^([ \t\r\n,]+)/)) {
d = d.substr(RegExp.$1.length);
} else if (d.match(/^([aAcChHlLmMqQsStTvVzZ])/)) {
tokens[tokens.length] = {type: COMMAND, text: RegExp.$1};
d = d.substr(RegExp.$1.length);
} else if (d.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/)) {
tokens[tokens.length] = {type: NUMBER, text: `${parseFloat(RegExp.$1)}`};
d = d.substr(RegExp.$1.length);
} else {
return [];
}
}
tokens[tokens.length] = {type: EOD, text: ""};
return tokens;
}
function isType(token, type) {
return token.type === type;
}
function parsePath(d) {
const segments = [];
const tokens = tokenize(d);
let mode = "BOD";
let index = 0;
let token = tokens[index];
while (!isType(token, EOD)) {
let paramsCount = 0;
const params = [];
if (mode === "BOD") {
if (token.text === "M" || token.text === "m") {
index++;
paramsCount = PARAMS[token.text];
mode = token.text;
} else {
return parsePath("M0,0" + d);
}
} else if (isType(token, NUMBER)) {
paramsCount = PARAMS[mode];
} else {
index++;
paramsCount = PARAMS[token.text];
mode = token.text;
}
if (index + paramsCount < tokens.length) {
for (let i = index; i < index + paramsCount; i++) {
const numbeToken = tokens[i];
if (isType(numbeToken, NUMBER)) {
params[params.length] = +numbeToken.text;
} else {
throw new Error("Param not a number: " + mode + "," + numbeToken.text);
}
}
if (typeof PARAMS[mode] === "number") {
const segment = {key: mode, data: params};
segments.push(segment);
index += paramsCount;
token = tokens[index];
if (mode === "M")
mode = "L";
if (mode === "m")
mode = "l";
} else {
throw new Error("Bad segment: " + mode);
}
} else {
throw new Error("Path data ended short");
}
}
return segments;
}
function serialize(segments) {
const tokens = [];
for (const {key, data} of segments) {
tokens.push(key);
switch (key) {
case "C":
case "c":
tokens.push(data[0], `${data[1]},`, data[2], `${data[3]},`, data[4], data[5]);
break;
case "S":
case "s":
case "Q":
case "q":
tokens.push(data[0], `${data[1]},`, data[2], data[3]);
break;
default:
tokens.push(...data);
break;
}
}
return tokens.join(" ");
}
function absolutize(segments) {
let cx = 0, cy = 0;
let subx = 0, suby = 0;
const out = [];
for (const {key, data} of segments) {
switch (key) {
case "M":
out.push({key: "M", data: [...data]});
[cx, cy] = data;
[subx, suby] = data;
break;
case "m":
cx += data[0];
cy += data[1];
out.push({key: "M", data: [cx, cy]});
subx = cx;
suby = cy;
break;
case "L":
out.push({key: "L", data: [...data]});
[cx, cy] = data;
break;
case "l":
cx += data[0];
cy += data[1];
out.push({key: "L", data: [cx, cy]});
break;
case "C":
out.push({key: "C", data: [...data]});
cx = data[4];
cy = data[5];
break;
case "c": {
const newdata = data.map((d, i) => i % 2 ? d + cy : d + cx);
out.push({key: "C", data: newdata});
cx = newdata[4];
cy = newdata[5];
break;
}
case "Q":
out.push({key: "Q", data: [...data]});
cx = data[2];
cy = data[3];
break;
case "q": {
const newdata = data.map((d, i) => i % 2 ? d + cy : d + cx);
out.push({key: "Q", data: newdata});
cx = newdata[2];
cy = newdata[3];
break;
}
case "A":
out.push({key: "A", data: [...data]});
cx = data[5];
cy = data[6];
break;
case "a":
cx += data[5];
cy += data[6];
out.push({key: "A", data: [data[0], data[1], data[2], data[3], data[4], cx, cy]});
break;
case "H":
out.push({key: "H", data: [...data]});
cx = data[0];
break;
case "h":
cx += data[0];
out.push({key: "H", data: [cx]});
break;
case "V":
out.push({key: "V", data: [...data]});
cy = data[0];
break;
case "v":
cy += data[0];
out.push({key: "V", data: [cy]});
break;
case "S":
out.push({key: "S", data: [...data]});
cx = data[2];
cy = data[3];
break;
case "s": {
const newdata = data.map((d, i) => i % 2 ? d + cy : d + cx);
out.push({key: "S", data: newdata});
cx = newdata[2];
cy = newdata[3];
break;
}
case "T":
out.push({key: "T", data: [...data]});
cx = data[0];
cy = data[1];
break;
case "t":
cx += data[0];
cy += data[1];
out.push({key: "T", data: [cx, cy]});
break;
case "Z":
case "z":
out.push({key: "Z", data: []});
cx = subx;
cy = suby;
break;
}
}
return out;
}
function normalize(segments) {
const out = [];
let lastType = "";
let cx = 0, cy = 0;
let subx = 0, suby = 0;
let lcx = 0, lcy = 0;
for (const {key, data} of segments) {
switch (key) {
case "M":
out.push({key: "M", data: [...data]});
[cx, cy] = data;
[subx, suby] = data;
break;
case "C":
out.push({key: "C", data: [...data]});
cx = data[4];
cy = data[5];
lcx = data[2];
lcy = data[3];
break;
case "L":
out.push({key: "L", data: [...data]});
[cx, cy] = data;
break;
case "H":
cx = data[0];
out.push({key: "L", data: [cx, cy]});
break;
case "V":
cy = data[0];
out.push({key: "L", data: [cx, cy]});
break;
case "S": {
let cx1 = 0, cy1 = 0;
if (lastType === "C" || lastType === "S") {
cx1 = cx + (cx - lcx);
cy1 = cy + (cy - lcy);
} else {
cx1 = cx;
cy1 = cy;
}
out.push({key: "C", data: [cx1, cy1, ...data]});
lcx = data[0];
lcy = data[1];
cx = data[2];
cy = data[3];
break;
}
case "T": {
const [x, y] = data;
let x1 = 0, y1 = 0;
if (lastType === "Q" || lastType === "T") {
x1 = cx + (cx - lcx);
y1 = cy + (cy - lcy);
} else {
x1 = cx;
y1 = cy;
}
const cx1 = cx + 2 * (x1 - cx) / 3;
const cy1 = cy + 2 * (y1 - cy) / 3;
const cx2 = x + 2 * (x1 - x) / 3;
const cy2 = y + 2 * (y1 - y) / 3;
out.push({key: "C", data: [cx1, cy1, cx2, cy2, x, y]});
lcx = x1;
lcy = y1;
cx = x;
cy = y;
break;
}
case "Q": {
const [x1, y1, x, y] = data;
const cx1 = cx + 2 * (x1 - cx) / 3;
const cy1 = cy + 2 * (y1 - cy) / 3;
const cx2 = x + 2 * (x1 - x) / 3;
const cy2 = y + 2 * (y1 - y) / 3;
out.push({key: "C", data: [cx1, cy1, cx2, cy2, x, y]});
lcx = x1;
lcy = y1;
cx = x;
cy = y;
break;
}
case "A": {
const r1 = Math.abs(data[0]);
const r2 = Math.abs(data[1]);
const angle = data[2];
const largeArcFlag = data[3];
const sweepFlag = data[4];
const x = data[5];
const y = data[6];
if (r1 === 0 || r2 === 0) {
out.push({key: "C", data: [cx, cy, x, y, x, y]});
cx = x;
cy = y;
} else {
if (cx !== x || cy !== y) {
const curves = arcToCubicCurves(cx, cy, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
curves.forEach(function(curve) {
out.push({key: "C", data: curve});
});
cx = x;
cy = y;
}
}
break;
}
case "Z":
out.push({key: "Z", data: []});
cx = subx;
cy = suby;
break;
}
lastType = key;
}
return out;
}
function degToRad(degrees) {
return Math.PI * degrees / 180;
}
function rotate(x, y, angleRad) {
const X = x * Math.cos(angleRad) - y * Math.sin(angleRad);
const Y = x * Math.sin(angleRad) + y * Math.cos(angleRad);
return [X, Y];
}
function arcToCubicCurves(x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, recursive) {
const angleRad = degToRad(angle);
let params = [];
let f1 = 0, f2 = 0, cx = 0, cy = 0;
if (recursive) {
[f1, f2, cx, cy] = recursive;
} else {
[x1, y1] = rotate(x1, y1, -angleRad);
[x2, y2] = rotate(x2, y2, -angleRad);
const x = (x1 - x2) / 2;
const y = (y1 - y2) / 2;
let h = x * x / (r1 * r1) + y * y / (r2 * r2);
if (h > 1) {
h = Math.sqrt(h);
r1 = h * r1;
r2 = h * r2;
}
const sign = largeArcFlag === sweepFlag ? -1 : 1;
const r1Pow = r1 * r1;
const r2Pow = r2 * r2;
const left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;
const right = r1Pow * y * y + r2Pow * x * x;
const k = sign * Math.sqrt(Math.abs(left / right));
cx = k * r1 * y / r2 + (x1 + x2) / 2;
cy = k * -r2 * x / r1 + (y1 + y2) / 2;
f1 = Math.asin(parseFloat(((y1 - cy) / r2).toFixed(9)));
f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9)));
if (x1 < cx) {
f1 = Math.PI - f1;
}
if (x2 < cx) {
f2 = Math.PI - f2;
}
if (f1 < 0) {
f1 = Math.PI * 2 + f1;
}
if (f2 < 0) {
f2 = Math.PI * 2 + f2;
}
if (sweepFlag && f1 > f2) {
f1 = f1 - Math.PI * 2;
}
if (!sweepFlag && f2 > f1) {
f2 = f2 - Math.PI * 2;
}
}
let df = f2 - f1;
if (Math.abs(df) > Math.PI * 120 / 180) {
const f2old = f2;
const x2old = x2;
const y2old = y2;
if (sweepFlag && f2 > f1) {
f2 = f1 + Math.PI * 120 / 180 * 1;
} else {
f2 = f1 + Math.PI * 120 / 180 * -1;
}
x2 = cx + r1 * Math.cos(f2);
y2 = cy + r2 * Math.sin(f2);
params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]);
}
df = f2 - f1;
const c1 = Math.cos(f1);
const s1 = Math.sin(f1);
const c2 = Math.cos(f2);
const s2 = Math.sin(f2);
const t = Math.tan(df / 4);
const hx = 4 / 3 * r1 * t;
const hy = 4 / 3 * r2 * t;
const m1 = [x1, y1];
const m2 = [x1 + hx * s1, y1 - hy * c1];
const m3 = [x2 + hx * s2, y2 - hy * c2];
const m4 = [x2, y2];
m2[0] = 2 * m1[0] - m2[0];
m2[1] = 2 * m1[1] - m2[1];
if (recursive) {
return [m2, m3, m4].concat(params);
} else {
params = [m2, m3, m4].concat(params);
const curves = [];
for (let i = 0; i < params.length; i += 3) {
const r12 = rotate(params[i][0], params[i][1], angleRad);
const r22 = rotate(params[i + 1][0], params[i + 1][1], angleRad);
const r3 = rotate(params[i + 2][0], params[i + 2][1], angleRad);
curves.push([r12[0], r12[1], r22[0], r22[1], r3[0], r3[1]]);
}
return curves;
}
}
exports.absolutize = absolutize;
exports.normalize = normalize;
exports.parsePath = parsePath;
exports.serialize = serialize;
}));