@emasoft/svg-matrix 1.0.28 → 1.0.30
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/README.md +325 -0
- package/bin/svg-matrix.js +985 -378
- package/bin/svglinter.cjs +4172 -433
- package/bin/svgm.js +723 -180
- package/package.json +16 -4
- package/src/animation-references.js +71 -52
- package/src/arc-length.js +160 -96
- package/src/bezier-analysis.js +257 -117
- package/src/bezier-intersections.js +411 -148
- package/src/browser-verify.js +240 -100
- package/src/clip-path-resolver.js +350 -142
- package/src/convert-path-data.js +279 -134
- package/src/css-specificity.js +78 -70
- package/src/flatten-pipeline.js +751 -263
- package/src/geometry-to-path.js +511 -182
- package/src/index.js +191 -46
- package/src/inkscape-support.js +18 -7
- package/src/marker-resolver.js +278 -164
- package/src/mask-resolver.js +209 -98
- package/src/matrix.js +147 -67
- package/src/mesh-gradient.js +187 -96
- package/src/off-canvas-detection.js +201 -104
- package/src/path-analysis.js +187 -107
- package/src/path-data-plugins.js +628 -167
- package/src/path-simplification.js +0 -1
- package/src/pattern-resolver.js +125 -88
- package/src/polygon-clip.js +111 -66
- package/src/svg-boolean-ops.js +194 -118
- package/src/svg-collections.js +22 -18
- package/src/svg-flatten.js +282 -164
- package/src/svg-parser.js +427 -200
- package/src/svg-rendering-context.js +147 -104
- package/src/svg-toolbox.js +16381 -3370
- package/src/svg2-polyfills.js +93 -224
- package/src/transform-decomposition.js +46 -41
- package/src/transform-optimization.js +89 -68
- package/src/transforms2d.js +49 -16
- package/src/transforms3d.js +58 -22
- package/src/use-symbol-resolver.js +150 -110
- package/src/vector.js +67 -15
- package/src/vendor/README.md +110 -0
- package/src/vendor/inkscape-hatch-polyfill.js +401 -0
- package/src/vendor/inkscape-hatch-polyfill.min.js +8 -0
- package/src/vendor/inkscape-mesh-polyfill.js +843 -0
- package/src/vendor/inkscape-mesh-polyfill.min.js +8 -0
- package/src/verification.js +288 -124
package/src/browser-verify.js
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* @module browser-verify
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import Decimal from
|
|
17
|
-
import { Matrix } from
|
|
18
|
-
import * as SVGFlatten from
|
|
16
|
+
import Decimal from "decimal.js";
|
|
17
|
+
import { Matrix as _Matrix } from "./matrix.js";
|
|
18
|
+
import * as SVGFlatten from "./svg-flatten.js";
|
|
19
19
|
|
|
20
20
|
// Playwright is loaded dynamically to avoid crashes when not installed
|
|
21
21
|
let chromium = null;
|
|
@@ -27,12 +27,12 @@ let chromium = null;
|
|
|
27
27
|
async function loadPlaywright() {
|
|
28
28
|
if (chromium) return;
|
|
29
29
|
try {
|
|
30
|
-
const playwright = await import(
|
|
30
|
+
const playwright = await import("playwright");
|
|
31
31
|
chromium = playwright.chromium;
|
|
32
|
-
} catch (
|
|
32
|
+
} catch (_e) {
|
|
33
33
|
throw new Error(
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
"Playwright is required for browser verification but not installed.\n" +
|
|
35
|
+
"Install with: npm install playwright && npx playwright install chromium",
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -91,22 +91,26 @@ export class BrowserVerifier {
|
|
|
91
91
|
*/
|
|
92
92
|
async getBrowserCTM(config) {
|
|
93
93
|
if (!this.page) {
|
|
94
|
-
throw new Error(
|
|
94
|
+
throw new Error("BrowserVerifier not initialized. Call init() first.");
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
return await this.page.evaluate((cfg) => {
|
|
98
|
-
const svg = document.createElementNS(
|
|
99
|
-
svg.setAttribute(
|
|
100
|
-
svg.setAttribute(
|
|
101
|
-
if (cfg.viewBox) svg.setAttribute(
|
|
102
|
-
if (cfg.preserveAspectRatio)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
rect.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
99
|
+
svg.setAttribute("width", cfg.width);
|
|
100
|
+
svg.setAttribute("height", cfg.height);
|
|
101
|
+
if (cfg.viewBox) svg.setAttribute("viewBox", cfg.viewBox);
|
|
102
|
+
if (cfg.preserveAspectRatio)
|
|
103
|
+
svg.setAttribute("preserveAspectRatio", cfg.preserveAspectRatio);
|
|
104
|
+
|
|
105
|
+
const rect = document.createElementNS(
|
|
106
|
+
"http://www.w3.org/2000/svg",
|
|
107
|
+
"rect",
|
|
108
|
+
);
|
|
109
|
+
rect.setAttribute("x", "0");
|
|
110
|
+
rect.setAttribute("y", "0");
|
|
111
|
+
rect.setAttribute("width", "10");
|
|
112
|
+
rect.setAttribute("height", "10");
|
|
113
|
+
if (cfg.transform) rect.setAttribute("transform", cfg.transform);
|
|
110
114
|
svg.appendChild(rect);
|
|
111
115
|
|
|
112
116
|
document.body.appendChild(svg);
|
|
@@ -125,22 +129,26 @@ export class BrowserVerifier {
|
|
|
125
129
|
*/
|
|
126
130
|
async getBrowserScreenCTM(config) {
|
|
127
131
|
if (!this.page) {
|
|
128
|
-
throw new Error(
|
|
132
|
+
throw new Error("BrowserVerifier not initialized. Call init() first.");
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
return await this.page.evaluate((cfg) => {
|
|
132
|
-
const svg = document.createElementNS(
|
|
133
|
-
svg.setAttribute(
|
|
134
|
-
svg.setAttribute(
|
|
135
|
-
if (cfg.viewBox) svg.setAttribute(
|
|
136
|
-
if (cfg.preserveAspectRatio)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
rect.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
137
|
+
svg.setAttribute("width", cfg.width);
|
|
138
|
+
svg.setAttribute("height", cfg.height);
|
|
139
|
+
if (cfg.viewBox) svg.setAttribute("viewBox", cfg.viewBox);
|
|
140
|
+
if (cfg.preserveAspectRatio)
|
|
141
|
+
svg.setAttribute("preserveAspectRatio", cfg.preserveAspectRatio);
|
|
142
|
+
|
|
143
|
+
const rect = document.createElementNS(
|
|
144
|
+
"http://www.w3.org/2000/svg",
|
|
145
|
+
"rect",
|
|
146
|
+
);
|
|
147
|
+
rect.setAttribute("x", "0");
|
|
148
|
+
rect.setAttribute("y", "0");
|
|
149
|
+
rect.setAttribute("width", "10");
|
|
150
|
+
rect.setAttribute("height", "10");
|
|
151
|
+
if (cfg.transform) rect.setAttribute("transform", cfg.transform);
|
|
144
152
|
svg.appendChild(rect);
|
|
145
153
|
|
|
146
154
|
document.body.appendChild(svg);
|
|
@@ -161,32 +169,42 @@ export class BrowserVerifier {
|
|
|
161
169
|
*/
|
|
162
170
|
async transformPoint(config, x, y) {
|
|
163
171
|
if (!this.page) {
|
|
164
|
-
throw new Error(
|
|
172
|
+
throw new Error("BrowserVerifier not initialized. Call init() first.");
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
return await this.page.evaluate(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
175
|
+
return await this.page.evaluate(
|
|
176
|
+
({ cfg, px, py }) => {
|
|
177
|
+
const svg = document.createElementNS(
|
|
178
|
+
"http://www.w3.org/2000/svg",
|
|
179
|
+
"svg",
|
|
180
|
+
);
|
|
181
|
+
svg.setAttribute("width", cfg.width);
|
|
182
|
+
svg.setAttribute("height", cfg.height);
|
|
183
|
+
if (cfg.viewBox) svg.setAttribute("viewBox", cfg.viewBox);
|
|
184
|
+
if (cfg.preserveAspectRatio)
|
|
185
|
+
svg.setAttribute("preserveAspectRatio", cfg.preserveAspectRatio);
|
|
186
|
+
|
|
187
|
+
const rect = document.createElementNS(
|
|
188
|
+
"http://www.w3.org/2000/svg",
|
|
189
|
+
"rect",
|
|
190
|
+
);
|
|
191
|
+
if (cfg.transform) rect.setAttribute("transform", cfg.transform);
|
|
192
|
+
svg.appendChild(rect);
|
|
193
|
+
|
|
194
|
+
document.body.appendChild(svg);
|
|
195
|
+
|
|
196
|
+
const ctm = rect.getCTM();
|
|
197
|
+
const point = svg.createSVGPoint();
|
|
198
|
+
point.x = px;
|
|
199
|
+
point.y = py;
|
|
200
|
+
const transformed = point.matrixTransform(ctm);
|
|
201
|
+
|
|
202
|
+
document.body.removeChild(svg);
|
|
203
|
+
|
|
204
|
+
return { x: transformed.x, y: transformed.y };
|
|
205
|
+
},
|
|
206
|
+
{ cfg: config, px: x, py: y },
|
|
207
|
+
);
|
|
190
208
|
}
|
|
191
209
|
|
|
192
210
|
/**
|
|
@@ -218,7 +236,7 @@ export class BrowserVerifier {
|
|
|
218
236
|
f: Math.abs(browserCTM.f - libraryCTM.f),
|
|
219
237
|
};
|
|
220
238
|
|
|
221
|
-
const matches = Object.values(differences).every(d => d < tolerance);
|
|
239
|
+
const matches = Object.values(differences).every((d) => d < tolerance);
|
|
222
240
|
|
|
223
241
|
return { matches, browserCTM, libraryCTM, differences };
|
|
224
242
|
}
|
|
@@ -233,17 +251,27 @@ export class BrowserVerifier {
|
|
|
233
251
|
* @param {number} [tolerance=1e-10] - Tolerance for comparison
|
|
234
252
|
* @returns {Promise<{matches: boolean, browserCTM: Object, libraryCTM: Object, differences: Object}>}
|
|
235
253
|
*/
|
|
236
|
-
async verifyViewBoxTransform(
|
|
254
|
+
async verifyViewBoxTransform(
|
|
255
|
+
width,
|
|
256
|
+
height,
|
|
257
|
+
viewBox,
|
|
258
|
+
preserveAspectRatio = "xMidYMid meet",
|
|
259
|
+
tolerance = 1e-10,
|
|
260
|
+
) {
|
|
237
261
|
const vb = SVGFlatten.parseViewBox(viewBox);
|
|
238
262
|
const par = SVGFlatten.parsePreserveAspectRatio(preserveAspectRatio);
|
|
239
263
|
const matrix = SVGFlatten.computeViewBoxTransform(vb, width, height, par);
|
|
240
264
|
|
|
241
|
-
return await this.verifyMatrix(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
265
|
+
return await this.verifyMatrix(
|
|
266
|
+
matrix,
|
|
267
|
+
{
|
|
268
|
+
width,
|
|
269
|
+
height,
|
|
270
|
+
viewBox,
|
|
271
|
+
preserveAspectRatio,
|
|
272
|
+
},
|
|
273
|
+
tolerance,
|
|
274
|
+
);
|
|
247
275
|
}
|
|
248
276
|
|
|
249
277
|
/**
|
|
@@ -257,11 +285,15 @@ export class BrowserVerifier {
|
|
|
257
285
|
const matrix = SVGFlatten.parseTransformAttribute(transform);
|
|
258
286
|
|
|
259
287
|
// Use a simple 100x100 SVG without viewBox to test just the transform
|
|
260
|
-
return await this.verifyMatrix(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
288
|
+
return await this.verifyMatrix(
|
|
289
|
+
matrix,
|
|
290
|
+
{
|
|
291
|
+
width: 100,
|
|
292
|
+
height: 100,
|
|
293
|
+
transform,
|
|
294
|
+
},
|
|
295
|
+
tolerance,
|
|
296
|
+
);
|
|
265
297
|
}
|
|
266
298
|
|
|
267
299
|
/**
|
|
@@ -280,19 +312,19 @@ export class BrowserVerifier {
|
|
|
280
312
|
|
|
281
313
|
const libPt = {
|
|
282
314
|
x: libraryPoint.x.toNumber(),
|
|
283
|
-
y: libraryPoint.y.toNumber()
|
|
315
|
+
y: libraryPoint.y.toNumber(),
|
|
284
316
|
};
|
|
285
317
|
|
|
286
318
|
const difference = Math.sqrt(
|
|
287
319
|
Math.pow(browserPoint.x - libPt.x, 2) +
|
|
288
|
-
|
|
320
|
+
Math.pow(browserPoint.y - libPt.y, 2),
|
|
289
321
|
);
|
|
290
322
|
|
|
291
323
|
return {
|
|
292
324
|
matches: difference < tolerance,
|
|
293
325
|
browserPoint,
|
|
294
326
|
libraryPoint: libPt,
|
|
295
|
-
difference
|
|
327
|
+
difference,
|
|
296
328
|
};
|
|
297
329
|
}
|
|
298
330
|
|
|
@@ -313,13 +345,13 @@ export class BrowserVerifier {
|
|
|
313
345
|
tc.width,
|
|
314
346
|
tc.height,
|
|
315
347
|
tc.viewBox,
|
|
316
|
-
tc.preserveAspectRatio ||
|
|
317
|
-
tolerance
|
|
348
|
+
tc.preserveAspectRatio || "xMidYMid meet",
|
|
349
|
+
tolerance,
|
|
318
350
|
);
|
|
319
351
|
|
|
320
352
|
results.push({
|
|
321
353
|
name: tc.name || `${tc.width}x${tc.height} viewBox="${tc.viewBox}"`,
|
|
322
|
-
...result
|
|
354
|
+
...result,
|
|
323
355
|
});
|
|
324
356
|
|
|
325
357
|
if (result.matches) passed++;
|
|
@@ -360,12 +392,22 @@ export async function quickVerify(matrix, config, tolerance = 1e-10) {
|
|
|
360
392
|
* @param {string} [preserveAspectRatio='xMidYMid meet'] - preserveAspectRatio
|
|
361
393
|
* @returns {Promise<{matches: boolean, browserCTM: Object, libraryCTM: Object}>}
|
|
362
394
|
*/
|
|
363
|
-
export async function verifyViewBox(
|
|
395
|
+
export async function verifyViewBox(
|
|
396
|
+
width,
|
|
397
|
+
height,
|
|
398
|
+
viewBox,
|
|
399
|
+
preserveAspectRatio = "xMidYMid meet",
|
|
400
|
+
) {
|
|
364
401
|
const verifier = new BrowserVerifier();
|
|
365
402
|
await verifier.init({ headless: true });
|
|
366
403
|
|
|
367
404
|
try {
|
|
368
|
-
return await verifier.verifyViewBoxTransform(
|
|
405
|
+
return await verifier.verifyViewBoxTransform(
|
|
406
|
+
width,
|
|
407
|
+
height,
|
|
408
|
+
viewBox,
|
|
409
|
+
preserveAspectRatio,
|
|
410
|
+
);
|
|
369
411
|
} finally {
|
|
370
412
|
await verifier.close();
|
|
371
413
|
}
|
|
@@ -399,30 +441,126 @@ export async function verifyTransform(transform) {
|
|
|
399
441
|
export async function runStandardTests(options = { verbose: true }) {
|
|
400
442
|
const testCases = [
|
|
401
443
|
// W3C SVG WG issue #215 cases
|
|
402
|
-
{
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
444
|
+
{
|
|
445
|
+
width: 21,
|
|
446
|
+
height: 10,
|
|
447
|
+
viewBox: "11 13 3 2",
|
|
448
|
+
preserveAspectRatio: "none",
|
|
449
|
+
name: "Issue #215: none",
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
width: 21,
|
|
453
|
+
height: 10,
|
|
454
|
+
viewBox: "11 13 3 2",
|
|
455
|
+
preserveAspectRatio: "xMinYMin meet",
|
|
456
|
+
name: "Issue #215: xMinYMin meet",
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
width: 21,
|
|
460
|
+
height: 10,
|
|
461
|
+
viewBox: "11 13 3 2",
|
|
462
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
463
|
+
name: "Issue #215: xMidYMid meet",
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
width: 21,
|
|
467
|
+
height: 10,
|
|
468
|
+
viewBox: "11 13 3 2",
|
|
469
|
+
preserveAspectRatio: "xMaxYMax meet",
|
|
470
|
+
name: "Issue #215: xMaxYMax meet",
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
width: 21,
|
|
474
|
+
height: 10,
|
|
475
|
+
viewBox: "11 13 3 2",
|
|
476
|
+
preserveAspectRatio: "xMinYMin slice",
|
|
477
|
+
name: "Issue #215: xMinYMin slice",
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
width: 21,
|
|
481
|
+
height: 10,
|
|
482
|
+
viewBox: "11 13 3 2",
|
|
483
|
+
preserveAspectRatio: "xMidYMid slice",
|
|
484
|
+
name: "Issue #215: xMidYMid slice",
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
width: 21,
|
|
488
|
+
height: 10,
|
|
489
|
+
viewBox: "11 13 3 2",
|
|
490
|
+
preserveAspectRatio: "xMaxYMax slice",
|
|
491
|
+
name: "Issue #215: xMaxYMax slice",
|
|
492
|
+
},
|
|
409
493
|
|
|
410
494
|
// Standard cases
|
|
411
|
-
{
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
495
|
+
{
|
|
496
|
+
width: 200,
|
|
497
|
+
height: 200,
|
|
498
|
+
viewBox: "0 0 100 100",
|
|
499
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
500
|
+
name: "Square 2x scale",
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
width: 100,
|
|
504
|
+
height: 100,
|
|
505
|
+
viewBox: "0 0 100 100",
|
|
506
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
507
|
+
name: "1:1 identity",
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
width: 400,
|
|
511
|
+
height: 200,
|
|
512
|
+
viewBox: "0 0 100 100",
|
|
513
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
514
|
+
name: "Wide viewport",
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
width: 200,
|
|
518
|
+
height: 400,
|
|
519
|
+
viewBox: "0 0 100 100",
|
|
520
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
521
|
+
name: "Tall viewport",
|
|
522
|
+
},
|
|
415
523
|
|
|
416
524
|
// Non-zero origins
|
|
417
|
-
{
|
|
418
|
-
|
|
525
|
+
{
|
|
526
|
+
width: 300,
|
|
527
|
+
height: 200,
|
|
528
|
+
viewBox: "50 50 100 100",
|
|
529
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
530
|
+
name: "Offset origin",
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
width: 300,
|
|
534
|
+
height: 200,
|
|
535
|
+
viewBox: "-50 -50 200 200",
|
|
536
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
537
|
+
name: "Negative origin",
|
|
538
|
+
},
|
|
419
539
|
|
|
420
540
|
// Stretch
|
|
421
|
-
{
|
|
541
|
+
{
|
|
542
|
+
width: 200,
|
|
543
|
+
height: 100,
|
|
544
|
+
viewBox: "0 0 100 100",
|
|
545
|
+
preserveAspectRatio: "none",
|
|
546
|
+
name: "Stretch non-uniform",
|
|
547
|
+
},
|
|
422
548
|
|
|
423
549
|
// Slice modes
|
|
424
|
-
{
|
|
425
|
-
|
|
550
|
+
{
|
|
551
|
+
width: 200,
|
|
552
|
+
height: 400,
|
|
553
|
+
viewBox: "0 0 100 100",
|
|
554
|
+
preserveAspectRatio: "xMidYMid slice",
|
|
555
|
+
name: "Tall slice",
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
width: 400,
|
|
559
|
+
height: 200,
|
|
560
|
+
viewBox: "0 0 100 100",
|
|
561
|
+
preserveAspectRatio: "xMidYMid slice",
|
|
562
|
+
name: "Wide slice",
|
|
563
|
+
},
|
|
426
564
|
];
|
|
427
565
|
|
|
428
566
|
const verifier = new BrowserVerifier();
|
|
@@ -432,19 +570,21 @@ export async function runStandardTests(options = { verbose: true }) {
|
|
|
432
570
|
const { passed, failed, results } = await verifier.runBatch(testCases);
|
|
433
571
|
|
|
434
572
|
if (options.verbose) {
|
|
435
|
-
console.log(
|
|
573
|
+
console.log("\n=== SVG-Matrix Browser Verification ===\n");
|
|
436
574
|
|
|
437
575
|
for (const r of results) {
|
|
438
|
-
const icon = r.matches ?
|
|
576
|
+
const icon = r.matches ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m";
|
|
439
577
|
console.log(` ${icon} ${r.name}`);
|
|
440
578
|
}
|
|
441
579
|
|
|
442
|
-
console.log(
|
|
580
|
+
console.log("\n" + "─".repeat(50));
|
|
443
581
|
if (failed === 0) {
|
|
444
582
|
console.log(`\x1b[32mAll ${passed} tests PASSED!\x1b[0m`);
|
|
445
|
-
console.log(
|
|
583
|
+
console.log("Library matches browser's W3C SVG2 implementation.\n");
|
|
446
584
|
} else {
|
|
447
|
-
console.log(
|
|
585
|
+
console.log(
|
|
586
|
+
`\x1b[31m${failed} tests FAILED\x1b[0m, ${passed} passed\n`,
|
|
587
|
+
);
|
|
448
588
|
}
|
|
449
589
|
}
|
|
450
590
|
|
|
@@ -459,5 +599,5 @@ export default {
|
|
|
459
599
|
quickVerify,
|
|
460
600
|
verifyViewBox,
|
|
461
601
|
verifyTransform,
|
|
462
|
-
runStandardTests
|
|
602
|
+
runStandardTests,
|
|
463
603
|
};
|