/
subsetLocalFontWithHarfbuzz.js
88 lines (71 loc) · 2.43 KB
/
subsetLocalFontWithHarfbuzz.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/* global WebAssembly */
const fs = require('fs');
const readFileAsync = require('util').promisify(fs.readFile);
const _ = require('lodash');
const fontverter = require('fontverter');
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();
originalFont = await fontverter.convert(originalFont, 'truetype');
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);
const subsetFont = Buffer.from(
heapu8.slice(offset, offset + exports.hb_blob_get_length(result))
);
// Clean up
exports.hb_blob_destroy(result);
exports.hb_face_destroy(subset);
return await fontverter.convert(subsetFont, targetFormat, 'truetype');
}
const limiter = require('p-limit')(1);
module.exports = (...args) => limiter(() => subset(...args));