@weasel-js/core 0.6.0 → 0.7.1

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 (35) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/dist/{DrawCommand-Dl0bXNfS.d.ts → DrawCommand-CJtqqt8H.d.ts} +26 -2
  3. package/dist/{chunk-7V6JEOXE.js → chunk-2J6V527H.js} +10033 -10912
  4. package/dist/chunk-2J6V527H.js.map +1 -0
  5. package/dist/chunk-SYM6RAM4.js +232 -0
  6. package/dist/chunk-SYM6RAM4.js.map +1 -0
  7. package/dist/clone.d.ts +1 -1
  8. package/dist/geometry-D9BDMiQi.d.ts +114 -0
  9. package/dist/{grid-Cf87knjU.d.ts → grid-CaSK9bHV.d.ts} +1 -1
  10. package/dist/index-DOYRTfP0.d.ts +2986 -0
  11. package/dist/index.css +0 -35
  12. package/dist/index.css.map +1 -1
  13. package/dist/index.d.ts +1174 -2481
  14. package/dist/index.js +2 -2
  15. package/dist/insert.d.ts +2 -2
  16. package/dist/move.d.ts +3 -3
  17. package/dist/{options-BPPBWMa7.d.ts → options-DMWeTELe.d.ts} +1 -1
  18. package/dist/{pointSnapToGrid-D7s7QmOF.d.ts → pointSnapToGrid-C3EruUwt.d.ts} +3 -54
  19. package/dist/renderer.css +0 -35
  20. package/dist/renderer.css.map +1 -1
  21. package/dist/renderer.d.ts +30 -5
  22. package/dist/renderer.js +2 -2
  23. package/dist/resize.d.ts +3 -3
  24. package/dist/routing.d.ts +10 -8
  25. package/dist/routing.js +1 -1
  26. package/dist/{types-BjUi2vA-.d.ts → types-Dcaa0tPq.d.ts} +1 -25
  27. package/dist/{registerFont-CP-wCsrz.d.ts → viewToMat3-D4lrBigW.d.ts} +2 -44
  28. package/package.json +6 -5
  29. package/dist/chunk-7V6JEOXE.js.map +0 -1
  30. package/dist/chunk-AM6ARSPN.js +0 -517
  31. package/dist/chunk-AM6ARSPN.js.map +0 -1
  32. package/dist/fitViewToBounds-evGsnR8Q.d.ts +0 -62
  33. package/dist/index-DZBYMsHI.d.ts +0 -1555
  34. package/dist/routing.css +0 -35
  35. package/dist/routing.css.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,238 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - a3af158: Gestures are now keyed per pointer. `gestureIdFor` returned the literal string
