@weasel-js/labkit 1.0.1 → 1.0.2

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.
@@ -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-W2WB4352.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-W2WB4352.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();
@@ -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;
@@ -3477,7 +3131,7 @@ function serialToEntry(se, custom, logger) {
3477
3131
  };
3478
3132
  }
3479
3133
 
3480
- // ../core/dist/chunk-FVNWK7U3.js
3134
+ // ../core/dist/chunk-PI56ORHE.js
3481
3135
  function worldToScreen(worldX, worldY, view) {
3482
3136
  return [view.panX + worldX * view.zoom.x, view.panY + worldY * view.zoom.y];
3483
3137
  }
@@ -3791,6 +3445,8 @@ var GLMeshCache = class {
3791
3445
  gl;
3792
3446
  aPositionLoc;
3793
3447
  map = /* @__PURE__ */ new WeakMap();
3448
+ /** Meshes this context has drawn at least once; see `uploadRecurring`. */
3449
+ seen = /* @__PURE__ */ new WeakSet();
3794
3450
  finalizer;
3795
3451
  pendingDeletes = [];
3796
3452
  /** Transient resources allocated this frame; freed at end of render(). */
@@ -3815,6 +3471,19 @@ var GLMeshCache = class {
3815
3471
  this.transientThisFrame.push({ vao: handle.vao, vbo, ibo });
3816
3472
  return handle;
3817
3473
  }
3474
+ /**
3475
+ * Upload a Mesh that may or may not recur across frames. Its first sight in
3476
+ * *this* context takes `uploadTransient`; every later one takes the
3477
+ * persistent handle. One transient upload to find out is cheaper than
3478
+ * stranding a persistent VAO on a mesh that never returns, whose release
3479
+ * would wait on GC. The transient upload does not populate the persistent
3480
+ * map, so steady-state reuse begins on the third frame.
3481
+ */
3482
+ uploadRecurring(mesh) {
3483
+ if (this.seen.has(mesh)) return this.handleFor(mesh);
3484
+ this.seen.add(mesh);
3485
+ return this.uploadTransient(mesh);
3486
+ }
3818
3487
  /**
3819
3488
  * Free all transient resources allocated since the last call. Called by
3820
3489
  * the renderer at the end of each `render()`. Safe under context loss.
@@ -4881,1258 +4550,1258 @@ function getMesh(path) {
4881
4550
  cache.set(path, mesh);
4882
4551
  return mesh;
4883
4552
  }
4884
- function extractPolylines(path, opts = {}) {
4885
- if (path.kind === "rect") return [extractRect(path)];
4886
- return extractPolygon(path, opts);
4553
+ var UNDERLINE_OFFSET = 0.1;
4554
+ var STRIKETHROUGH_OFFSET = -0.3;
4555
+ var DECORATION_THICKNESS = 0.05;
4556
+ function fillKey(p) {
4557
+ if ("color" in p) return `s:${p.color}:${p.opacity ?? 1}`;
4558
+ return `nx:${Math.random()}`;
4887
4559
  }
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
- };
4560
+ function sameFill(a, b) {
4561
+ if (a === b) return true;
4562
+ if ("color" in a && "color" in b) {
4563
+ return a.color === b.color && (a.opacity ?? 1) === (b.opacity ?? 1);
4564
+ }
4565
+ return false;
4897
4566
  }
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;
4567
+ function strokeKey(s) {
4568
+ if (!s) return "-";
4569
+ return [
4570
+ fillKey(s.paint),
4571
+ s.width ?? 1,
4572
+ s.join ?? "miter",
4573
+ s.cap ?? "butt",
4574
+ s.miterLimit ?? "",
4575
+ s.align ?? "center",
4576
+ (s.dash ?? []).join(",")
4577
+ ].join(":");
5026
4578
  }
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
4579
+ function groupKey(family, weight, style, synthetic, fill, stroke, source, page) {
4580
+ return `${family}|${weight}|${style}|${synthetic.bold ? 1 : 0}${synthetic.italic ? 1 : 0}|${fillKey(fill)}|${strokeKey(stroke)}|${source}|${page}`;
4581
+ }
4582
+ function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
4583
+ const atlasFamily = resolved.resolved.family;
4584
+ const resolvedWeight = resolved.resolved.weight;
4585
+ const resolvedStyle = resolved.resolved.style;
4586
+ const source = sourceOverride ?? resolved.source;
4587
+ const key = groupKey(
4588
+ atlasFamily,
4589
+ resolvedWeight,
4590
+ resolvedStyle,
4591
+ resolved.synthetic,
4592
+ run.fill,
4593
+ source === "outline" ? run.stroke : void 0,
4594
+ source,
4595
+ page
4596
+ );
4597
+ let g = ctx.groups.get(key);
4598
+ if (!g) {
4599
+ g = {
4600
+ family: atlasFamily,
4601
+ weight: resolvedWeight,
4602
+ style: resolvedStyle,
4603
+ synthetic: { ...resolved.synthetic },
4604
+ source,
4605
+ page,
4606
+ fill: run.fill,
4607
+ // Only the outline tier can paint it, and carrying it on an SDF group
4608
+ // would be a promise the renderer cannot keep.
4609
+ ...source === "outline" && run.stroke !== void 0 ? { stroke: run.stroke } : {},
4610
+ quads: [],
4611
+ glyphs: []
5051
4612
  };
4613
+ ctx.groups.set(key, g);
5052
4614
  }
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
- };
4615
+ return g;
5076
4616
  }
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);
4617
+ function resolveGlyph(run, font, resolved, cp) {
4618
+ const direct = font.charMap.get(cp);
4619
+ if (direct) return { glyph: direct, font, resolved };
4620
+ const fallback = resolveGlyphFallback(run.fontFamily, run.fontWeight, run.fontStyle);
4621
+ const face = fallback?.dynamicFace;
4622
+ if (fallback && face) {
4623
+ const glyph = face.requestGlyph(cp);
4624
+ if (glyph.xadvance > 0 || glyph.width > 0) {
4625
+ return { glyph, font: face.font, resolved: fallback };
5109
4626
  }
5110
4627
  }
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
- }
4628
+ warnMissingGlyphOnce(resolved.resolved.family, cp);
4629
+ return null;
5152
4630
  }
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;
4631
+ var warnedMissingGlyphs = /* @__PURE__ */ new Set();
4632
+ function warnMissingGlyphOnce(family, cp) {
4633
+ const key = `${family}|${cp}`;
4634
+ if (warnedMissingGlyphs.has(key)) return;
4635
+ warnedMissingGlyphs.add(key);
4636
+ const ch = String.fromCodePoint(cp);
4637
+ console.warn(
4638
+ `weasel layoutRuns: no glyph for U+${cp.toString(16).toUpperCase().padStart(4, "0")} (${JSON.stringify(ch)}) in "${family}", and the dynamic tier could not rasterize it \u2014 skipping the character. Bake it into the atlas, or call registerCanvasFont("${family}") to serve missing codepoints from installed fonts.`
4639
+ );
4640
+ }
4641
+ function layoutRuns(runs, opts) {
4642
+ const ctx = { groups: /* @__PURE__ */ new Map() };
4643
+ const entries = [];
4644
+ let prevCp;
4645
+ let prevFont;
4646
+ let prevFontSize;
4647
+ for (const run of runs) {
4648
+ const resolved = resolveFontVariant(run.fontFamily, run.fontWeight, run.fontStyle);
4649
+ const font = resolved.entry?.font ?? resolved.dynamicFace?.font;
4650
+ if (!font) {
4651
+ prevCp = void 0;
4652
+ prevFont = void 0;
4653
+ prevFontSize = void 0;
4654
+ continue;
4655
+ }
4656
+ const scale2 = run.fontSize / font.info.size;
4657
+ const tracking = run.letterSpacing;
4658
+ for (const ch of [...run.text]) {
4659
+ const cp = ch.codePointAt(0);
4660
+ const isNewline = cp === 10;
4661
+ const isSpace = cp === 32;
4662
+ if (isNewline) {
4663
+ entries.push({
4664
+ run,
4665
+ font,
4666
+ glyph: { id: cp, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
4667
+ // A newline consumes no advance, so it takes no tracking either.
4668
+ cp,
4669
+ advance: 0,
4670
+ tracking: 0,
4671
+ kerningBefore: 0,
4672
+ isSpace: false,
4673
+ isNewline: true,
4674
+ resolved,
4675
+ fontSize: run.fontSize
4676
+ });
4677
+ prevCp = void 0;
4678
+ prevFont = void 0;
4679
+ prevFontSize = void 0;
4680
+ continue;
4681
+ }
4682
+ if (isSpace) {
4683
+ const spaceGlyph = resolved.dynamicFace ? resolved.dynamicFace.requestGlyph(32) : font.charMap.get(32);
4684
+ const advance = spaceGlyph ? spaceGlyph.xadvance * scale2 : run.fontSize * 0.25;
4685
+ let kerningBefore2 = 0;
4686
+ if (prevCp !== void 0 && prevFont !== void 0 && prevFontSize !== void 0) {
4687
+ const kAtlas = prevFont.kerningMap.get(prevCp)?.get(cp) ?? 0;
4688
+ kerningBefore2 = kAtlas * (prevFontSize / prevFont.info.size);
4689
+ }
4690
+ entries.push({
4691
+ run,
4692
+ font,
4693
+ glyph: spaceGlyph ?? { id: 32, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
4694
+ cp,
4695
+ advance,
4696
+ tracking,
4697
+ kerningBefore: kerningBefore2,
4698
+ isSpace: true,
4699
+ isNewline: false,
4700
+ resolved,
4701
+ fontSize: run.fontSize
4702
+ });
4703
+ prevCp = cp;
4704
+ prevFont = font;
4705
+ prevFontSize = run.fontSize;
4706
+ continue;
4707
+ }
4708
+ const hit = resolved.dynamicFace ? { glyph: resolved.dynamicFace.requestGlyph(cp), font, resolved } : resolveGlyph(run, font, resolved, cp);
4709
+ if (!hit) {
4710
+ prevCp = cp;
4711
+ prevFont = font;
4712
+ prevFontSize = run.fontSize;
4713
+ continue;
4714
+ }
4715
+ const glyphFont = hit.font;
4716
+ const glyphScale = run.fontSize / glyphFont.info.size;
4717
+ let kerningBefore = 0;
4718
+ if (prevCp !== void 0 && prevFont !== void 0 && prevFontSize !== void 0) {
4719
+ const kAtlas = prevFont.kerningMap.get(prevCp)?.get(cp) ?? 0;
4720
+ kerningBefore = kAtlas * (prevFontSize / prevFont.info.size);
4721
+ }
4722
+ entries.push({
4723
+ run,
4724
+ font: glyphFont,
4725
+ glyph: hit.glyph,
4726
+ cp,
4727
+ advance: hit.glyph.xadvance * glyphScale,
4728
+ tracking,
4729
+ kerningBefore,
4730
+ isSpace,
4731
+ isNewline: false,
4732
+ resolved: hit.resolved,
4733
+ fontSize: run.fontSize
4734
+ });
4735
+ prevCp = cp;
4736
+ prevFont = glyphFont;
4737
+ prevFontSize = run.fontSize;
5172
4738
  }
5173
4739
  }
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;
4740
+ const lines = [];
4741
+ let cur = { entries: [], width: 0, height: 0 };
4742
+ function commitLine() {
4743
+ lines.push(cur);
4744
+ cur = { entries: [], width: 0, height: 0 };
5204
4745
  }
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;
4746
+ let i = 0;
4747
+ while (i < entries.length) {
4748
+ const e = entries[i];
4749
+ if (e.isNewline) {
4750
+ if (cur.entries.length === 0) {
4751
+ cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
4752
+ cur.blank = e;
4753
+ }
4754
+ commitLine();
4755
+ i++;
4756
+ continue;
5228
4757
  }
5229
- if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);
5230
- else idx.push(prevIdx, pivotIdx, rightIdx);
4758
+ if (e.isSpace) {
4759
+ if (cur.entries.length > 0) {
4760
+ cur.entries.push(e);
4761
+ cur.width += e.kerningBefore + e.advance + e.tracking;
4762
+ cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
4763
+ }
4764
+ i++;
4765
+ continue;
4766
+ }
4767
+ let j = i;
4768
+ let wordWidth = 0;
4769
+ while (j < entries.length && !entries[j].isSpace && !entries[j].isNewline) {
4770
+ const w = entries[j];
4771
+ wordWidth += w.kerningBefore + w.advance + w.tracking;
4772
+ j++;
4773
+ }
4774
+ if (Number.isFinite(opts.maxWidth) && cur.width + wordWidth > opts.maxWidth && cur.entries.length > 0) {
4775
+ commitLine();
4776
+ }
4777
+ for (let k = i; k < j; k++) {
4778
+ const w = entries[k];
4779
+ const kerningBefore = cur.entries.length === 0 ? 0 : w.kerningBefore;
4780
+ cur.entries.push({ ...w, kerningBefore });
4781
+ cur.width += kerningBefore + w.advance + w.tracking;
4782
+ cur.height = Math.max(cur.height, w.fontSize * opts.lineHeight);
4783
+ }
4784
+ i = j;
5231
4785
  }
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
- });
4786
+ if (cur.entries.length > 0) commitLine();
4787
+ function outlineFor(e) {
4788
+ const min = opts.outlineMinSize;
4789
+ if (min === void 0 || e.fontSize < min) return null;
4790
+ if (e.resolved.synthetic.bold) return null;
4791
+ const r = e.resolved.resolved;
4792
+ return glyphOutline(r.family, r.weight, r.style, e.cp);
4793
+ }
4794
+ const decorations = [];
4795
+ let span = null;
4796
+ function flushSpan() {
4797
+ const s = span;
4798
+ span = null;
4799
+ if (!s || s.x1 <= s.x0) return;
4800
+ const thickness = s.fontSize * DECORATION_THICKNESS;
4801
+ if (s.underline) {
4802
+ const y0 = s.baselineY + s.fontSize * UNDERLINE_OFFSET;
4803
+ decorations.push({ kind: "underline", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
5254
4804
  }
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;
4805
+ if (s.strikethrough) {
4806
+ const y0 = s.baselineY + s.fontSize * STRIKETHROUGH_OFFSET;
4807
+ decorations.push({ kind: "strikethrough", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
4808
+ }
4809
+ }
4810
+ const lineBoxes = [];
4811
+ let penY = 0;
4812
+ let maxLineWidth = 0;
4813
+ const finiteWidth = Number.isFinite(opts.maxWidth) ? opts.maxWidth : 0;
4814
+ for (const line of lines) {
4815
+ const alignShift = (() => {
4816
+ if (opts.align === "left") return 0;
4817
+ if (!Number.isFinite(opts.maxWidth)) {
4818
+ return opts.align === "center" ? -line.width / 2 : -line.width;
4819
+ }
4820
+ const slack = finiteWidth - line.width;
4821
+ return opts.align === "center" ? slack / 2 : slack;
4822
+ })();
4823
+ const lineX0 = alignShift;
4824
+ const baselineSource = line.entries[0] ?? line.blank;
4825
+ const lineBaselineY = baselineSource ? penY + baselineSource.font.common.base * (baselineSource.fontSize / baselineSource.font.info.size) : penY;
4826
+ let penX = lineX0;
4827
+ for (const e of line.entries) {
4828
+ penX += e.kerningBefore;
4829
+ const step = e.advance + e.tracking;
4830
+ const scale2 = e.fontSize / e.font.info.size;
4831
+ const baselineY = penY + e.font.common.base * scale2;
4832
+ if (e.run.underline || e.run.strikethrough) {
4833
+ 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)) {
4834
+ span.x1 = penX + step;
5309
4835
  } 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];
4836
+ flushSpan();
4837
+ span = {
4838
+ underline: e.run.underline,
4839
+ strikethrough: e.run.strikethrough,
4840
+ fill: e.run.fill,
4841
+ fontSize: e.fontSize,
4842
+ baselineY,
4843
+ x0: penX,
4844
+ x1: penX + step
4845
+ };
5337
4846
  }
4847
+ } else {
4848
+ flushSpan();
4849
+ }
4850
+ const outlineD = outlineFor(e);
4851
+ if (outlineD !== null) {
4852
+ const group2 = getOrCreateGroup(ctx, e.run, e.resolved, 0, "outline");
4853
+ group2.glyphs.push({
4854
+ d: outlineD,
4855
+ key: `${e.resolved.resolved.family}|${e.resolved.resolved.weight}|${e.resolved.resolved.style}|${e.cp}`,
4856
+ x: penX,
4857
+ baselineY,
4858
+ // Em space is unit-scale, so world units per em is just the size.
4859
+ scale: e.fontSize
4860
+ });
4861
+ penX += step;
4862
+ continue;
4863
+ }
4864
+ if (e.advance === 0 || e.glyph.width === 0 || e.glyph.page < 0) {
4865
+ penX += step;
4866
+ continue;
5338
4867
  }
4868
+ const group = getOrCreateGroup(ctx, e.run, e.resolved, e.glyph.page);
4869
+ const atlasW = e.font.common.scaleW;
4870
+ const atlasH = e.font.common.scaleH;
4871
+ const qx0 = penX + e.glyph.xoffset * scale2;
4872
+ const qy0 = penY + e.glyph.yoffset * scale2;
4873
+ const qx1 = qx0 + e.glyph.width * scale2;
4874
+ const qy1 = qy0 + e.glyph.height * scale2;
4875
+ const u0 = e.glyph.x / atlasW;
4876
+ const v0 = e.glyph.y / atlasH;
4877
+ const u1 = (e.glyph.x + e.glyph.width) / atlasW;
4878
+ const v1 = (e.glyph.y + e.glyph.height) / atlasH;
4879
+ group.quads.push({ x0: qx0, y0: qy0, x1: qx1, y1: qy1, u0, v0, u1, v1, baselineY });
4880
+ penX += step;
5339
4881
  }
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)
4882
+ flushSpan();
4883
+ lineBoxes.push({
4884
+ x0: lineX0,
4885
+ y0: penY,
4886
+ x1: lineX0 + line.width,
4887
+ y1: penY + line.height,
4888
+ baselineY: lineBaselineY
5348
4889
  });
4890
+ maxLineWidth = Math.max(maxLineWidth, line.width);
4891
+ penY += line.height;
5349
4892
  }
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
4893
  return {
5357
- ax,
5358
- ay,
5359
- bx,
5360
- by,
5361
- nxUnit: -dy / len,
5362
- nyUnit: dx / len,
5363
- halfA,
5364
- halfB,
5365
- len
4894
+ groups: [...ctx.groups.values()],
4895
+ decorations,
4896
+ lines: lineBoxes,
4897
+ bounds: { width: maxLineWidth, height: penY }
5366
4898
  };
