@fontmin-rs/wasm 1.0.2-rc.1 → 1.1.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/dist/fontmin_wasm_core_bg.wasm +0 -0
- package/dist/index.d.mts +191 -1
- package/dist/index.mjs +565 -2
- package/package.json +1 -1
|
Binary file
|
package/dist/index.d.mts
CHANGED
|
@@ -50,17 +50,129 @@ interface FontInfo {
|
|
|
50
50
|
metadata: FontMetadata;
|
|
51
51
|
size: number;
|
|
52
52
|
}
|
|
53
|
+
interface FontCollectionFaceInfo extends FontInfo {
|
|
54
|
+
capabilities: FontCapabilityReport;
|
|
55
|
+
/** Zero-based face index used by extractCollectionFace. */
|
|
56
|
+
index: number;
|
|
57
|
+
/** Absolute SFNT directory offset in the collection. */
|
|
58
|
+
offset: number;
|
|
59
|
+
}
|
|
60
|
+
type CapabilitySupport = 'subset' | 'passthrough' | 'unsupported';
|
|
61
|
+
type ColorFontTechnology = 'colr-cpal' | 'cbdt-cblc' | 'sbix' | 'svg';
|
|
62
|
+
interface ColorFontTechnologyCapability {
|
|
63
|
+
detail: string;
|
|
64
|
+
subsetSupport: CapabilitySupport;
|
|
65
|
+
tables: string[];
|
|
66
|
+
technology: ColorFontTechnology;
|
|
67
|
+
version?: number;
|
|
68
|
+
}
|
|
69
|
+
interface ColorFontCapabilityReport {
|
|
70
|
+
isColorFont: boolean;
|
|
71
|
+
subsetSupport?: CapabilitySupport;
|
|
72
|
+
technologies: ColorFontTechnologyCapability[];
|
|
73
|
+
}
|
|
74
|
+
interface FontCapabilityReport {
|
|
75
|
+
color: ColorFontCapabilityReport;
|
|
76
|
+
format: FontFormat;
|
|
77
|
+
}
|
|
78
|
+
interface FontCollectionInfo {
|
|
79
|
+
faces: FontCollectionFaceInfo[];
|
|
80
|
+
majorVersion: number;
|
|
81
|
+
minorVersion: number;
|
|
82
|
+
size: number;
|
|
83
|
+
}
|
|
53
84
|
interface SubsetOptions extends CoverageOptions {
|
|
85
|
+
/** Original glyph IDs to retain in addition to Unicode selection. */
|
|
86
|
+
gids?: number[];
|
|
87
|
+
/** PostScript glyph names to retain in addition to other selectors. */
|
|
88
|
+
glyphNames?: string[];
|
|
54
89
|
/** Retain the original glyph-zero outline; false emits the required empty glyph-zero slot. */
|
|
55
90
|
keepNotdef?: boolean;
|
|
91
|
+
/** Preserve original glyph IDs and leave null entries for empty mapping slots. */
|
|
92
|
+
retainGids?: boolean;
|
|
93
|
+
/** Retain PostScript glyph names in a rewritten version 2 `post` table. */
|
|
94
|
+
retainGlyphNames?: boolean;
|
|
95
|
+
/** Retain non-Unicode, non-symbol cmap records after remapping. */
|
|
96
|
+
retainLegacyCmap?: boolean;
|
|
97
|
+
/** Retain the Windows symbol cmap record after remapping. */
|
|
98
|
+
retainSymbolCmap?: boolean;
|
|
56
99
|
/** Drop layout, remap supported data, or reject known contextual loss. */
|
|
57
100
|
layout?: LayoutSubsetMode;
|
|
101
|
+
/** Four-byte OpenType feature tags to retain, or all features when omitted. */
|
|
102
|
+
layoutFeatures?: string[];
|
|
103
|
+
/** Four-byte OpenType script tags to retain, or all scripts when omitted. */
|
|
104
|
+
layoutScripts?: string[];
|
|
105
|
+
/** OpenType language tags to retain; `default` selects DefaultLangSys. */
|
|
106
|
+
layoutLanguages?: string[];
|
|
107
|
+
/** OpenType name IDs to retain, or all name IDs when omitted. */
|
|
108
|
+
nameIds?: number[];
|
|
109
|
+
/** Platform-specific name language IDs to retain, or all languages when omitted. */
|
|
110
|
+
nameLanguages?: number[];
|
|
111
|
+
/** Optional OpenType tables to remove after subsetting. */
|
|
112
|
+
dropTables?: string[];
|
|
113
|
+
/** Optional source tables to copy verbatim into the subset. */
|
|
114
|
+
passThroughTables?: string[];
|
|
58
115
|
missingGlyphs?: MissingGlyphPolicy;
|
|
59
116
|
/** Retain the cvt, fpgm, and prep TrueType program tables while trimming. */
|
|
60
117
|
preserveHinting?: boolean;
|
|
61
118
|
/** Skip subsetting and return the validated source bytes unchanged when false. */
|
|
62
119
|
trim?: boolean;
|
|
63
120
|
}
|
|
121
|
+
interface GidMapping {
|
|
122
|
+
newGid: number;
|
|
123
|
+
oldGid: number;
|
|
124
|
+
}
|
|
125
|
+
interface UnicodeGidMapping {
|
|
126
|
+
oldGid: number;
|
|
127
|
+
unicode: number;
|
|
128
|
+
}
|
|
129
|
+
interface GlyphNameGidMapping {
|
|
130
|
+
glyphName: string;
|
|
131
|
+
oldGid: number;
|
|
132
|
+
}
|
|
133
|
+
interface SubsetReport {
|
|
134
|
+
cffCharstringsVerbatim: boolean;
|
|
135
|
+
droppedContextSubtables: number;
|
|
136
|
+
glyphsRetained: number;
|
|
137
|
+
glyphNameToOldGid: GlyphNameGidMapping[];
|
|
138
|
+
missingGids: number[];
|
|
139
|
+
missingGlyphNames: string[];
|
|
140
|
+
newToOld: (number | null)[];
|
|
141
|
+
oldToNew: GidMapping[];
|
|
142
|
+
originalSize: number;
|
|
143
|
+
requestedGids: number[];
|
|
144
|
+
requestedGlyphNames: string[];
|
|
145
|
+
subsetSize: number;
|
|
146
|
+
supportedGids: number[];
|
|
147
|
+
supportedGlyphNames: string[];
|
|
148
|
+
tablesRetained: string[];
|
|
149
|
+
unicodeToOldGid: UnicodeGidMapping[];
|
|
150
|
+
}
|
|
151
|
+
interface SubsetResult {
|
|
152
|
+
data: Uint8Array;
|
|
153
|
+
report: SubsetReport;
|
|
154
|
+
}
|
|
155
|
+
/** Resolved selectors bound to one source font by SHA-256. */
|
|
156
|
+
interface SubsetPlan {
|
|
157
|
+
coverage: CoverageReport;
|
|
158
|
+
glyphNameToOldGid: GlyphNameGidMapping[];
|
|
159
|
+
missingGids: number[];
|
|
160
|
+
missingGlyphNames: string[];
|
|
161
|
+
options: SubsetOptions;
|
|
162
|
+
/** SHA-256 integrity digest of the canonical plan payload. */
|
|
163
|
+
planSha256: string;
|
|
164
|
+
requestedGids: number[];
|
|
165
|
+
requestedGlyphNames: string[];
|
|
166
|
+
schemaVersion: number;
|
|
167
|
+
/** Original glyph IDs used to seed glyph and layout closure. */
|
|
168
|
+
seedGids: number[];
|
|
169
|
+
sourceGlyphs: number;
|
|
170
|
+
sourceSha256: string;
|
|
171
|
+
sourceSize: number;
|
|
172
|
+
supportedGids: number[];
|
|
173
|
+
supportedGlyphNames: string[];
|
|
174
|
+
unicodeToOldGid: UnicodeGidMapping[];
|
|
175
|
+
}
|
|
64
176
|
interface WoffOptions {
|
|
65
177
|
compressionLevel?: number;
|
|
66
178
|
deflate?: boolean;
|
|
@@ -81,6 +193,25 @@ interface Otf2TtfOptions {
|
|
|
81
193
|
preserveHinting?: boolean;
|
|
82
194
|
variationCoordinates?: Record<string, number>;
|
|
83
195
|
}
|
|
196
|
+
interface InstanceOptions {
|
|
197
|
+
/** Axis values in fvar user units. Unspecified axes use their defaults. */
|
|
198
|
+
variationCoordinates?: Record<string, number>;
|
|
199
|
+
}
|
|
200
|
+
interface AxisRange {
|
|
201
|
+
/** Inclusive lower bound in fvar user units. */
|
|
202
|
+
min: number;
|
|
203
|
+
/** Inclusive upper bound in fvar user units. */
|
|
204
|
+
max: number;
|
|
205
|
+
/** New default, or the original default clamped into the range when omitted. */
|
|
206
|
+
default?: number;
|
|
207
|
+
}
|
|
208
|
+
type AxisSetting = number | AxisRange;
|
|
209
|
+
interface VariationSpaceOptions {
|
|
210
|
+
/** Axis tags mapped to a pin or retained range. Unlisted axes stay variable. */
|
|
211
|
+
axes: Record<string, AxisSetting>;
|
|
212
|
+
/** Convert fully pinned CFF2 outlines to CFF1 for older renderers. */
|
|
213
|
+
downgradeCff2?: boolean;
|
|
214
|
+
}
|
|
84
215
|
interface Svg2TtfOptions {
|
|
85
216
|
/** Compatibility option accepted without generating TrueType hint instructions. */
|
|
86
217
|
hinting?: boolean;
|
|
@@ -124,7 +255,12 @@ interface CssOptions {
|
|
|
124
255
|
//#endregion
|
|
125
256
|
//#region src/native.d.ts
|
|
126
257
|
declare function subsetTtf(input: Uint8Array, options?: SubsetOptions): Promise<Uint8Array>;
|
|
258
|
+
declare function subsetTtfWithReport(input: Uint8Array, options?: SubsetOptions): Promise<SubsetResult>;
|
|
259
|
+
declare function createTtfSubsetPlan(input: Uint8Array, options?: SubsetOptions): Promise<SubsetPlan>;
|
|
260
|
+
declare function subsetTtfWithPlan(input: Uint8Array, plan: SubsetPlan): Promise<SubsetResult>;
|
|
127
261
|
declare function analyzeCoverage(input: Uint8Array, options?: CoverageOptions): Promise<CoverageReport>;
|
|
262
|
+
declare function instantiateFont(input: Uint8Array, options?: InstanceOptions): Promise<Uint8Array>;
|
|
263
|
+
declare function reduceVariationSpace(input: Uint8Array, options: VariationSpaceOptions): Promise<Uint8Array>;
|
|
128
264
|
declare function ttfToWoff(input: Uint8Array, options?: WoffOptions): Promise<Uint8Array>;
|
|
129
265
|
declare function woffToTtf(input: Uint8Array): Promise<Uint8Array>;
|
|
130
266
|
declare function ttfToWoff2(input: Uint8Array, options?: Ttf2Woff2Options): Promise<Uint8Array>;
|
|
@@ -137,8 +273,49 @@ declare function svgFontToTtf(input: string, options?: Svg2TtfOptions): Promise<
|
|
|
137
273
|
declare function svgsToTtf(inputs: SvgIcon[], options?: Svgs2TtfOptions): Promise<Uint8Array>;
|
|
138
274
|
declare function otfToTtf(input: Uint8Array, options?: Otf2TtfOptions): Promise<Uint8Array>;
|
|
139
275
|
declare function inspect(input: Uint8Array): Promise<FontInfo>;
|
|
276
|
+
declare function inspectCapabilities(input: Uint8Array): Promise<FontCapabilityReport>;
|
|
277
|
+
declare function inspectCollection(input: Uint8Array): Promise<FontCollectionInfo>;
|
|
278
|
+
declare function extractCollectionFace(input: Uint8Array, faceIndex: number): Promise<Uint8Array>;
|
|
140
279
|
declare function generateFontFaceCss(sources: CssFontSource[], options?: CssOptions): Promise<string>;
|
|
141
280
|
//#endregion
|
|
281
|
+
//#region ../../packages/fontmin/src/runtime-neutral/auto-delivery.d.ts
|
|
282
|
+
type DeliveryLanguagePreset = 'ar' | 'el' | 'en' | 'hi' | 'ja' | 'ko' | 'ru' | 'zh-Hans' | 'zh-Hant';
|
|
283
|
+
interface AutoDeliveryPlanOptions {
|
|
284
|
+
frequencyText?: string;
|
|
285
|
+
languages?: DeliveryLanguagePreset[];
|
|
286
|
+
maxSlices?: number;
|
|
287
|
+
targetBytes?: number;
|
|
288
|
+
tolerance?: number;
|
|
289
|
+
}
|
|
290
|
+
interface AutoDeliveryPlanSlice {
|
|
291
|
+
codePoints: number[];
|
|
292
|
+
estimatedBytes: number;
|
|
293
|
+
name: string;
|
|
294
|
+
unicodeRanges: string[];
|
|
295
|
+
}
|
|
296
|
+
interface AutoDeliveryPlan {
|
|
297
|
+
codePointCount: number;
|
|
298
|
+
languages: DeliveryLanguagePreset[];
|
|
299
|
+
slices: AutoDeliveryPlanSlice[];
|
|
300
|
+
targetBytes: number;
|
|
301
|
+
tolerance: number;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Detect supported delivery-language presets from representative page text.
|
|
305
|
+
*
|
|
306
|
+
* @param text - Static or frequency-weighted business text.
|
|
307
|
+
* @returns Presets in first-observed order, with Han assigned to a detected
|
|
308
|
+
* Japanese, Korean, or Traditional Chinese context before Simplified Chinese.
|
|
309
|
+
*/
|
|
310
|
+
declare function detectDeliveryLanguages(text: string): DeliveryLanguagePreset[];
|
|
311
|
+
/**
|
|
312
|
+
* Compact code points into canonical CSS Unicode ranges.
|
|
313
|
+
*
|
|
314
|
+
* @param codePoints - Unicode scalar values in any order.
|
|
315
|
+
* @returns Sorted `U+XXXX[-YYYY]` ranges.
|
|
316
|
+
*/
|
|
317
|
+
declare function unicodeRangesFromCodePoints(codePoints: readonly number[]): string[];
|
|
318
|
+
//#endregion
|
|
142
319
|
//#region src/plugins.d.ts
|
|
143
320
|
type MaybePromise<T> = T | Promise<T>;
|
|
144
321
|
interface DeliverySlice {
|
|
@@ -148,6 +325,12 @@ interface DeliverySlice {
|
|
|
148
325
|
interface DeliverySlicesOptions {
|
|
149
326
|
slices: DeliverySlice[];
|
|
150
327
|
}
|
|
328
|
+
interface AutoDeliveryPluginOptions extends AutoDeliveryPlanOptions {
|
|
329
|
+
measureFormat?: 'ttf' | 'woff' | 'woff2';
|
|
330
|
+
subset?: SubsetOptions;
|
|
331
|
+
woff2Quality?: number;
|
|
332
|
+
woffCompressionLevel?: number;
|
|
333
|
+
}
|
|
151
334
|
interface GlyphOptions extends SubsetOptions {
|
|
152
335
|
clone?: boolean;
|
|
153
336
|
}
|
|
@@ -166,6 +349,9 @@ interface Ttf2SvgPluginOptions extends Ttf2SvgOptions {
|
|
|
166
349
|
interface Otf2TtfPluginOptions extends Otf2TtfOptions {
|
|
167
350
|
clone?: boolean;
|
|
168
351
|
}
|
|
352
|
+
interface VariationSpacePluginOptions extends VariationSpaceOptions {
|
|
353
|
+
clone?: boolean;
|
|
354
|
+
}
|
|
169
355
|
interface Svg2TtfPluginOptions extends Svg2TtfOptions {
|
|
170
356
|
clone?: boolean;
|
|
171
357
|
}
|
|
@@ -177,6 +363,8 @@ interface ModernWebOptions extends GlyphOptions, Otf2TtfPluginOptions, Ttf2WoffP
|
|
|
177
363
|
fontFamily?: string;
|
|
178
364
|
fontPath?: string;
|
|
179
365
|
local?: boolean;
|
|
366
|
+
variationAxes?: VariationSpaceOptions['axes'];
|
|
367
|
+
downgradeCff2?: boolean;
|
|
180
368
|
}
|
|
181
369
|
interface FontminCompatPresetOptions extends GlyphOptions, Otf2TtfPluginOptions, Ttf2EotPluginOptions, Ttf2SvgPluginOptions, Ttf2WoffPluginOptions, Ttf2Woff2PluginOptions {
|
|
182
370
|
fontDisplay?: CssOptions['fontDisplay'];
|
|
@@ -198,11 +386,13 @@ interface BrowserPlugin<Options extends object = object> {
|
|
|
198
386
|
}
|
|
199
387
|
declare function glyph(options?: GlyphOptions): BrowserPlugin<GlyphOptions>;
|
|
200
388
|
declare function deliverySlices(slices: DeliverySlice[]): BrowserPlugin<DeliverySlicesOptions>;
|
|
389
|
+
declare function autoDeliverySlices(options?: AutoDeliveryPluginOptions): BrowserPlugin<AutoDeliveryPluginOptions>;
|
|
201
390
|
declare function ttf2woff(options?: Ttf2WoffPluginOptions): BrowserPlugin<Ttf2WoffPluginOptions>;
|
|
202
391
|
declare function ttf2woff2(options?: Ttf2Woff2PluginOptions): BrowserPlugin<Ttf2Woff2PluginOptions>;
|
|
203
392
|
declare function ttf2eot(options?: Ttf2EotPluginOptions): BrowserPlugin<Ttf2EotPluginOptions>;
|
|
204
393
|
declare function ttf2svg(options?: Ttf2SvgPluginOptions): BrowserPlugin<Ttf2SvgPluginOptions>;
|
|
205
394
|
declare function otf2ttf(options?: Otf2TtfPluginOptions): BrowserPlugin<Otf2TtfPluginOptions>;
|
|
395
|
+
declare function variationSpace(options: VariationSpacePluginOptions): BrowserPlugin<VariationSpacePluginOptions>;
|
|
206
396
|
declare function svg2ttf(options?: Svg2TtfPluginOptions): BrowserPlugin<Svg2TtfPluginOptions>;
|
|
207
397
|
declare function svgs2ttf(options?: Svgs2TtfPluginOptions): BrowserPlugin<Svgs2TtfPluginOptions>;
|
|
208
398
|
declare function css(options?: CssOptions): BrowserPlugin<CssOptions>;
|
|
@@ -222,4 +412,4 @@ interface BrowserOptimizeConfig {
|
|
|
222
412
|
}
|
|
223
413
|
declare function optimizeBrowser(config: BrowserOptimizeConfig): Promise<BrowserAsset[]>;
|
|
224
414
|
//#endregion
|
|
225
|
-
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 };
|
|
415
|
+
export { type AutoDeliveryPlan, type AutoDeliveryPlanOptions, type AutoDeliveryPlanSlice, type AutoDeliveryPluginOptions, type AxisRange, type AxisSetting, type BrowserAsset, type BrowserOptimizeConfig, type BrowserPlugin, type BrowserPluginContext, type CapabilitySupport, type ColorFontCapabilityReport, type ColorFontTechnology, type ColorFontTechnologyCapability, type CoverageOptions, type CoverageReport, type CssFontSource, type CssGlyph, type CssOptions, type DeliveryLanguagePreset, type DeliverySlice, type DeliverySlicesOptions, type FontCapabilityReport, type FontCollectionFaceInfo, type FontCollectionInfo, type FontFormat, type FontInfo, type FontMetadata, type FontminCompatPresetOptions, type FontminDiagnosticCode, FontminDiagnosticError, type GidMapping, type GlyphNameGidMapping, type GlyphOptions, type InstanceOptions, type LayoutSubsetMode, type MaybePromise, type MissingGlyphPolicy, type ModernWebOptions, type Otf2TtfOptions, type Otf2TtfPluginOptions, type OutputFormat, type SubsetOptions, type SubsetPlan, type SubsetReport, type SubsetResult, 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 UnicodeGidMapping, type VariationSpaceOptions, type VariationSpacePluginOptions, type WoffOptions, analyzeCoverage, autoDeliverySlices, createTtfSubsetPlan, css, deliverySlices, detectDeliveryLanguages, eotToTtf, extractCollectionFace, fontminCompatPreset, generateFontFaceCss, glyph, initWasm, inspect, inspectCapabilities, inspectCollection, instantiateFont, isWasmInitialized, modernWeb, optimizeBrowser, otf2ttf, otfToTtf, reduceVariationSpace, subsetTtf, subsetTtfWithPlan, subsetTtfWithReport, svg2ttf, svgFontToTtf, svgs2ttf, svgsToTtf, ttf2eot, ttf2svg, ttf2woff, ttf2woff2, ttfToEot, ttfToSvg, ttfToWoff, ttfToWoff2, unicodeRangesFromCodePoints, validateWoff2, variationSpace, woff2ToTtf, woffToTtf };
|
package/dist/index.mjs
CHANGED
|
@@ -174,16 +174,51 @@ async function binary(operation, input, options = {}) {
|
|
|
174
174
|
return withFontminDiagnostics(() => bytes(wasm.transform(operation, input, options)));
|
|
175
175
|
}
|
|
176
176
|
async function subsetTtf(input, options = {}) {
|
|
177
|
-
if ((options.missingGlyphs ?? "warn") === "warn") {
|
|
177
|
+
if ((options.missingGlyphs ?? "warn") === "warn" && hasUnicodeSelection(options)) {
|
|
178
178
|
const warning = missingGlyphWarning(await analyzeCoverage(input, coverageOptions(options)));
|
|
179
179
|
if (warning !== void 0) console.warn(warning);
|
|
180
180
|
}
|
|
181
181
|
return binary("subsetTtf", input, options);
|
|
182
182
|
}
|
|
183
|
+
async function subsetTtfWithReport(input, options = {}) {
|
|
184
|
+
const wasm = await getWasmModule();
|
|
185
|
+
const result = withFontminDiagnostics(() => wasm.transform("subsetTtfWithReport", input, options));
|
|
186
|
+
return {
|
|
187
|
+
data: bytes(result.data),
|
|
188
|
+
report: {
|
|
189
|
+
...result.report,
|
|
190
|
+
newToOld: result.report.newToOld.map((gid) => gid ?? null)
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
async function createTtfSubsetPlan(input, options = {}) {
|
|
195
|
+
const wasm = await getWasmModule();
|
|
196
|
+
return withFontminDiagnostics(() => wasm.transform("createTtfSubsetPlan", input, options));
|
|
197
|
+
}
|
|
198
|
+
async function subsetTtfWithPlan(input, plan) {
|
|
199
|
+
const wasm = await getWasmModule();
|
|
200
|
+
const result = withFontminDiagnostics(() => wasm.transform("subsetTtfWithPlan", input, plan));
|
|
201
|
+
return {
|
|
202
|
+
data: bytes(result.data),
|
|
203
|
+
report: {
|
|
204
|
+
...result.report,
|
|
205
|
+
newToOld: result.report.newToOld.map((gid) => gid ?? null)
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function hasUnicodeSelection(options) {
|
|
210
|
+
return options.basicText === true || (options.text?.length ?? 0) > 0 || (options.unicodes?.length ?? 0) > 0 || (options.unicodeRanges?.length ?? 0) > 0;
|
|
211
|
+
}
|
|
183
212
|
async function analyzeCoverage(input, options = {}) {
|
|
184
213
|
const wasm = await getWasmModule();
|
|
185
214
|
return withFontminDiagnostics(() => wasm.transform("analyzeCoverage", input, options));
|
|
186
215
|
}
|
|
216
|
+
async function instantiateFont(input, options = {}) {
|
|
217
|
+
return binary("instantiateFont", input, options);
|
|
218
|
+
}
|
|
219
|
+
async function reduceVariationSpace(input, options) {
|
|
220
|
+
return binary("reduceVariationSpace", input, options);
|
|
221
|
+
}
|
|
187
222
|
async function ttfToWoff(input, options = {}) {
|
|
188
223
|
return binary("ttfToWoff", input, options);
|
|
189
224
|
}
|
|
@@ -225,6 +260,25 @@ async function inspect(input) {
|
|
|
225
260
|
const wasm = await getWasmModule();
|
|
226
261
|
return withFontminDiagnostics(() => wasm.transform("inspect", input, {}));
|
|
227
262
|
}
|
|
263
|
+
async function inspectCapabilities(input) {
|
|
264
|
+
const wasm = await getWasmModule();
|
|
265
|
+
const report = withFontminDiagnostics(() => wasm.transform("inspectCapabilities", input, {}));
|
|
266
|
+
if (report.color.subsetSupport === void 0) {
|
|
267
|
+
const { subsetSupport: _subsetSupport, ...color } = report.color;
|
|
268
|
+
return {
|
|
269
|
+
...report,
|
|
270
|
+
color
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return report;
|
|
274
|
+
}
|
|
275
|
+
async function inspectCollection(input) {
|
|
276
|
+
const wasm = await getWasmModule();
|
|
277
|
+
return withFontminDiagnostics(() => wasm.transform("inspectCollection", input, {}));
|
|
278
|
+
}
|
|
279
|
+
async function extractCollectionFace(input, faceIndex) {
|
|
280
|
+
return binary("extractCollectionFace", input, { faceIndex });
|
|
281
|
+
}
|
|
228
282
|
async function generateFontFaceCss(sources, options = {}) {
|
|
229
283
|
const wasm = await getWasmModule();
|
|
230
284
|
return withFontminDiagnostics(() => wasm.generate_css(sources, options));
|
|
@@ -238,6 +292,418 @@ function coverageOptions(options) {
|
|
|
238
292
|
return coverage;
|
|
239
293
|
}
|
|
240
294
|
//#endregion
|
|
295
|
+
//#region ../../packages/fontmin/src/runtime-neutral/auto-delivery.ts
|
|
296
|
+
const DEFAULT_TARGET_BYTES = 100 * 1024;
|
|
297
|
+
const DEFAULT_TOLERANCE = .15;
|
|
298
|
+
const DEFAULT_MAX_SLICES = 32;
|
|
299
|
+
const GROUPS = {
|
|
300
|
+
arabic: ranges([
|
|
301
|
+
[1536, 1791],
|
|
302
|
+
[1872, 1919],
|
|
303
|
+
[2160, 2207],
|
|
304
|
+
[2208, 2303],
|
|
305
|
+
[64336, 65023],
|
|
306
|
+
[65136, 65279]
|
|
307
|
+
]),
|
|
308
|
+
bopomofo: ranges([[12544, 12591], [12704, 12735]]),
|
|
309
|
+
cyrillic: ranges([
|
|
310
|
+
[1024, 1327],
|
|
311
|
+
[7296, 7311],
|
|
312
|
+
[11744, 11775],
|
|
313
|
+
[42560, 42655]
|
|
314
|
+
]),
|
|
315
|
+
devanagari: ranges([[2304, 2431], [43232, 43263]]),
|
|
316
|
+
greek: ranges([[880, 1023], [7936, 8191]]),
|
|
317
|
+
han: ranges([
|
|
318
|
+
[11904, 12255],
|
|
319
|
+
[13312, 19903],
|
|
320
|
+
[19968, 40959],
|
|
321
|
+
[63744, 64255],
|
|
322
|
+
[131072, 191471],
|
|
323
|
+
[196608, 205743]
|
|
324
|
+
]),
|
|
325
|
+
hangul: ranges([
|
|
326
|
+
[4352, 4607],
|
|
327
|
+
[12592, 12687],
|
|
328
|
+
[43360, 43391],
|
|
329
|
+
[44032, 55215],
|
|
330
|
+
[55216, 55295]
|
|
331
|
+
]),
|
|
332
|
+
kana: ranges([
|
|
333
|
+
[12352, 12543],
|
|
334
|
+
[12784, 12799],
|
|
335
|
+
[110592, 110959]
|
|
336
|
+
]),
|
|
337
|
+
latin: ranges([
|
|
338
|
+
[32, 126],
|
|
339
|
+
[160, 687],
|
|
340
|
+
[7680, 7935]
|
|
341
|
+
]),
|
|
342
|
+
punctuation: ranges([
|
|
343
|
+
[8192, 8303],
|
|
344
|
+
[12288, 12351],
|
|
345
|
+
[65040, 65055],
|
|
346
|
+
[65072, 65103],
|
|
347
|
+
[65280, 65519]
|
|
348
|
+
])
|
|
349
|
+
};
|
|
350
|
+
const LANGUAGE_GROUPS = {
|
|
351
|
+
ar: ["arabic"],
|
|
352
|
+
el: ["greek"],
|
|
353
|
+
en: ["latin"],
|
|
354
|
+
hi: ["devanagari"],
|
|
355
|
+
ja: [
|
|
356
|
+
"punctuation",
|
|
357
|
+
"kana",
|
|
358
|
+
"han"
|
|
359
|
+
],
|
|
360
|
+
ko: [
|
|
361
|
+
"punctuation",
|
|
362
|
+
"hangul",
|
|
363
|
+
"han"
|
|
364
|
+
],
|
|
365
|
+
ru: ["cyrillic"],
|
|
366
|
+
"zh-Hans": ["punctuation", "han"],
|
|
367
|
+
"zh-Hant": [
|
|
368
|
+
"punctuation",
|
|
369
|
+
"bopomofo",
|
|
370
|
+
"han"
|
|
371
|
+
]
|
|
372
|
+
};
|
|
373
|
+
/**
|
|
374
|
+
* Detect supported delivery-language presets from representative page text.
|
|
375
|
+
*
|
|
376
|
+
* @param text - Static or frequency-weighted business text.
|
|
377
|
+
* @returns Presets in first-observed order, with Han assigned to a detected
|
|
378
|
+
* Japanese, Korean, or Traditional Chinese context before Simplified Chinese.
|
|
379
|
+
*/
|
|
380
|
+
function detectDeliveryLanguages(text) {
|
|
381
|
+
const detected = [];
|
|
382
|
+
const seen = /* @__PURE__ */ new Set();
|
|
383
|
+
let pendingHan = false;
|
|
384
|
+
for (const character of text) {
|
|
385
|
+
const codePoint = character.codePointAt(0);
|
|
386
|
+
if (codePoint === void 0) continue;
|
|
387
|
+
const language = languageOf(codePoint);
|
|
388
|
+
if (language === "han") pendingHan = true;
|
|
389
|
+
else if (language !== void 0 && !seen.has(language)) {
|
|
390
|
+
seen.add(language);
|
|
391
|
+
detected.push(language);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (pendingHan && ![
|
|
395
|
+
"ja",
|
|
396
|
+
"ko",
|
|
397
|
+
"zh-Hant"
|
|
398
|
+
].some((tag) => seen.has(tag))) detected.push("zh-Hans");
|
|
399
|
+
return detected;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Plan byte-bounded delivery slices over code points supported by a font.
|
|
403
|
+
*
|
|
404
|
+
* @param supportedCodePoints - Unicode cmap coverage from one or more faces.
|
|
405
|
+
* @param options - Language, frequency, byte target, and request constraints.
|
|
406
|
+
* @param measure - Returns the actual encoded byte size for a proposed slice.
|
|
407
|
+
* @returns A deterministic, measured delivery plan.
|
|
408
|
+
*/
|
|
409
|
+
async function planAutoDeliverySlices(supportedCodePoints, options, measure) {
|
|
410
|
+
const normalized = normalizeOptions(options);
|
|
411
|
+
const languages = resolveLanguages(normalized);
|
|
412
|
+
const groups = planningGroups(supportedCodePoints, languages, normalized);
|
|
413
|
+
if (groups.length === 0) throw new Error("auto delivery presets matched no supported code points");
|
|
414
|
+
if (groups.length > normalized.maxSlices) throw new Error(`auto delivery requires at least ${groups.length} slices for the selected languages`);
|
|
415
|
+
const merged = await mergeSmallGroups(await splitOversizedGroups(await Promise.all(groups.map(async (group) => measureGroup(group, measure))), normalized, measure), normalized, measure);
|
|
416
|
+
const groupCounts = /* @__PURE__ */ new Map();
|
|
417
|
+
for (const group of merged) groupCounts.set(group.name, (groupCounts.get(group.name) ?? 0) + 1);
|
|
418
|
+
const groupIndexes = /* @__PURE__ */ new Map();
|
|
419
|
+
const slices = merged.map((group) => {
|
|
420
|
+
const index = (groupIndexes.get(group.name) ?? 0) + 1;
|
|
421
|
+
const count = groupCounts.get(group.name) ?? 1;
|
|
422
|
+
groupIndexes.set(group.name, index);
|
|
423
|
+
return {
|
|
424
|
+
codePoints: [...group.codePoints].toSorted((left, right) => left - right),
|
|
425
|
+
estimatedBytes: group.estimatedBytes,
|
|
426
|
+
name: count === 1 ? group.name : `${group.name}-${String(index).padStart(String(count).length, "0")}`,
|
|
427
|
+
unicodeRanges: unicodeRangesFromCodePoints(group.codePoints)
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
codePointCount: new Set(slices.flatMap((slice) => slice.codePoints)).size,
|
|
432
|
+
languages,
|
|
433
|
+
slices,
|
|
434
|
+
targetBytes: normalized.targetBytes,
|
|
435
|
+
tolerance: normalized.tolerance
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function normalizeOptions(options) {
|
|
439
|
+
const targetBytes = options.targetBytes ?? DEFAULT_TARGET_BYTES;
|
|
440
|
+
const tolerance = options.tolerance ?? DEFAULT_TOLERANCE;
|
|
441
|
+
const maxSlices = options.maxSlices ?? DEFAULT_MAX_SLICES;
|
|
442
|
+
if (!Number.isInteger(targetBytes) || targetBytes <= 0) throw new TypeError("auto delivery targetBytes must be a positive integer");
|
|
443
|
+
if (!Number.isFinite(tolerance) || tolerance < 0 || tolerance >= 1) throw new TypeError("auto delivery tolerance must be in [0, 1)");
|
|
444
|
+
if (!Number.isInteger(maxSlices) || maxSlices <= 0 || maxSlices > 256) throw new TypeError("auto delivery maxSlices must be an integer in [1, 256]");
|
|
445
|
+
return {
|
|
446
|
+
frequencyText: options.frequencyText ?? "",
|
|
447
|
+
languages: [...options.languages ?? []],
|
|
448
|
+
maxSlices,
|
|
449
|
+
targetBytes,
|
|
450
|
+
tolerance
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function resolveLanguages(options) {
|
|
454
|
+
const languages = options.languages.length === 0 ? detectDeliveryLanguages(options.frequencyText) : options.languages;
|
|
455
|
+
const resolved = languages.length === 0 ? ["en"] : languages;
|
|
456
|
+
return [...new Set(resolved)];
|
|
457
|
+
}
|
|
458
|
+
function planningGroups(supportedCodePoints, languages, options) {
|
|
459
|
+
const supported = new Set(supportedCodePoints.filter((codePoint) => isValidUnicodeScalar$1(codePoint)).toSorted((left, right) => left - right));
|
|
460
|
+
const languageGroups = groupsForLanguages(languages);
|
|
461
|
+
const selected = new Set(languageGroups.flatMap((group) => [...supported].filter((codePoint) => includesCodePoint(group, codePoint))));
|
|
462
|
+
const frequency = frequencyOrder(options.frequencyText).filter((codePoint) => selected.has(codePoint));
|
|
463
|
+
const prioritized = new Set(frequency);
|
|
464
|
+
const groups = [];
|
|
465
|
+
if (frequency.length > 0) groups.push({
|
|
466
|
+
codePoints: frequency,
|
|
467
|
+
name: "priority"
|
|
468
|
+
});
|
|
469
|
+
const assigned = new Set(prioritized);
|
|
470
|
+
for (const group of languageGroups) {
|
|
471
|
+
const codePoints = [...supported].filter((codePoint) => !assigned.has(codePoint) && includesCodePoint(group, codePoint));
|
|
472
|
+
for (const codePoint of codePoints) assigned.add(codePoint);
|
|
473
|
+
if (codePoints.length > 0) groups.push({
|
|
474
|
+
codePoints,
|
|
475
|
+
name: group.name
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
return groups;
|
|
479
|
+
}
|
|
480
|
+
function groupsForLanguages(languages) {
|
|
481
|
+
return [...new Set(languages.flatMap((language) => LANGUAGE_GROUPS[language]))].map((name) => ({
|
|
482
|
+
name,
|
|
483
|
+
ranges: GROUPS[name]
|
|
484
|
+
}));
|
|
485
|
+
}
|
|
486
|
+
async function splitOversizedGroups(initial, options, measure) {
|
|
487
|
+
const groups = [...initial];
|
|
488
|
+
const maximumBytes = options.targetBytes * (1 + options.tolerance);
|
|
489
|
+
while (groups.length < options.maxSlices) {
|
|
490
|
+
const candidateIndex = groups.map((group, index) => ({
|
|
491
|
+
group,
|
|
492
|
+
index
|
|
493
|
+
})).filter(({ group }) => group.estimatedBytes > maximumBytes && group.codePoints.length > 1).toSorted((left, right) => right.group.estimatedBytes - left.group.estimatedBytes || left.index - right.index)[0]?.index;
|
|
494
|
+
if (candidateIndex === void 0) break;
|
|
495
|
+
const candidate = groups[candidateIndex];
|
|
496
|
+
if (candidate === void 0) break;
|
|
497
|
+
const midpoint = Math.ceil(candidate.codePoints.length / 2);
|
|
498
|
+
const replacements = await Promise.all([measureGroup({
|
|
499
|
+
codePoints: candidate.codePoints.slice(0, midpoint),
|
|
500
|
+
name: candidate.name
|
|
501
|
+
}, measure), measureGroup({
|
|
502
|
+
codePoints: candidate.codePoints.slice(midpoint),
|
|
503
|
+
name: candidate.name
|
|
504
|
+
}, measure)]);
|
|
505
|
+
groups.splice(candidateIndex, 1, ...replacements);
|
|
506
|
+
}
|
|
507
|
+
return groups;
|
|
508
|
+
}
|
|
509
|
+
async function mergeSmallGroups(initial, options, measure) {
|
|
510
|
+
const groups = [...initial];
|
|
511
|
+
const minimumBytes = options.targetBytes * (1 - options.tolerance);
|
|
512
|
+
const maximumBytes = options.targetBytes * (1 + options.tolerance);
|
|
513
|
+
let index = 0;
|
|
514
|
+
while (index < groups.length - 1) {
|
|
515
|
+
const current = groups[index];
|
|
516
|
+
const next = groups[index + 1];
|
|
517
|
+
if (current === void 0 || next === void 0) break;
|
|
518
|
+
if (current.name !== next.name || current.estimatedBytes >= minimumBytes) {
|
|
519
|
+
index += 1;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
const merged = await measureGroup({
|
|
523
|
+
codePoints: [...current.codePoints, ...next.codePoints],
|
|
524
|
+
name: current.name
|
|
525
|
+
}, measure);
|
|
526
|
+
if (merged.estimatedBytes <= maximumBytes) groups.splice(index, 2, merged);
|
|
527
|
+
else index += 1;
|
|
528
|
+
}
|
|
529
|
+
return groups;
|
|
530
|
+
}
|
|
531
|
+
async function measureGroup(group, measure) {
|
|
532
|
+
const estimatedBytes = await measure(group.codePoints);
|
|
533
|
+
if (!Number.isInteger(estimatedBytes) || estimatedBytes < 0) throw new TypeError("auto delivery measure must return a non-negative integer");
|
|
534
|
+
return {
|
|
535
|
+
...group,
|
|
536
|
+
estimatedBytes
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function frequencyOrder(text) {
|
|
540
|
+
const frequencies = /* @__PURE__ */ new Map();
|
|
541
|
+
let index = 0;
|
|
542
|
+
for (const character of text) {
|
|
543
|
+
const codePoint = character.codePointAt(0);
|
|
544
|
+
if (codePoint !== void 0) {
|
|
545
|
+
const current = frequencies.get(codePoint);
|
|
546
|
+
frequencies.set(codePoint, {
|
|
547
|
+
count: (current?.count ?? 0) + 1,
|
|
548
|
+
index: current?.index ?? index
|
|
549
|
+
});
|
|
550
|
+
index += 1;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return [...frequencies.entries()].toSorted(([, left], [, right]) => right.count - left.count || left.index - right.index).map(([codePoint]) => codePoint);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Compact code points into canonical CSS Unicode ranges.
|
|
557
|
+
*
|
|
558
|
+
* @param codePoints - Unicode scalar values in any order.
|
|
559
|
+
* @returns Sorted `U+XXXX[-YYYY]` ranges.
|
|
560
|
+
*/
|
|
561
|
+
function unicodeRangesFromCodePoints(codePoints) {
|
|
562
|
+
const sorted = [...new Set(codePoints)].toSorted((left, right) => left - right);
|
|
563
|
+
const compact = [];
|
|
564
|
+
for (let index = 0; index < sorted.length;) {
|
|
565
|
+
const start = sorted[index];
|
|
566
|
+
if (start === void 0) break;
|
|
567
|
+
let end = start;
|
|
568
|
+
while (sorted[index + 1] === end + 1) {
|
|
569
|
+
end += 1;
|
|
570
|
+
index += 1;
|
|
571
|
+
}
|
|
572
|
+
compact.push(start === end ? `U+${unicodeHex(start)}` : `U+${unicodeHex(start)}-${unicodeHex(end)}`);
|
|
573
|
+
index += 1;
|
|
574
|
+
}
|
|
575
|
+
return compact;
|
|
576
|
+
}
|
|
577
|
+
function languageOf(codePoint) {
|
|
578
|
+
if (includesRanges(GROUPS.kana, codePoint)) return "ja";
|
|
579
|
+
if (includesRanges(GROUPS.hangul, codePoint)) return "ko";
|
|
580
|
+
if (includesRanges(GROUPS.bopomofo, codePoint)) return "zh-Hant";
|
|
581
|
+
if (includesRanges(GROUPS.han, codePoint)) return "han";
|
|
582
|
+
if (includesRanges(GROUPS.arabic, codePoint)) return "ar";
|
|
583
|
+
if (includesRanges(GROUPS.devanagari, codePoint)) return "hi";
|
|
584
|
+
if (includesRanges(GROUPS.cyrillic, codePoint)) return "ru";
|
|
585
|
+
if (includesRanges(GROUPS.greek, codePoint)) return "el";
|
|
586
|
+
if (includesRanges(GROUPS.latin, codePoint)) return "en";
|
|
587
|
+
}
|
|
588
|
+
function includesCodePoint(group, codePoint) {
|
|
589
|
+
return includesRanges(group.ranges, codePoint);
|
|
590
|
+
}
|
|
591
|
+
function includesRanges(values, codePoint) {
|
|
592
|
+
return values.some((range) => codePoint >= range.start && codePoint <= range.end);
|
|
593
|
+
}
|
|
594
|
+
function ranges(values) {
|
|
595
|
+
return values.map(([start, end]) => ({
|
|
596
|
+
end,
|
|
597
|
+
start
|
|
598
|
+
}));
|
|
599
|
+
}
|
|
600
|
+
function isValidUnicodeScalar$1(codePoint) {
|
|
601
|
+
return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 1114111 && (codePoint < 55296 || codePoint > 57343);
|
|
602
|
+
}
|
|
603
|
+
function unicodeHex(codePoint) {
|
|
604
|
+
return codePoint.toString(16).toUpperCase().padStart(4, "0");
|
|
605
|
+
}
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region ../../packages/fontmin/src/runtime-neutral/sfnt-unicode.ts
|
|
608
|
+
/**
|
|
609
|
+
* Read mapped Unicode scalar values from the Unicode cmap subtables of an SFNT.
|
|
610
|
+
*
|
|
611
|
+
* @param input - TTF or OTF bytes.
|
|
612
|
+
* @returns Sorted, unique code points whose mapped glyph ID is non-zero.
|
|
613
|
+
*/
|
|
614
|
+
function unicodeCodePointsFromSfnt(input) {
|
|
615
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
616
|
+
const cmapOffset = tableOffset(view, input, "cmap");
|
|
617
|
+
if (cmapOffset === void 0 || cmapOffset + 4 > view.byteLength) return [];
|
|
618
|
+
const recordCount = view.getUint16(cmapOffset + 2);
|
|
619
|
+
const codePoints = /* @__PURE__ */ new Set();
|
|
620
|
+
const visited = /* @__PURE__ */ new Set();
|
|
621
|
+
for (let index = 0; index < recordCount; index += 1) {
|
|
622
|
+
const recordOffset = cmapOffset + 4 + index * 8;
|
|
623
|
+
if (recordOffset + 8 > view.byteLength) break;
|
|
624
|
+
const platform = view.getUint16(recordOffset);
|
|
625
|
+
const encoding = view.getUint16(recordOffset + 2);
|
|
626
|
+
if (platform !== 0 && !(platform === 3 && (encoding === 1 || encoding === 10))) continue;
|
|
627
|
+
const subtableOffset = cmapOffset + view.getUint32(recordOffset + 4);
|
|
628
|
+
if (visited.has(subtableOffset) || subtableOffset + 2 > view.byteLength) continue;
|
|
629
|
+
visited.add(subtableOffset);
|
|
630
|
+
collectSubtable(view, subtableOffset, codePoints);
|
|
631
|
+
}
|
|
632
|
+
return [...codePoints].toSorted((left, right) => left - right);
|
|
633
|
+
}
|
|
634
|
+
function tableOffset(view, input, requestedTag) {
|
|
635
|
+
if (view.byteLength < 12) return;
|
|
636
|
+
const tableCount = view.getUint16(4);
|
|
637
|
+
const decoder = new TextDecoder();
|
|
638
|
+
for (let index = 0; index < tableCount; index += 1) {
|
|
639
|
+
const recordOffset = 12 + index * 16;
|
|
640
|
+
if (recordOffset + 16 > view.byteLength) return;
|
|
641
|
+
if (decoder.decode(input.subarray(recordOffset, recordOffset + 4)) === requestedTag) {
|
|
642
|
+
const offset = view.getUint32(recordOffset + 8);
|
|
643
|
+
return offset < view.byteLength ? offset : void 0;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function collectSubtable(view, offset, codePoints) {
|
|
648
|
+
const format = view.getUint16(offset);
|
|
649
|
+
if (format === 0) collectFormat0(view, offset, codePoints);
|
|
650
|
+
else if (format === 4) collectFormat4(view, offset, codePoints);
|
|
651
|
+
else if (format === 12 || format === 13) collectFormat12Or13(view, offset, codePoints, format);
|
|
652
|
+
}
|
|
653
|
+
function collectFormat0(view, offset, codePoints) {
|
|
654
|
+
if (offset + 262 > view.byteLength) return;
|
|
655
|
+
for (let codePoint = 0; codePoint < 256; codePoint += 1) if (view.getUint8(offset + 6 + codePoint) !== 0) codePoints.add(codePoint);
|
|
656
|
+
}
|
|
657
|
+
function collectFormat4(view, offset, codePoints) {
|
|
658
|
+
if (offset + 16 > view.byteLength) return;
|
|
659
|
+
const length = view.getUint16(offset + 2);
|
|
660
|
+
const end = Math.min(offset + length, view.byteLength);
|
|
661
|
+
const segmentCount = view.getUint16(offset + 6) / 2;
|
|
662
|
+
const endCodesOffset = offset + 14;
|
|
663
|
+
const startCodesOffset = endCodesOffset + segmentCount * 2 + 2;
|
|
664
|
+
const deltasOffset = startCodesOffset + segmentCount * 2;
|
|
665
|
+
const rangeOffsetsOffset = deltasOffset + segmentCount * 2;
|
|
666
|
+
if (rangeOffsetsOffset + segmentCount * 2 > end) return;
|
|
667
|
+
for (let segment = 0; segment < segmentCount; segment += 1) {
|
|
668
|
+
const start = view.getUint16(startCodesOffset + segment * 2);
|
|
669
|
+
const segmentEnd = view.getUint16(endCodesOffset + segment * 2);
|
|
670
|
+
const delta = view.getInt16(deltasOffset + segment * 2);
|
|
671
|
+
const rangeOffsetPosition = rangeOffsetsOffset + segment * 2;
|
|
672
|
+
const rangeOffset = view.getUint16(rangeOffsetPosition);
|
|
673
|
+
for (let codePoint = start; codePoint <= segmentEnd && codePoint < 65535; codePoint += 1) {
|
|
674
|
+
let glyphId;
|
|
675
|
+
if (rangeOffset === 0) glyphId = uint16(codePoint + delta);
|
|
676
|
+
else {
|
|
677
|
+
const glyphOffset = rangeOffsetPosition + rangeOffset + (codePoint - start) * 2;
|
|
678
|
+
if (glyphOffset + 2 > end) continue;
|
|
679
|
+
const rawGlyphId = view.getUint16(glyphOffset);
|
|
680
|
+
glyphId = rawGlyphId === 0 ? 0 : uint16(rawGlyphId + delta);
|
|
681
|
+
}
|
|
682
|
+
if (glyphId !== 0 && isValidUnicodeScalar(codePoint)) codePoints.add(codePoint);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function collectFormat12Or13(view, offset, codePoints, format) {
|
|
687
|
+
if (offset + 16 > view.byteLength) return;
|
|
688
|
+
const length = view.getUint32(offset + 4);
|
|
689
|
+
const end = Math.min(offset + length, view.byteLength);
|
|
690
|
+
const groupCount = view.getUint32(offset + 12);
|
|
691
|
+
for (let group = 0; group < groupCount; group += 1) {
|
|
692
|
+
const groupOffset = offset + 16 + group * 12;
|
|
693
|
+
if (groupOffset + 12 > end) break;
|
|
694
|
+
const start = view.getUint32(groupOffset);
|
|
695
|
+
const groupEnd = Math.min(view.getUint32(groupOffset + 4), 1114111);
|
|
696
|
+
const startGlyphId = view.getUint32(groupOffset + 8);
|
|
697
|
+
for (let codePoint = start; codePoint <= groupEnd; codePoint += 1) if ((format === 12 ? startGlyphId + codePoint - start : startGlyphId) !== 0 && isValidUnicodeScalar(codePoint)) codePoints.add(codePoint);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function isValidUnicodeScalar(codePoint) {
|
|
701
|
+
return codePoint < 55296 || codePoint > 57343;
|
|
702
|
+
}
|
|
703
|
+
function uint16(value) {
|
|
704
|
+
return (value % 65536 + 65536) % 65536;
|
|
705
|
+
}
|
|
706
|
+
//#endregion
|
|
241
707
|
//#region src/plugins.ts
|
|
242
708
|
function plugin(name, options) {
|
|
243
709
|
return {
|
|
@@ -250,9 +716,18 @@ function glyph(options = {}) {
|
|
|
250
716
|
basicText: false,
|
|
251
717
|
keepNotdef: true,
|
|
252
718
|
layout: "conservative",
|
|
719
|
+
layoutFeatures: [],
|
|
720
|
+
layoutScripts: [],
|
|
721
|
+
layoutLanguages: [],
|
|
722
|
+
nameIds: [],
|
|
723
|
+
nameLanguages: [],
|
|
724
|
+
dropTables: [],
|
|
725
|
+
passThroughTables: [],
|
|
253
726
|
missingGlyphs: "warn",
|
|
254
727
|
preserveHinting: false,
|
|
255
728
|
trim: true,
|
|
729
|
+
gids: [],
|
|
730
|
+
glyphNames: [],
|
|
256
731
|
unicodes: [],
|
|
257
732
|
...options
|
|
258
733
|
});
|
|
@@ -263,6 +738,9 @@ function deliverySlices(slices) {
|
|
|
263
738
|
unicodeRanges: [...slice.unicodeRanges]
|
|
264
739
|
})) });
|
|
265
740
|
}
|
|
741
|
+
function autoDeliverySlices(options = {}) {
|
|
742
|
+
return plugin("autoUnicodeSlices", structuredClone(options));
|
|
743
|
+
}
|
|
266
744
|
function normalizeDeliverySlices(options) {
|
|
267
745
|
return normalizeDeliverySlices$1(options.slices);
|
|
268
746
|
}
|
|
@@ -281,6 +759,9 @@ function ttf2svg(options = {}) {
|
|
|
281
759
|
function otf2ttf(options = {}) {
|
|
282
760
|
return plugin("otf2ttf", options);
|
|
283
761
|
}
|
|
762
|
+
function variationSpace(options) {
|
|
763
|
+
return plugin("variationSpace", options);
|
|
764
|
+
}
|
|
284
765
|
function svg2ttf(options = {}) {
|
|
285
766
|
return plugin("svg2ttf", options);
|
|
286
767
|
}
|
|
@@ -312,6 +793,11 @@ function modernWeb(options = {}) {
|
|
|
312
793
|
if (typeof options.preserveHinting === "boolean") otfOptions.preserveHinting = options.preserveHinting;
|
|
313
794
|
if (options.variationCoordinates !== void 0) otfOptions.variationCoordinates = options.variationCoordinates;
|
|
314
795
|
return [
|
|
796
|
+
...options.variationAxes === void 0 ? [] : [variationSpace({
|
|
797
|
+
axes: options.variationAxes,
|
|
798
|
+
clone: false,
|
|
799
|
+
...options.downgradeCff2 === void 0 ? {} : { downgradeCff2: options.downgradeCff2 }
|
|
800
|
+
})],
|
|
315
801
|
otf2ttf(otfOptions),
|
|
316
802
|
glyph(subset),
|
|
317
803
|
ttf2woff(options),
|
|
@@ -341,6 +827,29 @@ function fontminCompatPreset(options = {}) {
|
|
|
341
827
|
async function optimizeBrowser(config) {
|
|
342
828
|
let assets = config.assets.map(formatAsset);
|
|
343
829
|
for (const plugin of config.plugins ?? []) {
|
|
830
|
+
if (plugin.name === "variationSpace") {
|
|
831
|
+
const options = optionsOf(plugin);
|
|
832
|
+
assets = await flatMapAssets(assets, async (asset) => {
|
|
833
|
+
if (![
|
|
834
|
+
"eot",
|
|
835
|
+
"otf",
|
|
836
|
+
"ttf",
|
|
837
|
+
"woff",
|
|
838
|
+
"woff2"
|
|
839
|
+
].includes(asset.format)) return [asset];
|
|
840
|
+
const contents = await reduceVariationSpace(asset.contents, options);
|
|
841
|
+
const format = new TextDecoder().decode(contents.subarray(0, 4)) === "OTTO" ? "otf" : "ttf";
|
|
842
|
+
const normalizedFileName = replaceExtension(asset.fileName, format);
|
|
843
|
+
const reduced = {
|
|
844
|
+
...asset,
|
|
845
|
+
contents,
|
|
846
|
+
fileName: options.clone === true ? appendFileNameSuffix(normalizedFileName, "reduced") : normalizedFileName,
|
|
847
|
+
format
|
|
848
|
+
};
|
|
849
|
+
return options.clone === true ? [asset, reduced] : [reduced];
|
|
850
|
+
});
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
344
853
|
if (plugin.name === "glyph") {
|
|
345
854
|
const options = optionsOf(plugin);
|
|
346
855
|
assets = await flatMapAssets(assets, async (asset) => {
|
|
@@ -369,6 +878,10 @@ async function optimizeBrowser(config) {
|
|
|
369
878
|
});
|
|
370
879
|
continue;
|
|
371
880
|
}
|
|
881
|
+
if (plugin.name === "autoUnicodeSlices") {
|
|
882
|
+
assets = await runAutoDeliverySlices(assets, optionsOf(plugin));
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
372
885
|
if (plugin.name === "css") {
|
|
373
886
|
const options = optionsOf(plugin);
|
|
374
887
|
const css = await generateFontFaceCss(assets.filter((asset) => [
|
|
@@ -442,6 +955,56 @@ async function optimizeBrowser(config) {
|
|
|
442
955
|
}
|
|
443
956
|
return assets;
|
|
444
957
|
}
|
|
958
|
+
async function runAutoDeliverySlices(assets, options) {
|
|
959
|
+
const sources = assets.flatMap((asset, index) => asset.format === "ttf" ? [{
|
|
960
|
+
asset,
|
|
961
|
+
codePoints: new Set(unicodeCodePointsFromSfnt(asset.contents)),
|
|
962
|
+
index
|
|
963
|
+
}] : []);
|
|
964
|
+
if (sources.length === 0) return assets;
|
|
965
|
+
const supported = [...new Set(sources.flatMap((source) => [...source.codePoints]))];
|
|
966
|
+
const cache = /* @__PURE__ */ new Map();
|
|
967
|
+
const subsetFor = async (source, codePoints) => {
|
|
968
|
+
const key = `${source.index}:${codePoints.join(",")}`;
|
|
969
|
+
const cached = cache.get(key);
|
|
970
|
+
if (cached !== void 0) return cached;
|
|
971
|
+
const contents = await subsetTtf(source.asset.contents, {
|
|
972
|
+
...options.subset,
|
|
973
|
+
missingGlyphs: "ignore",
|
|
974
|
+
unicodeRanges: unicodeRangesFromCodePoints(codePoints)
|
|
975
|
+
});
|
|
976
|
+
cache.set(key, contents);
|
|
977
|
+
return contents;
|
|
978
|
+
};
|
|
979
|
+
const plan = await planAutoDeliverySlices(supported, options, async (codePoints) => {
|
|
980
|
+
const sizes = await Promise.all(sources.filter((source) => codePoints.some((codePoint) => source.codePoints.has(codePoint))).map(async (source) => measureAutoDeliverySubset(await subsetFor(source, codePoints), options)));
|
|
981
|
+
return Math.max(...sizes);
|
|
982
|
+
});
|
|
983
|
+
const output = [];
|
|
984
|
+
for (const [index, asset] of assets.entries()) {
|
|
985
|
+
const source = sources.find((candidate) => candidate.index === index);
|
|
986
|
+
if (source === void 0) {
|
|
987
|
+
output.push(asset);
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
for (const slice of plan.slices) {
|
|
991
|
+
const codePoints = slice.codePoints.filter((codePoint) => source.codePoints.has(codePoint));
|
|
992
|
+
if (codePoints.length > 0) output.push({
|
|
993
|
+
...asset,
|
|
994
|
+
contents: await subsetFor(source, codePoints),
|
|
995
|
+
fileName: appendFileNameSuffix(asset.fileName, slice.name),
|
|
996
|
+
unicodeRanges: unicodeRangesFromCodePoints(codePoints)
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return output;
|
|
1001
|
+
}
|
|
1002
|
+
async function measureAutoDeliverySubset(contents, options) {
|
|
1003
|
+
const format = options.measureFormat ?? "woff2";
|
|
1004
|
+
if (format === "ttf") return contents.byteLength;
|
|
1005
|
+
if (format === "woff") return (await ttfToWoff(contents, options.woffCompressionLevel === void 0 ? {} : { compressionLevel: options.woffCompressionLevel })).byteLength;
|
|
1006
|
+
return (await ttfToWoff2(contents, options.woff2Quality === void 0 ? {} : { quality: options.woff2Quality })).byteLength;
|
|
1007
|
+
}
|
|
445
1008
|
async function convert(asset, plugin, conversion) {
|
|
446
1009
|
if (conversion.name === "ttf2woff") return converted(asset, conversion.outputFormat, await ttfToWoff(asset.contents, optionsOf(plugin)));
|
|
447
1010
|
if (conversion.name === "ttf2woff2") return converted(asset, conversion.outputFormat, await ttfToWoff2(asset.contents, optionsOf(plugin)));
|
|
@@ -481,4 +1044,4 @@ function toKebabCase(value) {
|
|
|
481
1044
|
return value.trim().replaceAll(/(?<lower>[a-z])(?<upper>[A-Z])/gu, "$<lower>-$<upper>").replaceAll(/\s+/gu, "-").toLowerCase();
|
|
482
1045
|
}
|
|
483
1046
|
//#endregion
|
|
484
|
-
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 };
|
|
1047
|
+
export { FontminDiagnosticError, analyzeCoverage, autoDeliverySlices, createTtfSubsetPlan, css, deliverySlices, detectDeliveryLanguages, eotToTtf, extractCollectionFace, fontminCompatPreset, generateFontFaceCss, glyph, initWasm, inspect, inspectCapabilities, inspectCollection, instantiateFont, isWasmInitialized, modernWeb, optimizeBrowser, otf2ttf, otfToTtf, reduceVariationSpace, subsetTtf, subsetTtfWithPlan, subsetTtfWithReport, svg2ttf, svgFontToTtf, svgs2ttf, svgsToTtf, ttf2eot, ttf2svg, ttf2woff, ttf2woff2, ttfToEot, ttfToSvg, ttfToWoff, ttfToWoff2, unicodeRangesFromCodePoints, validateWoff2, variationSpace, woff2ToTtf, woffToTtf };
|