@weasel-js/font 1.10.1 → 1.11.1
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 +8 -0
- package/dist/{chunk-NM6K6M4Y.js → chunk-GQJL6VVQ.js} +191 -216
- package/dist/chunk-GQJL6VVQ.js.map +1 -0
- package/dist/chunk-XQPRAGHF.js +161 -0
- package/dist/chunk-XQPRAGHF.js.map +1 -0
- package/dist/fontStyle-D1y21YZ9.d.ts +217 -0
- package/dist/index.d.ts +57 -12
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/dist/metrics.d.ts +14 -0
- package/dist/metrics.js +3 -0
- package/dist/metrics.js.map +1 -0
- package/dist/{test-seams-BvULFsbo.d.ts → test-seams-CjRs8Yvp.d.ts} +50 -218
- package/dist/test-seams.d.ts +2 -1
- package/dist/test-seams.js +2 -1
- package/package.json +6 -2
- package/dist/chunk-NM6K6M4Y.js.map +0 -1
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
|
|
426
|
-
var
|
|
427
|
-
var
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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)
|
|
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
|
-
|
|
528
|
-
|
|
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 ??
|
|
623
|
-
const inkDescent = Math.ceil(m.actualBoundingBoxDescent ??
|
|
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)
|
|
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())
|
|
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
|
}
|
|
@@ -1161,7 +1109,7 @@ function parserOf(ns) {
|
|
|
1161
1109
|
const interop = ns;
|
|
1162
1110
|
return interop.default?.parse ?? ns.parse;
|
|
1163
1111
|
}
|
|
1164
|
-
function faceFor(font, gpos) {
|
|
1112
|
+
function faceFor(font, gpos, sfnt) {
|
|
1165
1113
|
const upem = font.unitsPerEm;
|
|
1166
1114
|
const { hhea, post, os2 } = font.tables;
|
|
1167
1115
|
const faceMetrics = faceMetricsFromTables({ unitsPerEm: upem, hhea, post, os2 });
|
|
@@ -1174,6 +1122,7 @@ function faceFor(font, gpos) {
|
|
|
1174
1122
|
if (!index) return null;
|
|
1175
1123
|
return (font.glyphs.get(index).advanceWidth ?? 0) / upem;
|
|
1176
1124
|
},
|
|
1125
|
+
...isSfnt(sfnt) ? { embedding: () => embeddingOf(font, sfnt) } : {},
|
|
1177
1126
|
kernOf(left, right) {
|
|
1178
1127
|
const l = font.charToGlyphIndex(String.fromCodePoint(left));
|
|
1179
1128
|
const r = font.charToGlyphIndex(String.fromCodePoint(right));
|
|
@@ -1189,6 +1138,32 @@ function faceFor(font, gpos) {
|
|
|
1189
1138
|
}
|
|
1190
1139
|
};
|
|
1191
1140
|
}
|
|
1141
|
+
function isSfnt(bytes) {
|
|
1142
|
+
if (bytes.byteLength < 4) return false;
|
|
1143
|
+
const tag2 = new DataView(bytes).getUint32(0);
|
|
1144
|
+
return tag2 === 65536 || tag2 === 1953658213 || tag2 === 1330926671;
|
|
1145
|
+
}
|
|
1146
|
+
var NO_OUTLINE_EMBEDDING = 2 | 512;
|
|
1147
|
+
function embeddingOf(font, sfnt) {
|
|
1148
|
+
const t = font.tables;
|
|
1149
|
+
const englishName = font.getEnglishName?.("postScriptName");
|
|
1150
|
+
return {
|
|
1151
|
+
sfnt,
|
|
1152
|
+
postScriptName: englishName ?? "",
|
|
1153
|
+
unitsPerEm: font.unitsPerEm,
|
|
1154
|
+
outlines: t.cff && !t.glyf ? "cff" : "truetype",
|
|
1155
|
+
embeddable: ((t.os2?.fsType ?? 0) & NO_OUTLINE_EMBEDDING) === 0,
|
|
1156
|
+
glyphIndexOf(cp) {
|
|
1157
|
+
return font.charToGlyphIndex(String.fromCodePoint(cp)) || null;
|
|
1158
|
+
},
|
|
1159
|
+
advanceOfGlyph: (gid) => font.glyphs.get(gid).advanceWidth ?? 0,
|
|
1160
|
+
ascent: font.ascender,
|
|
1161
|
+
descent: font.descender,
|
|
1162
|
+
capHeight: t.os2?.sCapHeight || font.ascender,
|
|
1163
|
+
italicAngle: t.post?.italicAngle ?? 0,
|
|
1164
|
+
bbox: [t.head?.xMin ?? 0, t.head?.yMin ?? 0, t.head?.xMax ?? 0, t.head?.yMax ?? 0]
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1192
1167
|
function createOpenTypeParser(postScriptName) {
|
|
1193
1168
|
return async (bytes) => {
|
|
1194
1169
|
const parse = parserOf(await loadOpenType());
|
|
@@ -1198,7 +1173,7 @@ function createOpenTypeParser(postScriptName) {
|
|
|
1198
1173
|
`no member of the font file is named "${postScriptName}" (it holds ${names2.join(", ")})`
|
|
1199
1174
|
);
|
|
1200
1175
|
}
|
|
1201
|
-
return faceFor(parse(single), gposPairKerning(single));
|
|
1176
|
+
return faceFor(parse(single), gposPairKerning(single), single);
|
|
1202
1177
|
};
|
|
1203
1178
|
}
|
|
1204
1179
|
var openTypeParser = createOpenTypeParser();
|
|
@@ -1884,6 +1859,6 @@ function _resetDomFacesForTests() {
|
|
|
1884
1859
|
warned3.clear();
|
|
1885
1860
|
}
|
|
1886
1861
|
|
|
1887
|
-
export { DEFAULT_BAKE_BUDGET,
|
|
1888
|
-
//# sourceMappingURL=chunk-
|
|
1889
|
-
//# sourceMappingURL=chunk-
|
|
1862
|
+
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 };
|
|
1863
|
+
//# sourceMappingURL=chunk-GQJL6VVQ.js.map
|
|
1864
|
+
//# sourceMappingURL=chunk-GQJL6VVQ.js.map
|