@weasel-js/font 1.2.0 → 1.3.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/dist/{chunk-3RSTZDXH.js → chunk-NRVRDX3F.js} +300 -263
- package/dist/chunk-NRVRDX3F.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{test-seams-DLNsXb8P.d.ts → test-seams-Dx9wi20Z.d.ts} +90 -52
- package/dist/test-seams.d.ts +1 -1
- package/dist/test-seams.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3RSTZDXH.js.map +0 -1
|
@@ -475,6 +475,291 @@ function _resetDynamicFontsForTests() {
|
|
|
475
475
|
rasterizer = null;
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
+
// src/outline/OutlineFace.ts
|
|
479
|
+
var OUTLINE_PRECISION = 5;
|
|
480
|
+
|
|
481
|
+
// src/outline/sfnt.ts
|
|
482
|
+
var SFNT_HEADER_BYTES = 12;
|
|
483
|
+
var TABLE_RECORD_BYTES = 16;
|
|
484
|
+
function tagAt(view, offset) {
|
|
485
|
+
return String.fromCharCode(
|
|
486
|
+
view.getUint8(offset),
|
|
487
|
+
view.getUint8(offset + 1),
|
|
488
|
+
view.getUint8(offset + 2),
|
|
489
|
+
view.getUint8(offset + 3)
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
function isFontCollection(bytes) {
|
|
493
|
+
if (bytes.byteLength < 4) return false;
|
|
494
|
+
return tagAt(new DataView(bytes), 0) === "ttcf";
|
|
495
|
+
}
|
|
496
|
+
var FONT_SIGNATURES = /* @__PURE__ */ new Set([
|
|
497
|
+
"\0\0\0",
|
|
498
|
+
"true",
|
|
499
|
+
"typ1",
|
|
500
|
+
"OTTO",
|
|
501
|
+
"ttcf",
|
|
502
|
+
"wOFF",
|
|
503
|
+
"wOF2"
|
|
504
|
+
]);
|
|
505
|
+
var RESOURCE_HEADER_BYTES = 16;
|
|
506
|
+
function isDataForkFont(bytes) {
|
|
507
|
+
if (bytes.byteLength < RESOURCE_HEADER_BYTES) return false;
|
|
508
|
+
const view = new DataView(bytes);
|
|
509
|
+
if (FONT_SIGNATURES.has(tagAt(view, 0))) return false;
|
|
510
|
+
const dataOffset = view.getUint32(0);
|
|
511
|
+
const mapOffset = view.getUint32(4);
|
|
512
|
+
const dataLength = view.getUint32(8);
|
|
513
|
+
const mapLength = view.getUint32(12);
|
|
514
|
+
return dataOffset >= RESOURCE_HEADER_BYTES && dataOffset + dataLength === mapOffset && mapOffset + mapLength <= bytes.byteLength;
|
|
515
|
+
}
|
|
516
|
+
function readTableDirectory(view, dirOffset) {
|
|
517
|
+
const numTables = view.getUint16(dirOffset + 4);
|
|
518
|
+
const records = [];
|
|
519
|
+
for (let i = 0; i < numTables; i++) {
|
|
520
|
+
const at = dirOffset + SFNT_HEADER_BYTES + i * TABLE_RECORD_BYTES;
|
|
521
|
+
records.push({
|
|
522
|
+
tag: tagAt(view, at),
|
|
523
|
+
checksum: view.getUint32(at + 4),
|
|
524
|
+
offset: view.getUint32(at + 8),
|
|
525
|
+
length: view.getUint32(at + 12)
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return records;
|
|
529
|
+
}
|
|
530
|
+
function postScriptNameAt(view, records) {
|
|
531
|
+
const name = records.find((r) => r.tag === "name");
|
|
532
|
+
if (!name) return null;
|
|
533
|
+
const base = name.offset;
|
|
534
|
+
const count = view.getUint16(base + 2);
|
|
535
|
+
const stringOffset = view.getUint16(base + 4);
|
|
536
|
+
for (let i = 0; i < count; i++) {
|
|
537
|
+
const rec = base + 6 + i * 12;
|
|
538
|
+
if (view.getUint16(rec + 6) !== 6) continue;
|
|
539
|
+
const length = view.getUint16(rec + 8);
|
|
540
|
+
const offset = view.getUint16(rec + 10);
|
|
541
|
+
let out = "";
|
|
542
|
+
for (let b = 0; b < length; b++) {
|
|
543
|
+
const code = view.getUint8(base + stringOffset + offset + b);
|
|
544
|
+
if (code !== 0) out += String.fromCharCode(code);
|
|
545
|
+
}
|
|
546
|
+
if (out.length > 0) return out;
|
|
547
|
+
}
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
function extractFont(source, view, dirOffset) {
|
|
551
|
+
const records = readTableDirectory(view, dirOffset);
|
|
552
|
+
const padded = (n) => n + 3 & -4;
|
|
553
|
+
let total = SFNT_HEADER_BYTES + records.length * TABLE_RECORD_BYTES;
|
|
554
|
+
for (const r of records) total += padded(r.length);
|
|
555
|
+
const out = new ArrayBuffer(total);
|
|
556
|
+
const dst = new DataView(out);
|
|
557
|
+
const dstBytes = new Uint8Array(out);
|
|
558
|
+
const srcBytes = new Uint8Array(source);
|
|
559
|
+
dst.setUint32(0, view.getUint32(dirOffset));
|
|
560
|
+
dst.setUint16(4, records.length);
|
|
561
|
+
const entrySelector = Math.floor(Math.log2(records.length));
|
|
562
|
+
const searchRange = 2 ** entrySelector * 16;
|
|
563
|
+
dst.setUint16(6, searchRange);
|
|
564
|
+
dst.setUint16(8, entrySelector);
|
|
565
|
+
dst.setUint16(10, records.length * 16 - searchRange);
|
|
566
|
+
let cursor = SFNT_HEADER_BYTES + records.length * TABLE_RECORD_BYTES;
|
|
567
|
+
records.forEach((r, i) => {
|
|
568
|
+
const at = SFNT_HEADER_BYTES + i * TABLE_RECORD_BYTES;
|
|
569
|
+
for (let b = 0; b < 4; b++) dst.setUint8(at + b, r.tag.charCodeAt(b));
|
|
570
|
+
dst.setUint32(at + 4, r.checksum);
|
|
571
|
+
dst.setUint32(at + 8, cursor);
|
|
572
|
+
dst.setUint32(at + 12, r.length);
|
|
573
|
+
dstBytes.set(srcBytes.subarray(r.offset, r.offset + r.length), cursor);
|
|
574
|
+
cursor += padded(r.length);
|
|
575
|
+
});
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
578
|
+
function sfntFromCollection(bytes, postScriptName) {
|
|
579
|
+
if (isDataForkFont(bytes)) {
|
|
580
|
+
throw new Error(
|
|
581
|
+
"Datafork TrueType (.dfont) is not supported \u2014 the outline tier reads sfnt tables and a .dfont holds them inside a Macintosh resource map."
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
if (!isFontCollection(bytes)) return { bytes, matched: true };
|
|
585
|
+
const view = new DataView(bytes);
|
|
586
|
+
const numFonts = view.getUint32(8);
|
|
587
|
+
if (numFonts === 0) throw new Error("font collection contains no fonts");
|
|
588
|
+
const offsets = [];
|
|
589
|
+
for (let i = 0; i < numFonts; i++) offsets.push(view.getUint32(12 + i * 4));
|
|
590
|
+
if (postScriptName) {
|
|
591
|
+
for (const dirOffset of offsets) {
|
|
592
|
+
const records = readTableDirectory(view, dirOffset);
|
|
593
|
+
if (postScriptNameAt(view, records) === postScriptName) {
|
|
594
|
+
return { bytes: extractFont(bytes, view, dirOffset), matched: true };
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return { bytes: extractFont(bytes, view, offsets[0]), matched: !postScriptName };
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/outline/opentypeParser.ts
|
|
602
|
+
var modulePromise = null;
|
|
603
|
+
function loadOpenType() {
|
|
604
|
+
modulePromise ??= import('opentype.js');
|
|
605
|
+
return modulePromise;
|
|
606
|
+
}
|
|
607
|
+
function parserOf(ns) {
|
|
608
|
+
const interop = ns;
|
|
609
|
+
return interop.default?.parse ?? ns.parse;
|
|
610
|
+
}
|
|
611
|
+
function faceFor(font) {
|
|
612
|
+
const upem = font.unitsPerEm;
|
|
613
|
+
return {
|
|
614
|
+
unitsPerEm: upem,
|
|
615
|
+
ascender: font.ascender / upem,
|
|
616
|
+
advanceOf(cp) {
|
|
617
|
+
const index = font.charToGlyphIndex(String.fromCodePoint(cp));
|
|
618
|
+
if (!index) return null;
|
|
619
|
+
return (font.glyphs.get(index).advanceWidth ?? 0) / upem;
|
|
620
|
+
},
|
|
621
|
+
kernOf(left, right) {
|
|
622
|
+
const l = font.charToGlyphIndex(String.fromCodePoint(left));
|
|
623
|
+
const r = font.charToGlyphIndex(String.fromCodePoint(right));
|
|
624
|
+
if (!l || !r) return 0;
|
|
625
|
+
return font.getKerningValue(font.glyphs.get(l), font.glyphs.get(r)) / upem;
|
|
626
|
+
},
|
|
627
|
+
glyphD(cp) {
|
|
628
|
+
const index = font.charToGlyphIndex(String.fromCodePoint(cp));
|
|
629
|
+
if (!index) return null;
|
|
630
|
+
const d = font.glyphs.get(index).getPath(0, 0, 1).toPathData(OUTLINE_PRECISION);
|
|
631
|
+
return d.length > 0 ? d : null;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function createOpenTypeParser(postScriptName) {
|
|
636
|
+
return async (bytes) => {
|
|
637
|
+
const parse = parserOf(await loadOpenType());
|
|
638
|
+
const { bytes: single } = sfntFromCollection(bytes, postScriptName);
|
|
639
|
+
return faceFor(parse(single));
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
var openTypeParser = createOpenTypeParser();
|
|
643
|
+
|
|
644
|
+
// src/outline/outlineRegistry.ts
|
|
645
|
+
var slots = /* @__PURE__ */ new Map();
|
|
646
|
+
function slotKey(family, weight, style) {
|
|
647
|
+
return `${family}|${weight}|${style}`;
|
|
648
|
+
}
|
|
649
|
+
function normalize(v) {
|
|
650
|
+
return { weight: v.weight ?? 400, style: v.style ?? "normal" };
|
|
651
|
+
}
|
|
652
|
+
function registerFontOutlines(family, variant, source, opts = {}) {
|
|
653
|
+
const { weight, style } = normalize(variant);
|
|
654
|
+
slots.set(slotKey(family, weight, style), {
|
|
655
|
+
family,
|
|
656
|
+
weight,
|
|
657
|
+
style,
|
|
658
|
+
source,
|
|
659
|
+
parser: opts.parser ?? openTypeParser,
|
|
660
|
+
status: "idle",
|
|
661
|
+
face: null,
|
|
662
|
+
glyphs: /* @__PURE__ */ new Map()
|
|
663
|
+
});
|
|
664
|
+
notifyGlyphReady();
|
|
665
|
+
}
|
|
666
|
+
function unregisterFontOutlines(family, variant = {}) {
|
|
667
|
+
const { weight, style } = normalize(variant);
|
|
668
|
+
if (slots.delete(slotKey(family, weight, style))) notifyGlyphReady();
|
|
669
|
+
}
|
|
670
|
+
function hasFontOutlines(family, weight = 400, style = "normal") {
|
|
671
|
+
return slots.has(slotKey(family, weight, style));
|
|
672
|
+
}
|
|
673
|
+
function outlineStatus(family, weight = 400, style = "normal") {
|
|
674
|
+
return slots.get(slotKey(family, weight, style))?.status ?? null;
|
|
675
|
+
}
|
|
676
|
+
function listFontOutlines() {
|
|
677
|
+
return [...slots.values()].map(({ family, weight, style, status }) => ({ family, weight, style, status })).sort((a, b) => a.family.localeCompare(b.family) || a.weight - b.weight || a.style.localeCompare(b.style));
|
|
678
|
+
}
|
|
679
|
+
function closeContours(d) {
|
|
680
|
+
let out = "";
|
|
681
|
+
let start = 0;
|
|
682
|
+
for (let i = 1; i < d.length; i++) {
|
|
683
|
+
const c = d[i];
|
|
684
|
+
if (c !== "M" && c !== "m") continue;
|
|
685
|
+
out += closeOne(d.slice(start, i));
|
|
686
|
+
start = i;
|
|
687
|
+
}
|
|
688
|
+
return out + closeOne(d.slice(start));
|
|
689
|
+
}
|
|
690
|
+
function closeOne(contour) {
|
|
691
|
+
const trimmed = contour.trimEnd();
|
|
692
|
+
if (trimmed.length === 0) return "";
|
|
693
|
+
const last = trimmed[trimmed.length - 1];
|
|
694
|
+
return last === "Z" || last === "z" ? trimmed : `${trimmed}Z`;
|
|
695
|
+
}
|
|
696
|
+
function glyphOutline(family, weight, style, cp) {
|
|
697
|
+
const slot = slots.get(slotKey(family, weight, style));
|
|
698
|
+
if (!slot) return null;
|
|
699
|
+
if (slot.status === "idle") {
|
|
700
|
+
void beginLoad(slot);
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
if (slot.status !== "ready") return null;
|
|
704
|
+
const cached = slot.glyphs.get(cp);
|
|
705
|
+
if (cached !== void 0) return cached;
|
|
706
|
+
let d = null;
|
|
707
|
+
try {
|
|
708
|
+
const raw = slot.face.glyphD(cp);
|
|
709
|
+
d = raw === null ? null : closeContours(raw);
|
|
710
|
+
} catch (err) {
|
|
711
|
+
warnOnce(
|
|
712
|
+
`glyph|${slotKey(family, weight, style)}`,
|
|
713
|
+
`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.`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
slot.glyphs.set(cp, d);
|
|
717
|
+
return d;
|
|
718
|
+
}
|
|
719
|
+
function outlineMetrics(family, weight, style) {
|
|
720
|
+
const slot = slots.get(slotKey(family, weight, style));
|
|
721
|
+
if (!slot) return null;
|
|
722
|
+
if (slot.status === "idle") {
|
|
723
|
+
void beginLoad(slot);
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
return slot.status === "ready" ? slot.face : null;
|
|
727
|
+
}
|
|
728
|
+
async function beginLoad(slot) {
|
|
729
|
+
slot.status = "loading";
|
|
730
|
+
try {
|
|
731
|
+
slot.face = await slot.parser(await readSource(slot.source));
|
|
732
|
+
slot.status = "ready";
|
|
733
|
+
notifyGlyphReady();
|
|
734
|
+
} catch (err) {
|
|
735
|
+
slot.status = "failed";
|
|
736
|
+
warnOnce(
|
|
737
|
+
`load|${slotKey(slot.family, slot.weight, slot.style)}`,
|
|
738
|
+
`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.`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
async function readSource(source) {
|
|
743
|
+
const resolved = typeof source === "function" ? await source() : source;
|
|
744
|
+
if (typeof resolved === "string") {
|
|
745
|
+
const res = await fetch(resolved);
|
|
746
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${resolved}`);
|
|
747
|
+
return res.arrayBuffer();
|
|
748
|
+
}
|
|
749
|
+
if (resolved instanceof ArrayBuffer) return resolved;
|
|
750
|
+
return resolved.arrayBuffer();
|
|
751
|
+
}
|
|
752
|
+
var warned = /* @__PURE__ */ new Set();
|
|
753
|
+
function warnOnce(key, message) {
|
|
754
|
+
if (warned.has(key)) return;
|
|
755
|
+
warned.add(key);
|
|
756
|
+
console.warn(message);
|
|
757
|
+
}
|
|
758
|
+
function _resetFontOutlinesForTests() {
|
|
759
|
+
slots = /* @__PURE__ */ new Map();
|
|
760
|
+
warned.clear();
|
|
761
|
+
}
|
|
762
|
+
|
|
478
763
|
// src/registerFont.ts
|
|
479
764
|
var registry = /* @__PURE__ */ new Map();
|
|
480
765
|
function variantKey(weight, style) {
|
|
@@ -553,10 +838,20 @@ function missResolveResult(family, weight, style, suppressWarn = false) {
|
|
|
553
838
|
if (isExplicitCanvasFont(family)) {
|
|
554
839
|
return {
|
|
555
840
|
entry: null,
|
|
556
|
-
dynamicFace: getDynamicFace(family, weight, style),
|
|
841
|
+
dynamicFace: getDynamicFace(family, weight, style),
|
|
842
|
+
resolved: { family, weight, style },
|
|
843
|
+
synthetic: { bold: false, italic: false },
|
|
844
|
+
source: "canvas"
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
const face = outlineMetrics(family, weight, style === "italic" ? "italic" : "normal");
|
|
848
|
+
if (face) {
|
|
849
|
+
return {
|
|
850
|
+
entry: null,
|
|
851
|
+
outlineFace: face,
|
|
557
852
|
resolved: { family, weight, style },
|
|
558
853
|
synthetic: { bold: false, italic: false },
|
|
559
|
-
source: "
|
|
854
|
+
source: "outline"
|
|
560
855
|
};
|
|
561
856
|
}
|
|
562
857
|
const policy2 = getFontFallbackPolicy();
|
|
@@ -745,264 +1040,6 @@ function resolveFontVariantInternal(family, weight, style, suppressWarn) {
|
|
|
745
1040
|
return missResolveResult(family, weight, style, suppressWarn);
|
|
746
1041
|
}
|
|
747
1042
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
// src/outline/sfnt.ts
|
|
752
|
-
var SFNT_HEADER_BYTES = 12;
|
|
753
|
-
var TABLE_RECORD_BYTES = 16;
|
|
754
|
-
function tagAt(view, offset) {
|
|
755
|
-
return String.fromCharCode(
|
|
756
|
-
view.getUint8(offset),
|
|
757
|
-
view.getUint8(offset + 1),
|
|
758
|
-
view.getUint8(offset + 2),
|
|
759
|
-
view.getUint8(offset + 3)
|
|
760
|
-
);
|
|
761
|
-
}
|
|
762
|
-
function isFontCollection(bytes) {
|
|
763
|
-
if (bytes.byteLength < 4) return false;
|
|
764
|
-
return tagAt(new DataView(bytes), 0) === "ttcf";
|
|
765
|
-
}
|
|
766
|
-
var FONT_SIGNATURES = /* @__PURE__ */ new Set([
|
|
767
|
-
"\0\0\0",
|
|
768
|
-
"true",
|
|
769
|
-
"typ1",
|
|
770
|
-
"OTTO",
|
|
771
|
-
"ttcf",
|
|
772
|
-
"wOFF",
|
|
773
|
-
"wOF2"
|
|
774
|
-
]);
|
|
775
|
-
var RESOURCE_HEADER_BYTES = 16;
|
|
776
|
-
function isDataForkFont(bytes) {
|
|
777
|
-
if (bytes.byteLength < RESOURCE_HEADER_BYTES) return false;
|
|
778
|
-
const view = new DataView(bytes);
|
|
779
|
-
if (FONT_SIGNATURES.has(tagAt(view, 0))) return false;
|
|
780
|
-
const dataOffset = view.getUint32(0);
|
|
781
|
-
const mapOffset = view.getUint32(4);
|
|
782
|
-
const dataLength = view.getUint32(8);
|
|
783
|
-
const mapLength = view.getUint32(12);
|
|
784
|
-
return dataOffset >= RESOURCE_HEADER_BYTES && dataOffset + dataLength === mapOffset && mapOffset + mapLength <= bytes.byteLength;
|
|
785
|
-
}
|
|
786
|
-
function readTableDirectory(view, dirOffset) {
|
|
787
|
-
const numTables = view.getUint16(dirOffset + 4);
|
|
788
|
-
const records = [];
|
|
789
|
-
for (let i = 0; i < numTables; i++) {
|
|
790
|
-
const at = dirOffset + SFNT_HEADER_BYTES + i * TABLE_RECORD_BYTES;
|
|
791
|
-
records.push({
|
|
792
|
-
tag: tagAt(view, at),
|
|
793
|
-
checksum: view.getUint32(at + 4),
|
|
794
|
-
offset: view.getUint32(at + 8),
|
|
795
|
-
length: view.getUint32(at + 12)
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
return records;
|
|
799
|
-
}
|
|
800
|
-
function postScriptNameAt(view, records) {
|
|
801
|
-
const name = records.find((r) => r.tag === "name");
|
|
802
|
-
if (!name) return null;
|
|
803
|
-
const base = name.offset;
|
|
804
|
-
const count = view.getUint16(base + 2);
|
|
805
|
-
const stringOffset = view.getUint16(base + 4);
|
|
806
|
-
for (let i = 0; i < count; i++) {
|
|
807
|
-
const rec = base + 6 + i * 12;
|
|
808
|
-
if (view.getUint16(rec + 6) !== 6) continue;
|
|
809
|
-
const length = view.getUint16(rec + 8);
|
|
810
|
-
const offset = view.getUint16(rec + 10);
|
|
811
|
-
let out = "";
|
|
812
|
-
for (let b = 0; b < length; b++) {
|
|
813
|
-
const code = view.getUint8(base + stringOffset + offset + b);
|
|
814
|
-
if (code !== 0) out += String.fromCharCode(code);
|
|
815
|
-
}
|
|
816
|
-
if (out.length > 0) return out;
|
|
817
|
-
}
|
|
818
|
-
return null;
|
|
819
|
-
}
|
|
820
|
-
function extractFont(source, view, dirOffset) {
|
|
821
|
-
const records = readTableDirectory(view, dirOffset);
|
|
822
|
-
const padded = (n) => n + 3 & -4;
|
|
823
|
-
let total = SFNT_HEADER_BYTES + records.length * TABLE_RECORD_BYTES;
|
|
824
|
-
for (const r of records) total += padded(r.length);
|
|
825
|
-
const out = new ArrayBuffer(total);
|
|
826
|
-
const dst = new DataView(out);
|
|
827
|
-
const dstBytes = new Uint8Array(out);
|
|
828
|
-
const srcBytes = new Uint8Array(source);
|
|
829
|
-
dst.setUint32(0, view.getUint32(dirOffset));
|
|
830
|
-
dst.setUint16(4, records.length);
|
|
831
|
-
const entrySelector = Math.floor(Math.log2(records.length));
|
|
832
|
-
const searchRange = 2 ** entrySelector * 16;
|
|
833
|
-
dst.setUint16(6, searchRange);
|
|
834
|
-
dst.setUint16(8, entrySelector);
|
|
835
|
-
dst.setUint16(10, records.length * 16 - searchRange);
|
|
836
|
-
let cursor = SFNT_HEADER_BYTES + records.length * TABLE_RECORD_BYTES;
|
|
837
|
-
records.forEach((r, i) => {
|
|
838
|
-
const at = SFNT_HEADER_BYTES + i * TABLE_RECORD_BYTES;
|
|
839
|
-
for (let b = 0; b < 4; b++) dst.setUint8(at + b, r.tag.charCodeAt(b));
|
|
840
|
-
dst.setUint32(at + 4, r.checksum);
|
|
841
|
-
dst.setUint32(at + 8, cursor);
|
|
842
|
-
dst.setUint32(at + 12, r.length);
|
|
843
|
-
dstBytes.set(srcBytes.subarray(r.offset, r.offset + r.length), cursor);
|
|
844
|
-
cursor += padded(r.length);
|
|
845
|
-
});
|
|
846
|
-
return out;
|
|
847
|
-
}
|
|
848
|
-
function sfntFromCollection(bytes, postScriptName) {
|
|
849
|
-
if (isDataForkFont(bytes)) {
|
|
850
|
-
throw new Error(
|
|
851
|
-
"Datafork TrueType (.dfont) is not supported \u2014 the outline tier reads sfnt tables and a .dfont holds them inside a Macintosh resource map."
|
|
852
|
-
);
|
|
853
|
-
}
|
|
854
|
-
if (!isFontCollection(bytes)) return { bytes, matched: true };
|
|
855
|
-
const view = new DataView(bytes);
|
|
856
|
-
const numFonts = view.getUint32(8);
|
|
857
|
-
if (numFonts === 0) throw new Error("font collection contains no fonts");
|
|
858
|
-
const offsets = [];
|
|
859
|
-
for (let i = 0; i < numFonts; i++) offsets.push(view.getUint32(12 + i * 4));
|
|
860
|
-
if (postScriptName) {
|
|
861
|
-
for (const dirOffset of offsets) {
|
|
862
|
-
const records = readTableDirectory(view, dirOffset);
|
|
863
|
-
if (postScriptNameAt(view, records) === postScriptName) {
|
|
864
|
-
return { bytes: extractFont(bytes, view, dirOffset), matched: true };
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
return { bytes: extractFont(bytes, view, offsets[0]), matched: !postScriptName };
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
// src/outline/opentypeParser.ts
|
|
872
|
-
var modulePromise = null;
|
|
873
|
-
function loadOpenType() {
|
|
874
|
-
modulePromise ??= import('opentype.js');
|
|
875
|
-
return modulePromise;
|
|
876
|
-
}
|
|
877
|
-
function faceFor(font) {
|
|
878
|
-
return {
|
|
879
|
-
unitsPerEm: font.unitsPerEm,
|
|
880
|
-
glyphD(cp) {
|
|
881
|
-
const index = font.charToGlyphIndex(String.fromCodePoint(cp));
|
|
882
|
-
if (!index) return null;
|
|
883
|
-
const d = font.glyphs.get(index).getPath(0, 0, 1).toPathData(OUTLINE_PRECISION);
|
|
884
|
-
return d.length > 0 ? d : null;
|
|
885
|
-
}
|
|
886
|
-
};
|
|
887
|
-
}
|
|
888
|
-
function createOpenTypeParser(postScriptName) {
|
|
889
|
-
return async (bytes) => {
|
|
890
|
-
const opentype = await loadOpenType();
|
|
891
|
-
const { bytes: single } = sfntFromCollection(bytes, postScriptName);
|
|
892
|
-
return faceFor(opentype.parse(single));
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
var openTypeParser = createOpenTypeParser();
|
|
896
|
-
|
|
897
|
-
// src/outline/outlineRegistry.ts
|
|
898
|
-
var slots = /* @__PURE__ */ new Map();
|
|
899
|
-
function slotKey(family, weight, style) {
|
|
900
|
-
return `${family}|${weight}|${style}`;
|
|
901
|
-
}
|
|
902
|
-
function normalize(v) {
|
|
903
|
-
return { weight: v.weight ?? 400, style: v.style ?? "normal" };
|
|
904
|
-
}
|
|
905
|
-
function registerFontOutlines(family, variant, source, opts = {}) {
|
|
906
|
-
const { weight, style } = normalize(variant);
|
|
907
|
-
slots.set(slotKey(family, weight, style), {
|
|
908
|
-
family,
|
|
909
|
-
weight,
|
|
910
|
-
style,
|
|
911
|
-
source,
|
|
912
|
-
parser: opts.parser ?? openTypeParser,
|
|
913
|
-
status: "idle",
|
|
914
|
-
face: null,
|
|
915
|
-
glyphs: /* @__PURE__ */ new Map()
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
function unregisterFontOutlines(family, variant = {}) {
|
|
919
|
-
const { weight, style } = normalize(variant);
|
|
920
|
-
slots.delete(slotKey(family, weight, style));
|
|
921
|
-
}
|
|
922
|
-
function hasFontOutlines(family, weight = 400, style = "normal") {
|
|
923
|
-
return slots.has(slotKey(family, weight, style));
|
|
924
|
-
}
|
|
925
|
-
function outlineStatus(family, weight = 400, style = "normal") {
|
|
926
|
-
return slots.get(slotKey(family, weight, style))?.status ?? null;
|
|
927
|
-
}
|
|
928
|
-
function listFontOutlines() {
|
|
929
|
-
return [...slots.values()].map(({ family, weight, style, status }) => ({ family, weight, style, status })).sort((a, b) => a.family.localeCompare(b.family) || a.weight - b.weight || a.style.localeCompare(b.style));
|
|
930
|
-
}
|
|
931
|
-
function closeContours(d) {
|
|
932
|
-
let out = "";
|
|
933
|
-
let start = 0;
|
|
934
|
-
for (let i = 1; i < d.length; i++) {
|
|
935
|
-
const c = d[i];
|
|
936
|
-
if (c !== "M" && c !== "m") continue;
|
|
937
|
-
out += closeOne(d.slice(start, i));
|
|
938
|
-
start = i;
|
|
939
|
-
}
|
|
940
|
-
return out + closeOne(d.slice(start));
|
|
941
|
-
}
|
|
942
|
-
function closeOne(contour) {
|
|
943
|
-
const trimmed = contour.trimEnd();
|
|
944
|
-
if (trimmed.length === 0) return "";
|
|
945
|
-
const last = trimmed[trimmed.length - 1];
|
|
946
|
-
return last === "Z" || last === "z" ? trimmed : `${trimmed}Z`;
|
|
947
|
-
}
|
|
948
|
-
function glyphOutline(family, weight, style, cp) {
|
|
949
|
-
const slot = slots.get(slotKey(family, weight, style));
|
|
950
|
-
if (!slot) return null;
|
|
951
|
-
if (slot.status === "idle") {
|
|
952
|
-
void beginLoad(slot);
|
|
953
|
-
return null;
|
|
954
|
-
}
|
|
955
|
-
if (slot.status !== "ready") return null;
|
|
956
|
-
const cached = slot.glyphs.get(cp);
|
|
957
|
-
if (cached !== void 0) return cached;
|
|
958
|
-
let d = null;
|
|
959
|
-
try {
|
|
960
|
-
const raw = slot.face.glyphD(cp);
|
|
961
|
-
d = raw === null ? null : closeContours(raw);
|
|
962
|
-
} catch (err) {
|
|
963
|
-
warnOnce(
|
|
964
|
-
`glyph|${slotKey(family, weight, style)}`,
|
|
965
|
-
`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.`
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
slot.glyphs.set(cp, d);
|
|
969
|
-
return d;
|
|
970
|
-
}
|
|
971
|
-
async function beginLoad(slot) {
|
|
972
|
-
slot.status = "loading";
|
|
973
|
-
try {
|
|
974
|
-
slot.face = await slot.parser(await readSource(slot.source));
|
|
975
|
-
slot.status = "ready";
|
|
976
|
-
notifyGlyphReady();
|
|
977
|
-
} catch (err) {
|
|
978
|
-
slot.status = "failed";
|
|
979
|
-
warnOnce(
|
|
980
|
-
`load|${slotKey(slot.family, slot.weight, slot.style)}`,
|
|
981
|
-
`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.`
|
|
982
|
-
);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
async function readSource(source) {
|
|
986
|
-
const resolved = typeof source === "function" ? await source() : source;
|
|
987
|
-
if (typeof resolved === "string") {
|
|
988
|
-
const res = await fetch(resolved);
|
|
989
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${resolved}`);
|
|
990
|
-
return res.arrayBuffer();
|
|
991
|
-
}
|
|
992
|
-
if (resolved instanceof ArrayBuffer) return resolved;
|
|
993
|
-
return resolved.arrayBuffer();
|
|
994
|
-
}
|
|
995
|
-
var warned = /* @__PURE__ */ new Set();
|
|
996
|
-
function warnOnce(key, message) {
|
|
997
|
-
if (warned.has(key)) return;
|
|
998
|
-
warned.add(key);
|
|
999
|
-
console.warn(message);
|
|
1000
|
-
}
|
|
1001
|
-
function _resetFontOutlinesForTests() {
|
|
1002
|
-
slots = /* @__PURE__ */ new Map();
|
|
1003
|
-
warned.clear();
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
export { DEFAULT_BAKE_BUDGET, FIXTURE_FONT, __setGlyphRasterizerForTests, _getPagesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests, createOpenTypeParser, dynamicPageTextureId, ensureFontTexture, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFonts, markAllFontsNotUploaded, outlineStatus, parseBmFont, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, subscribeGlyphReady, syncDynamicPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines };
|
|
1007
|
-
//# sourceMappingURL=chunk-3RSTZDXH.js.map
|
|
1008
|
-
//# sourceMappingURL=chunk-3RSTZDXH.js.map
|
|
1043
|
+
export { DEFAULT_BAKE_BUDGET, FIXTURE_FONT, __setGlyphRasterizerForTests, _getPagesForTests, _resetDynamicFontsForTests, _resetFallbackForTests, _resetFontOutlinesForTests, _resetFontRegistryForTests, createOpenTypeParser, dynamicPageTextureId, ensureFontTexture, getDefaultFontFamily, getFont, getFontFallbackPolicy, glyphGeneration, glyphOutline, hasFontOutlines, isCanvasFont, listCanvasFonts, listFontOutlines, listFonts, markAllFontsNotUploaded, outlineMetrics, outlineStatus, parseBmFont, registerCanvasFont, registerFont, registerFontOutlines, resetBakeBudget, resolveFontVariant, resolveGlyphFallback, setDefaultFontFamily, setFontFallbackPolicy, subscribeGlyphReady, syncDynamicPageTexture, textureCacheKey, unregisterCanvasFont, unregisterFontOutlines };
|
|
1044
|
+
//# sourceMappingURL=chunk-NRVRDX3F.js.map
|
|
1045
|
+
//# sourceMappingURL=chunk-NRVRDX3F.js.map
|