@weasel-js/font 1.0.2 → 1.0.4

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.
package/dist/index.d.ts CHANGED
@@ -1,361 +1,5 @@
1
- /**
2
- * BmFont JSON types and parser for the MSDF atlas format produced by
3
- * msdf-bmfont-xml. The parser builds accelerator maps (charMap, kerningMap)
4
- * for O(1) glyph and kerning lookup during layout.
5
- */
6
- interface BmFontInfo {
7
- face: string;
8
- size: number;
9
- }
10
- interface BmFontCommon {
11
- lineHeight: number;
12
- base: number;
13
- scaleW: number;
14
- scaleH: number;
15
- }
16
- interface BmFontChar {
17
- id: number;
18
- x: number;
19
- y: number;
20
- width: number;
21
- height: number;
22
- xoffset: number;
23
- yoffset: number;
24
- xadvance: number;
25
- page: number;
26
- }
27
- interface BmFontKerning {
28
- first: number;
29
- second: number;
30
- amount: number;
31
- }
32
- interface BmFont {
33
- info: BmFontInfo;
34
- common: BmFontCommon;
35
- chars: BmFontChar[];
36
- kernings: BmFontKerning[];
37
- charMap: Map<number, BmFontChar>;
38
- kerningMap: Map<number, Map<number, number>>;
39
- }
40
- /** Two-glyph fixture for unit tests. */
41
- declare const FIXTURE_FONT: {
42
- info: {
43
- face: string;
44
- size: number;
45
- };
46
- common: {
47
- lineHeight: number;
48
- base: number;
49
- scaleW: number;
50
- scaleH: number;
51
- };
52
- chars: {
53
- id: number;
54
- x: number;
55
- y: number;
56
- width: number;
57
- height: number;
58
- xoffset: number;
59
- yoffset: number;
60
- xadvance: number;
61
- page: number;
62
- }[];
63
- kernings: {
64
- first: number;
65
- second: number;
66
- amount: number;
67
- }[];
68
- };
69
- declare function parseBmFont(raw: unknown): BmFont;
70
-
71
- /**
72
- * The only thing the glyph tier needs from a GL texture cache. Core's
73
- * `GLTextureCache` satisfies this structurally — there is no adapter and no
74
- * registration step, because the type *is* the seam.
75
- *
76
- * Declared here rather than imported so this package has zero reach-back into
77
- * core. If core's cache ever drops one of these methods, the failure surfaces
78
- * as a type error at the call site in `renderer/draw.ts`, which is where it
79
- * belongs.
80
- */
81
- /** Anything WebGL can upload as a texture. */
82
- type TexSource = HTMLImageElement | ImageBitmap | ImageData | HTMLCanvasElement;
83
- interface GlyphTextureSink {
84
- has(id: string): boolean;
85
- upload(id: string, source: TexSource): string;
86
- uploadR8(id: string, width: number, height: number, data: Uint8Array): void;
87
- subImageR8(id: string, x: number, y: number, w: number, h: number, data: Uint8Array): void;
88
- }
89
-
90
- interface FaceMetrics {
91
- ascent: number;
92
- descent: number;
93
- }
94
- interface RasterizedGlyph {
95
- width: number;
96
- height: number;
97
- alpha: Uint8ClampedArray;
98
- left: number;
99
- top: number;
100
- advance: number;
101
- }
102
- interface GlyphRasterizer {
103
- faceMetrics(family: string, weight: number, style: 'normal' | 'italic'): FaceMetrics;
104
- rasterize(family: string, weight: number, style: 'normal' | 'italic', codepoint: number): RasterizedGlyph;
105
- }
106
-
107
- /**
108
- * DynamicGlyphAtlas — runtime single-channel SDF glyphs for canvas-sourced
109
- * (installed machine) fonts. TinySDF technique: canvas fillText at 48 px →
110
- * Euclidean distance transform → shelf-packed R8 pages (1024², max 4).
111
- *
112
- * Baked MSDF atlases always win: this tier only serves families registered
113
- * via `registerCanvasFont` that have no baked entry (see resolveFontVariant).
114
- *
115
- * Faces expose a BmFont-shaped `font` whose charMap grows as glyphs are
116
- * requested, so `layoutRuns` consumes them through the same code path as
117
- * baked atlases. A char's advance is valid immediately (measureText); its
118
- * atlas rect fills in when the bake lands (width 0 / page -1 until then, so
119
- * layout advances the pen but emits no quad).
120
- */
121
-
122
- declare const DEFAULT_BAKE_BUDGET = 16;
123
- interface DynamicFace {
124
- family: string;
125
- weight: number;
126
- style: 'normal' | 'italic';
127
- /** BmFont-shaped view consumed by layoutRuns; charMap grows lazily. */
128
- font: BmFont;
129
- /** Char record for `cp`, measured on first request (advance always valid
130
- * immediately; atlas rect fills in when the bake lands). */
131
- requestGlyph(cp: number): BmFontChar;
132
- }
133
- interface DynamicPage {
134
- data: Uint8Array;
135
- /** Bumps on every glyph blit; each blit appends a patch with seq = version. */
136
- version: number;
137
- patches: {
138
- seq: number;
139
- x: number;
140
- y: number;
141
- w: number;
142
- h: number;
143
- }[];
144
- }
145
- /** One family served by the dynamic canvas-SDF tier. See `listCanvasFonts`. */
146
- interface CanvasFontEntry {
147
- family: string;
148
- /** `'explicit'` — a `registerCanvasFont` call; `'auto'` — the `'canvas'`
149
- * fallback policy enrolled it on a miss, and the enrollment lapses if
150
- * that policy changes. */
151
- enrollment: 'explicit' | 'auto';
152
- }
153
- /** Mark `family` as canvas-sourced: when no baked atlas covers it,
154
- * resolveFontVariant serves it from this dynamic atlas. */
155
- declare function registerCanvasFont(family: string): void;
156
- /**
157
- * Will `family` be served by the dynamic canvas-SDF tier *right now*?
158
- *
159
- * Service, not membership — the answer depends on the fallback policy in
160
- * force and can change without any enrollment call:
161
- * - explicitly enrolled via `registerCanvasFont` → `true` under every
162
- * policy; a consumer naming a family outranks the policy.
163
- * - auto-enrolled by the `'canvas'` policy → `true` only while that policy
164
- * is still in force. The enrollment lapses rather than being discarded,
165
- * so returning to `'canvas'` makes it `true` again.
166
- * - never enrolled → `false`, including under `'canvas'` (that policy
167
- * enrolls lazily, on the first miss).
168
- *
169
- * The membership reading would answer `true` for an auto-enrolled family
170
- * under `'substitute'` / `'none'`, where nothing routes to this tier — a
171
- * caller predicting what renders would be told the opposite of the truth.
172
- * Mirrors `listFonts`, which reports the baked registry alone for the same
173
- * reason: "enrolled" is not "will render".
174
- */
175
- declare function isCanvasFont(family: string): boolean;
176
- /**
177
- * Every family the dynamic canvas-SDF tier will serve *right now* — the
178
- * enumeration companion to `isCanvasFont`, which answers one family at a
179
- * time. Same "service, not membership" rule: an auto-enrolled family appears
180
- * only while the `'canvas'` policy is in force.
181
- *
182
- * Exists so a consumer can offer these families in a font picker.
183
- * `listFonts` reports the baked registry alone, so without this the only
184
- * ways to present canvas-served families were to hard-code a list beside the
185
- * `registerCanvasFont` calls or to omit them — a UI inventing its own answer
186
- * about what will render.
187
- *
188
- * `enrollment` distinguishes a consumer's explicit `registerCanvasFont` from
189
- * a lazy enrollment the policy made on a miss; both render identically, but
190
- * only the explicit one survives a policy change.
191
- */
192
- declare function listCanvasFonts(): readonly CanvasFontEntry[];
193
- /** Remove a canvas family. Its faces are dropped; already-baked glyph
194
- * pixels stay in their pages (no eviction in v1). */
195
- declare function unregisterCanvasFont(family: string): void;
196
- /** Reset the per-frame synchronous bake budget. Called by
197
- * `WeaselRenderer.render()` at frame start; the headless
198
- * `renderSceneToPixels` path passes Infinity so print never defers. */
199
- declare function resetBakeBudget(n?: number): void;
200
- /** Texture-cache key for a dynamic page (parallel to `textureCacheKey`). */
201
- declare function dynamicPageTextureId(page: number): string;
202
- /** Bring `cache`'s copy of page `pageIndex` up to date: full R8 upload the
203
- * first time, `texSubImage2D` patches after. Returns false if the page
204
- * doesn't exist yet. */
205
- declare function syncDynamicPageTexture(cache: GlyphTextureSink, pageIndex: number): boolean;
206
- /** @internal test seam — inject a fake rasterizer (jsdom has no canvas
207
- * metrics). Pass null to restore the lazy default. */
208
- declare function __setGlyphRasterizerForTests(r: GlyphRasterizer | null): void;
209
- /** @internal test seam — inspect CPU-side pages. */
210
- declare function _getPagesForTests(): readonly DynamicPage[];
211
- /** @internal test seam — clear all dynamic-font state. */
212
- declare function _resetDynamicFontsForTests(): void;
213
-
214
- /**
215
- * FontRegistry and registerFont() public API.
216
- *
217
- * Variants are keyed by (family, weight, style). registerFont() takes a
218
- * FontVariant alongside the family and the two URLs; the registry stores
219
- * entries in a two-level Map so resolveFontVariant() can iterate a family's
220
- * variants for the fallback chain.
221
- */
222
-
223
- interface FontEntry {
224
- font: BmFont;
225
- bitmap: ImageBitmap;
226
- }
227
- interface FontVariant {
228
- weight?: number;
229
- style?: 'normal' | 'italic';
230
- }
231
- type FontStyle = 'normal' | 'italic';
232
- /** Test helper. Do not call from product code. */
233
- declare function _resetFontRegistryForTests(): void;
234
- /** Exact lookup — does NOT walk the fallback chain. Use `resolveFontVariant` for that. */
235
- declare function getFont(family: string, weight?: number, style?: FontStyle): FontEntry | null;
236
- interface RegisteredFont {
237
- family: string;
238
- variants: readonly {
239
- weight: number;
240
- style: FontStyle;
241
- }[];
242
- }
243
- /**
244
- * Enumerate the registry — what a font picker can honestly offer. Families
245
- * come back in registration order; variants sorted by weight, then style, so
246
- * the output is stable enough to assert against.
247
- */
248
- declare function listFonts(): readonly RegisteredFont[];
249
- declare function registerFont(family: string, variant: FontVariant, metricsUrl: string, atlasUrl: string): Promise<void>;
250
- /**
251
- * Ensure the atlas for `(family, weight, style)` is uploaded to
252
- * `textureCache`. Cache key is `${family}|${weight}|${style}` so each
253
- * variant occupies its own texture slot.
254
- */
255
- declare function ensureFontTexture(family: string, weight: number, style: FontStyle, textureCache: GlyphTextureSink): boolean;
256
- /** The texture cache key used by `ensureFontTexture` for a given variant. */
257
- declare function textureCacheKey(family: string, weight: number, style: FontStyle): string;
258
- /** Kept as a no-op for context-restore call sites; per-cache dedup handles it now. */
259
- declare function markAllFontsNotUploaded(): void;
260
- interface ResolveResult {
261
- entry: FontEntry | null;
262
- /**
263
- * The (family, weight, style) triple that was actually matched. May differ
264
- * from the requested values when the resolver walked the fallback chain —
265
- * including `family`, when the cross-family policy substituted a default.
266
- *
267
- * This is the atlas identity: pass all three to `getFont` /
268
- * `textureCacheKey` and the lookup hits. Describing only weight and style
269
- * here once let a caller key its draw on the *requested* family, which
270
- * resolves to no atlas at all and paints nothing.
271
- *
272
- * The synthetic flags describe the gap between requested and resolved for
273
- * shader-side compensation. When `entry` is null, these mirror the
274
- * requested values.
275
- */
276
- resolved: {
277
- family: string;
278
- weight: number;
279
- style: FontStyle;
280
- };
281
- synthetic: {
282
- bold: boolean;
283
- italic: boolean;
284
- };
285
- /** Which tier resolved: a baked MSDF atlas, or the runtime canvas-SDF
286
- * dynamic atlas. Misses report 'atlas' (the default tier). */
287
- source: 'atlas' | 'canvas';
288
- /** Set only when source === 'canvas': the dynamic face whose BmFont-shaped
289
- * `font` layoutRuns consumes in place of `entry.font`. */
290
- dynamicFace?: DynamicFace;
291
- /**
292
- * Set when the requested family was not registered and the fallback policy
293
- * substituted a different one. Reported structurally so a UI can say
294
- * "Inter — not loaded, showing Roboto" instead of leaving the user to
295
- * wonder why the family control did nothing.
296
- */
297
- substituted?: {
298
- requested: string;
299
- resolved: string;
300
- };
301
- }
302
- /**
303
- * Resolve a `(family, weight, style)` request to a registered font entry,
304
- * walking the fallback chain when an exact match isn't available. Returns
305
- * synthetic flags describing the gap between requested and resolved so the
306
- * renderer can apply SDF-thicken / vertex-skew fakes.
307
- */
308
- declare function resolveFontVariant(family: string, weight: number, style: FontStyle): ResolveResult;
309
- /**
310
- * Escalate a *single codepoint* to the dynamic tier when the atlas that
311
- * resolved for the run has no glyph for it.
312
- *
313
- * `resolveFontVariant` answers at family granularity: it picks one tier for a
314
- * whole run. But a baked MSDF atlas covers a fixed charset, so a run served by
315
- * a perfectly good atlas can still contain a character that atlas never baked
316
- * — an em dash, a curly quote, anything outside the subset. The dynamic tier
317
- * rasterizes on demand from installed fonts and can serve exactly those.
318
- *
319
- * Returns a canvas-tier `ResolveResult` whose `dynamicFace` the caller drives
320
- * with `requestGlyph(cp)`, or `null` when escalation isn't available:
321
- *
322
- * - Policy `'none'` documents a miss as a *hard* miss. Quietly reaching for
323
- * another tier per codepoint would undo that, so it doesn't.
324
- * - No canvas to rasterize into (SSR, a jsdom test without one) makes the
325
- * dynamic tier constructible-but-broken; `getDynamicFace` throws and this
326
- * reports the miss instead of taking the caller down with it.
327
- *
328
- * Cheap to call per missing codepoint: faces are cached by variant and glyphs
329
- * by codepoint, so a repeat is two map lookups.
330
- */
331
- declare function resolveGlyphFallback(family: string, weight: number, style: FontStyle): ResolveResult | null;
332
-
333
- /**
334
- * Cross-family fallback policy. The per-family chain in `resolveFontVariant`
335
- * (weight → style → synthetic) has always been rich; what was missing is what
336
- * happens when the family itself was never registered. That case used to
337
- * render nothing at all, which is the single most common cause of "my text is
338
- * invisible".
339
- */
340
- /**
341
- * What happens when a requested family has no baked atlas:
342
- * - `'substitute'` — render with the default family (see
343
- * `setDefaultFontFamily`), reporting the swap on `ResolveResult.substituted`.
344
- * - `'canvas'` — auto-enroll the family with the dynamic canvas-SDF
345
- * rasterizer. The consumer gets the *real* typeface if the browser has it,
346
- * at canvas-SDF quality rather than baked-MSDF quality.
347
- * - `'none'` — hard miss; the run renders nothing.
348
- */
349
- type FontFallbackPolicy = 'substitute' | 'canvas' | 'none';
350
- declare function setFontFallbackPolicy(next: FontFallbackPolicy): void;
351
- declare function getFontFallbackPolicy(): FontFallbackPolicy;
352
- /** Explicit default family for `'substitute'`. When unset, the first
353
- * registered family wins — the right answer for the common case of an app
354
- * that registers exactly one. */
355
- declare function setDefaultFontFamily(family: string): void;
356
- declare function getDefaultFontFamily(): string | null;
357
- /** Test helper. Do not call from product code. */
358
- declare function _resetFallbackForTests(): void;
1
+ import { O as OutlineFontStyle } from './test-seams-DLNsXb8P.js';
2
+ export { B as BmFont, a as BmFontChar, b as BmFontCommon, c as BmFontInfo, d as BmFontKerning, C as CanvasFontEntry, D as DEFAULT_BAKE_BUDGET, F as FIXTURE_FONT, e as FontEntry, f as FontFallbackPolicy, g as FontVariant, G as GlyphTextureSink, h as OutlineFace, i as OutlineFontOptions, j as OutlineParser, k as OutlineSource, l as OutlineStatus, m as OutlineVariant, R as RegisteredFont, n as ResolveResult, T as TexSource, o as dynamicPageTextureId, p as ensureFontTexture, q as getDefaultFontFamily, r as getFont, s as getFontFallbackPolicy, t as glyphOutline, u as hasFontOutlines, v as isCanvasFont, w as listCanvasFonts, x as listFontOutlines, y as listFonts, z as markAllFontsNotUploaded, A as outlineStatus, E as parseBmFont, H as registerCanvasFont, I as registerFont, J as registerFontOutlines, K as resetBakeBudget, L as resolveFontVariant, M as resolveGlyphFallback, N as setDefaultFontFamily, P as setFontFallbackPolicy, Q as syncDynamicPageTexture, S as textureCacheKey, U as unregisterCanvasFont, V as unregisterFontOutlines } from './test-seams-DLNsXb8P.js';
359
3
 
