@weasel-js/core 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/dist/{DrawCommand-DCmiNkBj.d.ts → DrawCommand-BbPrSTra.d.ts} +24 -0
  3. package/dist/{chunk-Z3KNFTTS.js → chunk-3JYFAQIB.js} +3 -3
  4. package/dist/chunk-3JYFAQIB.js.map +1 -0
  5. package/dist/{chunk-UB6A6L77.js → chunk-3KGMJMOS.js} +3 -3
  6. package/dist/{chunk-UB6A6L77.js.map → chunk-3KGMJMOS.js.map} +1 -1
  7. package/dist/{chunk-IO5Z75X4.js → chunk-HGF5X6MZ.js} +2 -2
  8. package/dist/chunk-HGF5X6MZ.js.map +1 -0
  9. package/dist/{chunk-775XXAHR.js → chunk-JOSUVS6C.js} +2 -2
  10. package/dist/{chunk-775XXAHR.js.map → chunk-JOSUVS6C.js.map} +1 -1
  11. package/dist/{chunk-FVNWK7U3.js → chunk-SMCZRSYB.js} +1677 -1371
  12. package/dist/chunk-SMCZRSYB.js.map +1 -0
  13. package/dist/{chunk-DCIU3SRK.js → chunk-TR3HZN34.js} +79 -151
  14. package/dist/chunk-TR3HZN34.js.map +1 -0
  15. package/dist/{chunk-6SHZHXXR.js → chunk-U45SODC2.js} +2 -2
  16. package/dist/{chunk-6SHZHXXR.js.map → chunk-U45SODC2.js.map} +1 -1
  17. package/dist/clipboard.d.ts +5 -2
  18. package/dist/clipboard.js +1 -2
  19. package/dist/clone.d.ts +2 -2
  20. package/dist/clone.js +0 -1
  21. package/dist/{geometry-DVeZ2i-c.d.ts → geometry-5X6TrAkX.d.ts} +1 -1
  22. package/dist/{grid-XcFQVS0f.d.ts → grid-sfilv5HP.d.ts} +7 -2
  23. package/dist/index.d.ts +695 -49
  24. package/dist/index.js +7 -8
  25. package/dist/insert.d.ts +6 -3
  26. package/dist/insert.js +1 -2
  27. package/dist/insert.js.map +1 -1
  28. package/dist/move.d.ts +26 -5
  29. package/dist/move.js +2 -3
  30. package/dist/move.js.map +1 -1
  31. package/dist/{options-wAvCnOZd.d.ts → options-CbsIgQci.d.ts} +3 -1
  32. package/dist/patterns-builtin.d.ts +8 -0
  33. package/dist/patterns-builtin.js +1 -2
  34. package/dist/{pointSnapToGrid-BuDZWBrL.d.ts → pointSnapToGrid-C3wowsQy.d.ts} +9 -3
  35. package/dist/{index-DPI8DV0A.d.ts → registry-CxnkXbG8.d.ts} +225 -350
  36. package/dist/renderer.d.ts +36 -4
  37. package/dist/renderer.js +7 -8
  38. package/dist/resize.d.ts +12 -5
  39. package/dist/resize.js +2 -3
  40. package/dist/routing.d.ts +106 -7
  41. package/dist/routing.js +1 -2
  42. package/dist/{types-BJ8_cyT7.d.ts → types-BHK2dkMu.d.ts} +43 -1
  43. package/dist/{types-BhJJ2OCM.d.ts → types-ByZxoD6P.d.ts} +1 -1
  44. package/dist/{types-B_-khFM0.d.ts → types-DpQ0cWYC.d.ts} +32 -0
  45. package/dist/{types-Kh_osAq9.d.ts → types-vqxpCFVp.d.ts} +31 -0
  46. package/dist/{viewToMat3-BvaWCN99.d.ts → viewToMat3-CQW_YqDw.d.ts} +1 -1
  47. package/package.json +6 -6
  48. package/dist/chunk-DCIU3SRK.js.map +0 -1
  49. package/dist/chunk-FVNWK7U3.js.map +0 -1
  50. package/dist/chunk-IO5Z75X4.js.map +0 -1
  51. package/dist/chunk-PZ5AY32C.js +0 -9
  52. package/dist/chunk-PZ5AY32C.js.map +0 -1
  53. package/dist/chunk-Z3KNFTTS.js.map +0 -1
@@ -1,11 +1,11 @@
1
- import { ActionDisabledReason, defineViewportTool, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsProvider, useDepRegistry, defineTool, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-DCIU3SRK.js';
2
- import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-UB6A6L77.js';
3
- import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-Z3KNFTTS.js';
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';
5
- import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvider, applyOpsTo, extractWeaselClipboardFromSvg, parseWeaselClipboardText, materializePaste, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, sniffWeaselClipboardText } from './chunk-775XXAHR.js';
1
+ import { ActionDisabledReason, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsProvider, useDepRegistry, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-TR3HZN34.js';
2
+ import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-3KGMJMOS.js';
3
+ import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-3JYFAQIB.js';
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-U45SODC2.js';
5
+ import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvider, applyOpsTo, extractWeaselClipboardFromSvg, parseWeaselClipboardText, materializePaste, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, sniffWeaselClipboardText } from './chunk-JOSUVS6C.js';
6
6
  import { createSetSelectionOp } from './chunk-BHVYVFGV.js';
7
7
  import { registerOpFactory, createDeleteOp, createInsertOp, rebuildOp } from './chunk-GVCNT7UH.js';
8
- import { getTexture, chunks, dots, crosshatch, hatch } from './chunk-IO5Z75X4.js';
8
+ import { getTexture, chunks, dots, crosshatch, hatch } from './chunk-HGF5X6MZ.js';
9
9
  import { createContext, createElement, forwardRef, useMemo, useRef, useState, useCallback, useImperativeHandle, useEffect, useSyncExternalStore, useContext, useReducer, useLayoutEffect } from 'react';
10
10
  import { subscribeGlyphReady, DEFAULT_BAKE_BUDGET, TEXT_VERT_SRC, TEXT_FRAG_SRC, TEXT_SDF_UNIFORMS, TEXT_SDF_ATTRIBUTES, TEXT_FRAG_R8_SRC, markAllFontsNotUploaded, resetBakeBudget, glyphGeneration, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey, resolveFontVariant, resolveGlyphFallback, glyphOutline } from '@weasel-js/font';
11
11
  export { canQueryLocalFonts, canQueryLocalFonts as canQueryLocalFonts2, enableLocalFontOutlines, enableLocalFontOutlines as enableLocalFontOutlines2, hasFontOutlines, hasFontOutlines as hasFontOutlines2, isCanvasFont, isCanvasFont as isCanvasFont2, listFontOutlines, listFontOutlines as listFontOutlines2, outlineStatus, outlineStatus as outlineStatus2, registerCanvasFont, registerCanvasFont as registerCanvasFont2, registerFont, registerFont as registerFont2, registerFontOutlines, registerFontOutlines as registerFontOutlines2, subscribeGlyphReady, subscribeGlyphReady as subscribeGlyphReady2, unregisterCanvasFont, unregisterCanvasFont as unregisterCanvasFont2, unregisterFontOutlines, unregisterFontOutlines as unregisterFontOutlines2 } from '@weasel-js/font';
