svger-cli 4.0.2 → 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 +114 -0
- package/README.md +27 -27
- package/dist/builder.d.ts +6 -3
- package/dist/builder.js +34 -24
- package/dist/cli.js +30 -24
- package/dist/config.d.ts +8 -2
- package/dist/config.js +17 -124
- 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/optimizers/path-parser.js +199 -115
- package/dist/optimizers/path-simplifier.js +27 -24
- package/dist/optimizers/shape-conversion.js +22 -27
- package/dist/optimizers/transform-collapsing.js +11 -15
- package/dist/optimizers/transform-optimizer.js +16 -21
- package/dist/optimizers/types.js +64 -74
- package/dist/processors/svg-processor.js +20 -20
- package/dist/services/config.js +8 -4
- package/dist/services/svg-service.js +9 -2
- package/dist/utils/native.d.ts +0 -1
- package/dist/utils/native.js +6 -14
- package/dist/watch.js +4 -3
- package/docs/ERROR-HANDLING-STANDARD.md +1 -1
- package/docs/OPTIONAL-DEPENDENCIES.md +1 -1
- package/package.json +1 -1
|
@@ -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 };
|
|
@@ -144,8 +144,9 @@ function shouldConvert(element, pathData, threshold) {
|
|
|
144
144
|
function convertShapeToPath(node, threshold) {
|
|
145
145
|
const tag = node.tag;
|
|
146
146
|
let pathData = null;
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
// O(1) object lookup for shape tag → path converter
|
|
148
|
+
const shapeConverters = {
|
|
149
|
+
rect: () => {
|
|
149
150
|
const x = parseFloat(node.attrs.get('x') || '0');
|
|
150
151
|
const y = parseFloat(node.attrs.get('y') || '0');
|
|
151
152
|
const width = parseFloat(node.attrs.get('width') || '0');
|
|
@@ -156,41 +157,35 @@ function convertShapeToPath(node, threshold) {
|
|
|
156
157
|
const ry = node.attrs.has('ry')
|
|
157
158
|
? parseFloat(node.attrs.get('ry'))
|
|
158
159
|
: undefined;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
case 'circle': {
|
|
160
|
+
return rectToPath(x, y, width, height, rx, ry);
|
|
161
|
+
},
|
|
162
|
+
circle: () => {
|
|
163
163
|
const cx = parseFloat(node.attrs.get('cx') || '0');
|
|
164
164
|
const cy = parseFloat(node.attrs.get('cy') || '0');
|
|
165
165
|
const r = parseFloat(node.attrs.get('r') || '0');
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
case 'ellipse': {
|
|
166
|
+
return circleToPath(cx, cy, r);
|
|
167
|
+
},
|
|
168
|
+
ellipse: () => {
|
|
170
169
|
const cx = parseFloat(node.attrs.get('cx') || '0');
|
|
171
170
|
const cy = parseFloat(node.attrs.get('cy') || '0');
|
|
172
171
|
const rx = parseFloat(node.attrs.get('rx') || '0');
|
|
173
172
|
const ry = parseFloat(node.attrs.get('ry') || '0');
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
case 'polygon': {
|
|
173
|
+
return ellipseToPath(cx, cy, rx, ry);
|
|
174
|
+
},
|
|
175
|
+
polygon: () => {
|
|
178
176
|
const points = node.attrs.get('points');
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
case 'polyline': {
|
|
177
|
+
return points ? polygonToPath(points) : null;
|
|
178
|
+
},
|
|
179
|
+
polyline: () => {
|
|
185
180
|
const points = node.attrs.get('points');
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return { converted: false, savings: 0 };
|
|
181
|
+
return points ? polylineToPath(points) : null;
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
const converter = shapeConverters[tag];
|
|
185
|
+
if (!converter) {
|
|
186
|
+
return { converted: false, savings: 0 };
|
|
193
187
|
}
|
|
188
|
+
pathData = converter();
|
|
194
189
|
// Check if conversion was possible
|
|
195
190
|
if (!pathData) {
|
|
196
191
|
return { converted: false, savings: 0 };
|
|
@@ -220,21 +220,17 @@ function applyTransformToPath(node, matrix) {
|
|
|
220
220
|
* Apply transform to shape coordinates
|
|
221
221
|
*/
|
|
222
222
|
function applyTransformToShape(node, matrix) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return applyTransformToPath(node, matrix);
|
|
235
|
-
default:
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
223
|
+
// O(1) object lookup for shape tag → transform handler
|
|
224
|
+
const shapeHandlers = {
|
|
225
|
+
rect: applyTransformToRect,
|
|
226
|
+
circle: applyTransformToCircle,
|
|
227
|
+
line: applyTransformToLine,
|
|
228
|
+
polygon: applyTransformToPoints,
|
|
229
|
+
polyline: applyTransformToPoints,
|
|
230
|
+
path: applyTransformToPath,
|
|
231
|
+
};
|
|
232
|
+
const handler = shapeHandlers[node.tag];
|
|
233
|
+
return handler ? handler(node, matrix) : false;
|
|
238
234
|
}
|
|
239
235
|
/**
|
|
240
236
|
* Collapse transforms by propagating down the tree
|
|
@@ -67,32 +67,27 @@ function multiplyMatrices(m1, m2) {
|
|
|
67
67
|
*/
|
|
68
68
|
function transformToMatrix(transform) {
|
|
69
69
|
const { type, values } = transform;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
return IDENTITY_MATRIX;
|
|
76
|
-
case 'translate': {
|
|
70
|
+
// O(1) object lookup for transform type → matrix factory
|
|
71
|
+
const matrixFactories = {
|
|
72
|
+
matrix: () => (values.length === 6 ? values : IDENTITY_MATRIX),
|
|
73
|
+
translate: () => {
|
|
77
74
|
const tx = values[0] || 0;
|
|
78
75
|
const ty = values[1] || 0;
|
|
79
76
|
return [1, 0, 0, 1, tx, ty];
|
|
80
|
-
}
|
|
81
|
-
|
|
77
|
+
},
|
|
78
|
+
scale: () => {
|
|
82
79
|
const sx = values[0] || 1;
|
|
83
80
|
const sy = values[1] !== undefined ? values[1] : sx;
|
|
84
81
|
return [sx, 0, 0, sy, 0, 0];
|
|
85
|
-
}
|
|
86
|
-
|
|
82
|
+
},
|
|
83
|
+
rotate: () => {
|
|
87
84
|
const angle = values[0] || 0;
|
|
88
85
|
const rad = (angle * Math.PI) / 180;
|
|
89
86
|
const cos = Math.cos(rad);
|
|
90
87
|
const sin = Math.sin(rad);
|
|
91
|
-
// Rotate around point (cx, cy) if provided
|
|
92
88
|
if (values.length === 3) {
|
|
93
89
|
const cx = values[1];
|
|
94
90
|
const cy = values[2];
|
|
95
|
-
// translate(-cx, -cy) × rotate(angle) × translate(cx, cy)
|
|
96
91
|
return [
|
|
97
92
|
cos,
|
|
98
93
|
sin,
|
|
@@ -103,22 +98,22 @@ function transformToMatrix(transform) {
|
|
|
103
98
|
];
|
|
104
99
|
}
|
|
105
100
|
return [cos, sin, -sin, cos, 0, 0];
|
|
106
|
-
}
|
|
107
|
-
|
|
101
|
+
},
|
|
102
|
+
skewX: () => {
|
|
108
103
|
const angle = values[0] || 0;
|
|
109
104
|
const rad = (angle * Math.PI) / 180;
|
|
110
105
|
const tan = Math.tan(rad);
|
|
111
106
|
return [1, 0, tan, 1, 0, 0];
|
|
112
|
-
}
|
|
113
|
-
|
|
107
|
+
},
|
|
108
|
+
skewY: () => {
|
|
114
109
|
const angle = values[0] || 0;
|
|
115
110
|
const rad = (angle * Math.PI) / 180;
|
|
116
111
|
const tan = Math.tan(rad);
|
|
117
112
|
return [1, tan, 0, 1, 0, 0];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const factory = matrixFactories[type];
|
|
116
|
+
return factory ? factory() : IDENTITY_MATRIX;
|
|
122
117
|
}
|
|
123
118
|
/**
|
|
124
119
|
* Consolidate transform list into single matrix
|