- (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.SvgPath = factory());
- })(this, (() => {
- const epsilon = 0.0000000001;
- const torad = Math.PI / 180;
- class Ellipse {
- constructor(rx, ry, ax) {
- if (!(this instanceof Ellipse)) return new Ellipse(rx, ry, ax);
- this.rx = rx;
- this.ry = ry;
- this.ax = ax;
- }
- transform(m) {
- const c = Math.cos(this.ax * torad);
- const s = Math.sin(this.ax * torad);
- const ma = [
- this.rx * (m[0] * c + m[2] * s),
- this.rx * (m[1] * c + m[3] * s),
- this.ry * (-m[0] * s + m[2] * c),
- this.ry * (-m[1] * s + m[3] * c)
- ];
- const J = ma[0] * ma[0] + ma[2] * ma[2];
- const K = ma[1] * ma[1] + ma[3] * ma[3];
- let D = (
- (ma[0] - ma[3]) *
- (ma[0] - ma[3]) +
- (ma[2] + ma[1]) *
- (ma[2] + ma[1])
- ) * (
- (ma[0] + ma[3]) *
- (ma[0] + ma[3]) +
- (ma[2] - ma[1]) *
- (ma[2] - ma[1])
- );
- const JK = (J + K) / 2;
- if (D < epsilon * JK) {
- this.rx = this.ry = Math.sqrt(JK);
- this.ax = 0;
- return this;
- }
- const L = ma[0] * ma[1] + ma[2] * ma[3];
- D = Math.sqrt(D);
- const l1 = JK + D / 2;
- const l2 = JK - D / 2;
- this.ax = (Math.abs(L) < epsilon && Math.abs(l1 - K) < epsilon)
- ? 90
- : Math.atan(
- Math.abs(L) > Math.abs(l1 - K)
- ? (l1 - J) / L
- : L / (l1 - K)
- ) * 180 / Math.PI;
- if (this.ax >= 0) {
- this.rx = Math.sqrt(l1);
- this.ry = Math.sqrt(l2);
- } else {
- this.ax += 90;
- this.rx = Math.sqrt(l2);
- this.ry = Math.sqrt(l1);
- }
- return this;
- }
- isDegenerate() {
- return (this.rx < epsilon * this.ry || this.ry < epsilon * this.rx);
- }
- }
- const TAU = Math.PI * 2;
- const unit_vector_angle = (ux, uy, vx, vy) => {
- const sign = (ux * vy - uy * vx < 0) ? -1 : 1;
- let dot = ux * vx + uy * vy;
- if (dot > 1.0) { dot = 1.0; }
- if (dot < -1) { dot = -1; }
- return sign * Math.acos(dot);
- };
- const get_arc_center = (x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi) => {
- const x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2;
- const y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2;
- const rx_sq = rx * rx;
- const ry_sq = ry * ry;
- const x1p_sq = x1p * x1p;
- const y1p_sq = y1p * y1p;
- let radicant = (rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq);
- if (radicant < 0) {
- radicant = 0;
- }
- radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq);
- radicant = Math.sqrt(radicant) * (fa === fs ? -1 : 1);
- const cxp = radicant * rx/ry * y1p;
- const cyp = radicant * -ry/rx * x1p;
- const cx = cos_phi*cxp - sin_phi*cyp + (x1+x2)/2;
- const cy = sin_phi*cxp + cos_phi*cyp + (y1+y2)/2;
- const v1x = (x1p - cxp) / rx;
- const v1y = (y1p - cyp) / ry;
- const v2x = (-x1p - cxp) / rx;
- const v2y = (-y1p - cyp) / ry;
- const theta1 = unit_vector_angle(1, 0, v1x, v1y);
- let delta_theta = unit_vector_angle(v1x, v1y, v2x, v2y);
- if (fs === 0 && delta_theta > 0) {
- delta_theta -= TAU;
- }
- if (fs === 1 && delta_theta < 0) {
- delta_theta += TAU;
- }
- return [ cx, cy, theta1, delta_theta ];
- };
- const approximate_unit_arc = (theta1, delta_theta) => {
- const alpha = 4/3 * Math.tan(delta_theta/4);
- const x1 = Math.cos(theta1);
- const y1 = Math.sin(theta1);
- const x2 = Math.cos(theta1 + delta_theta);
- const y2 = Math.sin(theta1 + delta_theta);
- return [ x1, y1, x1 - y1*alpha, y1 + x1*alpha, x2 + y2*alpha, y2 - x2*alpha, x2, y2 ];
- };
- const a2c = (x1, y1, x2, y2, fa, fs, rx, ry, phi) => {
- const sin_phi = Math.sin(phi * TAU / 360);
- const cos_phi = Math.cos(phi * TAU / 360);
- const x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2;
- const y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2;
- if (x1p === 0 && y1p === 0) return [];
- if (rx === 0 || ry === 0) return [];
- rx = Math.abs(rx);
- ry = Math.abs(ry);
- const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
- if (lambda > 1) {
- rx *= Math.sqrt(lambda);
- ry *= Math.sqrt(lambda);
- }
- const cc = get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi);
- const result = [];
- let theta1 = cc[2];
- let delta_theta = cc[3];
- const segments = Math.max(Math.ceil(Math.abs(delta_theta) / (TAU / 4)), 1);
- delta_theta /= segments;
- for (let i = 0; i < segments; i++) {
- result.push(approximate_unit_arc(theta1, delta_theta));
- theta1 += delta_theta;
- }
- return result.map(curve => {
- for (let i = 0; i < curve.length; i += 2) {
- let x = curve[i + 0];
- let y = curve[i + 1];
- x *= rx;
- y *= ry;
- const xp = cos_phi*x - sin_phi*y;
- const yp = sin_phi*x + cos_phi*y;
- curve[i + 0] = xp + cc[0];
- curve[i + 1] = yp + cc[1];
- }
- return curve;
- });
- };
- const combine = (m1, m2) => {
- return [
- 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]
- ];
- };
- class Matrix {
- constructor() {
- if (!(this instanceof Matrix)) return new Matrix();
- this.queue = [];
- this.cache = null;
- }
- matrix(m) {
- if (m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0) return this;
- this.cache = null;
- this.queue.push(m);
- return this;
- }
- translate(tx, ty) {
- if (tx !== 0 || ty !== 0) {
- this.cache = null;
- this.queue.push([1, 0, 0, 1, tx, ty]);
- }
- return this;
- }
- scale(sx, sy) {
- if (sx !== 1 || sy !== 1) {
- this.cache = null;
- this.queue.push([sx, 0, 0, sy, 0, 0]);
- }
- return this;
- }
- rotate(angle, rx, ry) {
- if (angle !== 0) {
- this.translate(rx, ry);
- const rad = angle * Math.PI / 180;
- const cos = Math.cos(rad);
- const sin = Math.sin(rad);
- this.queue.push([cos, sin, -sin, cos, 0, 0]);
- this.cache = null;
- this.translate(-rx, -ry);
- }
- return this;
- }
- skewX(angle) {
- if (angle !== 0) {
- this.cache = null;
- this.queue.push([1, 0, Math.tan(angle * Math.PI / 180), 1, 0, 0]);
- }
- return this;
- }
- skewY(angle) {
- if (angle !== 0) {
- this.cache = null;
- this.queue.push([1, Math.tan(angle * Math.PI / 180), 0, 1, 0, 0]);
- }
- return this;
- }
- toArray() {
- if (this.cache) return this.cache;
- if (!this.queue.length) {
- this.cache = [1, 0, 0, 1, 0, 0];
- return this.cache;
- }
- this.cache = this.queue[0];
- if (this.queue.length === 1) {
- return this.cache;
- }
- for (let i = 1; i < this.queue.length; i++) {
- this.cache = combine(this.cache, this.queue[i]);
- }
- return this.cache;
- }
- calc(x, y, isRelative) {
- if (!this.queue.length) return [x, y];
- if (!this.cache) {
- this.cache = this.toArray();
- }
- const m = this.cache;
- return [
- x * m[0] + y * m[2] + (isRelative ? 0 : m[4]),
- x * m[1] + y * m[3] + (isRelative ? 0 : m[5])
- ];
- }
- }
- const paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0 };
- const SPECIAL_SPACES = [
- 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
- 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF
- ];
- const isSpace = ch => (
- (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) ||
- (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
- (ch >= 0x1680 && SPECIAL_SPACES.indexOf(ch) >= 0)
- );
- const isCommand = code => {
- switch (code | 0x20) {
- case 0x6D:
- case 0x7A:
- case 0x6C:
- case 0x68:
- case 0x76:
- case 0x63:
- case 0x73:
- case 0x71:
- case 0x74:
- case 0x61:
- case 0x72:
- return true;
- }
- return false;
- };
- const isArc = code => (code | 0x20) === 0x61;
- const isDigit = code => (code >= 48 && code <= 57);
- const isDigitStart = code => (
- (code >= 48 && code <= 57) ||
- code === 0x2B ||
- code === 0x2D ||
- code === 0x2E
- );
- class State {
- constructor(path) {
- this.index = 0;
- this.path = path;
- this.max = path.length;
- this.result = [];
- this.param = 0.0;
- this.err = '';
- this.segmentStart = 0;
- this.data = [];
- }
- }
- const skipSpaces = state => {
- while (state.index < state.max && isSpace(state.path.charCodeAt(state.index))) state.index++;
- };
- const scanFlag = state => {
- const ch = state.path.charCodeAt(state.index);
- if (ch === 0x30) {
- state.param = 0;
- state.index++;
- return;
- }
- if (ch === 0x31) {
- state.param = 1;
- state.index++;
- return;
- }
- state.err = `SvgPath: arc flag can be 0 or 1 only (at pos ${ state.index })`;
- };
- const scanParam = state => {
- const start = state.index;
- let index = start;
- const max = state.max;
- let zeroFirst = false;
- let hasCeiling = false;
- let hasDecimal = false;
- let hasDot = false;
- let ch;
- if (index >= max) {
- state.err = `SvgPath: missed param (at pos ${ index })`;
- return;
- }
- ch = state.path.charCodeAt(index);
- if (ch === 0x2B || ch === 0x2D) {
- index++;
- ch = (index < max) ? state.path.charCodeAt(index) : 0;
- }
- if (!isDigit(ch) && ch !== 0x2E) {
- state.err = `SvgPath: param should start with 0..9 or \`.\` (at pos ${ index })`;
- return;
- }
- if (ch !== 0x2E) {
- zeroFirst = (ch === 0x30);
- index++;
- ch = (index < max) ? state.path.charCodeAt(index) : 0;
- if (zeroFirst && index < max) {
- if (ch && isDigit(ch)) {
- state.err = `SvgPath: numbers started with \`0\` such as \`09\` are illegal (at pos ${ start })`;
- return;
- }
- }
- while (index < max && isDigit(state.path.charCodeAt(index))) {
- index++;
- hasCeiling = true;
- }
- ch = (index < max) ? state.path.charCodeAt(index) : 0;
- }
- if (ch === 0x2E) {
- hasDot = true;
- index++;
- while (isDigit(state.path.charCodeAt(index))) {
- index++;
- hasDecimal = true;
- }
- ch = (index < max) ? state.path.charCodeAt(index) : 0;
- }
- if (ch === 0x65 || ch === 0x45) {
- if (hasDot && !hasCeiling && !hasDecimal) {
- state.err = `SvgPath: invalid float exponent (at pos ${ index })`;
- return;
- }
- index++;
- ch = (index < max) ? state.path.charCodeAt(index) : 0;
- if (ch === 0x2B || ch === 0x2D) index++;
- if (index < max && isDigit(state.path.charCodeAt(index))) {
- while (index < max && isDigit(state.path.charCodeAt(index))) index++;
- } else {
- state.err = `SvgPath: invalid float exponent (at pos ${ index })`;
- return;
- }
- }
- state.index = index;
- state.param = parseFloat(state.path.slice(start, index)) + 0.0;
- };
- const finalizeSegment = state => {
- let cmd = state.path[state.segmentStart];
- let cmdLC = cmd.toLowerCase();
- let params = state.data;
- if (cmdLC === 'm' && params.length > 2) {
- state.result.push([ cmd, params[0], params[1] ]);
- params = params.slice(2);
- cmdLC = 'l';
- cmd = (cmd === 'm') ? 'l' : 'L';
- }
- if (cmdLC === 'r') state.result.push([ cmd ].concat(params));
- else {
- while (params.length >= paramCounts[cmdLC]) {
- state.result.push([ cmd ].concat(params.splice(0, paramCounts[cmdLC])));
- if (!paramCounts[cmdLC]) break;
- }
- }
- };
- const scanSegment = state => {
- const max = state.max;
- state.segmentStart = state.index;
- const cmdCode = state.path.charCodeAt(state.index);
- const is_arc = isArc(cmdCode);
- if (!isCommand(cmdCode)) {
- state.err = `SvgPath: bad command ${ state.path[state.index] } (at pos ${ state.index })`;
- return;
- }
- const need_params = paramCounts[state.path[state.index].toLowerCase()];
- state.index++;
- skipSpaces(state);
- state.data = [];
- if (!need_params) {
- finalizeSegment(state);
- return;
- }
- let comma_found = false;
- for (;;) {
- for (let i = need_params; i > 0; i--) {
- if (is_arc && (i === 3 || i === 4)) scanFlag(state);
- else scanParam(state);
- if (state.err.length) {
- finalizeSegment(state);
- return;
- }
- state.data.push(state.param);
- skipSpaces(state);
- comma_found = false;
- if (state.index < max && state.path.charCodeAt(state.index) === 0x2C) {
- state.index++;
- skipSpaces(state);
- comma_found = true;
- }
- }
- if (comma_found) continue;
- if (state.index >= state.max) break;
- if (!isDigitStart(state.path.charCodeAt(state.index))) break;
- }
- finalizeSegment(state);
- };
- const pathParse = svgPath => {
- const state = new State(svgPath);
- const max = state.max;
- skipSpaces(state);
- while (state.index < max && !state.err.length) scanSegment(state);
- if (state.result.length) {
- if ('mM'.indexOf(state.result[0][0]) < 0) {
- state.err = 'SvgPath: string should start with `M` or `m`';
- state.result = [];
- } else {
- state.result[0][0] = 'M';
- }
- }
- return {
- err: state.err,
- segments: state.result
- };
- };
- const operations = {
- matrix: true,
- scale: true,
- rotate: true,
- translate: true,
- skewX: true,
- skewY: true
- };
- const CMD_SPLIT_RE = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/;
- const PARAMS_SPLIT_RE = /[\s,]+/;
- const transformParse = transformString => {
- const matrix = new Matrix();
- let cmd;
- let params;
- transformString.split(CMD_SPLIT_RE).forEach(item => {
- if (!item.length) return;
- if (typeof operations[item] !== 'undefined') {
- cmd = item;
- return;
- }
- params = item.split(PARAMS_SPLIT_RE).map(i => +i || 0);
- switch (cmd) {
- case 'matrix':
- if (params.length === 6) matrix.matrix(params);
- return;
- case 'scale':
- if (params.length === 1) matrix.scale(params[0], params[0]);
- else if (params.length === 2) matrix.scale(params[0], params[1]);
- return;
- case 'rotate':
- if (params.length === 1) matrix.rotate(params[0], 0, 0);
- else if (params.length === 3) matrix.rotate(params[0], params[1], params[2]);
- return;
- case 'translate':
- if (params.length === 1) matrix.translate(params[0], 0);
- else if (params.length === 2) matrix.translate(params[0], params[1]);
- return;
- case 'skewX':
- if (params.length === 1) matrix.skewX(params[0]);
- return;
- case 'skewY':
- if (params.length === 1) matrix.skewY(params[0]);
- return;
- }
- });
- return matrix;
- };
- class SvgPath {
- constructor(path) {
- if (!(this instanceof SvgPath)) return new SvgPath(path);
- const pstate = pathParse(path);
- this.segments = pstate.segments;
- this.err = pstate.err;
- this.__stack = [];
- }
- static from(src) {
- if (typeof src === 'string') return new SvgPath(src);
- if (src instanceof SvgPath) {
- const s = new SvgPath('');
- s.err = src.err;
- s.segments = src.segments.map(sgm => sgm.slice());
- s.__stack = src.__stack.map(m => new Matrix().matrix(m.toArray()));
- return s;
- }
- throw new Error(`SvgPath.from: invalid param type ${ src}`);
- }
- __matrix(m) {
- const self = this;
- if (!m.queue.length) return;
- this.iterate((s, index, x, y) => {
- let p;
- let result;
- let name;
- let isRelative;
- switch (s[0]) {
- case 'v':
- p = m.calc(0, s[1], true);
- result = (p[0] === 0) ? ['v', p[1]] : ['l', p[0], p[1]];
- break;
- case 'V':
- p = m.calc(x, s[1], false);
- result = (p[0] === m.calc(x, y, false)[0]) ? ['V', p[1]] : ['L', p[0], p[1]];
- break;
- case 'h':
- p = m.calc(s[1], 0, true);
- result = (p[1] === 0) ? ['h', p[0]] : ['l', p[0], p[1]];
- break;
- case 'H':
- p = m.calc(s[1], y, false);
- result = (p[1] === m.calc(x, y, false)[1]) ? ['H', p[0]] : ['L', p[0], p[1]];
- break;
- case 'a':
- case 'A': {
- /*if ((s[0] === 'A' && s[6] === x && s[7] === y) ||
- (s[0] === 'a' && s[6] === 0 && s[7] === 0)) {
- return [];
- }*/
- const ma = m.toArray();
- const e = new Ellipse(s[1], s[2], s[3]).transform(ma);
- if (ma[0] * ma[3] - ma[1] * ma[2] < 0) {
- s[5] = s[5] ? '0' : '1';
- }
- p = m.calc(s[6], s[7], s[0] === 'a');
- if (
- (s[0] === 'A' && s[6] === x && s[7] === y) ||
- (s[0] === 'a' && s[6] === 0 && s[7] === 0)
- ) {
- result = [s[0] === 'a' ? 'l' : 'L', p[0], p[1]];
- break;
- }
- result = e.isDegenerate() ? [s[0] === 'a' ? 'l' : 'L', p[0], p[1]] : [s[0], e.rx, e.ry, e.ax, s[4], s[5], p[0], p[1]];
- break;
- }
- case 'm':
- isRelative = index > 0;
- p = m.calc(s[1], s[2], isRelative);
- result = ['m', p[0], p[1]];
- break;
- default:
- name = s[0];
- result = [name];
- isRelative = (name.toLowerCase() === name);
- for (let i = 1; i < s.length; i += 2) {
- p = m.calc(s[i], s[i + 1], isRelative);
- result.push(p[0], p[1]);
- }
- }
- self.segments[index] = result;
- }, true);
- }
- __evaluateStack() {
- if (!this.__stack.length) return;
- if (this.__stack.length === 1) {
- this.__matrix(this.__stack[0]);
- this.__stack = [];
- return;
- }
- const m = new Matrix();
- let i = this.__stack.length;
- while (--i >= 0) m.matrix(this.__stack[i].toArray());
- this.__matrix(m);
- this.__stack = [];
- }
- toString() {
- let result = '';
- let prevCmd = '';
- let cmdSkipped = false;
- this.__evaluateStack();
- for (let i = 0, len = this.segments.length; i < len; i++) {
- const segment = this.segments[i];
- const cmd = segment[0];
- if (cmd !== prevCmd || cmd === 'm' || cmd === 'M') {
- if (cmd === 'm' && prevCmd === 'z') result += ' ';
- result += cmd;
- cmdSkipped = false;
- } else {
- cmdSkipped = true;
- }
- for (let pos = 1; pos < segment.length; pos++) {
- const val = segment[pos];
- if (pos === 1) {
- if (cmdSkipped && val >= 0) result += ' ';
- } else if (val >= 0) result += ' ';
- result += val;
- }
- prevCmd = cmd;
- }
- return result;
- }
- translate(x, y) {
- this.__stack.push(new Matrix().translate(x, y || 0));
- return this;
- }
- scale(sx, sy) {
- this.__stack.push(new Matrix().scale(sx, (!sy && (sy !== 0)) ? sx : sy));
- return this;
- }
- rotate(angle, rx, ry) {
- this.__stack.push(new Matrix().rotate(angle, rx || 0, ry || 0));
- return this;
- }
- skewX(degrees) {
- this.__stack.push(new Matrix().skewX(degrees));
- return this;
- }
- skewY(degrees) {
- this.__stack.push(new Matrix().skewY(degrees));
- return this;
- }
- matrix(m) {
- this.__stack.push(new Matrix().matrix(m));
- return this;
- }
- transform(transformString) {
- if (!transformString.trim()) return this;
- this.__stack.push(transformParse(transformString));
- return this;
- }
- round(d) {
- let contourStartDeltaX = 0;
- let contourStartDeltaY = 0;
- let deltaX = 0;
- let deltaY = 0;
- let l;
- d = d || 0;
- this.__evaluateStack();
- this.segments.forEach(s => {
- const isRelative = (s[0].toLowerCase() === s[0]);
- switch (s[0]) {
- case 'H':
- case 'h':
- if (isRelative) { s[1] += deltaX; }
- deltaX = s[1] - s[1].toFixed(d);
- s[1] = +s[1].toFixed(d);
- return;
- case 'V':
- case 'v':
- if (isRelative) { s[1] += deltaY; }
- deltaY = s[1] - s[1].toFixed(d);
- s[1] = +s[1].toFixed(d);
- return;
- case 'Z':
- case 'z':
- deltaX = contourStartDeltaX;
- deltaY = contourStartDeltaY;
- return;
- case 'M':
- case 'm':
- if (isRelative) {
- s[1] += deltaX;
- s[2] += deltaY;
- }
- deltaX = s[1] - s[1].toFixed(d);
- deltaY = s[2] - s[2].toFixed(d);
- contourStartDeltaX = deltaX;
- contourStartDeltaY = deltaY;
- s[1] = +s[1].toFixed(d);
- s[2] = +s[2].toFixed(d);
- return;
- case 'A':
- case 'a':
- if (isRelative) {
- s[6] += deltaX;
- s[7] += deltaY;
- }
- deltaX = s[6] - s[6].toFixed(d);
- deltaY = s[7] - s[7].toFixed(d);
- s[1] = +s[1].toFixed(d);
- s[2] = +s[2].toFixed(d);
- s[3] = +s[3].toFixed(d + 2);
- s[6] = +s[6].toFixed(d);
- s[7] = +s[7].toFixed(d);
- return;
- default:
- l = s.length;
- if (isRelative) {
- s[l - 2] += deltaX;
- s[l - 1] += deltaY;
- }
- deltaX = s[l - 2] - s[l - 2].toFixed(d);
- deltaY = s[l - 1] - s[l - 1].toFixed(d);
- s.forEach((val, i) => {
- if (!i) return;
- s[i] = +s[i].toFixed(d);
- });
- return;
- }
- });
- return this;
- }
- iterate(iterator, keepLazyStack) {
- const segments = this.segments;
- const replacements = {};
- let needReplace = false;
- let lastX = 0;
- let lastY = 0;
- let countourStartX = 0;
- let countourStartY = 0;
- if (!keepLazyStack) this.__evaluateStack();
- segments.forEach((s, index) => {
- const res = iterator(s, index, lastX, lastY);
- if (Array.isArray(res)) {
- replacements[index] = res;
- needReplace = true;
- }
- const isRelative = (s[0] === s[0].toLowerCase());
- switch (s[0]) {
- case 'm':
- case 'M':
- lastX = s[1] + (isRelative ? lastX : 0);
- lastY = s[2] + (isRelative ? lastY : 0);
- countourStartX = lastX;
- countourStartY = lastY;
- return;
- case 'h':
- case 'H':
- lastX = s[1] + (isRelative ? lastX : 0);
- return;
- case 'v':
- case 'V':
- lastY = s[1] + (isRelative ? lastY : 0);
- return;
- case 'z':
- case 'Z':
- lastX = countourStartX;
- lastY = countourStartY;
- return;
- default:
- lastX = s[s.length - 2] + (isRelative ? lastX : 0);
- lastY = s[s.length - 1] + (isRelative ? lastY : 0);
- }
- });
- if (!needReplace) return this;
- const newSegments = [];
- for (let i = 0; i < segments.length; i++) {
- if (typeof replacements[i] !== 'undefined') {
- for (let j = 0; j < replacements[i].length; j++) {
- newSegments.push(replacements[i][j]);
- }
- } else {
- newSegments.push(segments[i]);
- }
- }
- this.segments = newSegments;
- return this;
- }
- abs() {
- this.iterate((s, index, x, y) => {
- const name = s[0];
- const nameUC = name.toUpperCase();
- if (name === nameUC) return;
- s[0] = nameUC;
- switch (name) {
- case 'v':
- s[1] += y;
- return;
- case 'a':
- s[6] += x;
- s[7] += y;
- return;
- default:
- for (let i = 1; i < s.length; i++) {
- s[i] += i % 2 ? x : y;
- }
- }
- }, true);
- return this;
- }
- rel() {
- this.iterate((s, index, x, y) => {
- const name = s[0];
- const nameLC = name.toLowerCase();
- if (name === nameLC) return;
- if (index === 0 && name === 'M') return;
- s[0] = nameLC;
- switch (name) {
- case 'V':
- s[1] -= y;
- return;
- case 'A':
- s[6] -= x;
- s[7] -= y;
- return;
- default:
- for (let i = 1; i < s.length; i++) {
- s[i] -= i % 2 ? x : y;
- }
- }
- }, true);
- return this;
- }
- unarc() {
- this.iterate((s, index, x, y) => {
- let nextX;
- let nextY;
- const result = [];
- const name = s[0];
- if (name !== 'A' && name !== 'a') return null;
- if (name === 'a') {
- nextX = x + s[6];
- nextY = y + s[7];
- } else {
- nextX = s[6];
- nextY = s[7];
- }
- const new_segments = a2c(x, y, nextX, nextY, s[4], s[5], s[1], s[2], s[3]);
- if (new_segments.length === 0) return [[s[0] === 'a' ? 'l' : 'L', s[6], s[7]]];
- new_segments.forEach(s => result.push(['C', s[2], s[3], s[4], s[5], s[6], s[7]]));
- return result;
- });
- return this;
- }
- unshort() {
- const segments = this.segments;
- let prevControlX;
- let prevControlY;
- let prevSegment;
- let curControlX;
- let curControlY;
- this.iterate((s, idx, x, y) => {
- const name = s[0];
- const nameUC = name.toUpperCase();
- let isRelative;
- if (!idx) return;
- if (nameUC === 'T') {
- isRelative = (name === 't');
- prevSegment = segments[idx - 1];
- if (prevSegment[0] === 'Q') {
- prevControlX = prevSegment[1] - x;
- prevControlY = prevSegment[2] - y;
- } else if (prevSegment[0] === 'q') {
- prevControlX = prevSegment[1] - prevSegment[3];
- prevControlY = prevSegment[2] - prevSegment[4];
- } else {
- prevControlX = 0;
- prevControlY = 0;
- }
- curControlX = -prevControlX;
- curControlY = -prevControlY;
- if (!isRelative) {
- curControlX += x;
- curControlY += y;
- }
- segments[idx] = [
- isRelative ? 'q' : 'Q',
- curControlX, curControlY,
- s[1], s[2]
- ];
- } else if (nameUC === 'S') {
- isRelative = (name === 's');
- prevSegment = segments[idx - 1];
- if (prevSegment[0] === 'C') {
- prevControlX = prevSegment[3] - x;
- prevControlY = prevSegment[4] - y;
- } else if (prevSegment[0] === 'c') {
- prevControlX = prevSegment[3] - prevSegment[5];
- prevControlY = prevSegment[4] - prevSegment[6];
- } else {
- prevControlX = 0;
- prevControlY = 0;
- }
- curControlX = -prevControlX;
- curControlY = -prevControlY;
- if (!isRelative) {
- curControlX += x;
- curControlY += y;
- }
- segments[idx] = [
- isRelative ? 'c' : 'C',
- curControlX, curControlY,
- s[1], s[2], s[3], s[4]
- ];
- }
- });
- return this;
- }
- }
- return SvgPath;
- }));