From 38bf36eba4370ecdbc777cb2457696b9bc7c7d1c Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Fri, 5 Aug 2022 12:11:54 +0200 Subject: [PATCH] Move the missing glyph detection to a function --- lib/subsetFonts.js | 179 +++++++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 88 deletions(-) diff --git a/lib/subsetFonts.js b/lib/subsetFonts.js index 98800c2a..d32f6c85 100644 --- a/lib/subsetFonts.js +++ b/lib/subsetFonts.js @@ -574,6 +574,96 @@ function cssAssetIsEmpty(cssAsset) { ); } +function warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph) { + const missingGlyphsErrors = []; + + for (const { + htmlOrSvgAsset, + fontUsages, + accumulatedFontFaceDeclarations, + } of htmlOrSvgAssetTextsWithProps) { + for (const fontUsage of fontUsages) { + if (fontUsage.subsets) { + const characterSet = fontkit.create( + Object.values(fontUsage.subsets)[0] + ).characterSet; + + let missedAny = false; + for (const char of [...fontUsage.pageText]) { + // Turns out that browsers don't mind that these are missing: + if (char === '\t' || char === '\n') { + continue; + } + + const codePoint = char.codePointAt(0); + + const isMissing = !characterSet.includes(codePoint); + + if (isMissing) { + let location; + const charIdx = htmlOrSvgAsset.text.indexOf(char); + + if (charIdx === -1) { + location = `${htmlOrSvgAsset.urlOrDescription} (generated content)`; + } else { + const position = new LinesAndColumns( + htmlOrSvgAsset.text + ).locationForIndex(charIdx); + location = `${htmlOrSvgAsset.urlOrDescription}:${ + position.line + 1 + }:${position.column + 1}`; + } + + missingGlyphsErrors.push({ + codePoint, + char, + htmlOrSvgAsset, + fontUsage, + location, + }); + missedAny = true; + } + } + if (missedAny) { + const fontFaces = accumulatedFontFaceDeclarations.filter((fontFace) => + fontUsage.fontFamilies.has(fontFace['font-family']) + ); + for (const fontFace of fontFaces) { + const cssFontFaceSrc = fontFace.relations[0]; + const fontFaceDeclaration = cssFontFaceSrc.node; + if ( + !fontFaceDeclaration.some((node) => node.prop === 'unicode-range') + ) { + fontFaceDeclaration.append({ + prop: 'unicode-range', + value: unicodeRange(fontUsage.codepoints.original), + }); + cssFontFaceSrc.from.markDirty(); + } + } + } + } + } + } + + if (missingGlyphsErrors.length) { + const errorLog = missingGlyphsErrors.map( + ({ char, fontUsage, location }) => + `- \\u{${char.codePointAt(0).toString(16)}} (${char}) in font-family '${ + fontUsage.props['font-family'] + }' (${fontUsage.props['font-weight']}/${ + fontUsage.props['font-style'] + }) at ${location}` + ); + + const message = `Missing glyph fallback detected. +When your primary webfont doesn't contain the glyphs you use, browsers that don't support unicode-range will load your fallback fonts, which will be a potential waste of bandwidth. +These glyphs are used on your site, but they don't exist in the font you applied to them:`; + + assetGraph.info(new Error(`${message}\n${errorLog.join('\n')}`)); + } +} + async function subsetFonts( assetGraph, { @@ -844,94 +934,7 @@ async function subsetFonts( formats ); - // Warn about missing glyphs - const missingGlyphsErrors = []; - - for (const { - htmlOrSvgAsset, - fontUsages, - accumulatedFontFaceDeclarations, - } of htmlOrSvgAssetTextsWithProps) { - for (const fontUsage of fontUsages) { - if (fontUsage.subsets) { - const characterSet = fontkit.create( - Object.values(fontUsage.subsets)[0] - ).characterSet; - - let missedAny = false; - for (const char of [...fontUsage.pageText]) { - // Turns out that browsers don't mind that these are missing: - if (char === '\t' || char === '\n') { - continue; - } - - const codePoint = char.codePointAt(0); - - const isMissing = !characterSet.includes(codePoint); - - if (isMissing) { - let location; - const charIdx = htmlOrSvgAsset.text.indexOf(char); - - if (charIdx === -1) { - location = `${htmlOrSvgAsset.urlOrDescription} (generated content)`; - } else { - const position = new LinesAndColumns( - htmlOrSvgAsset.text - ).locationForIndex(charIdx); - location = `${htmlOrSvgAsset.urlOrDescription}:${ - position.line + 1 - }:${position.column + 1}`; - } - - missingGlyphsErrors.push({ - codePoint, - char, - htmlOrSvgAsset, - fontUsage, - location, - }); - missedAny = true; - } - } - if (missedAny) { - const fontFaces = accumulatedFontFaceDeclarations.filter((fontFace) => - fontUsage.fontFamilies.has(fontFace['font-family']) - ); - for (const fontFace of fontFaces) { - const cssFontFaceSrc = fontFace.relations[0]; - const fontFaceDeclaration = cssFontFaceSrc.node; - if ( - !fontFaceDeclaration.some((node) => node.prop === 'unicode-range') - ) { - fontFaceDeclaration.append({ - prop: 'unicode-range', - value: unicodeRange(fontUsage.codepoints.original), - }); - cssFontFaceSrc.from.markDirty(); - } - } - } - } - } - } - - if (missingGlyphsErrors.length) { - const errorLog = missingGlyphsErrors.map( - ({ char, fontUsage, location }) => - `- \\u{${char.codePointAt(0).toString(16)}} (${char}) in font-family '${ - fontUsage.props['font-family'] - }' (${fontUsage.props['font-weight']}/${ - fontUsage.props['font-style'] - }) at ${location}` - ); - - const message = `Missing glyph fallback detected. -When your primary webfont doesn't contain the glyphs you use, browsers that don't support unicode-range will load your fallback fonts, which will be a potential waste of bandwidth. -These glyphs are used on your site, but they don't exist in the font you applied to them:`; - - assetGraph.info(new Error(`${message}\n${errorLog.join('\n')}`)); - } + warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph); // Insert subsets: