@weasel-js/labkit 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.
Files changed (256) hide show
  1. package/README.md +3 -3
  2. package/dist/_dts/{CanvasStackContext-Wo6OwIVM.d.ts → CanvasStackContext-Cjy6I-Iv.d.ts} +4 -1
  3. package/dist/_dts/{Stage-DWBBi8IU.d.ts → Stage-D6t7o0pF.d.ts} +59 -18
  4. package/dist/_dts/{index-CKyX0ME_.d.ts → index-BTmaRy6a.d.ts} +10 -6
  5. package/dist/_dts/{types-B7yBTTF9.d.ts → index-DCMA4Fy1.d.ts} +59 -113
  6. package/dist/_dts/{types-DTuViBvc.d.ts → types-BUBSIZuV.d.ts} +18 -8
  7. package/dist/_dts/{useTrialState-CMaV6UD4.d.ts → useTrialState-Bh8bzFgE.d.ts} +1 -1
  8. package/dist/canvas/index.d.ts +8 -6
  9. package/dist/canvas/index.js +6 -6
  10. package/dist/chrome/index.d.ts +6 -4
  11. package/dist/chrome/index.js +11 -10
  12. package/dist/chunk-2ES4W47N.js +8 -0
  13. package/dist/chunk-2ES4W47N.js.map +1 -0
  14. package/dist/{chunk-WPLAANH4.js → chunk-2N7UBSS2.js} +12 -12
  15. package/dist/chunk-2N7UBSS2.js.map +1 -0
  16. package/dist/chunk-4KSC7NXL.js +15491 -0
  17. package/dist/chunk-4KSC7NXL.js.map +1 -0
  18. package/dist/{chunk-SLAQPUI4.js → chunk-CEGFXTBD.js} +10 -63
  19. package/dist/chunk-CEGFXTBD.js.map +1 -0
  20. package/dist/chunk-CZZPXUOU.js +3 -0
  21. package/dist/{chunk-SBXNQW4G.js.map → chunk-CZZPXUOU.js.map} +1 -1
  22. package/dist/{chunk-QGMOA2YE.js → chunk-E23Q6A3Y.js} +296 -104
  23. package/dist/chunk-E23Q6A3Y.js.map +1 -0
  24. package/dist/{chunk-GHGO52SO.js → chunk-E6NARGU7.js} +5 -5
  25. package/dist/{chunk-GHGO52SO.js.map → chunk-E6NARGU7.js.map} +1 -1
  26. package/dist/{chunk-IJJUK4FU.js → chunk-EESJUSYR.js} +5 -6
  27. package/dist/chunk-EESJUSYR.js.map +1 -0
  28. package/dist/{chunk-HXXTULDN.js → chunk-EFXRM6XQ.js} +8 -11
  29. package/dist/chunk-EFXRM6XQ.js.map +1 -0
  30. package/dist/chunk-F4ACIK7Z.js +210 -0
  31. package/dist/chunk-F4ACIK7Z.js.map +1 -0
  32. package/dist/{chunk-6FLXL3I7.js → chunk-FB4MIZOO.js} +77 -45
  33. package/dist/chunk-FB4MIZOO.js.map +1 -0
  34. package/dist/{chunk-AWQG7FCC.js → chunk-FG3EXZDV.js} +3 -3
  35. package/dist/{chunk-AWQG7FCC.js.map → chunk-FG3EXZDV.js.map} +1 -1
  36. package/dist/{chunk-4TT3PNPR.js → chunk-GAHELLJT.js} +95 -95
  37. package/dist/chunk-GAHELLJT.js.map +1 -0
  38. package/dist/{chunk-HKV7LRHY.js → chunk-IBVRMHW5.js} +5 -5
  39. package/dist/{chunk-HKV7LRHY.js.map → chunk-IBVRMHW5.js.map} +1 -1
  40. package/dist/{chunk-ASOK76ZH.js → chunk-JFOJESJF.js} +72 -29
  41. package/dist/chunk-JFOJESJF.js.map +1 -0
  42. package/dist/{chunk-RWNDDKOH.js → chunk-PMLZP7RT.js} +18 -15
  43. package/dist/chunk-PMLZP7RT.js.map +1 -0
  44. package/dist/{chunk-TJ77A4A3.js → chunk-READTDQX.js} +5 -9
  45. package/dist/chunk-READTDQX.js.map +1 -0
  46. package/dist/{chunk-JVBYV6TL.js → chunk-ROGJWDQA.js} +3 -3
  47. package/dist/{chunk-JVBYV6TL.js.map → chunk-ROGJWDQA.js.map} +1 -1
  48. package/dist/{chunk-CGHF6RZV.js → chunk-T6DLGEJB.js} +18 -18
  49. package/dist/chunk-T6DLGEJB.js.map +1 -0
  50. package/dist/{chunk-24W5VEJW.js → chunk-ULYYNWGP.js} +2 -2
  51. package/dist/{chunk-24W5VEJW.js.map → chunk-ULYYNWGP.js.map} +1 -1
  52. package/dist/{chunk-DWMSB4CA.js → chunk-VYVGVBGY.js} +95 -32
  53. package/dist/chunk-VYVGVBGY.js.map +1 -0
  54. package/dist/{chunk-IRTGUQ5O.js → chunk-WLMDTI2Q.js} +16 -6
  55. package/dist/chunk-WLMDTI2Q.js.map +1 -0
  56. package/dist/{chunk-NEXR37YJ.js → chunk-XE5HJOPL.js} +6 -8
  57. package/dist/chunk-XE5HJOPL.js.map +1 -0
  58. package/dist/chunk-XVUKDSYV.js +38 -0
  59. package/dist/chunk-XVUKDSYV.js.map +1 -0
  60. package/dist/{chunk-O6FFFOHS.js → chunk-YCFKW5RX.js} +16 -8
  61. package/dist/chunk-YCFKW5RX.js.map +1 -0
  62. package/dist/{chunk-CQLQPQ4P.js → chunk-ZQIHJXP4.js} +2 -2
  63. package/dist/chunk-ZQIHJXP4.js.map +1 -0
  64. package/dist/config/index.d.ts +28 -13
  65. package/dist/config/index.js +4 -4
  66. package/dist/controls/index.d.ts +4 -7
  67. package/dist/controls/index.js +6 -5
  68. package/dist/dragdrop/index.d.ts +23 -3
  69. package/dist/dragdrop/index.js +4 -3
  70. package/dist/fonts/oswald-tabular-digits.woff2 +0 -0
  71. package/dist/index.d.ts +72 -487
  72. package/dist/index.js +603 -441
  73. package/dist/index.js.map +1 -1
  74. package/dist/job/index.d.ts +8 -0
  75. package/dist/job/index.js +1 -1
  76. package/dist/layers/index.d.ts +6 -5
  77. package/dist/layers/index.js +3 -2
  78. package/dist/loupe/index.d.ts +100 -30
  79. package/dist/loupe/index.js +585 -6
  80. package/dist/loupe/index.js.map +1 -1
  81. package/dist/overview/index.d.ts +4 -2
  82. package/dist/overview/index.js +20 -18
  83. package/dist/overview/index.js.map +1 -1
  84. package/dist/passthrough/weasel-canvas.d.ts +2 -0
  85. package/dist/passthrough/weasel-ui.d.ts +2 -2701
  86. package/dist/passthrough/weasel-ui.js +4 -3
  87. package/dist/primitives/index.d.ts +37 -2
  88. package/dist/primitives/index.js +9 -8
  89. package/dist/state/index.d.ts +7 -5
  90. package/dist/state/index.js +6 -6
  91. package/dist/styles.css +150 -126
  92. package/dist/surface/index.d.ts +10 -2
  93. package/dist/surface/index.js +1 -1
  94. package/dist/undo/index.d.ts +5 -3
  95. package/package.json +14 -13
  96. package/src/annotations/AnnotationOverlay.abandon.test.tsx +143 -0
  97. package/src/annotations/AnnotationOverlay.tsx +52 -33
  98. package/src/annotations/Annotations.less +0 -10
  99. package/src/annotations/Annotations.overlay.test.tsx +31 -0
  100. package/src/annotations/MarkList.tsx +4 -8
  101. package/src/annotations/capture.browser.test.ts +41 -0
  102. package/src/annotations/capture.ts +14 -7
  103. package/src/annotations/drawOne.ts +2 -2
  104. package/src/annotations/frac.test.ts +44 -1
  105. package/src/annotations/frac.ts +46 -18
  106. package/src/annotations/index.ts +1 -0
  107. package/src/annotations/paint.test.ts +76 -46
  108. package/src/annotations/paint.ts +65 -43
  109. package/src/annotations/store.test.ts +212 -2
  110. package/src/annotations/store.ts +130 -27
  111. package/src/annotations/svgNodes.test.ts +28 -19
  112. package/src/annotations/svgNodes.ts +6 -6
  113. package/src/annotations/types.ts +29 -10
  114. package/src/canvas/AGENTS.md +1 -0
  115. package/src/canvas/CameraInput.test.tsx +30 -0
  116. package/src/canvas/CameraInput.tsx +60 -48
  117. package/src/canvas/CanvasStack.tsx +11 -6
  118. package/src/canvas/CanvasStackContext.ts +4 -1
  119. package/src/canvas/LinkedCursor.less +1 -1
  120. package/src/canvas/Stage.test.tsx +33 -4
  121. package/src/canvas/Stage.tsx +9 -4
  122. package/src/canvas/cameraRegistry.test.ts +55 -0
  123. package/src/canvas/cameraRegistry.ts +87 -0
  124. package/src/canvas/cameraZoom.test.ts +46 -0
  125. package/src/canvas/cameraZoom.ts +24 -0
  126. package/src/canvas/index.ts +14 -2
  127. package/src/canvas/sharedScope.test.tsx +94 -0
  128. package/src/canvas/worldSpec.ts +3 -1
  129. package/src/chrome/ChromeRegions.stories.tsx +1 -0
  130. package/src/chrome/builtins.test.ts +7 -6
  131. package/src/chrome/builtins.tsx +5 -1
  132. package/src/chrome/index.ts +1 -1
  133. package/src/chrome/merge.test.ts +5 -0
  134. package/src/chrome/merge.ts +4 -2
  135. package/src/chrome/regions/PaletteRegion.less +0 -7
  136. package/src/chrome/regions/PaletteRegion.test.tsx +2 -3
  137. package/src/chrome/regions/PaletteRegion.tsx +3 -4
  138. package/src/chrome/regions/SidebarRegion.less +12 -2
  139. package/src/chrome/regions/ViewportRegion.browser.test.less +6 -0
  140. package/src/chrome/regions/ViewportRegion.browser.test.tsx +38 -0
  141. package/src/chrome/regions/ViewportRegion.less +6 -0
  142. package/src/chrome/regions/ViewportRegion.tsx +10 -4
  143. package/src/chrome/types.ts +4 -2
  144. package/src/config/auto.ts +1 -0
  145. package/src/config/builder.test.ts +2 -1
  146. package/src/config/builder.ts +18 -5
  147. package/src/config/fromConfigField.test.ts +14 -0
  148. package/src/config/fromConfigField.ts +18 -4
  149. package/src/config/path.ts +2 -5
  150. package/src/config/resolve.ts +4 -0
  151. package/src/config/types.ts +14 -9
  152. package/src/controls/ControlPanel.less +1 -1
  153. package/src/controls/ControlPanel.stories.tsx +3 -2
  154. package/src/controls/ControlPanel.test.tsx +98 -11
  155. package/src/controls/ControlPanel.tsx +61 -2
  156. package/src/dragdrop/DragDropRuntime.tsx +22 -13
  157. package/src/dragdrop/DragGhost.tsx +3 -0
  158. package/src/dragdrop/Palette.tsx +2 -0
  159. package/src/index.test.ts +5 -0
  160. package/src/index.ts +16 -2
  161. package/src/instrument/types.ts +0 -6
  162. package/src/job/useJob.ts +14 -9
  163. package/src/lab/Lab.tsx +88 -73
  164. package/src/lab/{LabFit.stories.less → LabFit.browser.test.less} +1 -1
  165. package/src/lab/{LabFit.stories.tsx → LabFit.browser.test.tsx} +41 -63
  166. package/src/lab/LabFullChrome.stories.tsx +1 -0
  167. package/src/lab/LabHeader.test.tsx +24 -14
  168. package/src/lab/LabHeader.tsx +11 -12
  169. package/src/lab/LabShell.less +9 -17
  170. package/src/lab/LabSwitcher.browser.test.tsx +27 -0
  171. package/src/lab/LabSwitcher.less +8 -6
  172. package/src/lab/LabZoom.test.tsx +223 -0
  173. package/src/lab/LabZoom.tsx +170 -0
  174. package/src/lab/Workspace.tsx +48 -52
  175. package/src/lab/index.ts +1 -0
  176. package/src/loupe/AGENTS.md +28 -15
  177. package/src/loupe/CanvasLoupe.tsx +2 -3
  178. package/src/loupe/DomLoupe.tsx +1 -7
  179. package/src/loupe/Loupe.less +16 -0
  180. package/src/loupe/LoupeGestures.tsx +1 -1
  181. package/src/loupe/TrialLoupe.tsx +120 -57
  182. package/src/loupe/index.ts +1 -6
  183. package/src/loupe/types.test.ts +3 -3
  184. package/src/loupe/types.ts +24 -27
  185. package/src/loupe/useLoupe.test.tsx +29 -4
  186. package/src/loupe/useLoupe.ts +20 -17
  187. package/src/overview/OverviewMarks.tsx +12 -4
  188. package/src/overview/TrialOverview.tsx +10 -8
  189. package/src/passthrough/weasel-ui.test.ts +2 -0
  190. package/src/passthrough/weasel-ui.ts +17 -5
  191. package/src/primitives/FloatingPanel.test.tsx +11 -0
  192. package/src/primitives/FloatingPanel.tsx +8 -7
  193. package/src/primitives/JobProgress.less +2 -1
  194. package/src/primitives/Legend.stories.tsx +1 -0
  195. package/src/primitives/Readout.browser.test.tsx +137 -0
  196. package/src/primitives/Readout.less +12 -0
  197. package/src/primitives/Readout.stories.tsx +58 -0
  198. package/src/primitives/Readout.test.tsx +93 -0
  199. package/src/primitives/Readout.tsx +56 -0
  200. package/src/primitives/Split.tsx +69 -41
  201. package/src/primitives/Toolbar.tsx +3 -3
  202. package/src/primitives/ZoomControl.less +1 -2
  203. package/src/primitives/index.ts +2 -0
  204. package/src/specimen/Specimen.stories.tsx +1 -0
  205. package/src/specimen/Specimen.tsx +2 -3
  206. package/src/state/Persistence.browser.test.tsx +47 -0
  207. package/src/state/store.test.ts +5 -1
  208. package/src/state/undock.ts +3 -0
  209. package/src/state/useOpenOnce.ts +20 -16
  210. package/src/state/usePersistedState.test.tsx +1 -1
  211. package/src/state/view.ts +1 -0
  212. package/src/styles.less +1 -0
  213. package/src/surface/index.ts +1 -1
  214. package/src/surface/useTiledSurface.test.tsx +14 -0
  215. package/src/surface/useTiledSurface.ts +11 -3
  216. package/src/theme/Interstellar.stories.tsx +3 -1
  217. package/src/theme/base.less +21 -47
  218. package/src/theme/interstellar.test.ts +1 -1
  219. package/src/theme/interstellar.theme.json +10 -3
  220. package/src/trial/Trial.annotations.test.tsx +73 -0
  221. package/src/trial/Trial.less +0 -29
  222. package/src/trial/Trial.loupe.test.tsx +28 -11
  223. package/src/trial/Trial.tsx +85 -67
  224. package/src/trial/TrialChrome.tsx +27 -6
  225. package/src/trial/index.ts +2 -0
  226. package/src/trial/loupeSwitch.ts +15 -0
  227. package/dist/_dts/LayerList.d-BoELAMsM.d.ts +0 -83
  228. package/dist/_dts/PrefsRow.d-DFpPIeIC.d.ts +0 -39
  229. package/dist/_dts/PropertyField.d-D0ndfIIt.d.ts +0 -404
  230. package/dist/_dts/Select.d-Cr8-zoup.d.ts +0 -569
  231. package/dist/chunk-4TT3PNPR.js.map +0 -1
  232. package/dist/chunk-6FLXL3I7.js.map +0 -1
  233. package/dist/chunk-ASOK76ZH.js.map +0 -1
  234. package/dist/chunk-CGHF6RZV.js.map +0 -1
  235. package/dist/chunk-CQLQPQ4P.js.map +0 -1
  236. package/dist/chunk-DWMSB4CA.js.map +0 -1
  237. package/dist/chunk-HXXTULDN.js.map +0 -1
  238. package/dist/chunk-IJJUK4FU.js.map +0 -1
  239. package/dist/chunk-IRTGUQ5O.js.map +0 -1
  240. package/dist/chunk-KHK5FGER.js +0 -145
  241. package/dist/chunk-KHK5FGER.js.map +0 -1
  242. package/dist/chunk-NEXR37YJ.js.map +0 -1
  243. package/dist/chunk-O6FFFOHS.js.map +0 -1
  244. package/dist/chunk-QGMOA2YE.js.map +0 -1
  245. package/dist/chunk-RWNDDKOH.js.map +0 -1
  246. package/dist/chunk-S4NCI32V.js +0 -549
  247. package/dist/chunk-S4NCI32V.js.map +0 -1
  248. package/dist/chunk-SBXNQW4G.js +0 -3
  249. package/dist/chunk-SLAQPUI4.js.map +0 -1
  250. package/dist/chunk-TJ77A4A3.js.map +0 -1
  251. package/dist/chunk-TYN6MLXX.js +0 -13864
  252. package/dist/chunk-TYN6MLXX.js.map +0 -1
  253. package/dist/chunk-WPLAANH4.js.map +0 -1
  254. package/src/primitives/useRovingTabIndex.test.ts +0 -48
  255. package/src/primitives/useRovingTabIndex.ts +0 -88
  256. package/src/state/Persistence.stories.tsx +0 -75
