source: imaps-frontend/node_modules/svg-pathdata/src/SVGPathDataParser.ts@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 10.0 KB
Line 
1// Parse SVG PathData
2// http://www.w3.org/TR/SVG/paths.html#PathDataBNF
3import { COMMAND_ARG_COUNTS, SVGPathData } from "./SVGPathData";
4import { TransformableSVG } from "./TransformableSVG";
5import { SVGCommand, TransformFunction } from "./types";
6// Private consts : Char groups
7const isWhiteSpace = (c: string) =>
8 " " === c || "\t" === c || "\r" === c || "\n" === c;
9const isDigit = (c: string) =>
10 "0".charCodeAt(0) <= c.charCodeAt(0) && c.charCodeAt(0) <= "9".charCodeAt(0);
11const COMMANDS = "mMzZlLhHvVcCsSqQtTaA";
12
13export class SVGPathDataParser extends TransformableSVG {
14 private curNumber: string = "";
15 private curCommandType: SVGCommand["type"] | -1 = -1;
16 private curCommandRelative = false;
17 private canParseCommandOrComma = true;
18 private curNumberHasExp = false;
19 private curNumberHasExpDigits = false;
20 private curNumberHasDecimal = false;
21 private curArgs: number[] = [];
22
23 constructor() {
24 super();
25 }
26
27 finish(commands: SVGCommand[] = []) {
28 this.parse(" ", commands);
29 // Adding residual command
30 if (0 !== this.curArgs.length || !this.canParseCommandOrComma) {
31 throw new SyntaxError("Unterminated command at the path end.");
32 }
33 return commands;
34 }
35
36 parse(str: string, commands: SVGCommand[] = []) {
37 const finishCommand = (command: SVGCommand) => {
38 commands.push(command);
39 this.curArgs.length = 0;
40 this.canParseCommandOrComma = true;
41 };
42
43 for (let i = 0; i < str.length; i++) {
44 const c = str[i];
45 // White spaces parsing
46 const isAArcFlag = this.curCommandType === SVGPathData.ARC &&
47 (this.curArgs.length === 3 || this.curArgs.length === 4) &&
48 this.curNumber.length === 1 &&
49 (this.curNumber === "0" || this.curNumber === "1");
50 const isEndingDigit = isDigit(c) && (
51 (this.curNumber === "0" && c === "0") ||
52 isAArcFlag
53 );
54
55 if (
56 isDigit(c) &&
57 !isEndingDigit
58 ) {
59 this.curNumber += c;
60 this.curNumberHasExpDigits = this.curNumberHasExp;
61 continue;
62 }
63 if ("e" === c || "E" === c) {
64 this.curNumber += c;
65 this.curNumberHasExp = true;
66 continue;
67 }
68 if (
69 ("-" === c || "+" === c) &&
70 this.curNumberHasExp &&
71 !this.curNumberHasExpDigits
72 ) {
73 this.curNumber += c;
74 continue;
75 }
76 // if we already have a ".", it means we are starting a new number
77 if ("." === c && !this.curNumberHasExp && !this.curNumberHasDecimal && !isAArcFlag) {
78 this.curNumber += c;
79 this.curNumberHasDecimal = true;
80 continue;
81 }
82
83 // New number
84 if (this.curNumber && -1 !== this.curCommandType) {
85 const val = Number(this.curNumber);
86 if (isNaN(val)) {
87 throw new SyntaxError(`Invalid number ending at ${i}`);
88 }
89 if (this.curCommandType === SVGPathData.ARC) {
90 if (0 === this.curArgs.length || 1 === this.curArgs.length) {
91 if (0 > val) {
92 throw new SyntaxError(
93 `Expected positive number, got "${val}" at index "${i}"`,
94 );
95 }
96 } else if (3 === this.curArgs.length || 4 === this.curArgs.length) {
97 if ("0" !== this.curNumber && "1" !== this.curNumber) {
98 throw new SyntaxError(
99 `Expected a flag, got "${this.curNumber}" at index "${i}"`,
100 );
101 }
102 }
103 }
104 this.curArgs.push(val);
105 if (this.curArgs.length === COMMAND_ARG_COUNTS[this.curCommandType]) {
106 if (SVGPathData.HORIZ_LINE_TO === this.curCommandType) {
107 finishCommand({
108 type: SVGPathData.HORIZ_LINE_TO,
109 relative: this.curCommandRelative,
110 x: val,
111 });
112 } else if (SVGPathData.VERT_LINE_TO === this.curCommandType) {
113 finishCommand({
114 type: SVGPathData.VERT_LINE_TO,
115 relative: this.curCommandRelative,
116 y: val,
117 });
118 // Move to / line to / smooth quadratic curve to commands (x, y)
119 } else if (
120 this.curCommandType === SVGPathData.MOVE_TO ||
121 this.curCommandType === SVGPathData.LINE_TO ||
122 this.curCommandType === SVGPathData.SMOOTH_QUAD_TO
123 ) {
124 finishCommand({
125 type: this.curCommandType,
126 relative: this.curCommandRelative,
127 x: this.curArgs[0],
128 y: this.curArgs[1],
129 } as SVGCommand);
130 // Switch to line to state
131 if (SVGPathData.MOVE_TO === this.curCommandType) {
132 this.curCommandType = SVGPathData.LINE_TO;
133 }
134 } else if (this.curCommandType === SVGPathData.CURVE_TO) {
135 finishCommand({
136 type: SVGPathData.CURVE_TO,
137 relative: this.curCommandRelative,
138 x1: this.curArgs[0],
139 y1: this.curArgs[1],
140 x2: this.curArgs[2],
141 y2: this.curArgs[3],
142 x: this.curArgs[4],
143 y: this.curArgs[5],
144 });
145 } else if (this.curCommandType === SVGPathData.SMOOTH_CURVE_TO) {
146 finishCommand({
147 type: SVGPathData.SMOOTH_CURVE_TO,
148 relative: this.curCommandRelative,
149 x2: this.curArgs[0],
150 y2: this.curArgs[1],
151 x: this.curArgs[2],
152 y: this.curArgs[3],
153 });
154 } else if (this.curCommandType === SVGPathData.QUAD_TO) {
155 finishCommand({
156 type: SVGPathData.QUAD_TO,
157 relative: this.curCommandRelative,
158 x1: this.curArgs[0],
159 y1: this.curArgs[1],
160 x: this.curArgs[2],
161 y: this.curArgs[3],
162 });
163 } else if (this.curCommandType === SVGPathData.ARC) {
164 finishCommand({
165 type: SVGPathData.ARC,
166 relative: this.curCommandRelative,
167 rX: this.curArgs[0],
168 rY: this.curArgs[1],
169 xRot: this.curArgs[2],
170 lArcFlag: this.curArgs[3] as 0 | 1,
171 sweepFlag: this.curArgs[4] as 0 | 1,
172 x: this.curArgs[5],
173 y: this.curArgs[6],
174 });
175 }
176 }
177 this.curNumber = "";
178 this.curNumberHasExpDigits = false;
179 this.curNumberHasExp = false;
180 this.curNumberHasDecimal = false;
181 this.canParseCommandOrComma = true;
182 }
183 // Continue if a white space or a comma was detected
184 if (isWhiteSpace(c)) {
185 continue;
186 }
187 if ("," === c && this.canParseCommandOrComma) {
188 // L 0,0, H is not valid:
189 this.canParseCommandOrComma = false;
190 continue;
191 }
192 // if a sign is detected, then parse the new number
193 if ("+" === c || "-" === c || "." === c) {
194 this.curNumber = c;
195 this.curNumberHasDecimal = "." === c;
196 continue;
197 }
198 // if a 0 is detected, then parse the new number
199 if (isEndingDigit) {
200 this.curNumber = c;
201 this.curNumberHasDecimal = false;
202 continue;
203 }
204
205 // Adding residual command
206 if (0 !== this.curArgs.length) {
207 throw new SyntaxError(`Unterminated command at index ${i}.`);
208 }
209 if (!this.canParseCommandOrComma) {
210 throw new SyntaxError(
211 `Unexpected character "${c}" at index ${i}. Command cannot follow comma`,
212 );
213 }
214 this.canParseCommandOrComma = false;
215 // Detecting the next command
216 if ("z" === c || "Z" === c) {
217 commands.push({
218 type: SVGPathData.CLOSE_PATH,
219 });
220 this.canParseCommandOrComma = true;
221 this.curCommandType = -1;
222 continue;
223 // Horizontal move to command
224 } else if ("h" === c || "H" === c) {
225 this.curCommandType = SVGPathData.HORIZ_LINE_TO;
226 this.curCommandRelative = "h" === c;
227 // Vertical move to command
228 } else if ("v" === c || "V" === c) {
229 this.curCommandType = SVGPathData.VERT_LINE_TO;
230 this.curCommandRelative = "v" === c;
231 // Move to command
232 } else if ("m" === c || "M" === c) {
233 this.curCommandType = SVGPathData.MOVE_TO;
234 this.curCommandRelative = "m" === c;
235 // Line to command
236 } else if ("l" === c || "L" === c) {
237 this.curCommandType = SVGPathData.LINE_TO;
238 this.curCommandRelative = "l" === c;
239 // Curve to command
240 } else if ("c" === c || "C" === c) {
241 this.curCommandType = SVGPathData.CURVE_TO;
242 this.curCommandRelative = "c" === c;
243 // Smooth curve to command
244 } else if ("s" === c || "S" === c) {
245 this.curCommandType = SVGPathData.SMOOTH_CURVE_TO;
246 this.curCommandRelative = "s" === c;
247 // Quadratic bezier curve to command
248 } else if ("q" === c || "Q" === c) {
249 this.curCommandType = SVGPathData.QUAD_TO;
250 this.curCommandRelative = "q" === c;
251 // Smooth quadratic bezier curve to command
252 } else if ("t" === c || "T" === c) {
253 this.curCommandType = SVGPathData.SMOOTH_QUAD_TO;
254 this.curCommandRelative = "t" === c;
255 // Elliptic arc command
256 } else if ("a" === c || "A" === c) {
257 this.curCommandType = SVGPathData.ARC;
258 this.curCommandRelative = "a" === c;
259 } else {
260 throw new SyntaxError(`Unexpected character "${c}" at index ${i}.`);
261 }
262 }
263 return commands;
264 }
265 /**
266 * Return a wrapper around this parser which applies the transformation on parsed commands.
267 */
268 transform(transform: TransformFunction) {
269 const result = Object.create(this, {
270 parse: {
271 value(chunk: string, commands: SVGCommand[] = []) {
272 const parsedCommands = Object.getPrototypeOf(this).parse.call(
273 this,
274 chunk,
275 );
276 for (const c of parsedCommands) {
277 const cT = transform(c);
278 if (Array.isArray(cT)) {
279 commands.push(...cT);
280 } else {
281 commands.push(cT);
282 }
283 }
284 return commands;
285 },
286 },
287 });
288 return result as this;
289 }
290}
Note: See TracBrowser for help on using the repository browser.