@fontmin-rs/wasm 1.0.0 → 1.0.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.
Binary file
package/dist/index.d.mts CHANGED
@@ -5,7 +5,11 @@ type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Modul
5
5
  declare function isWasmInitialized(): boolean;
6
6
  declare function initWasm(input?: InitInput): Promise<void>;
7
7
  //#endregion
8
- //#region src/diagnostics.d.ts
8
+ //#region ../../packages/fontmin/src/runtime-neutral/diagnostics.d.ts
9
+ /**
10
+ * Normalizes Rust bridge diagnostics without depending on a concrete runtime.
11
+ * Native and WASM adapters use this module to expose the same error contract.
12
+ */
9
13
  type FontminDiagnosticCode = 'fontmin::config' | 'fontmin::convert_failed' | 'fontmin::invalid_font' | 'fontmin::io' | 'fontmin::missing_glyph' | 'fontmin::napi_bridge_failed' | 'fontmin::plugin_failed' | 'fontmin::unsupported_format';
10
14
  declare class FontminDiagnosticError extends Error {
11
15
  readonly name = "FontminDiagnosticError";
package/dist/index.mjs CHANGED
@@ -14,8 +14,13 @@ async function getWasmModule() {
14
14
  return module;
15
15
  }
16
16
  async function initWasm(input) {
17
- initialization ??= initializeWasm(input);
18
- await initialization;
17
+ const attempt = initialization ??= initializeWasm(input);
18
+ try {
19
+ await attempt;
20
+ } catch (error) {
21
+ if (initialization === attempt) initialization = void 0;
22
+ throw error;
23
+ }
19
24
  }
20
25
  async function initializeWasm(input) {
21
26
  const module = await import("./fontmin_wasm_core-C2e3XISU.mjs");
@@ -25,7 +30,7 @@ async function initializeWasm(input) {
25
30
  return module;
26
31
  }
27
32
  //#endregion
28
- //#region src/diagnostics.ts
33
+ //#region ../../packages/fontmin/src/runtime-neutral/diagnostics.ts
29
34
  const bridgeDiagnosticPattern = /^\[(?<code>fontmin::[a-z_]+)\] (?<message>[\s\S]+)$/u;
30
35
  var FontminDiagnosticError = class extends Error {
31
36
  name = "FontminDiagnosticError";
@@ -35,27 +40,129 @@ var FontminDiagnosticError = class extends Error {
35
40
  this.code = code;
36
41
  }
37
42
  };
38
- function normalizeFontminDiagnostic(error) {
39
- let message;
40
- if (error instanceof Error) message = error.message;
41
- else if (typeof error === "string") message = error;
42
- const match = message?.match(bridgeDiagnosticPattern);
43
- if (match === null || match === void 0) {
44
- if (error instanceof Error) return error;
45
- if (typeof error === "string") return new Error(error, { cause: error });
46
- return new Error("fontmin-rs WASM operation failed", { cause: error });
43
+ function createFontminDiagnostics(operationName) {
44
+ function normalizeFontminDiagnostic(error) {
45
+ let message;
46
+ if (error instanceof Error) message = error.message;
47
+ else if (typeof error === "string") message = error;
48
+ const match = message?.match(bridgeDiagnosticPattern);
49
+ if (match === null || match === void 0) {
50
+ if (error instanceof Error) return error;
51
+ if (typeof error === "string") return new Error(error, { cause: error });
52
+ return new Error(`${operationName} failed`, { cause: error });
53
+ }
54
+ const code = match.groups?.["code"];
55
+ const diagnosticMessage = match.groups?.["message"];
56
+ if (code === void 0 || diagnosticMessage === void 0) return new Error(`${operationName} returned an invalid diagnostic`, { cause: error });
57
+ return new FontminDiagnosticError(code, diagnosticMessage, { cause: error });
47
58
  }
48
- const code = match.groups?.["code"];
49
- const diagnosticMessage = match.groups?.["message"];
50
- if (code === void 0 || diagnosticMessage === void 0) return new Error("fontmin-rs WASM operation returned an invalid diagnostic", { cause: error });
51
- return new FontminDiagnosticError(code, diagnosticMessage, { cause: error });
59
+ function withFontminDiagnostics(operation) {
60
+ try {
61
+ return operation();
62
+ } catch (error) {
63
+ throw normalizeFontminDiagnostic(error);
64
+ }
65
+ }
66
+ return {
67
+ normalizeFontminDiagnostic,
68
+ withFontminDiagnostics
69
+ };
52
70
  }
53
- function withFontminDiagnostics(operation) {
54
- try {
55
- return operation();
56
- } catch (error) {
57
- throw normalizeFontminDiagnostic(error);
71
+ //#endregion
72
+ //#region src/diagnostics.ts
73
+ const { normalizeFontminDiagnostic, withFontminDiagnostics } = createFontminDiagnostics("fontmin-rs WASM operation");
74
+ //#endregion
75
+ //#region ../../packages/fontmin/src/runtime-neutral/optimize-policy.ts
76
+ const FONT_CONVERSIONS = [
77
+ {
78
+ inputFormat: "otf",
79
+ name: "otf2ttf",
80
+ outputFormat: "ttf"
81
+ },
82
+ {
83
+ inputFormat: "svg",
84
+ name: "svg2ttf",
85
+ outputFormat: "ttf"
86
+ },
87
+ {
88
+ inputFormat: "ttf",
89
+ name: "ttf2eot",
90
+ outputFormat: "eot"
91
+ },
92
+ {
93
+ inputFormat: "ttf",
94
+ name: "ttf2svg",
95
+ outputFormat: "svg"
96
+ },
97
+ {
98
+ inputFormat: "ttf",
99
+ name: "ttf2woff",
100
+ outputFormat: "woff"
101
+ },
102
+ {
103
+ inputFormat: "ttf",
104
+ name: "ttf2woff2",
105
+ outputFormat: "woff2"
58
106
  }
107
+ ];
108
+ async function applyAssetTransform(assets, transform, context, normalize) {
109
+ const transformedAssets = [];
110
+ for (const asset of assets) {
111
+ const result = await transform(asset, context);
112
+ if (result === void 0) transformedAssets.push(asset);
113
+ else if (Array.isArray(result)) transformedAssets.push(...result.map((asset) => normalize(asset)));
114
+ else if (result !== null) transformedAssets.push(normalize(result));
115
+ }
116
+ return transformedAssets;
117
+ }
118
+ async function applyAssetConversion(assets, clone, convert) {
119
+ const primaryAssets = [];
120
+ const clonedAssets = [];
121
+ for (const asset of assets) {
122
+ const convertedAsset = await convert(asset);
123
+ if (convertedAsset === void 0) primaryAssets.push(asset);
124
+ else if (clone) {
125
+ primaryAssets.push(asset);
126
+ clonedAssets.push(convertedAsset);
127
+ } else primaryAssets.push(convertedAsset);
128
+ }
129
+ return clone ? [...primaryAssets, ...clonedAssets] : primaryAssets;
130
+ }
131
+ async function applyFontConversion(assets, pluginName, clone, formatOf, convert) {
132
+ const conversion = FONT_CONVERSIONS.find((candidate) => candidate.name === pluginName);
133
+ if (conversion === void 0) return;
134
+ return applyAssetConversion(assets, clone, (asset) => formatOf(asset) === conversion.inputFormat ? convert(asset, conversion) : void 0);
135
+ }
136
+ async function flatMapAssets(assets, transform) {
137
+ const transformedAssets = [];
138
+ for (const asset of assets) transformedAssets.push(...await transform(asset));
139
+ return transformedAssets;
140
+ }
141
+ function missingGlyphWarning(report) {
142
+ if (report.missing.length === 0) return;
143
+ const visible = report.missing.slice(0, 16).map((codePoint) => `U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}`).join(", ");
144
+ const remaining = report.missing.length - 16;
145
+ return `missing glyphs for requested Unicode code points: ${visible}${remaining > 0 ? `, and ${remaining} more` : ""}`;
146
+ }
147
+ function normalizeDeliverySlices$1(values, options = {}) {
148
+ if (!Array.isArray(values)) throw new TypeError("unicode delivery slices must be an array");
149
+ if (values.length === 0) {
150
+ if (options.allowEmpty === true) return [];
151
+ throw new Error("unicode delivery slices must not be empty");
152
+ }
153
+ const names = /* @__PURE__ */ new Set();
154
+ return values.map((value, index) => {
155
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`unicode delivery slice ${index + 1} must be an object`);
156
+ const { name, unicodeRanges } = value;
157
+ 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`);
158
+ if (names.has(name)) throw new Error(`unicode delivery slice name is duplicated: ${name}`);
159
+ 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`);
160
+ names.add(name);
161
+ return {
162
+ name,
163
+ unicodeRanges: [...unicodeRanges]
164
+ };
165
+ });
59
166
  }
60
167
  //#endregion
61
168
  //#region src/native.ts
@@ -130,12 +237,6 @@ function coverageOptions(options) {
130
237
  if (options.unicodes !== void 0) coverage.unicodes = options.unicodes;
131
238
  return coverage;
132
239
  }
133
- function missingGlyphWarning(report) {
134
- if (report.missing.length === 0) return;
135
- const visible = report.missing.slice(0, 16).map((codepoint) => `U+${codepoint.toString(16).toUpperCase().padStart(4, "0")}`).join(", ");
136
- const remaining = report.missing.length - 16;
137
- return `missing glyphs for requested Unicode code points: ${visible}${remaining > 0 ? `, and ${remaining} more` : ""}`;
138
- }
139
240
  //#endregion
140
241
  //#region src/plugins.ts
141
242
  function plugin(name, options) {
@@ -163,21 +264,7 @@ function deliverySlices(slices) {
163
264
  })) });
164
265
  }