360
4
  /**
361
5
  * "A glyph the renderer asked for can now paint" — the redraw signal shared
@@ -397,161 +41,6 @@ declare function glyphGeneration(): number;
397
41
  */
398
42
  declare function subscribeGlyphReady(cb: () => void): () => void;
399
43
 
400
- /**
401
- * The parsed-font seam for the outline tier.
402
- *
403
- * A face answers exactly one question — "what does codepoint N look like?" —
404
- * and answers it as **SVG path data in em space**: one unit is one em, y
405
- * grows downward, and the origin sits on the baseline at the glyph's pen
406
- * position. That is a complete description of the glyph independent of size,
407
- * zoom, and position, which is the whole reason the tier exists: tessellate
408
- * once, transform per instance.
409
- *
410
- * ### Why `d` and not a command stream
411
- *
412
- * `@weasel-js/font` is a Tier A leaf — `@weasel-js/core` depends on it and
413
- * never the reverse (see `leaf-purity.test.ts`), so this package cannot name
414
- * core's `PolygonPath`. The alternative to a string would be re-declaring
415
- * core's `PATH_M`/`PATH_L`/… opcodes over here and trusting two packages to
416
- * keep the same numbering forever. SVG `d` is the kit's documented language
417
- * for geometry crossing a boundary (`docs/conventions.md`), core already
418
- * ships `pathFromD`, and a glyph outline is exactly the case it describes.
419
- *
420
- * The string is also a natural cache key and costs nothing to hold: the
421
- * parse happens once per glyph, and core caches the *tessellation*, not the
422
- * text.
423
- */
424
- /** Weight/style pair identifying one face within a family. */
425
- type OutlineFontStyle = 'normal' | 'italic';
426
- interface OutlineFace {
427
- /** Font design units per em. Reported for diagnostics; `glyphD` has already
428
- * divided by it, so callers never need to. */
429
- unitsPerEm: number;
430
- /**
431
- * Em-space SVG path data for `cp`, or `null` when this face has no glyph
432
- * for the codepoint *or* the glyph has no contours (a space). Both answers
433
- * mean the same thing to a caller: there is nothing here to tessellate, so
434
- * fall through to whatever tier would have drawn it.
435
- */
436
- glyphD(cp: number): string | null;
437
- }
438
- /**
439
- * Turn font file bytes into a face. Async because the default implementation
440
- * dynamically imports its parser — see `opentypeParser.ts` for why that
441
- * matters — and because a caller may want to hand back a face that finishes
442
- * initializing off-thread.
443
- */
444
- type OutlineParser = (bytes: ArrayBuffer) => OutlineFace | Promise<OutlineFace>;
445
-
446
- /**
447
- * The outline tier's registry: which faces have real font bytes behind them,
448
- * and what a given glyph looks like.
449
- *
450
- * ### What this tier is for
451
- *
452
- * A distance field is a *sampled* representation. The baked MSDF atlas is
453
- * generated from outlines and holds up well; the dynamic canvas-SDF tier
454
- * (`../dynamic/`) reconstructs its field from a 48px raster, so magnifying it
455
- * past a few times the bake size exposes the raster as contour wobble — bumps
456
- * one bake-texel apart, ±2–3px at 8×. No bake size fixes that, it only moves
457
- * the size at which it appears (`glyphRasterizer.ts` measures the tradeoff:
458
- * error is *minimized at* the bake size, so raising it spoils the 12–32px
459
- * range where most text lives).
460
- *
461
- * Above a size threshold the answer is to stop sampling: parse the font, take
462
- * the glyph's outline, and hand it to the path renderer the kit already has.
463
- * Exact at every zoom, and — because a glyph becomes an ordinary path — it
464
- * takes strokes and non-solid fills for free.
465
- *
466
- * Below the threshold the atlas still wins, and not narrowly: outlines carry
467
- * no hinting and no stem darkening, so body text at 12–16px rendered from
468
- * them looks *worse* than a platform rasterizer that puts stems on the pixel
469
- * grid. Two tiers with a size threshold is the standard hybrid, not a
470
- * compromise.
471
- *
472
- * ### Metric neutrality (the load-bearing invariant)
473
- *
474
- * This tier replaces how a glyph is *painted*, never where it sits. Advances,
475
- * kerning, line breaking and baselines all keep coming from whichever tier
476
- * resolved the run — the baked atlas or the canvas face. That is what lets
477
- * the threshold be a rendering decision: zooming past it swaps glyph geometry
478
- * with the layout untouched, so text cannot reflow under the user's cursor,
479
- * and `measureTextBounds` / `textLineBoxes` need to know nothing about
480
- * outlines at all.
481
- *
482
- * The cost is that a face whose real advances disagree with the atlas's
483
- * inherits the atlas's. For the bundled Inter that is exact (the subset ships
484
- * from the same source the atlas was baked from), and for a machine font the
485
- * outline bytes and the canvas metrics come from the same file.
486
- *
487
- * ### Availability
488
- *
489
- * Everything here degrades rather than blocks. Bytes arrive over `fetch` or
490
- * `queryLocalFonts` — asynchronous, and in the local case gated behind a
491
- * permission the user can refuse — so `glyphOutline` answers `null` until a
492
- * face is loaded and forever if it failed, and the caller falls back down the
493
- * ladder to SDF. A family must never render *nothing* because the outline
494
- * tier could not get bytes.
495
- */
496
-
497
- /**
498
- * Where a face's bytes come from. A URL is fetched; a buffer or blob is used
499
- * as-is; a thunk is called at first use, which is what lets `queryLocalFonts`
500
- * results be registered eagerly and read lazily — the permission prompt and
501
- * the (potentially many megabytes of) blob only happen for a family the
502
- * document actually sets text in.
503
- */
504
- type OutlineSource = string | ArrayBuffer | Blob | (() => ArrayBuffer | Blob | Promise<ArrayBuffer | Blob>);
505
- interface OutlineVariant {
506
- weight?: number;
507
- style?: OutlineFontStyle;
508
- }
509
- interface OutlineFontOptions {
510
- /** Override the default opentype.js parser. Mostly a test seam; also the
511
- * hook for a consumer who already has a font parser in their bundle. */
512
- parser?: OutlineParser;
513
- }
514
- /** Load state of one registered face. */
515
- type OutlineStatus = 'idle' | 'loading' | 'ready' | 'failed';
516
- /**
517
- * Register font file bytes for one face, so text set in it can render from
518
- * outlines above the size threshold.
519
- *
520
- * Registration is cheap and synchronous: nothing is fetched, and the parser
521
- * is not even imported, until a frame actually asks for a glyph from this
522
- * face. Registering a face that is never drawn large costs one map entry.
523
- *
524
- * The variant must match exactly what the layout tier resolved — this is
525
- * deliberately *not* a fallback chain. Painting 400-weight outlines at
526
- * 700-weight advances, or upright outlines where the shader was going to
527
- * fake an oblique, looks worse than the SDF it replaced; a miss here falls
528
- * back down the ladder instead, which is always safe.
529
- */
530
- declare function registerFontOutlines(family: string, variant: OutlineVariant, source: OutlineSource, opts?: OutlineFontOptions): void;
531
- /** Drop a registration. Glyphs already handed out stay valid — they are
532
- * plain path data — but nothing further resolves from this face. */
533
- declare function unregisterFontOutlines(family: string, variant?: OutlineVariant): void;
534
- /**
535
- * Is an outline face registered for this exact variant? Answers `true` while
536
- * the bytes are still loading — the question is "could this face serve
537
- * outlines", which is what a caller deciding whether to *ask* wants; use
538
- * `outlineStatus` for the narrower "can it right now".
539
- */
540
- declare function hasFontOutlines(family: string, weight?: number, style?: OutlineFontStyle): boolean;
541
- /** Load state of a registered face; `null` when nothing is registered. */
542
- declare function outlineStatus(family: string, weight?: number, style?: OutlineFontStyle): OutlineStatus | null;
543
- /** Every registered outline face and its load state — the enumeration a
544
- * debug overlay or font picker needs, mirroring `listFonts`. */
545
- declare function listFontOutlines(): readonly {
546
- family: string;
547
- weight: number;
548
- style: OutlineFontStyle;
549
- status: OutlineStatus;
550
- }[];
551
- declare function glyphOutline(family: string, weight: number, style: OutlineFontStyle, cp: number): string | null;
552
- /** @internal Test seam — registry and warn-once keys are module state. */
553
- declare function _resetFontOutlinesForTests(): void;
554
-
555
44
  /**
556
45
  * Local Font Access → outline registrations.
557
46
  *
@@ -591,6 +80,7 @@ declare function parseFontStyle(style: string): {
591
80
  weight: number;
592
81
  style: OutlineFontStyle;
593
82
  };
83
+ /** Options for `registerLocalFontOutlines`. */
594
84
  interface LocalFontOutlinesOptions {
595
85
  /**
596
86
  * Restrict registration to these families. Defaults to every installed
@@ -600,6 +90,7 @@ interface LocalFontOutlinesOptions {
600
90
  */
601
91
  families?: readonly string[];
602
92
  }