@@ -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.3" ;
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}`;
2167
- }
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
2182
- );
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);
2200
- }
2201
- return g;
2202
- }
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 };
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;
2212
2028
  }
2213
- }
2214
- warnMissingGlyphOnce(resolved.resolved.family, cp);
2215
- return null;
2216
- }
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
- );
2226
- }
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;
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;
2241
2038
  }
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;
2293
- }
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;
2300
- }
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);
2307
- }
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;
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;
2324
2046
  }
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;
2339
- }
2340
- commitLine();
2341
- i++;
2342
- continue;
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;
2343
2054
  }
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);
2349
- }
2350
- i++;
2351
- continue;
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;
2352
2065
  }
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++;
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;
2359
2078
  }
2360
- if (Number.isFinite(opts.maxWidth) && cur.width + wordWidth > opts.maxWidth && cur.entries.length > 0) {
2361
- commitLine();
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;
2362
2088
  }
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);
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;
2369
2100
  }
2370
- i = j;
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;
2371
2118
  }
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);
2119
+ }
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;
2379
2125
  }
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 });
2390
- }
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 });
2394
- }
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;
2395
2146
  }
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
- };
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
2168
+ );
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;
2195
+ }
2196
+ }
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;
2209
+ }
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);
2215
+ }
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;
2281
+ }
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;
2293
+ }
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);
2432
2305
  }
2433
- } else {
2434
- flushSpan();
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;
2435
2316
  }
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;
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;
2449
2340
  }
2450
- if (e.advance === 0 || e.glyph.width === 0 || e.glyph.page < 0) {
2451
- penX += step;
2452
- continue;
2341
+ case PATH_Z: {
2342
+ if (current) {
2343
+ current.closed = true;
2344
+ dropDuplicateClosingPoint();
2345
+ }
2346
+ break;
2453
2347
  }
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;
2348
+ default:
2349
+ throw new Error(`extractPolylines: unknown command code ${cmd}`);
2350
+ }
2351
+ }
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
+ };
2382
+ }
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);
2467
2397
  }
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
2398
  }
2479
2399
  return {
2480
- groups: [...ctx.groups.values()],
2481
- decorations,
2482
- lines: lineBoxes,
2483
- bounds: { width: maxLineWidth, height: penY }
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)
2484
2405
  };
2485
2406
  }
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)}`;
2498
- }
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();
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);
2426
+ }
2427
+ }
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);
2439
+ }
2440
+ }
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]);
2508
2481
  }
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;
2516
- }
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;
2523
2482
  }
2524
-
2525
- // src/features/paths/pathFromD.ts
2526
- var NUM_RE = /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g;
2527
- function tokenize(d) {
2528
- 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;
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);
2538
2498
  } else {
2539
- i++;
2499
+ const wa = read(a);
2500
+ const wb = read(b);
2501
+ widths[i] = wa + (wb - wa) * t;
2540
2502
  }
2541
2503
  }
2542
- return out;
2504
+ pl.widths = widths;
2543
2505
  }
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);
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;
2551
2534
  }
2552
- return out;
2553
- }
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
2576
- };
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;
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;
2584
2558
  }
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;
2559
+ if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);
2560
+ else idx.push(prevIdx, pivotIdx, rightIdx);
2561
+ }
2562
+ }
2563
+ function splitForDash(pl, dash) {
2564
+ const out = [];
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
+ });
2592
2584
  }
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);
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
+ }
2598
2669
  }
2599
2670
  }
2600
- return b.build();
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
+ });
2679
+ }
2680
+ return out;
2601
2681
  }
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;
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
2696
+ };
2697
+ }
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;
2735
- }
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;
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;
2756
+ }
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) {
@@ -3941,70 +4004,98 @@ function drawTextGroup(ctx, group, prog, dx, dy) {
3941
4004
  gl.deleteBuffer(vbo);
3942
4005
  gl.deleteBuffer(ibo);
3943
4006
  }
4007
+ var IMAGE_QUAD_RING_SIZE = 64;
4008
+ var IMAGE_QUAD_RINGS = /* @__PURE__ */ new WeakMap();
4009
+ var IMAGE_QUAD_INDICES = new Uint32Array([0, 1, 2, 1, 3, 2]);
4010
+ var IMAGE_QUAD_STRIDE = 16;
4011
+ function imageQuadRing(gl, prog) {
4012
+ const existing = IMAGE_QUAD_RINGS.get(prog);
4013
+ if (existing) return existing;
4014
+ const aPosLoc = prog.attribute("a_position");
4015
+ const aUvLoc = prog.attribute("a_uv");
4016
+ const ibo = gl.createBuffer();
4017
+ if (!ibo) throw new Error("drawImage: createBuffer (IBO) returned null");
4018
+ const vaos = [];
4019
+ const vbos = [];
4020
+ for (let slot = 0; slot < IMAGE_QUAD_RING_SIZE; slot++) {
4021
+ const vao = gl.createVertexArray();
4022
+ const vbo = gl.createBuffer();
4023
+ if (!vao) throw new Error("drawImage: createVertexArray returned null");
4024
+ if (!vbo) throw new Error("drawImage: createBuffer returned null");
4025
+ gl.bindVertexArray(vao);
4026
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
4027
+ gl.bufferData(gl.ARRAY_BUFFER, 4 * IMAGE_QUAD_STRIDE, gl.DYNAMIC_DRAW);
4028
+ if (aPosLoc !== void 0) {
4029
+ gl.enableVertexAttribArray(aPosLoc);
4030
+ gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, IMAGE_QUAD_STRIDE, 0);
4031
+ }
4032
+ if (aUvLoc !== void 0) {
4033
+ gl.enableVertexAttribArray(aUvLoc);
4034
+ gl.vertexAttribPointer(aUvLoc, 2, gl.FLOAT, false, IMAGE_QUAD_STRIDE, 8);
4035
+ }
4036
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
4037
+ if (slot === 0) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, IMAGE_QUAD_INDICES, gl.STATIC_DRAW);
4038
+ gl.bindVertexArray(null);
4039
+ vaos.push(vao);
4040
+ vbos.push(vbo);
4041
+ }
4042
+ const ring = { vaos, vbos, ibo, next: 0 };
4043
+ IMAGE_QUAD_RINGS.set(prog, ring);
4044
+ return ring;
4045
+ }
4046
+ function disposeImageQuads(gl, prog) {
4047
+ const ring = IMAGE_QUAD_RINGS.get(prog);
4048
+ if (!ring) return;
4049
+ for (const vao of ring.vaos) gl.deleteVertexArray(vao);
4050
+ for (const vbo of ring.vbos) gl.deleteBuffer(vbo);
4051
+ gl.deleteBuffer(ring.ibo);
4052
+ IMAGE_QUAD_RINGS.delete(prog);
4053
+ }
4054
+ var IMAGE_QUAD_VERTICES = new Float32Array(16);
3944
4055
  function drawImage(ctx, cmd) {
3945
4056
  ctx.imageCache.upload(cmd.image, cmd.image);
3946
4057
  const gl = ctx.gl;
3947
- gl.useProgram(ctx.imageFill.handle);
4058
+ const prog = ctx.imageFill;
4059
+ gl.useProgram(prog.handle);
3948
4060
  const x0 = cmd.x, y0 = cmd.y;
3949
4061
  const x1 = cmd.x + cmd.w, y1 = cmd.y + cmd.h;
3950
- const vertices = new Float32Array([
3951
- x0,
3952
- y0,
3953
- 0,
3954
- 0,
3955
- x1,
3956
- y0,
3957
- 1,
3958
- 0,
3959
- x0,
3960
- y1,
3961
- 0,
3962
- 1,
3963
- x1,
3964
- y1,
3965
- 1,
3966
- 1
3967
- ]);
3968
- const indices = new Uint32Array([0, 1, 2, 1, 3, 2]);
3969
- const vao = gl.createVertexArray();
3970
- if (!vao) throw new Error("drawImage: createVertexArray returned null");
3971
- gl.bindVertexArray(vao);
3972
- const vbo = gl.createBuffer();
3973
- if (!vbo) throw new Error("drawImage: createBuffer returned null");
3974
- gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
3975
- gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
3976
- const stride = 16;
3977
- const aPosLoc = ctx.imageFill.attribute("a_position");
3978
- const aUvLoc = ctx.imageFill.attribute("a_uv");
3979
- if (aPosLoc !== void 0) {
3980
- gl.enableVertexAttribArray(aPosLoc);
3981
- gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, stride, 0);
3982
- }
3983
- if (aUvLoc !== void 0) {
3984
- gl.enableVertexAttribArray(aUvLoc);
3985
- gl.vertexAttribPointer(aUvLoc, 2, gl.FLOAT, false, stride, 8);
3986
- }
3987
- const ibo = gl.createBuffer();
3988
- if (!ibo) throw new Error("drawImage: createBuffer (IBO) returned null");
3989
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
3990
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.DYNAMIC_DRAW);
3991
- setProjAndModel(ctx, ctx.imageFill);
3992
- setColorMatrixUniforms(ctx, ctx.imageFill);
4062
+ const v = IMAGE_QUAD_VERTICES;
4063
+ v[0] = x0;
4064
+ v[1] = y0;
4065
+ v[2] = 0;
4066
+ v[3] = 0;
4067
+ v[4] = x1;
4068
+ v[5] = y0;
4069
+ v[6] = 1;
4070
+ v[7] = 0;
4071
+ v[8] = x0;
4072
+ v[9] = y1;
4073
+ v[10] = 0;
4074
+ v[11] = 1;
4075
+ v[12] = x1;
4076
+ v[13] = y1;
4077
+ v[14] = 1;
4078
+ v[15] = 1;
4079
+ const ring = imageQuadRing(gl, prog);
4080
+ const slot = ring.next;
4081
+ ring.next = (slot + 1) % IMAGE_QUAD_RING_SIZE;
4082
+ gl.bindVertexArray(ring.vaos[slot]);
4083
+ gl.bindBuffer(gl.ARRAY_BUFFER, ring.vbos[slot]);
4084
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, v);
4085
+ setProjAndModel(ctx, prog);
4086
+ setColorMatrixUniforms(ctx, prog);
3993
4087
  ctx.imageCache.bind(cmd.image, 0);
