@weasel-js/core 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.
Files changed (37) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/dist/{chunk-FVNWK7U3.js → chunk-PI56ORHE.js} +1593 -1316
  3. package/dist/chunk-PI56ORHE.js.map +1 -0
  4. package/dist/{chunk-DCIU3SRK.js → chunk-W2WB4352.js} +79 -151
  5. package/dist/chunk-W2WB4352.js.map +1 -0
  6. package/dist/clipboard.d.ts +1 -1
  7. package/dist/clipboard.js +0 -1
  8. package/dist/clone.d.ts +2 -2
  9. package/dist/clone.js +0 -1
  10. package/dist/{geometry-DVeZ2i-c.d.ts → geometry-CnISZ11w.d.ts} +1 -1
  11. package/dist/{grid-XcFQVS0f.d.ts → grid-CBiLBnyc.d.ts} +2 -2
  12. package/dist/index.d.ts +163 -43
  13. package/dist/index.js +2 -3
  14. package/dist/insert.d.ts +3 -3
  15. package/dist/insert.js +0 -1
  16. package/dist/insert.js.map +1 -1
  17. package/dist/move.d.ts +5 -5
  18. package/dist/move.js +0 -1
  19. package/dist/move.js.map +1 -1
  20. package/dist/{options-wAvCnOZd.d.ts → options-DoOvx50U.d.ts} +1 -1
  21. package/dist/patterns-builtin.js +0 -1
  22. package/dist/{pointSnapToGrid-BuDZWBrL.d.ts → pointSnapToGrid-Bj4r_lEQ.d.ts} +3 -3
  23. package/dist/{index-DPI8DV0A.d.ts → registry-BQL4oyTg.d.ts} +139 -348
  24. package/dist/renderer.d.ts +11 -0
  25. package/dist/renderer.js +2 -3
  26. package/dist/resize.d.ts +5 -5
  27. package/dist/resize.js +0 -1
  28. package/dist/routing.d.ts +104 -5
  29. package/dist/routing.js +1 -2
  30. package/dist/{types-B_-khFM0.d.ts → types-Ch0Goo-j.d.ts} +12 -0
  31. package/dist/{types-BhJJ2OCM.d.ts → types-Ct4n0Z38.d.ts} +1 -1
  32. package/dist/{types-BJ8_cyT7.d.ts → types-Dtxe7jTS.d.ts} +20 -1
  33. package/package.json +6 -6
  34. package/dist/chunk-DCIU3SRK.js.map +0 -1
  35. package/dist/chunk-FVNWK7U3.js.map +0 -1
  36. package/dist/chunk-PZ5AY32C.js +0 -9
  37. package/dist/chunk-PZ5AY32C.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { ActionDisabledReason, defineViewportTool, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsProvider, useDepRegistry, defineTool, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-DCIU3SRK.js';
1
+ import { ActionDisabledReason, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsProvider, useDepRegistry, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-W2WB4352.js';
2
2
  import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-UB6A6L77.js';
3
3
  import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-Z3KNFTTS.js';
4
4
  import { AUTO_POSE_DESCRIPTOR, RECT_POSE_DESCRIPTOR, resolveUnit, pathPoseDescriptor, polygonFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, strokeHitTest, withinPxRadius, boundsOfPath, PATH_M, PATH_L, PATH_Z, PATH_Q, PATH_C, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, PathBuilder, pointInPath, transformPath, translatePath, pxExtent, remapRotatedLeaf, withinPxBox, flattenCubicWithArcLen, flattenQuadraticWithArcLen, PATH_CMD_LENGTHS, pointToGridCell } from './chunk-6SHZHXXR.js';
@@ -13,13 +13,13 @@ import earcut from 'earcut';
13
13
  import { applyToPoint, rotateAboutPoint, boxToBox, pointInPolygon, segmentsCross } from '@weasel-js/geom';
14
14
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
15
  import polygonClipping from 'polygon-clipping';
16
- import { matchSpec, parseTargetSpec, mimeMatchesGlob } from '@weasel-js/gestures';
16
+ import { matchSpec, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, parseTargetSpec, mimeMatchesGlob } from '@weasel-js/gestures';
17
17
  import { NORMAL, IMPLICIT_TAGS } from '@weasel-js/modes';
18
18
  import { createHistory } from '@weasel-js/history';
19
19
  export * from '@weasel-js/history';
20
20
 
21
21
  // src/version.ts
22
- var VERSION = "1.0.1" ;
22
+ var VERSION = "1.0.2" ;
23
23
 
24
24
  // src/features/grid/roundToCell.ts
25
25
  function roundToCell(value, cellSize) {
@@ -399,6 +399,8 @@ var GLMeshCache = class {
399
399
  gl;
400
400
  aPositionLoc;
401
401
  map = /* @__PURE__ */ new WeakMap();
402
+ /** Meshes this context has drawn at least once; see `uploadRecurring`. */
403
+ seen = /* @__PURE__ */ new WeakSet();
402
404
  finalizer;
403
405
  pendingDeletes = [];
404
406
  /** Transient resources allocated this frame; freed at end of render(). */
@@ -423,6 +425,19 @@ var GLMeshCache = class {
423
425
  this.transientThisFrame.push({ vao: handle.vao, vbo, ibo });
424
426
  return handle;
425
427
  }
428
+ /**
429
+ * Upload a Mesh that may or may not recur across frames. Its first sight in
430
+ * *this* context takes `uploadTransient`; every later one takes the
431
+ * persistent handle. One transient upload to find out is cheaper than
432
+ * stranding a persistent VAO on a mesh that never returns, whose release
433
+ * would wait on GC. The transient upload does not populate the persistent
434
+ * map, so steady-state reuse begins on the third frame.
435
+ */
436
+ uploadRecurring(mesh) {
437
+ if (this.seen.has(mesh)) return this.handleFor(mesh);
438
+ this.seen.add(mesh);
439
+ return this.uploadTransient(mesh);
440
+ }
426
441
  /**
427
442
  * Free all transient resources allocated since the last call. Called by
428
443
  * the renderer at the end of each `render()`. Safe under context loss.
@@ -1532,1269 +1547,1269 @@ function getMesh(path) {
1532
1547
  cache.set(path, mesh);
1533
1548
  return mesh;
1534
1549
  }
1535
-
1536
- // src/features/paths/tessellate/polyline.ts
1537
- function extractPolylines(path, opts = {}) {
1538
- if (path.kind === "rect") return [extractRect(path)];
1539
- return extractPolygon(path, opts);
1540
- }
1541
- function extractRect(p) {
1542
- const { x, y, width: w, height: h } = p;
1543
- return {
1544
- points: [x, y, x + w, y, x + w, y + h, x, y + h],
1545
- closed: true,
1546
- anchorA: new Uint32Array([0, 1, 2, 3]),
1547
- anchorB: new Uint32Array([0, 1, 2, 3]),
1548
- anchorT: new Float32Array([0, 0, 0, 0])
1549
- };
1550
+ var UNDERLINE_OFFSET = 0.1;
1551
+ var STRIKETHROUGH_OFFSET = -0.3;
1552
+ var DECORATION_THICKNESS = 0.05;
1553
+ function fillKey(p) {
1554
+ if ("color" in p) return `s:${p.color}:${p.opacity ?? 1}`;
1555
+ return `nx:${Math.random()}`;
1550
1556
  }
1551
- function extractPolygon(p, opts) {
1552
- const tolerance = opts.flattenTolerance ?? DEFAULT_FLATTEN_TOLERANCE;
1553
- const { commands, coords } = p;
1554
- const out = [];
1555
- let anchorCounter = 0;
1556
- let pts = null;
1557
- let aA = null;
1558
- let aB = null;
1559
- let aT = null;
1560
- let current = null;
1561
- let coordIdx = 0;
1562
- let prevX = 0;
1563
- let prevY = 0;
1564
- let prevAnchor = -1;
1565
- const beginContour = () => {
1566
- pts = [];
1567
- aA = [];
1568
- aB = [];
1569
- aT = [];
1570
- const next = { points: pts, closed: false };
1571
- out.push(next);
1572
- return next;
1573
- };
1574
- const dropDuplicateClosingPoint = () => {
1575
- if (!pts || !aA || !aB || !aT) return;
1576
- const n = pts.length / 2;
1577
- if (n < 2) return;
1578
- if (pts[0] !== pts[(n - 1) * 2] || pts[1] !== pts[(n - 1) * 2 + 1]) return;
1579
- pts.length -= 2;
1580
- aA.length -= 1;
1581
- aB.length -= 1;
1582
- aT.length -= 1;
1583
- };
1584
- const commit = (target) => {
1585
- if (!pts || !aA || !aB || !aT) return;
1586
- target.anchorA = new Uint32Array(aA);
1587
- target.anchorB = new Uint32Array(aB);
1588
- target.anchorT = new Float32Array(aT);
1589
- };
1590
- for (let cmdIdx = 0; cmdIdx < commands.length; cmdIdx++) {
1591
- const cmd = commands[cmdIdx];
1592
- switch (cmd) {
1593
- case PATH_M: {
1594
- if (current) commit(current);
1595
- current = beginContour();
1596
- prevX = coords[coordIdx];
1597
- prevY = coords[coordIdx + 1];
1598
- pts.push(prevX, prevY);
1599
- aA.push(anchorCounter);
1600
- aB.push(anchorCounter);
1601
- aT.push(0);
1602
- prevAnchor = anchorCounter;
1603
- anchorCounter++;
1604
- coordIdx += 2;
1605
- break;
1606
- }
1607
- case PATH_L: {
1608
- prevX = coords[coordIdx];
1609
- prevY = coords[coordIdx + 1];
1610
- pts.push(prevX, prevY);
1611
- aA.push(anchorCounter);
1612
- aB.push(anchorCounter);
1613
- aT.push(0);
1614
- prevAnchor = anchorCounter;
1615
- anchorCounter++;
1616
- coordIdx += 2;
1617
- break;
1618
- }
1619
- case PATH_Q: {
1620
- const cx = coords[coordIdx], cy = coords[coordIdx + 1];
1621
- const ex = coords[coordIdx + 2], ey = coords[coordIdx + 3];
1622
- const targetAnchor = anchorCounter;
1623
- const segStart = pts.length / 2;
1624
- const arcAccum = [];
1625
- const total = flattenQuadraticWithArcLen(prevX, prevY, cx, cy, ex, ey, tolerance, pts, arcAccum);
1626
- for (let k = 0; k < arcAccum.length; k++) {
1627
- aA.push(prevAnchor);
1628
- aB.push(targetAnchor);
1629
- aT.push(total > 0 ? arcAccum[k] / total : 0);
1630
- }
1631
- const lastIdx = segStart + arcAccum.length - 1;
1632
- aA[lastIdx] = targetAnchor;
1633
- aB[lastIdx] = targetAnchor;
1634
- aT[lastIdx] = 0;
1635
- prevX = ex;
1636
- prevY = ey;
1637
- prevAnchor = targetAnchor;
1638
- anchorCounter++;
1639
- coordIdx += 4;
1640
- break;
1641
- }
1642
- case PATH_C: {
1643
- const c1x = coords[coordIdx], c1y = coords[coordIdx + 1];
1644
- const c2x = coords[coordIdx + 2], c2y = coords[coordIdx + 3];
1645
- const ex = coords[coordIdx + 4], ey = coords[coordIdx + 5];
1646
- const targetAnchor = anchorCounter;
1647
- const segStart = pts.length / 2;
1648
- const arcAccum = [];
1649
- const total = flattenCubicWithArcLen(prevX, prevY, c1x, c1y, c2x, c2y, ex, ey, tolerance, pts, arcAccum);
1650
- for (let k = 0; k < arcAccum.length; k++) {
1651
- aA.push(prevAnchor);
1652
- aB.push(targetAnchor);
1653
- aT.push(total > 0 ? arcAccum[k] / total : 0);
1654
- }
1655
- const lastIdx = segStart + arcAccum.length - 1;
1656
- aA[lastIdx] = targetAnchor;
1657
- aB[lastIdx] = targetAnchor;
1658
- aT[lastIdx] = 0;
1659
- prevX = ex;
1660
- prevY = ey;
1661
- prevAnchor = targetAnchor;
1662
- anchorCounter++;
1663
- coordIdx += 6;
1664
- break;
1665
- }
1666
- case PATH_Z: {
1667
- if (current) {
1668
- current.closed = true;
1669
- dropDuplicateClosingPoint();
1670
- }
1671
- break;
1672
- }
1673
- default:
1674
- throw new Error(`extractPolylines: unknown command code ${cmd}`);
1675
- }
1557
+ function sameFill(a, b) {
1558
+ if (a === b) return true;
1559
+ if ("color" in a && "color" in b) {
1560
+ return a.color === b.color && (a.opacity ?? 1) === (b.opacity ?? 1);
1676
1561
  }
1677
- if (current) commit(current);
1678
- return out;
1562
+ return false;
1679
1563
  }
1680
-
1681
- // src/features/paths/tessellate/stroke.ts
1682
- var EMPTY_MESH = {
1683
- vertices: new Float32Array(0),
1684
- indices: new Uint32Array(0),
1685
- anchorA: new Uint32Array(0),
1686
- anchorB: new Uint32Array(0),
1687
- anchorT: new Float32Array(0)
1688
- };
1689
- function tessellateStroke(path, stroke, opts = {}) {
1690
- const width = stroke.width ?? 1;
1691
- if (width <= 0) return EMPTY_MESH;
1692
- const join = stroke.join ?? "miter";
1693
- const cap = stroke.cap ?? "butt";
1694
- const align = stroke.align ?? "center";
1695
- const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;
1696
- const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;
1697
- let workingPath = path;
1698
- if (path.kind === "rect" && align !== "center") {
1699
- const aligned = alignedStrokeRect(path, align, width);
1700
- workingPath = {
1701
- kind: "rect",
1702
- x: aligned.x,
1703
- y: aligned.y,
1704
- width: aligned.width,
1705
- height: aligned.height
1564
+ function strokeKey(s) {
1565
+ if (!s) return "-";
1566
+ return [
1567
+ fillKey(s.paint),
1568
+ s.width ?? 1,
1569
+ s.join ?? "miter",
1570
+ s.cap ?? "butt",
1571
+ s.miterLimit ?? "",
1572
+ s.align ?? "center",
1573
+ (s.dash ?? []).join(",")
1574
+ ].join(":");
1575
+ }
1576
+ function groupKey(family, weight, style, synthetic, fill, stroke, source, page) {
1577
+ return `${family}|${weight}|${style}|${synthetic.bold ? 1 : 0}${synthetic.italic ? 1 : 0}|${fillKey(fill)}|${strokeKey(stroke)}|${source}|${page}`;
1578
+ }
1579
+ function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
1580
+ const atlasFamily = resolved.resolved.family;
1581
+ const resolvedWeight = resolved.resolved.weight;
1582
+ const resolvedStyle = resolved.resolved.style;
1583
+ const source = sourceOverride ?? resolved.source;
1584
+ const key = groupKey(
1585
+ atlasFamily,
1586
+ resolvedWeight,
1587
+ resolvedStyle,
1588
+ resolved.synthetic,
1589
+ run.fill,
1590
+ source === "outline" ? run.stroke : void 0,
1591
+ source,
1592
+ page
1593
+ );
1594
+ let g = ctx.groups.get(key);
1595
+ if (!g) {
1596
+ g = {
1597
+ family: atlasFamily,
1598
+ weight: resolvedWeight,
1599
+ style: resolvedStyle,
1600
+ synthetic: { ...resolved.synthetic },
1601
+ source,
1602
+ page,
1603
+ fill: run.fill,
1604
+ // Only the outline tier can paint it, and carrying it on an SDF group
1605
+ // would be a promise the renderer cannot keep.
1606
+ ...source === "outline" && run.stroke !== void 0 ? { stroke: run.stroke } : {},
1607
+ quads: [],
1608
+ glyphs: []
1706
1609
  };
1610
+ ctx.groups.set(key, g);
1707
1611
  }
1708
- const polylines = extractPolylines(workingPath, opts);
1709
- if (stroke.vertexWidths && stroke.vertexWidths.length > 0) {
1710
- for (const pl of polylines) populatePolylineWidths(pl, stroke.vertexWidths, width);
1711
- }
1712
- const dash = stroke.dash ?? [];
1713
- const verts = [];
1714
- const idx = [];
1715
- const anchorA = [];
1716
- const anchorB = [];
1717
- const anchorT = [];
1718
- for (const pl of polylines) {
1719
- const subs = dash.length > 0 ? splitForDash(pl, dash) : [pl];
1720
- for (const sub2 of subs) {
1721
- expandPolyline(sub2, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);
1612
+ return g;
1613
+ }
1614
+ function resolveGlyph(run, font, resolved, cp) {
1615
+ const direct = font.charMap.get(cp);
1616
+ if (direct) return { glyph: direct, font, resolved };
1617
+ const fallback = resolveGlyphFallback(run.fontFamily, run.fontWeight, run.fontStyle);
1618
+ const face = fallback?.dynamicFace;
1619
+ if (fallback && face) {
1620
+ const glyph = face.requestGlyph(cp);
1621
+ if (glyph.xadvance > 0 || glyph.width > 0) {
1622
+ return { glyph, font: face.font, resolved: fallback };
1722
1623
  }
1723
1624
  }
1724
- return {
1725
- vertices: new Float32Array(verts),
1726
- indices: new Uint32Array(idx),
1727
- anchorA: new Uint32Array(anchorA),
1728
- anchorB: new Uint32Array(anchorB),
1729
- anchorT: new Float32Array(anchorT)
1730
- };
1625
+ warnMissingGlyphOnce(resolved.resolved.family, cp);
1626
+ return null;
1731
1627
  }
1732
- function expandPolyline(pl, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT) {
1733
- const half = width / 2;
1734
- const pts = pl.points;
1735
- const ptCount = pts.length / 2;
1736
- const segCount = ptCount - 1;
1737
- if (segCount < 1) return;
1738
- const plA = pl.anchorA ?? new Uint32Array(ptCount);
1739
- const plB = pl.anchorB ?? new Uint32Array(ptCount);
1740
- const plT = pl.anchorT ?? new Float32Array(ptCount);
1741
- const plHalf = pl.widths ? pl.widths.map((w) => w / 2) : null;
1742
- const segs = [];
1743
- const segSrcIdx = [];
1744
- for (let s = 0; s < segCount; s++) {
1745
- const ha = plHalf ? plHalf[s] : half;
1746
- const hb = plHalf ? plHalf[s + 1] : half;
1747
- const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], ha, hb);
1748
- if (seg) {
1749
- segs.push(seg);
1750
- segSrcIdx.push(s);
1628
+ var warnedMissingGlyphs = /* @__PURE__ */ new Set();
1629
+ function warnMissingGlyphOnce(family, cp) {
1630
+ const key = `${family}|${cp}`;
1631
+ if (warnedMissingGlyphs.has(key)) return;
1632
+ warnedMissingGlyphs.add(key);
1633
+ const ch = String.fromCodePoint(cp);
1634
+ console.warn(
1635
+ `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.`
1636
+ );
1637
+ }
1638
+ function layoutRuns(runs, opts) {
1639
+ const ctx = { groups: /* @__PURE__ */ new Map() };
1640
+ const entries = [];
1641
+ let prevCp;
1642
+ let prevFont;
1643
+ let prevFontSize;
1644
+ for (const run of runs) {
1645
+ const resolved = resolveFontVariant(run.fontFamily, run.fontWeight, run.fontStyle);
1646
+ const font = resolved.entry?.font ?? resolved.dynamicFace?.font;
1647
+ if (!font) {
1648
+ prevCp = void 0;
1649
+ prevFont = void 0;
1650
+ prevFontSize = void 0;
1651
+ continue;
1751
1652
  }
1752
- }
1753
- let closerSrcIdx = -1;
1754
- if (pl.closed && segs.length >= 1) {
1755
- const last = segs[segs.length - 1];
1756
- const first = segs[0];
1757
- const haCloser = plHalf ? plHalf[ptCount - 1] : half;
1758
- const hbCloser = plHalf ? plHalf[0] : half;
1759
- const closer = makeSeg(last.bx, last.by, first.ax, first.ay, haCloser, hbCloser);
1760
- if (closer) {
1761
- segs.push(closer);
1762
- closerSrcIdx = ptCount - 1;
1763
- segSrcIdx.push(closerSrcIdx);
1653
+ const scale2 = run.fontSize / font.info.size;
1654
+ const tracking = run.letterSpacing;
1655
+ for (const ch of [...run.text]) {
1656
+ const cp = ch.codePointAt(0);
1657
+ const isNewline = cp === 10;
1658
+ const isSpace = cp === 32;
1659
+ if (isNewline) {
1660
+ entries.push({
1661
+ run,
1662
+ font,
1663
+ glyph: { id: cp, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
1664
+ // A newline consumes no advance, so it takes no tracking either.
1665
+ cp,
1666
+ advance: 0,
1667
+ tracking: 0,
1668
+ kerningBefore: 0,
1669
+ isSpace: false,
1670
+ isNewline: true,
1671
+ resolved,
1672
+ fontSize: run.fontSize
1673
+ });
1674
+ prevCp = void 0;
1675
+ prevFont = void 0;
1676
+ prevFontSize = void 0;
1677
+ continue;
1678
+ }
1679
+ if (isSpace) {
1680
+ const spaceGlyph = resolved.dynamicFace ? resolved.dynamicFace.requestGlyph(32) : font.charMap.get(32);
1681
+ const advance = spaceGlyph ? spaceGlyph.xadvance * scale2 : run.fontSize * 0.25;
1682
+ let kerningBefore2 = 0;
1683
+ if (prevCp !== void 0 && prevFont !== void 0 && prevFontSize !== void 0) {
1684
+ const kAtlas = prevFont.kerningMap.get(prevCp)?.get(cp) ?? 0;
1685
+ kerningBefore2 = kAtlas * (prevFontSize / prevFont.info.size);
1686
+ }
1687
+ entries.push({
1688
+ run,
1689
+ font,
1690
+ glyph: spaceGlyph ?? { id: 32, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
1691
+ cp,
1692
+ advance,
1693
+ tracking,
1694
+ kerningBefore: kerningBefore2,
1695
+ isSpace: true,
1696
+ isNewline: false,
1697
+ resolved,
1698
+ fontSize: run.fontSize
1699
+ });
1700
+ prevCp = cp;
1701
+ prevFont = font;
1702
+ prevFontSize = run.fontSize;
1703
+ continue;
1704
+ }
1705
+ const hit = resolved.dynamicFace ? { glyph: resolved.dynamicFace.requestGlyph(cp), font, resolved } : resolveGlyph(run, font, resolved, cp);
1706
+ if (!hit) {
1707
+ prevCp = cp;
1708
+ prevFont = font;
1709
+ prevFontSize = run.fontSize;
1710
+ continue;
1711
+ }
1712
+ const glyphFont = hit.font;
1713
+ const glyphScale = run.fontSize / glyphFont.info.size;
1714
+ let kerningBefore = 0;
1715
+ if (prevCp !== void 0 && prevFont !== void 0 && prevFontSize !== void 0) {
1716
+ const kAtlas = prevFont.kerningMap.get(prevCp)?.get(cp) ?? 0;
1717
+ kerningBefore = kAtlas * (prevFontSize / prevFont.info.size);
1718
+ }
1719
+ entries.push({
1720
+ run,
1721
+ font: glyphFont,
1722
+ glyph: hit.glyph,
1723
+ cp,
1724
+ advance: hit.glyph.xadvance * glyphScale,
1725
+ tracking,
1726
+ kerningBefore,
1727
+ isSpace,
1728
+ isNewline: false,
1729
+ resolved: hit.resolved,
1730
+ fontSize: run.fontSize
1731
+ });
1732
+ prevCp = cp;
1733
+ prevFont = glyphFont;
1734
+ prevFontSize = run.fontSize;
1764
1735
  }
1765
1736
  }
1766
- if (segs.length === 0) return;
1767
- const segBaseIdx = [];
1768
- for (let s = 0; s < segs.length; s++) {
1769
- const seg = segs[s];
1770
- const startSrc = segSrcIdx[s];
1771
- const endSrc = s === segs.length - 1 && pl.closed && closerSrcIdx >= 0 ? 0 : startSrc + 1;
1772
- const base = verts.length / 2;
1773
- segBaseIdx.push(base);
1774
- const nxA = seg.nxUnit * seg.halfA, nyA = seg.nyUnit * seg.halfA;
1775
- const nxB = seg.nxUnit * seg.halfB, nyB = seg.nyUnit * seg.halfB;
1776
- verts.push(seg.ax + nxA, seg.ay + nyA);
1777
- anchorA.push(plA[startSrc]);
1778
- anchorB.push(plB[startSrc]);
1779
- anchorT.push(plT[startSrc]);
1780
- verts.push(seg.ax - nxA, seg.ay - nyA);
1781
- anchorA.push(plA[startSrc]);
1782
- anchorB.push(plB[startSrc]);
1783
- anchorT.push(plT[startSrc]);
1784
- verts.push(seg.bx + nxB, seg.by + nyB);
1785
- anchorA.push(plA[endSrc]);
1786
- anchorB.push(plB[endSrc]);
1787
- anchorT.push(plT[endSrc]);
1788
- verts.push(seg.bx - nxB, seg.by - nyB);
1789
- anchorA.push(plA[endSrc]);
1790
- anchorB.push(plB[endSrc]);
1791
- anchorT.push(plT[endSrc]);
1792
- idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
1793
- }
1794
- const joinCount = pl.closed ? segs.length : segs.length - 1;
1795
- for (let j = 0; j < joinCount; j++) {
1796
- emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);
1797
- }
1798
- if (!pl.closed && cap !== "butt") {
1799
- const first = segs[0];
1800
- const firstBase = segBaseIdx[0];
1801
- emitCap(first, firstBase + 0, firstBase + 1, false, first.halfA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);
1802
- const last = segs[segs.length - 1];
1803
- const lastBase = segBaseIdx[segs.length - 1];
1804
- const lastSrc = segSrcIdx[segs.length - 1] + 1;
1805
- emitCap(last, lastBase + 2, lastBase + 3, true, last.halfB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);
1737
+ const lines = [];
1738
+ let cur = { entries: [], width: 0, height: 0 };
1739
+ function commitLine() {
1740
+ lines.push(cur);
1741
+ cur = { entries: [], width: 0, height: 0 };
1806
1742
  }
1807
- }
1808
- function populatePolylineWidths(pl, vertexWidths, fallbackWidth) {
1809
- const ptCount = pl.points.length / 2;
1810
- if (ptCount === 0) return;
1811
- const aA = pl.anchorA ?? new Uint32Array(ptCount);
1812
- const aB = pl.anchorB ?? new Uint32Array(ptCount);
1813
- const aT = pl.anchorT ?? new Float32Array(ptCount);
1814
- const widths = new Float32Array(ptCount);
1815
- const read = (i) => {
1816
- const v = vertexWidths[i];
1817
- return typeof v === "number" && isFinite(v) && v > 0 ? v : fallbackWidth;
1818
- };
1819
- for (let i = 0; i < ptCount; i++) {
1820
- const a = aA[i], b = aB[i], t = aT[i];
1821
- if (a === b) {
1822
- widths[i] = read(a);
1823
- } else {
1824
- const wa = read(a);
1825
- const wb = read(b);
1826
- widths[i] = wa + (wb - wa) * t;
1743
+ let i = 0;
1744
+ while (i < entries.length) {
1745
+ const e = entries[i];
1746
+ if (e.isNewline) {
1747
+ if (cur.entries.length === 0) {
1748
+ cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
1749
+ cur.blank = e;
1750
+ }
1751
+ commitLine();
1752
+ i++;
1753
+ continue;
1827
1754
  }
1828
- }
1829
- pl.widths = widths;
1830
- }
1831
- var ROUND_CAP_STEP_RAD = 10 * Math.PI / 180;
1832
- function emitCap(seg, leftIdx, rightIdx, atEnd, half, cap, verts, idx, anchorA, anchorB, anchorT, endA, endB, endT) {
1833
- const sign = atEnd ? 1 : -1;
1834
- const dx = (seg.bx - seg.ax) / seg.len * sign;
1835
- const dy = (seg.by - seg.ay) / seg.len * sign;
1836
- const cx = atEnd ? seg.bx : seg.ax;
1837
- const cy = atEnd ? seg.by : seg.ay;
1838
- const nx = seg.nxUnit * half;
1839
- const ny = seg.nyUnit * half;
1840
- if (cap === "square") {
1841
- const ox = cx + dx * half;
1842
- const oy = cy + dy * half;
1843
- const lOut = verts.length / 2;
1844
- verts.push(ox + nx, oy + ny);
1845
- anchorA.push(endA);
1846
- anchorB.push(endB);
1847
- anchorT.push(endT);
1848
- const rOut = verts.length / 2;
1849
- verts.push(ox - nx, oy - ny);
1850
- anchorA.push(endA);
1851
- anchorB.push(endB);
1852
- anchorT.push(endT);
1853
- if (atEnd) {
1854
- idx.push(leftIdx, lOut, rOut, leftIdx, rOut, rightIdx);
1855
- } else {
1856
- idx.push(leftIdx, rOut, lOut, leftIdx, rightIdx, rOut);
1755
+ if (e.isSpace) {
1756
+ if (cur.entries.length > 0) {
1757
+ cur.entries.push(e);
1758
+ cur.width += e.kerningBefore + e.advance + e.tracking;
1759
+ cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
1760
+ }
1761
+ i++;
1762
+ continue;
1857
1763
  }
1858
- return;
1859
- }
1860
- if (cap === "round") {
1861
- const pivotIdx = verts.length / 2;
1862
- verts.push(cx, cy);
1863
- anchorA.push(endA);
1864
- anchorB.push(endB);
1865
- anchorT.push(endT);
1866
- const startAngle = Math.atan2(ny, nx);
1867
- const sweep = atEnd ? -Math.PI : Math.PI;
1868
- const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_CAP_STEP_RAD));
1869
- const stepAngle = sweep / steps;
1870
- let prevIdx = leftIdx;
1871
- for (let i = 1; i < steps; i++) {
1872
- const ang = startAngle + i * stepAngle;
1873
- const fx = cx + Math.cos(ang) * half;
1874
- const fy = cy + Math.sin(ang) * half;
1875
- const newIdx = verts.length / 2;
1876
- verts.push(fx, fy);
1877
- anchorA.push(endA);
1878
- anchorB.push(endB);
1879
- anchorT.push(endT);
1880
- if (atEnd) idx.push(prevIdx, newIdx, pivotIdx);
1881
- else idx.push(prevIdx, pivotIdx, newIdx);
1882
- prevIdx = newIdx;
1764
+ let j = i;
1765
+ let wordWidth = 0;
1766
+ while (j < entries.length && !entries[j].isSpace && !entries[j].isNewline) {
1767
+ const w = entries[j];
1768
+ wordWidth += w.kerningBefore + w.advance + w.tracking;
1769
+ j++;
1883
1770
  }
1884
- if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);
1885
- else idx.push(prevIdx, pivotIdx, rightIdx);
1771
+ if (Number.isFinite(opts.maxWidth) && cur.width + wordWidth > opts.maxWidth && cur.entries.length > 0) {
1772
+ commitLine();
1773
+ }
1774
+ for (let k = i; k < j; k++) {
1775
+ const w = entries[k];
1776
+ const kerningBefore = cur.entries.length === 0 ? 0 : w.kerningBefore;
1777
+ cur.entries.push({ ...w, kerningBefore });
1778
+ cur.width += kerningBefore + w.advance + w.tracking;
1779
+ cur.height = Math.max(cur.height, w.fontSize * opts.lineHeight);
1780
+ }
1781
+ i = j;
1886
1782
  }
1887
- }
1888
- function splitForDash(pl, dash) {
1889
- const out = [];
1890
- const plA = pl.anchorA ?? new Uint32Array(pl.points.length / 2);
1891
- const plB = pl.anchorB ?? new Uint32Array(pl.points.length / 2);
1892
- const plT = pl.anchorT ?? new Float32Array(pl.points.length / 2);
1893
- let dashIdx = 0;
1894
- let dashRemaining = dash[0];
1895
- let onPhase = true;
1896
- let curPts = onPhase ? [pl.points[0], pl.points[1]] : null;
1897
- let curA = onPhase ? [plA[0]] : null;
1898
- let curB = onPhase ? [plB[0]] : null;
1899
- let curT = onPhase ? [plT[0]] : null;
1900
- const flushAndAdvance = () => {
1901
- if (curPts && curPts.length >= 4) {
1902
- out.push({
1903
- points: curPts,
1904
- closed: false,
1905
- anchorA: new Uint32Array(curA),
1906
- anchorB: new Uint32Array(curB),
1907
- anchorT: new Float32Array(curT)
1908
- });
1783
+ if (cur.entries.length > 0) commitLine();
1784
+ function outlineFor(e) {
1785
+ const min = opts.outlineMinSize;
1786
+ if (min === void 0 || e.fontSize < min) return null;
1787
+ if (e.resolved.synthetic.bold) return null;
1788
+ const r = e.resolved.resolved;
1789
+ return glyphOutline(r.family, r.weight, r.style, e.cp);
1790
+ }
1791
+ const decorations = [];
1792
+ let span = null;
1793
+ function flushSpan() {
1794
+ const s = span;
1795
+ span = null;
1796
+ if (!s || s.x1 <= s.x0) return;
1797
+ const thickness = s.fontSize * DECORATION_THICKNESS;
1798
+ if (s.underline) {
1799
+ const y0 = s.baselineY + s.fontSize * UNDERLINE_OFFSET;
1800
+ decorations.push({ kind: "underline", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
1909
1801
  }
1910
- curPts = null;
1911
- curA = null;
1912
- curB = null;
1913
- curT = null;
1914
- dashIdx = (dashIdx + 1) % dash.length;
1915
- dashRemaining = dash[dashIdx];
1916
- onPhase = !onPhase;
1917
- };
1918
- let prevX = pl.points[0], prevY = pl.points[1];
1919
- const ptCount = pl.points.length / 2;
1920
- const segCount = pl.closed ? ptCount : ptCount - 1;
1921
- for (let i = 0; i < segCount; i++) {
1922
- const nextIdx = (i + 1) % ptCount;
1923
- const cx = pl.points[nextIdx * 2], cy = pl.points[nextIdx * 2 + 1];
1924
- const startA = plA[i], startB = plB[i], startT = plT[i];
1925
- const endA = plA[nextIdx], endB = plB[nextIdx], endT = plT[nextIdx];
1926
- const segFullDx = cx - prevX, segFullDy = cy - prevY;
1927
- const segFullLen = Math.hypot(segFullDx, segFullDy);
1928
- let segDx = segFullDx, segDy = segFullDy;
1929
- let segLen = segFullLen;
1930
- let traveled = 0;
1931
- while (segLen > 1e-9) {
1932
- if (segLen <= dashRemaining) {
1933
- if (onPhase && curPts) {
1934
- curPts.push(cx, cy);
1935
- curA.push(endA);
1936
- curB.push(endB);
1937
- curT.push(endT);
1938
- }
1939
- dashRemaining -= segLen;
1940
- prevX = cx;
1941
- prevY = cy;
1942
- traveled = segFullLen;
1943
- segLen = 0;
1944
- if (dashRemaining <= 1e-9) {
1945
- flushAndAdvance();
1946
- if (onPhase) {
1947
- curPts = [prevX, prevY];
1948
- curA = [endA];
1949
- curB = [endB];
1950
- curT = [endT];
1951
- }
1952
- }
1953
- } else {
1954
- const tConsume = dashRemaining / segLen;
1955
- const ix = prevX + segDx * tConsume;
1956
- const iy = prevY + segDy * tConsume;
1957
- traveled += dashRemaining;
1958
- const frac = traveled / segFullLen;
1959
- let mA, mB, mT;
1960
- if (startA === endA && startB === endB) {
1961
- mA = startA;
1962
- mB = startB;
1963
- mT = startT + (endT - startT) * frac;
1802
+ if (s.strikethrough) {
1803
+ const y0 = s.baselineY + s.fontSize * STRIKETHROUGH_OFFSET;
1804
+ decorations.push({ kind: "strikethrough", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
1805
+ }
1806
+ }
1807
+ const lineBoxes = [];
1808
+ let penY = 0;
1809
+ let maxLineWidth = 0;
1810
+ const finiteWidth = Number.isFinite(opts.maxWidth) ? opts.maxWidth : 0;
1811
+ for (const line of lines) {
1812
+ const alignShift = (() => {
1813
+ if (opts.align === "left") return 0;
1814
+ if (!Number.isFinite(opts.maxWidth)) {
1815
+ return opts.align === "center" ? -line.width / 2 : -line.width;
1816
+ }
1817
+ const slack = finiteWidth - line.width;
1818
+ return opts.align === "center" ? slack / 2 : slack;
1819
+ })();
1820
+ const lineX0 = alignShift;
1821
+ const baselineSource = line.entries[0] ?? line.blank;
1822
+ const lineBaselineY = baselineSource ? penY + baselineSource.font.common.base * (baselineSource.fontSize / baselineSource.font.info.size) : penY;
1823
+ let penX = lineX0;
1824
+ for (const e of line.entries) {
1825
+ penX += e.kerningBefore;
1826
+ const step = e.advance + e.tracking;
1827
+ const scale2 = e.fontSize / e.font.info.size;
1828
+ const baselineY = penY + e.font.common.base * scale2;
1829
+ if (e.run.underline || e.run.strikethrough) {
1830
+ 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)) {
1831
+ span.x1 = penX + step;
1964
1832
  } else {
1965
- if (frac < 0.5) {
1966
- mA = startA;
1967
- mB = startB;
1968
- mT = startT;
1969
- } else {
1970
- mA = endA;
1971
- mB = endB;
1972
- mT = endT;
1973
- }
1974
- }
1975
- if (onPhase && curPts) {
1976
- curPts.push(ix, iy);
1977
- curA.push(mA);
1978
- curB.push(mB);
1979
- curT.push(mT);
1980
- }
1981
- prevX = ix;
1982
- prevY = iy;
1983
- segDx = cx - prevX;
1984
- segDy = cy - prevY;
1985
- segLen = Math.hypot(segDx, segDy);
1986
- flushAndAdvance();
1987
- if (onPhase) {
1988
- curPts = [prevX, prevY];
1989
- curA = [mA];
1990
- curB = [mB];
1991
- curT = [mT];
1833
+ flushSpan();
1834
+ span = {
1835
+ underline: e.run.underline,
1836
+ strikethrough: e.run.strikethrough,
1837
+ fill: e.run.fill,
1838
+ fontSize: e.fontSize,
1839
+ baselineY,
1840
+ x0: penX,
1841
+ x1: penX + step
1842
+ };
1992
1843
  }
1844
+ } else {
1845
+ flushSpan();
1846
+ }
1847
+ const outlineD = outlineFor(e);
1848
+ if (outlineD !== null) {
1849
+ const group2 = getOrCreateGroup(ctx, e.run, e.resolved, 0, "outline");
1850
+ group2.glyphs.push({
1851
+ d: outlineD,
1852
+ key: `${e.resolved.resolved.family}|${e.resolved.resolved.weight}|${e.resolved.resolved.style}|${e.cp}`,
1853
+ x: penX,
1854
+ baselineY,
1855
+ // Em space is unit-scale, so world units per em is just the size.
1856
+ scale: e.fontSize
1857
+ });
1858
+ penX += step;
1859
+ continue;
1860
+ }
1861
+ if (e.advance === 0 || e.glyph.width === 0 || e.glyph.page < 0) {
1862
+ penX += step;
1863
+ continue;
1993
1864
  }
1865
+ const group = getOrCreateGroup(ctx, e.run, e.resolved, e.glyph.page);
1866
+ const atlasW = e.font.common.scaleW;
1867
+ const atlasH = e.font.common.scaleH;
1868
+ const qx0 = penX + e.glyph.xoffset * scale2;
1869
+ const qy0 = penY + e.glyph.yoffset * scale2;
1870
+ const qx1 = qx0 + e.glyph.width * scale2;
1871
+ const qy1 = qy0 + e.glyph.height * scale2;
1872
+ const u0 = e.glyph.x / atlasW;
1873
+ const v0 = e.glyph.y / atlasH;
1874
+ const u1 = (e.glyph.x + e.glyph.width) / atlasW;
1875
+ const v1 = (e.glyph.y + e.glyph.height) / atlasH;
1876
+ group.quads.push({ x0: qx0, y0: qy0, x1: qx1, y1: qy1, u0, v0, u1, v1, baselineY });
1877
+ penX += step;
1994
1878
  }
1995
- }
1996
- if (curPts && curPts.length >= 4) {
1997
- out.push({
1998
- points: curPts,
1999
- closed: false,
2000
- anchorA: new Uint32Array(curA),
2001
- anchorB: new Uint32Array(curB),
2002
- anchorT: new Float32Array(curT)
1879
+ flushSpan();
1880
+ lineBoxes.push({
1881
+ x0: lineX0,
1882
+ y0: penY,
1883
+ x1: lineX0 + line.width,
1884
+ y1: penY + line.height,
1885
+ baselineY: lineBaselineY
2003
1886
  });
1887
+ maxLineWidth = Math.max(maxLineWidth, line.width);
1888
+ penY += line.height;
2004
1889
  }
2005
- return out;
2006
- }
2007
- function makeSeg(ax, ay, bx, by, halfA, halfB) {
2008
- const dx = bx - ax, dy = by - ay;
2009
- const len = Math.hypot(dx, dy);
2010
- if (len === 0) return null;
2011
1890
  return {
2012
- ax,
2013
- ay,
2014
- bx,
2015
- by,
2016
- nxUnit: -dy / len,
2017
- nyUnit: dx / len,
2018
- halfA,
2019
- halfB,
2020
- len
1891
+ groups: [...ctx.groups.values()],
1892
+ decorations,
1893
+ lines: lineBoxes,
1894
+ bounds: { width: maxLineWidth, height: penY }
2021
1895
  };
2022
1896
  }
2023
- var DEFAULT_MITER_LIMIT = 10;
2024
- var DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;
2025
- function emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT) {
2026
- const a = segs[j];
2027
- const b = segs[(j + 1) % segs.length];
2028
- const aBase = segBaseIdx[j];
2029
- const bBase = segBaseIdx[(j + 1) % segs.length];
2030
- const adx = a.bx - a.ax, ady = a.by - a.ay;
2031
- const bdx = b.bx - b.ax, bdy = b.by - b.ay;
2032
- const cross = adx * bdy - ady * bdx;
2033
- if (cross === 0) return;
2034
- const onPositive = cross > 0;
2035
- const aOuterEnd = onPositive ? aBase + 3 : aBase + 2;
2036
- const bOuterStart = onPositive ? bBase + 1 : bBase + 0;
2037
- const nextSeg = (j + 1) % segs.length;
2038
- const cornerSrc = segSrcIdx[nextSeg];
2039
- const cornerA = plA[cornerSrc];
2040
- const cornerB = plB[cornerSrc];
2041
- const cornerT = plT[cornerSrc];
2042
- const halfAtCorner = a.halfB;
2043
- const taperA = Math.max(a.halfA, a.halfB) / Math.max(1e-9, Math.min(a.halfA, a.halfB));
2044
- const taperB = Math.max(b.halfA, b.halfB) / Math.max(1e-9, Math.min(b.halfA, b.halfB));
2045
- const forceBevelForVarying = taperA > varyingThreshold || taperB > varyingThreshold;
2046
- if (join === "bevel" || forceBevelForVarying) {
2047
- emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
2048
- return;
1897
+ var LAYOUT_CACHE_VARIANT_LIMIT = 8;
1898
+ var cache2 = /* @__PURE__ */ new WeakMap();
1899
+ function outlineBucket(runs, min) {
1900
+ if (min === void 0) return -1;
1901
+ const sizes = /* @__PURE__ */ new Set();
1902
+ for (const r of runs) sizes.add(r.fontSize);
1903
+ let n = 0;
1904
+ for (const size of sizes) if (size >= min) n++;
1905
+ return n;
1906
+ }
1907
+ function variantKey(runs, opts) {
1908
+ return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}`;
1909
+ }
1910
+ function cachedLayoutRuns(runs, opts) {
1911
+ const fonts = glyphGeneration();
1912
+ let entry = cache2.get(runs);
1913
+ if (entry === void 0) {
1914
+ entry = { generation: fonts, byVariant: /* @__PURE__ */ new Map() };
1915
+ cache2.set(runs, entry);
1916
+ } else if (entry.generation !== fonts) {
1917
+ entry.generation = fonts;
1918
+ entry.byVariant.clear();
2049
1919
  }
2050
- if (join === "miter") {
2051
- const aOX = verts[aOuterEnd * 2], aOY = verts[aOuterEnd * 2 + 1];
2052
- const bOX = verts[bOuterStart * 2], bOY = verts[bOuterStart * 2 + 1];
2053
- const apex = lineLineIntersect(aOX, aOY, adx, ady, bOX, bOY, -bdx, -bdy);
2054
- if (!apex) {
2055
- emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
2056
- return;
2057
- }
2058
- const miterLen = Math.hypot(apex[0] - a.bx, apex[1] - a.by);
2059
- if (miterLen > miterLimit * halfAtCorner) {
2060
- emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
2061
- return;
2062
- }
2063
- const apexIdx = verts.length / 2;
2064
- verts.push(apex[0], apex[1]);
2065
- anchorA.push(cornerA);
2066
- anchorB.push(cornerB);
2067
- anchorT.push(cornerT);
2068
- const jIdx = verts.length / 2;
2069
- verts.push(a.bx, a.by);
2070
- anchorA.push(cornerA);
2071
- anchorB.push(cornerB);
2072
- anchorT.push(cornerT);
2073
- if (onPositive) {
2074
- idx.push(aOuterEnd, apexIdx, bOuterStart);
2075
- idx.push(aOuterEnd, bOuterStart, jIdx);
1920
+ const key = variantKey(runs, opts);
1921
+ const hit = entry.byVariant.get(key);
1922
+ if (hit !== void 0) return hit;
1923
+ const laid = layoutRuns(runs, opts);
1924
+ if (entry.byVariant.size >= LAYOUT_CACHE_VARIANT_LIMIT) entry.byVariant.clear();
1925
+ entry.byVariant.set(key, laid);
1926
+ return laid;
1927
+ }
1928
+
1929
+ // src/features/text/verticalAlign.ts
1930
+ function verticalAlignOffset(align, boxHeight, textHeight) {
1931
+ if (align === void 0 || align === "top" || boxHeight === void 0) return 0;
1932
+ const slack = boxHeight - textHeight;
1933
+ return align === "center" ? slack / 2 : slack;
1934
+ }
1935
+
1936
+ // src/features/paths/pathFromD.ts
1937
+ var NUM_RE = /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g;
1938
+ function tokenize(d) {
1939
+ const out = [];
1940
+ let i = 0;
1941
+ while (i < d.length) {
1942
+ const ch = d[i];
1943
+ if (/[A-Za-z]/.test(ch)) {
1944
+ let j = i + 1;
1945
+ while (j < d.length && !/[A-Za-z]/.test(d[j])) j++;
1946
+ const argSlice = d.slice(i + 1, j);
1947
+ out.push({ cmd: ch, args: readNumbers(argSlice) });
1948
+ i = j;
2076
1949
  } else {
2077
- idx.push(aOuterEnd, bOuterStart, apexIdx);
2078
- idx.push(aOuterEnd, jIdx, bOuterStart);
1950
+ i++;
2079
1951
  }
2080
- return;
2081
- }
2082
- if (join === "round") {
2083
- emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
2084
- return;
2085
1952
  }
1953
+ return out;
2086
1954
  }
2087
- var ROUND_STEP_RAD = 10 * Math.PI / 180;
2088
- function emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
2089
- const cx = a.bx, cy = a.by;
2090
- const pivotIdx = verts.length / 2;
2091
- verts.push(cx, cy);
2092
- anchorA.push(cornerA);
2093
- anchorB.push(cornerB);
2094
- anchorT.push(cornerT);
2095
- const startX = verts[aOuterEnd * 2] - cx;
2096
- const startY = verts[aOuterEnd * 2 + 1] - cy;
2097
- const endX = verts[bOuterStart * 2] - cx;
2098
- const endY = verts[bOuterStart * 2 + 1] - cy;
2099
- const r = Math.hypot(startX, startY);
2100
- const startAngle = Math.atan2(startY, startX);
2101
- const endAngle = Math.atan2(endY, endX);
2102
- let sweep = endAngle - startAngle;
2103
- if (onPositive && sweep < 0) sweep += 2 * Math.PI;
2104
- else if (!onPositive && sweep > 0) sweep -= 2 * Math.PI;
2105
- const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_STEP_RAD));
2106
- const stepAngle = sweep / steps;
2107
- let prevIdx = aOuterEnd;
2108
- for (let i = 1; i < steps; i++) {
2109
- const ang = startAngle + i * stepAngle;
2110
- const fx = cx + Math.cos(ang) * r;
2111
- const fy = cy + Math.sin(ang) * r;
2112
- const newIdx = verts.length / 2;
2113
- verts.push(fx, fy);
2114
- anchorA.push(cornerA);
2115
- anchorB.push(cornerB);
2116
- anchorT.push(cornerT);
2117
- if (onPositive) idx.push(prevIdx, pivotIdx, newIdx);
2118
- else idx.push(prevIdx, newIdx, pivotIdx);
2119
- prevIdx = newIdx;
1955
+ function readNumbers(s) {
1956
+ const out = [];
1957
+ let m;
1958
+ NUM_RE.lastIndex = 0;
1959
+ while ((m = NUM_RE.exec(s)) !== null) {
1960
+ const n = Number(m[0]);
1961
+ if (Number.isFinite(n)) out.push(n);
2120
1962
  }
2121
- if (onPositive) idx.push(prevIdx, pivotIdx, bOuterStart);
2122
- else idx.push(prevIdx, bOuterStart, pivotIdx);
2123
- }
2124
- function emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
2125
- const jIdx = verts.length / 2;
2126
- verts.push(a.bx, a.by);
2127
- anchorA.push(cornerA);
2128
- anchorB.push(cornerB);
2129
- anchorT.push(cornerT);
2130
- if (onPositive) idx.push(aOuterEnd, jIdx, bOuterStart);
2131
- else idx.push(aOuterEnd, bOuterStart, jIdx);
2132
- }
2133
- function lineLineIntersect(apx, apy, adx, ady, bpx, bpy, bdx, bdy) {
2134
- const denom = adx * bdy - ady * bdx;
2135
- if (Math.abs(denom) < 1e-9) return null;
2136
- const t = ((bpx - apx) * bdy - (bpy - apy) * bdx) / denom;
2137
- return [apx + t * adx, apy + t * ady];
2138
- }
2139
- var UNDERLINE_OFFSET = 0.1;
2140
- var STRIKETHROUGH_OFFSET = -0.3;
2141
- var DECORATION_THICKNESS = 0.05;
2142
- function fillKey(p) {
2143
- if ("color" in p) return `s:${p.color}:${p.opacity ?? 1}`;
2144
- return `nx:${Math.random()}`;
1963
+ return out;
2145
1964
  }
2146
- function sameFill(a, b) {
2147
- if (a === b) return true;
2148
- if ("color" in a && "color" in b) {
2149
- return a.color === b.color && (a.opacity ?? 1) === (b.opacity ?? 1);
1965
+ var ARG_COUNTS = {
1966
+ M: 2,
1967
+ L: 2,
1968
+ H: 1,
1969
+ V: 1,
1970
+ C: 6,
1971
+ S: 4,
1972
+ Q: 4,
1973
+ T: 2,
1974
+ A: 7,
1975
+ Z: 0
1976
+ };
1977
+ function pathFromD(d, onWarn) {
1978
+ const tokens = tokenize(d);
1979
+ const b = new PathBuilder();
1980
+ const st = {
1981
+ x: 0,
1982
+ y: 0,
1983
+ startX: 0,
1984
+ startY: 0,
1985
+ lastCubicCtrl: null,
1986
+ lastQuadCtrl: null
1987
+ };
1988
+ for (const { cmd, args } of tokens) {
1989
+ const upper = cmd.toUpperCase();
1990
+ const relative = cmd !== upper;
1991
+ const argCount = ARG_COUNTS[upper];
1992
+ if (argCount === void 0) {
1993
+ onWarn?.(`unknown path command: ${cmd}`);
1994
+ continue;
1995
+ }
1996
+ if (upper === "Z") {
1997
+ b.close();
1998
+ st.x = st.startX;
1999
+ st.y = st.startY;
2000
+ st.lastCubicCtrl = null;
2001
+ st.lastQuadCtrl = null;
2002
+ continue;
2003
+ }
2004
+ const groups = argCount === 0 ? 1 : Math.floor(args.length / argCount);
2005
+ for (let g = 0; g < groups; g++) {
2006
+ const a = args.slice(g * argCount, (g + 1) * argCount);
2007
+ const effectiveUpper = upper === "M" && g > 0 ? "L" : upper;
2008
+ runCommand(effectiveUpper, relative, a, st, b);
2009
+ }
2150
2010
  }
2151
- return false;
2011
+ return b.build();
2152
2012
  }
2153
- function strokeKey(s) {
2154
- if (!s) return "-";
2155
- return [
2156
- fillKey(s.paint),
2157
- s.width ?? 1,
2158
- s.join ?? "miter",
2159
- s.cap ?? "butt",
2160
- s.miterLimit ?? "",
2161
- s.align ?? "center",
2162
- (s.dash ?? []).join(",")
2163
- ].join(":");
2164
- }
2165
- function groupKey(family, weight, style, synthetic, fill, stroke, source, page) {
2166
- return `${family}|${weight}|${style}|${synthetic.bold ? 1 : 0}${synthetic.italic ? 1 : 0}|${fillKey(fill)}|${strokeKey(stroke)}|${source}|${page}`;
2013
+ function runCommand(upper, relative, a, st, b) {
2014
+ const rx = relative ? st.x : 0;
2015
+ const ry = relative ? st.y : 0;
2016
+ switch (upper) {
2017
+ case "M": {
2018
+ const x = a[0] + rx;
2019
+ const y = a[1] + ry;
2020
+ b.moveTo(x, y);
2021
+ st.x = x;
2022
+ st.y = y;
2023
+ st.startX = x;
2024
+ st.startY = y;
2025
+ st.lastCubicCtrl = null;
2026
+ st.lastQuadCtrl = null;
2027
+ return;
2028
+ }
2029
+ case "L": {
2030
+ const x = a[0] + rx;
2031
+ const y = a[1] + ry;
2032
+ b.lineTo(x, y);
2033
+ st.x = x;
2034
+ st.y = y;
2035
+ st.lastCubicCtrl = null;
2036
+ st.lastQuadCtrl = null;
2037
+ return;
2038
+ }
2039
+ case "H": {
2040
+ const x = a[0] + (relative ? st.x : 0);
2041
+ b.lineTo(x, st.y);
2042
+ st.x = x;
2043
+ st.lastCubicCtrl = null;
2044
+ st.lastQuadCtrl = null;
2045
+ return;
2046
+ }
2047
+ case "V": {
2048
+ const y = a[0] + (relative ? st.y : 0);
2049
+ b.lineTo(st.x, y);
2050
+ st.y = y;
2051
+ st.lastCubicCtrl = null;
2052
+ st.lastQuadCtrl = null;
2053
+ return;
2054
+ }
2055
+ case "C": {
2056
+ const x1 = a[0] + rx, y1 = a[1] + ry;
2057
+ const x2 = a[2] + rx, y2 = a[3] + ry;
2058
+ const x = a[4] + rx, y = a[5] + ry;
2059
+ b.curveTo(x1, y1, x2, y2, x, y);
2060
+ st.x = x;
2061
+ st.y = y;
2062
+ st.lastCubicCtrl = { x: x2, y: y2 };
2063
+ st.lastQuadCtrl = null;
2064
+ return;
2065
+ }
2066
+ case "S": {
2067
+ const ref = st.lastCubicCtrl ?? { x: st.x, y: st.y };
2068
+ const x1 = 2 * st.x - ref.x;
2069
+ const y1 = 2 * st.y - ref.y;
2070
+ const x2 = a[0] + rx, y2 = a[1] + ry;
2071
+ const x = a[2] + rx, y = a[3] + ry;
2072
+ b.curveTo(x1, y1, x2, y2, x, y);
2073
+ st.x = x;
2074
+ st.y = y;
2075
+ st.lastCubicCtrl = { x: x2, y: y2 };
2076
+ st.lastQuadCtrl = null;
2077
+ return;
2078
+ }
2079
+ case "Q": {
2080
+ const x1 = a[0] + rx, y1 = a[1] + ry;
2081
+ const x = a[2] + rx, y = a[3] + ry;
2082
+ b.quadTo(x1, y1, x, y);
2083
+ st.x = x;
2084
+ st.y = y;
2085
+ st.lastQuadCtrl = { x: x1, y: y1 };
2086
+ st.lastCubicCtrl = null;
2087
+ return;
2088
+ }
2089
+ case "T": {
2090
+ const ref = st.lastQuadCtrl ?? { x: st.x, y: st.y };
2091
+ const x1 = 2 * st.x - ref.x;
2092
+ const y1 = 2 * st.y - ref.y;
2093
+ const x = a[0] + rx, y = a[1] + ry;
2094
+ b.quadTo(x1, y1, x, y);
2095
+ st.x = x;
2096
+ st.y = y;
2097
+ st.lastQuadCtrl = { x: x1, y: y1 };
2098
+ st.lastCubicCtrl = null;
2099
+ return;
2100
+ }
2101
+ case "A": {
2102
+ const rxA = Math.abs(a[0]);
2103
+ const ryA = Math.abs(a[1]);
2104
+ const xRot = a[2];
2105
+ const largeArc = a[3] !== 0;
2106
+ const sweep = a[4] !== 0;
2107
+ const ex = a[5] + rx;
2108
+ const ey = a[6] + ry;
2109
+ arcToCubics(st.x, st.y, ex, ey, rxA, ryA, xRot, largeArc, sweep, b);
2110
+ st.x = ex;
2111
+ st.y = ey;
2112
+ st.lastCubicCtrl = null;
2113
+ st.lastQuadCtrl = null;
2114
+ return;
2115
+ }
2116
+ default:
2117
+ return;
2118
+ }
2167
2119
  }
2168
- function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
2169
- const atlasFamily = resolved.resolved.family;
2170
- const resolvedWeight = resolved.resolved.weight;
2171
- const resolvedStyle = resolved.resolved.style;
2172
- const source = sourceOverride ?? resolved.source;
2173
- const key = groupKey(
2174
- atlasFamily,
2175
- resolvedWeight,
2176
- resolvedStyle,
2177
- resolved.synthetic,
2178
- run.fill,
2179
- source === "outline" ? run.stroke : void 0,
2180
- source,
2181
- page
2120
+ function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep, b) {
2121
+ if (x1 === x2 && y1 === y2) return;
2122
+ if (rx === 0 || ry === 0) {
2123
+ b.lineTo(x2, y2);
2124
+ return;
2125
+ }
2126
+ const phi = xAxisRotationDeg * Math.PI / 180;
2127
+ const cosPhi = Math.cos(phi);
2128
+ const sinPhi = Math.sin(phi);
2129
+ const dx = (x1 - x2) / 2;
2130
+ const dy = (y1 - y2) / 2;
2131
+ const x1p = cosPhi * dx + sinPhi * dy;
2132
+ const y1p = -sinPhi * dx + cosPhi * dy;
2133
+ let rxs = rx * rx;
2134
+ let rys = ry * ry;
2135
+ const x1ps = x1p * x1p;
2136
+ const y1ps = y1p * y1p;
2137
+ const lambda = x1ps / rxs + y1ps / rys;
2138
+ let rxFixed = rx;
2139
+ let ryFixed = ry;
2140
+ if (lambda > 1) {
2141
+ const sqrtLambda = Math.sqrt(lambda);
2142
+ rxFixed = sqrtLambda * rx;
2143
+ ryFixed = sqrtLambda * ry;
2144
+ rxs = rxFixed * rxFixed;
2145
+ rys = ryFixed * ryFixed;
2146
+ }
2147
+ const sign = largeArc === sweep ? -1 : 1;
2148
+ const num = rxs * rys - rxs * y1ps - rys * x1ps;
2149
+ const den = rxs * y1ps + rys * x1ps;
2150
+ const factor = sign * Math.sqrt(Math.max(0, num / den));
2151
+ const cxp = factor * (rxFixed * y1p) / ryFixed;
2152
+ const cyp = factor * -(ryFixed * x1p) / rxFixed;
2153
+ const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
2154
+ const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
2155
+ const angle = (ux, uy, vx, vy) => {
2156
+ const dot = ux * vx + uy * vy;
2157
+ const len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
2158
+ let a = Math.acos(Math.max(-1, Math.min(1, dot / len)));
2159
+ if (ux * vy - uy * vx < 0) a = -a;
2160
+ return a;
2161
+ };
2162
+ const theta1 = angle(1, 0, (x1p - cxp) / rxFixed, (y1p - cyp) / ryFixed);
2163
+ let dTheta = angle(
2164
+ (x1p - cxp) / rxFixed,
2165
+ (y1p - cyp) / ryFixed,
2166
+ (-x1p - cxp) / rxFixed,
2167
+ (-y1p - cyp) / ryFixed
2182
2168
  );
2183
- let g = ctx.groups.get(key);
2184
- if (!g) {
2185
- g = {
2186
- family: atlasFamily,
2187
- weight: resolvedWeight,
2188
- style: resolvedStyle,
2189
- synthetic: { ...resolved.synthetic },
2190
- source,
2191
- page,
2192
- fill: run.fill,
2193
- // Only the outline tier can paint it, and carrying it on an SDF group
2194
- // would be a promise the renderer cannot keep.
2195
- ...source === "outline" && run.stroke !== void 0 ? { stroke: run.stroke } : {},
2196
- quads: [],
2197
- glyphs: []
2198
- };
2199
- ctx.groups.set(key, g);
2169
+ if (!sweep && dTheta > 0) dTheta -= 2 * Math.PI;
2170
+ else if (sweep && dTheta < 0) dTheta += 2 * Math.PI;
2171
+ const segCount = Math.max(1, Math.ceil(Math.abs(dTheta) / (Math.PI / 2)));
2172
+ const segAngle = dTheta / segCount;
2173
+ const t = 4 / 3 * Math.tan(segAngle / 4);
2174
+ let prevX = x1;
2175
+ let prevY = y1;
2176
+ let prevDx = -Math.sin(theta1) * rxFixed;
2177
+ let prevDy = Math.cos(theta1) * ryFixed;
2178
+ for (let i = 1; i <= segCount; i++) {
2179
+ const theta = theta1 + i * segAngle;
2180
+ const cosT = Math.cos(theta);
2181
+ const sinT = Math.sin(theta);
2182
+ const px = cosPhi * (cosT * rxFixed) - sinPhi * (sinT * ryFixed) + cx;
2183
+ const py = sinPhi * (cosT * rxFixed) + cosPhi * (sinT * ryFixed) + cy;
2184
+ const dxNew = -sinT * rxFixed;
2185
+ const dyNew = cosT * ryFixed;
2186
+ const c1x = prevX + cosPhi * (t * prevDx) - sinPhi * (t * prevDy);
2187
+ const c1y = prevY + sinPhi * (t * prevDx) + cosPhi * (t * prevDy);
2188
+ const c2x = px - cosPhi * (t * dxNew) + sinPhi * (t * dyNew);
2189
+ const c2y = py - sinPhi * (t * dxNew) - cosPhi * (t * dyNew);
2190
+ b.curveTo(c1x, c1y, c2x, c2y, px, py);
2191
+ prevX = px;
2192
+ prevY = py;
2193
+ prevDx = dxNew;
2194
+ prevDy = dyNew;
2200
2195
  }
2201
- return g;
2202
2196
  }
2203
- function resolveGlyph(run, font, resolved, cp) {
2204
- const direct = font.charMap.get(cp);
2205
- if (direct) return { glyph: direct, font, resolved };
2206
- const fallback = resolveGlyphFallback(run.fontFamily, run.fontWeight, run.fontStyle);
2207
- const face = fallback?.dynamicFace;
2208
- if (fallback && face) {
2209
- const glyph = face.requestGlyph(cp);
2210
- if (glyph.xadvance > 0 || glyph.width > 0) {
2211
- return { glyph, font: face.font, resolved: fallback };
2212
- }
2213
- }
2214
- warnMissingGlyphOnce(resolved.resolved.family, cp);
2215
- return null;
2197
+
2198
+ // src/renderer/cache/outlineMeshCache.ts
2199
+ var OUTLINE_FLATTEN_TOLERANCE = 1 / 4096;
2200
+ var OUTLINE_MESH_CACHE_LIMIT = 2048;
2201
+ var cache3 = /* @__PURE__ */ new Map();
2202
+ function outlineMesh(key, d) {
2203
+ const cached = cache3.get(key);
2204
+ if (cached !== void 0) return cached;
2205
+ if (cache3.size >= OUTLINE_MESH_CACHE_LIMIT) cache3 = /* @__PURE__ */ new Map();
2206
+ const mesh = tessellate(pathFromD(d), { flattenTolerance: OUTLINE_FLATTEN_TOLERANCE });
2207
+ cache3.set(key, mesh);
2208
+ return mesh;
2216
2209
  }
2217
- var warnedMissingGlyphs = /* @__PURE__ */ new Set();
2218
- function warnMissingGlyphOnce(family, cp) {
2219
- const key = `${family}|${cp}`;
2220
- if (warnedMissingGlyphs.has(key)) return;
2221
- warnedMissingGlyphs.add(key);
2222
- const ch = String.fromCodePoint(cp);
2223
- console.warn(
2224
- `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.`
2225
- );
2210
+
2211
+ // src/features/paths/tessellate/polyline.ts
2212
+ function extractPolylines(path, opts = {}) {
2213
+ if (path.kind === "rect") return [extractRect(path)];
2214
+ return extractPolygon(path, opts);
2226
2215
  }
2227
- function layoutRuns(runs, opts) {
2228
- const ctx = { groups: /* @__PURE__ */ new Map() };
2229
- const entries = [];
2230
- let prevCp;
2231
- let prevFont;
2232
- let prevFontSize;
2233
- for (const run of runs) {
2234
- const resolved = resolveFontVariant(run.fontFamily, run.fontWeight, run.fontStyle);
2235
- const font = resolved.entry?.font ?? resolved.dynamicFace?.font;
2236
- if (!font) {
2237
- prevCp = void 0;
2238
- prevFont = void 0;
2239
- prevFontSize = void 0;
2240
- continue;
2241
- }
2242
- const scale2 = run.fontSize / font.info.size;
2243
- const tracking = run.letterSpacing;
2244
- for (const ch of [...run.text]) {
2245
- const cp = ch.codePointAt(0);
2246
- const isNewline = cp === 10;
2247
- const isSpace = cp === 32;
2248
- if (isNewline) {
2249
- entries.push({
2250
- run,
2251
- font,
2252
- glyph: { id: cp, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
2253
- // A newline consumes no advance, so it takes no tracking either.
2254
- cp,
2255
- advance: 0,
2256
- tracking: 0,
2257
- kerningBefore: 0,
2258
- isSpace: false,
2259
- isNewline: true,
2260
- resolved,
2261
- fontSize: run.fontSize
2262
- });
2263
- prevCp = void 0;
2264
- prevFont = void 0;
2265
- prevFontSize = void 0;
2266
- continue;
2267
- }
2268
- if (isSpace) {
2269
- const spaceGlyph = resolved.dynamicFace ? resolved.dynamicFace.requestGlyph(32) : font.charMap.get(32);
2270
- const advance = spaceGlyph ? spaceGlyph.xadvance * scale2 : run.fontSize * 0.25;
2271
- let kerningBefore2 = 0;
2272
- if (prevCp !== void 0 && prevFont !== void 0 && prevFontSize !== void 0) {
2273
- const kAtlas = prevFont.kerningMap.get(prevCp)?.get(cp) ?? 0;
2274
- kerningBefore2 = kAtlas * (prevFontSize / prevFont.info.size);
2275
- }
2276
- entries.push({
2277
- run,
2278
- font,
2279
- glyph: spaceGlyph ?? { id: 32, x: 0, y: 0, width: 0, height: 0, xoffset: 0, yoffset: 0, xadvance: 0, page: 0 },
2280
- cp,
2281
- advance,
2282
- tracking,
2283
- kerningBefore: kerningBefore2,
2284
- isSpace: true,
2285
- isNewline: false,
2286
- resolved,
2287
- fontSize: run.fontSize
2288
- });
2289
- prevCp = cp;
2290
- prevFont = font;
2291
- prevFontSize = run.fontSize;
2292
- continue;
2216
+ function extractRect(p) {
2217
+ const { x, y, width: w, height: h } = p;
2218
+ return {
2219
+ points: [x, y, x + w, y, x + w, y + h, x, y + h],
2220
+ closed: true,
2221
+ anchorA: new Uint32Array([0, 1, 2, 3]),
2222
+ anchorB: new Uint32Array([0, 1, 2, 3]),
2223
+ anchorT: new Float32Array([0, 0, 0, 0])
2224
+ };
2225
+ }
2226
+ function extractPolygon(p, opts) {
2227
+ const tolerance = opts.flattenTolerance ?? DEFAULT_FLATTEN_TOLERANCE;
2228
+ const { commands, coords } = p;
2229
+ const out = [];
2230
+ let anchorCounter = 0;
2231
+ let pts = null;
2232
+ let aA = null;
2233
+ let aB = null;
2234
+ let aT = null;
2235
+ let current = null;
2236
+ let coordIdx = 0;
2237
+ let prevX = 0;
2238
+ let prevY = 0;
2239
+ let prevAnchor = -1;
2240
+ const beginContour = () => {
2241
+ pts = [];
2242
+ aA = [];
2243
+ aB = [];
2244
+ aT = [];
2245
+ const next = { points: pts, closed: false };
2246
+ out.push(next);
2247
+ return next;
2248
+ };
2249
+ const dropDuplicateClosingPoint = () => {
2250
+ if (!pts || !aA || !aB || !aT) return;
2251
+ const n = pts.length / 2;
2252
+ if (n < 2) return;
2253
+ if (pts[0] !== pts[(n - 1) * 2] || pts[1] !== pts[(n - 1) * 2 + 1]) return;
2254
+ pts.length -= 2;
2255
+ aA.length -= 1;
2256
+ aB.length -= 1;
2257
+ aT.length -= 1;
2258
+ };
2259
+ const commit = (target) => {
2260
+ if (!pts || !aA || !aB || !aT) return;
2261
+ target.anchorA = new Uint32Array(aA);
2262
+ target.anchorB = new Uint32Array(aB);
2263
+ target.anchorT = new Float32Array(aT);
2264
+ };
2265
+ for (let cmdIdx = 0; cmdIdx < commands.length; cmdIdx++) {
2266
+ const cmd = commands[cmdIdx];
2267
+ switch (cmd) {
2268
+ case PATH_M: {
2269
+ if (current) commit(current);
2270
+ current = beginContour();
2271
+ prevX = coords[coordIdx];
2272
+ prevY = coords[coordIdx + 1];
2273
+ pts.push(prevX, prevY);
2274
+ aA.push(anchorCounter);
2275
+ aB.push(anchorCounter);
2276
+ aT.push(0);
2277
+ prevAnchor = anchorCounter;
2278
+ anchorCounter++;
2279
+ coordIdx += 2;
2280
+ break;
2293
2281
  }
2294
- const hit = resolved.dynamicFace ? { glyph: resolved.dynamicFace.requestGlyph(cp), font, resolved } : resolveGlyph(run, font, resolved, cp);
2295
- if (!hit) {
2296
- prevCp = cp;
2297
- prevFont = font;
2298
- prevFontSize = run.fontSize;
2299
- continue;
2282
+ case PATH_L: {
2283
+ prevX = coords[coordIdx];
2284
+ prevY = coords[coordIdx + 1];
2285
+ pts.push(prevX, prevY);
2286
+ aA.push(anchorCounter);
2287
+ aB.push(anchorCounter);
2288
+ aT.push(0);
2289
+ prevAnchor = anchorCounter;
2290
+ anchorCounter++;
2291
+ coordIdx += 2;
2292
+ break;
2300
2293
  }
2301
- const glyphFont = hit.font;
2302
- const glyphScale = run.fontSize / glyphFont.info.size;
2303
- let kerningBefore = 0;
2304
- if (prevCp !== void 0 && prevFont !== void 0 && prevFontSize !== void 0) {
2305
- const kAtlas = prevFont.kerningMap.get(prevCp)?.get(cp) ?? 0;
2306
- kerningBefore = kAtlas * (prevFontSize / prevFont.info.size);
2294
+ case PATH_Q: {
2295
+ const cx = coords[coordIdx], cy = coords[coordIdx + 1];
2296
+ const ex = coords[coordIdx + 2], ey = coords[coordIdx + 3];
2297
+ const targetAnchor = anchorCounter;
2298
+ const segStart = pts.length / 2;
2299
+ const arcAccum = [];
2300
+ const total = flattenQuadraticWithArcLen(prevX, prevY, cx, cy, ex, ey, tolerance, pts, arcAccum);
2301
+ for (let k = 0; k < arcAccum.length; k++) {
2302
+ aA.push(prevAnchor);
2303
+ aB.push(targetAnchor);
2304
+ aT.push(total > 0 ? arcAccum[k] / total : 0);
2305
+ }
2306
+ const lastIdx = segStart + arcAccum.length - 1;
2307
+ aA[lastIdx] = targetAnchor;
2308
+ aB[lastIdx] = targetAnchor;
2309
+ aT[lastIdx] = 0;
2310
+ prevX = ex;
2311
+ prevY = ey;
2312
+ prevAnchor = targetAnchor;
2313
+ anchorCounter++;
2314
+ coordIdx += 4;
2315
+ break;
2307
2316
  }
2308
- entries.push({
2309
- run,
2310
- font: glyphFont,
2311
- glyph: hit.glyph,
2312
- cp,
2313
- advance: hit.glyph.xadvance * glyphScale,
2314
- tracking,
2315
- kerningBefore,
2316
- isSpace,
2317
- isNewline: false,
2318
- resolved: hit.resolved,
2319
- fontSize: run.fontSize
2320
- });
2321
- prevCp = cp;
2322
- prevFont = glyphFont;
2323
- prevFontSize = run.fontSize;
2324
- }
2325
- }
2326
- const lines = [];
2327
- let cur = { entries: [], width: 0, height: 0 };
2328
- function commitLine() {
2329
- lines.push(cur);
2330
- cur = { entries: [], width: 0, height: 0 };
2331
- }
2332
- let i = 0;
2333
- while (i < entries.length) {
2334
- const e = entries[i];
2335
- if (e.isNewline) {
2336
- if (cur.entries.length === 0) {
2337
- cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
2338
- cur.blank = e;
2317
+ case PATH_C: {
2318
+ const c1x = coords[coordIdx], c1y = coords[coordIdx + 1];
2319
+ const c2x = coords[coordIdx + 2], c2y = coords[coordIdx + 3];
2320
+ const ex = coords[coordIdx + 4], ey = coords[coordIdx + 5];
2321
+ const targetAnchor = anchorCounter;
2322
+ const segStart = pts.length / 2;
2323
+ const arcAccum = [];
2324
+ const total = flattenCubicWithArcLen(prevX, prevY, c1x, c1y, c2x, c2y, ex, ey, tolerance, pts, arcAccum);
2325
+ for (let k = 0; k < arcAccum.length; k++) {
2326
+ aA.push(prevAnchor);
2327
+ aB.push(targetAnchor);
2328
+ aT.push(total > 0 ? arcAccum[k] / total : 0);
2329
+ }
2330
+ const lastIdx = segStart + arcAccum.length - 1;
2331
+ aA[lastIdx] = targetAnchor;
2332
+ aB[lastIdx] = targetAnchor;
2333
+ aT[lastIdx] = 0;
2334
+ prevX = ex;
2335
+ prevY = ey;
2336
+ prevAnchor = targetAnchor;
2337
+ anchorCounter++;
2338
+ coordIdx += 6;
2339
+ break;
2339
2340
  }
2340
- commitLine();
2341
- i++;
2342
- continue;
2343
- }
2344
- if (e.isSpace) {
2345
- if (cur.entries.length > 0) {
2346
- cur.entries.push(e);
2347
- cur.width += e.kerningBefore + e.advance + e.tracking;
2348
- cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
2341
+ case PATH_Z: {
2342
+ if (current) {
2343
+ current.closed = true;
2344
+ dropDuplicateClosingPoint();
2345
+ }
2346
+ break;
2349
2347
  }
2350
- i++;
2351
- continue;
2352
- }
2353
- let j = i;
2354
- let wordWidth = 0;
2355
- while (j < entries.length && !entries[j].isSpace && !entries[j].isNewline) {
2356
- const w = entries[j];
2357
- wordWidth += w.kerningBefore + w.advance + w.tracking;
2358
- j++;
2359
- }
2360
- if (Number.isFinite(opts.maxWidth) && cur.width + wordWidth > opts.maxWidth && cur.entries.length > 0) {
2361
- commitLine();
2362
- }
2363
- for (let k = i; k < j; k++) {
2364
- const w = entries[k];
2365
- const kerningBefore = cur.entries.length === 0 ? 0 : w.kerningBefore;
2366
- cur.entries.push({ ...w, kerningBefore });
2367
- cur.width += kerningBefore + w.advance + w.tracking;
2368
- cur.height = Math.max(cur.height, w.fontSize * opts.lineHeight);
2348
+ default:
2349
+ throw new Error(`extractPolylines: unknown command code ${cmd}`);
2369
2350
  }
2370
- i = j;
2371
2351
  }
2372
- if (cur.entries.length > 0) commitLine();
2373
- function outlineFor(e) {
2374
- const min = opts.outlineMinSize;
2375
- if (min === void 0 || e.fontSize < min) return null;
2376
- if (e.resolved.synthetic.bold) return null;
2377
- const r = e.resolved.resolved;
2378
- return glyphOutline(r.family, r.weight, r.style, e.cp);
2352
+ if (current) commit(current);
2353
+ return out;
2354
+ }
2355
+
2356
+ // src/features/paths/tessellate/stroke.ts
2357
+ var EMPTY_MESH = {
2358
+ vertices: new Float32Array(0),
2359
+ indices: new Uint32Array(0),
2360
+ anchorA: new Uint32Array(0),
2361
+ anchorB: new Uint32Array(0),
2362
+ anchorT: new Float32Array(0)
2363
+ };
2364
+ function tessellateStroke(path, stroke, opts = {}) {
2365
+ const width = stroke.width ?? 1;
2366
+ if (width <= 0) return EMPTY_MESH;
2367
+ const join = stroke.join ?? "miter";
2368
+ const cap = stroke.cap ?? "butt";
2369
+ const align = stroke.align ?? "center";
2370
+ const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;
2371
+ const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;
2372
+ let workingPath = path;
2373
+ if (path.kind === "rect" && align !== "center") {
2374
+ const aligned = alignedStrokeRect(path, align, width);
2375
+ workingPath = {
2376
+ kind: "rect",
2377
+ x: aligned.x,
2378
+ y: aligned.y,
2379
+ width: aligned.width,
2380
+ height: aligned.height
2381
+ };
2379
2382
  }
2380
- const decorations = [];
2381
- let span = null;
2382
- function flushSpan() {
2383
- const s = span;
2384
- span = null;
2385
- if (!s || s.x1 <= s.x0) return;
2386
- const thickness = s.fontSize * DECORATION_THICKNESS;
2387
- if (s.underline) {
2388
- const y0 = s.baselineY + s.fontSize * UNDERLINE_OFFSET;
2389
- decorations.push({ kind: "underline", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
2383
+ const polylines = extractPolylines(workingPath, opts);
2384
+ if (stroke.vertexWidths && stroke.vertexWidths.length > 0) {
2385
+ for (const pl of polylines) populatePolylineWidths(pl, stroke.vertexWidths, width);
2386
+ }
2387
+ const dash = stroke.dash ?? [];
2388
+ const verts = [];
2389
+ const idx = [];
2390
+ const anchorA = [];
2391
+ const anchorB = [];
2392
+ const anchorT = [];
2393
+ for (const pl of polylines) {
2394
+ const subs = dash.length > 0 ? splitForDash(pl, dash) : [pl];
2395
+ for (const sub2 of subs) {
2396
+ expandPolyline(sub2, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);
2390
2397
  }
2391
- if (s.strikethrough) {
2392
- const y0 = s.baselineY + s.fontSize * STRIKETHROUGH_OFFSET;
2393
- decorations.push({ kind: "strikethrough", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
2398
+ }
2399
+ return {
2400
+ vertices: new Float32Array(verts),
2401
+ indices: new Uint32Array(idx),
2402
+ anchorA: new Uint32Array(anchorA),
2403
+ anchorB: new Uint32Array(anchorB),
2404
+ anchorT: new Float32Array(anchorT)
2405
+ };
2406
+ }
2407
+ function expandPolyline(pl, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT) {
2408
+ const half = width / 2;
2409
+ const pts = pl.points;
2410
+ const ptCount = pts.length / 2;
2411
+ const segCount = ptCount - 1;
2412
+ if (segCount < 1) return;
2413
+ const plA = pl.anchorA ?? new Uint32Array(ptCount);
2414
+ const plB = pl.anchorB ?? new Uint32Array(ptCount);
2415
+ const plT = pl.anchorT ?? new Float32Array(ptCount);
2416
+ const plHalf = pl.widths ? pl.widths.map((w) => w / 2) : null;
2417
+ const segs = [];
2418
+ const segSrcIdx = [];
2419
+ for (let s = 0; s < segCount; s++) {
2420
+ const ha = plHalf ? plHalf[s] : half;
2421
+ const hb = plHalf ? plHalf[s + 1] : half;
2422
+ const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], ha, hb);
2423
+ if (seg) {
2424
+ segs.push(seg);
2425
+ segSrcIdx.push(s);
2394
2426
  }
2395
2427
  }
2396
- const lineBoxes = [];
2397
- let penY = 0;
2398
- let maxLineWidth = 0;
2399
- const finiteWidth = Number.isFinite(opts.maxWidth) ? opts.maxWidth : 0;
2400
- for (const line of lines) {
2401
- const alignShift = (() => {
2402
- if (opts.align === "left") return 0;
2403
- if (!Number.isFinite(opts.maxWidth)) {
2404
- return opts.align === "center" ? -line.width / 2 : -line.width;
2405
- }
2406
- const slack = finiteWidth - line.width;
2407
- return opts.align === "center" ? slack / 2 : slack;
2408
- })();
2409
- const lineX0 = alignShift;
2410
- const baselineSource = line.entries[0] ?? line.blank;
2411
- const lineBaselineY = baselineSource ? penY + baselineSource.font.common.base * (baselineSource.fontSize / baselineSource.font.info.size) : penY;
2412
- let penX = lineX0;
2413
- for (const e of line.entries) {
2414
- penX += e.kerningBefore;
2415
- const step = e.advance + e.tracking;
2416
- const scale2 = e.fontSize / e.font.info.size;
2417
- const baselineY = penY + e.font.common.base * scale2;
2418
- if (e.run.underline || e.run.strikethrough) {
2419
- 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)) {
2420
- span.x1 = penX + step;
2421
- } else {
2422
- flushSpan();
2423
- span = {
2424
- underline: e.run.underline,
2425
- strikethrough: e.run.strikethrough,
2426
- fill: e.run.fill,
2427
- fontSize: e.fontSize,
2428
- baselineY,
2429
- x0: penX,
2430
- x1: penX + step
2431
- };
2432
- }
2433
- } else {
2434
- flushSpan();
2435
- }
2436
- const outlineD = outlineFor(e);
2437
- if (outlineD !== null) {
2438
- const group2 = getOrCreateGroup(ctx, e.run, e.resolved, 0, "outline");
2439
- group2.glyphs.push({
2440
- d: outlineD,
2441
- key: `${e.resolved.resolved.family}|${e.resolved.resolved.weight}|${e.resolved.resolved.style}|${e.cp}`,
2442
- x: penX,
2443
- baselineY,
2444
- // Em space is unit-scale, so world units per em is just the size.
2445
- scale: e.fontSize
2446
- });
2447
- penX += step;
2448
- continue;
2449
- }
2450
- if (e.advance === 0 || e.glyph.width === 0 || e.glyph.page < 0) {
2451
- penX += step;
2452
- continue;
2453
- }
2454
- const group = getOrCreateGroup(ctx, e.run, e.resolved, e.glyph.page);
2455
- const atlasW = e.font.common.scaleW;
2456
- const atlasH = e.font.common.scaleH;
2457
- const qx0 = penX + e.glyph.xoffset * scale2;
2458
- const qy0 = penY + e.glyph.yoffset * scale2;
2459
- const qx1 = qx0 + e.glyph.width * scale2;
2460
- const qy1 = qy0 + e.glyph.height * scale2;
2461
- const u0 = e.glyph.x / atlasW;
2462
- const v0 = e.glyph.y / atlasH;
2463
- const u1 = (e.glyph.x + e.glyph.width) / atlasW;
2464
- const v1 = (e.glyph.y + e.glyph.height) / atlasH;
2465
- group.quads.push({ x0: qx0, y0: qy0, x1: qx1, y1: qy1, u0, v0, u1, v1, baselineY });
2466
- penX += step;
2428
+ let closerSrcIdx = -1;
2429
+ if (pl.closed && segs.length >= 1) {
2430
+ const last = segs[segs.length - 1];
2431
+ const first = segs[0];
2432
+ const haCloser = plHalf ? plHalf[ptCount - 1] : half;
2433
+ const hbCloser = plHalf ? plHalf[0] : half;
2434
+ const closer = makeSeg(last.bx, last.by, first.ax, first.ay, haCloser, hbCloser);
2435
+ if (closer) {
2436
+ segs.push(closer);
2437
+ closerSrcIdx = ptCount - 1;
2438
+ segSrcIdx.push(closerSrcIdx);
2467
2439
  }
2468
- flushSpan();
2469
- lineBoxes.push({
2470
- x0: lineX0,
2471
- y0: penY,
2472
- x1: lineX0 + line.width,
2473
- y1: penY + line.height,
2474
- baselineY: lineBaselineY
2475
- });
2476
- maxLineWidth = Math.max(maxLineWidth, line.width);
2477
- penY += line.height;
2478
2440
  }
2479
- return {
2480
- groups: [...ctx.groups.values()],
2481
- decorations,
2482
- lines: lineBoxes,
2483
- bounds: { width: maxLineWidth, height: penY }
2484
- };
2485
- }
2486
- var LAYOUT_CACHE_VARIANT_LIMIT = 8;
2487
- var cache2 = /* @__PURE__ */ new WeakMap();
2488
- function outlineBucket(runs, min) {
2489
- if (min === void 0) return -1;
2490
- const sizes = /* @__PURE__ */ new Set();
2491
- for (const r of runs) sizes.add(r.fontSize);
2492
- let n = 0;
2493
- for (const size of sizes) if (size >= min) n++;
2494
- return n;
2495
- }
2496
- function variantKey(runs, opts) {
2497
- return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}`;
2441
+ if (segs.length === 0) return;
2442
+ const segBaseIdx = [];
2443
+ for (let s = 0; s < segs.length; s++) {
2444
+ const seg = segs[s];
2445
+ const startSrc = segSrcIdx[s];
2446
+ const endSrc = s === segs.length - 1 && pl.closed && closerSrcIdx >= 0 ? 0 : startSrc + 1;
2447
+ const base = verts.length / 2;
2448
+ segBaseIdx.push(base);
2449
+ const nxA = seg.nxUnit * seg.halfA, nyA = seg.nyUnit * seg.halfA;
2450
+ const nxB = seg.nxUnit * seg.halfB, nyB = seg.nyUnit * seg.halfB;
2451
+ verts.push(seg.ax + nxA, seg.ay + nyA);
2452
+ anchorA.push(plA[startSrc]);
2453
+ anchorB.push(plB[startSrc]);
2454
+ anchorT.push(plT[startSrc]);
2455
+ verts.push(seg.ax - nxA, seg.ay - nyA);
2456
+ anchorA.push(plA[startSrc]);
2457
+ anchorB.push(plB[startSrc]);
2458
+ anchorT.push(plT[startSrc]);
2459
+ verts.push(seg.bx + nxB, seg.by + nyB);
2460
+ anchorA.push(plA[endSrc]);
2461
+ anchorB.push(plB[endSrc]);
2462
+ anchorT.push(plT[endSrc]);
2463
+ verts.push(seg.bx - nxB, seg.by - nyB);
2464
+ anchorA.push(plA[endSrc]);
2465
+ anchorB.push(plB[endSrc]);
2466
+ anchorT.push(plT[endSrc]);
2467
+ idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
2468
+ }
2469
+ const joinCount = pl.closed ? segs.length : segs.length - 1;
2470
+ for (let j = 0; j < joinCount; j++) {
2471
+ emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);
2472
+ }
2473
+ if (!pl.closed && cap !== "butt") {
2474
+ const first = segs[0];
2475
+ const firstBase = segBaseIdx[0];
2476
+ emitCap(first, firstBase + 0, firstBase + 1, false, first.halfA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);
2477
+ const last = segs[segs.length - 1];
2478
+ const lastBase = segBaseIdx[segs.length - 1];
2479
+ const lastSrc = segSrcIdx[segs.length - 1] + 1;
2480
+ emitCap(last, lastBase + 2, lastBase + 3, true, last.halfB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);
2481
+ }
2498
2482
  }
2499
- function cachedLayoutRuns(runs, opts) {
2500
- const fonts = glyphGeneration();
2501
- let entry = cache2.get(runs);
2502
- if (entry === void 0) {
2503
- entry = { generation: fonts, byVariant: /* @__PURE__ */ new Map() };
2504
- cache2.set(runs, entry);
2505
- } else if (entry.generation !== fonts) {
2506
- entry.generation = fonts;
2507
- entry.byVariant.clear();
2483
+ function populatePolylineWidths(pl, vertexWidths, fallbackWidth) {
2484
+ const ptCount = pl.points.length / 2;
2485
+ if (ptCount === 0) return;
2486
+ const aA = pl.anchorA ?? new Uint32Array(ptCount);
2487
+ const aB = pl.anchorB ?? new Uint32Array(ptCount);
2488
+ const aT = pl.anchorT ?? new Float32Array(ptCount);
2489
+ const widths = new Float32Array(ptCount);
2490
+ const read = (i) => {
2491
+ const v = vertexWidths[i];
2492
+ return typeof v === "number" && isFinite(v) && v > 0 ? v : fallbackWidth;
2493
+ };
2494
+ for (let i = 0; i < ptCount; i++) {
2495
+ const a = aA[i], b = aB[i], t = aT[i];
2496
+ if (a === b) {
2497
+ widths[i] = read(a);
2498
+ } else {
2499
+ const wa = read(a);
2500
+ const wb = read(b);
2501
+ widths[i] = wa + (wb - wa) * t;
2502
+ }
2508
2503
  }
2509
- const key = variantKey(runs, opts);
2510
- const hit = entry.byVariant.get(key);
2511
- if (hit !== void 0) return hit;
2512
- const laid = layoutRuns(runs, opts);
2513
- if (entry.byVariant.size >= LAYOUT_CACHE_VARIANT_LIMIT) entry.byVariant.clear();
2514
- entry.byVariant.set(key, laid);
2515
- return laid;
2504
+ pl.widths = widths;
2516
2505
  }
2517
-
2518
- // src/features/text/verticalAlign.ts
2519
- function verticalAlignOffset(align, boxHeight, textHeight) {
2520
- if (align === void 0 || align === "top" || boxHeight === void 0) return 0;
2521
- const slack = boxHeight - textHeight;
2522
- return align === "center" ? slack / 2 : slack;
2506
+ var ROUND_CAP_STEP_RAD = 10 * Math.PI / 180;
2507
+ function emitCap(seg, leftIdx, rightIdx, atEnd, half, cap, verts, idx, anchorA, anchorB, anchorT, endA, endB, endT) {
2508
+ const sign = atEnd ? 1 : -1;
2509
+ const dx = (seg.bx - seg.ax) / seg.len * sign;
2510
+ const dy = (seg.by - seg.ay) / seg.len * sign;
2511
+ const cx = atEnd ? seg.bx : seg.ax;
2512
+ const cy = atEnd ? seg.by : seg.ay;
2513
+ const nx = seg.nxUnit * half;
2514
+ const ny = seg.nyUnit * half;
2515
+ if (cap === "square") {
2516
+ const ox = cx + dx * half;
2517
+ const oy = cy + dy * half;
2518
+ const lOut = verts.length / 2;
2519
+ verts.push(ox + nx, oy + ny);
2520
+ anchorA.push(endA);
2521
+ anchorB.push(endB);
2522
+ anchorT.push(endT);
2523
+ const rOut = verts.length / 2;
2524
+ verts.push(ox - nx, oy - ny);
2525
+ anchorA.push(endA);
2526
+ anchorB.push(endB);
2527
+ anchorT.push(endT);
2528
+ if (atEnd) {
2529
+ idx.push(leftIdx, lOut, rOut, leftIdx, rOut, rightIdx);
2530
+ } else {
2531
+ idx.push(leftIdx, rOut, lOut, leftIdx, rightIdx, rOut);
2532
+ }
2533
+ return;
2534
+ }
2535
+ if (cap === "round") {
2536
+ const pivotIdx = verts.length / 2;
2537
+ verts.push(cx, cy);
2538
+ anchorA.push(endA);
2539
+ anchorB.push(endB);
2540
+ anchorT.push(endT);
2541
+ const startAngle = Math.atan2(ny, nx);
2542
+ const sweep = atEnd ? -Math.PI : Math.PI;
2543
+ const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_CAP_STEP_RAD));
2544
+ const stepAngle = sweep / steps;
2545
+ let prevIdx = leftIdx;
2546
+ for (let i = 1; i < steps; i++) {
2547
+ const ang = startAngle + i * stepAngle;
2548
+ const fx = cx + Math.cos(ang) * half;
2549
+ const fy = cy + Math.sin(ang) * half;
2550
+ const newIdx = verts.length / 2;
2551
+ verts.push(fx, fy);
2552
+ anchorA.push(endA);
2553
+ anchorB.push(endB);
2554
+ anchorT.push(endT);
2555
+ if (atEnd) idx.push(prevIdx, newIdx, pivotIdx);
2556
+ else idx.push(prevIdx, pivotIdx, newIdx);
2557
+ prevIdx = newIdx;
2558
+ }
2559
+ if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);
2560
+ else idx.push(prevIdx, pivotIdx, rightIdx);
2561
+ }
2523
2562
  }
2524
-
2525
- // src/features/paths/pathFromD.ts
2526
- var NUM_RE = /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g;
2527
- function tokenize(d) {
2563
+ function splitForDash(pl, dash) {
2528
2564
  const out = [];
2529
- let i = 0;
2530
- while (i < d.length) {
2531
- const ch = d[i];
2532
- if (/[A-Za-z]/.test(ch)) {
2533
- let j = i + 1;
2534
- while (j < d.length && !/[A-Za-z]/.test(d[j])) j++;
2535
- const argSlice = d.slice(i + 1, j);
2536
- out.push({ cmd: ch, args: readNumbers(argSlice) });
2537
- i = j;
2538
- } else {
2539
- i++;
2565
+ const plA = pl.anchorA ?? new Uint32Array(pl.points.length / 2);
2566
+ const plB = pl.anchorB ?? new Uint32Array(pl.points.length / 2);
2567
+ const plT = pl.anchorT ?? new Float32Array(pl.points.length / 2);
2568
+ let dashIdx = 0;
2569
+ let dashRemaining = dash[0];
2570
+ let onPhase = true;
2571
+ let curPts = onPhase ? [pl.points[0], pl.points[1]] : null;
2572
+ let curA = onPhase ? [plA[0]] : null;
2573
+ let curB = onPhase ? [plB[0]] : null;
2574
+ let curT = onPhase ? [plT[0]] : null;
2575
+ const flushAndAdvance = () => {
2576
+ if (curPts && curPts.length >= 4) {
2577
+ out.push({
2578
+ points: curPts,
2579
+ closed: false,
2580
+ anchorA: new Uint32Array(curA),
2581
+ anchorB: new Uint32Array(curB),
2582
+ anchorT: new Float32Array(curT)
2583
+ });
2584
+ }
2585
+ curPts = null;
2586
+ curA = null;
2587
+ curB = null;
2588
+ curT = null;
2589
+ dashIdx = (dashIdx + 1) % dash.length;
2590
+ dashRemaining = dash[dashIdx];
2591
+ onPhase = !onPhase;
2592
+ };
2593
+ let prevX = pl.points[0], prevY = pl.points[1];
2594
+ const ptCount = pl.points.length / 2;
2595
+ const segCount = pl.closed ? ptCount : ptCount - 1;
2596
+ for (let i = 0; i < segCount; i++) {
2597
+ const nextIdx = (i + 1) % ptCount;
2598
+ const cx = pl.points[nextIdx * 2], cy = pl.points[nextIdx * 2 + 1];
2599
+ const startA = plA[i], startB = plB[i], startT = plT[i];
2600
+ const endA = plA[nextIdx], endB = plB[nextIdx], endT = plT[nextIdx];
2601
+ const segFullDx = cx - prevX, segFullDy = cy - prevY;
2602
+ const segFullLen = Math.hypot(segFullDx, segFullDy);
2603
+ let segDx = segFullDx, segDy = segFullDy;
2604
+ let segLen = segFullLen;
2605
+ let traveled = 0;
2606
+ while (segLen > 1e-9) {
2607
+ if (segLen <= dashRemaining) {
2608
+ if (onPhase && curPts) {
2609
+ curPts.push(cx, cy);
2610
+ curA.push(endA);
2611
+ curB.push(endB);
2612
+ curT.push(endT);
2613
+ }
2614
+ dashRemaining -= segLen;
2615
+ prevX = cx;
2616
+ prevY = cy;
2617
+ traveled = segFullLen;
2618
+ segLen = 0;
2619
+ if (dashRemaining <= 1e-9) {
2620
+ flushAndAdvance();
2621
+ if (onPhase) {
2622
+ curPts = [prevX, prevY];
2623
+ curA = [endA];
2624
+ curB = [endB];
2625
+ curT = [endT];
2626
+ }
2627
+ }
2628
+ } else {
2629
+ const tConsume = dashRemaining / segLen;
2630
+ const ix = prevX + segDx * tConsume;
2631
+ const iy = prevY + segDy * tConsume;
2632
+ traveled += dashRemaining;
2633
+ const frac = traveled / segFullLen;
2634
+ let mA, mB, mT;
2635
+ if (startA === endA && startB === endB) {
2636
+ mA = startA;
2637
+ mB = startB;
2638
+ mT = startT + (endT - startT) * frac;
2639
+ } else {
2640
+ if (frac < 0.5) {
2641
+ mA = startA;
2642
+ mB = startB;
2643
+ mT = startT;
2644
+ } else {
2645
+ mA = endA;
2646
+ mB = endB;
2647
+ mT = endT;
2648
+ }
2649
+ }
2650
+ if (onPhase && curPts) {
2651
+ curPts.push(ix, iy);
2652
+ curA.push(mA);
2653
+ curB.push(mB);
2654
+ curT.push(mT);
2655
+ }
2656
+ prevX = ix;
2657
+ prevY = iy;
2658
+ segDx = cx - prevX;
2659
+ segDy = cy - prevY;
2660
+ segLen = Math.hypot(segDx, segDy);
2661
+ flushAndAdvance();
2662
+ if (onPhase) {
2663
+ curPts = [prevX, prevY];
2664
+ curA = [mA];
2665
+ curB = [mB];
2666
+ curT = [mT];
2667
+ }
2668
+ }
2540
2669
  }
2541
2670
  }
2542
- return out;
2543
- }
2544
- function readNumbers(s) {
2545
- const out = [];
2546
- let m;
2547
- NUM_RE.lastIndex = 0;
2548
- while ((m = NUM_RE.exec(s)) !== null) {
2549
- const n = Number(m[0]);
2550
- if (Number.isFinite(n)) out.push(n);
2671
+ if (curPts && curPts.length >= 4) {
2672
+ out.push({
2673
+ points: curPts,
2674
+ closed: false,
2675
+ anchorA: new Uint32Array(curA),
2676
+ anchorB: new Uint32Array(curB),
2677
+ anchorT: new Float32Array(curT)
2678
+ });
2551
2679
  }
2552
2680
  return out;
2553
2681
  }
2554
- var ARG_COUNTS = {
2555
- M: 2,
2556
- L: 2,
2557
- H: 1,
2558
- V: 1,
2559
- C: 6,
2560
- S: 4,
2561
- Q: 4,
2562
- T: 2,
2563
- A: 7,
2564
- Z: 0
2565
- };
2566
- function pathFromD(d, onWarn) {
2567
- const tokens = tokenize(d);
2568
- const b = new PathBuilder();
2569
- const st = {
2570
- x: 0,
2571
- y: 0,
2572
- startX: 0,
2573
- startY: 0,
2574
- lastCubicCtrl: null,
2575
- lastQuadCtrl: null
2682
+ function makeSeg(ax, ay, bx, by, halfA, halfB) {
2683
+ const dx = bx - ax, dy = by - ay;
2684
+ const len = Math.hypot(dx, dy);
2685
+ if (len === 0) return null;
2686
+ return {
2687
+ ax,
2688
+ ay,
2689
+ bx,
2690
+ by,
2691
+ nxUnit: -dy / len,
2692
+ nyUnit: dx / len,
2693
+ halfA,
2694
+ halfB,
2695
+ len
2576
2696
  };
2577
- for (const { cmd, args } of tokens) {
2578
- const upper = cmd.toUpperCase();
2579
- const relative = cmd !== upper;
2580
- const argCount = ARG_COUNTS[upper];
2581
- if (argCount === void 0) {
2582
- onWarn?.(`unknown path command: ${cmd}`);
2583
- continue;
2584
- }
2585
- if (upper === "Z") {
2586
- b.close();
2587
- st.x = st.startX;
2588
- st.y = st.startY;
2589
- st.lastCubicCtrl = null;
2590
- st.lastQuadCtrl = null;
2591
- continue;
2592
- }
2593
- const groups = argCount === 0 ? 1 : Math.floor(args.length / argCount);
2594
- for (let g = 0; g < groups; g++) {
2595
- const a = args.slice(g * argCount, (g + 1) * argCount);
2596
- const effectiveUpper = upper === "M" && g > 0 ? "L" : upper;
2597
- runCommand(effectiveUpper, relative, a, st, b);
2598
- }
2599
- }
2600
- return b.build();
2601
2697
  }
2602
- function runCommand(upper, relative, a, st, b) {
2603
- const rx = relative ? st.x : 0;
2604
- const ry = relative ? st.y : 0;
2605
- switch (upper) {
2606
- case "M": {
2607
- const x = a[0] + rx;
2608
- const y = a[1] + ry;
2609
- b.moveTo(x, y);
2610
- st.x = x;
2611
- st.y = y;
2612
- st.startX = x;
2613
- st.startY = y;
2614
- st.lastCubicCtrl = null;
2615
- st.lastQuadCtrl = null;
2616
- return;
2617
- }
2618
- case "L": {
2619
- const x = a[0] + rx;
2620
- const y = a[1] + ry;
2621
- b.lineTo(x, y);
2622
- st.x = x;
2623
- st.y = y;
2624
- st.lastCubicCtrl = null;
2625
- st.lastQuadCtrl = null;
2626
- return;
2627
- }
2628
- case "H": {
2629
- const x = a[0] + (relative ? st.x : 0);
2630
- b.lineTo(x, st.y);
2631
- st.x = x;
2632
- st.lastCubicCtrl = null;
2633
- st.lastQuadCtrl = null;
2634
- return;
2635
- }
2636
- case "V": {
2637
- const y = a[0] + (relative ? st.y : 0);
2638
- b.lineTo(st.x, y);
2639
- st.y = y;
2640
- st.lastCubicCtrl = null;
2641
- st.lastQuadCtrl = null;
2642
- return;
2643
- }
2644
- case "C": {
2645
- const x1 = a[0] + rx, y1 = a[1] + ry;
2646
- const x2 = a[2] + rx, y2 = a[3] + ry;
2647
- const x = a[4] + rx, y = a[5] + ry;
2648
- b.curveTo(x1, y1, x2, y2, x, y);
2649
- st.x = x;
2650
- st.y = y;
2651
- st.lastCubicCtrl = { x: x2, y: y2 };
2652
- st.lastQuadCtrl = null;
2653
- return;
2654
- }
2655
- case "S": {
2656
- const ref = st.lastCubicCtrl ?? { x: st.x, y: st.y };
2657
- const x1 = 2 * st.x - ref.x;
2658
- const y1 = 2 * st.y - ref.y;
2659
- const x2 = a[0] + rx, y2 = a[1] + ry;
2660
- const x = a[2] + rx, y = a[3] + ry;
2661
- b.curveTo(x1, y1, x2, y2, x, y);
2662
- st.x = x;
2663
- st.y = y;
2664
- st.lastCubicCtrl = { x: x2, y: y2 };
2665
- st.lastQuadCtrl = null;
2666
- return;
2667
- }
2668
- case "Q": {
2669
- const x1 = a[0] + rx, y1 = a[1] + ry;
2670
- const x = a[2] + rx, y = a[3] + ry;
2671
- b.quadTo(x1, y1, x, y);
2672
- st.x = x;
2673
- st.y = y;
2674
- st.lastQuadCtrl = { x: x1, y: y1 };
2675
- st.lastCubicCtrl = null;
2676
- return;
2677
- }
2678
- case "T": {
2679
- const ref = st.lastQuadCtrl ?? { x: st.x, y: st.y };
2680
- const x1 = 2 * st.x - ref.x;
2681
- const y1 = 2 * st.y - ref.y;
2682
- const x = a[0] + rx, y = a[1] + ry;
2683
- b.quadTo(x1, y1, x, y);
2684
- st.x = x;
2685
- st.y = y;
2686
- st.lastQuadCtrl = { x: x1, y: y1 };
2687
- st.lastCubicCtrl = null;
2698
+ var DEFAULT_MITER_LIMIT = 10;
2699
+ var DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;
2700
+ function emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT) {
2701
+ const a = segs[j];
2702
+ const b = segs[(j + 1) % segs.length];
2703
+ const aBase = segBaseIdx[j];
2704
+ const bBase = segBaseIdx[(j + 1) % segs.length];
2705
+ const adx = a.bx - a.ax, ady = a.by - a.ay;
2706
+ const bdx = b.bx - b.ax, bdy = b.by - b.ay;
2707
+ const cross = adx * bdy - ady * bdx;
2708
+ if (cross === 0) return;
2709
+ const onPositive = cross > 0;
2710
+ const aOuterEnd = onPositive ? aBase + 3 : aBase + 2;
2711
+ const bOuterStart = onPositive ? bBase + 1 : bBase + 0;
2712
+ const nextSeg = (j + 1) % segs.length;
2713
+ const cornerSrc = segSrcIdx[nextSeg];
2714
+ const cornerA = plA[cornerSrc];
2715
+ const cornerB = plB[cornerSrc];
2716
+ const cornerT = plT[cornerSrc];
2717
+ const halfAtCorner = a.halfB;
2718
+ const taperA = Math.max(a.halfA, a.halfB) / Math.max(1e-9, Math.min(a.halfA, a.halfB));
2719
+ const taperB = Math.max(b.halfA, b.halfB) / Math.max(1e-9, Math.min(b.halfA, b.halfB));
2720
+ const forceBevelForVarying = taperA > varyingThreshold || taperB > varyingThreshold;
2721
+ if (join === "bevel" || forceBevelForVarying) {
2722
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
2723
+ return;
2724
+ }
2725
+ if (join === "miter") {
2726
+ const aOX = verts[aOuterEnd * 2], aOY = verts[aOuterEnd * 2 + 1];
2727
+ const bOX = verts[bOuterStart * 2], bOY = verts[bOuterStart * 2 + 1];
2728
+ const apex = lineLineIntersect(aOX, aOY, adx, ady, bOX, bOY, -bdx, -bdy);
2729
+ if (!apex) {
2730
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
2688
2731
  return;
2689
2732
  }
2690
- case "A": {
2691
- const rxA = Math.abs(a[0]);
2692
- const ryA = Math.abs(a[1]);
2693
- const xRot = a[2];
2694
- const largeArc = a[3] !== 0;
2695
- const sweep = a[4] !== 0;
2696
- const ex = a[5] + rx;
2697
- const ey = a[6] + ry;
2698
- arcToCubics(st.x, st.y, ex, ey, rxA, ryA, xRot, largeArc, sweep, b);
2699
- st.x = ex;
2700
- st.y = ey;
2701
- st.lastCubicCtrl = null;
2702
- st.lastQuadCtrl = null;
2733
+ const miterLen = Math.hypot(apex[0] - a.bx, apex[1] - a.by);
2734
+ if (miterLen > miterLimit * halfAtCorner) {
2735
+ emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
2703
2736
  return;
2704
2737
  }
2705
- default:
2706
- return;
2707
- }
2708
- }
2709
- function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep, b) {
2710
- if (x1 === x2 && y1 === y2) return;
2711
- if (rx === 0 || ry === 0) {
2712
- b.lineTo(x2, y2);
2713
- return;
2714
- }
2715
- const phi = xAxisRotationDeg * Math.PI / 180;
2716
- const cosPhi = Math.cos(phi);
2717
- const sinPhi = Math.sin(phi);
2718
- const dx = (x1 - x2) / 2;
2719
- const dy = (y1 - y2) / 2;
2720
- const x1p = cosPhi * dx + sinPhi * dy;
2721
- const y1p = -sinPhi * dx + cosPhi * dy;
2722
- let rxs = rx * rx;
2723
- let rys = ry * ry;
2724
- const x1ps = x1p * x1p;
2725
- const y1ps = y1p * y1p;
2726
- const lambda = x1ps / rxs + y1ps / rys;
2727
- let rxFixed = rx;
2728
- let ryFixed = ry;
2729
- if (lambda > 1) {
2730
- const sqrtLambda = Math.sqrt(lambda);
2731
- rxFixed = sqrtLambda * rx;
2732
- ryFixed = sqrtLambda * ry;
2733
- rxs = rxFixed * rxFixed;
2734
- rys = ryFixed * ryFixed;
2738
+ const apexIdx = verts.length / 2;
2739
+ verts.push(apex[0], apex[1]);
2740
+ anchorA.push(cornerA);
2741
+ anchorB.push(cornerB);
2742
+ anchorT.push(cornerT);
2743
+ const jIdx = verts.length / 2;
2744
+ verts.push(a.bx, a.by);
2745
+ anchorA.push(cornerA);
2746
+ anchorB.push(cornerB);
2747
+ anchorT.push(cornerT);
2748
+ if (onPositive) {
2749
+ idx.push(aOuterEnd, apexIdx, bOuterStart);
2750
+ idx.push(aOuterEnd, bOuterStart, jIdx);
2751
+ } else {
2752
+ idx.push(aOuterEnd, bOuterStart, apexIdx);
2753
+ idx.push(aOuterEnd, jIdx, bOuterStart);
2754
+ }
2755
+ return;
2735
2756
  }
2736
- const sign = largeArc === sweep ? -1 : 1;
2737
- const num = rxs * rys - rxs * y1ps - rys * x1ps;
2738
- const den = rxs * y1ps + rys * x1ps;
2739
- const factor = sign * Math.sqrt(Math.max(0, num / den));
2740
- const cxp = factor * (rxFixed * y1p) / ryFixed;
2741
- const cyp = factor * -(ryFixed * x1p) / rxFixed;
2742
- const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
2743
- const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
2744
- const angle = (ux, uy, vx, vy) => {
2745
- const dot = ux * vx + uy * vy;
2746
- const len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
2747
- let a = Math.acos(Math.max(-1, Math.min(1, dot / len)));
2748
- if (ux * vy - uy * vx < 0) a = -a;
2749
- return a;
2750
- };
2751
- const theta1 = angle(1, 0, (x1p - cxp) / rxFixed, (y1p - cyp) / ryFixed);
2752
- let dTheta = angle(
2753
- (x1p - cxp) / rxFixed,
2754
- (y1p - cyp) / ryFixed,
2755
- (-x1p - cxp) / rxFixed,
2756
- (-y1p - cyp) / ryFixed
2757
- );
2758
- if (!sweep && dTheta > 0) dTheta -= 2 * Math.PI;
2759
- else if (sweep && dTheta < 0) dTheta += 2 * Math.PI;
2760
- const segCount = Math.max(1, Math.ceil(Math.abs(dTheta) / (Math.PI / 2)));
2761
- const segAngle = dTheta / segCount;
2762
- const t = 4 / 3 * Math.tan(segAngle / 4);
2763
- let prevX = x1;
2764
- let prevY = y1;
2765
- let prevDx = -Math.sin(theta1) * rxFixed;
2766
- let prevDy = Math.cos(theta1) * ryFixed;
2767
- for (let i = 1; i <= segCount; i++) {
2768
- const theta = theta1 + i * segAngle;
2769
- const cosT = Math.cos(theta);
2770
- const sinT = Math.sin(theta);
2771
- const px = cosPhi * (cosT * rxFixed) - sinPhi * (sinT * ryFixed) + cx;
2772
- const py = sinPhi * (cosT * rxFixed) + cosPhi * (sinT * ryFixed) + cy;
2773
- const dxNew = -sinT * rxFixed;
2774
- const dyNew = cosT * ryFixed;
2775
- const c1x = prevX + cosPhi * (t * prevDx) - sinPhi * (t * prevDy);
2776
- const c1y = prevY + sinPhi * (t * prevDx) + cosPhi * (t * prevDy);
2777
- const c2x = px - cosPhi * (t * dxNew) + sinPhi * (t * dyNew);
2778
- const c2y = py - sinPhi * (t * dxNew) - cosPhi * (t * dyNew);
2779
- b.curveTo(c1x, c1y, c2x, c2y, px, py);
2780
- prevX = px;
2781
- prevY = py;
2782
- prevDx = dxNew;
2783
- prevDy = dyNew;
2757
+ if (join === "round") {
2758
+ emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
2759
+ return;
2784
2760
  }
2785
2761
  }
2786
-
2787
- // src/renderer/cache/outlineMeshCache.ts
2788
- var OUTLINE_FLATTEN_TOLERANCE = 1 / 4096;
2789
- var OUTLINE_MESH_CACHE_LIMIT = 2048;
2790
- var cache3 = /* @__PURE__ */ new Map();
2791
- function outlineMesh(key, d) {
2792
- const cached = cache3.get(key);
2793
- if (cached !== void 0) return cached;
2794
- if (cache3.size >= OUTLINE_MESH_CACHE_LIMIT) cache3 = /* @__PURE__ */ new Map();
2795
- const mesh = tessellate(pathFromD(d), { flattenTolerance: OUTLINE_FLATTEN_TOLERANCE });
2796
- cache3.set(key, mesh);
2797
- return mesh;
2762
+ var ROUND_STEP_RAD = 10 * Math.PI / 180;
2763
+ function emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
2764
+ const cx = a.bx, cy = a.by;
2765
+ const pivotIdx = verts.length / 2;
2766
+ verts.push(cx, cy);
2767
+ anchorA.push(cornerA);
2768
+ anchorB.push(cornerB);
2769
+ anchorT.push(cornerT);
2770
+ const startX = verts[aOuterEnd * 2] - cx;
2771
+ const startY = verts[aOuterEnd * 2 + 1] - cy;
2772
+ const endX = verts[bOuterStart * 2] - cx;
2773
+ const endY = verts[bOuterStart * 2 + 1] - cy;
2774
+ const r = Math.hypot(startX, startY);
2775
+ const startAngle = Math.atan2(startY, startX);
2776
+ const endAngle = Math.atan2(endY, endX);
2777
+ let sweep = endAngle - startAngle;
2778
+ if (onPositive && sweep < 0) sweep += 2 * Math.PI;
2779
+ else if (!onPositive && sweep > 0) sweep -= 2 * Math.PI;
2780
+ const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_STEP_RAD));
2781
+ const stepAngle = sweep / steps;
2782
+ let prevIdx = aOuterEnd;
2783
+ for (let i = 1; i < steps; i++) {
2784
+ const ang = startAngle + i * stepAngle;
2785
+ const fx = cx + Math.cos(ang) * r;
2786
+ const fy = cy + Math.sin(ang) * r;
2787
+ const newIdx = verts.length / 2;
2788
+ verts.push(fx, fy);
2789
+ anchorA.push(cornerA);
2790
+ anchorB.push(cornerB);
2791
+ anchorT.push(cornerT);
2792
+ if (onPositive) idx.push(prevIdx, pivotIdx, newIdx);
2793
+ else idx.push(prevIdx, newIdx, pivotIdx);
2794
+ prevIdx = newIdx;
2795
+ }
2796
+ if (onPositive) idx.push(prevIdx, pivotIdx, bOuterStart);
2797
+ else idx.push(prevIdx, bOuterStart, pivotIdx);
2798
+ }
2799
+ function emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT) {
2800
+ const jIdx = verts.length / 2;
2801
+ verts.push(a.bx, a.by);
2802
+ anchorA.push(cornerA);
2803
+ anchorB.push(cornerB);
2804
+ anchorT.push(cornerT);
2805
+ if (onPositive) idx.push(aOuterEnd, jIdx, bOuterStart);
2806
+ else idx.push(aOuterEnd, bOuterStart, jIdx);
2807
+ }
2808
+ function lineLineIntersect(apx, apy, adx, ady, bpx, bpy, bdx, bdy) {
2809
+ const denom = adx * bdy - ady * bdx;
2810
+ if (Math.abs(denom) < 1e-9) return null;
2811
+ const t = ((bpx - apx) * bdy - (bpy - apy) * bdx) / denom;
2812
+ return [apx + t * adx, apy + t * ady];
2798
2813
  }
2799
2814
 
2800
2815
  // src/renderer/cache/outlineStrokeMeshCache.ts
@@ -2827,6 +2842,38 @@ function outlineStrokeMesh(glyphKey, d, emWidth, stroke) {
2827
2842
  return mesh.indices.length === 0 ? null : mesh;
2828
2843
  }
2829
2844
 
2845
+ // src/renderer/cache/strokeMeshCache.ts
2846
+ var STROKE_CONFIGS_PER_PATH = 8;
2847
+ var cache5 = /* @__PURE__ */ new WeakMap();
2848
+ function configKey(stroke, flattenTolerance) {
2849
+ return [
2850
+ stroke.width ?? 1,
2851
+ stroke.cap ?? "butt",
2852
+ stroke.join ?? "miter",
2853
+ stroke.miterLimit ?? "",
2854
+ stroke.align ?? "center",
2855
+ (stroke.dash ?? []).join(","),
2856
+ stroke.varyingWidthJoinThreshold ?? "",
2857
+ flattenTolerance ?? ""
2858
+ ].join("|");
2859
+ }
2860
+ function strokeMesh(path, stroke, flattenTolerance) {
2861
+ let byConfig = cache5.get(path);
2862
+ if (byConfig === void 0) {
2863
+ byConfig = /* @__PURE__ */ new Map();
2864
+ cache5.set(path, byConfig);
2865
+ }
2866
+ const key = configKey(stroke, flattenTolerance);
2867
+ const entry = byConfig.get(key);
2868
+ if (entry !== void 0 && entry.vertexWidths === stroke.vertexWidths) {
2869
+ return entry.mesh;
2870
+ }
2871
+ const mesh = tessellateStroke(path, stroke, { flattenTolerance });
2872
+ if (entry === void 0 && byConfig.size >= STROKE_CONFIGS_PER_PATH) byConfig.clear();
2873
+ byConfig.set(key, { mesh, vertexWidths: stroke.vertexWidths });
2874
+ return mesh;
2875
+ }
2876
+
2830
2877
  // src/renderer/solidBatch.ts
2831
2878
  var MAX_VERTICES_PER_BATCH = 32768;
2832
2879
  var FLOATS_PER_VERTEX = 6;
@@ -3231,44 +3278,40 @@ function drawGroup(ctx, cmd) {
3231
3278
  "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."
3232
3279
  );
3233
3280
  }
3234
- flushSolids(ctx);
3235
3281
  pushClip(ctx, cmd.clip, newDepth);
3236
3282
  ctx.clipDepth = newDepth;
3237
3283
  }
3238
3284
  for (const child of cmd.children) dispatch(ctx, child);
3239
3285
  if (cmd.clip) {
3240
- flushSolids(ctx);
3241
3286
  popClip(ctx, cmd.clip, ctx.clipDepth - 1);
3242
3287
  ctx.clipDepth -= 1;
3243
3288
  }
3244
3289
  ctx.state.pop();
3245
3290
  }
3246
3291
  function drawPath(ctx, cmd) {
3247
- if (!cmd.fill && !cmd.stroke) return;
3248
- if (cmd.fill) {
3249
- const batchablePaint = (cmd.fill.fill === void 0 || cmd.fill.fill === "solid") && (!cmd.vertexColors || cmd.vertexColors.length === 0);
3250
- const solid = cmd.fill;
3251
- if (batchablePaint && cmd.path.kind === "rect") {
3252
- pushRect(ctx, cmd.path, solid);
3253
- } else {
3254
- const mesh = fillMesh(ctx, cmd.path);
3255
- if (batchablePaint && canBatchMesh(mesh)) {
3256
- pushMesh(ctx, mesh, solid);
3257
- } else {
3258
- flushSolids(ctx);
3259
- const handle = meshHandle(ctx, mesh);
3260
- if (cmd.vertexColors && cmd.vertexColors.length > 0 && (cmd.fill.fill === void 0 || cmd.fill.fill === "solid")) {
3261
- drawPathFillVColor(ctx, cmd, solid, handle);
3262
- } else if (handle.requiresStencil) {
3263
- drawPathFillStencil(ctx, cmd.fill, handle);
3264
- } else {
3265
- drawPathFillByKind(ctx, cmd.fill, handle);
3266
- }
3267
- }
3268
- }
3269
- }
3292
+ if (cmd.fill) drawPathFill(ctx, cmd);
3270
3293
  if (cmd.stroke) drawPathStroke(ctx, cmd);
3271
3294
  }
3295
+ function drawPathFill(ctx, cmd) {
3296
+ const fill = cmd.fill;
3297
+ const isSolid = fill.fill === void 0 || fill.fill === "solid";
3298
+ const hasVColors = !!(cmd.vertexColors && cmd.vertexColors.length > 0);
3299
+ const solid = fill;
3300
+ if (isSolid && !hasVColors && cmd.path.kind === "rect") {
3301
+ pushRect(ctx, cmd.path, solid);
3302
+ return;
3303
+ }
3304
+ const mesh = fillMesh(ctx, cmd.path);
3305
+ if (tryStageSolid(ctx, mesh, isSolid && !hasVColors ? solid : void 0)) return;
3306
+ const handle = meshHandle(ctx, mesh);
3307
+ if (isSolid && hasVColors) {
3308
+ drawPathFillVColor(ctx, cmd, solid, handle);
3309
+ } else if (handle.requiresStencil) {
3310
+ drawPathFillStencil(ctx, fill, handle);
3311
+ } else {
3312
+ drawPathFillByKind(ctx, fill, handle);
3313
+ }
3314
+ }
3272
3315
  function isIdentityColorMatrix(cm) {
3273
3316
  return cm === IDENTITY_COLOR_MATRIX || sameValues(IDENTITY_COLOR_MATRIX, cm);
3274
3317
  }
@@ -3321,6 +3364,14 @@ function pushMesh(ctx, mesh, paint) {
3321
3364
  const [r, g, b, a] = stagedColor(ctx, staged, paint);
3322
3365
  ctx.solidBatch.pushMesh(mesh, ctx.state.transform, r, g, b, a);
3323
3366
  }
3367
+ function tryStageSolid(ctx, mesh, paint) {
3368
+ if (paint !== void 0 && canBatchMesh(mesh)) {
3369
+ pushMesh(ctx, mesh, paint);
3370
+ return true;
3371
+ }
3372
+ flushSolids(ctx);
3373
+ return false;
3374
+ }
3324
3375
  function flushSolids(ctx) {
3325
3376
  const batch = ctx.solidBatch;
3326
3377
  const staged = ctx.solidState;
@@ -3524,6 +3575,7 @@ function rasterizePathToStencil(ctx, path) {
3524
3575
  gl.bindVertexArray(null);
3525
3576
  }
3526
3577
  function pushClip(ctx, path, newDepth) {
3578
+ flushSolids(ctx);
3527
3579
  const gl = ctx.gl;
3528
3580
  const ancestors = ancestorMask(newDepth - 1);
3529
3581
  const newBit = 1 << newDepth;
@@ -3538,6 +3590,7 @@ function pushClip(ctx, path, newDepth) {
3538
3590
  gl.stencilMask(255);
3539
3591
  }
3540
3592
  function popClip(ctx, path, oldDepth) {
3593
+ flushSolids(ctx);
3541
3594
  const gl = ctx.gl;
3542
3595
  gl.enable(gl.STENCIL_TEST);
3543
3596
  const oldBit = 1 << oldDepth + 1;
@@ -3594,17 +3647,13 @@ function drawPathStroke(ctx, cmd) {
3594
3647
  function drawPathStrokeUnclipped(ctx, cmd) {
3595
3648
  const stroke = cmd.stroke;
3596
3649
  const solid = stroke.paint;
3597
- const mesh = tessellateStroke(cmd.path, stroke, { flattenTolerance: ctx.flattenTolerance });
3650
+ const mesh = strokeMesh(cmd.path, stroke, ctx.flattenTolerance);
3598
3651
  if (mesh.indices.length === 0) return;
3599
3652
  const hasVColors = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
3600
- if (!hasVColors && canBatchMesh(mesh)) {
3601
- pushMesh(ctx, mesh, solid);
3602
- return;
3603
- }
3604
- flushSolids(ctx);
3605
- const handle = ctx.meshCache.uploadTransient(mesh);
3653
+ if (tryStageSolid(ctx, mesh, hasVColors ? void 0 : solid)) return;
3654
+ const handle = hasVColors ? ctx.meshCache.uploadTransient(mesh) : ctx.meshCache.uploadRecurring(mesh);
3606
3655
  const gl = ctx.gl;
3607
- if (stroke.vertexColors && stroke.vertexColors.length > 0) {
3656
+ if (hasVColors) {
3608
3657
  const prog = ctx.pathFillVColor;
3609
3658
  gl.useProgram(prog.handle);
3610
3659
  gl.bindVertexArray(handle.vao);
@@ -3635,16 +3684,30 @@ function drawPathStrokeUnclipped(ctx, cmd) {
3635
3684
  gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
3636
3685
  gl.bindVertexArray(null);
3637
3686
  }
3687
+ var DOUBLED_VERTEX_WIDTHS = /* @__PURE__ */ new WeakMap();
3688
+ function doubledVertexWidths(widths) {
3689
+ let doubled = DOUBLED_VERTEX_WIDTHS.get(widths);
3690
+ if (doubled === void 0) {
3691
+ doubled = widths.map((w) => w * 2);
3692
+ DOUBLED_VERTEX_WIDTHS.set(widths, doubled);
3693
+ }
3694
+ return doubled;
3695
+ }
3638
3696
  function drawPathStrokeStenciled(ctx, cmd, align) {
3639
3697
  const stroke = cmd.stroke;
3640
3698
  const solid = stroke.paint;
3641
- const widerStroke = { ...stroke, width: (stroke.width ?? 1) * 2, align: "center" };
3699
+ const widerStroke = {
3700
+ ...stroke,
3701
+ width: (stroke.width ?? 1) * 2,
3702
+ ...stroke.vertexWidths ? { vertexWidths: doubledVertexWidths(stroke.vertexWidths) } : {},
3703
+ align: "center"
3704
+ };
3705
+ const useVColor = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
3642
3706
  const fillHandle = fillMeshHandle(ctx, cmd.path);
3643
- const ribbonMesh = tessellateStroke(cmd.path, widerStroke, { flattenTolerance: ctx.flattenTolerance });
3707
+ const ribbonMesh = strokeMesh(cmd.path, widerStroke, ctx.flattenTolerance);
3644
3708
  if (ribbonMesh.indices.length === 0) return;
3645
- const ribbonHandle = ctx.meshCache.uploadTransient(ribbonMesh);
3709
+ const ribbonHandle = useVColor ? ctx.meshCache.uploadTransient(ribbonMesh) : ctx.meshCache.uploadRecurring(ribbonMesh);
3646
3710
  const gl = ctx.gl;
3647
- const useVColor = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
3648
3711
  const prog = useVColor ? ctx.pathFillVColor : ctx.pathFill;
3649
3712
  gl.useProgram(prog.handle);
3650
3713
  setProjAndModel(ctx, prog);
@@ -3680,11 +3743,11 @@ function drawPathStrokeStenciled(ctx, cmd, align) {
3680
3743
  }
3681
3744
  }
3682
3745
  gl.drawElements(gl.TRIANGLES, ribbonHandle.indexCount, gl.UNSIGNED_INT, 0);
3683
- if (colorVbo) gl.deleteBuffer(colorVbo);
3684
3746
  gl.stencilMask(1);
3685
3747
  gl.clear(gl.STENCIL_BUFFER_BIT);
3686
3748
  gl.disable(gl.STENCIL_TEST);
3687
3749
  gl.bindVertexArray(null);
3750
+ if (colorVbo) gl.deleteBuffer(colorVbo);
3688
3751
  }
3689
3752
  var OUTLINE_MIN_SCREEN_PX = 48;
3690
3753
  function modelScale(m) {
@@ -3733,9 +3796,9 @@ function drawTextOutlineGroup(ctx, group, dx, dy) {
3733
3796
  const mesh = outlineGroupMesh(group, dx, dy);
3734
3797
  if (mesh) drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
3735
3798
  if (!group.stroke) return;
3736
- const strokeMesh = outlineGroupStrokeMesh(group, dx, dy);
3737
- if (strokeMesh) {
3738
- drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(strokeMesh));
3799
+ const ribbon = outlineGroupStrokeMesh(group, dx, dy);
3800
+ if (ribbon) {
3801
+ drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(ribbon));
3739
3802
  }
3740
3803
  }
3741
3804
  function outlineGroupMesh(group, dx, dy) {
@@ -4075,16 +4138,28 @@ function registerProgram(id, vert, frag) {
4075
4138
  }
4076
4139
 
4077
4140
  // src/renderer/WeaselRenderer.ts
4141
+ var UNIFORM_QUALIFIER = /^(?:lowp|mediump|highp|precise|invariant|centroid|flat|smooth|noperspective)$/;
4078
4142
  function extractUniformNames(glsl) {
4079
- const reScalar = /\buniform\s+\S+\s+(\w+)\s*;/g;
4080
- const reArray = /\buniform\s+\S+\s+(\w+)\s*\[\s*(\d+)\s*\]\s*;/g;
4143
+ const re = /\buniform\s+([^;{]+);/g;
4081
4144
  const names = [];
4082
4145
  let m;
4083
- while ((m = reScalar.exec(glsl)) !== null) names.push(m[1]);
4084
- while ((m = reArray.exec(glsl)) !== null) {
4085
- const name = m[1];
4086
- const size = parseInt(m[2], 10);
4087
- for (let i = 0; i < size; i++) names.push(`${name}[${i}]`);
4146
+ while ((m = re.exec(glsl)) !== null) {
4147
+ const words = m[1].trim().split(/\s+/);
4148
+ let i = 0;
4149
+ while (i < words.length && UNIFORM_QUALIFIER.test(words[i])) i++;
4150
+ i++;
4151
+ const declarators = words.slice(i).join(" ");
4152
+ if (!declarators) continue;
4153
+ for (const raw of declarators.split(",")) {
4154
+ const d = raw.trim();
4155
+ const arr = /^(\w+)\s*\[\s*(\d+)\s*\]$/.exec(d);
4156
+ if (arr) {
4157
+ const size = parseInt(arr[2], 10);
4158
+ for (let k = 0; k < size; k++) names.push(`${arr[1]}[${k}]`);
4159
+ } else if (/^\w+$/.test(d)) {
4160
+ names.push(d);
4161
+ }
4162
+ }
4088
4163
  }
4089
4164
  return names;
4090
4165
  }
@@ -4613,6 +4688,14 @@ function createViewportLayer(opts) {
4613
4688
  id,
4614
4689
  label,
4615
4690
  space: "screen",
4691
+ reproject(outer, dims, screen) {
4692
+ const b = bounds(outer, dims);
4693
+ if (screen.x < b.x || screen.x >= b.x + b.w || screen.y < b.y || screen.y >= b.y + b.h) return null;
4694
+ return {
4695
+ x: (screen.x - b.x) / view.scale.x + view.x,
4696
+ y: (screen.y - b.y) / view.scale.y + view.y
4697
+ };
4698
+ },
4616
4699
  draw: (data, outerView, dims) => {
4617
4700
  const b = bounds(outerView, dims);
4618
4701
  const children = [];
@@ -4639,6 +4722,14 @@ function createViewportLayer(opts) {
4639
4722
  }
4640
4723
  };
4641
4724
  }
4725
+ function viewportsAt(layers, outer, dims, screen) {
4726
+ for (let i = layers.length - 1; i >= 0; i--) {
4727
+ const layer = layers[i];
4728
+ const point = layer.reproject(outer, dims, screen);
4729
+ if (point) return { layer, point };
4730
+ }
4731
+ return null;
4732
+ }
4642
4733
 
4643
4734
  // src/features/parallax/deriveParallaxView.ts
4644
4735
  function asXY(v) {
@@ -5772,6 +5863,12 @@ function runLayoutPass(scratch, moveCtx) {
5772
5863
  if (layout2.contains) return layout2.contains(cPose, draggedCenter);
5773
5864
  return draggedCenter.x >= aabb.x && draggedCenter.x < aabb.x + aabb.width && draggedCenter.y >= aabb.y && draggedCenter.y < aabb.y + aabb.height;
5774
5865
  };
5866
+ const draggedArg = {
5867
+ id: draggedId,
5868
+ originPose: startWorld,
5869
+ pose: draggedWorld,
5870
+ sourceContainerId
5871
+ };
5775
5872
  const consider = (id, zPath) => {
5776
5873
  if (id === draggedId) return;
5777
5874
  const layout2 = layoutDep.getLayout(id);
@@ -5781,6 +5878,7 @@ function runLayoutPass(scratch, moveCtx) {
5781
5878
  const worldPose = composeWorldPose(poseAdapter, id, pc.compose);
5782
5879
  const worldAABB = boundsOf2(worldPose);
5783
5880
  if (!testInside(worldPose, worldAABB, layout2)) return;
5881
+ if (layout2.acceptsDrop && !layout2.acceptsDrop({ id, bounds: worldAABB }, draggedArg)) return;
5784
5882
  candidates.push({
5785
5883
  id,
5786
5884
  bounds: worldAABB,
@@ -5829,20 +5927,16 @@ function runLayoutPass(scratch, moveCtx) {
5829
5927
  id: cid,
5830
5928
  pose: composeWorldPose(poseAdapter, cid, pc.compose)
5831
5929
  }));
5832
- const draggedArg = {
5833
- id: draggedId,
5834
- originPose: startWorld,
5835
- pose: draggedWorld,
5836
- sourceContainerId
5837
- };
5838
5930
  const targets = layout.getDropTargets(container, children, draggedArg);
5839
5931
  const target = layout.snap.pickTarget(targets, { x: moveCtx.drag.current.x, y: moveCtx.drag.current.y });
5840
5932
  if (!target) return;
5933
+ const reflowIds = /* @__PURE__ */ new Set();
5841
5934
  for (const [cid, pose] of layout.reflowPoses(container, children, draggedArg, target)) {
5842
5935
  if (asNodeId(cid) === draggedId) continue;
5843
5936
  const parent = scene.get(asNodeId(cid))?.parent ?? null;
5844
5937
  const local = rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose);
5845
5938
  scratch.previews.set(asNodeId(cid), local);
5939
+ reflowIds.add(asNodeId(cid));
5846
5940
  }
5847
5941
  const sourceReflow = /* @__PURE__ */ new Map();
5848
5942
  if (sourceContainerId && sourceContainerId !== dest.id) {
@@ -5865,10 +5959,11 @@ function runLayoutPass(scratch, moveCtx) {
5865
5959
  sourceReflow.set(cid, pose);
5866
5960
  const parent = scene.get(asNodeId(cid))?.parent ?? null;
5867
5961
  scratch.previews.set(asNodeId(cid), rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose));
5962
+ reflowIds.add(asNodeId(cid));
5868
5963
  }
5869
5964
  }
5870
5965
  }
5871
- scratch.layoutPass = { layout, container, children, target, sourceReflow };
5966
+ scratch.layoutPass = { layout, container, children, target, sourceReflow, reflowIds };
5872
5967
  }
5873
5968
  function scenePoseAdapter(scene) {
5874
5969
  return {
@@ -6176,7 +6271,8 @@ var moveAction = {
6176
6271
  scratch.previews.clear();
6177
6272
  },
6178
6273
  previewIds: () => scratch.previews.keys(),
6179
- previewPose: (id) => scratch.previews.get(id) ?? null
6274
+ previewPose: (id) => scratch.previews.get(id) ?? null,
6275
+ previewOpaqueIds: () => scratch.layoutPass?.reflowIds ?? null
6180
6276
  };
6181
6277
  }
6182
6278
  },
@@ -6945,7 +7041,8 @@ var KIT_INSERT_KINDS = /* @__PURE__ */ new Set([
6945
7041
  "line",
6946
7042
  "polygon",
6947
7043
  "star",
6948
- "pencil"
7044
+ "pencil",
7045
+ "image"
6949
7046
  ]);
6950
7047
  var liveInsertScratch = null;
6951
7048
  function effectiveOriginMode(paramsOrigin, altHeld) {
@@ -8074,7 +8171,7 @@ var pinchZoomAction = {
8074
8171
  const multiTouch = ctx.multiTouch;
8075
8172
  if (!multiTouch) return {};
8076
8173
  const startSpread = multiTouch.spread > 0 ? multiTouch.spread : 1;
8077
- const scratch = { view, startSpread };
8174
+ const scratch = { view, startSpread, centroid: multiTouch.centroid };
8078
8175
  return {
8079
8176
  kind: "pinch",
8080
8177
  onMove(moveCtx) {
@@ -8084,8 +8181,15 @@ var pinchZoomAction = {
8084
8181
  if (currentSpread <= 0 || scratch.startSpread <= 0) return;
8085
8182
  const factor = currentSpread / scratch.startSpread;
8086
8183
  scratch.startSpread = currentSpread;
8184
+ const prev = scratch.centroid;
8185
+ scratch.centroid = centroid;
8087
8186
  const current = scratch.view.get();
8088
- scratch.view.set(zoomAt(current, centroid, factor));
8187
+ const zoomed = zoomAt(current, prev, factor);
8188
+ scratch.view.set({
8189
+ scale: zoomed.scale,
8190
+ x: zoomed.x - (centroid.x - prev.x) / zoomed.scale.x,
8191
+ y: zoomed.y - (centroid.y - prev.y) / zoomed.scale.y
8192
+ });
8089
8193
  }
8090
8194
  // onEnd: nothing to do — zoom was applied each frame.
8091
8195
  };
@@ -8569,10 +8673,12 @@ function normalizeNegativeExtent(pose) {
8569
8673
  }
8570
8674
 
8571
8675
  // src/interactions/actions/defaults/flip.ts
8572
- function flipSelection(selection, scene, axis, applyOps, geometryProjection) {
8676
+ function flipSelection(selection, scene, axis, pivot, applyOps, geometryProjection) {
8573
8677
  const ids = selection.get();
8574
8678
  if (ids.length === 0) return;
8575
8679
  const geom = AUTO_POSE_DESCRIPTOR;
8680
+ const nodes = ids.map((id) => scene.get(id)).filter((n) => n != null);
8681
+ const unionPivot = pivot === "union" ? unionBounds(nodes.map((n) => geom.getBounds(n.pose))) : null;
8576
8682
  const ops = [];
8577
8683
  for (const id of ids) {
8578
8684
  const node = scene.get(id);
@@ -8580,10 +8686,10 @@ function flipSelection(selection, scene, axis, applyOps, geometryProjection) {
8580
8686
  ops.push(createTransformOp({
8581
8687
  id,
8582
8688
  from: node.pose,
8583
- to: flipPoseViaDescriptor(node.pose, axis, geom),
8689
+ to: unionPivot ? flipPoseAboutBounds(node.pose, axis, geom, unionPivot) : flipPoseViaDescriptor(node.pose, axis, geom),
8584
8690
  label: "Flip"
8585
8691
  }));
8586
- const g = geom.getBounds(node.pose);
8692
+ const g = unionPivot ?? geom.getBounds(node.pose);
8587
8693
  const cx = g.x + g.width / 2;
8588
8694
  const cy = g.y + g.height / 2;
8589
8695
  const m = axis === "x" ? [-1, 0, 0, 1, 2 * cx, 0] : [1, 0, 0, -1, 0, 2 * cy];
@@ -8612,12 +8718,13 @@ var flipAction = {
8612
8718
  timing: "immediate",
8613
8719
  run: (deps, params) => {
8614
8720
  const axis = params?.axis ?? "x";
8721
+ const pivot = params?.pivot ?? "each";
8615
8722
  const selection = deps.selection;
8616
8723
  const scene = deps.scene;
8617
8724
  const applyOps = deps.applyOps;
8618
8725
  const geometryProjection = deps.geometryProjection;
8619
8726
  if (!selection || !scene) return;
8620
- flipSelection(selection, scene, axis, applyOps, geometryProjection);
8727
+ flipSelection(selection, scene, axis, pivot, applyOps, geometryProjection);
8621
8728
  }
8622
8729
  },
8623
8730
  enabled: requiresSelection
@@ -10566,6 +10673,12 @@ var DEFAULT_DEBUG_THEME = {
10566
10673
  fpsText: "#e0e0e0",
10567
10674
  fpsTextBg: "rgba(0, 0, 0, 0.6)"
10568
10675
  };
10676
+ var DEFAULT_DEBUG_STROKES = {
10677
+ hitbox: { width: 1, dash: [2, 2] },
10678
+ bounds: { width: 1 },
10679
+ handle: { width: 1 },
10680
+ snap: { width: 1 }
10681
+ };
10569
10682
 
10570
10683
  // src/debug/createDebugOverlayLayer.ts
10571
10684
  function createDebugOverlayLayer({
@@ -10573,6 +10686,7 @@ function createDebugOverlayLayer({
10573
10686
  config
10574
10687
  }) {
10575
10688
  const theme = { ...DEFAULT_DEBUG_THEME, ...config.theme ?? {} };
10689
+ const strokes = { ...DEFAULT_DEBUG_STROKES, ...config.strokes ?? {} };
10576
10690
  const fpsHistory = [];
10577
10691
  const FPS_WINDOW = 60;
10578
10692
  return {
@@ -10584,11 +10698,11 @@ function createDebugOverlayLayer({
10584
10698
  const s = sink.snapshot();
10585
10699
  const t = viewToTransform(view);
10586
10700
  const out = [];
10587
- if (config.hitboxes) emitHitboxes(out, s, view, t, theme);
10588
- if (config.bounds) emitBounds(out, s, view, t, theme);
10589
- if (config.handles) emitHandles(out, s, t, theme);
10701
+ if (config.hitboxes) emitHitboxes(out, s, view, t, theme, strokes);
10702
+ if (config.bounds) emitBounds(out, s, view, t, theme, strokes);
10703
+ if (config.handles) emitHandles(out, s, t, theme, strokes);
10590
10704
  if (config.origins) emitOrigins(out, s, t, theme);
10591
- if (config.snap) emitSnap(out, s, t, theme);
10705
+ if (config.snap) emitSnap(out, s, t, theme, strokes);
10592
10706
  if (config.ids) emitIds(out, s, t, theme);
10593
10707
  if (config.layers) emitLayersPanel(out, s, dims, theme);
10594
10708
  if (config.fps) {
@@ -10621,9 +10735,16 @@ function approxCircleScreen(cx, cy, r, segments = 24) {
10621
10735
  function rectPath2(x, y, w, h) {
10622
10736
  return { kind: "rect", x, y, width: w, height: h };
10623
10737
  }
10624
- function emitHitboxes(out, s, view, t, theme) {
10738
+ function strokeOf(s, color) {
10739
+ return {
10740
+ paint: { fill: "solid", color },
10741
+ width: s.width,
10742
+ ...s.dash && s.dash.length > 0 ? { dash: [...s.dash] } : {}
10743
+ };
10744
+ }
10745
+ function emitHitboxes(out, s, view, t, theme, strokes) {
10625
10746
  const fill = { fill: "solid", color: theme.hitboxFill };
10626
- const stroke = { paint: { fill: "solid", color: theme.hitboxStroke }, width: 1, dash: [2, 2] };
10747
+ const stroke = strokeOf(strokes.hitbox, theme.hitboxStroke);
10627
10748
  for (const h of s.hitboxes) {
10628
10749
  if (h.shape.kind === "rect") {
10629
10750
  const [sx, sy] = worldToScreen(h.shape.x, h.shape.y, t);
@@ -10637,8 +10758,8 @@ function emitHitboxes(out, s, view, t, theme) {
10637
10758
  }
10638
10759
  }
10639
10760
  }
10640
- function emitBounds(out, s, view, t, theme) {
10641
- const stroke = { paint: { fill: "solid", color: theme.bounds }, width: 1 };
10761
+ function emitBounds(out, s, view, t, theme, strokes) {
10762
+ const stroke = strokeOf(strokes.bounds, theme.bounds);
10642
10763
  for (const b of s.bounds) {
10643
10764
  const [sx, sy] = worldToScreen(b.bounds.x, b.bounds.y, t);
10644
10765
  const sw = b.bounds.width * view.scale.x;
@@ -10646,8 +10767,8 @@ function emitBounds(out, s, view, t, theme) {
10646
10767
  out.push({ kind: "path", path: rectPath2(sx, sy, sw, sh), stroke });
10647
10768
  }
10648
10769
  }
10649
- function emitHandles(out, s, t, theme) {
10650
- const stroke = { paint: { fill: "solid", color: theme.handle }, width: 1 };
10770
+ function emitHandles(out, s, t, theme, strokes) {
10771
+ const stroke = strokeOf(strokes.handle, theme.handle);
10651
10772
  for (const h of s.handles) {
10652
10773
  const [cx, cy] = worldToScreen(h.position.x, h.position.y, t);
10653
10774
  const horiz = {
@@ -10673,14 +10794,14 @@ function emitOrigins(out, s, t, theme) {
10673
10794
  out.push({ kind: "path", path: approxCircleScreen(cx, cy, 3), fill });
10674
10795
  }
10675
10796
  }
10676
- function emitSnap(out, s, t, theme) {
10797
+ function emitSnap(out, s, t, theme, strokes) {
10677
10798
  const color = theme.snap;
10678
10799
  for (const c of s.snap) {
10679
10800
  const [cx, cy] = worldToScreen(c.point.x, c.point.y, t);
10680
10801
  const cmd = {
10681
10802
  kind: "path",
10682
10803
  path: approxCircleScreen(cx, cy, 4),
10683
- ...c.accepted ? { fill: { fill: "solid", color } } : { stroke: { paint: { fill: "solid", color }, width: 1 } }
10804
+ ...c.accepted ? { fill: { fill: "solid", color } } : { stroke: strokeOf(strokes.snap, color) }
10684
10805
  };
10685
10806
  out.push(cmd);
10686
10807
  }
@@ -11063,11 +11184,22 @@ function reportDeadClaim(owner, warn) {
11063
11184
  `[weasel] exclusive claim by "${key}" matched no binding: no \`affordance:\` or \`kindOf\` target resolved against it, so the press was dropped.`
11064
11185
  );
11065
11186
  }
11187
+ function phaseRank(spec) {
11188
+ if (spec === void 0) return 0;
11189
+ const atoms = typeof spec === "string" ? [{ channel: "&", phase: spec }] : spec;
11190
+ if (atoms.length === 0) return 0;
11191
+ let min = 2;
11192
+ for (const a of atoms) {
11193
+ const rank = (a.channel === "*" ? 0 : 1) + (a.phase === "*" ? 0 : 1);
11194
+ if (rank < min) min = rank;
11195
+ }
11196
+ return min;
11197
+ }
11066
11198
  function specificity(spec) {
11067
11199
  const t = targetRank(specTargetOf(spec));
11068
11200
  const mods = "mods" in spec ? spec.mods : void 0;
11069
11201
  const m = modsCount(mods);
11070
- const p = "phase" in spec && spec.phase !== void 0 ? 1 : 0;
11202
+ const p = phaseRank("phase" in spec ? spec.phase : void 0);
11071
11203
  const typed = (spec.kind === "drop" || spec.kind === "paste") && spec.types !== void 0 && spec.types.length > 0 ? 2 : 1;
11072
11204
  return [t, m, p, typed];
11073
11205
  }
@@ -12727,8 +12859,7 @@ function useGestureDispatcher(opts) {
12727
12859
  if (!cd) return;
12728
12860
  const items = itemsFromClipboardData(cd);
12729
12861
  if (items.length === 0) return;
12730
- e.preventDefault();
12731
- dispatch2({
12862
+ const result = dispatch2({
12732
12863
  kind: "paste",
12733
12864
  items,
12734
12865
  // ClipboardEvent carries no modifier state.
@@ -12737,6 +12868,7 @@ function useGestureDispatcher(opts) {
12737
12868
  metaKey: false,
12738
12869
  shiftKey: false
12739
12870
  });
12871
+ if (result === "handled") e.preventDefault();
12740
12872
  };
12741
12873
  if (keyboard) {
12742
12874
  window.addEventListener("keydown", onKeyDown);
@@ -13080,6 +13212,52 @@ function useViewAnimation(setView) {
13080
13212
  );
13081
13213
  return { animateTo, animateToBounds, cancel };
13082
13214
  }
13215
+ function validateId(id, kind) {
13216
+ if (id.length === 0) {
13217
+ throw new Error(`weasel: ${kind} id may not be empty`);
13218
+ }
13219
+ if (RESERVED_ID_PREFIXES.has(id[0])) {
13220
+ throw new Error(
13221
+ `weasel: ${kind} id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
13222
+ );
13223
+ }
13224
+ if (RESERVED_ID_NAMES.has(id)) {
13225
+ throw new Error(
13226
+ `weasel: ${kind} id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
13227
+ );
13228
+ }
13229
+ }
13230
+ function defineTool(def) {
13231
+ validateId(def.id, "tool");
13232
+ const resolveCursor = (ctx) => {
13233
+ if (def.cursor == null) return "";
13234
+ return typeof def.cursor === "function" ? def.cursor(ctx) : def.cursor;
13235
+ };
13236
+ return {
13237
+ id: def.id,
13238
+ eligibility: {
13239
+ focus: true,
13240
+ capabilities: def.capabilities,
13241
+ ...def.hotkey ? { offhand: def.hotkey } : {}
13242
+ },
13243
+ actions: def.actions,
13244
+ def,
13245
+ presentation: def.presentation,
13246
+ keybinding: def.keybinding,
13247
+ onActivate: def.onActivate,
13248
+ onDeactivate: def.onDeactivate,
13249
+ initScratch: def.initScratch ?? (() => null),
13250
+ cursor: resolveCursor,
13251
+ bindings: def.bindings,
13252
+ overlay: def.overlay,
13253
+ overlayPosition: def.overlayPosition
13254
+ };
13255
+ }
13256
+
13257
+ // src/tools/defineViewportTool.ts
13258
+ function defineViewportTool(def) {
13259
+ return defineTool(def);
13260
+ }
13083
13261
 
13084
13262
  // src/interactions/actions/defaults/toolOffhand.ts
13085
13263
  var TOOL_OFFHAND_ID = "tool.offhand";
@@ -13174,17 +13352,19 @@ function useContributions(opts) {
13174
13352
  const { ordered, state } = snapshot();
13175
13353
  return scopeBindings(ordered, state);
13176
13354
  }, [snapshot]);
13177
- const overlays = useCallback(() => {
13355
+ const overlays = useCallback((position = "top") => {
13178
13356
  const { ordered, state } = snapshot();
13179
13357
  const active = [];
13180
13358
  const hotkey = [];
13181
13359
  const ambient = [];
13182
13360
  for (const entry of ordered) {
13183
13361
  if (!entry.overlay) continue;
13362
+ if ((entry.overlayPosition ?? "top") !== position) continue;
13363
+ const layers = Array.isArray(entry.overlay) ? entry.overlay : [entry.overlay];
13184
13364
  const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
13185
- if (scope === "active") active.push(entry.overlay);
13186
- else if (scope === "hotkey") hotkey.push(entry.overlay);
13187
- else if (scope === "ambient") ambient.push(entry.overlay);
13365
+ if (scope === "active") active.push(...layers);
13366
+ else if (scope === "hotkey") hotkey.push(...layers);
13367
+ else if (scope === "ambient") ambient.push(...layers);
13188
13368
  }
13189
13369
  return [...active, ...hotkey, ...ambient];
13190
13370
  }, [snapshot]);
@@ -14011,7 +14191,7 @@ function isBoundsGradient(fill) {
14011
14191
  }
14012
14192
 
14013
14193
  // src/features/patterns/resolveSpec.ts
14014
- var cache5 = /* @__PURE__ */ new Map();
14194
+ var cache6 = /* @__PURE__ */ new Map();
14015
14195
  function specKey(spec) {
14016
14196
  const keys = Object.keys(spec).sort();
14017
14197
  const bag = spec;
@@ -14019,10 +14199,10 @@ function specKey(spec) {
14019
14199
  }
14020
14200
  function resolvePatternSpec(spec) {
14021
14201
  const key = specKey(spec);
14022
- const hit = cache5.get(key);
14202
+ const hit = cache6.get(key);
14023
14203
  if (hit !== void 0) return hit;
14024
14204
  const built = buildTile(spec);
14025
- cache5.set(key, built);
14205
+ cache6.set(key, built);
14026
14206
  return built;
14027
14207
  }
14028
14208
  function buildTile(spec) {
@@ -14058,7 +14238,7 @@ function resolveFillPattern(fill) {
14058
14238
  }
14059
14239
 
14060
14240
  // src/features/images/imageCache.ts
14061
- var cache6 = /* @__PURE__ */ new Map();
14241
+ var cache7 = /* @__PURE__ */ new Map();
14062
14242
  var subscribers = /* @__PURE__ */ new Set();
14063
14243
  var defaultLoader = async (src) => {
14064
14244
  if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
@@ -14076,28 +14256,28 @@ function notify() {
14076
14256
  }
14077
14257
  function startLoad(src) {
14078
14258
  const entry = { status: "loading" };
14079
- cache6.set(src, entry);
14259
+ cache7.set(src, entry);
14080
14260
  loader(src).then(
14081
14261
  (bitmap) => {
14082
- if (cache6.get(src) !== entry) return;
14083
- cache6.set(src, { status: "ready", bitmap });
14262
+ if (cache7.get(src) !== entry) return;
14263
+ cache7.set(src, { status: "ready", bitmap });
14084
14264
  notify();
14085
14265
  },
14086
14266
  () => {
14087
- if (cache6.get(src) !== entry) return;
14088
- cache6.set(src, { status: "error" });
14267
+ if (cache7.get(src) !== entry) return;
14268
+ cache7.set(src, { status: "error" });
14089
14269
  notify();
14090
14270
  }
14091
14271
  );
14092
14272
  }
14093
14273
  function getImageBitmap(src) {
14094
- const entry = cache6.get(src);
14274
+ const entry = cache7.get(src);
14095
14275
  if (entry) return entry.bitmap;
14096
14276
  startLoad(src);
14097
14277
  return void 0;
14098
14278
  }
14099
14279
  function imageStatus(src) {
14100
- return cache6.get(src)?.status ?? "idle";
14280
+ return cache7.get(src)?.status ?? "idle";
14101
14281
  }
14102
14282
  function subscribeImageReady(cb) {
14103
14283
  subscribers.add(cb);
@@ -14464,6 +14644,7 @@ registerBuiltInShapePainters();
14464
14644
  function pickTopMostHit(ids, adapter) {
14465
14645
  if (ids.length === 0) return null;
14466
14646
  if (ids.length === 1) return ids[0];
14647
+ let candidates = ids;
14467
14648
  const getParent = adapter?.getParent;
14468
14649
  if (typeof getParent === "function") {
14469
14650
  const set = new Set(ids);
@@ -14478,15 +14659,40 @@ function pickTopMostHit(ids, adapter) {
14478
14659
  }
14479
14660
  if (dropped.size > 0) {
14480
14661
  const filtered = ids.filter((id) => !dropped.has(id));
14481
- if (filtered.length === 0) ; else if (filtered.length === 1) {
14482
- return filtered[0];
14483
- } else {
14484
- return filtered[filtered.length - 1];
14662
+ if (filtered.length === 1) return filtered[0];
14663
+ if (filtered.length > 1) candidates = filtered;
14664
+ }
14665
+ }
14666
+ return pickHighestZ(candidates, adapter);
14667
+ }
14668
+ function pickHighestZ(ids, adapter) {
14669
+ const compareZ = adapter?.compareZ;
14670
+ if (typeof compareZ === "function") {
14671
+ let best = ids[0];
14672
+ for (let i = 1; i < ids.length; i++) {
14673
+ if (compareZ(ids[i], best) >= 0) best = ids[i];
14674
+ }
14675
+ return best;
14676
+ }
14677
+ const getZIndex = adapter?.getZIndex;
14678
+ if (typeof getZIndex === "function") {
14679
+ let best = ids[0];
14680
+ let bestZ = zOf(getZIndex, best);
14681
+ for (let i = 1; i < ids.length; i++) {
14682
+ const z = zOf(getZIndex, ids[i]);
14683
+ if (z >= bestZ) {
14684
+ best = ids[i];
14685
+ bestZ = z;
14485
14686
  }
14486
14687
  }
14688
+ return best;
14487
14689
  }
14488
14690
  return ids[ids.length - 1];
14489
14691
  }
14692
+ function zOf(getZIndex, id) {
14693
+ const z = getZIndex(id);
14694
+ return typeof z === "number" && Number.isFinite(z) ? z : -Infinity;
14695
+ }
14490
14696
 
14491
14697
  // src/core/selection/selectionTarget.ts
14492
14698
  var MULTI_RESIZE_TARGET_ID = "__weasel:multi-selection";
@@ -14891,9 +15097,19 @@ function composeAffordanceLayer(id, label, affordances) {
14891
15097
  },
14892
15098
  // The walk itself lives in `hitAffordanceRegions` so this layer and
14893
15099
  // `buildAffordanceAt` share one implementation rather than two that can
14894
- // drift. This wrapper exists because `RenderLayer.hitTest` wants only the
14895
- // binding back.
14896
- hitTest: (wx, wy, state, view, _dims, isVisible) => hitAffordanceRegions(affordances, wx, wy, state, view, isVisible)?.binding ?? null
15100
+ // drift. This wrapper lifts the region's declared cursor and claim onto
15101
+ // the binding, the way `buildAffordanceAt` lifts them onto `AffordanceHit`.
15102
+ hitTest: (wx, wy, state, view, _dims, isVisible) => {
15103
+ const hit = hitAffordanceRegions(affordances, wx, wy, state, view, isVisible);
15104
+ if (!hit) return null;
15105
+ const { cursor, strength, claimedKinds } = hit.region;
15106
+ return {
15107
+ ...hit.binding,
15108
+ ...cursor !== void 0 ? { cursor } : {},
15109
+ ...strength !== void 0 ? { strength } : {},
15110
+ ...claimedKinds !== void 0 ? { claimedKinds } : {}
15111
+ };
15112
+ }
14897
15113
  };
14898
15114
  }
14899
15115
  function asChromeState(data) {
@@ -15676,7 +15892,7 @@ function useEllipseTool() {
15676
15892
  );
15677
15893
  }
15678
15894
  function useImageTool(options) {
15679
- const { src, label = "Image" } = options;
15895
+ const { src, label = "Image", preview = "bitmap" } = options;
15680
15896
  return useMemo(
15681
15897
  () => defineTool({
15682
15898
  id: "image",
@@ -15695,11 +15911,11 @@ function useImageTool(options) {
15695
15911
  {
15696
15912
  spec: { kind: "drag" },
15697
15913
  actionId: "insert",
15698
- opts: { params: { kind: "image", src } }
15914
+ opts: { params: { kind: "image", src, preview } }
15699
15915
  }
15700
15916
  ]
15701
15917
  }),
15702
- [src, label]
15918
+ [src, label, preview]
15703
15919
  );
15704
15920
  }
15705
15921
  function useEyedropperTool(opts) {
@@ -16166,6 +16382,19 @@ function composeOrderedLayers(layersMap, standardLayers, sceneLayers) {
16166
16382
  }
16167
16383
  return out;
16168
16384
  }
16385
+ function placeToolOverlays(ordered, selectionOverlay, overlays) {
16386
+ const before = overlays("before-selection");
16387
+ const after = overlays("after-selection");
16388
+ const at = selectionOverlay ? ordered.indexOf(selectionOverlay) : -1;
16389
+ if (at >= 0) {
16390
+ ordered.splice(at + 1, 0, ...after);
16391
+ ordered.splice(at, 0, ...before);
16392
+ } else {
16393
+ ordered.push(...before, ...after);
16394
+ }
16395
+ ordered.push(...overlays("top"));
16396
+ return ordered;
16397
+ }
16169
16398
 
16170
16399
  // src/canvas/toolPreview.ts
16171
16400
  function* toolsInPriorityOrder(tools) {
@@ -17712,7 +17941,7 @@ function CanvasInner(props, ref) {
17712
17941
  const out = composeOrderedLayers(effectiveLayersMap, standardLayers, sceneLayers);
17713
17942
  if (decorationLayer) out.push(decorationLayer);
17714
17943
  if (tools) {
17715
- out.push(...tools.getActiveOverlays());
17944
+ placeToolOverlays(out, standardLayers.selectionOverlay, tools.getActiveOverlays);
17716
17945
  }
17717
17946
  return out;
17718
17947
  }, [layersMap, adapter, selectedIds, effectiveBoundsOf, multiActive2, debugSink, tools, backgroundLayer, decorationLayer]);
@@ -19379,6 +19608,7 @@ function useSceneSelectTool(args) {
19379
19608
  boundsOf: wiredBoundsOf
19380
19609
  };
19381
19610
  }
19611
+ var GHOST_ALPHA = 0.85;
19382
19612
  function usePreviewGhostLayer(args) {
19383
19613
  const { scene, tools, sceneSlot, dispatcher } = args;
19384
19614
  const sceneRef = useRef(scene);
@@ -19465,15 +19695,26 @@ function usePreviewGhostLayer(args) {
19465
19695
  }
19466
19696
  return [group];
19467
19697
  };
19698
+ const opaqueSet = /* @__PURE__ */ new Set();
19699
+ for (const source of sources) {
19700
+ const ids = source.previewOpaqueIds?.();
19701
+ if (!ids) continue;
19702
+ for (const id of ids) opaqueSet.add(id);
19703
+ }
19468
19704
  const children = [];
19705
+ const opaque = [];
19469
19706
  for (const id of idSet) {
19470
19707
  const node = sc.get(asNodeId(id));
19471
19708
  const parent = node?.parent;
19472
19709
  if (parent != null && idSet.has(parent)) continue;
19473
- for (const cmd of buildSubtree(id)) children.push(cmd);
19710
+ const sink = opaqueSet.has(id) ? opaque : children;
19711
+ for (const cmd of buildSubtree(id)) sink.push(cmd);
19474
19712
  }
19475
- if (children.length === 0) return [];
19476
- return [{ kind: "group", alpha: 0.85, children }];
19713
+ if (children.length === 0 && opaque.length === 0) return [];
19714
+ const out = [];
19715
+ if (opaque.length > 0) out.push({ kind: "group", children: opaque });
19716
+ if (children.length > 0) out.push({ kind: "group", alpha: GHOST_ALPHA, children });
19717
+ return out;
19477
19718
  }
19478
19719
  }), []);
19479
19720
  }
@@ -19483,6 +19724,7 @@ var DEFAULT_STYLE = {
19483
19724
  dash: [3, 3],
19484
19725
  lineWidth: 1
19485
19726
  };
19727
+ var PREVIEW_CONTENT_OPACITY = 0.85;
19486
19728
  function useDispatcherOverlayLayer(args) {
19487
19729
  const { dispatcher, style } = args;
19488
19730
  const dispatcherRef = useRef(dispatcher);
@@ -19545,6 +19787,26 @@ function useDispatcherOverlayLayer(args) {
19545
19787
  pathCmd = rectPath(sx, sy, b.width * view.scale.x, b.height * view.scale.y);
19546
19788
  break;
19547
19789
  }
19790
+ case "image": {
19791
+ const e = ov.extras;
19792
+ const [sx, sy] = worldToScreen(b.x, b.y, t);
19793
+ const sw = b.width * view.scale.x;
19794
+ const sh = b.height * view.scale.y;
19795
+ const bmp = e.preview === "outline" || !e.src ? void 0 : getImageBitmap(e.src);
19796
+ if (bmp) {
19797
+ out.push({
19798
+ kind: "image",
19799
+ image: bmp,
19800
+ x: sx,
19801
+ y: sy,
19802
+ w: sw,
19803
+ h: sh,
19804
+ opacity: PREVIEW_CONTENT_OPACITY
19805
+ });
19806
+ }
19807
+ pathCmd = rectPath(sx, sy, sw, sh);
19808
+ break;
19809
+ }
19548
19810
  case "ellipse": {
19549
19811
  const [sx, sy] = worldToScreen(b.x, b.y, t);
19550
19812
  pathCmd = ellipsePath({
@@ -19603,7 +19865,7 @@ function useDispatcherOverlayLayer(args) {
19603
19865
  const cmd = {
19604
19866
  kind: "path",
19605
19867
  path: pathCmd,
19606
- fill: ov.shape === "line" || ov.shape === "pencil" ? void 0 : { color: cfg.fill },
19868
+ fill: ov.shape === "line" || ov.shape === "pencil" || ov.shape === "image" ? void 0 : { color: cfg.fill },
19607
19869
  stroke: { paint: { color: cfg.stroke }, width: cfg.lineWidth, dash: cfg.dash }
19608
19870
  };
19609
19871
  out.push(cmd);
@@ -20316,9 +20578,6 @@ function buildClassifyTarget(getSelection, pickBest, kindOfNode) {
20316
20578
  function w2s3(wx, wy, view) {
20317
20579
  return [(wx - view.x) * view.scale.x, (wy - view.y) * view.scale.y];
20318
20580
  }
20319
- function r2s(radiusWorld, view) {
20320
- return radiusWorld * meanScale(view.scale);
20321
- }
20322
20581
  var SLOP_FILL = "rgba(255, 80, 140, 0.18)";
20323
20582
  var SLOP_STROKE = "rgba(255, 80, 140, 0.55)";
20324
20583
  function createSlopsDebugLayer(opts) {
@@ -20329,7 +20588,7 @@ function createSlopsDebugLayer(opts) {
20329
20588
  draw: (_data, view) => {
20330
20589
  const out = [];
20331
20590
  const sel = opts.selectionRef.current?.current ?? [];
20332
- const r = r2s(HANDLE_HIT_RADIUS, view);
20591
+ const r = HANDLE_HIT_RADIUS;
20333
20592
  for (const id of sel) {
20334
20593
  const b = opts.boundsOf(id);
20335
20594
  if (!b) continue;
@@ -20366,6 +20625,7 @@ function createSlopsDebugLayer(opts) {
20366
20625
  }
20367
20626
  const editingId = opts.getEditingId();
20368
20627
  if (editingId) {
20628
+ const anchorR = ANCHOR_HIT_RADIUS;
20369
20629
  const pose = opts.getPose(editingId);
20370
20630
  if (pose && pose.kind === "polygon") {
20371
20631
  const anchors = enumerateAnchors(pose);
@@ -20373,7 +20633,7 @@ function createSlopsDebugLayer(opts) {
20373
20633
  const [sx, sy] = w2s3(a.x, a.y, view);
20374
20634
  out.push({
20375
20635
  kind: "path",
20376
- path: circlePath(sx, sy, r),
20636
+ path: circlePath(sx, sy, anchorR),
20377
20637
  fill: { fill: "solid", color: SLOP_FILL },
20378
20638
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
20379
20639
  });
@@ -20381,7 +20641,7 @@ function createSlopsDebugLayer(opts) {
20381
20641
  const [csx, csy] = w2s3(a.controlIn.x, a.controlIn.y, view);
20382
20642
  out.push({
20383
20643
  kind: "path",
20384
- path: circlePath(csx, csy, r),
20644
+ path: circlePath(csx, csy, anchorR),
20385
20645
  fill: { fill: "solid", color: SLOP_FILL },
20386
20646
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
20387
20647
  });
@@ -20390,7 +20650,7 @@ function createSlopsDebugLayer(opts) {
20390
20650
  const [csx, csy] = w2s3(a.controlOut.x, a.controlOut.y, view);
20391
20651
  out.push({
20392
20652
  kind: "path",
20393
- path: circlePath(csx, csy, r),
20653
+ path: circlePath(csx, csy, anchorR),
20394
20654
  fill: { fill: "solid", color: SLOP_FILL },
20395
20655
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
20396
20656
  });
@@ -20472,9 +20732,9 @@ function DepRegistryProviderIfRoot({ children }) {
20472
20732
  function hitTestArea(scene, bounds) {
20473
20733
  const { x, y, width: w, height: h } = bounds;
20474
20734
  const area = [x, y, x + w, y, x + w, y + h, x, y + h];
20475
- return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h });
20735
+ return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h }, true);
20476
20736
  }
20477
- function hitTestAreaPolygon(scene, area, areaBounds) {
20737
+ function hitTestAreaPolygon(scene, area, areaBounds, areaIsRect = false) {
20478
20738
  const ab = areaBounds ?? boundsOf(area);
20479
20739
  if (!ab) return [];
20480
20740
  const hits = [];
@@ -20491,13 +20751,22 @@ function hitTestAreaPolygon(scene, area, areaBounds) {
20491
20751
  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) {
20492
20752
  continue;
20493
20753
  }
20494
- if (!silhouette) {
20754
+ 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) {
20495
20755
  hits.push(node.id);
20496
20756
  continue;
20497
20757
  }
20498
- if (silhouetteOverlapsArea(pose.coords, area)) {
20499
- hits.push(node.id);
20758
+ if (silhouette) {
20759
+ if (silhouetteOverlapsArea(pose.coords, area)) {
20760
+ hits.push(node.id);
20761
+ }
20762
+ continue;
20763
+ }
20764
+ const drawn = findShapeSilhouette(node, pose);
20765
+ if (drawn?.kind === "polygon") {
20766
+ if (silhouetteOverlapsArea(drawn.coords, area)) hits.push(node.id);
20767
+ continue;
20500
20768
  }
20769
+ hits.push(node.id);
20501
20770
  }
20502
20771
  return hits;
20503
20772
  }
@@ -24324,6 +24593,14 @@ function useSceneTextEdit(scene, container, options = {}) {
24324
24593
  const setter = optsRef.current.setRuns;
24325
24594
  const data = setter ? setter(node.data, runs) : { ...node.data, runs };
24326
24595
  sceneRef.current.update(nid, { data });
24596
+ },
24597
+ setStyle: (id, style) => {
24598
+ const nid = asNodeId(id);
24599
+ const node = sceneRef.current.get(nid);
24600
+ if (!node) return;
24601
+ const setter = optsRef.current.setStyle;
24602
+ const data = setter ? setter(node.data, style) : { ...node.data, style };
24603
+ sceneRef.current.update(nid, { data });
24327
24604
  }
24328
24605
  });
24329
24606
  const onDoubleClick = useCallback((e) => {
@@ -28032,6 +28309,6 @@ function mergeContributions(...bundles) {
28032
28309
  return out;
28033
28310
  }
28034
28311
 
28035
- export { ALWAYS, ANCHOR_HIT_BASE_PX, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, BUNDLE_TOOLS, COARSE_TARGET_SCALE, CURVE_REPS, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_THEME, DEFAULT_DEVICE_PROFILE, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_STROKE_COLOR, DEFAULT_TEXT_STYLE, DEFAULT_VELOCITY_DECAY, DepRegistryProviderIfRoot, DeviceProfileProvider, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_POSE_COMPOSITION, IntersectIcon, KIT_SHAPE_KINDS, LassoIcon, LineIcon, MIXED, MOVE_ANCHORS, MinimapCanvas, NEVER, OUTLINE_MIN_SCREEN_PX, PATH_ANCHOR_CHROME_ID, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_ALIGN_PROJECTION, ROTATION_HANDLE_BASE_PX, RectIcon, SPRING_PRESETS, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, ShaderCompileError, StarIcon, SubtractIcon, TextIcon, UnionIcon, UnknownIcon, VERSION, WeaselProvider, WeaselRenderer, aabbCenter, actionIs, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, alignedStrokeRect, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyHitExistingGate, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, bezierCubic, bezierQuadratic, buildChromeCtx, buildGradientRamp, buildRuleCtx, buildSceneViewCommands, canBringForward, canHover, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, clipboardCopyAction, clipboardCutAction, cloneAction, coarsePointer, composeAffordanceLayer, composePath, composeRectPose, composeSelectionPose, composeWorldPose, computeFitView2 as computeFitView, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMarkdownRenderer, createMoveToIndexOp, createNodeProperties, createNodeRouting, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createViewportLayer, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic2 as easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, fontString, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getStylusData, gradientForBounds, gradientGeometry, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isEditableTarget2 as isEditableTarget, isPatternSpec, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linear, liveScope, makeViewportZoomAction, markdownToRuns, mat3, matchAlignment, matchesKeyBinding, meanScale, measureText, measureTextBounds, measuredWidth, mergeAlphaFromPrev, mergeContributions, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, or, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInRotatedRect, pointInTextPose, poseRotationOf, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectCorners, registerContentHandler, registerNodeShape, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveDeviceProfile, resolveFillPattern, resolvePatternSpec, resolveRuns, resolveTextStyle, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, runsToMarkdown, runsToPlainText, sampleGradientStops, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, scopeBindings, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setFlagOverRange, shapeCoversPoint, sliceAction, snapPoint, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textLineBoxes, tileGrid, toHex8, toRuns, translatePoseViaDescriptor, translateRectPose, tweenPose, tweenVertexColors, unionBounds, unionBoundsPath, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useOptionalActiveToolContext, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useViewTween, useZoom, verticalAlignOffset, viewToMat3, viewToTransform, viewportDragPanAction, viewportZoomAction, when, withAlpha01, withCoord, withGradientKind, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
28036
- //# sourceMappingURL=chunk-FVNWK7U3.js.map
28037
- //# sourceMappingURL=chunk-FVNWK7U3.js.map
28312
+ export { ALWAYS, ANCHOR_HIT_BASE_PX, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, BUNDLE_TOOLS, COARSE_TARGET_SCALE, CURVE_REPS, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_STROKES, DEFAULT_DEBUG_THEME, DEFAULT_DEVICE_PROFILE, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_STROKE_COLOR, DEFAULT_TEXT_STYLE, DEFAULT_VELOCITY_DECAY, DepRegistryProviderIfRoot, DeviceProfileProvider, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_POSE_COMPOSITION, IntersectIcon, KIT_SHAPE_KINDS, LassoIcon, LineIcon, MIXED, MOVE_ANCHORS, MinimapCanvas, NEVER, OUTLINE_MIN_SCREEN_PX, PATH_ANCHOR_CHROME_ID, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_ALIGN_PROJECTION, ROTATION_HANDLE_BASE_PX, RectIcon, SPRING_PRESETS, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, ShaderCompileError, StarIcon, SubtractIcon, TextIcon, UnionIcon, UnknownIcon, VERSION, WeaselProvider, WeaselRenderer, aabbCenter, actionIs, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, alignedStrokeRect, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyHitExistingGate, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, bezierCubic, bezierQuadratic, buildChromeCtx, buildGradientRamp, buildRuleCtx, buildSceneViewCommands, canBringForward, canHover, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, clipboardCopyAction, clipboardCutAction, cloneAction, coarsePointer, composeAffordanceLayer, composePath, composeRectPose, composeSelectionPose, composeWorldPose, computeFitView2 as computeFitView, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMarkdownRenderer, createMoveToIndexOp, createNodeProperties, createNodeRouting, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createViewportLayer, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, defineTool, defineViewportTool, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic2 as easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, fontString, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getStylusData, gradientForBounds, gradientGeometry, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isEditableTarget2 as isEditableTarget, isPatternSpec, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linear, liveScope, makeViewportZoomAction, markdownToRuns, mat3, matchAlignment, matchesKeyBinding, meanScale, measureText, measureTextBounds, measuredWidth, mergeAlphaFromPrev, mergeContributions, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, or, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInRotatedRect, pointInTextPose, poseRotationOf, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectCorners, registerContentHandler, registerNodeShape, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveDeviceProfile, resolveFillPattern, resolvePatternSpec, resolveRuns, resolveTextStyle, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, runsToMarkdown, runsToPlainText, sampleGradientStops, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, scopeBindings, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setFlagOverRange, shapeCoversPoint, sliceAction, snapPoint, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textLineBoxes, tileGrid, toHex8, toRuns, translatePoseViaDescriptor, translateRectPose, tweenPose, tweenVertexColors, unionBounds, unionBoundsPath, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useOptionalActiveToolContext, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useViewTween, useZoom, verticalAlignOffset, viewToMat3, viewToTransform, viewportDragPanAction, viewportZoomAction, viewportsAt, when, withAlpha01, withCoord, withGradientKind, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
28313
+ //# sourceMappingURL=chunk-PI56ORHE.js.map
28314
+ //# sourceMappingURL=chunk-PI56ORHE.js.map