Skip to content

Commit

Permalink
Merge c609514 into bdb27b4
Browse files Browse the repository at this point in the history
  • Loading branch information
papandreou committed Apr 15, 2023
2 parents bdb27b4 + c609514 commit 504826f
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 72 deletions.
27 changes: 27 additions & 0 deletions lib/getFontInfo.js
@@ -0,0 +1,27 @@
const fontverter = require('fontverter');

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
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;
}

const fontInfoPromiseByBuffer = new WeakMap();

module.exports = function getFontInfo(buffer) {
if (!fontInfoPromiseByBuffer.has(buffer)) {
fontInfoPromiseByBuffer.set(buffer, getFontInfoFromBuffer(buffer));
}
return fontInfoPromiseByBuffer.get(buffer);
};
70 changes: 31 additions & 39 deletions lib/subsetFonts.js
Expand Up @@ -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/;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -435,7 +426,6 @@ async function getSubsetsForFontUsage(
htmlOrSvgAssetTextsWithProps,
formats,
seenAxisValuesByFontUrlAndAxisName,
fontkitMemoizer,
instance = false
) {
const allFonts = [];
Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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('}');
Expand Down Expand Up @@ -761,7 +751,10 @@ function parseFontStretchRange(str) {
return [minFontStretch, maxFontStretch];
}

function warnAboutMissingGlyphs(htmlOrSvgAssetTextsWithProps, assetGraph) {
async function warnAboutMissingGlyphs(
htmlOrSvgAssetTextsWithProps,
assetGraph
) {
const missingGlyphsErrors = [];

for (const {
Expand All @@ -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]) {
Expand Down Expand Up @@ -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 [
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -55,9 +55,9 @@
"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.3",
"lines-and-columns": "^1.1.6",
"lodash": "^4.17.15",
"memoizesync": "^1.1.1",
Expand Down
14 changes: 7 additions & 7 deletions test/subfont.js
Expand Up @@ -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),')
);
});
});
Expand All @@ -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,'));
});
});

Expand All @@ -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')
);
});
});
Expand All @@ -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,')
);
});
});
Expand Down Expand Up @@ -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')
);
});
});
Expand Down Expand Up @@ -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')
);
});
});
Expand Down
46 changes: 21 additions & 25 deletions test/subsetFonts.js
Expand Up @@ -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 = [
{
Expand Down Expand Up @@ -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', {});
});
});

Expand All @@ -3231,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 },
});
});
});
});
Expand Down

0 comments on commit 504826f

Please sign in to comment.