@@ -1,22 +1,7 @@
1
- import { resolveScreenLength, SCRIPT_METRICS, boundsOfPath, getMarker, PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, resolveAlign, getPaintKind } from '@weasel-js/core';
1
+ import { identity, multiply } from './chunk-XVUKDSYV.js';
2
+ import { resolveScreenLength, scriptMetricsFor, boundsOfPath, getMarker, PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, resolveAlign, resolveRunFace, resolveTextStyle, parseColor, rgbaToHex, getPaintKind, resolveMarkerSize } from '@weasel-js/core';
2
3
  import { tileGeometry } from '@weasel-js/core/patterns-builtin';
3
4
 
4
- // ../svg/dist/index.js
5
-
6
- // ../geom/dist/index.js
7
- function identity() {
8
- return [1, 0, 0, 1, 0, 0];
9
- }
10
- function multiply(m, n) {
11
- return [
12
- m[0] * n[0] + m[2] * n[1],
13
- m[1] * n[0] + m[3] * n[1],
14
- m[0] * n[2] + m[2] * n[3],
15
- m[1] * n[2] + m[3] * n[3],
16
- m[0] * n[4] + m[2] * n[5] + m[4],
17
- m[1] * n[4] + m[3] * n[5] + m[5]
18
- ];
19
- }
20
5
  var IDENTITY_MATRIX = identity();