5367
4899
  }
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;
4900
+ var LAYOUT_CACHE_VARIANT_LIMIT = 8;
4901
+ var cache2 = /* @__PURE__ */ new WeakMap();
4902
+ function outlineBucket(runs, min) {
4903
+ if (min === void 0) return -1;
4904
+ const sizes = /* @__PURE__ */ new Set();
4905
+ for (const r of runs) sizes.add(r.fontSize);
4906
+ let n = 0;
4907
+ for (const size of sizes) if (size >= min) n++;
4908
+ return n;
4909
+ }
4910
+ function variantKey2(runs, opts) {
4911
+ return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}`;
4912
+ }
4913
+ function cachedLayoutRuns(runs, opts) {
4914
+ const fonts = glyphGeneration();
4915
+ let entry = cache2.get(runs);
4916
+ if (entry === void 0) {
4917
+ entry = { generation: fonts, byVariant: /* @__PURE__ */ new Map() };
4918
+ cache2.set(runs, entry);
4919
+ } else if (entry.generation !== fonts) {
4920
+ entry.generation = fonts;
4921
+ entry.byVariant.clear();
5394
4922
  }
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);
4923
+ const key = variantKey2(runs, opts);
4924
+ const hit = entry.byVariant.get(key);
4925
+ if (hit !== void 0) return hit;
4926
+ const laid = layoutRuns(runs, opts);
4927
+ if (entry.byVariant.size >= LAYOUT_CACHE_VARIANT_LIMIT) entry.byVariant.clear();
4928
+ entry.byVariant.set(key, laid);
4929
+ return laid;
4930
+ }
4931
+ function verticalAlignOffset(align, boxHeight, textHeight) {
4932
+ if (align === void 0 || align === "top" || boxHeight === void 0) return 0;
4933
+ const slack = boxHeight - textHeight;
4934
+ return align === "center" ? slack / 2 : slack;
4935
+ }
4936
+ var NUM_RE = /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g;
4937
+ function tokenize(d) {
4938
+ const out = [];
4939
+ let i = 0;
4940
+ while (i < d.length) {
4941
+ const ch = d[i];
4942
+ if (/[A-Za-z]/.test(ch)) {
4943
+ let j = i + 1;
4944
+ while (j < d.length && !/[A-Za-z]/.test(d[j])) j++;
4945
+ const argSlice = d.slice(i + 1, j);
4946
+ out.push({ cmd: ch, args: readNumbers(argSlice) });
4947
+ i = j;
4948
+ } else {
4949
+ i++;
4950
+ }
4951
+ }
4952
+ return out;
4953
+ }
4954
+ function readNumbers(s) {
4955
+ const out = [];
4956
+ let m;
4957
+ NUM_RE.lastIndex = 0;
4958
+ while ((m = NUM_RE.exec(s)) !== null) {
4959
+ const n = Number(m[0]);
4960
+ if (Number.isFinite(n)) out.push(n);
4961
+ }
4962
+ return out;
4963
+ }
4964
+ var ARG_COUNTS = {
4965
+ M: 2,
4966
+ L: 2,
4967
+ H: 1,
4968
+ V: 1,
4969
+ C: 6,
4970
+ S: 4,
4971
+ Q: 4,
4972
+ T: 2,
4973
+ A: 7,
4974
+ Z: 0
4975
+ };
4976
+ function pathFromD(d, onWarn) {
4977
+ const tokens = tokenize(d);
4978
+ const b = new PathBuilder();
4979
+ const st = {
4980
+ x: 0,
4981
+ y: 0,
4982
+ startX: 0,
4983
+ startY: 0,
4984
+ lastCubicCtrl: null,
4985
+ lastQuadCtrl: null
4986
+ };
4987
+ for (const { cmd, args } of tokens) {
4988
+ const upper = cmd.toUpperCase();
4989
+ const relative = cmd !== upper;
4990
+ const argCount = ARG_COUNTS[upper];
4991
+ if (argCount === void 0) {
4992
+ continue;
4993
+ }
4994
+ if (upper === "Z") {
4995
+ b.close();
4996
+ st.x = st.startX;
4997
+ st.y = st.startY;
4998
+ st.lastCubicCtrl = null;
4999
+ st.lastQuadCtrl = null;
5000
+ continue;
5001
+ }
5002
+ const groups = argCount === 0 ? 1 : Math.floor(args.length / argCount);
5003
+ for (let g = 0; g < groups; g++) {
5004
+ const a = args.slice(g * argCount, (g + 1) * argCount);
5005
+ const effectiveUpper = upper === "M" && g > 0 ? "L" : upper;
5006
+ runCommand(effectiveUpper, relative, a, st, b);
5007
+ }
5008
+ }
5009
+ return b.build();
5010
+ }
5011
+ function runCommand(upper, relative, a, st, b) {
5012
+ const rx = relative ? st.x : 0;
5013
+ const ry = relative ? st.y : 0;
5014
+ switch (upper) {
5015
+ case "M": {
5016
+ const x = a[0] + rx;
5017
+ const y = a[1] + ry;
5018
+ b.moveTo(x, y);
5019
+ st.x = x;
5020
+ st.y = y;
5021
+ st.startX = x;
5022
+ st.startY = y;
5023
+ st.lastCubicCtrl = null;
5024
+ st.lastQuadCtrl = null;
5025
+ return;
5026
+ }
5027
+ case "L": {
5028
+ const x = a[0] + rx;
5029
+ const y = a[1] + ry;
5030
+ b.lineTo(x, y);
5031
+ st.x = x;
5032
+ st.y = y;
5033
+ st.lastCubicCtrl = null;
5034
+ st.lastQuadCtrl = null;
5035
+ return;
5036
+ }
5037
+ case "H": {
5038
+ const x = a[0] + (relative ? st.x : 0);
5039
+ b.lineTo(x, st.y);
5040
+ st.x = x;
5041
+ st.lastCubicCtrl = null;
5042
+ st.lastQuadCtrl = null;
5043
+ return;
5044
+ }
5045
+ case "V": {
5046
+ const y = a[0] + (relative ? st.y : 0);
5047
+ b.lineTo(st.x, y);
5048
+ st.y = y;
5049
+ st.lastCubicCtrl = null;
5050
+ st.lastQuadCtrl = null;
5051
+ return;
5052
+ }
5053
+ case "C": {
5054
+ const x1 = a[0] + rx, y1 = a[1] + ry;
5055
+ const x2 = a[2] + rx, y2 = a[3] + ry;
5056
+ const x = a[4] + rx, y = a[5] + ry;
5057
+ b.curveTo(x1, y1, x2, y2, x, y);
5058
+ st.x = x;
5059
+ st.y = y;
5060
+ st.lastCubicCtrl = { x: x2, y: y2 };
5061
+ st.lastQuadCtrl = null;
5401
5062
  return;
5402
5063
  }
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);
5064
+ case "S": {
5065
+ const ref = st.lastCubicCtrl ?? { x: st.x, y: st.y };
5066
+ const x1 = 2 * st.x - ref.x;
5067
+ const y1 = 2 * st.y - ref.y;
5068
+ const x2 = a[0] + rx, y2 = a[1] + ry;
5069
+ const x = a[2] + rx, y = a[3] + ry;
5070
+ b.curveTo(x1, y1, x2, y2, x, y);
5071
+ st.x = x;
5072
+ st.y = y;
5073
+ st.lastCubicCtrl = { x: x2, y: y2 };
5074
+ st.lastQuadCtrl = null;
5406
5075
  return;
5407
5076
  }
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);
5077
+ case "Q": {
5078
+ const x1 = a[0] + rx, y1 = a[1] + ry;
5079
+ const x = a[2] + rx, y = a[3] + ry;
5080
+ b.quadTo(x1, y1, x, y);
5081
+ st.x = x;
5082
+ st.y = y;
5083
+ st.lastQuadCtrl = { x: x1, y: y1 };
5084
+ st.lastCubicCtrl = null;
5085
+ return;
5424
5086
  }
5425
- return;
5426
- }
5427
- if (join === "round") {
5428
- emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
5429
- return;
5087
+ case "T": {
5088
+ const ref = st.lastQuadCtrl ?? { x: st.x, y: st.y };
5089
+ const x1 = 2 * st.x - ref.x;
5090
+ const y1 = 2 * st.y - ref.y;
5091
+ const x = a[0] + rx, y = a[1] + ry;
5092
+ b.quadTo(x1, y1, x, y);
5093
+ st.x = x;
5094
+ st.y = y;
5095
+ st.lastQuadCtrl = { x: x1, y: y1 };
5096
+ st.lastCubicCtrl = null;
5097
+ return;
5098
+ }
5099
+ case "A": {
5100
+ const rxA = Math.abs(a[0]);
5101
+ const ryA = Math.abs(a[1]);
5102
+ const xRot = a[2];
5103
+ const largeArc = a[3] !== 0;
5104
+ const sweep = a[4] !== 0;
5105
+ const ex = a[5] + rx;
5106
+ const ey = a[6] + ry;
5107
+ arcToCubics(st.x, st.y, ex, ey, rxA, ryA, xRot, largeArc, sweep, b);
5108
+ st.x = ex;
5109
+ st.y = ey;
5110
+ st.lastCubicCtrl = null;
5111
+ st.lastQuadCtrl = null;
5112
+ return;
5113
+ }
5114
+ default:
5115
+ return;
5430
5116
  }
5431
5117
  }
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;
5118
+ function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep, b) {
5119
+ if (x1 === x2 && y1 === y2) return;
5120
+ if (rx === 0 || ry === 0) {
5121
+ b.lineTo(x2, y2);
5122
+ return;
5465
5123
  }
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);
5124
+ const phi = xAxisRotationDeg * Math.PI / 180;
5125
+ const cosPhi = Math.cos(phi);
5126
+ const sinPhi = Math.sin(phi);
5127
+ const dx = (x1 - x2) / 2;
5128
+ const dy = (y1 - y2) / 2;
5129
+ const x1p = cosPhi * dx + sinPhi * dy;
5130
+ const y1p = -sinPhi * dx + cosPhi * dy;
5131
+ let rxs = rx * rx;
5132
+ let rys = ry * ry;
5133
+ const x1ps = x1p * x1p;
5134
+ const y1ps = y1p * y1p;
5135
+ const lambda = x1ps / rxs + y1ps / rys;
5136
+ let rxFixed = rx;
5137
+ let ryFixed = ry;
5138
+ if (lambda > 1) {
5139
+ const sqrtLambda = Math.sqrt(lambda);
5140
+ rxFixed = sqrtLambda * rx;
5141
+ ryFixed = sqrtLambda * ry;
5142
+ rxs = rxFixed * rxFixed;
5143
+ rys = ryFixed * ryFixed;
5495
5144
  }
5496
- return false;
5497
- }
5498
- function strokeKey(s) {
5499
- if (!s) return "-";
5500
- return [
5501
- fillKey(s.paint),
5502
- s.width ?? 1,
5503
- s.join ?? "miter",
5504
- s.cap ?? "butt",
5505
- s.miterLimit ?? "",
5506
- s.align ?? "center",
5507
- (s.dash ?? []).join(",")
5508
- ].join(":");
5509
- }
5510
- function groupKey(family, weight, style, synthetic, fill, stroke, source, page) {
5511
- return `${family}|${weight}|${style}|${synthetic.bold ? 1 : 0}${synthetic.italic ? 1 : 0}|${fillKey(fill)}|${strokeKey(stroke)}|${source}|${page}`;
5512
- }
5513
- function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
5514
- const atlasFamily = resolved.resolved.family;
5515
- const resolvedWeight = resolved.resolved.weight;
5516
- const resolvedStyle = resolved.resolved.style;
5517
- const source = sourceOverride ?? resolved.source;
5518
- const key = groupKey(
5519
- atlasFamily,
5520
- resolvedWeight,
5521
- resolvedStyle,
5522
- resolved.synthetic,
5523
- run.fill,
5524
- source === "outline" ? run.stroke : void 0,
5525
- source,
5526
- page
5145
+ const sign2 = largeArc === sweep ? -1 : 1;
5146
+ const num = rxs * rys - rxs * y1ps - rys * x1ps;
5147
+ const den = rxs * y1ps + rys * x1ps;
5148
+ const factor = sign2 * Math.sqrt(Math.max(0, num / den));
5149
+ const cxp = factor * (rxFixed * y1p) / ryFixed;
5150
+ const cyp = factor * -(ryFixed * x1p) / rxFixed;
5151
+ const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
5152
+ const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
5153
+ const angle = (ux, uy, vx, vy) => {
5154
+ const dot = ux * vx + uy * vy;
5155
+ const len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
5156
+ let a = Math.acos(Math.max(-1, Math.min(1, dot / len)));
5157
+ if (ux * vy - uy * vx < 0) a = -a;
5158
+ return a;
5159
+ };
5160
+ const theta1 = angle(1, 0, (x1p - cxp) / rxFixed, (y1p - cyp) / ryFixed);
5161
+ let dTheta = angle(
5162
+ (x1p - cxp) / rxFixed,
5163
+ (y1p - cyp) / ryFixed,
5164
+ (-x1p - cxp) / rxFixed,
5165
+ (-y1p - cyp) / ryFixed
5527
5166
  );
5528
- let g = ctx.groups.get(key);
5529
- if (!g) {
5530
- g = {
5531
- family: atlasFamily,
5532
- weight: resolvedWeight,
5533
- style: resolvedStyle,
5534
- synthetic: { ...resolved.synthetic },
5535
- source,
5536
- page,
5537
- fill: run.fill,
5538
- // Only the outline tier can paint it, and carrying it on an SDF group
5539
- // would be a promise the renderer cannot keep.
5540
- ...source === "outline" && run.stroke !== void 0 ? { stroke: run.stroke } : {},
5541
- quads: [],
5542
- glyphs: []
5543
- };
5544
- ctx.groups.set(key, g);
5167
+ if (!sweep && dTheta > 0) dTheta -= 2 * Math.PI;
5168
+ else if (sweep && dTheta < 0) dTheta += 2 * Math.PI;
5169
+ const segCount = Math.max(1, Math.ceil(Math.abs(dTheta) / (Math.PI / 2)));
5170
+ const segAngle = dTheta / segCount;
5171
+ const t = 4 / 3 * Math.tan(segAngle / 4);
5172
+ let prevX = x1;
5173
+ let prevY = y1;
5174
+ let prevDx = -Math.sin(theta1) * rxFixed;
5175
+ let prevDy = Math.cos(theta1) * ryFixed;
5176
+ for (let i = 1; i <= segCount; i++) {
5177
+ const theta = theta1 + i * segAngle;
5178
+ const cosT = Math.cos(theta);
5179
+ const sinT = Math.sin(theta);
5180
+ const px = cosPhi * (cosT * rxFixed) - sinPhi * (sinT * ryFixed) + cx;
5181
+ const py = sinPhi * (cosT * rxFixed) + cosPhi * (sinT * ryFixed) + cy;
5182
+ const dxNew = -sinT * rxFixed;
5183
+ const dyNew = cosT * ryFixed;
5184
+ const c1x = prevX + cosPhi * (t * prevDx) - sinPhi * (t * prevDy);
5185
+ const c1y = prevY + sinPhi * (t * prevDx) + cosPhi * (t * prevDy);
5186
+ const c2x = px - cosPhi * (t * dxNew) + sinPhi * (t * dyNew);
5187
+ const c2y = py - sinPhi * (t * dxNew) - cosPhi * (t * dyNew);
5188
+ b.curveTo(c1x, c1y, c2x, c2y, px, py);
5189
+ prevX = px;
5190
+ prevY = py;
5191
+ prevDx = dxNew;
5192
+ prevDy = dyNew;
5545
5193
  }
5546
- return g;
5547
5194
  }
5548
- function resolveGlyph(run, font, resolved, cp) {
5549
- const direct = font.charMap.get(cp);
5550
- if (direct) return { glyph: direct, font, resolved };
5551
- const fallback = resolveGlyphFallback(run.fontFamily, run.fontWeight, run.fontStyle);
5552
- const face = fallback?.dynamicFace;
5553
- if (fallback && face) {
5554
- const glyph = face.requestGlyph(cp);
5555
- if (glyph.xadvance > 0 || glyph.width > 0) {
5556
- return { glyph, font: face.font, resolved: fallback };
5557
- }
5558
- }
5559
- warnMissingGlyphOnce(resolved.resolved.family, cp);
5560
- return null;
5195
+ var OUTLINE_FLATTEN_TOLERANCE = 1 / 4096;
5196
+ var OUTLINE_MESH_CACHE_LIMIT = 2048;
5197
+ var cache3 = /* @__PURE__ */ new Map();
5198
+ function outlineMesh(key, d) {
5199
+ const cached2 = cache3.get(key);
5200
+ if (cached2 !== void 0) return cached2;
5201
+ if (cache3.size >= OUTLINE_MESH_CACHE_LIMIT) cache3 = /* @__PURE__ */ new Map();
5202
+ const mesh = tessellate(pathFromD(d), { flattenTolerance: OUTLINE_FLATTEN_TOLERANCE });
5203
+ cache3.set(key, mesh);
5204
+ return mesh;
5561
5205
  }
5562
- var warnedMissingGlyphs = /* @__PURE__ */ new Set();
5563
- function warnMissingGlyphOnce(family, cp) {
5564
- const key = `${family}|${cp}`;
5565
- if (warnedMissingGlyphs.has(key)) return;
5566
- warnedMissingGlyphs.add(key);
5567
- const ch = String.fromCodePoint(cp);
5568
- console.warn(
5569
- `weasel layoutRuns: no glyph for U+${cp.toString(16).toUpperCase().padStart(4, "0")} (${JSON.stringify(ch)}) in "${family}", and the dynamic tier could not rasterize it \u2014 skipping the character. Bake it into the atlas, or call registerCanvasFont("${family}") to serve missing codepoints from installed fonts.`
5570
- );
5206
+ function extractPolylines(path, opts = {}) {
5207
+ if (path.kind === "rect") return [extractRect(path)];
5208
+ return extractPolygon(path, opts);
5571
5209
  }
5572
- function layoutRuns(runs, opts) {
5573
- const ctx = { groups: /* @__PURE__ */ new Map() };
5574
- const entries = [];
5575
- let prevCp;
5576
- let prevFont;
5577
- let prevFontSize;
5578
- for (const run of runs) {
5579
- const resolved = resolveFontVariant(run.fontFamily, run.fontWeight, run.fontStyle);
5580
- const font = resolved.entry?.font ?? resolved.dynamicFace?.font;
5581
- if (!font) {
5582
- prevCp = void 0;
5583
- prevFont = void 0;
5584
- prevFontSize = void 0;
5585
- continue;
5586
- }
5587
- const scale2 = run.fontSize / font.info.size;
5588
- const tracking = run.letterSpacing;
5589
- for (const ch of [...run.text]) {
5590
- const cp = ch.codePointAt(0);
5591
- const isNewline = cp === 10;
5592
- const isSpace = cp === 32;
5593
- if (isNewline) {
5594
- entries.push({
5595
- run,
5596
- font,
5597
- glyph: { id: cp, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
5598
- // A newline consumes no advance, so it takes no tracking either.
5599
- cp,
5600
- advance: 0,
5601
- tracking: 0,
5602
- kerningBefore: 0,
5603
- isSpace: false,
5604
- isNewline: true,
5605
- resolved,
5606
- fontSize: run.fontSize
5607
- });
5608
- prevCp = void 0;
5609
- prevFont = void 0;
5610
- prevFontSize = void 0;
5611
- continue;
5612
- }
5613
- if (isSpace) {
5614
- const spaceGlyph = resolved.dynamicFace ? resolved.dynamicFace.requestGlyph(32) : font.charMap.get(32);
5615
- const advance = spaceGlyph ? spaceGlyph.xadvance * scale2 : run.fontSize * 0.25;
5616
- let kerningBefore2 = 0;
5617
- if (prevCp !== void 0 && prevFont !== void 0 && prevFontSize !== void 0) {
5618
- const kAtlas = prevFont.kerningMap.get(prevCp)?.get(cp) ?? 0;
5619
- kerningBefore2 = kAtlas * (prevFontSize / prevFont.info.size);
5620
- }
5621
- entries.push({
5622
- run,
5623
- font,
5624
- glyph: spaceGlyph ?? { id: 32, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
5625
- cp,
5626
- advance,
5627
- tracking,
5628
- kerningBefore: kerningBefore2,
5629
- isSpace: true,
5630
- isNewline: false,
5631
- resolved,
5632
- fontSize: run.fontSize
5633
- });
5634
- prevCp = cp;
5635
- prevFont = font;
5636
- prevFontSize = run.fontSize;
5637
- continue;
5210
+ function extractRect(p) {
5211
+ const { x, y, width: w, height: h } = p;
5212
+ return {
5213
+ points: [x, y, x + w, y, x + w, y + h, x, y + h],
5214
+ closed: true,
5215
+ anchorA: new Uint32Array([0, 1, 2, 3]),
5216
+ anchorB: new Uint32Array([0, 1, 2, 3]),
5217
+ anchorT: new Float32Array([0, 0, 0, 0])
5218
+ };
5219
+ }
5220
+ function extractPolygon(p, opts) {
5221
+ const tolerance = opts.flattenTolerance ?? DEFAULT_FLATTEN_TOLERANCE;
5222
+ const { commands, coords } = p;
5223
+ const out = [];
5224
+ let anchorCounter = 0;
5225
+ let pts = null;
5226
+ let aA = null;
5227
+ let aB = null;
5228
+ let aT = null;
5229
+ let current = null;
5230
+ let coordIdx = 0;
5231
+ let prevX = 0;
5232
+ let prevY = 0;
5233
+ let prevAnchor = -1;
5234
+ const beginContour = () => {
5235
+ pts = [];
5236
+ aA = [];
5237
+ aB = [];
5238
+ aT = [];
5239
+ const next = { points: pts, closed: false };
5240
+ out.push(next);
5241
+ return next;
5242
+ };
5243
+ const dropDuplicateClosingPoint = () => {
5244
+ if (!pts || !aA || !aB || !aT) return;
5245
+ const n = pts.length / 2;
5246
+ if (n < 2) return;
5247
+ if (pts[0] !== pts[(n - 1) * 2] || pts[1] !== pts[(n - 1) * 2 + 1]) return;
5248
+ pts.length -= 2;
5249
+ aA.length -= 1;
5250
+ aB.length -= 1;
5251
+ aT.length -= 1;
5252
+ };
5253
+ const commit = (target) => {
5254
+ if (!pts || !aA || !aB || !aT) return;
5255
+ target.anchorA = new Uint32Array(aA);
5256
+ target.anchorB = new Uint32Array(aB);
5257
+ target.anchorT = new Float32Array(aT);
5258
+ };
5259
+ for (let cmdIdx = 0; cmdIdx < commands.length; cmdIdx++) {
5260
+ const cmd = commands[cmdIdx];
5261
+ switch (cmd) {
5262
+ case PATH_M2: {
5263
+ if (current) commit(current);
5264
+ current = beginContour();
5265
+ prevX = coords[coordIdx];
5266
+ prevY = coords[coordIdx + 1];
5267
+ pts.push(prevX, prevY);
5268
+ aA.push(anchorCounter);
5269
+ aB.push(anchorCounter);
5270
+ aT.push(0);
5271
+ prevAnchor = anchorCounter;
5272
+ anchorCounter++;
5273
+ coordIdx += 2;
5274
+ break;
5638
5275
  }
5639
- const hit = resolved.dynamicFace ? { glyph: resolved.dynamicFace.requestGlyph(cp), font, resolved } : resolveGlyph(run, font, resolved, cp);
5640
- if (!hit) {
5641
- prevCp = cp;
5642
- prevFont = font;
5643
- prevFontSize = run.fontSize;
5644
- continue;
5276
+ case PATH_L2: {
5277
+ prevX = coords[coordIdx];
5278
+ prevY = coords[coordIdx + 1];
5279
+ pts.push(prevX, prevY);
5280
+ aA.push(anchorCounter);
5281
+ aB.push(anchorCounter);
5282
+ aT.push(0);
5283
+ prevAnchor = anchorCounter;
5284
+ anchorCounter++;
5285
+ coordIdx += 2;
5286
+ break;
5645
5287
  }
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);
5288
+ case PATH_Q2: {
5289
+ const cx = coords[coordIdx], cy = coords[coordIdx + 1];
5290
+ const ex = coords[coordIdx + 2], ey = coords[coordIdx + 3];
5291
+ const targetAnchor = anchorCounter;
5292
+ const segStart = pts.length / 2;
5293
+ const arcAccum = [];
5294
+ const total = flattenQuadraticWithArcLen(prevX, prevY, cx, cy, ex, ey, tolerance, pts, arcAccum);
5295
+ for (let k = 0; k < arcAccum.length; k++) {
5296
+ aA.push(prevAnchor);
5297
+ aB.push(targetAnchor);
5298
+ aT.push(total > 0 ? arcAccum[k] / total : 0);
5299
+ }
5300
+ const lastIdx = segStart + arcAccum.length - 1;
5301
+ aA[lastIdx] = targetAnchor;
5302
+ aB[lastIdx] = targetAnchor;
5303
+ aT[lastIdx] = 0;
5304
+ prevX = ex;
5305
+ prevY = ey;
5306
+ prevAnchor = targetAnchor;
5307
+ anchorCounter++;
5308
+ coordIdx += 4;
5309
+ break;
5652
5310
  }
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;
5669
- }
5670
- }
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 };
5676
- }
5677
- 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;
5311
+ case PATH_C2: {
5312
+ const c1x = coords[coordIdx], c1y = coords[coordIdx + 1];
5313
+ const c2x = coords[coordIdx + 2], c2y = coords[coordIdx + 3];
5314
+ const ex = coords[coordIdx + 4], ey = coords[coordIdx + 5];
5315
+ const targetAnchor = anchorCounter;
5316
+ const segStart = pts.length / 2;
5317
+ const arcAccum = [];
5318
+ const total = flattenCubicWithArcLen(prevX, prevY, c1x, c1y, c2x, c2y, ex, ey, tolerance, pts, arcAccum);
5319
+ for (let k = 0; k < arcAccum.length; k++) {
5320
+ aA.push(prevAnchor);
5321
+ aB.push(targetAnchor);
5322
+ aT.push(total > 0 ? arcAccum[k] / total : 0);
5323
+ }
5324
+ const lastIdx = segStart + arcAccum.length - 1;
5325
+ aA[lastIdx] = targetAnchor;
5326
+ aB[lastIdx] = targetAnchor;
5327
+ aT[lastIdx] = 0;
5328
+ prevX = ex;
5329
+ prevY = ey;
5330
+ prevAnchor = targetAnchor;
5331
+ anchorCounter++;
5332
+ coordIdx += 6;
5333
+ break;
5684
5334
  }
5685
- commitLine();
5686
- i++;
5687
- continue;
5688
- }
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);
5335
+ case PATH_Z2: {
5336
+ if (current) {
5337
+ current.closed = true;
5338
+ dropDuplicateClosingPoint();
5339
+ }
5340
+ break;
5694
5341
  }
5695
- i++;
5696
- continue;
5697
- }
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++;
5704
- }
5705
- if (Number.isFinite(opts.maxWidth) && cur.width + wordWidth > opts.maxWidth && cur.entries.length > 0) {
5706
- commitLine();
5707
- }
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);
5342
+ default:
5343
+ throw new Error(`extractPolylines: unknown command code ${cmd}`);
5714
5344
  }
5715
- i = j;
5716
5345
  }
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);
5346
+ if (current) commit(current);
5347
+ return out;
5348
+ }
5349
+ var EMPTY_MESH = {
5350
+ vertices: new Float32Array(0),
5351
+ indices: new Uint32Array(0),
5352
+ anchorA: new Uint32Array(0),
5353
+ anchorB: new Uint32Array(0),
5354
+ anchorT: new Float32Array(0)
5355
+ };
5356
+ function tessellateStroke(path, stroke, opts = {}) {
5357
+ const width = stroke.width ?? 1;
5358
+ if (width <= 0) return EMPTY_MESH;
5359
+ const join = stroke.join ?? "miter";
5360
+ const cap = stroke.cap ?? "butt";
5361
+ const align = stroke.align ?? "center";
5362
+ const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;
5363
+ const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;
5364
+ let workingPath = path;
5365
+ if (path.kind === "rect" && align !== "center") {
5366
+ const aligned = alignedStrokeRect(path, align, width);
5367
+ workingPath = {
5368
+ kind: "rect",
5369
+ x: aligned.x,
5370
+ y: aligned.y,
5371
+ width: aligned.width,
5372
+ height: aligned.height
5373
+ };
5724
5374
  }
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 });
5375
+ const polylines = extractPolylines(workingPath, opts);
5376
+ if (stroke.vertexWidths && stroke.vertexWidths.length > 0) {
5377
+ for (const pl of polylines) populatePolylineWidths(pl, stroke.vertexWidths, width);
5378
+ }
5379
+ const dash = stroke.dash ?? [];
5380
+ const verts = [];
5381
+ const idx = [];
5382
+ const anchorA = [];
5383
+ const anchorB = [];
5384
+ const anchorT = [];
5385
+ for (const pl of polylines) {
5386
+ const subs = dash.length > 0 ? splitForDash(pl, dash) : [pl];
5387
+ for (const sub2 of subs) {
5388
+ expandPolyline(sub2, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);
5735
5389
  }
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 });
5390
+ }
5391
+ return {
5392
+ vertices: new Float32Array(verts),
5393
+ indices: new Uint32Array(idx),
5394
+ anchorA: new Uint32Array(anchorA),
5395
+ anchorB: new Uint32Array(anchorB),
5396
+ anchorT: new Float32Array(anchorT)
5397
+ };
5398
+ }
5399
+ function expandPolyline(pl, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT) {
5400
+ const half = width / 2;
5401
+ const pts = pl.points;
5402
+ const ptCount = pts.length / 2;
5403
+ const segCount = ptCount - 1;
5404
+ if (segCount < 1) return;
5405
+ const plA = pl.anchorA ?? new Uint32Array(ptCount);
5406
+ const plB = pl.anchorB ?? new Uint32Array(ptCount);
5407
+ const plT = pl.anchorT ?? new Float32Array(ptCount);
5408
+ const plHalf = pl.widths ? pl.widths.map((w) => w / 2) : null;
5409
+ const segs = [];
5410
+ const segSrcIdx = [];
5411
+ for (let s = 0; s < segCount; s++) {
5412
+ const ha = plHalf ? plHalf[s] : half;
5413
+ const hb = plHalf ? plHalf[s + 1] : half;
5414
+ const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], ha, hb);
5415
+ if (seg) {
5416
+ segs.push(seg);
5417
+ segSrcIdx.push(s);
5739
5418
  }
5740
5419
  }
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
- };
5777
- }
5778
- } else {
5779
- flushSpan();
5780
- }
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;
5794
- }
5795
- if (e.advance === 0 || e.glyph.width === 0 || e.glyph.page < 0) {
5796
- penX += step;
5797
- continue;
5798
- }
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;
5420
+ let closerSrcIdx = -1;
5421
+ if (pl.closed && segs.length >= 1) {
5422
+ const last = segs[segs.length - 1];
5423
+ const first = segs[0];
5424
+ const haCloser = plHalf ? plHalf[ptCount - 1] : half;
5425
+ const hbCloser = plHalf ? plHalf[0] : half;
5426
+ const closer = makeSeg(last.bx, last.by, first.ax, first.ay, haCloser, hbCloser);
5427
+ if (closer) {
5428
+ segs.push(closer);
5429
+ closerSrcIdx = ptCount - 1;
5430
+ segSrcIdx.push(closerSrcIdx);
5812
5431
  }
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
5432
  }
5824
- return {
5825
- groups: [...ctx.groups.values()],
5826
- decorations,
5827
- lines: lineBoxes,
5828
- bounds: { width: maxLineWidth, height: penY }
5829
- };
5830
- }
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();
5433
+ if (segs.length === 0) return;
5434
+ const segBaseIdx = [];
5435
+ for (let s = 0; s < segs.length; s++) {
5436
+ const seg = segs[s];
5437
+ const startSrc = segSrcIdx[s];
5438
+ const endSrc = s === segs.length - 1 && pl.closed && closerSrcIdx >= 0 ? 0 : startSrc + 1;
5439
+ const base = verts.length / 2;
5440
+ segBaseIdx.push(base);
5441
+ const nxA = seg.nxUnit * seg.halfA, nyA = seg.nyUnit * seg.halfA;
5442
+ const nxB = seg.nxUnit * seg.halfB, nyB = seg.nyUnit * seg.halfB;
5443
+ verts.push(seg.ax + nxA, seg.ay + nyA);
5444
+ anchorA.push(plA[startSrc]);
5445
+ anchorB.push(plB[startSrc]);
5446
+ anchorT.push(plT[startSrc]);
5447
+ verts.push(seg.ax - nxA, seg.ay - nyA);
5448
+ anchorA.push(plA[startSrc]);
5449
+ anchorB.push(plB[startSrc]);
5450
+ anchorT.push(plT[startSrc]);
5451
+ verts.push(seg.bx + nxB, seg.by + nyB);
5452
+ anchorA.push(plA[endSrc]);
5453
+ anchorB.push(plB[endSrc]);
5454
+ anchorT.push(plT[endSrc]);
5455
+ verts.push(seg.bx - nxB, seg.by - nyB);
5456
+ anchorA.push(plA[endSrc]);
5457
+ anchorB.push(plB[endSrc]);
5458
+ anchorT.push(plT[endSrc]);
5459
+ idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
5460
+ }
5461
+ const joinCount = pl.closed ? segs.length : segs.length - 1;
5462
+ for (let j = 0; j < joinCount; j++) {
5463
+ emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);
5464
+ }
5465
+ if (!pl.closed && cap !== "butt") {
5466
+ const first = segs[0];
5467
+ const firstBase = segBaseIdx[0];
5468
+ emitCap(first, firstBase + 0, firstBase + 1, false, first.halfA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);
5469
+ const last = segs[segs.length - 1];
5470
+ const lastBase = segBaseIdx[segs.length - 1];
5471
+ const lastSrc = segSrcIdx[segs.length - 1] + 1;
5472
+ emitCap(last, lastBase + 2, lastBase + 3, true, last.halfB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);
5853
5473
  }
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
5474
  }
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;
5475
+ function populatePolylineWidths(pl, vertexWidths, fallbackWidth) {
5476
+ const ptCount = pl.points.length / 2;
5477
+ if (ptCount === 0) return;
5478
+ const aA = pl.anchorA ?? new Uint32Array(ptCount);
5479
+ const aB = pl.anchorB ?? new Uint32Array(ptCount);
5480
+ const aT = pl.anchorT ?? new Float32Array(ptCount);
5481
+ const widths = new Float32Array(ptCount);
5482
+ const read2 = (i) => {
5483
+ const v = vertexWidths[i];
5484
+ return typeof v === "number" && isFinite(v) && v > 0 ? v : fallbackWidth;
5485
+ };
5486
+ for (let i = 0; i < ptCount; i++) {
5487
+ const a = aA[i], b = aB[i], t = aT[i];
5488
+ if (a === b) {
5489
+ widths[i] = read2(a);
5490
+ } else {
5491
+ const wa = read2(a);
5492
+ const wb = read2(b);
5493
+ widths[i] = wa + (wb - wa) * t;
5494
+ }
5495
+ }
5496
+ pl.widths = widths;
5866
5497
  }
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;
5498
+ var ROUND_CAP_STEP_RAD = 10 * Math.PI / 180;
5499
+ function emitCap(seg, leftIdx, rightIdx, atEnd, half, cap, verts, idx, anchorA, anchorB, anchorT, endA, endB, endT) {
5500
+ const sign2 = atEnd ? 1 : -1;
5501
+ const dx = (seg.bx - seg.ax) / seg.len * sign2;
5502
+ const dy = (seg.by - seg.ay) / seg.len * sign2;
5503
+ const cx = atEnd ? seg.bx : seg.ax;
5504
+ const cy = atEnd ? seg.by : seg.ay;
5505
+ const nx = seg.nxUnit * half;
5506
+ const ny = seg.nyUnit * half;
5507
+ if (cap === "square") {
5508
+ const ox = cx + dx * half;
5509
+ const oy = cy + dy * half;
5510
+ const lOut = verts.length / 2;
5511
+ verts.push(ox + nx, oy + ny);
5512
+ anchorA.push(endA);
5513
+ anchorB.push(endB);
5514
+ anchorT.push(endT);
5515
+ const rOut = verts.length / 2;
5516
+ verts.push(ox - nx, oy - ny);
5517
+ anchorA.push(endA);
5518
+ anchorB.push(endB);
5519
+ anchorT.push(endT);
5520
+ if (atEnd) {
5521
+ idx.push(leftIdx, lOut, rOut, leftIdx, rOut, rightIdx);
5879
5522
  } else {
5880
- i++;
5523
+ idx.push(leftIdx, rOut, lOut, leftIdx, rightIdx, rOut);
5881
5524
  }
5525
+ return;
5526
+ }
5527
+ if (cap === "round") {
5528
+ const pivotIdx = verts.length / 2;
5529
+ verts.push(cx, cy);
5530
+ anchorA.push(endA);
5531
+ anchorB.push(endB);
5532
+ anchorT.push(endT);
5533
+ const startAngle = Math.atan2(ny, nx);
5534
+ const sweep = atEnd ? -Math.PI : Math.PI;
5535
+ const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_CAP_STEP_RAD));
5536
+ const stepAngle = sweep / steps;
5537
+ let prevIdx = leftIdx;
5538
+ for (let i = 1; i < steps; i++) {
5539
+ const ang = startAngle + i * stepAngle;
5540
+ const fx = cx + Math.cos(ang) * half;
5541
+ const fy = cy + Math.sin(ang) * half;
5542
+ const newIdx = verts.length / 2;
5543
+ verts.push(fx, fy);
5544
+ anchorA.push(endA);
5545
+ anchorB.push(endB);
5546
+ anchorT.push(endT);
5547
+ if (atEnd) idx.push(prevIdx, newIdx, pivotIdx);
5548
+ else idx.push(prevIdx, pivotIdx, newIdx);
5549
+ prevIdx = newIdx;
5550
+ }
5551
+ if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);
5552
+ else idx.push(prevIdx, pivotIdx, rightIdx);
5882
5553
  }
5883
- return out;
5884
5554
  }
5885
- function readNumbers(s) {
5555
+ function splitForDash(pl, dash) {
5886
5556
  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);
5557
+ const plA = pl.anchorA ?? new Uint32Array(pl.points.length / 2);
5558
+ const plB = pl.anchorB ?? new Uint32Array(pl.points.length / 2);
5559
+ const plT = pl.anchorT ?? new Float32Array(pl.points.length / 2);
5560
+ let dashIdx = 0;
5561
+ let dashRemaining = dash[0];
5562
+ let onPhase = true;
5563
+ let curPts = onPhase ? [pl.points[0], pl.points[1]] : null;
5564
+ let curA = onPhase ? [plA[0]] : null;
5565
+ let curB = onPhase ? [plB[0]] : null;
5566
+ let curT = onPhase ? [plT[0]] : null;
5567
+ const flushAndAdvance = () => {
5568
+ if (curPts && curPts.length >= 4) {
5569
+ out.push({
5570
+ points: curPts,
5571
+ closed: false,
5572
+ anchorA: new Uint32Array(curA),
5573
+ anchorB: new Uint32Array(curB),
5574
+ anchorT: new Float32Array(curT)
5575
+ });
5576
+ }
5577
+ curPts = null;
5578
+ curA = null;
5579
+ curB = null;
5580
+ curT = null;
5581
+ dashIdx = (dashIdx + 1) % dash.length;
5582
+ dashRemaining = dash[dashIdx];
5583
+ onPhase = !onPhase;
5584
+ };
5585
+ let prevX = pl.points[0], prevY = pl.points[1];
5586
+ const ptCount = pl.points.length / 2;
5587
+ const segCount = pl.closed ? ptCount : ptCount - 1;
5588
+ for (let i = 0; i < segCount; i++) {
5589
+ const nextIdx = (i + 1) % ptCount;
5590
+ const cx = pl.points[nextIdx * 2], cy = pl.points[nextIdx * 2 + 1];
5591
+ const startA = plA[i], startB = plB[i], startT = plT[i];
5592
+ const endA = plA[nextIdx], endB = plB[nextIdx], endT = plT[nextIdx];
5593
+ const segFullDx = cx - prevX, segFullDy = cy - prevY;
5594
+ const segFullLen = Math.hypot(segFullDx, segFullDy);
5595
+ let segDx = segFullDx, segDy = segFullDy;
5596
+ let segLen = segFullLen;
5597
+ let traveled = 0;
5598
+ while (segLen > 1e-9) {
5599
+ if (segLen <= dashRemaining) {
5600
+ if (onPhase && curPts) {
5601
+ curPts.push(cx, cy);
5602
+ curA.push(endA);
5603
+ curB.push(endB);
5604
+ curT.push(endT);
5605
+ }
5606
+ dashRemaining -= segLen;
5607
+ prevX = cx;
5608
+ prevY = cy;
5609
+ traveled = segFullLen;
5610
+ segLen = 0;
5611
+ if (dashRemaining <= 1e-9) {
5612
+ flushAndAdvance();
5613
+ if (onPhase) {
5614
+ curPts = [prevX, prevY];
5615
+ curA = [endA];
5616
+ curB = [endB];
5617
+ curT = [endT];
5618
+ }
5619
+ }
5620
+ } else {
5621
+ const tConsume = dashRemaining / segLen;
5622
+ const ix = prevX + segDx * tConsume;
5623
+ const iy = prevY + segDy * tConsume;
5624
+ traveled += dashRemaining;
5625
+ const frac = traveled / segFullLen;
5626
+ let mA, mB, mT;
5627
+ if (startA === endA && startB === endB) {
5628
+ mA = startA;
5629
+ mB = startB;
5630
+ mT = startT + (endT - startT) * frac;
5631
+ } else {
5632
+ if (frac < 0.5) {
5633
+ mA = startA;
5634
+ mB = startB;
5635
+ mT = startT;
5636
+ } else {
5637
+ mA = endA;
5638
+ mB = endB;
5639
+ mT = endT;
5640
+ }
5641
+ }
5642
+ if (onPhase && curPts) {
5643
+ curPts.push(ix, iy);
5644
+ curA.push(mA);
5645
+ curB.push(mB);
5646
+ curT.push(mT);
5647
+ }
5648
+ prevX = ix;
5649
+ prevY = iy;
5650
+ segDx = cx - prevX;
5651
+ segDy = cy - prevY;
5652
+ segLen = Math.hypot(segDx, segDy);
5653
+ flushAndAdvance();
5654
+ if (onPhase) {
5655
+ curPts = [prevX, prevY];
5656
+ curA = [mA];
5657
+ curB = [mB];
5658
+ curT = [mT];
5659
+ }
5660
+ }
5661
+ }
5662
+ }
5663
+ if (curPts && curPts.length >= 4) {
5664
+ out.push({
5665
+ points: curPts,
5666
+ closed: false,
5667
+ anchorA: new Uint32Array(curA),
5668
+ anchorB: new Uint32Array(curB),
5669
+ anchorT: new Float32Array(curT)
5670
+ });
5892
5671
  }
5893
5672
  return out;
5894
5673
  }
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
5674
+ function makeSeg(ax, ay, bx, by, halfA, halfB) {
5675
+ const dx = bx - ax, dy = by - ay;
5676
+ const len = Math.hypot(dx, dy);
5677
+ if (len === 0) return null;
5678
+ return {
5679
+ ax,
5680
+ ay,
5681
+ bx,
5682
+ by,
5683
+ nxUnit: -dy / len,
5684
+ nyUnit: dx / len,
5685
+ halfA,
5686
+ halfB,
5687
+ len
5917
5688
  };
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;
5924
- }
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;
5932
- }
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);
5938
- }
5939
- }
5940
- return b.build();
5941
5689
  }
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;
5690
+ var DEFAULT_MITER_LIMIT = 10;
5691
+ var DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;
5692
+ function emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT) {
5693
+ const a = segs[j];
5694
+ const b = segs[(j + 1) % segs.length];
5695
+ const aBase = segBaseIdx[j];
5696
+ const bBase = segBaseIdx[(j + 1) % segs.length];
5697
+ const adx = a.bx - a.ax, ady = a.by - a.ay;
5698
+ const bdx = b.bx - b.ax, bdy = b.by - b.ay;
5699
+ const cross = adx * bdy - ady * bdx;
5700
+ if (cross === 0) return;
5701
+ const onPositive = cross > 0;
5702
+ const aOuterEnd = onPositive ? aBase + 3 : aBase + 2;
5703
+ const bOuterStart = onPositive ? bBase + 1 : bBase + 0;
5704
+ const nextSeg = (j + 1) % segs.length;
5705
+ const cornerSrc = segSrcIdx[nextSeg];
5706
+ const cornerA = plA[cornerSrc];
5707
+ const cornerB = plB[cornerSrc];
5708
+ const cornerT = plT[cornerSrc];
5709
+ const halfAtCorner = a.halfB;
5710
+ const taperA = Math.max(a.halfA, a.halfB) / Math.max(1e-9, Math.min(a.halfA, a.halfB));
5711
+ const taperB = Math.max(b.halfA, b.halfB) / Math.max(1e-9, Math.min(b.halfA, b.halfB));
5712
+ const forceBevelForVarying = taperA > varyingThreshold || taperB > varyingThreshold;
5713
+ if (join === "bevel" || forceBevelForVarying) {
5714
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
5715
+ return;
5716
+ }
5717
+ if (join === "miter") {
5718
+ const aOX = verts[aOuterEnd * 2], aOY = verts[aOuterEnd * 2 + 1];
5719
+ const bOX = verts[bOuterStart * 2], bOY = verts[bOuterStart * 2 + 1];
5720
+ const apex = lineLineIntersect(aOX, aOY, adx, ady, bOX, bOY, -bdx, -bdy);
5721
+ if (!apex) {
5722
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
6028
5723
  return;
6029
5724
  }
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;
5725
+ const miterLen = Math.hypot(apex[0] - a.bx, apex[1] - a.by);
5726
+ if (miterLen > miterLimit * halfAtCorner) {
5727
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
6043
5728
  return;
6044
5729
  }
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;
5730
+ const apexIdx = verts.length / 2;
5731
+ verts.push(apex[0], apex[1]);
5732
+ anchorA.push(cornerA);
5733
+ anchorB.push(cornerB);
5734
+ anchorT.push(cornerT);
5735
+ const jIdx = verts.length / 2;
5736
+ verts.push(a.bx, a.by);
5737
+ anchorA.push(cornerA);
5738
+ anchorB.push(cornerB);
5739
+ anchorT.push(cornerT);
5740
+ if (onPositive) {
5741
+ idx.push(aOuterEnd, apexIdx, bOuterStart);
5742
+ idx.push(aOuterEnd, bOuterStart, jIdx);
5743
+ } else {
5744
+ idx.push(aOuterEnd, bOuterStart, apexIdx);
5745
+ idx.push(aOuterEnd, jIdx, bOuterStart);
5746
+ }
5747
+ return;
6075
5748
  }
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;
5749
+ if (join === "round") {
5750
+ emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
5751
+ return;
6124
5752
  }
6125
5753
  }
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;
5754
+ var ROUND_STEP_RAD = 10 * Math.PI / 180;
5755
+ function emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
5756
+ const cx = a.bx, cy = a.by;
5757
+ const pivotIdx = verts.length / 2;
5758
+ verts.push(cx, cy);
5759
+ anchorA.push(cornerA);
5760
+ anchorB.push(cornerB);
5761
+ anchorT.push(cornerT);
5762
+ const startX = verts[aOuterEnd * 2] - cx;
5763
+ const startY = verts[aOuterEnd * 2 + 1] - cy;
5764
+ const endX = verts[bOuterStart * 2] - cx;
5765
+ const endY = verts[bOuterStart * 2 + 1] - cy;
5766
+ const r = Math.hypot(startX, startY);
5767
+ const startAngle = Math.atan2(startY, startX);
5768
+ const endAngle = Math.atan2(endY, endX);
5769
+ let sweep = endAngle - startAngle;
5770
+ if (onPositive && sweep < 0) sweep += 2 * Math.PI;
5771
+ else if (!onPositive && sweep > 0) sweep -= 2 * Math.PI;
5772
+ const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_STEP_RAD));
5773
+ const stepAngle = sweep / steps;
5774
+ let prevIdx = aOuterEnd;
5775
+ for (let i = 1; i < steps; i++) {
5776
+ const ang = startAngle + i * stepAngle;
5777
+ const fx = cx + Math.cos(ang) * r;
5778
+ const fy = cy + Math.sin(ang) * r;
5779
+ const newIdx = verts.length / 2;
5780
+ verts.push(fx, fy);
5781
+ anchorA.push(cornerA);
5782
+ anchorB.push(cornerB);
5783
+ anchorT.push(cornerT);
5784
+ if (onPositive) idx.push(prevIdx, pivotIdx, newIdx);
5785
+ else idx.push(prevIdx, newIdx, pivotIdx);
5786
+ prevIdx = newIdx;
5787
+ }
5788
+ if (onPositive) idx.push(prevIdx, pivotIdx, bOuterStart);
5789
+ else idx.push(prevIdx, bOuterStart, pivotIdx);
5790
+ }
5791
+ function emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
5792
+ const jIdx = verts.length / 2;
5793
+ verts.push(a.bx, a.by);
5794
+ anchorA.push(cornerA);
5795
+ anchorB.push(cornerB);
5796
+ anchorT.push(cornerT);
5797
+ if (onPositive) idx.push(aOuterEnd, jIdx, bOuterStart);
5798
+ else idx.push(aOuterEnd, bOuterStart, jIdx);
5799
+ }
5800
+ function lineLineIntersect(apx, apy, adx, ady, bpx, bpy, bdx, bdy) {
5801
+ const denom = adx * bdy - ady * bdx;
5802
+ if (Math.abs(denom) < 1e-9) return null;
5803
+ const t = ((bpx - apx) * bdy - (bpy - apy) * bdx) / denom;
5804
+ return [apx + t * adx, apy + t * ady];
6136
5805
  }
6137
5806
  var OUTLINE_STROKE_MESH_CACHE_LIMIT = 2048;
6138
5807
  var WIDTH_QUANTUM = 1 / 1024;
@@ -6162,6 +5831,36 @@ function outlineStrokeMesh(glyphKey, d, emWidth, stroke) {
6162
5831
  cache4.set(key, mesh);
6163
5832
  return mesh.indices.length === 0 ? null : mesh;
6164
5833
  }
5834
+ var STROKE_CONFIGS_PER_PATH = 8;
5835
+ var cache5 = /* @__PURE__ */ new WeakMap();
5836
+ function configKey(stroke, flattenTolerance) {
5837
+ return [
5838
+ stroke.width ?? 1,
5839
+ stroke.cap ?? "butt",
5840
+ stroke.join ?? "miter",
5841
+ stroke.miterLimit ?? "",
5842
+ stroke.align ?? "center",
5843
+ (stroke.dash ?? []).join(","),
5844
+ stroke.varyingWidthJoinThreshold ?? "",
5845
+ flattenTolerance ?? ""
5846
+ ].join("|");
5847
+ }
5848
+ function strokeMesh(path, stroke, flattenTolerance) {
5849
+ let byConfig = cache5.get(path);
5850
+ if (byConfig === void 0) {
5851
+ byConfig = /* @__PURE__ */ new Map();
5852
+ cache5.set(path, byConfig);
5853
+ }
5854
+ const key = configKey(stroke, flattenTolerance);
5855
+ const entry = byConfig.get(key);
5856
+ if (entry !== void 0 && entry.vertexWidths === stroke.vertexWidths) {
5857
+ return entry.mesh;
5858
+ }
5859
+ const mesh = tessellateStroke(path, stroke, { flattenTolerance });
5860
+ if (entry === void 0 && byConfig.size >= STROKE_CONFIGS_PER_PATH) byConfig.clear();
5861
+ byConfig.set(key, { mesh, vertexWidths: stroke.vertexWidths });
5862
+ return mesh;
5863
+ }
6165
5864
  var MAX_VERTICES_PER_BATCH = 32768;
6166
5865
  var FLOATS_PER_VERTEX = 6;
6167
5866
  var INITIAL_VERTICES = 256;
@@ -6563,44 +6262,40 @@ function drawGroup(ctx, cmd) {
6563
6262
  "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
6263
  );
6565
6264
  }
6566
- flushSolids(ctx);
6567
6265
  pushClip(ctx, cmd.clip, newDepth);
6568
6266
  ctx.clipDepth = newDepth;
6569
6267
  }
6570
6268
  for (const child of cmd.children) dispatch(ctx, child);
6571
6269
  if (cmd.clip) {
6572
- flushSolids(ctx);
6573
6270
  popClip(ctx, cmd.clip, ctx.clipDepth - 1);
6574
6271
  ctx.clipDepth -= 1;
6575
6272
  }
6576
6273
  ctx.state.pop();
6577
6274
  }
6578
6275
  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
- }
6276
+ if (cmd.fill) drawPathFill(ctx, cmd);
6602
6277
  if (cmd.stroke) drawPathStroke(ctx, cmd);
6603
6278
  }
6279
+ function drawPathFill(ctx, cmd) {
6280
+ const fill = cmd.fill;
6281
+ const isSolid = fill.fill === void 0 || fill.fill === "solid";
6282
+ const hasVColors = !!(cmd.vertexColors && cmd.vertexColors.length > 0);
6283
+ const solid = fill;
6284
+ if (isSolid && !hasVColors && cmd.path.kind === "rect") {
6285
+ pushRect(ctx, cmd.path, solid);
6286
+ return;
6287
+ }
6288
+ const mesh = fillMesh(ctx, cmd.path);
6289
+ if (tryStageSolid(ctx, mesh, isSolid && !hasVColors ? solid : void 0)) return;
6290
+ const handle = meshHandle(ctx, mesh);
6291
+ if (isSolid && hasVColors) {
6292
+ drawPathFillVColor(ctx, cmd, solid, handle);
6293
+ } else if (handle.requiresStencil) {
6294
+ drawPathFillStencil(ctx, fill, handle);
6295
+ } else {
6296
+ drawPathFillByKind(ctx, fill, handle);
6297
+ }
6298
+ }
6604
6299
  function isIdentityColorMatrix(cm) {
6605
6300
  return cm === IDENTITY_COLOR_MATRIX || sameValues(IDENTITY_COLOR_MATRIX, cm);
6606
6301
  }
@@ -6653,6 +6348,14 @@ function pushMesh(ctx, mesh, paint) {
6653
6348
  const [r, g, b, a] = stagedColor(ctx, staged, paint);
6654
6349
  ctx.solidBatch.pushMesh(mesh, ctx.state.transform, r, g, b, a);
6655
6350
  }
6351
+ function tryStageSolid(ctx, mesh, paint) {
6352
+ if (paint !== void 0 && canBatchMesh(mesh)) {
6353
+ pushMesh(ctx, mesh, paint);
6354
+ return true;
6355
+ }
6356
+ flushSolids(ctx);
6357
+ return false;
6358
+ }
6656
6359
  function flushSolids(ctx) {
6657
6360
  const batch = ctx.solidBatch;
6658
6361
  const staged = ctx.solidState;
@@ -6856,6 +6559,7 @@ function rasterizePathToStencil(ctx, path) {
6856
6559
  gl.bindVertexArray(null);
6857
6560
  }
6858
6561
  function pushClip(ctx, path, newDepth) {
6562
+ flushSolids(ctx);
6859
6563
  const gl = ctx.gl;
6860
6564
  const ancestors = ancestorMask(newDepth - 1);
6861
6565
  const newBit = 1 << newDepth;
@@ -6870,6 +6574,7 @@ function pushClip(ctx, path, newDepth) {
6870
6574
  gl.stencilMask(255);
6871
6575
  }
6872
6576
  function popClip(ctx, path, oldDepth) {
6577
+ flushSolids(ctx);
6873
6578
  const gl = ctx.gl;
6874
6579
  gl.enable(gl.STENCIL_TEST);
6875
6580
  const oldBit = 1 << oldDepth + 1;
@@ -6926,17 +6631,13 @@ function drawPathStroke(ctx, cmd) {
6926
6631
  function drawPathStrokeUnclipped(ctx, cmd) {
6927
6632
  const stroke = cmd.stroke;
6928
6633
  const solid = stroke.paint;
6929
- const mesh = tessellateStroke(cmd.path, stroke, { flattenTolerance: ctx.flattenTolerance });
6634
+ const mesh = strokeMesh(cmd.path, stroke, ctx.flattenTolerance);
6930
6635
  if (mesh.indices.length === 0) return;
6931
6636
  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);
6637
+ if (tryStageSolid(ctx, mesh, hasVColors ? void 0 : solid)) return;
6638
+ const handle = hasVColors ? ctx.meshCache.uploadTransient(mesh) : ctx.meshCache.uploadRecurring(mesh);
6938
6639
  const gl = ctx.gl;
6939
- if (stroke.vertexColors && stroke.vertexColors.length > 0) {
6640
+ if (hasVColors) {
6940
6641
  const prog = ctx.pathFillVColor;
6941
6642
  gl.useProgram(prog.handle);
6942
6643
  gl.bindVertexArray(handle.vao);
@@ -6967,16 +6668,30 @@ function drawPathStrokeUnclipped(ctx, cmd) {
6967
6668
  gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
6968
6669
  gl.bindVertexArray(null);
6969
6670
  }
6671
+ var DOUBLED_VERTEX_WIDTHS = /* @__PURE__ */ new WeakMap();
6672
+ function doubledVertexWidths(widths) {
6673
+ let doubled = DOUBLED_VERTEX_WIDTHS.get(widths);
6674
+ if (doubled === void 0) {
6675
+ doubled = widths.map((w) => w * 2);
6676
+ DOUBLED_VERTEX_WIDTHS.set(widths, doubled);
6677
+ }
6678
+ return doubled;
6679
+ }
6970
6680
  function drawPathStrokeStenciled(ctx, cmd, align) {
6971
6681
  const stroke = cmd.stroke;
6972
6682
  const solid = stroke.paint;
6973
- const widerStroke = { ...stroke, width: (stroke.width ?? 1) * 2, align: "center" };
6683
+ const widerStroke = {
6684
+ ...stroke,
6685
+ width: (stroke.width ?? 1) * 2,
6686
+ ...stroke.vertexWidths ? { vertexWidths: doubledVertexWidths(stroke.vertexWidths) } : {},
6687
+ align: "center"
6688
+ };
6689
+ const useVColor = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
6974
6690
  const fillHandle = fillMeshHandle(ctx, cmd.path);
6975
- const ribbonMesh = tessellateStroke(cmd.path, widerStroke, { flattenTolerance: ctx.flattenTolerance });
6691
+ const ribbonMesh = strokeMesh(cmd.path, widerStroke, ctx.flattenTolerance);
6976
6692
  if (ribbonMesh.indices.length === 0) return;
6977
- const ribbonHandle = ctx.meshCache.uploadTransient(ribbonMesh);
6693
+ const ribbonHandle = useVColor ? ctx.meshCache.uploadTransient(ribbonMesh) : ctx.meshCache.uploadRecurring(ribbonMesh);
6978
6694
  const gl = ctx.gl;
6979
- const useVColor = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
6980
6695
  const prog = useVColor ? ctx.pathFillVColor : ctx.pathFill;
6981
6696
  gl.useProgram(prog.handle);
6982
6697
  setProjAndModel(ctx, prog);
@@ -7012,11 +6727,11 @@ function drawPathStrokeStenciled(ctx, cmd, align) {
7012
6727
  }
7013
6728
  }
7014
6729
  gl.drawElements(gl.TRIANGLES, ribbonHandle.indexCount, gl.UNSIGNED_INT, 0);
7015
- if (colorVbo) gl.deleteBuffer(colorVbo);
7016
6730
  gl.stencilMask(1);
7017
6731
  gl.clear(gl.STENCIL_BUFFER_BIT);
7018
6732
  gl.disable(gl.STENCIL_TEST);
7019
6733
  gl.bindVertexArray(null);
6734
+ if (colorVbo) gl.deleteBuffer(colorVbo);
7020
6735
  }
7021
6736
  var OUTLINE_MIN_SCREEN_PX = 48;
7022
6737
  function modelScale(m) {
@@ -7065,9 +6780,9 @@ function drawTextOutlineGroup(ctx, group, dx, dy) {
7065
6780
  const mesh = outlineGroupMesh(group, dx, dy);
7066
6781
  if (mesh) drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
7067
6782
  if (!group.stroke) return;
7068
- const strokeMesh = outlineGroupStrokeMesh(group, dx, dy);
7069
- if (strokeMesh) {
7070
- drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(strokeMesh));
6783
+ const ribbon = outlineGroupStrokeMesh(group, dx, dy);
6784
+ if (ribbon) {
6785
+ drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(ribbon));
7071
6786
  }
7072
6787
  }
7073
6788
  function outlineGroupMesh(group, dx, dy) {
@@ -7393,16 +7108,28 @@ var registry3 = /* @__PURE__ */ new Map();
7393
7108
  function getProgramSource(id) {
7394
7109
  return registry3.get(id) ?? null;
7395
7110
  }
7111
+ var UNIFORM_QUALIFIER = /^(?:lowp|mediump|highp|precise|invariant|centroid|flat|smooth|noperspective)$/;
7396
7112
  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;
7113
+ const re = /\buniform\s+([^;{]+);/g;
7399
7114
  const names = [];
7400
7115
  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}]`);
