@turntrout/subfont 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/FontTracerPool.js +24 -1
- package/lib/HeadlessBrowser.js +5 -4
- package/lib/cli.js +1 -1
- package/lib/collectTextsByPage.js +1 -3
- package/lib/extractVisibleText.js +13 -1
- package/lib/fontFaceHelpers.js +22 -22
- package/lib/subfont.js +25 -14
- package/lib/subsetGeneration.js +10 -0
- package/lib/unicodeRange.js +1 -1
- package/package.json +1 -1
package/lib/FontTracerPool.js
CHANGED
|
@@ -159,7 +159,30 @@ class FontTracerPool {
|
|
|
159
159
|
}
|
|
160
160
|
this._pendingTasks = [];
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
// Reject any in-flight tasks still assigned to workers.
|
|
163
|
+
// Clear _taskByWorker before terminate() so _onWorkerExit won't double-reject.
|
|
164
|
+
for (const [, taskId] of this._taskByWorker) {
|
|
165
|
+
const cb = this._taskCallbacks.get(taskId);
|
|
166
|
+
if (cb) {
|
|
167
|
+
this._taskCallbacks.delete(taskId);
|
|
168
|
+
cb.reject(new Error('Worker pool destroyed'));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
this._taskByWorker.clear();
|
|
172
|
+
|
|
173
|
+
// Terminate workers with a 5-second timeout to prevent hanging
|
|
174
|
+
const TERMINATE_TIMEOUT_MS = 5000;
|
|
175
|
+
await Promise.all(
|
|
176
|
+
this._workers.map((w) =>
|
|
177
|
+
Promise.race([
|
|
178
|
+
w.terminate(),
|
|
179
|
+
new Promise((resolve) => {
|
|
180
|
+
const timer = setTimeout(resolve, TERMINATE_TIMEOUT_MS);
|
|
181
|
+
timer.unref();
|
|
182
|
+
}),
|
|
183
|
+
])
|
|
184
|
+
)
|
|
185
|
+
);
|
|
163
186
|
this._workers = [];
|
|
164
187
|
this._idle = [];
|
|
165
188
|
}
|
package/lib/HeadlessBrowser.js
CHANGED
|
@@ -148,10 +148,10 @@ class HeadlessBrowser {
|
|
|
148
148
|
`${request.method()} ${request.url()} returned ${response.status()}`
|
|
149
149
|
);
|
|
150
150
|
} else {
|
|
151
|
+
const failure = request.failure();
|
|
152
|
+
const reason = failure ? failure.errorText : 'unknown error';
|
|
151
153
|
this.console.error(
|
|
152
|
-
`${request.method()} ${request.url()} failed: ${
|
|
153
|
-
request.failure().errorText
|
|
154
|
-
}`
|
|
154
|
+
`${request.method()} ${request.url()} failed: ${reason}`
|
|
155
155
|
);
|
|
156
156
|
}
|
|
157
157
|
});
|
|
@@ -180,7 +180,8 @@ class HeadlessBrowser {
|
|
|
180
180
|
urlTools.resolveUrl(
|
|
181
181
|
baseUrl,
|
|
182
182
|
urlTools.buildRelativeUrl(assetGraph.root, htmlAsset.url)
|
|
183
|
-
)
|
|
183
|
+
),
|
|
184
|
+
{ timeout: 30000 }
|
|
184
185
|
);
|
|
185
186
|
|
|
186
187
|
await page.addScriptTag({
|
package/lib/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ const { yargs, help, ...options } = require('./parseCommandLineOptions')();
|
|
|
5
5
|
require('@gustavnikolaj/async-main-wrap')(require('./subfont'), {
|
|
6
6
|
processError(err) {
|
|
7
7
|
yargs.showHelp();
|
|
8
|
-
if (err.
|
|
8
|
+
if (err.name === 'UsageError') {
|
|
9
9
|
// Avoid rendering a stack trace for the wrong usage errors
|
|
10
10
|
err.customOutput = err.message;
|
|
11
11
|
}
|
|
@@ -397,10 +397,8 @@ async function tracePages(
|
|
|
397
397
|
}
|
|
398
398
|
});
|
|
399
399
|
await Promise.all(tracePromises);
|
|
400
|
+
} finally {
|
|
400
401
|
await pool.destroy();
|
|
401
|
-
} catch (err) {
|
|
402
|
-
await pool.destroy();
|
|
403
|
-
throw err;
|
|
404
402
|
}
|
|
405
403
|
} else if (pagesNeedingFullTrace.length > 0) {
|
|
406
404
|
const totalPages = pagesNeedingFullTrace.length;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
const parse5 = require('parse5');
|
|
2
2
|
|
|
3
|
-
const INVISIBLE_ELEMENTS = new Set([
|
|
3
|
+
const INVISIBLE_ELEMENTS = new Set([
|
|
4
|
+
'script',
|
|
5
|
+
'style',
|
|
6
|
+
'svg',
|
|
7
|
+
'template',
|
|
8
|
+
'head',
|
|
9
|
+
'noscript',
|
|
10
|
+
'iframe',
|
|
11
|
+
'object',
|
|
12
|
+
'embed',
|
|
13
|
+
'datalist',
|
|
14
|
+
]);
|
|
4
15
|
const TEXT_ATTRIBUTES = new Set([
|
|
5
16
|
'alt',
|
|
6
17
|
'title',
|
|
@@ -62,3 +73,4 @@ function extractVisibleText(html) {
|
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
module.exports = extractVisibleText;
|
|
76
|
+
module.exports.INVISIBLE_ELEMENTS = INVISIBLE_ELEMENTS;
|
package/lib/fontFaceHelpers.js
CHANGED
|
@@ -18,7 +18,9 @@ function stringifyFontFamily(name) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function maybeCssQuote(value) {
|
|
21
|
-
|
|
21
|
+
// CSS identifiers must start with a letter or underscore (or hyphen
|
|
22
|
+
// followed by a letter/underscore), not a digit or bare hyphen.
|
|
23
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_-]*$|^-[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value)) {
|
|
22
24
|
return value;
|
|
23
25
|
} else {
|
|
24
26
|
return `'${value.replace(/'/g, "\\'")}'`;
|
|
@@ -26,31 +28,29 @@ function maybeCssQuote(value) {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
function getPreferredFontUrl(cssFontFaceSrcRelations = []) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
// Priority: woff2 > woff > truetype > opentype, preferring explicit
|
|
32
|
+
// format() declarations over asset-type guesses.
|
|
33
|
+
const formatPriority = { woff2: 0, woff: 1, truetype: 2, opentype: 3 };
|
|
34
|
+
const typePriority = { Woff2: 4, Woff: 5, Ttf: 6, Otf: 7 };
|
|
35
|
+
|
|
36
|
+
let bestUrl;
|
|
37
|
+
let bestPriority = Infinity;
|
|
38
|
+
|
|
39
|
+
for (const r of cssFontFaceSrcRelations) {
|
|
40
|
+
let priority;
|
|
41
|
+
if (r.format) {
|
|
42
|
+
priority = formatPriority[r.format.toLowerCase()];
|
|
40
43
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (relation) {
|
|
49
|
-
return relation.to.url;
|
|
44
|
+
if (priority === undefined) {
|
|
45
|
+
priority = typePriority[r.to.type];
|
|
46
|
+
}
|
|
47
|
+
if (priority !== undefined && priority < bestPriority) {
|
|
48
|
+
bestPriority = priority;
|
|
49
|
+
bestUrl = r.to.url;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
return
|
|
53
|
+
return bestUrl;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Temporarily switch all relation hrefs to absolute so that
|
package/lib/subfont.js
CHANGED
|
@@ -6,6 +6,13 @@ const urlTools = require('urltools');
|
|
|
6
6
|
const util = require('util');
|
|
7
7
|
const subsetFonts = require('./subsetFonts');
|
|
8
8
|
|
|
9
|
+
class UsageError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'UsageError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
module.exports = async function subfont(
|
|
10
17
|
{
|
|
11
18
|
root,
|
|
@@ -30,6 +37,13 @@ module.exports = async function subfont(
|
|
|
30
37
|
},
|
|
31
38
|
console
|
|
32
39
|
) {
|
|
40
|
+
if (
|
|
41
|
+
concurrency !== undefined &&
|
|
42
|
+
(!Number.isInteger(concurrency) || concurrency < 1)
|
|
43
|
+
) {
|
|
44
|
+
throw new UsageError('--concurrency must be a positive integer');
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
const formats = ['woff2'];
|
|
34
48
|
|
|
35
49
|
function logToConsole(severity, ...args) {
|
|
@@ -51,7 +65,7 @@ module.exports = async function subfont(
|
|
|
51
65
|
try {
|
|
52
66
|
await fsPromises.access(rootPath);
|
|
53
67
|
} catch {
|
|
54
|
-
throw new
|
|
68
|
+
throw new UsageError(`The --root path does not exist: ${rootPath}`);
|
|
55
69
|
}
|
|
56
70
|
}
|
|
57
71
|
const outRoot = output && urlTools.urlOrFsPathToUrl(output, true);
|
|
@@ -75,19 +89,19 @@ module.exports = async function subfont(
|
|
|
75
89
|
inputUrls = [`${rootUrl}**/*.html`];
|
|
76
90
|
warn(`No input files specified, defaulting to ${inputUrls[0]}`);
|
|
77
91
|
} else {
|
|
78
|
-
throw new
|
|
92
|
+
throw new UsageError(
|
|
79
93
|
"No input files and no --root specified (or it isn't file:), cannot proceed.\n"
|
|
80
94
|
);
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
if (!inputUrls[0].startsWith('file:') && !outRoot && !dryRun) {
|
|
84
|
-
throw new
|
|
98
|
+
throw new UsageError(
|
|
85
99
|
'--output has to be specified when using non-file input urls'
|
|
86
100
|
);
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
if (!inPlace && !outRoot && !dryRun) {
|
|
90
|
-
throw new
|
|
104
|
+
throw new UsageError(
|
|
91
105
|
'Either --output, --in-place, or --dry-run has to be specified'
|
|
92
106
|
);
|
|
93
107
|
}
|
|
@@ -202,14 +216,15 @@ module.exports = async function subfont(
|
|
|
202
216
|
}
|
|
203
217
|
}
|
|
204
218
|
|
|
205
|
-
|
|
206
|
-
for (const asset of assetGraph.findAssets({
|
|
219
|
+
const sizeableAssetQuery = {
|
|
207
220
|
isInline: false,
|
|
208
221
|
isLoaded: true,
|
|
209
222
|
type: {
|
|
210
223
|
$in: ['Html', 'Svg', 'Css', 'JavaScript'],
|
|
211
224
|
},
|
|
212
|
-
}
|
|
225
|
+
};
|
|
226
|
+
let sumSizesBefore = 0;
|
|
227
|
+
for (const asset of assetGraph.findAssets(sizeableAssetQuery)) {
|
|
213
228
|
sumSizesBefore += asset.rawSrc.length;
|
|
214
229
|
}
|
|
215
230
|
|
|
@@ -255,13 +270,7 @@ module.exports = async function subfont(
|
|
|
255
270
|
|
|
256
271
|
phaseStart = Date.now();
|
|
257
272
|
let sumSizesAfter = 0;
|
|
258
|
-
for (const asset of assetGraph.findAssets({
|
|
259
|
-
isInline: false,
|
|
260
|
-
isLoaded: true,
|
|
261
|
-
type: {
|
|
262
|
-
$in: ['Html', 'Svg', 'Css', 'JavaScript'],
|
|
263
|
-
},
|
|
264
|
-
})) {
|
|
273
|
+
for (const asset of assetGraph.findAssets(sizeableAssetQuery)) {
|
|
265
274
|
sumSizesAfter += asset.rawSrc.length;
|
|
266
275
|
}
|
|
267
276
|
|
|
@@ -575,3 +584,5 @@ module.exports = async function subfont(
|
|
|
575
584
|
}
|
|
576
585
|
return assetGraph;
|
|
577
586
|
};
|
|
587
|
+
|
|
588
|
+
module.exports.UsageError = UsageError;
|
package/lib/subsetGeneration.js
CHANGED
|
@@ -63,6 +63,16 @@ class SubsetDiskCache {
|
|
|
63
63
|
try {
|
|
64
64
|
await fs.writeFile(filePath, buffer);
|
|
65
65
|
} catch (err) {
|
|
66
|
+
// If the directory was removed after init, retry once
|
|
67
|
+
if (err.code === 'ENOENT') {
|
|
68
|
+
try {
|
|
69
|
+
await fs.mkdir(this._cacheDir, { recursive: true });
|
|
70
|
+
await fs.writeFile(filePath, buffer);
|
|
71
|
+
return;
|
|
72
|
+
} catch {
|
|
73
|
+
// Fall through to warning below
|
|
74
|
+
}
|
|
75
|
+
}
|
|
66
76
|
if (this._warnedWrite) return;
|
|
67
77
|
this._warnedWrite = true;
|
|
68
78
|
if (this._console) {
|
package/lib/unicodeRange.js
CHANGED
package/package.json
CHANGED