21
6
  function isIdentity(m, eps = 1e-9) {
22
7
  return Math.abs(m[0] - 1) < eps && Math.abs(m[1]) < eps && Math.abs(m[2]) < eps && Math.abs(m[3] - 1) < eps && Math.abs(m[4]) < eps && Math.abs(m[5]) < eps;
@@ -31,10 +16,201 @@ function trimNumber(n) {
31
16
  if (Number.isInteger(r)) return String(r);
32
17
  return String(r);
33
18
  }
34
- function anchorOffset(style, width) {
19
+ function anchorOffset(style, width2) {
35
20
  const edge = resolveAlign(style?.align ?? "left", style?.direction ?? "ltr");
36
- return edge === "center" ? width / 2 : edge === "right" ? width : 0;
21
+ return edge === "center" ? width2 / 2 : edge === "right" ? width2 : 0;
22
+ }
23
+ function parsePaintAttr(raw) {
24
+ if (raw == null) return null;
25
+ const s = raw.trim();
26
+ if (s === "") return null;
27
+ if (s === "none") return { kind: "none" };
28
+ if (s.toLowerCase() === "currentcolor") {
29
+ return { kind: "solid", color: "#000000", alpha: 1 };
30
+ }
31
+ const urlMatch = /^url\(\s*#([^)\s]+)\s*\)(.*)$/.exec(s);
32
+ if (urlMatch) {
33
+ const out = { kind: "ref", id: urlMatch[1] };
34
+ const rest = urlMatch[2].trim();
35
+ if (rest !== "" && rest !== "none") {
36
+ const fallback = parsePaintAttr(rest);
37
+ if (fallback?.kind === "solid") {
38
+ out.fallback = { color: fallback.color, alpha: fallback.alpha };
39
+ }
40
+ }
41
+ return out;
42
+ }
43
+ try {
44
+ const [r, g, b, a] = parseColor(s);
45
+ return { kind: "solid", color: rgbaToHex([r, g, b]), alpha: a };
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+ var TEXT_TRANSFORMS = /* @__PURE__ */ new Set(["none", "uppercase", "lowercase", "capitalize"]);
51
+ var DIMENSION = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)([a-z%]*)$/i;
52
+ function dimension(v) {
53
+ const m = DIMENSION.exec(v);
54
+ return m ? { n: Number(m[1]), unit: m[2].toLowerCase() } : void 0;
55
+ }
56
+ var clamp01 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
57
+ var length = (native, coercedAs) => (v, warn, prop) => {
58
+ const d = dimension(v);
59
+ if (!d) return void 0;
60
+ if (d.unit && !native.includes(d.unit)) {
61
+ warn(`${prop} "${v}" uses unit "${d.unit}", which is not converted; treated as ${d.n} ${coercedAs}`);
62
+ }
63
+ return d.n;
64
+ };
65
+ var worldLength = length(["px"], "world units");
66
+ var keyword2 = (...words) => (v) => words.includes(v) ? v : void 0;
67
+ var isCurrentColor = (v) => v.toLowerCase() === "currentcolor";
68
+ function readPaint(v) {
69
+ if (v === "context-fill") return { kind: "context", of: "fill" };
70
+ if (v === "context-stroke") return { kind: "context", of: "stroke" };
71
+ return parsePaintAttr(v) ?? void 0;
72
+ }
73
+ function readRatio(v) {
74
+ const d = dimension(v);
75
+ if (!d || d.unit !== "" && d.unit !== "%") return void 0;
76
+ return clamp01(d.unit === "%" ? d.n / 100 : d.n);
77
+ }
78
+ function readSolidColor(v, warn, prop) {
79
+ const parsed = parsePaintAttr(v);
80
+ if (parsed?.kind !== "solid") return void 0;
81
+ if (isCurrentColor(v)) warn(`${prop}="${v}" is read as black; it is not resolved against color`);
82
+ return parsed;
83
+ }
84
+ function readMarkerRef(v) {
85
+ if (v === "none") return null;
86
+ return /^url\(\s*#([^)\s]+)\s*\)$/.exec(v)?.[1];
87
+ }
88
+ function readLinecap(v, warn) {
89
+ const cap = keyword2("butt", "round", "square")(v);
90
+ if (!cap) warn(`unsupported stroke-linecap: ${v}`);
91
+ return cap;
92
+ }
93
+ function readLinejoin(v, warn) {
94
+ const join = keyword2("miter", "round", "bevel")(v);
95
+ if (join) return join;
96
+ if (v === "arcs" || v === "miter-clip") {
97
+ warn(`stroke-linejoin "${v}" not supported; falling back to miter`);
98
+ return "miter";
99
+ }
100
+ warn(`unsupported stroke-linejoin: ${v}`);
101
+ return void 0;
102
+ }
103
+ function readDashArray(v, warn) {
104
+ if (v === "none" || v === "") return null;
105
+ const nums = [];
106
+ for (const t of v.split(/[\s,]+/).filter(Boolean)) {
107
+ const d = dimension(t);
108
+ if (!d || d.unit !== "" && d.unit !== "px" || d.n < 0) {
109
+ warn(`unrecognized stroke-dasharray: ${v}`);
110
+ return void 0;
111
+ }
112
+ nums.push(d.n);
113
+ }
114
+ return nums.length % 2 === 1 ? [...nums, ...nums] : nums;
115
+ }
116
+ function readMiterLimit(v, warn) {
117
+ const d = dimension(v);
118
+ if (d && d.unit === "" && d.n >= 1) return d.n;
119
+ warn(`unrecognized stroke-miterlimit: ${v}`);
120
+ return void 0;
121
+ }
122
+ function readColor(v) {
123
+ return isCurrentColor(v) || parsePaintAttr(v)?.kind === "solid" ? v : void 0;
124
+ }
125
+ function readFontSize(v, warn, prop) {
126
+ const d = dimension(v);
127
+ if (d?.unit === "%") return { fontScale: d.n / 100 };
128
+ const n = worldLength(v, warn, prop);
129
+ return n == null ? void 0 : { fontSize: n };
130
+ }
131
+ function readFontWeight(v) {
132
+ if (v === "normal" || v === "bold" || v === "bolder") return v;
133
+ const d = dimension(v);
134
+ return d && d.unit === "" && d.n >= 1 && d.n <= 1e3 ? d.n : void 0;
135
+ }
136
+ function readFontStyle(v, warn) {
137
+ if (v === "normal" || v === "italic") return v;
138
+ if (v === "oblique") {
139
+ warn('font-style "oblique" is read as italic');
140
+ return "italic";
141
+ }
142
+ return void 0;
143
+ }
144
+ function readLetterSpacing(v, warn, prop) {
145
+ return v === "normal" ? null : worldLength(v, warn, prop);
146
+ }
147
+ function readDecoration(v, warn) {
148
+ const out = {};
149
+ for (const t of v.toLowerCase().split(/\s+/)) {
150
+ if (t === "underline") out.underline = true;
151
+ else if (t === "line-through") out.strikethrough = true;
152
+ else if (t === "overline") out.overline = true;
153
+ else if (t !== "none" && t !== "blink") warn(`text-decoration "${v}": "${t}" is not modeled; dropped`);
154
+ }
155
+ return out;
156
+ }
157
+ function readFontVariant(v, warn) {
158
+ let out;
159
+ for (const t of v.toLowerCase().split(/\s+/)) {
160
+ if (t === "small-caps" || t === "normal") out = t;
161
+ else if (t) warn(`font-variant "${v}": "${t}" is not modeled; dropped`);
162
+ }
163
+ return out;
37
164
  }
165
+ function readTextTransform(v) {
166
+ const t = v.toLowerCase();
167
+ return TEXT_TRANSFORMS.has(t) ? t : void 0;
168
+ }
169
+ var emLength = length(["em"], "em");
170
+ function readBaselineShift(v, warn, prop) {
171
+ const k = v.toLowerCase();
172
+ if (k === "super" || k === "sub") return { script: k };
173
+ if (k === "baseline") return {};
174
+ const d = dimension(v);
175
+ if (d?.unit === "%") return d.n === 0 ? {} : { baselineShift: d.n / 100 };
176
+ const n = emLength(v, warn, prop);
177
+ if (n == null) return void 0;
178
+ return n === 0 ? {} : { baselineShift: n };
179
+ }
180
+ var inherited = (read) => ({ inherits: true, read });
181
+ var own = (read) => ({ inherits: false, read });
182
+ var PROPERTIES = {
183
+ "fill": inherited(readPaint),
184
+ "fill-opacity": inherited(readRatio),
185
+ "fill-rule": inherited(keyword2("nonzero", "evenodd")),
186
+ "stroke": inherited(readPaint),
187
+ "stroke-width": inherited(worldLength),
188
+ "stroke-opacity": inherited(readRatio),
189
+ "stroke-linecap": inherited(readLinecap),
190
+ "stroke-linejoin": inherited(readLinejoin),
191
+ "stroke-dasharray": inherited(readDashArray),
192
+ "stroke-miterlimit": inherited(readMiterLimit),
193
+ "marker-start": inherited(readMarkerRef),
194
+ "marker-mid": inherited(readMarkerRef),
195
+ "marker-end": inherited(readMarkerRef),
196
+ "color": inherited(readColor),
197
+ "font-size": inherited(readFontSize),
198
+ "font-family": inherited((v) => v.length > 0 ? v : void 0),
199
+ "font-weight": inherited(readFontWeight),
200
+ "font-style": inherited(readFontStyle),
201
+ "text-anchor": inherited(keyword2("start", "middle", "end")),
202
+ "letter-spacing": inherited(readLetterSpacing),
203
+ "text-decoration": inherited(readDecoration),
204
+ "direction": inherited(keyword2("ltr", "rtl")),
205
+ "text-transform": inherited(readTextTransform),
206
+ "font-variant": inherited(readFontVariant),
207
+ "opacity": own(readRatio),
208
+ "stop-color": own(readSolidColor),
209
+ "stop-opacity": own(readRatio),
210
+ "baseline-shift": own(readBaselineShift),
211
+ "vector-effect": own(keyword2("none", "non-scaling-stroke"))
212
+ };
213
+ Object.keys(PROPERTIES).filter((p) => PROPERTIES[p].inherits);
38
214
  function patternSpecOf(paint) {
39
215
  return "tile" in paint.pattern ? paint.pattern : null;
40
216
  }
@@ -53,7 +229,11 @@ function patternXml(id, paint, onWarn) {
53
229
  `width="${trimNumber(geometry.size)}"`,
54
230
  `height="${trimNumber(geometry.size)}"`
55
231
  ];
56
- if (origin.x !== 0 || origin.y !== 0) {
232
+ const [a, b, c, d] = paint.transform ?? [1, 0, 0, 1];
233
+ const placement = formatMatrix([a, b, c, d, origin.x, origin.y]);
234
+ if (paint.transform && !isIdentity([a, b, c, d, 0, 0]) && placement) {
235
+ attrs.push(`patternTransform="${placement}"`);
236
+ } else if (origin.x !== 0 || origin.y !== 0) {
57
237
  attrs.push(`x="${trimNumber(origin.x)}"`, `y="${trimNumber(origin.y)}"`);
58
238
  }
59
239
  attrs.push(`data-weasel-tile="${escapeAttr(JSON.stringify(spec))}"`);
@@ -90,8 +270,8 @@ function escapeAttr(s) {
90
270
  }
91
271
  function serializePathD(path) {
92
272
  if (path.kind === "rect") {
93
- const { x, y, width, height } = path;
94
- return `M${trimNumber(x)} ${trimNumber(y)}h${trimNumber(width)}v${trimNumber(height)}h${trimNumber(-width)}Z`;
273
+ const { x, y, width: width2, height: height2 } = path;
274
+ return `M${trimNumber(x)} ${trimNumber(y)}h${trimNumber(width2)}v${trimNumber(height2)}h${trimNumber(-width2)}Z`;
95
275
  }
96
276
  const cmds = path.commands;
97
277
  const xs = path.coords;
@@ -123,29 +303,35 @@ function serializePathD(path) {
123
303
  }
124
304
  var WEASEL_NS = "urn:weasel-js:svg";
125
305
  var WEASEL_NS_PREFIX = "wzl";
126
- function hasNativeSvgForm(paint) {
127
- const kind = paint.fill ?? "solid";
306
+ function nativeSvgKind(kind) {
128
307
  return kind === "solid" || kind === "linear-gradient" || kind === "radial-gradient" || kind === "pattern";
129
308
  }
309
+ function hasNativeSvgForm(paint) {
310
+ return nativeSvgKind(paint.fill ?? "solid");
311
+ }
130
312
  var INTERPOLATE_SPACES = /* @__PURE__ */ new Set(["rgb", "oklab", "oklch"]);
313
+ function nativeSvgSpace(space) {
314
+ return !space || space === "rgb";
315
+ }
131
316
  function interpolateAttr(space) {
132
- if (!space || space === "rgb") return "";
317
+ if (nativeSvgSpace(space)) return "";
133
318
  return ` ${WEASEL_NS_PREFIX}:interpolate="${space}"`;
134
319
  }
135
320
  function usesInterpolateAttr(paint) {
136
321
  const space = paint.interpolate;
137
- return space != null && space !== "rgb" && INTERPOLATE_SPACES.has(space);
322
+ return !nativeSvgSpace(space) && INTERPOLATE_SPACES.has(space);
138
323
  }
139
324
  var PaintServerRegistry = class {
140
325
  byPaint = /* @__PURE__ */ new Map();
141
326
  order = [];
142
327
  counter = 0;
143
- // Marker keys referenced by any stroke, in first-use order. The `<defs>`
144
- // id is the marker key itself, not a minted counter id: `parseSvg` reads a
145
- // `marker-end="url(#id)"` fragment back as the marker key directly when the
146
- // registry knows it, so a synthetic id would come back as a stranger.
147
- markerKeys = [];
148
- markerKeySet = /* @__PURE__ */ new Set();
328
+ // Marker defs referenced by any stroke, by `<defs>` id in first-use order.
329
+ // An unsized reference's id is the marker key itself, not a minted counter
330
+ // id: `parseSvg` reads a `marker-end="url(#id)"` fragment back as the marker
331
+ // key directly when the registry knows it, so a synthetic id would come
332
+ // back as a stranger. A sized one gets a def of its own, stamped with its
333
+ // key and size so the parser can read the reference back whole.
334
+ markers = /* @__PURE__ */ new Map();
149
335
  // Clip paths, in first-use order. Identity, not value: two groups clipped by
150
336
  // equal outlines get a def each, which costs bytes and misleads nobody.
151
337
  clips = [];
@@ -170,22 +356,26 @@ var PaintServerRegistry = class {
170
356
  if (hasNativeSvgForm(paint)) return `url(#${id})`;
171
357
  return `url(#${id}) ${paintFallback(paint)}`;
172
358
  }
359
+ /** The paint kind of each registered paint, once, in first-use order. */
360
+ paintKinds() {
361
+ return [...new Set(this.order.map((paint) => paint.fill ?? "solid"))];
362
+ }
173
363
  /** Whether any registered paint serializes outside SVG's own vocabulary, so
174
364
  * the root has to declare {@link WEASEL_NS}. */
175
365
  usesPrivateNamespace() {
176
- return this.order.some((paint) => !hasNativeSvgForm(paint) || usesInterpolateAttr(paint));
366
+ return this.order.some((paint) => !hasNativeSvgForm(paint) || usesInterpolateAttr(paint)) || [...this.markers.values()].some((m) => m.size !== void 0 || writesInset(getMarker(m.key)));
177
367
  }
178
- /** The `<defs>` id for a marker key — the key itself, minting nothing.
179
- * `undefined` when nothing is registered under `key`, so a caller emits no
180
- * attribute at all rather than a `url(#…)` pointing at a def that will
181
- * never be written. */
182
- markerId(key) {
368
+ /** The `<defs>` id for a marker reference — the key itself when it has no
369
+ * size, else the key suffixed with the size. `undefined` when nothing is
370
+ * registered under the key, so a caller emits no attribute at all rather
371
+ * than a `url(#…)` pointing at a def that will never be written. */
372
+ markerId(ref) {
373
+ const key = typeof ref === "string" ? ref : ref.key;
374
+ const size = typeof ref === "string" ? void 0 : ref.size;
183
375
  if (getMarker(key) === void 0) return void 0;
184
- if (!this.markerKeySet.has(key)) {
185
- this.markerKeySet.add(key);
186
- this.markerKeys.push(key);
187
- }
188
- return key;
376
+ const id = size === void 0 ? key : `${key}-s${typeof size === "number" ? trimNumber(size) : `${trimNumber(size.px)}px`}`;
377
+ if (!this.markers.has(id)) this.markers.set(id, size === void 0 ? { key } : { key, size });
378
+ return id;
189
379
  }
190
380
  /** Mint (or recall) the `<defs>` id for a clip outline. */
191
381
  clipId(path) {
@@ -199,17 +389,22 @@ var PaintServerRegistry = class {
199
389
  /** Emit `<defs>...</defs>` XML for every registered paint server, marker
200
390
  * and clip path. */
201
391
  toDefsXml(onWarn) {
202
- if (this.order.length === 0 && this.markerKeys.length === 0 && this.clips.length === 0) {
392
+ if (this.order.length === 0 && this.markers.size === 0 && this.clips.length === 0) {
203
393
  return "";
204
394
  }
205
395
  const parts = ["<defs>"];
206
396
  for (const paint of this.order) {
207
397
  parts.push(paintServerXml(this.byPaint.get(paint), paint, onWarn));
208
398
  }
209
- for (const key of this.markerKeys) {
399
+ for (const [id, { key, size }] of this.markers) {
210
400
  const entry = getMarker(key);
211
401
  if (!entry) continue;
212
- parts.push(entry.toSvg ? entry.toSvg(key, entry) : defaultMarkerXml(key, entry, onWarn));
402
+ if (entry.toSvg) {
403
+ if (size !== void 0) onWarn?.(`marker "${key}" writes its own <marker> def, which cannot carry a reference's size; written at its own size`);
404
+ parts.push(entry.toSvg(id, entry));
405
+ } else {
406
+ parts.push(defaultMarkerXml(id, entry, onWarn, key, size));
407
+ }
213
408
  }
214
409
  for (const path of this.clips) {
215
410
  parts.push(
@@ -220,13 +415,19 @@ var PaintServerRegistry = class {
220
415
  return parts.join("");
221
416
  }
222
417
  };
223
- function defaultMarkerXml(id, entry, onWarn) {
224
- const path = entry.path({ size: 1, stroke: { paint: { fill: "solid", color: "#000" } } });
418
+ function writesInset(entry) {
419
+ return entry !== void 0 && !entry.toSvg && (entry.inset ?? 0) !== 0;
420
+ }
421
+ function defaultMarkerXml(id, entry, onWarn, key, size) {
422
+ const unit = size === void 0 ? 1 : resolveMarkerSize({ key: key ?? id, size }, 1);
423
+ const path = entry.path({ size: unit, stroke: { paint: { fill: "solid", color: "#000" } } });
225
424
  const d = serializePathD(path);
226
425
  const fill = markerPaintAttrs("fill", entry.fill, id, onWarn);
227
- const outline = entry.outline ? ` ${markerPaintAttrs("stroke", entry.outline.paint, id, onWarn)} stroke-width="${trimNumber(entry.outline.width)}" stroke-linecap="round" stroke-linejoin="round"` : "";
426
+ const outline = entry.outline ? ` ${markerPaintAttrs("stroke", entry.outline.paint, id, onWarn)} stroke-width="${trimNumber(entry.outline.width * unit)}" stroke-linecap="round" stroke-linejoin="round"` : "";
228
427
  const orient = typeof entry.orient === "number" ? trimNumber(entry.orient * 180 / Math.PI) : "auto-start-reverse";
229
- return `<marker id="${id}" markerUnits="strokeWidth" markerWidth="8" markerHeight="8" refX="0" refY="0" orient="${orient}" overflow="visible"><path d="${d}" ${fill}${outline}/></marker>`;
428
+ 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`}"`;
429
+ const inset = writesInset(entry) ? ` ${WEASEL_NS_PREFIX}:inset="${trimNumber(entry.inset)}"` : "";
430
+ return `<marker id="${id}" ${units}${inset} refX="0" refY="0" orient="${orient}" overflow="visible"><path d="${d}" ${fill}${outline}/></marker>`;
230
431
  }
231
432
  function markerPaintAttrs(attr, paint, id, onWarn) {
232
433
  if (paint === "none") return `${attr}="none"`;
@@ -241,9 +442,10 @@ function paintServerXml(id, paint, onWarn) {
241
442
  if (paint.fill === "pattern") return patternXml(id, paint, onWarn);
242
443
  const builtin = gradientXml(id, paint);
243
444
  if (builtin) return builtin;
244
- const custom = getPaintKind(paint.fill)?.toSvg?.(id, paint);
445
+ const kind = getPaintKind(paint.fill);
446
+ const custom = kind?.toSvg?.(id, paint);
245
447
  if (custom) return custom;
246
- onWarn?.(`${paint.fill} fill has no vector form \u2014 omitted from <defs>`);
448
+ 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`);
247
449
  return "";
248
450
  }
249
451
  function gradientUnitsAttr(units) {
@@ -388,16 +590,10 @@ function registerPaintServers(nodes, registry) {
388
590
  function registerTextPaint(paint, registry) {
389
591
  if (paint && !("color" in paint)) registry.register(paint);
390
592
  }
391
- function markerKeyOfRef(ref) {
392
- if (typeof ref === "string") return ref;
393
- if (ref && typeof ref === "object" && "key" in ref) return String(ref.key);
394
- return void 0;
395
- }
396
593
  function registerMarkers(stroke, registry) {
397
594
  if (!stroke) return;
398
595
  for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {
399
- const key = markerKeyOfRef(ref);
400
- if (key) registry.markerId(key);
596
+ if (ref !== void 0) registry.markerId(ref);
401
597
  }
402
598
  }
403
599
  function nodeXml(node, registry, namespaces, warn) {
@@ -486,35 +682,30 @@ function pathXml(node, registry, namespaces, warn) {
486
682
  }
487
683
  function paintAttrs(paint, name, registry, includeOpacity = true) {
488
684
  if (paint.kind === "none") return [`${name}="none"`];
489
- if (paint.kind === "solid") {
490
- const out = [`${name}="${paint.color}"`];
491
- if (includeOpacity && paint.opacity != null && paint.opacity !== 1) {
492
- out.push(`${name}-opacity="${trimNumber(paint.opacity)}"`);
493
- }
494
- return out;
495
- }
496
- return [`${name}="${registry.ref(paint.paint)}"`];
685
+ const out = [`${name}="${paint.kind === "solid" ? paint.color : registry.ref(paint.paint)}"`];
686
+ if (includeOpacity) out.push(...opacityAttr(`${name}-opacity`, svgPaintOpacity(paint)));
687
+ return out;
688
+ }
689
+ function svgPaintOpacity(paint) {
690
+ if (paint.kind === "solid") return paint.opacity;
691
+ return paint.kind === "gradient" ? paint.paint.opacity : void 0;
692
+ }
693
+ function opacityAttr(name, v) {
694
+ return v != null && v !== 1 ? [`${name}="${trimNumber(v)}"`] : [];
497
695
  }
498
- function strokeWidthAttrs(width) {
499
- if (typeof width === "number") return [`stroke-width="${trimNumber(width)}"`];
500
- return [`stroke-width="${trimNumber(width.px)}"`, 'vector-effect="non-scaling-stroke"'];
696
+ function strokeWidthAttrs(width2) {
697
+ if (typeof width2 === "number") return [`stroke-width="${trimNumber(width2)}"`];
698
+ return [`stroke-width="${trimNumber(width2.px)}"`, 'vector-effect="non-scaling-stroke"'];
501
699
  }
502
700
  function coreStrokeAttrs(stroke, registry, warn) {
503
701
  const paint = stroke?.paint;
504
702
  if (!stroke || !paint) return [];
505
- const width = stroke.width ?? 1;
506
- if (!((typeof width === "object" ? width.px : width) > 0)) return [];
703
+ const width2 = stroke.width ?? 1;
704
+ if (!((typeof width2 === "object" ? width2.px : width2) > 0)) return [];
507
705
  warnStrokeAlign(stroke.align, warn);
508
- const attrs = [];
509
- if ("color" in paint) {
510
- attrs.push(`stroke="${paint.color}"`);
511
- if (paint.opacity != null && paint.opacity !== 1) {
512
- attrs.push(`stroke-opacity="${trimNumber(paint.opacity)}"`);
513
- }
514
- } else {
515
- attrs.push(`stroke="${registry.ref(paint)}"`);
516
- }
517
- attrs.push(...strokeWidthAttrs(width));
706
+ const attrs = [`stroke="${"color" in paint ? paint.color : registry.ref(paint)}"`];
707
+ attrs.push(...opacityAttr("stroke-opacity", paint.opacity));
708
+ attrs.push(...strokeWidthAttrs(width2));
518
709
  if (stroke.cap) attrs.push(`stroke-linecap="${stroke.cap}"`);
519
710
  if (stroke.join) attrs.push(`stroke-linejoin="${stroke.join}"`);
520
711
  if (stroke.dash && stroke.dash.length > 0) {
@@ -528,8 +719,7 @@ function coreStrokeAttrs(stroke, registry, warn) {
528
719
  ]) {
529
720
  const ref = stroke[field];
530
721
  if (ref === void 0) continue;
531
- const key = typeof ref === "string" ? ref : ref.key;
532
- const id = registry.markerId(key);
722
+ const id = registry.markerId(ref);
533
723
  if (id === void 0) continue;
534
724
  attrs.push(`${attr}="url(#${id})"`);
535
725
  }
@@ -539,10 +729,7 @@ function strokeAttrsFor(stroke, registry, warn) {
539
729
  warnStrokeAlign(stroke.align, warn);
540
730
  const attrs = paintAttrs(stroke.paint, "stroke", registry, false);
541
731
  attrs.push(...strokeWidthAttrs(stroke.width));
542
- const opacity = stroke.opacity ?? (stroke.paint.kind === "solid" ? stroke.paint.opacity : void 0);
543
- if (opacity != null && opacity !== 1) {
544
- attrs.push(`stroke-opacity="${trimNumber(opacity)}"`);
545
- }
732
+ attrs.push(...opacityAttr("stroke-opacity", stroke.opacity ?? svgPaintOpacity(stroke.paint)));
546
733
  if (stroke.cap) {
547
734
  attrs.push(`stroke-linecap="${stroke.cap}"`);
548
735
  }
@@ -619,18 +806,21 @@ function textXml(node, registry, namespaces, warn) {
619
806
  if (style?.fontFamily) attrs.push(`font-family="${escapeAttr2(style.fontFamily)}"`);
620
807
  if (style?.fontWeight != null) attrs.push(`font-weight="${String(style.fontWeight)}"`);
621
808
  if (style?.fontStyle && style.fontStyle !== "normal") attrs.push(`font-style="${style.fontStyle}"`);
809
+ if (style?.fontVariantCaps === "small-caps") attrs.push('font-variant="small-caps"');
622
810
  const direction = style?.direction ?? "ltr";
623
811
  if (direction === "rtl") attrs.push('direction="rtl"');
624
812
  if (style?.align != null || direction === "rtl") {
625
813
  const align = style?.align ?? "left";
626
- const anchor = align === "center" ? "middle" : align === "start" ? "start" : align === "end" ? "end" : align === "left" === (direction === "ltr") ? "start" : "end";
814
+ const anchor = align === "center" ? "middle" : align === "start" || align === "justify" ? "start" : align === "end" ? "end" : align === "left" === (direction === "ltr") ? "start" : "end";
627
815
  if (anchor !== "start") attrs.push(`text-anchor="${anchor}"`);
816
+ if (align === "justify") attrs.push('data-weasel-align="justify"');
628
817
  }
629
818
  if (style?.letterSpacing != null && style.letterSpacing !== 0) {
630
819
  attrs.push(`letter-spacing="${trimNumber(resolveScreenLength(style.letterSpacing, 1))}"`);
631
820
  }
632
821
  const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
633
822
  if (decoration) attrs.push(`text-decoration="${decoration}"`);
823
+ if (style?.script) attrs.push(`baseline-shift="${style.script}"`);
634
824
  if (style?.textTransform && style.textTransform !== "none") {
635
825
  attrs.push(`style="text-transform:${style.textTransform}"`);
636
826
  }
@@ -645,14 +835,8 @@ function textXml(node, registry, namespaces, warn) {
645
835
  if (node.fill === null) {
646
836
  attrs.push('fill="none"');
647
837
  } else if (node.fill) {
648
- if ("color" in node.fill) {
649
- attrs.push(`fill="${node.fill.color}"`);
650
- if (node.fill.opacity != null && node.fill.opacity !== 1) {
651
- attrs.push(`fill-opacity="${trimNumber(node.fill.opacity)}"`);
652
- }
653
- } else {
654
- attrs.push(`fill="${registry.ref(node.fill)}"`);
655
- }
838
+ attrs.push(`fill="${"color" in node.fill ? node.fill.color : registry.ref(node.fill)}"`);
839
+ attrs.push(...opacityAttr("fill-opacity", node.fill.opacity));
656
840
  }
657
841
  for (const a of coreStrokeAttrs(node.stroke, registry, warn)) attrs.push(a);
658
842
  if (node.opacity != null && node.opacity !== 1) {
@@ -664,17 +848,25 @@ function textXml(node, registry, namespaces, warn) {
664
848
  const deg = node.rotation * 180 / Math.PI;
665
849
  attrs.push(`transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`);
666
850
  }
667
- const body = node.runs && node.runs.length > 0 ? node.runs.map((r) => runXml(r, registry, warn)).join("") : escapeText(node.text);
851
+ const body = node.runs && node.runs.length > 0 ? node.runs.map((r) => runXml(r, style, registry, warn)).join("") : escapeText(node.text);
668
852
  const metaAttrs = metaAttrsXml(node.meta, namespaces);
669
853
  const metaEls = metaElementsXml(node.meta, namespaces);
670
854
  return `<text ${attrs.join(" ")}${metaAttrs}>${body}${metaEls}</text>`;
671
855
  }
672
- function runXml(run, registry, warn) {
856
+ function scriptSizeFace(run, style) {
857
+ if (run.script == null || run.baselineShift == null) return void 0;
858
+ const face = resolveRunFace(run, resolveTextStyle(style));
859
+ return { family: face.fontFamily, weight: face.fontWeight, style: face.fontStyle };
860
+ }
861
+ function runXml(run, style, registry, warn) {
673
862
  const attrs = [];
674
- if (run.bold) attrs.push(`font-weight="700"`);
863
+ if (run.fontWeight != null) attrs.push(`font-weight="${run.fontWeight}"`);
864
+ else if (run.bold) attrs.push(`font-weight="700"`);
675
865
  if (run.italic) attrs.push(`font-style="italic"`);
866
+ if (run.fontVariantCaps) attrs.push(`font-variant="${run.fontVariantCaps}"`);
676
867
  if (run.fontFamily) attrs.push(`font-family="${escapeAttr2(run.fontFamily)}"`);
677
- const presetScale = run.script != null && run.baselineShift != null ? SCRIPT_METRICS[run.script].size : void 0;
868
+ const face = scriptSizeFace(run, style);
869
+ const presetScale = face && run.script ? scriptMetricsFor(face.family, face.weight, face.style)[run.script].size : void 0;
678
870
  const scale = run.fontScale ?? presetScale;
679
871
  if (run.fontSize != null) attrs.push(`font-size="${trimNumber(resolveScreenLength(run.fontSize, 1))}"`);
680
872
  else if (scale != null) attrs.push(`font-size="${trimNumber(scale * 100)}%"`);
@@ -726,6 +918,6 @@ function pathBounds(path) {
726
918
  return { minX: b.x, minY: b.y, maxX: b.x + b.width, maxY: b.y + b.height };
727
919
  }
728
920
 
729
- export { serializeSvg, tilePreviewCssUrl };
730
- //# sourceMappingURL=chunk-QGMOA2YE.js.map
731
- //# sourceMappingURL=chunk-QGMOA2YE.js.map
921
+ export { nativeSvgKind, nativeSvgSpace, serializeSvg, tilePreviewCssUrl };
922
+ //# sourceMappingURL=chunk-E23Q6A3Y.js.map
923
+ //# sourceMappingURL=chunk-E23Q6A3Y.js.map