@fontmin-rs/wasm 0.1.0-beta.1 → 0.1.0-beta.3

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,10 +5,31 @@ 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
9
+ 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
+ declare class FontminDiagnosticError extends Error {
11
+ readonly name = "FontminDiagnosticError";
12
+ readonly code: FontminDiagnosticCode;
13
+ constructor(code: FontminDiagnosticCode, message: string, options?: ErrorOptions);
14
+ }
15
+ //#endregion
8
16
  //#region types.d.ts
9
17
  type FontFormat = 'ttf' | 'otf' | 'woff' | 'woff2' | 'eot' | 'svg' | 'css' | 'unknown';
10
18
  type OutputFormat = 'ttf' | 'woff' | 'woff2' | 'eot' | 'svg' | 'css';
11
19
  type LayoutSubsetMode = 'drop' | 'conservative' | 'preserve';
20
+ type MissingGlyphPolicy = 'ignore' | 'warn' | 'error';
21
+ interface CoverageOptions {
22
+ basicText?: boolean;
23
+ text?: string;
24
+ unicodeRanges?: string[];
25
+ unicodes?: number[];
26
+ }
27
+ interface CoverageReport {
28
+ coveragePercent: number;
29
+ missing: number[];
30
+ requested: number[];
31
+ supported: number[];
32
+ }
12
33
  interface FontMetadata {
13
34
  ascender: number;
14
35
  descender: number;
@@ -25,15 +46,12 @@ interface FontInfo {
25
46
  metadata: FontMetadata;
26
47
  size: number;
27
48
  }
28
- interface SubsetOptions {
29
- basicText?: boolean;
49
+ interface SubsetOptions extends CoverageOptions {
30
50
  keepNotdef?: boolean;
31
51
  layout?: LayoutSubsetMode;
52
+ missingGlyphs?: MissingGlyphPolicy;
32
53
  preserveHinting?: boolean;
33
- text?: string;
34
54
  trim?: boolean;
35
- unicodeRanges?: string[];
36
- unicodes?: number[];
37
55
  }
38
56
  interface WoffOptions {
39
57
  compressionLevel?: number;
@@ -96,6 +114,7 @@ interface CssOptions {
96
114
  //#endregion
97
115
  //#region src/native.d.ts
98
116
  declare function subsetTtf(input: Uint8Array, options?: SubsetOptions): Promise<Uint8Array>;
117
+ declare function analyzeCoverage(input: Uint8Array, options?: CoverageOptions): Promise<CoverageReport>;
99
118
  declare function ttfToWoff(input: Uint8Array, options?: WoffOptions): Promise<Uint8Array>;
100
119
  declare function woffToTtf(input: Uint8Array): Promise<Uint8Array>;
101
120
  declare function ttfToWoff2(input: Uint8Array, options?: Ttf2Woff2Options): Promise<Uint8Array>;
@@ -193,4 +212,4 @@ interface BrowserOptimizeConfig {
193
212
  }
194
213
  declare function optimizeBrowser(config: BrowserOptimizeConfig): Promise<BrowserAsset[]>;
195
214
  //#endregion
196
- export { type BrowserAsset, type BrowserOptimizeConfig, type BrowserPlugin, type BrowserPluginContext, type CssFontSource, type CssGlyph, type CssOptions, type DeliverySlice, type DeliverySlicesOptions, type FontFormat, type FontInfo, type FontMetadata, type FontminCompatPresetOptions, type GlyphOptions, type LayoutSubsetMode, type MaybePromise, type ModernWebOptions, type Otf2TtfOptions, type Otf2TtfPluginOptions, type OutputFormat, type SubsetOptions, type Svg2TtfOptions, type Svg2TtfPluginOptions, type SvgIcon, type Svgs2TtfOptions, type Svgs2TtfPluginOptions, type Ttf2EotOptions, type Ttf2EotPluginOptions, type Ttf2SvgOptions, type Ttf2SvgPluginOptions, type Ttf2Woff2Options, type Ttf2Woff2PluginOptions, type Ttf2WoffPluginOptions, type WoffOptions, css, deliverySlices, eotToTtf, fontminCompatPreset, generateFontFaceCss, glyph, initWasm, inspect, isWasmInitialized, modernWeb, optimizeBrowser, otf2ttf, otfToTtf, subsetTtf, svg2ttf, svgFontToTtf, svgs2ttf, svgsToTtf, ttf2eot, ttf2svg, ttf2woff, ttf2woff2, ttfToEot, ttfToSvg, ttfToWoff, ttfToWoff2, validateWoff2, woff2ToTtf, woffToTtf };
215
+ export { type BrowserAsset, type BrowserOptimizeConfig, type BrowserPlugin, type BrowserPluginContext, type CoverageOptions, type CoverageReport, type CssFontSource, type CssGlyph, type CssOptions, type DeliverySlice, type DeliverySlicesOptions, type FontFormat, type FontInfo, type FontMetadata, type FontminCompatPresetOptions, type FontminDiagnosticCode, FontminDiagnosticError, type GlyphOptions, type LayoutSubsetMode, type MaybePromise, type MissingGlyphPolicy, type ModernWebOptions, type Otf2TtfOptions, type Otf2TtfPluginOptions, type OutputFormat, type SubsetOptions, type Svg2TtfOptions, type Svg2TtfPluginOptions, type SvgIcon, type Svgs2TtfOptions, type Svgs2TtfPluginOptions, type Ttf2EotOptions, type Ttf2EotPluginOptions, type Ttf2SvgOptions, type Ttf2SvgPluginOptions, type Ttf2Woff2Options, type Ttf2Woff2PluginOptions, type Ttf2WoffPluginOptions, type WoffOptions, analyzeCoverage, css, deliverySlices, eotToTtf, fontminCompatPreset, generateFontFaceCss, glyph, initWasm, inspect, isWasmInitialized, modernWeb, optimizeBrowser, otf2ttf, otfToTtf, subsetTtf, svg2ttf, svgFontToTtf, svgs2ttf, svgsToTtf, ttf2eot, ttf2svg, ttf2woff, ttf2woff2, ttfToEot, ttfToSvg, ttfToWoff, ttfToWoff2, validateWoff2, woff2ToTtf, woffToTtf };
package/dist/index.mjs CHANGED
@@ -9,28 +9,74 @@ function assertWasmInitialized() {
9
9
  }
10
10
  async function getWasmModule() {
11
11
  assertWasmInitialized();
12
- return initialization;
12
+ const module = initialization;
13
+ if (module === void 0) throw new Error("fontmin-rs WASM runtime initialization is unavailable");
14
+ return module;
13
15
  }
14
16
  async function initWasm(input) {
15
- initialization ??= import("./fontmin_wasm_core-Cn_EVAE9.mjs").then(async (module) => {
16
- await module.default(input === void 0 ? void 0 : { module_or_path: input });
17
- if (module.runtime_name() !== "fontmin-rs") throw new Error("fontmin-rs WASM runtime did not initialize correctly");
18
- initialized = true;
19
- return module;
20
- });
17
+ initialization ??= initializeWasm(input);
21
18
  await initialization;
22
19
  }
20
+ async function initializeWasm(input) {
21
+ const module = await import("./fontmin_wasm_core-Cn_EVAE9.mjs");
22
+ await module.default(input === void 0 ? void 0 : { module_or_path: input });
23
+ if (module.runtime_name() !== "fontmin-rs") throw new Error("fontmin-rs WASM runtime did not initialize correctly");
24
+ initialized = true;
25
+ return module;
26
+ }
27
+ //#endregion
28
+ //#region src/diagnostics.ts
29
+ const bridgeDiagnosticPattern = /^\[(?<code>fontmin::[a-z_]+)\] (?<message>[\s\S]+)$/u;
30
+ var FontminDiagnosticError = class extends Error {
31
+ name = "FontminDiagnosticError";
32
+ code;
33
+ constructor(code, message, options) {
34
+ super(message, options);
35
+ this.code = code;
36
+ }
37
+ };
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 });
47
+ }
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 });
52
+ }
53
+ function withFontminDiagnostics(operation) {
54
+ try {
55
+ return operation();
56
+ } catch (error) {
57
+ throw normalizeFontminDiagnostic(error);
58
+ }
59
+ }
23
60
  //#endregion
