@weasel-js/core 1.8.0 → 1.9.0

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 (139) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +9 -5
  3. package/dist/animation/useAnimator.js +20 -13
  4. package/dist/animation/useAnimator.js.map +1 -1
  5. package/dist/{builtins-BW32dOXy.d.ts → builtins-CQcvcLXh.d.ts} +105 -31
  6. package/dist/canvas/DrawCanvas.js +33 -33
  7. package/dist/canvas/DrawCanvas.js.map +1 -1
  8. package/dist/canvas/MinimapCanvas.js +29 -29
  9. package/dist/canvas/MinimapCanvas.js.map +1 -1
  10. package/dist/canvas/NodeShape.js +63 -54
  11. package/dist/canvas/NodeShape.js.map +1 -1
  12. package/dist/canvas/SceneCanvas/features.js +26 -5
  13. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  14. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +3 -2
  15. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  16. package/dist/canvas/SceneCanvas.js +526 -529
  17. package/dist/canvas/SceneCanvas.js.map +1 -1
  18. package/dist/canvas/SceneViewCanvas.js +35 -35
  19. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  20. package/dist/canvas/defaultDrawOne.js +6 -1
  21. package/dist/canvas/defaultDrawOne.js.map +1 -1
  22. package/dist/canvas/pickWalk.js.map +1 -1
  23. package/dist/canvas/sceneAdapter.js.map +1 -1
  24. package/dist/canvas/useHitObserver.js +23 -0
  25. package/dist/canvas/useHitObserver.js.map +1 -0
  26. package/dist/canvas/useHostAnchor.js.map +1 -1
  27. package/dist/core/device/useDeviceProfile.js.map +1 -1
  28. package/dist/core/scene/derivedPath.js.map +1 -1
  29. package/dist/core/scene/effectivePose.js.map +1 -1
  30. package/dist/core/scene/kitRegistry.js.map +1 -1
  31. package/dist/core/scene/scene.js +9 -0
  32. package/dist/core/scene/scene.js.map +1 -1
  33. package/dist/core/scene/types.js.map +1 -1
  34. package/dist/core/scene/useScene.js.map +1 -1
  35. package/dist/core/selection/useSelection.js +27 -33
  36. package/dist/core/selection/useSelection.js.map +1 -1
  37. package/dist/core/stableStringify.js +8 -0
  38. package/dist/core/stableStringify.js.map +1 -0
  39. package/dist/core/viewport/fitViewToBounds.js +3 -3
  40. package/dist/core/viewport/fitViewToBounds.js.map +1 -1
  41. package/dist/core/viewport/screenSlop.js +8 -2
  42. package/dist/core/viewport/screenSlop.js.map +1 -1
  43. package/dist/features/minimap/actions.js.map +1 -1
  44. package/dist/features/paths/markerCommands.js +95 -63
  45. package/dist/features/paths/markerCommands.js.map +1 -1
  46. package/dist/features/paths/tessellate/dash.js +52 -0
  47. package/dist/features/paths/tessellate/dash.js.map +1 -0
  48. package/dist/features/paths/tessellate/insets.js +32 -0
  49. package/dist/features/paths/tessellate/insets.js.map +1 -0
  50. package/dist/features/paths/tessellate/metric.js +72 -0
  51. package/dist/features/paths/tessellate/metric.js.map +1 -0
  52. package/dist/features/paths/tessellate/stroke.js +74 -134
  53. package/dist/features/paths/tessellate/stroke.js.map +1 -1
  54. package/dist/features/paths/tessellate/vertexWidths.js +32 -0
  55. package/dist/features/paths/tessellate/vertexWidths.js.map +1 -0
  56. package/dist/features/pointer/PointerContext.js +2 -2
  57. package/dist/features/pointer/PointerContext.js.map +1 -1
  58. package/dist/features/poseRun/usePoseRun.js.map +1 -1
  59. package/dist/features/text/useTextEdit.js +86 -84
  60. package/dist/features/text/useTextEdit.js.map +1 -1
  61. package/dist/index.d.ts +84 -45
  62. package/dist/index.js +269 -268
  63. package/dist/interactions/actions/defaults/align.js.map +1 -1
  64. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  65. package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
  66. package/dist/interactions/actions/defaults/booleans.js.map +1 -1
  67. package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
  68. package/dist/interactions/actions/defaults/clearSelection.js.map +1 -1
  69. package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
  70. package/dist/interactions/actions/defaults/clone.js.map +1 -1
  71. package/dist/interactions/actions/defaults/createOutlines.js +1 -1
  72. package/dist/interactions/actions/defaults/createOutlines.js.map +1 -1
  73. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  74. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  75. package/dist/interactions/actions/defaults/distribute.js.map +1 -1
  76. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  77. package/dist/interactions/actions/defaults/editAnchors.js.map +1 -1
  78. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  79. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  80. package/dist/interactions/actions/defaults/escape.js +1 -1
  81. package/dist/interactions/actions/defaults/escape.js.map +1 -1
  82. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  83. package/dist/interactions/actions/defaults/flip.js.map +1 -1
  84. package/dist/interactions/actions/defaults/group.js.map +1 -1
  85. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  86. package/dist/interactions/actions/defaults/insert.js.map +1 -1
  87. package/dist/interactions/actions/defaults/insertPathAnchor.js +1 -1
  88. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  89. package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
  90. package/dist/interactions/actions/defaults/move.js.map +1 -1
  91. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  92. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  93. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  94. package/dist/interactions/actions/defaults/requiresSelection.js.map +1 -1
  95. package/dist/interactions/actions/defaults/resize.js.map +1 -1
  96. package/dist/interactions/actions/defaults/rotate.js.map +1 -1
  97. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  98. package/dist/interactions/actions/defaults/slice.js.map +1 -1
  99. package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
  100. package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
  101. package/dist/interactions/actions/defaults/undoRedo.js +1 -1
  102. package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
  103. package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
  104. package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
  105. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  106. package/dist/interactions/actions/planeInput.js.map +1 -1
  107. package/dist/interactions/gestures/shared/screenTolerance.js.map +1 -1
  108. package/dist/{registerProgram-ateHDGjw.d.ts → registerProgram-BMZRDgrh.d.ts} +4 -0
  109. package/dist/renderer/cache/markerCommandCache.js +46 -42
  110. package/dist/renderer/cache/markerCommandCache.js.map +1 -1
  111. package/dist/renderer/cache/outlineStrokeMeshCache.js +30 -25
  112. package/dist/renderer/cache/outlineStrokeMeshCache.js.map +1 -1
  113. package/dist/renderer/cache/strokeMeshCache.js +42 -31
  114. package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
  115. package/dist/renderer/cullDrawCommands.js +32 -31
  116. package/dist/renderer/cullDrawCommands.js.map +1 -1
  117. package/dist/renderer/draw.js +315 -362
  118. package/dist/renderer/draw.js.map +1 -1
  119. package/dist/renderer/drawBatch.js.map +1 -1
  120. package/dist/renderer/glyphOutlineMesh.js +67 -0
  121. package/dist/renderer/glyphOutlineMesh.js.map +1 -0
  122. package/dist/renderer/math/mat3.js +8 -3
  123. package/dist/renderer/math/mat3.js.map +1 -1
  124. package/dist/renderer/shaders/batchFill.js +3 -2
  125. package/dist/renderer/shaders/batchFill.js.map +1 -1
  126. package/dist/renderer/strokeLengths.js +50 -0
  127. package/dist/renderer/strokeLengths.js.map +1 -0
  128. package/dist/renderer.d.ts +2 -2
  129. package/dist/{test-seams-fYf6m5pu.d.ts → test-seams-CuIxe0xY.d.ts} +1 -1
  130. package/dist/test-seams.d.ts +2 -2
  131. package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
  132. package/dist/tools/builtin/pen/usePenTool.js +1 -1
  133. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  134. package/dist/tools/builtin/select/selectionContributions.js +43 -23
  135. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  136. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  137. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  138. package/dist/version.js +1 -1
  139. package/package.json +17 -16
@@ -1,10 +1,11 @@
1
- import { getMarker as e } from "../../core/strokeMarkers.js";
2
- import { markerKeyOf as t, resolveMarkerSize as n } from "../../core/markerInset.js";
3
- import { markerSites as r } from "./markerSites.js";
4
- import { boundsOfPath as i } from "@weasel-js/geom";
5
- import { extractPolylines as a } from "@weasel-js/geom/tessellate";
1
+ import { coordsOutOfMetric as e, polylineIntoMetric as t } from "./tessellate/metric.js";
2
+ import { getMarker as n } from "../../core/strokeMarkers.js";
3
+ import { markerKeyOf as r, resolveMarkerSize as i } from "../../core/markerInset.js";
4
+ import { markerSites as a } from "./markerSites.js";
5
+ import { boundsOfPath as o } from "@weasel-js/geom";
6
+ import { extractPolylines as s } from "@weasel-js/geom/tessellate";
6
7
  //#region src/features/paths/markerCommands.ts
