@weasel-js/svg 1.5.2 → 1.6.1
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 +5 -2
- package/dist/index.js +338 -62
- 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,5 +1,5 @@
|
|
|
1
1
|
import { Mat3 } from '@weasel-js/geom';
|
|
2
|
-
import { Path, FillStyle, ScreenLength, StrokeAlign, StyledRun, TextStyle, TextVerticalAlign, Stroke, MarkerEntry, TilePatternSpec, IngestCtx } from '@weasel-js/core';
|
|
2
|
+
import { Path, FillStyle, ScreenLength, StrokeAlign, StyledRun, TextStyle, TextVerticalAlign, Stroke, MarkerEntry, TilePatternSpec, ImageNodeData, IngestCtx } from '@weasel-js/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Public types for `@weasel-js/svg`. The package exposes a flat,
|
|
@@ -426,6 +426,9 @@ type SvgSceneDraft = {
|
|
|
426
426
|
* `userSpaceOnUse` gradient survives the fit-clamp and the drop-point
|
|
427
427
|
* placement. */
|
|
428
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;
|
|
429
432
|
/**
|
|
430
433
|
* Walk an `SvgNode[]` tree and emit a flat, parent-before-child list of
|
|
431
434
|
* {@link SvgSceneDraft}s. Each `<g>` becomes a container whose pose is the
|
|
@@ -441,4 +444,4 @@ declare function svgNodesToKitDrafts(nodes: readonly SvgNode[], nextId: () => st
|
|
|
441
444
|
*/
|
|
442
445
|
declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
|
|
443
446
|
|
|
444
|
-
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
|
@@ -13,9 +13,9 @@ function isIdentity(m, eps = 1e-9) {
|
|
|
13
13
|
var NUM_RE = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
|
14
14
|
function readNumbers(s) {
|
|
15
15
|
const out = [];
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
18
|
-
for (const m of
|
|
16
|
+
const matches2 = s.match(NUM_RE);
|
|
17
|
+
if (!matches2) return out;
|
|
18
|
+
for (const m of matches2) {
|
|
19
19
|
const n = Number(m);
|
|
20
20
|
if (Number.isFinite(n)) out.push(n);
|
|
21
21
|
}
|
|
@@ -249,6 +249,302 @@ function parsePaintAttr(raw) {
|
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
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
|
+
|
|
252
548
|
// src/elements.ts
|
|
253
549
|
function collectElementsByTag(root, tags) {
|
|
254
550
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -437,7 +733,7 @@ function warnUnsupportedDefsChildren(svg, onWarn) {
|
|
|
437
733
|
const child = root.children[i];
|
|
438
734
|
const tag = child.tagName.toLowerCase();
|
|
439
735
|
if (GRADIENT_TAGS.has(tag) || GRADIENT_TAGS.has(localName(tag))) continue;
|
|
440
|
-
if (tag === "pattern" || tag === "marker" || tag === "clippath") continue;
|
|
736
|
+
if (tag === "pattern" || tag === "marker" || tag === "clippath" || tag === "style") continue;
|
|
441
737
|
onWarn(`unsupported <defs> child: <${child.tagName}>`);
|
|
442
738
|
}
|
|
443
739
|
}
|
|
@@ -474,10 +770,10 @@ function readStops(el, onWarn) {
|
|
|
474
770
|
if (c.tagName.toLowerCase() !== "stop") continue;
|
|
475
771
|
const offsetRaw = parseRatio(c.getAttribute("offset") ?? "0");
|
|
476
772
|
const offset = Number.isFinite(offsetRaw) ? offsetRaw : 0;
|
|
477
|
-
const colorAttr = c
|
|
773
|
+
const colorAttr = ownProp(c, "stop-color") ?? "#000000";
|
|
478
774
|
const parsed = parsePaintAttr(colorAttr);
|
|
479
775
|
if (parsed && parsed.kind === "solid") {
|
|
480
|
-
const opacityAttr = c
|
|
776
|
+
const opacityAttr = ownProp(c, "stop-opacity");
|
|
481
777
|
const own = opacityAttr != null ? parseRatio(opacityAttr) : NaN;
|
|
482
778
|
const alpha = Number.isFinite(own) ? own : parsed.alpha;
|
|
483
779
|
stops.push({ offset, color: alpha < 1 ? applyAlpha(parsed.color, alpha) : parsed.color });
|
|
@@ -721,59 +1017,6 @@ function stopXml(s) {
|
|
|
721
1017
|
return `<stop offset="${trimNumber(s.offset)}" stop-color="${s.color}"/>`;
|
|
722
1018
|
}
|
|
723
1019
|
|
|
724
|
-
// src/cascade.ts
|
|
725
|
-
var EMPTY_STYLE = {};
|
|
726
|
-
var INHERITABLE = [
|
|
727
|
-
"fill",
|
|
728
|
-
"fill-opacity",
|
|
729
|
-
"fill-rule",
|
|
730
|
-
"stroke",
|
|
731
|
-
"stroke-width",
|
|
732
|
-
"stroke-opacity",
|
|
733
|
-
"stroke-linecap",
|
|
734
|
-
"stroke-linejoin",
|
|
735
|
-
"stroke-dasharray",
|
|
736
|
-
"stroke-miterlimit",
|
|
737
|
-
"marker-start",
|
|
738
|
-
"marker-mid",
|
|
739
|
-
"marker-end",
|
|
740
|
-
"color",
|
|
741
|
-
"font-size",
|
|
742
|
-
"font-family",
|
|
743
|
-
"font-weight",
|
|
744
|
-
"font-style",
|
|
745
|
-
"text-anchor",
|
|
746
|
-
"letter-spacing",
|
|
747
|
-
"text-decoration",
|
|
748
|
-
"direction"
|
|
749
|
-
];
|
|
750
|
-
function readStyleProp(el, prop) {
|
|
751
|
-
const style = el.getAttribute("style");
|
|
752
|
-
if (!style) return null;
|
|
753
|
-
const re = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([^;]+)`, "i");
|
|
754
|
-
const m = re.exec(style);
|
|
755
|
-
if (!m) return null;
|
|
756
|
-
return m[1].trim();
|
|
757
|
-
}
|
|
758
|
-
function ownProp(el, prop) {
|
|
759
|
-
return readStyleProp(el, prop) ?? el.getAttribute(prop);
|
|
760
|
-
}
|
|
761
|
-
function deriveStyle(parent, el) {
|
|
762
|
-
let next = null;
|
|
763
|
-
for (const prop of INHERITABLE) {
|
|
764
|
-
const own = ownProp(el, prop);
|
|
765
|
-
if (own == null || own === "inherit") continue;
|
|
766
|
-
if (!next) next = { ...parent };
|
|
767
|
-
next[prop] = own;
|
|
768
|
-
}
|
|
769
|
-
return next ?? parent;
|
|
770
|
-
}
|
|
771
|
-
function resolveCurrentColor(raw, style) {
|
|
772
|
-
if (raw == null) return null;
|
|
773
|
-
if (raw.trim().toLowerCase() === "currentcolor") return style["color"] ?? "#000000";
|
|
774
|
-
return raw;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
1020
|
// src/parse.ts
|
|
778
1021
|
var SUPPORTED_LEAF_TAGS = /* @__PURE__ */ new Set([
|
|
779
1022
|
"rect",
|
|
@@ -794,7 +1037,8 @@ var IGNORED_TAGS = /* @__PURE__ */ new Set([
|
|
|
794
1037
|
"clippath",
|
|
795
1038
|
"title",
|
|
796
1039
|
"desc",
|
|
797
|
-
"metadata"
|
|
1040
|
+
"metadata",
|
|
1041
|
+
"style"
|
|
798
1042
|
]);
|
|
799
1043
|
function parseSvg(svg, opts = {}) {
|
|
800
1044
|
const warnings = [];
|
|
@@ -1546,6 +1790,11 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
1546
1790
|
}
|
|
1547
1791
|
return node;
|
|
1548
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
|
+
}
|
|
1549
1798
|
function readTspanRun(el, gradients, style, onWarn) {
|
|
1550
1799
|
const text = el.textContent ?? "";
|
|
1551
1800
|
const run = { text };
|
|
@@ -1576,6 +1825,8 @@ function readTspanRun(el, gradients, style, onWarn) {
|
|
|
1576
1825
|
if (decoration.underline) run.underline = true;
|
|
1577
1826
|
if (decoration.strikethrough) run.strikethrough = true;
|
|
1578
1827
|
if (decoration.overline) run.overline = true;
|
|
1828
|
+
const transform = parseTextTransform(ownProp(el, "text-transform"));
|
|
1829
|
+
if (transform) run.textTransform = transform;
|
|
1579
1830
|
const tspanStyle = deriveStyle(style, el);
|
|
1580
1831
|
const fillAttr = resolveCurrentColor(ownProp(el, "fill"), tspanStyle);
|
|
1581
1832
|
if (fillAttr) {
|
|
@@ -1623,6 +1874,8 @@ function readTextStyle(style, onWarn) {
|
|
|
1623
1874
|
if (decoration.underline) out.underline = true;
|
|
1624
1875
|
if (decoration.strikethrough) out.strikethrough = true;
|
|
1625
1876
|
if (decoration.overline) out.overline = true;
|
|
1877
|
+
const transform = parseTextTransform(style["text-transform"] ?? null);
|
|
1878
|
+
if (transform && transform !== "none") out.textTransform = transform;
|
|
1626
1879
|
return out;
|
|
1627
1880
|
}
|
|
1628
1881
|
function readTextPaint(style, gradients, onWarn, nonScalingStroke = false) {
|
|
@@ -2188,6 +2441,9 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
2188
2441
|
}
|
|
2189
2442
|
const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
|
|
2190
2443
|
if (decoration) attrs.push(`text-decoration="${decoration}"`);
|
|
2444
|
+
if (style?.textTransform && style.textTransform !== "none") {
|
|
2445
|
+
attrs.push(`style="text-transform:${style.textTransform}"`);
|
|
2446
|
+
}
|
|
2191
2447
|
if (style?.wrap) {
|
|
2192
2448
|
attrs.push('data-weasel-wrap="true"');
|
|
2193
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)");
|
|
@@ -2250,6 +2506,7 @@ function runXml(run, registry, warn) {
|
|
|
2250
2506
|
}
|
|
2251
2507
|
}
|
|
2252
2508
|
for (const a of coreStrokeAttrs(run.stroke, registry, warn)) attrs.push(a);
|
|
2509
|
+
if (run.textTransform) attrs.push(`style="text-transform:${run.textTransform}"`);
|
|
2253
2510
|
const head = attrs.length > 0 ? `<tspan ${attrs.join(" ")}>` : "<tspan>";
|
|
2254
2511
|
return `${head}${escapeText(run.text)}</tspan>`;
|
|
2255
2512
|
}
|
|
@@ -2316,6 +2573,22 @@ function strokeDataFromSvg(stroke, box) {
|
|
|
2316
2573
|
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
|
|
2317
2574
|
};
|
|
2318
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
|
+
}
|
|
2319
2592
|
function fillInBoxFrame(fill, box) {
|
|
2320
2593
|
const units = "units" in fill ? fill.units : void 0;
|
|
2321
2594
|
return units === "world" ? fillToBoundsFrame(fill, box) : fill;
|
|
@@ -2397,7 +2670,10 @@ function svgNodesToKitDrafts(nodes, nextId) {
|
|
|
2397
2670
|
data: {
|
|
2398
2671
|
image: {
|
|
2399
2672
|
src: n.href,
|
|
2400
|
-
...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 } : {}
|
|
2401
2677
|
}
|
|
2402
2678
|
}
|
|
2403
2679
|
});
|
|
@@ -2509,6 +2785,6 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
2509
2785
|
}
|
|
2510
2786
|
}
|
|
2511
2787
|
|
|
2512
|
-
export { IDENTITY_MATRIX, parseSvg, serializeSvg, strokeDataFromSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
2788
|
+
export { IDENTITY_MATRIX, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
2513
2789
|
//# sourceMappingURL=index.js.map
|
|
2514
2790
|
//# sourceMappingURL=index.js.map
|