7116
+ while ((m = re.exec(glsl)) !== null) {
7117
+ const words = m[1].trim().split(/\s+/);
7118
+ let i = 0;
7119
+ while (i < words.length && UNIFORM_QUALIFIER.test(words[i])) i++;
7120
+ i++;
7121
+ const declarators = words.slice(i).join(" ");
7122
+ if (!declarators) continue;
7123
+ for (const raw of declarators.split(",")) {
7124
+ const d = raw.trim();
7125
+ const arr = /^(\w+)\s*\[\s*(\d+)\s*\]$/.exec(d);
7126
+ if (arr) {
7127
+ const size = parseInt(arr[2], 10);
7128
+ for (let k = 0; k < size; k++) names.push(`${arr[1]}[${k}]`);
7129
+ } else if (/^\w+$/.test(d)) {
7130
+ names.push(d);
7131
+ }
7132
+ }
7406
7133
  }
7407
7134
  return names;
7408
7135
  }
@@ -8178,6 +7905,12 @@ function runLayoutPass(scratch, moveCtx) {
8178
7905
  if (layout2.contains) return layout2.contains(cPose, draggedCenter);
8179
7906
  return draggedCenter.x >= aabb.x && draggedCenter.x < aabb.x + aabb.width && draggedCenter.y >= aabb.y && draggedCenter.y < aabb.y + aabb.height;
8180
7907
  };
