UNPKG

@liammartens/svg-path-properties

Version:

Calculate the length for an SVG path, to use it with node or a Canvas element

274 lines (273 loc) 14 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const parse_1 = __importDefault(require("./parse")); const linear_1 = require("./linear"); const arc_1 = require("./arc"); const bezier_1 = require("./bezier"); class SVGPathProperties { constructor(source) { this.length = 0; this.partial_lengths = []; this.functions = []; this.initial_point = null; this.getPartAtLength = (fractionLength) => { if (fractionLength < 0) { fractionLength = 0; } else if (fractionLength > this.length) { fractionLength = this.length; } let i = this.partial_lengths.length - 1; while (this.partial_lengths[i] >= fractionLength && i > 0) { i--; } i++; return { fraction: fractionLength - this.partial_lengths[i - 1], i: i }; }; this.getTotalLength = () => { return this.length; }; this.getPointAtLength = (fractionLength) => { const fractionPart = this.getPartAtLength(fractionLength); const functionAtPart = this.functions[fractionPart.i]; if (functionAtPart) { return functionAtPart.getPointAtLength(fractionPart.fraction); } else if (this.initial_point) { return this.initial_point; } throw new Error("Wrong function at this part."); }; this.getTangentAtLength = (fractionLength) => { const fractionPart = this.getPartAtLength(fractionLength); const functionAtPart = this.functions[fractionPart.i]; if (functionAtPart) { return functionAtPart.getTangentAtLength(fractionPart.fraction); } else if (this.initial_point) { return { x: 0, y: 0 }; } throw new Error("Wrong function at this part."); }; this.getPropertiesAtLength = (fractionLength) => { const fractionPart = this.getPartAtLength(fractionLength); const functionAtPart = this.functions[fractionPart.i]; if (functionAtPart) { return functionAtPart.getPropertiesAtLength(fractionPart.fraction); } else if (this.initial_point) { return { x: this.initial_point.x, y: this.initial_point.y, tangentX: 0, tangentY: 0, }; } throw new Error("Wrong function at this part."); }; this.getParts = () => { const parts = []; for (var i = 0; i < this.functions.length; i++) { if (this.functions[i] !== null) { this.functions[i] = this.functions[i]; const properties = { start: this.functions[i].getPointAtLength(0), end: this.functions[i].getPointAtLength(this.partial_lengths[i] - this.partial_lengths[i - 1]), length: this.partial_lengths[i] - this.partial_lengths[i - 1], getPointAtLength: this.functions[i].getPointAtLength, getTangentAtLength: this.functions[i].getTangentAtLength, getPropertiesAtLength: this.functions[i].getPropertiesAtLength, }; parts.push(properties); } } return parts; }; const parsed = Array.isArray(source) ? source : (0, parse_1.default)(source); let cur = [0, 0]; let prev_point = [0, 0]; let curve; let ringStart = [0, 0]; for (let i = 0; i < parsed.length; i++) { //moveTo if (parsed[i][0] === "M") { cur = [parsed[i][1], parsed[i][2]]; ringStart = [cur[0], cur[1]]; this.functions.push(null); if (i === 0) { this.initial_point = { x: parsed[i][1], y: parsed[i][2] }; } } else if (parsed[i][0] === "m") { cur = [parsed[i][1] + cur[0], parsed[i][2] + cur[1]]; ringStart = [cur[0], cur[1]]; this.functions.push(null); //lineTo } else if (parsed[i][0] === "L") { this.length += Math.sqrt(Math.pow(cur[0] - parsed[i][1], 2) + Math.pow(cur[1] - parsed[i][2], 2)); this.functions.push(new linear_1.LinearPosition(cur[0], parsed[i][1], cur[1], parsed[i][2])); cur = [parsed[i][1], parsed[i][2]]; } else if (parsed[i][0] === "l") { this.length += Math.sqrt(Math.pow(parsed[i][1], 2) + Math.pow(parsed[i][2], 2)); this.functions.push(new linear_1.LinearPosition(cur[0], parsed[i][1] + cur[0], cur[1], parsed[i][2] + cur[1])); cur = [parsed[i][1] + cur[0], parsed[i][2] + cur[1]]; } else if (parsed[i][0] === "H") { this.length += Math.abs(cur[0] - parsed[i][1]); this.functions.push(new linear_1.LinearPosition(cur[0], parsed[i][1], cur[1], cur[1])); cur[0] = parsed[i][1]; } else if (parsed[i][0] === "h") { this.length += Math.abs(parsed[i][1]); this.functions.push(new linear_1.LinearPosition(cur[0], cur[0] + parsed[i][1], cur[1], cur[1])); cur[0] = parsed[i][1] + cur[0]; } else if (parsed[i][0] === "V") { this.length += Math.abs(cur[1] - parsed[i][1]); this.functions.push(new linear_1.LinearPosition(cur[0], cur[0], cur[1], parsed[i][1])); cur[1] = parsed[i][1]; } else if (parsed[i][0] === "v") { this.length += Math.abs(parsed[i][1]); this.functions.push(new linear_1.LinearPosition(cur[0], cur[0], cur[1], cur[1] + parsed[i][1])); cur[1] = parsed[i][1] + cur[1]; //Close path } else if (parsed[i][0] === "z" || parsed[i][0] === "Z") { this.length += Math.sqrt(Math.pow(ringStart[0] - cur[0], 2) + Math.pow(ringStart[1] - cur[1], 2)); this.functions.push(new linear_1.LinearPosition(cur[0], ringStart[0], cur[1], ringStart[1])); cur = [ringStart[0], ringStart[1]]; //Cubic Bezier curves } else if (parsed[i][0] === "C") { curve = new bezier_1.Bezier(cur[0], cur[1], parsed[i][1], parsed[i][2], parsed[i][3], parsed[i][4], parsed[i][5], parsed[i][6]); this.length += curve.getTotalLength(); cur = [parsed[i][5], parsed[i][6]]; this.functions.push(curve); } else if (parsed[i][0] === "c") { curve = new bezier_1.Bezier(cur[0], cur[1], cur[0] + parsed[i][1], cur[1] + parsed[i][2], cur[0] + parsed[i][3], cur[1] + parsed[i][4], cur[0] + parsed[i][5], cur[1] + parsed[i][6]); if (curve.getTotalLength() > 0) { this.length += curve.getTotalLength(); this.functions.push(curve); cur = [parsed[i][5] + cur[0], parsed[i][6] + cur[1]]; } else { this.functions.push(new linear_1.LinearPosition(cur[0], cur[0], cur[1], cur[1])); } } else if (parsed[i][0] === "S") { if (i > 0 && ["C", "c", "S", "s"].indexOf(parsed[i - 1][0]) > -1) { if (curve) { const c = curve.getC(); curve = new bezier_1.Bezier(cur[0], cur[1], 2 * cur[0] - c.x, 2 * cur[1] - c.y, parsed[i][1], parsed[i][2], parsed[i][3], parsed[i][4]); } } else { curve = new bezier_1.Bezier(cur[0], cur[1], cur[0], cur[1], parsed[i][1], parsed[i][2], parsed[i][3], parsed[i][4]); } if (curve) { this.length += curve.getTotalLength(); cur = [parsed[i][3], parsed[i][4]]; this.functions.push(curve); } } else if (parsed[i][0] === "s") { //240 225 if (i > 0 && ["C", "c", "S", "s"].indexOf(parsed[i - 1][0]) > -1) { if (curve) { const c = curve.getC(); const d = curve.getD(); curve = new bezier_1.Bezier(cur[0], cur[1], cur[0] + d.x - c.x, cur[1] + d.y - c.y, cur[0] + parsed[i][1], cur[1] + parsed[i][2], cur[0] + parsed[i][3], cur[1] + parsed[i][4]); } } else { curve = new bezier_1.Bezier(cur[0], cur[1], cur[0], cur[1], cur[0] + parsed[i][1], cur[1] + parsed[i][2], cur[0] + parsed[i][3], cur[1] + parsed[i][4]); } if (curve) { this.length += curve.getTotalLength(); cur = [parsed[i][3] + cur[0], parsed[i][4] + cur[1]]; this.functions.push(curve); } } //Quadratic Bezier curves else if (parsed[i][0] === "Q") { if (cur[0] == parsed[i][1] && cur[1] == parsed[i][2]) { let linearCurve = new linear_1.LinearPosition(parsed[i][1], parsed[i][3], parsed[i][2], parsed[i][4]); this.length += linearCurve.getTotalLength(); this.functions.push(linearCurve); } else { curve = new bezier_1.Bezier(cur[0], cur[1], parsed[i][1], parsed[i][2], parsed[i][3], parsed[i][4], undefined, undefined); this.length += curve.getTotalLength(); this.functions.push(curve); } cur = [parsed[i][3], parsed[i][4]]; prev_point = [parsed[i][1], parsed[i][2]]; } else if (parsed[i][0] === "q") { if (!(parsed[i][1] == 0 && parsed[i][2] == 0)) { curve = new bezier_1.Bezier(cur[0], cur[1], cur[0] + parsed[i][1], cur[1] + parsed[i][2], cur[0] + parsed[i][3], cur[1] + parsed[i][4], undefined, undefined); this.length += curve.getTotalLength(); this.functions.push(curve); } else { let linearCurve = new linear_1.LinearPosition(cur[0] + parsed[i][1], cur[0] + parsed[i][3], cur[1] + parsed[i][2], cur[1] + parsed[i][4]); this.length += linearCurve.getTotalLength(); this.functions.push(linearCurve); } prev_point = [cur[0] + parsed[i][1], cur[1] + parsed[i][2]]; cur = [parsed[i][3] + cur[0], parsed[i][4] + cur[1]]; } else if (parsed[i][0] === "T") { if (i > 0 && ["Q", "q", "T", "t"].indexOf(parsed[i - 1][0]) > -1) { curve = new bezier_1.Bezier(cur[0], cur[1], 2 * cur[0] - prev_point[0], 2 * cur[1] - prev_point[1], parsed[i][1], parsed[i][2], undefined, undefined); this.functions.push(curve); this.length += curve.getTotalLength(); } else { let linearCurve = new linear_1.LinearPosition(cur[0], parsed[i][1], cur[1], parsed[i][2]); this.functions.push(linearCurve); this.length += linearCurve.getTotalLength(); } prev_point = [2 * cur[0] - prev_point[0], 2 * cur[1] - prev_point[1]]; cur = [parsed[i][1], parsed[i][2]]; } else if (parsed[i][0] === "t") { if (i > 0 && ["Q", "q", "T", "t"].indexOf(parsed[i - 1][0]) > -1) { curve = new bezier_1.Bezier(cur[0], cur[1], 2 * cur[0] - prev_point[0], 2 * cur[1] - prev_point[1], cur[0] + parsed[i][1], cur[1] + parsed[i][2], undefined, undefined); this.length += curve.getTotalLength(); this.functions.push(curve); } else { let linearCurve = new linear_1.LinearPosition(cur[0], cur[0] + parsed[i][1], cur[1], cur[1] + parsed[i][2]); this.length += linearCurve.getTotalLength(); this.functions.push(linearCurve); } prev_point = [2 * cur[0] - prev_point[0], 2 * cur[1] - prev_point[1]]; cur = [parsed[i][1] + cur[0], parsed[i][2] + cur[1]]; } else if (parsed[i][0] === "A") { const arcCurve = new arc_1.Arc(cur[0], cur[1], parsed[i][1], parsed[i][2], parsed[i][3], parsed[i][4] === 1, parsed[i][5] === 1, parsed[i][6], parsed[i][7]); this.length += arcCurve.getTotalLength(); cur = [parsed[i][6], parsed[i][7]]; this.functions.push(arcCurve); } else if (parsed[i][0] === "a") { const arcCurve = new arc_1.Arc(cur[0], cur[1], parsed[i][1], parsed[i][2], parsed[i][3], parsed[i][4] === 1, parsed[i][5] === 1, cur[0] + parsed[i][6], cur[1] + parsed[i][7]); this.length += arcCurve.getTotalLength(); cur = [cur[0] + parsed[i][6], cur[1] + parsed[i][7]]; this.functions.push(arcCurve); } this.partial_lengths.push(this.length); } } } exports.default = SVGPathProperties;