path-data-parser-umd

UMD version of path-data-parser

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @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;
}));