diff --git a/README.md b/README.md index 24c65ee..86784ec 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@ Options: [boolean] [default: false] --relative-urls Issue relative urls instead of root-relative ones [boolean] [default: false] + --instance Experimentally instance variable fonts when the variation + space isn't fully used [boolean] [default: false] --silent, -s Do not write anything to stdout [boolean] [default: false] --debug, -d Verbose insights into font glyph detection [boolean] [default: false] diff --git a/lib/parseCommandLineOptions.js b/lib/parseCommandLineOptions.js index 62988e3..c47d55f 100644 --- a/lib/parseCommandLineOptions.js +++ b/lib/parseCommandLineOptions.js @@ -75,7 +75,7 @@ module.exports = function parseCommandLineOptions(argv) { default: false, }) .options('font-display', { - describe: 'Injects a font-display value into the @font-face CSS.', + describe: 'Injects a font-display value into the @font-face CSS', type: 'string', default: 'swap', choices: ['auto', 'block', 'swap', 'fallback', 'optional'], @@ -94,7 +94,7 @@ module.exports = function parseCommandLineOptions(argv) { }) .options('instance', { describe: - 'Experimentally instance variable fonts when every variation axis only has one value (only supports full instancing for now)', + "Experimentally instance variable fonts when the variation space isn't fully used", type: 'boolean', default: false, }) diff --git a/lib/subfont.js b/lib/subfont.js index 92a1937..1f223c4 100644 --- a/lib/subfont.js +++ b/lib/subfont.js @@ -359,8 +359,30 @@ module.exports = async function subfont( fontUsage.smallestOriginalSize !== undefined && fontUsage.smallestSubsetSize !== undefined ) { - if (fontUsage.variationAxes) { + if (fontUsage.fullyInstanced) { status += ', fully instanced'; + } else if (fontUsage.numAxesReduced > 0 || fontUsage.numAxesPinned) { + const instancingInfos = []; + if (fontUsage.numAxesPinned > 0) { + instancingInfos.push( + `${fontUsage.numAxesPinned} ${ + fontUsage.numAxesPinned === 1 ? 'axis' : 'axes' + } pinned` + ); + } + if (fontUsage.numAxesReduced) { + instancingInfos.push( + `${fontUsage.numAxesReduced}${ + fontUsage.numAxesPinned > 0 + ? '' + : fontUsage.numAxesReduced === 1 + ? ' axis' + : ' axes' + } reduced` + ); + } + + status += `, partially instanced (${instancingInfos.join(', ')})`; } status += `, ${prettyBytes(fontUsage.smallestOriginalSize)} (${ fontUsage.smallestOriginalFormat diff --git a/lib/subsetFonts.js b/lib/subsetFonts.js index 4e500ae..5addcd3 100644 --- a/lib/subsetFonts.js +++ b/lib/subsetFonts.js @@ -383,7 +383,7 @@ function getSubsetPromiseId(fontUsage, format, variationAxes = null) { ].join('\x1d'); } -async function getFullyPinnedVariationAxes( +async function getVariationAxisBounds( assetGraph, fontUrl, seenAxisValuesByFontUrlAndAxisName @@ -392,13 +392,14 @@ async function getFullyPinnedVariationAxes( assetGraph.findAssets({ url: fontUrl })[0].rawSrc ); - let variationAxes; + const variationAxes = {}; + let fullyInstanced = true; + let numAxesPinned = 0; + let numAxesReduced = 0; const fontVariationEntries = Object.entries(fontInfo.variationAxes); const seenAxisValuesByAxisName = seenAxisValuesByFontUrlAndAxisName.get(fontUrl); if (fontVariationEntries.length > 0 && seenAxisValuesByAxisName) { - variationAxes = {}; - let everyAxisPinned = true; for (const [ axisName, { min, max, default: defaultValue }, @@ -409,16 +410,27 @@ async function getFullyPinnedVariationAxes( } if (seenAxisValues && seenAxisValues.size === 1) { variationAxes[axisName] = _.clamp([...seenAxisValues][0], min, max); - } else { - everyAxisPinned = false; + numAxesPinned += 1; + } else if (seenAxisValues) { + const minSeenValue = Math.min(...seenAxisValues); + const maxSeenValue = Math.max(...seenAxisValues); + variationAxes[axisName] = { + min: Math.min(minSeenValue, min), + max: Math.min(maxSeenValue, max), + }; + fullyInstanced = false; + if (minSeenValue > min || maxSeenValue < max) { + numAxesReduced += 1; + } } } - if (!everyAxisPinned) { - // Not all variation axes can be fully pinned, bail out - variationAxes = undefined; - } } - return variationAxes; + return { + fullyInstanced, + numAxesReduced, + numAxesPinned, + variationAxes, + }; } async function getSubsetsForFontUsage( @@ -462,22 +474,25 @@ async function getSubsetsForFontUsage( }, {}); const subsetPromiseMap = {}; - const notFullyInstancedFontUrls = new Set(); for (const item of htmlOrSvgAssetTextsWithProps) { for (const fontUsage of item.fontUsages) { const fontBuffer = originalFontBuffers[fontUsage.fontUrl]; const text = fontUsage.text; let variationAxes; + let fullyInstanced = false; + let numAxesReduced = 0; + let numAxesPinned = 0; if (instance) { - variationAxes = await getFullyPinnedVariationAxes( + const res = await getVariationAxisBounds( assetGraph, fontUsage.fontUrl, seenAxisValuesByFontUrlAndAxisName ); - } - if (!variationAxes) { - notFullyInstancedFontUrls.add(fontUsage.fontUrl); + variationAxes = res.variationAxes; + fullyInstanced = res.fullyInstanced; + numAxesReduced = res.numAxesReduced; + numAxesPinned = res.numAxesPinned; } for (const targetFormat of formats) { @@ -515,6 +530,9 @@ async function getSubsetsForFontUsage( fontUsage.smallestSubsetSize = size; fontUsage.smallestSubsetFormat = targetFormat; fontUsage.variationAxes = variationAxes; + fontUsage.fullyInstanced = fullyInstanced; + fontUsage.numAxesPinned = numAxesPinned; + fontUsage.numAxesReduced = numAxesReduced; } } }); @@ -523,8 +541,6 @@ async function getSubsetsForFontUsage( } await Promise.all(Object.values(subsetPromiseMap)); - - return { notFullyInstancedFontUrls }; } const fontOrder = ['woff2', 'woff', 'truetype']; @@ -949,17 +965,13 @@ function getVariationAxisUsage(htmlOrSvgAssetTextsWithProps) { async function warnAboutUnusedVariationAxes( assetGraph, seenAxisValuesByFontUrlAndAxisName, - outOfBoundsAxesByFontUrl, - notFullyInstancedFontUrls + outOfBoundsAxesByFontUrl ) { const warnings = []; for (const [ fontUrl, seenAxisValuesByAxisName, ] of seenAxisValuesByFontUrlAndAxisName.entries()) { - if (!notFullyInstancedFontUrls.has(fontUrl)) { - continue; - } const outOfBoundsAxes = outOfBoundsAxesByFontUrl.get(fontUrl) || new Set(); let fontInfo; try { @@ -1030,7 +1042,7 @@ async function warnAboutUnusedVariationAxes( new Error(`🪓 Unused variation axes detected in your variable fonts. The below variable fonts contain custom axes that do not appear to be fully used on any of your pages. This bloats your fonts and also the subset fonts that subfont creates. -Consider removing the unused axis ranges using a tool like Slice +Consider removing the unused axis ranges by specifying the --instance switch ${warnings.join('\n')}`) ); } @@ -1320,7 +1332,7 @@ async function subsetFonts( getVariationAxisUsage(htmlOrSvgAssetTextsWithProps); // Generate subsets: - const { notFullyInstancedFontUrls } = await getSubsetsForFontUsage( + await getSubsetsForFontUsage( assetGraph, htmlOrSvgAssetTextsWithProps, formats, @@ -1329,12 +1341,13 @@ async function subsetFonts( ); await warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph); - await warnAboutUnusedVariationAxes( - assetGraph, - seenAxisValuesByFontUrlAndAxisName, - outOfBoundsAxesByFontUrl, - notFullyInstancedFontUrls - ); + if (!instance) { + await warnAboutUnusedVariationAxes( + assetGraph, + seenAxisValuesByFontUrlAndAxisName, + outOfBoundsAxesByFontUrl + ); + } // Insert subsets: diff --git a/test/subsetFonts.js b/test/subsetFonts.js index ee40089..772f950 100644 --- a/test/subsetFonts.js +++ b/test/subsetFonts.js @@ -299,6 +299,10 @@ describe('subsetFonts', function () { page: [72, 101, 108, 111, 32], }, preload: true, + variationAxes: undefined, + fullyInstanced: false, + numAxesPinned: 0, + numAxesReduced: 0, }, ], }, @@ -3211,8 +3215,8 @@ describe('subsetFonts', function () { }); }); - describe('with a variable font that can only be partially instanced', function () { - it('should keep the variation axes', async function () { + describe('with a variable font that can be partially instanced', function () { + it('should perform a partial instancing', async function () { const assetGraph = new AssetGraph({ root: pathModule.resolve( __dirname, @@ -3232,19 +3236,7 @@ describe('subsetFonts', function () { const { variationAxes } = await getFontInfo(subsetFontAssets[0].rawSrc); expect(variationAxes, 'to equal', { - wght: { min: 100, default: 400, max: 1000 }, - wdth: { min: 25, default: 100, max: 151 }, - opsz: { min: 8, default: 14, max: 144 }, - GRAD: { min: -200, default: 0, max: 150 }, - slnt: { min: -10, default: 0, max: 0 }, - XTRA: { min: 323, default: 468, max: 603 }, - XOPQ: { min: 27, default: 96, max: 175 }, - YOPQ: { min: 25, default: 79, max: 135 }, - YTLC: { min: 416, default: 514, max: 570 }, - YTUC: { min: 528, default: 712, max: 760 }, - YTAS: { min: 649, default: 750, max: 854 }, - YTDE: { min: -305, default: -203, max: -98 }, - YTFI: { min: 560, default: 738, max: 788 }, + wght: { min: 100, default: 400, max: 405 }, }); }); });