UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

191 lines (190 loc) • 5.9 kB
'use strict'; import { ReanimatedError } from '../../../../common'; import { arcToCubic, lineToCubic, NUMBER_PATTERN, PATH_COMMAND_LENGTHS, quadraticToCubic, reflectControlPoint, SEGMENT_PATTERN } from '../../../utils'; export const ERROR_MESSAGES = { invalidSVGPathCommand: (command, args) => `Invalid SVG Path command: ${JSON.stringify(command)} ${JSON.stringify(args)}`, invalidSVGPathStart: command => `Invalid SVG Path: Path must start with "M" or "m", but found "${command}"` }; export const processSVGPath = d => { let pathSegments = parsePathString(d); pathSegments = normalizePath(pathSegments); return pathSegments.flatMap(subArr => subArr).join(' '); }; function processPathSegment(command, args, pathSegments) { let type = command.toLowerCase(); if (pathSegments.length === 0 && type !== 'm') { throw new ReanimatedError(ERROR_MESSAGES.invalidSVGPathStart(command)); } let argsStartIdx = 0; if (type === 'm' && args.length > PATH_COMMAND_LENGTHS['m']) { pathSegments.push([command, ...args.slice(0, PATH_COMMAND_LENGTHS['m'])]); argsStartIdx += PATH_COMMAND_LENGTHS['m']; type = 'l'; // If m has more than 2 (length['m']) arguments, use them in implicit l commands command = command === 'm' ? 'l' : 'L'; } do { if (args.length - argsStartIdx < PATH_COMMAND_LENGTHS[type]) { throw new ReanimatedError(ERROR_MESSAGES.invalidSVGPathCommand(command, args.slice(argsStartIdx))); } pathSegments.push([command, ...args.slice(argsStartIdx, argsStartIdx + PATH_COMMAND_LENGTHS[type])]); argsStartIdx += PATH_COMMAND_LENGTHS[type]; } while (args.length - argsStartIdx !== 0); } function parsePathString(d) { const pathSegments = []; d.replace(SEGMENT_PATTERN, (_, command, argsString) => { const numbers = argsString.match(NUMBER_PATTERN); const args = numbers ? numbers.map(Number) : []; processPathSegment(command, args, pathSegments); return ''; }); return pathSegments; } function normalizePath(pathSegments) { const absoluteSegments = absolutizePath(pathSegments); const out = []; const state = { curX: 0, curY: 0, startX: 0, startY: 0, lastCubicCtrlX: 0, lastCubicCtrlY: 0, lastQuadCtrlX: 0, lastQuadCtrlY: 0 }; for (const seg of absoluteSegments) { let cmd = seg[0]; let args = [...seg.slice(1)]; if (cmd === 'S') { const [rX, rY] = reflectControlPoint(state.curX, state.curY, state.lastCubicCtrlX, state.lastCubicCtrlY); cmd = 'C'; args = [rX, rY, args[0], args[1], args[2], args[3]]; } else if (cmd === 'T') { const [rX, rY] = reflectControlPoint(state.curX, state.curY, state.lastQuadCtrlX, state.lastQuadCtrlY); cmd = 'Q'; args = [rX, rY, args[0], args[1]]; } if (cmd === 'H') { cmd = 'L'; args = [args[0], state.curY]; } if (cmd === 'V') { cmd = 'L'; args = [state.curX, args[0]]; } const result = handlers[cmd](state, args); out.push(...result); const isCubic = cmd === 'C'; const isQuad = cmd === 'Q'; if (!isCubic) { state.lastCubicCtrlX = state.curX; state.lastCubicCtrlY = state.curY; } if (!isQuad) { state.lastQuadCtrlX = state.curX; state.lastQuadCtrlY = state.curY; } } return out; } function absolutizePath(pathSegments) { let curX = 0, curY = 0; let startX = 0, startY = 0; return pathSegments.map(seg => { const cmd = seg[0]; const upperCmd = cmd.toUpperCase(); const isRelative = cmd != upperCmd; const args = seg.slice(1); if (isRelative) { if (upperCmd === 'A') { args[5] += curX; args[6] += curY; } else if (upperCmd === 'V') { args[0] += curY; } else if (upperCmd === 'H') { args[0] += curX; } else { for (let i = 0; i < args.length; i += 2) { args[i] += curX; args[i + 1] += curY; } } } switch (upperCmd) { case 'Z': curX = startX; curY = startY; break; case 'H': curX = args[0]; break; case 'V': curY = args[0]; break; case 'M': curX = args[0]; curY = args[1]; startX = curX; startY = curY; break; default: // For L, C, S, Q, T, A the last pair is the new cursor if (args.length >= 2) { curX = args[args.length - 2]; curY = args[args.length - 1]; } } return [upperCmd, ...args]; }); } const handlers = { M: (state, args) => { const [x, y] = args; state.curX = state.startX = x; state.curY = state.startY = y; return [['M', x, y]]; }, L: (state, args) => { const [x, y] = args; const cubic = lineToCubic(state.curX, state.curY, x, y); state.curX = x; state.curY = y; return [['C', ...cubic]]; }, Q: (state, args) => { const [qcx, qcy, x, y] = args; const cubic = quadraticToCubic(state.curX, state.curY, qcx, qcy, x, y); state.lastQuadCtrlX = qcx; state.lastQuadCtrlY = qcy; state.curX = x; state.curY = y; return [['C', ...cubic]]; }, C: (state, args) => { const [_cp1x, _cp1y, cp2x, cp2y, x, y] = args; state.lastCubicCtrlX = cp2x; state.lastCubicCtrlY = cp2y; state.curX = x; state.curY = y; return [['C', ...args]]; }, A: (state, args) => { const cubics = arcToCubic(state.curX, state.curY, args[0], args[1], args[2], args[3], args[4], args[5], args[6]); if (cubics.length === 0) return []; const last = cubics[cubics.length - 1]; state.lastCubicCtrlX = last[2]; state.lastCubicCtrlY = last[3]; state.curX = last[4]; state.curY = last[5]; return cubics.map(c => ['C', ...c]); }, Z: state => { state.curX = state.startX; state.curY = state.startY; return [['Z']]; } }; //# sourceMappingURL=path.js.map