24
61
  //#region src/native.ts
25
62
  function bytes(value) {
26
63
  return new Uint8Array(value);
27
64
  }
28
65
  async function binary(operation, input, options = {}) {
29
- return bytes((await getWasmModule()).transform(operation, input, options));
66
+ const wasm = await getWasmModule();
67
+ return withFontminDiagnostics(() => bytes(wasm.transform(operation, input, options)));
30
68
  }
31
69
  async function subsetTtf(input, options = {}) {
70
+ if ((options.missingGlyphs ?? "warn") === "warn") {
71
+ const warning = missingGlyphWarning(await analyzeCoverage(input, coverageOptions(options)));
72
+ if (warning !== void 0) console.warn(warning);
73
+ }
32
74
  return binary("subsetTtf", input, options);
33
75
  }
76
+ async function analyzeCoverage(input, options = {}) {
77
+ const wasm = await getWasmModule();
78
+ return withFontminDiagnostics(() => wasm.transform("analyzeCoverage", input, options));
79
+ }
34
80
  async function ttfToWoff(input, options = {}) {
35
81
  return binary("ttfToWoff", input, options);
36
82
  }
@@ -44,7 +90,8 @@ async function woff2ToTtf(input) {
44
90
  return binary("woff2ToTtf", input);
45
91
  }