8
+ `pointer-mouse` with nothing interpolated — despite its own docs promising
9
+ `pointer-<pointerId>` — so every pointer shared one in-flight handle slot and
10
+ two pointer gestures could not coexist. Pointer events carry `pointerId` now
11
+ and the id interpolates; events without one (synthetic probes, most tests)
12
+ still key to `pointer-mouse`.
13
+
14
+ That removes an accident the pinch path was relying on, so the multitouch
15
+ policy is stated rather than implied: when a second pointer lands, the
16
+ multitouch channel claims every pointer that hasn't already committed to a
17
+ gesture, suppressing both drags and taps from those pointers. A drag already in
18
+ flight keeps running — yanking a gesture away from someone who rested a palm is
19
+ worse than letting it finish.
20
+
21
+ `useTools` also reports routing conflicts now. `findConflicts` had been
22
+ written, tested, and never called, so the kit could detect its one class of
23
+ genuine routing ambiguity and never looked. It runs at registry assembly under
24
+ a dev guard and warns each conflict in the grammar the route was written in. It
25
+ warns and never throws: a consumer tool colliding with a kit tool can be
26
+ deliberate, since the loser may still take the gesture by declining through
27
+ `enabled()`.
28
+
29
+ - a3af158: Scenes with many shapes or much text draw far less work per frame. Two caches
30
+ the kit already had were missing on essentially every node of every frame,
31
+ because the values they key on were rebuilt each time.
32
+
33
+ The tessellation cache (`WeakMap<Path, Mesh>`) keys on `Path` identity, but
34
+ `kit:shape` allocated a fresh path for every ellipse, polygon and star on every
35
+ draw. Painters now memoize `paint` against the node, so the same path comes
36
+ back and the cache does its job: 1000 shape nodes went from 6.69 to 0.20
37
+ ms/frame through paint and tessellation.
38
+
39
+ Text layout is the larger one. `layoutRuns` — which walks every codepoint,
40
+ resolves a face per run, measures, wraps and places each glyph — ran per text
41
+ command per frame. It is now cached in the renderer, keyed on the resolved
42
+ runs. 200 wrapped paragraphs went from 31.8 to 0.06 ms/frame, 1000 short
43
+ labels from 12.5 to 0.42. The cache drops itself when a font becomes
44
+ available, so text still reflows the moment the real face lands.
45
+
46
+ Two contracts follow from this, for anyone writing a custom painter or calling
47
+ these directly:
48
+
49
+ - The array a painter's `paint` returns belongs to the painter. Treat it as
50
+ immutable and copy before appending — `defaultDrawOne` now does, for its
51
+ label overlay.
52
+ - `registerFont` now notifies `subscribeGlyphReady` when a family finishes
53
+ registering, so a font loaded mid-session repaints without waiting for an
54
+ unrelated redraw. `glyphGeneration()` is a new pull-based companion to that
55
+ signal, for caches that can't hold a subscription.
56
+
57
+ - 6af4806: Reorder: `Cmd/Ctrl+Shift+]` and `Cmd/Ctrl+Shift+[` now bring-to-front and
58
+ send-to-back. The shortcut every drawing app uses could never fire — modifier
59
+ matching is strict, so a binding without `shift` in its spec cannot match a
60
+ keystroke that holds it, which also made the shifted `'}'` / `'{'` characters
61
+ in those key lists unreachable. `Cmd+Alt+]` / `Cmd+Alt+[` remain as the
62
+ fallback for browsers that reserve `Cmd+Shift+[`/`]` for tab switching.
63
+
64
+ `BUNDLE_TOOLS.standard` no longer includes `pencil`. Freehand is a specialist
65
+ instrument rather than part of the everyday shape-drawing set; it is still in
66
+ `exhaustive`. Consumers wanting it back can pass `tools={{ pencil: true }}`
67
+ alongside the bundle.
68
+
69
+ - a3af158: Clicks now land on the shape a node actually paints, not on its bounding box.
70
+ The pose rect is the wrong answer for anything that isn't a rectangle: a click
71
+ in a star's notch, in the corner outside an ellipse, or in the blank half of a
72
+ text box went to the node that merely bounds that point, burying whatever was
73
+ really underneath. `picking: 'shape'` had been available since it shipped and
74
+ was opt-in only because flipping it changes what a click selects.
75
+
76
+ Ink counts too, not just the boundary. A shape whose interior isn't filled —
77
+ an outlined rect, a pencil stroke, a bare line — is now grabbable along its
78
+ outline and not through its empty middle, which is the opposite of what a fill
79
+ test alone answers. Painters declare this through the new `NodeShapeEntry.ink`;
80
+ one that declares none is treated as filled, the previous behavior.
81
+
82
+ Pass `geometry={{ picking: 'pose' }}` to `SceneCanvas` for the old rect
83
+ behavior. Painters with no silhouette are unaffected either way, so nothing
84
+ becomes unreachable.
85
+
86
+ - 2003597: Export `VERSION` — the kit version a build was compiled from, baked in at
87
+ build time. Apps can pair it with their own compile timestamp to report what
88
+ they're running (WeaselDraw now shows `0.7.0 · Jul 30` in its status bar).
89
+ - Updated dependencies [6af4806]
90
+ - Updated dependencies [a3af158]
91
+ - @weasel-js/font@0.7.1
92
+ - @weasel-js/geom@0.7.1
93
+ - @weasel-js/gestures@0.7.1
94
+ - @weasel-js/history@0.7.1
95
+ - @weasel-js/modes@0.7.1
96
+
97
+ ## 0.7.0
98
+
99
+ ### Minor Changes
100
+
101
+ - d3e5597: Extract the MSDF glyph tier into a new `@weasel-js/font` package: font
102
+ registry, atlas parsing, glyph layout, runtime rasterization, and the SDF
103
+ text shader source. `@weasel-js/core` depends on it; `registerFont` is still
104
+ re-exported from `@weasel-js/core/renderer`, so existing call sites keep
105
+ working.
106
+
107
+ Unregistered font families now render in the default family with a one-time
108
+ warning instead of rendering nothing. Configure with
109
+ `setFontFallbackPolicy('substitute' | 'canvas' | 'none')` — `'none'`
110
+ restores the previous hard-miss behavior, and `'canvas'` rasterizes the real
111
+ typeface at runtime when the browser has it. A family the `'canvas'` policy
112
+ enrolled for itself stops being canvas-served once the policy changes; one
113
+ you name with `registerCanvasFont` is served under every policy, and
114
+ `isCanvasFont` reports that distinction — it answers "will the dynamic tier
115
+ serve this family right now", so an auto-enrolled family reads `false` under
116
+ `'substitute'` and `'none'`. The default
117
+ family may be a canvas-registered family, and when it cannot serve the
118
+ request either, the resulting blank text is reported with its own warning
119
+ naming the default family rather than failing silently. Requesting the
120
+ default family itself also warns — whether it is registered at a variant it
121
+ can't serve, or `setDefaultFontFamily` named a family that was never
122
+ registered at all; either way there is nothing left to fall back to. An app
123
+ that has registered no fonts and set no default stays silent, since that is
124
+ not a misconfiguration.
125
+
126
+ `ResolveResult.substituted` reports the substitution structurally, and
127
+ `ResolveResult.resolved` now
128
+ carries the matched `family` alongside `weight` and `style` — the full atlas
129
+ identity to pass to `getFont` / `textureCacheKey`.
130
+
131
+ Adds `listFonts()` for enumerating registered families.
132
+
133
+ - a925117: Text antialiasing is derived from the screen-space derivative instead of a
134
+ constant.
135
+
136
+ Both SDF text shaders computed their smoothstep band from a fixed `u_aaWidth`
137
+ (0.05, set once per draw). A constant band cannot be correct at more than one
138
+ scale: at 16px it collapsed to well under a screen pixel, so glyph coverage
139
+ quantized to all-or-nothing and edges rendered as hard stair-steps; at display
140
+ sizes the same constant read mushy. `TEXT_FRAG_SRC` and `TEXT_FRAG_R8_SRC` now
141
+ take the band from `fwidth(sdfVal)`, which folds in font size, zoom, and DPR
142
+ together, with a small floor so a degenerate derivative can't reproduce the
143
+ aliased behavior.
144
+
145
+ This changes how all GL-rendered text looks — most visibly at UI sizes, where
146
+ it is the difference between binary and antialiased edges.
147
+
148
+ Breaking, for anyone driving the shaders directly:
149
+
150
+ - `u_aaWidth` is gone from both fragment sources and from `TEXT_SDF_UNIFORMS`.
151
+ There is no CPU-side AA knob to set; the shader derives it. Setting the
152
+ uniform was never useful — the kit only ever wrote 0.05 to it.
153
+
154
+ - eeae450: A codepoint the atlas never baked now falls back to a real glyph instead of a
155
+ literal `?`.
156
+
157
+ Font fallback resolved at family granularity: `resolveFontVariant` picks one
158
+ tier for a whole run. But a baked MSDF atlas covers a fixed charset, so a run
159
+ served by a perfectly good atlas can still contain a character that atlas has
160
+ no glyph for — an em dash, a curly quote, anything outside the subset. Those
161
+ drew codepoint 63. That fabricates a character the author never wrote and is
162
+ indistinguishable from one they did; the committed text baseline read "Themed
163
+ editing ? magenta caret" for a full commit without anyone noticing.
164
+
165
+ `layoutRuns` now escalates the individual codepoint to the dynamic canvas
166
+ tier, which rasterizes from installed fonts and can usually serve it for real.
167
+ The escalated glyph gets its own draw group (different texture and shader) and
168
+ is scaled by its own atlas's bake size. When escalation isn't available the
169
+ character is skipped with a warning naming it, rather than substituted —
170
+ `.notdef` is what a text stack should draw here, and the BmFont format has no
171
+ such glyph.
172
+
173
+ New in `@weasel-js/font`:
174
+
175
+ - `resolveGlyphFallback(family, weight, style)` returns a canvas-tier
176
+ `ResolveResult` for per-codepoint escalation, or `null` when it isn't
177
+ available. Declines under the `'none'` fallback policy, which documents a
178
+ miss as a hard miss, and when there is no canvas to rasterize into (SSR)
179
+ rather than throwing into the layout pass.
180
+
181
+ - e7d71c9: Text properties: node-level typography through the schema-driven panel, and
182
+ caret-range styling through a new tool options bar.
183
+
184
+ `TextStyle` and `StyledRun` gain `letterSpacing`, `underline`, and
185
+ `strikethrough`, with GL rendering, DOM-overlay, and SVG round-trips for all
186
+ three. `styleAtRange` / `applyStyleToRange` expose the run algebra publicly,
187
+ and `useTextEdit` gains `selection`, `rangeStyle`, and
188
+ `applyStyleToSelection`. Text nodes get Character and Paragraph schema
189
+ groups. New `ToolOptionsBar` component; `ToggleBar` renders indeterminate
190
+ segments via `mixedValues`.
191
+
192
+ Behavior changes worth knowing about:
193
+
194
+ - **`SelectionPanel` reads and writes node paths of any depth** (two or more
195
+ segments). It previously split at the first dot and read exactly one level,
196
+ so `data.style.fontSize` resolved to `data['style.fontSize']`.
197
+ - **The default `kit:text` painter paints a node's `runs`** when it has them,
198
+ instead of re-flattening `data.text`. Run styling was previously invisible
199
+ to anything drawn by the default scene layer.
200
+ - **`useTextEdit` no longer commits when focus moves into editing chrome**
201
+ (`isEditorChrome`), and commits on a pointerdown outside both the overlay
202
+ and that chrome. Its published `selection` survives focus leaving the
203
+ overlay — it clears on `startEdit` and when the edit ends. Without this a
204
+ character bar could not exist: clicking its controls ended the edit they
205
+ were there to change.
206
+ - **The edit overlay can scale with the view** (`TextEditScreenPose.zoom`),
207
+ keeping every metric on it — including run-level `fontSize` and
208
+ `letterSpacing` — in world units. Omitting `zoom` keeps the old
209
+ screen-pixel contract.
210
+ - **The canvas-2D measurement path counts tracking**, as the GL path already
211
+ did, so wrap points, `caretIndexAt`, and `fitTextPose` agree on tracked
212
+ text. This moves wrap points on any text with a non-zero `letterSpacing`.
213
+ - **The text tool enters edit on the box it inserts.**
214
+
215
+ Breaking-ish, in packages that have not been published with these paths:
216
+
217
+ - `splitNodePath` is removed from `@weasel-js/ui`'s public API — it was dead
218
+ and encoded the superseded one-level path model. `nodeValueAt` and
219
+ `setAtPath` are exported in its place.
220
+ - A text node's color leaf is now `data.style.fill` of the new `paint` kind
221
+ rather than `data.style.fill.color` of the `color` kind. `TextStyle.fill`
222
+ is a tagged union, so the old leaf read `undefined` off a gradient and
223
+ wrote a hybrid the renderer painted flat solid.
224
+
225
+ ### Patch Changes
226
+
227
+ - Updated dependencies [d3e5597]
228
+ - Updated dependencies [a925117]
229
+ - Updated dependencies [eeae450]
230
+ - @weasel-js/font@0.7.0
231
+ - @weasel-js/geom@0.7.0
232
+ - @weasel-js/gestures@0.7.0
233
+ - @weasel-js/history@0.7.0
234
+ - @weasel-js/modes@0.7.0
235
+
3
236
  ## 0.6.0
