Skip to content

Commit

Permalink
Merge 47a6f27 into 2abac1a
Browse files Browse the repository at this point in the history
  • Loading branch information
papandreou committed Jul 17, 2020
2 parents 2abac1a + 47a6f27 commit 6e86058
Show file tree
Hide file tree
Showing 21 changed files with 442 additions and 520 deletions.
36 changes: 36 additions & 0 deletions lib/convertFontBuffer.js
@@ -0,0 +1,36 @@
const wawoff2 = require('wawoff2');
const woffTool = require('woff2sfnt-sfnt2woff');

const supportedFormats = new Set(['truetype', 'woff', 'woff2']);
const detectFontFormat = require('./detectFontFormat');

async function convertFontBuffer(buffer, toFormat, fromFormat) {
if (!supportedFormats.has(toFormat)) {
throw new Error(`Cannot convert to ${toFormat}`);
}
if (fromFormat) {
if (!supportedFormats.has(fromFormat)) {
throw new Error(`Cannot convert from ${toFormat}`);
}
} else {
fromFormat = detectFontFormat(buffer);
}

if (fromFormat === toFormat) {
return buffer;
}
if (fromFormat === 'woff') {
buffer = woffTool.toSfnt(buffer);
} else if (fromFormat === 'woff2') {
buffer = Buffer.from(await wawoff2.decompress(buffer));
}

if (toFormat === 'woff') {
buffer = woffTool.toWoff(buffer);
} else if (toFormat === 'woff2') {
buffer = Buffer.from(await wawoff2.compress(buffer));
}
return buffer;
}

module.exports = convertFontBuffer;
18 changes: 18 additions & 0 deletions lib/detectFontFormat.js
@@ -0,0 +1,18 @@
function detectFontFormat(buffer) {
const signature = buffer.toString('ascii', 0, 4);
if (signature === 'wOFF') {
return 'woff';
} else if (signature === 'wOF2') {
return 'woff2';
} else if (
signature === 'true' ||
signature === 'OTTO' ||
signature === '\x00\x01\x00\x00'
) {
return 'truetype';
} else {
throw new Error(`Unrecognized font signature: ${signature}`);
}
}

module.exports = detectFontFormat;
47 changes: 19 additions & 28 deletions lib/subsetFonts.js
Expand Up @@ -25,6 +25,7 @@ const unquote = require('./unquote');
const normalizeFontPropertyValue = require('./normalizeFontPropertyValue');
const getCssRulesByProperty = require('./getCssRulesByProperty');
const unicodeRange = require('./unicodeRange');
const convertFontBuffer = require('./convertFontBuffer');

const googleFontsCssUrlRegex = /^(?:https?:)?\/\/fonts\.googleapis\.com\/css/;

Expand Down Expand Up @@ -382,15 +383,13 @@ async function getSubsetsForFontUsage(
continue;
}

