@weasel-js/font 1.10.1 → 1.11.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/README.md CHANGED
@@ -17,6 +17,14 @@ no glyphs at all.
17
17
  | `textureSink` | The `GlyphTextureSink` seam — the renderer injects GL texture upload, so this package never imports one |
18
18
  | `textSdf` | The GLSL that turns an atlas sample into glyph coverage, for the renderer's batch program to paste in |
19
19
 
20
+ ## Without a browser
21
+
22
+ `@weasel-js/font/metrics` carries the part that runs in Node or a worker:
23
+ `parseBmFont`, `faceMetricsFromTables` and the other face-metric readers, and
24
+ `cssFamilyName`. The package root also loads faces through `document.fonts`
25
+ and rasterizes through a canvas, so a server measuring text imports the
26
+ subpath instead.
27
+
20
28
  ## Fallback
21
29
 
22
30
  An unregistered family renders in the default family with a one-time warning:
@@ -1,141 +1,6 @@
1
+ import { parseBmFont, cssFamilyName, faceMetricsFromTables } from './chunk-XQPRAGHF.js';
1
2
  import { createEmitter, createReflectable } from '@weasel-js/registry';
2
3
 
3
- // src/faceMetrics.ts
4
- var finite = (n) => typeof n === "number" && Number.isFinite(n);
5
- var USE_TYPO_METRICS = 1 << 7;
6
- function verticalMetricsFromTables(t) {
7
- const upem = t.unitsPerEm;
8
- if (!finite(upem) || upem <= 0) return void 0;
9
- const os2 = t.os2;
10
- const pair = (a, d, down) => finite(a) && finite(d) && a + (down ? d : -d) > 0 ? { ascent: a / upem, descent: (down ? d : -d) / upem } : void 0;
11
- const typo = pair(os2?.sTypoAscender, os2?.sTypoDescender, false);
12
- const hhea = pair(t.hhea?.ascender, t.hhea?.descender, false);
13
- const win = pair(os2?.usWinAscent, os2?.usWinDescent, true);
14
- const useTypo = finite(os2?.fsSelection) && (os2.fsSelection & USE_TYPO_METRICS) !== 0;
15
- return (useTypo ? typo : void 0) ?? hhea ?? typo ?? win;
16
- }
17
- function faceMetricsFromTables(t) {
18
- const upem = t.unitsPerEm;
19
- if (!finite(upem) || upem <= 0) return void 0;
20
- const out = {};
21
- const rule = (position, thickness) => finite(position) && finite(thickness) && thickness > 0 ? { offset: -position / upem, thickness: thickness / upem } : void 0;
22
- const script = (size, shift) => finite(size) && finite(shift) && size > 0 ? { size: size / upem, shift: shift / upem } : void 0;
23
- const underline = rule(t.post?.underlinePosition, t.post?.underlineThickness);
24
- const strikethrough = rule(t.os2?.yStrikeoutPosition, t.os2?.yStrikeoutSize);
25
- const superscript = script(t.os2?.ySuperscriptYSize, t.os2?.ySuperscriptYOffset);
26
- const sub = t.os2?.ySubscriptYOffset;
27
- const subscript = script(t.os2?.ySubscriptYSize, finite(sub) ? -sub : sub);
28
- const vertical = verticalMetricsFromTables(t);
29
- if (vertical) {
30
- out.ascent = vertical.ascent;
31
- out.descent = vertical.descent;
32
- }
33
- if (underline) out.underline = underline;
34
- if (strikethrough) out.strikethrough = strikethrough;
35
- if (superscript) out.superscript = superscript;
36
- if (subscript) out.subscript = subscript;
37
- const xHeight = t.os2?.sxHeight;
38
- const capHeight = t.os2?.sCapHeight;
39
- if (finite(xHeight) && xHeight > 0) out.xHeight = xHeight / upem;
40
- if (finite(capHeight) && capHeight > 0) out.capHeight = capHeight / upem;
41
- return Object.keys(out).length > 0 ? out : void 0;
42
- }
43
- function parseFaceMetrics(raw) {
44
- if (raw === void 0) return void 0;
45
- if (typeof raw !== "object" || raw === null) throw new Error("parseBmFont: faceMetrics must be an object");
46
- const r = raw;
47
- const out = {};
48
- if (r.ascent !== void 0 || r.descent !== void 0) {
49
- if (!finite(r.ascent) || !finite(r.descent) || r.ascent + r.descent <= 0) {
50
- throw new Error("parseBmFont: malformed faceMetrics.ascent/descent");
51
- }
52
- out.ascent = r.ascent;
53
- out.descent = r.descent;
54
- }
55
- const pair = (key, a, b, positive) => {
56
- const v = r[key];
57
- if (v === void 0) return;
58
- const e = v;
59
- if (typeof e !== "object" || e === null || !finite(e[a]) || !finite(e[b]) || e[positive] <= 0) {
60
- throw new Error(`parseBmFont: malformed faceMetrics.${key}`);
61
- }
62
- out[key] = { [a]: e[a], [b]: e[b] };
63
- };
64
- pair("underline", "offset", "thickness", "thickness");
65
- pair("strikethrough", "offset", "thickness", "thickness");
66
- pair("superscript", "size", "shift", "size");
67
- pair("subscript", "size", "shift", "size");
68
- for (const key of ["xHeight", "capHeight"]) {
69
- const v = r[key];
70
- if (v === void 0) continue;
71
- if (!finite(v) || v <= 0) throw new Error(`parseBmFont: malformed faceMetrics.${key}`);
72
- out[key] = v;
73
- }
74
- return Object.keys(out).length > 0 ? out : void 0;
75
- }
76
- function faceMetricsOf(result) {
77
- return result.outlineFace?.faceMetrics ?? result.entry?.font.faceMetrics;
78
- }
79
-
80
- // src/FontAtlas.ts
81
- var FIXTURE_FONT = {
82
- info: { face: "Inter", size: 32 },
83
- common: { lineHeight: 38, base: 29, scaleW: 512, scaleH: 512 },
84
- chars: [
85
- { id: 65, x: 0, y: 0, width: 22, height: 28, xoffset: 1, yoffset: 4, xadvance: 23, page: 0 },
86
- { id: 66, x: 24, y: 0, width: 20, height: 28, xoffset: 2, yoffset: 4, xadvance: 22, page: 0 }
87
- ],
88
- kernings: [
89
- { first: 65, second: 66, amount: -1 }
90
- ]
91
- };
92
- function parseBmFont(raw) {
93
- if (typeof raw !== "object" || raw === null) throw new Error("parseBmFont: expected object");
94
- const r = raw;
95
- if (!r.info || typeof r.info !== "object") throw new Error("parseBmFont: missing info");
96
- if (!r.common || typeof r.common !== "object") throw new Error("parseBmFont: missing common");
97
- if (!Array.isArray(r.chars)) throw new Error("parseBmFont: chars must be an array");
98
- const info = r.info;
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
- }
113
- const chars = r.chars;
114
- const kernings = Array.isArray(r.kernings) ? r.kernings : [];
115
- const charMap = /* @__PURE__ */ new Map();
116
- for (const ch of chars) charMap.set(ch.id, ch);
117
- const kerningMap = /* @__PURE__ */ new Map();
118
- for (const k of kernings) {
119
- let inner = kerningMap.get(k.first);
120
- if (!inner) {
121
- inner = /* @__PURE__ */ new Map();
122
- kerningMap.set(k.first, inner);
123
- }
124
- inner.set(k.second, k.amount);
125
- }
126
- const faceMetrics = parseFaceMetrics(r.faceMetrics);
127
- const range = r.distanceField?.distanceRange;
128
- return {
129
- info,
130
- common,
131
- chars,
132
- kernings,
133
- charMap,
134
- kerningMap,
135
- ...faceMetrics ? { faceMetrics } : {},
136
- ...typeof range === "number" && range > 0 ? { distanceRange: range } : {}
137
- };
138
- }
139
4
  var ready = createEmitter();
