File size: 3,630 Bytes
f56a29b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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',
};

/**
 * 简单解析SVG路径
 * @param d SVG path d属性
 */
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>;

/**
 * 解析SVG路径,并将圆弧(A)类型的路径转为三次贝塞尔(C)类型的路径
 * @param d SVG path d属性
 *
 * Returns an empty array if the path is malformed (e.g. unrecognised commands).
 * Mirrors the defensive behaviour of {@link getSvgPathRange}: a single bad path
 * (often produced by upstream LLM hallucinations) shouldn't take down the whole
 * PPTX export.
 */
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>;