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