140
5
  var generation = 0;
141
6
  function glyphGeneration() {
@@ -278,15 +143,28 @@ var colors = new PageSet({
278
143
  upload: (cache, id, data) => cache.uploadRGBA(id, PAGE_SIZE, PAGE_SIZE, data),
279
144
  patch: (cache, id, x, y, w, h, data) => cache.subImageRGBA(id, x, y, w, h, data)
280
145
  });
146
+ var sharpColors = new PageSet({
147
+ channels: 4,
148
+ idPrefix: "weasel-dyn-color-sharp-page-",
149
+ upload: (cache, id, data) => cache.uploadRGBA(id, PAGE_SIZE, PAGE_SIZE, data),
150
+ patch: (cache, id, x, y, w, h, data) => cache.subImageRGBA(id, x, y, w, h, data)
151
+ });
281
152
  function blitToPage(pageIndex, x, y, w, h, bytes) {
282
153
  fields.blit(pageIndex, x, y, w, h, bytes);
283
154
  }
284
155
  function blitToColorPage(pageIndex, x, y, w, h, rgba) {
285
156
  colors.blit(pageIndex, x, y, w, h, rgba);
286
157
  }
158
+ function blitToSharpColorPage(pageIndex, x, y, w, h, rgba) {
159
+ sharpColors.blit(pageIndex, x, y, w, h, rgba);
160
+ }
287
161
  function clearPages() {
288
162
  fields.clear();
289
163
  colors.clear();
164
+ sharpColors.clear();
165
+ }
166
+ function clearSharpPages() {
167
+ sharpColors.clear();
290
168
  }
291
169
  function dynamicPageTextureId(page) {
292
170
  return fields.textureId(page);
@@ -294,18 +172,25 @@ function dynamicPageTextureId(page) {
294
172
  function colorPageTextureId(page) {
295
173
  return colors.textureId(page);
296
174
  }
175
+ function sharpColorPageTextureId(page) {
176
+ return sharpColors.textureId(page);
177
+ }
297
178
  function syncDynamicPageTexture(cache, pageIndex) {
298
179
  return fields.sync(cache, pageIndex);
299
180
  }
300
181
  function syncColorPageTexture(cache, pageIndex) {
301
182
  return colors.sync(cache, pageIndex);
302
183
  }
184
+ function syncSharpColorPageTexture(cache, pageIndex) {
185
+ return sharpColors.sync(cache, pageIndex);
186
+ }
303
187
  function _getPagesForTests() {
304
188
  return fields.pages;
305
189
  }
306
190
  function _resetPagesForTests() {
307
191
  fields.pages = [];
308
192
  colors.pages = [];
193
+ sharpColors.pages = [];
309
194
  }
310
195
 
311
196
  // src/dynamic/shelfPack.ts
@@ -422,22 +307,26 @@ function alphaToSdf(alpha, width, height, radius, cutoff) {
422
307
  var SDF_RADIUS = 8;
423
308
  var SDF_CUTOFF = 0.5;
424
309
  var DEFAULT_BAKE_BUDGET = 16;
425
- var newPacker = () => new ShelfPacker(PAGE_SIZE, MAX_PAGES);
426
- var fieldPacker = newPacker();
427
- var colorPacker = newPacker();
310
+ var newRoom = () => ({ packer: new ShelfPacker(PAGE_SIZE, MAX_PAGES), settled: true, reclaimScheduled: false });
311
+ var fieldRoom = newRoom();
312
+ var colorRoom = newRoom();
313
+ var sharpRoom = newRoom();
428
314
  var pending = [];
429
315
  var flushScheduled = false;
430
316
  var budget = DEFAULT_BAKE_BUDGET;
431
317
  var asked = 0;
432
- var settled = true;
433
- var reclaimScheduled = false;
434
318
  var forgetGlyphs = () => {
435
319
  };
320
+ var forgetSharp = () => {
321
+ };
436
322
  var warned = /* @__PURE__ */ new Set();
437
323
  function onPagesEmptied(forget) {
438
324
  forgetGlyphs = forget;
439
325
  }
440
- function requestBake(char, raster) {
326
+ function onSharpPagesEmptied(forget) {
327
+ forgetSharp = forget;
328
+ }
329
+ function requestBake(char, raster, sharp = false) {
441
330
  asked++;
442
331
  if (raster.width === 0 || raster.height === 0) {
443
332
  char.page = 0;
@@ -445,24 +334,26 @@ function requestBake(char, raster) {
445
334
  }
446
335
  if (budget > 0) {
447
336
  budget--;
448
- bake(char, raster);
337
+ bake(char, raster, sharp);
449
338
  } else {
450
- pending.push({ char, raster });
339
+ pending.push({ char, raster, sharp });
451
340
  scheduleFlush();
452
341
  }
453
342
  }
454
343
  function dropBakes(chars) {
455
344
  pending = pending.filter((job) => !chars.has(job.char));
456
345
  }
457
- function bake(char, raster) {
346
+ function bake(char, raster, sharp) {
458
347
  const color = "rgba" in raster;
459
- const packer = color ? colorPacker : fieldPacker;
460
- const spot = packer.alloc(raster.width, raster.height);
348
+ const room = sharp ? sharpRoom : color ? colorRoom : fieldRoom;
349
+ const spot = room.packer.alloc(raster.width, raster.height);
461
350
  if (!spot) {
462
- noRoom(packer, raster);
351
+ noRoom(room, raster, sharp ? reclaimSharp : reclaim);
463
352
  return;
464
353
  }
465
- if (color) {
354
+ if (color && sharp) {
355
+ blitToSharpColorPage(spot.page, spot.x, spot.y, raster.width, raster.height, raster.rgba);
356
+ } else if (color) {
466
357
  blitToColorPage(spot.page, spot.x, spot.y, raster.width, raster.height, raster.rgba);
467
358
  } else {
468
359
  const sdf = alphaToSdf(raster.alpha, raster.width, raster.height, SDF_RADIUS, SDF_CUTOFF);
@@ -474,16 +365,17 @@ function bake(char, raster) {
474
365
  char.height = raster.height;
475
366
  char.page = spot.page;
476
367
  }
477
- function noRoom(packer, raster) {
478
- if (!packer.fits(raster.width, raster.height)) {
368
+ function noRoom(room, raster, empty) {
369
+ if (!room.packer.fits(raster.width, raster.height)) {
479
370
  warnOnce("weasel DynamicGlyphAtlas: a glyph is larger than a page and will not render.");
480
- } else if (!settled) {
371
+ } else if (!room.settled) {
372
+ if (room === sharpRoom) return;
481
373
  warnOnce(
482
374
  `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
375
  );
484
- } else if (!reclaimScheduled) {
485
- reclaimScheduled = true;
486
- setTimeout(reclaim, 0);
376
+ } else if (!room.reclaimScheduled) {
377
+ room.reclaimScheduled = true;
378
+ setTimeout(empty, 0);
487
379
  }
488
380
  }
489
381
  function warnOnce(message) {
@@ -492,13 +384,20 @@ function warnOnce(message) {
492
384
  console.warn(message);
493
385
  }
494
386
  function reclaim() {
495
- reclaimScheduled = false;
496
- settled = false;
497
- fieldPacker = newPacker();
498
- colorPacker = newPacker();
387
+ fieldRoom = { ...newRoom(), settled: false };
388
+ colorRoom = { ...newRoom(), settled: false };
389
+ sharpRoom = { ...newRoom(), settled: false };
499
390
  pending = [];
500
391
  clearPages();
501
392
  forgetGlyphs();
393
+ forgetSharp();
394
+ notifyGlyphReady();
395
+ }
396
+ function reclaimSharp() {
397
+ sharpRoom = { ...newRoom(), settled: false };
398
+ pending = pending.filter((job) => !job.sharp);
399
+ clearSharpPages();
400
+ forgetSharp();
502
401
  notifyGlyphReady();
503
402
  }
504
403
  function scheduleFlush() {
@@ -512,7 +411,7 @@ function flushPending() {
512
411
  while (pending.length > 0 && n < DEFAULT_BAKE_BUDGET) {
513
412
  const job = pending.shift();
514
413
  if (job.char.page !== -1) continue;
515
- bake(job.char, job.raster);
414
+ bake(job.char, job.raster, job.sharp);
516
415
  n++;
517
416
  }
518
417
  if (pending.length > 0) scheduleFlush();
@@ -520,58 +419,24 @@ function flushPending() {
520
419
  }
521
420
  function resetBakeBudget(n = DEFAULT_BAKE_BUDGET) {
522
421
  budget = n;
523
- if (asked === 0 && pending.length === 0) settled = true;
422
+ if (asked === 0 && pending.length === 0) {
423
+ fieldRoom.settled = true;
424
+ colorRoom.settled = true;
425
+ sharpRoom.settled = true;
426
+ }
524
427
  asked = 0;
525
428
  }
526
429
  function _resetBakeQueueForTests() {
527
- fieldPacker = newPacker();
528
- colorPacker = newPacker();
430
+ fieldRoom = newRoom();
431
+ colorRoom = newRoom();
432
+ sharpRoom = newRoom();
529
433
  pending = [];
530
434
  flushScheduled = false;
531
435
  asked = 0;
532
- settled = true;
533
- reclaimScheduled = false;
534
436
  warned.clear();
535
437
  budget = DEFAULT_BAKE_BUDGET;
536
438
  }
537
439
 
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
-
575
440
  // src/dynamic/glyphRasterizer.ts
576
441
  var BAKE_SIZE = 48;
577
442
  var PAD = 8;
@@ -613,14 +478,14 @@ function createCanvasRasterizer() {
613
478
  ctx.textBaseline = "alphabetic";
614
479
  ctx.textAlign = "left";
615
480
  }
616
- function draw(family, weight, style, text) {
617
- setFont(family, weight, style);
481
+ function draw(family, weight, style, text, size = BAKE_SIZE) {
482
+ setFont(family, weight, style, size);
618
483
  const m = ctx.measureText(text);
619
484
  const advance = m.width;
620
485
  const inkLeft = Math.ceil(m.actualBoundingBoxLeft ?? 0);
621
486
  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);
487
+ const inkAscent = Math.ceil(m.actualBoundingBoxAscent ?? size * 0.8);
488
+ const inkDescent = Math.ceil(m.actualBoundingBoxDescent ?? size * 0.2);
624
489
  const inkW = inkLeft + inkRight;
625
490
  const inkH = inkAscent + inkDescent;
626
491
  if (inkW <= 0 || inkH <= 0) {
@@ -631,7 +496,7 @@ function createCanvasRasterizer() {
631
496
  if (canvas.width < w || canvas.height < h) {
632
497
  canvas.width = Math.max(canvas.width, w);
633
498
  canvas.height = Math.max(canvas.height, h);
634
- setFont(family, weight, style);
499
+ setFont(family, weight, style, size);
635
500
  }
636
501
  ctx.clearRect(0, 0, w, h);
637
502
  ctx.fillStyle = "#fff";
@@ -661,14 +526,90 @@ function createCanvasRasterizer() {
661
526
  for (let i = 0; i < alpha.length; i++) alpha[i] = data[i * 4 + 3];
662
527
  return { ...box, alpha };
663
528
  },
664
- rasterizeColor(family, weight, style, text) {
665
- const { data, ...box } = draw(family, weight, style, text);
529
+ rasterizeColor(family, weight, style, text, size) {
530
+ const { data, ...box } = draw(family, weight, style, text, size);
666
531
  const rgba = premultipliedColor(data);
667
532
  return rgba && { ...box, rgba };
668
533
  }
669
534
  };
670
535
  }
671
536
 
537
+ // src/dynamic/sharpColor.ts
538
+ var MAX_SHARP_LEVEL = 3;
539
+ var sources = /* @__PURE__ */ new WeakMap();
540
+ var generation2 = 0;
541
+ onSharpPagesEmptied(() => {
542
+ generation2++;
543
+ });
544
+ function addSharpSource(char, base, raster) {
545
+ sources.set(char, { raster, base, levels: [], generation: generation2 });
546
+ }
547
+ function levelsOf(source) {
548
+ if (source.generation !== generation2) {
549
+ source.levels = [];
550
+ source.generation = generation2;
551
+ }
552
+ return source.levels;
553
+ }
554
+ function sharpBakesOf(char) {
555
+ const source = sources.get(char);
556
+ if (!source) return [];
557
+ return levelsOf(source).flatMap((sharp) => sharp ? [sharp.glyph] : []);
558
+ }
559
+ function requestLevel(source, level) {
560
+ const levels = levelsOf(source);
561
+ const known = levels[level];
562
+ if (known !== void 0) return known;
563
+ const k = 2 ** level;
564
+ const raster = source.raster(BAKE_SIZE * k);
565
+ const sharp = raster && {
566
+ level,
567
+ glyph: {
568
+ id: 0,
569
+ x: 0,
570
+ y: 0,
571
+ width: 0,
572
+ height: 0,
573
+ xoffset: raster.left / k,
574
+ yoffset: source.base - raster.top / k,
575
+ xadvance: raster.advance / k,
576
+ page: -1,
577
+ color: true
578
+ }
579
+ };
580
+ levels[level] = sharp;
581
+ if (sharp && raster) requestBake(sharp.glyph, raster, true);
582
+ return sharp;
583
+ }
584
+ function sharperColorGlyph(char, scale) {
585
+ const source = sources.get(char);
586
+ if (!source || !(scale > 1)) return null;
587
+ const wanted = Math.min(MAX_SHARP_LEVEL, Math.ceil(Math.log2(scale)));
588
+ requestLevel(source, wanted);
589
+ const levels = levelsOf(source);
590
+ for (let level = wanted; level > 0; level--) {
591
+ const sharp = levels[level];
592
+ if (sharp && sharp.glyph.page >= 0) return sharp;
593
+ }
594
+ return null;
595
+ }
596
+
597
+ // src/dynamic/fontLoads.ts
598
+ function familyKey(family) {
599
+ return family.trim().replace(/^(["'])(.*)\1$/, "$2").toLowerCase();
600
+ }
601
+ function watchFontLoads(onLoaded) {
602
+ const fonts = typeof document === "undefined" ? void 0 : document.fonts;
603
+ if (!fonts?.addEventListener) return () => {
604
+ };
605
+ const listener = (event) => {
606
+ const faces4 = event.fontfaces ?? [];
607
+ if (faces4.length > 0) onLoaded(faces4.map((face) => face.family));
608
+ };
609
+ fonts.addEventListener("loadingdone", listener);
610
+ return () => fonts.removeEventListener("loadingdone", listener);
611
+ }
612
+
672
613
  // src/dynamic/dynamicAtlas.ts
673
614
  var canvasFamilies = /* @__PURE__ */ new Set();
674
615
  var autoEnrolledFamilies = /* @__PURE__ */ new Set();
@@ -770,7 +711,10 @@ function getDynamicFace(family, weight, style) {
770
711
  const raster = r.rasterizeColor?.(family, weight, style, text) ?? null;
771
712
  const char = raster && { ...record(text.codePointAt(0), raster), color: true };
772
713
  clusters.set(text, char);
773
- if (char && raster) requestBake(char, raster);
714
+ if (char && raster) {
715
+ requestBake(char, raster);
716
+ addSharpSource(char, font.common.base, (size) => r.rasterizeColor?.(family, weight, style, text, size) ?? null);
717
+ }
774
718
  return char;
775
719
  }
776
720
  };
@@ -797,7 +741,11 @@ function refreshFamilies(families) {
797
741
  for (const face of faces2.values()) {
798
742
  if (keys && !face.family.split(",").some((name) => keys.has(familyKey(name)))) continue;
799
743
  for (const char of face.font.chars) dropped.add(char);
800
- for (const char of face.clusters.values()) if (char) dropped.add(char);
744
+ for (const char of face.clusters.values()) {
745
+ if (!char) continue;
746
+ dropped.add(char);
747
+ for (const sharp of sharpBakesOf(char)) dropped.add(sharp);
748
+ }
801
749
  measureFace(face);
802
750
  measured++;
803
751
  }
@@ -1884,6 +1832,6 @@ function _resetDomFacesForTests() {
1884
1832
  warned3.clear();
1885
1833
  }
1886
1834
 
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
1835
+ export { BAKE_SIZE, DEFAULT_BAKE_BUDGET, MAX_SHARP_LEVEL, PAGE_SIZE, SDF_RADIUS, __setGlyphRasterizerForTests, _getPagesForTests, _resetDomFacesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests, claimFallbackWarning, colorPageTextureId, createOpenTypeParser, cssFontFamily, cssFontFamilyLoading, dynamicPageTextureId, ensureFontTexture, faceGeneration, faceMetricsFor, fallbackWarningClaimed, fontOutlineRegistry, fontPending, fontRegistry, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFontWeights, listFonts, loadFontOutlines, markAllFontsNotUploaded, outlineMetrics, outlineStatus, refreshCanvasFont, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, sharpColorPageTextureId, sharperColorGlyph, subscribeGlyphReady, syncColorPageTexture, syncDynamicPageTexture, syncSharpColorPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines, warmFonts };
1836
+ //# sourceMappingURL=chunk-RECWJ5LR.js.map
1837
+ //# sourceMappingURL=chunk-RECWJ5LR.js.map