UNPKG

@remotion/paths

Version:

Utilities for working with SVG paths

264 lines (263 loc) 8.35 kB
"use strict"; // Copied from: https://github.com/rveciana/svg-path-properties Object.defineProperty(exports, "__esModule", { value: true }); exports.parsePath = void 0; const length = { 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, }; const chunkExact = (array, instruction) => { const chunks = []; const expectedSize = length[instruction]; if (array.length % expectedSize !== 0) { throw new Error(`Expected number of arguments of SVG instruction "${instruction} ${array.join(' ')}" to be a multiple of ${expectedSize}`); } for (let i = 0; i < array.length; i += expectedSize) { chunks.push(array.slice(i, i + expectedSize)); } return chunks; }; const makeInstructions = (arr, instruction, cb) => { return chunkExact(arr, instruction).map((args) => { return cb(args); }); }; const segmentRegExp = /([astvzqmhlc])([^astvzqmhlc]*)/gi; const numberRegExp = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/gi; const parseValues = (args, instructionType) => { const numbers = args.match(numberRegExp); if (!numbers) { if (instructionType === 'Z' || instructionType === 'z') { return []; } throw new Error(`Malformed path data: ${instructionType} was expected to have numbers afterwards`); } const expectedArguments = length[instructionType]; if (numbers.length % expectedArguments !== 0) { throw new Error(`Malformed path data: ${instructionType} was expected to have a multiple of ${expectedArguments} numbers, but got "${instructionType} ${numbers.join(' ')} instead"`); } return numbers.map(Number); }; /* * @description Parses an SVG string path into an array of Instruction's. * @see [Documentation](https://www.remotion.dev/docs/paths/parse-path) */ const parsePath = (path) => { if (!path) { throw new Error('No path provided'); } const segments = path.match(segmentRegExp); if (!segments) { throw new Error(`No path elements found in string ${path}`); } return segments .map((segmentString) => { const command = segmentString.charAt(0); const args = parseValues(segmentString.substring(1), command); // overloaded moveTo if (command === 'M' && args.length > 2) { const segmentsArray = []; segmentsArray.push({ type: command, x: args[0], y: args[1], }); segmentsArray.push(...makeInstructions(args.slice(2), 'L', (numbers) => ({ type: 'L', x: numbers[0], y: numbers[1], }))); return segmentsArray; } if (command === 'm' && args.length > 2) { const segmentsArray = []; segmentsArray.push({ type: command, dx: args[0], dy: args[1], }); segmentsArray.push(...makeInstructions(args.slice(2), 'l', (numbers) => ({ type: 'l', dx: numbers[0], dy: numbers[1], }))); return segmentsArray; } if (command === 'Z' || command === 'z') { return [ { type: 'Z', }, ]; } if (command === 'A') { return makeInstructions(args, command, (numbers) => ({ type: command, rx: numbers[0], ry: numbers[1], xAxisRotation: numbers[2], largeArcFlag: numbers[3] === 1, sweepFlag: numbers[4] === 1, x: numbers[5], y: numbers[6], })); } if (command === 'a') { return makeInstructions(args, command, (numbers) => ({ type: command, rx: numbers[0], ry: numbers[1], xAxisRotation: numbers[2], largeArcFlag: numbers[3] === 1, sweepFlag: numbers[4] === 1, dx: numbers[5], dy: numbers[6], })); } if (command === 'C') { return makeInstructions(args, command, (numbers) => ({ type: command, cp1x: numbers[0], cp1y: numbers[1], cp2x: numbers[2], cp2y: numbers[3], x: numbers[4], y: numbers[5], })); } if (command === 'c') { return makeInstructions(args, command, (numbers) => ({ type: command, cp1dx: numbers[0], cp1dy: numbers[1], cp2dx: numbers[2], cp2dy: numbers[3], dx: numbers[4], dy: numbers[5], })); } if (command === 'S') { return makeInstructions(args, command, (numbers) => ({ type: command, cpx: numbers[0], cpy: numbers[1], x: numbers[2], y: numbers[3], })); } if (command === 's') { return makeInstructions(args, command, (numbers) => ({ type: command, cpdx: numbers[0], cpdy: numbers[1], dx: numbers[2], dy: numbers[3], })); } if (command === 'H') { return makeInstructions(args, command, (numbers) => ({ type: command, x: numbers[0], })); } if (command === 'h') { return makeInstructions(args, command, (numbers) => ({ type: command, dx: numbers[0], })); } if (command === 'V') { return makeInstructions(args, command, (numbers) => ({ type: command, y: numbers[0], })); } if (command === 'v') { return makeInstructions(args, command, (numbers) => ({ type: command, dy: numbers[0], })); } if (command === 'L') { return makeInstructions(args, command, (numbers) => ({ type: command, x: numbers[0], y: numbers[1], })); } if (command === 'M') { return makeInstructions(args, command, (numbers) => ({ type: command, x: numbers[0], y: numbers[1], })); } if (command === 'm') { return makeInstructions(args, command, (numbers) => ({ type: command, dx: numbers[0], dy: numbers[1], })); } if (command === 'l') { return makeInstructions(args, command, (numbers) => ({ type: command, dx: numbers[0], dy: numbers[1], })); } if (command === 'Q') { return makeInstructions(args, command, (numbers) => ({ type: command, cpx: numbers[0], cpy: numbers[1], x: numbers[2], y: numbers[3], })); } if (command === 'q') { return makeInstructions(args, command, (numbers) => ({ type: command, cpdx: numbers[0], cpdy: numbers[1], dx: numbers[2], dy: numbers[3], })); } if (command === 'T') { return makeInstructions(args, command, (numbers) => ({ type: command, x: numbers[0], y: numbers[1], })); } if (command === 't') { return makeInstructions(args, command, (numbers) => ({ type: command, dx: numbers[0], dy: numbers[1], })); } throw new Error(`Invalid path element ${segmentString}`); }, []) .flat(1); }; exports.parsePath = parsePath;