@weasel-js/svg 1.6.1 → 1.7.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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
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';
1
+ import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, resolveScreenLength, scriptMetricsFor, warmFonts, warmPaintKinds, isPaintKindKnown, solid, fillToBoundsFrame, getMarker, registerMarker, dwarn, unionAABB, createInsertOp, fillInPoseFrame, DEFAULT_SHAPE_FILL, pathInPoseFrame, DEFAULT_TEXT_STYLE, PathBuilder, resolveTextStyle, layoutRuns, resolveRuns, resolveAlign, PATH_C, PATH_Q, resolveRunFace, parseColor, rgbaToHex, resolveMarkerSize, getPaintKind } from '@weasel-js/core';
2
2
  import { identity, multiply, invert } from '@weasel-js/geom';
3
3
  import { tileGeometry } from '@weasel-js/core/patterns-builtin';
4
4
 
@@ -145,16 +145,16 @@ function rectToPolygon(r) {
145
145
  b.close();
146
146
  return b.build();
147
147
  }
148
- function rectElementToPath(x, y, width, height, rx, ry, m = IDENTITY_MATRIX) {
148
+ function rectElementToPath(x, y, width2, height2, rx, ry, m = IDENTITY_MATRIX) {
149
149
  if (rx <= 0 && ry <= 0) {
150
- return transformPath({ kind: "rect", x, y, width, height }, m);
150
+ return transformPath({ kind: "rect", x, y, width: width2, height: height2 }, m);
151
151
  }
152
- const r = Math.min(Math.max(rx, ry), Math.min(width, height) / 2);
153
- const r2 = Math.min(rx > 0 ? rx : r, ry > 0 ? ry : r, Math.min(width, height) / 2);
152
+ const r = Math.min(Math.max(rx, ry), Math.min(width2, height2) / 2);
153
+ const r2 = Math.min(rx > 0 ? rx : r, ry > 0 ? ry : r, Math.min(width2, height2) / 2);
154
154
  const rad = r2;
155
155
  const k = KAPPA * rad;
156
- const x2 = x + width;
157
- const y2 = y + height;
156
+ const x2 = x + width2;
157
+ const y2 = y + height2;
158
158
  const b = new PathBuilder();
159
159
  b.moveTo(x + rad, y);
160
160
  b.lineTo(x2 - rad, y);
@@ -217,65 +217,12 @@ function polygonToPath(points, m = IDENTITY_MATRIX) {
217
217
  b.close();
218
218
  return transformPath(b.build(), m);
219
219
  }
220
- function anchorOffset(style, width) {
220
+ function anchorOffset(style, width2) {
221
221
  const edge = resolveAlign(style?.align ?? "left", style?.direction ?? "ltr");
222
- return edge === "center" ? width / 2 : edge === "right" ? width : 0;
223
- }
224
- function parsePaintAttr(raw) {
225
- if (raw == null) return null;
226
- const s = raw.trim();
227
- if (s === "") return null;
228
- if (s === "none") return { kind: "none" };
229
- if (s === "currentColor" || s === "currentcolor") {
230
- return { kind: "solid", color: "#000000", alpha: 1 };
231
- }
232
- const urlMatch = /^url\(\s*#([^)\s]+)\s*\)(.*)$/.exec(s);
233
- if (urlMatch) {
234
- const out = { kind: "ref", id: urlMatch[1] };
235
- const rest = urlMatch[2].trim();
236
- if (rest !== "" && rest !== "none") {
237
- const fallback = parsePaintAttr(rest);
238
- if (fallback?.kind === "solid") {
239
- out.fallback = { color: fallback.color, alpha: fallback.alpha };
240
- }
241
- }
242
- return out;
243
- }
244
- try {
245
- const [r, g, b, a] = parseColor(s);
246
- return { kind: "solid", color: rgbaToHex([r, g, b]), alpha: a };
247
- } catch {
248
- return null;
249
- }
222
+ return edge === "center" ? width2 / 2 : edge === "right" ? width2 : 0;
250
223
  }
251
224
 
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
- ];
225
+ // src/cssScan.ts
279
226
  function skipString(s, i) {
280
227
  const q = s[i];
281
228
  for (let j = i + 1; j < s.length; j++) {
@@ -350,26 +297,21 @@ var IMPORTANT = /!\s*important\s*$/i;
350
297
  function parseDeclarations(block) {
351
298
  const out = [];
352
299
  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 });
300
+ const d = parseDeclaration(part);
301
+ if (d) out.push(d);
362
302
  }
363
303
  return out;
364
304
  }
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;
305
+ function parseDeclaration(text) {
306
+ const colon = text.indexOf(":");
307
+ if (colon < 0) return null;
308
+ const prop = text.slice(0, colon).trim().toLowerCase();
309
+ if (!PROP_NAME.test(prop)) return null;
310
+ let value = text.slice(colon + 1).trim();
311
+ const important = IMPORTANT.test(value);
312
+ if (important) value = value.replace(IMPORTANT, "").trim();
313
+ if (!value) return null;
314
+ return { prop, value, important };
373
315
  }
374
316
  function parenEnd(s, i) {
375
317
  let depth = 0;
@@ -385,6 +327,561 @@ function parenEnd(s, i) {
385
327
  }
386
328
  return s.length;
387
329
  }
330
+
331
+ // src/condition.ts
332
+ var ConditionSyntaxError = class extends Error {
333
+ };
334
+ var triNot = (a) => a == null ? null : !a;
335
+ var triAnd = (a, b) => a === false || b === false ? false : a == null || b == null ? null : true;
336
+ var triOr = (a, b) => a === true || b === true ? true : a == null || b == null ? null : false;
337
+ var WS = /\s/;
338
+ var IDENT = /^-?[a-zA-Z_][\w-]*/;
339
+ function skipWs(p) {
340
+ while (p.i < p.s.length && WS.test(p.s[p.i])) p.i++;
341
+ }
342
+ function keyword(p, word) {
343
+ const end = p.i + word.length;
344
+ if (p.s.slice(p.i, end).toLowerCase() !== word) return false;
345
+ if (end >= p.s.length || !WS.test(p.s[end])) return false;
346
+ p.i = end;
347
+ return true;
348
+ }
349
+ function evaluateCondition(s, leaves, allowOr = true) {
350
+ const p = { s, i: 0 };
351
+ const r = condition(p, leaves, allowOr);
352
+ skipWs(p);
353
+ if (p.i < s.length) throw new ConditionSyntaxError(`unexpected "${s.slice(p.i)}"`);
354
+ return r;
355
+ }
356
+ function condition(p, leaves, allowOr) {
357
+ skipWs(p);
358
+ if (keyword(p, "not")) return triNot(inParens(p, leaves));
359
+ let r = inParens(p, leaves);
360
+ let op = null;
361
+ for (; ; ) {
362
+ const save = p.i;
363
+ skipWs(p);
364
+ if (p.i >= p.s.length) {
365
+ p.i = save;
366
+ return r;
367
+ }
368
+ const next = keyword(p, "and") ? "and" : keyword(p, "or") ? "or" : null;
369
+ if (!next || op && next !== op || next === "or" && !allowOr) {
370
+ throw new ConditionSyntaxError(`unexpected "${p.s.slice(p.i)}"`);
371
+ }
372
+ op = next;
373
+ const rhs = inParens(p, leaves);
374
+ r = op === "and" ? triAnd(r, rhs) : triOr(r, rhs);
375
+ }
376
+ }
377
+ function looksLikeCondition(inner) {
378
+ const t = inner.trimStart();
379
+ return t.startsWith("(") || /^not\s/i.test(t) || /^-?[a-zA-Z_][\w-]*\(/.test(t);
380
+ }
381
+ function inParens(p, leaves) {
382
+ skipWs(p);
383
+ const start = p.i;
384
+ let name = null;
385
+ if (p.s[p.i] !== "(") {
386
+ const m = IDENT.exec(p.s.slice(p.i));
387
+ if (!m || p.s[p.i + m[0].length] !== "(") throw new ConditionSyntaxError(`expected "(" at "${p.s.slice(p.i)}"`);
388
+ name = m[0].toLowerCase();
389
+ p.i += m[0].length;
390
+ }
391
+ const end = parenEnd(p.s, p.i);
392
+ if (p.s[end - 1] !== ")") throw new ConditionSyntaxError(`unbalanced "${p.s.slice(start)}"`);
393
+ const inner = p.s.slice(p.i + 1, end - 1);
394
+ p.i = end;
395
+ if (name != null) return leaves.fn(name, inner);
396
+ if (looksLikeCondition(inner)) {
397
+ try {
398
+ return evaluateCondition(inner, leaves);
399
+ } catch (e) {
400
+ if (!(e instanceof ConditionSyntaxError)) throw e;
401
+ }
402
+ }
403
+ return leaves.paren(inner);
404
+ }
405
+
406
+ // src/media.ts
407
+ var DEFAULT_MEDIA_ENVIRONMENT = {
408
+ type: "screen",
409
+ width: 300,
410
+ height: 150,
411
+ resolution: 1,
412
+ prefersColorScheme: "light",
413
+ prefersReducedMotion: "no-preference",
414
+ hover: "none",
415
+ pointer: "none",
416
+ update: "none",
417
+ color: 8,
418
+ monochrome: 0
419
+ };
420
+ var ABSOLUTE_PX = {
421
+ px: 1,
422
+ in: 96,
423
+ cm: 96 / 2.54,
424
+ mm: 96 / 25.4,
425
+ q: 96 / 101.6,
426
+ pt: 96 / 72,
427
+ pc: 16
428
+ };
429
+ function absoluteLength(raw) {
430
+ const m = raw == null ? null : /^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)\s*([a-z]*)\s*$/i.exec(raw);
431
+ if (!m) return void 0;
432
+ const scale = ABSOLUTE_PX[m[2].toLowerCase() || "px"];
433
+ const n = parseFloat(m[1]) * (scale ?? NaN);
434
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
435
+ }
436
+ function mediaEnvironmentFor(root, overrides = {}) {
437
+ let width2 = absoluteLength(root.getAttribute("width"));
438
+ let height2 = absoluteLength(root.getAttribute("height"));
439
+ const vb = (root.getAttribute("viewBox") ?? "").trim().split(/[\s,]+/).map(Number);
440
+ if (vb.length === 4 && vb.every(Number.isFinite) && vb[2] > 0 && vb[3] > 0) {
441
+ const [, , w, h] = vb;
442
+ if (width2 == null && height2 == null) {
443
+ width2 = w;
444
+ height2 = h;
445
+ } else if (width2 == null) width2 = height2 * (w / h);
446
+ else if (height2 == null) height2 = width2 * (h / w);
447
+ }
448
+ return {
449
+ ...DEFAULT_MEDIA_ENVIRONMENT,
450
+ width: width2 ?? DEFAULT_MEDIA_ENVIRONMENT.width,
451
+ height: height2 ?? DEFAULT_MEDIA_ENVIRONMENT.height,
452
+ ...overrides
453
+ };
454
+ }
455
+ var TOKEN = /\s*(?:([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)([a-z%]*)|(-?[a-z_][\w-]*)|(<=|>=|<|>|=)|(:)|(\/))/iy;
456
+ function tokenize(s) {
457
+ const out = [];
458
+ TOKEN.lastIndex = 0;
459
+ while (TOKEN.lastIndex < s.length) {
460
+ if (/^\s*$/.test(s.slice(TOKEN.lastIndex))) break;
461
+ const m = TOKEN.exec(s);
462
+ if (!m) return null;
463
+ if (m[1] != null) out.push({ k: "num", v: parseFloat(m[1]), unit: m[2].toLowerCase() });
464
+ else if (m[3] != null) out.push({ k: "ident", v: m[3].toLowerCase() });
465
+ else if (m[4] != null) out.push({ k: "op", v: m[4] });
466
+ else if (m[5] != null) out.push({ k: ":" });
467
+ else out.push({ k: "/" });
468
+ }
469
+ return out;
470
+ }
471
+ var width = (e) => e.width;
472
+ var height = (e) => e.height;
473
+ var aspect = (e) => e.width / e.height;
474
+ var hover = { kind: "discrete", values: ["none", "hover"], off: "none", get: (e) => e.hover };
475
+ var pointer = { kind: "discrete", values: ["none", "coarse", "fine"], off: "none", get: (e) => e.pointer };
476
+ var FEATURES = {
477
+ "width": { kind: "range", type: "length", get: width },
478
+ "height": { kind: "range", type: "length", get: height },
479
+ "device-width": { kind: "range", type: "length", get: width },
480
+ "device-height": { kind: "range", type: "length", get: height },
481
+ "aspect-ratio": { kind: "range", type: "ratio", get: aspect },
482
+ "device-aspect-ratio": { kind: "range", type: "ratio", get: aspect },
483
+ "resolution": { kind: "range", type: "resolution", get: (e) => e.resolution },
484
+ "color": { kind: "range", type: "integer", get: (e) => e.color },
485
+ "monochrome": { kind: "range", type: "integer", get: (e) => e.monochrome },
486
+ "orientation": {
487
+ kind: "discrete",
488
+ values: ["portrait", "landscape"],
489
+ get: (e) => e.height >= e.width ? "portrait" : "landscape"
490
+ },
491
+ "prefers-color-scheme": { kind: "discrete", values: ["light", "dark"], get: (e) => e.prefersColorScheme },
492
+ "prefers-reduced-motion": {
493
+ kind: "discrete",
494
+ values: ["no-preference", "reduce"],
495
+ off: "no-preference",
496
+ get: (e) => e.prefersReducedMotion
497
+ },
498
+ "update": { kind: "discrete", values: ["none", "slow", "fast"], off: "none", get: (e) => e.update },
499
+ "hover": hover,
500
+ "any-hover": hover,
501
+ "pointer": pointer,
502
+ "any-pointer": pointer
503
+ };
504
+ var FONT_PX = 16;
505
+ function lengthPx(v, unit, env) {
506
+ if (unit === "") return v === 0 ? 0 : null;
507
+ const abs = ABSOLUTE_PX[unit];
508
+ if (abs != null) return v * abs;
509
+ switch (unit) {
510
+ case "em":
511
+ case "rem":
512
+ return v * FONT_PX;
513
+ case "ex":
514
+ case "ch":
515
+ return v * FONT_PX / 2;
516
+ case "vw":
517
+ return v * env.width / 100;
518
+ case "vh":
519
+ return v * env.height / 100;
520
+ case "vmin":
521
+ return v * Math.min(env.width, env.height) / 100;
522
+ case "vmax":
523
+ return v * Math.max(env.width, env.height) / 100;
524
+ default:
525
+ return null;
526
+ }
527
+ }
528
+ var DPPX = { dppx: 1, x: 1, dpi: 1 / 96, dpcm: 2.54 / 96 };
529
+ function rangeValue(toks, type, env) {
530
+ const [a, b, c] = toks;
531
+ if (type === "ratio") {
532
+ if (toks.length === 1 && a.k === "num" && a.unit === "" && a.v >= 0) return a.v;
533
+ if (toks.length === 3 && a.k === "num" && b.k === "/" && c.k === "num" && a.unit === "" && c.unit === "" && a.v >= 0 && c.v > 0) return a.v / c.v;
534
+ return null;
535
+ }
536
+ if (toks.length !== 1 || a.k !== "num") return null;
537
+ if (type === "length") return lengthPx(a.v, a.unit, env);
538
+ if (type === "resolution") return DPPX[a.unit] != null ? a.v * DPPX[a.unit] : null;
539
+ return a.unit === "" && Number.isInteger(a.v) ? a.v : null;
540
+ }
541
+ function close(a, b) {
542
+ return Math.abs(a - b) <= 1e-9 * Math.max(1, Math.abs(a), Math.abs(b));
543
+ }
544
+ function compare(a, op, b) {
545
+ switch (op) {
546
+ case "=":
547
+ return close(a, b);
548
+ case "<":
549
+ return a < b && !close(a, b);
550
+ case "<=":
551
+ return a < b || close(a, b);
552
+ case ">":
553
+ return a > b && !close(a, b);
554
+ default:
555
+ return a > b || close(a, b);
556
+ }
557
+ }
558
+ var FLIP = { "<": ">", "<=": ">=", ">": "<", ">=": "<=", "=": "=" };
559
+ function evaluateFeature(inner, env) {
560
+ const toks = tokenize(inner);
561
+ if (!toks || toks.length === 0) return null;
562
+ const first = toks[0];
563
+ if (toks.length === 1) {
564
+ if (first.k !== "ident") return null;
565
+ const f = FEATURES[first.v];
566
+ if (!f) return null;
567
+ return f.kind === "range" ? f.get(env) !== 0 : f.get(env) !== f.off;
568
+ }
569
+ if (first.k === "ident" && toks[1].k === ":") {
570
+ const prefix = /^(min|max)-/.exec(first.v)?.[1];
571
+ const f = FEATURES[prefix ? first.v.slice(4) : first.v];
572
+ if (!f) return null;
573
+ const rest = toks.slice(2);
574
+ if (f.kind === "discrete") {
575
+ if (prefix || rest.length !== 1 || rest[0].k !== "ident" || !f.values.includes(rest[0].v)) return null;
576
+ return f.get(env) === rest[0].v;
577
+ }
578
+ const v = rangeValue(rest, f.type, env);
579
+ if (v == null) return null;
580
+ return compare(f.get(env), prefix === "min" ? ">=" : prefix === "max" ? "<=" : "=", v);
581
+ }
582
+ const segments = [[]];
583
+ const ops = [];
584
+ for (const t of toks) {
585
+ if (t.k === "op") {
586
+ ops.push(t.v);
587
+ segments.push([]);
588
+ } else segments[segments.length - 1].push(t);
589
+ }
590
+ if (segments.some((s) => s.length === 0)) return null;
591
+ const featureAt = (s) => s.length === 1 && s[0].k === "ident" ? FEATURES[s[0].v] : void 0;
592
+ if (ops.length === 1) {
593
+ const [lhs, rhs] = segments;
594
+ const named = featureAt(lhs) ?? featureAt(rhs);
595
+ if (!named) return null;
596
+ if (named.kind !== "range") return null;
597
+ const nameFirst = featureAt(lhs) != null;
598
+ const v = rangeValue(nameFirst ? rhs : lhs, named.type, env);
599
+ if (v == null) return null;
600
+ return nameFirst ? compare(named.get(env), ops[0], v) : compare(named.get(env), FLIP[ops[0]], v);
601
+ }
602
+ if (ops.length === 2) {
603
+ const f = featureAt(segments[1]);
604
+ if (!f || f.kind !== "range") return null;
605
+ const lt = ops.every((o) => o === "<" || o === "<=");
606
+ const gt = ops.every((o) => o === ">" || o === ">=");
607
+ if (!lt && !gt) return null;
608
+ const lo = rangeValue(segments[0], f.type, env);
609
+ const hi = rangeValue(segments[2], f.type, env);
610
+ if (lo == null || hi == null) return null;
611
+ const x = f.get(env);
612
+ return compare(lo, ops[0], x) && compare(x, ops[1], hi);
613
+ }
614
+ return null;
615
+ }
616
+ var RESERVED_TYPES = /* @__PURE__ */ new Set(["not", "and", "or", "only", "layer"]);
617
+ var TYPED_QUERY = /^(?:(not|only)\s+)?(-?[a-z_][\w-]*)(?:\s+and\s+([\s\S]+))?$/;
618
+ function evaluateQuery(query, env) {
619
+ const leaves = {
620
+ paren: (inner) => evaluateFeature(inner, env),
621
+ fn: () => null
622
+ };
623
+ if (/^(?:\(|not\s+\(|-?[a-z_][\w-]*\()/.test(query)) return evaluateCondition(query, leaves);
624
+ const m = TYPED_QUERY.exec(query);
625
+ if (!m || RESERVED_TYPES.has(m[2])) throw new ConditionSyntaxError(`invalid media query "${query}"`);
626
+ const typed = triAnd(m[2] === "all" || m[2] === env.type, m[3] ? evaluateCondition(m[3], leaves, false) : true);
627
+ return m[1] === "not" ? triNot(typed) : typed;
628
+ }
629
+ function evaluateMediaQuery(list, env) {
630
+ const src = list.trim().toLowerCase();
631
+ if (src === "") return true;
632
+ return splitTopLevel(src, ",").some((raw) => {
633
+ const query = raw.trim();
634
+ if (query === "") return false;
635
+ try {
636
+ return evaluateQuery(query, env) === true;
637
+ } catch (e) {
638
+ if (e instanceof ConditionSyntaxError) return false;
639
+ throw e;
640
+ }
641
+ });
642
+ }
643
+ function parsePaintAttr(raw) {
644
+ if (raw == null) return null;
645
+ const s = raw.trim();
646
+ if (s === "") return null;
647
+ if (s === "none") return { kind: "none" };
648
+ if (s.toLowerCase() === "currentcolor") {
649
+ return { kind: "solid", color: "#000000", alpha: 1 };
650
+ }
651
+ const urlMatch = /^url\(\s*#([^)\s]+)\s*\)(.*)$/.exec(s);
652
+ if (urlMatch) {
653
+ const out = { kind: "ref", id: urlMatch[1] };
654
+ const rest = urlMatch[2].trim();
655
+ if (rest !== "" && rest !== "none") {
656
+ const fallback = parsePaintAttr(rest);
657
+ if (fallback?.kind === "solid") {
658
+ out.fallback = { color: fallback.color, alpha: fallback.alpha };
659
+ }
660
+ }
661
+ return out;
662
+ }
663
+ try {
664
+ const [r, g, b, a] = parseColor(s);
665
+ return { kind: "solid", color: rgbaToHex([r, g, b]), alpha: a };
666
+ } catch {
667
+ return null;
668
+ }
669
+ }
670
+
671
+ // src/properties.ts
672
+ var TEXT_TRANSFORMS = /* @__PURE__ */ new Set(["none", "uppercase", "lowercase", "capitalize"]);
673
+ var DIMENSION = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)([a-z%]*)$/i;
674
+ function dimension(v) {
675
+ const m = DIMENSION.exec(v);
676
+ return m ? { n: Number(m[1]), unit: m[2].toLowerCase() } : void 0;
677
+ }
678
+ var clamp01 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
679
+ var length = (native, coercedAs) => (v, warn, prop) => {
680
+ const d = dimension(v);
681
+ if (!d) return void 0;
682
+ if (d.unit && !native.includes(d.unit)) {
683
+ warn(`${prop} "${v}" uses unit "${d.unit}", which is not converted; treated as ${d.n} ${coercedAs}`);
684
+ }
685
+ return d.n;
686
+ };
687
+ var worldLength = length(["px"], "world units");
688
+ var keyword2 = (...words) => (v) => words.includes(v) ? v : void 0;
689
+ var isCurrentColor = (v) => v.toLowerCase() === "currentcolor";
690
+ function readPaint(v) {
691
+ if (v === "context-fill") return { kind: "context", of: "fill" };
692
+ if (v === "context-stroke") return { kind: "context", of: "stroke" };
693
+ return parsePaintAttr(v) ?? void 0;
694
+ }
695
+ function readRatio(v) {
696
+ const d = dimension(v);
697
+ if (!d || d.unit !== "" && d.unit !== "%") return void 0;
698
+ return clamp01(d.unit === "%" ? d.n / 100 : d.n);
699
+ }
700
+ function readSolidColor(v, warn, prop) {
701
+ const parsed = parsePaintAttr(v);
702
+ if (parsed?.kind !== "solid") return void 0;
703
+ if (isCurrentColor(v)) warn(`${prop}="${v}" is read as black; it is not resolved against color`);
704
+ return parsed;
705
+ }
706
+ function readMarkerRef(v) {
707
+ if (v === "none") return null;
708
+ return /^url\(\s*#([^)\s]+)\s*\)$/.exec(v)?.[1];
709
+ }
710
+ function readLinecap(v, warn) {
711
+ const cap = keyword2("butt", "round", "square")(v);
712
+ if (!cap) warn(`unsupported stroke-linecap: ${v}`);
713
+ return cap;
714
+ }
715
+ function readLinejoin(v, warn) {
716
+ const join = keyword2("miter", "round", "bevel")(v);
717
+ if (join) return join;
718
+ if (v === "arcs" || v === "miter-clip") {
719
+ warn(`stroke-linejoin "${v}" not supported; falling back to miter`);
720
+ return "miter";
721
+ }
722
+ warn(`unsupported stroke-linejoin: ${v}`);
723
+ return void 0;
724
+ }
725
+ function readDashArray(v, warn) {
726
+ if (v === "none" || v === "") return null;
727
+ const nums = [];
728
+ for (const t of v.split(/[\s,]+/).filter(Boolean)) {
729
+ const d = dimension(t);
730
+ if (!d || d.unit !== "" && d.unit !== "px" || d.n < 0) {
731
+ warn(`unrecognized stroke-dasharray: ${v}`);
732
+ return void 0;
733
+ }
734
+ nums.push(d.n);
735
+ }
736
+ return nums.length % 2 === 1 ? [...nums, ...nums] : nums;
737
+ }
738
+ function readMiterLimit(v, warn) {
739
+ const d = dimension(v);
740
+ if (d && d.unit === "" && d.n >= 1) return d.n;
741
+ warn(`unrecognized stroke-miterlimit: ${v}`);
742
+ return void 0;
743
+ }
744
+ function readColor(v) {
745
+ return isCurrentColor(v) || parsePaintAttr(v)?.kind === "solid" ? v : void 0;
746
+ }
747
+ function readFontSize(v, warn, prop) {
748
+ const d = dimension(v);
749
+ if (d?.unit === "%") return { fontScale: d.n / 100 };
750
+ const n = worldLength(v, warn, prop);
751
+ return n == null ? void 0 : { fontSize: n };
752
+ }
753
+ function readFontWeight(v) {
754
+ if (v === "normal" || v === "bold" || v === "bolder") return v;
755
+ const d = dimension(v);
756
+ return d && d.unit === "" && d.n >= 1 && d.n <= 1e3 ? d.n : void 0;
757
+ }
758
+ function readFontStyle(v, warn) {
759
+ if (v === "normal" || v === "italic") return v;
760
+ if (v === "oblique") {
761
+ warn('font-style "oblique" is read as italic');
762
+ return "italic";
763
+ }
764
+ return void 0;
765
+ }
766
+ function readLetterSpacing(v, warn, prop) {
767
+ return v === "normal" ? null : worldLength(v, warn, prop);
768
+ }
769
+ function readDecoration(v, warn) {
770
+ const out = {};
771
+ for (const t of v.toLowerCase().split(/\s+/)) {
772
+ if (t === "underline") out.underline = true;
773
+ else if (t === "line-through") out.strikethrough = true;
774
+ else if (t === "overline") out.overline = true;
775
+ else if (t !== "none" && t !== "blink") warn(`text-decoration "${v}": "${t}" is not modeled; dropped`);
776
+ }
777
+ return out;
778
+ }
779
+ function readFontVariant(v, warn) {
780
+ let out;
781
+ for (const t of v.toLowerCase().split(/\s+/)) {
782
+ if (t === "small-caps" || t === "normal") out = t;
783
+ else if (t) warn(`font-variant "${v}": "${t}" is not modeled; dropped`);
784
+ }
785
+ return out;
786
+ }
787
+ function readTextTransform(v) {
788
+ const t = v.toLowerCase();
789
+ return TEXT_TRANSFORMS.has(t) ? t : void 0;
790
+ }
791
+ var emLength = length(["em"], "em");
792
+ function readBaselineShift(v, warn, prop) {
793
+ const k = v.toLowerCase();
794
+ if (k === "super" || k === "sub") return { script: k };
795
+ if (k === "baseline") return {};
796
+ const d = dimension(v);
797
+ if (d?.unit === "%") return d.n === 0 ? {} : { baselineShift: d.n / 100 };
798
+ const n = emLength(v, warn, prop);
799
+ if (n == null) return void 0;
800
+ return n === 0 ? {} : { baselineShift: n };
801
+ }
802
+ var inherited = (read) => ({ inherits: true, read });
803
+ var own = (read) => ({ inherits: false, read });
804
+ var PROPERTIES = {
805
+ "fill": inherited(readPaint),
806
+ "fill-opacity": inherited(readRatio),
807
+ "fill-rule": inherited(keyword2("nonzero", "evenodd")),
808
+ "stroke": inherited(readPaint),
809
+ "stroke-width": inherited(worldLength),
810
+ "stroke-opacity": inherited(readRatio),
811
+ "stroke-linecap": inherited(readLinecap),
812
+ "stroke-linejoin": inherited(readLinejoin),
813
+ "stroke-dasharray": inherited(readDashArray),
814
+ "stroke-miterlimit": inherited(readMiterLimit),
815
+ "marker-start": inherited(readMarkerRef),
816
+ "marker-mid": inherited(readMarkerRef),
817
+ "marker-end": inherited(readMarkerRef),
818
+ "color": inherited(readColor),
819
+ "font-size": inherited(readFontSize),
820
+ "font-family": inherited((v) => v.length > 0 ? v : void 0),
821
+ "font-weight": inherited(readFontWeight),
822
+ "font-style": inherited(readFontStyle),
823
+ "text-anchor": inherited(keyword2("start", "middle", "end")),
824
+ "letter-spacing": inherited(readLetterSpacing),
825
+ "text-decoration": inherited(readDecoration),
826
+ "direction": inherited(keyword2("ltr", "rtl")),
827
+ "text-transform": inherited(readTextTransform),
828
+ "font-variant": inherited(readFontVariant),
829
+ "opacity": own(readRatio),
830
+ "stop-color": own(readSolidColor),
831
+ "stop-opacity": own(readRatio),
832
+ "baseline-shift": own(readBaselineShift),
833
+ "vector-effect": own(keyword2("none", "non-scaling-stroke"))
834
+ };
835
+ var INHERITED_PROPERTIES = Object.keys(PROPERTIES).filter((p) => PROPERTIES[p].inherits);
836
+ var silent = () => {
837
+ };
838
+ function readProperty(prop, raw, warn = silent) {
839
+ if (raw == null) return void 0;
840
+ const v = raw.trim();
841
+ if (v === "inherit") return void 0;
842
+ return PROPERTIES[prop].read(v, warn, prop);
843
+ }
844
+ function isPropertyName(prop) {
845
+ return Object.prototype.hasOwnProperty.call(PROPERTIES, prop);
846
+ }
847
+ function isDeclarationHonored(prop, value) {
848
+ const name = prop.toLowerCase();
849
+ if (!isPropertyName(name)) return false;
850
+ const v = value.trim();
851
+ if (v === "inherit") return PROPERTIES[name].inherits;
852
+ let warned = false;
853
+ return readProperty(name, v, () => {
854
+ warned = true;
855
+ }) !== void 0 && !warned;
856
+ }
857
+
858
+ // src/supports.ts
859
+ function evaluateSupports(condition2, opts = {}) {
860
+ try {
861
+ return evaluateCondition(condition2, {
862
+ paren: (inner) => {
863
+ const d = parseDeclaration(inner);
864
+ return d != null && isDeclarationHonored(d.prop, d.value);
865
+ },
866
+ fn: (name, args) => name === "selector" && (opts.selector?.(args) ?? false)
867
+ }) === true;
868
+ } catch (e) {
869
+ if (e instanceof ConditionSyntaxError) return false;
870
+ throw e;
871
+ }
872
+ }
873
+
874
+ // src/cascade.ts
875
+ var EMPTY_STYLE = {};
876
+ function identEnd(s, i) {
877
+ while (i < s.length) {
878
+ const ch = s[i];
879
+ if (ch === "\\") i += 2;
880
+ else if (/[\w-]/.test(ch) || ch.charCodeAt(0) > 127) i++;
881
+ else break;
882
+ }
883
+ return i;
884
+ }
388
885
  var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set(["before", "after", "first-line", "first-letter"]);
389
886
  var MAX_ARG_PSEUDOS = /* @__PURE__ */ new Set(["not", "is", "matches", "-webkit-any", "has"]);
390
887
  function compareSpecificity(a, b) {
@@ -444,19 +941,76 @@ function specificity(selector) {
444
941
  }
445
942
  return [a, b, c];
446
943
  }
447
- function parseStylesheet(text, firstOrder = 0) {
944
+ var CascadeLayers = class {
945
+ children = /* @__PURE__ */ new Map();
946
+ anonymous = 0;
947
+ /** Declare `name` (possibly dotted) inside `parent`, keeping any earlier position; returns its full name. */
948
+ declare(parent, name) {
949
+ let full = parent;
950
+ for (const segment of name.split(".")) {
951
+ const child = full ? `${full}.${segment}` : segment;
952
+ const siblings = this.children.get(full) ?? [];
953
+ if (!siblings.includes(child)) {
954
+ siblings.push(child);
955
+ this.children.set(full, siblings);
956
+ }
957
+ full = child;
958
+ }
959
+ return full;
960
+ }
961
+ /** A fresh layer inside `parent` that nothing can reopen. */
962
+ declareAnonymous(parent) {
963
+ return this.declare(parent, `\0${this.anonymous++}`);
964
+ }
965
+ /**
966
+ * Each layer's rank for normal declarations, higher winning: a layer's
967
+ * sublayers rank below its own rules, in declaration order, and unlayered
968
+ * rules (`''`) rank above every layer.
969
+ */
970
+ ranks() {
971
+ const out = /* @__PURE__ */ new Map();
972
+ const visit = (name) => {
973
+ for (const child of this.children.get(name) ?? []) visit(child);
974
+ out.set(name, out.size);
975
+ };
976
+ visit("");
977
+ return out;
978
+ }
979
+ };
980
+ function parseStylesheet(text, firstOrder = 0, ctx = {}, layers = new CascadeLayers()) {
448
981
  const src = stripComments(text).replace(/<!\[CDATA\[|\]\]>|<!--|-->/g, " ");
449
982
  const rules = [];
450
- let order = firstOrder;
451
- let i = 0;
452
- let preludeStart = 0;
453
- while (i < src.length) {
983
+ parseRuleList(src, 0, src.length, "", ctx, rules, { order: firstOrder, importsAllowed: true, layers });
984
+ return rules;
985
+ }
986
+ var IDENT2 = "-?[a-zA-Z_\\u0080-\\uffff][\\w\\u0080-\\uffff-]*";
987
+ var LAYER_NAME = new RegExp(`^${IDENT2}(?:\\.${IDENT2})*$`);
988
+ function layerNames(prelude) {
989
+ if (!prelude.trim()) return [];
990
+ const names = splitTopLevel(prelude, ",").map((n) => n.trim());
991
+ return names.every((n) => LAYER_NAME.test(n)) ? names : null;
992
+ }
993
+ var AT_RULE = /^@([\w-]+)\s*([\s\S]*)$/;
994
+ function parseRuleList(src, from, to, layer, ctx, rules, state) {
995
+ let i = from;
996
+ let preludeStart = from;
997
+ while (i < to) {
454
998
  const ch = src[i];
455
999
  if (ch === '"' || ch === "'") {
456
1000
  i = skipString(src, i);
457
1001
  continue;
458
1002
  }
459
1003
  if (ch === ";") {
1004
+ const text = src.slice(preludeStart, i).trim();
1005
+ const statement = AT_RULE.exec(text);
1006
+ const name = statement?.[1].toLowerCase();
1007
+ if (name === "import" && state.importsAllowed) {
1008
+ ctx.onWarn?.(`@import ${statement?.[2]} is not fetched; its rules do not apply`);
1009
+ } else if (name === "layer") {
1010
+ for (const n of layerNames(statement?.[2] ?? "") ?? []) state.layers.declare(layer, n);
1011
+ } else if (text && name !== "charset") {
1012
+ state.importsAllowed = false;
1013
+ }
460
1014
  i++;
461
1015
  preludeStart = i;
462
1016
  continue;
@@ -466,41 +1020,77 @@ function parseStylesheet(text, firstOrder = 0) {
466
1020
  continue;
467
1021
  }
468
1022
  const prelude = src.slice(preludeStart, i).trim();
469
- const end = skipBlock(src, i);
470
- if (!prelude.startsWith("@") && prelude) {
1023
+ const end = Math.min(skipBlock(src, i), to);
1024
+ const at = AT_RULE.exec(prelude);
1025
+ state.importsAllowed = false;
1026
+ if (at) {
1027
+ const name = at[1].toLowerCase();
1028
+ const names = name === "layer" ? layerNames(at[2]) : null;
1029
+ if (names && names.length <= 1) {
1030
+ const inner = names.length ? state.layers.declare(layer, names[0]) : state.layers.declareAnonymous(layer);
1031
+ parseRuleList(src, i + 1, end - 1, inner, ctx, rules, state);
1032
+ } else if (conditionHolds(name, at[2], ctx)) {
1033
+ parseRuleList(src, i + 1, end - 1, layer, ctx, rules, state);
1034
+ }
1035
+ } else if (prelude) {
471
1036
  const declarations = parseDeclarations(src.slice(i + 1, end - 1));
472
1037
  for (const raw of splitTopLevel(prelude, ",")) {
473
1038
  const selector = raw.trim();
474
1039
  if (selector) {
475
- rules.push({ selector, declarations, specificity: specificity(selector), order: order++ });
1040
+ rules.push({ selector, declarations, specificity: specificity(selector), order: state.order++, layer });
476
1041
  }
477
1042
  }
478
1043
  }
479
1044
  i = end;
480
1045
  preludeStart = end;
481
1046
  }
482
- return rules;
483
1047
  }
484
- function appliesAsCss(style) {
1048
+ function conditionHolds(name, condition2, ctx) {
1049
+ if (name === "media") return evaluateMediaQuery(condition2, ctx.media ?? DEFAULT_MEDIA_ENVIRONMENT);
1050
+ if (name === "supports") return evaluateSupports(condition2, { selector: ctx.selector });
1051
+ return false;
1052
+ }
1053
+ function appliesAsCss(style, ctx) {
485
1054
  const type = style.getAttribute("type")?.trim().toLowerCase();
486
1055
  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");
1056
+ return evaluateMediaQuery(style.getAttribute("media") ?? "", ctx.media ?? DEFAULT_MEDIA_ENVIRONMENT);
490
1057
  }
491
1058
  var sheetCache = /* @__PURE__ */ new WeakMap();
1059
+ function bindStylesheets(doc, media, onWarn) {
1060
+ sheetCache.set(doc, collectRules(doc, media, onWarn));
1061
+ }
492
1062
  function documentRules(doc) {
493
1063
  const cached = sheetCache.get(doc);
494
1064
  if (cached) return cached;
1065
+ const root = doc.documentElement;
1066
+ const rules = collectRules(doc, root ? mediaEnvironmentFor(root) : DEFAULT_MEDIA_ENVIRONMENT);
1067
+ sheetCache.set(doc, rules);
1068
+ return rules;
1069
+ }
1070
+ function collectRules(doc, media, onWarn) {
1071
+ const ctx = { media, onWarn, selector: (s) => isValidSelector(doc, s) };
1072
+ const layers = new CascadeLayers();
495
1073
  const rules = [];
496
1074
  const styles = doc.getElementsByTagNameNS("*", "style");
497
1075
  for (let i = 0; i < styles.length; i++) {
498
- if (!appliesAsCss(styles[i])) continue;
499
- rules.push(...parseStylesheet(styles[i].textContent ?? "", rules.length));
1076
+ if (!appliesAsCss(styles[i], ctx)) continue;
1077
+ rules.push(...parseStylesheet(styles[i].textContent ?? "", rules.length, ctx, layers));
1078
+ }
1079
+ const ranks = layers.ranks();
1080
+ return rules.map((r) => ({ ...r, rank: ranks.get(r.layer) ?? 0 })).sort((x, y) => x.rank - y.rank || compareCascade(x, y));
1081
+ }
1082
+ function compareCascade(x, y) {
1083
+ return compareSpecificity(x.specificity, y.specificity) || x.order - y.order;
1084
+ }
1085
+ function isValidSelector(doc, selector) {
1086
+ const root = doc.documentElement;
1087
+ if (!root || !selector.trim()) return false;
1088
+ try {
1089
+ root.matches(selector);
1090
+ return true;
1091
+ } catch {
1092
+ return false;
500
1093
  }
501
- rules.sort((x, y) => compareSpecificity(x.specificity, y.specificity) || x.order - y.order);
502
- sheetCache.set(doc, rules);
503
- return rules;
504
1094
  }
505
1095
  function matches(el, selector) {
506
1096
  try {
@@ -517,8 +1107,9 @@ function authorDeclarations(el) {
517
1107
  const matched = rules.filter((r) => matches(el, r.selector));
518
1108
  const inline = parseDeclarations(el.getAttribute("style") ?? "");
519
1109
  const out = /* @__PURE__ */ new Map();
520
- for (const important of [false, true]) {
521
- for (const r of matched) {
1110
+ const importantOrder = [...matched].sort((x, y) => y.rank - x.rank || compareCascade(x, y));
1111
+ for (const [important, ordered] of [[false, matched], [true, importantOrder]]) {
1112
+ for (const r of ordered) {
522
1113
  for (const d of r.declarations) if (d.important === important) out.set(d.prop, d.value);
523
1114
  }
524
1115
  for (const d of inline) if (d.important === important) out.set(d.prop, d.value);
@@ -531,18 +1122,25 @@ function ownProp(el, prop) {
531
1122
  }
532
1123
  function deriveStyle(parent, el) {
533
1124
  let next = null;
534
- for (const prop of INHERITABLE) {
535
- const own = ownProp(el, prop);
536
- if (own == null || own === "inherit") continue;
1125
+ for (const prop of INHERITED_PROPERTIES) {
1126
+ const own2 = ownProp(el, prop);
1127
+ if (own2 == null || own2 === "inherit" || prop === "color" && own2.trim().toLowerCase() === "currentcolor") continue;
537
1128
  if (!next) next = { ...parent };
538
- next[prop] = own;
1129
+ next[prop] = own2;
539
1130
  }
540
1131
  return next ?? parent;
541
1132
  }
542
1133
  function resolveCurrentColor(raw, style) {
543
1134
  if (raw == null) return null;
544
- if (raw.trim().toLowerCase() === "currentcolor") return style["color"] ?? "#000000";
545
- return raw;
1135
+ if (raw.trim().toLowerCase() !== "currentcolor") return raw;
1136
+ const color = readProperty("color", style["color"]);
1137
+ return color == null || color.toLowerCase() === "currentcolor" ? "#000000" : color;
1138
+ }
1139
+ function ownValue(el, prop, warn) {
1140
+ return readProperty(prop, ownProp(el, prop), warn);
1141
+ }
1142
+ function styleValue(style, prop, warn) {
1143
+ return readProperty(prop, style[prop], warn);
546
1144
  }
547
1145
 
548
1146
  // src/elements.ts
@@ -578,7 +1176,11 @@ function patternXml(id, paint, onWarn) {
578
1176
  `width="${trimNumber(geometry.size)}"`,
579
1177
  `height="${trimNumber(geometry.size)}"`
580
1178
  ];
581
- if (origin.x !== 0 || origin.y !== 0) {
1179
+ const [a, b, c, d] = paint.transform ?? [1, 0, 0, 1];
1180
+ const placement = formatMatrix([a, b, c, d, origin.x, origin.y]);
1181
+ if (paint.transform && !isIdentity([a, b, c, d, 0, 0]) && placement) {
1182
+ attrs.push(`patternTransform="${placement}"`);
1183
+ } else if (origin.x !== 0 || origin.y !== 0) {
582
1184
  attrs.push(`x="${trimNumber(origin.x)}"`, `y="${trimNumber(origin.y)}"`);
583
1185
  }
584
1186
  attrs.push(`data-weasel-tile="${escapeAttr(JSON.stringify(spec))}"`);
@@ -638,8 +1240,11 @@ function readPattern(el, onWarn) {
638
1240
  }
639
1241
  const x = parseFloat(el.getAttribute("x") ?? "0");
640
1242
  const y = parseFloat(el.getAttribute("y") ?? "0");
1243
+ const m = parseTransform(el.getAttribute("patternTransform"), onWarn);
1244
+ const origin = applyMatrix(m, x, y);
641
1245
  const paint = { fill: "pattern", pattern: spec };
642
- if (x !== 0 || y !== 0) paint.origin = { x, y };
1246
+ if (origin.x !== 0 || origin.y !== 0) paint.origin = origin;
1247
+ if (!isIdentity([m[0], m[1], m[2], m[3], 0, 0])) paint.transform = [m[0], m[1], m[2], m[3]];
643
1248
  return paint;
644
1249
  }
645
1250
  function escapeAttr(s) {
@@ -647,8 +1252,8 @@ function escapeAttr(s) {
647
1252
  }
648
1253
  function serializePathD(path) {
649
1254
  if (path.kind === "rect") {
650
- const { x, y, width, height } = path;
651
- return `M${trimNumber(x)} ${trimNumber(y)}h${trimNumber(width)}v${trimNumber(height)}h${trimNumber(-width)}Z`;
1255
+ const { x, y, width: width2, height: height2 } = path;
1256
+ return `M${trimNumber(x)} ${trimNumber(y)}h${trimNumber(width2)}v${trimNumber(height2)}h${trimNumber(-width2)}Z`;
652
1257
  }
653
1258
  const cmds = path.commands;
654
1259
  const xs = path.coords;
@@ -682,13 +1287,18 @@ function serializePathD(path) {
682
1287
  // src/gradients.ts
683
1288
  var WEASEL_NS = "urn:weasel-js:svg";
684
1289
  var WEASEL_NS_PREFIX = "wzl";
685
- function hasNativeSvgForm(paint) {
686
- const kind = paint.fill ?? "solid";
1290
+ function nativeSvgKind(kind) {
687
1291
  return kind === "solid" || kind === "linear-gradient" || kind === "radial-gradient" || kind === "pattern";
688
1292
  }
1293
+ function hasNativeSvgForm(paint) {
1294
+ return nativeSvgKind(paint.fill ?? "solid");
1295
+ }
689
1296
  var INTERPOLATE_SPACES = /* @__PURE__ */ new Set(["rgb", "oklab", "oklch"]);
1297
+ function nativeSvgSpace(space) {
1298
+ return !space || space === "rgb";
1299
+ }
690
1300
  function interpolateAttr(space) {
691
- if (!space || space === "rgb") return "";
1301
+ if (nativeSvgSpace(space)) return "";
692
1302
  return ` ${WEASEL_NS_PREFIX}:interpolate="${space}"`;
693
1303
  }
694
1304
  function readInterpolate(el, elements) {
@@ -698,7 +1308,7 @@ function readInterpolate(el, elements) {
698
1308
  }
699
1309
  function usesInterpolateAttr(paint) {
700
1310
  const space = paint.interpolate;
701
- return space != null && space !== "rgb" && INTERPOLATE_SPACES.has(space);
1311
+ return !nativeSvgSpace(space) && INTERPOLATE_SPACES.has(space);
702
1312
  }
703
1313
  function localName(tag) {
704
1314
  const i = tag.indexOf(":");
@@ -751,11 +1361,11 @@ function inheritedAttr(el, elements, name, seen = /* @__PURE__ */ new Set()) {
751
1361
  return inheritedAttr(ref, elements, name, seen);
752
1362
  }
753
1363
  function inheritedStops(el, elements, onWarn, seen = /* @__PURE__ */ new Set()) {
754
- const own = readStops(el, onWarn);
755
- if (own.length > 0) return own;
1364
+ const own2 = readStops(el, onWarn);
1365
+ if (own2.length > 0) return own2;
756
1366
  seen.add(el);
757
1367
  const ref = hrefTarget(el, elements);
758
- if (!ref || seen.has(ref)) return own;
1368
+ if (!ref || seen.has(ref)) return own2;
759
1369
  return inheritedStops(ref, elements, onWarn, seen);
760
1370
  }
761
1371
  function parseRatio(raw) {
@@ -771,11 +1381,9 @@ function readStops(el, onWarn) {
771
1381
  const offsetRaw = parseRatio(c.getAttribute("offset") ?? "0");
772
1382
  const offset = Number.isFinite(offsetRaw) ? offsetRaw : 0;
773
1383
  const colorAttr = ownProp(c, "stop-color") ?? "#000000";
774
- const parsed = parsePaintAttr(colorAttr);
775
- if (parsed && parsed.kind === "solid") {
776
- const opacityAttr = ownProp(c, "stop-opacity");
777
- const own = opacityAttr != null ? parseRatio(opacityAttr) : NaN;
778
- const alpha = Number.isFinite(own) ? own : parsed.alpha;
1384
+ const parsed = readProperty("stop-color", colorAttr, onWarn);
1385
+ if (parsed) {
1386
+ const alpha = ownValue(c, "stop-opacity") ?? parsed.alpha;
779
1387
  stops.push({ offset, color: alpha < 1 ? applyAlpha(parsed.color, alpha) : parsed.color });
780
1388
  } else {
781
1389
  onWarn?.(`gradient stop has unrecognized stop-color: ${colorAttr}`);
@@ -877,12 +1485,13 @@ var PaintServerRegistry = class {
877
1485
  byPaint = /* @__PURE__ */ new Map();
878
1486
  order = [];
879
1487
  counter = 0;
880
- // Marker keys referenced by any stroke, in first-use order. The `<defs>`
881
- // id is the marker key itself, not a minted counter id: `parseSvg` reads a
882
- // `marker-end="url(#id)"` fragment back as the marker key directly when the
883
- // registry knows it, so a synthetic id would come back as a stranger.
884
- markerKeys = [];
885
- markerKeySet = /* @__PURE__ */ new Set();
1488
+ // Marker defs referenced by any stroke, by `<defs>` id in first-use order.
1489
+ // An unsized reference's id is the marker key itself, not a minted counter
1490
+ // id: `parseSvg` reads a `marker-end="url(#id)"` fragment back as the marker
1491
+ // key directly when the registry knows it, so a synthetic id would come
1492
+ // back as a stranger. A sized one gets a def of its own, stamped with its
1493
+ // key and size so the parser can read the reference back whole.
1494
+ markers = /* @__PURE__ */ new Map();
886
1495
  // Clip paths, in first-use order. Identity, not value: two groups clipped by
887
1496
  // equal outlines get a def each, which costs bytes and misleads nobody.
888
1497
  clips = [];
@@ -907,22 +1516,26 @@ var PaintServerRegistry = class {
907
1516
  if (hasNativeSvgForm(paint)) return `url(#${id})`;
908
1517
  return `url(#${id}) ${paintFallback(paint)}`;
909
1518
  }
1519
+ /** The paint kind of each registered paint, once, in first-use order. */
1520
+ paintKinds() {
1521
+ return [...new Set(this.order.map((paint) => paint.fill ?? "solid"))];
1522
+ }
910
1523
  /** Whether any registered paint serializes outside SVG's own vocabulary, so
911
1524
  * the root has to declare {@link WEASEL_NS}. */
912
1525
  usesPrivateNamespace() {
913
- return this.order.some((paint) => !hasNativeSvgForm(paint) || usesInterpolateAttr(paint));
1526
+ return this.order.some((paint) => !hasNativeSvgForm(paint) || usesInterpolateAttr(paint)) || [...this.markers.values()].some((m) => m.size !== void 0 || writesInset(getMarker(m.key)));
914
1527
  }
915
- /** The `<defs>` id for a marker key — the key itself, minting nothing.
916
- * `undefined` when nothing is registered under `key`, so a caller emits no
917
- * attribute at all rather than a `url(#…)` pointing at a def that will
918
- * never be written. */
919
- markerId(key) {
1528
+ /** The `<defs>` id for a marker reference — the key itself when it has no
1529
+ * size, else the key suffixed with the size. `undefined` when nothing is
1530
+ * registered under the key, so a caller emits no attribute at all rather
1531
+ * than a `url(#…)` pointing at a def that will never be written. */
1532
+ markerId(ref) {
1533
+ const key = typeof ref === "string" ? ref : ref.key;
1534
+ const size = typeof ref === "string" ? void 0 : ref.size;
920
1535
  if (getMarker(key) === void 0) return void 0;
921
- if (!this.markerKeySet.has(key)) {
922
- this.markerKeySet.add(key);
923
- this.markerKeys.push(key);
924
- }
925
- return key;
1536
+ const id = size === void 0 ? key : `${key}-s${typeof size === "number" ? trimNumber(size) : `${trimNumber(size.px)}px`}`;
1537
+ if (!this.markers.has(id)) this.markers.set(id, size === void 0 ? { key } : { key, size });
1538
+ return id;
926
1539
  }
927
1540
  /** Mint (or recall) the `<defs>` id for a clip outline. */
928
1541
  clipId(path) {
@@ -936,17 +1549,22 @@ var PaintServerRegistry = class {
936
1549
  /** Emit `<defs>...</defs>` XML for every registered paint server, marker
937
1550
  * and clip path. */
938
1551
  toDefsXml(onWarn) {
939
- if (this.order.length === 0 && this.markerKeys.length === 0 && this.clips.length === 0) {
1552
+ if (this.order.length === 0 && this.markers.size === 0 && this.clips.length === 0) {
940
1553
  return "";
941
1554
  }
942
1555
  const parts = ["<defs>"];
943
1556
  for (const paint of this.order) {
944
1557
  parts.push(paintServerXml(this.byPaint.get(paint), paint, onWarn));
945
1558
  }
946
- for (const key of this.markerKeys) {
1559
+ for (const [id, { key, size }] of this.markers) {
947
1560
  const entry = getMarker(key);
948
1561
  if (!entry) continue;
949
- parts.push(entry.toSvg ? entry.toSvg(key, entry) : defaultMarkerXml(key, entry, onWarn));
1562
+ if (entry.toSvg) {
1563
+ if (size !== void 0) onWarn?.(`marker "${key}" writes its own <marker> def, which cannot carry a reference's size; written at its own size`);
1564
+ parts.push(entry.toSvg(id, entry));
1565
+ } else {
1566
+ parts.push(defaultMarkerXml(id, entry, onWarn, key, size));
1567
+ }
950
1568
  }
951
1569
  for (const path of this.clips) {
952
1570
  parts.push(
@@ -957,13 +1575,19 @@ var PaintServerRegistry = class {
957
1575
  return parts.join("");
958
1576
  }
959
1577
  };
960
- function defaultMarkerXml(id, entry, onWarn) {
961
- const path = entry.path({ size: 1, stroke: { paint: { fill: "solid", color: "#000" } } });
1578
+ function writesInset(entry) {
1579
+ return entry !== void 0 && !entry.toSvg && (entry.inset ?? 0) !== 0;
1580
+ }
1581
+ function defaultMarkerXml(id, entry, onWarn, key, size) {
1582
+ const unit = size === void 0 ? 1 : resolveMarkerSize({ key: key ?? id, size }, 1);
1583
+ const path = entry.path({ size: unit, stroke: { paint: { fill: "solid", color: "#000" } } });
962
1584
  const d = serializePathD(path);
963
1585
  const fill = markerPaintAttrs("fill", entry.fill, id, onWarn);
964
- const outline = entry.outline ? ` ${markerPaintAttrs("stroke", entry.outline.paint, id, onWarn)} stroke-width="${trimNumber(entry.outline.width)}" stroke-linecap="round" stroke-linejoin="round"` : "";
1586
+ const outline = entry.outline ? ` ${markerPaintAttrs("stroke", entry.outline.paint, id, onWarn)} stroke-width="${trimNumber(entry.outline.width * unit)}" stroke-linecap="round" stroke-linejoin="round"` : "";
965
1587
  const orient = typeof entry.orient === "number" ? trimNumber(entry.orient * 180 / Math.PI) : "auto-start-reverse";
966
- return `<marker id="${id}" markerUnits="strokeWidth" markerWidth="8" markerHeight="8" refX="0" refY="0" orient="${orient}" overflow="visible"><path d="${d}" ${fill}${outline}/></marker>`;
1588
+ const units = size === void 0 ? 'markerUnits="strokeWidth" markerWidth="8" markerHeight="8"' : `markerUnits="userSpaceOnUse" markerWidth="${trimNumber(8 * unit)}" markerHeight="${trimNumber(8 * unit)}" ${WEASEL_NS_PREFIX}:key="${key}" ${WEASEL_NS_PREFIX}:size="${typeof size === "number" ? trimNumber(size) : `${trimNumber(size.px)}px`}"`;
1589
+ const inset = writesInset(entry) ? ` ${WEASEL_NS_PREFIX}:inset="${trimNumber(entry.inset)}"` : "";
1590
+ return `<marker id="${id}" ${units}${inset} refX="0" refY="0" orient="${orient}" overflow="visible"><path d="${d}" ${fill}${outline}/></marker>`;
967
1591
  }
968
1592
  function markerPaintAttrs(attr, paint, id, onWarn) {
969
1593
  if (paint === "none") return `${attr}="none"`;
@@ -978,9 +1602,10 @@ function paintServerXml(id, paint, onWarn) {
978
1602
  if (paint.fill === "pattern") return patternXml(id, paint, onWarn);
979
1603
  const builtin = gradientXml(id, paint);
980
1604
  if (builtin) return builtin;
981
- const custom = getPaintKind(paint.fill)?.toSvg?.(id, paint);
1605
+ const kind = getPaintKind(paint.fill);
1606
+ const custom = kind?.toSvg?.(id, paint);
982
1607
  if (custom) return custom;
983
- onWarn?.(`${paint.fill} fill has no vector form \u2014 omitted from <defs>`);
1608
+ onWarn?.(kind ? `${paint.fill} fill has no vector form \u2014 omitted from <defs>` : `${paint.fill} fill is not registered \u2014 omitted from <defs>; a kind loaded on demand needs \`await warmSvg(nodes)\` before a synchronous export`);
984
1609
  return "";
985
1610
  }
986
1611
  function gradientUnitsAttr(units) {
@@ -1059,6 +1684,7 @@ function parseSvg(svg, opts = {}) {
1059
1684
  if (!root || root.tagName.toLowerCase() !== "svg") {
1060
1685
  return { nodes: [], warnings: ["root element is not <svg>"] };
1061
1686
  }
1687
+ bindStylesheets(doc, mediaEnvironmentFor(root, opts.media), onWarn);
1062
1688
  const namespaces = opts.namespaces ?? {};
1063
1689
  const uriToPrefix = /* @__PURE__ */ new Map();
1064
1690
  for (const [prefix, uri] of Object.entries(namespaces)) {
@@ -1076,10 +1702,10 @@ function parseSvg(svg, opts = {}) {
1076
1702
  if (documentMeta) result.documentMeta = documentMeta;
1077
1703
  const viewBox = parseViewBoxAttr(root.getAttribute("viewBox"));
1078
1704
  if (viewBox) result.viewBox = viewBox;
1079
- const width = parseRootLength(root.getAttribute("width"), "width", onWarn);
1080
- if (width != null) result.width = width;
1081
- const height = parseRootLength(root.getAttribute("height"), "height", onWarn);
1082
- if (height != null) result.height = height;
1705
+ const width2 = parseRootLength(root.getAttribute("width"), "width", onWarn);
1706
+ if (width2 != null) result.width = width2;
1707
+ const height2 = parseRootLength(root.getAttribute("height"), "height", onWarn);
1708
+ if (height2 != null) result.height = height2;
1083
1709
  for (let i = 0; i < root.children.length; i++) {
1084
1710
  const c = root.children[i];
1085
1711
  if (c.tagName.toLowerCase() === "title") {
@@ -1244,7 +1870,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
1244
1870
  const childCtm = multiply(ctm, local);
1245
1871
  const childStyle = deriveStyle(style, el);
1246
1872
  const children = parseChildren(el, childCtm, childStyle, gradients, clips, onWarn, uriToPrefix);
1247
- const opacity2 = readOpacityAttr(el, "opacity");
1873
+ const opacity2 = ownValue(el, "opacity");
1248
1874
  const group = { kind: "group", children };
1249
1875
  const clip = clipFor(el, childCtm, clips, onWarn);
1250
1876
  if (clip) group.clip = clip;
@@ -1306,11 +1932,10 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
1306
1932
  }
1307
1933
  }
1308
1934
  const leafStyle = deriveStyle(style, el);
1309
- const fill = readPaint(leafStyle, "fill", "#000000", gradients, onWarn);
1935
+ const fill = readPaint2(leafStyle, "fill", "#000000", gradients, onWarn);
1310
1936
  const stroke = readStroke(leafStyle, gradients, onWarn, isNonScalingStroke(el));
1311
- const opacity = readOpacityAttr(el, "opacity");
1312
- const fillRuleRaw = leafStyle["fill-rule"] ?? null;
1313
- if (fillRuleRaw === "evenodd" && path.kind === "polygon") {
1937
+ const opacity = ownValue(el, "opacity");
1938
+ if (styleValue(leafStyle, "fill-rule") === "evenodd" && path.kind === "polygon") {
1314
1939
  path = { ...path, fillRule: "evenodd" };
1315
1940
  }
1316
1941
  const node = { kind: "path", path, fill };
@@ -1376,10 +2001,10 @@ function polygonToRectIfAxisAligned(p) {
1376
2001
  if (y0 !== y1 || x1 !== x2 || y2 !== y3 || x3 !== x0) return null;
1377
2002
  const x = Math.min(x0, x1);
1378
2003
  const y = Math.min(y0, y2);
1379
- const width = Math.abs(x1 - x0);
1380
- const height = Math.abs(y2 - y1);
1381
- if (width === 0 || height === 0) return null;
1382
- return { kind: "rect", x, y, width, height };
2004
+ const width2 = Math.abs(x1 - x0);
2005
+ const height2 = Math.abs(y2 - y1);
2006
+ if (width2 === 0 || height2 === 0) return null;
2007
+ return { kind: "rect", x, y, width: width2, height: height2 };
1383
2008
  }
1384
2009
  function pathAabb(path) {
1385
2010
  if (path.kind === "rect") {
@@ -1387,22 +2012,21 @@ function pathAabb(path) {
1387
2012
  }
1388
2013
  return boundsOfPath(path);
1389
2014
  }
1390
- function readPaint(style, attr, defaultColor, gradients, onWarn) {
2015
+ function readPaint2(style, attr, defaultColor, gradients, onWarn) {
1391
2016
  const raw = resolveCurrentColor(style[attr] ?? null, style);
1392
- const opacityRaw = style[`${attr}-opacity`] ?? null;
1393
- const opacity = opacityRaw != null ? clamp01(parseFloat(opacityRaw)) : void 0;
2017
+ const opacity = styleValue(style, `${attr}-opacity`, onWarn);
1394
2018
  if (raw == null) {
1395
2019
  if (attr === "stroke") return { kind: "none" };
1396
2020
  const out2 = { kind: "solid", color: defaultColor };
1397
2021
  if (opacity != null) out2.opacity = opacity;
1398
2022
  return out2;
1399
2023
  }
1400
- const parsed = parsePaintAttr(raw);
2024
+ const parsed = readProperty(attr, raw, onWarn);
1401
2025
  if (!parsed) {
1402
2026
  onWarn(`unrecognized ${attr} value: ${raw}`);
1403
2027
  return { kind: "solid", color: defaultColor };
1404
2028
  }
1405
- if (parsed.kind === "none") return { kind: "none" };
2029
+ if (parsed.kind === "none" || parsed.kind === "context") return { kind: "none" };
1406
2030
  if (parsed.kind === "ref") {
1407
2031
  const paint = gradients.get(parsed.id);
1408
2032
  if (!paint) {
@@ -1415,7 +2039,7 @@ function readPaint(style, attr, defaultColor, gradients, onWarn) {
1415
2039
  onWarn(`${attr} references unknown gradient #${parsed.id}`);
1416
2040
  return { kind: "solid", color: defaultColor };
1417
2041
  }
1418
- return { kind: "gradient", paint };
2042
+ return { kind: "gradient", paint: opacity != null ? { ...paint, opacity } : paint };
1419
2043
  }
1420
2044
  const out = { kind: "solid", color: parsed.color };
1421
2045
  const a = opacity ?? (parsed.alpha < 1 ? parsed.alpha : void 0);
@@ -1426,59 +2050,30 @@ function readStroke(style, gradients, onWarn, nonScaling = false) {
1426
2050
  const inheritedStroke = style["stroke"] ?? null;
1427
2051
  const inheritedWidth = style["stroke-width"] ?? null;
1428
2052
  if (inheritedStroke == null && inheritedWidth == null) return void 0;
1429
- const paint = readPaint(style, "stroke", "#000000", gradients, onWarn);
2053
+ const paint = readPaint2(style, "stroke", "#000000", gradients, onWarn);
1430
2054
  if (paint.kind === "none") return void 0;
1431
- const n = inheritedWidth != null ? parseFloat(inheritedWidth) : 1;
2055
+ const n = styleValue(style, "stroke-width", onWarn) ?? 1;
1432
2056
  const stroke = { paint, width: nonScaling ? { px: n } : n };
1433
- const opacityRaw = style["stroke-opacity"] ?? null;
1434
- if (opacityRaw != null) {
1435
- const a = clamp01(parseFloat(opacityRaw));
1436
- if (Number.isFinite(a)) stroke.opacity = a;
1437
- }
1438
- const cap = style["stroke-linecap"] ?? null;
1439
- if (cap === "butt" || cap === "round" || cap === "square") {
1440
- stroke.cap = cap;
1441
- } else if (cap != null) {
1442
- onWarn(`unsupported stroke-linecap: ${cap}`);
1443
- }
1444
- const join = style["stroke-linejoin"] ?? null;
1445
- if (join === "miter" || join === "round" || join === "bevel") {
1446
- stroke.join = join;
1447
- } else if (join === "arcs" || join === "miter-clip") {
1448
- onWarn(`stroke-linejoin "${join}" not supported; falling back to miter`);
1449
- stroke.join = "miter";
1450
- } else if (join != null) {
1451
- onWarn(`unsupported stroke-linejoin: ${join}`);
1452
- }
1453
- const dashAttr = style["stroke-dasharray"] ?? null;
1454
- if (dashAttr != null && dashAttr.trim() !== "" && dashAttr.trim() !== "none") {
1455
- const parsed = parseDashArray(dashAttr);
1456
- if (parsed) stroke.dash = parsed;
1457
- else onWarn(`unrecognized stroke-dasharray: ${dashAttr}`);
1458
- }
1459
- const miterAttr = style["stroke-miterlimit"] ?? null;
1460
- if (miterAttr != null) {
1461
- const m = parseFloat(miterAttr);
1462
- if (Number.isFinite(m) && m >= 1) stroke.miterLimit = m;
1463
- else onWarn(`unrecognized stroke-miterlimit: ${miterAttr}`);
1464
- }
2057
+ const opacity = styleValue(style, "stroke-opacity", onWarn);
2058
+ if (opacity != null) stroke.opacity = opacity;
2059
+ const cap = styleValue(style, "stroke-linecap", onWarn);
2060
+ if (cap) stroke.cap = cap;
2061
+ const join = styleValue(style, "stroke-linejoin", onWarn);
2062
+ if (join) stroke.join = join;
2063
+ const dash = styleValue(style, "stroke-dasharray", onWarn);
2064
+ if (dash) stroke.dash = dash;
2065
+ const miterLimit = styleValue(style, "stroke-miterlimit", onWarn);
2066
+ if (miterLimit != null) stroke.miterLimit = miterLimit;
1465
2067
  for (const [attr, field] of [
1466
2068
  ["marker-start", "markerStart"],
1467
2069
  ["marker-mid", "markerMid"],
1468
2070
  ["marker-end", "markerEnd"]
1469
2071
  ]) {
1470
- const id = parseMarkerRef(style[attr] ?? null);
1471
- if (id !== void 0) stroke[field] = id;
2072
+ const id = styleValue(style, attr, onWarn);
2073
+ if (id) stroke[field] = id;
1472
2074
  }
1473
2075
  return stroke;
1474
2076
  }
1475
- function parseMarkerRef(raw) {
1476
- if (!raw) return void 0;
1477
- const v = raw.trim();
1478
- if (v === "" || v === "none") return void 0;
1479
- const m = /^url\(\s*#([^)\s]+)\s*\)/.exec(v);
1480
- return m ? m[1] : void 0;
1481
- }
1482
2077
  var STROKE_KEYS = [
1483
2078
  "stroke",
1484
2079
  "stroke-width",
@@ -1492,7 +2087,7 @@ var STROKE_KEYS = [
1492
2087
  "marker-end"
1493
2088
  ];
1494
2089
  function isNonScalingStroke(el) {
1495
- return ownProp(el, "vector-effect") === "non-scaling-stroke";
2090
+ return ownValue(el, "vector-effect") === "non-scaling-stroke";
1496
2091
  }
1497
2092
  function ownStrokeStyle(el) {
1498
2093
  const out = {};
@@ -1522,66 +2117,6 @@ function coreStroke(stroke) {
1522
2117
  if (stroke.markerEnd) out.markerEnd = stroke.markerEnd;
1523
2118
  return out;
1524
2119
  }
1525
- function parseDashArray(s) {
1526
- const tokens = s.trim().split(/[\s,]+/).filter(Boolean);
1527
- if (tokens.length === 0) return null;
1528
- const nums = [];
1529
- for (const t of tokens) {
1530
- const n = parseFloat(t);
1531
- if (!Number.isFinite(n) || n < 0) return null;
1532
- nums.push(n);
1533
- }
1534
- return nums.length % 2 === 1 ? [...nums, ...nums] : nums;
1535
- }
1536
- function readOpacityAttr(el, name) {
1537
- const raw = ownProp(el, name);
1538
- if (raw == null) return void 0;
1539
- const n = parseFloat(raw);
1540
- return Number.isFinite(n) ? clamp01(n) : void 0;
1541
- }
1542
- function clamp01(n) {
1543
- return n < 0 ? 0 : n > 1 ? 1 : n;
1544
- }
1545
- function parseLetterSpacing(raw, onWarn) {
1546
- const n = parseFloat(raw);
1547
- if (!Number.isFinite(n)) return void 0;
1548
- const unit = /^-?[\d.]+([a-z%]+)$/i.exec(raw.trim())?.[1]?.toLowerCase();
1549
- if (unit && unit !== "px") {
1550
- onWarn(`letter-spacing "${raw}" uses unit "${unit}", which is not converted; treated as ${n} world units`);
1551
- }
1552
- return n;
1553
- }
1554
- function parseTextDecoration(raw) {
1555
- if (raw == null) return {};
1556
- const tokens = raw.trim().toLowerCase().split(/\s+/);
1557
- const out = {};
1558
- if (tokens.includes("underline")) out.underline = true;
1559
- if (tokens.includes("line-through")) out.strikethrough = true;
1560
- if (tokens.includes("overline")) out.overline = true;
1561
- return out;
1562
- }
1563
- function parseBaselineShift(raw, onWarn) {
1564
- const keyword = raw.trim().toLowerCase();
1565
- if (keyword === "super" || keyword === "sub") return { script: keyword };
1566
- const n = parseFloat(raw);
1567
- if (!Number.isFinite(n)) return {};
1568
- const unit = /^-?[\d.]+([a-z%]+)$/i.exec(raw.trim())?.[1]?.toLowerCase();
1569
- if (unit === "%") return { baselineShift: n / 100 };
1570
- if (unit && unit !== "em") {
1571
- onWarn(`baseline-shift "${raw}" uses unit "${unit}", which is not converted; treated as ${n} em`);
1572
- }
1573
- return n === 0 ? {} : { baselineShift: n };
1574
- }
1575
- function parseRunFontSize(raw, onWarn) {
1576
- const n = parseFloat(raw);
1577
- if (!Number.isFinite(n)) return {};
1578
- const unit = /^-?[\d.]+([a-z%]+)$/i.exec(raw.trim())?.[1]?.toLowerCase();
1579
- if (unit === "%") return { fontScale: n / 100 };
1580
- if (unit && unit !== "px") {
1581
- onWarn(`font-size "${raw}" uses unit "${unit}", which is not converted; treated as ${n} world units`);
1582
- }
1583
- return { fontSize: n };
1584
- }
1585
2120
  var XLINK_NS2 = "http://www.w3.org/1999/xlink";
1586
2121
  function parseImageElement(el, ctm, onWarn) {
1587
2122
  const href = imageHref(el);
@@ -1619,7 +2154,7 @@ function imageNodeAt(boxEl, ownerEl, href, ctm, onWarn) {
1619
2154
  width: Math.abs(x1 - x0),
1620
2155
  height: Math.abs(y1 - y0)
1621
2156
  };
1622
- const opacity = readOpacityAttr(ownerEl, "opacity");
2157
+ const opacity = ownValue(ownerEl, "opacity");
1623
2158
  if (opacity != null) node.opacity = opacity;
1624
2159
  const localTransform = parseTransform(ownerEl.getAttribute("transform"), onWarn);
1625
2160
  if (!isIdentity(localTransform)) {
@@ -1724,8 +2259,7 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
1724
2259
  const dominantBaseline = el.getAttribute("dominant-baseline");
1725
2260
  const explicitTopAnchor = dominantBaseline === "text-before-edge" || dominantBaseline === "hanging";
1726
2261
  const topY = explicitTopAnchor ? ay : ay - fontSize;
1727
- const ownShift = ownProp(el, "baseline-shift");
1728
- const inheritedShift = ownShift != null ? parseBaselineShift(ownShift, onWarn) : {};
2262
+ const inheritedShift = ownValue(el, "baseline-shift", onWarn) ?? {};
1729
2263
  const runs = [];
1730
2264
  for (let i = 0; i < el.childNodes.length; i++) {
1731
2265
  const child = el.childNodes[i];
@@ -1749,16 +2283,16 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
1749
2283
  const plain = runs.map((r) => r.text).join("");
1750
2284
  const dataW = num2(el.getAttribute("data-weasel-width"), NaN);
1751
2285
  const dataH = num2(el.getAttribute("data-weasel-height"), NaN);
1752
- const width = Number.isFinite(dataW) ? dataW : laidOutWidth(runs, textStyle, plain);
2286
+ const width2 = Number.isFinite(dataW) ? dataW : laidOutWidth(runs, textStyle, plain);
1753
2287
  const lines = (plain.match(/\n/g)?.length ?? 0) + 1;
1754
- const height = Number.isFinite(dataH) ? dataH : fontSize * lineHeight * lines;
1755
- const opacity = readOpacityAttr(el, "opacity");
2288
+ const height2 = Number.isFinite(dataH) ? dataH : fontSize * lineHeight * lines;
2289
+ const opacity = ownValue(el, "opacity");
1756
2290
  const node = {
1757
2291
  kind: "text",
1758
- x: ax - anchorOffset(textStyle, width),
2292
+ x: ax - anchorOffset(textStyle, width2),
1759
2293
  y: topY,
1760
- width,
1761
- height,
2294
+ width: width2,
2295
+ height: height2,
1762
2296
  text: plain
1763
2297
  };
1764
2298
  const hasStyling = runs.some(
@@ -1766,6 +2300,7 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
1766
2300
  );
1767
2301
  if (hasStyling) node.runs = runs;
1768
2302
  if (el.getAttribute("data-weasel-wrap") === "true") textStyle.wrap = true;
2303
+ if (el.getAttribute("data-weasel-align") === "justify") textStyle.align = "justify";
1769
2304
  const va = el.getAttribute("data-weasel-vertical-align");
1770
2305
  if (va === "center" || va === "bottom") node.verticalAlign = va;
1771
2306
  if (Object.keys(textStyle).length > 0) node.style = textStyle;
@@ -1790,47 +2325,36 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
1790
2325
  }
1791
2326
  return node;
1792
2327
  }
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
- }
1798
2328
  function readTspanRun(el, gradients, style, onWarn) {
1799
2329
  const text = el.textContent ?? "";
1800
2330
  const run = { text };
1801
- const fw = ownProp(el, "font-weight");
1802
- if (fw === "bold" || fw === "700" || fw === "bolder") run.bold = true;
1803
- const fs = ownProp(el, "font-style");
1804
- if (fs === "italic" || fs === "oblique") run.italic = true;
1805
- const ff = ownProp(el, "font-family");
2331
+ const fw = ownValue(el, "font-weight", onWarn);
2332
+ if (fw === "bold" || fw === "bolder" || fw === 700) run.bold = true;
2333
+ else if (fw === "normal") run.fontWeight = 400;
2334
+ else if (typeof fw === "number") run.fontWeight = fw;
2335
+ if (ownValue(el, "font-style", onWarn) === "italic") run.italic = true;
2336
+ const ff = ownValue(el, "font-family", onWarn);
1806
2337
  if (ff) run.fontFamily = ff;
1807
- const sz = ownProp(el, "font-size");
1808
- if (sz != null) {
1809
- const size = parseRunFontSize(sz, onWarn);
1810
- if (size.fontSize != null) run.fontSize = size.fontSize;
1811
- if (size.fontScale != null) run.fontScale = size.fontScale;
1812
- }
1813
- const ls = ownProp(el, "letter-spacing");
1814
- if (ls != null) {
1815
- const n = parseLetterSpacing(ls, onWarn);
1816
- if (n != null) run.letterSpacing = n;
1817
- }
1818
- const bs = ownProp(el, "baseline-shift");
1819
- if (bs != null) {
1820
- const shift = parseBaselineShift(bs, onWarn);
1821
- if (shift.script != null) run.script = shift.script;
1822
- if (shift.baselineShift != null) run.baselineShift = shift.baselineShift;
1823
- }
1824
- const decoration = parseTextDecoration(ownProp(el, "text-decoration"));
1825
- if (decoration.underline) run.underline = true;
1826
- if (decoration.strikethrough) run.strikethrough = true;
1827
- if (decoration.overline) run.overline = true;
1828
- const transform = parseTextTransform(ownProp(el, "text-transform"));
2338
+ const size = ownValue(el, "font-size", onWarn);
2339
+ if (size?.fontSize != null) run.fontSize = size.fontSize;
2340
+ if (size?.fontScale != null) run.fontScale = size.fontScale;
2341
+ const ls = ownValue(el, "letter-spacing", onWarn);
2342
+ if (ls != null) run.letterSpacing = ls;
2343
+ const shift = ownValue(el, "baseline-shift", onWarn);
2344
+ if (shift?.script != null) run.script = shift.script;
2345
+ if (shift?.baselineShift != null) run.baselineShift = shift.baselineShift;
2346
+ const decoration = ownValue(el, "text-decoration", onWarn);
2347
+ if (decoration?.underline) run.underline = true;
2348
+ if (decoration?.strikethrough) run.strikethrough = true;
2349
+ if (decoration?.overline) run.overline = true;
2350
+ const transform = ownValue(el, "text-transform", onWarn);
1829
2351
  if (transform) run.textTransform = transform;
2352
+ const variant = ownValue(el, "font-variant", onWarn);
2353
+ if (variant) run.fontVariantCaps = variant;
1830
2354
  const tspanStyle = deriveStyle(style, el);
1831
2355
  const fillAttr = resolveCurrentColor(ownProp(el, "fill"), tspanStyle);
1832
2356
  if (fillAttr) {
1833
- const parsed = parsePaintAttr(fillAttr);
2357
+ const parsed = readProperty("fill", fillAttr);
1834
2358
  if (parsed?.kind === "solid") {
1835
2359
  run.fill = { fill: "solid", color: parsed.color };
1836
2360
  } else if (parsed?.kind === "ref") {
@@ -1845,44 +2369,41 @@ function readTspanRun(el, gradients, style, onWarn) {
1845
2369
  }
1846
2370
  function readTextStyle(style, onWarn) {
1847
2371
  const out = {};
1848
- const sz = style["font-size"];
1849
- if (sz != null) {
1850
- const n = parseFloat(sz);
1851
- if (Number.isFinite(n)) out.fontSize = n;
2372
+ const size = styleValue(style, "font-size", onWarn);
2373
+ if (size?.fontSize != null) out.fontSize = size.fontSize;
2374
+ else if (size?.fontScale != null) {
2375
+ onWarn(`font-size "${style["font-size"]}" on <text> is resolved against the 16px default`);
2376
+ out.fontSize = 16 * size.fontScale;
1852
2377
  }
1853
- const ff = style["font-family"];
2378
+ const ff = styleValue(style, "font-family", onWarn);
1854
2379
  if (ff) out.fontFamily = ff;
1855
- const fw = style["font-weight"];
1856
- if (fw != null) {
1857
- const n = parseFloat(fw);
1858
- out.fontWeight = Number.isFinite(n) ? n : fw;
1859
- }
1860
- const fs = style["font-style"];
1861
- if (fs === "italic" || fs === "normal") out.fontStyle = fs;
1862
- if (style["direction"] === "rtl") out.direction = "rtl";
2380
+ const fw = styleValue(style, "font-weight", onWarn);
2381
+ if (fw != null) out.fontWeight = fw;
2382
+ const fs = styleValue(style, "font-style", onWarn);
2383
+ if (fs) out.fontStyle = fs;
2384
+ if (styleValue(style, "direction") === "rtl") out.direction = "rtl";
1863
2385
  const ltr = out.direction !== "rtl";
1864
- const anchor = style["text-anchor"] ?? (ltr ? void 0 : "start");
2386
+ const anchor = styleValue(style, "text-anchor") ?? (ltr ? void 0 : "start");
1865
2387
  if (anchor === "start") out.align = ltr ? "left" : "right";
1866
2388
  else if (anchor === "middle") out.align = "center";
1867
2389
  else if (anchor === "end") out.align = ltr ? "right" : "left";
1868
- const ls = style["letter-spacing"];
1869
- if (ls != null) {
1870
- const n = parseLetterSpacing(ls, onWarn);
1871
- if (n != null) out.letterSpacing = n;
1872
- }
1873
- const decoration = parseTextDecoration(style["text-decoration"] ?? null);
1874
- if (decoration.underline) out.underline = true;
1875
- if (decoration.strikethrough) out.strikethrough = true;
1876
- if (decoration.overline) out.overline = true;
1877
- const transform = parseTextTransform(style["text-transform"] ?? null);
2390
+ const ls = styleValue(style, "letter-spacing", onWarn);
2391
+ if (ls != null) out.letterSpacing = ls;
2392
+ const decoration = styleValue(style, "text-decoration", onWarn);
2393
+ if (decoration?.underline) out.underline = true;
2394
+ if (decoration?.strikethrough) out.strikethrough = true;
2395
+ if (decoration?.overline) out.overline = true;
2396
+ const transform = styleValue(style, "text-transform", onWarn);
1878
2397
  if (transform && transform !== "none") out.textTransform = transform;
2398
+ const variant = styleValue(style, "font-variant", onWarn);
2399
+ if (variant === "small-caps") out.fontVariantCaps = variant;
1879
2400
  return out;
1880
2401
  }
1881
2402
  function readTextPaint(style, gradients, onWarn, nonScalingStroke = false) {
1882
2403
  const out = {};
1883
2404
  const fillRaw = resolveCurrentColor(style["fill"] ?? null, style);
1884
2405
  if (fillRaw) {
1885
- const parsed = parsePaintAttr(fillRaw);
2406
+ const parsed = readProperty("fill", fillRaw);
1886
2407
  if (parsed?.kind === "solid") {
1887
2408
  out.fill = { fill: "solid", color: parsed.color };
1888
2409
  } else if (parsed?.kind === "ref") {
@@ -1912,8 +2433,8 @@ function ingestMarkers(root, nodes, gradients, onWarn) {
1912
2433
  if (id && !defs.has(id)) defs.set(id, all[i]);
1913
2434
  }
1914
2435
  const minted = /* @__PURE__ */ new Map();
1915
- const resolve = (stroke, width) => {
1916
- const w = width === void 0 ? 1 : typeof width === "number" ? width : width.px;
2436
+ const resolve = (stroke, width2) => {
2437
+ const w = width2 === void 0 ? 1 : typeof width2 === "number" ? width2 : width2.px;
1917
2438
  for (const [field, role, attr] of MARKER_FIELDS) {
1918
2439
  const id = stroke[field];
1919
2440
  if (typeof id !== "string" || getMarker(id) !== void 0) continue;
@@ -1923,6 +2444,16 @@ function ingestMarkers(root, nodes, gradients, onWarn) {
1923
2444
  delete stroke[field];
1924
2445
  continue;
1925
2446
  }
2447
+ const sized = sizedMarkerRef(el);
2448
+ if (sized) {
2449
+ if (getMarker(sized.key) === void 0 && !minted.has(sized.key)) {
2450
+ const entry2 = markerEntryFrom(el, id, role, resolveMarkerSize(sized, 1), gradients, onWarn);
2451
+ minted.set(sized.key, entry2 && { ...entry2, id: sized.key });
2452
+ }
2453
+ if (minted.get(sized.key) === null) delete stroke[field];
2454
+ else stroke[field] = sized;
2455
+ continue;
2456
+ }
1926
2457
  const cacheKey = `${id}\0${role}\0${w}`;
1927
2458
  if (!minted.has(cacheKey)) minted.set(cacheKey, markerEntryFrom(el, id, role, w, gradients, onWarn));
1928
2459
  const entry = minted.get(cacheKey);
@@ -1945,6 +2476,22 @@ function ingestMarkers(root, nodes, gradients, onWarn) {
1945
2476
  for (const entry of minted.values()) if (entry) out.set(entry.id, entry);
1946
2477
  return [...out.values()];
1947
2478
  }
2479
+ function weaselAttr(el, name) {
2480
+ return el.getAttributeNS(WEASEL_NS, name) ?? el.getAttribute(`${WEASEL_NS_PREFIX}:${name}`);
2481
+ }
2482
+ function sizedMarkerRef(el) {
2483
+ const key = weaselAttr(el, "key");
2484
+ const raw = weaselAttr(el, "size")?.trim();
2485
+ if (!key || !raw) return void 0;
2486
+ const px = raw.endsWith("px");
2487
+ const n = parseFloat(px ? raw.slice(0, -2) : raw);
2488
+ if (!Number.isFinite(n) || n <= 0) return void 0;
2489
+ return { key, size: px ? { px: n } : n };
2490
+ }
2491
+ function markerInsetAttr(el) {
2492
+ const n = parseFloat(weaselAttr(el, "inset") ?? "");
2493
+ return Number.isFinite(n) && n > 0 ? n : 0;
2494
+ }
1948
2495
  function styleAt(el) {
1949
2496
  const chain = [];
1950
2497
  for (let e = el; e; e = e.parentElement) chain.unshift(e);
@@ -1970,13 +2517,12 @@ function markerOrient(raw) {
1970
2517
  return { orient: rad, reverseAtStart: false };
1971
2518
  }
1972
2519
  function markerPaint(style, attr, gradients, onWarn) {
1973
- const raw = style[attr]?.trim();
1974
- if (raw === "context-stroke") return "line";
1975
- if (raw === "context-fill") {
1976
- onWarn(`a marker's ${attr}="context-fill" is read as the line's stroke paint`);
2520
+ const context = readProperty(attr, style[attr]);
2521
+ if (context?.kind === "context") {
2522
+ if (context.of === "fill") onWarn(`a marker's ${attr}="context-fill" is read as the line's stroke paint`);
1977
2523
  return "line";
1978
2524
  }
1979
- const paint = readPaint(style, attr, "#000000", gradients, onWarn);
2525
+ const paint = readPaint2(style, attr, "#000000", gradients, onWarn);
1980
2526
  if (paint.kind === "none") return "none";
1981
2527
  if (paint.kind === "gradient") return paint.paint;
1982
2528
  return paint.opacity != null ? { ...solid(paint.color), opacity: paint.opacity } : solid(paint.color);
@@ -2066,6 +2612,7 @@ function markerEntryFrom(el, id, role, strokeWidth, gradients, onWarn) {
2066
2612
  pi += p.path.coords.length;
2067
2613
  }
2068
2614
  const fillRule = parts[0].path.fillRule ?? "nonzero";
2615
+ const inset = markerInsetAttr(el);
2069
2616
  const round = (v) => Math.round(v * 1e4) / 1e4 + 0;
2070
2617
  const hash = shortHash(JSON.stringify([
2071
2618
  Array.from(commands),
@@ -2073,11 +2620,13 @@ function markerEntryFrom(el, id, role, strokeWidth, gradients, onWarn) {
2073
2620
  fillRule,
2074
2621
  fill,
2075
2622
  outline,
2076
- typeof orient === "number" ? round(orient) : orient
2623
+ typeof orient === "number" ? round(orient) : orient,
2624
+ ...inset !== 0 ? [round(inset)] : []
2077
2625
  ]));
2078
2626
  const key = id.endsWith(`-${hash}`) ? id : `${id}-${hash}`;
2079
2627
  return {
2080
2628
  id: key,
2629
+ reads: [],
2081
2630
  path: ({ size }) => {
2082
2631
  const scaled = new Float32Array(coords.length);
2083
2632
  for (let i = 0; i < coords.length; i++) scaled[i] = coords[i] * size;
@@ -2086,7 +2635,7 @@ function markerEntryFrom(el, id, role, strokeWidth, gradients, onWarn) {
2086
2635
  fill,
2087
2636
  outline,
2088
2637
  orient,
2089
- inset: 0
2638
+ inset
2090
2639
  };
2091
2640
  }
2092
2641
  function serializeSvg(nodes, opts = {}) {
@@ -2198,16 +2747,10 @@ function registerPaintServers(nodes, registry) {
2198
2747
  function registerTextPaint(paint, registry) {
2199
2748
  if (paint && !("color" in paint)) registry.register(paint);
2200
2749
  }
2201
- function markerKeyOfRef(ref) {
2202
- if (typeof ref === "string") return ref;
2203
- if (ref && typeof ref === "object" && "key" in ref) return String(ref.key);
2204
- return void 0;
2205
- }
2206
2750
  function registerMarkers(stroke, registry) {
2207
2751
  if (!stroke) return;
2208
2752
  for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {
2209
- const key = markerKeyOfRef(ref);
2210
- if (key) registry.markerId(key);
2753
+ if (ref !== void 0) registry.markerId(ref);
2211
2754
  }
2212
2755
  }
2213
2756
  function nodeXml(node, registry, namespaces, warn) {
@@ -2296,35 +2839,30 @@ function pathXml(node, registry, namespaces, warn) {
2296
2839
  }
2297
2840
  function paintAttrs(paint, name, registry, includeOpacity = true) {
2298
2841
  if (paint.kind === "none") return [`${name}="none"`];
2299
- if (paint.kind === "solid") {
2300
- const out = [`${name}="${paint.color}"`];
2301
- if (includeOpacity && paint.opacity != null && paint.opacity !== 1) {
2302
- out.push(`${name}-opacity="${trimNumber(paint.opacity)}"`);
2303
- }
2304
- return out;
2305
- }
2306
- return [`${name}="${registry.ref(paint.paint)}"`];
2842
+ const out = [`${name}="${paint.kind === "solid" ? paint.color : registry.ref(paint.paint)}"`];
2843
+ if (includeOpacity) out.push(...opacityAttr(`${name}-opacity`, svgPaintOpacity(paint)));
2844
+ return out;
2845
+ }
2846
+ function svgPaintOpacity(paint) {
2847
+ if (paint.kind === "solid") return paint.opacity;
2848
+ return paint.kind === "gradient" ? paint.paint.opacity : void 0;
2307
2849
  }
2308
- function strokeWidthAttrs(width) {
2309
- if (typeof width === "number") return [`stroke-width="${trimNumber(width)}"`];
2310
- return [`stroke-width="${trimNumber(width.px)}"`, 'vector-effect="non-scaling-stroke"'];
2850
+ function opacityAttr(name, v) {
2851
+ return v != null && v !== 1 ? [`${name}="${trimNumber(v)}"`] : [];
2852
+ }
2853
+ function strokeWidthAttrs(width2) {
2854
+ if (typeof width2 === "number") return [`stroke-width="${trimNumber(width2)}"`];
2855
+ return [`stroke-width="${trimNumber(width2.px)}"`, 'vector-effect="non-scaling-stroke"'];
2311
2856
  }
2312
2857
  function coreStrokeAttrs(stroke, registry, warn) {
2313
2858
  const paint = stroke?.paint;
2314
2859
  if (!stroke || !paint) return [];
2315
- const width = stroke.width ?? 1;
2316
- if (!((typeof width === "object" ? width.px : width) > 0)) return [];
2860
+ const width2 = stroke.width ?? 1;
2861
+ if (!((typeof width2 === "object" ? width2.px : width2) > 0)) return [];
2317
2862
  warnStrokeAlign(stroke.align, warn);
2318
- const attrs = [];
2319
- if ("color" in paint) {
2320
- attrs.push(`stroke="${paint.color}"`);
2321
- if (paint.opacity != null && paint.opacity !== 1) {
2322
- attrs.push(`stroke-opacity="${trimNumber(paint.opacity)}"`);
2323
- }
2324
- } else {
2325
- attrs.push(`stroke="${registry.ref(paint)}"`);
2326
- }
2327
- attrs.push(...strokeWidthAttrs(width));
2863
+ const attrs = [`stroke="${"color" in paint ? paint.color : registry.ref(paint)}"`];
2864
+ attrs.push(...opacityAttr("stroke-opacity", paint.opacity));
2865
+ attrs.push(...strokeWidthAttrs(width2));
2328
2866
  if (stroke.cap) attrs.push(`stroke-linecap="${stroke.cap}"`);
2329
2867
  if (stroke.join) attrs.push(`stroke-linejoin="${stroke.join}"`);
2330
2868
  if (stroke.dash && stroke.dash.length > 0) {
@@ -2338,8 +2876,7 @@ function coreStrokeAttrs(stroke, registry, warn) {
2338
2876
  ]) {
2339
2877
  const ref = stroke[field];
2340
2878
  if (ref === void 0) continue;
2341
- const key = typeof ref === "string" ? ref : ref.key;
2342
- const id = registry.markerId(key);
2879
+ const id = registry.markerId(ref);
2343
2880
  if (id === void 0) continue;
2344
2881
  attrs.push(`${attr}="url(#${id})"`);
2345
2882
  }
@@ -2349,10 +2886,7 @@ function strokeAttrsFor(stroke, registry, warn) {
2349
2886
  warnStrokeAlign(stroke.align, warn);
2350
2887
  const attrs = paintAttrs(stroke.paint, "stroke", registry, false);
2351
2888
  attrs.push(...strokeWidthAttrs(stroke.width));
2352
- const opacity = stroke.opacity ?? (stroke.paint.kind === "solid" ? stroke.paint.opacity : void 0);
2353
- if (opacity != null && opacity !== 1) {
2354
- attrs.push(`stroke-opacity="${trimNumber(opacity)}"`);
2355
- }
2889
+ attrs.push(...opacityAttr("stroke-opacity", stroke.opacity ?? svgPaintOpacity(stroke.paint)));
2356
2890
  if (stroke.cap) {
2357
2891
  attrs.push(`stroke-linecap="${stroke.cap}"`);
2358
2892
  }
@@ -2429,18 +2963,21 @@ function textXml(node, registry, namespaces, warn) {
2429
2963
  if (style?.fontFamily) attrs.push(`font-family="${escapeAttr2(style.fontFamily)}"`);
2430
2964
  if (style?.fontWeight != null) attrs.push(`font-weight="${String(style.fontWeight)}"`);
2431
2965
  if (style?.fontStyle && style.fontStyle !== "normal") attrs.push(`font-style="${style.fontStyle}"`);
2966
+ if (style?.fontVariantCaps === "small-caps") attrs.push('font-variant="small-caps"');
2432
2967
  const direction = style?.direction ?? "ltr";
2433
2968
  if (direction === "rtl") attrs.push('direction="rtl"');
2434
2969
  if (style?.align != null || direction === "rtl") {
2435
2970
  const align = style?.align ?? "left";
2436
- const anchor = align === "center" ? "middle" : align === "start" ? "start" : align === "end" ? "end" : align === "left" === (direction === "ltr") ? "start" : "end";
2971
+ const anchor = align === "center" ? "middle" : align === "start" || align === "justify" ? "start" : align === "end" ? "end" : align === "left" === (direction === "ltr") ? "start" : "end";
2437
2972
  if (anchor !== "start") attrs.push(`text-anchor="${anchor}"`);
2973
+ if (align === "justify") attrs.push('data-weasel-align="justify"');
2438
2974
  }
2439
2975
  if (style?.letterSpacing != null && style.letterSpacing !== 0) {
2440
2976
  attrs.push(`letter-spacing="${trimNumber(resolveScreenLength(style.letterSpacing, 1))}"`);
2441
2977
  }
2442
2978
  const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
2443
2979
  if (decoration) attrs.push(`text-decoration="${decoration}"`);
2980
+ if (style?.script) attrs.push(`baseline-shift="${style.script}"`);
2444
2981
  if (style?.textTransform && style.textTransform !== "none") {
2445
2982
  attrs.push(`style="text-transform:${style.textTransform}"`);
2446
2983
  }
@@ -2455,14 +2992,8 @@ function textXml(node, registry, namespaces, warn) {
2455
2992
  if (node.fill === null) {
2456
2993
  attrs.push('fill="none"');
2457
2994
  } else if (node.fill) {
2458
- if ("color" in node.fill) {
2459
- attrs.push(`fill="${node.fill.color}"`);
2460
- if (node.fill.opacity != null && node.fill.opacity !== 1) {
2461
- attrs.push(`fill-opacity="${trimNumber(node.fill.opacity)}"`);
2462
- }
2463
- } else {
2464
- attrs.push(`fill="${registry.ref(node.fill)}"`);
2465
- }
2995
+ attrs.push(`fill="${"color" in node.fill ? node.fill.color : registry.ref(node.fill)}"`);
2996
+ attrs.push(...opacityAttr("fill-opacity", node.fill.opacity));
2466
2997
  }
2467
2998
  for (const a of coreStrokeAttrs(node.stroke, registry, warn)) attrs.push(a);
2468
2999
  if (node.opacity != null && node.opacity !== 1) {
@@ -2474,17 +3005,25 @@ function textXml(node, registry, namespaces, warn) {
2474
3005
  const deg = node.rotation * 180 / Math.PI;
2475
3006
  attrs.push(`transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`);
2476
3007
  }
2477
- const body = node.runs && node.runs.length > 0 ? node.runs.map((r) => runXml(r, registry, warn)).join("") : escapeText(node.text);
3008
+ const body = node.runs && node.runs.length > 0 ? node.runs.map((r) => runXml(r, style, registry, warn)).join("") : escapeText(node.text);
2478
3009
  const metaAttrs = metaAttrsXml(node.meta, namespaces);
2479
3010
  const metaEls = metaElementsXml(node.meta, namespaces);
2480
3011
  return `<text ${attrs.join(" ")}${metaAttrs}>${body}${metaEls}</text>`;
2481
3012
  }
2482
- function runXml(run, registry, warn) {
3013
+ function scriptSizeFace(run, style) {
3014
+ if (run.script == null || run.baselineShift == null) return void 0;
3015
+ const face = resolveRunFace(run, resolveTextStyle(style));
3016
+ return { family: face.fontFamily, weight: face.fontWeight, style: face.fontStyle };
3017
+ }
3018
+ function runXml(run, style, registry, warn) {
2483
3019
  const attrs = [];
2484
- if (run.bold) attrs.push(`font-weight="700"`);
3020
+ if (run.fontWeight != null) attrs.push(`font-weight="${run.fontWeight}"`);
3021
+ else if (run.bold) attrs.push(`font-weight="700"`);
2485
3022
  if (run.italic) attrs.push(`font-style="italic"`);
3023
+ if (run.fontVariantCaps) attrs.push(`font-variant="${run.fontVariantCaps}"`);
2486
3024
  if (run.fontFamily) attrs.push(`font-family="${escapeAttr2(run.fontFamily)}"`);
2487
- const presetScale = run.script != null && run.baselineShift != null ? SCRIPT_METRICS[run.script].size : void 0;
3025
+ const face = scriptSizeFace(run, style);
3026
+ const presetScale = face && run.script ? scriptMetricsFor(face.family, face.weight, face.style)[run.script].size : void 0;
2488
3027
  const scale = run.fontScale ?? presetScale;
2489
3028
  if (run.fontSize != null) attrs.push(`font-size="${trimNumber(resolveScreenLength(run.fontSize, 1))}"`);
2490
3029
  else if (scale != null) attrs.push(`font-size="${trimNumber(scale * 100)}%"`);
@@ -2535,6 +3074,31 @@ function pathBounds(path) {
2535
3074
  const b = boundsOfPath(path);
2536
3075
  return { minX: b.x, minY: b.y, maxX: b.x + b.width, maxY: b.y + b.height };
2537
3076
  }
3077
+ function svgNeeds(nodes) {
3078
+ const registry = new PaintServerRegistry();
3079
+ registerPaintServers(nodes, registry);
3080
+ const fonts = /* @__PURE__ */ new Map();
3081
+ const walk = (list) => {
3082
+ for (const n of list) {
3083
+ if (n.kind === "group") walk(n.children);
3084
+ else if (n.kind === "text") {
3085
+ for (const run of n.runs ?? []) {
3086
+ const face = scriptSizeFace(run, n.style);
3087
+ if (face) fonts.set(`${face.family}|${face.weight}|${face.style}`, face);
3088
+ }
3089
+ }
3090
+ }
3091
+ };
3092
+ walk(nodes);
3093
+ return { fonts: [...fonts.values()], paintKinds: registry.paintKinds() };
3094
+ }
3095
+ function warmSvg(nodes) {
3096
+ const needs = svgNeeds(nodes);
3097
+ return Promise.all([
3098
+ warmFonts(needs.fonts),
3099
+ warmPaintKinds(needs.paintKinds.filter(isPaintKindKnown))
3100
+ ]).then(() => void 0);
3101
+ }
2538
3102
  var CASCADE_OFFSET_PX = 24;
2539
3103
  var VIEWPORT_FIT = 0.9;
2540
3104
  function readFileText(file) {
@@ -2549,17 +3113,31 @@ var svgIdCounter = 0;
2549
3113
  function freshSvgNodeId() {
2550
3114
  return `n${(svgIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
2551
3115
  }
2552
- function fillFromPaint(paint, box) {
3116
+ function fillDataFromSvg(paint, box) {
2553
3117
  if (!paint) return void 0;
2554
3118
  if (paint.kind === "none") return null;
2555
- if (paint.kind === "solid") return solid(paint.color);
2556
- const g = paint.paint;
2557
- const units = "units" in g ? g.units : void 0;
2558
- return units === "world" ? fillToBoundsFrame(g, box) : g;
3119
+ if (paint.kind === "solid") {
3120
+ return paint.opacity != null ? { ...solid(paint.color), opacity: paint.opacity } : solid(paint.color);
3121
+ }
3122
+ return fillInBoxFrame(paint.paint, box);
3123
+ }
3124
+ function fadeFill(fill, k) {
3125
+ return k === 1 ? fill : { ...fill, opacity: (fill.opacity ?? 1) * k };
3126
+ }
3127
+ function fadeStroke(stroke, k) {
3128
+ return k === 1 || !stroke.paint ? stroke : { ...stroke, paint: fadeFill(stroke.paint, k) };
3129
+ }
3130
+ function fadeRun(run, k) {
3131
+ if (k === 1 || !run.fill && !run.stroke) return run;
3132
+ return {
3133
+ ...run,
3134
+ ...run.fill ? { fill: fadeFill(run.fill, k) } : {},
3135
+ ...run.stroke ? { stroke: fadeStroke(run.stroke, k) } : {}
3136
+ };
2559
3137
  }
2560
3138
  function strokeDataFromSvg(stroke, box) {
2561
3139
  if (!stroke || stroke.paint.kind === "none") return void 0;
2562
- const paint = fillFromPaint(stroke.paint, box);
3140
+ const paint = fillDataFromSvg(stroke.paint, box);
2563
3141
  if (paint === void 0 || paint === null) return void 0;
2564
3142
  return {
2565
3143
  paint: stroke.opacity !== void 0 ? { ...paint, opacity: stroke.opacity } : paint,
@@ -2573,22 +3151,6 @@ function strokeDataFromSvg(stroke, box) {
2573
3151
  ...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
2574
3152
  };
2575
3153
  }
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
- }
2592
3154
  function fillInBoxFrame(fill, box) {
2593
3155
  const units = "units" in fill ? fill.units : void 0;
2594
3156
  return units === "world" ? fillToBoundsFrame(fill, box) : fill;
@@ -2604,28 +3166,36 @@ function strokeInBoxFrame(stroke, box) {
2604
3166
  const paint = fillInBoxFrame(stroke.paint, box);
2605
3167
  return paint === stroke.paint ? stroke : { ...stroke, paint };
2606
3168
  }
2607
- function svgNodesToKitDrafts(nodes, nextId) {
3169
+ function svgNodesToKitDrafts(input, nextId, options = {}) {
3170
+ const nodes = isNodeList(input) ? input : input.nodes;
3171
+ if (!isNodeList(input)) {
3172
+ for (const m of input.markers ?? []) if (getMarker(m.id) === void 0) registerMarker(m);
3173
+ }
2608
3174
  const drafts = [];
2609
- const visit = (n, parentId) => {
3175
+ const pushLeaf = (source, parentId, pose, data) => {
3176
+ const id = nextId(source);
3177
+ const out = options.leaf ? options.leaf(id, { pose, data }, source) : data;
3178
+ if (out === null) return null;
3179
+ drafts.push({ kind: "leaf", id, parentId, pose, data: out });
3180
+ return pose;
3181
+ };
3182
+ const visit = (n, parentId, inherited2) => {
3183
+ const k = inherited2 * (n.opacity ?? 1);
2610
3184
  if (n.kind === "group") {
2611
3185
  const draft = {
2612
3186
  kind: "container",
2613
- id: nextId(),
3187
+ id: nextId(n),
2614
3188
  parentId,
2615
3189
  pose: { x: 0, y: 0, width: 0, height: 0 }
2616
3190
  };
2617
3191
  drafts.push(draft);
2618
- let acc = null;
2619
- for (const c of n.children) {
2620
- const b2 = visit(c, draft.id);
2621
- if (b2) acc = acc ? unionRect(acc, b2) : b2;
2622
- }
3192
+ const acc = unionAABB(n.children.map((c) => visit(c, draft.id, k)));
2623
3193
  if (!acc) {
2624
3194
  drafts.splice(drafts.indexOf(draft), 1);
2625
3195
  return null;
2626
3196
  }
2627
3197
  draft.pose = acc;
2628
- return acc;
3198
+ return draft.pose;
2629
3199
  }
2630
3200
  if (n.kind === "text") {
2631
3201
  const pose2 = {
@@ -2641,65 +3211,48 @@ function svgNodesToKitDrafts(nodes, nextId) {
2641
3211
  width: pose2.width,
2642
3212
  height: pose2.height
2643
3213
  };
2644
- drafts.push({
2645
- kind: "leaf",
2646
- id: nextId(),
2647
- parentId,
2648
- pose: pose2,
2649
- data: {
2650
- text: n.text,
2651
- ...n.style ? { style: n.style } : {},
2652
- ...n.verticalAlign ? { verticalAlign: n.verticalAlign } : {},
2653
- ...n.runs ? { runs: n.runs.map((r) => runInBoxFrame(r, box)) } : {},
2654
- // `!== undefined`, not a truthiness test: `null` is the document
2655
- // saying `fill="none"`, and absent takes the painter's default.
2656
- ...n.fill !== void 0 ? { fill: n.fill === null ? null : fillInBoxFrame(n.fill, box) } : {},
2657
- ...n.stroke !== void 0 ? { stroke: strokeInBoxFrame(n.stroke, box) } : {}
2658
- }
3214
+ const fill2 = n.fill === void 0 && k !== 1 ? DEFAULT_TEXT_STYLE.fill : n.fill;
3215
+ return pushLeaf(n, parentId, pose2, {
3216
+ text: n.text,
3217
+ ...n.style ? { style: n.style } : {},
3218
+ ...n.verticalAlign ? { verticalAlign: n.verticalAlign } : {},
3219
+ ...n.runs ? { runs: n.runs.map((r) => fadeRun(runInBoxFrame(r, box), k)) } : {},
3220
+ // `!== undefined`, not a truthiness test: `null` is the document
3221
+ // saying `fill="none"`, and absent takes the painter's default.
3222
+ ...fill2 !== void 0 ? { fill: fill2 === null ? null : fadeFill(fillInBoxFrame(fill2, box), k) } : {},
3223
+ ...n.stroke !== void 0 ? { stroke: fadeStroke(strokeInBoxFrame(n.stroke, box), k) } : {}
2659
3224
  });
2660
- return pose2;
2661
3225
  }
2662
3226
  if (n.kind === "image") {
2663
3227
  const pose2 = { x: n.x, y: n.y, width: n.width, height: n.height };
2664
3228
  if (n.rotation) pose2.rotation = n.rotation;
2665
- drafts.push({
2666
- kind: "leaf",
2667
- id: nextId(),
2668
- parentId,
2669
- pose: pose2,
2670
- data: {
2671
- image: {
2672
- src: n.href,
2673
- ...n.opacity != null ? { opacity: n.opacity } : {},
2674
- ...n.source ? { source: { ...n.source } } : {},
2675
- ...n.flipX ? { flipX: true } : {},
2676
- ...n.flipY ? { flipY: true } : {}
2677
- }
3229
+ return pushLeaf(n, parentId, pose2, {
3230
+ image: {
3231
+ src: n.href,
3232
+ ...k !== 1 ? { opacity: k } : {},
3233
+ ...n.source ? { source: { ...n.source } } : {},
3234
+ ...n.flipX ? { flipX: true } : {},
3235
+ ...n.flipY ? { flipY: true } : {}
2678
3236
  }
2679
3237
  });
2680
- return pose2;
2681
3238
  }
2682
3239
  const b = n.path.kind === "rect" ? { x: n.path.x, y: n.path.y, width: n.path.width, height: n.path.height } : boundsOfPath(n.path);
2683
3240
  const pose = { x: b.x, y: b.y, width: b.width, height: b.height };
2684
3241
  if (n.rotation) pose.rotation = n.rotation;
2685
- const fill = fillFromPaint(n.fill, b);
3242
+ const fill = fillDataFromSvg(n.fill, b);
2686
3243
  const strokeFromSvg = strokeDataFromSvg(n.stroke, b);
2687
- drafts.push({
2688
- kind: "leaf",
2689
- id: nextId(),
2690
- parentId,
2691
- pose,
2692
- data: {
2693
- path: n.path,
2694
- ...fill !== void 0 ? { fill } : {},
2695
- ...strokeFromSvg !== void 0 ? { stroke: strokeFromSvg } : {}
2696
- }
3244
+ return pushLeaf(n, parentId, pose, {
3245
+ path: n.path,
3246
+ ...fill !== void 0 ? { fill: fill && fadeFill(fill, k) } : {},
3247
+ ...strokeFromSvg !== void 0 ? { stroke: fadeStroke(strokeFromSvg, k) } : {}
2697
3248
  });
2698
- return pose;
2699
3249
  };
2700
- for (const n of nodes) visit(n, null);
3250
+ for (const n of nodes) visit(n, null, 1);
2701
3251
  return drafts;
2702
3252
  }
3253
+ function isNodeList(input) {
3254
+ return Array.isArray(input);
3255
+ }
2703
3256
  function scaleTextData(data, scale) {
2704
3257
  if (scale === 1 || typeof data.text !== "string") return data;
2705
3258
  const style = data.style ?? {};
@@ -2713,13 +3266,6 @@ function scaleTextData(data, scale) {
2713
3266
  style: { ...style, fontSize: scaleSize(style.fontSize ?? resolveTextStyle(style).fontSize) }
2714
3267
  };
2715
3268
  }
2716
- function unionRect(a, b) {
2717
- const minX = Math.min(a.x, b.x);
2718
- const minY = Math.min(a.y, b.y);
2719
- const maxX = Math.max(a.x + a.width, b.x + b.width);
2720
- const maxY = Math.max(a.y + a.height, b.y + b.height);
2721
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
2722
- }
2723
3269
  async function unpackSvgFiles(files, ctx) {
2724
3270
  const layer = ctx.scene.layers[0]?.id ?? "default";
2725
3271
  let index = 0;
@@ -2728,8 +3274,7 @@ async function unpackSvgFiles(files, ctx) {
2728
3274
  const text = await readFileText(file);
2729
3275
  const parsed = parseSvg(text);
2730
3276
  for (const w of parsed.warnings) dwarn("ingest", `svg "${file.name}":`, w);
2731
- for (const m of parsed.markers ?? []) if (getMarker(m.id) === void 0) registerMarker(m);
2732
- let drafts = svgNodesToKitDrafts(parsed.nodes, freshSvgNodeId);
3277
+ let drafts = svgNodesToKitDrafts(parsed, freshSvgNodeId);
2733
3278
  if (drafts.length === 0) {
2734
3279
  console.warn(`weasel ingest: svg "${file.name}" parsed to no drawable nodes`);
2735
3280
  continue;
@@ -2737,13 +3282,13 @@ async function unpackSvgFiles(files, ctx) {
2737
3282
  const roots = drafts.filter((d) => d.parentId === null);
2738
3283
  if (roots.length > 1) {
2739
3284
  const wrapperId = freshSvgNodeId();
2740
- const union2 = roots.map((d) => d.pose).reduce(unionRect);
3285
+ const union2 = unionAABB(roots.map((d) => d.pose));
2741
3286
  drafts = [
2742
3287
  { kind: "container", id: wrapperId, parentId: null, pose: union2 },
2743
3288
  ...drafts.map((d) => d.parentId === null ? { ...d, parentId: wrapperId } : d)
2744
3289
  ];
2745
3290
  }
2746
- const union = drafts.filter((d) => d.parentId === null).map((d) => d.pose).reduce(unionRect);
3291
+ const union = unionAABB(drafts.filter((d) => d.parentId === null).map((d) => d.pose));
2747
3292
  const view = ctx.viewportWorldRect();
2748
3293
  const scale = Math.min(
2749
3294
  1,
@@ -2784,7 +3329,115 @@ async function unpackSvgFiles(files, ctx) {
2784
3329
  }
2785
3330
  }
2786
3331
  }
3332
+ function svgPaintFromKit(fill, box) {
3333
+ if (fill === null) return { kind: "none" };
3334
+ if (fill.fill === void 0 || fill.fill === "solid") {
3335
+ return { kind: "solid", color: fill.color, ...fill.opacity != null ? { opacity: fill.opacity } : {} };
3336
+ }
3337
+ return { kind: "gradient", paint: fillInPoseFrame(fill, box) };
3338
+ }
3339
+ function svgStrokeFromKit(stroke, box) {
3340
+ if (!stroke?.paint) return void 0;
3341
+ const width2 = stroke.width ?? 1;
3342
+ if (!((typeof width2 === "object" ? width2.px : width2) > 0)) return void 0;
3343
+ const opacity = stroke.paint.opacity;
3344
+ return {
3345
+ paint: svgPaintFromKit(stroke.paint, box),
3346
+ width: width2,
3347
+ ...opacity != null ? { opacity } : {},
3348
+ ...stroke.cap !== void 0 ? { cap: stroke.cap } : {},
3349
+ ...stroke.join !== void 0 ? { join: stroke.join } : {},
3350
+ ...stroke.dash !== void 0 ? { dash: stroke.dash } : {},
3351
+ ...stroke.miterLimit !== void 0 ? { miterLimit: stroke.miterLimit } : {},
3352
+ ...stroke.align !== void 0 ? { align: stroke.align } : {},
3353
+ ...stroke.markerStart !== void 0 ? { markerStart: stroke.markerStart } : {},
3354
+ ...stroke.markerMid !== void 0 ? { markerMid: stroke.markerMid } : {},
3355
+ ...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
3356
+ };
3357
+ }
3358
+ function svgImageFromKit(image, pose) {
3359
+ const node = {
3360
+ kind: "image",
3361
+ href: image.src,
3362
+ x: pose.x,
3363
+ y: pose.y,
3364
+ width: pose.width,
3365
+ height: pose.height
3366
+ };
3367
+ if (image.source) node.source = { ...image.source };
3368
+ if (image.flipX) node.flipX = true;
3369
+ if (image.flipY) node.flipY = true;
3370
+ if (image.opacity != null) node.opacity = image.opacity;
3371
+ if (pose.rotation) node.rotation = pose.rotation;
3372
+ return node;
3373
+ }
3374
+ function strokeInPoseFrame(stroke, box) {
3375
+ return stroke.paint ? { ...stroke, paint: fillInPoseFrame(stroke.paint, box) } : stroke;
3376
+ }
3377
+ function runInPoseFrame(run, box) {
3378
+ if (!run.fill && !run.stroke) return run;
3379
+ return {
3380
+ ...run,
3381
+ ...run.fill ? { fill: fillInPoseFrame(run.fill, box) } : {},
3382
+ ...run.stroke ? { stroke: strokeInPoseFrame(run.stroke, box) } : {}
3383
+ };
3384
+ }
3385
+ function svgLeafFromKit(data, pose) {
3386
+ if (data.image) return svgImageFromKit(data.image, pose);
3387
+ if (data.text != null) {
3388
+ const node2 = {
3389
+ kind: "text",
3390
+ x: pose.x,
3391
+ y: pose.y,
3392
+ width: pose.width,
3393
+ height: pose.height,
3394
+ text: data.text
3395
+ };
3396
+ if (data.style) node2.style = data.style;
3397
+ if (data.runs) node2.runs = data.runs.map((r) => runInPoseFrame(r, pose));
3398
+ if (data.verticalAlign) node2.verticalAlign = data.verticalAlign;
3399
+ if (data.fill !== void 0) node2.fill = data.fill && fillInPoseFrame(data.fill, pose);
3400
+ if (data.stroke) node2.stroke = strokeInPoseFrame(data.stroke, pose);
3401
+ if (pose.rotation) node2.rotation = pose.rotation;
3402
+ return node2;
3403
+ }
3404
+ if (!data.path) return null;
3405
+ const stroke = svgStrokeFromKit(data.stroke, pose);
3406
+ const fill = data.fill !== void 0 ? data.fill : stroke ? null : DEFAULT_SHAPE_FILL;
3407
+ const node = {
3408
+ kind: "path",
3409
+ path: pathInPoseFrame(data.path, pose),
3410
+ fill: svgPaintFromKit(fill, pose)
3411
+ };
3412
+ if (stroke) node.stroke = stroke;
3413
+ if (pose.rotation) node.rotation = pose.rotation;
3414
+ return node;
3415
+ }
3416
+ function svgNodesFromKit(tree, options = {}) {
3417
+ const { include, leaf, group } = options;
3418
+ const emit = (id) => {
3419
+ if (include && !include(id)) return null;
3420
+ const node = tree.get(id);
3421
+ if (!node) return null;
3422
+ if (node.kind === "container") {
3423
+ const children = [];
3424
+ for (const c of tree.childrenOf(id)) {
3425
+ const child = emit(c);
3426
+ if (child) children.push(child);
3427
+ }
3428
+ const g = { kind: "group", children };
3429
+ return group ? group(id, g) : g;
3430
+ }
3431
+ return leaf ? leaf(id, node) : svgLeafFromKit(node.data, node.pose);
3432
+ };
3433
+ const out = [];
3434
+ for (const id of options.roots ?? tree.roots) {
3435
+ const n = emit(id);
3436
+ if (n) out.push(n);
3437
+ }
3438
+ return out;
3439
+ }
2787
3440
 
2788
- export { IDENTITY_MATRIX, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
3441
+ export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNeeds, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles, warmSvg };
2789
3442
  //# sourceMappingURL=index.js.map
2790
3443
  //# sourceMappingURL=index.js.map