@weasel-js/labkit 1.0.1 → 1.0.3

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 (100) hide show
  1. package/dist/_dts/{DrawCommand-CitWPxMY.d.ts → DrawCommand-uKHt4Vul.d.ts} +56 -0
  2. package/dist/_dts/{index-C6Yze7sQ.d.ts → index-CFlDPeZh.d.ts} +30 -1
  3. package/dist/_dts/{types-Si4Fw-1F.d.ts → types-x92Kfeme.d.ts} +14 -0
  4. package/dist/_dts/{useExperimentState-CJn2hHzd.d.ts → useExperimentState-vrttakTt.d.ts} +43 -0
  5. package/dist/canvas/index.d.ts +13 -2
  6. package/dist/canvas/index.js +2 -2
  7. package/dist/{chunk-ISEK5LXT.js → chunk-3WPOGKUP.js} +1603 -1670
  8. package/dist/chunk-3WPOGKUP.js.map +1 -0
  9. package/dist/{chunk-54IQ2DX7.js → chunk-574LJAV4.js} +3 -3
  10. package/dist/chunk-574LJAV4.js.map +1 -0
  11. package/dist/{chunk-VLAHRJOC.js → chunk-5R2ATYPJ.js} +2 -2
  12. package/dist/{chunk-VLAHRJOC.js.map → chunk-5R2ATYPJ.js.map} +1 -1
  13. package/dist/{chunk-EXBV7A6X.js → chunk-73PXCRCR.js} +3 -3
  14. package/dist/chunk-73PXCRCR.js.map +1 -0
  15. package/dist/{chunk-PWC7AQZM.js → chunk-CPUJ3QXL.js} +2 -2
  16. package/dist/chunk-CPUJ3QXL.js.map +1 -0
  17. package/dist/{chunk-NRD3TDNQ.js → chunk-N5KTQKQA.js} +1462 -1487
  18. package/dist/chunk-N5KTQKQA.js.map +1 -0
  19. package/dist/{chunk-HXZHVU4G.js → chunk-PMAU3SEE.js} +3 -3
  20. package/dist/chunk-PMAU3SEE.js.map +1 -0
  21. package/dist/{chunk-7BKDG73Z.js → chunk-RL2LOLNI.js} +2 -2
  22. package/dist/chunk-RL2LOLNI.js.map +1 -0
  23. package/dist/{chunk-2QNYYL3V.js → chunk-SFL7NFKN.js} +2 -2
  24. package/dist/chunk-SFL7NFKN.js.map +1 -0
  25. package/dist/{chunk-53XSBIUK.js → chunk-T7OKNJTY.js} +2 -2
  26. package/dist/chunk-T7OKNJTY.js.map +1 -0
  27. package/dist/controls/index.d.ts +4 -2
  28. package/dist/controls/index.js +1 -1
  29. package/dist/dragdrop/index.d.ts +2 -2
  30. package/dist/index.d.ts +122 -6
  31. package/dist/index.js +18 -18
  32. package/dist/index.js.map +1 -1
  33. package/dist/layers/index.d.ts +6 -3
  34. package/dist/layers/index.js +1 -1
  35. package/dist/passthrough/weasel-canvas.d.ts +5 -1
  36. package/dist/passthrough/weasel-canvas.js +1 -1
  37. package/dist/passthrough/weasel-ui.d.ts +514 -36
  38. package/dist/passthrough/weasel-ui.js +2 -2
  39. package/dist/primitives/index.d.ts +13 -0
  40. package/dist/primitives/index.js +2 -2
  41. package/dist/state/index.d.ts +15 -2
  42. package/dist/state/index.js +2 -2
  43. package/dist/state/index.js.map +1 -1
  44. package/dist/styles.css +147 -0
  45. package/dist/ui/layers/index.d.ts +5 -0
  46. package/dist/ui/layers/index.js +3 -3
  47. package/dist/undo/index.d.ts +17 -2
  48. package/dist/undo/index.js +1 -1
  49. package/package.json +2 -2
  50. package/src/canvas/CanvasStack.tsx +4 -0
  51. package/src/canvas/CanvasStackContext.ts +3 -0
  52. package/src/canvas/canvasCoords.ts +2 -0
  53. package/src/canvas/useLayerScheduler.ts +2 -0
  54. package/src/controls/ControlPanel.tsx +2 -0
  55. package/src/controls/types.ts +14 -0
  56. package/src/instrument/capabilityDetector.ts +3 -0
  57. package/src/instrument/defineInstrument.ts +2 -0
  58. package/src/instrument/types.ts +29 -0
  59. package/src/instrument/validateConfigSchema.ts +5 -0
  60. package/src/lab/Lab.tsx +3 -0
  61. package/src/lab/LabContext.ts +5 -0
  62. package/src/lab/LabShell.tsx +4 -0
  63. package/src/lab/WorkspaceGrid.tsx +2 -0
  64. package/src/lab/gridDims.ts +2 -0
  65. package/src/layers/LayerList.tsx +3 -0
  66. package/src/primitives/FpsMeter.tsx +1 -0
  67. package/src/primitives/ScaleIndicator.tsx +4 -0
  68. package/src/primitives/Sidebar.tsx +3 -0
  69. package/src/primitives/StatusBar.tsx +3 -0
  70. package/src/primitives/Toolbar.tsx +6 -0
  71. package/src/state/SingletonExperiment.tsx +1 -0
  72. package/src/state/adapters.ts +8 -0
  73. package/src/state/context.tsx +9 -0
  74. package/src/state/helpers.ts +12 -0
  75. package/src/state/store.ts +6 -0
  76. package/src/state/types.ts +17 -0
  77. package/src/state/useExperimentState.ts +3 -0
  78. package/src/ui/layers/LayerStack.tsx +5 -0
  79. package/src/ui/properties/CurveField.tsx +4 -1
  80. package/src/ui/properties/EffectCard.tsx +12 -0
  81. package/src/ui/properties/PropertyGroup.tsx +1 -0
  82. package/src/ui/properties/PropertyPanel.tsx +28 -0
  83. package/src/undo/eventBus.ts +4 -0
  84. package/src/undo/undoStack.ts +11 -0
  85. package/src/workspace/DefaultSidebar.tsx +3 -0
  86. package/src/workspace/DefaultStatusBar.tsx +3 -0
  87. package/src/workspace/DefaultToolbar.tsx +3 -0
  88. package/src/workspace/Workspace.tsx +3 -0
  89. package/src/workspace/WorkspaceChrome.tsx +4 -0
  90. package/src/workspace/slotTypes.ts +9 -0
  91. package/src/workspace/workspaceOps.ts +8 -0
  92. package/dist/chunk-2QNYYL3V.js.map +0 -1
  93. package/dist/chunk-53XSBIUK.js.map +0 -1
  94. package/dist/chunk-54IQ2DX7.js.map +0 -1
  95. package/dist/chunk-7BKDG73Z.js.map +0 -1
  96. package/dist/chunk-EXBV7A6X.js.map +0 -1
  97. package/dist/chunk-HXZHVU4G.js.map +0 -1
  98. package/dist/chunk-ISEK5LXT.js.map +0 -1
  99. package/dist/chunk-NRD3TDNQ.js.map +0 -1
  100. package/dist/chunk-PWC7AQZM.js.map +0 -1
@@ -3,12 +3,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import earcut from 'earcut';
4
4
  import polygonClipping from 'polygon-clipping';
5
5
 
6
- // ../core/dist/chunk-PZ5AY32C.js
7
- var __defProp = Object.defineProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
6
+ // ../core/dist/chunk-TR3HZN34.js
12
7
 
13
8
  // ../gestures/dist/index.js
