@turntrout/subfont 1.10.2 → 1.10.4
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 +21 -21
- package/lib/HeadlessBrowser.d.ts +2 -0
- package/lib/HeadlessBrowser.d.ts.map +1 -1
- package/lib/HeadlessBrowser.js +64 -58
- package/lib/HeadlessBrowser.js.map +1 -1
- package/lib/collectTextsByPage.d.ts.map +1 -1
- package/lib/collectTextsByPage.js +145 -136
- package/lib/collectTextsByPage.js.map +1 -1
- package/lib/concurrencyLimit.d.ts +1 -0
- package/lib/concurrencyLimit.d.ts.map +1 -1
- package/lib/concurrencyLimit.js +5 -1
- package/lib/concurrencyLimit.js.map +1 -1
- package/lib/findCustomPropertyDefinitions.js.map +1 -1
- package/lib/fontConverter.d.ts.map +1 -1
- package/lib/fontConverter.js +2 -1
- package/lib/fontConverter.js.map +1 -1
- package/lib/getCssRulesByProperty.d.ts.map +1 -1
- package/lib/getCssRulesByProperty.js +234 -207
- package/lib/getCssRulesByProperty.js.map +1 -1
- package/lib/normalizeFontPropertyValue.js +1 -1
- package/lib/normalizeFontPropertyValue.js.map +1 -1
- package/lib/parseCommandLineOptions.d.ts.map +1 -1
- package/lib/parseCommandLineOptions.js +30 -19
- package/lib/parseCommandLineOptions.js.map +1 -1
- package/lib/sfntCache.d.ts.map +1 -1
- package/lib/sfntCache.js +2 -0
- package/lib/sfntCache.js.map +1 -1
- package/lib/subfont.d.ts.map +1 -1
- package/lib/subfont.js +347 -311
- package/lib/subfont.js.map +1 -1
- package/lib/subsetFontWithGlyphs.d.ts.map +1 -1
- package/lib/subsetFontWithGlyphs.js +62 -49
- package/lib/subsetFontWithGlyphs.js.map +1 -1
- package/lib/subsetFonts.d.ts.map +1 -1
- package/lib/subsetFonts.js +345 -283
- package/lib/subsetFonts.js.map +1 -1
- package/lib/subsetGeneration.d.ts.map +1 -1
- package/lib/subsetGeneration.js +152 -127
- package/lib/subsetGeneration.js.map +1 -1
- package/lib/unquote.js +2 -2
- package/lib/unquote.js.map +1 -1
- package/lib/warnAboutMissingGlyphs.d.ts.map +1 -1
- package/lib/warnAboutMissingGlyphs.js +132 -112
- package/lib/warnAboutMissingGlyphs.js.map +1 -1
- package/package.json +1 -1
package/lib/subsetFonts.js
CHANGED
|
@@ -455,47 +455,52 @@ async function computeCodepoints(assetGraph, htmlOrSvgAssetTextsWithProps, fontD
|
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
|
-
|
|
459
|
-
// so the browser picks up the subset fonts instead of the originals.
|
|
460
|
-
function injectSubsetFontFamilies(assetGraph, htmlOrSvgAssetTextsWithProps, omitFallbacks) {
|
|
458
|
+
function buildWebfontNameMap(htmlOrSvgAssetTextsWithProps) {
|
|
461
459
|
const webfontNameMap = Object.create(null);
|
|
462
460
|
for (const { fontUsages } of htmlOrSvgAssetTextsWithProps) {
|
|
463
461
|
for (const { subsets, fontFamilies, props } of fontUsages) {
|
|
464
|
-
if (subsets)
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
462
|
+
if (!subsets)
|
|
463
|
+
continue;
|
|
464
|
+
for (const fontFamily of fontFamilies) {
|
|
465
|
+
webfontNameMap[fontFamily.toLowerCase()] =
|
|
466
|
+
`${props['font-family']}__subset`;
|
|
469
467
|
}
|
|
470
468
|
}
|
|
471
469
|
}
|
|
472
|
-
|
|
473
|
-
|
|
470
|
+
return webfontNameMap;
|
|
471
|
+
}
|
|
472
|
+
// Rewrites a comma-separated font-family list to prepend the matching
|
|
473
|
+
// __subset family. Returns null if no change was needed.
|
|
474
|
+
function rewriteFontFamilyList(value, webfontNameMap, omitFallbacks) {
|
|
475
|
+
const fontFamilies = cssListHelpers.splitByCommas(value);
|
|
476
|
+
const updatedFamilies = [];
|
|
477
|
+
let modified = false;
|
|
478
|
+
for (const family of fontFamilies) {
|
|
479
|
+
const parsed = cssFontParser.parseFontFamily(family)[0];
|
|
480
|
+
const subsetFontFamily = parsed
|
|
481
|
+
? webfontNameMap[parsed.toLowerCase()]
|
|
482
|
+
: undefined;
|
|
483
|
+
if (subsetFontFamily && !fontFamilies.includes(subsetFontFamily)) {
|
|
484
|
+
updatedFamilies.push((0, fontFaceHelpers_1.maybeCssQuote)(subsetFontFamily));
|
|
485
|
+
if (!omitFallbacks)
|
|
486
|
+
updatedFamilies.push(family);
|
|
487
|
+
modified = true;
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
updatedFamilies.push(family);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return modified ? updatedFamilies.join(', ') : null;
|
|
494
|
+
}
|
|
495
|
+
function injectSubsetIntoSvgAssets(assetGraph, webfontNameMap, omitFallbacks) {
|
|
474
496
|
for (const svgAsset of assetGraph.findAssets({ type: 'Svg' })) {
|
|
475
497
|
if (!svgAsset.isLoaded)
|
|
476
498
|
continue;
|
|
477
499
|
let changesMade = false;
|
|
478
500
|
for (const element of Array.from(svgAsset.parseTree.querySelectorAll('[font-family]'))) {
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
for (const family of fontFamilies) {
|
|
483
|
-
const parsed = cssFontParser.parseFontFamily(family)[0];
|
|
484
|
-
const subsetFontFamily = parsed
|
|
485
|
-
? webfontNameMap[parsed.toLowerCase()]
|
|
486
|
-
: undefined;
|
|
487
|
-
if (subsetFontFamily && !fontFamilies.includes(subsetFontFamily)) {
|
|
488
|
-
updatedFamilies.push((0, fontFaceHelpers_1.maybeCssQuote)(subsetFontFamily));
|
|
489
|
-
if (!omitFallbacks)
|
|
490
|
-
updatedFamilies.push(family);
|
|
491
|
-
modified = true;
|
|
492
|
-
}
|
|
493
|
-
else {
|
|
494
|
-
updatedFamilies.push(family);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
if (modified) {
|
|
498
|
-
element.setAttribute('font-family', updatedFamilies.join(', '));
|
|
501
|
+
const updated = rewriteFontFamilyList(element.getAttribute('font-family'), webfontNameMap, omitFallbacks);
|
|
502
|
+
if (updated !== null) {
|
|
503
|
+
element.setAttribute('font-family', updated);
|
|
499
504
|
changesMade = true;
|
|
500
505
|
}
|
|
501
506
|
}
|
|
@@ -503,11 +508,61 @@ function injectSubsetFontFamilies(assetGraph, htmlOrSvgAssetTextsWithProps, omit
|
|
|
503
508
|
svgAsset.markDirty();
|
|
504
509
|
}
|
|
505
510
|
}
|
|
511
|
+
}
|
|
512
|
+
function rewriteFontShorthand(value, webfontNameMap, omitFallbacks) {
|
|
513
|
+
const fontProperties = cssFontParser.parseFont(value);
|
|
514
|
+
const fontFamilies = fontProperties && fontProperties['font-family'].map(unquote);
|
|
515
|
+
if (!fontFamilies || fontFamilies.length === 0)
|
|
516
|
+
return null;
|
|
517
|
+
const subsetFontFamily = webfontNameMap[fontFamilies[0].toLowerCase()];
|
|
518
|
+
if (!subsetFontFamily || fontFamilies.includes(subsetFontFamily)) {
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
if (omitFallbacks) {
|
|
522
|
+
fontFamilies.shift();
|
|
523
|
+
}
|
|
524
|
+
fontFamilies.unshift(subsetFontFamily);
|
|
525
|
+
const stylePrefix = fontProperties['font-style']
|
|
526
|
+
? `${fontProperties['font-style']} `
|
|
527
|
+
: '';
|
|
528
|
+
const weightPrefix = fontProperties['font-weight']
|
|
529
|
+
? `${fontProperties['font-weight']} `
|
|
530
|
+
: '';
|
|
531
|
+
const lineHeightSuffix = fontProperties['line-height']
|
|
532
|
+
? `/${fontProperties['line-height']}`
|
|
533
|
+
: '';
|
|
534
|
+
return `${stylePrefix}${weightPrefix}${fontProperties['font-size']}${lineHeightSuffix} ${fontFamilies.map(fontFaceHelpers_1.maybeCssQuote).join(', ')}`;
|
|
535
|
+
}
|
|
536
|
+
function injectSubsetIntoCssAssets(assetGraph, webfontNameMap, omitFallbacks) {
|
|
506
537
|
const cssAssets = assetGraph.findAssets({ type: 'Css', isLoaded: true });
|
|
507
538
|
const parseTreeToAsset = new Map();
|
|
508
539
|
for (const cssAsset of cssAssets) {
|
|
509
540
|
parseTreeToAsset.set(cssAsset.parseTree, cssAsset);
|
|
510
541
|
}
|
|
542
|
+
const cssAssetsDirtiedByCustomProps = new Set();
|
|
543
|
+
// Lazy: findCustomPropertyDefinitions walks every CSS asset's parse tree.
|
|
544
|
+
// Skip the work entirely when no rule references var(); pay it once on the
|
|
545
|
+
// first hit.
|
|
546
|
+
let customPropertyDefinitions;
|
|
547
|
+
const injectVarRule = (cssRule) => {
|
|
548
|
+
if (!customPropertyDefinitions) {
|
|
549
|
+
customPropertyDefinitions = findCustomPropertyDefinitions(cssAssets);
|
|
550
|
+
}
|
|
551
|
+
for (const customPropertyName of extractReferencedCustomPropertyNames(cssRule.value)) {
|
|
552
|
+
for (const relatedCssRule of [
|
|
553
|
+
cssRule,
|
|
554
|
+
...(customPropertyDefinitions[customPropertyName] || []),
|
|
555
|
+
]) {
|
|
556
|
+
const modifiedValue = injectSubsetDefinitions(relatedCssRule.value, webfontNameMap, omitFallbacks);
|
|
557
|
+
if (modifiedValue === relatedCssRule.value)
|
|
558
|
+
continue;
|
|
559
|
+
relatedCssRule.value = modifiedValue;
|
|
560
|
+
const ownerAsset = parseTreeToAsset.get(relatedCssRule.root());
|
|
561
|
+
if (ownerAsset)
|
|
562
|
+
cssAssetsDirtiedByCustomProps.add(ownerAsset);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
511
566
|
for (const cssAsset of cssAssets) {
|
|
512
567
|
let changesMade = false;
|
|
513
568
|
cssAsset.eachRuleInParseTree((cssRule) => {
|
|
@@ -516,82 +571,37 @@ function injectSubsetFontFamilies(assetGraph, htmlOrSvgAssetTextsWithProps, omit
|
|
|
516
571
|
const propName = cssRule.prop.toLowerCase();
|
|
517
572
|
if ((propName === 'font' || propName === 'font-family') &&
|
|
518
573
|
cssRule.value.includes('var(')) {
|
|
519
|
-
|
|
520
|
-
customPropertyDefinitions = findCustomPropertyDefinitions(cssAssets);
|
|
521
|
-
}
|
|
522
|
-
for (const customPropertyName of extractReferencedCustomPropertyNames(cssRule.value)) {
|
|
523
|
-
for (const relatedCssRule of [
|
|
524
|
-
cssRule,
|
|
525
|
-
...(customPropertyDefinitions[customPropertyName] || []),
|
|
526
|
-
]) {
|
|
527
|
-
const modifiedValue = injectSubsetDefinitions(relatedCssRule.value, webfontNameMap, omitFallbacks);
|
|
528
|
-
if (modifiedValue !== relatedCssRule.value) {
|
|
529
|
-
relatedCssRule.value = modifiedValue;
|
|
530
|
-
const ownerAsset = parseTreeToAsset.get(relatedCssRule.root());
|
|
531
|
-
if (ownerAsset) {
|
|
532
|
-
cssAssetsDirtiedByCustomProps.add(ownerAsset);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
574
|
+
injectVarRule(cssRule);
|
|
537
575
|
}
|
|
538
576
|
else if (propName === 'font-family') {
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
for (const family of fontFamilies) {
|
|
543
|
-
const parsed = cssFontParser.parseFontFamily(family)[0];
|
|
544
|
-
const subsetFontFamily = parsed
|
|
545
|
-
? webfontNameMap[parsed.toLowerCase()]
|
|
546
|
-
: undefined;
|
|
547
|
-
if (subsetFontFamily && !fontFamilies.includes(subsetFontFamily)) {
|
|
548
|
-
updatedFamilies.push((0, fontFaceHelpers_1.maybeCssQuote)(subsetFontFamily));
|
|
549
|
-
if (!omitFallbacks)
|
|
550
|
-
updatedFamilies.push(family);
|
|
551
|
-
familyModified = true;
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
updatedFamilies.push(family);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
if (familyModified) {
|
|
558
|
-
cssRule.value = updatedFamilies.join(', ');
|
|
577
|
+
const updated = rewriteFontFamilyList(cssRule.value, webfontNameMap, omitFallbacks);
|
|
578
|
+
if (updated !== null) {
|
|
579
|
+
cssRule.value = updated;
|
|
559
580
|
changesMade = true;
|
|
560
581
|
}
|
|
561
582
|
}
|
|
562
583
|
else if (propName === 'font') {
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const subsetFontFamily = webfontNameMap[fontFamilies[0].toLowerCase()];
|
|
568
|
-
if (!subsetFontFamily || fontFamilies.includes(subsetFontFamily))
|
|
569
|
-
return;
|
|
570
|
-
if (omitFallbacks) {
|
|
571
|
-
fontFamilies.shift();
|
|
584
|
+
const updated = rewriteFontShorthand(cssRule.value, webfontNameMap, omitFallbacks);
|
|
585
|
+
if (updated !== null) {
|
|
586
|
+
cssRule.value = updated;
|
|
587
|
+
changesMade = true;
|
|
572
588
|
}
|
|
573
|
-
fontFamilies.unshift(subsetFontFamily);
|
|
574
|
-
const stylePrefix = fontProperties['font-style']
|
|
575
|
-
? `${fontProperties['font-style']} `
|
|
576
|
-
: '';
|
|
577
|
-
const weightPrefix = fontProperties['font-weight']
|
|
578
|
-
? `${fontProperties['font-weight']} `
|
|
579
|
-
: '';
|
|
580
|
-
const lineHeightSuffix = fontProperties['line-height']
|
|
581
|
-
? `/${fontProperties['line-height']}`
|
|
582
|
-
: '';
|
|
583
|
-
cssRule.value = `${stylePrefix}${weightPrefix}${fontProperties['font-size']}${lineHeightSuffix} ${fontFamilies.map(fontFaceHelpers_1.maybeCssQuote).join(', ')}`;
|
|
584
|
-
changesMade = true;
|
|
585
589
|
}
|
|
586
590
|
});
|
|
587
|
-
if (changesMade)
|
|
591
|
+
if (changesMade)
|
|
588
592
|
cssAsset.markDirty();
|
|
589
|
-
}
|
|
590
593
|
}
|
|
591
594
|
for (const dirtiedAsset of cssAssetsDirtiedByCustomProps) {
|
|
592
595
|
dirtiedAsset.markDirty();
|
|
593
596
|
}
|
|
594
597
|
}
|
|
598
|
+
// Inject __subset font-family names into CSS declarations and SVG attributes
|
|
599
|
+
// so the browser picks up the subset fonts instead of the originals.
|
|
600
|
+
function injectSubsetFontFamilies(assetGraph, htmlOrSvgAssetTextsWithProps, omitFallbacks) {
|
|
601
|
+
const webfontNameMap = buildWebfontNameMap(htmlOrSvgAssetTextsWithProps);
|
|
602
|
+
injectSubsetIntoSvgAssets(assetGraph, webfontNameMap, omitFallbacks);
|
|
603
|
+
injectSubsetIntoCssAssets(assetGraph, webfontNameMap, omitFallbacks);
|
|
604
|
+
}
|
|
595
605
|
// Build subset CSS assets and inject them (plus preload hints) into each
|
|
596
606
|
// page. All cache state is local; nothing leaks back to the caller.
|
|
597
607
|
async function insertSubsets({ assetGraph, pages, formats, subsetUrl, hrefType, inlineCss, omitFallbacks, }) {
|
|
@@ -644,9 +654,12 @@ async function insertSubsets({ assetGraph, pages, formats, subsetUrl, hrefType,
|
|
|
644
654
|
})) {
|
|
645
655
|
const index = relTypeToIndex[relation.type];
|
|
646
656
|
const from = relation.from;
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
657
|
+
let arr = index.get(from);
|
|
658
|
+
if (!arr) {
|
|
659
|
+
arr = [];
|
|
660
|
+
index.set(from, arr);
|
|
661
|
+
}
|
|
662
|
+
arr.push(relation);
|
|
650
663
|
}
|
|
651
664
|
let numFontUsagesWithSubset = 0;
|
|
652
665
|
for (const { htmlOrSvgAsset, fontUsages, accumulatedFontFaceDeclarations, } of pages) {
|
|
@@ -754,176 +767,83 @@ async function insertSubsets({ assetGraph, pages, formats, subsetUrl, hrefType,
|
|
|
754
767
|
}
|
|
755
768
|
return { numFontUsagesWithSubset };
|
|
756
769
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
if (debug) {
|
|
766
|
-
console.warn('WASM warmup failed (will retry on first subset call):', err);
|
|
770
|
+
// Walk up the postcss ancestor chain to find the @media query enclosing
|
|
771
|
+
// a rule (if any). Returns the empty string for rules outside any @media.
|
|
772
|
+
function findEnclosingMediaQuery(rule) {
|
|
773
|
+
let ancestor = rule.parent;
|
|
774
|
+
while (ancestor) {
|
|
775
|
+
if (ancestor.type === 'atrule' &&
|
|
776
|
+
ancestor.name?.toLowerCase() === 'media') {
|
|
777
|
+
return ancestor.params ?? '';
|
|
767
778
|
}
|
|
768
|
-
|
|
769
|
-
const subsetUrl = urltools.ensureTrailingSlash(assetGraph.root + subsetPath);
|
|
770
|
-
const timings = {};
|
|
771
|
-
const trackPhase = (0, progress_1.makePhaseTracker)(console, debug);
|
|
772
|
-
const applySourceMapsPhase = trackPhase('applySourceMaps');
|
|
773
|
-
if (sourceMaps) {
|
|
774
|
-
await assetGraph.applySourceMaps({ type: 'Css' });
|
|
779
|
+
ancestor = ancestor.parent;
|
|
775
780
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
]
|
|
790
|
-
});
|
|
791
|
-
const collectPhase = trackPhase(`collectTextsByPage (${htmlOrSvgAssets.length} pages)`);
|
|
792
|
-
const { htmlOrSvgAssetTextsWithProps, fontFaceDeclarationsByHtmlOrSvgAsset, subTimings, } = await collectTextsByPage(assetGraph, htmlOrSvgAssets, {
|
|
793
|
-
text,
|
|
794
|
-
console,
|
|
795
|
-
dynamic,
|
|
796
|
-
debug,
|
|
797
|
-
concurrency,
|
|
798
|
-
chromeArgs,
|
|
799
|
-
});
|
|
800
|
-
timings.collectTextsByPage = collectPhase.end();
|
|
801
|
-
timings.collectTextsByPageDetails = subTimings;
|
|
802
|
-
// textByProps is consumed inside collectTextsByPage (see buildPerPageFontUsages)
|
|
803
|
-
// and never read again by anything in the subsetFonts pipeline; the raw
|
|
804
|
-
// font-tracer text strings inside scale with #pages and are the largest
|
|
805
|
-
// per-page artefact at the 1800-page scale. Release them before
|
|
806
|
-
// computeCodepoints / subsetting / injection so they don't pin heap.
|
|
807
|
-
for (const entry of htmlOrSvgAssetTextsWithProps) {
|
|
808
|
-
entry.textByProps = [];
|
|
809
|
-
}
|
|
810
|
-
const omitFallbacksPhase = trackPhase('omitFallbacks processing');
|
|
811
|
-
const potentiallyOrphanedAssets = new Set();
|
|
812
|
-
if (omitFallbacks) {
|
|
813
|
-
removeOriginalFontFaceRules(htmlOrSvgAssets, fontFaceDeclarationsByHtmlOrSvgAsset, potentiallyOrphanedAssets);
|
|
814
|
-
}
|
|
815
|
-
timings['omitFallbacks processing'] = omitFallbacksPhase.end();
|
|
816
|
-
// Stage 1 → 2 placeholder: SubsettedFontUsage only adds optional fields
|
|
817
|
-
// on top of TracedFontUsage, so this upcast is structurally valid even
|
|
818
|
-
// before getSubsetsForFontUsage runs.
|
|
819
|
-
const pages = htmlOrSvgAssetTextsWithProps;
|
|
820
|
-
const codepointPhase = trackPhase('codepoint generation');
|
|
821
|
-
await computeCodepoints(assetGraph, pages, fontDisplay);
|
|
822
|
-
timings['codepoint generation'] = codepointPhase.end();
|
|
823
|
-
if (onlyInfo) {
|
|
824
|
-
// Stage 2 hasn't run, but buildFontInfoReport's input only requires
|
|
825
|
-
// `codepoints` (stage 3) — already attached above. Subset fields are
|
|
826
|
-
// optional and simply absent here, matching how the report describes
|
|
827
|
-
// a "no subset created" entry.
|
|
828
|
-
return {
|
|
829
|
-
fontInfo: buildFontInfoReport(pages),
|
|
830
|
-
timings,
|
|
831
|
-
};
|
|
781
|
+
return '';
|
|
782
|
+
}
|
|
783
|
+
// Group @font-face rules by their enclosing @media context so the fallback
|
|
784
|
+
// CSS preserves the original media-conditional loading.
|
|
785
|
+
function buildFallbackCssText(containedRelationsByFontFaceRule) {
|
|
786
|
+
const rulesByMedia = new Map();
|
|
787
|
+
for (const rule of containedRelationsByFontFaceRule.keys()) {
|
|
788
|
+
const mediaKey = findEnclosingMediaQuery(rule);
|
|
789
|
+
let texts = rulesByMedia.get(mediaKey);
|
|
790
|
+
if (!texts) {
|
|
791
|
+
texts = [];
|
|
792
|
+
rulesByMedia.set(mediaKey, texts);
|
|
793
|
+
}
|
|
794
|
+
texts.push((0, fontFaceHelpers_1.getFontFaceDeclarationText)(rule, containedRelationsByFontFaceRule.get(rule) ?? []));
|
|
832
795
|
}
|
|
833
|
-
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
console.log(` Subsetting ${uniqueFontUrls} unique font file${uniqueFontUrls === 1 ? '' : 's'}...`);
|
|
796
|
+
let fallbackCssText = '';
|
|
797
|
+
for (const [media, texts] of rulesByMedia) {
|
|
798
|
+
if (media) {
|
|
799
|
+
fallbackCssText += `@media ${media}{${texts.join('')}}`;
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
fallbackCssText += texts.join('');
|
|
841
803
|
}
|
|
842
804
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
return { fontInfo: [], timings };
|
|
805
|
+
return fallbackCssText;
|
|
806
|
+
}
|
|
807
|
+
// Returns the map of @font-face rule node → sibling relations sharing that
|
|
808
|
+
// rule. As a side effect, adds every retained relation to originalRelations
|
|
809
|
+
// so the caller can later remove the original CSS in one pass.
|
|
810
|
+
function collectFontFaceRelations(accumulatedFontFaceDeclarations, originalRelations) {
|
|
811
|
+
const containedRelationsByFontFaceRule = new Map();
|
|
812
|
+
for (const { relations } of accumulatedFontFaceDeclarations) {
|
|
813
|
+
for (const relation of relations) {
|
|
814
|
+
if (
|
|
815
|
+
// Google Web Fonts handled separately in handleGoogleFontStylesheets
|
|
816
|
+
relation.from.hostname ===
|
|
817
|
+
'fonts.googleapis.com' ||
|
|
818
|
+
containedRelationsByFontFaceRule.has(relation.node)) {
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
originalRelations.add(relation);
|
|
822
|
+
containedRelationsByFontFaceRule.set(relation.node, relation.from.outgoingRelations.filter((otherRelation) => otherRelation.node === relation.node));
|
|
823
|
+
}
|
|
863
824
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
825
|
+
return containedRelationsByFontFaceRule;
|
|
826
|
+
}
|
|
827
|
+
// Lazy load the original @font-face declarations of self-hosted fonts (unless
|
|
828
|
+
// omitFallbacks), and collect references into originalRelations so subsetFonts
|
|
829
|
+
// can remove them after the lazy fallback is in place.
|
|
830
|
+
async function emitLazyFallbackCss(ctx, relationsToRemove, originalRelations) {
|
|
831
|
+
const { assetGraph, htmlOrSvgAssets, fontFaceDeclarationsByHtmlOrSvgAsset, omitFallbacks, hrefType, subsetUrl, } = ctx;
|
|
868
832
|
const fallbackCssAssetCache = new Map();
|
|
869
833
|
for (const htmlOrSvgAsset of htmlOrSvgAssets) {
|
|
870
834
|
const accumulatedFontFaceDeclarations = fontFaceDeclarationsByHtmlOrSvgAsset.get(htmlOrSvgAsset);
|
|
871
835
|
if (!accumulatedFontFaceDeclarations)
|
|
872
836
|
continue;
|
|
873
|
-
const containedRelationsByFontFaceRule =
|
|
874
|
-
for (const { relations } of accumulatedFontFaceDeclarations) {
|
|
875
|
-
for (const relation of relations) {
|
|
876
|
-
if (relation.from.hostname ===
|
|
877
|
-
'fonts.googleapis.com' || // Google Web Fonts handled separately below
|
|
878
|
-
containedRelationsByFontFaceRule.has(relation.node)) {
|
|
879
|
-
continue;
|
|
880
|
-
}
|
|
881
|
-
originalRelations.add(relation);
|
|
882
|
-
containedRelationsByFontFaceRule.set(relation.node, relation.from.outgoingRelations.filter((otherRelation) => otherRelation.node === relation.node));
|
|
883
|
-
}
|
|
884
|
-
}
|
|
837
|
+
const containedRelationsByFontFaceRule = collectFontFaceRelations(accumulatedFontFaceDeclarations, originalRelations);
|
|
885
838
|
if (containedRelationsByFontFaceRule.size === 0 ||
|
|
886
839
|
omitFallbacks ||
|
|
887
840
|
htmlOrSvgAsset.type !== 'Html') {
|
|
888
841
|
continue;
|
|
889
842
|
}
|
|
890
|
-
|
|
891
|
-
// fallback CSS preserves the original media-conditional loading.
|
|
892
|
-
// Walk up the ancestor chain in case the rule is nested (e.g.
|
|
893
|
-
// inside @supports inside @media).
|
|
894
|
-
const rulesByMedia = new Map();
|
|
895
|
-
for (const rule of containedRelationsByFontFaceRule.keys()) {
|
|
896
|
-
let mediaKey = '';
|
|
897
|
-
let ancestor = rule.parent;
|
|
898
|
-
while (ancestor) {
|
|
899
|
-
if (ancestor.type === 'atrule' &&
|
|
900
|
-
ancestor.name?.toLowerCase() === 'media') {
|
|
901
|
-
mediaKey = ancestor.params ?? '';
|
|
902
|
-
break;
|
|
903
|
-
}
|
|
904
|
-
ancestor = ancestor.parent;
|
|
905
|
-
}
|
|
906
|
-
if (!rulesByMedia.has(mediaKey))
|
|
907
|
-
rulesByMedia.set(mediaKey, []);
|
|
908
|
-
rulesByMedia
|
|
909
|
-
.get(mediaKey)
|
|
910
|
-
.push((0, fontFaceHelpers_1.getFontFaceDeclarationText)(rule, containedRelationsByFontFaceRule.get(rule) ?? []));
|
|
911
|
-
}
|
|
912
|
-
let fallbackCssText = '';
|
|
913
|
-
for (const [media, texts] of rulesByMedia) {
|
|
914
|
-
if (media) {
|
|
915
|
-
fallbackCssText += `@media ${media}{${texts.join('')}}`;
|
|
916
|
-
}
|
|
917
|
-
else {
|
|
918
|
-
fallbackCssText += texts.join('');
|
|
919
|
-
}
|
|
920
|
-
}
|
|
843
|
+
const fallbackCssText = buildFallbackCssText(containedRelationsByFontFaceRule);
|
|
921
844
|
let cssAsset = fallbackCssAssetCache.get(fallbackCssText);
|
|
922
845
|
if (!cssAsset) {
|
|
923
|
-
cssAsset = assetGraph.addAsset({
|
|
924
|
-
type: 'Css',
|
|
925
|
-
text: fallbackCssText,
|
|
926
|
-
});
|
|
846
|
+
cssAsset = assetGraph.addAsset({ type: 'Css', text: fallbackCssText });
|
|
927
847
|
for (const relation of cssAsset.outgoingRelations) {
|
|
928
848
|
relation.hrefType = hrefType;
|
|
929
849
|
}
|
|
@@ -931,7 +851,8 @@ async function subsetFonts(assetGraph, { formats = ['woff2'], subsetPath = 'subf
|
|
|
931
851
|
cssAsset.url = `${subsetUrl}fallback-${cssAsset.md5Hex.slice(0, 10)}.css`;
|
|
932
852
|
fallbackCssAssetCache.set(fallbackCssText, cssAsset);
|
|
933
853
|
}
|
|
934
|
-
// Create a <link rel="stylesheet"> that asyncLoadStyleRelationWithFallback
|
|
854
|
+
// Create a <link rel="stylesheet"> that asyncLoadStyleRelationWithFallback
|
|
855
|
+
// can convert to async with noscript fallback.
|
|
935
856
|
const fallbackHtmlStyle = htmlOrSvgAsset.addRelation({
|
|
936
857
|
type: 'HtmlStyle',
|
|
937
858
|
to: cssAsset,
|
|
@@ -939,11 +860,12 @@ async function subsetFonts(assetGraph, { formats = ['woff2'], subsetPath = 'subf
|
|
|
939
860
|
asyncLoadStyleRelationWithFallback(htmlOrSvgAsset, fallbackHtmlStyle, hrefType);
|
|
940
861
|
relationsToRemove.add(fallbackHtmlStyle);
|
|
941
862
|
}
|
|
942
|
-
timings['lazy load fallback CSS'] = lazyFallbackPhase.end();
|
|
943
863
|
// Same reasoning as subsetCssAssetCache: keys are full CSS text.
|
|
944
864
|
fallbackCssAssetCache.clear();
|
|
945
|
-
|
|
946
|
-
|
|
865
|
+
}
|
|
866
|
+
// Remove the original @font-face blocks, and don't leave behind empty
|
|
867
|
+
// stylesheets.
|
|
868
|
+
function removeOriginalFontFaceRelations(assetGraph, originalRelations) {
|
|
947
869
|
const maybeEmptyCssAssets = new Set();
|
|
948
870
|
for (const relation of originalRelations) {
|
|
949
871
|
const cssAsset = relation.from;
|
|
@@ -962,12 +884,24 @@ async function subsetFonts(assetGraph, { formats = ['woff2'], subsetPath = 'subf
|
|
|
962
884
|
assetGraph.removeAsset(cssAsset);
|
|
963
885
|
}
|
|
964
886
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
887
|
+
}
|
|
888
|
+
function getHtmlParentsForGoogleFontsRelation(googleFontStylesheetRelation) {
|
|
889
|
+
if (googleFontStylesheetRelation.type === 'CssImport') {
|
|
890
|
+
return getParents(googleFontStylesheetRelation.to, {
|
|
891
|
+
type: { $in: ['Html', 'Svg'] },
|
|
892
|
+
isInline: false,
|
|
893
|
+
isLoaded: true,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
if (['Html', 'Svg'].includes(googleFontStylesheetRelation.from.type ?? '')) {
|
|
897
|
+
return [googleFontStylesheetRelation.from];
|
|
898
|
+
}
|
|
899
|
+
return [];
|
|
900
|
+
}
|
|
901
|
+
// Async load Google Web Fonts CSS. Skip the regex findAssets scan and the
|
|
902
|
+
// surrounding loop entirely when no Google Fonts were detected up front.
|
|
903
|
+
async function handleGoogleFontStylesheets(ctx, relationsToRemove) {
|
|
904
|
+
const { assetGraph, hasGoogleFonts, omitFallbacks, formats, hrefType, subsetUrl, } = ctx;
|
|
971
905
|
const googleFontStylesheets = hasGoogleFonts
|
|
972
906
|
? assetGraph.findAssets({
|
|
973
907
|
type: 'Css',
|
|
@@ -976,23 +910,10 @@ async function subsetFonts(assetGraph, { formats = ['woff2'], subsetPath = 'subf
|
|
|
976
910
|
: [];
|
|
977
911
|
const selfHostedGoogleCssByUrl = new Map();
|
|
978
912
|
for (const googleFontStylesheet of googleFontStylesheets) {
|
|
979
|
-
|
|
913
|
+
// Only do the work once for each font on each page
|
|
914
|
+
const seenPages = new Set();
|
|
980
915
|
for (const googleFontStylesheetRelation of googleFontStylesheet.incomingRelations) {
|
|
981
|
-
|
|
982
|
-
if (googleFontStylesheetRelation.type === 'CssImport') {
|
|
983
|
-
// Gather Html parents. Relevant if we are dealing with CSS @import relations
|
|
984
|
-
htmlParents = getParents(googleFontStylesheetRelation.to, {
|
|
985
|
-
type: { $in: ['Html', 'Svg'] },
|
|
986
|
-
isInline: false,
|
|
987
|
-
isLoaded: true,
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
else if (['Html', 'Svg'].includes(googleFontStylesheetRelation.from.type ?? '')) {
|
|
991
|
-
htmlParents = [googleFontStylesheetRelation.from];
|
|
992
|
-
}
|
|
993
|
-
else {
|
|
994
|
-
htmlParents = [];
|
|
995
|
-
}
|
|
916
|
+
const htmlParents = getHtmlParentsForGoogleFontsRelation(googleFontStylesheetRelation);
|
|
996
917
|
for (const htmlParent of htmlParents) {
|
|
997
918
|
if (seenPages.has(htmlParent))
|
|
998
919
|
continue;
|
|
@@ -1020,23 +941,164 @@ async function subsetFonts(assetGraph, { formats = ['woff2'], subsetPath = 'subf
|
|
|
1020
941
|
}
|
|
1021
942
|
googleFontStylesheet.unload();
|
|
1022
943
|
}
|
|
1023
|
-
// Cache served its purpose
|
|
1024
|
-
// function don't need it. Free the URL keys before injection runs.
|
|
944
|
+
// Cache served its purpose. Free the URL keys before injection runs.
|
|
1025
945
|
selfHostedGoogleCssByUrl.clear();
|
|
1026
|
-
|
|
946
|
+
}
|
|
947
|
+
async function runCollectAndPrepPagesPhase(ctx) {
|
|
948
|
+
const collectPhase = ctx.trackPhase(`collectTextsByPage (${ctx.htmlOrSvgAssets.length} pages)`);
|
|
949
|
+
const { htmlOrSvgAssetTextsWithProps, fontFaceDeclarationsByHtmlOrSvgAsset, subTimings, } = await collectTextsByPage(ctx.assetGraph, ctx.htmlOrSvgAssets, {
|
|
950
|
+
text: ctx.text,
|
|
951
|
+
console: ctx.console,
|
|
952
|
+
dynamic: ctx.dynamic,
|
|
953
|
+
debug: ctx.debug,
|
|
954
|
+
concurrency: ctx.concurrency,
|
|
955
|
+
chromeArgs: ctx.chromeArgs,
|
|
956
|
+
});
|
|
957
|
+
ctx.timings.collectTextsByPage = collectPhase.end();
|
|
958
|
+
ctx.timings.collectTextsByPageDetails = subTimings;
|
|
959
|
+
// textByProps is consumed inside collectTextsByPage (see buildPerPageFont-
|
|
960
|
+
// Usages) and never read again by anything in the subsetFonts pipeline;
|
|
961
|
+
// the raw font-tracer text strings inside scale with #pages and are the
|
|
962
|
+
// largest per-page artefact at the 1800-page scale. Release them before
|
|
963
|
+
// computeCodepoints / subsetting / injection so they don't pin heap.
|
|
964
|
+
for (const entry of htmlOrSvgAssetTextsWithProps) {
|
|
965
|
+
entry.textByProps = [];
|
|
966
|
+
}
|
|
967
|
+
const omitFallbacksPhase = ctx.trackPhase('omitFallbacks processing');
|
|
968
|
+
if (ctx.omitFallbacks) {
|
|
969
|
+
removeOriginalFontFaceRules(ctx.htmlOrSvgAssets, fontFaceDeclarationsByHtmlOrSvgAsset, ctx.potentiallyOrphanedAssets);
|
|
970
|
+
}
|
|
971
|
+
ctx.timings['omitFallbacks processing'] = omitFallbacksPhase.end();
|
|
972
|
+
// Stage 1 → 2 placeholder: SubsettedFontUsage only adds optional fields
|
|
973
|
+
// on top of TracedFontUsage; this upcast happens implicitly in the
|
|
974
|
+
// returned AssetTextWithProps shape.
|
|
975
|
+
return {
|
|
976
|
+
pages: htmlOrSvgAssetTextsWithProps,
|
|
977
|
+
fontFaceDeclarationsByHtmlOrSvgAsset,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
async function runSubsetPhase(ctx) {
|
|
981
|
+
const variationPhase = ctx.trackPhase('variation axis usage');
|
|
982
|
+
const { seenAxisValuesByFontUrlAndAxisName } = (0, variationAxes_1.getVariationAxisUsage)(ctx.pages, fontFaceHelpers_1.parseFontWeightRange, fontFaceHelpers_1.parseFontStretchRange);
|
|
983
|
+
ctx.timings['variation axis usage'] = variationPhase.end();
|
|
984
|
+
if (ctx.console) {
|
|
985
|
+
const uniqueFontUrls = countUniqueFontUrls(ctx.pages);
|
|
986
|
+
if (uniqueFontUrls > 0) {
|
|
987
|
+
ctx.console.log(` Subsetting ${uniqueFontUrls} unique font file${uniqueFontUrls === 1 ? '' : 's'}...`);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
const subsetPhase = ctx.trackPhase('getSubsetsForFontUsage');
|
|
991
|
+
await (0, subsetGeneration_1.getSubsetsForFontUsage)(ctx.assetGraph, ctx.pages, ctx.formats, seenAxisValuesByFontUrlAndAxisName, ctx.cacheDir, ctx.console, ctx.debug);
|
|
992
|
+
ctx.timings.getSubsetsForFontUsage = subsetPhase.end();
|
|
993
|
+
const warnGlyphsPhase = ctx.trackPhase('warnAboutMissingGlyphs');
|
|
994
|
+
await warnAboutMissingGlyphs(ctx.pages, ctx.assetGraph);
|
|
995
|
+
ctx.timings.warnAboutMissingGlyphs = warnGlyphsPhase.end();
|
|
996
|
+
}
|
|
997
|
+
async function runPostSubsetCleanup(ctx) {
|
|
998
|
+
const relationsToRemove = new Set();
|
|
999
|
+
const originalRelations = new Set();
|
|
1000
|
+
const lazyFallbackPhase = ctx.trackPhase('lazy load fallback CSS');
|
|
1001
|
+
await emitLazyFallbackCss(ctx, relationsToRemove, originalRelations);
|
|
1002
|
+
ctx.timings['lazy load fallback CSS'] = lazyFallbackPhase.end();
|
|
1003
|
+
const removeFontFacePhase = ctx.trackPhase('remove original @font-face');
|
|
1004
|
+
removeOriginalFontFaceRelations(ctx.assetGraph, originalRelations);
|
|
1005
|
+
ctx.timings['remove original @font-face'] = removeFontFacePhase.end();
|
|
1006
|
+
const googleCleanupPhase = ctx.trackPhase('Google Fonts + cleanup');
|
|
1007
|
+
await handleGoogleFontStylesheets(ctx, relationsToRemove);
|
|
1008
|
+
// Clean up, making sure not to detach the same relation twice, eg. when
|
|
1009
|
+
// multiple pages use the same stylesheet that imports a font.
|
|
1027
1010
|
for (const relation of relationsToRemove) {
|
|
1028
1011
|
relation.detach();
|
|
1029
1012
|
}
|
|
1030
|
-
timings['Google Fonts + cleanup'] = googleCleanupPhase.end();
|
|
1031
|
-
const injectPhase = trackPhase('inject subset font-family into CSS/SVG');
|
|
1032
|
-
injectSubsetFontFamilies(assetGraph, pages, omitFallbacks);
|
|
1033
|
-
timings['inject subset font-family'] = injectPhase.end();
|
|
1034
|
-
const orphanCleanupPhase = trackPhase('source maps + orphan cleanup');
|
|
1013
|
+
ctx.timings['Google Fonts + cleanup'] = googleCleanupPhase.end();
|
|
1014
|
+
const injectPhase = ctx.trackPhase('inject subset font-family into CSS/SVG');
|
|
1015
|
+
injectSubsetFontFamilies(ctx.assetGraph, ctx.pages, ctx.omitFallbacks);
|
|
1016
|
+
ctx.timings['inject subset font-family'] = injectPhase.end();
|
|
1017
|
+
const orphanCleanupPhase = ctx.trackPhase('source maps + orphan cleanup');
|
|
1018
|
+
if (ctx.sourceMaps) {
|
|
1019
|
+
await rewriteCssSourceMaps(ctx.assetGraph, ctx.hrefType);
|
|
1020
|
+
}
|
|
1021
|
+
removeOrphanedAssets(ctx.assetGraph, ctx.potentiallyOrphanedAssets);
|
|
1022
|
+
ctx.timings['source maps + orphan cleanup'] = orphanCleanupPhase.end();
|
|
1023
|
+
}
|
|
1024
|
+
async function subsetFonts(assetGraph, { formats = ['woff2'], subsetPath = 'subfont/', omitFallbacks = false, inlineCss = false, fontDisplay, hrefType = 'rootRelative', onlyInfo, dynamic, console = global.console, text, sourceMaps = false, debug = false, concurrency, chromeArgs = [], cacheDir = null, } = {}) {
|
|
1025
|
+
if (fontDisplay && !validFontDisplayValues.includes(fontDisplay)) {
|
|
1026
|
+
fontDisplay = undefined;
|
|
1027
|
+
}
|
|
1028
|
+
// Pre-warm the WASM pool: start compiling harfbuzz WASM while
|
|
1029
|
+
// collectTextsByPage traces fonts. Compilation (~50-200ms) overlaps
|
|
1030
|
+
// with tracing work rather than appearing on the critical path.
|
|
1031
|
+
subsetFontWithGlyphs.warmup().catch((err) => {
|
|
1032
|
+
if (debug) {
|
|
1033
|
+
console.warn('WASM warmup failed (will retry on first subset call):', err);
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
const subsetUrl = urltools.ensureTrailingSlash(assetGraph.root + subsetPath);
|
|
1037
|
+
const timings = {};
|
|
1038
|
+
const trackPhase = (0, progress_1.makePhaseTracker)(console, debug);
|
|
1039
|
+
const applySourceMapsPhase = trackPhase('applySourceMaps');
|
|
1035
1040
|
if (sourceMaps) {
|
|
1036
|
-
await
|
|
1041
|
+
await assetGraph.applySourceMaps({ type: 'Css' });
|
|
1042
|
+
}
|
|
1043
|
+
timings.applySourceMaps = applySourceMapsPhase.end();
|
|
1044
|
+
const googlePopulatePhase = trackPhase('populate (google fonts)');
|
|
1045
|
+
const hasGoogleFonts = await populateGoogleFontsIfPresent(assetGraph);
|
|
1046
|
+
timings['populate (google fonts)'] = googlePopulatePhase.end(hasGoogleFonts ? null : 'skipped, no Google Fonts found');
|
|
1047
|
+
const preCtx = {
|
|
1048
|
+
assetGraph,
|
|
1049
|
+
htmlOrSvgAssets: assetGraph.findAssets({
|
|
1050
|
+
$or: [{ type: 'Html', isInline: false }, { type: 'Svg' }],
|
|
1051
|
+
}),
|
|
1052
|
+
console,
|
|
1053
|
+
debug,
|
|
1054
|
+
text,
|
|
1055
|
+
dynamic,
|
|
1056
|
+
concurrency,
|
|
1057
|
+
chromeArgs,
|
|
1058
|
+
formats,
|
|
1059
|
+
hrefType,
|
|
1060
|
+
subsetUrl,
|
|
1061
|
+
omitFallbacks,
|
|
1062
|
+
sourceMaps,
|
|
1063
|
+
cacheDir,
|
|
1064
|
+
hasGoogleFonts,
|
|
1065
|
+
potentiallyOrphanedAssets: new Set(),
|
|
1066
|
+
trackPhase,
|
|
1067
|
+
timings,
|
|
1068
|
+
};
|
|
1069
|
+
const { pages, fontFaceDeclarationsByHtmlOrSvgAsset } = await runCollectAndPrepPagesPhase(preCtx);
|
|
1070
|
+
const ctx = {
|
|
1071
|
+
...preCtx,
|
|
1072
|
+
pages,
|
|
1073
|
+
fontFaceDeclarationsByHtmlOrSvgAsset,
|
|
1074
|
+
};
|
|
1075
|
+
const codepointPhase = trackPhase('codepoint generation');
|
|
1076
|
+
await computeCodepoints(assetGraph, pages, fontDisplay);
|
|
1077
|
+
timings['codepoint generation'] = codepointPhase.end();
|
|
1078
|
+
if (onlyInfo) {
|
|
1079
|
+
// Stage 2 hasn't run, but buildFontInfoReport's input only requires
|
|
1080
|
+
// `codepoints` (stage 3) — already attached above.
|
|
1081
|
+
return {
|
|
1082
|
+
fontInfo: buildFontInfoReport(pages),
|
|
1083
|
+
timings,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
await runSubsetPhase(ctx);
|
|
1087
|
+
const insertPhase = trackPhase(`insert subsets loop (${pages.length} pages)`);
|
|
1088
|
+
const { numFontUsagesWithSubset } = await insertSubsets({
|
|
1089
|
+
assetGraph,
|
|
1090
|
+
pages,
|
|
1091
|
+
formats,
|
|
1092
|
+
subsetUrl,
|
|
1093
|
+
hrefType,
|
|
1094
|
+
inlineCss,
|
|
1095
|
+
omitFallbacks,
|
|
1096
|
+
});
|
|
1097
|
+
timings['insert subsets loop'] = insertPhase.end();
|
|
1098
|
+
if (numFontUsagesWithSubset === 0) {
|
|
1099
|
+
return { fontInfo: [], timings };
|
|
1037
1100
|
}
|
|
1038
|
-
|
|
1039
|
-
timings['source maps + orphan cleanup'] = orphanCleanupPhase.end();
|
|
1101
|
+
await runPostSubsetCleanup(ctx);
|
|
1040
1102
|
return {
|
|
1041
1103
|
fontInfo: buildFontInfoReport(pages),
|
|
1042
1104
|
timings,
|