165
266
  function normalizeDeliverySlices(options) {
166
- const values = options.slices;
167
- if (!Array.isArray(values) || values.length === 0) throw new Error("unicode delivery slices must not be empty");
168
- const names = /* @__PURE__ */ new Set();
169
- return values.map((value, index) => {
170
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`unicode delivery slice ${index + 1} must be an object`);
171
- const { name, unicodeRanges } = value;
172
- 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`);
173
- if (names.has(name)) throw new Error(`unicode delivery slice name is duplicated: ${name}`);
174
- 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`);
175
- names.add(name);
176
- return {
177
- name,
178
- unicodeRanges: [...unicodeRanges]
179
- };
180
- });
267
+ return normalizeDeliverySlices$1(options.slices);
181
268
  }
182
269
  function ttf2woff(options = {}) {
183
270
  return plugin("ttf2woff", options);
@@ -256,30 +343,21 @@ async function optimizeBrowser(config) {
256
343
  for (const plugin of config.plugins ?? []) {
257
344
  if (plugin.name === "glyph") {
258
345
  const options = optionsOf(plugin);
259
- const subsetAssets = [];
260
- for (const asset of assets) {
261
- if (asset.format !== "ttf") {
262
- subsetAssets.push(asset);
263
- continue;
264
- }
346
+ assets = await flatMapAssets(assets, async (asset) => {
347
+ if (asset.format !== "ttf") return [asset];
265
348
  const subsetAsset = {
266
349
  ...asset,
267
350
  contents: await subsetTtf(asset.contents, options)
268
351
  };
269
- subsetAssets.push(...options.clone === true ? [asset, subsetAsset] : [subsetAsset]);
270
- }
271
- assets = subsetAssets;
352
+ return options.clone === true ? [asset, subsetAsset] : [subsetAsset];
353
+ });
272
354
  continue;
273
355
  }
274
356
  if (plugin.name === "unicodeSlices") {
275
357
  const slices = normalizeDeliverySlices(optionsOf(plugin));
276
- const slicedAssets = [];
277
- for (const asset of assets) {
278
- if (asset.format !== "ttf") {
279
- slicedAssets.push(asset);
280
- continue;
281
- }
282
- for (const slice of slices) slicedAssets.push({
358
+ assets = await flatMapAssets(assets, async (asset) => {
359
+ if (asset.format !== "ttf") return [asset];
360
+ return Promise.all(slices.map(async (slice) => ({
283
361
  ...asset,
284
362
  contents: await subsetTtf(asset.contents, {
285
363
  missingGlyphs: "ignore",
@@ -287,12 +365,12 @@ async function optimizeBrowser(config) {
287
365
  }),
288
366
  fileName: appendFileNameSuffix(asset.fileName, slice.name),
289
367
  unicodeRanges: slice.unicodeRanges
290
- });
291
- }
292
- assets = slicedAssets;
368
+ })));
369
+ });
293
370
  continue;
294
371
  }
295
372
  if (plugin.name === "css") {
373
+ const options = optionsOf(plugin);
296
374
  const css = await generateFontFaceCss(assets.filter((asset) => [
297
375
  "eot",
298
376
  "svg",
@@ -309,11 +387,17 @@ async function optimizeBrowser(config) {
309
387
  ...source,
310
388
  unicodeRanges: asset.unicodeRanges
311
389
  };
312
- }), optionsOf(plugin));
313
- const firstFont = assets.find((asset) => asset.format === "ttf");
390
+ }), options);
391
+ const firstFont = assets.find((asset) => [
392
+ "eot",
393
+ "svg",
394
+ "ttf",
395
+ "woff",
396
+ "woff2"
397
+ ].includes(asset.format ?? ""));
314
398
  if (firstFont !== void 0) assets.push({
315
399
  contents: new TextEncoder().encode(css),
316
- fileName: replaceExtension(firstFont.fileName, "css"),
400
+ fileName: replaceExtension(firstFont.fileName, options.target ?? "css"),
317
401
  format: "css"
318
402
  });
319
403
  continue;
@@ -349,38 +433,22 @@ async function optimizeBrowser(config) {
349
433
  });
350
434
  }