46
92
  async function validateWoff2(input) {
47
- (await getWasmModule()).transform("validateWoff2", input, {});
93
+ const wasm = await getWasmModule();
94
+ withFontminDiagnostics(() => wasm.transform("validateWoff2", input, {}));
48
95
  }
49
96
  async function ttfToEot(input, options = {}) {
50
97
  return binary("ttfToEot", input, options);
@@ -53,22 +100,41 @@ async function eotToTtf(input) {
53
100
  return binary("eotToTtf", input);
54
101
  }
55
102
  async function ttfToSvg(input, options = {}) {
56
- return (await getWasmModule()).transform("ttfToSvg", input, options);
103
+ const wasm = await getWasmModule();
104
+ return withFontminDiagnostics(() => wasm.transform("ttfToSvg", input, options));
57
105
  }
58
106
  async function svgFontToTtf(input, options = {}) {
59
- return bytes((await getWasmModule()).transform_text("svgFontToTtf", input, options));
107
+ const wasm = await getWasmModule();
108
+ return withFontminDiagnostics(() => bytes(wasm.transform_text("svgFontToTtf", input, options)));
60
109
  }
61
110
  async function svgsToTtf(inputs, options = {}) {
62
- return bytes((await getWasmModule()).transform_icons(inputs, options));
111
+ const wasm = await getWasmModule();
112
+ return withFontminDiagnostics(() => bytes(wasm.transform_icons(inputs, options)));
63
113
  }
64
114
  async function otfToTtf(input, options = {}) {
65
115
  return binary("otfToTtf", input, options);
66
116
  }
67
117
  async function inspect(input) {
68
- return (await getWasmModule()).transform("inspect", input, {});
118
+ const wasm = await getWasmModule();
119
+ return withFontminDiagnostics(() => wasm.transform("inspect", input, {}));
69
120
  }
70
121
  async function generateFontFaceCss(sources, options = {}) {
71
- return (await getWasmModule()).generate_css(sources, options);
122
+ const wasm = await getWasmModule();
123
+ return withFontminDiagnostics(() => wasm.generate_css(sources, options));
124
+ }
125
+ function coverageOptions(options) {
126
+ const coverage = {};
127
+ if (options.basicText !== void 0) coverage.basicText = options.basicText;
128
+ if (options.text !== void 0) coverage.text = options.text;
129
+ if (options.unicodeRanges !== void 0) coverage.unicodeRanges = options.unicodeRanges;
130
+ if (options.unicodes !== void 0) coverage.unicodes = options.unicodes;
131
+ return coverage;
132
+ }
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` : ""}`;
72
138
  }
73
139
  //#endregion
74
140
  //#region src/plugins.ts
