@vyaz/core 0.0.10 → 0.0.11
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/dist/index.browser.js +123 -20
- package/dist/index.js +120 -17
- package/dist/measure/FontMetricsProvider.d.ts +32 -26
- package/package.json +1 -1
package/dist/index.browser.js
CHANGED
|
@@ -18200,12 +18200,31 @@ function normaliseWeight(weight) {
|
|
|
18200
18200
|
return mapped;
|
|
18201
18201
|
return weight;
|
|
18202
18202
|
}
|
|
18203
|
-
function
|
|
18204
|
-
|
|
18203
|
+
function weightToNumber(weight) {
|
|
18204
|
+
const num = parseInt(weight, 10);
|
|
18205
|
+
return isNaN(num) ? 400 : num;
|
|
18206
|
+
}
|
|
18207
|
+
function variantKey(weight, style) {
|
|
18208
|
+
return `${normaliseWeight(weight)}_${style}`;
|
|
18209
|
+
}
|
|
18210
|
+
function nearestWeight(registeredWeights, requested) {
|
|
18211
|
+
if (registeredWeights.length === 0)
|
|
18212
|
+
return null;
|
|
18213
|
+
let best = registeredWeights[0];
|
|
18214
|
+
let bestDist = Math.abs(best - requested);
|
|
18215
|
+
for (let i = 1;i < registeredWeights.length; i++) {
|
|
18216
|
+
const w = registeredWeights[i];
|
|
18217
|
+
const dist = Math.abs(w - requested);
|
|
18218
|
+
if (dist < bestDist || dist === bestDist && w > best) {
|
|
18219
|
+
best = w;
|
|
18220
|
+
bestDist = dist;
|
|
18221
|
+
}
|
|
18222
|
+
}
|
|
18223
|
+
return best;
|
|
18205
18224
|
}
|
|
18206
18225
|
|
|
18207
18226
|
class FontMetricsProvider {
|
|
18208
|
-
|
|
18227
|
+
registry = new Map;
|
|
18209
18228
|
metricsCache = new Map;
|
|
18210
18229
|
mode = "browser";
|
|
18211
18230
|
pendingRegistrations = new Set;
|
|
@@ -18229,7 +18248,7 @@ class FontMetricsProvider {
|
|
|
18229
18248
|
this.mode = mode;
|
|
18230
18249
|
this.metricsCache.clear();
|
|
18231
18250
|
if (mode === "office") {
|
|
18232
|
-
enableOfficeTextMeasure(this.
|
|
18251
|
+
enableOfficeTextMeasure(this._flattenCache());
|
|
18233
18252
|
} else {
|
|
18234
18253
|
disableOfficeTextMeasure();
|
|
18235
18254
|
}
|
|
@@ -18237,6 +18256,15 @@ class FontMetricsProvider {
|
|
|
18237
18256
|
getMode() {
|
|
18238
18257
|
return this.mode;
|
|
18239
18258
|
}
|
|
18259
|
+
_flattenCache() {
|
|
18260
|
+
const flat = new Map;
|
|
18261
|
+
for (const [family, variants] of this.registry) {
|
|
18262
|
+
for (const [vKey, font] of variants) {
|
|
18263
|
+
flat.set(`${family}_${vKey}`, font);
|
|
18264
|
+
}
|
|
18265
|
+
}
|
|
18266
|
+
return flat;
|
|
18267
|
+
}
|
|
18240
18268
|
async registerFont(family, options, source, sourcePath) {
|
|
18241
18269
|
const promise = this._registerFontInternal(family, options, source, sourcePath);
|
|
18242
18270
|
this.pendingRegistrations.add(promise);
|
|
@@ -18254,9 +18282,18 @@ class FontMetricsProvider {
|
|
|
18254
18282
|
source = await getFontBuffer2(source);
|
|
18255
18283
|
}
|
|
18256
18284
|
const font = await createFontFace2(source);
|
|
18257
|
-
const
|
|
18258
|
-
|
|
18259
|
-
|
|
18285
|
+
const w = options.weight || "normal";
|
|
18286
|
+
const s = options.style || "normal";
|
|
18287
|
+
const vKey = variantKey(w, s);
|
|
18288
|
+
if (!this.registry.has(family)) {
|
|
18289
|
+
this.registry.set(family, new Map);
|
|
18290
|
+
}
|
|
18291
|
+
this.registry.get(family).set(vKey, font);
|
|
18292
|
+
for (const key of this.metricsCache.keys()) {
|
|
18293
|
+
if (key.startsWith(family)) {
|
|
18294
|
+
this.metricsCache.delete(key);
|
|
18295
|
+
}
|
|
18296
|
+
}
|
|
18260
18297
|
if (sourcePath) {
|
|
18261
18298
|
registerCanvasFont2(sourcePath, family);
|
|
18262
18299
|
}
|
|
@@ -18264,22 +18301,88 @@ class FontMetricsProvider {
|
|
|
18264
18301
|
async waitForPendingRegistrations() {
|
|
18265
18302
|
await Promise.all(this.pendingRegistrations);
|
|
18266
18303
|
}
|
|
18304
|
+
getRegisteredFamilies() {
|
|
18305
|
+
return Array.from(this.registry.keys());
|
|
18306
|
+
}
|
|
18307
|
+
getFamilyVariants(family) {
|
|
18308
|
+
const variants = this.registry.get(family);
|
|
18309
|
+
return variants ? Array.from(variants.keys()) : [];
|
|
18310
|
+
}
|
|
18267
18311
|
getFont(family, weight = "normal", style = "normal") {
|
|
18268
|
-
const
|
|
18269
|
-
|
|
18312
|
+
const resolved = this._resolveFont(family, weight, style);
|
|
18313
|
+
if (!resolved)
|
|
18314
|
+
return;
|
|
18315
|
+
return resolved.font;
|
|
18316
|
+
}
|
|
18317
|
+
_resolveFont(family, weight, style) {
|
|
18318
|
+
const variants = this.registry.get(family);
|
|
18319
|
+
if (!variants || variants.size === 0)
|
|
18320
|
+
return null;
|
|
18321
|
+
const normalisedW = normaliseWeight(weight);
|
|
18322
|
+
const normalisedS = style;
|
|
18323
|
+
let key = variantKey(normalisedW, normalisedS);
|
|
18324
|
+
let font = variants.get(key);
|
|
18325
|
+
if (font)
|
|
18326
|
+
return { font, resolvedWeight: normalisedW, resolvedStyle: normalisedS };
|
|
18327
|
+
if (normalisedS === "italic") {
|
|
18328
|
+
key = variantKey(normalisedW, "normal");
|
|
18329
|
+
font = variants.get(key);
|
|
18330
|
+
if (font)
|
|
18331
|
+
return { font, resolvedWeight: normalisedW, resolvedStyle: "normal" };
|
|
18332
|
+
}
|
|
18333
|
+
const sameStyleWeights = [];
|
|
18334
|
+
for (const vKey of variants.keys()) {
|
|
18335
|
+
const [_w, _s] = vKey.split("_");
|
|
18336
|
+
if (_s === normalisedS) {
|
|
18337
|
+
sameStyleWeights.push(weightToNumber(_w));
|
|
18338
|
+
}
|
|
18339
|
+
}
|
|
18340
|
+
const sameStyleNearest = nearestWeight(sameStyleWeights, weightToNumber(normalisedW));
|
|
18341
|
+
if (sameStyleNearest !== null) {
|
|
18342
|
+
key = variantKey(String(sameStyleNearest), normalisedS);
|
|
18343
|
+
font = variants.get(key);
|
|
18344
|
+
if (font)
|
|
18345
|
+
return { font, resolvedWeight: String(sameStyleNearest), resolvedStyle: normalisedS };
|
|
18346
|
+
}
|
|
18347
|
+
if (normalisedS === "italic") {
|
|
18348
|
+
const normalWeights = [];
|
|
18349
|
+
for (const vKey of variants.keys()) {
|
|
18350
|
+
const [_w, _s] = vKey.split("_");
|
|
18351
|
+
if (_s === "normal") {
|
|
18352
|
+
normalWeights.push(weightToNumber(_w));
|
|
18353
|
+
}
|
|
18354
|
+
}
|
|
18355
|
+
const normalNearest = nearestWeight(normalWeights, weightToNumber(normalisedW));
|
|
18356
|
+
if (normalNearest !== null) {
|
|
18357
|
+
key = variantKey(String(normalNearest), "normal");
|
|
18358
|
+
font = variants.get(key);
|
|
18359
|
+
if (font)
|
|
18360
|
+
return { font, resolvedWeight: String(normalNearest), resolvedStyle: "normal" };
|
|
18361
|
+
}
|
|
18362
|
+
}
|
|
18363
|
+
const firstKey = variants.keys().next().value;
|
|
18364
|
+
if (firstKey) {
|
|
18365
|
+
font = variants.get(firstKey);
|
|
18366
|
+
if (font) {
|
|
18367
|
+
const [_w, _s] = firstKey.split("_");
|
|
18368
|
+
return { font, resolvedWeight: _w, resolvedStyle: _s };
|
|
18369
|
+
}
|
|
18370
|
+
}
|
|
18371
|
+
return null;
|
|
18270
18372
|
}
|
|
18271
18373
|
getMetrics(fontFamily, fontSize, weight = "normal", style = "normal") {
|
|
18272
18374
|
const _process2 = typeof globalThis !== "undefined" ? globalThis.process : undefined;
|
|
18273
18375
|
if (this.pendingRegistrations.size > 0 && _process2?.env?.NODE_ENV !== "production") {
|
|
18274
18376
|
console.warn("[vyaz] getMetrics() called while registerFont() promises are still pending. Wait for registerFont() to resolve before layout to avoid inaccurate metrics. Use fontMetricsProvider.waitForPendingRegistrations() if needed.");
|
|
18275
18377
|
}
|
|
18276
|
-
const
|
|
18277
|
-
const metricsKey = `${
|
|
18378
|
+
const normalisedW = normaliseWeight(weight);
|
|
18379
|
+
const metricsKey = `${fontFamily}_${normalisedW}_${style}_${fontSize}_${this.mode}`;
|
|
18278
18380
|
const cached = this.metricsCache.get(metricsKey);
|
|
18279
18381
|
if (cached)
|
|
18280
18382
|
return cached;
|
|
18281
|
-
const
|
|
18282
|
-
if (
|
|
18383
|
+
const resolved = this._resolveFont(fontFamily, weight, style);
|
|
18384
|
+
if (resolved) {
|
|
18385
|
+
const font = resolved.font;
|
|
18283
18386
|
const scale = fontSize / font.unitsPerEm;
|
|
18284
18387
|
let ascent;
|
|
18285
18388
|
let descent;
|
|
@@ -18303,7 +18406,7 @@ class FontMetricsProvider {
|
|
|
18303
18406
|
this.metricsCache.set(metricsKey, metrics);
|
|
18304
18407
|
return metrics;
|
|
18305
18408
|
}
|
|
18306
|
-
if (this.
|
|
18409
|
+
if (this.registry.has(fontFamily)) {
|
|
18307
18410
|
throw new FontNotFoundError(fontFamily, weight, style);
|
|
18308
18411
|
}
|
|
18309
18412
|
if (typeof document !== "undefined") {
|
|
@@ -24368,15 +24471,15 @@ function isLineStartCursor(cursor) {
|
|
|
24368
24471
|
return cursor.segmentIndex === 0 && cursor.graphemeIndex === 0;
|
|
24369
24472
|
}
|
|
24370
24473
|
function getCollapsedSpaceWidth(font, letterSpacing, cache) {
|
|
24371
|
-
const
|
|
24372
|
-
const cached = cache.get(
|
|
24474
|
+
const cacheKey2 = `${font}\x00${letterSpacing}`;
|
|
24475
|
+
const cached = cache.get(cacheKey2);
|
|
24373
24476
|
if (cached !== undefined)
|
|
24374
24477
|
return cached;
|
|
24375
24478
|
const options = letterSpacing === 0 ? undefined : { letterSpacing };
|
|
24376
24479
|
const joinedWidth = measureNaturalWidth(prepareWithSegments("A A", font, options));
|
|
24377
24480
|
const compactWidth = measureNaturalWidth(prepareWithSegments("AA", font, options));
|
|
24378
24481
|
const collapsedWidth = Math.max(0, joinedWidth - compactWidth);
|
|
24379
|
-
cache.set(
|
|
24482
|
+
cache.set(cacheKey2, collapsedWidth);
|
|
24380
24483
|
return collapsedWidth;
|
|
24381
24484
|
}
|
|
24382
24485
|
function prepareWholeItemLine(prepared) {
|
|
@@ -24621,11 +24724,11 @@ class ParagraphLayoutEngine {
|
|
|
24621
24724
|
layout(paragraph, maxWidth, yOffset = 0, fontProvider, listStyle, listIndex, listMarkerWidth) {
|
|
24622
24725
|
const provider = fontProvider || fontMetricsProvider;
|
|
24623
24726
|
const items = compileParagraph(paragraph);
|
|
24624
|
-
const
|
|
24625
|
-
let prepared = this.preparedCache.get(
|
|
24727
|
+
const cacheKey2 = JSON.stringify(paragraph);
|
|
24728
|
+
let prepared = this.preparedCache.get(cacheKey2);
|
|
24626
24729
|
if (!prepared) {
|
|
24627
24730
|
prepared = prepareRichInline(items);
|
|
24628
|
-
this.preparedCache.set(
|
|
24731
|
+
this.preparedCache.set(cacheKey2, prepared);
|
|
24629
24732
|
}
|
|
24630
24733
|
const effectiveMaxWidth = paragraph.style.whiteSpace === "nowrap" ? Infinity : maxWidth;
|
|
24631
24734
|
const pretextLines = [];
|
package/dist/index.js
CHANGED
|
@@ -497,12 +497,31 @@ function normaliseWeight(weight) {
|
|
|
497
497
|
return mapped;
|
|
498
498
|
return weight;
|
|
499
499
|
}
|
|
500
|
-
function
|
|
501
|
-
|
|
500
|
+
function weightToNumber(weight) {
|
|
501
|
+
const num = parseInt(weight, 10);
|
|
502
|
+
return isNaN(num) ? 400 : num;
|
|
503
|
+
}
|
|
504
|
+
function variantKey(weight, style) {
|
|
505
|
+
return `${normaliseWeight(weight)}_${style}`;
|
|
506
|
+
}
|
|
507
|
+
function nearestWeight(registeredWeights, requested) {
|
|
508
|
+
if (registeredWeights.length === 0)
|
|
509
|
+
return null;
|
|
510
|
+
let best = registeredWeights[0];
|
|
511
|
+
let bestDist = Math.abs(best - requested);
|
|
512
|
+
for (let i = 1;i < registeredWeights.length; i++) {
|
|
513
|
+
const w = registeredWeights[i];
|
|
514
|
+
const dist = Math.abs(w - requested);
|
|
515
|
+
if (dist < bestDist || dist === bestDist && w > best) {
|
|
516
|
+
best = w;
|
|
517
|
+
bestDist = dist;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return best;
|
|
502
521
|
}
|
|
503
522
|
|
|
504
523
|
class FontMetricsProvider {
|
|
505
|
-
|
|
524
|
+
registry = new Map;
|
|
506
525
|
metricsCache = new Map;
|
|
507
526
|
mode = "browser";
|
|
508
527
|
pendingRegistrations = new Set;
|
|
@@ -526,7 +545,7 @@ class FontMetricsProvider {
|
|
|
526
545
|
this.mode = mode;
|
|
527
546
|
this.metricsCache.clear();
|
|
528
547
|
if (mode === "office") {
|
|
529
|
-
enableOfficeTextMeasure(this.
|
|
548
|
+
enableOfficeTextMeasure(this._flattenCache());
|
|
530
549
|
} else {
|
|
531
550
|
disableOfficeTextMeasure();
|
|
532
551
|
}
|
|
@@ -534,6 +553,15 @@ class FontMetricsProvider {
|
|
|
534
553
|
getMode() {
|
|
535
554
|
return this.mode;
|
|
536
555
|
}
|
|
556
|
+
_flattenCache() {
|
|
557
|
+
const flat = new Map;
|
|
558
|
+
for (const [family, variants] of this.registry) {
|
|
559
|
+
for (const [vKey, font] of variants) {
|
|
560
|
+
flat.set(`${family}_${vKey}`, font);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return flat;
|
|
564
|
+
}
|
|
537
565
|
async registerFont(family, options, source, sourcePath) {
|
|
538
566
|
const promise = this._registerFontInternal(family, options, source, sourcePath);
|
|
539
567
|
this.pendingRegistrations.add(promise);
|
|
@@ -551,9 +579,18 @@ class FontMetricsProvider {
|
|
|
551
579
|
source = await getFontBuffer2(source);
|
|
552
580
|
}
|
|
553
581
|
const font = await createFontFace2(source);
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
582
|
+
const w = options.weight || "normal";
|
|
583
|
+
const s = options.style || "normal";
|
|
584
|
+
const vKey = variantKey(w, s);
|
|
585
|
+
if (!this.registry.has(family)) {
|
|
586
|
+
this.registry.set(family, new Map);
|
|
587
|
+
}
|
|
588
|
+
this.registry.get(family).set(vKey, font);
|
|
589
|
+
for (const key of this.metricsCache.keys()) {
|
|
590
|
+
if (key.startsWith(family)) {
|
|
591
|
+
this.metricsCache.delete(key);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
557
594
|
if (sourcePath) {
|
|
558
595
|
registerCanvasFont2(sourcePath, family);
|
|
559
596
|
}
|
|
@@ -561,22 +598,88 @@ class FontMetricsProvider {
|
|
|
561
598
|
async waitForPendingRegistrations() {
|
|
562
599
|
await Promise.all(this.pendingRegistrations);
|
|
563
600
|
}
|
|
601
|
+
getRegisteredFamilies() {
|
|
602
|
+
return Array.from(this.registry.keys());
|
|
603
|
+
}
|
|
604
|
+
getFamilyVariants(family) {
|
|
605
|
+
const variants = this.registry.get(family);
|
|
606
|
+
return variants ? Array.from(variants.keys()) : [];
|
|
607
|
+
}
|
|
564
608
|
getFont(family, weight = "normal", style = "normal") {
|
|
565
|
-
const
|
|
566
|
-
|
|
609
|
+
const resolved = this._resolveFont(family, weight, style);
|
|
610
|
+
if (!resolved)
|
|
611
|
+
return;
|
|
612
|
+
return resolved.font;
|
|
613
|
+
}
|
|
614
|
+
_resolveFont(family, weight, style) {
|
|
615
|
+
const variants = this.registry.get(family);
|
|
616
|
+
if (!variants || variants.size === 0)
|
|
617
|
+
return null;
|
|
618
|
+
const normalisedW = normaliseWeight(weight);
|
|
619
|
+
const normalisedS = style;
|
|
620
|
+
let key = variantKey(normalisedW, normalisedS);
|
|
621
|
+
let font = variants.get(key);
|
|
622
|
+
if (font)
|
|
623
|
+
return { font, resolvedWeight: normalisedW, resolvedStyle: normalisedS };
|
|
624
|
+
if (normalisedS === "italic") {
|
|
625
|
+
key = variantKey(normalisedW, "normal");
|
|
626
|
+
font = variants.get(key);
|
|
627
|
+
if (font)
|
|
628
|
+
return { font, resolvedWeight: normalisedW, resolvedStyle: "normal" };
|
|
629
|
+
}
|
|
630
|
+
const sameStyleWeights = [];
|
|
631
|
+
for (const vKey of variants.keys()) {
|
|
632
|
+
const [_w, _s] = vKey.split("_");
|
|
633
|
+
if (_s === normalisedS) {
|
|
634
|
+
sameStyleWeights.push(weightToNumber(_w));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const sameStyleNearest = nearestWeight(sameStyleWeights, weightToNumber(normalisedW));
|
|
638
|
+
if (sameStyleNearest !== null) {
|
|
639
|
+
key = variantKey(String(sameStyleNearest), normalisedS);
|
|
640
|
+
font = variants.get(key);
|
|
641
|
+
if (font)
|
|
642
|
+
return { font, resolvedWeight: String(sameStyleNearest), resolvedStyle: normalisedS };
|
|
643
|
+
}
|
|
644
|
+
if (normalisedS === "italic") {
|
|
645
|
+
const normalWeights = [];
|
|
646
|
+
for (const vKey of variants.keys()) {
|
|
647
|
+
const [_w, _s] = vKey.split("_");
|
|
648
|
+
if (_s === "normal") {
|
|
649
|
+
normalWeights.push(weightToNumber(_w));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const normalNearest = nearestWeight(normalWeights, weightToNumber(normalisedW));
|
|
653
|
+
if (normalNearest !== null) {
|
|
654
|
+
key = variantKey(String(normalNearest), "normal");
|
|
655
|
+
font = variants.get(key);
|
|
656
|
+
if (font)
|
|
657
|
+
return { font, resolvedWeight: String(normalNearest), resolvedStyle: "normal" };
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const firstKey = variants.keys().next().value;
|
|
661
|
+
if (firstKey) {
|
|
662
|
+
font = variants.get(firstKey);
|
|
663
|
+
if (font) {
|
|
664
|
+
const [_w, _s] = firstKey.split("_");
|
|
665
|
+
return { font, resolvedWeight: _w, resolvedStyle: _s };
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return null;
|
|
567
669
|
}
|
|
568
670
|
getMetrics(fontFamily, fontSize, weight = "normal", style = "normal") {
|
|
569
671
|
const _process2 = typeof globalThis !== "undefined" ? globalThis.process : undefined;
|
|
570
672
|
if (this.pendingRegistrations.size > 0 && _process2?.env?.NODE_ENV !== "production") {
|
|
571
673
|
console.warn("[vyaz] getMetrics() called while registerFont() promises are still pending. Wait for registerFont() to resolve before layout to avoid inaccurate metrics. Use fontMetricsProvider.waitForPendingRegistrations() if needed.");
|
|
572
674
|
}
|
|
573
|
-
const
|
|
574
|
-
const metricsKey = `${
|
|
675
|
+
const normalisedW = normaliseWeight(weight);
|
|
676
|
+
const metricsKey = `${fontFamily}_${normalisedW}_${style}_${fontSize}_${this.mode}`;
|
|
575
677
|
const cached = this.metricsCache.get(metricsKey);
|
|
576
678
|
if (cached)
|
|
577
679
|
return cached;
|
|
578
|
-
const
|
|
579
|
-
if (
|
|
680
|
+
const resolved = this._resolveFont(fontFamily, weight, style);
|
|
681
|
+
if (resolved) {
|
|
682
|
+
const font = resolved.font;
|
|
580
683
|
const scale = fontSize / font.unitsPerEm;
|
|
581
684
|
let ascent;
|
|
582
685
|
let descent;
|
|
@@ -600,7 +703,7 @@ class FontMetricsProvider {
|
|
|
600
703
|
this.metricsCache.set(metricsKey, metrics);
|
|
601
704
|
return metrics;
|
|
602
705
|
}
|
|
603
|
-
if (this.
|
|
706
|
+
if (this.registry.has(fontFamily)) {
|
|
604
707
|
throw new FontNotFoundError(fontFamily, weight, style);
|
|
605
708
|
}
|
|
606
709
|
if (typeof document !== "undefined") {
|
|
@@ -1170,11 +1273,11 @@ class ParagraphLayoutEngine {
|
|
|
1170
1273
|
layout(paragraph, maxWidth, yOffset = 0, fontProvider, listStyle, listIndex, listMarkerWidth) {
|
|
1171
1274
|
const provider = fontProvider || fontMetricsProvider;
|
|
1172
1275
|
const items = compileParagraph(paragraph);
|
|
1173
|
-
const
|
|
1174
|
-
let prepared = this.preparedCache.get(
|
|
1276
|
+
const cacheKey2 = JSON.stringify(paragraph);
|
|
1277
|
+
let prepared = this.preparedCache.get(cacheKey2);
|
|
1175
1278
|
if (!prepared) {
|
|
1176
1279
|
prepared = prepareRichInline(items);
|
|
1177
|
-
this.preparedCache.set(
|
|
1280
|
+
this.preparedCache.set(cacheKey2, prepared);
|
|
1178
1281
|
}
|
|
1179
1282
|
const effectiveMaxWidth = paragraph.style.whiteSpace === "nowrap" ? Infinity : maxWidth;
|
|
1180
1283
|
const pretextLines = [];
|
|
@@ -2,63 +2,69 @@
|
|
|
2
2
|
* FontMetricsProvider.ts — isomorphic font metrics provider.
|
|
3
3
|
*
|
|
4
4
|
* Strategy (priority):
|
|
5
|
-
* 1. FontEngine (fontkit) — from registered buffer
|
|
6
|
-
* - 'browser' mode: hhea.ascender / hhea.descender
|
|
7
|
-
* - 'office' mode: OS/2.usWinAscent / OS/2.usWinDescent
|
|
5
|
+
* 1. FontEngine (fontkit) — from registered buffer, with smart weight fallback
|
|
8
6
|
* 2. Canvas TextMetrics (browser fallback when fontkit unavailable)
|
|
9
7
|
*
|
|
10
|
-
*
|
|
8
|
+
* Key features:
|
|
9
|
+
* - Nested registry: family → variants (weight + style)
|
|
10
|
+
* - Smart weight fallback: if exact weight not found, picks closest
|
|
11
|
+
* - Style fallback: if exact style not found, falls back to 'normal'
|
|
12
|
+
* - Tracks pending registrations to warn about race conditions
|
|
13
|
+
* - Reusable off-screen canvas for Canvas TextMetrics fallback
|
|
11
14
|
*/
|
|
12
15
|
import type { FontMetrics, IFontMetricsProvider } from '../types/FontTypes.js';
|
|
13
16
|
import type { FontFace } from './FontEngine.js';
|
|
14
17
|
/**
|
|
15
18
|
* Factor used when a glyph is not found in the font.
|
|
16
19
|
* Multiplied by fontSize to estimate the missing glyph width.
|
|
17
|
-
* Used across all measurement code paths (ParagraphLayoutEngine, canvas-polyfill).
|
|
18
20
|
*/
|
|
19
21
|
export declare const MISSING_GLYPH_FACTOR = 0.5;
|
|
20
22
|
export declare class FontMetricsProvider implements IFontMetricsProvider {
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Nested registry: family → variantKey → FontFace.
|
|
25
|
+
* Example:
|
|
26
|
+
* "Roboto" → { "400_normal": FontFace, "700_normal": FontFace, "400_italic": FontFace }
|
|
27
|
+
*/
|
|
28
|
+
private registry;
|
|
29
|
+
/** Metrics cache keyed by variantKey_fontSize_mode */
|
|
23
30
|
private metricsCache;
|
|
24
31
|
private mode;
|
|
25
|
-
/** Set of in-flight registerFont() promises for race-condition guarding */
|
|
26
32
|
private pendingRegistrations;
|
|
27
33
|
private _measureCanvas;
|
|
28
34
|
private _measureCtx;
|
|
29
|
-
/** Get or create a reusable canvas context for Canvas TextMetrics. */
|
|
30
35
|
private _getMeasureContext;
|
|
31
36
|
setMode(mode: 'browser' | 'office'): void;
|
|
32
37
|
getMode(): 'browser' | 'office';
|
|
33
38
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* In both Node.js and browser the font is loaded via FontEngine.
|
|
37
|
-
* In the browser the caller must provide font bytes (e.g. fetched via
|
|
38
|
-
* `getFontBuffer()` from `../utils/font.js`).
|
|
39
|
-
*
|
|
40
|
-
* Tracks pending registrations to detect race conditions where
|
|
41
|
-
* getMetrics() is called before registration completes.
|
|
42
|
-
*
|
|
43
|
-
* @param source Font file bytes (ArrayBuffer / Uint8Array), or a URL string
|
|
44
|
-
* @param sourcePath Optional filesystem path (used for @napi-rs/canvas in Node.js)
|
|
39
|
+
* Flatten the nested registry into a single map for office mode.
|
|
40
|
+
* Office mode needs key → FontFace lookup by family_weight_style.
|
|
45
41
|
*/
|
|
42
|
+
private _flattenCache;
|
|
46
43
|
registerFont(family: string, options: {
|
|
47
44
|
weight?: string;
|
|
48
45
|
style?: string;
|
|
49
46
|
}, source: string | ArrayBuffer | Uint8Array, sourcePath?: string): Promise<void>;
|
|
50
|
-
/** Internal registration logic — not tracked, exposed for direct use if needed. */
|
|
51
47
|
private _registerFontInternal;
|
|
48
|
+
waitForPendingRegistrations(): Promise<void>;
|
|
52
49
|
/**
|
|
53
|
-
*
|
|
54
|
-
* Useful after a batch of registerFont() calls before layout.
|
|
50
|
+
* List all families registered in the provider.
|
|
55
51
|
*/
|
|
56
|
-
|
|
52
|
+
getRegisteredFamilies(): string[];
|
|
57
53
|
/**
|
|
58
|
-
* Get
|
|
59
|
-
* Returns
|
|
54
|
+
* Get all variant keys registered for a given family.
|
|
55
|
+
* Returns empty array if family not found.
|
|
56
|
+
*/
|
|
57
|
+
getFamilyVariants(family: string): string[];
|
|
58
|
+
/**
|
|
59
|
+
* Get font engine FontFace for per-character calculations.
|
|
60
|
+
* Uses the same smart fallback logic as getMetrics().
|
|
60
61
|
*/
|
|
61
62
|
getFont(family: string, weight?: string, style?: string): FontFace | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a font request with fallback.
|
|
65
|
+
* Returns { font, resolvedWeight, resolvedStyle } or null.
|
|
66
|
+
*/
|
|
67
|
+
private _resolveFont;
|
|
62
68
|
getMetrics(fontFamily: string, fontSize: number, weight?: string, style?: string): FontMetrics;
|
|
63
69
|
}
|
|
64
70
|
/** Singleton */
|