14
9
  var GESTURE_DESCRIPTORS = [
@@ -22,7 +17,12 @@ var GESTURE_DESCRIPTORS = [
22
17
  { name: "keyHeld", hasTarget: false, arg: { name: "key", values: "free" } },
23
18
  { name: "contextMenu", hasTarget: true },
24
19
  { name: "longPress", hasTarget: true },
25
- { name: "multiTouchTap", hasTarget: false, arg: { name: "fingers", values: ["2", "3", "4"] } }
20
+ { name: "multiTouchTap", hasTarget: false, arg: { name: "fingers", values: ["2", "3", "4"] } },
21
+ // The arg is the MIME-glob filter. No default: a spec with no `types`
22
+ // matches any payload, which is what an omitted arg slot means for a
23
+ // `'free'` arg.
24
+ { name: "drop", hasTarget: false, arg: { name: "types", values: "free" } },
25
+ { name: "paste", hasTarget: false, arg: { name: "types", values: "free" } }
26
26
  ];
27
27
  var BY_NAME = new Map(
28
28
  GESTURE_DESCRIPTORS.map((d) => [d.name, d])
@@ -50,176 +50,11 @@ var RESERVED_ID_NAMES = /* @__PURE__ */ new Set([
50
50
  "engaged"
51
51
  ]);
52
52
  var VALID_MOD_NAMES = ["mod", "shift", "alt", "ctrl", "meta"];
53
- var RESERVED_SIGILS = /* @__PURE__ */ new Set(["!", "@", "#", "$", "%", "^", "&"]);
54
- var ACTIVE_SIGILS = /* @__PURE__ */ new Set(["+", "?"]);
55
- var MOD_NAME_SET = new Set(VALID_MOD_NAMES);
56
- function parseRoute(input) {
57
- const trimmed = input.trim();
58
- if (!trimmed.startsWith("[")) {
59
- throw new Error(`invalid route (phase brackets required): ${input}`);
60
- }
61
- const closeIdx = trimmed.indexOf("]");
62
- if (closeIdx < 0) throw new Error(`invalid route (unclosed phase bracket): ${input}`);
63
- const phaseListRaw = trimmed.slice(1, closeIdx).trim();
64
- const phases = parsePhaseList(phaseListRaw, input);
65
- let rest = trimmed.slice(closeIdx + 1).trim();
66
- const gestureMatch = /^([A-Za-z]+)/.exec(rest);
67
- if (!gestureMatch) throw new Error(`invalid route (no gesture name): ${input}`);
68
- const gestureName = gestureMatch[1];
69
- if (!isKnownGestureName(gestureName)) {
70
- throw new Error(`invalid route (unknown gesture "${gestureName}"): ${input}`);
71
- }
72
- rest = rest.slice(gestureMatch[0].length).trimStart();
73
- const desc = getGestureDescriptor(gestureName);
74
- let arg;
75
- if (rest.startsWith("(")) {
76
- const closeArg = rest.indexOf(")");
77
- if (closeArg < 0) throw new Error(`invalid route (unbalanced arg parens): ${input}`);
78
- const argRaw = rest.slice(1, closeArg);
79
- if (!desc.arg) throw new Error(`invalid route (${gestureName} has no arg): ${input}`);
80
- if (argRaw !== "*" && desc.arg.values !== "free" && !desc.arg.values.includes(argRaw)) {
81
- throw new Error(`invalid route ("${argRaw}" not in ${desc.arg.values.join("|")}): ${input}`);
82
- }
83
- arg = argRaw;
84
- rest = rest.slice(closeArg + 1).trimStart();
85
- } else if (desc.arg) {
86
- arg = desc.arg.default ?? "*";
87
- }
88
- let target;
89
- if (rest.startsWith("=>")) {
90
- if (!desc.hasTarget) throw new Error(`invalid route (${gestureName} has no target): ${input}`);
91
- rest = rest.slice(2).trimStart();
92
- const tgtMatch = /^([^\s+?!@#$%^&]+)/.exec(rest);
93
- if (!tgtMatch) throw new Error(`invalid route (missing target after "=>"): ${input}`);
94
- target = tgtMatch[1];
95
- rest = rest.slice(tgtMatch[0].length).trimStart();
96
- } else if (desc.hasTarget) {
97
- target = "*";
98
- }
99
- const modifiers = {};
100
- while (rest.length > 0) {
101
- const ch = rest[0];
102
- if (RESERVED_SIGILS.has(ch)) {
103
- throw new Error(`invalid route ("${ch}" is reserved for future use): ${input}`);
104
- }
105
- if (!ACTIVE_SIGILS.has(ch)) {
106
- throw new Error(`invalid route (unexpected "${ch}" at "${rest}"): ${input}`);
107
- }
108
- const sigil = ch;
109
- rest = rest.slice(1);
110
- const nameMatch = /^([a-z]+)/.exec(rest);
111
- if (!nameMatch) throw new Error(`invalid route (sigil "${sigil}" without modifier name): ${input}`);
112
- const name = nameMatch[1];
113
- if (!MOD_NAME_SET.has(name)) {
114
- throw new Error(`invalid route (unknown modifier "${name}"): ${input}`);
115
- }
116
- if (modifiers[name] !== void 0) {
117
- throw new Error(`invalid route (duplicate modifier "${name}"): ${input}`);
118
- }
119
- modifiers[name] = sigil === "+" ? "required" : "optional";
120
- rest = rest.slice(nameMatch[0].length).trimStart();
121
- }
122
- return { phases, gesture: gestureName, arg, target, modifiers };
123
- }
124
- function parsePhaseList(raw, input) {
125
- if (raw === "") throw new Error(`invalid route (empty phase list): ${input}`);
126
- const tokens = raw.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
127
- if (tokens.length === 0) throw new Error(`invalid route (empty phase list): ${input}`);
128
- return tokens.map((t) => parsePhaseAtom(t, input));
129
- }
130
- var VALID_PHASES = /* @__PURE__ */ new Set(["initial", "engaged", "*"]);
131
- function parsePhaseAtom(raw, input) {
132
- const parts = raw.split(":");
133
- if (parts.length > 2) {
134
- throw new Error(`invalid route (phase atom "${raw}" has multiple ":"): ${input}`);
135
- }
136
- if (parts.length === 1) {
137
- const phase2 = parts[0];
138
- if (!VALID_PHASES.has(phase2)) {
139
- throw new Error(`invalid route (unknown phase "${phase2}" in "${raw}"): ${input}`);
140
- }
141
- return { channel: "&", phase: phase2 };
142
- }
143
- const channel = parts[0];
144
- const phase = parts[1];
145
- if (channel === "") {
146
- throw new Error(`invalid route (empty channel before ":" in "${raw}"): ${input}`);
147
- }
148
- if (!VALID_PHASES.has(phase)) {
149
- throw new Error(`invalid route (unknown phase "${phase}" in "${raw}"): ${input}`);
150
- }
151
- if (channel !== "&" && channel !== "*") {
152
- if (RESERVED_ID_NAMES.has(channel)) {
153
- throw new Error(`invalid route (channel "${channel}" is a reserved phase keyword): ${input}`);
154
- }
155
- if (RESERVED_ID_PREFIXES.has(channel[0])) {
156
- throw new Error(`invalid route (channel "${channel}" starts with reserved sigil "${channel[0]}"): ${input}`);
157
- }
158
- }
159
- return { channel, phase };
160
- }
53
+ new Set(VALID_MOD_NAMES);
161
54
  function formatPhaseAtom(a) {
162
55
  if (a.channel === "&") return a.phase;
163
56
  return `${a.channel}:${a.phase}`;
164
57
  }
165
- function collapseShiftPairs(routes) {
166
- if (routes.length < 2) return [...routes];
167
- const parsed = routes.map((r) => ({ raw: r, p: parseRoute(r) }));
168
- const consumed = /* @__PURE__ */ new Set();
169
- const out = [];
170
- for (let i = 0; i < parsed.length; i++) {
171
- if (consumed.has(i)) continue;
172
- const a = parsed[i];
173
- let twinIndex = -1;
174
- for (let j = i + 1; j < parsed.length; j++) {
175
- if (consumed.has(j)) continue;
176
- const b = parsed[j];
177
- if (isShiftTwin(a.p, b.p)) {
178
- twinIndex = j;
179
- break;
180
- }
181
- }
182
- if (twinIndex < 0) {
183
- out.push(a.raw);
184
- continue;
185
- }
186
- consumed.add(twinIndex);
187
- const folded = {
188
- ...a.p,
189
- modifiers: { ...withoutShift(a.p.modifiers), shift: "optional" }
190
- };
191
- out.push(formatRoute(folded));
192
- }
193
- return out;
194
- }
195
- function isShiftTwin(a, b) {
196
- if (a.gesture !== b.gesture) return false;
197
- if (a.arg !== b.arg) return false;
198
- if (a.target !== b.target) return false;
199
- if (!samePhases(a.phases, b.phases)) return false;
200
- const aShift = a.modifiers.shift;
201
- const bShift = b.modifiers.shift;
202
- const aHas = aShift === "required";
203
- const bHas = bShift === "required";
204
- if (!(aHas !== bHas)) return false;
205
- if (aShift === "optional" || bShift === "optional") return false;
206
- for (const name of VALID_MOD_NAMES) {
207
- if (name === "shift") continue;
208
- if (a.modifiers[name] !== b.modifiers[name]) return false;
209
- }
210
- return true;
211
- }
212
- function samePhases(a, b) {
213
- if (a.length !== b.length) return false;
214
- for (let i = 0; i < a.length; i++) {
215
- if (a[i].channel !== b[i].channel || a[i].phase !== b[i].phase) return false;
216
- }
217
- return true;
218
- }
219
- function withoutShift(m) {
220
- const { shift: _shift, ...rest } = m;
221
- return rest;
222
- }
223
58
  var MOD_ORDER = ["mod", "shift", "alt", "ctrl", "meta"];
224
59
  function formatRoute(r) {
225
60
  const desc = getGestureDescriptor(r.gesture);
@@ -240,122 +75,6 @@ function formatRoute(r) {
240
75
  return out;
241
76
  }
242
77
  new Set(VALID_MOD_NAMES);
243
- var MOD_NAMES = {
244
- mod: "Mod",
245
- shift: "Shift",
246
- alt: "Alt",
247
- ctrl: "Ctrl",
248
- meta: "Meta"
249
- };
250
- var MOD_ORDER2 = ["mod", "shift", "alt", "ctrl", "meta"];
251
- var ROUTE_TERMS = {
252
- idle: "Not in the middle of a drag or other synchronous operation.",
253
- "mid-gesture": "In the middle of a drag or other synchronous operation."
254
- };
255
- var ROUTE_FIELD_DEFINITIONS = {
256
- phases: "Which lifecycle stage(s) a channel must be in for this route to fire. `initial` = the channel is idle; `engaged` = the channel is mid-gesture. The channel is the binding's own tool (`&`), any tool (`*`), or a named tool id.",
257
- gesture: "The class of input event that triggers this route \u2014 click, drag, double-tap, keyDown/keyUp, wheel, contextMenu, or multiTouchTap.",
258
- arg: "Sub-class of the gesture. Direction for wheel (up / down / *), key name for keyDown / keyUp, finger count for multiTouchTap. Other gestures have no arg slot.",
259
- target: "Which hit-test result the gesture must land on. `*` matches any target; `empty` matches the empty canvas; otherwise the value names a specific hit-target kind.",
260
- modifiers: "Modifier keys the user must hold (`+key`) or may optionally hold (`?key`) for this route to match. Unlisted modifiers must not be held."
261
- };
262
- function term(label) {
263
- return { kind: "term", label, definition: ROUTE_TERMS[label] };
264
- }
265
- function joinAnd(parts) {
266
- if (parts.length === 0) return "";
267
- if (parts.length === 1) return parts[0];
268
- if (parts.length === 2) return `${parts[0]} and ${parts[1]}`;
269
- return `${parts.slice(0, -1).join(", ")}, and ${parts[parts.length - 1]}`;
270
- }
271
- function partitionModifiers(mods2) {
272
- const required = [];
273
- const optional = [];
274
- for (const name of MOD_ORDER2) {
275
- const req = mods2[name];
276
- if (req === "required") required.push(MOD_NAMES[name]);
277
- else if (req === "optional") optional.push(MOD_NAMES[name]);
278
- }
279
- return { required, optional };
280
- }
281
- function phaseAtomParts({ channel, phase }) {
282
- const subject = channel === "&" ? "the tool" : channel === "*" ? "any tool" : `the ${channel} tool`;
283
- if (phase === "initial") return [`${subject} is `, term("idle")];
284
- if (phase === "engaged") return [`${subject} is `, term("mid-gesture")];
285
- return [`${subject} is in any phase`];
286
- }
287
- function phasesParts(phases) {
288
- if (phases.length === 1) return phaseAtomParts(phases[0]);
289
- const out = [];
290
- phases.forEach((p, i) => {
291
- if (i > 0) out.push(" or ");
292
- out.push(...phaseAtomParts(p));
293
- });
294
- return out;
295
- }
296
- function targetClause(target, hasTarget) {
297
- if (!hasTarget || target === void 0) return "";
298
- if (target === "*") return " anywhere";
299
- if (target === "empty") return " on empty canvas";
300
- return ` on a ${target}`;
301
- }
302
- function actionClause(parsed, required) {
303
- const desc = getGestureDescriptor(parsed.gesture);
304
- const modPrefix = required.length > 0 ? `${joinAnd(required)}-` : "";
305
- const target = targetClause(parsed.target, desc.hasTarget);
306
- switch (parsed.gesture) {
307
- case "keyDown":
308
- case "keyUp":
309
- case "keyHeld": {
310
- const verb = parsed.gesture === "keyDown" ? "presses" : parsed.gesture === "keyUp" ? "releases" : "holds";
311
- const key = parsed.arg ?? "any key";
312
- return required.length > 0 ? `the user holds ${joinAnd(required)} and ${verb} ${key}` : `the user ${verb} ${key}`;
313
- }
314
- case "wheel": {
315
- const direction = parsed.arg === "up" ? " up" : parsed.arg === "down" ? " down" : "";
316
- return `the user ${modPrefix}scrolls${direction}`;
317
- }
318
- case "multiTouchTap":
319
- return `the user taps with ${parsed.arg ?? "multiple"} fingers`;
320
- case "contextMenu":
321
- return `the user ${modPrefix}opens the context menu${target}`;
322
- case "longPress":
323
- return `the user ${modPrefix}presses and holds${target}`;
324
- case "click":
325
- return `the user ${modPrefix}clicks${target}`;
326
- case "pointerDown":
327
- return `the user ${modPrefix}presses${target}`;
328
- case "dblTap":
329
- return `the user ${modPrefix}double-taps${target}`;
330
- case "drag":
331
- return `the user ${modPrefix}drags${target}`;
332
- }
333
- }
334
- function describeRouteParts(parsed, opts = {}) {
335
- const { capitalize = true, period = true } = opts;
336
- const { required, optional } = partitionModifiers(parsed.modifiers);
337
- const action = actionClause(parsed, required);
338
- const optClause = optional.length > 0 ? ` (${joinAnd(optional)} optional)` : "";
339
- const parts = [];
340
- const head = `fires when ${action}, while `;
341
- parts.push(capitalize ? head.charAt(0).toUpperCase() + head.slice(1) : head);
342
- parts.push(...phasesParts(parsed.phases));
343
- if (optClause) parts.push(optClause);
344
- if (period) parts.push(".");
345
- return mergeAdjacentStrings(parts);
346
- }
347
- function describeRoute(parsed, opts = {}) {
348
- return describeRouteParts(parsed, opts).map((p) => typeof p === "string" ? p : p.label).join("");
349
- }
350
- function mergeAdjacentStrings(parts) {
351
- const out = [];
352
- for (const p of parts) {
353
- const last = out[out.length - 1];
354
- if (typeof p === "string" && typeof last === "string") out[out.length - 1] = last + p;
355
- else out.push(p);
356
- }
357
- return out;
358
- }
359
78
  function canonicalModifiers(mods2) {
360
79
  return Object.entries(mods2).sort(([a], [b]) => a.localeCompare(b)).map(([name, req]) => `${name}=${req}`).join("&");
361
80
  }
@@ -568,144 +287,8 @@ function matchSpec(e, spec, isMac, phaseCtx) {
568
287
  }
569
288
  }
570
289
  var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
571
- var routing_exports = {};
572
- __export(routing_exports, {
573
- GESTURE_DESCRIPTORS: () => GESTURE_DESCRIPTORS,
574
- PREDICATE_TARGET: () => PREDICATE_TARGET,
575
- RESERVED_ID_NAMES: () => RESERVED_ID_NAMES,
576
- RESERVED_ID_PREFIXES: () => RESERVED_ID_PREFIXES,
577
- ROUTE_FIELD_DEFINITIONS: () => ROUTE_FIELD_DEFINITIONS,
578
- ROUTE_TERMS: () => ROUTE_TERMS,
579
- buildRouteRegistry: () => buildRouteRegistry,
580
- canonicalModifiers: () => canonicalModifiers,
581
- collapseShiftPairs: () => collapseShiftPairs,
582
- defineTool: () => defineTool,
583
- defineViewportTool: () => defineViewportTool,
584
- describeRoute: () => describeRoute,
585
- describeRouteParts: () => describeRouteParts,
586
- findConflicts: () => findConflicts,
587
- findScopedConflicts: () => findScopedConflicts,
588
- formatConflict: () => formatConflict,
589
- formatPhaseAtom: () => formatPhaseAtom,
590
- formatRoute: () => formatRoute,
591
- getGestureDescriptor: () => getGestureDescriptor,
592
- isKnownGestureName: () => isKnownGestureName,
593
- parseRoute: () => parseRoute,
594
- reportRouteConflicts: () => reportRouteConflicts
595
- });
596
- function validateId(id, kind) {
597
- if (id.length === 0) {
598
- throw new Error(`weasel: ${kind} id may not be empty`);
599
- }
600
- if (RESERVED_ID_PREFIXES.has(id[0])) {
601
- throw new Error(
602
- `weasel: ${kind} id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
603
- );
604
- }
605
- if (RESERVED_ID_NAMES.has(id)) {
606
- throw new Error(
607
- `weasel: ${kind} id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
608
- );
609
- }
610
- }
611
- function defineTool(def) {
612
- validateId(def.id, "tool");
613
- const resolveCursor = (ctx) => {
614
- if (def.cursor == null) return "";
615
- return typeof def.cursor === "function" ? def.cursor(ctx) : def.cursor;
616
- };
617
- return {
618
- id: def.id,
619
- eligibility: {
620
- focus: true,
621
- capabilities: def.capabilities,
622
- ...def.hotkey ? { offhand: def.hotkey } : {}
623
- },
624
- actions: def.actions,
625
- def,
626
- presentation: def.presentation,
627
- keybinding: def.keybinding,
628
- onActivate: def.onActivate,
629
- onDeactivate: def.onDeactivate,
630
- initScratch: def.initScratch ?? (() => null),
631
- cursor: resolveCursor,
632
- bindings: def.bindings,
633
- overlay: def.overlay
634
- };
635
- }
636
- function defineViewportTool(def) {
637
- return defineTool(def);
638
- }
639
- var PREDICATE_TARGET = "predicate";
640
- var SPEC_KIND_TO_GESTURE = {
641
- key: "keyDown",
642
- "key-held": "keyHeld",
643
- wheel: "wheel",
644
- click: "click",
645
- doubleClick: "dblTap",
646
- contextMenu: "contextMenu",
647
- longPress: "longPress",
648
- drag: "drag",
649
- pointerDown: "pointerDown",
650
- multiTouch: void 0,
651
- multiTouchTap: "multiTouchTap",
652
- drop: void 0,
653
- paste: void 0
654
- };
655
- function buildRouteRegistry(tools) {
656
- const out = [];
657
- for (const tool of tools) {
658
- for (const binding of tool.bindings ?? []) {
659
- const entry = entryFor(tool.id, binding.spec, binding.actionId);
660
- if (entry) out.push(entry);
661
- }
662
- }
663
- return out;
664
- }
665
- function entryFor(toolId, spec, actionId) {
666
- const gesture = SPEC_KIND_TO_GESTURE[spec.kind];
667
- if (!gesture || !isKnownGestureName(gesture)) return null;
668
- const descriptor = getGestureDescriptor(gesture);
669
- return {
670
- toolId,
671
- actionId,
672
- gesture,
673
- phase: phaseOf("phase" in spec ? spec.phase : void 0),
674
- modifiers: parseModSpec("mods" in spec ? spec.mods : void 0),
675
- arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : void 0,
676
- target: descriptor.hasTarget ? targetOf("target" in spec ? spec.target : void 0) : void 0,
677
- spec
678
- };
679
- }
680
- function phaseOf(phase) {
681
- if (phase === void 0 || phase === "*") return "any";
682
- if (phase === "initial" || phase === "engaged") return phase;
683
- const distinct = new Set(phase.map((atom) => atom.phase));
684
- if (distinct.size !== 1) return "any";
685
- const only = [...distinct][0];
686
- return only === "initial" || only === "engaged" ? only : "any";
687
- }
688
- function parseModSpec(mods) {
689
- if (!mods) return {};
690
- const out = {};
691
- for (const [name, req] of Object.entries(mods)) {
692
- if (req === true) out[name] = "required";
693
- else if (req === "optional") out[name] = "optional";
694
- }
695
- return out;
696
- }
697
- function targetOf(target) {
698
- if (target === void 0) return void 0;
699
- return typeof target === "string" ? target : PREDICATE_TARGET;
700
- }
701
- function argOf(spec, fallback) {
702
- if ("key" in spec) {
703
- return Array.isArray(spec.key) ? spec.key.join("|") : spec.key;
704
- }
705
- if ("fingers" in spec) return String(spec.fingers);
706
- if ("direction" in spec) return spec.direction ?? fallback;
707
- return fallback;
708
- }
290
+
291
+ // ../core/dist/chunk-TR3HZN34.js
709
292
  var DepRegistryContext = createContext(null);
710
293
  function DepRegistryProvider({ children }) {
711
294
  const sourcesRef = useRef(/* @__PURE__ */ new Map());
@@ -955,6 +538,77 @@ function ActionsProvider({ children }) {
955
538
  function useActionsRegistry() {
956
539
  return useContext(ActionsContext);
957
540
  }
541
+ var PREDICATE_TARGET = "predicate";
542
+ var SPEC_KIND_TO_GESTURE = {
543
+ key: "keyDown",
544
+ "key-held": "keyHeld",
545
+ wheel: "wheel",
546
+ click: "click",
547
+ doubleClick: "dblTap",
548
+ contextMenu: "contextMenu",
549
+ longPress: "longPress",
550
+ drag: "drag",
551
+ pointerDown: "pointerDown",
552
+ multiTouch: void 0,
553
+ multiTouchTap: "multiTouchTap",
554
+ drop: "drop",
555
+ paste: "paste"
556
+ };
557
+ function buildRouteRegistry(tools) {
558
+ const out = [];
559
+ for (const tool of tools) {
560
+ for (const binding of tool.bindings ?? []) {
561
+ const entry = entryFor(tool.id, binding.spec, binding.actionId);
562
+ if (entry) out.push(entry);
563
+ }
564
+ }
565
+ return out;
566
+ }
567
+ function entryFor(toolId, spec, actionId) {
568
+ const gesture = SPEC_KIND_TO_GESTURE[spec.kind];
569
+ if (!gesture || !isKnownGestureName(gesture)) return null;
570
+ const descriptor = getGestureDescriptor(gesture);
571
+ return {
572
+ toolId,
573
+ actionId,
574
+ gesture,
575
+ phase: phaseOf("phase" in spec ? spec.phase : void 0),
576
+ modifiers: parseModSpec("mods" in spec ? spec.mods : void 0),
577
+ arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : void 0,
578
+ target: descriptor.hasTarget ? targetOf("target" in spec ? spec.target : void 0) : void 0,
579
+ spec
580
+ };
581
+ }
582
+ function phaseOf(phase) {
583
+ if (phase === void 0 || phase === "*") return "any";
584
+ if (phase === "initial" || phase === "engaged") return phase;
585
+ const distinct = new Set(phase.map((atom) => atom.phase));
586
+ if (distinct.size !== 1) return "any";
587
+ const only = [...distinct][0];
588
+ return only === "initial" || only === "engaged" ? only : "any";
589
+ }
590
+ function parseModSpec(mods) {
591
+ if (!mods) return {};
592
+ const out = {};
593
+ for (const [name, req] of Object.entries(mods)) {
594
+ if (req === true) out[name] = "required";
595
+ else if (req === "optional") out[name] = "optional";
596
+ }
597
+ return out;
598
+ }
599
+ function targetOf(target) {
600
+ if (target === void 0) return void 0;
601
+ return typeof target === "string" ? target : PREDICATE_TARGET;
602
+ }
603
+ function argOf(spec, fallback) {
604
+ if ("key" in spec) {
605
+ return Array.isArray(spec.key) ? spec.key.join("|") : spec.key;
606
+ }
607
+ if ("fingers" in spec) return String(spec.fingers);
608
+ if ("direction" in spec) return spec.direction ?? fallback;
609
+ if ("types" in spec) return spec.types?.length ? spec.types.join("|") : fallback;
610
+ return fallback;
611
+ }
958
612
  function findConflicts(tools) {
959
613
  const entries = buildRouteRegistry(tools);
960
614
  const groups = /* @__PURE__ */ new Map();
@@ -1045,7 +699,7 @@ function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
1045
699
  return conflicts;
1046
700
  }
1047
701
 
1048
- // ../geom/dist/chunk-K5JJ6WRB.js
702
+ // ../geom/dist/chunk-UULRZUHO.js
1049
703
  function cubicEvalAt(x0, y0, x1, y1, x2, y2, x3, y3, t) {
1050
704
  const u = 1 - t;
1051
705
  const a = u * u * u, b = 3 * u * u * t, c = 3 * u * t * t, d = t * t * t;
@@ -1161,7 +815,7 @@ function transformCoords(coords, m) {
1161
815
  return out;
1162
816
  }
1163
817
 
1164
- // ../core/dist/chunk-6SHZHXXR.js
818
+ // ../core/dist/chunk-U45SODC2.js
1165
819
  function resolveUnit(v, unitSystem) {
1166
820
  if (typeof v === "number") return v;
1167
821
  if (!unitSystem) {
@@ -1909,7 +1563,7 @@ function createDeleteOp(args) {
1909
1563
  }
1910
1564
  registerOpFactory("delete", (args) => createDeleteOp(args));
1911
1565
 
1912
- // ../core/dist/chunk-UB6A6L77.js
1566
+ // ../core/dist/chunk-3KGMJMOS.js
1913
1567
  function createTransformOp(args) {
1914
1568
  const { id, from, to, label, coalesceKey = `transform:${id}` } = args;
1915
1569
  return {
@@ -1990,7 +1644,7 @@ function snap(strategy, opts = {}) {
1990
1644
  };
1991
1645
  }
1992
1646
 
1993
- // ../core/dist/chunk-Z3KNFTTS.js
1647
+ // ../core/dist/chunk-3JYFAQIB.js
1994
1648
  function lockAspectWithModifier(opts = {}) {
1995
1649
  const { key = "shift" } = opts;
1996
1650
  return {
@@ -2186,7 +1840,7 @@ function materializePaste(adapter, snapshot, opts) {
2186
1840
  return { ops, newIds, created };
2187
1841
  }
2188
1842
 
2189
- // ../core/dist/chunk-IO5Z75X4.js
1843
+ // ../core/dist/chunk-HGF5X6MZ.js
2190
1844
  var counter = 0;
2191
1845
  var registry = /* @__PURE__ */ new Map();
2192
1846
  function getTexture(id) {
@@ -2344,7 +1998,7 @@ function chunks(params) {
2344
1998
  return build({ tile: "chunks", ...params });
2345
1999
  }
2346
2000
 
2347
- // ../font/dist/index.js
2001
+ // ../font/dist/chunk-3RSTZDXH.js
2348
2002
  var subscribers = /* @__PURE__ */ new Set();
2349
2003
  var generation = 0;
2350
2004
  function glyphGeneration() {
@@ -2687,17 +2341,17 @@ function dynamicPageTextureId(page) {
2687
2341
  return `weasel-dyn-sdf-page-${page}`;
2688
2342
  }
2689
2343
  var uploadedVersions = /* @__PURE__ */ new WeakMap();
2690
- function syncDynamicPageTexture(cache7, pageIndex) {
2344
+ function syncDynamicPageTexture(cache8, pageIndex) {
2691
2345
  const page = pages[pageIndex];
2692
2346
  if (!page) return false;
2693
2347
  const id = dynamicPageTextureId(pageIndex);
2694
- let seen = uploadedVersions.get(cache7);
2348
+ let seen = uploadedVersions.get(cache8);
2695
2349
  if (!seen) {
2696
2350
  seen = /* @__PURE__ */ new Map();
2697
- uploadedVersions.set(cache7, seen);
2351
+ uploadedVersions.set(cache8, seen);
2698
2352
  }
2699
- if (!cache7.has(id)) {
2700
- cache7.uploadR8(id, PAGE_SIZE, PAGE_SIZE, page.data);
2353
+ if (!cache8.has(id)) {
2354
+ cache8.uploadR8(id, PAGE_SIZE, PAGE_SIZE, page.data);
2701
2355
  seen.set(pageIndex, page.version);
2702
2356
  return true;
2703
2357
  }
@@ -2710,7 +2364,7 @@ function syncDynamicPageTexture(cache7, pageIndex) {
2710
2364
  const src = (patch.y + row) * PAGE_SIZE + patch.x;
2711
2365
  tight.set(page.data.subarray(src, src + patch.w), row * patch.w);
2712
2366
  }
2713
- cache7.subImageR8(id, patch.x, patch.y, patch.w, patch.h, tight);
2367
+ cache8.subImageR8(id, patch.x, patch.y, patch.w, patch.h, tight);
2714
2368
  }
2715
2369
  seen.set(pageIndex, page.version);
2716
2370
  return true;
@@ -2988,6 +2642,8 @@ function warnOnce(key, message) {
2988
2642
  warned.add(key);
2989
2643
  console.warn(message);
2990
2644
  }
2645
+
2646
+ // ../font/dist/index.js
2991
2647
  var TEXT_VERT_SRC = (
2992
2648
  /* glsl */
2993
2649
  `#version 300 es
@@ -3084,7 +2740,7 @@ var TEXT_SDF_UNIFORMS = [
3084
2740
  ];
3085
2741
  var TEXT_SDF_ATTRIBUTES = ["a_position", "a_uv", "a_baselineY"];
3086
2742
 
3087
- // ../modes/dist/chunk-KILROWYB.js
2743
+ // ../modes/dist/chunk-YAJYQMBG.js
3088
2744
  var NORMAL = {
3089
2745
  allows: [
3090
2746
  "creates-selection",
@@ -3477,7 +3133,7 @@ function serialToEntry(se, custom, logger) {
3477
3133
  };
3478
3134
  }
3479
3135
 
3480
- // ../core/dist/chunk-FVNWK7U3.js
3136
+ // ../core/dist/chunk-SMCZRSYB.js
3481
3137
  function worldToScreen(worldX, worldY, view) {
3482
3138
  return [view.panX + worldX * view.zoom.x, view.panY + worldY * view.zoom.y];
3483
3139
  }
@@ -3791,6 +3447,8 @@ var GLMeshCache = class {
3791
3447
  gl;
3792
3448
  aPositionLoc;
3793
3449
  map = /* @__PURE__ */ new WeakMap();
3450
+ /** Meshes this context has drawn at least once; see `uploadRecurring`. */
3451
+ seen = /* @__PURE__ */ new WeakSet();
3794
3452
  finalizer;
3795
3453
  pendingDeletes = [];
3796
3454
  /** Transient resources allocated this frame; freed at end of render(). */
@@ -3815,6 +3473,19 @@ var GLMeshCache = class {
3815
3473
  this.transientThisFrame.push({ vao: handle.vao, vbo, ibo });
3816
3474
  return handle;
3817
3475
  }
3476
+ /**
3477
+ * Upload a Mesh that may or may not recur across frames. Its first sight in
3478
+ * *this* context takes `uploadTransient`; every later one takes the
3479
+ * persistent handle. One transient upload to find out is cheaper than
3480
+ * stranding a persistent VAO on a mesh that never returns, whose release
3481
+ * would wait on GC. The transient upload does not populate the persistent
3482
+ * map, so steady-state reuse begins on the third frame.
3483
+ */
3484
+ uploadRecurring(mesh) {
3485
+ if (this.seen.has(mesh)) return this.handleFor(mesh);
3486
+ this.seen.add(mesh);
3487
+ return this.uploadTransient(mesh);
3488
+ }
3818
3489
  /**
3819
3490
  * Free all transient resources allocated since the last call. Called by
3820
3491
  * the renderer at the end of each `render()`. Safe under context loss.
@@ -4881,619 +4552,19 @@ function getMesh(path) {
4881
4552
  cache.set(path, mesh);
4882
4553
  return mesh;
4883
4554
  }
4884
- function extractPolylines(path, opts = {}) {
4885
- if (path.kind === "rect") return [extractRect(path)];
4886
- return extractPolygon(path, opts);
4555
+ var UNDERLINE_OFFSET = 0.1;
4556
+ var STRIKETHROUGH_OFFSET = -0.3;
4557
+ var DECORATION_THICKNESS = 0.05;
4558
+ function fillKey(p) {
4559
+ if ("color" in p) return `s:${p.color}:${p.opacity ?? 1}`;
4560
+ return `nx:${Math.random()}`;
4887
4561
  }
4888
- function extractRect(p) {
4889
- const { x, y, width: w, height: h } = p;
4890
- return {
4891
- points: [x, y, x + w, y, x + w, y + h, x, y + h],
4892
- closed: true,
4893
- anchorA: new Uint32Array([0, 1, 2, 3]),
4894
- anchorB: new Uint32Array([0, 1, 2, 3]),
4895
- anchorT: new Float32Array([0, 0, 0, 0])
4896
- };
4897
- }
4898
- function extractPolygon(p, opts) {
4899
- const tolerance = opts.flattenTolerance ?? DEFAULT_FLATTEN_TOLERANCE;
4900
- const { commands, coords } = p;
4901
- const out = [];
4902
- let anchorCounter = 0;
4903
- let pts = null;
4904
- let aA = null;
4905
- let aB = null;
4906
- let aT = null;
4907
- let current = null;
4908
- let coordIdx = 0;
4909
- let prevX = 0;
4910
- let prevY = 0;
4911
- let prevAnchor = -1;
4912
- const beginContour = () => {
4913
- pts = [];
4914
- aA = [];
4915
- aB = [];
4916
- aT = [];
4917
- const next = { points: pts, closed: false };
4918
- out.push(next);
4919
- return next;
4920
- };
4921
- const dropDuplicateClosingPoint = () => {
4922
- if (!pts || !aA || !aB || !aT) return;
4923
- const n = pts.length / 2;
4924
- if (n < 2) return;
4925
- if (pts[0] !== pts[(n - 1) * 2] || pts[1] !== pts[(n - 1) * 2 + 1]) return;
4926
- pts.length -= 2;
4927
- aA.length -= 1;
4928
- aB.length -= 1;
4929
- aT.length -= 1;
4930
- };
4931
- const commit = (target) => {
4932
- if (!pts || !aA || !aB || !aT) return;
4933
- target.anchorA = new Uint32Array(aA);
4934
- target.anchorB = new Uint32Array(aB);
4935
- target.anchorT = new Float32Array(aT);
4936
- };
4937
- for (let cmdIdx = 0; cmdIdx < commands.length; cmdIdx++) {
4938
- const cmd = commands[cmdIdx];
4939
- switch (cmd) {
4940
- case PATH_M2: {
4941
- if (current) commit(current);
4942
- current = beginContour();
4943
- prevX = coords[coordIdx];
4944
- prevY = coords[coordIdx + 1];
4945
- pts.push(prevX, prevY);
4946
- aA.push(anchorCounter);
4947
- aB.push(anchorCounter);
4948
- aT.push(0);
4949
- prevAnchor = anchorCounter;
4950
- anchorCounter++;
4951
- coordIdx += 2;
4952
- break;
4953
- }
4954
- case PATH_L2: {
4955
- prevX = coords[coordIdx];
4956
- prevY = coords[coordIdx + 1];
4957
- pts.push(prevX, prevY);
4958
- aA.push(anchorCounter);
4959
- aB.push(anchorCounter);
4960
- aT.push(0);
4961
- prevAnchor = anchorCounter;
4962
- anchorCounter++;
4963
- coordIdx += 2;
4964
- break;
4965
- }
4966
- case PATH_Q2: {
4967
- const cx = coords[coordIdx], cy = coords[coordIdx + 1];
4968
- const ex = coords[coordIdx + 2], ey = coords[coordIdx + 3];
4969
- const targetAnchor = anchorCounter;
4970
- const segStart = pts.length / 2;
4971
- const arcAccum = [];
4972
- const total = flattenQuadraticWithArcLen(prevX, prevY, cx, cy, ex, ey, tolerance, pts, arcAccum);
4973
- for (let k = 0; k < arcAccum.length; k++) {
4974
- aA.push(prevAnchor);
4975
- aB.push(targetAnchor);
4976
- aT.push(total > 0 ? arcAccum[k] / total : 0);
4977
- }
4978
- const lastIdx = segStart + arcAccum.length - 1;
4979
- aA[lastIdx] = targetAnchor;
4980
- aB[lastIdx] = targetAnchor;
4981
- aT[lastIdx] = 0;
4982
- prevX = ex;
4983
- prevY = ey;
4984
- prevAnchor = targetAnchor;
4985
- anchorCounter++;
4986
- coordIdx += 4;
4987
- break;
4988
- }
4989
- case PATH_C2: {
4990
- const c1x = coords[coordIdx], c1y = coords[coordIdx + 1];
4991
- const c2x = coords[coordIdx + 2], c2y = coords[coordIdx + 3];
4992
- const ex = coords[coordIdx + 4], ey = coords[coordIdx + 5];
4993
- const targetAnchor = anchorCounter;
4994
- const segStart = pts.length / 2;
4995
- const arcAccum = [];
4996
- const total = flattenCubicWithArcLen(prevX, prevY, c1x, c1y, c2x, c2y, ex, ey, tolerance, pts, arcAccum);
4997
- for (let k = 0; k < arcAccum.length; k++) {
4998
- aA.push(prevAnchor);
4999
- aB.push(targetAnchor);
5000
- aT.push(total > 0 ? arcAccum[k] / total : 0);
5001
- }
5002
- const lastIdx = segStart + arcAccum.length - 1;
5003
- aA[lastIdx] = targetAnchor;
5004
- aB[lastIdx] = targetAnchor;
5005
- aT[lastIdx] = 0;
5006
- prevX = ex;
5007
- prevY = ey;
5008
- prevAnchor = targetAnchor;
5009
- anchorCounter++;
5010
- coordIdx += 6;
5011
- break;
5012
- }
5013
- case PATH_Z2: {
5014
- if (current) {
5015
- current.closed = true;
5016
- dropDuplicateClosingPoint();
5017
- }
5018
- break;
5019
- }
5020
- default:
5021
- throw new Error(`extractPolylines: unknown command code ${cmd}`);
5022
- }
5023
- }
5024
- if (current) commit(current);
5025
- return out;
5026
- }
5027
- var EMPTY_MESH = {
5028
- vertices: new Float32Array(0),
5029
- indices: new Uint32Array(0),
5030
- anchorA: new Uint32Array(0),
5031
- anchorB: new Uint32Array(0),
5032
- anchorT: new Float32Array(0)
5033
- };
5034
- function tessellateStroke(path, stroke, opts = {}) {
5035
- const width = stroke.width ?? 1;
5036
- if (width <= 0) return EMPTY_MESH;
5037
- const join = stroke.join ?? "miter";
5038
- const cap = stroke.cap ?? "butt";
5039
- const align = stroke.align ?? "center";
5040
- const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;
5041
- const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;
5042
- let workingPath = path;
5043
- if (path.kind === "rect" && align !== "center") {
5044
- const aligned = alignedStrokeRect(path, align, width);
5045
- workingPath = {
5046
- kind: "rect",
5047
- x: aligned.x,
5048
- y: aligned.y,
5049
- width: aligned.width,
5050
- height: aligned.height
5051
- };
5052
- }
5053
- const polylines = extractPolylines(workingPath, opts);
5054
- if (stroke.vertexWidths && stroke.vertexWidths.length > 0) {
5055
- for (const pl of polylines) populatePolylineWidths(pl, stroke.vertexWidths, width);
5056
- }
5057
- const dash = stroke.dash ?? [];
5058
- const verts = [];
5059
- const idx = [];
5060
- const anchorA = [];
5061
- const anchorB = [];
5062
- const anchorT = [];
5063
- for (const pl of polylines) {
5064
- const subs = dash.length > 0 ? splitForDash(pl, dash) : [pl];
5065
- for (const sub2 of subs) {
5066
- expandPolyline(sub2, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);
5067
- }
5068
- }
5069
- return {
5070
- vertices: new Float32Array(verts),
5071
- indices: new Uint32Array(idx),
5072
- anchorA: new Uint32Array(anchorA),
5073
- anchorB: new Uint32Array(anchorB),
5074
- anchorT: new Float32Array(anchorT)
5075
- };
5076
- }
5077
- function expandPolyline(pl, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT) {
5078
- const half = width / 2;
5079
- const pts = pl.points;
5080
- const ptCount = pts.length / 2;
5081
- const segCount = ptCount - 1;
5082
- if (segCount < 1) return;
5083
- const plA = pl.anchorA ?? new Uint32Array(ptCount);
5084
- const plB = pl.anchorB ?? new Uint32Array(ptCount);
5085
- const plT = pl.anchorT ?? new Float32Array(ptCount);
5086
- const plHalf = pl.widths ? pl.widths.map((w) => w / 2) : null;
5087
- const segs = [];
5088
- const segSrcIdx = [];
5089
- for (let s = 0; s < segCount; s++) {
5090
- const ha = plHalf ? plHalf[s] : half;
5091
- const hb = plHalf ? plHalf[s + 1] : half;
5092
- const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], ha, hb);
5093
- if (seg) {
5094
- segs.push(seg);
5095
- segSrcIdx.push(s);
5096
- }
5097
- }
5098
- let closerSrcIdx = -1;
5099
- if (pl.closed && segs.length >= 1) {
5100
- const last = segs[segs.length - 1];
5101
- const first = segs[0];
5102
- const haCloser = plHalf ? plHalf[ptCount - 1] : half;
5103
- const hbCloser = plHalf ? plHalf[0] : half;
5104
- const closer = makeSeg(last.bx, last.by, first.ax, first.ay, haCloser, hbCloser);
5105
- if (closer) {
5106
- segs.push(closer);
5107
- closerSrcIdx = ptCount - 1;
5108
- segSrcIdx.push(closerSrcIdx);
5109
- }
5110
- }
5111
- if (segs.length === 0) return;
5112
- const segBaseIdx = [];
5113
- for (let s = 0; s < segs.length; s++) {
5114
- const seg = segs[s];
5115
- const startSrc = segSrcIdx[s];
5116
- const endSrc = s === segs.length - 1 && pl.closed && closerSrcIdx >= 0 ? 0 : startSrc + 1;
5117
- const base = verts.length / 2;
5118
- segBaseIdx.push(base);
5119
- const nxA = seg.nxUnit * seg.halfA, nyA = seg.nyUnit * seg.halfA;
5120
- const nxB = seg.nxUnit * seg.halfB, nyB = seg.nyUnit * seg.halfB;
5121
- verts.push(seg.ax + nxA, seg.ay + nyA);
5122
- anchorA.push(plA[startSrc]);
5123
- anchorB.push(plB[startSrc]);
5124
- anchorT.push(plT[startSrc]);
5125
- verts.push(seg.ax - nxA, seg.ay - nyA);
5126
- anchorA.push(plA[startSrc]);
5127
- anchorB.push(plB[startSrc]);
5128
- anchorT.push(plT[startSrc]);
5129
- verts.push(seg.bx + nxB, seg.by + nyB);
5130
- anchorA.push(plA[endSrc]);
5131
- anchorB.push(plB[endSrc]);
5132
- anchorT.push(plT[endSrc]);
5133
- verts.push(seg.bx - nxB, seg.by - nyB);
5134
- anchorA.push(plA[endSrc]);
5135
- anchorB.push(plB[endSrc]);
5136
- anchorT.push(plT[endSrc]);
5137
- idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
5138
- }
5139
- const joinCount = pl.closed ? segs.length : segs.length - 1;
5140
- for (let j = 0; j < joinCount; j++) {
5141
- emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);
5142
- }
5143
- if (!pl.closed && cap !== "butt") {
5144
- const first = segs[0];
5145
- const firstBase = segBaseIdx[0];
5146
- emitCap(first, firstBase + 0, firstBase + 1, false, first.halfA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);
5147
- const last = segs[segs.length - 1];
5148
- const lastBase = segBaseIdx[segs.length - 1];
5149
- const lastSrc = segSrcIdx[segs.length - 1] + 1;
5150
- emitCap(last, lastBase + 2, lastBase + 3, true, last.halfB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);
5151
- }
5152
- }
5153
- function populatePolylineWidths(pl, vertexWidths, fallbackWidth) {
5154
- const ptCount = pl.points.length / 2;
5155
- if (ptCount === 0) return;
5156
- const aA = pl.anchorA ?? new Uint32Array(ptCount);
5157
- const aB = pl.anchorB ?? new Uint32Array(ptCount);
5158
- const aT = pl.anchorT ?? new Float32Array(ptCount);
5159
- const widths = new Float32Array(ptCount);
5160
- const read2 = (i) => {
5161
- const v = vertexWidths[i];
5162
- return typeof v === "number" && isFinite(v) && v > 0 ? v : fallbackWidth;
5163
- };
5164
- for (let i = 0; i < ptCount; i++) {
5165
- const a = aA[i], b = aB[i], t = aT[i];
5166
- if (a === b) {
5167
- widths[i] = read2(a);
5168
- } else {
5169
- const wa = read2(a);
5170
- const wb = read2(b);
5171
- widths[i] = wa + (wb - wa) * t;
5172
- }
5173
- }
5174
- pl.widths = widths;
5175
- }
5176
- var ROUND_CAP_STEP_RAD = 10 * Math.PI / 180;
5177
- function emitCap(seg, leftIdx, rightIdx, atEnd, half, cap, verts, idx, anchorA, anchorB, anchorT, endA, endB, endT) {
5178
- const sign2 = atEnd ? 1 : -1;
5179
- const dx = (seg.bx - seg.ax) / seg.len * sign2;
5180
- const dy = (seg.by - seg.ay) / seg.len * sign2;
5181
- const cx = atEnd ? seg.bx : seg.ax;
5182
- const cy = atEnd ? seg.by : seg.ay;
5183
- const nx = seg.nxUnit * half;
5184
- const ny = seg.nyUnit * half;
5185
- if (cap === "square") {
5186
- const ox = cx + dx * half;
5187
- const oy = cy + dy * half;
5188
- const lOut = verts.length / 2;
5189
- verts.push(ox + nx, oy + ny);
5190
- anchorA.push(endA);
5191
- anchorB.push(endB);
5192
- anchorT.push(endT);
5193
- const rOut = verts.length / 2;
5194
- verts.push(ox - nx, oy - ny);
5195
- anchorA.push(endA);
5196
- anchorB.push(endB);
5197
- anchorT.push(endT);
5198
- if (atEnd) {
5199
- idx.push(leftIdx, lOut, rOut, leftIdx, rOut, rightIdx);
5200
- } else {
5201
- idx.push(leftIdx, rOut, lOut, leftIdx, rightIdx, rOut);
5202
- }
5203
- return;
5204
- }
5205
- if (cap === "round") {
5206
- const pivotIdx = verts.length / 2;
5207
- verts.push(cx, cy);
5208
- anchorA.push(endA);
5209
- anchorB.push(endB);
5210
- anchorT.push(endT);
5211
- const startAngle = Math.atan2(ny, nx);
5212
- const sweep = atEnd ? -Math.PI : Math.PI;
5213
- const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_CAP_STEP_RAD));
5214
- const stepAngle = sweep / steps;
5215
- let prevIdx = leftIdx;
5216
- for (let i = 1; i < steps; i++) {
5217
- const ang = startAngle + i * stepAngle;
5218
- const fx = cx + Math.cos(ang) * half;
5219
- const fy = cy + Math.sin(ang) * half;
5220
- const newIdx = verts.length / 2;
5221
- verts.push(fx, fy);
5222
- anchorA.push(endA);
5223
- anchorB.push(endB);
5224
- anchorT.push(endT);
5225
- if (atEnd) idx.push(prevIdx, newIdx, pivotIdx);
5226
- else idx.push(prevIdx, pivotIdx, newIdx);
5227
- prevIdx = newIdx;
5228
- }
5229
- if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);
5230
- else idx.push(prevIdx, pivotIdx, rightIdx);
5231
- }
5232
- }
5233
- function splitForDash(pl, dash) {
5234
- const out = [];
5235
- const plA = pl.anchorA ?? new Uint32Array(pl.points.length / 2);
5236
- const plB = pl.anchorB ?? new Uint32Array(pl.points.length / 2);
5237
- const plT = pl.anchorT ?? new Float32Array(pl.points.length / 2);
5238
- let dashIdx = 0;
5239
- let dashRemaining = dash[0];
5240
- let onPhase = true;
5241
- let curPts = onPhase ? [pl.points[0], pl.points[1]] : null;
5242
- let curA = onPhase ? [plA[0]] : null;
5243
- let curB = onPhase ? [plB[0]] : null;
5244
- let curT = onPhase ? [plT[0]] : null;
5245
- const flushAndAdvance = () => {
5246
- if (curPts && curPts.length >= 4) {
5247
- out.push({
5248
- points: curPts,
5249
- closed: false,
5250
- anchorA: new Uint32Array(curA),
5251
- anchorB: new Uint32Array(curB),
5252
- anchorT: new Float32Array(curT)
5253
- });
5254
- }
5255
- curPts = null;
5256
- curA = null;
5257
- curB = null;
5258
- curT = null;
5259
- dashIdx = (dashIdx + 1) % dash.length;
5260
- dashRemaining = dash[dashIdx];
5261
- onPhase = !onPhase;
5262
- };
5263
- let prevX = pl.points[0], prevY = pl.points[1];
5264
- const ptCount = pl.points.length / 2;
5265
- const segCount = pl.closed ? ptCount : ptCount - 1;
5266
- for (let i = 0; i < segCount; i++) {
5267
- const nextIdx = (i + 1) % ptCount;
5268
- const cx = pl.points[nextIdx * 2], cy = pl.points[nextIdx * 2 + 1];
5269
- const startA = plA[i], startB = plB[i], startT = plT[i];
5270
- const endA = plA[nextIdx], endB = plB[nextIdx], endT = plT[nextIdx];
5271
- const segFullDx = cx - prevX, segFullDy = cy - prevY;
5272
- const segFullLen = Math.hypot(segFullDx, segFullDy);
5273
- let segDx = segFullDx, segDy = segFullDy;
5274
- let segLen = segFullLen;
5275
- let traveled = 0;
5276
- while (segLen > 1e-9) {
5277
- if (segLen <= dashRemaining) {
5278
- if (onPhase && curPts) {
5279
- curPts.push(cx, cy);
5280
- curA.push(endA);
5281
- curB.push(endB);
5282
- curT.push(endT);
5283
- }
5284
- dashRemaining -= segLen;
5285
- prevX = cx;
5286
- prevY = cy;
5287
- traveled = segFullLen;
5288
- segLen = 0;
5289
- if (dashRemaining <= 1e-9) {
5290
- flushAndAdvance();
5291
- if (onPhase) {
5292
- curPts = [prevX, prevY];
5293
- curA = [endA];
5294
- curB = [endB];
5295
- curT = [endT];
5296
- }
5297
- }
5298
- } else {
5299
- const tConsume = dashRemaining / segLen;
5300
- const ix = prevX + segDx * tConsume;
5301
- const iy = prevY + segDy * tConsume;
5302
- traveled += dashRemaining;
5303
- const frac = traveled / segFullLen;
5304
- let mA, mB, mT;
5305
- if (startA === endA && startB === endB) {
5306
- mA = startA;
5307
- mB = startB;
5308
- mT = startT + (endT - startT) * frac;
5309
- } else {
5310
- if (frac < 0.5) {
5311
- mA = startA;
5312
- mB = startB;
5313
- mT = startT;
5314
- } else {
5315
- mA = endA;
5316
- mB = endB;
5317
- mT = endT;
5318
- }
5319
- }
5320
- if (onPhase && curPts) {
5321
- curPts.push(ix, iy);
5322
- curA.push(mA);
5323
- curB.push(mB);
5324
- curT.push(mT);
5325
- }
5326
- prevX = ix;
5327
- prevY = iy;
5328
- segDx = cx - prevX;
5329
- segDy = cy - prevY;
5330
- segLen = Math.hypot(segDx, segDy);
5331
- flushAndAdvance();
5332
- if (onPhase) {
5333
- curPts = [prevX, prevY];
5334
- curA = [mA];
5335
- curB = [mB];
5336
- curT = [mT];
5337
- }
5338
- }
5339
- }
5340
- }
5341
- if (curPts && curPts.length >= 4) {
5342
- out.push({
5343
- points: curPts,
5344
- closed: false,
5345
- anchorA: new Uint32Array(curA),
5346
- anchorB: new Uint32Array(curB),
5347
- anchorT: new Float32Array(curT)
5348
- });
5349
- }
5350
- return out;
5351
- }
5352
- function makeSeg(ax, ay, bx, by, halfA, halfB) {
5353
- const dx = bx - ax, dy = by - ay;
5354
- const len = Math.hypot(dx, dy);
5355
- if (len === 0) return null;
5356
- return {
5357
- ax,
5358
- ay,
5359
- bx,
5360
- by,
5361
- nxUnit: -dy / len,
5362
- nyUnit: dx / len,
5363
- halfA,
5364
- halfB,
5365
- len
5366
- };
5367
- }
5368
- var DEFAULT_MITER_LIMIT = 10;
5369
- var DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;
5370
- function emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT) {
5371
- const a = segs[j];
5372
- const b = segs[(j + 1) % segs.length];
5373
- const aBase = segBaseIdx[j];
5374
- const bBase = segBaseIdx[(j + 1) % segs.length];
5375
- const adx = a.bx - a.ax, ady = a.by - a.ay;
5376
- const bdx = b.bx - b.ax, bdy = b.by - b.ay;
5377
- const cross = adx * bdy - ady * bdx;
5378
- if (cross === 0) return;
5379
- const onPositive = cross > 0;
5380
- const aOuterEnd = onPositive ? aBase + 3 : aBase + 2;
5381
- const bOuterStart = onPositive ? bBase + 1 : bBase + 0;
5382
- const nextSeg = (j + 1) % segs.length;
5383
- const cornerSrc = segSrcIdx[nextSeg];
5384
- const cornerA = plA[cornerSrc];
5385
- const cornerB = plB[cornerSrc];
5386
- const cornerT = plT[cornerSrc];
5387
- const halfAtCorner = a.halfB;
5388
- const taperA = Math.max(a.halfA, a.halfB) / Math.max(1e-9, Math.min(a.halfA, a.halfB));
5389
- const taperB = Math.max(b.halfA, b.halfB) / Math.max(1e-9, Math.min(b.halfA, b.halfB));
5390
- const forceBevelForVarying = taperA > varyingThreshold || taperB > varyingThreshold;
5391
- if (join === "bevel" || forceBevelForVarying) {
5392
- emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
5393
- return;
5394
- }
5395
- if (join === "miter") {
5396
- const aOX = verts[aOuterEnd * 2], aOY = verts[aOuterEnd * 2 + 1];
5397
- const bOX = verts[bOuterStart * 2], bOY = verts[bOuterStart * 2 + 1];
5398
- const apex = lineLineIntersect(aOX, aOY, adx, ady, bOX, bOY, -bdx, -bdy);
5399
- if (!apex) {
5400
- emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
5401
- return;
5402
- }
5403
- const miterLen = Math.hypot(apex[0] - a.bx, apex[1] - a.by);
5404
- if (miterLen > miterLimit * halfAtCorner) {
5405
- emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
5406
- return;
5407
- }
5408
- const apexIdx = verts.length / 2;
5409
- verts.push(apex[0], apex[1]);
5410
- anchorA.push(cornerA);
5411
- anchorB.push(cornerB);
5412
- anchorT.push(cornerT);
5413
- const jIdx = verts.length / 2;
5414
- verts.push(a.bx, a.by);
5415
- anchorA.push(cornerA);
5416
- anchorB.push(cornerB);
5417
- anchorT.push(cornerT);
5418
- if (onPositive) {
5419
- idx.push(aOuterEnd, apexIdx, bOuterStart);
5420
- idx.push(aOuterEnd, bOuterStart, jIdx);
5421
- } else {
5422
- idx.push(aOuterEnd, bOuterStart, apexIdx);
5423
- idx.push(aOuterEnd, jIdx, bOuterStart);
5424
- }
5425
- return;
5426
- }
5427
- if (join === "round") {
5428
- emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
5429
- return;
5430
- }
5431
- }
5432
- var ROUND_STEP_RAD = 10 * Math.PI / 180;
5433
- function emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
5434
- const cx = a.bx, cy = a.by;
5435
- const pivotIdx = verts.length / 2;
5436
- verts.push(cx, cy);
5437
- anchorA.push(cornerA);
5438
- anchorB.push(cornerB);
5439
- anchorT.push(cornerT);
5440
- const startX = verts[aOuterEnd * 2] - cx;
5441
- const startY = verts[aOuterEnd * 2 + 1] - cy;
5442
- const endX = verts[bOuterStart * 2] - cx;
5443
- const endY = verts[bOuterStart * 2 + 1] - cy;
5444
- const r = Math.hypot(startX, startY);
5445
- const startAngle = Math.atan2(startY, startX);
5446
- const endAngle = Math.atan2(endY, endX);
5447
- let sweep = endAngle - startAngle;
5448
- if (onPositive && sweep < 0) sweep += 2 * Math.PI;
5449
- else if (!onPositive && sweep > 0) sweep -= 2 * Math.PI;
5450
- const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_STEP_RAD));
5451
- const stepAngle = sweep / steps;
5452
- let prevIdx = aOuterEnd;
5453
- for (let i = 1; i < steps; i++) {
5454
- const ang = startAngle + i * stepAngle;
5455
- const fx = cx + Math.cos(ang) * r;
5456
- const fy = cy + Math.sin(ang) * r;
5457
- const newIdx = verts.length / 2;
5458
- verts.push(fx, fy);
5459
- anchorA.push(cornerA);
5460
- anchorB.push(cornerB);
5461
- anchorT.push(cornerT);
5462
- if (onPositive) idx.push(prevIdx, pivotIdx, newIdx);
5463
- else idx.push(prevIdx, newIdx, pivotIdx);
5464
- prevIdx = newIdx;
5465
- }
5466
- if (onPositive) idx.push(prevIdx, pivotIdx, bOuterStart);
5467
- else idx.push(prevIdx, bOuterStart, pivotIdx);
5468
- }
5469
- function emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
5470
- const jIdx = verts.length / 2;
5471
- verts.push(a.bx, a.by);
5472
- anchorA.push(cornerA);
5473
- anchorB.push(cornerB);
5474
- anchorT.push(cornerT);
5475
- if (onPositive) idx.push(aOuterEnd, jIdx, bOuterStart);
5476
- else idx.push(aOuterEnd, bOuterStart, jIdx);
5477
- }
5478
- function lineLineIntersect(apx, apy, adx, ady, bpx, bpy, bdx, bdy) {
5479
- const denom = adx * bdy - ady * bdx;
5480
- if (Math.abs(denom) < 1e-9) return null;
5481
- const t = ((bpx - apx) * bdy - (bpy - apy) * bdx) / denom;
5482
- return [apx + t * adx, apy + t * ady];
5483
- }
5484
- var UNDERLINE_OFFSET = 0.1;
5485
- var STRIKETHROUGH_OFFSET = -0.3;
5486
- var DECORATION_THICKNESS = 0.05;
5487
- function fillKey(p) {
5488
- if ("color" in p) return `s:${p.color}:${p.opacity ?? 1}`;
5489
- return `nx:${Math.random()}`;
5490
- }
5491
- function sameFill(a, b) {
5492
- if (a === b) return true;
5493
- if ("color" in a && "color" in b) {
5494
- return a.color === b.color && (a.opacity ?? 1) === (b.opacity ?? 1);
5495
- }
5496
- return false;
4562
+ function sameFill(a, b) {
4563
+ if (a === b) return true;
4564
+ if ("color" in a && "color" in b) {
4565
+ return a.color === b.color && (a.opacity ?? 1) === (b.opacity ?? 1);
4566
+ }
4567
+ return false;
5497
4568
  }
5498
4569
  function strokeKey(s) {
5499
4570
  if (!s) return "-";
@@ -5643,496 +4714,1096 @@ function layoutRuns(runs, opts) {
5643
4714
  prevFontSize = run.fontSize;
5644
4715
  continue;
5645
4716
  }
5646
- const glyphFont = hit.font;
5647
- const glyphScale = run.fontSize / glyphFont.info.size;
5648
- let kerningBefore = 0;
5649
- if (prevCp !== void 0 && prevFont !== void 0 && prevFontSize !== void 0) {
5650
- const kAtlas = prevFont.kerningMap.get(prevCp)?.get(cp) ?? 0;
5651
- kerningBefore = kAtlas * (prevFontSize / prevFont.info.size);
4717
+ const glyphFont = hit.font;
4718
+ const glyphScale = run.fontSize / glyphFont.info.size;
4719
+ let kerningBefore = 0;
4720
+ if (prevCp !== void 0 && prevFont !== void 0 && prevFontSize !== void 0) {
4721
+ const kAtlas = prevFont.kerningMap.get(prevCp)?.get(cp) ?? 0;
4722
+ kerningBefore = kAtlas * (prevFontSize / prevFont.info.size);
4723
+ }
4724
+ entries.push({
4725
+ run,
4726
+ font: glyphFont,
4727
+ glyph: hit.glyph,
4728
+ cp,
4729
+ advance: hit.glyph.xadvance * glyphScale,
4730
+ tracking,
4731
+ kerningBefore,
4732
+ isSpace,
4733
+ isNewline: false,
4734
+ resolved: hit.resolved,
4735
+ fontSize: run.fontSize
4736
+ });
4737
+ prevCp = cp;
4738
+ prevFont = glyphFont;
4739
+ prevFontSize = run.fontSize;
4740
+ }
4741
+ }
4742
+ const lines = [];
4743
+ let cur = { entries: [], width: 0, height: 0 };
4744
+ function commitLine() {
4745
+ lines.push(cur);
4746
+ cur = { entries: [], width: 0, height: 0 };
4747
+ }
4748
+ let i = 0;
4749
+ while (i < entries.length) {
4750
+ const e = entries[i];
4751
+ if (e.isNewline) {
4752
+ if (cur.entries.length === 0) {
4753
+ cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
4754
+ cur.blank = e;
4755
+ }
4756
+ commitLine();
4757
+ i++;
4758
+ continue;
4759
+ }
4760
+ if (e.isSpace) {
4761
+ if (cur.entries.length > 0) {
4762
+ cur.entries.push(e);
4763
+ cur.width += e.kerningBefore + e.advance + e.tracking;
4764
+ cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
4765
+ }
4766
+ i++;
4767
+ continue;
4768
+ }
4769
+ let j = i;
4770
+ let wordWidth = 0;
4771
+ while (j < entries.length && !entries[j].isSpace && !entries[j].isNewline) {
4772
+ const w = entries[j];
4773
+ wordWidth += w.kerningBefore + w.advance + w.tracking;
4774
+ j++;
4775
+ }
4776
+ if (Number.isFinite(opts.maxWidth) && cur.width + wordWidth > opts.maxWidth && cur.entries.length > 0) {
4777
+ commitLine();
4778
+ }
4779
+ for (let k = i; k < j; k++) {
4780
+ const w = entries[k];
4781
+ const kerningBefore = cur.entries.length === 0 ? 0 : w.kerningBefore;
4782
+ cur.entries.push({ ...w, kerningBefore });
4783
+ cur.width += kerningBefore + w.advance + w.tracking;
4784
+ cur.height = Math.max(cur.height, w.fontSize * opts.lineHeight);
4785
+ }
4786
+ i = j;
4787
+ }
4788
+ if (cur.entries.length > 0) commitLine();
4789
+ function outlineFor(e) {
4790
+ const min = opts.outlineMinSize;
4791
+ if (min === void 0 || e.fontSize < min) return null;
4792
+ if (e.resolved.synthetic.bold) return null;
4793
+ const r = e.resolved.resolved;
4794
+ return glyphOutline(r.family, r.weight, r.style, e.cp);
4795
+ }
4796
+ const decorations = [];
4797
+ let span = null;
4798
+ function flushSpan() {
4799
+ const s = span;
4800
+ span = null;
4801
+ if (!s || s.x1 <= s.x0) return;
4802
+ const thickness = s.fontSize * DECORATION_THICKNESS;
4803
+ if (s.underline) {
4804
+ const y0 = s.baselineY + s.fontSize * UNDERLINE_OFFSET;
4805
+ decorations.push({ kind: "underline", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
4806
+ }
4807
+ if (s.strikethrough) {
4808
+ const y0 = s.baselineY + s.fontSize * STRIKETHROUGH_OFFSET;
4809
+ decorations.push({ kind: "strikethrough", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
4810
+ }
4811
+ }
4812
+ const lineBoxes = [];
4813
+ let penY = 0;
4814
+ let maxLineWidth = 0;
4815
+ const finiteWidth = Number.isFinite(opts.maxWidth) ? opts.maxWidth : 0;
4816
+ for (const line of lines) {
4817
+ const alignShift = (() => {
4818
+ if (opts.align === "left") return 0;
4819
+ if (!Number.isFinite(opts.maxWidth)) {
4820
+ return opts.align === "center" ? -line.width / 2 : -line.width;
4821
+ }
4822
+ const slack = finiteWidth - line.width;
4823
+ return opts.align === "center" ? slack / 2 : slack;
4824
+ })();
4825
+ const lineX0 = alignShift;
4826
+ const baselineSource = line.entries[0] ?? line.blank;
4827
+ const lineBaselineY = baselineSource ? penY + baselineSource.font.common.base * (baselineSource.fontSize / baselineSource.font.info.size) : penY;
4828
+ let penX = lineX0;
4829
+ for (const e of line.entries) {
4830
+ penX += e.kerningBefore;
4831
+ const step = e.advance + e.tracking;
4832
+ const scale2 = e.fontSize / e.font.info.size;
4833
+ const baselineY = penY + e.font.common.base * scale2;
4834
+ if (e.run.underline || e.run.strikethrough) {
4835
+ if (span !== null && span.underline === e.run.underline && span.strikethrough === e.run.strikethrough && span.fontSize === e.fontSize && span.baselineY === baselineY && sameFill(span.fill, e.run.fill)) {
4836
+ span.x1 = penX + step;
4837
+ } else {
4838
+ flushSpan();
4839
+ span = {
4840
+ underline: e.run.underline,
4841
+ strikethrough: e.run.strikethrough,
4842
+ fill: e.run.fill,
4843
+ fontSize: e.fontSize,
4844
+ baselineY,
4845
+ x0: penX,
4846
+ x1: penX + step
4847
+ };
4848
+ }
4849
+ } else {
4850
+ flushSpan();
4851
+ }
4852
+ const outlineD = outlineFor(e);
4853
+ if (outlineD !== null) {
4854
+ const group2 = getOrCreateGroup(ctx, e.run, e.resolved, 0, "outline");
4855
+ group2.glyphs.push({
4856
+ d: outlineD,
4857
+ key: `${e.resolved.resolved.family}|${e.resolved.resolved.weight}|${e.resolved.resolved.style}|${e.cp}`,
4858
+ x: penX,
4859
+ baselineY,
4860
+ // Em space is unit-scale, so world units per em is just the size.
4861
+ scale: e.fontSize
4862
+ });
4863
+ penX += step;
4864
+ continue;
4865
+ }
4866
+ if (e.advance === 0 || e.glyph.width === 0 || e.glyph.page < 0) {
4867
+ penX += step;
4868
+ continue;
5652
4869
  }
5653
- entries.push({
5654
- run,
5655
- font: glyphFont,
5656
- glyph: hit.glyph,
5657
- cp,
5658
- advance: hit.glyph.xadvance * glyphScale,
5659
- tracking,
5660
- kerningBefore,
5661
- isSpace,
5662
- isNewline: false,
5663
- resolved: hit.resolved,
5664
- fontSize: run.fontSize
5665
- });
5666
- prevCp = cp;
5667
- prevFont = glyphFont;
5668
- prevFontSize = run.fontSize;
4870
+ const group = getOrCreateGroup(ctx, e.run, e.resolved, e.glyph.page);
4871
+ const atlasW = e.font.common.scaleW;
4872
+ const atlasH = e.font.common.scaleH;
4873
+ const qx0 = penX + e.glyph.xoffset * scale2;
4874
+ const qy0 = penY + e.glyph.yoffset * scale2;
4875
+ const qx1 = qx0 + e.glyph.width * scale2;
4876
+ const qy1 = qy0 + e.glyph.height * scale2;
4877
+ const u0 = e.glyph.x / atlasW;
4878
+ const v0 = e.glyph.y / atlasH;
4879
+ const u1 = (e.glyph.x + e.glyph.width) / atlasW;
4880
+ const v1 = (e.glyph.y + e.glyph.height) / atlasH;
4881
+ group.quads.push({ x0: qx0, y0: qy0, x1: qx1, y1: qy1, u0, v0, u1, v1, baselineY });
4882
+ penX += step;
5669
4883
  }
4884
+ flushSpan();
4885
+ lineBoxes.push({
4886
+ x0: lineX0,
4887
+ y0: penY,
4888
+ x1: lineX0 + line.width,
4889
+ y1: penY + line.height,
4890
+ baselineY: lineBaselineY
4891
+ });
4892
+ maxLineWidth = Math.max(maxLineWidth, line.width);
4893
+ penY += line.height;
5670
4894
  }
5671
- const lines = [];
5672
- let cur = { entries: [], width: 0, height: 0 };
5673
- function commitLine() {
5674
- lines.push(cur);
5675
- cur = { entries: [], width: 0, height: 0 };
4895
+ return {
4896
+ groups: [...ctx.groups.values()],
4897
+ decorations,
4898
+ lines: lineBoxes,
4899
+ bounds: { width: maxLineWidth, height: penY }
4900
+ };
4901
+ }
4902
+ var LAYOUT_CACHE_VARIANT_LIMIT = 8;
4903
+ var cache2 = /* @__PURE__ */ new WeakMap();
4904
+ function outlineBucket(runs, min) {
4905
+ if (min === void 0) return -1;
4906
+ const sizes = /* @__PURE__ */ new Set();
4907
+ for (const r of runs) sizes.add(r.fontSize);
4908
+ let n = 0;
4909
+ for (const size of sizes) if (size >= min) n++;
4910
+ return n;
4911
+ }
4912
+ function variantKey2(runs, opts) {
4913
+ return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}`;
4914
+ }
4915
+ function cachedLayoutRuns(runs, opts) {
4916
+ const fonts = glyphGeneration();
4917
+ let entry = cache2.get(runs);
4918
+ if (entry === void 0) {
4919
+ entry = { generation: fonts, byVariant: /* @__PURE__ */ new Map() };
4920
+ cache2.set(runs, entry);
4921
+ } else if (entry.generation !== fonts) {
4922
+ entry.generation = fonts;
4923
+ entry.byVariant.clear();
5676
4924
  }
4925
+ const key = variantKey2(runs, opts);
4926
+ const hit = entry.byVariant.get(key);
4927
+ if (hit !== void 0) return hit;
4928
+ const laid = layoutRuns(runs, opts);
4929
+ if (entry.byVariant.size >= LAYOUT_CACHE_VARIANT_LIMIT) entry.byVariant.clear();
4930
+ entry.byVariant.set(key, laid);
4931
+ return laid;
4932
+ }
4933
+ function verticalAlignOffset(align, boxHeight, textHeight) {
4934
+ if (align === void 0 || align === "top" || boxHeight === void 0) return 0;
4935
+ const slack = boxHeight - textHeight;
4936
+ return align === "center" ? slack / 2 : slack;
4937
+ }
4938
+ var NUM_RE = /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g;
4939
+ function tokenize(d) {
4940
+ const out = [];
5677
4941
  let i = 0;
5678
- while (i < entries.length) {
5679
- const e = entries[i];
5680
- if (e.isNewline) {
5681
- if (cur.entries.length === 0) {
5682
- cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
5683
- cur.blank = e;
5684
- }
5685
- commitLine();
4942
+ while (i < d.length) {
4943
+ const ch = d[i];
4944
+ if (/[A-Za-z]/.test(ch)) {
4945
+ let j = i + 1;
4946
+ while (j < d.length && !/[A-Za-z]/.test(d[j])) j++;
4947
+ const argSlice = d.slice(i + 1, j);
4948
+ out.push({ cmd: ch, args: readNumbers(argSlice) });
4949
+ i = j;
4950
+ } else {
5686
4951
  i++;
4952
+ }
4953
+ }
4954
+ return out;
4955
+ }
4956
+ function readNumbers(s) {
4957
+ const out = [];
4958
+ let m;
4959
+ NUM_RE.lastIndex = 0;
4960
+ while ((m = NUM_RE.exec(s)) !== null) {
4961
+ const n = Number(m[0]);
4962
+ if (Number.isFinite(n)) out.push(n);
4963
+ }
4964
+ return out;
4965
+ }
4966
+ var ARG_COUNTS = {
4967
+ M: 2,
4968
+ L: 2,
4969
+ H: 1,
4970
+ V: 1,
4971
+ C: 6,
4972
+ S: 4,
4973
+ Q: 4,
4974
+ T: 2,
4975
+ A: 7,
4976
+ Z: 0
4977
+ };
4978
+ function pathFromD(d, onWarn) {
4979
+ const tokens = tokenize(d);
4980
+ const b = new PathBuilder();
4981
+ const st = {
4982
+ x: 0,
4983
+ y: 0,
4984
+ startX: 0,
4985
+ startY: 0,
4986
+ lastCubicCtrl: null,
4987
+ lastQuadCtrl: null
4988
+ };
4989
+ for (const { cmd, args } of tokens) {
4990
+ const upper = cmd.toUpperCase();
4991
+ const relative = cmd !== upper;
4992
+ const argCount = ARG_COUNTS[upper];
4993
+ if (argCount === void 0) {
5687
4994
  continue;
5688
4995
  }
5689
- if (e.isSpace) {
5690
- if (cur.entries.length > 0) {
5691
- cur.entries.push(e);
5692
- cur.width += e.kerningBefore + e.advance + e.tracking;
5693
- cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
5694
- }
5695
- i++;
4996
+ if (upper === "Z") {
4997
+ b.close();
4998
+ st.x = st.startX;
4999
+ st.y = st.startY;
5000
+ st.lastCubicCtrl = null;
5001
+ st.lastQuadCtrl = null;
5696
5002
  continue;
5697
5003
  }
5698
- let j = i;
5699
- let wordWidth = 0;
5700
- while (j < entries.length && !entries[j].isSpace && !entries[j].isNewline) {
5701
- const w = entries[j];
5702
- wordWidth += w.kerningBefore + w.advance + w.tracking;
5703
- j++;
5004
+ const groups = argCount === 0 ? 1 : Math.floor(args.length / argCount);
5005
+ for (let g = 0; g < groups; g++) {
5006
+ const a = args.slice(g * argCount, (g + 1) * argCount);
5007
+ const effectiveUpper = upper === "M" && g > 0 ? "L" : upper;
5008
+ runCommand(effectiveUpper, relative, a, st, b);
5009
+ }
5010
+ }
5011
+ return b.build();
5012
+ }
5013
+ function runCommand(upper, relative, a, st, b) {
5014
+ const rx = relative ? st.x : 0;
5015
+ const ry = relative ? st.y : 0;
5016
+ switch (upper) {
5017
+ case "M": {
5018
+ const x = a[0] + rx;
5019
+ const y = a[1] + ry;
5020
+ b.moveTo(x, y);
5021
+ st.x = x;
5022
+ st.y = y;
5023
+ st.startX = x;
5024
+ st.startY = y;
5025
+ st.lastCubicCtrl = null;
5026
+ st.lastQuadCtrl = null;
5027
+ return;
5028
+ }
5029
+ case "L": {
5030
+ const x = a[0] + rx;
5031
+ const y = a[1] + ry;
5032
+ b.lineTo(x, y);
5033
+ st.x = x;
5034
+ st.y = y;
5035
+ st.lastCubicCtrl = null;
5036
+ st.lastQuadCtrl = null;
5037
+ return;
5038
+ }
5039
+ case "H": {
5040
+ const x = a[0] + (relative ? st.x : 0);
5041
+ b.lineTo(x, st.y);
5042
+ st.x = x;
5043
+ st.lastCubicCtrl = null;
5044
+ st.lastQuadCtrl = null;
5045
+ return;
5046
+ }
5047
+ case "V": {
5048
+ const y = a[0] + (relative ? st.y : 0);
5049
+ b.lineTo(st.x, y);
5050
+ st.y = y;
5051
+ st.lastCubicCtrl = null;
5052
+ st.lastQuadCtrl = null;
5053
+ return;
5054
+ }
5055
+ case "C": {
5056
+ const x1 = a[0] + rx, y1 = a[1] + ry;
5057
+ const x2 = a[2] + rx, y2 = a[3] + ry;
5058
+ const x = a[4] + rx, y = a[5] + ry;
5059
+ b.curveTo(x1, y1, x2, y2, x, y);
5060
+ st.x = x;
5061
+ st.y = y;
5062
+ st.lastCubicCtrl = { x: x2, y: y2 };
5063
+ st.lastQuadCtrl = null;
5064
+ return;
5065
+ }
5066
+ case "S": {
5067
+ const ref = st.lastCubicCtrl ?? { x: st.x, y: st.y };
5068
+ const x1 = 2 * st.x - ref.x;
5069
+ const y1 = 2 * st.y - ref.y;
5070
+ const x2 = a[0] + rx, y2 = a[1] + ry;
5071
+ const x = a[2] + rx, y = a[3] + ry;
5072
+ b.curveTo(x1, y1, x2, y2, x, y);
5073
+ st.x = x;
5074
+ st.y = y;
5075
+ st.lastCubicCtrl = { x: x2, y: y2 };
5076
+ st.lastQuadCtrl = null;
5077
+ return;
5078
+ }
5079
+ case "Q": {
5080
+ const x1 = a[0] + rx, y1 = a[1] + ry;
5081
+ const x = a[2] + rx, y = a[3] + ry;
5082
+ b.quadTo(x1, y1, x, y);
5083
+ st.x = x;
5084
+ st.y = y;
5085
+ st.lastQuadCtrl = { x: x1, y: y1 };
5086
+ st.lastCubicCtrl = null;
5087
+ return;
5704
5088
  }
5705
- if (Number.isFinite(opts.maxWidth) && cur.width + wordWidth > opts.maxWidth && cur.entries.length > 0) {
5706
- commitLine();
5089
+ case "T": {
5090
+ const ref = st.lastQuadCtrl ?? { x: st.x, y: st.y };
5091
+ const x1 = 2 * st.x - ref.x;
5092
+ const y1 = 2 * st.y - ref.y;
5093
+ const x = a[0] + rx, y = a[1] + ry;
5094
+ b.quadTo(x1, y1, x, y);
5095
+ st.x = x;
5096
+ st.y = y;
5097
+ st.lastQuadCtrl = { x: x1, y: y1 };
5098
+ st.lastCubicCtrl = null;
5099
+ return;
5707
5100
  }
5708
- for (let k = i; k < j; k++) {
5709
- const w = entries[k];
5710
- const kerningBefore = cur.entries.length === 0 ? 0 : w.kerningBefore;
5711
- cur.entries.push({ ...w, kerningBefore });
5712
- cur.width += kerningBefore + w.advance + w.tracking;
5713
- cur.height = Math.max(cur.height, w.fontSize * opts.lineHeight);
5101
+ case "A": {
5102
+ const rxA = Math.abs(a[0]);
5103
+ const ryA = Math.abs(a[1]);
5104
+ const xRot = a[2];
5105
+ const largeArc = a[3] !== 0;
5106
+ const sweep = a[4] !== 0;
5107
+ const ex = a[5] + rx;
5108
+ const ey = a[6] + ry;
5109
+ arcToCubics(st.x, st.y, ex, ey, rxA, ryA, xRot, largeArc, sweep, b);
5110
+ st.x = ex;
5111
+ st.y = ey;
5112
+ st.lastCubicCtrl = null;
5113
+ st.lastQuadCtrl = null;
5114
+ return;
5714
5115
  }
5715
- i = j;
5116
+ default:
5117
+ return;
5716
5118
  }
5717
- if (cur.entries.length > 0) commitLine();
5718
- function outlineFor(e) {
5719
- const min = opts.outlineMinSize;
5720
- if (min === void 0 || e.fontSize < min) return null;
5721
- if (e.resolved.synthetic.bold) return null;
5722
- const r = e.resolved.resolved;
5723
- return glyphOutline(r.family, r.weight, r.style, e.cp);
5119
+ }
5120
+ function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep, b) {
5121
+ if (x1 === x2 && y1 === y2) return;
5122
+ if (rx === 0 || ry === 0) {
5123
+ b.lineTo(x2, y2);
5124
+ return;
5724
5125
  }
5725
- const decorations = [];
5726
- let span = null;
5727
- function flushSpan() {
5728
- const s = span;
5729
- span = null;
5730
- if (!s || s.x1 <= s.x0) return;
5731
- const thickness = s.fontSize * DECORATION_THICKNESS;
5732
- if (s.underline) {
5733
- const y0 = s.baselineY + s.fontSize * UNDERLINE_OFFSET;
5734
- decorations.push({ kind: "underline", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
5735
- }
5736
- if (s.strikethrough) {
5737
- const y0 = s.baselineY + s.fontSize * STRIKETHROUGH_OFFSET;
5738
- decorations.push({ kind: "strikethrough", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
5739
- }
5126
+ const phi = xAxisRotationDeg * Math.PI / 180;
5127
+ const cosPhi = Math.cos(phi);
5128
+ const sinPhi = Math.sin(phi);
5129
+ const dx = (x1 - x2) / 2;
5130
+ const dy = (y1 - y2) / 2;
5131
+ const x1p = cosPhi * dx + sinPhi * dy;
5132
+ const y1p = -sinPhi * dx + cosPhi * dy;
5133
+ let rxs = rx * rx;
5134
+ let rys = ry * ry;
5135
+ const x1ps = x1p * x1p;
5136
+ const y1ps = y1p * y1p;
5137
+ const lambda = x1ps / rxs + y1ps / rys;
5138
+ let rxFixed = rx;
5139
+ let ryFixed = ry;
5140
+ if (lambda > 1) {
5141
+ const sqrtLambda = Math.sqrt(lambda);
5142
+ rxFixed = sqrtLambda * rx;
5143
+ ryFixed = sqrtLambda * ry;
5144
+ rxs = rxFixed * rxFixed;
5145
+ rys = ryFixed * ryFixed;
5740
5146
  }
5741
- const lineBoxes = [];
5742
- let penY = 0;
5743
- let maxLineWidth = 0;
5744
- const finiteWidth = Number.isFinite(opts.maxWidth) ? opts.maxWidth : 0;
5745
- for (const line of lines) {
5746
- const alignShift = (() => {
5747
- if (opts.align === "left") return 0;
5748
- if (!Number.isFinite(opts.maxWidth)) {
5749
- return opts.align === "center" ? -line.width / 2 : -line.width;
5750
- }
5751
- const slack = finiteWidth - line.width;
5752
- return opts.align === "center" ? slack / 2 : slack;
5753
- })();
5754
- const lineX0 = alignShift;
5755
- const baselineSource = line.entries[0] ?? line.blank;
5756
- const lineBaselineY = baselineSource ? penY + baselineSource.font.common.base * (baselineSource.fontSize / baselineSource.font.info.size) : penY;
5757
- let penX = lineX0;
5758
- for (const e of line.entries) {
5759
- penX += e.kerningBefore;
5760
- const step = e.advance + e.tracking;
5761
- const scale2 = e.fontSize / e.font.info.size;
5762
- const baselineY = penY + e.font.common.base * scale2;
5763
- if (e.run.underline || e.run.strikethrough) {
5764
- if (span !== null && span.underline === e.run.underline && span.strikethrough === e.run.strikethrough && span.fontSize === e.fontSize && span.baselineY === baselineY && sameFill(span.fill, e.run.fill)) {
5765
- span.x1 = penX + step;
5766
- } else {
5767
- flushSpan();
5768
- span = {
5769
- underline: e.run.underline,
5770
- strikethrough: e.run.strikethrough,
5771
- fill: e.run.fill,
5772
- fontSize: e.fontSize,
5773
- baselineY,
5774
- x0: penX,
5775
- x1: penX + step
5776
- };
5147
+ const sign2 = largeArc === sweep ? -1 : 1;
5148
+ const num = rxs * rys - rxs * y1ps - rys * x1ps;
5149
+ const den = rxs * y1ps + rys * x1ps;
5150
+ const factor = sign2 * Math.sqrt(Math.max(0, num / den));
5151
+ const cxp = factor * (rxFixed * y1p) / ryFixed;
5152
+ const cyp = factor * -(ryFixed * x1p) / rxFixed;
5153
+ const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
5154
+ const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
5155
+ const angle = (ux, uy, vx, vy) => {
5156
+ const dot = ux * vx + uy * vy;
5157
+ const len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
5158
+ let a = Math.acos(Math.max(-1, Math.min(1, dot / len)));
5159
+ if (ux * vy - uy * vx < 0) a = -a;
5160
+ return a;
5161
+ };
5162
+ const theta1 = angle(1, 0, (x1p - cxp) / rxFixed, (y1p - cyp) / ryFixed);
5163
+ let dTheta = angle(
5164
+ (x1p - cxp) / rxFixed,
5165
+ (y1p - cyp) / ryFixed,
5166
+ (-x1p - cxp) / rxFixed,
5167
+ (-y1p - cyp) / ryFixed
5168
+ );
5169
+ if (!sweep && dTheta > 0) dTheta -= 2 * Math.PI;
5170
+ else if (sweep && dTheta < 0) dTheta += 2 * Math.PI;
5171
+ const segCount = Math.max(1, Math.ceil(Math.abs(dTheta) / (Math.PI / 2)));
5172
+ const segAngle = dTheta / segCount;
5173
+ const t = 4 / 3 * Math.tan(segAngle / 4);
5174
+ let prevX = x1;
5175
+ let prevY = y1;
5176
+ let prevDx = -Math.sin(theta1) * rxFixed;
5177
+ let prevDy = Math.cos(theta1) * ryFixed;
5178
+ for (let i = 1; i <= segCount; i++) {
5179
+ const theta = theta1 + i * segAngle;
5180
+ const cosT = Math.cos(theta);
5181
+ const sinT = Math.sin(theta);
5182
+ const px = cosPhi * (cosT * rxFixed) - sinPhi * (sinT * ryFixed) + cx;
5183
+ const py = sinPhi * (cosT * rxFixed) + cosPhi * (sinT * ryFixed) + cy;
5184
+ const dxNew = -sinT * rxFixed;
5185
+ const dyNew = cosT * ryFixed;
5186
+ const c1x = prevX + cosPhi * (t * prevDx) - sinPhi * (t * prevDy);
5187
+ const c1y = prevY + sinPhi * (t * prevDx) + cosPhi * (t * prevDy);
5188
+ const c2x = px - cosPhi * (t * dxNew) + sinPhi * (t * dyNew);
5189
+ const c2y = py - sinPhi * (t * dxNew) - cosPhi * (t * dyNew);
5190
+ b.curveTo(c1x, c1y, c2x, c2y, px, py);
5191
+ prevX = px;
5192
+ prevY = py;
5193
+ prevDx = dxNew;
5194
+ prevDy = dyNew;
5195
+ }
5196
+ }
5197
+ var OUTLINE_FLATTEN_TOLERANCE = 1 / 4096;
5198
+ var OUTLINE_MESH_CACHE_LIMIT = 2048;
5199
+ var cache3 = /* @__PURE__ */ new Map();
5200
+ function outlineMesh(key, d) {
5201
+ const cached2 = cache3.get(key);
5202
+ if (cached2 !== void 0) return cached2;
5203
+ if (cache3.size >= OUTLINE_MESH_CACHE_LIMIT) cache3 = /* @__PURE__ */ new Map();
5204
+ const mesh = tessellate(pathFromD(d), { flattenTolerance: OUTLINE_FLATTEN_TOLERANCE });
5205
+ cache3.set(key, mesh);
5206
+ return mesh;
5207
+ }
5208
+ function extractPolylines(path, opts = {}) {
5209
+ if (path.kind === "rect") return [extractRect(path)];
5210
+ return extractPolygon(path, opts);
5211
+ }
5212
+ function extractRect(p) {
5213
+ const { x, y, width: w, height: h } = p;
5214
+ return {
5215
+ points: [x, y, x + w, y, x + w, y + h, x, y + h],
5216
+ closed: true,
5217
+ anchorA: new Uint32Array([0, 1, 2, 3]),
5218
+ anchorB: new Uint32Array([0, 1, 2, 3]),
5219
+ anchorT: new Float32Array([0, 0, 0, 0])
5220
+ };
5221
+ }
5222
+ function extractPolygon(p, opts) {
5223
+ const tolerance = opts.flattenTolerance ?? DEFAULT_FLATTEN_TOLERANCE;
5224
+ const { commands, coords } = p;
5225
+ const out = [];
5226
+ let anchorCounter = 0;
5227
+ let pts = null;
5228
+ let aA = null;
5229
+ let aB = null;
5230
+ let aT = null;
5231
+ let current = null;
5232
+ let coordIdx = 0;
5233
+ let prevX = 0;
5234
+ let prevY = 0;
5235
+ let prevAnchor = -1;
5236
+ const beginContour = () => {
5237
+ pts = [];
5238
+ aA = [];
5239
+ aB = [];
5240
+ aT = [];
5241
+ const next = { points: pts, closed: false };
5242
+ out.push(next);
5243
+ return next;
5244
+ };
5245
+ const dropDuplicateClosingPoint = () => {
5246
+ if (!pts || !aA || !aB || !aT) return;
5247
+ const n = pts.length / 2;
5248
+ if (n < 2) return;
5249
+ if (pts[0] !== pts[(n - 1) * 2] || pts[1] !== pts[(n - 1) * 2 + 1]) return;
5250
+ pts.length -= 2;
5251
+ aA.length -= 1;
5252
+ aB.length -= 1;
5253
+ aT.length -= 1;
5254
+ };
5255
+ const commit = (target) => {
5256
+ if (!pts || !aA || !aB || !aT) return;
5257
+ target.anchorA = new Uint32Array(aA);
5258
+ target.anchorB = new Uint32Array(aB);
5259
+ target.anchorT = new Float32Array(aT);
5260
+ };
5261
+ for (let cmdIdx = 0; cmdIdx < commands.length; cmdIdx++) {
5262
+ const cmd = commands[cmdIdx];
5263
+ switch (cmd) {
5264
+ case PATH_M2: {
5265
+ if (current) commit(current);
5266
+ current = beginContour();
5267
+ prevX = coords[coordIdx];
5268
+ prevY = coords[coordIdx + 1];
5269
+ pts.push(prevX, prevY);
5270
+ aA.push(anchorCounter);
5271
+ aB.push(anchorCounter);
5272
+ aT.push(0);
5273
+ prevAnchor = anchorCounter;
5274
+ anchorCounter++;
5275
+ coordIdx += 2;
5276
+ break;
5277
+ }
5278
+ case PATH_L2: {
5279
+ prevX = coords[coordIdx];
5280
+ prevY = coords[coordIdx + 1];
5281
+ pts.push(prevX, prevY);
5282
+ aA.push(anchorCounter);
5283
+ aB.push(anchorCounter);
5284
+ aT.push(0);
5285
+ prevAnchor = anchorCounter;
5286
+ anchorCounter++;
5287
+ coordIdx += 2;
5288
+ break;
5289
+ }
5290
+ case PATH_Q2: {
5291
+ const cx = coords[coordIdx], cy = coords[coordIdx + 1];
5292
+ const ex = coords[coordIdx + 2], ey = coords[coordIdx + 3];
5293
+ const targetAnchor = anchorCounter;
5294
+ const segStart = pts.length / 2;
5295
+ const arcAccum = [];
5296
+ const total = flattenQuadraticWithArcLen(prevX, prevY, cx, cy, ex, ey, tolerance, pts, arcAccum);
5297
+ for (let k = 0; k < arcAccum.length; k++) {
5298
+ aA.push(prevAnchor);
5299
+ aB.push(targetAnchor);
5300
+ aT.push(total > 0 ? arcAccum[k] / total : 0);
5777
5301
  }
5778
- } else {
5779
- flushSpan();
5302
+ const lastIdx = segStart + arcAccum.length - 1;
5303
+ aA[lastIdx] = targetAnchor;
5304
+ aB[lastIdx] = targetAnchor;
5305
+ aT[lastIdx] = 0;
5306
+ prevX = ex;
5307
+ prevY = ey;
5308
+ prevAnchor = targetAnchor;
5309
+ anchorCounter++;
5310
+ coordIdx += 4;
5311
+ break;
5780
5312
  }
5781
- const outlineD = outlineFor(e);
5782
- if (outlineD !== null) {
5783
- const group2 = getOrCreateGroup(ctx, e.run, e.resolved, 0, "outline");
5784
- group2.glyphs.push({
5785
- d: outlineD,
5786
- key: `${e.resolved.resolved.family}|${e.resolved.resolved.weight}|${e.resolved.resolved.style}|${e.cp}`,
5787
- x: penX,
5788
- baselineY,
5789
- // Em space is unit-scale, so world units per em is just the size.
5790
- scale: e.fontSize
5791
- });
5792
- penX += step;
5793
- continue;
5313
+ case PATH_C2: {
5314
+ const c1x = coords[coordIdx], c1y = coords[coordIdx + 1];
5315
+ const c2x = coords[coordIdx + 2], c2y = coords[coordIdx + 3];
5316
+ const ex = coords[coordIdx + 4], ey = coords[coordIdx + 5];
5317
+ const targetAnchor = anchorCounter;
5318
+ const segStart = pts.length / 2;
5319
+ const arcAccum = [];
5320
+ const total = flattenCubicWithArcLen(prevX, prevY, c1x, c1y, c2x, c2y, ex, ey, tolerance, pts, arcAccum);
5321
+ for (let k = 0; k < arcAccum.length; k++) {
5322
+ aA.push(prevAnchor);
5323
+ aB.push(targetAnchor);
5324
+ aT.push(total > 0 ? arcAccum[k] / total : 0);
5325
+ }
5326
+ const lastIdx = segStart + arcAccum.length - 1;
5327
+ aA[lastIdx] = targetAnchor;
5328
+ aB[lastIdx] = targetAnchor;
5329
+ aT[lastIdx] = 0;
5330
+ prevX = ex;
5331
+ prevY = ey;
5332
+ prevAnchor = targetAnchor;
5333
+ anchorCounter++;
5334
+ coordIdx += 6;
5335
+ break;
5794
5336
  }
5795
- if (e.advance === 0 || e.glyph.width === 0 || e.glyph.page < 0) {
5796
- penX += step;
5797
- continue;
5337
+ case PATH_Z2: {
5338
+ if (current) {
5339
+ current.closed = true;
5340
+ dropDuplicateClosingPoint();
5341
+ }
5342
+ break;
5798
5343
  }
5799
- const group = getOrCreateGroup(ctx, e.run, e.resolved, e.glyph.page);
5800
- const atlasW = e.font.common.scaleW;
5801
- const atlasH = e.font.common.scaleH;
5802
- const qx0 = penX + e.glyph.xoffset * scale2;
5803
- const qy0 = penY + e.glyph.yoffset * scale2;
5804
- const qx1 = qx0 + e.glyph.width * scale2;
5805
- const qy1 = qy0 + e.glyph.height * scale2;
5806
- const u0 = e.glyph.x / atlasW;
5807
- const v0 = e.glyph.y / atlasH;
5808
- const u1 = (e.glyph.x + e.glyph.width) / atlasW;
5809
- const v1 = (e.glyph.y + e.glyph.height) / atlasH;
5810
- group.quads.push({ x0: qx0, y0: qy0, x1: qx1, y1: qy1, u0, v0, u1, v1, baselineY });
5811
- penX += step;
5344
+ default:
5345
+ throw new Error(`extractPolylines: unknown command code ${cmd}`);
5346
+ }
5347
+ }
5348
+ if (current) commit(current);
5349
+ return out;
5350
+ }
5351
+ var EMPTY_MESH = {
5352
+ vertices: new Float32Array(0),
5353
+ indices: new Uint32Array(0),
5354
+ anchorA: new Uint32Array(0),
5355
+ anchorB: new Uint32Array(0),
5356
+ anchorT: new Float32Array(0)
5357
+ };
5358
+ function tessellateStroke(path, stroke, opts = {}) {
5359
+ const width = stroke.width ?? 1;
5360
+ if (width <= 0) return EMPTY_MESH;
5361
+ const join = stroke.join ?? "miter";
5362
+ const cap = stroke.cap ?? "butt";
5363
+ const align = stroke.align ?? "center";
5364
+ const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;
5365
+ const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;
5366
+ let workingPath = path;
5367
+ if (path.kind === "rect" && align !== "center") {
5368
+ const aligned = alignedStrokeRect(path, align, width);
5369
+ workingPath = {
5370
+ kind: "rect",
5371
+ x: aligned.x,
5372
+ y: aligned.y,
5373
+ width: aligned.width,
5374
+ height: aligned.height
5375
+ };
5376
+ }
5377
+ const polylines = extractPolylines(workingPath, opts);
5378
+ if (stroke.vertexWidths && stroke.vertexWidths.length > 0) {
5379
+ for (const pl of polylines) populatePolylineWidths(pl, stroke.vertexWidths, width);
5380
+ }
5381
+ const dash = stroke.dash ?? [];
5382
+ const verts = [];
5383
+ const idx = [];
5384
+ const anchorA = [];
5385
+ const anchorB = [];
5386
+ const anchorT = [];
5387
+ for (const pl of polylines) {
5388
+ const subs = dash.length > 0 ? splitForDash(pl, dash) : [pl];
5389
+ for (const sub2 of subs) {
5390
+ expandPolyline(sub2, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);
5812
5391
  }
5813
- flushSpan();
5814
- lineBoxes.push({
5815
- x0: lineX0,
5816
- y0: penY,
5817
- x1: lineX0 + line.width,
5818
- y1: penY + line.height,
5819
- baselineY: lineBaselineY
5820
- });
5821
- maxLineWidth = Math.max(maxLineWidth, line.width);
5822
- penY += line.height;
5823
5392
  }
5824
5393
  return {
5825
- groups: [...ctx.groups.values()],
5826
- decorations,
5827
- lines: lineBoxes,
5828
- bounds: { width: maxLineWidth, height: penY }
5394
+ vertices: new Float32Array(verts),
5395
+ indices: new Uint32Array(idx),
5396
+ anchorA: new Uint32Array(anchorA),
5397
+ anchorB: new Uint32Array(anchorB),
5398
+ anchorT: new Float32Array(anchorT)
5829
5399
  };
5830
5400
  }
5831
- var LAYOUT_CACHE_VARIANT_LIMIT = 8;
5832
- var cache2 = /* @__PURE__ */ new WeakMap();
5833
- function outlineBucket(runs, min) {
5834
- if (min === void 0) return -1;
5835
- const sizes = /* @__PURE__ */ new Set();
5836
- for (const r of runs) sizes.add(r.fontSize);
5837
- let n = 0;
5838
- for (const size of sizes) if (size >= min) n++;
5839
- return n;
5840
- }
5841
- function variantKey2(runs, opts) {
5842
- return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}`;
5843
- }
5844
- function cachedLayoutRuns(runs, opts) {
5845
- const fonts = glyphGeneration();
5846
- let entry = cache2.get(runs);
5847
- if (entry === void 0) {
5848
- entry = { generation: fonts, byVariant: /* @__PURE__ */ new Map() };
5849
- cache2.set(runs, entry);
5850
- } else if (entry.generation !== fonts) {
5851
- entry.generation = fonts;
5852
- entry.byVariant.clear();
5401
+ function expandPolyline(pl, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT) {
5402
+ const half = width / 2;
5403
+ const pts = pl.points;
5404
+ const ptCount = pts.length / 2;
5405
+ const segCount = ptCount - 1;
5406
+ if (segCount < 1) return;
5407
+ const plA = pl.anchorA ?? new Uint32Array(ptCount);
5408
+ const plB = pl.anchorB ?? new Uint32Array(ptCount);
5409
+ const plT = pl.anchorT ?? new Float32Array(ptCount);
5410
+ const plHalf = pl.widths ? pl.widths.map((w) => w / 2) : null;
5411
+ const segs = [];
5412
+ const segSrcIdx = [];
5413
+ for (let s = 0; s < segCount; s++) {
5414
+ const ha = plHalf ? plHalf[s] : half;
5415
+ const hb = plHalf ? plHalf[s + 1] : half;
5416
+ const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], ha, hb);
5417
+ if (seg) {
5418
+ segs.push(seg);
5419
+ segSrcIdx.push(s);
5420
+ }
5421
+ }
5422
+ let closerSrcIdx = -1;
5423
+ if (pl.closed && segs.length >= 1) {
5424
+ const last = segs[segs.length - 1];
5425
+ const first = segs[0];
5426
+ const haCloser = plHalf ? plHalf[ptCount - 1] : half;
5427
+ const hbCloser = plHalf ? plHalf[0] : half;
5428
+ const closer = makeSeg(last.bx, last.by, first.ax, first.ay, haCloser, hbCloser);
5429
+ if (closer) {
5430
+ segs.push(closer);
5431
+ closerSrcIdx = ptCount - 1;
5432
+ segSrcIdx.push(closerSrcIdx);
5433
+ }
5434
+ }
5435
+ if (segs.length === 0) return;
5436
+ const segBaseIdx = [];
5437
+ for (let s = 0; s < segs.length; s++) {
5438
+ const seg = segs[s];
5439
+ const startSrc = segSrcIdx[s];
5440
+ const endSrc = s === segs.length - 1 && pl.closed && closerSrcIdx >= 0 ? 0 : startSrc + 1;
5441
+ const base = verts.length / 2;
5442
+ segBaseIdx.push(base);
5443
+ const nxA = seg.nxUnit * seg.halfA, nyA = seg.nyUnit * seg.halfA;
5444
+ const nxB = seg.nxUnit * seg.halfB, nyB = seg.nyUnit * seg.halfB;
5445
+ verts.push(seg.ax + nxA, seg.ay + nyA);
5446
+ anchorA.push(plA[startSrc]);
5447
+ anchorB.push(plB[startSrc]);
5448
+ anchorT.push(plT[startSrc]);
5449
+ verts.push(seg.ax - nxA, seg.ay - nyA);
5450
+ anchorA.push(plA[startSrc]);
5451
+ anchorB.push(plB[startSrc]);
5452
+ anchorT.push(plT[startSrc]);
5453
+ verts.push(seg.bx + nxB, seg.by + nyB);
5454
+ anchorA.push(plA[endSrc]);
5455
+ anchorB.push(plB[endSrc]);
5456
+ anchorT.push(plT[endSrc]);
5457
+ verts.push(seg.bx - nxB, seg.by - nyB);
5458
+ anchorA.push(plA[endSrc]);
5459
+ anchorB.push(plB[endSrc]);
5460
+ anchorT.push(plT[endSrc]);
5461
+ idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
5462
+ }
5463
+ const joinCount = pl.closed ? segs.length : segs.length - 1;
5464
+ for (let j = 0; j < joinCount; j++) {
5465
+ emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);
5466
+ }
5467
+ if (!pl.closed && cap !== "butt") {
5468
+ const first = segs[0];
5469
+ const firstBase = segBaseIdx[0];
5470
+ emitCap(first, firstBase + 0, firstBase + 1, false, first.halfA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);
5471
+ const last = segs[segs.length - 1];
5472
+ const lastBase = segBaseIdx[segs.length - 1];
5473
+ const lastSrc = segSrcIdx[segs.length - 1] + 1;
5474
+ emitCap(last, lastBase + 2, lastBase + 3, true, last.halfB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);
5853
5475
  }
5854
- const key = variantKey2(runs, opts);
5855
- const hit = entry.byVariant.get(key);
5856
- if (hit !== void 0) return hit;
5857
- const laid = layoutRuns(runs, opts);
5858
- if (entry.byVariant.size >= LAYOUT_CACHE_VARIANT_LIMIT) entry.byVariant.clear();
5859
- entry.byVariant.set(key, laid);
5860
- return laid;
5861
- }
5862
- function verticalAlignOffset(align, boxHeight, textHeight) {
5863
- if (align === void 0 || align === "top" || boxHeight === void 0) return 0;
5864
- const slack = boxHeight - textHeight;
5865
- return align === "center" ? slack / 2 : slack;
5866
5476
  }
5867
- var NUM_RE = /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g;
5868
- function tokenize(d) {
5869
- const out = [];
5870
- let i = 0;
5871
- while (i < d.length) {
5872
- const ch = d[i];
5873
- if (/[A-Za-z]/.test(ch)) {
5874
- let j = i + 1;
5875
- while (j < d.length && !/[A-Za-z]/.test(d[j])) j++;
5876
- const argSlice = d.slice(i + 1, j);
5877
- out.push({ cmd: ch, args: readNumbers(argSlice) });
5878
- i = j;
5477
+ function populatePolylineWidths(pl, vertexWidths, fallbackWidth) {
5478
+ const ptCount = pl.points.length / 2;
5479
+ if (ptCount === 0) return;
5480
+ const aA = pl.anchorA ?? new Uint32Array(ptCount);
5481
+ const aB = pl.anchorB ?? new Uint32Array(ptCount);
5482
+ const aT = pl.anchorT ?? new Float32Array(ptCount);
5483
+ const widths = new Float32Array(ptCount);
5484
+ const read2 = (i) => {
5485
+ const v = vertexWidths[i];
5486
+ return typeof v === "number" && isFinite(v) && v > 0 ? v : fallbackWidth;
5487
+ };
5488
+ for (let i = 0; i < ptCount; i++) {
5489
+ const a = aA[i], b = aB[i], t = aT[i];
5490
+ if (a === b) {
5491
+ widths[i] = read2(a);
5879
5492
  } else {
5880
- i++;
5493
+ const wa = read2(a);
5494
+ const wb = read2(b);
5495
+ widths[i] = wa + (wb - wa) * t;
5881
5496
  }
5882
5497
  }
5883
- return out;
5498
+ pl.widths = widths;
5884
5499
  }
5885
- function readNumbers(s) {
5886
- const out = [];
5887
- let m;
5888
- NUM_RE.lastIndex = 0;
5889
- while ((m = NUM_RE.exec(s)) !== null) {
5890
- const n = Number(m[0]);
5891
- if (Number.isFinite(n)) out.push(n);
5500
+ var ROUND_CAP_STEP_RAD = 10 * Math.PI / 180;
5501
+ function emitCap(seg, leftIdx, rightIdx, atEnd, half, cap, verts, idx, anchorA, anchorB, anchorT, endA, endB, endT) {
5502
+ const sign2 = atEnd ? 1 : -1;
5503
+ const dx = (seg.bx - seg.ax) / seg.len * sign2;
5504
+ const dy = (seg.by - seg.ay) / seg.len * sign2;
5505
+ const cx = atEnd ? seg.bx : seg.ax;
5506
+ const cy = atEnd ? seg.by : seg.ay;
5507
+ const nx = seg.nxUnit * half;
5508
+ const ny = seg.nyUnit * half;
5509
+ if (cap === "square") {
5510
+ const ox = cx + dx * half;
5511
+ const oy = cy + dy * half;
5512
+ const lOut = verts.length / 2;
5513
+ verts.push(ox + nx, oy + ny);
5514
+ anchorA.push(endA);
5515
+ anchorB.push(endB);
5516
+ anchorT.push(endT);
5517
+ const rOut = verts.length / 2;
5518
+ verts.push(ox - nx, oy - ny);
5519
+ anchorA.push(endA);
5520
+ anchorB.push(endB);
5521
+ anchorT.push(endT);
5522
+ if (atEnd) {
5523
+ idx.push(leftIdx, lOut, rOut, leftIdx, rOut, rightIdx);
5524
+ } else {
5525
+ idx.push(leftIdx, rOut, lOut, leftIdx, rightIdx, rOut);
5526
+ }
5527
+ return;
5892
5528
  }
5893
- return out;
5894
- }
5895
- var ARG_COUNTS = {
5896
- M: 2,
5897
- L: 2,
5898
- H: 1,
5899
- V: 1,
5900
- C: 6,
5901
- S: 4,
5902
- Q: 4,
5903
- T: 2,
5904
- A: 7,
5905
- Z: 0
5906
- };
5907
- function pathFromD(d, onWarn) {
5908
- const tokens = tokenize(d);
5909
- const b = new PathBuilder();
5910
- const st = {
5911
- x: 0,
5912
- y: 0,
5913
- startX: 0,
5914
- startY: 0,
5915
- lastCubicCtrl: null,
5916
- lastQuadCtrl: null
5917
- };
5918
- for (const { cmd, args } of tokens) {
5919
- const upper = cmd.toUpperCase();
5920
- const relative = cmd !== upper;
5921
- const argCount = ARG_COUNTS[upper];
5922
- if (argCount === void 0) {
5923
- continue;
5529
+ if (cap === "round") {
5530
+ const pivotIdx = verts.length / 2;
5531
+ verts.push(cx, cy);
5532
+ anchorA.push(endA);
5533
+ anchorB.push(endB);
5534
+ anchorT.push(endT);
5535
+ const startAngle = Math.atan2(ny, nx);
5536
+ const sweep = atEnd ? -Math.PI : Math.PI;
5537
+ const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_CAP_STEP_RAD));
5538
+ const stepAngle = sweep / steps;
5539
+ let prevIdx = leftIdx;
5540
+ for (let i = 1; i < steps; i++) {
5541
+ const ang = startAngle + i * stepAngle;
5542
+ const fx = cx + Math.cos(ang) * half;
5543
+ const fy = cy + Math.sin(ang) * half;
5544
+ const newIdx = verts.length / 2;
5545
+ verts.push(fx, fy);
5546
+ anchorA.push(endA);
5547
+ anchorB.push(endB);
5548
+ anchorT.push(endT);
5549
+ if (atEnd) idx.push(prevIdx, newIdx, pivotIdx);
5550
+ else idx.push(prevIdx, pivotIdx, newIdx);
5551
+ prevIdx = newIdx;
5924
5552
  }
5925
- if (upper === "Z") {
5926
- b.close();
5927
- st.x = st.startX;
5928
- st.y = st.startY;
5929
- st.lastCubicCtrl = null;
5930
- st.lastQuadCtrl = null;
5931
- continue;
5553
+ if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);
5554
+ else idx.push(prevIdx, pivotIdx, rightIdx);
5555
+ }
5556
+ }
5557
+ function splitForDash(pl, dash) {
5558
+ const out = [];
5559
+ const plA = pl.anchorA ?? new Uint32Array(pl.points.length / 2);
5560
+ const plB = pl.anchorB ?? new Uint32Array(pl.points.length / 2);
5561
+ const plT = pl.anchorT ?? new Float32Array(pl.points.length / 2);
5562
+ let dashIdx = 0;
5563
+ let dashRemaining = dash[0];
5564
+ let onPhase = true;
5565
+ let curPts = onPhase ? [pl.points[0], pl.points[1]] : null;
5566
+ let curA = onPhase ? [plA[0]] : null;
5567
+ let curB = onPhase ? [plB[0]] : null;
5568
+ let curT = onPhase ? [plT[0]] : null;
5569
+ const flushAndAdvance = () => {
5570
+ if (curPts && curPts.length >= 4) {
5571
+ out.push({
5572
+ points: curPts,
5573
+ closed: false,
5574
+ anchorA: new Uint32Array(curA),
5575
+ anchorB: new Uint32Array(curB),
5576
+ anchorT: new Float32Array(curT)
5577
+ });
5932
5578
  }
5933
- const groups = argCount === 0 ? 1 : Math.floor(args.length / argCount);
5934
- for (let g = 0; g < groups; g++) {
5935
- const a = args.slice(g * argCount, (g + 1) * argCount);
5936
- const effectiveUpper = upper === "M" && g > 0 ? "L" : upper;
5937
- runCommand(effectiveUpper, relative, a, st, b);
5579
+ curPts = null;
5580
+ curA = null;
5581
+ curB = null;
5582
+ curT = null;
5583
+ dashIdx = (dashIdx + 1) % dash.length;
5584
+ dashRemaining = dash[dashIdx];
5585
+ onPhase = !onPhase;
5586
+ };
5587
+ let prevX = pl.points[0], prevY = pl.points[1];
5588
+ const ptCount = pl.points.length / 2;
5589
+ const segCount = pl.closed ? ptCount : ptCount - 1;
5590
+ for (let i = 0; i < segCount; i++) {
5591
+ const nextIdx = (i + 1) % ptCount;
5592
+ const cx = pl.points[nextIdx * 2], cy = pl.points[nextIdx * 2 + 1];
5593
+ const startA = plA[i], startB = plB[i], startT = plT[i];
5594
+ const endA = plA[nextIdx], endB = plB[nextIdx], endT = plT[nextIdx];
5595
+ const segFullDx = cx - prevX, segFullDy = cy - prevY;
5596
+ const segFullLen = Math.hypot(segFullDx, segFullDy);
5597
+ let segDx = segFullDx, segDy = segFullDy;
5598
+ let segLen = segFullLen;
5599
+ let traveled = 0;
5600
+ while (segLen > 1e-9) {
5601
+ if (segLen <= dashRemaining) {
5602
+ if (onPhase && curPts) {
5603
+ curPts.push(cx, cy);
5604
+ curA.push(endA);
5605
+ curB.push(endB);
5606
+ curT.push(endT);
5607
+ }
5608
+ dashRemaining -= segLen;
5609
+ prevX = cx;
5610
+ prevY = cy;
5611
+ traveled = segFullLen;
5612
+ segLen = 0;
5613
+ if (dashRemaining <= 1e-9) {
5614
+ flushAndAdvance();
5615
+ if (onPhase) {
5616
+ curPts = [prevX, prevY];
5617
+ curA = [endA];
5618
+ curB = [endB];
5619
+ curT = [endT];
5620
+ }
5621
+ }
5622
+ } else {
5623
+ const tConsume = dashRemaining / segLen;
5624
+ const ix = prevX + segDx * tConsume;
5625
+ const iy = prevY + segDy * tConsume;
5626
+ traveled += dashRemaining;
5627
+ const frac = traveled / segFullLen;
5628
+ let mA, mB, mT;
5629
+ if (startA === endA && startB === endB) {
5630
+ mA = startA;
5631
+ mB = startB;
5632
+ mT = startT + (endT - startT) * frac;
5633
+ } else {
5634
+ if (frac < 0.5) {
5635
+ mA = startA;
5636
+ mB = startB;
5637
+ mT = startT;
5638
+ } else {
5639
+ mA = endA;
5640
+ mB = endB;
5641
+ mT = endT;
5642
+ }
5643
+ }
5644
+ if (onPhase && curPts) {
5645
+ curPts.push(ix, iy);
5646
+ curA.push(mA);
5647
+ curB.push(mB);
5648
+ curT.push(mT);
5649
+ }
5650
+ prevX = ix;
5651
+ prevY = iy;
5652
+ segDx = cx - prevX;
5653
+ segDy = cy - prevY;
5654
+ segLen = Math.hypot(segDx, segDy);
5655
+ flushAndAdvance();
5656
+ if (onPhase) {
5657
+ curPts = [prevX, prevY];
5658
+ curA = [mA];
5659
+ curB = [mB];
5660
+ curT = [mT];
5661
+ }
5662
+ }
5938
5663
  }
5939
5664
  }
5940
- return b.build();
5665
+ if (curPts && curPts.length >= 4) {
5666
+ out.push({
5667
+ points: curPts,
5668
+ closed: false,
5669
+ anchorA: new Uint32Array(curA),
5670
+ anchorB: new Uint32Array(curB),
5671
+ anchorT: new Float32Array(curT)
5672
+ });
5673
+ }
5674
+ return out;
5941
5675
  }
5942
- function runCommand(upper, relative, a, st, b) {
5943
- const rx = relative ? st.x : 0;
5944
- const ry = relative ? st.y : 0;
5945
- switch (upper) {
5946
- case "M": {
5947
- const x = a[0] + rx;
5948
- const y = a[1] + ry;
5949
- b.moveTo(x, y);
5950
- st.x = x;
5951
- st.y = y;
5952
- st.startX = x;
5953
- st.startY = y;
5954
- st.lastCubicCtrl = null;
5955
- st.lastQuadCtrl = null;
5956
- return;
5957
- }
5958
- case "L": {
5959
- const x = a[0] + rx;
5960
- const y = a[1] + ry;
5961
- b.lineTo(x, y);
5962
- st.x = x;
5963
- st.y = y;
5964
- st.lastCubicCtrl = null;
5965
- st.lastQuadCtrl = null;
5966
- return;
5967
- }
5968
- case "H": {
5969
- const x = a[0] + (relative ? st.x : 0);
5970
- b.lineTo(x, st.y);
5971
- st.x = x;
5972
- st.lastCubicCtrl = null;
5973
- st.lastQuadCtrl = null;
5974
- return;
5975
- }
5976
- case "V": {
5977
- const y = a[0] + (relative ? st.y : 0);
5978
- b.lineTo(st.x, y);
5979
- st.y = y;
5980
- st.lastCubicCtrl = null;
5981
- st.lastQuadCtrl = null;
5982
- return;
5983
- }
5984
- case "C": {
5985
- const x1 = a[0] + rx, y1 = a[1] + ry;
5986
- const x2 = a[2] + rx, y2 = a[3] + ry;
5987
- const x = a[4] + rx, y = a[5] + ry;
5988
- b.curveTo(x1, y1, x2, y2, x, y);
5989
- st.x = x;
5990
- st.y = y;
5991
- st.lastCubicCtrl = { x: x2, y: y2 };
5992
- st.lastQuadCtrl = null;
5993
- return;
5994
- }
5995
- case "S": {
5996
- const ref = st.lastCubicCtrl ?? { x: st.x, y: st.y };
5997
- const x1 = 2 * st.x - ref.x;
5998
- const y1 = 2 * st.y - ref.y;
5999
- const x2 = a[0] + rx, y2 = a[1] + ry;
6000
- const x = a[2] + rx, y = a[3] + ry;
6001
- b.curveTo(x1, y1, x2, y2, x, y);
6002
- st.x = x;
6003
- st.y = y;
6004
- st.lastCubicCtrl = { x: x2, y: y2 };
6005
- st.lastQuadCtrl = null;
6006
- return;
6007
- }
6008
- case "Q": {
6009
- const x1 = a[0] + rx, y1 = a[1] + ry;
6010
- const x = a[2] + rx, y = a[3] + ry;
6011
- b.quadTo(x1, y1, x, y);
6012
- st.x = x;
6013
- st.y = y;
6014
- st.lastQuadCtrl = { x: x1, y: y1 };
6015
- st.lastCubicCtrl = null;
6016
- return;
6017
- }
6018
- case "T": {
6019
- const ref = st.lastQuadCtrl ?? { x: st.x, y: st.y };
6020
- const x1 = 2 * st.x - ref.x;
6021
- const y1 = 2 * st.y - ref.y;
6022
- const x = a[0] + rx, y = a[1] + ry;
6023
- b.quadTo(x1, y1, x, y);
6024
- st.x = x;
6025
- st.y = y;
6026
- st.lastQuadCtrl = { x: x1, y: y1 };
6027
- st.lastCubicCtrl = null;
5676
+ function makeSeg(ax, ay, bx, by, halfA, halfB) {
5677
+ const dx = bx - ax, dy = by - ay;
5678
+ const len = Math.hypot(dx, dy);
5679
+ if (len === 0) return null;
5680
+ return {
5681
+ ax,
5682
+ ay,
5683
+ bx,
5684
+ by,
5685
+ nxUnit: -dy / len,
5686
+ nyUnit: dx / len,
5687
+ halfA,
5688
+ halfB,
5689
+ len
5690
+ };
5691
+ }
5692
+ var DEFAULT_MITER_LIMIT = 10;
5693
+ var DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;
5694
+ function emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT) {
5695
+ const a = segs[j];
5696
+ const b = segs[(j + 1) % segs.length];
5697
+ const aBase = segBaseIdx[j];
5698
+ const bBase = segBaseIdx[(j + 1) % segs.length];
5699
+ const adx = a.bx - a.ax, ady = a.by - a.ay;
5700
+ const bdx = b.bx - b.ax, bdy = b.by - b.ay;
5701
+ const cross = adx * bdy - ady * bdx;
5702
+ if (cross === 0) return;
5703
+ const onPositive = cross > 0;
5704
+ const aOuterEnd = onPositive ? aBase + 3 : aBase + 2;
5705
+ const bOuterStart = onPositive ? bBase + 1 : bBase + 0;
5706
+ const nextSeg = (j + 1) % segs.length;
5707
+ const cornerSrc = segSrcIdx[nextSeg];
5708
+ const cornerA = plA[cornerSrc];
5709
+ const cornerB = plB[cornerSrc];
5710
+ const cornerT = plT[cornerSrc];
5711
+ const halfAtCorner = a.halfB;
5712
+ const taperA = Math.max(a.halfA, a.halfB) / Math.max(1e-9, Math.min(a.halfA, a.halfB));
5713
+ const taperB = Math.max(b.halfA, b.halfB) / Math.max(1e-9, Math.min(b.halfA, b.halfB));
5714
+ const forceBevelForVarying = taperA > varyingThreshold || taperB > varyingThreshold;
5715
+ if (join === "bevel" || forceBevelForVarying) {
5716
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
5717
+ return;
5718
+ }
5719
+ if (join === "miter") {
5720
+ const aOX = verts[aOuterEnd * 2], aOY = verts[aOuterEnd * 2 + 1];
5721
+ const bOX = verts[bOuterStart * 2], bOY = verts[bOuterStart * 2 + 1];
5722
+ const apex = lineLineIntersect(aOX, aOY, adx, ady, bOX, bOY, -bdx, -bdy);
5723
+ if (!apex) {
5724
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
6028
5725
  return;
6029
5726
  }
6030
- case "A": {
6031
- const rxA = Math.abs(a[0]);
6032
- const ryA = Math.abs(a[1]);
6033
- const xRot = a[2];
6034
- const largeArc = a[3] !== 0;
6035
- const sweep = a[4] !== 0;
6036
- const ex = a[5] + rx;
6037
- const ey = a[6] + ry;
6038
- arcToCubics(st.x, st.y, ex, ey, rxA, ryA, xRot, largeArc, sweep, b);
6039
- st.x = ex;
6040
- st.y = ey;
6041
- st.lastCubicCtrl = null;
6042
- st.lastQuadCtrl = null;
5727
+ const miterLen = Math.hypot(apex[0] - a.bx, apex[1] - a.by);
5728
+ if (miterLen > miterLimit * halfAtCorner) {
5729
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
6043
5730
  return;
6044
5731
  }
6045
- default:
6046
- return;
6047
- }
6048
- }
6049
- function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep, b) {
6050
- if (x1 === x2 && y1 === y2) return;
6051
- if (rx === 0 || ry === 0) {
6052
- b.lineTo(x2, y2);
6053
- return;
6054
- }
6055
- const phi = xAxisRotationDeg * Math.PI / 180;
6056
- const cosPhi = Math.cos(phi);
6057
- const sinPhi = Math.sin(phi);
6058
- const dx = (x1 - x2) / 2;
6059
- const dy = (y1 - y2) / 2;
6060
- const x1p = cosPhi * dx + sinPhi * dy;
6061
- const y1p = -sinPhi * dx + cosPhi * dy;
6062
- let rxs = rx * rx;
6063
- let rys = ry * ry;
6064
- const x1ps = x1p * x1p;
6065
- const y1ps = y1p * y1p;
6066
- const lambda = x1ps / rxs + y1ps / rys;
6067
- let rxFixed = rx;
6068
- let ryFixed = ry;
6069
- if (lambda > 1) {
6070
- const sqrtLambda = Math.sqrt(lambda);
6071
- rxFixed = sqrtLambda * rx;
6072
- ryFixed = sqrtLambda * ry;
6073
- rxs = rxFixed * rxFixed;
6074
- rys = ryFixed * ryFixed;
6075
- }
6076
- const sign2 = largeArc === sweep ? -1 : 1;
6077
- const num = rxs * rys - rxs * y1ps - rys * x1ps;
6078
- const den = rxs * y1ps + rys * x1ps;
6079
- const factor = sign2 * Math.sqrt(Math.max(0, num / den));
6080
- const cxp = factor * (rxFixed * y1p) / ryFixed;
6081
- const cyp = factor * -(ryFixed * x1p) / rxFixed;
6082
- const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
6083
- const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
6084
- const angle = (ux, uy, vx, vy) => {
6085
- const dot = ux * vx + uy * vy;
6086
- const len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
6087
- let a = Math.acos(Math.max(-1, Math.min(1, dot / len)));
6088
- if (ux * vy - uy * vx < 0) a = -a;
6089
- return a;
6090
- };
6091
- const theta1 = angle(1, 0, (x1p - cxp) / rxFixed, (y1p - cyp) / ryFixed);
6092
- let dTheta = angle(
6093
- (x1p - cxp) / rxFixed,
6094
- (y1p - cyp) / ryFixed,
6095
- (-x1p - cxp) / rxFixed,
6096
- (-y1p - cyp) / ryFixed
6097
- );
6098
- if (!sweep && dTheta > 0) dTheta -= 2 * Math.PI;
6099
- else if (sweep && dTheta < 0) dTheta += 2 * Math.PI;
6100
- const segCount = Math.max(1, Math.ceil(Math.abs(dTheta) / (Math.PI / 2)));
6101
- const segAngle = dTheta / segCount;
6102
- const t = 4 / 3 * Math.tan(segAngle / 4);
6103
- let prevX = x1;
6104
- let prevY = y1;
6105
- let prevDx = -Math.sin(theta1) * rxFixed;
6106
- let prevDy = Math.cos(theta1) * ryFixed;
6107
- for (let i = 1; i <= segCount; i++) {
6108
- const theta = theta1 + i * segAngle;
6109
- const cosT = Math.cos(theta);
6110
- const sinT = Math.sin(theta);
6111
- const px = cosPhi * (cosT * rxFixed) - sinPhi * (sinT * ryFixed) + cx;
6112
- const py = sinPhi * (cosT * rxFixed) + cosPhi * (sinT * ryFixed) + cy;
6113
- const dxNew = -sinT * rxFixed;
6114
- const dyNew = cosT * ryFixed;
6115
- const c1x = prevX + cosPhi * (t * prevDx) - sinPhi * (t * prevDy);
6116
- const c1y = prevY + sinPhi * (t * prevDx) + cosPhi * (t * prevDy);
6117
- const c2x = px - cosPhi * (t * dxNew) + sinPhi * (t * dyNew);
6118
- const c2y = py - sinPhi * (t * dxNew) - cosPhi * (t * dyNew);
6119
- b.curveTo(c1x, c1y, c2x, c2y, px, py);
6120
- prevX = px;
6121
- prevY = py;
6122
- prevDx = dxNew;
6123
- prevDy = dyNew;
5732
+ const apexIdx = verts.length / 2;
5733
+ verts.push(apex[0], apex[1]);
5734
+ anchorA.push(cornerA);
5735
+ anchorB.push(cornerB);
5736
+ anchorT.push(cornerT);
5737
+ const jIdx = verts.length / 2;
5738
+ verts.push(a.bx, a.by);
5739
+ anchorA.push(cornerA);
5740
+ anchorB.push(cornerB);
5741
+ anchorT.push(cornerT);
5742
+ if (onPositive) {
5743
+ idx.push(aOuterEnd, apexIdx, bOuterStart);
5744
+ idx.push(aOuterEnd, bOuterStart, jIdx);
5745
+ } else {
5746
+ idx.push(aOuterEnd, bOuterStart, apexIdx);
5747
+ idx.push(aOuterEnd, jIdx, bOuterStart);
5748
+ }
5749
+ return;
5750
+ }
5751
+ if (join === "round") {
5752
+ emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
5753
+ return;
6124
5754
  }
6125
5755
  }
6126
- var OUTLINE_FLATTEN_TOLERANCE = 1 / 4096;
6127
- var OUTLINE_MESH_CACHE_LIMIT = 2048;
6128
- var cache3 = /* @__PURE__ */ new Map();
6129
- function outlineMesh(key, d) {
6130
- const cached2 = cache3.get(key);
6131
- if (cached2 !== void 0) return cached2;
6132
- if (cache3.size >= OUTLINE_MESH_CACHE_LIMIT) cache3 = /* @__PURE__ */ new Map();
6133
- const mesh = tessellate(pathFromD(d), { flattenTolerance: OUTLINE_FLATTEN_TOLERANCE });
6134
- cache3.set(key, mesh);
6135
- return mesh;
5756
+ var ROUND_STEP_RAD = 10 * Math.PI / 180;
5757
+ function emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
5758
+ const cx = a.bx, cy = a.by;
5759
+ const pivotIdx = verts.length / 2;
5760
+ verts.push(cx, cy);
5761
+ anchorA.push(cornerA);
5762
+ anchorB.push(cornerB);
5763
+ anchorT.push(cornerT);
5764
+ const startX = verts[aOuterEnd * 2] - cx;
5765
+ const startY = verts[aOuterEnd * 2 + 1] - cy;
5766
+ const endX = verts[bOuterStart * 2] - cx;
5767
+ const endY = verts[bOuterStart * 2 + 1] - cy;
5768
+ const r = Math.hypot(startX, startY);
5769
+ const startAngle = Math.atan2(startY, startX);
5770
+ const endAngle = Math.atan2(endY, endX);
5771
+ let sweep = endAngle - startAngle;
5772
+ if (onPositive && sweep < 0) sweep += 2 * Math.PI;
5773
+ else if (!onPositive && sweep > 0) sweep -= 2 * Math.PI;
5774
+ const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_STEP_RAD));
5775
+ const stepAngle = sweep / steps;
5776
+ let prevIdx = aOuterEnd;
5777
+ for (let i = 1; i < steps; i++) {
5778
+ const ang = startAngle + i * stepAngle;
5779
+ const fx = cx + Math.cos(ang) * r;
5780
+ const fy = cy + Math.sin(ang) * r;
5781
+ const newIdx = verts.length / 2;
5782
+ verts.push(fx, fy);
5783
+ anchorA.push(cornerA);
5784
+ anchorB.push(cornerB);
5785
+ anchorT.push(cornerT);
5786
+ if (onPositive) idx.push(prevIdx, pivotIdx, newIdx);
5787
+ else idx.push(prevIdx, newIdx, pivotIdx);
5788
+ prevIdx = newIdx;
5789
+ }
5790
+ if (onPositive) idx.push(prevIdx, pivotIdx, bOuterStart);
5791
+ else idx.push(prevIdx, bOuterStart, pivotIdx);
5792
+ }
5793
+ function emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
5794
+ const jIdx = verts.length / 2;
5795
+ verts.push(a.bx, a.by);
5796
+ anchorA.push(cornerA);
5797
+ anchorB.push(cornerB);
5798
+ anchorT.push(cornerT);
5799
+ if (onPositive) idx.push(aOuterEnd, jIdx, bOuterStart);
5800
+ else idx.push(aOuterEnd, bOuterStart, jIdx);
5801
+ }
5802
+ function lineLineIntersect(apx, apy, adx, ady, bpx, bpy, bdx, bdy) {
5803
+ const denom = adx * bdy - ady * bdx;
5804
+ if (Math.abs(denom) < 1e-9) return null;
5805
+ const t = ((bpx - apx) * bdy - (bpy - apy) * bdx) / denom;
5806
+ return [apx + t * adx, apy + t * ady];
6136
5807
  }
6137
5808
  var OUTLINE_STROKE_MESH_CACHE_LIMIT = 2048;
6138
5809
  var WIDTH_QUANTUM = 1 / 1024;
@@ -6162,6 +5833,36 @@ function outlineStrokeMesh(glyphKey, d, emWidth, stroke) {
6162
5833
  cache4.set(key, mesh);
6163
5834
  return mesh.indices.length === 0 ? null : mesh;
6164
5835
  }
5836
+ var STROKE_CONFIGS_PER_PATH = 8;
5837
+ var cache5 = /* @__PURE__ */ new WeakMap();
5838
+ function configKey(stroke, flattenTolerance) {
5839
+ return [
5840
+ stroke.width ?? 1,
5841
+ stroke.cap ?? "butt",
5842
+ stroke.join ?? "miter",
5843
+ stroke.miterLimit ?? "",
5844
+ stroke.align ?? "center",
5845
+ (stroke.dash ?? []).join(","),
5846
+ stroke.varyingWidthJoinThreshold ?? "",
5847
+ flattenTolerance ?? ""
5848
+ ].join("|");
5849
+ }
5850
+ function strokeMesh(path, stroke, flattenTolerance) {
5851
+ let byConfig = cache5.get(path);
5852
+ if (byConfig === void 0) {
5853
+ byConfig = /* @__PURE__ */ new Map();
5854
+ cache5.set(path, byConfig);
5855
+ }
5856
+ const key = configKey(stroke, flattenTolerance);
5857
+ const entry = byConfig.get(key);
5858
+ if (entry !== void 0 && entry.vertexWidths === stroke.vertexWidths) {
5859
+ return entry.mesh;
5860
+ }
5861
+ const mesh = tessellateStroke(path, stroke, { flattenTolerance });
5862
+ if (entry === void 0 && byConfig.size >= STROKE_CONFIGS_PER_PATH) byConfig.clear();
5863
+ byConfig.set(key, { mesh, vertexWidths: stroke.vertexWidths });
5864
+ return mesh;
5865
+ }
6165
5866
  var MAX_VERTICES_PER_BATCH = 32768;
6166
5867
  var FLOATS_PER_VERTEX = 6;
6167
5868
  var INITIAL_VERTICES = 256;
@@ -6563,44 +6264,40 @@ function drawGroup(ctx, cmd) {
6563
6264
  "weasel: clip nesting depth exceeded (max 7). You can't nest more than 7 levels of clipped containers in a single draw tree. Flatten the hierarchy or compose poses outside the scene graph."
6564
6265
  );
6565
6266
  }
6566
- flushSolids(ctx);
6567
6267
  pushClip(ctx, cmd.clip, newDepth);
6568
6268
  ctx.clipDepth = newDepth;
6569
6269
  }
6570
6270
  for (const child of cmd.children) dispatch(ctx, child);
6571
6271
  if (cmd.clip) {
6572
- flushSolids(ctx);
6573
6272
  popClip(ctx, cmd.clip, ctx.clipDepth - 1);
6574
6273
  ctx.clipDepth -= 1;
6575
6274
  }
6576
6275
  ctx.state.pop();
6577
6276
  }
6578
6277
  function drawPath(ctx, cmd) {
6579
- if (!cmd.fill && !cmd.stroke) return;
6580
- if (cmd.fill) {
6581
- const batchablePaint = (cmd.fill.fill === void 0 || cmd.fill.fill === "solid") && (!cmd.vertexColors || cmd.vertexColors.length === 0);
6582
- const solid = cmd.fill;
6583
- if (batchablePaint && cmd.path.kind === "rect") {
6584
- pushRect(ctx, cmd.path, solid);
6585
- } else {
6586
- const mesh = fillMesh(ctx, cmd.path);
6587
- if (batchablePaint && canBatchMesh(mesh)) {
6588
- pushMesh(ctx, mesh, solid);
6589
- } else {
6590
- flushSolids(ctx);
6591
- const handle = meshHandle(ctx, mesh);
6592
- if (cmd.vertexColors && cmd.vertexColors.length > 0 && (cmd.fill.fill === void 0 || cmd.fill.fill === "solid")) {
6593
- drawPathFillVColor(ctx, cmd, solid, handle);
6594
- } else if (handle.requiresStencil) {
6595
- drawPathFillStencil(ctx, cmd.fill, handle);
6596
- } else {
6597
- drawPathFillByKind(ctx, cmd.fill, handle);
6598
- }
6599
- }
6600
- }
6601
- }
6278
+ if (cmd.fill) drawPathFill(ctx, cmd);
6602
6279
  if (cmd.stroke) drawPathStroke(ctx, cmd);
6603
6280
  }
6281
+ function drawPathFill(ctx, cmd) {
6282
+ const fill = cmd.fill;
6283
+ const isSolid = fill.fill === void 0 || fill.fill === "solid";
6284
+ const hasVColors = !!(cmd.vertexColors && cmd.vertexColors.length > 0);
6285
+ const solid = fill;
6286
+ if (isSolid && !hasVColors && cmd.path.kind === "rect") {
6287
+ pushRect(ctx, cmd.path, solid);
6288
+ return;
6289
+ }
6290
+ const mesh = fillMesh(ctx, cmd.path);
6291
+ if (tryStageSolid(ctx, mesh, isSolid && !hasVColors ? solid : void 0)) return;
6292
+ const handle = meshHandle(ctx, mesh);
6293
+ if (isSolid && hasVColors) {
6294
+ drawPathFillVColor(ctx, cmd, solid, handle);
6295
+ } else if (handle.requiresStencil) {
6296
+ drawPathFillStencil(ctx, fill, handle);
6297
+ } else {
6298
+ drawPathFillByKind(ctx, fill, handle);
6299
+ }
6300
+ }
6604
6301
  function isIdentityColorMatrix(cm) {
6605
6302
  return cm === IDENTITY_COLOR_MATRIX || sameValues(IDENTITY_COLOR_MATRIX, cm);
6606
6303
  }
@@ -6653,6 +6350,14 @@ function pushMesh(ctx, mesh, paint) {
6653
6350
  const [r, g, b, a] = stagedColor(ctx, staged, paint);
6654
6351
  ctx.solidBatch.pushMesh(mesh, ctx.state.transform, r, g, b, a);
6655
6352
  }
6353
+ function tryStageSolid(ctx, mesh, paint) {
6354
+ if (paint !== void 0 && canBatchMesh(mesh)) {
6355
+ pushMesh(ctx, mesh, paint);
6356
+ return true;
6357
+ }
6358
+ flushSolids(ctx);
6359
+ return false;
6360
+ }
6656
6361
  function flushSolids(ctx) {
6657
6362
  const batch = ctx.solidBatch;
6658
6363
  const staged = ctx.solidState;
@@ -6856,6 +6561,7 @@ function rasterizePathToStencil(ctx, path) {
6856
6561
  gl.bindVertexArray(null);
6857
6562
  }
6858
6563
  function pushClip(ctx, path, newDepth) {
6564
+ flushSolids(ctx);
6859
6565
  const gl = ctx.gl;
6860
6566
  const ancestors = ancestorMask(newDepth - 1);
6861
6567
  const newBit = 1 << newDepth;
@@ -6870,6 +6576,7 @@ function pushClip(ctx, path, newDepth) {
6870
6576
  gl.stencilMask(255);
6871
6577
  }
6872
6578
  function popClip(ctx, path, oldDepth) {
6579
+ flushSolids(ctx);
6873
6580
  const gl = ctx.gl;
6874
6581
  gl.enable(gl.STENCIL_TEST);
6875
6582
  const oldBit = 1 << oldDepth + 1;
@@ -6926,17 +6633,13 @@ function drawPathStroke(ctx, cmd) {
6926
6633
  function drawPathStrokeUnclipped(ctx, cmd) {
6927
6634
  const stroke = cmd.stroke;
6928
6635
  const solid = stroke.paint;
6929
- const mesh = tessellateStroke(cmd.path, stroke, { flattenTolerance: ctx.flattenTolerance });
6636
+ const mesh = strokeMesh(cmd.path, stroke, ctx.flattenTolerance);
6930
6637
  if (mesh.indices.length === 0) return;
6931
6638
  const hasVColors = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
6932
- if (!hasVColors && canBatchMesh(mesh)) {
6933
- pushMesh(ctx, mesh, solid);
6934
- return;
6935
- }
6936
- flushSolids(ctx);
6937
- const handle = ctx.meshCache.uploadTransient(mesh);
6639
+ if (tryStageSolid(ctx, mesh, hasVColors ? void 0 : solid)) return;
6640
+ const handle = hasVColors ? ctx.meshCache.uploadTransient(mesh) : ctx.meshCache.uploadRecurring(mesh);
6938
6641
  const gl = ctx.gl;
6939
- if (stroke.vertexColors && stroke.vertexColors.length > 0) {
6642
+ if (hasVColors) {
6940
6643
  const prog = ctx.pathFillVColor;
6941
6644
  gl.useProgram(prog.handle);
6942
6645
  gl.bindVertexArray(handle.vao);
@@ -6967,16 +6670,30 @@ function drawPathStrokeUnclipped(ctx, cmd) {
6967
6670
  gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
6968
6671
  gl.bindVertexArray(null);
6969
6672
  }
6673
+ var DOUBLED_VERTEX_WIDTHS = /* @__PURE__ */ new WeakMap();
6674
+ function doubledVertexWidths(widths) {
6675
+ let doubled = DOUBLED_VERTEX_WIDTHS.get(widths);
6676
+ if (doubled === void 0) {
6677
+ doubled = widths.map((w) => w * 2);
6678
+ DOUBLED_VERTEX_WIDTHS.set(widths, doubled);
6679
+ }
6680
+ return doubled;
6681
+ }
6970
6682
  function drawPathStrokeStenciled(ctx, cmd, align) {
6971
6683
  const stroke = cmd.stroke;
6972
6684
  const solid = stroke.paint;
6973
- const widerStroke = { ...stroke, width: (stroke.width ?? 1) * 2, align: "center" };
6685
+ const widerStroke = {
6686
+ ...stroke,
6687
+ width: (stroke.width ?? 1) * 2,
6688
+ ...stroke.vertexWidths ? { vertexWidths: doubledVertexWidths(stroke.vertexWidths) } : {},
6689
+ align: "center"
6690
+ };
6691
+ const useVColor = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
6974
6692
  const fillHandle = fillMeshHandle(ctx, cmd.path);
6975
- const ribbonMesh = tessellateStroke(cmd.path, widerStroke, { flattenTolerance: ctx.flattenTolerance });
6693
+ const ribbonMesh = strokeMesh(cmd.path, widerStroke, ctx.flattenTolerance);
6976
6694
  if (ribbonMesh.indices.length === 0) return;
6977
- const ribbonHandle = ctx.meshCache.uploadTransient(ribbonMesh);
6695
+ const ribbonHandle = useVColor ? ctx.meshCache.uploadTransient(ribbonMesh) : ctx.meshCache.uploadRecurring(ribbonMesh);
6978
6696
  const gl = ctx.gl;
6979
- const useVColor = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
6980
6697
  const prog = useVColor ? ctx.pathFillVColor : ctx.pathFill;
6981
6698
  gl.useProgram(prog.handle);
6982
6699
  setProjAndModel(ctx, prog);
@@ -7012,11 +6729,11 @@ function drawPathStrokeStenciled(ctx, cmd, align) {
7012
6729
  }
7013
6730
  }
7014
6731
  gl.drawElements(gl.TRIANGLES, ribbonHandle.indexCount, gl.UNSIGNED_INT, 0);
7015
- if (colorVbo) gl.deleteBuffer(colorVbo);
7016
6732
  gl.stencilMask(1);
7017
6733
  gl.clear(gl.STENCIL_BUFFER_BIT);
7018
6734
  gl.disable(gl.STENCIL_TEST);
7019
6735
  gl.bindVertexArray(null);
6736
+ if (colorVbo) gl.deleteBuffer(colorVbo);
7020
6737
  }
7021
6738
  var OUTLINE_MIN_SCREEN_PX = 48;
7022
6739
  function modelScale(m) {
@@ -7065,9 +6782,9 @@ function drawTextOutlineGroup(ctx, group, dx, dy) {
7065
6782
  const mesh = outlineGroupMesh(group, dx, dy);
7066
6783
  if (mesh) drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
7067
6784
  if (!group.stroke) return;
7068
- const strokeMesh = outlineGroupStrokeMesh(group, dx, dy);
7069
- if (strokeMesh) {
7070
- drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(strokeMesh));
6785
+ const ribbon = outlineGroupStrokeMesh(group, dx, dy);
6786
+ if (ribbon) {
6787
+ drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(ribbon));
7071
6788
  }
7072
6789
  }
7073
6790
  function outlineGroupMesh(group, dx, dy) {
@@ -7273,70 +6990,98 @@ function drawTextGroup(ctx, group, prog, dx, dy) {
7273
6990
  gl.deleteBuffer(vbo);
7274
6991
  gl.deleteBuffer(ibo);
7275
6992
  }
6993
+ var IMAGE_QUAD_RING_SIZE = 64;
6994
+ var IMAGE_QUAD_RINGS = /* @__PURE__ */ new WeakMap();
6995
+ var IMAGE_QUAD_INDICES = new Uint32Array([0, 1, 2, 1, 3, 2]);
6996
+ var IMAGE_QUAD_STRIDE = 16;
6997
+ function imageQuadRing(gl, prog) {
6998
+ const existing = IMAGE_QUAD_RINGS.get(prog);
6999
+ if (existing) return existing;
7000
+ const aPosLoc = prog.attribute("a_position");
7001
+ const aUvLoc = prog.attribute("a_uv");
7002
+ const ibo = gl.createBuffer();
7003
+ if (!ibo) throw new Error("drawImage: createBuffer (IBO) returned null");
7004
+ const vaos = [];
7005
+ const vbos = [];
7006
+ for (let slot = 0; slot < IMAGE_QUAD_RING_SIZE; slot++) {
7007
+ const vao = gl.createVertexArray();
7008
+ const vbo = gl.createBuffer();
7009
+ if (!vao) throw new Error("drawImage: createVertexArray returned null");
7010
+ if (!vbo) throw new Error("drawImage: createBuffer returned null");
7011
+ gl.bindVertexArray(vao);
7012
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
7013
+ gl.bufferData(gl.ARRAY_BUFFER, 4 * IMAGE_QUAD_STRIDE, gl.DYNAMIC_DRAW);
7014
+ if (aPosLoc !== void 0) {
7015
+ gl.enableVertexAttribArray(aPosLoc);
7016
+ gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, IMAGE_QUAD_STRIDE, 0);
7017
+ }
7018
+ if (aUvLoc !== void 0) {
7019
+ gl.enableVertexAttribArray(aUvLoc);
7020
+ gl.vertexAttribPointer(aUvLoc, 2, gl.FLOAT, false, IMAGE_QUAD_STRIDE, 8);
7021
+ }
7022
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
7023
+ if (slot === 0) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, IMAGE_QUAD_INDICES, gl.STATIC_DRAW);
7024
+ gl.bindVertexArray(null);
7025
+ vaos.push(vao);
7026
+ vbos.push(vbo);
7027
+ }
7028
+ const ring = { vaos, vbos, ibo, next: 0 };
7029
+ IMAGE_QUAD_RINGS.set(prog, ring);
7030
+ return ring;
7031
+ }
7032
+ function disposeImageQuads(gl, prog) {
7033
+ const ring = IMAGE_QUAD_RINGS.get(prog);
7034
+ if (!ring) return;
7035
+ for (const vao of ring.vaos) gl.deleteVertexArray(vao);
7036
+ for (const vbo of ring.vbos) gl.deleteBuffer(vbo);
7037
+ gl.deleteBuffer(ring.ibo);
7038
+ IMAGE_QUAD_RINGS.delete(prog);
7039
+ }
7040
+ var IMAGE_QUAD_VERTICES = new Float32Array(16);
7276
7041
  function drawImage(ctx, cmd) {
7277
7042
  ctx.imageCache.upload(cmd.image, cmd.image);
7278
7043
  const gl = ctx.gl;
7279
- gl.useProgram(ctx.imageFill.handle);
7044
+ const prog = ctx.imageFill;
7045
+ gl.useProgram(prog.handle);
7280
7046
  const x0 = cmd.x, y0 = cmd.y;
7281
7047
  const x1 = cmd.x + cmd.w, y1 = cmd.y + cmd.h;
7282
- const vertices = new Float32Array([
7283
- x0,
7284
- y0,
7285
- 0,
7286
- 0,
7287
- x1,
7288
- y0,
7289
- 1,
7290
- 0,
7291
- x0,
7292
- y1,
7293
- 0,
7294
- 1,
7295
- x1,
7296
- y1,
7297
- 1,
7298
- 1
7299
- ]);
7300
- const indices = new Uint32Array([0, 1, 2, 1, 3, 2]);
7301
- const vao = gl.createVertexArray();
7302
- if (!vao) throw new Error("drawImage: createVertexArray returned null");
7303
- gl.bindVertexArray(vao);
7304
- const vbo = gl.createBuffer();
7305
- if (!vbo) throw new Error("drawImage: createBuffer returned null");
7306
- gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
7307
- gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
7308
- const stride = 16;
7309
- const aPosLoc = ctx.imageFill.attribute("a_position");
7310
- const aUvLoc = ctx.imageFill.attribute("a_uv");
7311
- if (aPosLoc !== void 0) {
7312
- gl.enableVertexAttribArray(aPosLoc);
7313
- gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, stride, 0);
7314
- }
7315
- if (aUvLoc !== void 0) {
7316
- gl.enableVertexAttribArray(aUvLoc);
7317
- gl.vertexAttribPointer(aUvLoc, 2, gl.FLOAT, false, stride, 8);
7318
- }
7319
- const ibo = gl.createBuffer();
7320
- if (!ibo) throw new Error("drawImage: createBuffer (IBO) returned null");
7321
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
7322
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.DYNAMIC_DRAW);
7323
- setProjAndModel(ctx, ctx.imageFill);
7324
- setColorMatrixUniforms(ctx, ctx.imageFill);
7048
+ const v = IMAGE_QUAD_VERTICES;
7049
+ v[0] = x0;
7050
+ v[1] = y0;
7051
+ v[2] = 0;
7052
+ v[3] = 0;
7053
+ v[4] = x1;
7054
+ v[5] = y0;
7055
+ v[6] = 1;
7056
+ v[7] = 0;
7057
+ v[8] = x0;
7058
+ v[9] = y1;
7059
+ v[10] = 0;
7060
+ v[11] = 1;
7061
+ v[12] = x1;
7062
+ v[13] = y1;
7063
+ v[14] = 1;
7064
+ v[15] = 1;
7065
+ const ring = imageQuadRing(gl, prog);
7066
+ const slot = ring.next;
7067
+ ring.next = (slot + 1) % IMAGE_QUAD_RING_SIZE;
7068
+ gl.bindVertexArray(ring.vaos[slot]);
7069
+ gl.bindBuffer(gl.ARRAY_BUFFER, ring.vbos[slot]);
7070
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, v);
7071
+ setProjAndModel(ctx, prog);
7072
+ setColorMatrixUniforms(ctx, prog);
7325
7073
  ctx.imageCache.bind(cmd.image, 0);
7326
7074
  gl.texParameteri(
7327
7075
  gl.TEXTURE_2D,
7328
7076
  gl.TEXTURE_MAG_FILTER,
7329
7077
  cmd.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
7330
7078
  );
7331
- gl.uniform1i(ctx.imageFill.uniform("u_sampler"), 0);
7332
- gl.uniform1f(ctx.imageFill.uniform("u_opacity"), cmd.opacity ?? 1);
7333
- gl.uniform1f(ctx.imageFill.uniform("u_alpha"), ctx.state.alpha);
7079
+ gl.uniform1i(prog.uniform("u_sampler"), 0);
7080
+ gl.uniform1f(prog.uniform("u_opacity"), cmd.opacity ?? 1);
7081
+ gl.uniform1f(prog.uniform("u_alpha"), ctx.state.alpha);
7334
7082
  applyClipTest(ctx);
7335
7083
  gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
7336
7084
  gl.bindVertexArray(null);
7337
- gl.deleteVertexArray(vao);
7338
- gl.deleteBuffer(vbo);
7339
- gl.deleteBuffer(ibo);
7340
7085
  }
7341
7086
  var CUSTOM_VERT_SRC = (
7342
7087
  /* glsl */
@@ -7393,16 +7138,28 @@ var registry3 = /* @__PURE__ */ new Map();
7393
7138
  function getProgramSource(id) {
7394
7139
  return registry3.get(id) ?? null;
7395
7140
  }
7141
+ var UNIFORM_QUALIFIER = /^(?:lowp|mediump|highp|precise|invariant|centroid|flat|smooth|noperspective)$/;
7396
7142
  function extractUniformNames(glsl) {
7397
- const reScalar = /\buniform\s+\S+\s+(\w+)\s*;/g;
7398
- const reArray = /\buniform\s+\S+\s+(\w+)\s*\[\s*(\d+)\s*\]\s*;/g;
7143
+ const re = /\buniform\s+([^;{]+);/g;
7399
7144
  const names = [];
7400
7145
  let m;
7401
- while ((m = reScalar.exec(glsl)) !== null) names.push(m[1]);
7402
- while ((m = reArray.exec(glsl)) !== null) {
7403
- const name = m[1];
7404
- const size = parseInt(m[2], 10);
7405
- for (let i = 0; i < size; i++) names.push(`${name}[${i}]`);
7146
+ while ((m = re.exec(glsl)) !== null) {
7147
+ const words = m[1].trim().split(/\s+/);
7148
+ let i = 0;
7149
+ while (i < words.length && UNIFORM_QUALIFIER.test(words[i])) i++;
7150
+ i++;
7151
+ const declarators = words.slice(i).join(" ");
7152
+ if (!declarators) continue;
7153
+ for (const raw of declarators.split(",")) {
7154
+ const d = raw.trim();
7155
+ const arr = /^(\w+)\s*\[\s*(\d+)\s*\]$/.exec(d);
7156
+ if (arr) {
7157
+ const size = parseInt(arr[2], 10);
7158
+ for (let k = 0; k < size; k++) names.push(`${arr[1]}[${k}]`);
7159
+ } else if (/^\w+$/.test(d)) {
7160
+ names.push(d);
7161
+ }
7162
+ }
7406
7163
  }
7407
7164
  return names;
7408
7165
  }
@@ -7644,6 +7401,7 @@ var WeaselRenderer = class {
7644
7401
  this.gradRampCache.free();
7645
7402
  if (this.quadVbo) gl.deleteBuffer(this.quadVbo);
7646
7403
  if (this.quadIbo) gl.deleteBuffer(this.quadIbo);
7404
+ disposeImageQuads(gl, this.imageFill);
7647
7405
  this.solidBatch.dispose();
7648
7406
  }
7649
7407
  /**
@@ -8178,6 +7936,12 @@ function runLayoutPass(scratch, moveCtx) {
8178
7936
  if (layout2.contains) return layout2.contains(cPose, draggedCenter);
8179
7937
  return draggedCenter.x >= aabb.x && draggedCenter.x < aabb.x + aabb.width && draggedCenter.y >= aabb.y && draggedCenter.y < aabb.y + aabb.height;
8180
7938
  };
7939
+ const draggedArg = {
7940
+ id: draggedId,
7941
+ originPose: startWorld,
7942
+ pose: draggedWorld,
7943
+ sourceContainerId
7944
+ };
8181
7945
  const consider = (id, zPath) => {
8182
7946
  if (id === draggedId) return;
8183
7947
  const layout2 = layoutDep.getLayout(id);
@@ -8187,6 +7951,7 @@ function runLayoutPass(scratch, moveCtx) {
8187
7951
  const worldPose = composeWorldPose(poseAdapter, id, pc.compose);
8188
7952
  const worldAABB = boundsOf2(worldPose);
8189
7953
  if (!testInside(worldPose, worldAABB, layout2)) return;
7954
+ if (layout2.acceptsDrop && !layout2.acceptsDrop({ id, bounds: worldAABB }, draggedArg)) return;
8190
7955
  candidates.push({
8191
7956
  id,
8192
7957
  bounds: worldAABB,
@@ -8235,20 +8000,16 @@ function runLayoutPass(scratch, moveCtx) {
8235
8000
  id: cid,
8236
8001
  pose: composeWorldPose(poseAdapter, cid, pc.compose)
8237
8002
  }));
8238
- const draggedArg = {
8239
- id: draggedId,
8240
- originPose: startWorld,
8241
- pose: draggedWorld,
8242
- sourceContainerId
8243
- };
8244
8003
  const targets = layout.getDropTargets(container, children, draggedArg);
8245
8004
  const target = layout.snap.pickTarget(targets, { x: moveCtx.drag.current.x, y: moveCtx.drag.current.y });
8246
8005
  if (!target) return;
8006
+ const reflowIds = /* @__PURE__ */ new Set();
8247
8007
  for (const [cid, pose] of layout.reflowPoses(container, children, draggedArg, target)) {
8248
8008
  if (asNodeId(cid) === draggedId) continue;
8249
8009
  const parent = scene.get(asNodeId(cid))?.parent ?? null;
8250
8010
  const local = rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose);
8251
8011
  scratch.previews.set(asNodeId(cid), local);
8012
+ reflowIds.add(asNodeId(cid));
8252
8013
  }
8253
8014
  const sourceReflow = /* @__PURE__ */ new Map();
8254
8015
  if (sourceContainerId && sourceContainerId !== dest.id) {
@@ -8271,10 +8032,11 @@ function runLayoutPass(scratch, moveCtx) {
8271
8032
  sourceReflow.set(cid, pose);
8272
8033
  const parent = scene.get(asNodeId(cid))?.parent ?? null;
8273
8034
  scratch.previews.set(asNodeId(cid), rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose));
8035
+ reflowIds.add(asNodeId(cid));
8274
8036
  }
8275
8037
  }
8276
8038
  }
8277
- scratch.layoutPass = { layout, container, children, target, sourceReflow };
8039
+ scratch.layoutPass = { layout, container, children, target, sourceReflow, reflowIds };
8278
8040
  }
8279
8041
  function scenePoseAdapter(scene) {
8280
8042
  return {
@@ -8582,7 +8344,8 @@ var moveAction = {
8582
8344
  scratch.previews.clear();
8583
8345
  },
8584
8346
  previewIds: () => scratch.previews.keys(),
8585
- previewPose: (id) => scratch.previews.get(id) ?? null
8347
+ previewPose: (id) => scratch.previews.get(id) ?? null,
8348
+ previewOpaqueIds: () => scratch.layoutPass?.reflowIds ?? null
8586
8349
  };
8587
8350
  }
8588
8351
  },
@@ -9318,7 +9081,8 @@ var KIT_INSERT_KINDS = /* @__PURE__ */ new Set([
9318
9081
  "line",
9319
9082
  "polygon",
9320
9083
  "star",
9321
- "pencil"
9084
+ "pencil",
9085
+ "image"
9322
9086
  ]);
9323
9087
  var liveInsertScratch = null;
9324
9088
  function effectiveOriginMode(paramsOrigin, altHeld) {
@@ -10366,7 +10130,7 @@ var pinchZoomAction = {
10366
10130
  const multiTouch = ctx.multiTouch;
10367
10131
  if (!multiTouch) return {};
10368
10132
  const startSpread = multiTouch.spread > 0 ? multiTouch.spread : 1;
10369
- const scratch = { view, startSpread };
10133
+ const scratch = { view, startSpread, centroid: multiTouch.centroid };
10370
10134
  return {
10371
10135
  kind: "pinch",
10372
10136
  onMove(moveCtx) {
@@ -10376,8 +10140,15 @@ var pinchZoomAction = {
10376
10140
  if (currentSpread <= 0 || scratch.startSpread <= 0) return;
10377
10141
  const factor = currentSpread / scratch.startSpread;
10378
10142
  scratch.startSpread = currentSpread;
10143
+ const prev = scratch.centroid;
10144
+ scratch.centroid = centroid;
10379
10145
  const current = scratch.view.get();
10380
- scratch.view.set(zoomAt(current, centroid, factor));
10146
+ const zoomed = zoomAt(current, prev, factor);
10147
+ scratch.view.set({
10148
+ scale: zoomed.scale,
10149
+ x: zoomed.x - (centroid.x - prev.x) / zoomed.scale.x,
10150
+ y: zoomed.y - (centroid.y - prev.y) / zoomed.scale.y
10151
+ });
10381
10152
  }
10382
10153
  // onEnd: nothing to do — zoom was applied each frame.
10383
10154
  };
@@ -10837,10 +10608,12 @@ function normalizeNegativeExtent(pose) {
10837
10608
  if (!mutated) return pose;
10838
10609
  return { ...pose, x: nx, y: ny, width: nw, height: nh };
10839
10610
  }
10840
- function flipSelection(selection, scene, axis, applyOps, geometryProjection) {
10611
+ function flipSelection(selection, scene, axis, pivot, applyOps, geometryProjection) {
10841
10612
  const ids = selection.get();
10842
10613
  if (ids.length === 0) return;
10843
10614
  const geom = AUTO_POSE_DESCRIPTOR;
10615
+ const nodes = ids.map((id) => scene.get(id)).filter((n) => n != null);
10616
+ const unionPivot = pivot === "union" ? unionBounds(nodes.map((n) => geom.getBounds(n.pose))) : null;
10844
10617
  const ops = [];
10845
10618
  for (const id of ids) {
10846
10619
  const node = scene.get(id);
@@ -10848,10 +10621,10 @@ function flipSelection(selection, scene, axis, applyOps, geometryProjection) {
10848
10621
  ops.push(createTransformOp({
10849
10622
  id,
10850
10623
  from: node.pose,
10851
- to: flipPoseViaDescriptor(node.pose, axis, geom),
10624
+ to: unionPivot ? flipPoseAboutBounds(node.pose, axis, geom, unionPivot) : flipPoseViaDescriptor(node.pose, axis, geom),
10852
10625
  label: "Flip"
10853
10626
  }));
10854
- const g = geom.getBounds(node.pose);
10627
+ const g = unionPivot ?? geom.getBounds(node.pose);
10855
10628
  const cx = g.x + g.width / 2;
10856
10629
  const cy = g.y + g.height / 2;
10857
10630
  const m = axis === "x" ? [-1, 0, 0, 1, 2 * cx, 0] : [1, 0, 0, -1, 0, 2 * cy];
@@ -10880,12 +10653,13 @@ var flipAction = {
10880
10653
  timing: "immediate",
10881
10654
  run: (deps, params) => {
10882
10655
  const axis = params?.axis ?? "x";
10656
+ const pivot = params?.pivot ?? "each";
10883
10657
  const selection = deps.selection;
10884
10658
  const scene = deps.scene;
10885
10659
  const applyOps = deps.applyOps;
10886
10660
  const geometryProjection = deps.geometryProjection;
10887
10661
  if (!selection || !scene) return;
10888
- flipSelection(selection, scene, axis, applyOps, geometryProjection);
10662
+ flipSelection(selection, scene, axis, pivot, applyOps, geometryProjection);
10889
10663
  }
10890
10664
  },
10891
10665
  enabled: requiresSelection
@@ -12743,11 +12517,18 @@ var DEFAULT_DEBUG_THEME = {
12743
12517
  fpsText: "#e0e0e0",
12744
12518
  fpsTextBg: "rgba(0, 0, 0, 0.6)"
12745
12519
  };
12520
+ var DEFAULT_DEBUG_STROKES = {
12521
+ hitbox: { width: 1, dash: [2, 2] },
12522
+ bounds: { width: 1 },
12523
+ handle: { width: 1 },
12524
+ snap: { width: 1 }
12525
+ };
12746
12526
  function createDebugOverlayLayer({
12747
12527
  sink,
12748
12528
  config
12749
12529
  }) {
12750
12530
  const theme = { ...DEFAULT_DEBUG_THEME, ...config.theme ?? {} };
12531
+ const strokes = { ...DEFAULT_DEBUG_STROKES, ...config.strokes ?? {} };
12751
12532
  const fpsHistory = [];
12752
12533
  const FPS_WINDOW = 60;
12753
12534
  return {
@@ -12759,11 +12540,11 @@ function createDebugOverlayLayer({
12759
12540
  const s = sink.snapshot();
12760
12541
  const t = viewToTransform(view);
12761
12542
  const out = [];
12762
- if (config.hitboxes) emitHitboxes(out, s, view, t, theme);
12763
- if (config.bounds) emitBounds(out, s, view, t, theme);
12764
- if (config.handles) emitHandles(out, s, t, theme);
12543
+ if (config.hitboxes) emitHitboxes(out, s, view, t, theme, strokes);
12544
+ if (config.bounds) emitBounds(out, s, view, t, theme, strokes);
12545
+ if (config.handles) emitHandles(out, s, t, theme, strokes);
12765
12546
  if (config.origins) emitOrigins(out, s, t, theme);
12766
- if (config.snap) emitSnap(out, s, t, theme);
12547
+ if (config.snap) emitSnap(out, s, t, theme, strokes);
12767
12548
  if (config.ids) emitIds(out, s, t, theme);
12768
12549
  if (config.layers) emitLayersPanel(out, s, dims, theme);
12769
12550
  if (config.fps) {
@@ -12796,9 +12577,16 @@ function approxCircleScreen(cx, cy, r, segments = 24) {
12796
12577
  function rectPath2(x, y, w, h) {
12797
12578
  return { kind: "rect", x, y, width: w, height: h };
12798
12579
  }
12799
- function emitHitboxes(out, s, view, t, theme) {
12580
+ function strokeOf(s, color) {
12581
+ return {
12582
+ paint: { fill: "solid", color },
12583
+ width: s.width,
12584
+ ...s.dash && s.dash.length > 0 ? { dash: [...s.dash] } : {}
12585
+ };
12586
+ }
12587
+ function emitHitboxes(out, s, view, t, theme, strokes) {
12800
12588
  const fill = { fill: "solid", color: theme.hitboxFill };
12801
- const stroke = { paint: { fill: "solid", color: theme.hitboxStroke }, width: 1, dash: [2, 2] };
12589
+ const stroke = strokeOf(strokes.hitbox, theme.hitboxStroke);
12802
12590
  for (const h of s.hitboxes) {
12803
12591
  if (h.shape.kind === "rect") {
12804
12592
  const [sx, sy] = worldToScreen(h.shape.x, h.shape.y, t);
@@ -12812,8 +12600,8 @@ function emitHitboxes(out, s, view, t, theme) {
12812
12600
  }
12813
12601
  }
12814
12602
  }
12815
- function emitBounds(out, s, view, t, theme) {
12816
- const stroke = { paint: { fill: "solid", color: theme.bounds }, width: 1 };
12603
+ function emitBounds(out, s, view, t, theme, strokes) {
12604
+ const stroke = strokeOf(strokes.bounds, theme.bounds);
12817
12605
  for (const b of s.bounds) {
12818
12606
  const [sx, sy] = worldToScreen(b.bounds.x, b.bounds.y, t);
12819
12607
  const sw = b.bounds.width * view.scale.x;
@@ -12821,8 +12609,8 @@ function emitBounds(out, s, view, t, theme) {
12821
12609
  out.push({ kind: "path", path: rectPath2(sx, sy, sw, sh), stroke });
12822
12610
  }
12823
12611
  }
12824
- function emitHandles(out, s, t, theme) {
12825
- const stroke = { paint: { fill: "solid", color: theme.handle }, width: 1 };
12612
+ function emitHandles(out, s, t, theme, strokes) {
12613
+ const stroke = strokeOf(strokes.handle, theme.handle);
12826
12614
  for (const h of s.handles) {
12827
12615
  const [cx, cy] = worldToScreen(h.position.x, h.position.y, t);
12828
12616
  const horiz = {
@@ -12848,14 +12636,14 @@ function emitOrigins(out, s, t, theme) {
12848
12636
  out.push({ kind: "path", path: approxCircleScreen(cx, cy, 3), fill });
12849
12637
  }
12850
12638
  }
12851
- function emitSnap(out, s, t, theme) {
12639
+ function emitSnap(out, s, t, theme, strokes) {
12852
12640
  const color = theme.snap;
12853
12641
  for (const c of s.snap) {
12854
12642
  const [cx, cy] = worldToScreen(c.point.x, c.point.y, t);
12855
12643
  const cmd = {
12856
12644
  kind: "path",
12857
12645
  path: approxCircleScreen(cx, cy, 4),
12858
- ...c.accepted ? { fill: { fill: "solid", color } } : { stroke: { paint: { fill: "solid", color }, width: 1 } }
12646
+ ...c.accepted ? { fill: { fill: "solid", color } } : { stroke: strokeOf(strokes.snap, color) }
12859
12647
  };
12860
12648
  out.push(cmd);
12861
12649
  }
@@ -13227,11 +13015,22 @@ function reportDeadClaim(owner, warn) {
13227
13015
  `[weasel] exclusive claim by "${key}" matched no binding: no \`affordance:\` or \`kindOf\` target resolved against it, so the press was dropped.`
13228
13016
  );
13229
13017
  }
13018
+ function phaseRank(spec) {
13019
+ if (spec === void 0) return 0;
13020
+ const atoms = typeof spec === "string" ? [{ channel: "&", phase: spec }] : spec;
13021
+ if (atoms.length === 0) return 0;
13022
+ let min = 2;
13023
+ for (const a of atoms) {
13024
+ const rank = (a.channel === "*" ? 0 : 1) + (a.phase === "*" ? 0 : 1);
13025
+ if (rank < min) min = rank;
13026
+ }
13027
+ return min;
13028
+ }
13230
13029
  function specificity(spec) {
13231
13030
  const t = targetRank(specTargetOf(spec));
13232
13031
  const mods = "mods" in spec ? spec.mods : void 0;
13233
13032
  const m = modsCount(mods);
13234
- const p = "phase" in spec && spec.phase !== void 0 ? 1 : 0;
13033
+ const p = phaseRank("phase" in spec ? spec.phase : void 0);
13235
13034
  const typed = (spec.kind === "drop" || spec.kind === "paste") && spec.types !== void 0 && spec.types.length > 0 ? 2 : 1;
13236
13035
  return [t, m, p, typed];
13237
13036
  }
@@ -14831,8 +14630,7 @@ function useGestureDispatcher(opts) {
14831
14630
  if (!cd) return;
14832
14631
  const items = itemsFromClipboardData(cd);
14833
14632
  if (items.length === 0) return;
14834
- e.preventDefault();
14835
- dispatch2({
14633
+ const result = dispatch2({
14836
14634
  kind: "paste",
14837
14635
  items,
14838
14636
  // ClipboardEvent carries no modifier state.
@@ -14841,6 +14639,7 @@ function useGestureDispatcher(opts) {
14841
14639
  metaKey: false,
14842
14640
  shiftKey: false
14843
14641
  });
14642
+ if (result === "handled") e.preventDefault();
14844
14643
  };
14845
14644
  if (keyboard) {
14846
14645
  window.addEventListener("keydown", onKeyDown);
@@ -15083,6 +14882,50 @@ function usePinchGesture(canvasRef, onPinch, enabled = true) {
15083
14882
  };
15084
14883
  }, [canvasRef, enabled]);
15085
14884
  }
14885
+ function validateId(id, kind) {
14886
+ if (id.length === 0) {
14887
+ throw new Error(`weasel: ${kind} id may not be empty`);
14888
+ }
14889
+ if (RESERVED_ID_PREFIXES.has(id[0])) {
14890
+ throw new Error(
14891
+ `weasel: ${kind} id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
14892
+ );
14893
+ }
14894
+ if (RESERVED_ID_NAMES.has(id)) {
14895
+ throw new Error(
14896
+ `weasel: ${kind} id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
14897
+ );
14898
+ }
14899
+ }
14900
+ function defineTool(def) {
14901
+ validateId(def.id, "tool");
14902
+ const resolveCursor = (ctx) => {
14903
+ if (def.cursor == null) return "";
14904
+ return typeof def.cursor === "function" ? def.cursor(ctx) : def.cursor;
14905
+ };
14906
+ return {
14907
+ id: def.id,
14908
+ eligibility: {
14909
+ focus: true,
14910
+ capabilities: def.capabilities,
14911
+ ...def.hotkey ? { offhand: def.hotkey } : {}
14912
+ },
14913
+ actions: def.actions,
14914
+ def,
14915
+ presentation: def.presentation,
14916
+ keybinding: def.keybinding,
14917
+ onActivate: def.onActivate,
14918
+ onDeactivate: def.onDeactivate,
14919
+ initScratch: def.initScratch ?? (() => null),
14920
+ cursor: resolveCursor,
14921
+ bindings: def.bindings,
14922
+ overlay: def.overlay,
14923
+ overlayPosition: def.overlayPosition
14924
+ };
14925
+ }
14926
+ function defineViewportTool(def) {
14927
+ return defineTool(def);
14928
+ }
15086
14929
  var TOOL_OFFHAND_ID = "tool.offhand";
15087
14930
  function offhandKeyFor(trigger) {
15088
14931
  switch (trigger) {
@@ -15173,17 +15016,19 @@ function useContributions(opts) {
15173
15016
  const { ordered, state } = snapshot();
15174
15017
  return scopeBindings(ordered, state);
15175
15018
  }, [snapshot]);
15176
- const overlays = useCallback(() => {
15019
+ const overlays = useCallback((position = "top") => {
15177
15020
  const { ordered, state } = snapshot();
15178
15021
  const active = [];
15179
15022
  const hotkey = [];
15180
15023
  const ambient = [];
15181
15024
  for (const entry of ordered) {
15182
15025
  if (!entry.overlay) continue;
15026
+ if ((entry.overlayPosition ?? "top") !== position) continue;
15027
+ const layers = Array.isArray(entry.overlay) ? entry.overlay : [entry.overlay];
15183
15028
  const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
15184
- if (scope === "active") active.push(entry.overlay);
15185
- else if (scope === "hotkey") hotkey.push(entry.overlay);
15186
- else if (scope === "ambient") ambient.push(entry.overlay);
15029
+ if (scope === "active") active.push(...layers);
15030
+ else if (scope === "hotkey") hotkey.push(...layers);
15031
+ else if (scope === "ambient") ambient.push(...layers);
15187
15032
  }
15188
15033
  return [...active, ...hotkey, ...ambient];
15189
15034
  }, [snapshot]);
@@ -15811,7 +15656,7 @@ function isBoundsGradient(fill) {
15811
15656
  }
15812
15657
  return fill.units === "bounds";
15813
15658
  }
15814
- var cache5 = /* @__PURE__ */ new Map();
15659
+ var cache6 = /* @__PURE__ */ new Map();
15815
15660
  function specKey(spec) {
15816
15661
  const keys = Object.keys(spec).sort();
15817
15662
  const bag = spec;
@@ -15819,10 +15664,10 @@ function specKey(spec) {
15819
15664
  }
15820
15665
  function resolvePatternSpec(spec) {
15821
15666
  const key = specKey(spec);
15822
- const hit = cache5.get(key);
15667
+ const hit = cache6.get(key);
15823
15668
  if (hit !== void 0) return hit;
15824
15669
  const built = buildTile(spec);
15825
- cache5.set(key, built);
15670
+ cache6.set(key, built);
15826
15671
  return built;
15827
15672
  }
15828
15673
  function buildTile(spec) {
@@ -15856,7 +15701,7 @@ function resolveFillPattern(fill) {
15856
15701
  const handle = resolvePatternSpec(fill.pattern);
15857
15702
  return handle ? { ...fill, pattern: handle } : null;
15858
15703
  }
15859
- var cache6 = /* @__PURE__ */ new Map();
15704
+ var cache7 = /* @__PURE__ */ new Map();
15860
15705
  var subscribers2 = /* @__PURE__ */ new Set();
15861
15706
  var defaultLoader = async (src) => {
15862
15707
  if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
@@ -15874,28 +15719,28 @@ function notify() {
15874
15719
  }
15875
15720
  function startLoad(src) {
15876
15721
  const entry = { status: "loading" };
15877
- cache6.set(src, entry);
15722
+ cache7.set(src, entry);
15878
15723
  loader(src).then(
15879
15724
  (bitmap) => {
15880
- if (cache6.get(src) !== entry) return;
15881
- cache6.set(src, { status: "ready", bitmap });
15725
+ if (cache7.get(src) !== entry) return;
15726
+ cache7.set(src, { status: "ready", bitmap });
15882
15727
  notify();
15883
15728
  },
15884
15729
  () => {
15885
- if (cache6.get(src) !== entry) return;
15886
- cache6.set(src, { status: "error" });
15730
+ if (cache7.get(src) !== entry) return;
15731
+ cache7.set(src, { status: "error" });
15887
15732
  notify();
15888
15733
  }
15889
15734
  );
15890
15735
  }
15891
15736
  function getImageBitmap(src) {
15892
- const entry = cache6.get(src);
15737
+ const entry = cache7.get(src);
15893
15738
  if (entry) return entry.bitmap;
15894
15739
  startLoad(src);
15895
15740
  return void 0;
15896
15741
  }
15897
15742
  function imageStatus(src) {
15898
- return cache6.get(src)?.status ?? "idle";
15743
+ return cache7.get(src)?.status ?? "idle";
15899
15744
  }
15900
15745
  function subscribeImageReady(cb) {
15901
15746
  subscribers2.add(cb);
@@ -16253,6 +16098,7 @@ registerBuiltInShapePainters();
16253
16098
  function pickTopMostHit(ids, adapter) {
16254
16099
  if (ids.length === 0) return null;
16255
16100
  if (ids.length === 1) return ids[0];
16101
+ let candidates = ids;
16256
16102
  const getParent = adapter?.getParent;
16257
16103
  if (typeof getParent === "function") {
16258
16104
  const set = new Set(ids);
@@ -16267,16 +16113,40 @@ function pickTopMostHit(ids, adapter) {
16267
16113
  }
16268
16114
  if (dropped.size > 0) {
16269
16115
  const filtered = ids.filter((id) => !dropped.has(id));
16270
- if (filtered.length === 0) ;
16271
- else if (filtered.length === 1) {
16272
- return filtered[0];
16273
- } else {
16274
- return filtered[filtered.length - 1];
16116
+ if (filtered.length === 1) return filtered[0];
16117
+ if (filtered.length > 1) candidates = filtered;
16118
+ }
16119
+ }
16120
+ return pickHighestZ(candidates, adapter);
16121
+ }
16122
+ function pickHighestZ(ids, adapter) {
16123
+ const compareZ = adapter?.compareZ;
16124
+ if (typeof compareZ === "function") {
16125
+ let best = ids[0];
16126
+ for (let i = 1; i < ids.length; i++) {
16127
+ if (compareZ(ids[i], best) >= 0) best = ids[i];
16128
+ }
16129
+ return best;
16130
+ }
16131
+ const getZIndex = adapter?.getZIndex;
16132
+ if (typeof getZIndex === "function") {
16133
+ let best = ids[0];
16134
+ let bestZ = zOf(getZIndex, best);
16135
+ for (let i = 1; i < ids.length; i++) {
16136
+ const z = zOf(getZIndex, ids[i]);
16137
+ if (z >= bestZ) {
16138
+ best = ids[i];
16139
+ bestZ = z;
16275
16140
  }
16276
16141
  }
16142
+ return best;
16277
16143
  }
16278
16144
  return ids[ids.length - 1];
16279
16145
  }
16146
+ function zOf(getZIndex, id) {
16147
+ const z = getZIndex(id);
16148
+ return typeof z === "number" && Number.isFinite(z) ? z : -Infinity;
16149
+ }
16280
16150
  var MULTI_RESIZE_TARGET_ID = "__weasel:multi-selection";
16281
16151
  function useSelectTool(adapter, options) {
16282
16152
  const poseBoundsFn = options.poseBounds ?? ((p) => p);
@@ -16673,9 +16543,19 @@ function composeAffordanceLayer(id, label, affordances) {
16673
16543
  },
16674
16544
  // The walk itself lives in `hitAffordanceRegions` so this layer and
16675
16545
  // `buildAffordanceAt` share one implementation rather than two that can
16676
- // drift. This wrapper exists because `RenderLayer.hitTest` wants only the
16677
- // binding back.
16678
- hitTest: (wx, wy, state, view, _dims, isVisible) => hitAffordanceRegions(affordances, wx, wy, state, view, isVisible)?.binding ?? null
16546
+ // drift. This wrapper lifts the region's declared cursor and claim onto
16547
+ // the binding, the way `buildAffordanceAt` lifts them onto `AffordanceHit`.
16548
+ hitTest: (wx, wy, state, view, _dims, isVisible) => {
16549
+ const hit = hitAffordanceRegions(affordances, wx, wy, state, view, isVisible);
16550
+ if (!hit) return null;
16551
+ const { cursor, strength, claimedKinds } = hit.region;
16552
+ return {
16553
+ ...hit.binding,
16554
+ ...cursor !== void 0 ? { cursor } : {},
16555
+ ...strength !== void 0 ? { strength } : {},
16556
+ ...claimedKinds !== void 0 ? { claimedKinds } : {}
16557
+ };
16558
+ }
16679
16559
  };
16680
16560
  }
16681
16561
  function asChromeState(data) {
@@ -17835,6 +17715,19 @@ function composeOrderedLayers(layersMap, standardLayers, sceneLayers) {
17835
17715
  }
17836
17716
  return out;
17837
17717
  }
17718
+ function placeToolOverlays(ordered, selectionOverlay, overlays) {
17719
+ const before = overlays("before-selection");
17720
+ const after = overlays("after-selection");
17721
+ const at = selectionOverlay ? ordered.indexOf(selectionOverlay) : -1;
17722
+ if (at >= 0) {
17723
+ ordered.splice(at + 1, 0, ...after);
17724
+ ordered.splice(at, 0, ...before);
17725
+ } else {
17726
+ ordered.push(...before, ...after);
17727
+ }
17728
+ ordered.push(...overlays("top"));
17729
+ return ordered;
17730
+ }
17838
17731
  function* toolsInPriorityOrder(tools) {
17839
17732
  const seen = /* @__PURE__ */ new Set();
17840
17733
  const hotkey = tools.hotkeyEngaged ? tools.registry[tools.hotkeyEngaged] : void 0;
@@ -19263,7 +19156,7 @@ function CanvasInner(props, ref) {
19263
19156
  const out = composeOrderedLayers(effectiveLayersMap, standardLayers, sceneLayers);
19264
19157
  if (decorationLayer) out.push(decorationLayer);
19265
19158
  if (tools) {
19266
- out.push(...tools.getActiveOverlays());
19159
+ placeToolOverlays(out, standardLayers.selectionOverlay, tools.getActiveOverlays);
19267
19160
  }
19268
19161
  return out;
19269
19162
  }, [layersMap, adapter, selectedIds, effectiveBoundsOf, multiActive2, debugSink, tools, backgroundLayer, decorationLayer]);
@@ -20893,6 +20786,7 @@ function useSceneSelectTool(args) {
20893
20786
  boundsOf: wiredBoundsOf
20894
20787
  };
20895
20788
  }
20789
+ var GHOST_ALPHA = 0.85;
20896
20790
  function usePreviewGhostLayer(args) {
20897
20791
  const { scene, tools, sceneSlot, dispatcher } = args;
20898
20792
  const sceneRef = useRef(scene);
@@ -20979,15 +20873,26 @@ function usePreviewGhostLayer(args) {
20979
20873
  }
20980
20874
  return [group];
20981
20875
  };
20876
+ const opaqueSet = /* @__PURE__ */ new Set();
20877
+ for (const source of sources) {
20878
+ const ids = source.previewOpaqueIds?.();
20879
+ if (!ids) continue;
20880
+ for (const id of ids) opaqueSet.add(id);
20881
+ }
20982
20882
  const children = [];
20883
+ const opaque = [];
20983
20884
  for (const id of idSet) {
20984
20885
  const node = sc.get(asNodeId(id));
20985
20886
  const parent = node?.parent;
20986
20887
  if (parent != null && idSet.has(parent)) continue;
20987
- for (const cmd of buildSubtree(id)) children.push(cmd);
20888
+ const sink = opaqueSet.has(id) ? opaque : children;
20889
+ for (const cmd of buildSubtree(id)) sink.push(cmd);
20988
20890
  }
20989
- if (children.length === 0) return [];
20990
- return [{ kind: "group", alpha: 0.85, children }];
20891
+ if (children.length === 0 && opaque.length === 0) return [];
20892
+ const out = [];
20893
+ if (opaque.length > 0) out.push({ kind: "group", children: opaque });
20894
+ if (children.length > 0) out.push({ kind: "group", alpha: GHOST_ALPHA, children });
20895
+ return out;
20991
20896
  }
20992
20897
  }), []);
20993
20898
  }
@@ -20997,6 +20902,7 @@ var DEFAULT_STYLE = {
20997
20902
  dash: [3, 3],
20998
20903
  lineWidth: 1
20999
20904
  };
20905
+ var PREVIEW_CONTENT_OPACITY = 0.85;
21000
20906
  function useDispatcherOverlayLayer(args) {
21001
20907
  const { dispatcher, style } = args;
21002
20908
  const dispatcherRef = useRef(dispatcher);
@@ -21059,6 +20965,26 @@ function useDispatcherOverlayLayer(args) {
21059
20965
  pathCmd = rectPath(sx, sy, b.width * view.scale.x, b.height * view.scale.y);
21060
20966
  break;
21061
20967
  }
20968
+ case "image": {
20969
+ const e = ov.extras;
20970
+ const [sx, sy] = worldToScreen(b.x, b.y, t);
20971
+ const sw = b.width * view.scale.x;
20972
+ const sh = b.height * view.scale.y;
20973
+ const bmp = e.preview === "outline" || !e.src ? void 0 : getImageBitmap(e.src);
20974
+ if (bmp) {
20975
+ out.push({
20976
+ kind: "image",
20977
+ image: bmp,
20978
+ x: sx,
20979
+ y: sy,
20980
+ w: sw,
20981
+ h: sh,
20982
+ opacity: PREVIEW_CONTENT_OPACITY
20983
+ });
20984
+ }
20985
+ pathCmd = rectPath(sx, sy, sw, sh);
20986
+ break;
20987
+ }
21062
20988
  case "ellipse": {
21063
20989
  const [sx, sy] = worldToScreen(b.x, b.y, t);
21064
20990
  pathCmd = ellipsePath({
@@ -21117,7 +21043,7 @@ function useDispatcherOverlayLayer(args) {
21117
21043
  const cmd = {
21118
21044
  kind: "path",
21119
21045
  path: pathCmd,
21120
- fill: ov.shape === "line" || ov.shape === "pencil" ? void 0 : { color: cfg.fill },
21046
+ fill: ov.shape === "line" || ov.shape === "pencil" || ov.shape === "image" ? void 0 : { color: cfg.fill },
21121
21047
  stroke: { paint: { color: cfg.stroke }, width: cfg.lineWidth, dash: cfg.dash }
21122
21048
  };
21123
21049
  out.push(cmd);
@@ -21806,9 +21732,6 @@ function buildClassifyTarget(getSelection, pickBest, kindOfNode) {
21806
21732
  function w2s3(wx, wy, view) {
21807
21733
  return [(wx - view.x) * view.scale.x, (wy - view.y) * view.scale.y];
21808
21734
  }
21809
- function r2s(radiusWorld, view) {
21810
- return radiusWorld * meanScale(view.scale);
21811
- }
21812
21735
  var SLOP_FILL = "rgba(255, 80, 140, 0.18)";
21813
21736
  var SLOP_STROKE = "rgba(255, 80, 140, 0.55)";
21814
21737
  function createSlopsDebugLayer(opts) {
@@ -21819,7 +21742,7 @@ function createSlopsDebugLayer(opts) {
21819
21742
  draw: (_data, view) => {
21820
21743
  const out = [];
21821
21744
  const sel = opts.selectionRef.current?.current ?? [];
21822
- const r = r2s(HANDLE_HIT_RADIUS, view);
21745
+ const r = HANDLE_HIT_RADIUS;
21823
21746
  for (const id of sel) {
21824
21747
  const b = opts.boundsOf(id);
21825
21748
  if (!b) continue;
@@ -21856,6 +21779,7 @@ function createSlopsDebugLayer(opts) {
21856
21779
  }
21857
21780
  const editingId = opts.getEditingId();
21858
21781
  if (editingId) {
21782
+ const anchorR = ANCHOR_HIT_RADIUS;
21859
21783
  const pose = opts.getPose(editingId);
21860
21784
  if (pose && pose.kind === "polygon") {
21861
21785
  const anchors = enumerateAnchors(pose);
@@ -21863,7 +21787,7 @@ function createSlopsDebugLayer(opts) {
21863
21787
  const [sx, sy] = w2s3(a.x, a.y, view);
21864
21788
  out.push({
21865
21789
  kind: "path",
21866
- path: circlePath(sx, sy, r),
21790
+ path: circlePath(sx, sy, anchorR),
21867
21791
  fill: { fill: "solid", color: SLOP_FILL },
21868
21792
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
21869
21793
  });
@@ -21871,7 +21795,7 @@ function createSlopsDebugLayer(opts) {
21871
21795
  const [csx, csy] = w2s3(a.controlIn.x, a.controlIn.y, view);
21872
21796
  out.push({
21873
21797
  kind: "path",
21874
- path: circlePath(csx, csy, r),
21798
+ path: circlePath(csx, csy, anchorR),
21875
21799
  fill: { fill: "solid", color: SLOP_FILL },
21876
21800
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
21877
21801
  });
@@ -21880,7 +21804,7 @@ function createSlopsDebugLayer(opts) {
21880
21804
  const [csx, csy] = w2s3(a.controlOut.x, a.controlOut.y, view);
21881
21805
  out.push({
21882
21806
  kind: "path",
21883
- path: circlePath(csx, csy, r),
21807
+ path: circlePath(csx, csy, anchorR),
21884
21808
  fill: { fill: "solid", color: SLOP_FILL },
21885
21809
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
21886
21810
  });
@@ -21958,9 +21882,9 @@ function DepRegistryProviderIfRoot({ children }) {
21958
21882
  function hitTestArea(scene, bounds) {
21959
21883
  const { x, y, width: w, height: h } = bounds;
21960
21884
  const area = [x, y, x + w, y, x + w, y + h, x, y + h];
21961
- return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h });
21885
+ return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h }, true);
21962
21886
  }
21963
- function hitTestAreaPolygon(scene, area, areaBounds) {
21887
+ function hitTestAreaPolygon(scene, area, areaBounds, areaIsRect = false) {
21964
21888
  const ab = areaBounds ?? boundsOf(area);
21965
21889
  if (!ab) return [];
21966
21890
  const hits = [];
@@ -21977,13 +21901,22 @@ function hitTestAreaPolygon(scene, area, areaBounds) {
21977
21901
  if (b.x >= ab.x + ab.width || b.x + b.width <= ab.x || b.y >= ab.y + ab.height || b.y + b.height <= ab.y) {
21978
21902
  continue;
21979
21903
  }
21980
- if (!silhouette) {
21904
+ if (areaIsRect && b.x >= ab.x && b.y >= ab.y && b.x + b.width <= ab.x + ab.width && b.y + b.height <= ab.y + ab.height) {
21981
21905
  hits.push(node.id);
21982
21906
  continue;
21983
21907
  }
21984
- if (silhouetteOverlapsArea(pose.coords, area)) {
21985
- hits.push(node.id);
21908
+ if (silhouette) {
21909
+ if (silhouetteOverlapsArea(pose.coords, area)) {
21910
+ hits.push(node.id);
21911
+ }
21912
+ continue;
21913
+ }
21914
+ const drawn = findShapeSilhouette(node, pose);
21915
+ if (drawn?.kind === "polygon") {
21916
+ if (silhouetteOverlapsArea(drawn.coords, area)) hits.push(node.id);
21917
+ continue;
21986
21918
  }
21919
+ hits.push(node.id);
21987
21920
  }
21988
21921
  return hits;
21989
21922
  }
@@ -24573,5 +24506,5 @@ function oklchToOklab(L, C, h) {
24573
24506
  }
24574
24507
 
24575
24508
  export { FALLBACK_FIT_VIEW, MinimapCanvas, SceneViewCanvas, UnknownIcon, buildSceneViewCommands, computeFitView2, computeIndicatorCommand, eligibleContribution, evaluateEnabled, oklabToSrgbU8, oklchToOklab, renderSceneToCanvas, rgbaToHex, useActionsRegistry, useOptionalDepRegistry };
24576
- //# sourceMappingURL=chunk-ISEK5LXT.js.map
24577
- //# sourceMappingURL=chunk-ISEK5LXT.js.map
24509
+ //# sourceMappingURL=chunk-3WPOGKUP.js.map
24510
+ //# sourceMappingURL=chunk-3WPOGKUP.js.map