93
+ /** What `registerLocalFontOutlines` registered. */
603
94
  interface LocalFontOutlinesResult {
604
95
  /** Families that now have at least one outline face, in menu order. */
605
96
  families: readonly string[];
@@ -658,6 +149,8 @@ declare function enableLocalFontOutlines(opts?: LocalFontOutlinesOptions): Promi
658
149
  * zero-width band, which would be the aliased behavior all over again.
659
150
  */
660
151
  declare const TEXT_VERT_SRC = "#version 300 es\nin vec2 a_position;\nin vec2 a_uv;\nin float a_baselineY;\nuniform mat3 u_proj;\nuniform mat3 u_model;\nuniform float u_synthItalic;\nout vec2 v_uv;\nvoid main() {\n // Synthetic italic: shift x by (a_baselineY - a_position.y) * tan(angle).\n // Above-baseline vertices (lower y in screen coords) lean further right.\n vec2 skewed = vec2(\n a_position.x + (a_baselineY - a_position.y) * tan(u_synthItalic),\n a_position.y\n );\n vec3 screen = u_model * vec3(skewed, 1.0);\n vec3 clip = u_proj * vec3(screen.xy, 1.0);\n gl_Position = vec4(clip.xy, 0.0, 1.0);\n v_uv = a_uv;\n}\n";
152
+ /** Fragment shader for the MSDF text program. Samples the atlas, applies the
153
+ * color transform, and emits premultiplied alpha. */
661
154
  declare const TEXT_FRAG_SRC = "#version 300 es\nprecision highp float;\nin vec2 v_uv;\nuniform sampler2D u_atlas;\nuniform vec4 u_color;\nuniform float u_alpha;\nuniform float u_synthBold;\nuniform mat4 u_colorMatrix;\nuniform vec4 u_colorBias;\nout vec4 outColor;\n\nfloat median(float r, float g, float b) {\n return max(min(r, g), min(max(r, g), b));\n}\n\nvoid main() {\n vec3 sdf = texture(u_atlas, v_uv).rgb;\n float sdfVal = median(sdf.r, sdf.g, sdf.b);\n // Screen-space AA band \u2014 see the file header. Half of fwidth spans ~1px.\n float aaW = max(0.5 * fwidth(sdfVal), 0.0005);\n // u_synthBold shifts the SDF threshold to thicken strokes when the\n // resolver fell back from a missing bold variant to the regular atlas.\n float threshold = 0.5 - u_synthBold;\n float msdfAlpha = smoothstep(threshold - aaW, threshold + aaW, sdfVal);\n vec4 src = vec4(u_color.rgb, u_color.a);\n vec4 mapped = clamp(u_colorMatrix * src + u_colorBias, 0.0, 1.0);\n float a = mapped.a * msdfAlpha * u_alpha;\n outColor = vec4(mapped.rgb * a, a);\n}\n";
662
155
  /**
663
156
  * Single-channel sibling of TEXT_FRAG_SRC for runtime canvas-SDF glyphs
@@ -670,7 +163,11 @@ declare const TEXT_FRAG_SRC = "#version 300 es\nprecision highp float;\nin vec2
670
163
  * larger bake nor extra taps would improve the small-text end.
671
164
  */
672
165
  declare const TEXT_FRAG_R8_SRC = "#version 300 es\nprecision highp float;\nin vec2 v_uv;\nuniform sampler2D u_atlas;\nuniform vec4 u_color;\nuniform float u_alpha;\nuniform float u_synthBold;\nuniform mat4 u_colorMatrix;\nuniform vec4 u_colorBias;\nout vec4 outColor;\n\nvoid main() {\n float sdfVal = texture(u_atlas, v_uv).r;\n // Screen-space AA band \u2014 see the file header. Half of fwidth spans ~1px.\n float aaW = max(0.5 * fwidth(sdfVal), 0.0005);\n float threshold = 0.5 - u_synthBold;\n float sdfAlpha = smoothstep(threshold - aaW, threshold + aaW, sdfVal);\n vec4 src = vec4(u_color.rgb, u_color.a);\n vec4 mapped = clamp(u_colorMatrix * src + u_colorBias, 0.0, 1.0);\n float a = mapped.a * sdfAlpha * u_alpha;\n outColor = vec4(mapped.rgb * a, a);\n}\n";
166
+ /** Uniform names the text program declares, for the caller that looks up and
167
+ * caches their locations. */
673
168
  declare const TEXT_SDF_UNIFORMS: readonly ["u_proj", "u_model", "u_atlas", "u_color", "u_alpha", "u_synthBold", "u_synthItalic", "u_colorMatrix", "u_colorBias"];
169
+ /** Vertex attribute names the text program declares, in the order the
170
+ * interleaved buffer packs them. */
674
171
  declare const TEXT_SDF_ATTRIBUTES: readonly ["a_position", "a_uv", "a_baselineY"];
675
172
 
676
- export { type BmFont, type BmFontChar, type BmFontCommon, type BmFontInfo, type BmFontKerning, type CanvasFontEntry, DEFAULT_BAKE_BUDGET, FIXTURE_FONT, type FontEntry, type FontFallbackPolicy, type FontVariant, type GlyphTextureSink, type LocalFontOutlinesOptions, type LocalFontOutlinesResult, type OutlineFace, type OutlineFontOptions, type OutlineFontStyle, type OutlineParser, type OutlineSource, type OutlineStatus, type OutlineVariant, type RegisteredFont, type ResolveResult, TEXT_FRAG_R8_SRC, TEXT_FRAG_SRC, TEXT_SDF_ATTRIBUTES, TEXT_SDF_UNIFORMS, TEXT_VERT_SRC, type TexSource, __setGlyphRasterizerForTests, _getPagesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests, canQueryLocalFonts, dynamicPageTextureId, enableLocalFontOutlines, ensureFontTexture, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFonts, markAllFontsNotUploaded, outlineStatus, parseBmFont, parseFontStyle, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, subscribeGlyphReady, syncDynamicPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines };
173
+ export { type LocalFontOutlinesOptions, type LocalFontOutlinesResult, OutlineFontStyle, TEXT_FRAG_R8_SRC, TEXT_FRAG_SRC, TEXT_SDF_ATTRIBUTES, TEXT_SDF_UNIFORMS, TEXT_VERT_SRC, canQueryLocalFonts, enableLocalFontOutlines, glyphGeneration, parseFontStyle, subscribeGlyphReady };