7908
+ const draggedArg = {
7909
+ id: draggedId,
7910
+ originPose: startWorld,
7911
+ pose: draggedWorld,
7912
+ sourceContainerId
7913
+ };
8181
7914
  const consider = (id, zPath) => {
8182
7915
  if (id === draggedId) return;
8183
7916
  const layout2 = layoutDep.getLayout(id);
@@ -8187,6 +7920,7 @@ function runLayoutPass(scratch, moveCtx) {
8187
7920
  const worldPose = composeWorldPose(poseAdapter, id, pc.compose);
8188
7921
  const worldAABB = boundsOf2(worldPose);
8189
7922
  if (!testInside(worldPose, worldAABB, layout2)) return;
7923
+ if (layout2.acceptsDrop && !layout2.acceptsDrop({ id, bounds: worldAABB }, draggedArg)) return;
8190
7924
  candidates.push({
8191
7925
  id,
8192
7926
  bounds: worldAABB,
@@ -8235,20 +7969,16 @@ function runLayoutPass(scratch, moveCtx) {
8235
7969
  id: cid,
8236
7970
  pose: composeWorldPose(poseAdapter, cid, pc.compose)
8237
7971
  }));
8238
- const draggedArg = {
8239
- id: draggedId,
8240
- originPose: startWorld,
8241
- pose: draggedWorld,
8242
- sourceContainerId
8243
- };
8244
7972
  const targets = layout.getDropTargets(container, children, draggedArg);
8245
7973
  const target = layout.snap.pickTarget(targets, { x: moveCtx.drag.current.x, y: moveCtx.drag.current.y });
8246
7974
  if (!target) return;
7975
+ const reflowIds = /* @__PURE__ */ new Set();
8247
7976
  for (const [cid, pose] of layout.reflowPoses(container, children, draggedArg, target)) {
8248
7977
  if (asNodeId(cid) === draggedId) continue;
8249
7978
  const parent = scene.get(asNodeId(cid))?.parent ?? null;
8250
7979
  const local = rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose);
8251
7980
  scratch.previews.set(asNodeId(cid), local);
7981
+ reflowIds.add(asNodeId(cid));
8252
7982
  }
8253
7983
  const sourceReflow = /* @__PURE__ */ new Map();
8254
7984
  if (sourceContainerId && sourceContainerId !== dest.id) {
@@ -8271,10 +8001,11 @@ function runLayoutPass(scratch, moveCtx) {
8271
8001
  sourceReflow.set(cid, pose);
8272
8002
  const parent = scene.get(asNodeId(cid))?.parent ?? null;
8273
8003
  scratch.previews.set(asNodeId(cid), rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose));
8004
+ reflowIds.add(asNodeId(cid));
8274
8005
  }
8275
8006
  }