3994
4088
  gl.texParameteri(
3995
4089
  gl.TEXTURE_2D,
3996
4090
  gl.TEXTURE_MAG_FILTER,
3997
4091
  cmd.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
3998
4092
  );
3999
- gl.uniform1i(ctx.imageFill.uniform("u_sampler"), 0);
4000
- gl.uniform1f(ctx.imageFill.uniform("u_opacity"), cmd.opacity ?? 1);
4001
- gl.uniform1f(ctx.imageFill.uniform("u_alpha"), ctx.state.alpha);
4093
+ gl.uniform1i(prog.uniform("u_sampler"), 0);
4094
+ gl.uniform1f(prog.uniform("u_opacity"), cmd.opacity ?? 1);
4095
+ gl.uniform1f(prog.uniform("u_alpha"), ctx.state.alpha);
4002
4096
  applyClipTest(ctx);
4003
4097
  gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
4004
4098
  gl.bindVertexArray(null);
4005
- gl.deleteVertexArray(vao);
4006
- gl.deleteBuffer(vbo);
4007
- gl.deleteBuffer(ibo);
4008
4099
  }
4009
4100
 
4010
4101
  // src/renderer/shaders/customPrelude.ts
@@ -4075,16 +4166,28 @@ function registerProgram(id, vert, frag) {
4075
4166
  }
4076
4167
 
4077
4168
  // src/renderer/WeaselRenderer.ts
4169
+ var UNIFORM_QUALIFIER = /^(?:lowp|mediump|highp|precise|invariant|centroid|flat|smooth|noperspective)$/;
4078
4170
  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;
