fontmin-rs 0.1.0-beta.1

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.
@@ -0,0 +1,1257 @@
1
+ import { css, deliverySlices, glyph, otf2ttf, svg2ttf, svgs2ttf, ttf2eot, ttf2svg, ttf2woff, ttf2woff2 } from "./plugins.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
4
+ import { basename, dirname, extname, join, resolve } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { parse } from "jsonc-parser";
7
+ import { createHash } from "node:crypto";
8
+ import { glob } from "tinyglobby";
9
+ import { readFileSync } from "node:fs";
10
+ //#region src/config.ts
11
+ const DEFAULT_CONFIG_FILES = [
12
+ "fontmin.config.ts",
13
+ "fontmin.config.mts",
14
+ "fontmin.config.mjs",
15
+ "fontmin.config.cjs",
16
+ "fontmin.config.json",
17
+ "fontmin.config.jsonc"
18
+ ];
19
+ function defineConfig(config) {
20
+ return config;
21
+ }
22
+ async function loadConfig(configPath) {
23
+ const resolvedPath = configPath === void 0 ? await findConfig() : resolve(configPath);
24
+ const extension = extname(resolvedPath);
25
+ const configDir = dirname(resolvedPath);
26
+ let config;
27
+ if (extension === ".json") {
28
+ const contents = await readFile(resolvedPath, "utf8");
29
+ config = JSON.parse(contents);
30
+ } else if (extension === ".jsonc") config = parse(await readFile(resolvedPath, "utf8"));
31
+ else {
32
+ const configModule = await import(pathToFileURL(resolvedPath).href);
33
+ const loadedConfig = configModule.default ?? configModule.config;
34
+ config = typeof loadedConfig === "function" ? await loadedConfig() : loadedConfig;
35
+ }
36
+ return defineConfig(withConfigDefaults(config, configDir));
37
+ }
38
+ async function findConfig(cwd = process.cwd()) {
39
+ for (const fileName of DEFAULT_CONFIG_FILES) {
40
+ const configPath = resolve(cwd, fileName);
41
+ if (await isFile(configPath)) return configPath;
42
+ }
43
+ throw new Error(`could not find fontmin config in ${cwd}`);
44
+ }
45
+ async function isFile(path) {
46
+ try {
47
+ return (await stat(path)).isFile();
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+ function withConfigDefaults(config, configDir) {
53
+ if (config.cwd !== void 0) return config;
54
+ return {
55
+ ...config,
56
+ cwd: configDir
57
+ };
58
+ }
59
+ //#endregion
60
+ //#region src/native-loader.ts
61
+ const require$1 = createRequire(import.meta.url);
62
+ let binding;
63
+ var NativeBindingLoadError = class extends Error {
64
+ constructor(cause) {
65
+ super("fontmin-rs native binding is unavailable", { cause });
66
+ this.name = "NativeBindingLoadError";
67
+ }
68
+ };
69
+ function loadNativeBinding() {
70
+ if (binding !== void 0) return binding;
71
+ try {
72
+ binding = require$1("@fontmin-rs/binding");
73
+ } catch (error) {
74
+ throw new NativeBindingLoadError(error);
75
+ }
76
+ return binding;
77
+ }
78
+ //#endregion
79
+ //#region src/wasm-fallback.ts
80
+ const require = createRequire(import.meta.url);
81
+ const wasmPackageName = "@fontmin-rs/wasm";
82
+ let runtime;
83
+ async function loadWasmRuntime() {
84
+ runtime ??= initializeWasmRuntime();
85
+ return runtime;
86
+ }
87
+ async function initializeWasmRuntime() {
88
+ try {
89
+ const bytes = await readFile(join(dirname(require.resolve(wasmPackageName)), "fontmin_wasm_core_bg.wasm"));
90
+ const wasm = await import(wasmPackageName);
91
+ await wasm.initWasm(bytes);
92
+ return wasm;
93
+ } catch (cause) {
94
+ throw new Error("fontmin-rs WASM runtime failed to initialize", { cause });
95
+ }
96
+ }
97
+ //#endregion
98
+ //#region src/native.ts
99
+ function assignDefined(target, key, value) {
100
+ if (value !== void 0) target[key] = value;
101
+ }
102
+ function assertAvailableWoff2Fallback(fallback) {
103
+ if (fallback === void 0 || fallback === "native" || fallback === "auto") return;
104
+ throw new Error(fallback === "wasm" ? "WOFF2 fallback `wasm` is asynchronous; use ttfToWoff2Async() instead." : unavailableWoff2Fallback(fallback).message);
105
+ }
106
+ function unavailableWoff2Fallback(fallback) {
107
+ return /* @__PURE__ */ new Error(`WOFF2 fallback \`${fallback}\` is not available in this build`);
108
+ }
109
+ function toNativeSvgIcon(input) {
110
+ const nativeInput = {
111
+ contents: input.contents,
112
+ name: input.name
113
+ };
114
+ assignDefined(nativeInput, "unicode", input.unicode);
115
+ return nativeInput;
116
+ }
117
+ function toNativeSvg2TtfOptions(options) {
118
+ const nativeOptions = {};
119
+ assignDefined(nativeOptions, "hinting", options.hinting);
120
+ assignDefined(nativeOptions, "normalize", options.normalize);
121
+ return nativeOptions;
122
+ }
123
+ function toNativeSvgs2TtfOptions(options) {
124
+ const nativeOptions = {};
125
+ assignDefined(nativeOptions, "fontName", options.fontName);
126
+ assignDefined(nativeOptions, "startUnicode", options.startUnicode);
127
+ assignDefined(nativeOptions, "ascent", options.ascent);
128
+ assignDefined(nativeOptions, "descent", options.descent);
129
+ assignDefined(nativeOptions, "normalize", options.normalize);
130
+ return nativeOptions;
131
+ }
132
+ function toNativeWoffOptions(options) {
133
+ const nativeOptions = {};
134
+ assignDefined(nativeOptions, "deflate", options.deflate);
135
+ assignDefined(nativeOptions, "compressionLevel", options.compressionLevel);
136
+ assignDefined(nativeOptions, "metadata", options.metadata);
137
+ if (options.privateData !== void 0) nativeOptions.privateData = Buffer.isBuffer(options.privateData) ? options.privateData : Buffer.from(options.privateData);
138
+ return nativeOptions;
139
+ }
140
+ function subsetTtf(input, options = {}) {
141
+ const nativeOptions = {};
142
+ const text = resolveSubsetText(options);
143
+ if (text !== void 0) nativeOptions.text = text;
144
+ if (options.unicodes !== void 0) nativeOptions.unicodes = options.unicodes;
145
+ if (options.unicodeRanges !== void 0) nativeOptions.unicodeRanges = options.unicodeRanges;
146
+ if (options.basicText !== void 0) nativeOptions.basicText = options.basicText;
147
+ if (options.trim !== void 0) nativeOptions.trim = options.trim;
148
+ if (options.keepNotdef !== void 0) nativeOptions.keepNotdef = options.keepNotdef;
149
+ if (options.keepLayout !== void 0) nativeOptions.keepLayout = options.keepLayout;
150
+ const preserveHinting = options.preserveHinting ?? options.hinting;
151
+ if (preserveHinting !== void 0) nativeOptions.preserveHinting = preserveHinting;
152
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
153
+ return loadNativeBinding().subsetTtf(inputBuffer, nativeOptions);
154
+ }
155
+ function resolveSubsetText(options) {
156
+ if (options.textFile === void 0) return options.text;
157
+ const fileText = readFileSync(options.textFile, "utf8");
158
+ return options.text === void 0 ? fileText : `${options.text}${fileText}`;
159
+ }
160
+ function inspect(input) {
161
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
162
+ return loadNativeBinding().inspectFont(inputBuffer);
163
+ }
164
+ function ttfToWoff(input, options = {}) {
165
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
166
+ return loadNativeBinding().ttfToWoff(inputBuffer, toNativeWoffOptions(options));
167
+ }
168
+ function woffToTtf(input) {
169
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
170
+ return loadNativeBinding().woffToTtf(inputBuffer);
171
+ }
172
+ function woff2ToTtf(input) {
173
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
174
+ return loadNativeBinding().woff2ToTtf(inputBuffer);
175
+ }
176
+ function eotToTtf(input) {
177
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
178
+ return loadNativeBinding().eotToTtf(inputBuffer);
179
+ }
180
+ function otfToTtf(input, options = {}) {
181
+ const nativeOptions = {};
182
+ assignDefined(nativeOptions, "preserveHinting", options.preserveHinting);
183
+ assignDefined(nativeOptions, "variationCoordinates", options.variationCoordinates);
184
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
185
+ return loadNativeBinding().otfToTtf(inputBuffer, nativeOptions);
186
+ }
187
+ function ttfToWoff2(input, options = {}) {
188
+ const nativeOptions = {};
189
+ assertAvailableWoff2Fallback(options.fallback);
190
+ if (options.quality !== void 0) nativeOptions.quality = options.quality;
191
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
192
+ return loadNativeBinding().ttfToWoff2(inputBuffer, nativeOptions);
193
+ }
194
+ async function ttfToWoff2Async(input, options = {}) {
195
+ if (options.fallback === "wasm") return ttfToWoff2WithWasm(input, options);
196
+ if (options.fallback === "js") throw unavailableWoff2Fallback("js");
197
+ try {
198
+ return ttfToWoff2(input, {
199
+ ...options,
200
+ fallback: "native"
201
+ });
202
+ } catch (error) {
203
+ if (options.fallback === "native" || !(error instanceof NativeBindingLoadError)) throw error;
204
+ return ttfToWoff2WithWasm(input, options);
205
+ }
206
+ }
207
+ async function ttfToWoff2WithWasm(input, options) {
208
+ const wasm = await loadWasmRuntime();
209
+ const wasmOptions = options.quality === void 0 ? {} : { quality: options.quality };
210
+ try {
211
+ const output = await wasm.ttfToWoff2(input, wasmOptions);
212
+ return Buffer.from(output);
213
+ } catch (error) {
214
+ throw new Error("WOFF2 WASM fallback failed", { cause: error });
215
+ }
216
+ }
217
+ function validateWoff2(input) {
218
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
219
+ loadNativeBinding().validateWoff2(inputBuffer);
220
+ }
221
+ function ttfToEot(input, options = {}) {
222
+ const nativeOptions = {};
223
+ if (options.version !== void 0) nativeOptions.version = options.version;
224
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
225
+ return loadNativeBinding().ttfToEot(inputBuffer, nativeOptions);
226
+ }
227
+ function ttfToSvg(input, options = {}) {
228
+ const nativeOptions = {};
229
+ if (options.fontFamily !== void 0) nativeOptions.fontFamily = options.fontFamily;
230
+ const inputBuffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
231
+ return loadNativeBinding().ttfToSvg(inputBuffer, nativeOptions);
232
+ }
233
+ function svgFontToTtf(input, options = {}) {
234
+ return loadNativeBinding().svgFontToTtf(input, toNativeSvg2TtfOptions(options));
235
+ }
236
+ function svgsToTtf(inputs, options = {}) {
237
+ return loadNativeBinding().svgsToTtf(inputs.map((input) => toNativeSvgIcon(input)), toNativeSvgs2TtfOptions(options));
238
+ }
239
+ function generateFontFaceCss(sources, options = {}) {
240
+ const nativeSources = sources.map((source) => toNativeCssFontSource(source));
241
+ const nativeOptions = {};
242
+ if (options.fontFamily !== void 0) nativeOptions.fontFamily = resolveCssFontFamily(sources, options.fontFamily);
243
+ if (options.fontPath !== void 0) nativeOptions.fontPath = options.fontPath;
244
+ if (options.base64 !== void 0) nativeOptions.base64 = options.base64;
245
+ if (options.glyph !== void 0) nativeOptions.glyph = options.glyph;
246
+ if (options.iconPrefix !== void 0) nativeOptions.iconPrefix = options.iconPrefix;
247
+ if (options.asFileName !== void 0) nativeOptions.asFileName = options.asFileName;
248
+ if (options.local !== void 0) nativeOptions.local = options.local;
249
+ if (options.fontDisplay !== void 0) nativeOptions.fontDisplay = options.fontDisplay;
250
+ if (options.target !== void 0) nativeOptions.target = options.target;
251
+ if (options.unicodeRanges !== void 0) nativeOptions.unicodeRanges = options.unicodeRanges;
252
+ return loadNativeBinding().generateFontFaceCss(nativeSources, nativeOptions);
253
+ }
254
+ function resolveCssFontFamily(sources, fontFamily) {
255
+ if (typeof fontFamily === "string") return fontFamily;
256
+ const source = sources.find((source) => source.contents !== void 0);
257
+ if (source?.contents === void 0) throw new Error("CSS fontFamily resolver requires source contents");
258
+ return fontFamily(inspect(source.contents));
259
+ }
260
+ function toNativeCssFontSource(source) {
261
+ const nativeSource = {
262
+ fileName: source.fileName,
263
+ format: source.format
264
+ };
265
+ if (source.contents !== void 0) nativeSource.contents = Buffer.isBuffer(source.contents) ? source.contents : Buffer.from(source.contents);
266
+ if (source.glyphs !== void 0) nativeSource.glyphs = source.glyphs;
267
+ if (source.unicodeRanges !== void 0) nativeSource.unicodeRanges = source.unicodeRanges;
268
+ return nativeSource;
269
+ }
270
+ //#endregion
271
+ //#region src/optimize-runtime.ts
272
+ function resolvePipelineRuntimeMode(configured, fallbacks) {
273
+ if (fallbacks.includes("js")) throw new Error("WOFF2 fallback `js` is not available in this build");
274
+ const legacy = [...new Set(fallbacks)];
275
+ if (legacy.length > 1) throw new Error(`conflicting WOFF2 fallback modes: ${legacy.join(", ")}`);
276
+ const fallback = legacy[0];
277
+ if (configured !== void 0 && fallback !== void 0 && configured !== fallback) throw new Error(`runtime \`${configured}\` conflicts with WOFF2 fallback \`${fallback}\``);
278
+ return configured ?? fallback ?? "native";
279
+ }
280
+ function createRuntimeSelector(requested, loaders = defaultRuntimeLoaders) {
281
+ let selected;
282
+ return {
283
+ requested,
284
+ resolve() {
285
+ selected ??= selectRuntime(requested, loaders);
286
+ return selected;
287
+ }
288
+ };
289
+ }
290
+ async function selectRuntime(requested, loaders) {
291
+ if (requested === "native") return loaders.loadNative();
292
+ if (requested === "wasm") return loaders.loadWasm();
293
+ try {
294
+ return loaders.loadNative();
295
+ } catch (error) {
296
+ if (!(error instanceof NativeBindingLoadError)) throw error;
297
+ return loaders.loadWasm();
298
+ }
299
+ }
300
+ const nativeRuntime = {
301
+ kind: "native",
302
+ async generateFontFaceCss(sources, options) {
303
+ return generateFontFaceCss(sources, options);
304
+ },
305
+ async inspect(input) {
306
+ return inspect(input);
307
+ },
308
+ async otfToTtf(input, options) {
309
+ return otfToTtf(input, options);
310
+ },
311
+ async subsetTtf(input, options) {
312
+ return subsetTtf(input, options);
313
+ },
314
+ async svgFontToTtf(input, options) {
315
+ return svgFontToTtf(input, options);
316
+ },
317
+ async svgsToTtf(inputs, options) {
318
+ return svgsToTtf(inputs, options);
319
+ },
320
+ async ttfToEot(input, options) {
321
+ return ttfToEot(input, options);
322
+ },
323
+ async ttfToSvg(input, options) {
324
+ return ttfToSvg(input, options);
325
+ },
326
+ async ttfToWoff(input, options) {
327
+ return ttfToWoff(input, options);
328
+ },
329
+ async ttfToWoff2(input, options) {
330
+ return ttfToWoff2(input, options);
331
+ }
332
+ };
333
+ async function createWasmRuntime(loadRuntime = loadWasmRuntime) {
334
+ const wasm = await loadRuntime();
335
+ return {
336
+ kind: "wasm",
337
+ async generateFontFaceCss(sources, options) {
338
+ const { ext: _ext, fileName: _fileName, fontFamily, ...wasmOptions } = options;
339
+ assertWasmOptionSupported("generateFontFaceCss", "fontFamily", typeof fontFamily === "function" ? fontFamily : void 0);
340
+ return runWasmOperation("generateFontFaceCss", () => wasm.generateFontFaceCss(sources, {
341
+ ...wasmOptions,
342
+ ...typeof fontFamily === "string" ? { fontFamily } : {}
343
+ }));
344
+ },
345
+ inspect: (input) => runWasmOperation("inspect", () => wasm.inspect(input)),
346
+ otfToTtf: (input, options) => runWasmOperation("otfToTtf", () => wasm.otfToTtf(input, options)),
347
+ async subsetTtf(input, options) {
348
+ const { clone: _clone, keepLayout, hinting, textFile, ...wasmOptions } = options;
349
+ assertWasmOptionSupported("subsetTtf", "textFile", textFile);
350
+ return runWasmOperation("subsetTtf", () => wasm.subsetTtf(input, {
351
+ ...wasmOptions,
352
+ ...keepLayout === void 0 ? {} : { layout: keepLayout },
353
+ ...options.preserveHinting === void 0 && hinting !== void 0 ? { preserveHinting: hinting } : {}
354
+ }));
355
+ },
356
+ svgFontToTtf: (input, options) => runWasmOperation("svgFontToTtf", () => wasm.svgFontToTtf(input, options)),
357
+ svgsToTtf: (inputs, options) => runWasmOperation("svgsToTtf", () => wasm.svgsToTtf(inputs, options)),
358
+ ttfToEot: (input, options) => runWasmOperation("ttfToEot", () => wasm.ttfToEot(input, options)),
359
+ ttfToSvg: (input, options) => runWasmOperation("ttfToSvg", () => wasm.ttfToSvg(input, options)),
360
+ ttfToWoff: (input, options) => runWasmOperation("ttfToWoff", () => wasm.ttfToWoff(input, options)),
361
+ ttfToWoff2(input, options) {
362
+ const { clone: _clone, fallback: _fallback, ...wasmOptions } = options;
363
+ return runWasmOperation("ttfToWoff2", () => wasm.ttfToWoff2(input, wasmOptions));
364
+ }
365
+ };
366
+ }
367
+ async function runWasmOperation(operation, run) {
368
+ try {
369
+ return await run();
370
+ } catch (error) {
371
+ if (error instanceof Error) throw error;
372
+ throw new Error(typeof error === "string" ? error : `fontmin-rs WASM runtime failed during ${operation}`, { cause: error });
373
+ }
374
+ }
375
+ function assertWasmOptionSupported(operation, name, value) {
376
+ if (value !== void 0) throw new Error(`fontmin-rs WASM ${operation} does not support option ${name}`);
377
+ }
378
+ const defaultRuntimeLoaders = {
379
+ loadNative() {
380
+ loadNativeBinding();
381
+ return nativeRuntime;
382
+ },
383
+ loadWasm: createWasmRuntime
384
+ };
385
+ //#endregion
386
+ //#region src/optimize.ts
387
+ const CACHE_SCHEMA_VERSION = "v1";
388
+ const FONTMIN_VERSION = "0.1.0-beta.1";
389
+ const DEFAULT_CACHE_DIR = "node_modules/.cache/fontmin-rs";
390
+ const DEFAULT_SVG_ICON_START_UNICODE = 57345;
391
+ const CSS_GLYPHS_META_KEY = "cssGlyphs";
392
+ const CSS_UNICODE_RANGES_META_KEY = "cssUnicodeRanges";
393
+ const MIME_TYPES_BY_FORMAT = {
394
+ eot: "application/vnd.ms-fontobject",
395
+ svg: "image/svg+xml",
396
+ ttf: "font/ttf",
397
+ woff: "font/woff",
398
+ woff2: "font/woff2"
399
+ };
400
+ async function optimize(config) {
401
+ const cwd = config.cwd === void 0 ? process.cwd() : config.cwd;
402
+ config = await resolveConfigTextFile(config, cwd);
403
+ const plugins = sortPlugins(await resolvePluginTextFiles(pluginsFromConfig(config), cwd));
404
+ const legacyFallbacks = woff2FallbacksFromPlugins(plugins);
405
+ const runtime = createRuntimeSelector(resolvePipelineRuntimeMode(config.runtime, legacyFallbacks));
406
+ const cacheOptions = normalizeCacheOptions(config.cache, cwd);
407
+ const emittedAssets = [];
408
+ const context = createPluginContext(cwd, emittedAssets);
409
+ for (const plugin of plugins) if (plugin.buildStart !== void 0) await plugin.buildStart(context);
410
+ let assets = await loadInputAssets(config.input ?? [], cwd);
411
+ const cacheRuntime = cacheOptions.enabled && isCacheablePipeline(plugins) ? await cacheRuntimeIdentity(config, plugins, runtime) : void 0;
412
+ const cacheKey = cacheRuntime === void 0 ? void 0 : cacheKeyForAssets(assets, config, plugins, cacheRuntime);
413
+ const cachedAssets = cacheKey === void 0 || cacheRuntime === void 0 ? void 0 : await readCachedAssets(cacheOptions.dir, cacheKey, cacheRuntime);
414
+ if (cachedAssets === void 0) {
415
+ const subset = config.subset;
416
+ if (subset !== void 0) assets = await flatMapAssets(assets, async (asset) => runGlyph(asset, subset, await runtime.resolve()));
417
+ for (const plugin of plugins) {
418
+ assets = await transformAssets(assets, plugin, context, runtime);
419
+ assets = [...assets, ...emittedAssets.splice(0)];
420
+ }
421
+ for (const plugin of plugins) if (isBuiltin(plugin, "css")) {
422
+ const cssAsset = await runCss(assets, plugin.native.options, await runtime.resolve());
423
+ if (cssAsset !== void 0) assets = assets.concat(cssAsset);
424
+ } else {
425
+ if (plugin.generateBundle !== void 0) await plugin.generateBundle(assets, context);
426
+ assets = [...assets, ...emittedAssets.splice(0)];
427
+ }
428
+ if (cacheKey !== void 0 && cacheRuntime !== void 0) await writeCachedAssets(cacheOptions.dir, cacheKey, cacheRuntime, assets);
429
+ } else assets = cachedAssets;
430
+ if (config.outDir !== void 0) {
431
+ const outDir = resolve(cwd, config.outDir);
432
+ if (config.clean === true) await rm(outDir, {
433
+ recursive: true,
434
+ force: true
435
+ });
436
+ await writeAssets(outDir, assets);
437
+ }
438
+ for (const plugin of plugins) if (plugin.buildEnd !== void 0) await plugin.buildEnd(context);
439
+ return assets;
440
+ }
441
+ function createPluginContext(cwd, emittedAssets) {
442
+ const diagnostics = [];
443
+ return {
444
+ cwd,
445
+ diagnostics,
446
+ emitFile(asset) {
447
+ emittedAssets.push(asset);
448
+ },
449
+ readFile(path) {
450
+ return readFile(resolve(cwd, path));
451
+ },
452
+ resolve(path) {
453
+ return resolve(cwd, path);
454
+ },
455
+ warn(message) {
456
+ diagnostics.push({
457
+ level: "warn",
458
+ message: warningMessage(message)
459
+ });
460
+ },
461
+ async writeFile(path, contents) {
462
+ const filePath = resolve(cwd, path);
463
+ await mkdir(dirname(filePath), { recursive: true });
464
+ await writeFile(filePath, contents);
465
+ }
466
+ };
467
+ }
468
+ function warningMessage(message) {
469
+ return typeof message === "string" ? message : message.message;
470
+ }
471
+ async function resolveConfigTextFile(config, cwd) {
472
+ if (config.subset === void 0) return config;
473
+ const subset = await resolveSubsetTextFile(config.subset, cwd);
474
+ if (subset === config.subset) return config;
475
+ return {
476
+ ...config,
477
+ subset
478
+ };
479
+ }
480
+ async function resolvePluginTextFiles(plugins, cwd) {
481
+ const resolvedPlugins = [];
482
+ for (const plugin of plugins) resolvedPlugins.push(await resolvePluginTextFile(plugin, cwd));
483
+ return resolvedPlugins;
484
+ }
485
+ async function resolvePluginTextFile(plugin, cwd) {
486
+ if (!isBuiltin(plugin, "glyph")) return plugin;
487
+ const options = await resolveSubsetTextFile(plugin.native.options, cwd);
488
+ if (options === plugin.native.options) return plugin;
489
+ return {
490
+ ...plugin,
491
+ native: {
492
+ ...plugin.native,
493
+ options
494
+ }
495
+ };
496
+ }
497
+ async function resolveSubsetTextFile(options, cwd) {
498
+ if (options.textFile === void 0) return options;
499
+ const fileText = await readFile(resolve(cwd, options.textFile), "utf8");
500
+ const { textFile: _textFile, ...resolvedOptions } = options;
501
+ return {
502
+ ...resolvedOptions,
503
+ text: mergeSubsetText(options.text, fileText)
504
+ };
505
+ }
506
+ function mergeSubsetText(text, fileText) {
507
+ return text === void 0 ? fileText : `${text}${fileText}`;
508
+ }
509
+ function pluginsFromConfig(config) {
510
+ const plugins = [...config.plugins ?? []];
511
+ if (config.outputs === void 0) return plugins;
512
+ const outputs = config.outputs.map(normalizeOutputConfig);
513
+ const requestedOutputs = outputs.map((output) => output.format);
514
+ const fontOutputs = requestedOutputs.filter((format) => format !== "css");
515
+ for (const output of outputs) {
516
+ const plugin = outputPluginFromConfig(output);
517
+ if (plugin !== void 0) plugins.push(plugin);
518
+ }
519
+ if (fontOutputs.length > 0) plugins.push(outputFilterPlugin(fontOutputs));
520
+ if (requestedOutputs.includes("css")) {
521
+ const cssOutput = outputs.find((output) => output.format === "css");
522
+ plugins.push(builtinPlugin("css", {
523
+ ...cssOptionsRecord(config.css),
524
+ ...outputPathOptionsRecord(cssOutput)
525
+ }), outputFilterPlugin(requestedOutputs, "post"));
526
+ }
527
+ return plugins;
528
+ }
529
+ function normalizeOutputConfig(output) {
530
+ if (typeof output === "string") return {
531
+ clone: true,
532
+ format: output
533
+ };
534
+ const config = {
535
+ clone: output.clone ?? true,
536
+ format: output.format
537
+ };
538
+ if (output.ext !== void 0) config.ext = output.ext;
539
+ if (output.fileName !== void 0) config.fileName = output.fileName;
540
+ return config;
541
+ }
542
+ function outputPluginFromConfig(output) {
543
+ if (output.format === "ttf" || output.format === "css") return;
544
+ const options = {
545
+ clone: output.clone ?? true,
546
+ ...outputPathOptionsRecord(output)
547
+ };
548
+ if (output.format === "eot") return builtinPlugin("ttf2eot", options);
549
+ if (output.format === "svg") return builtinPlugin("ttf2svg", options);
550
+ if (output.format === "woff") return builtinPlugin("ttf2woff", options);
551
+ return builtinPlugin("ttf2woff2", options);
552
+ }
553
+ function outputPathOptionsRecord(output) {
554
+ const record = {};
555
+ if (output?.ext !== void 0) record["ext"] = output.ext;
556
+ if (output?.fileName !== void 0) record["fileName"] = output.fileName;
557
+ return record;
558
+ }
559
+ function builtinPlugin(name, options) {
560
+ return {
561
+ name: `fontmin:${name}`,
562
+ native: {
563
+ kind: "builtin",
564
+ name,
565
+ options
566
+ }
567
+ };
568
+ }
569
+ function outputFilterPlugin(formats, enforce) {
570
+ const plugin = {
571
+ name: "fontmin:output-filter",
572
+ generateBundle(assets) {
573
+ const retainedAssets = assets.filter((asset) => {
574
+ const format = outputFormatFromAsset(asset);
575
+ return format !== void 0 && formats.includes(format);
576
+ });
577
+ assets.splice(0, assets.length, ...retainedAssets);
578
+ }
579
+ };
580
+ if (enforce !== void 0) plugin.enforce = enforce;
581
+ return plugin;
582
+ }
583
+ function outputFormatFromAsset(asset) {
584
+ if (asset.format === "unknown" || asset.format === "otf") return;
585
+ return asset.format;
586
+ }
587
+ function cssOptionsRecord(options) {
588
+ const record = {};
589
+ if (options?.base64 !== void 0) record["base64"] = options.base64;
590
+ if (options?.asFileName !== void 0) record["asFileName"] = options.asFileName;
591
+ if (options?.fontDisplay !== void 0) record["fontDisplay"] = options.fontDisplay;
592
+ if (options?.fontFamily !== void 0) record["fontFamily"] = options.fontFamily;
593
+ if (options?.fontPath !== void 0) record["fontPath"] = options.fontPath;
594
+ if (options?.glyph !== void 0) record["glyph"] = options.glyph;
595
+ if (options?.iconPrefix !== void 0) record["iconPrefix"] = options.iconPrefix;
596
+ if (options?.local !== void 0) record["local"] = options.local;
597
+ if (options?.target !== void 0) record["target"] = options.target;
598
+ if (options?.unicodeRanges !== void 0) record["unicodeRanges"] = options.unicodeRanges;
599
+ return record;
600
+ }
601
+ async function readCachedAssets(cacheDir, key, runtime) {
602
+ let manifest;
603
+ try {
604
+ manifest = JSON.parse(await readFile(cacheManifestPath(cacheDir, key), "utf8"));
605
+ } catch {
606
+ return;
607
+ }
608
+ if (manifest.version !== CACHE_SCHEMA_VERSION || manifest.key !== key || manifest.runtime?.requested !== runtime.requested || manifest.runtime.resolved !== runtime.resolved) return;
609
+ const entryDir = cacheEntryDir(cacheDir, key);
610
+ const assets = [];
611
+ try {
612
+ for (const record of manifest.assets) {
613
+ const contents = await readFile(join(entryDir, record.fileName));
614
+ assets.push({
615
+ path: record.path,
616
+ contents,
617
+ format: record.format,
618
+ sourceFormat: record.sourceFormat,
619
+ meta: {
620
+ ...record.meta,
621
+ cache: {
622
+ hit: true,
623
+ key
624
+ }
625
+ }
626
+ });
627
+ }
628
+ } catch {
629
+ return;
630
+ }
631
+ return assets;
632
+ }
633
+ async function writeCachedAssets(cacheDir, key, runtime, assets) {
634
+ const entryDir = cacheEntryDir(cacheDir, key);
635
+ const records = [];
636
+ await mkdir(entryDir, { recursive: true });
637
+ for (const [index, asset] of assets.entries()) {
638
+ const fileName = `${String(index).padStart(3, "0")}.${asset.format}`;
639
+ await writeFile(join(entryDir, fileName), asset.contents);
640
+ records.push({
641
+ fileName,
642
+ format: asset.format,
643
+ meta: asset.meta,
644
+ path: asset.path,
645
+ sourceFormat: asset.sourceFormat
646
+ });
647
+ }
648
+ await writeFile(cacheManifestPath(cacheDir, key), `${JSON.stringify({
649
+ assets: records,
650
+ key,
651
+ runtime,
652
+ version: CACHE_SCHEMA_VERSION
653
+ }, void 0, 2)}\n`);
654
+ await updateCacheIndex(cacheDir, key, records);
655
+ }
656
+ async function updateCacheIndex(cacheDir, key, assets) {
657
+ const indexPath = cacheIndexPath(cacheDir);
658
+ let index = {
659
+ entries: {},
660
+ version: CACHE_SCHEMA_VERSION
661
+ };
662
+ try {
663
+ index = JSON.parse(await readFile(indexPath, "utf8"));
664
+ } catch {}
665
+ if (index.version !== CACHE_SCHEMA_VERSION) index = {
666
+ entries: {},
667
+ version: CACHE_SCHEMA_VERSION
668
+ };
669
+ index.entries[key] = {
670
+ assets: assets.map((asset) => asset.path),
671
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
672
+ };
673
+ await mkdir(dirname(indexPath), { recursive: true });
674
+ await writeFile(indexPath, `${JSON.stringify(index, void 0, 2)}\n`);
675
+ }
676
+ async function loadInputAssets(inputs, cwd) {
677
+ if (inputs.length === 0) throw new Error("fontmin-rs optimize requires at least one input");
678
+ const assets = [];
679
+ for (const input of inputs) if (typeof input === "string") {
680
+ const inputPaths = await expandInputPath(input, cwd);
681
+ for (const inputPath of inputPaths) {
682
+ const contents = await readFile(inputPath);
683
+ const format = detectFormat(contents);
684
+ assets.push({
685
+ path: basename(inputPath),
686
+ contents,
687
+ format,
688
+ sourceFormat: format,
689
+ meta: { inputPath }
690
+ });
691
+ }
692
+ } else {
693
+ const contents = Buffer.from(input);
694
+ const format = detectFormat(contents);
695
+ assets.push({
696
+ path: `fontmin.${extensionForFormat(format)}`,
697
+ contents,
698
+ format,
699
+ sourceFormat: format,
700
+ meta: {}
701
+ });
702
+ }
703
+ return assets;
704
+ }
705
+ async function expandInputPath(input, cwd) {
706
+ if (!isGlobPattern(input)) return [resolve(cwd, input)];
707
+ const matches = await glob(input, {
708
+ absolute: true,
709
+ cwd,
710
+ onlyFiles: true
711
+ });
712
+ if (matches.length === 0) throw new Error(`fontmin-rs input glob matched no files: ${input}`);
713
+ return matches.sort((left, right) => left.localeCompare(right));
714
+ }
715
+ function isGlobPattern(path) {
716
+ return /[*?[\]{}]/.test(path);
717
+ }
718
+ async function transformAssets(assets, plugin, context, runtime) {
719
+ if (isBuiltin(plugin, "glyph")) return flatMapAssets(assets, async (asset) => runGlyph(asset, plugin.native.options, await runtime.resolve()));
720
+ if (isBuiltin(plugin, "unicodeSlices")) return flatMapAssets(assets, async (asset) => runUnicodeSlices(asset, plugin.native.options, await runtime.resolve()));
721
+ if (isBuiltin(plugin, "otf2ttf")) return flatMapAssets(assets, async (asset) => runOtf2Ttf(asset, plugin.native.options, await runtime.resolve()));
722
+ if (isBuiltin(plugin, "ttf2woff")) return flatMapAssets(assets, async (asset) => runTtf2Woff(asset, plugin.native.options, await runtime.resolve()));
723
+ if (isBuiltin(plugin, "ttf2woff2")) return flatMapAssets(assets, async (asset) => runTtf2Woff2(asset, plugin.native.options, await runtime.resolve()));
724
+ if (isBuiltin(plugin, "ttf2eot")) return flatMapAssets(assets, async (asset) => runTtf2Eot(asset, plugin.native.options, await runtime.resolve()));
725
+ if (isBuiltin(plugin, "ttf2svg")) return flatMapAssets(assets, async (asset) => runTtf2Svg(asset, plugin.native.options, await runtime.resolve()));
726
+ if (isBuiltin(plugin, "svg2ttf")) return flatMapAssets(assets, async (asset) => runSvg2Ttf(asset, plugin.native.options, await runtime.resolve()));
727
+ if (isBuiltin(plugin, "svgs2ttf")) return runSvgs2Ttf(assets, plugin.native.options, await runtime.resolve());
728
+ if (isBuiltin(plugin, "css")) return assets;
729
+ if (plugin.transform === void 0) return assets;
730
+ const transformedAssets = [];
731
+ for (const asset of assets) {
732
+ const result = await plugin.transform(asset, context);
733
+ if (result === void 0) transformedAssets.push(asset);
734
+ else if (Array.isArray(result)) transformedAssets.push(...result);
735
+ else if (result !== null) transformedAssets.push(result);
736
+ }
737
+ return transformedAssets;
738
+ }
739
+ async function flatMapAssets(assets, transform) {
740
+ const transformed = [];
741
+ for (const asset of assets) transformed.push(...await transform(asset));
742
+ return transformed;
743
+ }
744
+ async function runGlyph(asset, options, runtime) {
745
+ if (asset.format !== "ttf") return [asset];
746
+ const meta = withCssGlyphs(asset.meta, cssGlyphsFromSubsetOptions(options));
747
+ const subsetAsset = {
748
+ path: replaceExtension(asset.path, "ttf"),
749
+ contents: Buffer.from(await runtime.subsetTtf(asset.contents, runtimeSubsetOptions(options))),
750
+ format: "ttf",
751
+ sourceFormat: asset.sourceFormat,
752
+ meta
753
+ };
754
+ return options.clone === true ? [asset, subsetAsset] : [subsetAsset];
755
+ }
756
+ async function runUnicodeSlices(asset, options, runtime) {
757
+ if (asset.format !== "ttf") return [asset];
758
+ return Promise.all(deliverySlicesFromOptions(options).map(async (slice) => ({
759
+ path: appendAssetSuffix(asset.path, slice.name),
760
+ contents: Buffer.from(await runtime.subsetTtf(asset.contents, { unicodeRanges: slice.unicodeRanges })),
761
+ format: "ttf",
762
+ sourceFormat: asset.sourceFormat,
763
+ meta: {
764
+ ...asset.meta,
765
+ [CSS_UNICODE_RANGES_META_KEY]: slice.unicodeRanges
766
+ }
767
+ })));
768
+ }
769
+ function deliverySlicesFromOptions(options) {
770
+ const values = options["slices"];
771
+ if (!Array.isArray(values) || values.length === 0) throw new Error("unicode delivery slices must not be empty");
772
+ const names = /* @__PURE__ */ new Set();
773
+ return values.map((value, index) => {
774
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`unicode delivery slice ${index + 1} must be an object`);
775
+ const { name, unicodeRanges } = value;
776
+ if (typeof name !== "string" || name.length === 0 || !/^[A-Za-z0-9_-]+$/u.test(name)) throw new Error(`unicode delivery slice ${index + 1} must have a name containing only letters, digits, hyphens, or underscores`);
777
+ if (names.has(name)) throw new Error(`unicode delivery slice name is duplicated: ${name}`);
778
+ if (!Array.isArray(unicodeRanges) || unicodeRanges.length === 0 || unicodeRanges.some((range) => typeof range !== "string" || range.length === 0)) throw new Error(`unicode delivery slice ${name} must include at least one Unicode range`);
779
+ names.add(name);
780
+ return {
781
+ name,
782
+ unicodeRanges: [...unicodeRanges]
783
+ };
784
+ });
785
+ }
786
+ function runtimeSubsetOptions(options) {
787
+ const { clone: _clone, ...runtimeOptions } = options;
788
+ return Object.fromEntries(Object.entries(runtimeOptions).filter(([, value]) => value !== void 0));
789
+ }
790
+ async function runTtf2Woff(asset, options, runtime) {
791
+ if (asset.format !== "ttf") return [asset];
792
+ const woffAsset = {
793
+ path: outputPathForAsset(asset.path, "woff", options),
794
+ contents: Buffer.from(await runtime.ttfToWoff(asset.contents, woffOptions(options))),
795
+ format: "woff",
796
+ sourceFormat: asset.sourceFormat,
797
+ meta: convertedMeta(asset)
798
+ };
799
+ return options["clone"] === false ? [woffAsset] : [asset, woffAsset];
800
+ }
801
+ async function runTtf2Woff2(asset, options, runtime) {
802
+ if (asset.format !== "ttf") return [asset];
803
+ const woff2Asset = {
804
+ path: outputPathForAsset(asset.path, "woff2", options),
805
+ contents: Buffer.from(await runtime.ttfToWoff2(asset.contents, woff2Options(options))),
806
+ format: "woff2",
807
+ sourceFormat: asset.sourceFormat,
808
+ meta: convertedMeta(asset)
809
+ };
810
+ return options["clone"] === false ? [woff2Asset] : [asset, woff2Asset];
811
+ }
812
+ async function runTtf2Eot(asset, options, runtime) {
813
+ if (asset.format !== "ttf") return [asset];
814
+ const eotAsset = {
815
+ path: outputPathForAsset(asset.path, "eot", options),
816
+ contents: Buffer.from(await runtime.ttfToEot(asset.contents, eotOptions(options))),
817
+ format: "eot",
818
+ sourceFormat: asset.sourceFormat,
819
+ meta: convertedMeta(asset)
820
+ };
821
+ return options["clone"] === false ? [eotAsset] : [asset, eotAsset];
822
+ }
823
+ async function runTtf2Svg(asset, options, runtime) {
824
+ if (asset.format !== "ttf") return [asset];
825
+ const svgAsset = {
826
+ path: outputPathForAsset(asset.path, "svg", options),
827
+ contents: Buffer.from(await runtime.ttfToSvg(asset.contents, svgOptions(options))),
828
+ format: "svg",
829
+ sourceFormat: asset.sourceFormat,
830
+ meta: convertedMeta(asset)
831
+ };
832
+ return options["clone"] === false ? [svgAsset] : [asset, svgAsset];
833
+ }
834
+ async function runOtf2Ttf(asset, options, runtime) {
835
+ if (asset.format !== "otf") return [asset];
836
+ const ttfAsset = {
837
+ path: outputPathForAsset(asset.path, "ttf", options),
838
+ contents: Buffer.from(await runtime.otfToTtf(asset.contents, otf2TtfOptions(options))),
839
+ format: "ttf",
840
+ sourceFormat: asset.sourceFormat,
841
+ meta: convertedMeta(asset)
842
+ };
843
+ return options["clone"] === false ? [ttfAsset] : [asset, ttfAsset];
844
+ }
845
+ async function runSvg2Ttf(asset, options, runtime) {
846
+ if (asset.format !== "svg") return [asset];
847
+ const ttfAsset = {
848
+ path: outputPathForAsset(asset.path, "ttf", options),
849
+ contents: Buffer.from(await runtime.svgFontToTtf(Buffer.from(asset.contents).toString("utf8"), svg2TtfOptions(options))),
850
+ format: "ttf",
851
+ sourceFormat: asset.sourceFormat,
852
+ meta: convertedMeta(asset)
853
+ };
854
+ return options["clone"] === false ? [ttfAsset] : [asset, ttfAsset];
855
+ }
856
+ async function runSvgs2Ttf(assets, options, runtime) {
857
+ const svgAssets = assets.filter((asset) => asset.format === "svg");
858
+ if (svgAssets.length === 0) return assets;
859
+ const nonSvgAssets = assets.filter((asset) => asset.format !== "svg");
860
+ const firstSvg = svgAssets[0];
861
+ if (firstSvg === void 0) return assets;
862
+ const fontName = typeof options["fontName"] === "string" ? options["fontName"] : basenameWithoutExtension(firstSvg.path);
863
+ const icons = svgAssets.map((asset, index) => svgIconFromAsset(asset, index));
864
+ const cssGlyphs = cssGlyphsFromSvgIcons(icons, typeof options["startUnicode"] === "number" ? options["startUnicode"] : DEFAULT_SVG_ICON_START_UNICODE);
865
+ const ttfAsset = {
866
+ path: `${fontName}.ttf`,
867
+ contents: Buffer.from(await runtime.svgsToTtf(icons, svgs2TtfOptions(options))),
868
+ format: "ttf",
869
+ sourceFormat: firstSvg.sourceFormat,
870
+ meta: {
871
+ [CSS_GLYPHS_META_KEY]: cssGlyphs,
872
+ sourcePaths: svgAssets.map((asset) => asset.path)
873
+ }
874
+ };
875
+ return options["clone"] === true ? [...assets, ttfAsset] : [...nonSvgAssets, ttfAsset];
876
+ }
877
+ function convertedMeta(asset) {
878
+ return {
879
+ ...asset.meta,
880
+ sourcePath: asset.path
881
+ };
882
+ }
883
+ async function runCss(assets, options, runtime) {
884
+ const sourceAssets = assets.filter((asset) => isCssSourceFormat(asset.format));
885
+ const sources = sourceAssets.flatMap((asset) => cssSourceFromAsset(asset, options.base64 === true));
886
+ const firstAsset = sourceAssets[0];
887
+ if (sources.length === 0 || firstAsset === void 0) return;
888
+ const css = await runtime.generateFontFaceCss(sources, await cssOptionsForSources(options, firstAsset, runtime));
889
+ return {
890
+ path: outputPathForAsset(firstAsset.path, cssTargetExtension(options.target), options),
891
+ contents: Buffer.from(css),
892
+ format: "css",
893
+ sourceFormat: firstAsset.sourceFormat,
894
+ meta: {}
895
+ };
896
+ }
897
+ function cssSourceFromAsset(asset, inline) {
898
+ if (!isCssSourceFormat(asset.format)) return [];
899
+ const source = {
900
+ fileName: inline ? dataUrlForAsset(asset) : asset.path,
901
+ format: asset.format
902
+ };
903
+ const glyphs = cssGlyphsFromAsset(asset);
904
+ if (glyphs.length > 0) source.glyphs = glyphs;
905
+ const unicodeRanges = asset.meta[CSS_UNICODE_RANGES_META_KEY];
906
+ if (Array.isArray(unicodeRanges) && unicodeRanges.every((range) => typeof range === "string")) source.unicodeRanges = unicodeRanges;
907
+ return [source];
908
+ }
909
+ async function cssOptionsForSources(options, source, runtime) {
910
+ const resolvedOptions = await cssOptionsWithResolvedFontFamily(options, source, runtime);
911
+ if (resolvedOptions.base64 !== true) return resolvedOptions;
912
+ const inlineOptions = { fontPath: "" };
913
+ if (resolvedOptions.fontFamily !== void 0) inlineOptions.fontFamily = resolvedOptions.fontFamily;
914
+ if (resolvedOptions.glyph !== void 0) inlineOptions.glyph = resolvedOptions.glyph;
915
+ if (resolvedOptions.iconPrefix !== void 0) inlineOptions.iconPrefix = resolvedOptions.iconPrefix;
916
+ if (resolvedOptions.asFileName !== void 0) inlineOptions.asFileName = resolvedOptions.asFileName;
917
+ if (resolvedOptions.local !== void 0) inlineOptions.local = resolvedOptions.local;
918
+ if (resolvedOptions.fontDisplay !== void 0) inlineOptions.fontDisplay = resolvedOptions.fontDisplay;
919
+ return inlineOptions;
920
+ }
921
+ async function cssOptionsWithResolvedFontFamily(options, source, runtime) {
922
+ if (typeof options.fontFamily !== "function") return options;
923
+ return {
924
+ ...options,
925
+ fontFamily: options.fontFamily(await runtime.inspect(source.contents))
926
+ };
927
+ }
928
+ function cssTargetExtension(target) {
929
+ return target ?? "css";
930
+ }
931
+ function outputPathForAsset(path, defaultExtension, options) {
932
+ if (options.fileName !== void 0) return options.fileName;
933
+ return replaceExtension(path, options.ext ?? defaultExtension);
934
+ }
935
+ function cssGlyphsFromSubsetOptions(options) {
936
+ const seen = /* @__PURE__ */ new Set();
937
+ const glyphs = [];
938
+ for (const character of options.text ?? "") {
939
+ const unicode = character.codePointAt(0);
940
+ if (unicode !== void 0 && !seen.has(unicode)) {
941
+ seen.add(unicode);
942
+ glyphs.push({ unicode });
943
+ }
944
+ }
945
+ for (const unicode of options.unicodes ?? []) if (!seen.has(unicode)) {
946
+ seen.add(unicode);
947
+ glyphs.push({ unicode });
948
+ }
949
+ return glyphs;
950
+ }
951
+ function cssGlyphsFromSvgIcons(icons, startUnicode) {
952
+ let nextUnicode = startUnicode;
953
+ const seen = /* @__PURE__ */ new Set();
954
+ const glyphs = [];
955
+ for (const icon of icons) {
956
+ let unicode = icon.unicode;
957
+ if (unicode === void 0) {
958
+ while (seen.has(nextUnicode)) nextUnicode += 1;
959
+ unicode = nextUnicode;
960
+ nextUnicode += 1;
961
+ }
962
+ seen.add(unicode);
963
+ glyphs.push({
964
+ name: icon.name,
965
+ unicode
966
+ });
967
+ }
968
+ return glyphs;
969
+ }
970
+ function withCssGlyphs(meta, glyphs) {
971
+ if (glyphs.length === 0) return meta;
972
+ return {
973
+ ...meta,
974
+ [CSS_GLYPHS_META_KEY]: glyphs
975
+ };
976
+ }
977
+ function cssGlyphsFromAsset(asset) {
978
+ const glyphs = asset.meta[CSS_GLYPHS_META_KEY];
979
+ if (!Array.isArray(glyphs)) return [];
980
+ return glyphs.flatMap((glyph) => {
981
+ if (typeof glyph !== "object" || glyph === null || !("unicode" in glyph) || typeof glyph.unicode !== "number") return [];
982
+ const cssGlyph = { unicode: glyph.unicode };
983
+ if ("name" in glyph && typeof glyph.name === "string") cssGlyph.name = glyph.name;
984
+ return [cssGlyph];
985
+ });
986
+ }
987
+ function dataUrlForAsset(asset) {
988
+ if (!isCssSourceFormat(asset.format)) throw new Error(`cannot inline ${asset.format} asset in CSS`);
989
+ const encoded = Buffer.from(asset.contents).toString("base64");
990
+ return `data:${mimeTypeForFormat(asset.format)};base64,${encoded}`;
991
+ }
992
+ function svgIconFromAsset(asset, index) {
993
+ const icon = {
994
+ contents: Buffer.from(asset.contents).toString("utf8"),
995
+ name: basenameWithoutExtension(asset.path) || `glyph-${index + 1}`
996
+ };
997
+ const { unicode } = asset.meta;
998
+ if (typeof unicode === "number") icon.unicode = unicode;
999
+ return icon;
1000
+ }
1001
+ function mimeTypeForFormat(format) {
1002
+ return MIME_TYPES_BY_FORMAT[format];
1003
+ }
1004
+ function detectFormat(input) {
1005
+ const bytes = Buffer.from(input);
1006
+ if (bytes.subarray(0, 4).equals(Buffer.from([
1007
+ 0,
1008
+ 1,
1009
+ 0,
1010
+ 0
1011
+ ]))) return "ttf";
1012
+ if (bytes.subarray(0, 4).toString("ascii") === "true") return "ttf";
1013
+ if (bytes.subarray(0, 4).toString("ascii") === "OTTO") return "otf";
1014
+ if (bytes.subarray(0, 4).toString("ascii") === "wOFF") return "woff";
1015
+ if (bytes.subarray(0, 4).toString("ascii") === "wOF2") return "woff2";
1016
+ if (looksLikeEot(bytes)) return "eot";
1017
+ if (looksLikeSvg(bytes)) return "svg";
1018
+ try {
1019
+ return inspect(bytes).format;
1020
+ } catch {
1021
+ return "unknown";
1022
+ }
1023
+ }
1024
+ function extensionForFormat(format) {
1025
+ return format === "unknown" ? "bin" : format;
1026
+ }
1027
+ function looksLikeEot(bytes) {
1028
+ if (bytes.byteLength < 12) return false;
1029
+ const version = bytes.subarray(8, 12);
1030
+ return version.equals(Buffer.from([
1031
+ 1,
1032
+ 0,
1033
+ 2,
1034
+ 0
1035
+ ])) || version.equals(Buffer.from([
1036
+ 2,
1037
+ 0,
1038
+ 2,
1039
+ 0
1040
+ ]));
1041
+ }
1042
+ function looksLikeSvg(bytes) {
1043
+ const prefix = bytes.subarray(0, 512).toString("utf8").trimStart();
1044
+ return prefix.startsWith("<svg") || prefix.startsWith("<?xml") && prefix.includes("<svg");
1045
+ }
1046
+ function isBuiltin(plugin, name) {
1047
+ return plugin.native !== void 0 && plugin.native.kind === "builtin" && plugin.native.name === name;
1048
+ }
1049
+ function isCssSourceFormat(format) {
1050
+ return format === "ttf" || format === "woff" || format === "woff2" || format === "eot" || format === "svg";
1051
+ }
1052
+ function replaceExtension(path, extension) {
1053
+ const currentExtension = extname(path);
1054
+ if (currentExtension === "") return `${path}.${extension}`;
1055
+ return `${path.slice(0, -currentExtension.length)}.${extension}`;
1056
+ }
1057
+ function appendAssetSuffix(path, suffix) {
1058
+ const currentExtension = extname(path);
1059
+ return currentExtension === "" ? `${path}-${suffix}` : `${path.slice(0, -currentExtension.length)}-${suffix}${currentExtension}`;
1060
+ }
1061
+ function basenameWithoutExtension(path) {
1062
+ const currentExtension = extname(path);
1063
+ return currentExtension === "" ? basename(path) : basename(path, currentExtension);
1064
+ }
1065
+ function sortPlugins(plugins) {
1066
+ const pre = [];
1067
+ const normal = [];
1068
+ const post = [];
1069
+ for (const plugin of plugins) if (plugin.enforce === "pre") pre.push(plugin);
1070
+ else if (plugin.enforce === "post") post.push(plugin);
1071
+ else normal.push(plugin);
1072
+ return [
1073
+ ...pre,
1074
+ ...normal,
1075
+ ...post
1076
+ ];
1077
+ }
1078
+ function woff2FallbacksFromPlugins(plugins) {
1079
+ return plugins.flatMap((plugin) => {
1080
+ if (!isBuiltin(plugin, "ttf2woff2")) return [];
1081
+ const fallback = plugin.native.options["fallback"];
1082
+ return isWoff2Fallback(fallback) ? [fallback] : [];
1083
+ });
1084
+ }
1085
+ function woffOptions(options) {
1086
+ const nativeOptions = {};
1087
+ if (typeof options["deflate"] === "boolean") nativeOptions.deflate = options["deflate"];
1088
+ if (typeof options["compressionLevel"] === "number") nativeOptions.compressionLevel = options["compressionLevel"];
1089
+ return nativeOptions;
1090
+ }
1091
+ function woff2Options(options) {
1092
+ const nativeOptions = {};
1093
+ if (typeof options["quality"] === "number") nativeOptions.quality = options["quality"];
1094
+ return nativeOptions;
1095
+ }
1096
+ function isWoff2Fallback(value) {
1097
+ return value === "native" || value === "wasm" || value === "js" || value === "auto";
1098
+ }
1099
+ function eotOptions(options) {
1100
+ const nativeOptions = {};
1101
+ if (typeof options["version"] === "number") nativeOptions.version = options["version"];
1102
+ return nativeOptions;
1103
+ }
1104
+ function otf2TtfOptions(options) {
1105
+ const nativeOptions = {};
1106
+ if (typeof options["preserveHinting"] === "boolean") nativeOptions.preserveHinting = options["preserveHinting"];
1107
+ const variationCoordinates = options["variationCoordinates"];
1108
+ if (variationCoordinates !== null && typeof variationCoordinates === "object" && !Array.isArray(variationCoordinates)) nativeOptions.variationCoordinates = Object.fromEntries(Object.entries(variationCoordinates).filter(([, value]) => typeof value === "number" && Number.isFinite(value)));
1109
+ return nativeOptions;
1110
+ }
1111
+ function svgOptions(options) {
1112
+ const nativeOptions = {};
1113
+ if (typeof options["fontFamily"] === "string") nativeOptions.fontFamily = options["fontFamily"];
1114
+ return nativeOptions;
1115
+ }
1116
+ function svg2TtfOptions(options) {
1117
+ const nativeOptions = {};
1118
+ if (typeof options["hinting"] === "boolean") nativeOptions.hinting = options["hinting"];
1119
+ if (typeof options["normalize"] === "boolean") nativeOptions.normalize = options["normalize"];
1120
+ return nativeOptions;
1121
+ }
1122
+ function svgs2TtfOptions(options) {
1123
+ const nativeOptions = {};
1124
+ if (typeof options["fontName"] === "string") nativeOptions.fontName = options["fontName"];
1125
+ if (typeof options["startUnicode"] === "number") nativeOptions.startUnicode = options["startUnicode"];
1126
+ if (typeof options["ascent"] === "number") nativeOptions.ascent = options["ascent"];
1127
+ if (typeof options["descent"] === "number") nativeOptions.descent = options["descent"];
1128
+ if (typeof options["normalize"] === "boolean") nativeOptions.normalize = options["normalize"];
1129
+ return nativeOptions;
1130
+ }
1131
+ function cacheEntryDir(cacheDir, key) {
1132
+ return join(cacheRoot(cacheDir), key.slice(0, 2), key.slice(2, 4), key);
1133
+ }
1134
+ function cacheIndexPath(cacheDir) {
1135
+ return join(cacheRoot(cacheDir), "index.json");
1136
+ }
1137
+ function cacheManifestPath(cacheDir, key) {
1138
+ return join(cacheEntryDir(cacheDir, key), "index.json");
1139
+ }
1140
+ function cacheRoot(cacheDir) {
1141
+ return join(cacheDir, CACHE_SCHEMA_VERSION);
1142
+ }
1143
+ function cacheKeyForAssets(assets, config, plugins, runtime) {
1144
+ return sha256(stableStringify({
1145
+ clean: config.clean,
1146
+ fontminVersion: FONTMIN_VERSION,
1147
+ inputs: assets.map((asset) => ({
1148
+ format: asset.format,
1149
+ hash: sha256(asset.contents),
1150
+ path: asset.path,
1151
+ sourceFormat: asset.sourceFormat
1152
+ })),
1153
+ plugins: plugins.map((plugin) => ({
1154
+ enforce: plugin.enforce,
1155
+ name: plugin.name,
1156
+ native: plugin.native
1157
+ })),
1158
+ preserveOriginal: config.preserveOriginal,
1159
+ runtime,
1160
+ schema: CACHE_SCHEMA_VERSION,
1161
+ subset: config.subset
1162
+ }));
1163
+ }
1164
+ async function cacheRuntimeIdentity(config, plugins, runtime) {
1165
+ const resolved = config.subset !== void 0 || plugins.some((plugin) => plugin.native?.kind === "builtin") ? await runtime.resolve() : void 0;
1166
+ return {
1167
+ requested: runtime.requested,
1168
+ resolved: resolved?.kind ?? null
1169
+ };
1170
+ }
1171
+ function isCacheablePipeline(plugins) {
1172
+ return plugins.every((plugin) => {
1173
+ return plugin.native?.kind === "builtin" && plugin.buildStart === void 0 && plugin.transform === void 0 && plugin.generateBundle === void 0 && plugin.buildEnd === void 0;
1174
+ });
1175
+ }
1176
+ function normalizeCacheOptions(options, cwd) {
1177
+ if (options === void 0 || options === false) return {
1178
+ dir: resolve(cwd, DEFAULT_CACHE_DIR),
1179
+ enabled: false
1180
+ };
1181
+ if (options === true) return {
1182
+ dir: resolve(cwd, DEFAULT_CACHE_DIR),
1183
+ enabled: true
1184
+ };
1185
+ return {
1186
+ dir: resolve(cwd, options.dir ?? DEFAULT_CACHE_DIR),
1187
+ enabled: options.enabled ?? true
1188
+ };
1189
+ }
1190
+ function sha256(input) {
1191
+ return createHash("sha256").update(input).digest("hex");
1192
+ }
1193
+ function stableStringify(value) {
1194
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
1195
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
1196
+ return `{${Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entryValue]) => {
1197
+ return `${JSON.stringify(key)}:${stableStringify(entryValue)}`;
1198
+ }).join(",")}}`;
1199
+ }
1200
+ async function writeAssets(outDir, assets) {
1201
+ for (const asset of assets) {
1202
+ const outputPath = join(outDir, asset.path);
1203
+ await mkdir(dirname(outputPath), { recursive: true });
1204
+ await writeFile(outputPath, asset.contents);
1205
+ }
1206
+ }
1207
+ //#endregion
1208
+ //#region src/compat.ts
1209
+ var FontminCompat = class {
1210
+ static glyph = glyph;
1211
+ static deliverySlices = deliverySlices;
1212
+ static ttf2eot = ttf2eot;
1213
+ static ttf2svg = ttf2svg;
1214
+ static ttf2woff = ttf2woff;
1215
+ static ttf2woff2 = ttf2woff2;
1216
+ static otf2ttf = otf2ttf;
1217
+ static svg2ttf = svg2ttf;
1218
+ static svgs2ttf = svgs2ttf;
1219
+ static css = css;
1220
+ input = [];
1221
+ outputDir;
1222
+ plugins = [];
1223
+ src(file) {
1224
+ this.input = Array.isArray(file) ? file : [file];
1225
+ return this;
1226
+ }
1227
+ dest(dir) {
1228
+ this.outputDir = dir;
1229
+ return this;
1230
+ }
1231
+ use(plugin) {
1232
+ this.plugins.push(plugin);
1233
+ return this;
1234
+ }
1235
+ config() {
1236
+ const config = {
1237
+ input: this.input,
1238
+ plugins: this.plugins
1239
+ };
1240
+ if (this.outputDir !== void 0) config.outDir = this.outputDir;
1241
+ return defineConfig(config);
1242
+ }
1243
+ runAsync() {
1244
+ return optimize(this.config());
1245
+ }
1246
+ run(callback) {
1247
+ const handleSuccess = (files) => {
1248
+ callback(null, files);
1249
+ };
1250
+ const handleError = (error) => {
1251
+ callback(error);
1252
+ };
1253
+ this.runAsync().then(handleSuccess).catch(handleError);
1254
+ }
1255
+ };
1256
+ //#endregion
1257
+ export { woffToTtf as _, inspect as a, svgFontToTtf as c, ttfToSvg as d, ttfToWoff as f, woff2ToTtf as g, validateWoff2 as h, generateFontFaceCss as i, svgsToTtf as l, ttfToWoff2Async as m, optimize as n, otfToTtf as o, ttfToWoff2 as p, eotToTtf as r, subsetTtf as s, FontminCompat as t, ttfToEot as u, defineConfig as v, loadConfig as y };