@weasel-js/font 1.9.3 → 1.10.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.
@@ -1,4 +1,4 @@
1
- import { createReflectable } from '@weasel-js/registry';
1
+ import { createEmitter, createReflectable } from '@weasel-js/registry';
2
2
 
3
3
  // src/faceMetrics.ts
4
4
  var finite = (n) => typeof n === "number" && Number.isFinite(n);
@@ -97,6 +97,19 @@ function parseBmFont(raw) {
97
97
  if (!Array.isArray(r.chars)) throw new Error("parseBmFont: chars must be an array");
98
98
  const info = r.info;
99
99
  const common = r.common;
100
+ const positive = [
101
+ ["info.size", info.size],
102
+ ["common.scaleW", common.scaleW],
103
+ ["common.scaleH", common.scaleH]
104
+ ];
105
+ for (const [name, value] of positive) {
106
+ if (typeof value !== "number" || !(value > 0) || !Number.isFinite(value)) {
107
+ throw new Error(`parseBmFont: ${name} must be a positive number`);
108
+ }
109
+ }
110
+ if (typeof common.base !== "number" || !Number.isFinite(common.base)) {
111
+ throw new Error("parseBmFont: common.base must be a number");
112
+ }
100
113
  const chars = r.chars;
101
114
  const kernings = Array.isArray(r.kernings) ? r.kernings : [];
102
115
  const charMap = /* @__PURE__ */ new Map();
@@ -123,25 +136,29 @@ function parseBmFont(raw) {
123
136
  ...typeof range === "number" && range > 0 ? { distanceRange: range } : {}
124
137
  };
125
138
  }
126
-
127
- // src/glyphReady.ts
128
- var subscribers = /* @__PURE__ */ new Set();
139
+ var ready = createEmitter();
129
140
  var generation = 0;
130
141
  function glyphGeneration() {
131
142
  return generation;
132
143
  }
133
144
  function subscribeGlyphReady(cb) {
134
- subscribers.add(cb);
135
- return () => {
136
- subscribers.delete(cb);
137
- };
145
+ return ready.subscribe(cb);
146
+ }
147
+ var faces = 0;
148
+ function faceGeneration() {
149
+ return faces;
138
150
  }
139
151
  function notifyGlyphReady() {
140
152
  generation++;
141
- for (const cb of subscribers) cb();
153
+ faces++;
154
+ ready.emit();
155
+ }
156
+ function notifyGlyphBaked() {
157
+ generation++;
158
+ ready.emit();
142
159
  }
143
160
  function _clearGlyphReadySubscribers() {
144
- subscribers.clear();
161
+ ready = createEmitter();
145
162
  }
146
163
 
147
164
  // src/fallback.ts
@@ -169,6 +186,9 @@ function claimFallbackWarning(key) {
169
186
  warnedFallbacks.add(key);
170
187
  return true;
171
188
  }
189
+ function fallbackWarningClaimed(key) {
190
+ return warnedFallbacks.has(key);
191
+ }
172
192
  function _clearFallbackWarnings() {
173
193
  warnedFallbacks.clear();
174
194
  }
@@ -178,26 +198,115 @@ function _resetFallbackForTests() {
178
198
  warnedFallbacks.clear();
179
199
  }
180
200
 