4171
+ const re = /\buniform\s+([^;{]+);/g;
4081
4172
  const names = [];
4082
4173
  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}]`);
4174
+ while ((m = re.exec(glsl)) !== null) {
4175
+ const words = m[1].trim().split(/\s+/);
4176
+ let i = 0;
4177
+ while (i < words.length && UNIFORM_QUALIFIER.test(words[i])) i++;
4178
+ i++;
4179
+ const declarators = words.slice(i).join(" ");
4180
+ if (!declarators) continue;
4181
+ for (const raw of declarators.split(",")) {
4182
+ const d = raw.trim();
4183
+ const arr = /^(\w+)\s*\[\s*(\d+)\s*\]$/.exec(d);
4184
+ if (arr) {
4185
+ const size = parseInt(arr[2], 10);
4186
+ for (let k = 0; k < size; k++) names.push(`${arr[1]}[${k}]`);
4187
+ } else if (/^\w+$/.test(d)) {
4188
+ names.push(d);
4189
+ }
4190
+ }
4088
4191
  }
4089
4192
  return names;
4090
4193
  }
@@ -4327,6 +4430,7 @@ var WeaselRenderer = class {
4327
4430
  this.gradRampCache.free();
4328
4431
  if (this.quadVbo) gl.deleteBuffer(this.quadVbo);
4329
4432
  if (this.quadIbo) gl.deleteBuffer(this.quadIbo);
4433
+ disposeImageQuads(gl, this.imageFill);
4330
4434
  this.solidBatch.dispose();
4331
4435
  }
4332
4436
  /**
@@ -4613,6 +4717,14 @@ function createViewportLayer(opts) {
4613
4717
  id,
4614
4718
  label,
4615
4719
  space: "screen",
4720
+ reproject(outer, dims, screen) {
4721
+ const b = bounds(outer, dims);
4722
+ if (screen.x < b.x || screen.x >= b.x + b.w || screen.y < b.y || screen.y >= b.y + b.h) return null;
4723
+ return {
4724
+ x: (screen.x - b.x) / view.scale.x + view.x,
4725
+ y: (screen.y - b.y) / view.scale.y + view.y
4726
+ };
4727
+ },
4616
4728
  draw: (data, outerView, dims) => {
4617
4729
  const b = bounds(outerView, dims);
4618
4730
  const children = [];
@@ -4639,6 +4751,14 @@ function createViewportLayer(opts) {
4639
4751
  }
4640
4752
  };
4641
4753
  }
4754
+ function viewportsAt(layers, outer, dims, screen) {
4755
+ for (let i = layers.length - 1; i >= 0; i--) {
4756
+ const layer = layers[i];
4757
+ const point = layer.reproject(outer, dims, screen);
4758
+ if (point) return { layer, point };
4759
+ }
4760
+ return null;
4761
+ }
4642
4762
 
4643
4763
  // src/features/parallax/deriveParallaxView.ts
4644
4764
  function asXY(v) {
@@ -5772,6 +5892,12 @@ function runLayoutPass(scratch, moveCtx) {
5772
5892
  if (layout2.contains) return layout2.contains(cPose, draggedCenter);
5773
5893
  return draggedCenter.x >= aabb.x && draggedCenter.x < aabb.x + aabb.width && draggedCenter.y >= aabb.y && draggedCenter.y < aabb.y + aabb.height;
5774
5894
  };
5895
+ const draggedArg = {
5896
+ id: draggedId,
5897
+ originPose: startWorld,
5898
+ pose: draggedWorld,
5899
+ sourceContainerId
5900
+ };
5775
5901
  const consider = (id, zPath) => {
5776
5902
  if (id === draggedId) return;
5777
5903
  const layout2 = layoutDep.getLayout(id);
@@ -5781,6 +5907,7 @@ function runLayoutPass(scratch, moveCtx) {
5781
5907
  const worldPose = composeWorldPose(poseAdapter, id, pc.compose);
5782
5908
  const worldAABB = boundsOf2(worldPose);
5783
5909
  if (!testInside(worldPose, worldAABB, layout2)) return;
5910
+ if (layout2.acceptsDrop && !layout2.acceptsDrop({ id, bounds: worldAABB }, draggedArg)) return;
5784
5911
  candidates.push({
5785
5912
  id,
5786
5913
  bounds: worldAABB,
@@ -5829,20 +5956,16 @@ function runLayoutPass(scratch, moveCtx) {
5829
5956
  id: cid,
5830
5957
  pose: composeWorldPose(poseAdapter, cid, pc.compose)
5831
5958
  }));
5832
- const draggedArg = {
5833
- id: draggedId,
5834
- originPose: startWorld,
5835
- pose: draggedWorld,
5836
- sourceContainerId
5837
- };
5838
5959
  const targets = layout.getDropTargets(container, children, draggedArg);
5839
5960
  const target = layout.snap.pickTarget(targets, { x: moveCtx.drag.current.x, y: moveCtx.drag.current.y });
5840
5961
  if (!target) return;
5962
+ const reflowIds = /* @__PURE__ */ new Set();
5841
5963
  for (const [cid, pose] of layout.reflowPoses(container, children, draggedArg, target)) {
5842
5964
  if (asNodeId(cid) === draggedId) continue;
5843
5965
  const parent = scene.get(asNodeId(cid))?.parent ?? null;
5844
5966
  const local = rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose);
5845
5967
  scratch.previews.set(asNodeId(cid), local);
5968
+ reflowIds.add(asNodeId(cid));
5846
5969
  }
5847
5970
  const sourceReflow = /* @__PURE__ */ new Map();
5848
5971
  if (sourceContainerId && sourceContainerId !== dest.id) {
@@ -5865,10 +5988,11 @@ function runLayoutPass(scratch, moveCtx) {
5865
5988
  sourceReflow.set(cid, pose);
5866
5989
  const parent = scene.get(asNodeId(cid))?.parent ?? null;
5867
5990
  scratch.previews.set(asNodeId(cid), rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose));
5991
+ reflowIds.add(asNodeId(cid));
5868
5992
  }
5869
5993
  }
5870
5994
  }
5871
- scratch.layoutPass = { layout, container, children, target, sourceReflow };
5995
+ scratch.layoutPass = { layout, container, children, target, sourceReflow, reflowIds };
5872
5996
  }
5873
5997
  function scenePoseAdapter(scene) {
5874
5998
  return {
@@ -6176,7 +6300,8 @@ var moveAction = {
6176
6300
  scratch.previews.clear();
6177
6301
  },
6178
6302
  previewIds: () => scratch.previews.keys(),
6179
- previewPose: (id) => scratch.previews.get(id) ?? null
6303
+ previewPose: (id) => scratch.previews.get(id) ?? null,
6304
+ previewOpaqueIds: () => scratch.layoutPass?.reflowIds ?? null
6180
6305
  };
6181
6306
  }
6182
6307
  },
@@ -6945,7 +7070,8 @@ var KIT_INSERT_KINDS = /* @__PURE__ */ new Set([
6945
7070
  "line",
6946
7071
  "polygon",
6947
7072
  "star",
6948
- "pencil"
7073
+ "pencil",
7074
+ "image"
6949
7075
  ]);
6950
7076
  var liveInsertScratch = null;
6951
7077
  function effectiveOriginMode(paramsOrigin, altHeld) {
@@ -8074,7 +8200,7 @@ var pinchZoomAction = {
8074
8200
  const multiTouch = ctx.multiTouch;
8075
8201
  if (!multiTouch) return {};
8076
8202
  const startSpread = multiTouch.spread > 0 ? multiTouch.spread : 1;
8077
- const scratch = { view, startSpread };
8203
+ const scratch = { view, startSpread, centroid: multiTouch.centroid };
8078
8204
  return {
8079
8205
  kind: "pinch",
8080
8206
  onMove(moveCtx) {
@@ -8084,8 +8210,15 @@ var pinchZoomAction = {
8084
8210
  if (currentSpread <= 0 || scratch.startSpread <= 0) return;
8085
8211
  const factor = currentSpread / scratch.startSpread;
8086
8212
  scratch.startSpread = currentSpread;
8213
+ const prev = scratch.centroid;
8214
+ scratch.centroid = centroid;
8087
8215
  const current = scratch.view.get();
8088
- scratch.view.set(zoomAt(current, centroid, factor));
8216
+ const zoomed = zoomAt(current, prev, factor);
8217
+ scratch.view.set({
8218
+ scale: zoomed.scale,
8219
+ x: zoomed.x - (centroid.x - prev.x) / zoomed.scale.x,
8220
+ y: zoomed.y - (centroid.y - prev.y) / zoomed.scale.y
8221
+ });
8089
8222
  }
8090
8223
  // onEnd: nothing to do — zoom was applied each frame.
8091
8224
  };
@@ -8569,10 +8702,12 @@ function normalizeNegativeExtent(pose) {
8569
8702
  }
8570
8703
 
8571
8704
  // src/interactions/actions/defaults/flip.ts
8572
- function flipSelection(selection, scene, axis, applyOps, geometryProjection) {
8705
+ function flipSelection(selection, scene, axis, pivot, applyOps, geometryProjection) {
8573
8706
  const ids = selection.get();
8574
8707
  if (ids.length === 0) return;
8575
8708
  const geom = AUTO_POSE_DESCRIPTOR;
8709
+ const nodes = ids.map((id) => scene.get(id)).filter((n) => n != null);
8710
+ const unionPivot = pivot === "union" ? unionBounds(nodes.map((n) => geom.getBounds(n.pose))) : null;
8576
8711
  const ops = [];
8577
8712
  for (const id of ids) {
8578
8713
  const node = scene.get(id);
@@ -8580,10 +8715,10 @@ function flipSelection(selection, scene, axis, applyOps, geometryProjection) {
8580
8715
  ops.push(createTransformOp({
8581
8716
  id,
8582
8717
  from: node.pose,
8583
- to: flipPoseViaDescriptor(node.pose, axis, geom),
8718
+ to: unionPivot ? flipPoseAboutBounds(node.pose, axis, geom, unionPivot) : flipPoseViaDescriptor(node.pose, axis, geom),
8584
8719
  label: "Flip"
8585
8720
  }));
8586
- const g = geom.getBounds(node.pose);
8721
+ const g = unionPivot ?? geom.getBounds(node.pose);
8587
8722
  const cx = g.x + g.width / 2;
8588
8723
  const cy = g.y + g.height / 2;
8589
8724
  const m = axis === "x" ? [-1, 0, 0, 1, 2 * cx, 0] : [1, 0, 0, -1, 0, 2 * cy];
@@ -8612,12 +8747,13 @@ var flipAction = {
8612
8747
  timing: "immediate",
8613
8748
  run: (deps, params) => {
8614
8749
  const axis = params?.axis ?? "x";
8750
+ const pivot = params?.pivot ?? "each";
8615
8751
  const selection = deps.selection;
8616
8752
  const scene = deps.scene;
8617
8753
  const applyOps = deps.applyOps;
8618
8754
  const geometryProjection = deps.geometryProjection;
8619
8755
  if (!selection || !scene) return;
8620
- flipSelection(selection, scene, axis, applyOps, geometryProjection);
8756
+ flipSelection(selection, scene, axis, pivot, applyOps, geometryProjection);
8621
8757
  }
8622
8758
  },
8623
8759
  enabled: requiresSelection
@@ -10566,6 +10702,12 @@ var DEFAULT_DEBUG_THEME = {
10566
10702
  fpsText: "#e0e0e0",
10567
10703
  fpsTextBg: "rgba(0, 0, 0, 0.6)"
10568
10704
  };
10705
+ var DEFAULT_DEBUG_STROKES = {
10706
+ hitbox: { width: 1, dash: [2, 2] },
10707
+ bounds: { width: 1 },
10708
+ handle: { width: 1 },
10709
+ snap: { width: 1 }
10710
+ };
10569
10711
 
10570
10712
  // src/debug/createDebugOverlayLayer.ts
10571
10713
  function createDebugOverlayLayer({
@@ -10573,6 +10715,7 @@ function createDebugOverlayLayer({
10573
10715
  config
10574
10716
  }) {
10575
10717
  const theme = { ...DEFAULT_DEBUG_THEME, ...config.theme ?? {} };
10718
+ const strokes = { ...DEFAULT_DEBUG_STROKES, ...config.strokes ?? {} };
10576
10719
  const fpsHistory = [];
10577
10720
  const FPS_WINDOW = 60;
10578
10721
  return {
@@ -10584,11 +10727,11 @@ function createDebugOverlayLayer({
10584
10727
  const s = sink.snapshot();
10585
10728
  const t = viewToTransform(view);
10586
10729
  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);
10730
+ if (config.hitboxes) emitHitboxes(out, s, view, t, theme, strokes);
10731
+ if (config.bounds) emitBounds(out, s, view, t, theme, strokes);
10732
+ if (config.handles) emitHandles(out, s, t, theme, strokes);
10590
10733
  if (config.origins) emitOrigins(out, s, t, theme);
10591
- if (config.snap) emitSnap(out, s, t, theme);
10734
+ if (config.snap) emitSnap(out, s, t, theme, strokes);
10592
10735
  if (config.ids) emitIds(out, s, t, theme);
10593
10736
  if (config.layers) emitLayersPanel(out, s, dims, theme);
10594
10737
  if (config.fps) {
@@ -10621,9 +10764,16 @@ function approxCircleScreen(cx, cy, r, segments = 24) {
10621
10764
  function rectPath2(x, y, w, h) {
10622
10765
  return { kind: "rect", x, y, width: w, height: h };
10623
10766
  }
10624
- function emitHitboxes(out, s, view, t, theme) {
10767
+ function strokeOf(s, color) {
10768
+ return {
10769
+ paint: { fill: "solid", color },
10770
+ width: s.width,
10771
+ ...s.dash && s.dash.length > 0 ? { dash: [...s.dash] } : {}
10772
+ };
10773
+ }
10774
+ function emitHitboxes(out, s, view, t, theme, strokes) {
10625
10775
  const fill = { fill: "solid", color: theme.hitboxFill };
10626
- const stroke = { paint: { fill: "solid", color: theme.hitboxStroke }, width: 1, dash: [2, 2] };
10776
+ const stroke = strokeOf(strokes.hitbox, theme.hitboxStroke);
10627
10777
  for (const h of s.hitboxes) {
10628
10778
  if (h.shape.kind === "rect") {
10629
10779
  const [sx, sy] = worldToScreen(h.shape.x, h.shape.y, t);
@@ -10637,8 +10787,8 @@ function emitHitboxes(out, s, view, t, theme) {
10637
10787
  }
10638
10788
  }
10639
10789
  }
10640
- function emitBounds(out, s, view, t, theme) {
10641
- const stroke = { paint: { fill: "solid", color: theme.bounds }, width: 1 };
10790
+ function emitBounds(out, s, view, t, theme, strokes) {
10791
+ const stroke = strokeOf(strokes.bounds, theme.bounds);
10642
10792
  for (const b of s.bounds) {
10643
10793
  const [sx, sy] = worldToScreen(b.bounds.x, b.bounds.y, t);
10644
10794
  const sw = b.bounds.width * view.scale.x;
@@ -10646,8 +10796,8 @@ function emitBounds(out, s, view, t, theme) {
10646
10796
  out.push({ kind: "path", path: rectPath2(sx, sy, sw, sh), stroke });
10647
10797
  }
10648
10798
  }
10649
- function emitHandles(out, s, t, theme) {
10650
- const stroke = { paint: { fill: "solid", color: theme.handle }, width: 1 };
10799
+ function emitHandles(out, s, t, theme, strokes) {
10800
+ const stroke = strokeOf(strokes.handle, theme.handle);
10651
10801
  for (const h of s.handles) {
10652
10802
  const [cx, cy] = worldToScreen(h.position.x, h.position.y, t);
10653
10803
  const horiz = {
@@ -10673,14 +10823,14 @@ function emitOrigins(out, s, t, theme) {
10673
10823
  out.push({ kind: "path", path: approxCircleScreen(cx, cy, 3), fill });
10674
10824
  }
10675
10825
  }
10676
- function emitSnap(out, s, t, theme) {
10826
+ function emitSnap(out, s, t, theme, strokes) {
10677
10827
  const color = theme.snap;
10678
10828
  for (const c of s.snap) {
10679
10829
  const [cx, cy] = worldToScreen(c.point.x, c.point.y, t);
10680
10830
  const cmd = {
10681
10831
  kind: "path",
10682
10832
  path: approxCircleScreen(cx, cy, 4),
10683
- ...c.accepted ? { fill: { fill: "solid", color } } : { stroke: { paint: { fill: "solid", color }, width: 1 } }
10833
+ ...c.accepted ? { fill: { fill: "solid", color } } : { stroke: strokeOf(strokes.snap, color) }
10684
10834
  };
10685
10835
  out.push(cmd);
10686
10836
  }
@@ -11063,11 +11213,22 @@ function reportDeadClaim(owner, warn) {
11063
11213
  `[weasel] exclusive claim by "${key}" matched no binding: no \`affordance:\` or \`kindOf\` target resolved against it, so the press was dropped.`