for (const format of formats) {
const mapId = getSubsetPromiseId(fontUsage, format);
const mapId = getSubsetPromiseId(fontUsage, 'truetype');

if (!fontCssUrlMap[mapId]) {
fontCssUrlMap[mapId] = `${getGoogleFontSubsetCssUrl(
fontUsage.props,
fontUsage.text
)}&format=${format}`;
}
if (!fontCssUrlMap[mapId]) {
fontCssUrlMap[mapId] = `${getGoogleFontSubsetCssUrl(
fontUsage.props,
fontUsage.text
)}`;
}
}
}
Expand All @@ -411,16 +410,8 @@ async function getSubsetsForFontUsage(

const assetGraphForLoadingFonts = new AssetGraph();

for (const format of formats) {
assetGraphForLoadingFonts.teepee.headers['User-Agent'] =
fontFormatUA[format];
const formatUrls = _.uniq(
Object.values(fontCssUrlMap).filter((url) =>
url.endsWith(`format=${format}`)
)
);
await assetGraphForLoadingFonts.loadAssets(Object.values(formatUrls));
}
const formatUrls = _.uniq(Object.values(fontCssUrlMap));
await assetGraphForLoadingFonts.loadAssets(Object.values(formatUrls));

await assetGraphForLoadingFonts.populate({
followRelations: {
Expand All @@ -431,7 +422,8 @@ async function getSubsetsForFontUsage(
for (const item of htmlAssetTextsWithProps) {
for (const fontUsage of item.fontUsages) {
for (const format of formats) {
const cssUrl = fontCssUrlMap[getSubsetPromiseId(fontUsage, format)];
const cssUrl =
fontCssUrlMap[getSubsetPromiseId(fontUsage, 'truetype')];
const cssAsset = assetGraphForLoadingFonts.findAssets({
url: cssUrl,
isLoaded: true,
Expand All @@ -445,8 +437,14 @@ async function getSubsetsForFontUsage(
fontUsage.subsets = {};
}

fontUsage.subsets[format] = fontAsset.rawSrc;
const size = fontAsset.rawSrc.length;
const buffer = await convertFontBuffer(
fontAsset.rawSrc,
format,
'truetype'
);

fontUsage.subsets[format] = buffer;
const size = buffer.length;
if (
!fontUsage.smallestSubsetSize ||
size < fontUsage.smallestSubsetSize
Expand Down Expand Up @@ -554,13 +552,6 @@ function getFontUsageStylesheet(fontUsages) {
.join('\n\n');
}

const fontFormatUA = {
woff:
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0',
woff2:
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.835.2 Safari/537.36',
};

const validFontDisplayValues = [
'auto',
'block',
Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -65,6 +65,8 @@
"puppeteer-core": "^3.1.0",
"specificity": "^0.4.1",
"urltools": "^0.4.1",
"wawoff2": "^1.0.2",
"woff2sfnt-sfnt2woff": "^1.0.0",
"yargs": "^15.4.0"
},
"devDependencies": {
Expand Down
153 changes: 153 additions & 0 deletions test/convertFontBuffer.js
@@ -0,0 +1,153 @@
const convertFontBuffer = require('../lib/convertFontBuffer');
const detectFontFormat = require('../lib/detectFontFormat');
const fs = require('fs').promises;
const pathModule = require('path');
const expect = require('unexpected').clone();

describe('convertFontBuffer', function () {
before(async function () {
this.truetype = await fs.readFile(
pathModule.resolve(
__dirname,
'..',
'testdata',
'subsetFonts',
'Roboto-400.ttf'
)
);
this.woff = await fs.readFile(
pathModule.resolve(
__dirname,
'..',
'testdata',
'subsetFonts',
'Roboto-400.woff'
)
);
this.woff2 = await fs.readFile(
pathModule.resolve(
__dirname,
'..',
'testdata',
'subsetFonts',
'Roboto-400.woff2'
)
);
});

describe('when the source format is not given', function () {
it('should throw if the source format could not be detected', async function () {
expect(
() => convertFontBuffer(Buffer.from('abcd'), 'truetype'),
'to error',
'Unrecognized font signature: abcd'
);
});

it('should convert a truetype font to truetype', async function () {
const buffer = await convertFontBuffer(this.truetype, 'truetype');
expect(detectFontFormat(buffer), 'to equal', 'truetype');
expect(buffer, 'to be', this.truetype); // Should be a noop
});

it('should convert a truetype font to woff', async function () {
const buffer = await convertFontBuffer(this.truetype, 'woff');
expect(detectFontFormat(buffer), 'to equal', 'woff');
});

it('should convert a truetype font to woff2', async function () {
const buffer = await convertFontBuffer(this.truetype, 'woff2');
expect(detectFontFormat(buffer), 'to equal', 'woff2');
});

it('should convert a woff font to truetype', async function () {
const buffer = await convertFontBuffer(this.woff, 'truetype');
expect(detectFontFormat(buffer), 'to equal', 'truetype');
});

it('should convert a woff font to woff', async function () {
const buffer = await convertFontBuffer(this.woff, 'woff');
expect(detectFontFormat(buffer), 'to equal', 'woff');
expect(buffer, 'to be', this.woff); // Should be a noop
});

it('should convert a woff font to woff2', async function () {
const buffer = await convertFontBuffer(this.woff, 'woff2');
expect(detectFontFormat(buffer), 'to equal', 'woff2');
});

it('should convert a woff2 font to truetype', async function () {
const buffer = await convertFontBuffer(this.woff2, 'truetype');
expect(detectFontFormat(buffer), 'to equal', 'truetype');
});

it('should convert a woff2 font to woff', async function () {
const buffer = await convertFontBuffer(this.woff2, 'woff');
expect(detectFontFormat(buffer), 'to equal', 'woff');
});

it('should convert a woff2 font to woff2', async function () {
const buffer = await convertFontBuffer(this.woff2, 'woff2');
expect(detectFontFormat(buffer), 'to equal', 'woff2');
expect(buffer, 'to be', this.woff2); // Should be a noop
});
});

describe('when the source format is given', function () {
it('should convert a truetype font to truetype', async function () {
const buffer = await convertFontBuffer(
this.truetype,
'truetype',
'truetype'
);
expect(detectFontFormat(buffer), 'to equal', 'truetype');
expect(buffer, 'to be', this.truetype); // Should be a noop
});

it('should convert a truetype font to woff', async function () {
const buffer = await convertFontBuffer(this.truetype, 'woff', 'truetype');
expect(detectFontFormat(buffer), 'to equal', 'woff');
});

it('should convert a truetype font to woff2', async function () {
const buffer = await convertFontBuffer(
this.truetype,
'woff2',
'truetype'
);
expect(detectFontFormat(buffer), 'to equal', 'woff2');
});

it('should convert a woff font to truetype', async function () {
const buffer = await convertFontBuffer(this.woff, 'truetype', 'woff');
expect(detectFontFormat(buffer), 'to equal', 'truetype');
});

it('should convert a woff font to woff', async function () {
const buffer = await convertFontBuffer(this.woff, 'woff', 'woff');
expect(detectFontFormat(buffer), 'to equal', 'woff');
expect(buffer, 'to be', this.woff); // Should be a noop
});

it('should convert a woff font to woff2', async function () {
const buffer = await convertFontBuffer(this.woff, 'woff2', 'woff');
expect(detectFontFormat(buffer), 'to equal', 'woff2');
});

it('should convert a woff2 font to truetype', async function () {
const buffer = await convertFontBuffer(this.woff2, 'truetype', 'woff2');
expect(detectFontFormat(buffer), 'to equal', 'truetype');
});

it('should convert a woff2 font to woff', async function () {
const buffer = await convertFontBuffer(this.woff2, 'woff', 'woff2');
expect(detectFontFormat(buffer), 'to equal', 'woff');
});

it('should convert a woff2 font to woff2', async function () {
const buffer = await convertFontBuffer(this.woff2, 'woff2', 'woff2');
expect(detectFontFormat(buffer), 'to equal', 'woff2');
expect(buffer, 'to be', this.woff2); // Should be a noop
});
});
});
54 changes: 54 additions & 0 deletions test/detectFontFormat.js
@@ -0,0 +1,54 @@
const convertFontBuffer = require('../lib/convertFontBuffer');
const detectFontFormat = require('../lib/detectFontFormat');
const fs = require('fs').promises;
const pathModule = require('path');
const expect = require('unexpected').clone();

describe('detectFontFormat', function () {
it('should throw if the contents of the buffer could not be recognized', async function () {
expect(
() => convertFontBuffer(Buffer.from('abcd'), 'truetype'),
'to error',
'Unrecognized font signature: abcd'
);
});

it('should detect a truetype font', async function () {
const buffer = await fs.readFile(
pathModule.resolve(
__dirname,
'..',
'testdata',
'subsetFonts',
'Roboto-400.ttf'
)
);
expect(detectFontFormat(buffer), 'to equal', 'truetype');
});

it('should detect a woff font', async function () {
const buffer = await fs.readFile(
pathModule.resolve(
__dirname,
'..',
'testdata',
'subsetFonts',
'Roboto-400.woff'
)
);
expect(detectFontFormat(buffer), 'to equal', 'woff');
});

it('should detect a woff2 font', async function () {
const buffer = await fs.readFile(
pathModule.resolve(
__dirname,
'..',
'testdata',
'subsetFonts',
'Roboto-400.woff2'
)
);
expect(detectFontFormat(buffer), 'to equal', 'woff2');
});
});

0 comments on commit 6e86058

Please sign in to comment.