@weasel-js/font 1.9.4 → 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.
- package/README.md +13 -3
- package/dist/{chunk-ERSHQYJL.js → chunk-NM6K6M4Y.js} +460 -242
- package/dist/chunk-NM6K6M4Y.js.map +1 -0
- package/dist/index.d.ts +142 -77
- package/dist/index.js +148 -3
- package/dist/index.js.map +1 -1
- package/dist/{test-seams-BC5Ykf4D.d.ts → test-seams-BvULFsbo.d.ts} +170 -230
- package/dist/test-seams.d.ts +1 -1
- package/dist/test-seams.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-ERSHQYJL.js.map +0 -1
|
@@ -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);
|
|
@@ -136,25 +136,29 @@ function parseBmFont(raw) {
|
|
|
136
136
|
...typeof range === "number" && range > 0 ? { distanceRange: range } : {}
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
-
|
|
140
|
-
// src/glyphReady.ts
|
|
141
|
-
var subscribers = /* @__PURE__ */ new Set();
|
|
139
|
+
var ready = createEmitter();
|
|
142
140
|
var generation = 0;
|
|
143
141
|
function glyphGeneration() {
|
|
144
142
|
return generation;
|
|
145
143
|
}
|
|
146
144
|
function subscribeGlyphReady(cb) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
return ready.subscribe(cb);
|
|
146
|
+
}
|
|
147
|
+
var faces = 0;
|
|
148
|
+
function faceGeneration() {
|
|
149
|
+
return faces;
|
|
151
150
|
}
|
|
152
151
|
function notifyGlyphReady() {
|
|
153
152
|
generation++;
|
|
154
|
-
|
|
153
|
+
faces++;
|
|
154
|
+
ready.emit();
|
|
155
|
+
}
|
|
156
|
+
function notifyGlyphBaked() {
|
|
157
|
+
generation++;
|
|
158
|
+
ready.emit();
|
|
155
159
|
}
|
|
156
160
|
function _clearGlyphReadySubscribers() {
|
|
157
|
-
|
|
161
|
+
ready = createEmitter();
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
// src/fallback.ts
|
|
@@ -182,6 +186,9 @@ function claimFallbackWarning(key) {
|
|
|
182
186
|
warnedFallbacks.add(key);
|
|
183
187
|
return true;
|
|
184
188
|
}
|
|
189
|
+
function fallbackWarningClaimed(key) {
|
|
190
|
+
return warnedFallbacks.has(key);
|
|
191
|
+
}
|
|
185
192
|
function _clearFallbackWarnings() {
|
|
186
193
|
warnedFallbacks.clear();
|
|
187
194
|
}
|
|
@@ -191,26 +198,115 @@ function _resetFallbackForTests() {
|
|
|
191
198
|
warnedFallbacks.clear();
|
|
192
199
|
}
|
|
193
200
|
|
|
194
|
-
// src/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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 = [];
|
|
198
309
|
}
|
|
199
|
-
var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
200
|
-
"serif",
|
|
201
|
-
"sans-serif",
|
|
202
|
-
"monospace",
|
|
203
|
-
"cursive",
|
|
204
|
-
"fantasy",
|
|
205
|
-
"system-ui",
|
|
206
|
-
"ui-serif",
|
|
207
|
-
"ui-sans-serif",
|
|
208
|
-
"ui-monospace",
|
|
209
|
-
"ui-rounded",
|
|
210
|
-
"math",
|
|
211
|
-
"emoji",
|
|
212
|
-
"fangsong"
|
|
213
|
-
]);
|
|
214
310
|
|
|
215
311
|
// src/dynamic/shelfPack.ts
|
|
216
312
|
var ShelfPacker = class {
|
|
@@ -221,13 +317,16 @@ var ShelfPacker = class {
|
|
|
221
317
|
pageSize;
|
|
222
318
|
maxPages;
|
|
223
319
|
pages = [];
|
|
224
|
-
warned = false;
|
|
225
320
|
get pageCount() {
|
|
226
321
|
return this.pages.length;
|
|
227
322
|
}
|
|
228
|
-
/**
|
|
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. */
|
|
229
328
|
alloc(w, h) {
|
|
230
|
-
if (
|
|
329
|
+
if (!this.fits(w, h)) return null;
|
|
231
330
|
for (let p = 0; p < this.pages.length; p++) {
|
|
232
331
|
const spot = this.allocInPage(this.pages[p], w, h);
|
|
233
332
|
if (spot) return { page: p, ...spot };
|
|
@@ -238,7 +337,7 @@ var ShelfPacker = class {
|
|
|
238
337
|
const spot = this.allocInPage(page, w, h);
|
|
239
338
|
if (spot) return { page: this.pages.length - 1, ...spot };
|
|
240
339
|
}
|
|
241
|
-
return
|
|
340
|
+
return null;
|
|
242
341
|
}
|
|
243
342
|
allocInPage(page, w, h) {
|
|
244
343
|
for (const shelf of page.shelves) {
|
|
@@ -256,15 +355,6 @@ var ShelfPacker = class {
|
|
|
256
355
|
}
|
|
257
356
|
return null;
|
|
258
357
|
}
|
|
259
|
-
fail() {
|
|
260
|
-
if (!this.warned) {
|
|
261
|
-
this.warned = true;
|
|
262
|
-
console.warn(
|
|
263
|
-
`weasel DynamicGlyphAtlas: glyph pages full (${this.maxPages} \xD7 ${this.pageSize}\xB2); further dynamic glyphs will not render.`
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
358
|
};
|
|
269
359
|
|
|
270
360
|
// src/dynamic/distanceTransform.ts
|
|
@@ -328,9 +418,179 @@ function alphaToSdf(alpha, width, height, radius, cutoff) {
|
|
|
328
418
|
return out;
|
|
329
419
|
}
|
|
330
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
|
+
|
|
331
575
|
// src/dynamic/glyphRasterizer.ts
|
|
332
576
|
var BAKE_SIZE = 48;
|
|
333
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
|
+
}
|
|
334
594
|
var METRICS_SIZE = 1e3;
|
|
335
595
|
function cssFontString(weight, style, family, size = BAKE_SIZE) {
|
|
336
596
|
return `${style === "italic" ? "italic " : ""}${weight} ${size}px ${cssFamilyName(family)}`;
|
|
@@ -353,6 +613,38 @@ function createCanvasRasterizer() {
|
|
|
353
613
|
ctx.textBaseline = "alphabetic";
|
|
354
614
|
ctx.textAlign = "left";
|
|
355
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
|
+
}
|
|
356
648
|
return {
|
|
357
649
|
faceMetrics(family, weight, style) {
|
|
358
650
|
setFont(family, weight, style, METRICS_SIZE);
|
|
@@ -364,59 +656,33 @@ function createCanvasRasterizer() {
|
|
|
364
656
|
};
|
|
365
657
|
},
|
|
366
658
|
rasterize(family, weight, style, codepoint) {
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
const inkH = inkAscent + inkDescent;
|
|
377
|
-
if (inkW <= 0 || inkH <= 0) {
|
|
378
|
-
return { width: 0, height: 0, alpha: new Uint8ClampedArray(0), left: 0, top: 0, advance };
|
|
379
|
-
}
|
|
380
|
-
const w = inkW + 2 * PAD;
|
|
381
|
-
const h = inkH + 2 * PAD;
|
|
382
|
-
if (canvas.width < w || canvas.height < h) {
|
|
383
|
-
canvas.width = Math.max(canvas.width, w);
|
|
384
|
-
canvas.height = Math.max(canvas.height, h);
|
|
385
|
-
setFont(family, weight, style);
|
|
386
|
-
}
|
|
387
|
-
ctx.clearRect(0, 0, w, h);
|
|
388
|
-
ctx.fillStyle = "#fff";
|
|
389
|
-
ctx.fillText(chStr, PAD + inkLeft, PAD + inkAscent);
|
|
390
|
-
const img = ctx.getImageData(0, 0, w, h);
|
|
391
|
-
const alpha = new Uint8ClampedArray(w * h);
|
|
392
|
-
for (let i = 0; i < alpha.length; i++) alpha[i] = img.data[i * 4 + 3];
|
|
393
|
-
return {
|
|
394
|
-
width: w,
|
|
395
|
-
height: h,
|
|
396
|
-
alpha,
|
|
397
|
-
left: -(inkLeft + PAD),
|
|
398
|
-
top: inkAscent + PAD,
|
|
399
|
-
advance
|
|
400
|
-
};
|
|
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 };
|
|
401
668
|
}
|
|
402
669
|
};
|
|
403
670
|
}
|
|
404
671
|
|
|
405
672
|
// src/dynamic/dynamicAtlas.ts
|
|
406
|
-
var PAGE_SIZE = 1024;
|
|
407
|
-
var MAX_PAGES = 4;
|
|
408
|
-
var SDF_RADIUS = 8;
|
|
409
|
-
var SDF_CUTOFF = 0.5;
|
|
410
|
-
var DEFAULT_BAKE_BUDGET = 16;
|
|
411
673
|
var canvasFamilies = /* @__PURE__ */ new Set();
|
|
412
674
|
var autoEnrolledFamilies = /* @__PURE__ */ new Set();
|
|
413
|
-
var
|
|
414
|
-
var pages = [];
|
|
415
|
-
var packer = new ShelfPacker(PAGE_SIZE, MAX_PAGES);
|
|
416
|
-
var pending = [];
|
|
417
|
-
var flushScheduled = false;
|
|
418
|
-
var budget = DEFAULT_BAKE_BUDGET;
|
|
675
|
+
var faces2 = /* @__PURE__ */ new Map();
|
|
419
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
|
+
});
|
|
420
686
|
function getRasterizer() {
|
|
421
687
|
if (!rasterizer) rasterizer = createCanvasRasterizer();
|
|
422
688
|
return rasterizer;
|
|
@@ -451,162 +717,105 @@ function unregisterCanvasFont(family) {
|
|
|
451
717
|
if (!canvasFamilies.has(family)) return;
|
|
452
718
|
canvasFamilies.delete(family);
|
|
453
719
|
autoEnrolledFamilies.delete(family);
|
|
454
|
-
for (const key of [...
|
|
455
|
-
if (
|
|
720
|
+
for (const key of [...faces2.keys()]) {
|
|
721
|
+
if (faces2.get(key).family === family) faces2.delete(key);
|
|
456
722
|
}
|
|
457
723
|
notifyGlyphReady();
|
|
458
724
|
}
|
|
459
725
|
function getDynamicFace(family, weight, style) {
|
|
460
726
|
const key = `${family}|${weight}|${style}`;
|
|
461
|
-
const existing =
|
|
727
|
+
const existing = faces2.get(key);
|
|
462
728
|
if (existing) return existing;
|
|
463
729
|
const r = getRasterizer();
|
|
464
|
-
const metrics = r.faceMetrics(family, weight, style);
|
|
465
|
-
const base = Math.round(metrics.ascent);
|
|
466
730
|
const font = {
|
|
467
731
|
info: { face: family, size: BAKE_SIZE },
|
|
468
|
-
common: {
|
|
469
|
-
lineHeight: Math.round(metrics.ascent + metrics.descent),
|
|
470
|
-
base,
|
|
471
|
-
scaleW: PAGE_SIZE,
|
|
472
|
-
scaleH: PAGE_SIZE
|
|
473
|
-
},
|
|
732
|
+
common: { lineHeight: 0, base: 0, scaleW: PAGE_SIZE, scaleH: PAGE_SIZE },
|
|
474
733
|
chars: [],
|
|
475
734
|
kernings: [],
|
|
476
735
|
// no kerning in v1 (measured-pair kerning is future work)
|
|
477
736
|
charMap: /* @__PURE__ */ new Map(),
|
|
478
|
-
kerningMap: /* @__PURE__ */ new Map()
|
|
479
|
-
// Unrounded, unlike `common`: layout places the baseline from these.
|
|
480
|
-
faceMetrics: { ascent: metrics.ascent / BAKE_SIZE, descent: metrics.descent / BAKE_SIZE }
|
|
737
|
+
kerningMap: /* @__PURE__ */ new Map()
|
|
481
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();
|
|
482
751
|
const face = {
|
|
483
752
|
family,
|
|
484
753
|
weight,
|
|
485
754
|
style,
|
|
486
755
|
font,
|
|
756
|
+
clusters,
|
|
487
757
|
requestGlyph(cp) {
|
|
488
758
|
const cached = font.charMap.get(cp);
|
|
489
759
|
if (cached) return cached;
|
|
490
760
|
const raster = r.rasterize(family, weight, style, cp);
|
|
491
|
-
const char =
|
|
492
|
-
id: cp,
|
|
493
|
-
x: 0,
|
|
494
|
-
y: 0,
|
|
495
|
-
width: 0,
|
|
496
|
-
height: 0,
|
|
497
|
-
xoffset: raster.left,
|
|
498
|
-
yoffset: base - raster.top,
|
|
499
|
-
xadvance: raster.advance,
|
|
500
|
-
page: -1
|
|
501
|
-
};
|
|
761
|
+
const char = record(cp, raster);
|
|
502
762
|
font.charMap.set(cp, char);
|
|
503
763
|
font.chars.push(char);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
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);
|
|
515
774
|
return char;
|
|
516
775
|
}
|
|
517
776
|
};
|
|
518
|
-
|
|
777
|
+
measureFace(face);
|
|
778
|
+
faces2.set(key, face);
|
|
779
|
+
stopWatchingLoads ??= watchFontLoads(refreshFamilies);
|
|
519
780
|
return face;
|
|
520
781
|
}
|
|
521
|
-
function
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
flushScheduled = true;
|
|
546
|
-
setTimeout(flushPending, 0);
|
|
547
|
-
}
|
|
548
|
-
function flushPending() {
|
|
549
|
-
flushScheduled = false;
|
|
550
|
-
let n = 0;
|
|
551
|
-
while (pending.length > 0 && n < DEFAULT_BAKE_BUDGET) {
|
|
552
|
-
const job = pending.shift();
|
|
553
|
-
if (job.char.page !== -1) continue;
|
|
554
|
-
bake(job.char, job.raster);
|
|
555
|
-
n++;
|
|
556
|
-
}
|
|
557
|
-
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);
|
|
558
806
|
notifyGlyphReady();
|
|
559
807
|
}
|
|
560
|
-
function resetBakeBudget(n = DEFAULT_BAKE_BUDGET) {
|
|
561
|
-
budget = n;
|
|
562
|
-
}
|
|
563
|
-
function dynamicPageTextureId(page) {
|
|
564
|
-
return `weasel-dyn-sdf-page-${page}`;
|
|
565
|
-
}
|
|
566
|
-
var uploadedVersions = /* @__PURE__ */ new WeakMap();
|
|
567
|
-
function syncDynamicPageTexture(cache, pageIndex) {
|
|
568
|
-
const page = pages[pageIndex];
|
|
569
|
-
if (!page) return false;
|
|
570
|
-
const id = dynamicPageTextureId(pageIndex);
|
|
571
|
-
let seen = uploadedVersions.get(cache);
|
|
572
|
-
if (!seen) {
|
|
573
|
-
seen = /* @__PURE__ */ new Map();
|
|
574
|
-
uploadedVersions.set(cache, seen);
|
|
575
|
-
}
|
|
576
|
-
if (!cache.has(id)) {
|
|
577
|
-
cache.uploadR8(id, PAGE_SIZE, PAGE_SIZE, page.data);
|
|
578
|
-
seen.set(pageIndex, page.version);
|
|
579
|
-
return true;
|
|
580
|
-
}
|
|
581
|
-
const last = seen.get(pageIndex) ?? 0;
|
|
582
|
-
if (last >= page.version) return true;
|
|
583
|
-
for (const patch of page.patches) {
|
|
584
|
-
if (patch.seq <= last) continue;
|
|
585
|
-
const tight = new Uint8Array(patch.w * patch.h);
|
|
586
|
-
for (let row = 0; row < patch.h; row++) {
|
|
587
|
-
const src = (patch.y + row) * PAGE_SIZE + patch.x;
|
|
588
|
-
tight.set(page.data.subarray(src, src + patch.w), row * patch.w);
|
|
589
|
-
}
|
|
590
|
-
cache.subImageR8(id, patch.x, patch.y, patch.w, patch.h, tight);
|
|
591
|
-
}
|
|
592
|
-
seen.set(pageIndex, page.version);
|
|
593
|
-
return true;
|
|
594
|
-
}
|
|
595
808
|
function __setGlyphRasterizerForTests(r) {
|
|
596
809
|
rasterizer = r;
|
|
597
810
|
}
|
|
598
|
-
function _getPagesForTests() {
|
|
599
|
-
return pages;
|
|
600
|
-
}
|
|
601
811
|
function _resetDynamicFontsForTests() {
|
|
602
812
|
canvasFamilies.clear();
|
|
603
813
|
autoEnrolledFamilies.clear();
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
budget = DEFAULT_BAKE_BUDGET;
|
|
814
|
+
faces2 = /* @__PURE__ */ new Map();
|
|
815
|
+
_resetPagesForTests();
|
|
816
|
+
_resetBakeQueueForTests();
|
|
817
|
+
stopWatchingLoads?.();
|
|
818
|
+
stopWatchingLoads = null;
|
|
610
819
|
_clearGlyphReadySubscribers();
|
|
611
820
|
rasterizer = null;
|
|
612
821
|
}
|
|
@@ -755,30 +964,34 @@ function extractFont(source, view, dirOffset) {
|
|
|
755
964
|
return out;
|
|
756
965
|
}
|
|
757
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) {
|
|
758
974
|
if (isDataForkFont(bytes)) {
|
|
759
975
|
const fonts = sfntResources(bytes);
|
|
760
976
|
if (fonts.length === 0) throw new Error("Datafork TrueType (.dfont) holds no sfnt resource.");
|
|
761
|
-
|
|
977
|
+
return fonts.map((font) => {
|
|
762
978
|
const view2 = new DataView(font);
|
|
763
|
-
return postScriptNameAt(view2, readTableDirectory(view2, 0))
|
|
764
|
-
})
|
|
765
|
-
return { bytes: named ?? fonts[0], matched: named !== void 0 || !postScriptName };
|
|
979
|
+
return { name: postScriptNameAt(view2, readTableDirectory(view2, 0)), bytes: () => font };
|
|
980
|
+
});
|
|
766
981
|
}
|
|
767
|
-
if (!isFontCollection(bytes)) return
|
|
982
|
+
if (!isFontCollection(bytes)) return null;
|
|
768
983
|
const view = new DataView(bytes);
|
|
769
984
|
const numFonts = view.getUint32(8);
|
|
770
985
|
if (numFonts === 0) throw new Error("font collection contains no fonts");
|
|
771
|
-
const
|
|
772
|
-
for (let i = 0; i < numFonts; i++)
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
}
|
|
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
|
+
});
|
|
780
993
|
}
|
|
781
|
-
return
|
|
994
|
+
return members;
|
|
782
995
|
}
|
|
783
996
|
|
|
784
997
|
// src/outline/gposKerning.ts
|
|
@@ -979,7 +1192,12 @@ function faceFor(font, gpos) {
|
|
|
979
1192
|
function createOpenTypeParser(postScriptName) {
|
|
980
1193
|
return async (bytes) => {
|
|
981
1194
|
const parse = parserOf(await loadOpenType());
|
|
982
|
-
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
|
+
}
|
|
983
1201
|
return faceFor(parse(single), gposPairKerning(single));
|
|
984
1202
|
};
|
|
985
1203
|
}
|
|
@@ -1056,7 +1274,7 @@ function glyphOutline(family, weight, style, cp) {
|
|
|
1056
1274
|
const raw = slot.face.glyphD(cp);
|
|
1057
1275
|
d = raw === null ? null : closeContours(raw);
|
|
1058
1276
|
} catch (err) {
|
|
1059
|
-
|
|
1277
|
+
warnOnce2(
|
|
1060
1278
|
`glyph|${slotKey(family, weight, style)}`,
|
|
1061
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.`
|
|
1062
1280
|
);
|
|
@@ -1098,7 +1316,7 @@ async function runLoad(slot) {
|
|
|
1098
1316
|
slot.status = "failed";
|
|
1099
1317
|
slots.bump();
|
|
1100
1318
|
notifyGlyphReady();
|
|
1101
|
-
|
|
1319
|
+
warnOnce2(
|
|
1102
1320
|
`load|${slotKey(slot.family, slot.weight, slot.style)}`,
|
|
1103
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.`
|
|
1104
1322
|
);
|
|
@@ -1134,15 +1352,15 @@ async function readSource(source) {
|
|
|
1134
1352
|
if (resolved instanceof ArrayBuffer) return resolved;
|
|
1135
1353
|
return resolved.arrayBuffer();
|
|
1136
1354
|
}
|
|
1137
|
-
var
|
|
1138
|
-
function
|
|
1139
|
-
if (
|
|
1140
|
-
|
|
1355
|
+
var warned2 = /* @__PURE__ */ new Set();
|
|
1356
|
+
function warnOnce2(key, message) {
|
|
1357
|
+
if (warned2.has(key)) return;
|
|
1358
|
+
warned2.add(key);
|
|
1141
1359
|
console.warn(message);
|
|
1142
1360
|
}
|
|
1143
1361
|
function _resetFontOutlinesForTests() {
|
|
1144
1362
|
slots.clear();
|
|
1145
|
-
|
|
1363
|
+
warned2.clear();
|
|
1146
1364
|
}
|
|
1147
1365
|
var registry = createReflectable();
|
|
1148
1366
|
var fontRegistry = registry.reflection;
|
|
@@ -1567,8 +1785,8 @@ function resolveFontVariantInternal(family, weight, style, suppressWarn) {
|
|
|
1567
1785
|
|
|
1568
1786
|
// src/domFace.ts
|
|
1569
1787
|
var names = /* @__PURE__ */ new Map();
|
|
1570
|
-
var
|
|
1571
|
-
var
|
|
1788
|
+
var faces3 = /* @__PURE__ */ new Map();
|
|
1789
|
+
var warned3 = /* @__PURE__ */ new Set();
|
|
1572
1790
|
function domFonts() {
|
|
1573
1791
|
return typeof FontFace !== "undefined" && typeof document !== "undefined" && !!document.fonts;
|
|
1574
1792
|
}
|
|
@@ -1592,8 +1810,8 @@ function privateFamily(family, variant) {
|
|
|
1592
1810
|
const target = resolved.resolved.family;
|
|
1593
1811
|
const variants = listFontOutlines().filter((f) => f.family === target && f.status !== "failed");
|
|
1594
1812
|
if (variants.length === 0) {
|
|
1595
|
-
if (resolved.entry && !
|
|
1596
|
-
|
|
1813
|
+
if (resolved.entry && !warned3.has(target)) {
|
|
1814
|
+
warned3.add(target);
|
|
1597
1815
|
console.warn(
|
|
1598
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).`
|
|
1599
1817
|
);
|
|
@@ -1617,7 +1835,7 @@ function cssFontFamilyLoading(family, variant = {}) {
|
|
|
1617
1835
|
const weight = variant.weight ?? 400;
|
|
1618
1836
|
const style = variant.style ?? "normal";
|
|
1619
1837
|
const exact = priv.variants.filter((v) => v.weight === weight && v.style === style);
|
|
1620
|
-
const pending2 = (exact.length > 0 ? exact : priv.variants).map((v) =>
|
|
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);
|
|
1621
1839
|
return pending2.length === 0 ? null : Promise.all(pending2).then(() => {
|
|
1622
1840
|
});
|
|
1623
1841
|
}
|
|
@@ -1625,7 +1843,7 @@ function ensureFace(name, family, weight, style, bytes) {
|
|
|
1625
1843
|
const key = `${family}|${weight}|${style}`;
|
|
1626
1844
|
const css = outlineCssSource(family, weight, style);
|
|
1627
1845
|
if (!css) return;
|
|
1628
|
-
const have =
|
|
1846
|
+
const have = faces3.get(key);
|
|
1629
1847
|
if (have?.from === css.registration) return;
|
|
1630
1848
|
if (have?.face) document.fonts.delete(have.face);
|
|
1631
1849
|
let settle2;
|
|
@@ -1641,10 +1859,10 @@ function ensureFace(name, family, weight, style, bytes) {
|
|
|
1641
1859
|
entry.settled = true;
|
|
1642
1860
|
settle2();
|
|
1643
1861
|
};
|
|
1644
|
-
|
|
1862
|
+
faces3.set(key, entry);
|
|
1645
1863
|
const descriptors = { weight: String(weight), style, display: "swap" };
|
|
1646
1864
|
const add = (face) => {
|
|
1647
|
-
if (
|
|
1865
|
+
if (faces3.get(key) !== entry) return finish();
|
|
1648
1866
|
entry.face = face;
|
|
1649
1867
|
document.fonts.add(face);
|
|
1650
1868
|
face.load().then(finish, () => {
|
|
@@ -1659,13 +1877,13 @@ function ensureFace(name, family, weight, style, bytes) {
|
|
|
1659
1877
|
}
|
|
1660
1878
|
function _resetDomFacesForTests() {
|
|
1661
1879
|
if (typeof document !== "undefined" && document.fonts) {
|
|
1662
|
-
for (const { face } of
|
|
1880
|
+
for (const { face } of faces3.values()) if (face) document.fonts.delete(face);
|
|
1663
1881
|
}
|
|
1664
1882
|
names.clear();
|
|
1665
|
-
|
|
1666
|
-
|
|
1883
|
+
faces3.clear();
|
|
1884
|
+
warned3.clear();
|
|
1667
1885
|
}
|
|
1668
1886
|
|
|
1669
|
-
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 };
|
|
1670
|
-
//# sourceMappingURL=chunk-
|
|
1671
|
-
//# sourceMappingURL=chunk-
|
|
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
|