@turntrout/subfont 1.7.0 → 1.8.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/CLAUDE.md +39 -13
- package/README.md +24 -14
- package/lib/FontTracerPool.d.ts +38 -0
- package/lib/FontTracerPool.d.ts.map +1 -0
- package/lib/FontTracerPool.js +230 -217
- package/lib/FontTracerPool.js.map +1 -0
- package/lib/HeadlessBrowser.d.ts +18 -0
- package/lib/HeadlessBrowser.d.ts.map +1 -0
- package/lib/HeadlessBrowser.js +216 -210
- package/lib/HeadlessBrowser.js.map +1 -0
- package/lib/cli.d.ts +3 -0
- package/lib/cli.d.ts.map +1 -0
- package/lib/cli.js +15 -12
- package/lib/cli.js.map +1 -0
- package/lib/codepointMaps.d.ts +4 -0
- package/lib/codepointMaps.d.ts.map +1 -0
- package/lib/codepointMaps.js +99 -0
- package/lib/codepointMaps.js.map +1 -0
- package/lib/collectFeatureGlyphIds.d.ts +3 -0
- package/lib/collectFeatureGlyphIds.d.ts.map +1 -0
- package/lib/collectFeatureGlyphIds.js +124 -138
- package/lib/collectFeatureGlyphIds.js.map +1 -0
- package/lib/collectTextsByPage.d.ts +41 -0
- package/lib/collectTextsByPage.d.ts.map +1 -0
- package/lib/collectTextsByPage.js +726 -965
- package/lib/collectTextsByPage.js.map +1 -0
- package/lib/concurrencyLimit.d.ts +3 -0
- package/lib/concurrencyLimit.d.ts.map +1 -0
- package/lib/concurrencyLimit.js +12 -11
- package/lib/concurrencyLimit.js.map +1 -0
- package/lib/escapeJsStringLiteral.d.ts +3 -0
- package/lib/escapeJsStringLiteral.d.ts.map +1 -0
- package/lib/escapeJsStringLiteral.js +7 -6
- package/lib/escapeJsStringLiteral.js.map +1 -0
- package/lib/extractReferencedCustomPropertyNames.d.ts +3 -0
- package/lib/extractReferencedCustomPropertyNames.d.ts.map +1 -0
- package/lib/extractReferencedCustomPropertyNames.js +15 -16
- package/lib/extractReferencedCustomPropertyNames.js.map +1 -0
- package/lib/extractVisibleText.d.ts +7 -0
- package/lib/extractVisibleText.d.ts.map +1 -0
- package/lib/extractVisibleText.js +110 -119
- package/lib/extractVisibleText.js.map +1 -0
- package/lib/findCustomPropertyDefinitions.d.ts +8 -0
- package/lib/findCustomPropertyDefinitions.d.ts.map +1 -0
- package/lib/findCustomPropertyDefinitions.js +41 -48
- package/lib/findCustomPropertyDefinitions.js.map +1 -0
- package/lib/fontConverter.d.ts +2 -0
- package/lib/fontConverter.d.ts.map +1 -0
- package/lib/fontConverter.js +40 -21
- package/lib/fontConverter.js.map +1 -0
- package/lib/fontConverterWorker.d.ts +2 -0
- package/lib/fontConverterWorker.d.ts.map +1 -0
- package/lib/fontConverterWorker.js +52 -15
- package/lib/fontConverterWorker.js.map +1 -0
- package/lib/fontFaceHelpers.d.ts +64 -0
- package/lib/fontFaceHelpers.d.ts.map +1 -0
- package/lib/fontFaceHelpers.js +237 -249
- package/lib/fontFaceHelpers.js.map +1 -0
- package/lib/fontFeatureHelpers.d.ts +30 -0
- package/lib/fontFeatureHelpers.d.ts.map +1 -0
- package/lib/fontFeatureHelpers.js +277 -212
- package/lib/fontFeatureHelpers.js.map +1 -0
- package/lib/fontTracerWorker.d.ts +11 -0
- package/lib/fontTracerWorker.d.ts.map +1 -0
- package/lib/fontTracerWorker.js +94 -60
- package/lib/fontTracerWorker.js.map +1 -0
- package/lib/gatherStylesheetsWithPredicates.d.ts +26 -0
- package/lib/gatherStylesheetsWithPredicates.d.ts.map +1 -0
- package/lib/gatherStylesheetsWithPredicates.js +75 -84
- package/lib/gatherStylesheetsWithPredicates.js.map +1 -0
- package/lib/getCssRulesByProperty.d.ts +29 -0
- package/lib/getCssRulesByProperty.d.ts.map +1 -0
- package/lib/getCssRulesByProperty.js +316 -316
- package/lib/getCssRulesByProperty.js.map +1 -0
- package/lib/getFontInfo.d.ts +11 -0
- package/lib/getFontInfo.d.ts.map +1 -0
- package/lib/getFontInfo.js +31 -33
- package/lib/getFontInfo.js.map +1 -0
- package/lib/initialValueByProp.d.ts +3 -0
- package/lib/initialValueByProp.d.ts.map +1 -0
- package/lib/initialValueByProp.js +20 -17
- package/lib/initialValueByProp.js.map +1 -0
- package/lib/injectSubsetDefinitions.d.ts +3 -0
- package/lib/injectSubsetDefinitions.d.ts.map +1 -0
- package/lib/injectSubsetDefinitions.js +55 -59
- package/lib/injectSubsetDefinitions.js.map +1 -0
- package/lib/normalizeFontPropertyValue.d.ts +3 -0
- package/lib/normalizeFontPropertyValue.d.ts.map +1 -0
- package/lib/normalizeFontPropertyValue.js +59 -54
- package/lib/normalizeFontPropertyValue.js.map +1 -0
- package/lib/parseCommandLineOptions.d.ts +9 -0
- package/lib/parseCommandLineOptions.d.ts.map +1 -0
- package/lib/parseCommandLineOptions.js +145 -149
- package/lib/parseCommandLineOptions.js.map +1 -0
- package/lib/parseFontVariationSettings.d.ts +3 -0
- package/lib/parseFontVariationSettings.d.ts.map +1 -0
- package/lib/parseFontVariationSettings.js +38 -36
- package/lib/parseFontVariationSettings.js.map +1 -0
- package/lib/progress.d.ts +27 -0
- package/lib/progress.d.ts.map +1 -0
- package/lib/progress.js +51 -54
- package/lib/progress.js.map +1 -0
- package/lib/sfntCache.d.ts +4 -0
- package/lib/sfntCache.d.ts.map +1 -0
- package/lib/sfntCache.js +67 -25
- package/lib/sfntCache.js.map +1 -0
- package/lib/stripLocalTokens.d.ts +3 -0
- package/lib/stripLocalTokens.d.ts.map +1 -0
- package/lib/stripLocalTokens.js +23 -21
- package/lib/stripLocalTokens.js.map +1 -0
- package/lib/subfont.d.ts +54 -0
- package/lib/subfont.d.ts.map +1 -0
- package/lib/subfont.js +531 -629
- package/lib/subfont.js.map +1 -0
- package/lib/subsetFontWithGlyphs.d.ts +21 -0
- package/lib/subsetFontWithGlyphs.d.ts.map +1 -0
- package/lib/subsetFontWithGlyphs.js +285 -259
- package/lib/subsetFontWithGlyphs.js.map +1 -0
- package/lib/subsetFonts.d.ts +55 -0
- package/lib/subsetFonts.d.ts.map +1 -0
- package/lib/subsetFonts.js +899 -1200
- package/lib/subsetFonts.js.map +1 -0
- package/lib/subsetGeneration.d.ts +36 -0
- package/lib/subsetGeneration.d.ts.map +1 -0
- package/lib/subsetGeneration.js +328 -325
- package/lib/subsetGeneration.js.map +1 -0
- package/lib/types/shared.d.ts +11 -0
- package/lib/types/shared.d.ts.map +1 -0
- package/lib/types/shared.js +3 -0
- package/lib/types/shared.js.map +1 -0
- package/lib/unicodeRange.d.ts +3 -0
- package/lib/unicodeRange.d.ts.map +1 -0
- package/lib/unicodeRange.js +17 -30
- package/lib/unicodeRange.js.map +1 -0
- package/lib/unquote.d.ts +3 -0
- package/lib/unquote.d.ts.map +1 -0
- package/lib/unquote.js +18 -25
- package/lib/unquote.js.map +1 -0
- package/lib/variationAxes.d.ts +33 -0
- package/lib/variationAxes.d.ts.map +1 -0
- package/lib/variationAxes.js +127 -157
- package/lib/variationAxes.js.map +1 -0
- package/lib/warnAboutMissingGlyphs.d.ts +43 -0
- package/lib/warnAboutMissingGlyphs.d.ts.map +1 -0
- package/lib/warnAboutMissingGlyphs.js +139 -147
- package/lib/warnAboutMissingGlyphs.js.map +1 -0
- package/lib/wasmQueue.d.ts +3 -0
- package/lib/wasmQueue.d.ts.map +1 -0
- package/lib/wasmQueue.js +13 -10
- package/lib/wasmQueue.js.map +1 -0
- package/package.json +12 -2
package/lib/subsetGeneration.js
CHANGED
|
@@ -1,352 +1,355 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports._SubsetDiskCache = void 0;
|
|
37
|
+
exports.getSubsetPromiseId = getSubsetPromiseId;
|
|
38
|
+
exports.getSubsetsForFontUsage = getSubsetsForFontUsage;
|
|
39
|
+
exports._subsetCacheKey = subsetCacheKey;
|
|
40
|
+
const fs = __importStar(require("fs/promises"));
|
|
41
|
+
const pathModule = require("path");
|
|
42
|
+
const crypto = __importStar(require("crypto"));
|
|
43
|
+
const variationAxes_1 = require("./variationAxes");
|
|
44
|
+
const collectFeatureGlyphIds = require("./collectFeatureGlyphIds");
|
|
45
|
+
const subsetFontWithGlyphs = require("./subsetFontWithGlyphs");
|
|
46
|
+
const codepointMaps_1 = require("./codepointMaps");
|
|
8
47
|
// Bump when subsetting behaviour changes to invalidate stale disk-cache
|
|
9
48
|
// entries (e.g. after adding hinting removal or table stripping).
|
|
10
|
-
const SUBSET_CACHE_VERSION = '
|
|
11
|
-
|
|
49
|
+
const SUBSET_CACHE_VERSION = '4';
|
|
12
50
|
// Cache the SHA-256 hash state after feeding SUBSET_CACHE_VERSION + fontBuffer.
|
|
13
51
|
// For a font with 2 target formats this halves the hashing work on large buffers.
|
|
14
52
|
// Uses WeakMap so entries are garbage-collected when the buffer is released.
|
|
15
53
|
const fontBufferHashPrefixes = new WeakMap();
|
|
16
54
|
function getFontBufferHashPrefix(fontBuffer) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
55
|
+
let cached = fontBufferHashPrefixes.get(fontBuffer);
|
|
56
|
+
if (!cached) {
|
|
57
|
+
cached = crypto.createHash('sha256');
|
|
58
|
+
cached.update(SUBSET_CACHE_VERSION);
|
|
59
|
+
cached.update(fontBuffer);
|
|
60
|
+
fontBufferHashPrefixes.set(fontBuffer, cached);
|
|
61
|
+
}
|
|
62
|
+
return cached;
|
|
24
63
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (variationAxes) hash.update(JSON.stringify(variationAxes));
|
|
40
|
-
if (featureGlyphIds) hash.update(JSON.stringify(featureGlyphIds));
|
|
41
|
-
return hash.digest('hex');
|
|
64
|
+
function subsetCacheKey(fontBuffer, text, targetFormat, variationAxes, featureGlyphIds, extraOptions = undefined) {
|
|
65
|
+
// Clone the pre-computed prefix (version + font buffer) and append
|
|
66
|
+
// the remaining fields. hash.copy() is O(1) — just copies the
|
|
67
|
+
// internal digest state, avoiding re-hashing the entire font buffer.
|
|
68
|
+
const hash = getFontBufferHashPrefix(fontBuffer).copy();
|
|
69
|
+
hash.update(text);
|
|
70
|
+
hash.update(targetFormat);
|
|
71
|
+
if (variationAxes)
|
|
72
|
+
hash.update(JSON.stringify(variationAxes));
|
|
73
|
+
if (featureGlyphIds)
|
|
74
|
+
hash.update(JSON.stringify(featureGlyphIds));
|
|
75
|
+
if (extraOptions)
|
|
76
|
+
hash.update(JSON.stringify(extraOptions));
|
|
77
|
+
return hash.digest('hex');
|
|
42
78
|
}
|
|
43
|
-
|
|
44
79
|
class SubsetDiskCache {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
_cacheDir;
|
|
81
|
+
_console;
|
|
82
|
+
_ensured;
|
|
83
|
+
_warnedWrite;
|
|
84
|
+
constructor(cacheDir, console) {
|
|
85
|
+
this._cacheDir = cacheDir;
|
|
86
|
+
this._console = console ?? null;
|
|
87
|
+
this._ensured = false;
|
|
88
|
+
this._warnedWrite = false;
|
|
89
|
+
}
|
|
90
|
+
async _ensureDir() {
|
|
91
|
+
if (!this._ensured) {
|
|
92
|
+
// Only attempt once — persistent failures (bad path, permissions)
|
|
93
|
+
// are far more common than transient ones, and retrying just
|
|
94
|
+
// produces repeated warnings.
|
|
95
|
+
this._ensured = true;
|
|
96
|
+
try {
|
|
97
|
+
await fs.mkdir(this._cacheDir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (this._console) {
|
|
101
|
+
this._console.warn(`subfont: cache directory ${this._cacheDir} could not be created: ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
65
104
|
}
|
|
66
|
-
}
|
|
67
105
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
106
|
+
async get(key) {
|
|
107
|
+
const filePath = pathModule.join(this._cacheDir, key);
|
|
108
|
+
try {
|
|
109
|
+
return await fs.readFile(filePath);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
76
114
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
await this._ensureDir();
|
|
81
|
-
const filePath = pathModule.join(this._cacheDir, key);
|
|
82
|
-
try {
|
|
83
|
-
await fs.writeFile(filePath, buffer);
|
|
84
|
-
} catch (err) {
|
|
85
|
-
// If the directory was removed after init, retry once
|
|
86
|
-
if (err.code === 'ENOENT') {
|
|
115
|
+
async set(key, buffer) {
|
|
116
|
+
await this._ensureDir();
|
|
117
|
+
const filePath = pathModule.join(this._cacheDir, key);
|
|
87
118
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
119
|
+
await fs.writeFile(filePath, buffer);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const errno = err;
|
|
123
|
+
// If the directory was removed after init, retry once
|
|
124
|
+
if (errno.code === 'ENOENT') {
|
|
125
|
+
try {
|
|
126
|
+
await fs.mkdir(this._cacheDir, { recursive: true });
|
|
127
|
+
await fs.writeFile(filePath, buffer);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Fall through to warning below
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (this._warnedWrite)
|
|
135
|
+
return;
|
|
136
|
+
this._warnedWrite = true;
|
|
137
|
+
if (this._console) {
|
|
138
|
+
this._console.warn(`subfont: failed to write cache entry ${key}: ${errno.message}`);
|
|
139
|
+
}
|
|
93
140
|
}
|
|
94
|
-
}
|
|
95
|
-
if (this._warnedWrite) return;
|
|
96
|
-
this._warnedWrite = true;
|
|
97
|
-
if (this._console) {
|
|
98
|
-
this._console.warn(
|
|
99
|
-
`subfont: failed to write cache entry ${key}: ${err.message}`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
141
|
}
|
|
103
|
-
}
|
|
104
142
|
}
|
|
105
|
-
|
|
143
|
+
exports._SubsetDiskCache = SubsetDiskCache;
|
|
106
144
|
function getSubsetPromiseId(fontUsage, format, variationAxes = null) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
145
|
+
return [
|
|
146
|
+
fontUsage.text,
|
|
147
|
+
fontUsage.fontUrl,
|
|
148
|
+
format,
|
|
149
|
+
JSON.stringify(variationAxes),
|
|
150
|
+
].join('\x1d');
|
|
113
151
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
const cacheStats = diskCache ? { hits: 0, misses: 0 } : null;
|
|
126
|
-
|
|
127
|
-
// Collect one canonical fontUsage per font URL
|
|
128
|
-
const canonicalFontUsageByUrl = new Map();
|
|
129
|
-
for (const item of htmlOrSvgAssetTextsWithProps) {
|
|
130
|
-
for (const fontUsage of item.fontUsages) {
|
|
131
|
-
if (
|
|
132
|
-
fontUsage.fontUrl &&
|
|
133
|
-
!canonicalFontUsageByUrl.has(fontUsage.fontUrl)
|
|
134
|
-
) {
|
|
135
|
-
canonicalFontUsageByUrl.set(fontUsage.fontUrl, fontUsage);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const allFontUrls = [...canonicalFontUsageByUrl.keys()];
|
|
141
|
-
|
|
142
|
-
// Load font assets
|
|
143
|
-
await assetGraph.populate({
|
|
144
|
-
followRelations: {
|
|
145
|
-
to: { url: { $or: allFontUrls } },
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const fontAssetsByUrl = new Map();
|
|
150
|
-
const originalFontBuffers = new Map();
|
|
151
|
-
for (const fontUrl of allFontUrls) {
|
|
152
|
-
const fontAsset = assetGraph.findAssets({
|
|
153
|
-
url: fontUrl,
|
|
154
|
-
isLoaded: true,
|
|
155
|
-
})[0];
|
|
156
|
-
if (fontAsset) {
|
|
157
|
-
fontAssetsByUrl.set(fontUrl, fontAsset);
|
|
158
|
-
originalFontBuffers.set(fontUrl, fontAsset.rawSrc);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Compute variation axis bounds for all fonts in parallel
|
|
163
|
-
const fontUrlsWithAssets = allFontUrls.filter((url) =>
|
|
164
|
-
fontAssetsByUrl.has(url)
|
|
165
|
-
);
|
|
166
|
-
const boundsResults = await Promise.all(
|
|
167
|
-
fontUrlsWithAssets.map((fontUrl) =>
|
|
168
|
-
getVariationAxisBounds(
|
|
169
|
-
fontAssetsByUrl,
|
|
170
|
-
fontUrl,
|
|
171
|
-
seenAxisValuesByFontUrlAndAxisName
|
|
172
|
-
)
|
|
173
|
-
)
|
|
174
|
-
);
|
|
175
|
-
const variationAxisBoundsCache = new Map();
|
|
176
|
-
for (let i = 0; i < fontUrlsWithAssets.length; i++) {
|
|
177
|
-
variationAxisBoundsCache.set(fontUrlsWithAssets[i], boundsResults[i]);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const subsetPromiseMap = new Map();
|
|
181
|
-
const subsetInfoByFontUrl = new Map();
|
|
182
|
-
|
|
183
|
-
// Process fonts concurrently — each font's feature glyph collection
|
|
184
|
-
// and subset queuing run in parallel, so fonts without feature settings
|
|
185
|
-
// don't wait behind fonts that need collectFeatureGlyphIds.
|
|
186
|
-
await Promise.all(
|
|
187
|
-
[...canonicalFontUsageByUrl].map(async ([fontUrl, fontUsage]) => {
|
|
188
|
-
const fontBuffer = originalFontBuffers.get(fontUrl);
|
|
189
|
-
if (!fontBuffer) return;
|
|
190
|
-
const text = fontUsage.text;
|
|
191
|
-
|
|
192
|
-
const bounds = variationAxisBoundsCache.get(fontUrl);
|
|
193
|
-
const subsetInfo = bounds
|
|
194
|
-
? {
|
|
195
|
-
variationAxes: bounds.variationAxes,
|
|
196
|
-
fullyInstanced: bounds.fullyInstanced,
|
|
197
|
-
numAxesPinned: bounds.numAxesPinned,
|
|
198
|
-
numAxesReduced: bounds.numAxesReduced,
|
|
199
|
-
}
|
|
200
|
-
: {
|
|
201
|
-
variationAxes: undefined,
|
|
202
|
-
fullyInstanced: false,
|
|
203
|
-
numAxesPinned: 0,
|
|
204
|
-
numAxesReduced: 0,
|
|
205
|
-
};
|
|
206
|
-
subsetInfoByFontUrl.set(fontUrl, subsetInfo);
|
|
207
|
-
|
|
208
|
-
let featureGlyphIds;
|
|
209
|
-
if (fontUsage.hasFontFeatureSettings && fontBuffer) {
|
|
210
|
-
try {
|
|
211
|
-
featureGlyphIds = await collectFeatureGlyphIds(
|
|
212
|
-
fontBuffer,
|
|
213
|
-
text,
|
|
214
|
-
fontUsage.fontFeatureTags
|
|
215
|
-
);
|
|
216
|
-
} catch (err) {
|
|
217
|
-
// Feature glyph collection failed — continue without feature
|
|
218
|
-
// glyphs rather than blocking all fonts (Promise.all would
|
|
219
|
-
// reject entirely if this propagated).
|
|
220
|
-
err.asset = err.asset || fontAssetsByUrl.get(fontUrl);
|
|
221
|
-
assetGraph.warn(err);
|
|
152
|
+
async function getSubsetsForFontUsage(assetGraph, htmlOrSvgAssetTextsWithProps, formats, seenAxisValuesByFontUrlAndAxisName, cacheDir = null, console = null, debug = false) {
|
|
153
|
+
const diskCache = cacheDir ? new SubsetDiskCache(cacheDir, console) : null;
|
|
154
|
+
const cacheStats = diskCache ? { hits: 0, misses: 0 } : null;
|
|
155
|
+
// Collect one canonical fontUsage per font URL
|
|
156
|
+
const canonicalFontUsageByUrl = new Map();
|
|
157
|
+
for (const item of htmlOrSvgAssetTextsWithProps) {
|
|
158
|
+
for (const fontUsage of item.fontUsages) {
|
|
159
|
+
if (fontUsage.fontUrl &&
|
|
160
|
+
!canonicalFontUsageByUrl.has(fontUsage.fontUrl)) {
|
|
161
|
+
canonicalFontUsageByUrl.set(fontUsage.fontUrl, fontUsage);
|
|
162
|
+
}
|
|
222
163
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
: null;
|
|
242
|
-
const cachedResult = diskCache ? await diskCache.get(cacheKey) : null;
|
|
243
|
-
|
|
244
|
-
if (cachedResult) {
|
|
245
|
-
if (cacheStats) cacheStats.hits++;
|
|
246
|
-
subsetPromiseMap.set(promiseId, Promise.resolve(cachedResult));
|
|
247
|
-
} else {
|
|
248
|
-
if (cacheStats) cacheStats.misses++;
|
|
249
|
-
const subsetCall = subsetFontWithGlyphs(fontBuffer, text, {
|
|
250
|
-
targetFormat,
|
|
251
|
-
glyphIds: featureGlyphIds,
|
|
252
|
-
variationAxes: subsetInfo.variationAxes,
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
subsetPromiseMap.set(
|
|
256
|
-
promiseId,
|
|
257
|
-
subsetCall
|
|
258
|
-
.then(async (result) => {
|
|
259
|
-
if (diskCache && result) {
|
|
260
|
-
// Fire-and-forget: cache writes are best-effort.
|
|
261
|
-
// Errors are handled inside set(); the catch is a
|
|
262
|
-
// safety net against unhandled rejections.
|
|
263
|
-
diskCache.set(cacheKey, result).catch(() => {});
|
|
264
|
-
}
|
|
265
|
-
return result;
|
|
266
|
-
})
|
|
267
|
-
.catch((err) => {
|
|
268
|
-
err.asset = err.asset || fontAssetsByUrl.get(fontUrl);
|
|
269
|
-
assetGraph.warn(err);
|
|
270
|
-
return null;
|
|
271
|
-
})
|
|
272
|
-
);
|
|
273
|
-
}
|
|
164
|
+
}
|
|
165
|
+
const allFontUrls = [...canonicalFontUsageByUrl.keys()];
|
|
166
|
+
// Load font assets
|
|
167
|
+
await assetGraph.populate({
|
|
168
|
+
followRelations: {
|
|
169
|
+
to: { url: { $or: allFontUrls } },
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
const fontAssetsByUrl = new Map();
|
|
173
|
+
const originalFontBuffers = new Map();
|
|
174
|
+
for (const fontUrl of allFontUrls) {
|
|
175
|
+
const fontAsset = assetGraph.findAssets({
|
|
176
|
+
url: fontUrl,
|
|
177
|
+
isLoaded: true,
|
|
178
|
+
})[0];
|
|
179
|
+
if (fontAsset) {
|
|
180
|
+
fontAssetsByUrl.set(fontUrl, fontAsset);
|
|
181
|
+
originalFontBuffers.set(fontUrl, fontAsset.rawSrc);
|
|
274
182
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
183
|
+
}
|
|
184
|
+
// Compute variation axis bounds for all fonts in parallel
|
|
185
|
+
const fontUrlsWithAssets = allFontUrls.filter((url) => fontAssetsByUrl.has(url));
|
|
186
|
+
const boundsResults = await Promise.all(fontUrlsWithAssets.map((fontUrl) => (0, variationAxes_1.getVariationAxisBounds)(fontAssetsByUrl, fontUrl, seenAxisValuesByFontUrlAndAxisName)));
|
|
187
|
+
const variationAxisBoundsCache = new Map();
|
|
188
|
+
for (let i = 0; i < fontUrlsWithAssets.length; i++) {
|
|
189
|
+
variationAxisBoundsCache.set(fontUrlsWithAssets[i], boundsResults[i]);
|
|
190
|
+
}
|
|
191
|
+
const subsetPromiseMap = new Map();
|
|
192
|
+
const subsetInfoByFontUrl = new Map();
|
|
193
|
+
// Process fonts concurrently — each font's feature glyph collection
|
|
194
|
+
// and subset queuing run in parallel, so fonts without feature settings
|
|
195
|
+
// don't wait behind fonts that need collectFeatureGlyphIds.
|
|
196
|
+
await Promise.all([...canonicalFontUsageByUrl].map(async ([fontUrl, fontUsage]) => {
|
|
197
|
+
const fontBuffer = originalFontBuffers.get(fontUrl);
|
|
198
|
+
if (!fontBuffer)
|
|
199
|
+
return;
|
|
200
|
+
const text = fontUsage.text;
|
|
201
|
+
const bounds = variationAxisBoundsCache.get(fontUrl);
|
|
202
|
+
const subsetInfo = bounds
|
|
203
|
+
? {
|
|
204
|
+
variationAxes: bounds.variationAxes,
|
|
205
|
+
fullyInstanced: bounds.fullyInstanced,
|
|
206
|
+
numAxesPinned: bounds.numAxesPinned,
|
|
207
|
+
numAxesReduced: bounds.numAxesReduced,
|
|
208
|
+
}
|
|
209
|
+
: {
|
|
210
|
+
variationAxes: undefined,
|
|
211
|
+
fullyInstanced: false,
|
|
212
|
+
numAxesPinned: 0,
|
|
213
|
+
numAxesReduced: 0,
|
|
214
|
+
};
|
|
215
|
+
subsetInfoByFontUrl.set(fontUrl, subsetInfo);
|
|
216
|
+
let featureGlyphIds;
|
|
217
|
+
if (fontUsage.hasFontFeatureSettings) {
|
|
218
|
+
try {
|
|
219
|
+
featureGlyphIds = await collectFeatureGlyphIds(fontBuffer, text, fontUsage.fontFeatureTags);
|
|
220
|
+
}
|
|
221
|
+
catch (rawErr) {
|
|
222
|
+
// Feature glyph collection failed — continue without feature
|
|
223
|
+
// glyphs rather than blocking all fonts (Promise.all would
|
|
224
|
+
// reject entirely if this propagated).
|
|
225
|
+
const err = rawErr instanceof Error
|
|
226
|
+
? rawErr
|
|
227
|
+
: new Error(String(rawErr));
|
|
228
|
+
err.asset =
|
|
229
|
+
err.asset || fontAssetsByUrl.get(fontUrl);
|
|
230
|
+
assetGraph.warn(err);
|
|
231
|
+
}
|
|
307
232
|
}
|
|
308
|
-
fontUsage
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
233
|
+
// Per-fontUsage decisions — same across all target formats.
|
|
234
|
+
// False positives (keeping a table the page doesn't need) cost a few
|
|
235
|
+
// hundred bytes; false negatives (dropping a needed table) break
|
|
236
|
+
// rendering, so the heuristics err on the side of keeping.
|
|
237
|
+
const extraOptions = {
|
|
238
|
+
dropMathTable: !(0, codepointMaps_1.pageNeedsMathTable)(text),
|
|
239
|
+
dropColorTables: !(0, codepointMaps_1.pageNeedsColorTables)(text),
|
|
240
|
+
scriptTags: (0, codepointMaps_1.scriptsForText)(text),
|
|
241
|
+
};
|
|
242
|
+
// Targeted feature retention when we can fully enumerate the
|
|
243
|
+
// CSS-requested feature tags. If the page declares feature settings
|
|
244
|
+
// but the tags couldn't be extracted (e.g. resolution through CSS
|
|
245
|
+
// custom-property var() chains is incomplete), fall back to retain-
|
|
246
|
+
// all so we don't drop features the page actually uses.
|
|
247
|
+
const featureTags = fontUsage.hasFontFeatureSettings && !fontUsage.fontFeatureTags
|
|
248
|
+
? undefined
|
|
249
|
+
: fontUsage.fontFeatureTags
|
|
250
|
+
? [...fontUsage.fontFeatureTags]
|
|
251
|
+
: [];
|
|
252
|
+
for (const targetFormat of formats) {
|
|
253
|
+
const promiseId = getSubsetPromiseId(fontUsage, targetFormat, subsetInfo.variationAxes);
|
|
254
|
+
if (!subsetPromiseMap.has(promiseId)) {
|
|
255
|
+
const cacheKey = diskCache
|
|
256
|
+
? subsetCacheKey(fontBuffer, text, targetFormat, subsetInfo.variationAxes, featureGlyphIds, extraOptions)
|
|
257
|
+
: null;
|
|
258
|
+
const cachedResult = diskCache && cacheKey ? await diskCache.get(cacheKey) : null;
|
|
259
|
+
if (cachedResult) {
|
|
260
|
+
if (cacheStats)
|
|
261
|
+
cacheStats.hits++;
|
|
262
|
+
subsetPromiseMap.set(promiseId, Promise.resolve(cachedResult));
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
if (cacheStats)
|
|
266
|
+
cacheStats.misses++;
|
|
267
|
+
const subsetCall = subsetFontWithGlyphs(fontBuffer, text, {
|
|
268
|
+
targetFormat,
|
|
269
|
+
glyphIds: featureGlyphIds,
|
|
270
|
+
variationAxes: subsetInfo.variationAxes,
|
|
271
|
+
featureTags,
|
|
272
|
+
...extraOptions,
|
|
273
|
+
});
|
|
274
|
+
subsetPromiseMap.set(promiseId, subsetCall
|
|
275
|
+
.then(async (result) => {
|
|
276
|
+
if (diskCache && result && cacheKey) {
|
|
277
|
+
// Fire-and-forget: cache writes are best-effort.
|
|
278
|
+
// Errors are handled inside set(); the catch is a
|
|
279
|
+
// safety net against unhandled rejections.
|
|
280
|
+
diskCache.set(cacheKey, result).catch(() => { });
|
|
281
|
+
}
|
|
282
|
+
return result;
|
|
283
|
+
})
|
|
284
|
+
.catch((rawErr) => {
|
|
285
|
+
const err = rawErr instanceof Error
|
|
286
|
+
? rawErr
|
|
287
|
+
: new Error(String(rawErr));
|
|
288
|
+
err.asset =
|
|
289
|
+
err.asset ||
|
|
290
|
+
fontAssetsByUrl.get(fontUrl);
|
|
291
|
+
assetGraph.warn(err);
|
|
292
|
+
return null;
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
320
296
|
}
|
|
321
|
-
|
|
297
|
+
}));
|
|
298
|
+
// Await all subset promises
|
|
299
|
+
const resolvedSubsets = new Map(await Promise.all([...subsetPromiseMap].map(async ([key, promise]) => [key, await promise])));
|
|
300
|
+
if (cacheStats && debug && console) {
|
|
301
|
+
const total = cacheStats.hits + cacheStats.misses;
|
|
302
|
+
const pct = total > 0 ? Math.round((cacheStats.hits * 100) / total) : 0;
|
|
303
|
+
console.log(`[subfont timing] subset disk cache: ${cacheStats.hits} hit${cacheStats.hits === 1 ? '' : 's'}, ${cacheStats.misses} miss${cacheStats.misses === 1 ? '' : 'es'} (${pct}% hit rate)`);
|
|
322
304
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// Propagate subsets to non-canonical font usages
|
|
326
|
-
for (const item of htmlOrSvgAssetTextsWithProps) {
|
|
327
|
-
for (const fontUsage of item.fontUsages) {
|
|
328
|
-
if (!fontUsage.fontUrl) continue;
|
|
329
|
-
const canonical = canonicalFontUsageByUrl.get(fontUsage.fontUrl);
|
|
330
|
-
if (canonical && canonical !== fontUsage && canonical.subsets) {
|
|
305
|
+
// Assign subset results to canonical font usages
|
|
306
|
+
for (const [, fontUsage] of canonicalFontUsageByUrl) {
|
|
331
307
|
const info = subsetInfoByFontUrl.get(fontUsage.fontUrl);
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
308
|
+
if (!info)
|
|
309
|
+
continue;
|
|
310
|
+
for (const targetFormat of formats) {
|
|
311
|
+
const promiseId = getSubsetPromiseId(fontUsage, targetFormat, info.variationAxes);
|
|
312
|
+
const subsetBuffer = resolvedSubsets.get(promiseId);
|
|
313
|
+
if (subsetBuffer) {
|
|
314
|
+
if (!fontUsage.subsets) {
|
|
315
|
+
fontUsage.subsets = {};
|
|
316
|
+
}
|
|
317
|
+
fontUsage.subsets[targetFormat] = subsetBuffer;
|
|
318
|
+
const size = subsetBuffer.length;
|
|
319
|
+
if (!fontUsage.smallestSubsetSize ||
|
|
320
|
+
size < fontUsage.smallestSubsetSize) {
|
|
321
|
+
fontUsage.smallestSubsetSize = size;
|
|
322
|
+
fontUsage.smallestSubsetFormat = targetFormat;
|
|
323
|
+
fontUsage.variationAxes = info.variationAxes;
|
|
324
|
+
fontUsage.fullyInstanced = info.fullyInstanced;
|
|
325
|
+
fontUsage.numAxesPinned = info.numAxesPinned;
|
|
326
|
+
fontUsage.numAxesReduced = info.numAxesReduced;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Propagate subsets to non-canonical font usages
|
|
332
|
+
for (const item of htmlOrSvgAssetTextsWithProps) {
|
|
333
|
+
for (const fontUsage of item.fontUsages) {
|
|
334
|
+
if (!fontUsage.fontUrl)
|
|
335
|
+
continue;
|
|
336
|
+
const canonical = canonicalFontUsageByUrl.get(fontUsage.fontUrl);
|
|
337
|
+
if (canonical && canonical !== fontUsage && canonical.subsets) {
|
|
338
|
+
const info = subsetInfoByFontUrl.get(fontUsage.fontUrl);
|
|
339
|
+
if (!info)
|
|
340
|
+
continue;
|
|
341
|
+
// Shallow-copy so per-page mutation of one fontUsage's subsets
|
|
342
|
+
// doesn't leak into the canonical entry or other pages.
|
|
343
|
+
fontUsage.subsets = { ...canonical.subsets };
|
|
344
|
+
fontUsage.smallestSubsetSize = canonical.smallestSubsetSize;
|
|
345
|
+
fontUsage.smallestSubsetFormat = canonical.smallestSubsetFormat;
|
|
346
|
+
fontUsage.variationAxes = info.variationAxes;
|
|
347
|
+
fontUsage.fullyInstanced = info.fullyInstanced;
|
|
348
|
+
fontUsage.numAxesPinned = info.numAxesPinned;
|
|
349
|
+
fontUsage.numAxesReduced = info.numAxesReduced;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
340
352
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return fontAssetsByUrl;
|
|
353
|
+
return fontAssetsByUrl;
|
|
344
354
|
}
|
|
345
|
-
|
|
346
|
-
module.exports = {
|
|
347
|
-
getSubsetPromiseId,
|
|
348
|
-
getSubsetsForFontUsage,
|
|
349
|
-
// Exported for testing
|
|
350
|
-
_subsetCacheKey: subsetCacheKey,
|
|
351
|
-
_SubsetDiskCache: SubsetDiskCache,
|
|
352
|
-
};
|
|
355
|
+
//# sourceMappingURL=subsetGeneration.js.map
|