4
237
 
5
238
  ### Patch Changes
@@ -242,6 +242,12 @@ interface TextStyle {
242
242
  selectionBackground?: string;
243
243
  /** Selection text color paired with `selectionBackground`. Default: inherits text color. */
244
244
  selectionColor?: string;
245
+ /** Extra advance added after each glyph, in world units. Default 0. */
246
+ letterSpacing?: number;
247
+ /** Default `false`. */
248
+ underline?: boolean;
249
+ /** Default `false`. */
250
+ strikethrough?: boolean;
245
251
  }
246
252
  /** `TextStyle` with all fields filled in from defaults — what the renderer actually consumes. */
247
253
  interface ResolvedTextStyle {
@@ -255,6 +261,9 @@ interface ResolvedTextStyle {
255
261
  caretColor: string;
256
262
  selectionBackground: string | null;
257
263
  selectionColor: string | null;
264
+ letterSpacing: number;
265
+ underline: boolean;
266
+ strikethrough: boolean;
258
267
  }
259
268
  /** Default resolved style used when a `TextPose` omits `style`. */
260
269
  declare const DEFAULT_TEXT_STYLE: ResolvedTextStyle;
@@ -281,6 +290,9 @@ interface StyledRun {
281
290
  fontFamily?: string;
282
291
  fontSize?: number;
283
292
  fill?: FillStyle;
293
+ letterSpacing?: number;
294
+ underline?: boolean;
295
+ strikethrough?: boolean;
284
296
  }
285
297
  declare function toRuns(input: string | StyledRun[]): StyledRun[];
286
298
  declare function runsToPlainText(runs: readonly StyledRun[]): string;
@@ -301,8 +313,14 @@ declare function markdownToRuns(input: string): StyledRun[];
301
313
  *
302
314
  * `bold`/`italic` toggles on a run are folded into `fontWeight`/`fontStyle`:
303
315
  * `bold: true` → fontWeight 700, `italic: true` → fontStyle 'italic'.
304
- * Explicit `fontFamily` / `fontSize` / `fill` on the run override the
305
- * node-level value.
316
+ * Explicit `fontFamily` / `fontSize` / `fill` / `letterSpacing` on the run
317
+ * override the node-level value (`letterSpacing: 0` on a run is an override,
318
+ * not an absence — it zeroes inherited tracking).
319
+ *
320
+ * `underline` / `strikethrough` are *additive*, like `bold`/`italic`: a run
321
+ * can turn a decoration on but never off, so they resolve as
322
+ * `run.x || style.x` and not `run.x ?? style.x`. See the header of
323
+ * `runs/rangeStyle.ts` for why the model collapses the tri-state.
306
324
  */
307
325
 
308
326
  interface ResolvedRun {
@@ -312,6 +330,12 @@ interface ResolvedRun {
312
330
  fontWeight: number;
313
331
  fontStyle: 'normal' | 'italic';
314
332
  fill: FillStyle;
333
+ /** Extra advance added after each glyph of this run, in world units. */
334
+ letterSpacing: number;
335
+ /** Draw a rule below this run's baseline. Additive over the node style. */
336
+ underline: boolean;
337
+ /** Draw a rule through this run's x-height. Additive over the node style. */
338
+ strikethrough: boolean;
315
339
  }
316
340
  declare function resolveRuns(runs: readonly StyledRun[], style: ResolvedTextStyle): ResolvedRun[];
317
341