351
435
  };
352
- const transformed = [];
353
- for (const asset of assets) {
354
- const result = await plugin.transform(asset, context);
355
- if (result === null) continue;
356
- if (result === void 0) transformed.push(asset);
357
- else transformed.push(...(Array.isArray(result) ? result : [result]).map((asset) => formatAsset(asset)));
358
- }
359
- assets = [...transformed, ...emitted];
436
+ assets = [...await applyAssetTransform(assets, plugin.transform, context, formatAsset), ...emitted];
360
437
  continue;
361
438
  }
362
439
  const clone = optionsOf(plugin).clone !== false;
363
- const convertedAssets = [];
364
- const additions = [];
365
- for (const asset of assets) {
366
- const convertedAsset = await convert(asset, plugin);
367
- if (convertedAsset === void 0) convertedAssets.push(asset);
368
- else if (clone) {
369
- convertedAssets.push(asset);
370
- additions.push(convertedAsset);
371
- } else convertedAssets.push(convertedAsset);
372
- }
373
- assets = [...convertedAssets, ...additions];
440
+ const convertedAssets = await applyFontConversion(assets, plugin.name, clone, (asset) => asset.format, (asset, conversion) => convert(asset, plugin, conversion));
441
+ if (convertedAssets !== void 0) assets = convertedAssets;
374
442
  }