@@ -83,6 +149,7 @@ function glyph(options = {}) {
83
149
  basicText: false,
84
150
  keepNotdef: true,
85
151
  layout: "conservative",
152
+ missingGlyphs: "warn",
86
153
  preserveHinting: false,
87
154
  trim: true,
88
155
  unicodes: [],
@@ -204,7 +271,10 @@ async function optimizeBrowser(config) {
204
271
  }
205
272
  for (const slice of slices) slicedAssets.push({
206
273
  ...asset,
207
- contents: await subsetTtf(asset.contents, { unicodeRanges: slice.unicodeRanges }),
274
+ contents: await subsetTtf(asset.contents, {
275
+ missingGlyphs: "ignore",
276
+ unicodeRanges: slice.unicodeRanges
277
+ }),
208
278
  fileName: appendFileNameSuffix(asset.fileName, slice.name),
209
279
  unicodeRanges: slice.unicodeRanges
210
280
  });
@@ -273,9 +343,9 @@ async function optimizeBrowser(config) {
273
343
  const result = await plugin.transform(asset, context);
274
344
  if (result === null) continue;
275
345
  if (result === void 0) transformed.push(asset);
276
- else transformed.push(...(Array.isArray(result) ? result : [result]).map(formatAsset));
346
+ else transformed.push(...(Array.isArray(result) ? result : [result]).map((asset) => formatAsset(asset)));
277
347
  }
278
- assets = transformed.concat(emitted);
348
+ assets = [...transformed, ...emitted];
279
349
  continue;
280
350
  }
281
351
  if (plugin.name === "otf2ttf" && optionsOf(plugin).clone === false) {
@@ -287,7 +357,7 @@ async function optimizeBrowser(config) {
287
357
  const converted = await convert(asset, plugin);
288
358
  if (converted !== void 0) additions.push(converted);
289
359
  }
290
- assets = assets.concat(additions);
360
+ assets = [...assets, ...additions];
291
361
  }
292
362
  return assets;
293
363
  }
@@ -323,11 +393,11 @@ function replaceExtension(fileName, extension) {
323
393
  return `${fileName.replace(/\.[^.]+$/u, "")}.${extension}`;
324
394
  }
325
395
  function appendFileNameSuffix(fileName, suffix) {
326
- const extension = fileName.match(/(\.[^.]+)$/u)?.[1] ?? "";
396
+ const extension = fileName.match(/(?<extension>\.[^.]+)$/u)?.groups?.["extension"] ?? "";
327
397
  return `${extension === "" ? fileName : fileName.slice(0, -extension.length)}-${suffix}${extension}`;
328
398
  }
329
399
  function toKebabCase(value) {
330
- return value.trim().replaceAll(/([a-z])([A-Z])/gu, "$1-$2").replaceAll(/\s+/gu, "-").toLowerCase();
400
+ return value.trim().replaceAll(/(?<lower>[a-z])(?<upper>[A-Z])/gu, "$<lower>-$<upper>").replaceAll(/\s+/gu, "-").toLowerCase();
331
401
  }
332
402
  //#endregion
333
- export { css, deliverySlices, eotToTtf, fontminCompatPreset, generateFontFaceCss, glyph, initWasm, inspect, isWasmInitialized, modernWeb, optimizeBrowser, otf2ttf, otfToTtf, subsetTtf, svg2ttf, svgFontToTtf, svgs2ttf, svgsToTtf, ttf2eot, ttf2svg, ttf2woff, ttf2woff2, ttfToEot, ttfToSvg, ttfToWoff, ttfToWoff2, validateWoff2, woff2ToTtf, woffToTtf };
403
+ export { FontminDiagnosticError, analyzeCoverage, css, deliverySlices, eotToTtf, fontminCompatPreset, generateFontFaceCss, glyph, initWasm, inspect, isWasmInitialized, modernWeb, optimizeBrowser, otf2ttf, otfToTtf, subsetTtf, svg2ttf, svgFontToTtf, svgs2ttf, svgsToTtf, ttf2eot, ttf2svg, ttf2woff, ttf2woff2, ttfToEot, ttfToSvg, ttfToWoff, ttfToWoff2, validateWoff2, woff2ToTtf, woffToTtf };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fontmin-rs/wasm",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.3",
4
4
  "description": "Browser WASM runtime for fontmin-rs.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "devDependencies": {
27
27
  "@ntnyq/tsconfig": "^3.1.0",
28
28
  "playwright": "^1.61.1",
29
- "tsdown": "^0.22.8",
29
+ "tsdown": "^0.22.12",
30
30
  "typescript": "^7.0.2",
31
31
  "vitest": "^4.1.10"
32
32
  },