8276
8007
  }
8277
- scratch.layoutPass = { layout, container, children, target, sourceReflow };
8008
+ scratch.layoutPass = { layout, container, children, target, sourceReflow, reflowIds };
8278
8009
  }
8279
8010
  function scenePoseAdapter(scene) {
8280
8011
  return {
@@ -8582,7 +8313,8 @@ var moveAction = {
8582
8313
  scratch.previews.clear();
8583
8314
  },
8584
8315
  previewIds: () => scratch.previews.keys(),
8585
- previewPose: (id) => scratch.previews.get(id) ?? null
8316
+ previewPose: (id) => scratch.previews.get(id) ?? null,
8317
+ previewOpaqueIds: () => scratch.layoutPass?.reflowIds ?? null
8586
8318
  };
8587
8319
  }
8588
8320
  },
@@ -9318,7 +9050,8 @@ var KIT_INSERT_KINDS = /* @__PURE__ */ new Set([
9318
9050
  "line",
9319
9051
  "polygon",
9320
9052
  "star",
9321
- "pencil"
9053
+ "pencil",
9054
+ "image"
9322
9055
  ]);
9323
9056
  var liveInsertScratch = null;
9324
9057
  function effectiveOriginMode(paramsOrigin, altHeld) {
@@ -10366,7 +10099,7 @@ var pinchZoomAction = {
10366
10099
  const multiTouch = ctx.multiTouch;
10367
10100
  if (!multiTouch) return {};
10368
10101
  const startSpread = multiTouch.spread > 0 ? multiTouch.spread : 1;
10369
- const scratch = { view, startSpread };
10102
+ const scratch = { view, startSpread, centroid: multiTouch.centroid };
10370
10103
  return {
10371
10104
  kind: "pinch",
10372
10105
  onMove(moveCtx) {
@@ -10376,8 +10109,15 @@ var pinchZoomAction = {
10376
10109
  if (currentSpread <= 0 || scratch.startSpread <= 0) return;
10377
10110
  const factor = currentSpread / scratch.startSpread;
10378
10111
  scratch.startSpread = currentSpread;
10112
+ const prev = scratch.centroid;
10113
+ scratch.centroid = centroid;
10379
10114
  const current = scratch.view.get();
10380
- scratch.view.set(zoomAt(current, centroid, factor));
10115
+ const zoomed = zoomAt(current, prev, factor);
10116
+ scratch.view.set({
10117
+ scale: zoomed.scale,
10118
+ x: zoomed.x - (centroid.x - prev.x) / zoomed.scale.x,
10119
+ y: zoomed.y - (centroid.y - prev.y) / zoomed.scale.y
10120
+ });
10381
10121
  }
10382
10122
  // onEnd: nothing to do — zoom was applied each frame.
10383
10123
  };
@@ -10837,10 +10577,12 @@ function normalizeNegativeExtent(pose) {
10837
10577
  if (!mutated) return pose;
10838
10578
  return { ...pose, x: nx, y: ny, width: nw, height: nh };
10839
10579
  }
10840
- function flipSelection(selection, scene, axis, applyOps, geometryProjection) {
10580
+ function flipSelection(selection, scene, axis, pivot, applyOps, geometryProjection) {
10841
10581
  const ids = selection.get();
10842
10582
  if (ids.length === 0) return;
10843
10583
  const geom = AUTO_POSE_DESCRIPTOR;
10584
+ const nodes = ids.map((id) => scene.get(id)).filter((n) => n != null);
10585
+ const unionPivot = pivot === "union" ? unionBounds(nodes.map((n) => geom.getBounds(n.pose))) : null;
10844
10586
  const ops = [];
10845
10587
  for (const id of ids) {
10846
10588
  const node = scene.get(id);
@@ -10848,10 +10590,10 @@ function flipSelection(selection, scene, axis, applyOps, geometryProjection) {
10848
10590
  ops.push(createTransformOp({
10849
10591
  id,
10850
10592
  from: node.pose,
10851
- to: flipPoseViaDescriptor(node.pose, axis, geom),
10593
+ to: unionPivot ? flipPoseAboutBounds(node.pose, axis, geom, unionPivot) : flipPoseViaDescriptor(node.pose, axis, geom),
10852
10594
  label: "Flip"
10853
10595
  }));
10854
- const g = geom.getBounds(node.pose);
10596
+ const g = unionPivot ?? geom.getBounds(node.pose);
10855
10597
  const cx = g.x + g.width / 2;
10856
10598
  const cy = g.y + g.height / 2;
10857
10599
  const m = axis === "x" ? [-1, 0, 0, 1, 2 * cx, 0] : [1, 0, 0, -1, 0, 2 * cy];
@@ -10880,12 +10622,13 @@ var flipAction = {
10880
10622
  timing: "immediate",
10881
10623
  run: (deps, params) => {
10882
10624
  const axis = params?.axis ?? "x";
10625
+ const pivot = params?.pivot ?? "each";
10883
10626
  const selection = deps.selection;
10884
10627
  const scene = deps.scene;
10885
10628
  const applyOps = deps.applyOps;
10886
10629
  const geometryProjection = deps.geometryProjection;
10887
10630
  if (!selection || !scene) return;
10888
- flipSelection(selection, scene, axis, applyOps, geometryProjection);
10631
+ flipSelection(selection, scene, axis, pivot, applyOps, geometryProjection);
10889
10632
  }
10890
10633
  },
10891
10634
  enabled: requiresSelection
@@ -12743,11 +12486,18 @@ var DEFAULT_DEBUG_THEME = {
12743
12486
  fpsText: "#e0e0e0",
12744
12487
  fpsTextBg: "rgba(0, 0, 0, 0.6)"
12745
12488
  };
12489
+ var DEFAULT_DEBUG_STROKES = {
12490
+ hitbox: { width: 1, dash: [2, 2] },
12491
+ bounds: { width: 1 },
12492
+ handle: { width: 1 },
12493
+ snap: { width: 1 }
12494
+ };
12746
12495
  function createDebugOverlayLayer({
12747
12496
  sink,
12748
12497
  config
12749
12498
  }) {
12750
12499
  const theme = { ...DEFAULT_DEBUG_THEME, ...config.theme ?? {} };
12500
+ const strokes = { ...DEFAULT_DEBUG_STROKES, ...config.strokes ?? {} };
12751
12501
  const fpsHistory = [];
12752
12502
  const FPS_WINDOW = 60;
12753
12503
  return {
@@ -12759,11 +12509,11 @@ function createDebugOverlayLayer({
12759
12509
  const s = sink.snapshot();
12760
12510
  const t = viewToTransform(view);
12761
12511
  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);
12512
+ if (config.hitboxes) emitHitboxes(out, s, view, t, theme, strokes);
12513
+ if (config.bounds) emitBounds(out, s, view, t, theme, strokes);
12514
+ if (config.handles) emitHandles(out, s, t, theme, strokes);
12765
12515
  if (config.origins) emitOrigins(out, s, t, theme);
12766
- if (config.snap) emitSnap(out, s, t, theme);
12516
+ if (config.snap) emitSnap(out, s, t, theme, strokes);
12767
12517
  if (config.ids) emitIds(out, s, t, theme);
12768
12518
  if (config.layers) emitLayersPanel(out, s, dims, theme);
12769
12519
  if (config.fps) {
@@ -12796,9 +12546,16 @@ function approxCircleScreen(cx, cy, r, segments = 24) {
12796
12546
  function rectPath2(x, y, w, h) {
12797
12547
  return { kind: "rect", x, y, width: w, height: h };
12798
12548
  }
12799
- function emitHitboxes(out, s, view, t, theme) {
12549
+ function strokeOf(s, color) {
12550
+ return {
12551
+ paint: { fill: "solid", color },
12552
+ width: s.width,
12553
+ ...s.dash && s.dash.length > 0 ? { dash: [...s.dash] } : {}
12554
+ };
12555
+ }
12556
+ function emitHitboxes(out, s, view, t, theme, strokes) {
12800
12557
  const fill = { fill: "solid", color: theme.hitboxFill };
12801
- const stroke = { paint: { fill: "solid", color: theme.hitboxStroke }, width: 1, dash: [2, 2] };
12558
+ const stroke = strokeOf(strokes.hitbox, theme.hitboxStroke);
12802
12559
  for (const h of s.hitboxes) {
12803
12560
  if (h.shape.kind === "rect") {
12804
12561
  const [sx, sy] = worldToScreen(h.shape.x, h.shape.y, t);
@@ -12812,8 +12569,8 @@ function emitHitboxes(out, s, view, t, theme) {
12812
12569
  }
12813
12570
  }
12814
12571
  }
12815
- function emitBounds(out, s, view, t, theme) {
12816
- const stroke = { paint: { fill: "solid", color: theme.bounds }, width: 1 };
12572
+ function emitBounds(out, s, view, t, theme, strokes) {
12573
+ const stroke = strokeOf(strokes.bounds, theme.bounds);
12817
12574
  for (const b of s.bounds) {
12818
12575
  const [sx, sy] = worldToScreen(b.bounds.x, b.bounds.y, t);
12819
12576
  const sw = b.bounds.width * view.scale.x;
@@ -12821,8 +12578,8 @@ function emitBounds(out, s, view, t, theme) {
12821
12578
  out.push({ kind: "path", path: rectPath2(sx, sy, sw, sh), stroke });
12822
12579
  }
12823
12580
  }
12824
- function emitHandles(out, s, t, theme) {
12825
- const stroke = { paint: { fill: "solid", color: theme.handle }, width: 1 };
12581
+ function emitHandles(out, s, t, theme, strokes) {
12582
+ const stroke = strokeOf(strokes.handle, theme.handle);
12826
12583
  for (const h of s.handles) {
12827
12584
  const [cx, cy] = worldToScreen(h.position.x, h.position.y, t);
12828
12585
  const horiz = {
@@ -12848,14 +12605,14 @@ function emitOrigins(out, s, t, theme) {
12848
12605
  out.push({ kind: "path", path: approxCircleScreen(cx, cy, 3), fill });
12849
12606
  }
12850
12607
  }
12851
- function emitSnap(out, s, t, theme) {
12608
+ function emitSnap(out, s, t, theme, strokes) {
12852
12609
  const color = theme.snap;
12853
12610
  for (const c of s.snap) {
12854
12611
  const [cx, cy] = worldToScreen(c.point.x, c.point.y, t);
12855
12612
  const cmd = {
12856
12613
  kind: "path",
12857
12614
  path: approxCircleScreen(cx, cy, 4),
12858
- ...c.accepted ? { fill: { fill: "solid", color } } : { stroke: { paint: { fill: "solid", color }, width: 1 } }
12615
+ ...c.accepted ? { fill: { fill: "solid", color } } : { stroke: strokeOf(strokes.snap, color) }
12859
12616
  };
12860
12617
  out.push(cmd);
12861
12618
  }
@@ -13227,11 +12984,22 @@ function reportDeadClaim(owner, warn) {
13227
12984
  `[weasel] exclusive claim by "${key}" matched no binding: no \`affordance:\` or \`kindOf\` target resolved against it, so the press was dropped.`
13228
12985
  );
13229
12986
  }
12987
+ function phaseRank(spec) {
12988
+ if (spec === void 0) return 0;
12989
+ const atoms = typeof spec === "string" ? [{ channel: "&", phase: spec }] : spec;
12990
+ if (atoms.length === 0) return 0;
12991
+ let min = 2;
12992
+ for (const a of atoms) {
12993
+ const rank = (a.channel === "*" ? 0 : 1) + (a.phase === "*" ? 0 : 1);
12994
+ if (rank < min) min = rank;
12995
+ }
12996
+ return min;
12997
+ }
13230
12998
  function specificity(spec) {
13231
12999
  const t = targetRank(specTargetOf(spec));
13232
13000
  const mods = "mods" in spec ? spec.mods : void 0;
13233
13001
  const m = modsCount(mods);
13234
- const p = "phase" in spec && spec.phase !== void 0 ? 1 : 0;
13002
+ const p = phaseRank("phase" in spec ? spec.phase : void 0);
13235
13003
  const typed = (spec.kind === "drop" || spec.kind === "paste") && spec.types !== void 0 && spec.types.length > 0 ? 2 : 1;
13236
13004
  return [t, m, p, typed];
13237
13005
  }
@@ -14831,8 +14599,7 @@ function useGestureDispatcher(opts) {
14831
14599
  if (!cd) return;
14832
14600
  const items = itemsFromClipboardData(cd);
14833
14601
  if (items.length === 0) return;
14834
- e.preventDefault();
14835
- dispatch2({
14602
+ const result = dispatch2({
14836
14603
  kind: "paste",
14837
14604
  items,
14838
14605
  // ClipboardEvent carries no modifier state.
@@ -14841,6 +14608,7 @@ function useGestureDispatcher(opts) {
14841
14608
  metaKey: false,
14842
14609
  shiftKey: false
14843
14610
  });
14611
+ if (result === "handled") e.preventDefault();
14844
14612
  };
14845
14613
  if (keyboard) {
14846
14614
  window.addEventListener("keydown", onKeyDown);
@@ -15083,6 +14851,50 @@ function usePinchGesture(canvasRef, onPinch, enabled = true) {
15083
14851
  };
15084
14852
  }, [canvasRef, enabled]);
15085
14853
  }
14854
+ function validateId(id, kind) {
14855
+ if (id.length === 0) {
14856
+ throw new Error(`weasel: ${kind} id may not be empty`);
14857
+ }
14858
+ if (RESERVED_ID_PREFIXES.has(id[0])) {
14859
+ throw new Error(
14860
+ `weasel: ${kind} id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
14861
+ );
14862
+ }
14863
+ if (RESERVED_ID_NAMES.has(id)) {
14864
+ throw new Error(
14865
+ `weasel: ${kind} id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
14866
+ );
14867
+ }
14868
+ }
14869
+ function defineTool(def) {
14870
+ validateId(def.id, "tool");
14871
+ const resolveCursor = (ctx) => {
14872
+ if (def.cursor == null) return "";
14873
+ return typeof def.cursor === "function" ? def.cursor(ctx) : def.cursor;
14874
+ };
14875
+ return {
14876
+ id: def.id,
14877
+ eligibility: {
14878
+ focus: true,
14879
+ capabilities: def.capabilities,
14880
+ ...def.hotkey ? { offhand: def.hotkey } : {}
14881
+ },
14882
+ actions: def.actions,
14883
+ def,
14884
+ presentation: def.presentation,
14885
+ keybinding: def.keybinding,
14886
+ onActivate: def.onActivate,
14887
+ onDeactivate: def.onDeactivate,
14888
+ initScratch: def.initScratch ?? (() => null),
14889
+ cursor: resolveCursor,
14890
+ bindings: def.bindings,
14891
+ overlay: def.overlay,
14892
+ overlayPosition: def.overlayPosition
14893
+ };
14894
+ }
14895
+ function defineViewportTool(def) {
14896
+ return defineTool(def);
14897
+ }
15086
14898
  var TOOL_OFFHAND_ID = "tool.offhand";
15087
14899
  function offhandKeyFor(trigger) {
15088
14900
  switch (trigger) {
@@ -15173,17 +14985,19 @@ function useContributions(opts) {
15173
14985
  const { ordered, state } = snapshot();
15174
14986
  return scopeBindings(ordered, state);
15175
14987
  }, [snapshot]);
15176
- const overlays = useCallback(() => {
14988
+ const overlays = useCallback((position = "top") => {
15177
14989
  const { ordered, state } = snapshot();
15178
14990
  const active = [];
15179
14991
  const hotkey = [];
15180
14992
  const ambient = [];
15181
14993
  for (const entry of ordered) {
15182
14994
  if (!entry.overlay) continue;
14995
+ if ((entry.overlayPosition ?? "top") !== position) continue;
14996
+ const layers = Array.isArray(entry.overlay) ? entry.overlay : [entry.overlay];
15183
14997
  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);
14998
+ if (scope === "active") active.push(...layers);
14999
+ else if (scope === "hotkey") hotkey.push(...layers);
15000
+ else if (scope === "ambient") ambient.push(...layers);
15187
15001
  }
15188
15002
  return [...active, ...hotkey, ...ambient];
15189
15003
  }, [snapshot]);
@@ -15811,7 +15625,7 @@ function isBoundsGradient(fill) {
15811
15625
  }
15812
15626
  return fill.units === "bounds";
15813
15627
  }
15814
- var cache5 = /* @__PURE__ */ new Map();
15628
+ var cache6 = /* @__PURE__ */ new Map();
15815
15629
  function specKey(spec) {
15816
15630
  const keys = Object.keys(spec).sort();
15817
15631
  const bag = spec;
@@ -15819,10 +15633,10 @@ function specKey(spec) {
15819
15633
  }
15820
15634
  function resolvePatternSpec(spec) {
15821
15635
  const key = specKey(spec);
15822
- const hit = cache5.get(key);
15636
+ const hit = cache6.get(key);
15823
15637
  if (hit !== void 0) return hit;
15824
15638
  const built = buildTile(spec);
15825
- cache5.set(key, built);
15639
+ cache6.set(key, built);
15826
15640
  return built;
15827
15641
  }
15828
15642
  function buildTile(spec) {
@@ -15856,7 +15670,7 @@ function resolveFillPattern(fill) {
15856
15670
  const handle = resolvePatternSpec(fill.pattern);
15857
15671
  return handle ? { ...fill, pattern: handle } : null;
15858
15672
  }
15859
- var cache6 = /* @__PURE__ */ new Map();
15673
+ var cache7 = /* @__PURE__ */ new Map();
15860
15674
  var subscribers2 = /* @__PURE__ */ new Set();
15861
15675
  var defaultLoader = async (src) => {
15862
15676
  if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
@@ -15874,28 +15688,28 @@ function notify() {
15874
15688
  }
15875
15689
  function startLoad(src) {
15876
15690
  const entry = { status: "loading" };
15877
- cache6.set(src, entry);
15691
+ cache7.set(src, entry);
15878
15692
  loader(src).then(
15879
15693
  (bitmap) => {
15880
- if (cache6.get(src) !== entry) return;
15881
- cache6.set(src, { status: "ready", bitmap });
15694
+ if (cache7.get(src) !== entry) return;
15695
+ cache7.set(src, { status: "ready", bitmap });
15882
15696
  notify();
15883
15697
  },
15884
15698
  () => {
15885
- if (cache6.get(src) !== entry) return;
15886
- cache6.set(src, { status: "error" });
15699
+ if (cache7.get(src) !== entry) return;
15700
+ cache7.set(src, { status: "error" });
15887
15701
  notify();
15888
15702
  }
15889
15703
  );
15890
15704
  }
15891
15705
  function getImageBitmap(src) {
15892
- const entry = cache6.get(src);
15706
+ const entry = cache7.get(src);
15893
15707
  if (entry) return entry.bitmap;
15894
15708
  startLoad(src);
15895
15709
  return void 0;
15896
15710
  }
15897
15711
  function imageStatus(src) {
15898
- return cache6.get(src)?.status ?? "idle";
15712
+ return cache7.get(src)?.status ?? "idle";
15899
15713
  }
15900
15714
  function subscribeImageReady(cb) {
15901
15715
  subscribers2.add(cb);
@@ -16253,6 +16067,7 @@ registerBuiltInShapePainters();
16253
16067
  function pickTopMostHit(ids, adapter) {
16254
16068
  if (ids.length === 0) return null;
16255
16069
  if (ids.length === 1) return ids[0];
16070
+ let candidates = ids;
16256
16071
  const getParent = adapter?.getParent;
16257
16072
  if (typeof getParent === "function") {
16258
16073
  const set = new Set(ids);
@@ -16267,16 +16082,40 @@ function pickTopMostHit(ids, adapter) {
16267
16082
  }
16268
16083
  if (dropped.size > 0) {
16269
16084
  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];
16085
+ if (filtered.length === 1) return filtered[0];
16086
+ if (filtered.length > 1) candidates = filtered;
16087
+ }
16088
+ }
16089
+ return pickHighestZ(candidates, adapter);
16090
+ }
16091
+ function pickHighestZ(ids, adapter) {
16092
+ const compareZ = adapter?.compareZ;
16093
+ if (typeof compareZ === "function") {
16094
+ let best = ids[0];
16095
+ for (let i = 1; i < ids.length; i++) {
16096
+ if (compareZ(ids[i], best) >= 0) best = ids[i];
16097
+ }
16098
+ return best;
16099
+ }
16100
+ const getZIndex = adapter?.getZIndex;
16101
+ if (typeof getZIndex === "function") {
16102
+ let best = ids[0];
16103
+ let bestZ = zOf(getZIndex, best);
16104
+ for (let i = 1; i < ids.length; i++) {
16105
+ const z = zOf(getZIndex, ids[i]);
16106
+ if (z >= bestZ) {
16107
+ best = ids[i];
16108
+ bestZ = z;
16275
16109
  }
16276
16110
  }
16111
+ return best;
16277
16112
  }
16278
16113
  return ids[ids.length - 1];
16279
16114
  }
16115
+ function zOf(getZIndex, id) {
16116
+ const z = getZIndex(id);
16117
+ return typeof z === "number" && Number.isFinite(z) ? z : -Infinity;
16118
+ }
16280
16119
  var MULTI_RESIZE_TARGET_ID = "__weasel:multi-selection";
16281
16120
  function useSelectTool(adapter, options) {
16282
16121
  const poseBoundsFn = options.poseBounds ?? ((p) => p);
@@ -16673,9 +16512,19 @@ function composeAffordanceLayer(id, label, affordances) {
16673
16512
  },
16674
16513
  // The walk itself lives in `hitAffordanceRegions` so this layer and
16675
16514
  // `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
16515
+ // drift. This wrapper lifts the region's declared cursor and claim onto
16516
+ // the binding, the way `buildAffordanceAt` lifts them onto `AffordanceHit`.
16517
+ hitTest: (wx, wy, state, view, _dims, isVisible) => {
16518
+ const hit = hitAffordanceRegions(affordances, wx, wy, state, view, isVisible);
16519
+ if (!hit) return null;
16520
+ const { cursor, strength, claimedKinds } = hit.region;
16521
+ return {
16522
+ ...hit.binding,
16523
+ ...cursor !== void 0 ? { cursor } : {},
16524
+ ...strength !== void 0 ? { strength } : {},
16525
+ ...claimedKinds !== void 0 ? { claimedKinds } : {}
16526
+ };
16527
+ }
16679
16528
  };
16680
16529
  }
16681
16530
  function asChromeState(data) {
@@ -17835,6 +17684,19 @@ function composeOrderedLayers(layersMap, standardLayers, sceneLayers) {
17835
17684
  }
17836
17685
  return out;
17837
17686
  }
17687
+ function placeToolOverlays(ordered, selectionOverlay, overlays) {
17688
+ const before = overlays("before-selection");
17689
+ const after = overlays("after-selection");
17690
+ const at = selectionOverlay ? ordered.indexOf(selectionOverlay) : -1;
17691
+ if (at >= 0) {
17692
+ ordered.splice(at + 1, 0, ...after);
17693
+ ordered.splice(at, 0, ...before);
17694
+ } else {
17695
+ ordered.push(...before, ...after);
17696
+ }
17697
+ ordered.push(...overlays("top"));
17698
+ return ordered;
17699
+ }
17838
17700
  function* toolsInPriorityOrder(tools) {
17839
17701
  const seen = /* @__PURE__ */ new Set();
17840
17702
  const hotkey = tools.hotkeyEngaged ? tools.registry[tools.hotkeyEngaged] : void 0;
@@ -19263,7 +19125,7 @@ function CanvasInner(props, ref) {
19263
19125
  const out = composeOrderedLayers(effectiveLayersMap, standardLayers, sceneLayers);
19264
19126
  if (decorationLayer) out.push(decorationLayer);
19265
19127
  if (tools) {
19266
- out.push(...tools.getActiveOverlays());
19128
+ placeToolOverlays(out, standardLayers.selectionOverlay, tools.getActiveOverlays);
19267
19129
  }
19268
19130
  return out;
19269
19131
  }, [layersMap, adapter, selectedIds, effectiveBoundsOf, multiActive2, debugSink, tools, backgroundLayer, decorationLayer]);
@@ -20893,6 +20755,7 @@ function useSceneSelectTool(args) {
20893
20755
  boundsOf: wiredBoundsOf
20894
20756
  };
20895
20757
  }
20758
+ var GHOST_ALPHA = 0.85;
20896
20759
  function usePreviewGhostLayer(args) {
20897
20760
  const { scene, tools, sceneSlot, dispatcher } = args;
20898
20761
  const sceneRef = useRef(scene);
@@ -20979,15 +20842,26 @@ function usePreviewGhostLayer(args) {
20979
20842
  }
20980
20843
  return [group];
20981
20844
  };
20845
+ const opaqueSet = /* @__PURE__ */ new Set();
20846
+ for (const source of sources) {
20847
+ const ids = source.previewOpaqueIds?.();
20848
+ if (!ids) continue;
20849
+ for (const id of ids) opaqueSet.add(id);
20850
+ }
20982
20851
  const children = [];
20852
+ const opaque = [];
20983
20853
  for (const id of idSet) {
20984
20854
  const node = sc.get(asNodeId(id));
20985
20855
  const parent = node?.parent;
20986
20856
  if (parent != null && idSet.has(parent)) continue;
20987
- for (const cmd of buildSubtree(id)) children.push(cmd);
20857
+ const sink = opaqueSet.has(id) ? opaque : children;
20858
+ for (const cmd of buildSubtree(id)) sink.push(cmd);
20988
20859
  }
20989
- if (children.length === 0) return [];
20990
- return [{ kind: "group", alpha: 0.85, children }];
20860
+ if (children.length === 0 && opaque.length === 0) return [];
20861
+ const out = [];
20862
+ if (opaque.length > 0) out.push({ kind: "group", children: opaque });
20863
+ if (children.length > 0) out.push({ kind: "group", alpha: GHOST_ALPHA, children });
20864
+ return out;
20991
20865
  }
20992
20866
  }), []);
20993
20867
  }
@@ -20997,6 +20871,7 @@ var DEFAULT_STYLE = {
20997
20871
  dash: [3, 3],
20998
20872
  lineWidth: 1
20999
20873
  };
20874
+ var PREVIEW_CONTENT_OPACITY = 0.85;
21000
20875
  function useDispatcherOverlayLayer(args) {
21001
20876
  const { dispatcher, style } = args;
21002
20877
  const dispatcherRef = useRef(dispatcher);
@@ -21059,6 +20934,26 @@ function useDispatcherOverlayLayer(args) {
21059
20934
  pathCmd = rectPath(sx, sy, b.width * view.scale.x, b.height * view.scale.y);
21060
20935
  break;
21061
20936
  }
20937
+ case "image": {
20938
+ const e = ov.extras;
20939
+ const [sx, sy] = worldToScreen(b.x, b.y, t);
20940
+ const sw = b.width * view.scale.x;
20941
+ const sh = b.height * view.scale.y;
20942
+ const bmp = e.preview === "outline" || !e.src ? void 0 : getImageBitmap(e.src);
20943
+ if (bmp) {
20944
+ out.push({
20945
+ kind: "image",
20946
+ image: bmp,
20947
+ x: sx,
20948
+ y: sy,
20949
+ w: sw,
20950
+ h: sh,
20951
+ opacity: PREVIEW_CONTENT_OPACITY
20952
+ });
20953
+ }
20954
+ pathCmd = rectPath(sx, sy, sw, sh);
20955
+ break;
20956
+ }
21062
20957
  case "ellipse": {
21063
20958
  const [sx, sy] = worldToScreen(b.x, b.y, t);
21064
20959
  pathCmd = ellipsePath({
@@ -21117,7 +21012,7 @@ function useDispatcherOverlayLayer(args) {
21117
21012
  const cmd = {
21118
21013
  kind: "path",
21119
21014
  path: pathCmd,
21120
- fill: ov.shape === "line" || ov.shape === "pencil" ? void 0 : { color: cfg.fill },
21015
+ fill: ov.shape === "line" || ov.shape === "pencil" || ov.shape === "image" ? void 0 : { color: cfg.fill },
21121
21016
  stroke: { paint: { color: cfg.stroke }, width: cfg.lineWidth, dash: cfg.dash }
21122
21017
  };
21123
21018
  out.push(cmd);
@@ -21806,9 +21701,6 @@ function buildClassifyTarget(getSelection, pickBest, kindOfNode) {
21806
21701
  function w2s3(wx, wy, view) {
21807
21702
  return [(wx - view.x) * view.scale.x, (wy - view.y) * view.scale.y];
21808
21703
  }
21809
- function r2s(radiusWorld, view) {
21810
- return radiusWorld * meanScale(view.scale);
21811
- }
21812
21704
  var SLOP_FILL = "rgba(255, 80, 140, 0.18)";
21813
21705
  var SLOP_STROKE = "rgba(255, 80, 140, 0.55)";
21814
21706
  function createSlopsDebugLayer(opts) {
@@ -21819,7 +21711,7 @@ function createSlopsDebugLayer(opts) {
21819
21711
  draw: (_data, view) => {
21820
21712
  const out = [];
21821
21713
  const sel = opts.selectionRef.current?.current ?? [];
21822
- const r = r2s(HANDLE_HIT_RADIUS, view);
21714
+ const r = HANDLE_HIT_RADIUS;
21823
21715
  for (const id of sel) {
21824
21716
  const b = opts.boundsOf(id);
21825
21717
  if (!b) continue;
@@ -21856,6 +21748,7 @@ function createSlopsDebugLayer(opts) {
21856
21748
  }
21857
21749
  const editingId = opts.getEditingId();
21858
21750
  if (editingId) {
21751
+ const anchorR = ANCHOR_HIT_RADIUS;
21859
21752
  const pose = opts.getPose(editingId);
21860
21753
  if (pose && pose.kind === "polygon") {
21861
21754
  const anchors = enumerateAnchors(pose);
@@ -21863,7 +21756,7 @@ function createSlopsDebugLayer(opts) {
21863
21756
  const [sx, sy] = w2s3(a.x, a.y, view);
21864
21757
  out.push({
21865
21758
  kind: "path",
21866
- path: circlePath(sx, sy, r),
21759
+ path: circlePath(sx, sy, anchorR),
21867
21760
  fill: { fill: "solid", color: SLOP_FILL },
21868
21761
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
21869
21762
  });
@@ -21871,7 +21764,7 @@ function createSlopsDebugLayer(opts) {
21871
21764
  const [csx, csy] = w2s3(a.controlIn.x, a.controlIn.y, view);
21872
21765
  out.push({
21873
21766
  kind: "path",
21874
- path: circlePath(csx, csy, r),
21767
+ path: circlePath(csx, csy, anchorR),
21875
21768
  fill: { fill: "solid", color: SLOP_FILL },
21876
21769
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
21877
21770
  });
@@ -21880,7 +21773,7 @@ function createSlopsDebugLayer(opts) {
21880
21773
  const [csx, csy] = w2s3(a.controlOut.x, a.controlOut.y, view);
21881
21774
  out.push({
21882
21775
  kind: "path",
21883
- path: circlePath(csx, csy, r),
21776
+ path: circlePath(csx, csy, anchorR),
21884
21777
  fill: { fill: "solid", color: SLOP_FILL },
21885
21778
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
21886
21779
  });
@@ -21958,9 +21851,9 @@ function DepRegistryProviderIfRoot({ children }) {
21958
21851
  function hitTestArea(scene, bounds) {
21959
21852
  const { x, y, width: w, height: h } = bounds;
21960
21853
  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 });
21854
+ return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h }, true);
21962
21855
  }
21963
- function hitTestAreaPolygon(scene, area, areaBounds) {
21856
+ function hitTestAreaPolygon(scene, area, areaBounds, areaIsRect = false) {
21964
21857
  const ab = areaBounds ?? boundsOf(area);
21965
21858
  if (!ab) return [];
21966
21859
  const hits = [];
@@ -21977,13 +21870,22 @@ function hitTestAreaPolygon(scene, area, areaBounds) {
21977
21870
  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
21871
  continue;
21979
21872
  }
21980
- if (!silhouette) {
21873
+ 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
21874
  hits.push(node.id);
21982
21875
  continue;
21983
21876
  }
21984
- if (silhouetteOverlapsArea(pose.coords, area)) {
21985
- hits.push(node.id);
21877
+ if (silhouette) {
21878
+ if (silhouetteOverlapsArea(pose.coords, area)) {
21879
+ hits.push(node.id);
21880
+ }
21881
+ continue;
21882
+ }
21883
+ const drawn = findShapeSilhouette(node, pose);
21884
+ if (drawn?.kind === "polygon") {
21885
+ if (silhouetteOverlapsArea(drawn.coords, area)) hits.push(node.id);
21886
+ continue;
21986
21887
  }
21888
+ hits.push(node.id);
21987
21889
  }
21988
21890
  return hits;
21989
21891
  }
@@ -24573,5 +24475,5 @@ function oklchToOklab(L, C, h) {
24573
24475
  }
24574
24476
 
24575
24477
  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
24478
+ //# sourceMappingURL=chunk-3P56ZCCJ.js.map
24479
+ //# sourceMappingURL=chunk-3P56ZCCJ.js.map