11064
11214
  );
11065
11215
  }
11216
+ function phaseRank(spec) {
11217
+ if (spec === void 0) return 0;
11218
+ const atoms = typeof spec === "string" ? [{ channel: "&", phase: spec }] : spec;
11219
+ if (atoms.length === 0) return 0;
11220
+ let min = 2;
11221
+ for (const a of atoms) {
11222
+ const rank = (a.channel === "*" ? 0 : 1) + (a.phase === "*" ? 0 : 1);
11223
+ if (rank < min) min = rank;
11224
+ }
11225
+ return min;
11226
+ }
11066
11227
  function specificity(spec) {
11067
11228
  const t = targetRank(specTargetOf(spec));
11068
11229
  const mods = "mods" in spec ? spec.mods : void 0;
11069
11230
  const m = modsCount(mods);
11070
- const p = "phase" in spec && spec.phase !== void 0 ? 1 : 0;
11231
+ const p = phaseRank("phase" in spec ? spec.phase : void 0);
11071
11232
  const typed = (spec.kind === "drop" || spec.kind === "paste") && spec.types !== void 0 && spec.types.length > 0 ? 2 : 1;
11072
11233
  return [t, m, p, typed];
11073
11234
  }
@@ -12727,8 +12888,7 @@ function useGestureDispatcher(opts) {
12727
12888
  if (!cd) return;
12728
12889
  const items = itemsFromClipboardData(cd);
12729
12890
  if (items.length === 0) return;
12730
- e.preventDefault();
12731
- dispatch2({
12891
+ const result = dispatch2({
12732
12892
  kind: "paste",
12733
12893
  items,
12734
12894
  // ClipboardEvent carries no modifier state.
@@ -12737,6 +12897,7 @@ function useGestureDispatcher(opts) {
12737
12897
  metaKey: false,
12738
12898
  shiftKey: false
12739
12899
  });
12900
+ if (result === "handled") e.preventDefault();
12740
12901
  };
12741
12902
  if (keyboard) {
12742
12903
  window.addEventListener("keydown", onKeyDown);
@@ -13080,6 +13241,52 @@ function useViewAnimation(setView) {
13080
13241
  );
13081
13242
  return { animateTo, animateToBounds, cancel };
13082
13243
  }
13244
+ function validateId(id, kind) {
13245
+ if (id.length === 0) {
13246
+ throw new Error(`weasel: ${kind} id may not be empty`);
13247
+ }
13248
+ if (RESERVED_ID_PREFIXES.has(id[0])) {
13249
+ throw new Error(
13250
+ `weasel: ${kind} id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
13251
+ );
13252
+ }
13253
+ if (RESERVED_ID_NAMES.has(id)) {
13254
+ throw new Error(
13255
+ `weasel: ${kind} id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
13256
+ );
13257
+ }
13258
+ }
13259
+ function defineTool(def) {
13260
+ validateId(def.id, "tool");
13261
+ const resolveCursor = (ctx) => {
13262
+ if (def.cursor == null) return "";
13263
+ return typeof def.cursor === "function" ? def.cursor(ctx) : def.cursor;
13264
+ };
13265
+ return {
13266
+ id: def.id,
13267
+ eligibility: {
13268
+ focus: true,
13269
+ capabilities: def.capabilities,
13270
+ ...def.hotkey ? { offhand: def.hotkey } : {}
13271
+ },
13272
+ actions: def.actions,
13273
+ def,
13274
+ presentation: def.presentation,
13275
+ keybinding: def.keybinding,
13276
+ onActivate: def.onActivate,
13277
+ onDeactivate: def.onDeactivate,
13278
+ initScratch: def.initScratch ?? (() => null),
13279
+ cursor: resolveCursor,
13280
+ bindings: def.bindings,
13281
+ overlay: def.overlay,
13282
+ overlayPosition: def.overlayPosition
13283
+ };
13284
+ }
13285
+
13286
+ // src/tools/defineViewportTool.ts
13287
+ function defineViewportTool(def) {
13288
+ return defineTool(def);
13289
+ }
13083
13290
 
13084
13291
  // src/interactions/actions/defaults/toolOffhand.ts
13085
13292
  var TOOL_OFFHAND_ID = "tool.offhand";
@@ -13174,17 +13381,19 @@ function useContributions(opts) {
13174
13381
  const { ordered, state } = snapshot();
13175
13382
  return scopeBindings(ordered, state);
13176
13383
  }, [snapshot]);
13177
- const overlays = useCallback(() => {
13384
+ const overlays = useCallback((position = "top") => {
13178
13385
  const { ordered, state } = snapshot();
13179
13386
  const active = [];
13180
13387
  const hotkey = [];
13181
13388
  const ambient = [];
13182
13389
  for (const entry of ordered) {
13183
13390
  if (!entry.overlay) continue;
13391
+ if ((entry.overlayPosition ?? "top") !== position) continue;
13392
+ const layers = Array.isArray(entry.overlay) ? entry.overlay : [entry.overlay];
13184
13393
  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);
13394
+ if (scope === "active") active.push(...layers);
13395
+ else if (scope === "hotkey") hotkey.push(...layers);
13396
+ else if (scope === "ambient") ambient.push(...layers);
13188
13397
  }
13189
13398
  return [...active, ...hotkey, ...ambient];
13190
13399
  }, [snapshot]);
@@ -14011,7 +14220,7 @@ function isBoundsGradient(fill) {
14011
14220
  }
14012
14221
 
14013
14222
  // src/features/patterns/resolveSpec.ts
14014
- var cache5 = /* @__PURE__ */ new Map();
14223
+ var cache6 = /* @__PURE__ */ new Map();
14015
14224
  function specKey(spec) {
14016
14225
  const keys = Object.keys(spec).sort();
14017
14226
  const bag = spec;
@@ -14019,10 +14228,10 @@ function specKey(spec) {
14019
14228
  }
14020
14229
  function resolvePatternSpec(spec) {
14021
14230
  const key = specKey(spec);
14022
- const hit = cache5.get(key);
14231
+ const hit = cache6.get(key);
14023
14232
  if (hit !== void 0) return hit;
14024
14233
  const built = buildTile(spec);
14025
- cache5.set(key, built);
14234
+ cache6.set(key, built);
14026
14235
  return built;
14027
14236
  }
14028
14237
  function buildTile(spec) {
@@ -14058,7 +14267,7 @@ function resolveFillPattern(fill) {
14058
14267
  }
14059
14268
 
14060
14269
  // src/features/images/imageCache.ts
14061
- var cache6 = /* @__PURE__ */ new Map();
14270
+ var cache7 = /* @__PURE__ */ new Map();
14062
14271
  var subscribers = /* @__PURE__ */ new Set();
14063
14272
  var defaultLoader = async (src) => {
14064
14273
  if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
@@ -14076,28 +14285,28 @@ function notify() {
14076
14285
  }
14077
14286
  function startLoad(src) {
14078
14287
  const entry = { status: "loading" };
14079
- cache6.set(src, entry);
14288
+ cache7.set(src, entry);
14080
14289
  loader(src).then(
14081
14290
  (bitmap) => {
14082
- if (cache6.get(src) !== entry) return;
14083
- cache6.set(src, { status: "ready", bitmap });
14291
+ if (cache7.get(src) !== entry) return;
14292
+ cache7.set(src, { status: "ready", bitmap });
14084
14293
  notify();
14085
14294
  },
14086
14295
  () => {
14087
- if (cache6.get(src) !== entry) return;
14088
- cache6.set(src, { status: "error" });
14296
+ if (cache7.get(src) !== entry) return;
14297
+ cache7.set(src, { status: "error" });
14089
14298
  notify();
14090
14299
  }
14091
14300
  );
14092
14301
  }
14093
14302
  function getImageBitmap(src) {
14094
- const entry = cache6.get(src);
14303
+ const entry = cache7.get(src);
14095
14304
  if (entry) return entry.bitmap;
14096
14305
  startLoad(src);
14097
14306
  return void 0;
14098
14307
  }
14099
14308
  function imageStatus(src) {
14100
- return cache6.get(src)?.status ?? "idle";
14309
+ return cache7.get(src)?.status ?? "idle";
14101
14310
  }
14102
14311
  function subscribeImageReady(cb) {
14103
14312
  subscribers.add(cb);
@@ -14464,6 +14673,7 @@ registerBuiltInShapePainters();
14464
14673
  function pickTopMostHit(ids, adapter) {
14465
14674
  if (ids.length === 0) return null;
14466
14675
  if (ids.length === 1) return ids[0];
14676
+ let candidates = ids;
14467
14677
  const getParent = adapter?.getParent;
14468
14678
  if (typeof getParent === "function") {
14469
14679
  const set = new Set(ids);
@@ -14478,15 +14688,40 @@ function pickTopMostHit(ids, adapter) {
14478
14688
  }
14479
14689
  if (dropped.size > 0) {
14480
14690
  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];
14691
+ if (filtered.length === 1) return filtered[0];
14692
+ if (filtered.length > 1) candidates = filtered;
14693
+ }
14694
+ }
14695
+ return pickHighestZ(candidates, adapter);
14696
+ }
14697
+ function pickHighestZ(ids, adapter) {
14698
+ const compareZ = adapter?.compareZ;
14699
+ if (typeof compareZ === "function") {
14700
+ let best = ids[0];
14701
+ for (let i = 1; i < ids.length; i++) {
14702
+ if (compareZ(ids[i], best) >= 0) best = ids[i];
14703
+ }
14704
+ return best;
14705
+ }
14706
+ const getZIndex = adapter?.getZIndex;
14707
+ if (typeof getZIndex === "function") {
14708
+ let best = ids[0];
14709
+ let bestZ = zOf(getZIndex, best);
14710
+ for (let i = 1; i < ids.length; i++) {
14711
+ const z = zOf(getZIndex, ids[i]);
14712
+ if (z >= bestZ) {
14713
+ best = ids[i];
14714
+ bestZ = z;
14485
14715
  }
14486
14716
  }
14717
+ return best;
14487
14718
  }
14488
14719
  return ids[ids.length - 1];
14489
14720
  }
14721
+ function zOf(getZIndex, id) {
14722
+ const z = getZIndex(id);
14723
+ return typeof z === "number" && Number.isFinite(z) ? z : -Infinity;
14724
+ }
14490
14725
 
14491
14726
  // src/core/selection/selectionTarget.ts
14492
14727
  var MULTI_RESIZE_TARGET_ID = "__weasel:multi-selection";
@@ -14891,9 +15126,19 @@ function composeAffordanceLayer(id, label, affordances) {
14891
15126
  },
14892
15127
  // The walk itself lives in `hitAffordanceRegions` so this layer and
14893
15128
  // `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
15129
+ // drift. This wrapper lifts the region's declared cursor and claim onto
15130
+ // the binding, the way `buildAffordanceAt` lifts them onto `AffordanceHit`.
15131
+ hitTest: (wx, wy, state, view, _dims, isVisible) => {
15132
+ const hit = hitAffordanceRegions(affordances, wx, wy, state, view, isVisible);
15133
+ if (!hit) return null;
15134
+ const { cursor, strength, claimedKinds } = hit.region;
15135
+ return {
15136
+ ...hit.binding,
15137
+ ...cursor !== void 0 ? { cursor } : {},
15138
+ ...strength !== void 0 ? { strength } : {},
15139
+ ...claimedKinds !== void 0 ? { claimedKinds } : {}
15140
+ };
15141
+ }
14897
15142
  };
14898
15143
  }
14899
15144
  function asChromeState(data) {
@@ -15676,7 +15921,7 @@ function useEllipseTool() {
15676
15921
  );
15677
15922
  }
15678
15923
  function useImageTool(options) {
15679
- const { src, label = "Image" } = options;
15924
+ const { src, label = "Image", preview = "bitmap" } = options;
15680
15925
  return useMemo(
15681
15926
  () => defineTool({
15682
15927
  id: "image",
@@ -15695,11 +15940,11 @@ function useImageTool(options) {
15695
15940
  {
15696
15941
  spec: { kind: "drag" },
15697
15942
  actionId: "insert",
15698
- opts: { params: { kind: "image", src } }
15943
+ opts: { params: { kind: "image", src, preview } }
15699
15944
  }
15700
15945
  ]
15701
15946
  }),
15702
- [src, label]
15947
+ [src, label, preview]
15703
15948
  );
15704
15949
  }
15705
15950
  function useEyedropperTool(opts) {
@@ -16166,6 +16411,19 @@ function composeOrderedLayers(layersMap, standardLayers, sceneLayers) {
16166
16411
  }
16167
16412
  return out;
16168
16413
  }
16414
+ function placeToolOverlays(ordered, selectionOverlay, overlays) {
16415
+ const before = overlays("before-selection");
16416
+ const after = overlays("after-selection");
16417
+ const at = selectionOverlay ? ordered.indexOf(selectionOverlay) : -1;
16418
+ if (at >= 0) {
16419
+ ordered.splice(at + 1, 0, ...after);
16420
+ ordered.splice(at, 0, ...before);
16421
+ } else {
16422
+ ordered.push(...before, ...after);
16423
+ }
16424
+ ordered.push(...overlays("top"));
16425
+ return ordered;
16426
+ }
16169
16427
 
16170
16428
  // src/canvas/toolPreview.ts
16171
16429
  function* toolsInPriorityOrder(tools) {
@@ -17712,7 +17970,7 @@ function CanvasInner(props, ref) {
17712
17970
  const out = composeOrderedLayers(effectiveLayersMap, standardLayers, sceneLayers);
17713
17971
  if (decorationLayer) out.push(decorationLayer);
17714
17972
  if (tools) {
17715
- out.push(...tools.getActiveOverlays());
17973
+ placeToolOverlays(out, standardLayers.selectionOverlay, tools.getActiveOverlays);
17716
17974
  }
17717
17975
  return out;
17718
17976
  }, [layersMap, adapter, selectedIds, effectiveBoundsOf, multiActive2, debugSink, tools, backgroundLayer, decorationLayer]);
@@ -19379,6 +19637,7 @@ function useSceneSelectTool(args) {
19379
19637
  boundsOf: wiredBoundsOf
19380
19638
  };
19381
19639
  }
19640
+ var GHOST_ALPHA = 0.85;
19382
19641
  function usePreviewGhostLayer(args) {
19383
19642
  const { scene, tools, sceneSlot, dispatcher } = args;
19384
19643
  const sceneRef = useRef(scene);
@@ -19465,15 +19724,26 @@ function usePreviewGhostLayer(args) {
19465
19724
  }
19466
19725
  return [group];
19467
19726
  };
19727
+ const opaqueSet = /* @__PURE__ */ new Set();
19728
+ for (const source of sources) {
19729
+ const ids = source.previewOpaqueIds?.();
19730
+ if (!ids) continue;
19731
+ for (const id of ids) opaqueSet.add(id);
19732
+ }
19468
19733
  const children = [];
19734
+ const opaque = [];
19469
19735
  for (const id of idSet) {
19470
19736
  const node = sc.get(asNodeId(id));
19471
19737
  const parent = node?.parent;
19472
19738
  if (parent != null && idSet.has(parent)) continue;
19473
- for (const cmd of buildSubtree(id)) children.push(cmd);
19739
+ const sink = opaqueSet.has(id) ? opaque : children;
19740
+ for (const cmd of buildSubtree(id)) sink.push(cmd);
19474
19741
  }
19475
- if (children.length === 0) return [];
19476
- return [{ kind: "group", alpha: 0.85, children }];
19742
+ if (children.length === 0 && opaque.length === 0) return [];
19743
+ const out = [];
19744
+ if (opaque.length > 0) out.push({ kind: "group", children: opaque });
19745
+ if (children.length > 0) out.push({ kind: "group", alpha: GHOST_ALPHA, children });
19746
+ return out;
19477
19747
  }
19478
19748
  }), []);