7
- function o(e, t) {
8
+ function c(e, t) {
8
9
  let n = e, r = Math.cos(t.angle), i = Math.sin(t.angle), a = new Float32Array(n.coords.length);
9
10
  for (let e = 0; e < n.coords.length; e += 2) {
10
11
  let o = n.coords[e], s = n.coords[e + 1];
@@ -17,52 +18,78 @@ function o(e, t) {
17
18
  fillRule: n.fillRule
18
19
  };
19
20
  }
20
- function s(e, t, n) {
21
+ function l(e, t, n) {
21
22
  let r = e ?? n;
22
23
  if (r !== "none") return r === "line" ? t.paint : r;
23
24
  }
24
- function c(i, s, c, l) {
25
- let u = {
26
- start: s.markerStart !== void 0,
27
- mid: s.markerMid !== void 0,
28
- end: s.markerEnd !== void 0
25
+ function u(o, l, u, d, f = {}) {
26
+ let p = {
27
+ start: l.markerStart,
28
+ mid: l.markerMid,
29
+ end: l.markerEnd
29
30
  };
30
- if (!u.start && !u.mid && !u.end) return [];
31
- let d = (e) => e === "start" ? s.markerStart : e === "mid" ? s.markerMid : s.markerEnd, f = [];
32
- for (let p of a(i, { flattenTolerance: l })) for (let i of r(p, u)) {
33
- let r = d(i.role);
34
- if (r === void 0) continue;
35
- let a = e(t(r));
36
- if (a === void 0) continue;
37
- let l = n(r, c), u = a.orient === void 0 || a.orient === "auto" ? i.angle : a.orient;
38
- f.push({
39
- entry: a,
40
- size: l,
41
- path: o(a.path({
42
- size: l,
43
- stroke: s
31
+ if (p.start === void 0 && p.mid === void 0 && p.end === void 0) return [];
32
+ let m = /* @__PURE__ */ new Map();
33
+ for (let e of [
34
+ "start",
35
+ "mid",
36
+ "end"
37
+ ]) {
38
+ if (p[e] === void 0) continue;
39
+ let t = f[e], n = m.get(t);
40
+ n === void 0 && m.set(t, n = {
41
+ start: !1,
42
+ mid: !1,
43
+ end: !1
44
+ }), n[e] = !0;
45
+ }
46
+ let h = [];
47
+ for (let f of s(o, { flattenTolerance: d })) for (let [o, s] of m) {
48
+ let d = o ? {
49
+ ...f,
50
+ points: f.points.slice()
51
+ } : f;
52
+ o && t(d, o.metric);
53
+ for (let t of a(d, s)) {
54
+ let a = p[t.role];
55
+ if (a === void 0) continue;
56
+ let s = n(r(a));
57
+ if (s === void 0) continue;
58
+ let d = i(a, u), f = s.orient === void 0 || s.orient === "auto" ? t.angle : s.orient, m = c(s.path({
59
+ size: d,
60
+ stroke: l
44
61
  }), {
45
- ...i,
46
- angle: u
47
- })
48
- });
62
+ ...t,
63
+ angle: f
64
+ });
65
+ o && e(m.coords, o.metric), h.push(o ? {
66
+ entry: s,
67
+ size: d,
68
+ path: m,
69
+ space: o
70
+ } : {
71
+ entry: s,
72
+ size: d,
73
+ path: m
74
+ });
75
+ }
49
76
  }
50
- return f;
77
+ return h;
51
78
  }
52
- function l(e, t) {
79
+ function d(e, t) {
53
80
  let n = [];
54
- for (let { entry: r, path: i, size: a } of e) {
55
- let e = s(r.fill, t, "line"), o = r.outline ? {
56
- paint: s(r.outline.paint, t, "line"),
57
- width: r.outline.width * a
81
+ for (let { entry: r, path: i, size: a, space: o } of e) {
82
+ let e = l(r.fill, t, "line"), s = r.outline ? r.outline.width * a : 0, c = r.outline ? {
83
+ paint: l(r.outline.paint, t, "line"),
84
+ width: o ? { px: s * o.pxPerUnit } : s
58
85
  } : null;
59
- (e !== void 0 || o !== null && o.paint !== void 0) && n.push({
86
+ (e !== void 0 || c !== null && c.paint !== void 0) && n.push({
60
87
  kind: "path",
61
88
  path: i,
62
89
  ...e ? { fill: e } : {},
63
- ...o && o.paint ? { stroke: {
64
- paint: o.paint,
65
- width: o.width,
90
+ ...c && c.paint ? { stroke: {
91
+ paint: c.paint,
92
+ width: c.width,
66
93
  cap: "round",
67
94
  join: "round"
68
95
  } } : {}
@@ -70,47 +97,52 @@ function l(e, t) {
70
97
  }
71
98
  return n;
72
99
  }
73
- function u(e, t) {
100
+ function f(e, t) {
74
101
  if (e.reads === void 0) return null;
75
102
  let n = "";
76
103
  for (let r of e.reads) n += `${JSON.stringify(t[r]) ?? ""}|`;
77
104
  return n;
78
105
  }
79
- var d = 64, f = /* @__PURE__ */ new WeakMap(), p = /* @__PURE__ */ new WeakMap();
80
- function m(e, t) {
81
- let n = i(e.path({
106
+ var p = 64, m = /* @__PURE__ */ new WeakMap(), h = /* @__PURE__ */ new WeakMap();
107
+ function g(e, t) {
108
+ let n = o(e.path({
82
109
  size: 1,
83
110
  stroke: t
84
111
  }));
85
112
  return Math.max(Math.abs(n.x), Math.abs(n.x + n.width), Math.abs(n.y), Math.abs(n.y + n.height)) + (e.outline ? e.outline.width / 2 : 0);
86
113
  }
87
- function h(e, t) {
88
- let n = u(e, t);
114
+ function _(e, t) {
115
+ let n = f(e, t);
89
116
  if (n === null) {
90
- let n = p.get(e);
91
- n === void 0 && p.set(e, n = /* @__PURE__ */ new WeakMap());
117
+ let n = h.get(e);
118
+ n === void 0 && h.set(e, n = /* @__PURE__ */ new WeakMap());
92
119
  let r = n.get(t);
93
- return r === void 0 && n.set(t, r = m(e, t)), r;
120
+ return r === void 0 && n.set(t, r = g(e, t)), r;
94
121
  }
95
- let r = f.get(e);
96
- r === void 0 && f.set(e, r = /* @__PURE__ */ new Map());
122
+ let r = m.get(e);
123
+ r === void 0 && m.set(e, r = /* @__PURE__ */ new Map());
97
124
  let i = r.get(n);
98
- return i === void 0 && (r.size >= d && r.clear(), r.set(n, i = m(e, t))), i;
125
+ return i === void 0 && (r.size >= p && r.clear(), r.set(n, i = g(e, t))), i;
99
126
  }
100
- function g(r, i, a = 1) {
101
- let o = 0;
102
- for (let s of [
103
- r.markerStart,
104
- r.markerMid,
105
- r.markerEnd
127
+ function v(e) {
128
+ let t = {
129
+ world: 0,
130
+ px: 0
131
+ }, i = e.width ?? 1;
132
+ for (let a of [
133
+ e.markerStart,
134
+ e.markerMid,
135
+ e.markerEnd
106
136
  ]) {
107
- if (s === void 0) continue;
108
- let c = e(t(s));
109
- c !== void 0 && (o = Math.max(o, h(c, r) * n(s, i, a)));
137
+ if (a === void 0) continue;
138
+ let o = n(r(a));
139
+ if (o === void 0) continue;
140
+ let s = typeof a == "string" || a.size === void 0 ? i : a.size, c = _(o, e);
141
+ typeof s == "number" ? t.world = Math.max(t.world, c * s) : t.px = Math.max(t.px, c * s.px);
110
142
  }
111
- return o;
143
+ return t;
112
144
  }
113
145
  //#endregion
114
- export { l as headCommands, c as markerHeads, g as markerReach, u as markerReadsKey };
146
+ export { d as headCommands, u as markerHeads, v as markerReach, f as markerReadsKey };
115
147
 
116
148
  //# sourceMappingURL=markerCommands.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"markerCommands.js","names":["placed","src","path","cos","site","sin","coords","i","x","y","resolvePaint","v","p","fallback","stroke","markerHeads","want","refFor","role","out","pl","extractPolylines","markerSites","ref","entry","getMarker","markerKeyOf","size","resolveMarkerSize","strokeWidth","angle","headCommands","heads","fill","outline","markerReadsKey","key","field","READINGS_PER_ENTRY","REACH_BY_READING","REACH_BY_STROKE","measureUnitReach","b","boundsOfPath","unitReach","byStroke","reach","byReading","markerReach","scale"],"sources":["../../../src/features/paths/markerCommands.ts"],"sourcesContent":["/**\n * Turning a stroke's markers into draw commands.\n *\n * Separate `PathDrawCommand`s rather than triangles appended to the stroke\n * ribbon: an entry may carry a fill and an outline at once, or a paint that\n * differs from the line's, neither of which one mesh can express — and folding\n * them in would drag the whole marker vocabulary into the ribbon cache key.\n */\n\nimport type { MarkerRef, Stroke } from '@weasel-js/paint';\nimport { type Path, type PolygonPath, boundsOfPath } from '@weasel-js/geom';\nimport type { PathDrawCommand } from '../../renderer/DrawCommand';\nimport { getMarker, type MarkerEntry, type MarkerPaint } from '../../core/strokeMarkers';\nimport { markerKeyOf, resolveMarkerSize } from '../../core/markerInset';\nimport { extractPolylines } from '@weasel-js/geom/tessellate';\nimport { markerSites, type MarkerSite } from './markerSites';\n\n/** Rotate + translate a marker's geometry onto its site. */\nfunction placed(path: Path, site: MarkerSite): PolygonPath {\n const src = path as PolygonPath;\n const cos = Math.cos(site.angle);\n const sin = Math.sin(site.angle);\n const coords = new Float32Array(src.coords.length);\n for (let i = 0; i < src.coords.length; i += 2) {\n const x = src.coords[i], y = src.coords[i + 1];\n coords[i] = site.x + x * cos - y * sin;\n coords[i + 1] = site.y + x * sin + y * cos;\n }\n return { kind: 'polygon', commands: src.commands, coords, fillRule: src.fillRule };\n}\n\nfunction resolvePaint(p: MarkerPaint | undefined, stroke: Stroke, fallback: MarkerPaint) {\n const v = p ?? fallback;\n if (v === 'none') return undefined;\n return v === 'line' ? stroke.paint : v;\n}\n\n/** One head's placed geometry, before any paint is applied. */\nexport interface MarkerHead {\n readonly entry: MarkerEntry;\n readonly path: PolygonPath;\n readonly size: number;\n}\n\n/**\n * Every marker head for `path` under `stroke`, placed but unpainted.\n * `strokeWidth` is the already width-resolved stroke width; `flattenTolerance`\n * matches what the ribbon used, so heads land on the same flattened vertices\n * the stroke did.\n */\nexport function markerHeads(\n path: Path,\n stroke: Stroke,\n strokeWidth: number,\n flattenTolerance: number | undefined,\n): MarkerHead[] {\n const want = {\n start: stroke.markerStart !== undefined,\n mid: stroke.markerMid !== undefined,\n end: stroke.markerEnd !== undefined,\n };\n if (!want.start && !want.mid && !want.end) return [];\n\n const refFor = (role: MarkerSite['role']): MarkerRef | undefined =>\n role === 'start' ? stroke.markerStart : role === 'mid' ? stroke.markerMid : stroke.markerEnd;\n\n const out: MarkerHead[] = [];\n for (const pl of extractPolylines(path, { flattenTolerance })) {\n for (const site of markerSites(pl, want)) {\n const ref = refFor(site.role);\n if (ref === undefined) continue;\n const entry = getMarker(markerKeyOf(ref));\n if (entry === undefined) continue;\n const size = resolveMarkerSize(ref, strokeWidth);\n const angle = entry.orient === undefined || entry.orient === 'auto' ? site.angle : entry.orient;\n out.push({ entry, size, path: placed(entry.path({ size, stroke }), { ...site, angle }) });\n }\n }\n return out;\n}\n\n/** `heads` as draw commands in `stroke`'s paint. A head whose fill and outline\n * both resolve to no paint is dropped. */\nexport function headCommands(heads: readonly MarkerHead[], stroke: Stroke): PathDrawCommand[] {\n const out: PathDrawCommand[] = [];\n for (const { entry, path, size } of heads) {\n const fill = resolvePaint(entry.fill, stroke, 'line');\n const outline = entry.outline\n ? { paint: resolvePaint(entry.outline.paint, stroke, 'line'), width: entry.outline.width * size }\n : null;\n if (fill === undefined && (outline === null || outline.paint === undefined)) continue;\n out.push({\n kind: 'path',\n path,\n ...(fill ? { fill } : {}),\n ...(outline && outline.paint\n ? { stroke: { paint: outline.paint, width: outline.width, cap: 'round', join: 'round' } }\n : {}),\n });\n }\n return out;\n}\n\n/** Every marker command for `path` under `stroke`; see {@link markerHeads}. */\nexport function markerDrawCommands(\n path: Path,\n stroke: Stroke,\n strokeWidth: number,\n flattenTolerance: number | undefined,\n): PathDrawCommand[] {\n return headCommands(markerHeads(path, stroke, strokeWidth, flattenTolerance), stroke);\n}\n\n/**\n * A key for the stroke fields `entry`'s geometry reads, or `null` when the\n * entry does not say which it reads and only the stroke's identity can stand\n * in for them.\n */\nexport function markerReadsKey(entry: MarkerEntry, stroke: Stroke): string | null {\n if (entry.reads === undefined) return null;\n let key = '';\n for (const field of entry.reads) key += `${JSON.stringify(stroke[field]) ?? ''}|`;\n return key;\n}\n\n/** Distinct stroke readings kept per entry before its map is dropped. */\nconst READINGS_PER_ENTRY = 64;\n\n/** Each entry's farthest reach from its anchor at `size` 1, outline included,\n * per value of the fields it reads — or per stroke object, when it does not\n * say which. */\nconst REACH_BY_READING = new WeakMap<MarkerEntry, Map<string, number>>();\nconst REACH_BY_STROKE = new WeakMap<MarkerEntry, WeakMap<Stroke, number>>();\n\nfunction measureUnitReach(entry: MarkerEntry, stroke: Stroke): number {\n const b = boundsOfPath(entry.path({ size: 1, stroke }));\n return Math.max(Math.abs(b.x), Math.abs(b.x + b.width), Math.abs(b.y), Math.abs(b.y + b.height))\n + (entry.outline ? entry.outline.width / 2 : 0);\n}\n\nfunction unitReach(entry: MarkerEntry, stroke: Stroke): number {\n const key = markerReadsKey(entry, stroke);\n if (key === null) {\n let byStroke = REACH_BY_STROKE.get(entry);\n if (byStroke === undefined) REACH_BY_STROKE.set(entry, (byStroke = new WeakMap()));\n let reach = byStroke.get(stroke);\n if (reach === undefined) byStroke.set(stroke, (reach = measureUnitReach(entry, stroke)));\n return reach;\n }\n let byReading = REACH_BY_READING.get(entry);\n if (byReading === undefined) REACH_BY_READING.set(entry, (byReading = new Map()));\n let reach = byReading.get(key);\n if (reach === undefined) {\n if (byReading.size >= READINGS_PER_ENTRY) byReading.clear();\n byReading.set(key, (reach = measureUnitReach(entry, stroke)));\n }\n return reach;\n}\n\n/**\n * How far any of `stroke`'s markers can paint from the vertex it sits on, in\n * world units. `strokeWidth` is the resolved width and `scale` the view scale\n * a `{ px }` size resolves against. Culling and a path node's grab reach both\n * measure through here.\n */\nexport function markerReach(stroke: Stroke, strokeWidth: number, scale = 1): number {\n let reach = 0;\n for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {\n if (ref === undefined) continue;\n const entry = getMarker(markerKeyOf(ref));\n if (entry === undefined) continue;\n reach = Math.max(reach, unitReach(entry, stroke) * resolveMarkerSize(ref, strokeWidth, scale));\n }\n return reach;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"markerCommands.js","names":["placed","src","path","cos","site","sin","coords","i","x","y","resolvePaint","v","p","fallback","stroke","markerHeads","refs","groups","role","space","spaces","want","out","pl","extractPolylines","inSpace","polylineIntoMetric","markerSites","ref","entry","getMarker","markerKeyOf","size","resolveMarkerSize","strokeWidth","angle","head","coordsOutOfMetric","headCommands","heads","fill","width","outline","markerReadsKey","key","field","READINGS_PER_ENTRY","REACH_BY_READING","REACH_BY_STROKE","measureUnitReach","b","boundsOfPath","unitReach","byStroke","reach","byReading","markerReach","unit"],"sources":["../../../src/features/paths/markerCommands.ts"],"sourcesContent":["/**\n * Turning a stroke's markers into draw commands.\n *\n * Separate `PathDrawCommand`s rather than triangles appended to the stroke\n * ribbon: an entry may carry a fill and an outline at once, or a paint that\n * differs from the line's, neither of which one mesh can express — and folding\n * them in would drag the whole marker vocabulary into the ribbon cache key.\n */\n\nimport type { MarkerRef, Stroke } from '@weasel-js/paint';\nimport { type Path, type PolygonPath, boundsOfPath } from '@weasel-js/geom';\nimport type { PathDrawCommand } from '../../renderer/DrawCommand';\nimport { getMarker, type MarkerEntry, type MarkerPaint } from '../../core/strokeMarkers';\nimport { markerKeyOf, resolveMarkerSize } from '../../core/markerInset';\nimport { extractPolylines } from '@weasel-js/geom/tessellate';\nimport { markerSites, type MarkerSite } from './markerSites';\nimport {\n coordsOutOfMetric, polylineIntoMetric, type ScreenSpace, type StrokeSpaces,\n} from './tessellate/metric';\n\n/** Rotate + translate a marker's geometry onto its site. */\nfunction placed(path: Path, site: MarkerSite): PolygonPath {\n const src = path as PolygonPath;\n const cos = Math.cos(site.angle);\n const sin = Math.sin(site.angle);\n const coords = new Float32Array(src.coords.length);\n for (let i = 0; i < src.coords.length; i += 2) {\n const x = src.coords[i], y = src.coords[i + 1];\n coords[i] = site.x + x * cos - y * sin;\n coords[i + 1] = site.y + x * sin + y * cos;\n }\n return { kind: 'polygon', commands: src.commands, coords, fillRule: src.fillRule };\n}\n\nfunction resolvePaint(p: MarkerPaint | undefined, stroke: Stroke, fallback: MarkerPaint) {\n const v = p ?? fallback;\n if (v === 'none') return undefined;\n return v === 'line' ? stroke.paint : v;\n}\n\n/** One head's placed geometry, before any paint is applied. */\nexport interface MarkerHead {\n readonly entry: MarkerEntry;\n readonly path: PolygonPath;\n readonly size: number;\n /** Where it was built, when not in world — `size` is in this space's units. */\n readonly space?: ScreenSpace;\n}\n\n/**\n * Every marker head for `path` under `stroke`, placed but unpainted.\n * `strokeWidth` is the already width-resolved stroke width; `flattenTolerance`\n * matches what the ribbon used, so heads land on the same flattened vertices\n * the stroke did. A head `spaces` puts in a screen space is placed and sized\n * there and comes back in world units, so it meets the line it caps.\n */\nexport function markerHeads(\n path: Path,\n stroke: Stroke,\n strokeWidth: number,\n flattenTolerance: number | undefined,\n spaces: StrokeSpaces = {},\n): MarkerHead[] {\n const refs = { start: stroke.markerStart, mid: stroke.markerMid, end: stroke.markerEnd };\n if (refs.start === undefined && refs.mid === undefined && refs.end === undefined) return [];\n\n // Heads sharing a space share one pass over the polyline mapped into it.\n const groups = new Map<ScreenSpace | undefined, { start: boolean; mid: boolean; end: boolean }>();\n for (const role of ['start', 'mid', 'end'] as const) {\n if (refs[role] === undefined) continue;\n const space = spaces[role];\n let want = groups.get(space);\n if (want === undefined) groups.set(space, (want = { start: false, mid: false, end: false }));\n want[role] = true;\n }\n\n const out: MarkerHead[] = [];\n for (const pl of extractPolylines(path, { flattenTolerance })) {\n for (const [space, want] of groups) {\n const inSpace = space ? { ...pl, points: pl.points.slice() } : pl;\n if (space) polylineIntoMetric(inSpace, space.metric);\n for (const site of markerSites(inSpace, want)) {\n const ref: MarkerRef | undefined = refs[site.role];\n if (ref === undefined) continue;\n const entry = getMarker(markerKeyOf(ref));\n if (entry === undefined) continue;\n const size = resolveMarkerSize(ref, strokeWidth);\n const angle = entry.orient === undefined || entry.orient === 'auto' ? site.angle : entry.orient;\n const head = placed(entry.path({ size, stroke }), { ...site, angle });\n if (space) coordsOutOfMetric(head.coords, space.metric);\n out.push(space ? { entry, size, path: head, space } : { entry, size, path: head });\n }\n }\n }\n return out;\n}\n\n/** `heads` as draw commands in `stroke`'s paint. A head whose fill and outline\n * both resolve to no paint is dropped. */\nexport function headCommands(heads: readonly MarkerHead[], stroke: Stroke): PathDrawCommand[] {\n const out: PathDrawCommand[] = [];\n for (const { entry, path, size, space } of heads) {\n const fill = resolvePaint(entry.fill, stroke, 'line');\n // A head built in a screen space is in world once placed, but its outline\n // width is still in that space's units — so it goes out as the screen\n // width it stands for, and is stroked as exactly as the line.\n const width = entry.outline ? entry.outline.width * size : 0;\n const outline = entry.outline\n ? { paint: resolvePaint(entry.outline.paint, stroke, 'line'), width: space ? { px: width * space.pxPerUnit } : width }\n : null;\n if (fill === undefined && (outline === null || outline.paint === undefined)) continue;\n out.push({\n kind: 'path',\n path,\n ...(fill ? { fill } : {}),\n ...(outline && outline.paint\n ? { stroke: { paint: outline.paint, width: outline.width, cap: 'round', join: 'round' } }\n : {}),\n });\n }\n return out;\n}\n\n/** Every marker command for `path` under `stroke`; see {@link markerHeads}. */\nexport function markerDrawCommands(\n path: Path,\n stroke: Stroke,\n strokeWidth: number,\n flattenTolerance: number | undefined,\n): PathDrawCommand[] {\n return headCommands(markerHeads(path, stroke, strokeWidth, flattenTolerance), stroke);\n}\n\n/**\n * A key for the stroke fields `entry`'s geometry reads, or `null` when the\n * entry does not say which it reads and only the stroke's identity can stand\n * in for them.\n */\nexport function markerReadsKey(entry: MarkerEntry, stroke: Stroke): string | null {\n if (entry.reads === undefined) return null;\n let key = '';\n for (const field of entry.reads) key += `${JSON.stringify(stroke[field]) ?? ''}|`;\n return key;\n}\n\n/** Distinct stroke readings kept per entry before its map is dropped. */\nconst READINGS_PER_ENTRY = 64;\n\n/** Each entry's farthest reach from its anchor at `size` 1, outline included,\n * per value of the fields it reads — or per stroke object, when it does not\n * say which. */\nconst REACH_BY_READING = new WeakMap<MarkerEntry, Map<string, number>>();\nconst REACH_BY_STROKE = new WeakMap<MarkerEntry, WeakMap<Stroke, number>>();\n\nfunction measureUnitReach(entry: MarkerEntry, stroke: Stroke): number {\n const b = boundsOfPath(entry.path({ size: 1, stroke }));\n return Math.max(Math.abs(b.x), Math.abs(b.x + b.width), Math.abs(b.y), Math.abs(b.y + b.height))\n + (entry.outline ? entry.outline.width / 2 : 0);\n}\n\nfunction unitReach(entry: MarkerEntry, stroke: Stroke): number {\n const key = markerReadsKey(entry, stroke);\n if (key === null) {\n let byStroke = REACH_BY_STROKE.get(entry);\n if (byStroke === undefined) REACH_BY_STROKE.set(entry, (byStroke = new WeakMap()));\n let reach = byStroke.get(stroke);\n if (reach === undefined) byStroke.set(stroke, (reach = measureUnitReach(entry, stroke)));\n return reach;\n }\n let byReading = REACH_BY_READING.get(entry);\n if (byReading === undefined) REACH_BY_READING.set(entry, (byReading = new Map()));\n let reach = byReading.get(key);\n if (reach === undefined) {\n if (byReading.size >= READINGS_PER_ENTRY) byReading.clear();\n byReading.set(key, (reach = measureUnitReach(entry, stroke)));\n }\n return reach;\n}\n\n/**\n * How far `stroke`'s markers can paint from the vertex each sits on, split by\n * where each is built: `world` in world units for a head with a world size,\n * `px` in screen pixels for one with a `{ px }` size. A head with no size\n * takes the stroke width, and with it the width's units. Culling and a path\n * node's grab reach both measure through here.\n */\nexport function markerReach(stroke: Stroke): { world: number; px: number } {\n const reach = { world: 0, px: 0 };\n const width = stroke.width ?? 1;\n for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {\n if (ref === undefined) continue;\n const entry = getMarker(markerKeyOf(ref));\n if (entry === undefined) continue;\n const size = typeof ref === 'string' || ref.size === undefined ? width : ref.size;\n const unit = unitReach(entry, stroke);\n if (typeof size === 'number') reach.world = Math.max(reach.world, unit * size);\n else reach.px = Math.max(reach.px, unit * size.px);\n }\n return reach;\n}\n"],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ //#region src/features/paths/tessellate/dash.ts
2
+ function e(e, n) {
3
+ let r = [], i = e.anchorA ?? new Uint32Array(e.points.length / 2), a = e.anchorB ?? new Uint32Array(e.points.length / 2), o = e.anchorT ?? new Float32Array(e.points.length / 2), s = 0, c = n[0], l = !0, u = l ? [e.points[0], e.points[1]] : null, d = l ? [i[0]] : null, f = l ? [a[0]] : null, p = l ? [o[0]] : null, m = () => {
4
+ u && u.length >= 4 && r.push({
5
+ points: u,
6
+ closed: !1,
7
+ anchorA: new Uint32Array(d),
8
+ anchorB: new Uint32Array(f),
9
+ anchorT: new Float32Array(p)
10
+ }), u = null, d = null, f = null, p = null, s = (s + 1) % n.length, c = n[s], l = !l;
11
+ }, h = e.points[0], g = e.points[1], _ = e.points.length / 2, v = e.closed ? _ : _ - 1;
12
+ for (let t = 0; t < v; t++) {
13
+ let n = (t + 1) % _, r = e.points[n * 2], s = e.points[n * 2 + 1], v = i[t], y = a[t], b = o[t], x = i[n], S = a[n], C = o[n], w = r - h, T = s - g, E = Math.hypot(w, T), D = w, O = T, k = E, A = 0;
14
+ for (; k > 1e-9;) if (k <= c) l && u && (u.push(r, s), d.push(x), f.push(S), p.push(C)), c -= k, h = r, g = s, A = E, k = 0, c <= 1e-9 && (m(), l && (u = [h, g], d = [x], f = [S], p = [C]));
15
+ else {
16
+ let e = c / k, t = h + D * e, n = g + O * e;
17
+ A += c;
18
+ let i = A / E, a, o, _;
19
+ v === x && y === S ? (a = v, o = y, _ = b + (C - b) * i) : i < .5 ? (a = v, o = y, _ = b) : (a = x, o = S, _ = C), l && u && (u.push(t, n), d.push(a), f.push(o), p.push(_)), h = t, g = n, D = r - h, O = s - g, k = Math.hypot(D, O), m(), l && (u = [h, g], d = [a], f = [o], p = [_]);
20
+ }
21
+ }
22
+ if (u && u.length >= 4) {
23
+ let n = {
24
+ points: u,
25
+ closed: !1,
26
+ anchorA: new Uint32Array(d),
27
+ anchorB: new Uint32Array(f),
28
+ anchorT: new Float32Array(p)
29
+ }, i = u[u.length - 2], a = u[u.length - 1], o = (e, t) => Math.hypot(i - e, a - t) <= 1e-9;
30
+ e.closed && r.length > 0 && o(r[0].points[0], r[0].points[1]) ? r[0] = t(n, r[0]) : e.closed && r.length === 0 && o(u[0], u[1]) ? r.push({
31
+ points: u.slice(0, -2),
32
+ closed: !0,
33
+ anchorA: n.anchorA.slice(0, -1),
34
+ anchorB: n.anchorB.slice(0, -1),
35
+ anchorT: n.anchorT.slice(0, -1)
36
+ }) : r.push(n);
37
+ }
38
+ return r;
39
+ }
40
+ function t(e, t) {
41
+ return {
42
+ points: [...e.points, ...t.points.slice(2)],
43
+ closed: !1,
44
+ anchorA: Uint32Array.from([...e.anchorA, ...t.anchorA.subarray(1)]),
45
+ anchorB: Uint32Array.from([...e.anchorB, ...t.anchorB.subarray(1)]),
46
+ anchorT: Float32Array.from([...e.anchorT, ...t.anchorT.subarray(1)])
47
+ };
48
+ }
49
+ //#endregion
50
+ export { e as splitForDash };
51
+
52
+ //# sourceMappingURL=dash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dash.js","names":["splitForDash","out","plA","pl","plB","plT","dashIdx","dashRemaining","dash","onPhase","curPts","curA","curB","curT","flushAndAdvance","prevX","prevY","ptCount","segCount","i","nextIdx","cx","cy","startA","startB","startT","endA","endB","endT","segFullDx","segFullDy","segFullLen","segDx","segDy","segLen","traveled","tConsume","ix","iy","frac","mA","mB","mT","tail","tx","ty","meets","x","y","joinAtSeam","head"],"sources":["../../../../src/features/paths/tessellate/dash.ts"],"sourcesContent":["import type { Polyline } from '@weasel-js/geom/tessellate';\n\n/**\n * Split a polyline into open sub-polylines for the \"on\" portions of a dash\n * pattern. Each output sub-polyline gets caps from the stroke's `cap` setting.\n * The \"off\" portions become invisible gaps. Anchor params are carried through:\n * sub-polyline endpoints inherit their source polyline-point's anchor params;\n * dash boundaries that land mid-segment (between two polyline points) snap\n * their anchor params to whichever endpoint they're closer to when the\n * segment crosses a path-anchor boundary, otherwise interpolate `t` linearly.\n */\nexport function splitForDash(pl: Polyline, dash: number[]): Polyline[] {\n const out: Polyline[] = [];\n const plA = pl.anchorA ?? new Uint32Array(pl.points.length / 2);\n const plB = pl.anchorB ?? new Uint32Array(pl.points.length / 2);\n const plT = pl.anchorT ?? new Float32Array(pl.points.length / 2);\n\n let dashIdx = 0;\n let dashRemaining = dash[0];\n let onPhase = true;\n let curPts: number[] | null = onPhase ? [pl.points[0], pl.points[1]] : null;\n let curA: number[] | null = onPhase ? [plA[0]] : null;\n let curB: number[] | null = onPhase ? [plB[0]] : null;\n let curT: number[] | null = onPhase ? [plT[0]] : null;\n\n const flushAndAdvance = () => {\n if (curPts && curPts.length >= 4) {\n out.push({\n points: curPts,\n closed: false,\n anchorA: new Uint32Array(curA!),\n anchorB: new Uint32Array(curB!),\n anchorT: new Float32Array(curT!),\n });\n }\n curPts = null; curA = null; curB = null; curT = null;\n dashIdx = (dashIdx + 1) % dash.length;\n dashRemaining = dash[dashIdx];\n onPhase = !onPhase;\n };\n\n let prevX = pl.points[0], prevY = pl.points[1];\n const ptCount = pl.points.length / 2;\n const segCount = pl.closed ? ptCount : ptCount - 1;\n\n for (let i = 0; i < segCount; i++) {\n const nextIdx = (i + 1) % ptCount;\n const cx = pl.points[nextIdx * 2], cy = pl.points[nextIdx * 2 + 1];\n const startA = plA[i], startB = plB[i], startT = plT[i];\n const endA = plA[nextIdx], endB = plB[nextIdx], endT = plT[nextIdx];\n const segFullDx = cx - prevX, segFullDy = cy - prevY;\n const segFullLen = Math.hypot(segFullDx, segFullDy);\n let segDx = segFullDx, segDy = segFullDy;\n let segLen = segFullLen;\n let traveled = 0;\n\n while (segLen > 1e-9) {\n if (segLen <= dashRemaining) {\n if (onPhase && curPts) {\n curPts.push(cx, cy);\n curA!.push(endA); curB!.push(endB); curT!.push(endT);\n }\n dashRemaining -= segLen;\n prevX = cx; prevY = cy;\n traveled = segFullLen;\n segLen = 0;\n if (dashRemaining <= 1e-9) {\n flushAndAdvance();\n if (onPhase) {\n curPts = [prevX, prevY];\n curA = [endA]; curB = [endB]; curT = [endT];\n }\n }\n } else {\n const tConsume = dashRemaining / segLen;\n const ix = prevX + segDx * tConsume;\n const iy = prevY + segDy * tConsume;\n traveled += dashRemaining;\n const frac = traveled / segFullLen;\n let mA: number, mB: number, mT: number;\n if (startA === endA && startB === endB) {\n mA = startA; mB = startB;\n mT = startT + (endT - startT) * frac;\n } else {\n // Cross-anchor segment: pick the nearer endpoint's params.\n if (frac < 0.5) { mA = startA; mB = startB; mT = startT; }\n else { mA = endA; mB = endB; mT = endT; }\n }\n if (onPhase && curPts) {\n curPts.push(ix, iy);\n curA!.push(mA); curB!.push(mB); curT!.push(mT);\n }\n prevX = ix; prevY = iy;\n segDx = cx - prevX; segDy = cy - prevY;\n segLen = Math.hypot(segDx, segDy);\n flushAndAdvance();\n if (onPhase) {\n curPts = [prevX, prevY];\n curA = [mA]; curB = [mB]; curT = [mT];\n }\n }\n }\n }\n\n if (curPts && curPts.length >= 4) {\n const tail: Polyline = {\n points: curPts,\n closed: false,\n anchorA: new Uint32Array(curA!),\n anchorB: new Uint32Array(curB!),\n anchorT: new Float32Array(curT!),\n };\n // A closed subpath's walk ends at the vertex it started from, so the run\n // still open there and the run that started there are one dash. Pushed\n // separately they are two butt-capped ribbons meeting at the seam — the\n // visible artifact whenever the pattern doesn't divide the perimeter.\n const tx = curPts[curPts.length - 2], ty = curPts[curPts.length - 1];\n const meets = (x: number, y: number) => Math.hypot(tx - x, ty - y) <= 1e-9;\n if (pl.closed && out.length > 0 && meets(out[0].points[0], out[0].points[1])) {\n out[0] = joinAtSeam(tail, out[0]);\n } else if (pl.closed && out.length === 0 && meets(curPts[0], curPts[1])) {\n // Every gap fell outside the perimeter: the dash is the whole loop, and\n // a loop stroked as an open run seams at its own start vertex too.\n out.push({\n points: curPts.slice(0, -2),\n closed: true,\n anchorA: tail.anchorA!.slice(0, -1),\n anchorB: tail.anchorB!.slice(0, -1),\n anchorT: tail.anchorT!.slice(0, -1),\n });\n } else {\n out.push(tail);\n }\n }\n return out;\n}\n\n/** `tail` continued by `head`, which starts where `tail` ends — the shared\n * vertex is carried once. */\nfunction joinAtSeam(tail: Polyline, head: Polyline): Polyline {\n return {\n points: [...tail.points, ...head.points.slice(2)],\n closed: false,\n anchorA: Uint32Array.from([...tail.anchorA!, ...head.anchorA!.subarray(1)]),\n anchorB: Uint32Array.from([...tail.anchorB!, ...head.anchorB!.subarray(1)]),\n anchorT: Float32Array.from([...tail.anchorT!, ...head.anchorT!.subarray(1)]),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { polylineIntoMetric as e, polylineOutOfMetric as t, sameMetric as n } from "./metric.js";
2
+ import { trimPolyline as r } from "@weasel-js/geom/tessellate";
3
+ //#region src/features/paths/tessellate/insets.ts
4
+ var i = (e, t) => e === void 0 ? {
5
+ length: 0,
6
+ metric: t
7
+ } : typeof e == "number" ? {
8
+ length: e,
9
+ metric: t
10
+ } : e;
11
+ function a(a, o, s, c, l) {
12
+ let u = i(c, s), d = i(l, s), f = a, p = o, m = (r) => {
13
+ f === null || n(p, r) || (p !== null && t(f, p), r !== null && e(f, r), p = r);
14
+ };
15
+ if (u.length > 0 || d.length > 0) {
16
+ if (n(u.metric, d.metric)) m(u.metric), f = r(f, u.length, d.length);
17
+ else for (let [e, t, n] of [[
18
+ u.length,
19
+ u.metric,
20
+ !0
21
+ ], [
22
+ d.length,
23
+ d.metric,
24
+ !1
25
+ ]]) e > 0 && f !== null && (m(t), f = r(f, n ? e : 0, n ? 0 : e));
26
+ }
27
+ return m(s), f;
28
+ }
29
+ //#endregion
30
+ export { a as trimInSpace };
31
+
32
+ //# sourceMappingURL=insets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insets.js","names":["trimOf","inset","ribbon","trimInSpace","s","start","e","end","cur","pl","space","from","moveTo","sameMetric","to","polylineOutOfMetric","polylineIntoMetric","trimPolyline","len","metric","atStart"],"sources":["../../../../src/features/paths/tessellate/insets.ts"],"sourcesContent":["/**\n * Trimming a stroke's ends where each inset is measured.\n *\n * A head stops the line by a length in the head's own space, which need not be\n * the ribbon's: a world-sized head on a `{ px }` stroke stops it a world\n * distance short, a `{ px }` head on a world-width stroke a screen distance.\n * Under a stretching view one length means different distances in the two\n * spaces depending on which way the line runs, so each end is trimmed in its\n * own space rather than converted.\n */\nimport { type Polyline, trimPolyline } from '@weasel-js/geom/tessellate';\nimport {\n type StrokeMetric, polylineIntoMetric, polylineOutOfMetric, sameMetric,\n} from './metric';\n\n/** A length along the line measured in `metric`'s space, or in world where\n * `metric` is `null`. */\nexport interface MeasuredLength {\n readonly length: number;\n readonly metric: StrokeMetric | null;\n}\n\n/** A plain number is a length in the ribbon's own space. */\nexport type InsetLength = number | MeasuredLength;\n\ninterface Trim {\n length: number;\n metric: StrokeMetric | null;\n}\n\nconst trimOf = (inset: InsetLength | undefined, ribbon: StrokeMetric | null): Trim =>\n inset === undefined ? { length: 0, metric: ribbon }\n : typeof inset === 'number' ? { length: inset, metric: ribbon }\n : inset;\n\n/**\n * `pl`, whose points are in `from`'s space, trimmed by `start` and `end` and\n * carried into `ribbon`'s — `null` for world in both. `pl` may be mapped in\n * place. Returns `null` when the insets consume the whole run.\n */\nexport function trimInSpace(\n pl: Polyline,\n from: StrokeMetric | null,\n ribbon: StrokeMetric | null,\n start: InsetLength | undefined,\n end: InsetLength | undefined,\n): Polyline | null {\n const s = trimOf(start, ribbon);\n const e = trimOf(end, ribbon);\n let cur: Polyline | null = pl;\n let space = from;\n const moveTo = (to: StrokeMetric | null) => {\n if (cur === null || sameMetric(space, to)) return;\n if (space !== null) polylineOutOfMetric(cur, space);\n if (to !== null) polylineIntoMetric(cur, to);\n space = to;\n };\n if (s.length > 0 || e.length > 0) {\n if (sameMetric(s.metric, e.metric)) {\n moveTo(s.metric);\n cur = trimPolyline(cur, s.length, e.length);\n } else {\n for (const [len, metric, atStart] of [[s.length, s.metric, true], [e.length, e.metric, false]] as const) {\n if (!(len > 0) || cur === null) continue;\n moveTo(metric);\n cur = trimPolyline(cur, atStart ? len : 0, atStart ? 0 : len);\n }\n }\n }\n moveTo(ribbon);\n return cur;\n}\n"],"mappings":";;;AA8BA,IAAMA,KAAU,GAAgC,MAC9CC,MAAU,KAAA,IAAY;CAAE,QAAQ;CAAG,QAAQC;AAAO,IAC9C,OAAOD,KAAU,WAAW;CAAE,QAAQA;CAAO,QAAQC;AAAO,IAC1DD;AAOR,SAAgBE,EACd,GACA,GACA,GACA,GACA,GACiB;CACjB,IAAMC,IAAIJ,EAAOK,GAAOH,CAAM,GACxBI,IAAIN,EAAOO,GAAKL,CAAM,GACxBM,IAAuBC,GACvBC,IAAQC,GACNC,KAAU,MAA4B;EAC1C,AAAIJ,MAAQ,QAAQK,EAAWH,GAAOI,CAAE,MACpCJ,MAAU,QAAMK,EAAoBP,GAAKE,CAAK,GAC9CI,MAAO,QAAME,EAAmBR,GAAKM,CAAE,GAC3C,IAAQA;CACV;CACA,IAAIV,EAAE,SAAS,KAAKE,EAAE,SAAS,GAAG;EAChC,IAAIO,EAAWT,EAAE,QAAQE,EAAE,MAAM,GAE/B,AADAM,EAAOR,EAAE,MAAM,GACf,IAAMa,EAAaT,GAAKJ,EAAE,QAAQE,EAAE,MAAM;OAE1C,KAAK,IAAM,CAACY,GAAKC,GAAQC,MAAY,CAAC;GAAChB,EAAE;GAAQA,EAAE;GAAQ;EAAI,GAAG;GAACE,EAAE;GAAQA,EAAE;GAAQ;EAAK,CAAC,GAC3F,AAAMY,IAAM,KAAMV,MAAQ,SAC1BI,EAAOO,CAAM,GACb,IAAMF,EAAaT,GAAKY,IAAUF,IAAM,GAAGE,IAAU,IAAIF,CAAG;CAGlE;CAEA,OADAN,EAAOV,CAAM,GACNM;AACT"}
@@ -0,0 +1,72 @@
1
+ //#region src/features/paths/tessellate/metric.ts
2
+ function e(e) {
3
+ return e === void 0 ? "" : `${r(e.metric)}@${e.pxPerUnit}`;
4
+ }
5
+ var t = 1e-9;
6
+ function n(e, n, r, i) {
7
+ let a = Math.abs(e * i - n * r), o = Math.sqrt(a);
8
+ if (!(a > 0) || !Number.isFinite(a)) return {
9
+ scale: 0,
10
+ metric: null
11
+ };
12
+ let s = (e * e + n * n) / a, c = (e * r + n * i) / a, l = (r * r + i * i) / a, u = Math.sqrt(s + l + 2), d = (s + 1) / u, f = c / u, p = (l + 1) / u;
13
+ return Math.abs(f) < t && Math.abs(d - p) < t ? {
14
+ scale: o,
15
+ metric: null
16
+ } : {
17
+ scale: o,
18
+ metric: {
19
+ p: d,
20
+ q: f,
21
+ r: p
22
+ }
23
+ };
24
+ }
25
+ function r(e) {
26
+ return e === void 0 ? "" : `${e.p},${e.q},${e.r}`;
27
+ }
28
+ function i(e, t) {
29
+ let n = e.points;
30
+ for (let e = 0; e < n.length; e += 2) {
31
+ let r = n[e], i = n[e + 1];
32
+ n[e] = t.p * r + t.q * i, n[e + 1] = t.q * r + t.r * i;
33
+ }
34
+ }
35
+ function a(e, t) {
36
+ let n = e.points;
37
+ for (let e = 0; e < n.length; e += 2) {
38
+ let r = n[e], i = n[e + 1];
39
+ n[e] = t.r * r - t.q * i, n[e + 1] = t.p * i - t.q * r;
40
+ }
41
+ }
42
+ function o(e, t) {
43
+ return e === t || e !== null && t !== null && e.p === t.p && e.q === t.q && e.r === t.r;
44
+ }
45
+ function s(e, t, n) {
46
+ return Math.hypot(e.r * t - e.q * n, e.p * n - e.q * t);
47
+ }
48
+ function c(e, t, n) {
49
+ return 1 / Math.hypot(e.p * t + e.q * n, e.q * t + e.r * n);
50
+ }
51
+ function l(e, t) {
52
+ for (let n = 0; n < e.length; n += 2) {
53
+ let r = e[n], i = e[n + 1];
54
+ e[n] = t.r * r - t.q * i, e[n + 1] = t.p * i - t.q * r;
55
+ }
56
+ }
57
+ function u(e) {
58
+ return e.q === 0;
59
+ }
60
+ function d(e, t) {
61
+ return u(t) ? {
62
+ kind: "rect",
63
+ x: t.p * e.x,
64
+ y: t.r * e.y,
65
+ width: t.p * e.width,
66
+ height: t.r * e.height
67
+ } : null;
68
+ }
69
+ //#endregion
70
+ export { l as coordsOutOfMetric, u as metricKeepsRects, r as metricKey, i as polylineIntoMetric, a as polylineOutOfMetric, d as rectIntoMetric, o as sameMetric, e as screenSpaceKey, n as strokeSpaceOf, s as worldWidthInMetric, c as worldWidthInMetricAlongWorld };
71
+
72
+ //# sourceMappingURL=metric.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metric.js","names":["screenSpaceKey","space","metricKey","CONFORMAL_EPSILON","strokeSpaceOf","det2","a","d","b","c","scale","g11","g12","g22","s","p","q","r","metric","polylineIntoMetric","pts","pl","i","x","y","m","polylineOutOfMetric","sameMetric","worldWidthInMetric","tx","ty","worldWidthInMetricAlongWorld","ux","uy","coordsOutOfMetric","coords","metricKeepsRects","rectIntoMetric","rect"],"sources":["../../../../src/features/paths/tessellate/metric.ts"],"sourcesContent":["/**\n * The space a screen-pixel stroke is built in.\n *\n * A `{ px }` stroke is exact on screen only when its ribbon is offset in a\n * space that measures lengths the way the screen does. The transform's linear\n * part is `scale · R · P`: a uniform scale, a rotation (or reflection) that\n * moves no lengths, and `P`, the symmetric unit-determinant stretch left over.\n * Offsetting the ribbon after `P` and mapping it back through `P⁻¹` gives a\n * world-space mesh that the full transform turns into a ribbon of exactly\n * `width · scale` pixels in every direction — and keeps the mesh drawable by\n * every path that draws a world-space ribbon.\n *\n * `P` depends only on the lengths the transform induces (`LᵀL`), not on any\n * rotation applied after it, so a view that only pans or zooms uniformly keeps\n * the same metric and the ribbon cache keeps hitting.\n */\nimport type { Polyline } from '@weasel-js/geom/tessellate';\n\n/** `P` = [[p, q], [q, r]], with `pr − q² = 1`. */\nexport interface StrokeMetric {\n readonly p: number;\n readonly q: number;\n readonly r: number;\n}\n\n/** A space a screen-sized part of a stroke is built in: the metric, and how\n * many screen pixels one of its units is. */\nexport interface ScreenSpace {\n readonly metric: StrokeMetric;\n readonly pxPerUnit: number;\n}\n\n/**\n * Where each part of a stroke is built. An absent part is built in world:\n * everything is under a similarity, and otherwise a part whose size is a world\n * length. The ribbon is screen-sized for a `{ px }` width; a head for a\n * `{ px }` size, or for no size on a screen-sized ribbon, since it then takes\n * the stroke width.\n */\nexport interface StrokeSpaces {\n readonly ribbon?: ScreenSpace;\n readonly start?: ScreenSpace;\n readonly mid?: ScreenSpace;\n readonly end?: ScreenSpace;\n}\n\n/** A cache-key fragment naming `space`; empty for world. */\nexport function screenSpaceKey(space: ScreenSpace | undefined): string {\n return space === undefined ? '' : `${metricKey(space.metric)}@${space.pxPerUnit}`;\n}\n\n/** Below this, a stretch is a rounding error and the transform a similarity. */\nconst CONFORMAL_EPSILON = 1e-9;\n\n/**\n * Split the linear map `x' = a·x + c·y, y' = b·x + d·y` — a GL `mat3`'s\n * `m[0], m[1], m[3], m[4]` — into its geometric-mean scale and its stroke\n * metric. `metric` is `null` when the map is a similarity, where world space\n * already measures like the screen; `scale` is `0` for a singular map.\n */\nexport function strokeSpaceOf(\n a: number, b: number, c: number, d: number,\n): { scale: number; metric: StrokeMetric | null } {\n const det2 = Math.abs(a * d - b * c);\n const scale = Math.sqrt(det2);\n if (!(det2 > 0) || !Number.isFinite(det2)) return { scale: 0, metric: null };\n // G = LᵀL / det, whose determinant is 1, so √G = (G + I) / √(tr G + 2).\n const g11 = (a * a + b * b) / det2;\n const g12 = (a * c + b * d) / det2;\n const g22 = (c * c + d * d) / det2;\n const s = Math.sqrt(g11 + g22 + 2);\n const p = (g11 + 1) / s;\n const q = g12 / s;\n const r = (g22 + 1) / s;\n if (Math.abs(q) < CONFORMAL_EPSILON && Math.abs(p - r) < CONFORMAL_EPSILON) {\n return { scale, metric: null };\n }\n return { scale, metric: { p, q, r } };\n}\n\n/** A cache-key fragment naming `metric`; empty for none. */\nexport function metricKey(metric: StrokeMetric | undefined): string {\n return metric === undefined ? '' : `${metric.p},${metric.q},${metric.r}`;\n}\n\n/** Map `pl`'s points through `P`, in place. */\nexport function polylineIntoMetric(pl: Polyline, m: StrokeMetric): void {\n const pts = pl.points;\n for (let i = 0; i < pts.length; i += 2) {\n const x = pts[i], y = pts[i + 1];\n pts[i] = m.p * x + m.q * y;\n pts[i + 1] = m.q * x + m.r * y;\n }\n}\n\n/** Map `pl`'s points back out through `P⁻¹`, in place. */\nexport function polylineOutOfMetric(pl: Polyline, m: StrokeMetric): void {\n const pts = pl.points;\n for (let i = 0; i < pts.length; i += 2) {\n const x = pts[i], y = pts[i + 1];\n pts[i] = m.r * x - m.q * y;\n pts[i + 1] = m.p * y - m.q * x;\n }\n}\n\n/** Whether two spaces are the same — world being `null`. */\nexport function sameMetric(a: StrokeMetric | null, b: StrokeMetric | null): boolean {\n if (a === b) return true;\n return a !== null && b !== null && a.p === b.p && a.q === b.q && a.r === b.r;\n}\n\n/**\n * What one world unit of width, across a line running along the unit\n * direction `(tx, ty)` of the metric's own space, measures in that space.\n * `P` keeps areas, so a strip keeps its area while its length stretches by\n * `|P u|` (`u` the line's world direction): its width shrinks by the same,\n * and `1 / |P u|` is `|P⁻¹ t|`.\n */\nexport function worldWidthInMetric(m: StrokeMetric, tx: number, ty: number): number {\n return Math.hypot(m.r * tx - m.q * ty, m.p * ty - m.q * tx);\n}\n\n/** The same, for a line running along the unit world direction `(ux, uy)`. */\nexport function worldWidthInMetricAlongWorld(m: StrokeMetric, ux: number, uy: number): number {\n return 1 / Math.hypot(m.p * ux + m.q * uy, m.q * ux + m.r * uy);\n}\n\n/** Map interleaved x,y pairs back out through `P⁻¹` = [[r, −q], [−q, p]], in\n * place. */\nexport function coordsOutOfMetric(coords: Float32Array, m: StrokeMetric): void {\n for (let i = 0; i < coords.length; i += 2) {\n const x = coords[i], y = coords[i + 1];\n coords[i] = m.r * x - m.q * y;\n coords[i + 1] = m.p * y - m.q * x;\n }\n}\n\n/** Whether `P` takes an axis-aligned rect to another one. */\nexport function metricKeepsRects(m: StrokeMetric): boolean {\n return m.q === 0;\n}\n\n/** `rect` in the metric's space, while `P` keeps it axis-aligned; otherwise\n * `null`, and the rect has to be carried as a polygon. */\nexport function rectIntoMetric(\n rect: { x: number; y: number; width: number; height: number },\n m: StrokeMetric,\n): { kind: 'rect'; x: number; y: number; width: number; height: number } | null {\n if (!metricKeepsRects(m)) return null;\n return { kind: 'rect', x: m.p * rect.x, y: m.r * rect.y, width: m.p * rect.width, height: m.r * rect.height };\n}\n"],"mappings":";AA+CA,SAAgBA,EAAe,GAAwC;CACrE,OAAOC,MAAU,KAAA,IAAY,KAAK,GAAGC,EAAUD,EAAM,MAAM,EAAE,GAAGA,EAAM;AACxE;AAGA,IAAME,IAAoB;AAQ1B,SAAgBC,EACd,GAAW,GAAW,GAAW,GACe;CAChD,IAAMC,IAAO,KAAK,IAAIC,IAAIC,IAAIC,IAAIC,CAAC,GAC7BC,IAAQ,KAAK,KAAKL,CAAI;CAC5B,IAAI,EAAEA,IAAO,MAAM,CAAC,OAAO,SAASA,CAAI,GAAG,OAAO;EAAE,OAAO;EAAG,QAAQ;CAAK;CAE3E,IAAMM,KAAOL,IAAIA,IAAIE,IAAIA,KAAKH,GACxBO,KAAON,IAAIG,IAAID,IAAID,KAAKF,GACxBQ,KAAOJ,IAAIA,IAAIF,IAAIA,KAAKF,GACxBS,IAAI,KAAK,KAAKH,IAAME,IAAM,CAAC,GAC3BE,KAAKJ,IAAM,KAAKG,GAChBE,IAAIJ,IAAME,GACVG,KAAKJ,IAAM,KAAKC;CAItB,OAHI,KAAK,IAAIE,CAAC,IAAIb,KAAqB,KAAK,IAAIY,IAAIE,CAAC,IAAId,IAChD;EAAE;EAAO,QAAQ;CAAK,IAExB;EAAE;EAAO,QAAQ;GAAE;GAAG;GAAG;EAAE;CAAE;AACtC;AAGA,SAAgBD,EAAU,GAA0C;CAClE,OAAOgB,MAAW,KAAA,IAAY,KAAK,GAAGA,EAAO,EAAE,GAAGA,EAAO,EAAE,GAAGA,EAAO;AACvE;AAGA,SAAgBC,EAAmB,GAAc,GAAuB;CACtE,IAAMC,IAAMC,EAAG;CACf,KAAK,IAAIC,IAAI,GAAGA,IAAIF,EAAI,QAAQ,KAAK,GAAG;EACtC,IAAMG,IAAIH,EAAIE,IAAIE,IAAIJ,EAAIE,IAAI;EAE9B,AADA,EAAIA,KAAKG,EAAE,IAAIF,IAAIE,EAAE,IAAID,GACzB,EAAIF,IAAI,KAAKG,EAAE,IAAIF,IAAIE,EAAE,IAAID;CAC/B;AACF;AAGA,SAAgBE,EAAoB,GAAc,GAAuB;CACvE,IAAMN,IAAMC,EAAG;CACf,KAAK,IAAIC,IAAI,GAAGA,IAAIF,EAAI,QAAQ,KAAK,GAAG;EACtC,IAAMG,IAAIH,EAAIE,IAAIE,IAAIJ,EAAIE,IAAI;EAE9B,AADA,EAAIA,KAAKG,EAAE,IAAIF,IAAIE,EAAE,IAAID,GACzB,EAAIF,IAAI,KAAKG,EAAE,IAAID,IAAIC,EAAE,IAAIF;CAC/B;AACF;AAGA,SAAgBI,EAAW,GAAwB,GAAiC;CAElF,OADIrB,MAAME,KACHF,MAAM,QAAQE,MAAM,QAAQF,EAAE,MAAME,EAAE,KAAKF,EAAE,MAAME,EAAE,KAAKF,EAAE,MAAME,EAAE;AAC7E;AASA,SAAgBoB,EAAmB,GAAiB,GAAY,GAAoB;CAClF,OAAO,KAAK,MAAMH,EAAE,IAAII,IAAKJ,EAAE,IAAIK,GAAIL,EAAE,IAAIK,IAAKL,EAAE,IAAII,CAAE;AAC5D;AAGA,SAAgBE,EAA6B,GAAiB,GAAY,GAAoB;CAC5F,OAAO,IAAI,KAAK,MAAMN,EAAE,IAAIO,IAAKP,EAAE,IAAIQ,GAAIR,EAAE,IAAIO,IAAKP,EAAE,IAAIQ,CAAE;AAChE;AAIA,SAAgBC,EAAkB,GAAsB,GAAuB;CAC7E,KAAK,IAAIZ,IAAI,GAAGA,IAAIa,EAAO,QAAQ,KAAK,GAAG;EACzC,IAAMZ,IAAIY,EAAOb,IAAIE,IAAIW,EAAOb,IAAI;EAEpC,AADA,EAAOA,KAAKG,EAAE,IAAIF,IAAIE,EAAE,IAAID,GAC5B,EAAOF,IAAI,KAAKG,EAAE,IAAID,IAAIC,EAAE,IAAIF;CAClC;AACF;AAGA,SAAgBa,EAAiB,GAA0B;CACzD,OAAOX,EAAE,MAAM;AACjB;AAIA,SAAgBY,EACd,GACA,GAC8E;CAE9E,OADKD,EAAiBX,CAAC,IAChB;EAAE,MAAM;EAAQ,GAAGA,EAAE,IAAIa,EAAK;EAAG,GAAGb,EAAE,IAAIa,EAAK;EAAG,OAAOb,EAAE,IAAIa,EAAK;EAAO,QAAQb,EAAE,IAAIa,EAAK;CAAO,IAD3E;AAEnC"}