From 7e49fae164510b90cebbcd6a2a99dcdef990c1c1 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sat, 8 Apr 2023 22:09:13 +0200 Subject: [PATCH 1/6] npm i harfbuzzjs@0.3.2 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index de40121..0bc2bed 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "fontkit": "^1.8.0", "fontverter": "^2.0.0", "gettemporaryfilepath": "^1.0.1", + "harfbuzzjs": "^0.3.2", "lines-and-columns": "^1.1.6", "lodash": "^4.17.15", "memoizesync": "^1.1.1", From 362f7590b33d2499745036382887596310623be6 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sat, 8 Apr 2023 23:38:53 +0200 Subject: [PATCH 2/6] Replace fontkit with harfbuzzjs --- lib/getFontInfo.js | 18 ++++++++++++ lib/subsetFonts.js | 70 ++++++++++++++++++++------------------------- test/subfont.js | 14 ++++----- test/subsetFonts.js | 9 ++---- 4 files changed, 59 insertions(+), 52 deletions(-) create mode 100644 lib/getFontInfo.js diff --git a/lib/getFontInfo.js b/lib/getFontInfo.js new file mode 100644 index 0000000..ee81dde --- /dev/null +++ b/lib/getFontInfo.js @@ -0,0 +1,18 @@ +const fontverter = require('fontverter'); + +module.exports = async function getFontInfo(buffer) { + const harfbuzzJs = await require('harfbuzzjs'); + + const blob = harfbuzzJs.createBlob(await fontverter.convert(buffer, 'sfnt')); // Load the font data into something Harfbuzz can use + const face = harfbuzzJs.createFace(blob, 0); // Select the first font in the file (there's normally only one!) + + const fontInfo = { + characterSet: Array.from(face.collectUnicodes()), + variationAxes: face.getAxisInfos(), + }; + + face.destroy(); + blob.destroy(); + + return fontInfo; +}; diff --git a/lib/subsetFonts.js b/lib/subsetFonts.js index 8d60fdf..677890d 100644 --- a/lib/subsetFonts.js +++ b/lib/subsetFonts.js @@ -20,13 +20,13 @@ const injectSubsetDefinitions = require('./injectSubsetDefinitions'); const cssFontParser = require('css-font-parser'); const cssListHelpers = require('css-list-helpers'); const LinesAndColumns = require('lines-and-columns').default; -const fontkit = require('fontkit'); const crypto = require('crypto'); const unquote = require('./unquote'); const normalizeFontPropertyValue = require('./normalizeFontPropertyValue'); const getCssRulesByProperty = require('./getCssRulesByProperty'); const unicodeRange = require('./unicodeRange'); +const getFontInfo = require('./getFontInfo'); const googleFontsCssUrlRegex = /^(?:https?:)?\/\/fonts\.googleapis\.com\/css/; @@ -383,26 +383,17 @@ function getSubsetPromiseId(fontUsage, format, variationAxes = null) { ].join('\x1d'); } -function createFontkitMemoizer(assetGraph) { - return memoizeSync(function (url) { - return fontkit.create(assetGraph.findAssets({ url })[0].rawSrc); - }); -} - -function getFullyPinnedVariationAxes( - fontkitMemoizer, +async function getFullyPinnedVariationAxes( + assetGraph, fontUrl, seenAxisValuesByFontUrlAndAxisName ) { - let font; - try { - font = fontkitMemoizer(fontUrl); - } catch (err) { - // Don't break if we encounter an invalid font or one that's unsupported by fontkit - return; - } + const fontInfo = await getFontInfo( + assetGraph.findAssets({ url: fontUrl })[0].rawSrc + ); + let variationAxes; - const fontVariationEntries = Object.entries(font.variationAxes); + const fontVariationEntries = Object.entries(fontInfo.variationAxes); const seenAxisValuesByAxisName = seenAxisValuesByFontUrlAndAxisName.get(fontUrl); if (fontVariationEntries.length > 0 && seenAxisValuesByAxisName) { @@ -435,7 +426,6 @@ async function getSubsetsForFontUsage( htmlOrSvgAssetTextsWithProps, formats, seenAxisValuesByFontUrlAndAxisName, - fontkitMemoizer, instance = false ) { const allFonts = []; @@ -480,8 +470,8 @@ async function getSubsetsForFontUsage( const text = fontUsage.text; let variationAxes; if (instance) { - variationAxes = getFullyPinnedVariationAxes( - fontkitMemoizer, + variationAxes = await getFullyPinnedVariationAxes( + assetGraph, fontUsage.fontUrl, seenAxisValuesByFontUrlAndAxisName ); @@ -681,7 +671,7 @@ async function createSelfHostedGoogleFontsCssAsset( lines.push(` src: ${srcFragments.join(', ')};`); lines.push( ` unicode-range: ${unicodeRange( - fontkit.create(cssFontFaceSrc.to.rawSrc).characterSet + (await getFontInfo(cssFontFaceSrc.to.rawSrc)).characterSet )};` ); lines.push('}'); @@ -761,7 +751,10 @@ function parseFontStretchRange(str) { return [minFontStretch, maxFontStretch]; } -function warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph) { +async function warnAboutMissingGlyphs( + htmlOrSvgAssetTextsWithProps, + assetGraph +) { const missingGlyphsErrors = []; for (const { @@ -771,9 +764,9 @@ function warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph) { } of htmlOrSvgAssetTextsWithProps) { for (const fontUsage of fontUsages) { if (fontUsage.subsets) { - const characterSet = fontkit.create( + const { characterSet } = await getFontInfo( Object.values(fontUsage.subsets)[0] - ).characterSet; + ); let missedAny = false; for (const char of [...fontUsage.pageText]) { @@ -953,12 +946,11 @@ function getVariationAxisUsage(htmlOrSvgAssetTextsWithProps) { return { seenAxisValuesByFontUrlAndAxisName, outOfBoundsAxesByFontUrl }; } -function warnAboutUnusedVariationAxes( +async function warnAboutUnusedVariationAxes( assetGraph, seenAxisValuesByFontUrlAndAxisName, outOfBoundsAxesByFontUrl, - notFullyInstancedFontUrls, - fontkitMemoizer + notFullyInstancedFontUrls ) { const warnings = []; for (const [ @@ -969,17 +961,20 @@ function warnAboutUnusedVariationAxes( continue; } const outOfBoundsAxes = outOfBoundsAxesByFontUrl.get(fontUrl) || new Set(); - let font; + let fontInfo; try { - font = fontkitMemoizer(fontUrl); + fontInfo = await getFontInfo( + assetGraph.findAssets({ url: fontUrl })[0].rawSrc + ); } catch (err) { - // Don't break if we encounter an invalid font or one that's unsupported by fontkit + // Don't break if we encounter an invalid font continue; } + const unusedAxes = []; const underutilizedAxes = []; for (const [name, { min, max, default: defaultValue }] of Object.entries( - font.variationAxes + fontInfo.variationAxes )) { if (ignoredVariationAxes.has(name)) { continue; @@ -1290,7 +1285,8 @@ async function subsetFonts( let originalCodepoints; try { // Guard against 'Unknown font format' errors - originalCodepoints = fontkit.create(originalFont.rawSrc).characterSet; + originalCodepoints = (await getFontInfo(originalFont.rawSrc)) + .characterSet; } catch (err) {} if (originalCodepoints) { const usedCodepoints = getCodepoints(fontUsage.text); @@ -1323,25 +1319,21 @@ async function subsetFonts( const { seenAxisValuesByFontUrlAndAxisName, outOfBoundsAxesByFontUrl } = getVariationAxisUsage(htmlOrSvgAssetTextsWithProps); - const fontkitMemoizer = createFontkitMemoizer(assetGraph); - // Generate subsets: const { notFullyInstancedFontUrls } = await getSubsetsForFontUsage( assetGraph, htmlOrSvgAssetTextsWithProps, formats, seenAxisValuesByFontUrlAndAxisName, - fontkitMemoizer, instance ); - warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph); - warnAboutUnusedVariationAxes( + await warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph); + await warnAboutUnusedVariationAxes( assetGraph, seenAxisValuesByFontUrlAndAxisName, outOfBoundsAxesByFontUrl, - notFullyInstancedFontUrls, - fontkitMemoizer + notFullyInstancedFontUrls ); // Insert subsets: diff --git a/test/subfont.js b/test/subfont.js index 6e02240..da6c4f6 100644 --- a/test/subfont.js +++ b/test/subfont.js @@ -451,11 +451,11 @@ describe('subfont', function () { ); expect(mockConsole.log, 'to have a call satisfying', () => { mockConsole.log( - expect.it('to contain', '400 : 6/214 codepoints used (3 on this page),') + expect.it('to contain', '400 : 6/213 codepoints used (3 on this page),') ); }).and('to have a call satisfying', () => { mockConsole.log( - expect.it('to contain', '400 : 6/214 codepoints used (4 on this page),') + expect.it('to contain', '400 : 6/213 codepoints used (4 on this page),') ); }); }); @@ -480,7 +480,7 @@ describe('subfont', function () { mockConsole ); expect(mockConsole.log, 'to have a call satisfying', () => { - mockConsole.log(expect.it('to contain', '400 : 3/214 codepoints used,')); + mockConsole.log(expect.it('to contain', '400 : 3/213 codepoints used,')); }); }); @@ -507,7 +507,7 @@ describe('subfont', function () { ); expect(mockConsole.log, 'to have a call satisfying', () => { mockConsole.log( - expect.it('to contain', '400 : 14/214 codepoints used') + expect.it('to contain', '400 : 14/213 codepoints used') ); }); }); @@ -534,7 +534,7 @@ describe('subfont', function () { ); expect(mockConsole.log, 'to have a call satisfying', () => { mockConsole.log( - expect.it('to contain', '400 : 16/214 codepoints used,') + expect.it('to contain', '400 : 16/213 codepoints used,') ); }); }); @@ -562,7 +562,7 @@ describe('subfont', function () { ); expect(mockConsole.log, 'to have a call satisfying', () => { mockConsole.log( - expect.it('to contain', '400 : 14/214 codepoints used') + expect.it('to contain', '400 : 14/213 codepoints used') ); }); }); @@ -590,7 +590,7 @@ describe('subfont', function () { ); expect(mockConsole.log, 'to have a call satisfying', () => { mockConsole.log( - expect.it('to contain', '400 : 14/214 codepoints used') + expect.it('to contain', '400 : 14/213 codepoints used') ); }); }); diff --git a/test/subsetFonts.js b/test/subsetFonts.js index bd7fd6b..6bd6f12 100644 --- a/test/subsetFonts.js +++ b/test/subsetFonts.js @@ -7,12 +7,12 @@ const expect = require('unexpected') const AssetGraph = require('assetgraph'); const pathModule = require('path'); const LinesAndColumns = require('lines-and-columns').default; -const fontkit = require('fontkit'); const httpception = require('httpception'); const sinon = require('sinon'); const fs = require('fs'); const subsetFonts = require('../lib/subsetFonts'); +const getFontInfo = require('../lib/getFontInfo'); const defaultLocalSubsetMock = [ { @@ -3206,11 +3206,8 @@ describe('subsetFonts', function () { const subsetFontAssets = assetGraph.findAssets({ type: 'Woff2' }); expect(subsetFontAssets, 'to have length', 1); - expect( - fontkit.create(subsetFontAssets[0].rawSrc).variationAxes, - 'to equal', - {} - ); + const { variationAxes } = await getFontInfo(subsetFontAssets[0].rawSrc); + expect(variationAxes, 'to equal', {}); }); }); From f79f1fa26d7b2aec2d897d94fe7e58c66da42f46 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sat, 8 Apr 2023 23:42:05 +0200 Subject: [PATCH 3/6] npm uninstall fontkit --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 0bc2bed..ebf52b2 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "css-list-helpers": "^2.0.0", "font-snapper": "^1.2.0", "font-tracer": "^3.6.0", - "fontkit": "^1.8.0", "fontverter": "^2.0.0", "gettemporaryfilepath": "^1.0.1", "harfbuzzjs": "^0.3.2", From 78a144e43176161eeae409ff9e848ab88e08bca8 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sun, 9 Apr 2023 09:57:12 +0200 Subject: [PATCH 4/6] Fix one more occurrence --- test/subsetFonts.js | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/test/subsetFonts.js b/test/subsetFonts.js index 6bd6f12..3913480 100644 --- a/test/subsetFonts.js +++ b/test/subsetFonts.js @@ -3228,25 +3228,24 @@ describe('subsetFonts', function () { const subsetFontAssets = assetGraph.findAssets({ type: 'Woff2' }); expect(subsetFontAssets, 'to have length', 1); - expect( - fontkit.create(subsetFontAssets[0].rawSrc).variationAxes, - 'to equal', - { - wght: { name: 'wght', min: 100, default: 400, max: 1000 }, - wdth: { name: 'wdth', min: 25, default: 100, max: 151 }, - opsz: { name: 'opsz', min: 8, default: 14, max: 144 }, - GRAD: { name: 'GRAD', min: -200, default: 0, max: 150 }, - slnt: { name: 'slnt', min: -10, default: 0, max: 0 }, - XTRA: { name: 'XTRA', min: 323, default: 468, max: 603 }, - XOPQ: { name: 'XOPQ', min: 27, default: 96, max: 175 }, - YOPQ: { name: 'YOPQ', min: 25, default: 79, max: 135 }, - YTLC: { name: 'YTLC', min: 416, default: 514, max: 570 }, - YTUC: { name: 'YTUC', min: 528, default: 712, max: 760 }, - YTAS: { name: 'YTAS', min: 649, default: 750, max: 854 }, - YTDE: { name: 'YTDE', min: -305, default: -203, max: -98 }, - YTFI: { name: 'YTFI', min: 560, default: 738, max: 788 }, - } - ); + + 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 }, + }); }); }); }); From 9884a50832a6e99847eb76d3d283f65062bc5577 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sun, 9 Apr 2023 10:10:53 +0200 Subject: [PATCH 5/6] Memoize again --- lib/getFontInfo.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/getFontInfo.js b/lib/getFontInfo.js index ee81dde..d84abf1 100644 --- a/lib/getFontInfo.js +++ b/lib/getFontInfo.js @@ -1,6 +1,6 @@ const fontverter = require('fontverter'); -module.exports = async function getFontInfo(buffer) { +async function getFontInfoFromBuffer(buffer) { const harfbuzzJs = await require('harfbuzzjs'); const blob = harfbuzzJs.createBlob(await fontverter.convert(buffer, 'sfnt')); // Load the font data into something Harfbuzz can use @@ -15,4 +15,13 @@ module.exports = async function getFontInfo(buffer) { blob.destroy(); return fontInfo; +} + +const fontInfoPromiseByBuffer = new WeakMap(); + +module.exports = function getFontInfo(buffer) { + if (!fontInfoPromiseByBuffer.has(buffer)) { + fontInfoPromiseByBuffer.set(buffer, getFontInfoFromBuffer(buffer)); + } + return fontInfoPromiseByBuffer.get(buffer); }; From c60951413eb52101d43b9a4086747bcce1b9ad36 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sat, 15 Apr 2023 19:24:55 +0200 Subject: [PATCH 6/6] Update harfbuzzjs to ^0.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ebf52b2..3401042 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "font-tracer": "^3.6.0", "fontverter": "^2.0.0", "gettemporaryfilepath": "^1.0.1", - "harfbuzzjs": "^0.3.2", + "harfbuzzjs": "^0.3.3", "lines-and-columns": "^1.1.6", "lodash": "^4.17.15", "memoizesync": "^1.1.1",