181
- // src/cssFamily.ts
182
- function cssFamilyName(family) {
183
- if (/[,"']/.test(family)) return family;
184
- return GENERIC_FAMILIES.has(family.trim().toLowerCase()) ? family.trim() : JSON.stringify(family);
201
+ // src/dynamic/dynamicPages.ts
202
+ var PAGE_SIZE = 1024;
203
+ var MAX_PAGES = 4;
204
+ var PageSet = class {
205
+ constructor(format) {
206
+ this.format = format;
207
+ }
208
+ format;
209
+ pages = [];
210
+ // Per-GL-cache upload progress: last page version each cache has seen.
211
+ // WeakMap-keyed so disposed renderers don't pin anything.
212
+ uploaded = /* @__PURE__ */ new WeakMap();
213
+ textureId(page) {
214
+ return `${this.format.idPrefix}${page}`;
215
+ }
216
+ /** Copy a `w`×`h` block of texels into page `pageIndex` at `(x, y)`. */
217
+ blit(pageIndex, x, y, w, h, bytes) {
218
+ const n = this.format.channels;
219
+ while (this.pages.length <= pageIndex) {
220
+ this.pages.push({ data: new Uint8Array(PAGE_SIZE * PAGE_SIZE * n), version: 0, patches: [] });
221
+ }
222
+ const page = this.pages[pageIndex];
223
+ for (let row = 0; row < h; row++) {
224
+ page.data.set(bytes.subarray(row * w * n, (row + 1) * w * n), ((y + row) * PAGE_SIZE + x) * n);
225
+ }
226
+ page.version++;
227
+ page.patches.push({ seq: page.version, x, y, w, h });
228
+ }
229
+ /** Zero every page. One whole-page patch stands in for all the patches before it. */
230
+ clear() {
231
+ for (const page of this.pages) {
232
+ page.data.fill(0);
233
+ page.version++;
234
+ page.patches = [{ seq: page.version, x: 0, y: 0, w: PAGE_SIZE, h: PAGE_SIZE }];
235
+ }
236
+ }
237
+ /** Bring `cache`'s copy of page `pageIndex` up to date: a full upload the
238
+ * first time, patches after. Returns false if the page doesn't exist yet. */
239
+ sync(cache, pageIndex) {
240
+ const page = this.pages[pageIndex];
241
+ if (!page) return false;
242
+ const n = this.format.channels;
243
+ const id = this.textureId(pageIndex);
244
+ let seen = this.uploaded.get(cache);
245
+ if (!seen) {
246
+ seen = /* @__PURE__ */ new Map();
247
+ this.uploaded.set(cache, seen);
248
+ }
249
+ if (!cache.has(id)) {
250
+ this.format.upload(cache, id, page.data);
251
+ seen.set(pageIndex, page.version);
252
+ return true;
253
+ }
254
+ const last = seen.get(pageIndex) ?? 0;
255
+ if (last >= page.version) return true;
256
+ for (const patch of page.patches) {
257
+ if (patch.seq <= last) continue;
258
+ const tight = new Uint8Array(patch.w * patch.h * n);
259
+ for (let row = 0; row < patch.h; row++) {
260
+ const src = ((patch.y + row) * PAGE_SIZE + patch.x) * n;
261
+ tight.set(page.data.subarray(src, src + patch.w * n), row * patch.w * n);
262
+ }
263
+ this.format.patch(cache, id, patch.x, patch.y, patch.w, patch.h, tight);
264
+ }
265
+ seen.set(pageIndex, page.version);
266
+ return true;
267
+ }
268
+ };
269
+ var fields = new PageSet({
270
+ channels: 1,
271
+ idPrefix: "weasel-dyn-sdf-page-",
272
+ upload: (cache, id, data) => cache.uploadR8(id, PAGE_SIZE, PAGE_SIZE, data),
273
+ patch: (cache, id, x, y, w, h, data) => cache.subImageR8(id, x, y, w, h, data)
274
+ });
275
+ var colors = new PageSet({
276
+ channels: 4,
277
+ idPrefix: "weasel-dyn-color-page-",
278
+ upload: (cache, id, data) => cache.uploadRGBA(id, PAGE_SIZE, PAGE_SIZE, data),
279
+ patch: (cache, id, x, y, w, h, data) => cache.subImageRGBA(id, x, y, w, h, data)
280
+ });
281
+ function blitToPage(pageIndex, x, y, w, h, bytes) {
282
+ fields.blit(pageIndex, x, y, w, h, bytes);
283
+ }
284
+ function blitToColorPage(pageIndex, x, y, w, h, rgba) {
285
+ colors.blit(pageIndex, x, y, w, h, rgba);
286
+ }
287
+ function clearPages() {
288
+ fields.clear();
289
+ colors.clear();
290
+ }
291
+ function dynamicPageTextureId(page) {
292
+ return fields.textureId(page);
293
+ }
294
+ function colorPageTextureId(page) {
295
+ return colors.textureId(page);
296
+ }
297
+ function syncDynamicPageTexture(cache, pageIndex) {
298
+ return fields.sync(cache, pageIndex);
299
+ }
300
+ function syncColorPageTexture(cache, pageIndex) {
301
+ return colors.sync(cache, pageIndex);
302
+ }
303
+ function _getPagesForTests() {
304
+ return fields.pages;
305
+ }
306
+ function _resetPagesForTests() {
307
+ fields.pages = [];
308
+ colors.pages = [];
185
309
  }
186
- var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
187
- "serif",
188
- "sans-serif",
189
- "monospace",
190
- "cursive",
191
- "fantasy",
192
- "system-ui",
193
- "ui-serif",
194
- "ui-sans-serif",
195
- "ui-monospace",
196
- "ui-rounded",
197
- "math",
198
- "emoji",
199
- "fangsong"
200
- ]);
201
310
 
202
311
  // src/dynamic/shelfPack.ts
203
312
  var ShelfPacker = class {
@@ -208,13 +317,16 @@ var ShelfPacker = class {
208
317
  pageSize;
209
318
  maxPages;
210
319
  pages = [];
211
- warned = false;
212
320
  get pageCount() {
213
321
  return this.pages.length;
214
322
  }
215
- /** Allocate a w×h rect. Returns null (warning once) when capacity is out. */
323
+ /** Whether an empty page could hold a w×h rect. */
324
+ fits(w, h) {
325
+ return w <= this.pageSize && h <= this.pageSize;
326
+ }
327
+ /** Allocate a w×h rect. Returns null when capacity is out. */
216
328
  alloc(w, h) {
217
- if (w > this.pageSize || h > this.pageSize) return this.fail();
329
+ if (!this.fits(w, h)) return null;
218
330
  for (let p = 0; p < this.pages.length; p++) {
219
331
  const spot = this.allocInPage(this.pages[p], w, h);
220
332
  if (spot) return { page: p, ...spot };
@@ -225,7 +337,7 @@ var ShelfPacker = class {
225
337
  const spot = this.allocInPage(page, w, h);
226
338
  if (spot) return { page: this.pages.length - 1, ...spot };
227
339
  }
228
- return this.fail();
340
+ return null;
229
341
  }
230
342
  allocInPage(page, w, h) {
231
343
  for (const shelf of page.shelves) {
@@ -243,15 +355,6 @@ var ShelfPacker = class {
243
355
  }
244
356
  return null;
245
357
  }
246
- fail() {
247
- if (!this.warned) {
248
- this.warned = true;
249
- console.warn(
250
- `weasel DynamicGlyphAtlas: glyph pages full (${this.maxPages} \xD7 ${this.pageSize}\xB2); further dynamic glyphs will not render.`
251
- );
252
- }
253
- return null;
254
- }
255
358
  };
256
359
 
257
360
  // src/dynamic/distanceTransform.ts
@@ -315,9 +418,179 @@ function alphaToSdf(alpha, width, height, radius, cutoff) {
315
418
  return out;
316
419
  }
317
420
 
421
+ // src/dynamic/bakeQueue.ts
422
+ var SDF_RADIUS = 8;
423
+ var SDF_CUTOFF = 0.5;
424
+ var DEFAULT_BAKE_BUDGET = 16;
425
+ var newPacker = () => new ShelfPacker(PAGE_SIZE, MAX_PAGES);
426
+ var fieldPacker = newPacker();
427
+ var colorPacker = newPacker();
428
+ var pending = [];
429
+ var flushScheduled = false;
430
+ var budget = DEFAULT_BAKE_BUDGET;
431
+ var asked = 0;
432
+ var settled = true;
433
+ var reclaimScheduled = false;
434
+ var forgetGlyphs = () => {
435
+ };
436
+ var warned = /* @__PURE__ */ new Set();
437
+ function onPagesEmptied(forget) {
438
+ forgetGlyphs = forget;
439
+ }
440
+ function requestBake(char, raster) {
441
+ asked++;
442
+ if (raster.width === 0 || raster.height === 0) {
443
+ char.page = 0;
444
+ return;
445
+ }
446
+ if (budget > 0) {
447
+ budget--;
448
+ bake(char, raster);
449
+ } else {
450
+ pending.push({ char, raster });
451
+ scheduleFlush();
452
+ }
453
+ }
454
+ function dropBakes(chars) {
455
+ pending = pending.filter((job) => !chars.has(job.char));
456
+ }
457
+ function bake(char, raster) {
458
+ const color = "rgba" in raster;
459
+ const packer = color ? colorPacker : fieldPacker;
460
+ const spot = packer.alloc(raster.width, raster.height);
461
+ if (!spot) {
462
+ noRoom(packer, raster);
463
+ return;
464
+ }
465
+ if (color) {
466
+ blitToColorPage(spot.page, spot.x, spot.y, raster.width, raster.height, raster.rgba);
467
+ } else {
468
+ const sdf = alphaToSdf(raster.alpha, raster.width, raster.height, SDF_RADIUS, SDF_CUTOFF);
469
+ blitToPage(spot.page, spot.x, spot.y, raster.width, raster.height, sdf);
470
+ }
471
+ char.x = spot.x;
472
+ char.y = spot.y;
473
+ char.width = raster.width;
474
+ char.height = raster.height;
475
+ char.page = spot.page;
476
+ }
477
+ function noRoom(packer, raster) {
478
+ if (!packer.fits(raster.width, raster.height)) {
479
+ warnOnce("weasel DynamicGlyphAtlas: a glyph is larger than a page and will not render.");
480
+ } else if (!settled) {
481
+ warnOnce(
482
+ `weasel DynamicGlyphAtlas: more glyphs are in use at once than its pages hold (${MAX_PAGES} \xD7 ${PAGE_SIZE}\xB2); the rest will not render.`
483
+ );
484
+ } else if (!reclaimScheduled) {
485
+ reclaimScheduled = true;
486
+ setTimeout(reclaim, 0);
487
+ }
488
+ }
489
+ function warnOnce(message) {
490
+ if (warned.has(message)) return;
491
+ warned.add(message);
492
+ console.warn(message);
493
+ }
494
+ function reclaim() {
495
+ reclaimScheduled = false;
496
+ settled = false;
497
+ fieldPacker = newPacker();
498
+ colorPacker = newPacker();
499
+ pending = [];
500
+ clearPages();
501
+ forgetGlyphs();
502
+ notifyGlyphReady();
503
+ }
504
+ function scheduleFlush() {
505
+ if (flushScheduled) return;
506
+ flushScheduled = true;
507
+ setTimeout(flushPending, 0);
508
+ }
509
+ function flushPending() {
510
+ flushScheduled = false;
511
+ let n = 0;
512
+ while (pending.length > 0 && n < DEFAULT_BAKE_BUDGET) {
513
+ const job = pending.shift();
514
+ if (job.char.page !== -1) continue;
515
+ bake(job.char, job.raster);
516
+ n++;
517
+ }
518
+ if (pending.length > 0) scheduleFlush();
519
+ notifyGlyphBaked();
520
+ }
521
+ function resetBakeBudget(n = DEFAULT_BAKE_BUDGET) {
522
+ budget = n;
523
+ if (asked === 0 && pending.length === 0) settled = true;
524
+ asked = 0;
525
+ }
526
+ function _resetBakeQueueForTests() {
527
+ fieldPacker = newPacker();
528
+ colorPacker = newPacker();
529
+ pending = [];
530
+ flushScheduled = false;
531
+ asked = 0;
532
+ settled = true;
533
+ reclaimScheduled = false;
534
+ warned.clear();
535
+ budget = DEFAULT_BAKE_BUDGET;
536
+ }
537
+
538
+ // src/cssFamily.ts
539
+ function cssFamilyName(family) {
540
+ if (/[,"']/.test(family)) return family;
541
+ return GENERIC_FAMILIES.has(family.trim().toLowerCase()) ? family.trim() : JSON.stringify(family);
542
+ }
543
+ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
544
+ "serif",
545
+ "sans-serif",
546
+ "monospace",
547
+ "cursive",
548
+ "fantasy",
549
+ "system-ui",
550
+ "ui-serif",
551
+ "ui-sans-serif",
552
+ "ui-monospace",
553
+ "ui-rounded",
554
+ "math",
555
+ "emoji",
556
+ "fangsong"
557
+ ]);
558
+
559
+ // src/dynamic/fontLoads.ts
560
+ function familyKey(family) {
561
+ return family.trim().replace(/^(["'])(.*)\1$/, "$2").toLowerCase();
562
+ }
563
+ function watchFontLoads(onLoaded) {
564
+ const fonts = typeof document === "undefined" ? void 0 : document.fonts;
565
+ if (!fonts?.addEventListener) return () => {
566
+ };
567
+ const listener = (event) => {
568
+ const faces4 = event.fontfaces ?? [];
569
+ if (faces4.length > 0) onLoaded(faces4.map((face) => face.family));
570
+ };
571
+ fonts.addEventListener("loadingdone", listener);
572
+ return () => fonts.removeEventListener("loadingdone", listener);
573
+ }
574
+
318
575
  // src/dynamic/glyphRasterizer.ts
319
576
  var BAKE_SIZE = 48;
320
577
  var PAD = 8;
578
+ var COLORED_BELOW = 240;
579
+ function premultipliedColor(data) {
580
+ const out = new Uint8ClampedArray(data.length);
581
+ let colored = false;
582
+ for (let i = 0; i < data.length; i += 4) {
583
+ const a = data[i + 3];
584
+ if (a === 0) continue;
585
+ const r = data[i], g = data[i + 1], b = data[i + 2];
586
+ if (a >= 128 && Math.min(r, g, b) < COLORED_BELOW) colored = true;
587
+ out[i] = Math.round(r * a / 255);
588
+ out[i + 1] = Math.round(g * a / 255);
589
+ out[i + 2] = Math.round(b * a / 255);
590
+ out[i + 3] = a;
591
+ }
592
+ return colored ? out : null;
593
+ }
321
594
  var METRICS_SIZE = 1e3;
322
595
  function cssFontString(weight, style, family, size = BAKE_SIZE) {
323
596
  return `${style === "italic" ? "italic " : ""}${weight} ${size}px ${cssFamilyName(family)}`;
@@ -340,6 +613,38 @@ function createCanvasRasterizer() {
340
613
  ctx.textBaseline = "alphabetic";
341
614
  ctx.textAlign = "left";
342
615
  }
616
+ function draw(family, weight, style, text) {
617
+ setFont(family, weight, style);
618
+ const m = ctx.measureText(text);
619
+ const advance = m.width;
620
+ const inkLeft = Math.ceil(m.actualBoundingBoxLeft ?? 0);
621
+ const inkRight = Math.ceil(m.actualBoundingBoxRight ?? advance);
622
+ const inkAscent = Math.ceil(m.actualBoundingBoxAscent ?? BAKE_SIZE * 0.8);
623
+ const inkDescent = Math.ceil(m.actualBoundingBoxDescent ?? BAKE_SIZE * 0.2);
624
+ const inkW = inkLeft + inkRight;
625
+ const inkH = inkAscent + inkDescent;
626
+ if (inkW <= 0 || inkH <= 0) {
627
+ return { data: new Uint8ClampedArray(0), width: 0, height: 0, left: 0, top: 0, advance };
628
+ }
629
+ const w = inkW + 2 * PAD;
630
+ const h = inkH + 2 * PAD;
631
+ if (canvas.width < w || canvas.height < h) {
632
+ canvas.width = Math.max(canvas.width, w);
633
+ canvas.height = Math.max(canvas.height, h);
634
+ setFont(family, weight, style);
635
+ }
636
+ ctx.clearRect(0, 0, w, h);
637
+ ctx.fillStyle = "#fff";
638
+ ctx.fillText(text, PAD + inkLeft, PAD + inkAscent);
639
+ return {
640
+ data: ctx.getImageData(0, 0, w, h).data,
641
+ width: w,
642
+ height: h,
643
+ left: -(inkLeft + PAD),
644
+ top: inkAscent + PAD,
645
+ advance
646
+ };
647
+ }
343
648
  return {
344
649
  faceMetrics(family, weight, style) {
345
650
  setFont(family, weight, style, METRICS_SIZE);
@@ -351,59 +656,33 @@ function createCanvasRasterizer() {
351
656
  };
352
657
  },
353
658
  rasterize(family, weight, style, codepoint) {
354
- setFont(family, weight, style);
355
- const chStr = String.fromCodePoint(codepoint);
356
- const m = ctx.measureText(chStr);
357
- const advance = m.width;
358
- const inkLeft = Math.ceil(m.actualBoundingBoxLeft ?? 0);
359
- const inkRight = Math.ceil(m.actualBoundingBoxRight ?? advance);
360
- const inkAscent = Math.ceil(m.actualBoundingBoxAscent ?? BAKE_SIZE * 0.8);
361
- const inkDescent = Math.ceil(m.actualBoundingBoxDescent ?? BAKE_SIZE * 0.2);
362
- const inkW = inkLeft + inkRight;
363
- const inkH = inkAscent + inkDescent;
364
- if (inkW <= 0 || inkH <= 0) {
365
- return { width: 0, height: 0, alpha: new Uint8ClampedArray(0), left: 0, top: 0, advance };
366
- }
367
- const w = inkW + 2 * PAD;
368
- const h = inkH + 2 * PAD;
369
- if (canvas.width < w || canvas.height < h) {
370
- canvas.width = Math.max(canvas.width, w);
371
- canvas.height = Math.max(canvas.height, h);
372
- setFont(family, weight, style);
373
- }
374
- ctx.clearRect(0, 0, w, h);
375
- ctx.fillStyle = "#fff";
376
- ctx.fillText(chStr, PAD + inkLeft, PAD + inkAscent);
377
- const img = ctx.getImageData(0, 0, w, h);
378
- const alpha = new Uint8ClampedArray(w * h);
379
- for (let i = 0; i < alpha.length; i++) alpha[i] = img.data[i * 4 + 3];
380
- return {
381
- width: w,
382
- height: h,
383
- alpha,
384
- left: -(inkLeft + PAD),
385
- top: inkAscent + PAD,
386
- advance
387
- };
659
+ const { data, ...box } = draw(family, weight, style, String.fromCodePoint(codepoint));
660
+ const alpha = new Uint8ClampedArray(box.width * box.height);
661
+ for (let i = 0; i < alpha.length; i++) alpha[i] = data[i * 4 + 3];
662
+ return { ...box, alpha };
663
+ },
664
+ rasterizeColor(family, weight, style, text) {
665
+ const { data, ...box } = draw(family, weight, style, text);
666
+ const rgba = premultipliedColor(data);
667
+ return rgba && { ...box, rgba };
388
668
  }
389
669
  };
390
670
  }
391
671
 
392
672
  // src/dynamic/dynamicAtlas.ts
393
- var PAGE_SIZE = 1024;
394
- var MAX_PAGES = 4;
395
- var SDF_RADIUS = 8;
396
- var SDF_CUTOFF = 0.5;
397
- var DEFAULT_BAKE_BUDGET = 16;
398
673
  var canvasFamilies = /* @__PURE__ */ new Set();
399
674
  var autoEnrolledFamilies = /* @__PURE__ */ new Set();
400
- var faces = /* @__PURE__ */ new Map();
401
- var pages = [];
402
- var packer = new ShelfPacker(PAGE_SIZE, MAX_PAGES);
403
- var pending = [];
404
- var flushScheduled = false;
405
- var budget = DEFAULT_BAKE_BUDGET;
675
+ var faces2 = /* @__PURE__ */ new Map();
406
676
  var rasterizer = null;
677
+ var stopWatchingLoads = null;
678
+ function forgetGlyphs2(face) {
679
+ face.font.charMap.clear();
680
+ face.font.chars.length = 0;
681
+ face.clusters.clear();
682
+ }
683
+ onPagesEmptied(() => {
684
+ for (const face of faces2.values()) forgetGlyphs2(face);
685
+ });
407
686
  function getRasterizer() {
408
687
  if (!rasterizer) rasterizer = createCanvasRasterizer();
409
688
  return rasterizer;
@@ -438,162 +717,105 @@ function unregisterCanvasFont(family) {
438
717
  if (!canvasFamilies.has(family)) return;
439
718
  canvasFamilies.delete(family);
440
719
  autoEnrolledFamilies.delete(family);
441
- for (const key of [...faces.keys()]) {
442
- if (faces.get(key).family === family) faces.delete(key);
720
+ for (const key of [...faces2.keys()]) {
721
+ if (faces2.get(key).family === family) faces2.delete(key);
443
722
  }
444
723
  notifyGlyphReady();
445
724
  }
446
725
  function getDynamicFace(family, weight, style) {
447
726
  const key = `${family}|${weight}|${style}`;
448
- const existing = faces.get(key);
727
+ const existing = faces2.get(key);
449
728
  if (existing) return existing;
450
729
  const r = getRasterizer();
451
- const metrics = r.faceMetrics(family, weight, style);
452
- const base = Math.round(metrics.ascent);
453
730
  const font = {
454
731
  info: { face: family, size: BAKE_SIZE },
455
- common: {
456
- lineHeight: Math.round(metrics.ascent + metrics.descent),
457
- base,
458
- scaleW: PAGE_SIZE,
459
- scaleH: PAGE_SIZE
460
- },
732
+ common: { lineHeight: 0, base: 0, scaleW: PAGE_SIZE, scaleH: PAGE_SIZE },
461
733
  chars: [],
462
734
  kernings: [],
463
735
  // no kerning in v1 (measured-pair kerning is future work)
464
736
  charMap: /* @__PURE__ */ new Map(),
465
- kerningMap: /* @__PURE__ */ new Map(),
466
- // Unrounded, unlike `common`: layout places the baseline from these.
467
- faceMetrics: { ascent: metrics.ascent / BAKE_SIZE, descent: metrics.descent / BAKE_SIZE }
737
+ kerningMap: /* @__PURE__ */ new Map()
468
738
  };
739
+ const record = (id, raster) => ({
740
+ id,
741
+ x: 0,
742
+ y: 0,
743
+ width: 0,
744
+ height: 0,
745
+ xoffset: raster.left,
746
+ yoffset: font.common.base - raster.top,
747
+ xadvance: raster.advance,
748
+ page: -1
749
+ });
750
+ const clusters = /* @__PURE__ */ new Map();
469
751
  const face = {
470
752
  family,
471
753
  weight,
472
754
  style,
473
755
  font,
756
+ clusters,
474
757
  requestGlyph(cp) {
475
758
  const cached = font.charMap.get(cp);
476
759
  if (cached) return cached;
477
760
  const raster = r.rasterize(family, weight, style, cp);
478
- const char = {
479
- id: cp,
480
- x: 0,
481
- y: 0,
482
- width: 0,
483
- height: 0,
484
- xoffset: raster.left,
485
- yoffset: base - raster.top,
486
- xadvance: raster.advance,
487
- page: -1
488
- };
761
+ const char = record(cp, raster);
489
762
  font.charMap.set(cp, char);
490
763
  font.chars.push(char);
491
- if (raster.width === 0 || raster.height === 0) {
492
- char.page = 0;
493
- return char;
494
- }
495
- if (budget > 0) {
496
- budget--;
497
- bake(char, raster);
498
- } else {
499
- pending.push({ char, raster });
500
- scheduleFlush();
501
- }
764
+ requestBake(char, raster);
765
+ return char;
766
+ },
767
+ requestCluster(text) {
768
+ const cached = clusters.get(text);
769
+ if (cached !== void 0) return cached;
770
+ const raster = r.rasterizeColor?.(family, weight, style, text) ?? null;
771
+ const char = raster && { ...record(text.codePointAt(0), raster), color: true };
772
+ clusters.set(text, char);
773
+ if (char && raster) requestBake(char, raster);
502
774
  return char;
503
775
  }
504
776
  };
505
- faces.set(key, face);
777
+ measureFace(face);
778
+ faces2.set(key, face);
779
+ stopWatchingLoads ??= watchFontLoads(refreshFamilies);
506
780
  return face;
507
781
  }
508
- function bake(char, raster) {
509
- const spot = packer.alloc(raster.width, raster.height);
510
- if (!spot) return;
511
- const sdf = alphaToSdf(raster.alpha, raster.width, raster.height, SDF_RADIUS, SDF_CUTOFF);
512
- while (pages.length <= spot.page) {
513
- pages.push({ data: new Uint8Array(PAGE_SIZE * PAGE_SIZE), version: 0, patches: [] });
514
- }
515
- const page = pages[spot.page];
516
- for (let row = 0; row < raster.height; row++) {
517
- page.data.set(
518
- sdf.subarray(row * raster.width, (row + 1) * raster.width),
519
- (spot.y + row) * PAGE_SIZE + spot.x
520
- );
521
- }
522
- page.version++;
523
- page.patches.push({ seq: page.version, x: spot.x, y: spot.y, w: raster.width, h: raster.height });
524
- char.x = spot.x;
525
- char.y = spot.y;
526
- char.width = raster.width;
527
- char.height = raster.height;
528
- char.page = spot.page;
529
- }
530
- function scheduleFlush() {
531
- if (flushScheduled) return;
532
- flushScheduled = true;
533
- setTimeout(flushPending, 0);
534
- }
535
- function flushPending() {
536
- flushScheduled = false;
537
- let n = 0;
538
- while (pending.length > 0 && n < DEFAULT_BAKE_BUDGET) {
539
- const job = pending.shift();
540
- if (job.char.page !== -1) continue;
541
- bake(job.char, job.raster);
542
- n++;
543
- }
544
- if (pending.length > 0) scheduleFlush();
782
+ function measureFace(face) {
783
+ const { font } = face;
784
+ const metrics = getRasterizer().faceMetrics(face.family, face.weight, face.style);
785
+ font.common.lineHeight = Math.round(metrics.ascent + metrics.descent);
786
+ font.common.base = Math.round(metrics.ascent);
787
+ font.faceMetrics = { ascent: metrics.ascent / BAKE_SIZE, descent: metrics.descent / BAKE_SIZE };
788
+ forgetGlyphs2(face);
789
+ }
790
+ function refreshCanvasFont(family) {
791
+ refreshFamilies(family === void 0 ? null : [family]);
792
+ }
793
+ function refreshFamilies(families) {
794
+ const keys = families && new Set(families.map(familyKey));
795
+ const dropped = /* @__PURE__ */ new Set();
796
+ let measured = 0;
797
+ for (const face of faces2.values()) {
798
+ if (keys && !face.family.split(",").some((name) => keys.has(familyKey(name)))) continue;
799
+ for (const char of face.font.chars) dropped.add(char);
800
+ for (const char of face.clusters.values()) if (char) dropped.add(char);
801
+ measureFace(face);
802
+ measured++;
803
+ }
804
+ if (measured === 0) return;
805
+ dropBakes(dropped);
545
806
  notifyGlyphReady();
546
807
  }
547
- function resetBakeBudget(n = DEFAULT_BAKE_BUDGET) {
548
- budget = n;
549
- }
550
- function dynamicPageTextureId(page) {
551
- return `weasel-dyn-sdf-page-${page}`;
552
- }
553
- var uploadedVersions = /* @__PURE__ */ new WeakMap();
554
- function syncDynamicPageTexture(cache, pageIndex) {
555
- const page = pages[pageIndex];
556
- if (!page) return false;
557
- const id = dynamicPageTextureId(pageIndex);
558
- let seen = uploadedVersions.get(cache);
559
- if (!seen) {
560
- seen = /* @__PURE__ */ new Map();
561
- uploadedVersions.set(cache, seen);
562
- }
563
- if (!cache.has(id)) {
564
- cache.uploadR8(id, PAGE_SIZE, PAGE_SIZE, page.data);
565
- seen.set(pageIndex, page.version);
566
- return true;
567
- }
568
- const last = seen.get(pageIndex) ?? 0;
569
- if (last >= page.version) return true;
570
- for (const patch of page.patches) {
571
- if (patch.seq <= last) continue;
572
- const tight = new Uint8Array(patch.w * patch.h);
573
- for (let row = 0; row < patch.h; row++) {
574
- const src = (patch.y + row) * PAGE_SIZE + patch.x;
575
- tight.set(page.data.subarray(src, src + patch.w), row * patch.w);
576
- }
577
- cache.subImageR8(id, patch.x, patch.y, patch.w, patch.h, tight);
578
- }
579
- seen.set(pageIndex, page.version);
580
- return true;
581
- }
582
808
  function __setGlyphRasterizerForTests(r) {
583
809
  rasterizer = r;
584
810
  }
585
- function _getPagesForTests() {
586
- return pages;
587
- }
588
811
  function _resetDynamicFontsForTests() {
589
812
  canvasFamilies.clear();
590
813
  autoEnrolledFamilies.clear();
591
- faces = /* @__PURE__ */ new Map();
592
- pages = [];
593
- packer = new ShelfPacker(PAGE_SIZE, MAX_PAGES);
594
- pending = [];
595
- flushScheduled = false;
596
- budget = DEFAULT_BAKE_BUDGET;
814
+ faces2 = /* @__PURE__ */ new Map();
815
+ _resetPagesForTests();
816
+ _resetBakeQueueForTests();
817
+ stopWatchingLoads?.();
818
+ stopWatchingLoads = null;
597
819
  _clearGlyphReadySubscribers();
598
820
  rasterizer = null;
599
821
  }
@@ -601,9 +823,7 @@ function _resetDynamicFontsForTests() {
601
823
  // src/outline/OutlineFace.ts
602
824
  var OUTLINE_PRECISION = 5;
603
825
 
604
- // src/outline/sfnt.ts
605
- var SFNT_HEADER_BYTES = 12;
606
- var TABLE_RECORD_BYTES = 16;
826
+ // src/outline/tag.ts
607
827
  function tagAt(view, offset) {
608
828
  return String.fromCharCode(
609
829
  view.getUint8(offset),
@@ -612,10 +832,11 @@ function tagAt(view, offset) {
612
832
  view.getUint8(offset + 3)
613
833
  );
614
834
  }
615
- function isFontCollection(bytes) {
616
- if (bytes.byteLength < 4) return false;
617
- return tagAt(new DataView(bytes), 0) === "ttcf";
618
- }
835
+
836
+ // src/outline/dataFork.ts
837
+ var RESOURCE_HEADER_BYTES = 16;
838
+ var TYPE_ENTRY_BYTES = 8;
839
+ var REFERENCE_BYTES = 12;
619
840
  var FONT_SIGNATURES = /* @__PURE__ */ new Set([
620
841
  "\0\0\0",
621
842
  "true",
@@ -625,7 +846,6 @@ var FONT_SIGNATURES = /* @__PURE__ */ new Set([
625
846
  "wOFF",
626
847
  "wOF2"
627
848
  ]);
628
- var RESOURCE_HEADER_BYTES = 16;
629
849
  function isDataForkFont(bytes) {
630
850
  if (bytes.byteLength < RESOURCE_HEADER_BYTES) return false;
631
851
  const view = new DataView(bytes);
@@ -636,6 +856,46 @@ function isDataForkFont(bytes) {
636
856
  const mapLength = view.getUint32(12);
637
857
  return dataOffset >= RESOURCE_HEADER_BYTES && dataOffset + dataLength === mapOffset && mapOffset + mapLength <= bytes.byteLength;
638
858
  }
859
+ var truncated = () => new Error("Datafork TrueType (.dfont) is truncated: a resource runs past the end of its segment.");
860
+ function sfntResources(bytes) {
861
+ const view = new DataView(bytes);
862
+ const dataOffset = view.getUint32(0);
863
+ const mapOffset = view.getUint32(4);
864
+ const dataLength = view.getUint32(8);
865
+ const mapEnd = mapOffset + view.getUint32(12);
866
+ const inMap = (at, count) => at >= mapOffset && at + count <= mapEnd;
867
+ if (!inMap(mapOffset + 24, 2)) return [];
868
+ const typeList = mapOffset + view.getUint16(mapOffset + 24);
869
+ if (!inMap(typeList, 2)) return [];
870
+ const typeCount = view.getUint16(typeList) + 1 & 65535;
871
+ const out = [];
872
+ for (let t = 0; t < typeCount; t++) {
873
+ const entry = typeList + 2 + t * TYPE_ENTRY_BYTES;
874
+ if (!inMap(entry, TYPE_ENTRY_BYTES)) throw truncated();
875
+ if (tagAt(view, entry) !== "sfnt") continue;
876
+ const count = view.getUint16(entry + 4) + 1;
877
+ const references = typeList + view.getUint16(entry + 6);
878
+ for (let i = 0; i < count; i++) {
879
+ const ref = references + i * REFERENCE_BYTES;
880
+ if (!inMap(ref, REFERENCE_BYTES)) throw truncated();
881
+ const offset = view.getUint32(ref + 4) & 16777215;
882
+ if (offset + 4 > dataLength) throw truncated();
883
+ const start = dataOffset + offset + 4;
884
+ const length = view.getUint32(start - 4);
885
+ if (offset + 4 + length > dataLength) throw truncated();
886
+ out.push(bytes.slice(start, start + length));
887
+ }
888
+ }
889
+ return out;
890
+ }
891
+
892
+ // src/outline/sfnt.ts
893
+ var SFNT_HEADER_BYTES = 12;
894
+ var TABLE_RECORD_BYTES = 16;
895
+ function isFontCollection(bytes) {
896
+ if (bytes.byteLength < 4) return false;
897
+ return tagAt(new DataView(bytes), 0) === "ttcf";
898
+ }
639
899
  function readTableDirectory(view, dirOffset) {
640
900
  const numTables = view.getUint16(dirOffset + 4);
641
901
  const records = [];
@@ -674,7 +934,12 @@ function extractFont(source, view, dirOffset) {
674
934
  const records = readTableDirectory(view, dirOffset);
675
935
  const padded = (n) => n + 3 & -4;
676
936
  let total = SFNT_HEADER_BYTES + records.length * TABLE_RECORD_BYTES;
677
- for (const r of records) total += padded(r.length);
937
+ for (const r of records) {
938
+ if (r.offset + r.length > source.byteLength) {
939
+ throw new Error(`font collection is truncated: table "${r.tag}" runs past the end of the file`);
940
+ }
941
+ total += padded(r.length);
942
+ }
678
943
  const out = new ArrayBuffer(total);
679
944
  const dst = new DataView(out);
680
945
  const dstBytes = new Uint8Array(out);
@@ -699,26 +964,34 @@ function extractFont(source, view, dirOffset) {
699
964
  return out;
700
965
  }
701
966
  function sfntFromCollection(bytes, postScriptName) {
967
+ const members = membersOf(bytes);
968
+ if (!members) return { bytes, matched: true, names: [] };
969
+ const names2 = members.map((m) => m.name);
970
+ const at = postScriptName ? names2.indexOf(postScriptName) : 0;
971
+ return { bytes: members[Math.max(at, 0)].bytes(), matched: at >= 0, names: names2 };
972
+ }
973
+ function membersOf(bytes) {
702
974
  if (isDataForkFont(bytes)) {
703
- throw new Error(
704
- "Datafork TrueType (.dfont) is not supported \u2014 the outline tier reads sfnt tables and a .dfont holds them inside a Macintosh resource map."
705
- );
975
+ const fonts = sfntResources(bytes);
976
+ if (fonts.length === 0) throw new Error("Datafork TrueType (.dfont) holds no sfnt resource.");
977
+ return fonts.map((font) => {
978
+ const view2 = new DataView(font);
979
+ return { name: postScriptNameAt(view2, readTableDirectory(view2, 0)), bytes: () => font };
980
+ });
706
981
  }
707
- if (!isFontCollection(bytes)) return { bytes, matched: true };
982
+ if (!isFontCollection(bytes)) return null;
708
983
  const view = new DataView(bytes);
709
984
  const numFonts = view.getUint32(8);
710
985
  if (numFonts === 0) throw new Error("font collection contains no fonts");
711
- const offsets = [];
712
- for (let i = 0; i < numFonts; i++) offsets.push(view.getUint32(12 + i * 4));
713
- if (postScriptName) {
714
- for (const dirOffset of offsets) {
715
- const records = readTableDirectory(view, dirOffset);
716
- if (postScriptNameAt(view, records) === postScriptName) {
717
- return { bytes: extractFont(bytes, view, dirOffset), matched: true };
718
- }
719
- }
986
+ const members = [];
987
+ for (let i = 0; i < numFonts; i++) {
988
+ const dirOffset = view.getUint32(12 + i * 4);
989
+ members.push({
990
+ name: postScriptNameAt(view, readTableDirectory(view, dirOffset)),
991
+ bytes: () => extractFont(bytes, view, dirOffset)
992
+ });
720
993
  }
721
- return { bytes: extractFont(bytes, view, offsets[0]), matched: !postScriptName };
994
+ return members;
722
995
  }
723
996
 
724
997
  // src/outline/gposKerning.ts
@@ -919,7 +1192,12 @@ function faceFor(font, gpos) {
919
1192
  function createOpenTypeParser(postScriptName) {
920
1193
  return async (bytes) => {
921
1194
  const parse = parserOf(await loadOpenType());
922
- const { bytes: single } = sfntFromCollection(bytes, postScriptName);
1195
+ const { bytes: single, matched, names: names2 } = sfntFromCollection(bytes, postScriptName);
1196
+ if (!matched) {
1197
+ throw new Error(
1198
+ `no member of the font file is named "${postScriptName}" (it holds ${names2.join(", ")})`
1199
+ );
1200
+ }
923
1201
  return faceFor(parse(single), gposPairKerning(single));
924
1202
  };
925
1203
  }
@@ -996,7 +1274,7 @@ function glyphOutline(family, weight, style, cp) {
996
1274
  const raw = slot.face.glyphD(cp);
997
1275
  d = raw === null ? null : closeContours(raw);
998
1276
  } catch (err) {
999
- warnOnce(
1277
+ warnOnce2(
1000
1278
  `glyph|${slotKey(family, weight, style)}`,
1001
1279
  `weasel: outline face "${family}" (${weight}/${style}) could not produce a glyph for U+${cp.toString(16).toUpperCase().padStart(4, "0")} \u2014 ${err instanceof Error ? err.message : String(err)}. Falling back to the SDF tier.`
1002
1280
  );
@@ -1038,7 +1316,7 @@ async function runLoad(slot) {
1038
1316
  slot.status = "failed";
1039
1317
  slots.bump();
1040
1318
  notifyGlyphReady();
1041
- warnOnce(
1319
+ warnOnce2(
1042
1320
  `load|${slotKey(slot.family, slot.weight, slot.style)}`,
1043
1321
  `weasel registerFontOutlines("${slot.family}" ${slot.weight}/${slot.style}): ${err instanceof Error ? err.message : String(err)}. Large text in this face keeps rendering from the SDF tier.`
1044
1322
  );
@@ -1074,15 +1352,15 @@ async function readSource(source) {
1074
1352
  if (resolved instanceof ArrayBuffer) return resolved;
1075
1353
  return resolved.arrayBuffer();
1076
1354
  }
1077
- var warned = /* @__PURE__ */ new Set();
1078
- function warnOnce(key, message) {
1079
- if (warned.has(key)) return;
1080
- warned.add(key);
1355
+ var warned2 = /* @__PURE__ */ new Set();
1356
+ function warnOnce2(key, message) {
1357
+ if (warned2.has(key)) return;
1358
+ warned2.add(key);
1081
1359
  console.warn(message);
1082
1360
  }
1083
1361
  function _resetFontOutlinesForTests() {
1084
1362
  slots.clear();
1085
- warned.clear();
1363
+ warned2.clear();
1086
1364
  }
1087
1365
  var registry = createReflectable();
1088
1366
  var fontRegistry = registry.reflection;
@@ -1301,6 +1579,7 @@ function missResolveResult(family, weight, style, suppressWarn = false) {
1301
1579
  source: "outline"
1302
1580
  };
1303
1581
  }
1582
+ if (fontPending(family, weight, style)) return pendingMiss(family, weight, style);
1304
1583
  const policy2 = getFontFallbackPolicy();
1305
1584
  if (policy2 === "canvas") {
1306
1585
  autoEnrollCanvasFont(family);
@@ -1506,8 +1785,8 @@ function resolveFontVariantInternal(family, weight, style, suppressWarn) {
1506
1785
 
1507
1786
  // src/domFace.ts
1508
1787
  var names = /* @__PURE__ */ new Map();
1509
- var faces2 = /* @__PURE__ */ new Map();
1510
- var warned2 = /* @__PURE__ */ new Set();
1788
+ var faces3 = /* @__PURE__ */ new Map();
1789
+ var warned3 = /* @__PURE__ */ new Set();
1511
1790
  function domFonts() {
1512
1791
  return typeof FontFace !== "undefined" && typeof document !== "undefined" && !!document.fonts;
1513
1792
  }
@@ -1531,8 +1810,8 @@ function privateFamily(family, variant) {
1531
1810
  const target = resolved.resolved.family;
1532
1811
  const variants = listFontOutlines().filter((f) => f.family === target && f.status !== "failed");
1533
1812
  if (variants.length === 0) {
1534
- if (resolved.entry && !warned2.has(target)) {
1535
- warned2.add(target);
1813
+ if (resolved.entry && !warned3.has(target)) {
1814
+ warned3.add(target);
1536
1815
  console.warn(
1537
1816
  `weasel: "${target}" draws from a baked atlas with no font file registered, so DOM text in it (the text edit overlay) uses the browser's own "${family}" and will not match the canvas. Register the file the atlas was baked from: registerFontOutlines("${target}", \u2026).`
1538
1817
  );
@@ -1556,7 +1835,7 @@ function cssFontFamilyLoading(family, variant = {}) {
1556
1835
  const weight = variant.weight ?? 400;
1557
1836
  const style = variant.style ?? "normal";
1558
1837
  const exact = priv.variants.filter((v) => v.weight === weight && v.style === style);
1559
- const pending2 = (exact.length > 0 ? exact : priv.variants).map((v) => faces2.get(`${v.family}|${v.weight}|${v.style}`)).filter((l) => l !== void 0 && !l.settled).map((l) => l.done);
1838
+ const pending2 = (exact.length > 0 ? exact : priv.variants).map((v) => faces3.get(`${v.family}|${v.weight}|${v.style}`)).filter((l) => l !== void 0 && !l.settled).map((l) => l.done);
1560
1839
  return pending2.length === 0 ? null : Promise.all(pending2).then(() => {
1561
1840
  });
1562
1841
  }
@@ -1564,7 +1843,7 @@ function ensureFace(name, family, weight, style, bytes) {
1564
1843
  const key = `${family}|${weight}|${style}`;
1565
1844
  const css = outlineCssSource(family, weight, style);
1566
1845
  if (!css) return;
1567
- const have = faces2.get(key);
1846
+ const have = faces3.get(key);
1568
1847
  if (have?.from === css.registration) return;
1569
1848
  if (have?.face) document.fonts.delete(have.face);
1570
1849
  let settle2;
@@ -1580,10 +1859,10 @@ function ensureFace(name, family, weight, style, bytes) {
1580
1859
  entry.settled = true;
1581
1860
  settle2();
1582
1861
  };
1583
- faces2.set(key, entry);
1862
+ faces3.set(key, entry);
1584
1863
  const descriptors = { weight: String(weight), style, display: "swap" };
1585
1864
  const add = (face) => {
1586
- if (faces2.get(key) !== entry) return finish();
1865
+ if (faces3.get(key) !== entry) return finish();
1587
1866
  entry.face = face;
1588
1867
  document.fonts.add(face);
1589
1868
  face.load().then(finish, () => {
@@ -1598,13 +1877,13 @@ function ensureFace(name, family, weight, style, bytes) {
1598
1877
  }
1599
1878
  function _resetDomFacesForTests() {
1600
1879
  if (typeof document !== "undefined" && document.fonts) {
1601
- for (const { face } of faces2.values()) if (face) document.fonts.delete(face);
1880
+ for (const { face } of faces3.values()) if (face) document.fonts.delete(face);
1602
1881
  }
1603
1882
  names.clear();
1604
- faces2.clear();
1605
- warned2.clear();
1883
+ faces3.clear();
1884
+ warned3.clear();
1606
1885
  }
1607
1886
 
1608
- export { DEFAULT_BAKE_BUDGET, FIXTURE_FONT, PAGE_SIZE, SDF_RADIUS, __setGlyphRasterizerForTests, _getPagesForTests, _resetDomFacesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests, createOpenTypeParser, cssFamilyName, cssFontFamily, cssFontFamilyLoading, dynamicPageTextureId, ensureFontTexture, faceMetricsFor, faceMetricsFromTables, faceMetricsOf, fontOutlineRegistry, fontPending, fontRegistry, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFontWeights, listFonts, loadFontOutlines, markAllFontsNotUploaded, outlineMetrics, outlineStatus, parseBmFont, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, subscribeGlyphReady, syncDynamicPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines, verticalMetricsFromTables, warmFonts };
1609
- //# sourceMappingURL=chunk-OYLAXZOV.js.map
1610
- //# sourceMappingURL=chunk-OYLAXZOV.js.map
1887
+ export { DEFAULT_BAKE_BUDGET, FIXTURE_FONT, PAGE_SIZE, SDF_RADIUS, __setGlyphRasterizerForTests, _getPagesForTests, _resetDomFacesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests, claimFallbackWarning, colorPageTextureId, createOpenTypeParser, cssFamilyName, cssFontFamily, cssFontFamilyLoading, dynamicPageTextureId, ensureFontTexture, faceGeneration, faceMetricsFor, faceMetricsFromTables, faceMetricsOf, fallbackWarningClaimed, fontOutlineRegistry, fontPending, fontRegistry, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFontWeights, listFonts, loadFontOutlines, markAllFontsNotUploaded, outlineMetrics, outlineStatus, parseBmFont, refreshCanvasFont, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, subscribeGlyphReady, syncColorPageTexture, syncDynamicPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines, verticalMetricsFromTables, warmFonts };
1888
+ //# sourceMappingURL=chunk-NM6K6M4Y.js.map
1889
+ //# sourceMappingURL=chunk-NM6K6M4Y.js.map