@weasel-js/svg 1.5.1 → 1.6.0
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 +5 -0
- package/dist/index.d.ts +17 -10
- package/dist/index.js +342 -79
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -91,6 +91,11 @@ decodes it, so an external URL round-trips as a reference and resolves only
|
|
|
91
91
|
when something downstream loads it. `unpackSvgFiles` maps the node onto the
|
|
92
92
|
kit's `kit:image` painter (`data.image.src`), which does load it.
|
|
93
93
|
|
|
94
|
+
A source rect and flips (`source`, `flipX`, `flipY`) are written as a nested
|
|
95
|
+
`<svg viewBox>` viewport and read back as the same node. `unpackSvgFiles`
|
|
96
|
+
carries them onto `data.image` under the same names, and `svgImageFromKit`
|
|
97
|
+
writes a `kit:image` leaf back as an `SvgImageNode`.
|
|
98
|
+
|
|
94
99
|
`preserveAspectRatio` is not modeled. The box is taken literally on the way
|
|
95
100
|
in (a non-`none` value warns) and written back as `none`, so a source file
|
|
96
101
|
that relied on letterboxing imports stretched.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mat3 } from '@weasel-js/geom';
|
|
2
|
+
import { Path, FillStyle, ScreenLength, StrokeAlign, StyledRun, TextStyle, TextVerticalAlign, Stroke, MarkerEntry, TilePatternSpec, ImageNodeData, IngestCtx } from '@weasel-js/core';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Public types for `@weasel-js/svg`. The package exposes a flat,
|
|
@@ -47,10 +48,15 @@ interface NamespacedElement {
|
|
|
47
48
|
children?: Record<string, NamespacedElement[]>;
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
50
|
-
* 2x3 affine matrix in
|
|
51
|
-
*
|
|
51
|
+
* 2x3 affine matrix in SVG's `matrix(a b c d e f)` order, mapping
|
|
52
|
+
* `[x', y'] = [a*x + c*y + e, b*x + d*y + f]`.
|
|
53
|
+
*
|
|
54
|
+
* The same order the kernel's affine tier uses, so this is that type under
|
|
55
|
+
* the name the SVG spec gives it — not a parallel one. An `SvgGroupNode`'s
|
|
56
|
+
* transform and a geom `Mat3` are interchangeable without a conversion.
|
|
52
57
|
*/
|
|
53
|
-
type Matrix =
|
|
58
|
+
type Matrix = Mat3;
|
|
59
|
+
|
|
54
60
|
/** Identity matrix — useful as a default in tests / constructors. */
|
|
55
61
|
declare const IDENTITY_MATRIX: Matrix;
|
|
56
62
|
/**
|
|
@@ -73,11 +79,9 @@ type SvgPaint = {
|
|
|
73
79
|
interface SvgStroke {
|
|
74
80
|
paint: SvgPaint;
|
|
75
81
|
/** World units, or `{ px }` for a width that holds its rendered thickness
|
|
76
|
-
* however the document is scaled — SVG's `vector-effect="non-scaling-stroke"
|
|
77
|
-
*
|
|
78
|
-
width:
|
|
79
|
-
px: number;
|
|
80
|
-
};
|
|
82
|
+
* however the document is scaled — SVG's `vector-effect="non-scaling-stroke"`.
|
|
83
|
+
* Literally the kit's own unit, not a second spelling of it. */
|
|
84
|
+
width: ScreenLength;
|
|
81
85
|
opacity?: number;
|
|
82
86
|
/** `stroke-linecap`. Default per SVG spec is `'butt'`. */
|
|
83
87
|
cap?: 'butt' | 'round' | 'square';
|
|
@@ -422,6 +426,9 @@ type SvgSceneDraft = {
|
|
|
422
426
|
* `userSpaceOnUse` gradient survives the fit-clamp and the drop-point
|
|
423
427
|
* placement. */
|
|
424
428
|
declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftBounds): Stroke | undefined;
|
|
429
|
+
/** Write a `kit:image` leaf as an `SvgImageNode` — the inverse of the image
|
|
430
|
+
* arm of {@link svgNodesToKitDrafts}. The pose is the image's box. */
|
|
431
|
+
declare function svgImageFromKit(image: ImageNodeData['image'], pose: DraftPose): SvgImageNode;
|
|
425
432
|
/**
|
|
426
433
|
* Walk an `SvgNode[]` tree and emit a flat, parent-before-child list of
|
|
427
434
|
* {@link SvgSceneDraft}s. Each `<g>` becomes a container whose pose is the
|
|
@@ -437,4 +444,4 @@ declare function svgNodesToKitDrafts(nodes: readonly SvgNode[], nextId: () => st
|
|
|
437
444
|
*/
|
|
438
445
|
declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
|
|
439
446
|
|
|
440
|
-
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgImageNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, parseSvg, serializeSvg, strokeDataFromSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
447
|
+
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgImageNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, resolveScreenLength, SCRIPT_METRICS, dwarn, getMarker, registerMarker, createInsertOp, PathBuilder, parseColor, rgbaToHex, resolveTextStyle, layoutRuns, resolveRuns, resolveAlign, PATH_C, PATH_Q, solid, fillToBoundsFrame, getPaintKind } from '@weasel-js/core';
|
|
2
|
-
import { invert } from '@weasel-js/geom';
|
|
2
|
+
import { identity, multiply, invert } from '@weasel-js/geom';
|
|
3
3
|
import { tileGeometry } from '@weasel-js/core/patterns-builtin';
|
|
4
4
|
|
|
5
5
|
// src/parse.ts
|
|
6
|
-
|
|
7
|
-
// src/types.ts
|
|
8
|
-
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
|
|
9
|
-
function multiply(a, b) {
|
|
10
|
-
return [
|
|
11
|
-
a[0] * b[0] + a[2] * b[1],
|
|
12
|
-
a[1] * b[0] + a[3] * b[1],
|
|
13
|
-
a[0] * b[2] + a[2] * b[3],
|
|
14
|
-
a[1] * b[2] + a[3] * b[3],
|
|
15
|
-
a[0] * b[4] + a[2] * b[5] + a[4],
|
|
16
|
-
a[1] * b[4] + a[3] * b[5] + a[5]
|
|
17
|
-
];
|
|
18
|
-
}
|
|
6
|
+
var IDENTITY_MATRIX = identity();
|
|
19
7
|
function applyMatrix(m, x, y) {
|
|
20
8
|
return { x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] };
|
|
21
9
|
}
|
|
@@ -25,9 +13,9 @@ function isIdentity(m, eps = 1e-9) {
|
|
|
25
13
|
var NUM_RE = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
|
26
14
|
function readNumbers(s) {
|
|
27
15
|
const out = [];
|
|
28
|
-
const
|
|
29
|
-
if (!
|
|
30
|
-
for (const m of
|
|
16
|
+
const matches2 = s.match(NUM_RE);
|
|
17
|
+
if (!matches2) return out;
|
|
18
|
+
for (const m of matches2) {
|
|
31
19
|
const n = Number(m);
|
|
32
20
|
if (Number.isFinite(n)) out.push(n);
|
|
33
21
|
}
|
|
@@ -77,10 +65,9 @@ function parseTransform(attr, onWarn) {
|
|
|
77
65
|
return result;
|
|
78
66
|
}
|
|
79
67
|
function rebaseTransform(ctm, local) {
|
|
80
|
-
const inv = invert(
|
|
68
|
+
const inv = invert(ctm);
|
|
81
69
|
if (!inv) return null;
|
|
82
|
-
|
|
83
|
-
return multiply(multiply(ctm, local), [a, b, c, d, e, f]);
|
|
70
|
+
return multiply(multiply(ctm, local), inv);
|
|
84
71
|
}
|
|
85
72
|
function decomposeRotation(m, cx, cy, eps = 1e-4) {
|
|
86
73
|
const [a, b, c, d, e, f] = m;
|
|
@@ -262,6 +249,302 @@ function parsePaintAttr(raw) {
|
|
|
262
249
|
}
|
|
263
250
|
}
|
|
264
251
|
|
|
252
|
+
// src/cascade.ts
|
|
253
|
+
var EMPTY_STYLE = {};
|
|
254
|
+
var INHERITABLE = [
|
|
255
|
+
"fill",
|
|
256
|
+
"fill-opacity",
|
|
257
|
+
"fill-rule",
|
|
258
|
+
"stroke",
|
|
259
|
+
"stroke-width",
|
|
260
|
+
"stroke-opacity",
|
|
261
|
+
"stroke-linecap",
|
|
262
|
+
"stroke-linejoin",
|
|
263
|
+
"stroke-dasharray",
|
|
264
|
+
"stroke-miterlimit",
|
|
265
|
+
"marker-start",
|
|
266
|
+
"marker-mid",
|
|
267
|
+
"marker-end",
|
|
268
|
+
"color",
|
|
269
|
+
"font-size",
|
|
270
|
+
"font-family",
|
|
271
|
+
"font-weight",
|
|
272
|
+
"font-style",
|
|
273
|
+
"text-anchor",
|
|
274
|
+
"letter-spacing",
|
|
275
|
+
"text-decoration",
|
|
276
|
+
"direction",
|
|
277
|
+
"text-transform"
|
|
278
|
+
];
|
|
279
|
+
function skipString(s, i) {
|
|
280
|
+
const q = s[i];
|
|
281
|
+
for (let j = i + 1; j < s.length; j++) {
|
|
282
|
+
if (s[j] === "\\") j++;
|
|
283
|
+
else if (s[j] === q) return j + 1;
|
|
284
|
+
}
|
|
285
|
+
return s.length;
|
|
286
|
+
}
|
|
287
|
+
function stripComments(s) {
|
|
288
|
+
if (!s.includes("/*")) return s;
|
|
289
|
+
let out = "";
|
|
290
|
+
let i = 0;
|
|
291
|
+
while (i < s.length) {
|
|
292
|
+
const ch = s[i];
|
|
293
|
+
if (ch === '"' || ch === "'") {
|
|
294
|
+
const end = skipString(s, i);
|
|
295
|
+
out += s.slice(i, end);
|
|
296
|
+
i = end;
|
|
297
|
+
} else if (ch === "/" && s[i + 1] === "*") {
|
|
298
|
+
const end = s.indexOf("*/", i + 2);
|
|
299
|
+
i = end < 0 ? s.length : end + 2;
|
|
300
|
+
out += " ";
|
|
301
|
+
} else {
|
|
302
|
+
out += ch;
|
|
303
|
+
i++;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
function splitTopLevel(s, sep, dropBlocks = false) {
|
|
309
|
+
const parts = [];
|
|
310
|
+
let depth = 0;
|
|
311
|
+
let start = 0;
|
|
312
|
+
let i = 0;
|
|
313
|
+
while (i < s.length) {
|
|
314
|
+
const ch = s[i];
|
|
315
|
+
if (ch === '"' || ch === "'") {
|
|
316
|
+
i = skipString(s, i);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (ch === "(" || ch === "[") depth++;
|
|
320
|
+
else if ((ch === ")" || ch === "]") && depth > 0) depth--;
|
|
321
|
+
else if (depth === 0 && ch === sep) {
|
|
322
|
+
parts.push(s.slice(start, i));
|
|
323
|
+
start = i + 1;
|
|
324
|
+
} else if (depth === 0 && dropBlocks && ch === "{") {
|
|
325
|
+
i = skipBlock(s, i);
|
|
326
|
+
start = i;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
i++;
|
|
330
|
+
}
|
|
331
|
+
parts.push(s.slice(start));
|
|
332
|
+
return parts;
|
|
333
|
+
}
|
|
334
|
+
function skipBlock(s, i) {
|
|
335
|
+
let depth = 0;
|
|
336
|
+
while (i < s.length) {
|
|
337
|
+
const ch = s[i];
|
|
338
|
+
if (ch === '"' || ch === "'") {
|
|
339
|
+
i = skipString(s, i);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (ch === "{") depth++;
|
|
343
|
+
else if (ch === "}" && --depth === 0) return i + 1;
|
|
344
|
+
i++;
|
|
345
|
+
}
|
|
346
|
+
return s.length;
|
|
347
|
+
}
|
|
348
|
+
var PROP_NAME = /^-?[a-z_][a-z0-9_-]*$/;
|
|
349
|
+
var IMPORTANT = /!\s*important\s*$/i;
|
|
350
|
+
function parseDeclarations(block) {
|
|
351
|
+
const out = [];
|
|
352
|
+
for (const part of splitTopLevel(stripComments(block), ";", true)) {
|
|
353
|
+
const colon = part.indexOf(":");
|
|
354
|
+
if (colon < 0) continue;
|
|
355
|
+
const prop = part.slice(0, colon).trim().toLowerCase();
|
|
356
|
+
if (!PROP_NAME.test(prop)) continue;
|
|
357
|
+
let value = part.slice(colon + 1).trim();
|
|
358
|
+
const important = IMPORTANT.test(value);
|
|
359
|
+
if (important) value = value.replace(IMPORTANT, "").trim();
|
|
360
|
+
if (!value) continue;
|
|
361
|
+
out.push({ prop, value, important });
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
function identEnd(s, i) {
|
|
366
|
+
while (i < s.length) {
|
|
367
|
+
const ch = s[i];
|
|
368
|
+
if (ch === "\\") i += 2;
|
|
369
|
+
else if (/[\w-]/.test(ch) || ch.charCodeAt(0) > 127) i++;
|
|
370
|
+
else break;
|
|
371
|
+
}
|
|
372
|
+
return i;
|
|
373
|
+
}
|
|
374
|
+
function parenEnd(s, i) {
|
|
375
|
+
let depth = 0;
|
|
376
|
+
while (i < s.length) {
|
|
377
|
+
const ch = s[i];
|
|
378
|
+
if (ch === '"' || ch === "'") {
|
|
379
|
+
i = skipString(s, i);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (ch === "(") depth++;
|
|
383
|
+
else if (ch === ")" && --depth === 0) return i + 1;
|
|
384
|
+
i++;
|
|
385
|
+
}
|
|
386
|
+
return s.length;
|
|
387
|
+
}
|
|
388
|
+
var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set(["before", "after", "first-line", "first-letter"]);
|
|
389
|
+
var MAX_ARG_PSEUDOS = /* @__PURE__ */ new Set(["not", "is", "matches", "-webkit-any", "has"]);
|
|
390
|
+
function compareSpecificity(a, b) {
|
|
391
|
+
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
392
|
+
}
|
|
393
|
+
function maxSpecificity(list) {
|
|
394
|
+
let best = [0, 0, 0];
|
|
395
|
+
for (const sel of splitTopLevel(list, ",")) {
|
|
396
|
+
const s = specificity(sel);
|
|
397
|
+
if (compareSpecificity(s, best) > 0) best = s;
|
|
398
|
+
}
|
|
399
|
+
return best;
|
|
400
|
+
}
|
|
401
|
+
function specificity(selector) {
|
|
402
|
+
let a = 0, b = 0, c = 0;
|
|
403
|
+
const s = selector;
|
|
404
|
+
let i = 0;
|
|
405
|
+
while (i < s.length) {
|
|
406
|
+
const ch = s[i];
|
|
407
|
+
if (ch === "#") {
|
|
408
|
+
a++;
|
|
409
|
+
i = identEnd(s, i + 1);
|
|
410
|
+
} else if (ch === ".") {
|
|
411
|
+
b++;
|
|
412
|
+
i = identEnd(s, i + 1);
|
|
413
|
+
} else if (ch === "[") {
|
|
414
|
+
b++;
|
|
415
|
+
while (i < s.length && s[i] !== "]") {
|
|
416
|
+
i = s[i] === '"' || s[i] === "'" ? skipString(s, i) : i + 1;
|
|
417
|
+
}
|
|
418
|
+
i++;
|
|
419
|
+
} else if (ch === ":") {
|
|
420
|
+
const element = s[i + 1] === ":";
|
|
421
|
+
const nameStart = i + (element ? 2 : 1);
|
|
422
|
+
const nameEnd = identEnd(s, nameStart);
|
|
423
|
+
const name = s.slice(nameStart, nameEnd).toLowerCase();
|
|
424
|
+
let arg = null;
|
|
425
|
+
i = nameEnd;
|
|
426
|
+
if (s[i] === "(") {
|
|
427
|
+
const end = parenEnd(s, i);
|
|
428
|
+
arg = s.slice(i + 1, end - 1);
|
|
429
|
+
i = end;
|
|
430
|
+
}
|
|
431
|
+
if (element || LEGACY_PSEUDO_ELEMENTS.has(name)) c++;
|
|
432
|
+
else if (name === "where") ; else if (MAX_ARG_PSEUDOS.has(name) && arg != null) {
|
|
433
|
+
const m = maxSpecificity(arg);
|
|
434
|
+
a += m[0];
|
|
435
|
+
b += m[1];
|
|
436
|
+
c += m[2];
|
|
437
|
+
} else b++;
|
|
438
|
+
} else if (/[a-zA-Z_\\-]/.test(ch) || ch.charCodeAt(0) > 127) {
|
|
439
|
+
c++;
|
|
440
|
+
i = identEnd(s, i);
|
|
441
|
+
} else {
|
|
442
|
+
i++;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return [a, b, c];
|
|
446
|
+
}
|
|
447
|
+
function parseStylesheet(text, firstOrder = 0) {
|
|
448
|
+
const src = stripComments(text).replace(/<!\[CDATA\[|\]\]>|<!--|-->/g, " ");
|
|
449
|
+
const rules = [];
|
|
450
|
+
let order = firstOrder;
|
|
451
|
+
let i = 0;
|
|
452
|
+
let preludeStart = 0;
|
|
453
|
+
while (i < src.length) {
|
|
454
|
+
const ch = src[i];
|
|
455
|
+
if (ch === '"' || ch === "'") {
|
|
456
|
+
i = skipString(src, i);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (ch === ";") {
|
|
460
|
+
i++;
|
|
461
|
+
preludeStart = i;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (ch !== "{") {
|
|
465
|
+
i++;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const prelude = src.slice(preludeStart, i).trim();
|
|
469
|
+
const end = skipBlock(src, i);
|
|
470
|
+
if (!prelude.startsWith("@") && prelude) {
|
|
471
|
+
const declarations = parseDeclarations(src.slice(i + 1, end - 1));
|
|
472
|
+
for (const raw of splitTopLevel(prelude, ",")) {
|
|
473
|
+
const selector = raw.trim();
|
|
474
|
+
if (selector) {
|
|
475
|
+
rules.push({ selector, declarations, specificity: specificity(selector), order: order++ });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
i = end;
|
|
480
|
+
preludeStart = end;
|
|
481
|
+
}
|
|
482
|
+
return rules;
|
|
483
|
+
}
|
|
484
|
+
function appliesAsCss(style) {
|
|
485
|
+
const type = style.getAttribute("type")?.trim().toLowerCase();
|
|
486
|
+
if (type && type !== "text/css") return false;
|
|
487
|
+
const media = style.getAttribute("media")?.trim().toLowerCase();
|
|
488
|
+
if (!media) return true;
|
|
489
|
+
return media.split(",").some((m) => m.trim() === "all" || m.trim() === "screen");
|
|
490
|
+
}
|
|
491
|
+
var sheetCache = /* @__PURE__ */ new WeakMap();
|
|
492
|
+
function documentRules(doc) {
|
|
493
|
+
const cached = sheetCache.get(doc);
|
|
494
|
+
if (cached) return cached;
|
|
495
|
+
const rules = [];
|
|
496
|
+
const styles = doc.getElementsByTagNameNS("*", "style");
|
|
497
|
+
for (let i = 0; i < styles.length; i++) {
|
|
498
|
+
if (!appliesAsCss(styles[i])) continue;
|
|
499
|
+
rules.push(...parseStylesheet(styles[i].textContent ?? "", rules.length));
|
|
500
|
+
}
|
|
501
|
+
rules.sort((x, y) => compareSpecificity(x.specificity, y.specificity) || x.order - y.order);
|
|
502
|
+
sheetCache.set(doc, rules);
|
|
503
|
+
return rules;
|
|
504
|
+
}
|
|
505
|
+
function matches(el, selector) {
|
|
506
|
+
try {
|
|
507
|
+
return el.matches(selector);
|
|
508
|
+
} catch {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
var elementCache = /* @__PURE__ */ new WeakMap();
|
|
513
|
+
function authorDeclarations(el) {
|
|
514
|
+
const cached = elementCache.get(el);
|
|
515
|
+
if (cached) return cached;
|
|
516
|
+
const rules = el.ownerDocument ? documentRules(el.ownerDocument) : [];
|
|
517
|
+
const matched = rules.filter((r) => matches(el, r.selector));
|
|
518
|
+
const inline = parseDeclarations(el.getAttribute("style") ?? "");
|
|
519
|
+
const out = /* @__PURE__ */ new Map();
|
|
520
|
+
for (const important of [false, true]) {
|
|
521
|
+
for (const r of matched) {
|
|
522
|
+
for (const d of r.declarations) if (d.important === important) out.set(d.prop, d.value);
|
|
523
|
+
}
|
|
524
|
+
for (const d of inline) if (d.important === important) out.set(d.prop, d.value);
|
|
525
|
+
}
|
|
526
|
+
elementCache.set(el, out);
|
|
527
|
+
return out;
|
|
528
|
+
}
|
|
529
|
+
function ownProp(el, prop) {
|
|
530
|
+
return authorDeclarations(el).get(prop) ?? el.getAttribute(prop);
|
|
531
|
+
}
|
|
532
|
+
function deriveStyle(parent, el) {
|
|
533
|
+
let next = null;
|
|
534
|
+
for (const prop of INHERITABLE) {
|
|
535
|
+
const own = ownProp(el, prop);
|
|
536
|
+
if (own == null || own === "inherit") continue;
|
|
537
|
+
if (!next) next = { ...parent };
|
|
538
|
+
next[prop] = own;
|
|
539
|
+
}
|
|
540
|
+
return next ?? parent;
|
|
541
|
+
}
|
|
542
|
+
function resolveCurrentColor(raw, style) {
|
|
543
|
+
if (raw == null) return null;
|
|
544
|
+
if (raw.trim().toLowerCase() === "currentcolor") return style["color"] ?? "#000000";
|
|
545
|
+
return raw;
|
|
546
|
+
}
|
|
547
|
+
|
|
265
548
|
// src/elements.ts
|
|
266
549
|
function collectElementsByTag(root, tags) {
|
|
267
550
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -450,7 +733,7 @@ function warnUnsupportedDefsChildren(svg, onWarn) {
|
|
|
450
733
|
const child = root.children[i];
|
|
451
734
|
const tag = child.tagName.toLowerCase();
|
|
452
735
|
if (GRADIENT_TAGS.has(tag) || GRADIENT_TAGS.has(localName(tag))) continue;
|
|
453
|
-
if (tag === "pattern" || tag === "marker" || tag === "clippath") continue;
|
|
736
|
+
if (tag === "pattern" || tag === "marker" || tag === "clippath" || tag === "style") continue;
|
|
454
737
|
onWarn(`unsupported <defs> child: <${child.tagName}>`);
|
|
455
738
|
}
|
|
456
739
|
}
|
|
@@ -487,10 +770,10 @@ function readStops(el, onWarn) {
|
|
|
487
770
|
if (c.tagName.toLowerCase() !== "stop") continue;
|
|
488
771
|
const offsetRaw = parseRatio(c.getAttribute("offset") ?? "0");
|
|
489
772
|
const offset = Number.isFinite(offsetRaw) ? offsetRaw : 0;
|
|
490
|
-
const colorAttr = c
|
|
773
|
+
const colorAttr = ownProp(c, "stop-color") ?? "#000000";
|
|
491
774
|
const parsed = parsePaintAttr(colorAttr);
|
|
492
775
|
if (parsed && parsed.kind === "solid") {
|
|
493
|
-
const opacityAttr = c
|
|
776
|
+
const opacityAttr = ownProp(c, "stop-opacity");
|
|
494
777
|
const own = opacityAttr != null ? parseRatio(opacityAttr) : NaN;
|
|
495
778
|
const alpha = Number.isFinite(own) ? own : parsed.alpha;
|
|
496
779
|
stops.push({ offset, color: alpha < 1 ? applyAlpha(parsed.color, alpha) : parsed.color });
|
|
@@ -734,59 +1017,6 @@ function stopXml(s) {
|
|
|
734
1017
|
return `<stop offset="${trimNumber(s.offset)}" stop-color="${s.color}"/>`;
|
|
735
1018
|
}
|
|
736
1019
|
|
|
737
|
-
// src/cascade.ts
|
|
738
|
-
var EMPTY_STYLE = {};
|
|
739
|
-
var INHERITABLE = [
|
|
740
|
-
"fill",
|
|
741
|
-
"fill-opacity",
|
|
742
|
-
"fill-rule",
|
|
743
|
-
"stroke",
|
|
744
|
-
"stroke-width",
|
|
745
|
-
"stroke-opacity",
|
|
746
|
-
"stroke-linecap",
|
|
747
|
-
"stroke-linejoin",
|
|
748
|
-
"stroke-dasharray",
|
|
749
|
-
"stroke-miterlimit",
|
|
750
|
-
"marker-start",
|
|
751
|
-
"marker-mid",
|
|
752
|
-
"marker-end",
|
|
753
|
-
"color",
|
|
754
|
-
"font-size",
|
|
755
|
-
"font-family",
|
|
756
|
-
"font-weight",
|
|
757
|
-
"font-style",
|
|
758
|
-
"text-anchor",
|
|
759
|
-
"letter-spacing",
|
|
760
|
-
"text-decoration",
|
|
761
|
-
"direction"
|
|
762
|
-
];
|
|
763
|
-
function readStyleProp(el, prop) {
|
|
764
|
-
const style = el.getAttribute("style");
|
|
765
|
-
if (!style) return null;
|
|
766
|
-
const re = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([^;]+)`, "i");
|
|
767
|
-
const m = re.exec(style);
|
|
768
|
-
if (!m) return null;
|
|
769
|
-
return m[1].trim();
|
|
770
|
-
}
|
|
771
|
-
function ownProp(el, prop) {
|
|
772
|
-
return readStyleProp(el, prop) ?? el.getAttribute(prop);
|
|
773
|
-
}
|
|
774
|
-
function deriveStyle(parent, el) {
|
|
775
|
-
let next = null;
|
|
776
|
-
for (const prop of INHERITABLE) {
|
|
777
|
-
const own = ownProp(el, prop);
|
|
778
|
-
if (own == null || own === "inherit") continue;
|
|
779
|
-
if (!next) next = { ...parent };
|
|
780
|
-
next[prop] = own;
|
|
781
|
-
}
|
|
782
|
-
return next ?? parent;
|
|
783
|
-
}
|
|
784
|
-
function resolveCurrentColor(raw, style) {
|
|
785
|
-
if (raw == null) return null;
|
|
786
|
-
if (raw.trim().toLowerCase() === "currentcolor") return style["color"] ?? "#000000";
|
|
787
|
-
return raw;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
1020
|
// src/parse.ts
|
|
791
1021
|
var SUPPORTED_LEAF_TAGS = /* @__PURE__ */ new Set([
|
|
792
1022
|
"rect",
|
|
@@ -807,7 +1037,8 @@ var IGNORED_TAGS = /* @__PURE__ */ new Set([
|
|
|
807
1037
|
"clippath",
|
|
808
1038
|
"title",
|
|
809
1039
|
"desc",
|
|
810
|
-
"metadata"
|
|
1040
|
+
"metadata",
|
|
1041
|
+
"style"
|
|
811
1042
|
]);
|
|
812
1043
|
function parseSvg(svg, opts = {}) {
|
|
813
1044
|
const warnings = [];
|
|
@@ -1559,6 +1790,11 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
1559
1790
|
}
|
|
1560
1791
|
return node;
|
|
1561
1792
|
}
|
|
1793
|
+
var TEXT_TRANSFORMS = /* @__PURE__ */ new Set(["none", "uppercase", "lowercase", "capitalize"]);
|
|
1794
|
+
function parseTextTransform(raw) {
|
|
1795
|
+
const v = raw?.trim().toLowerCase();
|
|
1796
|
+
return v !== void 0 && TEXT_TRANSFORMS.has(v) ? v : void 0;
|
|
1797
|
+
}
|
|
1562
1798
|
function readTspanRun(el, gradients, style, onWarn) {
|
|
1563
1799
|
const text = el.textContent ?? "";
|
|
1564
1800
|
const run = { text };
|
|
@@ -1589,6 +1825,8 @@ function readTspanRun(el, gradients, style, onWarn) {
|
|
|
1589
1825
|
if (decoration.underline) run.underline = true;
|
|
1590
1826
|
if (decoration.strikethrough) run.strikethrough = true;
|
|
1591
1827
|
if (decoration.overline) run.overline = true;
|
|
1828
|
+
const transform = parseTextTransform(ownProp(el, "text-transform"));
|
|
1829
|
+
if (transform) run.textTransform = transform;
|
|
1592
1830
|
const tspanStyle = deriveStyle(style, el);
|
|
1593
1831
|
const fillAttr = resolveCurrentColor(ownProp(el, "fill"), tspanStyle);
|
|
1594
1832
|
if (fillAttr) {
|
|
@@ -1636,6 +1874,8 @@ function readTextStyle(style, onWarn) {
|
|
|
1636
1874
|
if (decoration.underline) out.underline = true;
|
|
1637
1875
|
if (decoration.strikethrough) out.strikethrough = true;
|
|
1638
1876
|
if (decoration.overline) out.overline = true;
|
|
1877
|
+
const transform = parseTextTransform(style["text-transform"] ?? null);
|
|
1878
|
+
if (transform && transform !== "none") out.textTransform = transform;
|
|
1639
1879
|
return out;
|
|
1640
1880
|
}
|
|
1641
1881
|
function readTextPaint(style, gradients, onWarn, nonScalingStroke = false) {
|
|
@@ -2201,6 +2441,9 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
2201
2441
|
}
|
|
2202
2442
|
const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
|
|
2203
2443
|
if (decoration) attrs.push(`text-decoration="${decoration}"`);
|
|
2444
|
+
if (style?.textTransform && style.textTransform !== "none") {
|
|
2445
|
+
attrs.push(`style="text-transform:${style.textTransform}"`);
|
|
2446
|
+
}
|
|
2204
2447
|
if (style?.wrap) {
|
|
2205
2448
|
attrs.push('data-weasel-wrap="true"');
|
|
2206
2449
|
warn("text that wraps at its box width is written unwrapped: SVG text does not wrap, so other readers draw one line per hard break (weasel reads the wrap back)");
|
|
@@ -2263,6 +2506,7 @@ function runXml(run, registry, warn) {
|
|
|
2263
2506
|
}
|
|
2264
2507
|
}
|
|
2265
2508
|
for (const a of coreStrokeAttrs(run.stroke, registry, warn)) attrs.push(a);
|
|
2509
|
+
if (run.textTransform) attrs.push(`style="text-transform:${run.textTransform}"`);
|
|
2266
2510
|
const head = attrs.length > 0 ? `<tspan ${attrs.join(" ")}>` : "<tspan>";
|
|
2267
2511
|
return `${head}${escapeText(run.text)}</tspan>`;
|
|
2268
2512
|
}
|
|
@@ -2329,6 +2573,22 @@ function strokeDataFromSvg(stroke, box) {
|
|
|
2329
2573
|
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
|
|
2330
2574
|
};
|
|
2331
2575
|
}
|
|
2576
|
+
function svgImageFromKit(image, pose) {
|
|
2577
|
+
const node = {
|
|
2578
|
+
kind: "image",
|
|
2579
|
+
href: image.src,
|
|
2580
|
+
x: pose.x,
|
|
2581
|
+
y: pose.y,
|
|
2582
|
+
width: pose.width,
|
|
2583
|
+
height: pose.height
|
|
2584
|
+
};
|
|
2585
|
+
if (image.source) node.source = { ...image.source };
|
|
2586
|
+
if (image.flipX) node.flipX = true;
|
|
2587
|
+
if (image.flipY) node.flipY = true;
|
|
2588
|
+
if (image.opacity != null) node.opacity = image.opacity;
|
|
2589
|
+
if (pose.rotation) node.rotation = pose.rotation;
|
|
2590
|
+
return node;
|
|
2591
|
+
}
|
|
2332
2592
|
function fillInBoxFrame(fill, box) {
|
|
2333
2593
|
const units = "units" in fill ? fill.units : void 0;
|
|
2334
2594
|
return units === "world" ? fillToBoundsFrame(fill, box) : fill;
|
|
@@ -2410,7 +2670,10 @@ function svgNodesToKitDrafts(nodes, nextId) {
|
|
|
2410
2670
|
data: {
|
|
2411
2671
|
image: {
|
|
2412
2672
|
src: n.href,
|
|
2413
|
-
...n.opacity != null ? { opacity: n.opacity } : {}
|
|
2673
|
+
...n.opacity != null ? { opacity: n.opacity } : {},
|
|
2674
|
+
...n.source ? { source: { ...n.source } } : {},
|
|
2675
|
+
...n.flipX ? { flipX: true } : {},
|
|
2676
|
+
...n.flipY ? { flipY: true } : {}
|
|
2414
2677
|
}
|
|
2415
2678
|
}
|
|
2416
2679
|
});
|
|
@@ -2522,6 +2785,6 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
2522
2785
|
}
|
|
2523
2786
|
}
|
|
2524
2787
|
|
|
2525
|
-
export { IDENTITY_MATRIX, parseSvg, serializeSvg, strokeDataFromSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
2788
|
+
export { IDENTITY_MATRIX, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
2526
2789
|
//# sourceMappingURL=index.js.map
|
|
2527
2790
|
//# sourceMappingURL=index.js.map
|