@turntrout/subfont 1.5.1 → 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/CHANGELOG.md +7 -0
- package/README.md +52 -21
- package/lib/FontTracerPool.js +49 -1
- package/lib/HeadlessBrowser.js +11 -3
- package/lib/collectTextsByPage.js +496 -651
- package/lib/concurrencyLimit.js +3 -1
- 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/fontFeatureHelpers.js +249 -0
- package/lib/fontTracerWorker.js +0 -10
- package/lib/gatherStylesheetsWithPredicates.js +4 -5
- package/lib/normalizeFontPropertyValue.js +1 -1
- package/lib/progress.js +101 -0
- package/lib/sfntCache.js +10 -7
- package/lib/subfont.js +80 -52
- package/lib/subsetFontWithGlyphs.js +41 -22
- package/lib/subsetFonts.js +223 -211
- package/lib/subsetGeneration.js +13 -1
- package/lib/unquote.js +9 -4
- package/lib/variationAxes.js +3 -32
- package/lib/warnAboutMissingGlyphs.js +36 -25
- package/lib/wasmQueue.js +6 -2
- package/package.json +2 -2
package/lib/unquote.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
function unescapeCssString(str) {
|
|
2
2
|
return str.replace(
|
|
3
3
|
/\\([0-9a-f]{1,6})(\s?)/gi,
|
|
4
|
-
($0, hexChars, followingWhitespace) =>
|
|
5
|
-
|
|
6
|
-
hexChars
|
|
7
|
-
|
|
4
|
+
($0, hexChars, followingWhitespace) => {
|
|
5
|
+
try {
|
|
6
|
+
return `${String.fromCodePoint(parseInt(hexChars, 16))}${
|
|
7
|
+
hexChars.length === 6 ? followingWhitespace : ''
|
|
8
|
+
}`;
|
|
9
|
+
} catch {
|
|
10
|
+
return $0;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
8
13
|
);
|
|
9
14
|
}
|
|
10
15
|
|
package/lib/variationAxes.js
CHANGED
|
@@ -8,26 +8,11 @@ const standardVariationAxes = new Set(['wght', 'wdth', 'ital', 'slnt', 'opsz']);
|
|
|
8
8
|
// CSS maps oblique to slnt -14.
|
|
9
9
|
const DEFAULT_OBLIQUE_SLNT = -14;
|
|
10
10
|
|
|
11
|
-
// When no opsz values are determined from font-
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// When no opsz values are determined from font-variation-settings, the axis is
|
|
12
|
+
// pinned to its default value rather than preserving the full range, which can
|
|
13
|
+
// significantly bloat variable font subsets.
|
|
14
14
|
const ignoredVariationAxes = new Set();
|
|
15
15
|
|
|
16
|
-
// Parse a CSS font-size value to a numeric px value.
|
|
17
|
-
// Returns the number if the value is in absolute units (px, pt), NaN otherwise.
|
|
18
|
-
// Relative units (em, rem, %, vw, etc.) cannot be resolved without DOM context.
|
|
19
|
-
const PX_PER_PT = 4 / 3;
|
|
20
|
-
function parseFontSizePx(value) {
|
|
21
|
-
if (typeof value === 'number') return value;
|
|
22
|
-
if (typeof value !== 'string') return NaN;
|
|
23
|
-
const match = value.match(/^([\d.]+)(px|pt)?$/i);
|
|
24
|
-
if (!match) return NaN;
|
|
25
|
-
const num = parseFloat(match[1]);
|
|
26
|
-
if (Number.isNaN(num) || num <= 0) return NaN;
|
|
27
|
-
const unit = (match[2] || 'px').toLowerCase();
|
|
28
|
-
return unit === 'pt' ? num * PX_PER_PT : num;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
16
|
function clamp(value, min, max) {
|
|
32
17
|
return Math.min(Math.max(value, min), max);
|
|
33
18
|
}
|
|
@@ -71,7 +56,6 @@ function getVariationAxisUsage(
|
|
|
71
56
|
fontWeights,
|
|
72
57
|
fontStretches,
|
|
73
58
|
fontVariationSettings,
|
|
74
|
-
fontSizes,
|
|
75
59
|
props,
|
|
76
60
|
} of fontUsages) {
|
|
77
61
|
if (seenFontUrls.has(fontUrl)) continue;
|
|
@@ -113,18 +97,6 @@ function getVariationAxisUsage(
|
|
|
113
97
|
);
|
|
114
98
|
}
|
|
115
99
|
|
|
116
|
-
// Map font-size to the opsz axis. With font-optical-sizing: auto
|
|
117
|
-
// (the CSS default), browsers set opsz = font-size in px.
|
|
118
|
-
// Only absolute units (px, pt) can be resolved without DOM context.
|
|
119
|
-
if (fontSizes) {
|
|
120
|
-
for (const fontSize of fontSizes) {
|
|
121
|
-
const px = parseFontSizePx(fontSize);
|
|
122
|
-
if (!Number.isNaN(px)) {
|
|
123
|
-
noteUsedValue(fontUrl, 'opsz', px);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
100
|
for (const fontVariationSettingsValue of fontVariationSettings) {
|
|
129
101
|
for (const [axisName, axisValue] of parseFontVariationSettings(
|
|
130
102
|
fontVariationSettingsValue
|
|
@@ -195,7 +167,6 @@ async function getVariationAxisBounds(
|
|
|
195
167
|
module.exports = {
|
|
196
168
|
standardVariationAxes,
|
|
197
169
|
ignoredVariationAxes,
|
|
198
|
-
parseFontSizePx,
|
|
199
170
|
renderNumberRange,
|
|
200
171
|
getVariationAxisUsage,
|
|
201
172
|
getVariationAxisBounds,
|
|
@@ -42,6 +42,41 @@ async function warnAboutMissingGlyphs(
|
|
|
42
42
|
accumulatedFontFaceDeclarations,
|
|
43
43
|
} of htmlOrSvgAssetTextsWithProps) {
|
|
44
44
|
let linesAndColumns;
|
|
45
|
+
// Dedupe scans for the same missing char across different fontUsages on
|
|
46
|
+
// this page. On KaTeX-heavy pages the same symbol is often missing in
|
|
47
|
+
// several font-families, and each scan is an O(N) walk of the HTML text.
|
|
48
|
+
const charLookupCache = new Map();
|
|
49
|
+
const lookupChar = (char) => {
|
|
50
|
+
let cached = charLookupCache.get(char);
|
|
51
|
+
if (cached) return cached;
|
|
52
|
+
let firstLocation;
|
|
53
|
+
let occurrences = 0;
|
|
54
|
+
if (char.length > 0) {
|
|
55
|
+
const sourceText = htmlOrSvgAsset.text;
|
|
56
|
+
let searchIdx = 0;
|
|
57
|
+
while (true) {
|
|
58
|
+
const charIdx = sourceText.indexOf(char, searchIdx);
|
|
59
|
+
if (charIdx === -1) break;
|
|
60
|
+
occurrences++;
|
|
61
|
+
if (occurrences === 1) {
|
|
62
|
+
if (!linesAndColumns) {
|
|
63
|
+
linesAndColumns = new LinesAndColumns(sourceText);
|
|
64
|
+
}
|
|
65
|
+
const position = linesAndColumns.locationForIndex(charIdx);
|
|
66
|
+
firstLocation = `${htmlOrSvgAsset.urlOrDescription}:${
|
|
67
|
+
position.line + 1
|
|
68
|
+
}:${position.column + 1}`;
|
|
69
|
+
}
|
|
70
|
+
searchIdx = charIdx + char.length;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!firstLocation) {
|
|
74
|
+
firstLocation = `${htmlOrSvgAsset.urlOrDescription} (generated content)`;
|
|
75
|
+
}
|
|
76
|
+
cached = { firstLocation, occurrences };
|
|
77
|
+
charLookupCache.set(char, cached);
|
|
78
|
+
return cached;
|
|
79
|
+
};
|
|
45
80
|
for (const fontUsage of fontUsages) {
|
|
46
81
|
if (!fontUsage.subsets) continue;
|
|
47
82
|
const subsetBuffer = Object.values(fontUsage.subsets)[0];
|
|
@@ -63,31 +98,7 @@ async function warnAboutMissingGlyphs(
|
|
|
63
98
|
// Report only the first location plus a count of remaining
|
|
64
99
|
// occurrences. A character like U+200B can appear thousands of
|
|
65
100
|
// times on a page and per-occurrence lines drown the log.
|
|
66
|
-
|
|
67
|
-
let occurrences = 0;
|
|
68
|
-
if (char.length > 0) {
|
|
69
|
-
const sourceText = htmlOrSvgAsset.text;
|
|
70
|
-
let searchIdx = 0;
|
|
71
|
-
while (true) {
|
|
72
|
-
const charIdx = sourceText.indexOf(char, searchIdx);
|
|
73
|
-
if (charIdx === -1) break;
|
|
74
|
-
occurrences++;
|
|
75
|
-
if (occurrences === 1) {
|
|
76
|
-
if (!linesAndColumns) {
|
|
77
|
-
linesAndColumns = new LinesAndColumns(sourceText);
|
|
78
|
-
}
|
|
79
|
-
const position = linesAndColumns.locationForIndex(charIdx);
|
|
80
|
-
firstLocation = `${htmlOrSvgAsset.urlOrDescription}:${
|
|
81
|
-
position.line + 1
|
|
82
|
-
}:${position.column + 1}`;
|
|
83
|
-
}
|
|
84
|
-
searchIdx = charIdx + char.length;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!firstLocation) {
|
|
89
|
-
firstLocation = `${htmlOrSvgAsset.urlOrDescription} (generated content)`;
|
|
90
|
-
}
|
|
101
|
+
const { firstLocation, occurrences } = lookupChar(char);
|
|
91
102
|
|
|
92
103
|
missingGlyphsErrors.push({
|
|
93
104
|
codePoint,
|
package/lib/wasmQueue.js
CHANGED
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
let queue = Promise.resolve();
|
|
6
6
|
|
|
7
7
|
function enqueue(fn) {
|
|
8
|
-
|
|
8
|
+
// Chain fn after the previous task settles. Both handlers wrap fn() in
|
|
9
|
+
// an arrow to avoid leaking the previous result/error as an argument.
|
|
10
|
+
// The error handler ensures a prior rejection doesn't block the queue.
|
|
11
|
+
queue = queue.then(
|
|
9
12
|
() => fn(),
|
|
10
13
|
() => fn()
|
|
11
|
-
)
|
|
14
|
+
);
|
|
15
|
+
return queue;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
module.exports = enqueue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turntrout/subfont",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Automatically subset web fonts to only the characters used on your pages. Fork of Munter/subfont with modern defaults.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"jsdom": "^25.0.0",
|
|
58
58
|
"lines-and-columns": "^1.1.6",
|
|
59
59
|
"memoizesync": "^1.1.1",
|
|
60
|
-
"p-limit": "^3.0.0",
|
|
61
60
|
"parse5": "^7.0.0",
|
|
62
61
|
"postcss": "^8.3.11",
|
|
63
62
|
"postcss-value-parser": "^4.0.2",
|
|
64
63
|
"pretty-bytes": "^5.1.0",
|
|
65
64
|
"puppeteer-core": "^24.39.1",
|
|
65
|
+
"sanitize-filename": "^1.6.4",
|
|
66
66
|
"specificity": "^0.4.1",
|
|
67
67
|
"urltools": "^0.4.1",
|
|
68
68
|
"yargs": "^17.7.2"
|