@turntrout/subfont 1.6.0 → 1.7.0
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/README.md +43 -43
- package/lib/FontTracerPool.js +49 -1
- package/lib/HeadlessBrowser.js +11 -3
- package/lib/collectTextsByPage.js +425 -352
- package/lib/escapeJsStringLiteral.js +13 -0
- package/lib/extractVisibleText.js +6 -2
- package/lib/fontConverter.js +25 -0
- package/lib/fontConverterWorker.js +16 -0
- package/lib/fontFaceHelpers.js +16 -4
- package/lib/gatherStylesheetsWithPredicates.js +4 -5
- package/lib/normalizeFontPropertyValue.js +1 -1
- package/lib/sfntCache.js +10 -7
- package/lib/subfont.js +57 -16
- package/lib/subsetFontWithGlyphs.js +33 -22
- package/lib/subsetFonts.js +166 -123
- package/lib/unquote.js +9 -4
- package/lib/warnAboutMissingGlyphs.js +36 -25
- package/lib/wasmQueue.js +6 -2
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -55,27 +55,27 @@ subfont path/to/index.html -i --cache
|
|
|
55
55
|
|
|
56
56
|
## Options
|
|
57
57
|
|
|
58
|
-
| Flag | Default | Description
|
|
59
|
-
| -----------------: | :-----: |
|
|
60
|
-
| `-i, --in-place` | off | Modify files in-place
|
|
61
|
-
| `-o, --output` | | Output directory
|
|
62
|
-
| `--root` | | Path to web root (deduced from input files if not specified)
|
|
63
|
-
| `--canonical-root` | | URI root where the site will be deployed
|
|
64
|
-
| `-r, --recursive` | off | Crawl linked pages
|
|
65
|
-
| `--dynamic` | off | Trace with headless browser
|
|
66
|
-
| `--dry-run` | off | Preview without writing
|
|
67
|
-
| `--fallbacks` | on |
|
|
68
|
-
| `--font-display` | `swap` | `auto`/`block`/`swap`/`fallback`/`optional`
|
|
69
|
-
| `--text` | | Extra characters for every subset
|
|
70
|
-
| `--cache [dir]` | off | Cache subset results to disk between runs
|
|
71
|
-
| `--concurrency N` |
|
|
72
|
-
| `--chrome-flags` | | Custom Chrome flags for `--dynamic`
|
|
73
|
-
| `--source-maps` | off | Preserve CSS source maps (slower)
|
|
74
|
-
| `--strict` | off | Exit non-zero if any warnings are emitted
|
|
75
|
-
| `-s, --silent` | off | Suppress all console output
|
|
76
|
-
| `-d, --debug` | off | Verbose timing and font glyph detection info
|
|
77
|
-
| `--relative-urls` | off | Emit relative URLs instead of root-relative
|
|
78
|
-
| `--inline-css` | off | Inline the subset @font-face CSS into HTML
|
|
58
|
+
| Flag | Default | Description |
|
|
59
|
+
| -----------------: | :-----: | :------------------------------------------------------------------------------------ |
|
|
60
|
+
| `-i, --in-place` | off | Modify files in-place |
|
|
61
|
+
| `-o, --output` | | Output directory |
|
|
62
|
+
| `--root` | | Path to web root (deduced from input files if not specified) |
|
|
63
|
+
| `--canonical-root` | | URI root where the site will be deployed |
|
|
64
|
+
| `-r, --recursive` | off | Crawl linked pages |
|
|
65
|
+
| `--dynamic` | off | Trace with headless browser |
|
|
66
|
+
| `--dry-run` | off | Preview without writing |
|
|
67
|
+
| `--fallbacks` | on | Async-load the full original font as a fallback for dynamic content |
|
|
68
|
+
| `--font-display` | `swap` | `auto`/`block`/`swap`/`fallback`/`optional` |
|
|
69
|
+
| `--text` | | Extra characters for every subset |
|
|
70
|
+
| `--cache [dir]` | off | Cache subset results to disk between runs |
|
|
71
|
+
| `--concurrency N` | auto | Max worker threads (defaults to CPU count, capped by available memory at ~50 MB each) |
|
|
72
|
+
| `--chrome-flags` | | Custom Chrome flags for `--dynamic` (comma-separated) |
|
|
73
|
+
| `--source-maps` | off | Preserve CSS source maps (slower) |
|
|
74
|
+
| `--strict` | off | Exit non-zero if any warnings are emitted |
|
|
75
|
+
| `-s, --silent` | off | Suppress all console output |
|
|
76
|
+
| `-d, --debug` | off | Verbose timing and font glyph detection info |
|
|
77
|
+
| `--relative-urls` | off | Emit relative URLs instead of root-relative |
|
|
78
|
+
| `--inline-css` | off | Inline the subset @font-face CSS into HTML |
|
|
79
79
|
|
|
80
80
|
Run `subfont --help` for the full list.
|
|
81
81
|
|
|
@@ -113,28 +113,28 @@ with `log`, `warn`, and `error` methods — e.g. the global `console`). Pass
|
|
|
113
113
|
|
|
114
114
|
The `options` object accepts the following keys:
|
|
115
115
|
|
|
116
|
-
| Option | Type | Default | Description
|
|
117
|
-
| --------------- | ------------------- | -------- |
|
|
118
|
-
| `inputFiles` | `string[]` | `[]` | HTML entry points (file paths or URLs). At least one is required unless `root` is given.
|
|
119
|
-
| `root` | `string` | deduced | Path or URL to the web root. Deduced from `inputFiles` if omitted.
|
|
120
|
-
| `canonicalRoot` | `string` | — | URI root where the site will be deployed (used to rewrite absolute URLs).
|
|
121
|
-
| `output` | `string` | — | Output directory. Mutually exclusive with `inPlace`.
|
|
122
|
-
| `inPlace` | `boolean` | `false` | Modify input files in place.
|
|
123
|
-
| `dryRun` | `boolean` | `false` | Trace and compute subsets but do not write any files.
|
|
124
|
-
| `recursive` | `boolean` | `false` | Crawl linked pages starting from `inputFiles`.
|
|
125
|
-
| `dynamic` | `boolean` | `false` | Trace JS-rendered content in headless Chrome (via puppeteer).
|
|
126
|
-
| `fallbacks` | `boolean` | `true` |
|
|
127
|
-
| `fontDisplay` | `string` | `'swap'` | `font-display` CSS value: `auto`, `block`, `swap`, `fallback`, or `optional`.
|
|
128
|
-
| `text` | `string` | — | Extra characters to include in every subset.
|
|
129
|
-
| `inlineCss` | `boolean` | `false` | Inline the subset `@font-face` CSS into the HTML document.
|
|
130
|
-
| `relativeUrls` | `boolean` | `false` | Emit relative URLs instead of root-relative URLs.
|
|
131
|
-
| `sourceMaps` | `boolean` | `false` | Preserve CSS source maps (slower).
|
|
132
|
-
| `concurrency` | `number` | auto | Max parallel tracing workers.
|
|
133
|
-
| `chromeFlags` | `string[]` | `[]` | Extra Chrome flags forwarded to puppeteer when `dynamic` is set.
|
|
134
|
-
| `cache` | `boolean \| string` | `false` | Cache subset results between runs. Pass a path to customize the cache directory; `true` uses the
|
|
135
|
-
| `strict` | `boolean` | `false` | Resolve with a non-zero exit (via the CLI) if any warnings are emitted.
|
|
136
|
-
| `silent` | `boolean` | `false` | Suppress all log output to `console`.
|
|
137
|
-
| `debug` | `boolean` | `false` | Emit verbose timing and glyph-detection info.
|
|
116
|
+
| Option | Type | Default | Description |
|
|
117
|
+
| --------------- | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
118
|
+
| `inputFiles` | `string[]` | `[]` | HTML entry points (file paths or URLs). At least one is required unless `root` is given. |
|
|
119
|
+
| `root` | `string` | deduced | Path or URL to the web root. Deduced from `inputFiles` if omitted. |
|
|
120
|
+
| `canonicalRoot` | `string` | — | URI root where the site will be deployed (used to rewrite absolute URLs). |
|
|
121
|
+
| `output` | `string` | — | Output directory. Mutually exclusive with `inPlace`. |
|
|
122
|
+
| `inPlace` | `boolean` | `false` | Modify input files in place. |
|
|
123
|
+
| `dryRun` | `boolean` | `false` | Trace and compute subsets but do not write any files. |
|
|
124
|
+
| `recursive` | `boolean` | `false` | Crawl linked pages starting from `inputFiles`. |
|
|
125
|
+
| `dynamic` | `boolean` | `false` | Trace JS-rendered content in headless Chrome (via puppeteer). |
|
|
126
|
+
| `fallbacks` | `boolean` | `true` | Async-load the full original font as a fallback for dynamic content. |
|
|
127
|
+
| `fontDisplay` | `string` | `'swap'` | `font-display` CSS value: `auto`, `block`, `swap`, `fallback`, or `optional`. |
|
|
128
|
+
| `text` | `string` | — | Extra characters to include in every subset. |
|
|
129
|
+
| `inlineCss` | `boolean` | `false` | Inline the subset `@font-face` CSS into the HTML document. |
|
|
130
|
+
| `relativeUrls` | `boolean` | `false` | Emit relative URLs instead of root-relative URLs. |
|
|
131
|
+
| `sourceMaps` | `boolean` | `false` | Preserve CSS source maps (slower). |
|
|
132
|
+
| `concurrency` | `number` | auto | Max parallel tracing workers. Defaults to CPU count, capped by available memory (~50 MB per worker). |
|
|
133
|
+
| `chromeFlags` | `string[]` | `[]` | Extra Chrome flags forwarded to puppeteer when `dynamic` is set. |
|
|
134
|
+
| `cache` | `boolean \| string` | `false` | Cache subset results between runs. Pass a path to customize the cache directory; `true` uses `.subfont-cache` inside the `root` directory. |
|
|
135
|
+
| `strict` | `boolean` | `false` | Resolve with a non-zero exit (via the CLI) if any warnings are emitted. |
|
|
136
|
+
| `silent` | `boolean` | `false` | Suppress all log output to `console`. |
|
|
137
|
+
| `debug` | `boolean` | `false` | Emit verbose timing and glyph-detection info. |
|
|
138
138
|
|
|
139
139
|
## License
|
|
140
140
|
|
package/lib/FontTracerPool.js
CHANGED
|
@@ -5,14 +5,18 @@ const { Worker } = require('worker_threads');
|
|
|
5
5
|
* Worker pool for running fontTracer in parallel across pages.
|
|
6
6
|
* Each worker re-parses HTML with jsdom and runs fontTracer independently.
|
|
7
7
|
*/
|
|
8
|
+
const DEFAULT_TASK_TIMEOUT_MS = 60_000;
|
|
9
|
+
|
|
8
10
|
class FontTracerPool {
|
|
9
|
-
constructor(numWorkers) {
|
|
11
|
+
constructor(numWorkers, { taskTimeoutMs = DEFAULT_TASK_TIMEOUT_MS } = {}) {
|
|
10
12
|
this._workerPath = pathModule.join(__dirname, 'fontTracerWorker.js');
|
|
11
13
|
this._numWorkers = numWorkers;
|
|
14
|
+
this._taskTimeoutMs = taskTimeoutMs;
|
|
12
15
|
this._workers = [];
|
|
13
16
|
this._idle = [];
|
|
14
17
|
this._pendingTasks = [];
|
|
15
18
|
this._taskCallbacks = new Map();
|
|
19
|
+
this._taskTimers = new Map();
|
|
16
20
|
this._taskByWorker = new Map(); // track which taskId each worker is processing
|
|
17
21
|
this._nextTaskId = 0;
|
|
18
22
|
}
|
|
@@ -46,8 +50,17 @@ class FontTracerPool {
|
|
|
46
50
|
await Promise.all(initPromises);
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
_clearTaskTimer(taskId) {
|
|
54
|
+
const timer = this._taskTimers.get(taskId);
|
|
55
|
+
if (timer) {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
this._taskTimers.delete(taskId);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
_onWorkerMessage(worker, msg) {
|
|
50
62
|
this._taskByWorker.delete(worker);
|
|
63
|
+
this._clearTaskTimer(msg.taskId);
|
|
51
64
|
const cb = this._taskCallbacks.get(msg.taskId);
|
|
52
65
|
if (cb) {
|
|
53
66
|
this._taskCallbacks.delete(msg.taskId);
|
|
@@ -78,6 +91,7 @@ class FontTracerPool {
|
|
|
78
91
|
const taskId = this._taskByWorker.get(worker);
|
|
79
92
|
this._taskByWorker.delete(worker);
|
|
80
93
|
if (taskId !== undefined) {
|
|
94
|
+
this._clearTaskTimer(taskId);
|
|
81
95
|
const cb = this._taskCallbacks.get(taskId);
|
|
82
96
|
if (cb) {
|
|
83
97
|
this._taskCallbacks.delete(taskId);
|
|
@@ -101,6 +115,33 @@ class FontTracerPool {
|
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
|
|
118
|
+
_startTaskTimer(taskId) {
|
|
119
|
+
if (this._taskTimeoutMs <= 0) return;
|
|
120
|
+
const timer = setTimeout(() => {
|
|
121
|
+
this._taskTimers.delete(taskId);
|
|
122
|
+
const cb = this._taskCallbacks.get(taskId);
|
|
123
|
+
if (cb) {
|
|
124
|
+
this._taskCallbacks.delete(taskId);
|
|
125
|
+
cb.reject(
|
|
126
|
+
new Error(
|
|
127
|
+
`Font tracing task ${taskId} timed out after ${this._taskTimeoutMs}ms`
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
// Terminate the hung worker so it doesn't permanently consume a pool
|
|
132
|
+
// slot. _onWorkerExit will remove it from _workers and _idle.
|
|
133
|
+
for (const [worker, tid] of this._taskByWorker) {
|
|
134
|
+
if (tid === taskId) {
|
|
135
|
+
this._taskByWorker.delete(worker);
|
|
136
|
+
worker.terminate();
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}, this._taskTimeoutMs);
|
|
141
|
+
timer.unref();
|
|
142
|
+
this._taskTimers.set(taskId, timer);
|
|
143
|
+
}
|
|
144
|
+
|
|
104
145
|
_dispatchPending() {
|
|
105
146
|
while (this._idle.length > 0 && this._pendingTasks.length > 0) {
|
|
106
147
|
const worker = this._idle.pop();
|
|
@@ -108,6 +149,7 @@ class FontTracerPool {
|
|
|
108
149
|
this._taskByWorker.set(worker, task.message.taskId);
|
|
109
150
|
try {
|
|
110
151
|
worker.postMessage(task.message);
|
|
152
|
+
this._startTaskTimer(task.message.taskId);
|
|
111
153
|
} catch (err) {
|
|
112
154
|
// postMessage can fail synchronously (e.g. structured clone error).
|
|
113
155
|
// Return the worker to the idle pool and reject the task.
|
|
@@ -149,6 +191,12 @@ class FontTracerPool {
|
|
|
149
191
|
}
|
|
150
192
|
|
|
151
193
|
async destroy() {
|
|
194
|
+
// Clear all task timers
|
|
195
|
+
for (const timer of this._taskTimers.values()) {
|
|
196
|
+
clearTimeout(timer);
|
|
197
|
+
}
|
|
198
|
+
this._taskTimers.clear();
|
|
199
|
+
|
|
152
200
|
// Reject any tasks still waiting in the queue
|
|
153
201
|
for (const task of this._pendingTasks) {
|
|
154
202
|
const cb = this._taskCallbacks.get(task.message.taskId);
|
package/lib/HeadlessBrowser.js
CHANGED
|
@@ -13,8 +13,12 @@ async function transferResults(jsHandle) {
|
|
|
13
13
|
const results = await jsHandle.jsonValue();
|
|
14
14
|
for (const [i, result] of results.entries()) {
|
|
15
15
|
const resultHandle = await jsHandle.getProperty(String(i));
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
try {
|
|
17
|
+
const elementHandle = await resultHandle.getProperty('node');
|
|
18
|
+
result.node = elementHandle;
|
|
19
|
+
} finally {
|
|
20
|
+
await resultHandle.dispose();
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
return results;
|
|
20
24
|
}
|
|
@@ -193,7 +197,11 @@ class HeadlessBrowser {
|
|
|
193
197
|
/* istanbul ignore next */
|
|
194
198
|
() => fontTracer(document)
|
|
195
199
|
);
|
|
196
|
-
|
|
200
|
+
try {
|
|
201
|
+
return await transferResults(jsHandle);
|
|
202
|
+
} finally {
|
|
203
|
+
await jsHandle.dispose();
|
|
204
|
+
}
|
|
197
205
|
} finally {
|
|
198
206
|
await page.close();
|
|
199
207
|
}
|