19479
19749
  }
@@ -19483,6 +19753,7 @@ var DEFAULT_STYLE = {
19483
19753
  dash: [3, 3],
19484
19754
  lineWidth: 1
19485
19755
  };
19756
+ var PREVIEW_CONTENT_OPACITY = 0.85;
19486
19757
  function useDispatcherOverlayLayer(args) {
19487
19758
  const { dispatcher, style } = args;
19488
19759
  const dispatcherRef = useRef(dispatcher);
@@ -19545,6 +19816,26 @@ function useDispatcherOverlayLayer(args) {
19545
19816
  pathCmd = rectPath(sx, sy, b.width * view.scale.x, b.height * view.scale.y);
19546
19817
  break;
19547
19818
  }
19819
+ case "image": {
19820
+ const e = ov.extras;
19821
+ const [sx, sy] = worldToScreen(b.x, b.y, t);
19822
+ const sw = b.width * view.scale.x;
19823
+ const sh = b.height * view.scale.y;
19824
+ const bmp = e.preview === "outline" || !e.src ? void 0 : getImageBitmap(e.src);
19825
+ if (bmp) {
19826
+ out.push({
19827
+ kind: "image",
19828
+ image: bmp,
19829
+ x: sx,
19830
+ y: sy,
19831
+ w: sw,
19832
+ h: sh,
19833
+ opacity: PREVIEW_CONTENT_OPACITY
19834
+ });
19835
+ }
19836
+ pathCmd = rectPath(sx, sy, sw, sh);
19837
+ break;
19838
+ }
19548
19839
  case "ellipse": {
19549
19840
  const [sx, sy] = worldToScreen(b.x, b.y, t);
19550
19841
  pathCmd = ellipsePath({
@@ -19603,7 +19894,7 @@ function useDispatcherOverlayLayer(args) {
19603
19894
  const cmd = {
19604
19895
  kind: "path",
19605
19896
  path: pathCmd,
19606
- fill: ov.shape === "line" || ov.shape === "pencil" ? void 0 : { color: cfg.fill },
19897
+ fill: ov.shape === "line" || ov.shape === "pencil" || ov.shape === "image" ? void 0 : { color: cfg.fill },
19607
19898
  stroke: { paint: { color: cfg.stroke }, width: cfg.lineWidth, dash: cfg.dash }
19608
19899
  };
19609
19900
  out.push(cmd);
@@ -20316,9 +20607,6 @@ function buildClassifyTarget(getSelection, pickBest, kindOfNode) {
20316
20607
  function w2s3(wx, wy, view) {
20317
20608
  return [(wx - view.x) * view.scale.x, (wy - view.y) * view.scale.y];
20318
20609
  }
20319
- function r2s(radiusWorld, view) {
20320
- return radiusWorld * meanScale(view.scale);
20321
- }
20322
20610
  var SLOP_FILL = "rgba(255, 80, 140, 0.18)";
20323
20611
  var SLOP_STROKE = "rgba(255, 80, 140, 0.55)";
20324
20612
  function createSlopsDebugLayer(opts) {
@@ -20329,7 +20617,7 @@ function createSlopsDebugLayer(opts) {
20329
20617
  draw: (_data, view) => {
20330
20618
  const out = [];
20331
20619
  const sel = opts.selectionRef.current?.current ?? [];
20332
- const r = r2s(HANDLE_HIT_RADIUS, view);
20620
+ const r = HANDLE_HIT_RADIUS;
20333
20621
  for (const id of sel) {
20334
20622
  const b = opts.boundsOf(id);
20335
20623
  if (!b) continue;
@@ -20366,6 +20654,7 @@ function createSlopsDebugLayer(opts) {
20366
20654
  }
20367
20655
  const editingId = opts.getEditingId();
20368
20656
  if (editingId) {
20657
+ const anchorR = ANCHOR_HIT_RADIUS;
20369
20658
  const pose = opts.getPose(editingId);
20370
20659
  if (pose && pose.kind === "polygon") {
20371
20660
  const anchors = enumerateAnchors(pose);
@@ -20373,7 +20662,7 @@ function createSlopsDebugLayer(opts) {
20373
20662
  const [sx, sy] = w2s3(a.x, a.y, view);
20374
20663
  out.push({
20375
20664
  kind: "path",
20376
- path: circlePath(sx, sy, r),
20665
+ path: circlePath(sx, sy, anchorR),
20377
20666
  fill: { fill: "solid", color: SLOP_FILL },
20378
20667
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
20379
20668
  });
@@ -20381,7 +20670,7 @@ function createSlopsDebugLayer(opts) {
20381
20670
  const [csx, csy] = w2s3(a.controlIn.x, a.controlIn.y, view);
20382
20671
  out.push({
20383
20672
  kind: "path",
20384
- path: circlePath(csx, csy, r),
20673
+ path: circlePath(csx, csy, anchorR),
20385
20674
  fill: { fill: "solid", color: SLOP_FILL },
20386
20675
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
20387
20676
  });
@@ -20390,7 +20679,7 @@ function createSlopsDebugLayer(opts) {
20390
20679
  const [csx, csy] = w2s3(a.controlOut.x, a.controlOut.y, view);
20391
20680
  out.push({
20392
20681
  kind: "path",
20393
- path: circlePath(csx, csy, r),
20682
+ path: circlePath(csx, csy, anchorR),
20394
20683
  fill: { fill: "solid", color: SLOP_FILL },
20395
20684
  stroke: { paint: { fill: "solid", color: SLOP_STROKE }, width: 1 }
20396
20685
  });
@@ -20472,9 +20761,9 @@ function DepRegistryProviderIfRoot({ children }) {
20472
20761
  function hitTestArea(scene, bounds) {
20473
20762
  const { x, y, width: w, height: h } = bounds;
20474
20763
  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 });
20764
+ return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h }, true);
20476
20765
  }
20477
- function hitTestAreaPolygon(scene, area, areaBounds) {
20766
+ function hitTestAreaPolygon(scene, area, areaBounds, areaIsRect = false) {
20478
20767
  const ab = areaBounds ?? boundsOf(area);
20479
20768
  if (!ab) return [];
20480
20769
  const hits = [];
@@ -20491,13 +20780,22 @@ function hitTestAreaPolygon(scene, area, areaBounds) {
20491
20780
  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
20781
  continue;
20493
20782
  }
20494
- if (!silhouette) {
20783
+ 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
20784
  hits.push(node.id);
20496
20785
  continue;
20497
20786
  }
20498
- if (silhouetteOverlapsArea(pose.coords, area)) {
20499
- hits.push(node.id);
20787
+ if (silhouette) {
20788
+ if (silhouetteOverlapsArea(pose.coords, area)) {
20789
+ hits.push(node.id);
20790
+ }
20791
+ continue;
20792
+ }
20793
+ const drawn = findShapeSilhouette(node, pose);
20794
+ if (drawn?.kind === "polygon") {
20795
+ if (silhouetteOverlapsArea(drawn.coords, area)) hits.push(node.id);
20796
+ continue;
20500
20797
  }
20798
+ hits.push(node.id);
20501
20799
  }
20502
20800
  return hits;
20503
20801
  }
@@ -24324,6 +24622,14 @@ function useSceneTextEdit(scene, container, options = {}) {
24324
24622
  const setter = optsRef.current.setRuns;
24325
24623
  const data = setter ? setter(node.data, runs) : { ...node.data, runs };
24326
24624
  sceneRef.current.update(nid, { data });
24625
+ },
24626
+ setStyle: (id, style) => {
24627
+ const nid = asNodeId(id);
24628
+ const node = sceneRef.current.get(nid);
24629
+ if (!node) return;
24630
+ const setter = optsRef.current.setStyle;
24631
+ const data = setter ? setter(node.data, style) : { ...node.data, style };
24632
+ sceneRef.current.update(nid, { data });
24327
24633
  }
24328
24634
  });
24329
24635
  const onDoubleClick = useCallback((e) => {
@@ -28032,6 +28338,6 @@ function mergeContributions(...bundles) {
28032
28338
  return out;
28033
28339
  }
28034
28340
 
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
28341
+ 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 };
28342
+ //# sourceMappingURL=chunk-SMCZRSYB.js.map
28343
+ //# sourceMappingURL=chunk-SMCZRSYB.js.map