@weasel-js/font 0.7.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.
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/index.d.ts +395 -0
- package/dist/index.js +825 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
// src/FontAtlas.ts
|
|
2
|
+
var FIXTURE_FONT = {
|
|
3
|
+
info: { face: "Inter", size: 32 },
|
|
4
|
+
common: { lineHeight: 38, base: 29, scaleW: 512, scaleH: 512 },
|
|
5
|
+
chars: [
|
|
6
|
+
{ id: 65, x: 0, y: 0, width: 22, height: 28, xoffset: 1, yoffset: 4, xadvance: 23, page: 0 },
|
|
7
|
+
{ id: 66, x: 24, y: 0, width: 20, height: 28, xoffset: 2, yoffset: 4, xadvance: 22, page: 0 }
|
|
8
|
+
],
|
|
9
|
+
kernings: [
|
|
10
|
+
{ first: 65, second: 66, amount: -1 }
|
|
11
|
+
]
|
|
12
|
+
};
|
|
13
|
+
function parseBmFont(raw) {
|
|
14
|
+
if (typeof raw !== "object" || raw === null) throw new Error("parseBmFont: expected object");
|
|
15
|
+
const r = raw;
|
|
16
|
+
if (!r.info || typeof r.info !== "object") throw new Error("parseBmFont: missing info");
|
|
17
|
+
if (!r.common || typeof r.common !== "object") throw new Error("parseBmFont: missing common");
|
|
18
|
+
if (!Array.isArray(r.chars)) throw new Error("parseBmFont: chars must be an array");
|
|
19
|
+
const info = r.info;
|
|
20
|
+
const common = r.common;
|
|
21
|
+
const chars = r.chars;
|
|
22
|
+
const kernings = Array.isArray(r.kernings) ? r.kernings : [];
|
|
23
|
+
const charMap = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const ch of chars) charMap.set(ch.id, ch);
|
|
25
|
+
const kerningMap = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const k of kernings) {
|
|
27
|
+
let inner = kerningMap.get(k.first);
|
|
28
|
+
if (!inner) {
|
|
29
|
+
inner = /* @__PURE__ */ new Map();
|
|
30
|
+
kerningMap.set(k.first, inner);
|
|
31
|
+
}
|
|
32
|
+
inner.set(k.second, k.amount);
|
|
33
|
+
}
|
|
34
|
+
return { info, common, chars, kernings, charMap, kerningMap };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/fallback.ts
|
|
38
|
+
var policy = "substitute";
|
|
39
|
+
var defaultFamily = null;
|
|
40
|
+
function setFontFallbackPolicy(next) {
|
|
41
|
+
policy = next;
|
|
42
|
+
}
|
|
43
|
+
function getFontFallbackPolicy() {
|
|
44
|
+
return policy;
|
|
45
|
+
}
|
|
46
|
+
function setDefaultFontFamily(family) {
|
|
47
|
+
defaultFamily = family;
|
|
48
|
+
}
|
|
49
|
+
function getDefaultFontFamily() {
|
|
50
|
+
return defaultFamily;
|
|
51
|
+
}
|
|
52
|
+
var warnedFallbacks = /* @__PURE__ */ new Set();
|
|
53
|
+
function claimFallbackWarning(key) {
|
|
54
|
+
if (warnedFallbacks.has(key)) return false;
|
|
55
|
+
warnedFallbacks.add(key);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
function _clearFallbackWarnings() {
|
|
59
|
+
warnedFallbacks.clear();
|
|
60
|
+
}
|
|
61
|
+
function _resetFallbackForTests() {
|
|
62
|
+
policy = "substitute";
|
|
63
|
+
defaultFamily = null;
|
|
64
|
+
warnedFallbacks.clear();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/dynamic/shelfPack.ts
|
|
68
|
+
var ShelfPacker = class {
|
|
69
|
+
constructor(pageSize, maxPages) {
|
|
70
|
+
this.pageSize = pageSize;
|
|
71
|
+
this.maxPages = maxPages;
|
|
72
|
+
}
|
|
73
|
+
pageSize;
|
|
74
|
+
maxPages;
|
|
75
|
+
pages = [];
|
|
76
|
+
warned = false;
|
|
77
|
+
get pageCount() {
|
|
78
|
+
return this.pages.length;
|
|
79
|
+
}
|
|
80
|
+
/** Allocate a w×h rect. Returns null (warning once) when capacity is out. */
|
|
81
|
+
alloc(w, h) {
|
|
82
|
+
if (w > this.pageSize || h > this.pageSize) return this.fail();
|
|
83
|
+
for (let p = 0; p < this.pages.length; p++) {
|
|
84
|
+
const spot = this.allocInPage(this.pages[p], w, h);
|
|
85
|
+
if (spot) return { page: p, ...spot };
|
|
86
|
+
}
|
|
87
|
+
if (this.pages.length < this.maxPages) {
|
|
88
|
+
const page = { shelves: [], nextY: 0 };
|
|
89
|
+
this.pages.push(page);
|
|
90
|
+
const spot = this.allocInPage(page, w, h);
|
|
91
|
+
if (spot) return { page: this.pages.length - 1, ...spot };
|
|
92
|
+
}
|
|
93
|
+
return this.fail();
|
|
94
|
+
}
|
|
95
|
+
allocInPage(page, w, h) {
|
|
96
|
+
for (const shelf of page.shelves) {
|
|
97
|
+
if (h <= shelf.height && shelf.x + w <= this.pageSize) {
|
|
98
|
+
const x = shelf.x;
|
|
99
|
+
shelf.x += w;
|
|
100
|
+
return { x, y: shelf.y };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (page.nextY + h <= this.pageSize) {
|
|
104
|
+
const shelf = { y: page.nextY, height: h, x: w };
|
|
105
|
+
page.shelves.push(shelf);
|
|
106
|
+
page.nextY += h;
|
|
107
|
+
return { x: 0, y: shelf.y };
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
fail() {
|
|
112
|
+
if (!this.warned) {
|
|
113
|
+
this.warned = true;
|
|
114
|
+
console.warn(
|
|
115
|
+
`weasel DynamicGlyphAtlas: glyph pages full (${this.maxPages} \xD7 ${this.pageSize}\xB2); further dynamic glyphs will not render.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/dynamic/distanceTransform.ts
|
|
123
|
+
var INF = 1e20;
|
|
124
|
+
function edt1d(f, d, v, z, n) {
|
|
125
|
+
v[0] = 0;
|
|
126
|
+
z[0] = -INF;
|
|
127
|
+
z[1] = INF;
|
|
128
|
+
let k = 0;
|
|
129
|
+
for (let q = 1; q < n; q++) {
|
|
130
|
+
let s = (f[q] + q * q - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);
|
|
131
|
+
while (s <= z[k]) {
|
|
132
|
+
k--;
|
|
133
|
+
s = (f[q] + q * q - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);
|
|
134
|
+
}
|
|
135
|
+
k++;
|
|
136
|
+
v[k] = q;
|
|
137
|
+
z[k] = s;
|
|
138
|
+
z[k + 1] = INF;
|
|
139
|
+
}
|
|
140
|
+
k = 0;
|
|
141
|
+
for (let q = 0; q < n; q++) {
|
|
142
|
+
while (z[k + 1] < q) k++;
|
|
143
|
+
d[q] = (q - v[k]) * (q - v[k]) + f[v[k]];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function edt2d(grid, width, height, f, d, v, z) {
|
|
147
|
+
for (let x = 0; x < width; x++) {
|
|
148
|
+
for (let y = 0; y < height; y++) f[y] = grid[y * width + x];
|
|
149
|
+
edt1d(f, d, v, z, height);
|
|
150
|
+
for (let y = 0; y < height; y++) grid[y * width + x] = d[y];
|
|
151
|
+
}
|
|
152
|
+
for (let y = 0; y < height; y++) {
|
|
153
|
+
for (let x = 0; x < width; x++) f[x] = grid[y * width + x];
|
|
154
|
+
edt1d(f, d, v, z, width);
|
|
155
|
+
for (let x = 0; x < width; x++) grid[y * width + x] = d[x];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function alphaToSdf(alpha, width, height, radius, cutoff) {
|
|
159
|
+
const n = width * height;
|
|
160
|
+
const gridOuter = new Float64Array(n);
|
|
161
|
+
const gridInner = new Float64Array(n);
|
|
162
|
+
const size = Math.max(width, height);
|
|
163
|
+
const f = new Float64Array(size);
|
|
164
|
+
const d = new Float64Array(size);
|
|
165
|
+
const v = new Int32Array(size);
|
|
166
|
+
const z = new Float64Array(size + 1);
|
|
167
|
+
for (let i = 0; i < n; i++) {
|
|
168
|
+
const a = alpha[i] / 255;
|
|
169
|
+
gridOuter[i] = a === 1 ? 0 : a === 0 ? INF : Math.max(0, 0.5 - a) ** 2;
|
|
170
|
+
gridInner[i] = a === 1 ? INF : a === 0 ? 0 : Math.max(0, a - 0.5) ** 2;
|
|
171
|
+
}
|
|
172
|
+
edt2d(gridOuter, width, height, f, d, v, z);
|
|
173
|
+
edt2d(gridInner, width, height, f, d, v, z);
|
|
174
|
+
const out = new Uint8Array(n);
|
|
175
|
+
for (let i = 0; i < n; i++) {
|
|
176
|
+
const dist = Math.sqrt(gridOuter[i]) - Math.sqrt(gridInner[i]);
|
|
177
|
+
const byte = Math.round(255 - 255 * (dist / radius + cutoff));
|
|
178
|
+
out[i] = byte < 0 ? 0 : byte > 255 ? 255 : byte;
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/dynamic/glyphRasterizer.ts
|
|
184
|
+
var BAKE_SIZE = 48;
|
|
185
|
+
var PAD = 8;
|
|
186
|
+
function cssFontString(weight, style, family) {
|
|
187
|
+
return `${style === "italic" ? "italic " : ""}${weight} ${BAKE_SIZE}px ${JSON.stringify(family)}`;
|
|
188
|
+
}
|
|
189
|
+
function createCanvasRasterizer() {
|
|
190
|
+
let canvas;
|
|
191
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
192
|
+
canvas = new OffscreenCanvas(BAKE_SIZE * 3, BAKE_SIZE * 3);
|
|
193
|
+
} else if (typeof document !== "undefined") {
|
|
194
|
+
canvas = document.createElement("canvas");
|
|
195
|
+
canvas.width = BAKE_SIZE * 3;
|
|
196
|
+
canvas.height = BAKE_SIZE * 3;
|
|
197
|
+
} else {
|
|
198
|
+
throw new Error("weasel DynamicGlyphAtlas: no canvas available for glyph rasterization");
|
|
199
|
+
}
|
|
200
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
201
|
+
if (!ctx) throw new Error("weasel DynamicGlyphAtlas: 2D context unavailable");
|
|
202
|
+
function setFont(family, weight, style) {
|
|
203
|
+
ctx.font = cssFontString(weight, style, family);
|
|
204
|
+
ctx.textBaseline = "alphabetic";
|
|
205
|
+
ctx.textAlign = "left";
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
faceMetrics(family, weight, style) {
|
|
209
|
+
setFont(family, weight, style);
|
|
210
|
+
const m = ctx.measureText("Hg");
|
|
211
|
+
return {
|
|
212
|
+
ascent: m.fontBoundingBoxAscent ?? BAKE_SIZE * 0.8,
|
|
213
|
+
descent: m.fontBoundingBoxDescent ?? BAKE_SIZE * 0.2
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
rasterize(family, weight, style, codepoint) {
|
|
217
|
+
setFont(family, weight, style);
|
|
218
|
+
const chStr = String.fromCodePoint(codepoint);
|
|
219
|
+
const m = ctx.measureText(chStr);
|
|
220
|
+
const advance = m.width;
|
|
221
|
+
const inkLeft = Math.ceil(m.actualBoundingBoxLeft ?? 0);
|
|
222
|
+
const inkRight = Math.ceil(m.actualBoundingBoxRight ?? advance);
|
|
223
|
+
const inkAscent = Math.ceil(m.actualBoundingBoxAscent ?? BAKE_SIZE * 0.8);
|
|
224
|
+
const inkDescent = Math.ceil(m.actualBoundingBoxDescent ?? BAKE_SIZE * 0.2);
|
|
225
|
+
const inkW = inkLeft + inkRight;
|
|
226
|
+
const inkH = inkAscent + inkDescent;
|
|
227
|
+
if (inkW <= 0 || inkH <= 0) {
|
|
228
|
+
return { width: 0, height: 0, alpha: new Uint8ClampedArray(0), left: 0, top: 0, advance };
|
|
229
|
+
}
|
|
230
|
+
const w = inkW + 2 * PAD;
|
|
231
|
+
const h = inkH + 2 * PAD;
|
|
232
|
+
if (canvas.width < w || canvas.height < h) {
|
|
233
|
+
canvas.width = Math.max(canvas.width, w);
|
|
234
|
+
canvas.height = Math.max(canvas.height, h);
|
|
235
|
+
setFont(family, weight, style);
|
|
236
|
+
}
|
|
237
|
+
ctx.clearRect(0, 0, w, h);
|
|
238
|
+
ctx.fillStyle = "#fff";
|
|
239
|
+
ctx.fillText(chStr, PAD + inkLeft, PAD + inkAscent);
|
|
240
|
+
const img = ctx.getImageData(0, 0, w, h);
|
|
241
|
+
const alpha = new Uint8ClampedArray(w * h);
|
|
242
|
+
for (let i = 0; i < alpha.length; i++) alpha[i] = img.data[i * 4 + 3];
|
|
243
|
+
return {
|
|
244
|
+
width: w,
|
|
245
|
+
height: h,
|
|
246
|
+
alpha,
|
|
247
|
+
left: -(inkLeft + PAD),
|
|
248
|
+
top: inkAscent + PAD,
|
|
249
|
+
advance
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/dynamic/dynamicAtlas.ts
|
|
256
|
+
var PAGE_SIZE = 1024;
|
|
257
|
+
var MAX_PAGES = 4;
|
|
258
|
+
var SDF_RADIUS = 8;
|
|
259
|
+
var SDF_CUTOFF = 0.5;
|
|
260
|
+
var DEFAULT_BAKE_BUDGET = 16;
|
|
261
|
+
var canvasFamilies = /* @__PURE__ */ new Set();
|
|
262
|
+
var autoEnrolledFamilies = /* @__PURE__ */ new Set();
|
|
263
|
+
var faces = /* @__PURE__ */ new Map();
|
|
264
|
+
var pages = [];
|
|
265
|
+
var packer = new ShelfPacker(PAGE_SIZE, MAX_PAGES);
|
|
266
|
+
var pending = [];
|
|
267
|
+
var flushScheduled = false;
|
|
268
|
+
var budget = DEFAULT_BAKE_BUDGET;
|
|
269
|
+
var subscribers = /* @__PURE__ */ new Set();
|
|
270
|
+
var rasterizer = null;
|
|
271
|
+
function getRasterizer() {
|
|
272
|
+
if (!rasterizer) rasterizer = createCanvasRasterizer();
|
|
273
|
+
return rasterizer;
|
|
274
|
+
}
|
|
275
|
+
function registerCanvasFont(family) {
|
|
276
|
+
canvasFamilies.add(family);
|
|
277
|
+
autoEnrolledFamilies.delete(family);
|
|
278
|
+
}
|
|
279
|
+
function isCanvasFont(family) {
|
|
280
|
+
if (!canvasFamilies.has(family)) return false;
|
|
281
|
+
return !autoEnrolledFamilies.has(family) || getFontFallbackPolicy() === "canvas";
|
|
282
|
+
}
|
|
283
|
+
function autoEnrollCanvasFont(family) {
|
|
284
|
+
if (canvasFamilies.has(family) && !autoEnrolledFamilies.has(family)) return;
|
|
285
|
+
canvasFamilies.add(family);
|
|
286
|
+
autoEnrolledFamilies.add(family);
|
|
287
|
+
}
|
|
288
|
+
function isExplicitCanvasFont(family) {
|
|
289
|
+
return canvasFamilies.has(family) && !autoEnrolledFamilies.has(family);
|
|
290
|
+
}
|
|
291
|
+
function unregisterCanvasFont(family) {
|
|
292
|
+
canvasFamilies.delete(family);
|
|
293
|
+
autoEnrolledFamilies.delete(family);
|
|
294
|
+
for (const key of [...faces.keys()]) {
|
|
295
|
+
if (faces.get(key).family === family) faces.delete(key);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function getDynamicFace(family, weight, style) {
|
|
299
|
+
const key = `${family}|${weight}|${style}`;
|
|
300
|
+
const existing = faces.get(key);
|
|
301
|
+
if (existing) return existing;
|
|
302
|
+
const r = getRasterizer();
|
|
303
|
+
const metrics = r.faceMetrics(family, weight, style);
|
|
304
|
+
const base = Math.round(metrics.ascent);
|
|
305
|
+
const font = {
|
|
306
|
+
info: { face: family, size: BAKE_SIZE },
|
|
307
|
+
common: {
|
|
308
|
+
lineHeight: Math.round(metrics.ascent + metrics.descent),
|
|
309
|
+
base,
|
|
310
|
+
scaleW: PAGE_SIZE,
|
|
311
|
+
scaleH: PAGE_SIZE
|
|
312
|
+
},
|
|
313
|
+
chars: [],
|
|
314
|
+
kernings: [],
|
|
315
|
+
// no kerning in v1 (measured-pair kerning is future work)
|
|
316
|
+
charMap: /* @__PURE__ */ new Map(),
|
|
317
|
+
kerningMap: /* @__PURE__ */ new Map()
|
|
318
|
+
};
|
|
319
|
+
const face = {
|
|
320
|
+
family,
|
|
321
|
+
weight,
|
|
322
|
+
style,
|
|
323
|
+
font,
|
|
324
|
+
requestGlyph(cp) {
|
|
325
|
+
const cached = font.charMap.get(cp);
|
|
326
|
+
if (cached) return cached;
|
|
327
|
+
const raster = r.rasterize(family, weight, style, cp);
|
|
328
|
+
const char = {
|
|
329
|
+
id: cp,
|
|
330
|
+
x: 0,
|
|
331
|
+
y: 0,
|
|
332
|
+
width: 0,
|
|
333
|
+
height: 0,
|
|
334
|
+
xoffset: raster.left,
|
|
335
|
+
yoffset: base - raster.top,
|
|
336
|
+
xadvance: raster.advance,
|
|
337
|
+
page: -1
|
|
338
|
+
};
|
|
339
|
+
font.charMap.set(cp, char);
|
|
340
|
+
font.chars.push(char);
|
|
341
|
+
if (raster.width === 0 || raster.height === 0) {
|
|
342
|
+
char.page = 0;
|
|
343
|
+
return char;
|
|
344
|
+
}
|
|
345
|
+
if (budget > 0) {
|
|
346
|
+
budget--;
|
|
347
|
+
bake(char, raster);
|
|
348
|
+
} else {
|
|
349
|
+
pending.push({ char, raster });
|
|
350
|
+
scheduleFlush();
|
|
351
|
+
}
|
|
352
|
+
return char;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
faces.set(key, face);
|
|
356
|
+
return face;
|
|
357
|
+
}
|
|
358
|
+
function bake(char, raster) {
|
|
359
|
+
const spot = packer.alloc(raster.width, raster.height);
|
|
360
|
+
if (!spot) return;
|
|
361
|
+
const sdf = alphaToSdf(raster.alpha, raster.width, raster.height, SDF_RADIUS, SDF_CUTOFF);
|
|
362
|
+
while (pages.length <= spot.page) {
|
|
363
|
+
pages.push({ data: new Uint8Array(PAGE_SIZE * PAGE_SIZE), version: 0, patches: [] });
|
|
364
|
+
}
|
|
365
|
+
const page = pages[spot.page];
|
|
366
|
+
for (let row = 0; row < raster.height; row++) {
|
|
367
|
+
page.data.set(
|
|
368
|
+
sdf.subarray(row * raster.width, (row + 1) * raster.width),
|
|
369
|
+
(spot.y + row) * PAGE_SIZE + spot.x
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
page.version++;
|
|
373
|
+
page.patches.push({ seq: page.version, x: spot.x, y: spot.y, w: raster.width, h: raster.height });
|
|
374
|
+
char.x = spot.x;
|
|
375
|
+
char.y = spot.y;
|
|
376
|
+
char.width = raster.width;
|
|
377
|
+
char.height = raster.height;
|
|
378
|
+
char.page = spot.page;
|
|
379
|
+
}
|
|
380
|
+
function scheduleFlush() {
|
|
381
|
+
if (flushScheduled) return;
|
|
382
|
+
flushScheduled = true;
|
|
383
|
+
setTimeout(flushPending, 0);
|
|
384
|
+
}
|
|
385
|
+
function flushPending() {
|
|
386
|
+
flushScheduled = false;
|
|
387
|
+
let n = 0;
|
|
388
|
+
while (pending.length > 0 && n < DEFAULT_BAKE_BUDGET) {
|
|
389
|
+
const job = pending.shift();
|
|
390
|
+
if (job.char.page !== -1) continue;
|
|
391
|
+
bake(job.char, job.raster);
|
|
392
|
+
n++;
|
|
393
|
+
}
|
|
394
|
+
if (pending.length > 0) scheduleFlush();
|
|
395
|
+
for (const cb of subscribers) cb();
|
|
396
|
+
}
|
|
397
|
+
function subscribeGlyphReady(cb) {
|
|
398
|
+
subscribers.add(cb);
|
|
399
|
+
return () => {
|
|
400
|
+
subscribers.delete(cb);
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function resetBakeBudget(n = DEFAULT_BAKE_BUDGET) {
|
|
404
|
+
budget = n;
|
|
405
|
+
}
|
|
406
|
+
function dynamicPageTextureId(page) {
|
|
407
|
+
return `weasel-dyn-sdf-page-${page}`;
|
|
408
|
+
}
|
|
409
|
+
var uploadedVersions = /* @__PURE__ */ new WeakMap();
|
|
410
|
+
function syncDynamicPageTexture(cache, pageIndex) {
|
|
411
|
+
const page = pages[pageIndex];
|
|
412
|
+
if (!page) return false;
|
|
413
|
+
const id = dynamicPageTextureId(pageIndex);
|
|
414
|
+
let seen = uploadedVersions.get(cache);
|
|
415
|
+
if (!seen) {
|
|
416
|
+
seen = /* @__PURE__ */ new Map();
|
|
417
|
+
uploadedVersions.set(cache, seen);
|
|
418
|
+
}
|
|
419
|
+
if (!cache.has(id)) {
|
|
420
|
+
cache.uploadR8(id, PAGE_SIZE, PAGE_SIZE, page.data);
|
|
421
|
+
seen.set(pageIndex, page.version);
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
const last = seen.get(pageIndex) ?? 0;
|
|
425
|
+
if (last >= page.version) return true;
|
|
426
|
+
for (const patch of page.patches) {
|
|
427
|
+
if (patch.seq <= last) continue;
|
|
428
|
+
const tight = new Uint8Array(patch.w * patch.h);
|
|
429
|
+
for (let row = 0; row < patch.h; row++) {
|
|
430
|
+
const src = (patch.y + row) * PAGE_SIZE + patch.x;
|
|
431
|
+
tight.set(page.data.subarray(src, src + patch.w), row * patch.w);
|
|
432
|
+
}
|
|
433
|
+
cache.subImageR8(id, patch.x, patch.y, patch.w, patch.h, tight);
|
|
434
|
+
}
|
|
435
|
+
seen.set(pageIndex, page.version);
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
function __setGlyphRasterizerForTests(r) {
|
|
439
|
+
rasterizer = r;
|
|
440
|
+
}
|
|
441
|
+
function _getPagesForTests() {
|
|
442
|
+
return pages;
|
|
443
|
+
}
|
|
444
|
+
function _resetDynamicFontsForTests() {
|
|
445
|
+
canvasFamilies.clear();
|
|
446
|
+
autoEnrolledFamilies.clear();
|
|
447
|
+
faces = /* @__PURE__ */ new Map();
|
|
448
|
+
pages = [];
|
|
449
|
+
packer = new ShelfPacker(PAGE_SIZE, MAX_PAGES);
|
|
450
|
+
pending = [];
|
|
451
|
+
flushScheduled = false;
|
|
452
|
+
budget = DEFAULT_BAKE_BUDGET;
|
|
453
|
+
subscribers.clear();
|
|
454
|
+
rasterizer = null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/registerFont.ts
|
|
458
|
+
var registry = /* @__PURE__ */ new Map();
|
|
459
|
+
function variantKey(weight, style) {
|
|
460
|
+
return `${weight}|${style}`;
|
|
461
|
+
}
|
|
462
|
+
function normalizeVariant(v) {
|
|
463
|
+
return {
|
|
464
|
+
weight: v.weight ?? 400,
|
|
465
|
+
style: v.style ?? "normal"
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function _resetFontRegistryForTests() {
|
|
469
|
+
registry = /* @__PURE__ */ new Map();
|
|
470
|
+
_clearFallbackWarnings();
|
|
471
|
+
}
|
|
472
|
+
function getFont(family, weight = 400, style = "normal") {
|
|
473
|
+
return registry.get(family)?.get(variantKey(weight, style)) ?? null;
|
|
474
|
+
}
|
|
475
|
+
function listFonts() {
|
|
476
|
+
const out = [];
|
|
477
|
+
for (const [family, variantMap] of registry) {
|
|
478
|
+
const variants = [...variantMap.keys()].map((key) => {
|
|
479
|
+
const [w, s] = key.split("|");
|
|
480
|
+
return { weight: Number(w), style: s };
|
|
481
|
+
}).sort((a, b) => a.weight - b.weight || a.style.localeCompare(b.style));
|
|
482
|
+
out.push({ family, variants });
|
|
483
|
+
}
|
|
484
|
+
return out;
|
|
485
|
+
}
|
|
486
|
+
async function registerFont(family, variant, metricsUrl, atlasUrl) {
|
|
487
|
+
const { weight, style } = normalizeVariant(variant);
|
|
488
|
+
const key = variantKey(weight, style);
|
|
489
|
+
if (registry.get(family)?.has(key)) return;
|
|
490
|
+
try {
|
|
491
|
+
const [metricsRes, atlasRes] = await Promise.all([
|
|
492
|
+
fetch(metricsUrl),
|
|
493
|
+
fetch(atlasUrl)
|
|
494
|
+
]);
|
|
495
|
+
if (!metricsRes.ok) {
|
|
496
|
+
throw new Error(`HTTP ${metricsRes.status} fetching metrics from ${metricsUrl}`);
|
|
497
|
+
}
|
|
498
|
+
if (!atlasRes.ok) {
|
|
499
|
+
throw new Error(`HTTP ${atlasRes.status} fetching atlas from ${atlasUrl}`);
|
|
500
|
+
}
|
|
501
|
+
const [rawJson, blob] = await Promise.all([
|
|
502
|
+
metricsRes.json(),
|
|
503
|
+
atlasRes.blob()
|
|
504
|
+
]);
|
|
505
|
+
const font = parseBmFont(rawJson);
|
|
506
|
+
const bitmap = await createImageBitmap(blob);
|
|
507
|
+
let familyMap = registry.get(family);
|
|
508
|
+
if (!familyMap) {
|
|
509
|
+
familyMap = /* @__PURE__ */ new Map();
|
|
510
|
+
registry.set(family, familyMap);
|
|
511
|
+
}
|
|
512
|
+
familyMap.set(key, { font, bitmap });
|
|
513
|
+
} catch (err) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`weasel registerFont("${family}" ${weight}/${style}): ${err instanceof Error ? err.message : String(err)}`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function ensureFontTexture(family, weight, style, textureCache) {
|
|
520
|
+
const entry = getFont(family, weight, style);
|
|
521
|
+
if (!entry) return false;
|
|
522
|
+
textureCache.upload(textureCacheKey(family, weight, style), entry.bitmap);
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
function textureCacheKey(family, weight, style) {
|
|
526
|
+
return `${family}|${weight}|${style}`;
|
|
527
|
+
}
|
|
528
|
+
function markAllFontsNotUploaded() {
|
|
529
|
+
}
|
|
530
|
+
function missResolveResult(family, weight, style, suppressWarn = false) {
|
|
531
|
+
if (isExplicitCanvasFont(family)) {
|
|
532
|
+
return {
|
|
533
|
+
entry: null,
|
|
534
|
+
dynamicFace: getDynamicFace(family, weight, style),
|
|
535
|
+
resolved: { family, weight, style },
|
|
536
|
+
synthetic: { bold: false, italic: false },
|
|
537
|
+
source: "canvas"
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
const policy2 = getFontFallbackPolicy();
|
|
541
|
+
if (policy2 === "canvas") {
|
|
542
|
+
autoEnrollCanvasFont(family);
|
|
543
|
+
return {
|
|
544
|
+
entry: null,
|
|
545
|
+
dynamicFace: getDynamicFace(family, weight, style),
|
|
546
|
+
resolved: { family, weight, style },
|
|
547
|
+
synthetic: { bold: false, italic: false },
|
|
548
|
+
source: "canvas"
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (policy2 === "substitute") {
|
|
552
|
+
const fallback = getDefaultFontFamily() ?? firstRegisteredFamily();
|
|
553
|
+
if (fallback !== null && fallback !== family) {
|
|
554
|
+
if (registry.has(fallback) || isCanvasFont(fallback)) {
|
|
555
|
+
const result = resolveFontVariantInternal(fallback, weight, style, true);
|
|
556
|
+
if (result.entry !== null || result.dynamicFace !== void 0) {
|
|
557
|
+
if (!suppressWarn) warnMissingFamilyOnce(family, weight, style, fallback);
|
|
558
|
+
return { ...result, substituted: { requested: family, resolved: fallback } };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (!suppressWarn) warnUnusableDefaultOnce(family, weight, style, fallback);
|
|
562
|
+
} else if (fallback === family && !suppressWarn) {
|
|
563
|
+
if (registry.has(family)) {
|
|
564
|
+
warnSelfUnusableDefaultOnce(family, weight, style);
|
|
565
|
+
} else {
|
|
566
|
+
warnUnregisteredDefaultOnce(family, weight, style);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
entry: null,
|
|
572
|
+
resolved: { family, weight, style },
|
|
573
|
+
synthetic: { bold: false, italic: false },
|
|
574
|
+
source: "atlas"
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function firstRegisteredFamily() {
|
|
578
|
+
for (const family of registry.keys()) return family;
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
function warnMissingFamilyOnce(family, weight, style, resolved) {
|
|
582
|
+
if (!claimFallbackWarning(`substituted|${family}|${weight}|${style}`)) return;
|
|
583
|
+
const cause = registry.has(family) ? `has no variant matching ${weight}/${style}, and none of its registered variants are close enough for the within-family chain to substitute \u2014 rendering with "${resolved}" instead. Bake that variant with registerFont("${family}", { weight: ${weight}, style: '${style}' }, \u2026)` : `is not registered \u2014 rendering with "${resolved}" instead. Call registerFont("${family}", \u2026)`;
|
|
584
|
+
console.warn(
|
|
585
|
+
`weasel: font family "${family}" (${weight}/${style}) ${cause}. Advance widths will differ from the requested font. Use setFontFallbackPolicy('none') to make this a hard miss instead.`
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
function warnUnusableDefaultOnce(family, weight, style, fallback) {
|
|
589
|
+
if (!claimFallbackWarning(`unusable-default|${family}|${weight}|${style}`)) return;
|
|
590
|
+
const origin = getDefaultFontFamily() === fallback ? "set via setDefaultFontFamily" : "the first registered family, since setDefaultFontFamily was never called";
|
|
591
|
+
const gap = registry.has(fallback) || isCanvasFont(fallback) ? `has no variant that can serve ${weight}/${style}` : "is not registered either";
|
|
592
|
+
console.warn(
|
|
593
|
+
`weasel: font family "${family}" (${weight}/${style}) is not available, and the fallback family "${fallback}" (${origin}) ${gap} \u2014 this text will not render at all. Bake that variant with registerFont("${fallback}", { weight: ${weight}, style: '${style}' }, \u2026), or point setDefaultFontFamily() at a family that covers it.`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
function warnSelfUnusableDefaultOnce(family, weight, style) {
|
|
597
|
+
if (!claimFallbackWarning(`self-unusable-default|${family}|${weight}|${style}`)) return;
|
|
598
|
+
const origin = getDefaultFontFamily() === family ? "set via setDefaultFontFamily" : "the first registered family, since setDefaultFontFamily was never called";
|
|
599
|
+
console.warn(
|
|
600
|
+
`weasel: font family "${family}" (${weight}/${style}) has no variant that can serve this request, and "${family}" is also the fallback family (${origin}) \u2014 there is nothing left to fall back to, so this text will not render at all. Bake that variant with registerFont("${family}", { weight: ${weight}, style: '${style}' }, \u2026), or point setDefaultFontFamily() at a different family.`
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
function warnUnregisteredDefaultOnce(family, weight, style) {
|
|
604
|
+
if (!claimFallbackWarning(`unregistered-default|${family}|${weight}|${style}`)) return;
|
|
605
|
+
console.warn(
|
|
606
|
+
`weasel: font family "${family}" (${weight}/${style}) was never registered, and it is also the fallback family \u2014 setDefaultFontFamily("${family}") names a family with no registered variants at all, so there is nothing left to fall back to and this text will not render at all. Call registerFont("${family}", { weight: ${weight}, style: '${style}' }, \u2026), or point setDefaultFontFamily() at a family you have registered.`
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
function weightBucket(w) {
|
|
610
|
+
return w >= 600 ? "bold" : "regular";
|
|
611
|
+
}
|
|
612
|
+
function resolveFontVariant(family, weight, style) {
|
|
613
|
+
return resolveFontVariantInternal(family, weight, style, false);
|
|
614
|
+
}
|
|
615
|
+
function resolveGlyphFallback(family, weight, style) {
|
|
616
|
+
if (getFontFallbackPolicy() === "none") return null;
|
|
617
|
+
try {
|
|
618
|
+
return {
|
|
619
|
+
entry: null,
|
|
620
|
+
dynamicFace: getDynamicFace(family, weight, style),
|
|
621
|
+
resolved: { family, weight, style },
|
|
622
|
+
// The dynamic tier rasterizes the real weight and style, so there is
|
|
623
|
+
// nothing for the shader to fake.
|
|
624
|
+
synthetic: { bold: false, italic: false },
|
|
625
|
+
source: "canvas"
|
|
626
|
+
};
|
|
627
|
+
} catch {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function resolveFontVariantInternal(family, weight, style, suppressWarn) {
|
|
632
|
+
const familyMap = registry.get(family);
|
|
633
|
+
if (!familyMap || familyMap.size === 0) {
|
|
634
|
+
return missResolveResult(family, weight, style, suppressWarn);
|
|
635
|
+
}
|
|
636
|
+
const exact = familyMap.get(variantKey(weight, style));
|
|
637
|
+
if (exact) {
|
|
638
|
+
return {
|
|
639
|
+
entry: exact,
|
|
640
|
+
resolved: { family, weight, style },
|
|
641
|
+
synthetic: { bold: false, italic: false },
|
|
642
|
+
source: "atlas"
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
const requestedBucket = weightBucket(weight);
|
|
646
|
+
let bestSameStyle = null;
|
|
647
|
+
for (const [key, entry] of familyMap) {
|
|
648
|
+
const [wStr, s] = key.split("|");
|
|
649
|
+
const w = Number(wStr);
|
|
650
|
+
if (s !== style) continue;
|
|
651
|
+
if (weightBucket(w) !== requestedBucket) continue;
|
|
652
|
+
const distance = Math.abs(w - weight);
|
|
653
|
+
if (bestSameStyle === null || distance < bestSameStyle.distance || distance === bestSameStyle.distance && w > bestSameStyle.weight) {
|
|
654
|
+
bestSameStyle = { entry, weight: w, distance };
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (bestSameStyle) {
|
|
658
|
+
return {
|
|
659
|
+
entry: bestSameStyle.entry,
|
|
660
|
+
resolved: { family, weight: bestSameStyle.weight, style },
|
|
661
|
+
synthetic: { bold: false, italic: false },
|
|
662
|
+
source: "atlas"
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const sameStyleRegular = familyMap.get(variantKey(400, style));
|
|
666
|
+
if (sameStyleRegular) {
|
|
667
|
+
return {
|
|
668
|
+
entry: sameStyleRegular,
|
|
669
|
+
resolved: { family, weight: 400, style },
|
|
670
|
+
synthetic: {
|
|
671
|
+
bold: weight >= 600,
|
|
672
|
+
italic: false
|
|
673
|
+
},
|
|
674
|
+
source: "atlas"
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
const sameWeightNormal = familyMap.get(variantKey(weight, "normal"));
|
|
678
|
+
if (sameWeightNormal) {
|
|
679
|
+
return {
|
|
680
|
+
entry: sameWeightNormal,
|
|
681
|
+
resolved: { family, weight, style: "normal" },
|
|
682
|
+
synthetic: {
|
|
683
|
+
bold: false,
|
|
684
|
+
italic: style === "italic"
|
|
685
|
+
},
|
|
686
|
+
source: "atlas"
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
let bestNormal = null;
|
|
690
|
+
for (const [key, entry] of familyMap) {
|
|
691
|
+
const [wStr, s] = key.split("|");
|
|
692
|
+
const w = Number(wStr);
|
|
693
|
+
if (s !== "normal") continue;
|
|
694
|
+
if (weightBucket(w) !== requestedBucket) continue;
|
|
695
|
+
const distance = Math.abs(w - weight);
|
|
696
|
+
if (bestNormal === null || distance < bestNormal.distance || distance === bestNormal.distance && w > bestNormal.weight) {
|
|
697
|
+
bestNormal = { entry, weight: w, distance };
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (bestNormal) {
|
|
701
|
+
return {
|
|
702
|
+
entry: bestNormal.entry,
|
|
703
|
+
resolved: { family, weight: bestNormal.weight, style: "normal" },
|
|
704
|
+
synthetic: {
|
|
705
|
+
bold: false,
|
|
706
|
+
italic: style === "italic"
|
|
707
|
+
},
|
|
708
|
+
source: "atlas"
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const regular = familyMap.get(variantKey(400, "normal"));
|
|
712
|
+
if (regular) {
|
|
713
|
+
return {
|
|
714
|
+
entry: regular,
|
|
715
|
+
resolved: { family, weight: 400, style: "normal" },
|
|
716
|
+
synthetic: {
|
|
717
|
+
bold: weight >= 600,
|
|
718
|
+
italic: style === "italic"
|
|
719
|
+
},
|
|
720
|
+
source: "atlas"
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
return missResolveResult(family, weight, style, suppressWarn);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/textSdf.ts
|
|
727
|
+
var TEXT_VERT_SRC = (
|
|
728
|
+
/* glsl */
|
|
729
|
+
`#version 300 es
|
|
730
|
+
in vec2 a_position;
|
|
731
|
+
in vec2 a_uv;
|
|
732
|
+
in float a_baselineY;
|
|
733
|
+
uniform mat3 u_proj;
|
|
734
|
+
uniform mat3 u_model;
|
|
735
|
+
uniform float u_synthItalic;
|
|
736
|
+
out vec2 v_uv;
|
|
737
|
+
void main() {
|
|
738
|
+
// Synthetic italic: shift x by (a_baselineY - a_position.y) * tan(angle).
|
|
739
|
+
// Above-baseline vertices (lower y in screen coords) lean further right.
|
|
740
|
+
vec2 skewed = vec2(
|
|
741
|
+
a_position.x + (a_baselineY - a_position.y) * tan(u_synthItalic),
|
|
742
|
+
a_position.y
|
|
743
|
+
);
|
|
744
|
+
vec3 screen = u_model * vec3(skewed, 1.0);
|
|
745
|
+
vec3 clip = u_proj * vec3(screen.xy, 1.0);
|
|
746
|
+
gl_Position = vec4(clip.xy, 0.0, 1.0);
|
|
747
|
+
v_uv = a_uv;
|
|
748
|
+
}
|
|
749
|
+
`
|
|
750
|
+
);
|
|
751
|
+
var TEXT_FRAG_SRC = (
|
|
752
|
+
/* glsl */
|
|
753
|
+
`#version 300 es
|
|
754
|
+
precision highp float;
|
|
755
|
+
in vec2 v_uv;
|
|
756
|
+
uniform sampler2D u_atlas;
|
|
757
|
+
uniform vec4 u_color;
|
|
758
|
+
uniform float u_alpha;
|
|
759
|
+
uniform float u_synthBold;
|
|
760
|
+
uniform mat4 u_colorMatrix;
|
|
761
|
+
uniform vec4 u_colorBias;
|
|
762
|
+
out vec4 outColor;
|
|
763
|
+
|
|
764
|
+
float median(float r, float g, float b) {
|
|
765
|
+
return max(min(r, g), min(max(r, g), b));
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
void main() {
|
|
769
|
+
vec3 sdf = texture(u_atlas, v_uv).rgb;
|
|
770
|
+
float sdfVal = median(sdf.r, sdf.g, sdf.b);
|
|
771
|
+
// Screen-space AA band \u2014 see the file header. Half of fwidth spans ~1px.
|
|
772
|
+
float aaW = max(0.5 * fwidth(sdfVal), 0.0005);
|
|
773
|
+
// u_synthBold shifts the SDF threshold to thicken strokes when the
|
|
774
|
+
// resolver fell back from a missing bold variant to the regular atlas.
|
|
775
|
+
float threshold = 0.5 - u_synthBold;
|
|
776
|
+
float msdfAlpha = smoothstep(threshold - aaW, threshold + aaW, sdfVal);
|
|
777
|
+
vec4 src = vec4(u_color.rgb, u_color.a);
|
|
778
|
+
vec4 mapped = clamp(u_colorMatrix * src + u_colorBias, 0.0, 1.0);
|
|
779
|
+
float a = mapped.a * msdfAlpha * u_alpha;
|
|
780
|
+
outColor = vec4(mapped.rgb * a, a);
|
|
781
|
+
}
|
|
782
|
+
`
|
|
783
|
+
);
|
|
784
|
+
var TEXT_FRAG_R8_SRC = (
|
|
785
|
+
/* glsl */
|
|
786
|
+
`#version 300 es
|
|
787
|
+
precision highp float;
|
|
788
|
+
in vec2 v_uv;
|
|
789
|
+
uniform sampler2D u_atlas;
|
|
790
|
+
uniform vec4 u_color;
|
|
791
|
+
uniform float u_alpha;
|
|
792
|
+
uniform float u_synthBold;
|
|
793
|
+
uniform mat4 u_colorMatrix;
|
|
794
|
+
uniform vec4 u_colorBias;
|
|
795
|
+
out vec4 outColor;
|
|
796
|
+
|
|
797
|
+
void main() {
|
|
798
|
+
float sdfVal = texture(u_atlas, v_uv).r;
|
|
799
|
+
// Screen-space AA band \u2014 see the file header. Half of fwidth spans ~1px.
|
|
800
|
+
float aaW = max(0.5 * fwidth(sdfVal), 0.0005);
|
|
801
|
+
float threshold = 0.5 - u_synthBold;
|
|
802
|
+
float sdfAlpha = smoothstep(threshold - aaW, threshold + aaW, sdfVal);
|
|
803
|
+
vec4 src = vec4(u_color.rgb, u_color.a);
|
|
804
|
+
vec4 mapped = clamp(u_colorMatrix * src + u_colorBias, 0.0, 1.0);
|
|
805
|
+
float a = mapped.a * sdfAlpha * u_alpha;
|
|
806
|
+
outColor = vec4(mapped.rgb * a, a);
|
|
807
|
+
}
|
|
808
|
+
`
|
|
809
|
+
);
|
|
810
|
+
var TEXT_SDF_UNIFORMS = [
|
|
811
|
+
"u_proj",
|
|
812
|
+
"u_model",
|
|
813
|
+
"u_atlas",
|
|
814
|
+
"u_color",
|
|
815
|
+
"u_alpha",
|
|
816
|
+
"u_synthBold",
|
|
817
|
+
"u_synthItalic",
|
|
818
|
+
"u_colorMatrix",
|
|
819
|
+
"u_colorBias"
|
|
820
|
+
];
|
|
821
|
+
var TEXT_SDF_ATTRIBUTES = ["a_position", "a_uv", "a_baselineY"];
|
|
822
|
+
|
|
823
|
+
export { DEFAULT_BAKE_BUDGET, FIXTURE_FONT, TEXT_FRAG_R8_SRC, TEXT_FRAG_SRC, TEXT_SDF_ATTRIBUTES, TEXT_SDF_UNIFORMS, TEXT_VERT_SRC, __setGlyphRasterizerForTests, _getPagesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontRegistryForTests, dynamicPageTextureId, ensureFontTexture, getDefaultFontFamily, getFont, getFontFallbackPolicy, isCanvasFont, listFonts, markAllFontsNotUploaded, parseBmFont, registerCanvasFont, registerFont, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, subscribeGlyphReady, syncDynamicPageTexture, textureCacheKey, unregisterCanvasFont };
|
|
824
|
+
//# sourceMappingURL=index.js.map
|
|
825
|
+
//# sourceMappingURL=index.js.map
|