Skip to content

Commit

Permalink
Merge 36b3fb5 into 46a9b2e
Browse files Browse the repository at this point in the history
  • Loading branch information
papandreou committed Jul 21, 2020
2 parents 46a9b2e + 36b3fb5 commit 4303a90
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 8 deletions.
7 changes: 4 additions & 3 deletions .eslintignore
@@ -1,3 +1,4 @@
testdata
node_modules
coverage
/testdata/
/node_modules/
/coverage/
/vendor/
13 changes: 13 additions & 0 deletions lib/parseCommandLineOptions.js
Expand Up @@ -79,6 +79,12 @@ module.exports = function parseCommandLineOptions(argv) {
type: 'boolean',
default: false,
})
.options('harfbuzz', {
type: 'boolean',
describe:
'Experimental: Use the harfbuzz subsetter instead of pyftsubset. Requires node.js 10+ for wasm support',
default: false,
})
.options('silent', {
alias: 's',
describe: `Do not write anything to stdout`,
Expand All @@ -97,6 +103,13 @@ module.exports = function parseCommandLineOptions(argv) {
type: 'boolean',
default: false,
})
.check(({ harfbuzz }) => {
if (harfbuzz && parseInt(process.versions.node) < 10) {
return 'The --harfbuzz option requires node.js 10 or above';
} else {
return true;
}
})
.wrap(require('yargs').terminalWidth());

const { _: inputFiles, ...rest } = yargs.argv;
Expand Down
2 changes: 2 additions & 0 deletions lib/subfont.js
Expand Up @@ -23,6 +23,7 @@ module.exports = async function subfont(
recursive = false,
fallbacks = true,
dynamic = false,
harfbuzz = false,
},
console
) {
Expand Down Expand Up @@ -185,6 +186,7 @@ module.exports = async function subfont(
subsetPerPage,
formats,
omitFallbacks: !fallbacks,
harfbuzz,
dynamic,
console,
});
Expand Down
21 changes: 16 additions & 5 deletions lib/subsetFonts.js
Expand Up @@ -278,13 +278,18 @@ function getSubsetPromiseId(fontUsage, format) {
async function getSubsetsForFontUsage(
assetGraph,
htmlAssetTextsWithProps,
formats
formats,
harfbuzz
) {
let subsetLocalFont;

try {
subsetLocalFont = require('./subsetLocalFont');
} catch (err) {}
if (harfbuzz) {
subsetLocalFont = require('./subsetLocalFontWithHarfbuzz');
} else {
try {
subsetLocalFont = require('./subsetLocalFont');
} catch (err) {}
}

const allFonts = [];

Expand Down Expand Up @@ -582,6 +587,7 @@ async function subsetFonts(
onlyInfo,
dynamic,
console = global.console,
harfbuzz,
} = {}
) {
if (!validFontDisplayValues.includes(fontDisplay)) {
Expand Down Expand Up @@ -830,7 +836,12 @@ async function subsetFonts(
}

// Generate subsets:
await getSubsetsForFontUsage(assetGraph, htmlAssetTextsWithProps, formats);
await getSubsetsForFontUsage(
assetGraph,
htmlAssetTextsWithProps,
formats,
harfbuzz
);

// Warn about missing glyphs
const missingGlyphsErrors = [];
Expand Down
103 changes: 103 additions & 0 deletions lib/subsetLocalFontWithHarfbuzz.js
@@ -0,0 +1,103 @@
/* global WebAssembly */
const fs = require('fs');
const readFileAsync = require('util').promisify(fs.readFile);
const _ = require('lodash');
const wawoff2 = require('wawoff2');
const woffTool = require('woff2sfnt-sfnt2woff');

function HB_TAG(chunkName) {
return chunkName.split('').reduce(function (a, ch) {
return (a << 8) + ch.charCodeAt(0);
}, 0);
}

const loadAndInitializeHarfbuzz = _.once(async () => {
const {
instance: { exports },
} = await WebAssembly.instantiate(
await readFileAsync(require.resolve('harfbuzzjs/subset/hb-subset.wasm'))
);
exports.memory.grow(400); // each page is 64kb in size

const heapu8 = new Uint8Array(exports.memory.buffer);
return [exports, heapu8];
});

async function subset(originalFont, targetFormat, text) {
const [exports, heapu8] = await loadAndInitializeHarfbuzz();

const signature = originalFont.slice(0, 4).toString();
if (signature === 'wOFF') {
originalFont = woffTool.toSfnt(originalFont);
} else if (signature === 'wOF2') {
originalFont = await wawoff2.decompress(originalFont);
}

const fontBuffer = exports.malloc(originalFont.byteLength);
heapu8.set(new Uint8Array(originalFont), fontBuffer);

// Create the face
const blob = exports.hb_blob_create(
fontBuffer,
originalFont.byteLength,
2, // HB_MEMORY_MODE_WRITABLE
0,
0
);
const face = exports.hb_face_create(blob, 0);
exports.hb_blob_destroy(blob);

// Add glyph indices and subset
const glyphs = exports.hb_set_create();

for (let i = 0; i < text.length; i += 1) {
exports.hb_set_add(glyphs, text.charCodeAt(i));
}

const input = exports.hb_subset_input_create_or_fail();
const inputGlyphs = exports.hb_subset_input_unicode_set(input);
exports.hb_set_del(
exports.hb_subset_input_drop_tables_set(input),
HB_TAG('GSUB')
);
exports.hb_set_del(
exports.hb_subset_input_drop_tables_set(input),
HB_TAG('GPOS')
);
exports.hb_set_del(
exports.hb_subset_input_drop_tables_set(input),
HB_TAG('GDEF')
);

exports.hb_set_union(inputGlyphs, glyphs);
const subset = exports.hb_subset(face, input);

// Clean up
exports.hb_subset_input_destroy(input);

// Get result blob
const result = exports.hb_face_reference_blob(subset);

const offset = exports.hb_blob_get_data(result, 0);
let subsetFont = heapu8.slice(
offset,
offset + exports.hb_blob_get_length(result)
);

// Clean up
exports.hb_blob_destroy(result);
exports.hb_face_destroy(subset);

if (targetFormat === 'woff2') {
subsetFont = Buffer.from(await wawoff2.compress(subsetFont));
} else if (targetFormat === 'woff') {
subsetFont = woffTool.toWoff(subsetFont);
} else {
// targetFormat === 'truetype'
subsetFont = Buffer.from(subsetFont);
}
return subsetFont;
}

const limiter = require('p-limit')(1);
module.exports = (...args) => limiter(() => subset(...args));
4 changes: 4 additions & 0 deletions package.json
Expand Up @@ -56,15 +56,19 @@
"font-tracer": "^2.0.1",
"fontkit": "^1.8.0",
"gettemporaryfilepath": "^1.0.1",
"harfbuzzjs": "^0.1.3",
"lines-and-columns": "^1.1.6",
"lodash": "^4.17.15",
"memoizesync": "^1.1.1",
"p-limit": "^3.0.2",
"postcss": "^7.0.32",
"postcss-value-parser": "^4.0.2",
"pretty-bytes": "^5.1.0",
"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
1 change: 1 addition & 0 deletions test/parseCommandLineOptions.js
Expand Up @@ -26,6 +26,7 @@ describe('parseCommandLineOptions', function () {
recursive: true,
fallbacks: false,
dynamic: false,
harfbuzz: false,
}
);
});
Expand Down
1 change: 1 addition & 0 deletions test/referenceImages.js
Expand Up @@ -7,6 +7,7 @@ describe('reference images', function () {
inlineSubsets: [false, true],
omitFallbacks: [false, true],
dynamic: [false, true],
harfbuzz: [false, true],
})) {
describe(`with ${Object.keys(options)
.map((key) => `${key}: ${options[key]}`)
Expand Down

0 comments on commit 4303a90

Please sign in to comment.