svger-cli 4.0.1 → 4.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +230 -0
- package/README.md +27 -27
- package/dist/builder.d.ts +6 -3
- package/dist/builder.js +34 -24
- package/dist/cli.js +122 -10
- package/dist/config.d.ts +8 -2
- package/dist/config.js +17 -124
- package/dist/core/enhanced-plugin-manager.d.ts +1 -0
- package/dist/core/enhanced-plugin-manager.js +37 -11
- package/dist/core/framework-templates.js +4 -0
- package/dist/core/logger.js +8 -4
- package/dist/core/performance-engine.js +16 -3
- package/dist/core/style-compiler.js +6 -7
- package/dist/core/template-manager.js +18 -14
- package/dist/index.d.ts +1 -2
- package/dist/index.js +8 -2
- package/dist/integrations/jest-preset.js +30 -2
- package/dist/lock.js +1 -1
- package/dist/optimizers/basic-cleaner.js +4 -0
- package/dist/optimizers/path-parser.js +199 -115
- package/dist/optimizers/path-simplifier.js +27 -24
- package/dist/optimizers/remove-unused-defs.js +16 -0
- package/dist/optimizers/shape-conversion.js +22 -27
- package/dist/optimizers/style-optimizer.js +5 -0
- package/dist/optimizers/svg-tree-parser.js +4 -0
- package/dist/optimizers/transform-collapsing.js +11 -15
- package/dist/optimizers/transform-optimizer.js +20 -21
- package/dist/optimizers/types.js +64 -74
- package/dist/plugins/gradient-optimizer.js +4 -0
- package/dist/processors/svg-processor.js +28 -10
- package/dist/services/config.js +28 -11
- package/dist/services/file-watcher.js +8 -3
- package/dist/services/svg-service.d.ts +1 -1
- package/dist/services/svg-service.js +24 -11
- package/dist/utils/native.d.ts +0 -1
- package/dist/utils/native.js +6 -14
- package/dist/utils/visual-diff.js +7 -2
- package/dist/watch.js +4 -3
- package/docs/ERROR-HANDLING-STANDARD.md +111 -0
- package/docs/OPTIONAL-DEPENDENCIES.md +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
* A high-performance, framework-agnostic SVG processing toolkit with enterprise-grade
|
|
5
5
|
* architecture and comprehensive styling capabilities.
|
|
6
6
|
*
|
|
7
|
-
* @version 2.0.0
|
|
8
7
|
* @author SVGER-CLI Development Team
|
|
9
8
|
* @license MIT
|
|
10
9
|
*/
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { dirname, join } from 'path';
|
|
13
|
+
// Get package version dynamically
|
|
14
|
+
const __index_filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __index_dirname = dirname(__index_filename);
|
|
16
|
+
const __packageJson = JSON.parse(readFileSync(join(__index_dirname, '../package.json'), 'utf-8'));
|
|
11
17
|
// ============================================================================
|
|
12
18
|
// CORE SERVICES
|
|
13
19
|
// ============================================================================
|
|
@@ -149,7 +155,7 @@ export const SVGER = {
|
|
|
149
155
|
/**
|
|
150
156
|
* Package Version Information
|
|
151
157
|
*/
|
|
152
|
-
export const VERSION =
|
|
158
|
+
export const VERSION = __packageJson.version;
|
|
153
159
|
export const PACKAGE_NAME = 'svger-cli';
|
|
154
160
|
// ============================================================================
|
|
155
161
|
// BUILD TOOL INTEGRATIONS
|
|
@@ -72,12 +72,40 @@ module.exports.default = ${componentName};
|
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* Synchronous SVG content cleaning for Jest compatibility.
|
|
77
|
+
* svgProcessor.cleanSVGContent() is async (uses optimizer pipeline) and cannot
|
|
78
|
+
* be called from Jest's synchronous process() method. This provides equivalent
|
|
79
|
+
* basic cleaning without the async optimizer pipeline.
|
|
80
|
+
*/
|
|
81
|
+
function cleanSVGContentSync(svgContent) {
|
|
82
|
+
return (svgContent
|
|
83
|
+
// Remove XML declaration
|
|
84
|
+
.replace(/<\?xml.*?\?>/g, '')
|
|
85
|
+
// Remove DOCTYPE declaration
|
|
86
|
+
.replace(/<!DOCTYPE.*?>/g, '')
|
|
87
|
+
// Remove comments
|
|
88
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
89
|
+
// Normalize whitespace
|
|
90
|
+
.replace(/\r?\n|\r/g, '')
|
|
91
|
+
.replace(/\s{2,}/g, ' ')
|
|
92
|
+
// Remove xmlns attributes
|
|
93
|
+
.replace(/\s+xmlns(:xlink)?="[^"]*"/g, '')
|
|
94
|
+
// Remove metadata
|
|
95
|
+
.replace(/<metadata[\s\S]*?<\/metadata>/gi, '')
|
|
96
|
+
.replace(/<title[\s\S]*?<\/title>/gi, '')
|
|
97
|
+
.replace(/<desc[\s\S]*?<\/desc>/gi, '')
|
|
98
|
+
.trim()
|
|
99
|
+
// Extract inner content from <svg> tag
|
|
100
|
+
.replace(/^<svg[^>]*>([\s\S]*)<\/svg>$/i, '$1')
|
|
101
|
+
.trim());
|
|
102
|
+
}
|
|
75
103
|
/**
|
|
76
104
|
* Generate component synchronously (for Jest compatibility)
|
|
77
105
|
*/
|
|
78
106
|
function generateComponentSync(componentName, svgContent, options) {
|
|
79
|
-
// Clean SVG content
|
|
80
|
-
const cleanedContent =
|
|
107
|
+
// Clean SVG content synchronously (cleanSVGContent is async and cannot be used here)
|
|
108
|
+
const cleanedContent = cleanSVGContentSync(svgContent);
|
|
81
109
|
// For Jest, we'll generate a simple React component
|
|
82
110
|
const viewBox = svgProcessor.extractViewBox(svgContent);
|
|
83
111
|
const template = `
|
package/dist/lock.js
CHANGED
|
@@ -7,7 +7,7 @@ const LOCK_FILE = '.svg-lock';
|
|
|
7
7
|
* @returns {string} Absolute path to .svg-lock
|
|
8
8
|
*/
|
|
9
9
|
function getLockFilePath() {
|
|
10
|
-
return path.resolve(LOCK_FILE);
|
|
10
|
+
return path.resolve(process.cwd(), LOCK_FILE);
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Read the current locked SVG files from the lock file.
|
|
@@ -204,6 +204,10 @@ export function sortAttributes(svg, config) {
|
|
|
204
204
|
const attrRegex = /([a-zA-Z][a-zA-Z0-9-]*)="([^"]*)"/g;
|
|
205
205
|
let attrMatch;
|
|
206
206
|
while ((attrMatch = attrRegex.exec(attrs)) !== null) {
|
|
207
|
+
// Prevent infinite loop if regex doesn't advance
|
|
208
|
+
if (attrMatch.index === attrRegex.lastIndex) {
|
|
209
|
+
attrRegex.lastIndex++;
|
|
210
|
+
}
|
|
207
211
|
attrPairs.push([attrMatch[1], attrMatch[2]]);
|
|
208
212
|
}
|
|
209
213
|
// Sort alphabetically
|
|
@@ -37,16 +37,55 @@ const COMMAND_PARAMS = {
|
|
|
37
37
|
z: 0, // no params
|
|
38
38
|
};
|
|
39
39
|
/**
|
|
40
|
-
* Check if character is a path command letter
|
|
40
|
+
* Check if character is a path command letter - O(1) Set lookup
|
|
41
41
|
*/
|
|
42
|
+
const COMMAND_LETTERS = new Set([
|
|
43
|
+
'M',
|
|
44
|
+
'm',
|
|
45
|
+
'L',
|
|
46
|
+
'l',
|
|
47
|
+
'H',
|
|
48
|
+
'h',
|
|
49
|
+
'V',
|
|
50
|
+
'v',
|
|
51
|
+
'C',
|
|
52
|
+
'c',
|
|
53
|
+
'S',
|
|
54
|
+
's',
|
|
55
|
+
'Q',
|
|
56
|
+
'q',
|
|
57
|
+
'T',
|
|
58
|
+
't',
|
|
59
|
+
'A',
|
|
60
|
+
'a',
|
|
61
|
+
'Z',
|
|
62
|
+
'z',
|
|
63
|
+
]);
|
|
42
64
|
function isCommandLetter(char) {
|
|
43
|
-
return
|
|
65
|
+
return COMMAND_LETTERS.has(char);
|
|
44
66
|
}
|
|
45
67
|
/**
|
|
46
|
-
* Check if character is numeric (digit, decimal, sign, exponent)
|
|
68
|
+
* Check if character is numeric (digit, decimal, sign, exponent) - O(1) Set lookup
|
|
47
69
|
*/
|
|
70
|
+
const NUMERIC_CHARS = new Set([
|
|
71
|
+
'0',
|
|
72
|
+
'1',
|
|
73
|
+
'2',
|
|
74
|
+
'3',
|
|
75
|
+
'4',
|
|
76
|
+
'5',
|
|
77
|
+
'6',
|
|
78
|
+
'7',
|
|
79
|
+
'8',
|
|
80
|
+
'9',
|
|
81
|
+
'.',
|
|
82
|
+
'e',
|
|
83
|
+
'E',
|
|
84
|
+
'+',
|
|
85
|
+
'-',
|
|
86
|
+
]);
|
|
48
87
|
function isNumericChar(char) {
|
|
49
|
-
return
|
|
88
|
+
return NUMERIC_CHARS.has(char);
|
|
50
89
|
}
|
|
51
90
|
/**
|
|
52
91
|
* Tokenize path string into array of tokens (commands and numbers)
|
|
@@ -158,67 +197,87 @@ export function parsePath(pathData) {
|
|
|
158
197
|
// Skip incomplete command
|
|
159
198
|
continue;
|
|
160
199
|
}
|
|
161
|
-
// Update current position
|
|
200
|
+
// Update current position using O(1) object lookup instead of switch
|
|
162
201
|
const isUpperCase = currentCommand === currentCommand.toUpperCase();
|
|
163
202
|
if (isUpperCase) {
|
|
164
|
-
// Absolute
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
currentY =
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
currentX =
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
// Absolute position update handlers
|
|
204
|
+
const absoluteHandlers = {
|
|
205
|
+
M: v => {
|
|
206
|
+
currentX = v[v.length - 2];
|
|
207
|
+
currentY = v[v.length - 1];
|
|
208
|
+
},
|
|
209
|
+
L: v => {
|
|
210
|
+
currentX = v[v.length - 2];
|
|
211
|
+
currentY = v[v.length - 1];
|
|
212
|
+
},
|
|
213
|
+
T: v => {
|
|
214
|
+
currentX = v[v.length - 2];
|
|
215
|
+
currentY = v[v.length - 1];
|
|
216
|
+
},
|
|
217
|
+
H: v => {
|
|
218
|
+
currentX = v[v.length - 1];
|
|
219
|
+
},
|
|
220
|
+
V: v => {
|
|
221
|
+
currentY = v[v.length - 1];
|
|
222
|
+
},
|
|
223
|
+
C: v => {
|
|
224
|
+
currentX = v[v.length - 2];
|
|
225
|
+
currentY = v[v.length - 1];
|
|
226
|
+
},
|
|
227
|
+
S: v => {
|
|
228
|
+
currentX = v[v.length - 2];
|
|
229
|
+
currentY = v[v.length - 1];
|
|
230
|
+
},
|
|
231
|
+
Q: v => {
|
|
232
|
+
currentX = v[v.length - 2];
|
|
233
|
+
currentY = v[v.length - 1];
|
|
234
|
+
},
|
|
235
|
+
A: v => {
|
|
236
|
+
currentX = v[v.length - 2];
|
|
237
|
+
currentY = v[v.length - 1];
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
absoluteHandlers[currentCommand]?.(values);
|
|
192
241
|
}
|
|
193
242
|
else {
|
|
194
|
-
// Relative
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
currentY +=
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
currentX +=
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
243
|
+
// Relative position update handlers
|
|
244
|
+
const relativeHandlers = {
|
|
245
|
+
m: v => {
|
|
246
|
+
currentX += v[v.length - 2];
|
|
247
|
+
currentY += v[v.length - 1];
|
|
248
|
+
},
|
|
249
|
+
l: v => {
|
|
250
|
+
currentX += v[v.length - 2];
|
|
251
|
+
currentY += v[v.length - 1];
|
|
252
|
+
},
|
|
253
|
+
t: v => {
|
|
254
|
+
currentX += v[v.length - 2];
|
|
255
|
+
currentY += v[v.length - 1];
|
|
256
|
+
},
|
|
257
|
+
h: v => {
|
|
258
|
+
currentX += v[v.length - 1];
|
|
259
|
+
},
|
|
260
|
+
v: v => {
|
|
261
|
+
currentY += v[v.length - 1];
|
|
262
|
+
},
|
|
263
|
+
c: v => {
|
|
264
|
+
currentX += v[v.length - 2];
|
|
265
|
+
currentY += v[v.length - 1];
|
|
266
|
+
},
|
|
267
|
+
s: v => {
|
|
268
|
+
currentX += v[v.length - 2];
|
|
269
|
+
currentY += v[v.length - 1];
|
|
270
|
+
},
|
|
271
|
+
q: v => {
|
|
272
|
+
currentX += v[v.length - 2];
|
|
273
|
+
currentY += v[v.length - 1];
|
|
274
|
+
},
|
|
275
|
+
a: v => {
|
|
276
|
+
currentX += v[v.length - 2];
|
|
277
|
+
currentY += v[v.length - 1];
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
relativeHandlers[currentCommand]?.(values);
|
|
222
281
|
}
|
|
223
282
|
// Store command with absolute position
|
|
224
283
|
commands.push({
|
|
@@ -248,33 +307,40 @@ export function toAbsolute(cmd, prevX = 0, prevY = 0) {
|
|
|
248
307
|
if (type === type.toUpperCase()) {
|
|
249
308
|
return cmd;
|
|
250
309
|
}
|
|
251
|
-
// Convert relative to absolute
|
|
310
|
+
// Convert relative to absolute using O(1) lookup
|
|
252
311
|
const absoluteType = type.toUpperCase();
|
|
253
312
|
const values = [...cmd.values];
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
case 't':
|
|
258
|
-
// x y → x+prevX y+prevY
|
|
313
|
+
// Transformation strategies keyed by command letter
|
|
314
|
+
const transformers = {
|
|
315
|
+
m: () => {
|
|
259
316
|
for (let i = 0; i < values.length; i += 2) {
|
|
260
317
|
values[i] += prevX;
|
|
261
318
|
values[i + 1] += prevY;
|
|
262
319
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
320
|
+
},
|
|
321
|
+
l: () => {
|
|
322
|
+
for (let i = 0; i < values.length; i += 2) {
|
|
323
|
+
values[i] += prevX;
|
|
324
|
+
values[i + 1] += prevY;
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
t: () => {
|
|
328
|
+
for (let i = 0; i < values.length; i += 2) {
|
|
329
|
+
values[i] += prevX;
|
|
330
|
+
values[i + 1] += prevY;
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
h: () => {
|
|
266
334
|
for (let i = 0; i < values.length; i++) {
|
|
267
335
|
values[i] += prevX;
|
|
268
336
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
// y → y+prevY
|
|
337
|
+
},
|
|
338
|
+
v: () => {
|
|
272
339
|
for (let i = 0; i < values.length; i++) {
|
|
273
340
|
values[i] += prevY;
|
|
274
341
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// x1 y1 x2 y2 x y → all +prev
|
|
342
|
+
},
|
|
343
|
+
c: () => {
|
|
278
344
|
for (let i = 0; i < values.length; i += 6) {
|
|
279
345
|
values[i] += prevX;
|
|
280
346
|
values[i + 1] += prevY;
|
|
@@ -283,28 +349,34 @@ export function toAbsolute(cmd, prevX = 0, prevY = 0) {
|
|
|
283
349
|
values[i + 4] += prevX;
|
|
284
350
|
values[i + 5] += prevY;
|
|
285
351
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
352
|
+
},
|
|
353
|
+
s: () => {
|
|
354
|
+
for (let i = 0; i < values.length; i += 4) {
|
|
355
|
+
values[i] += prevX;
|
|
356
|
+
values[i + 1] += prevY;
|
|
357
|
+
values[i + 2] += prevX;
|
|
358
|
+
values[i + 3] += prevY;
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
q: () => {
|
|
290
362
|
for (let i = 0; i < values.length; i += 4) {
|
|
291
363
|
values[i] += prevX;
|
|
292
364
|
values[i + 1] += prevY;
|
|
293
365
|
values[i + 2] += prevX;
|
|
294
366
|
values[i + 3] += prevY;
|
|
295
367
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
// rx ry rotation large-arc sweep x y → only x,y +prev
|
|
368
|
+
},
|
|
369
|
+
a: () => {
|
|
299
370
|
for (let i = 0; i < values.length; i += 7) {
|
|
300
371
|
values[i + 5] += prevX;
|
|
301
372
|
values[i + 6] += prevY;
|
|
302
373
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
374
|
+
},
|
|
375
|
+
z: () => {
|
|
376
|
+
/* No conversion needed */
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
transformers[type]?.();
|
|
308
380
|
return {
|
|
309
381
|
type: absoluteType,
|
|
310
382
|
values,
|
|
@@ -320,33 +392,39 @@ export function toRelative(cmd, prevX = 0, prevY = 0) {
|
|
|
320
392
|
if (type === type.toLowerCase()) {
|
|
321
393
|
return cmd;
|
|
322
394
|
}
|
|
323
|
-
// Convert absolute to relative
|
|
395
|
+
// Convert absolute to relative using O(1) lookup
|
|
324
396
|
const relativeType = type.toLowerCase();
|
|
325
397
|
const values = [...cmd.values];
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
398
|
+
const transformers = {
|
|
399
|
+
M: () => {
|
|
400
|
+
for (let i = 0; i < values.length; i += 2) {
|
|
401
|
+
values[i] -= prevX;
|
|
402
|
+
values[i + 1] -= prevY;
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
L: () => {
|
|
406
|
+
for (let i = 0; i < values.length; i += 2) {
|
|
407
|
+
values[i] -= prevX;
|
|
408
|
+
values[i + 1] -= prevY;
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
T: () => {
|
|
331
412
|
for (let i = 0; i < values.length; i += 2) {
|
|
332
413
|
values[i] -= prevX;
|
|
333
414
|
values[i + 1] -= prevY;
|
|
334
415
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
// x → x-prevX
|
|
416
|
+
},
|
|
417
|
+
H: () => {
|
|
338
418
|
for (let i = 0; i < values.length; i++) {
|
|
339
419
|
values[i] -= prevX;
|
|
340
420
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// y → y-prevY
|
|
421
|
+
},
|
|
422
|
+
V: () => {
|
|
344
423
|
for (let i = 0; i < values.length; i++) {
|
|
345
424
|
values[i] -= prevY;
|
|
346
425
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// x1 y1 x2 y2 x y → all -prev
|
|
426
|
+
},
|
|
427
|
+
C: () => {
|
|
350
428
|
for (let i = 0; i < values.length; i += 6) {
|
|
351
429
|
values[i] -= prevX;
|
|
352
430
|
values[i + 1] -= prevY;
|
|
@@ -355,28 +433,34 @@ export function toRelative(cmd, prevX = 0, prevY = 0) {
|
|
|
355
433
|
values[i + 4] -= prevX;
|
|
356
434
|
values[i + 5] -= prevY;
|
|
357
435
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
case 'Q':
|
|
361
|
-
// x1 y1 x y → all -prev
|
|
436
|
+
},
|
|
437
|
+
S: () => {
|
|
362
438
|
for (let i = 0; i < values.length; i += 4) {
|
|
363
439
|
values[i] -= prevX;
|
|
364
440
|
values[i + 1] -= prevY;
|
|
365
441
|
values[i + 2] -= prevX;
|
|
366
442
|
values[i + 3] -= prevY;
|
|
367
443
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
444
|
+
},
|
|
445
|
+
Q: () => {
|
|
446
|
+
for (let i = 0; i < values.length; i += 4) {
|
|
447
|
+
values[i] -= prevX;
|
|
448
|
+
values[i + 1] -= prevY;
|
|
449
|
+
values[i + 2] -= prevX;
|
|
450
|
+
values[i + 3] -= prevY;
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
A: () => {
|
|
371
454
|
for (let i = 0; i < values.length; i += 7) {
|
|
372
455
|
values[i + 5] -= prevX;
|
|
373
456
|
values[i + 6] -= prevY;
|
|
374
457
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
458
|
+
},
|
|
459
|
+
Z: () => {
|
|
460
|
+
/* No conversion needed */
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
transformers[type]?.();
|
|
380
464
|
return {
|
|
381
465
|
type: relativeType,
|
|
382
466
|
values,
|
|
@@ -179,45 +179,48 @@ function extractLinearPoints(commands, startX, startY) {
|
|
|
179
179
|
for (let i = 0; i < commands.length; i++) {
|
|
180
180
|
const cmd = commands[i];
|
|
181
181
|
const isRelative = cmd.type === cmd.type.toLowerCase();
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
const upperType = cmd.type.toUpperCase();
|
|
183
|
+
// O(1) object lookup for command type → point extraction
|
|
184
|
+
const pointExtractors = {
|
|
185
|
+
L: () => {
|
|
184
186
|
const x = isRelative ? currentX + cmd.values[0] : cmd.values[0];
|
|
185
187
|
const y = isRelative ? currentY + cmd.values[1] : cmd.values[1];
|
|
186
188
|
points.push({ x, y, cmdIndex: i + 1 });
|
|
187
189
|
indices.push(i + 1);
|
|
188
190
|
currentX = x;
|
|
189
191
|
currentY = y;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
case 'H': {
|
|
192
|
+
},
|
|
193
|
+
H: () => {
|
|
193
194
|
const x = isRelative ? currentX + cmd.values[0] : cmd.values[0];
|
|
194
195
|
points.push({ x, y: currentY, cmdIndex: i + 1 });
|
|
195
196
|
indices.push(i + 1);
|
|
196
197
|
currentX = x;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
case 'V': {
|
|
198
|
+
},
|
|
199
|
+
V: () => {
|
|
200
200
|
const y = isRelative ? currentY + cmd.values[0] : cmd.values[0];
|
|
201
201
|
points.push({ x: currentX, y, cmdIndex: i + 1 });
|
|
202
202
|
indices.push(i + 1);
|
|
203
203
|
currentY = y;
|
|
204
|
-
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
const extractor = pointExtractors[upperType];
|
|
207
|
+
if (extractor) {
|
|
208
|
+
extractor();
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// Update position for other commands but don't add to points
|
|
212
|
+
if (upperType === 'M') {
|
|
213
|
+
currentX = isRelative ? currentX + cmd.values[0] : cmd.values[0];
|
|
214
|
+
currentY = isRelative ? currentY + cmd.values[1] : cmd.values[1];
|
|
215
|
+
}
|
|
216
|
+
else if (upperType === 'C') {
|
|
217
|
+
currentX = isRelative ? currentX + cmd.values[4] : cmd.values[4];
|
|
218
|
+
currentY = isRelative ? currentY + cmd.values[5] : cmd.values[5];
|
|
219
|
+
}
|
|
220
|
+
else if (upperType === 'Q') {
|
|
221
|
+
currentX = isRelative ? currentX + cmd.values[2] : cmd.values[2];
|
|
222
|
+
currentY = isRelative ? currentY + cmd.values[3] : cmd.values[3];
|
|
205
223
|
}
|
|
206
|
-
default:
|
|
207
|
-
// Update position for other commands but don't add to points
|
|
208
|
-
if (cmd.type.toUpperCase() === 'M') {
|
|
209
|
-
currentX = isRelative ? currentX + cmd.values[0] : cmd.values[0];
|
|
210
|
-
currentY = isRelative ? currentY + cmd.values[1] : cmd.values[1];
|
|
211
|
-
}
|
|
212
|
-
else if (cmd.type.toUpperCase() === 'C') {
|
|
213
|
-
currentX = isRelative ? currentX + cmd.values[4] : cmd.values[4];
|
|
214
|
-
currentY = isRelative ? currentY + cmd.values[5] : cmd.values[5];
|
|
215
|
-
}
|
|
216
|
-
else if (cmd.type.toUpperCase() === 'Q') {
|
|
217
|
-
currentX = isRelative ? currentX + cmd.values[2] : cmd.values[2];
|
|
218
|
-
currentY = isRelative ? currentY + cmd.values[3] : cmd.values[3];
|
|
219
|
-
}
|
|
220
|
-
break;
|
|
221
224
|
}
|
|
222
225
|
}
|
|
223
226
|
return { points, indices };
|
|
@@ -39,12 +39,20 @@ function findReferencedIds(root) {
|
|
|
39
39
|
let match;
|
|
40
40
|
URL_REFERENCE_REGEX.lastIndex = 0;
|
|
41
41
|
while ((match = URL_REFERENCE_REGEX.exec(attrValue)) !== null) {
|
|
42
|
+
// Prevent infinite loop if regex doesn't advance
|
|
43
|
+
if (match.index === URL_REFERENCE_REGEX.lastIndex) {
|
|
44
|
+
URL_REFERENCE_REGEX.lastIndex++;
|
|
45
|
+
}
|
|
42
46
|
referencedIds.add(match[1]);
|
|
43
47
|
}
|
|
44
48
|
// Check for #id pattern (href, xlink:href)
|
|
45
49
|
if (attrName === 'href' || attrName === 'xlink:href') {
|
|
46
50
|
HREF_REFERENCE_REGEX.lastIndex = 0;
|
|
47
51
|
while ((match = HREF_REFERENCE_REGEX.exec(attrValue)) !== null) {
|
|
52
|
+
// Prevent infinite loop if regex doesn't advance
|
|
53
|
+
if (match.index === HREF_REFERENCE_REGEX.lastIndex) {
|
|
54
|
+
HREF_REFERENCE_REGEX.lastIndex++;
|
|
55
|
+
}
|
|
48
56
|
referencedIds.add(match[1]);
|
|
49
57
|
}
|
|
50
58
|
}
|
|
@@ -56,6 +64,10 @@ function findReferencedIds(root) {
|
|
|
56
64
|
let match;
|
|
57
65
|
URL_REFERENCE_REGEX.lastIndex = 0;
|
|
58
66
|
while ((match = URL_REFERENCE_REGEX.exec(style)) !== null) {
|
|
67
|
+
// Prevent infinite loop if regex doesn't advance
|
|
68
|
+
if (match.index === URL_REFERENCE_REGEX.lastIndex) {
|
|
69
|
+
URL_REFERENCE_REGEX.lastIndex++;
|
|
70
|
+
}
|
|
59
71
|
referencedIds.add(match[1]);
|
|
60
72
|
}
|
|
61
73
|
}
|
|
@@ -64,6 +76,10 @@ function findReferencedIds(root) {
|
|
|
64
76
|
let match;
|
|
65
77
|
URL_REFERENCE_REGEX.lastIndex = 0;
|
|
66
78
|
while ((match = URL_REFERENCE_REGEX.exec(node.content)) !== null) {
|
|
79
|
+
// Prevent infinite loop if regex doesn't advance
|
|
80
|
+
if (match.index === URL_REFERENCE_REGEX.lastIndex) {
|
|
81
|
+
URL_REFERENCE_REGEX.lastIndex++;
|
|
82
|
+
}
|
|
67
83
|
referencedIds.add(match[1]);
|
|
68
84
|
}
|
|
69
85
|
}
|