| import { SVGPathData } from 'svg-pathdata'; |
| import arcToBezier from 'svg-arc-to-cubic-bezier'; |
| import { createLogger } from '@/lib/logger'; |
|
|
| const log = createLogger('SvgPathParser'); |
|
|
| const typeMap = { |
| 1: 'Z', |
| 2: 'M', |
| 4: 'H', |
| 8: 'V', |
| 16: 'L', |
| 32: 'C', |
| 64: 'S', |
| 128: 'Q', |
| 256: 'T', |
| 512: 'A', |
| }; |
|
|
| |
| |
| |
| |
| export const parseSvgPath = (d: string) => { |
| const pathData = new SVGPathData(d); |
|
|
| const ret = pathData.commands.map((item) => { |
| return { ...item, type: typeMap[item.type] }; |
| }); |
| return ret; |
| }; |
|
|
| export type SvgPath = ReturnType<typeof parseSvgPath>; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const toPoints = (d: string) => { |
| let pathData: SVGPathData; |
| try { |
| pathData = new SVGPathData(d); |
| } catch (err) { |
| log.warn(`Failed to parse SVG path "${d}":`, err); |
| return []; |
| } |
|
|
| const points = []; |
| for (const item of pathData.commands) { |
| const type = typeMap[item.type]; |
|
|
| if (item.type === 2 || item.type === 16) { |
| points.push({ |
| x: item.x, |
| y: item.y, |
| relative: item.relative, |
| type, |
| }); |
| } |
| if (item.type === 32) { |
| points.push({ |
| x: item.x, |
| y: item.y, |
| curve: { |
| type: 'cubic', |
| x1: item.x1, |
| y1: item.y1, |
| x2: item.x2, |
| y2: item.y2, |
| }, |
| relative: item.relative, |
| type, |
| }); |
| } else if (item.type === 128) { |
| points.push({ |
| x: item.x, |
| y: item.y, |
| curve: { |
| type: 'quadratic', |
| x1: item.x1, |
| y1: item.y1, |
| }, |
| relative: item.relative, |
| type, |
| }); |
| } else if (item.type === 512) { |
| const lastPoint = points[points.length - 1]; |
| if (!['M', 'L', 'Q', 'C'].includes(lastPoint.type)) continue; |
|
|
| const cubicBezierPoints = arcToBezier({ |
| px: lastPoint.x as number, |
| py: lastPoint.y as number, |
| cx: item.x, |
| cy: item.y, |
| rx: item.rX, |
| ry: item.rY, |
| xAxisRotation: item.xRot, |
| largeArcFlag: item.lArcFlag, |
| sweepFlag: item.sweepFlag, |
| }); |
| for (const cbPoint of cubicBezierPoints) { |
| points.push({ |
| x: cbPoint.x, |
| y: cbPoint.y, |
| curve: { |
| type: 'cubic', |
| x1: cbPoint.x1, |
| y1: cbPoint.y1, |
| x2: cbPoint.x2, |
| y2: cbPoint.y2, |
| }, |
| relative: false, |
| type: 'C', |
| }); |
| } |
| } else if (item.type === 1) { |
| points.push({ close: true, type }); |
| } else continue; |
| } |
| return points; |
| }; |
|
|
| export const getSvgPathRange = (path: string) => { |
| try { |
| const pathData = new SVGPathData(path); |
| const xList = []; |
| const yList = []; |
| for (const item of pathData.commands) { |
| const x = 'x' in item ? item.x : 0; |
| const y = 'y' in item ? item.y : 0; |
| xList.push(x); |
| yList.push(y); |
| } |
| return { |
| minX: Math.min(...xList), |
| minY: Math.min(...yList), |
| maxX: Math.max(...xList), |
| maxY: Math.max(...yList), |
| }; |
| } catch { |
| return { |
| minX: 0, |
| minY: 0, |
| maxX: 0, |
| maxY: 0, |
| }; |
| } |
| }; |
|
|
| export type SvgPoints = ReturnType<typeof toPoints>; |
|
|