svgpath-umd

A UMD build of svgpath

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/525014/1527476/svgpath-umd.js

(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;
}));