375
443
  return assets;
376
444
  }
377
- async function convert(asset, plugin) {
378
- if (plugin.name === "ttf2woff" && asset.format === "ttf") return converted(asset, "woff", await ttfToWoff(asset.contents, optionsOf(plugin)));
379
- if (plugin.name === "ttf2woff2" && asset.format === "ttf") return converted(asset, "woff2", await ttfToWoff2(asset.contents, optionsOf(plugin)));
380
- if (plugin.name === "ttf2eot" && asset.format === "ttf") return converted(asset, "eot", await ttfToEot(asset.contents, optionsOf(plugin)));
381
- if (plugin.name === "ttf2svg" && asset.format === "ttf") return converted(asset, "svg", new TextEncoder().encode(await ttfToSvg(asset.contents, optionsOf(plugin))));
382
- if (plugin.name === "otf2ttf" && asset.format === "otf") return converted(asset, "ttf", await otfToTtf(asset.contents, optionsOf(plugin)));
383
- if (plugin.name === "svg2ttf" && asset.format === "svg") return converted(asset, "ttf", await svgFontToTtf(new TextDecoder().decode(asset.contents), optionsOf(plugin)));
445
+ async function convert(asset, plugin, conversion) {
446
+ if (conversion.name === "ttf2woff") return converted(asset, conversion.outputFormat, await ttfToWoff(asset.contents, optionsOf(plugin)));
447
+ if (conversion.name === "ttf2woff2") return converted(asset, conversion.outputFormat, await ttfToWoff2(asset.contents, optionsOf(plugin)));
448
+ if (conversion.name === "ttf2eot") return converted(asset, conversion.outputFormat, await ttfToEot(asset.contents, optionsOf(plugin)));
449
+ if (conversion.name === "ttf2svg") return converted(asset, conversion.outputFormat, new TextEncoder().encode(await ttfToSvg(asset.contents, optionsOf(plugin))));
450
+ if (conversion.name === "otf2ttf") return converted(asset, conversion.outputFormat, await otfToTtf(asset.contents, optionsOf(plugin)));
451
+ return converted(asset, conversion.outputFormat, await svgFontToTtf(new TextDecoder().decode(asset.contents), optionsOf(plugin)));
384
452
  }
385
453
  function optionsOf(plugin) {
386
454
  return plugin.options ?? {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fontmin-rs/wasm",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Browser WASM runtime for fontmin-rs.",
5
5
  "license": "MIT",
6
6
  "repository": {