fontmin-rs 0.1.1 → 0.2.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/bin/fontmin-rs.mjs +2 -2004
- package/dist/cli.mjs +784 -0
- package/dist/compat.mjs +51 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -1
- package/dist/{compat-BzwWTy_g.mjs → optimize-pipeline-4Thm4vQb.mjs} +397 -407
- package/package.json +4 -4
package/bin/fontmin-rs.mjs
CHANGED
|
@@ -1,2007 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
mkdir,
|
|
6
|
-
lstat,
|
|
7
|
-
readFile,
|
|
8
|
-
realpath,
|
|
9
|
-
rename,
|
|
10
|
-
rm,
|
|
11
|
-
stat,
|
|
12
|
-
writeFile,
|
|
13
|
-
} from 'node:fs/promises'
|
|
14
|
-
import {
|
|
15
|
-
basename,
|
|
16
|
-
dirname,
|
|
17
|
-
extname,
|
|
18
|
-
isAbsolute,
|
|
19
|
-
join,
|
|
20
|
-
parse,
|
|
21
|
-
relative,
|
|
22
|
-
resolve,
|
|
23
|
-
sep,
|
|
24
|
-
} from 'node:path'
|
|
25
|
-
import { pathToFileURL } from 'node:url'
|
|
26
|
-
import {
|
|
27
|
-
analyzeCoverage,
|
|
28
|
-
eotToTtf,
|
|
29
|
-
generateFontFaceCss,
|
|
30
|
-
inspectFont,
|
|
31
|
-
otfToTtf,
|
|
32
|
-
subsetTtf,
|
|
33
|
-
svgsToTtf,
|
|
34
|
-
ttfToEot,
|
|
35
|
-
ttfToSvg,
|
|
36
|
-
ttfToWoff,
|
|
37
|
-
ttfToWoff2,
|
|
38
|
-
woff2ToTtf,
|
|
39
|
-
woffToTtf,
|
|
40
|
-
} from '@fontmin-rs/binding'
|
|
41
|
-
import { parse as parseJsonc } from 'jsonc-parser'
|
|
42
|
-
import { glob } from 'tinyglobby'
|
|
43
|
-
import { withCacheLock } from './cache-lock.mjs'
|
|
3
|
+
import { runCli } from '../dist/cli.mjs'
|
|
44
4
|
|
|
45
|
-
|
|
46
|
-
'fontmin.config.ts',
|
|
47
|
-
'fontmin.config.mts',
|
|
48
|
-
'fontmin.config.mjs',
|
|
49
|
-
'fontmin.config.cjs',
|
|
50
|
-
'fontmin.config.json',
|
|
51
|
-
'fontmin.config.jsonc',
|
|
52
|
-
]
|
|
53
|
-
const MODULE_CONFIG_EXTENSIONS = new Set(['.ts', '.mts', '.mjs', '.cjs'])
|
|
54
|
-
const INIT_CONFIG_FILE = 'fontmin.config.jsonc'
|
|
55
|
-
const CACHE_SCHEMA_VERSION = 'v1'
|
|
56
|
-
const FONTMIN_VERSION = '0.1.1'
|
|
57
|
-
const DEFAULT_CACHE_DIR = 'node_modules/.cache/fontmin-rs'
|
|
58
|
-
let emitWarnings = true
|
|
59
|
-
let temporaryFileCounter = 0
|
|
60
|
-
const DEFAULT_INIT_CONFIG = `{
|
|
61
|
-
// Generated by fontmin-rs init.
|
|
62
|
-
"input": ["fonts/*.ttf"],
|
|
63
|
-
"outDir": "build",
|
|
64
|
-
"subset": {
|
|
65
|
-
"text": "Hello",
|
|
66
|
-
"basicText": true
|
|
67
|
-
},
|
|
68
|
-
"outputs": [
|
|
69
|
-
{ "format": "woff2" },
|
|
70
|
-
{ "format": "woff" },
|
|
71
|
-
{ "format": "css" }
|
|
72
|
-
],
|
|
73
|
-
"css": {
|
|
74
|
-
"fontFamily": "MyFont",
|
|
75
|
-
"fontPath": "./",
|
|
76
|
-
"fontDisplay": "swap"
|
|
77
|
-
},
|
|
78
|
-
"cache": {
|
|
79
|
-
"enabled": true
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
`
|
|
83
|
-
|
|
84
|
-
const argv = process.argv.slice(2)
|
|
85
|
-
const command = argv[0]
|
|
86
|
-
const commandArgs = argv.slice(1)
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
if (
|
|
90
|
-
command === undefined ||
|
|
91
|
-
command === '--help' ||
|
|
92
|
-
command === '-h' ||
|
|
93
|
-
commandArgs.includes('--help') ||
|
|
94
|
-
commandArgs.includes('-h')
|
|
95
|
-
) {
|
|
96
|
-
usage(process.stdout)
|
|
97
|
-
} else if (command === '--version' || command === '-V') {
|
|
98
|
-
process.stdout.write(`${FONTMIN_VERSION}\n`)
|
|
99
|
-
} else if (command === 'doctor') {
|
|
100
|
-
assertNoUnexpectedArgs(commandArgs, 0)
|
|
101
|
-
process.stdout.write('fontmin-rs doctor ok\n')
|
|
102
|
-
} else if (command === 'subset') {
|
|
103
|
-
await subsetCommand(commandArgs)
|
|
104
|
-
} else if (command === 'coverage') {
|
|
105
|
-
await coverageCommand(commandArgs)
|
|
106
|
-
} else if (command === 'convert') {
|
|
107
|
-
await convertCommand(commandArgs)
|
|
108
|
-
} else if (command === 'build') {
|
|
109
|
-
await buildCommand(commandArgs)
|
|
110
|
-
} else if (command === 'inspect') {
|
|
111
|
-
await inspectCommand(commandArgs)
|
|
112
|
-
} else if (command === 'init') {
|
|
113
|
-
assertNoUnexpectedArgs(commandArgs, 0)
|
|
114
|
-
await initCommand()
|
|
115
|
-
} else if (command === 'bench') {
|
|
116
|
-
await benchCommand(commandArgs)
|
|
117
|
-
} else {
|
|
118
|
-
usage(process.stderr)
|
|
119
|
-
process.exitCode = 1
|
|
120
|
-
}
|
|
121
|
-
} catch (error) {
|
|
122
|
-
console.error(error instanceof Error ? error.message : String(error))
|
|
123
|
-
process.exitCode = 1
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function subsetCommand(args) {
|
|
127
|
-
const output = readOption(args, ['-o', '--output'])
|
|
128
|
-
const subsetOptions = await subsetOptionsFromArgs(args)
|
|
129
|
-
const basicText = readFlag(args, ['-b', '--basic-text'])
|
|
130
|
-
assertNoUnexpectedArgs(args)
|
|
131
|
-
const [input] = args
|
|
132
|
-
|
|
133
|
-
requireValue(input, 'subset requires an input font')
|
|
134
|
-
requireValue(output, 'subset requires -o, --output')
|
|
135
|
-
if (
|
|
136
|
-
subsetOptions.text === undefined &&
|
|
137
|
-
subsetOptions.unicodes.length === 0 &&
|
|
138
|
-
!basicText
|
|
139
|
-
) {
|
|
140
|
-
throw new Error(
|
|
141
|
-
'subset requires --text, --text-file, --unicodes, or --basic-text',
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const contents = await readFile(input)
|
|
146
|
-
const subset = subsetWithCoverage(contents, {
|
|
147
|
-
basicText,
|
|
148
|
-
missingGlyphs: subsetOptions.missingGlyphs,
|
|
149
|
-
text: subsetOptions.text,
|
|
150
|
-
unicodes: subsetOptions.unicodes,
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
await writeOutput(output, subset)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function coverageCommand(args) {
|
|
157
|
-
const json = readFlag(args, ['--json'])
|
|
158
|
-
const basicText = readFlag(args, ['-b', '--basic-text'])
|
|
159
|
-
const options = await subsetOptionsFromArgs(args)
|
|
160
|
-
assertNoUnexpectedArgs(args)
|
|
161
|
-
const [input] = args
|
|
162
|
-
|
|
163
|
-
requireValue(input, 'coverage requires an input font')
|
|
164
|
-
if (
|
|
165
|
-
options.text === undefined &&
|
|
166
|
-
options.unicodes.length === 0 &&
|
|
167
|
-
!basicText
|
|
168
|
-
) {
|
|
169
|
-
throw new Error(
|
|
170
|
-
'coverage requires --text, --text-file, --unicodes, or --basic-text',
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const report = analyzeCoverage(await readFile(input), {
|
|
175
|
-
basicText,
|
|
176
|
-
text: options.text,
|
|
177
|
-
unicodes: options.unicodes,
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
if (json) {
|
|
181
|
-
process.stdout.write(`${JSON.stringify(report, undefined, 2)}\n`)
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
process.stdout.write(
|
|
186
|
-
`coverage: ${report.coveragePercent.toFixed(2)}% (${report.supported.length}/${report.requested.length})\nrequested: ${report.requested.length}\nsupported: ${report.supported.length}\nmissing: ${report.missing.length}\n`,
|
|
187
|
-
)
|
|
188
|
-
const warning = missingGlyphMessage(report)
|
|
189
|
-
if (warning !== undefined) {
|
|
190
|
-
process.stdout.write(`${warning}\n`)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async function convertCommand(args) {
|
|
195
|
-
const output = readOption(args, ['-o', '--output'])
|
|
196
|
-
const format = readOption(args, ['-f', '--format'])
|
|
197
|
-
const variationCoordinates = parseVariations(
|
|
198
|
-
readOptions(args, ['--variation']),
|
|
199
|
-
)
|
|
200
|
-
assertNoUnexpectedArgs(args)
|
|
201
|
-
const [input] = args
|
|
202
|
-
|
|
203
|
-
requireValue(input, 'convert requires an input font')
|
|
204
|
-
requireValue(output, 'convert requires -o, --output')
|
|
205
|
-
requireValue(format, 'convert requires -f, --format')
|
|
206
|
-
|
|
207
|
-
const contents = await readFile(input)
|
|
208
|
-
const converted = convertFont(contents, format, { variationCoordinates })
|
|
209
|
-
|
|
210
|
-
await writeOutput(output, converted)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
async function benchCommand(args) {
|
|
214
|
-
const json = readFlag(args, ['--json'])
|
|
215
|
-
const basicText = readFlag(args, ['-b', '--basic-text'])
|
|
216
|
-
const subsetOptions = await subsetOptionsFromArgs(args)
|
|
217
|
-
assertNoUnexpectedArgs(args)
|
|
218
|
-
const [input] = args
|
|
219
|
-
|
|
220
|
-
requireValue(input, 'bench requires an input font')
|
|
221
|
-
if (
|
|
222
|
-
subsetOptions.text === undefined &&
|
|
223
|
-
subsetOptions.unicodes.length === 0 &&
|
|
224
|
-
!basicText
|
|
225
|
-
) {
|
|
226
|
-
throw new Error(
|
|
227
|
-
'bench requires --text, --text-file, --unicodes, or --basic-text',
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const contents = await readFile(input)
|
|
232
|
-
const startedAt = process.hrtime.bigint()
|
|
233
|
-
const subset = subsetWithCoverage(contents, {
|
|
234
|
-
basicText,
|
|
235
|
-
missingGlyphs: subsetOptions.missingGlyphs,
|
|
236
|
-
text: subsetOptions.text,
|
|
237
|
-
unicodes: subsetOptions.unicodes,
|
|
238
|
-
})
|
|
239
|
-
const elapsedMs = Math.round(
|
|
240
|
-
Number(process.hrtime.bigint() - startedAt) / 1_000_000,
|
|
241
|
-
)
|
|
242
|
-
const report = {
|
|
243
|
-
elapsedMs,
|
|
244
|
-
inputBytes: contents.byteLength,
|
|
245
|
-
operation: 'subset',
|
|
246
|
-
outputBytes: subset.byteLength,
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (json) {
|
|
250
|
-
process.stdout.write(`${JSON.stringify(report)}\n`)
|
|
251
|
-
} else {
|
|
252
|
-
process.stdout.write(
|
|
253
|
-
`fontmin-rs bench subset completed in ${elapsedMs} ms\ninput: ${report.inputBytes} bytes\noutput: ${report.outputBytes} bytes\n`,
|
|
254
|
-
)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async function buildCommand(args) {
|
|
259
|
-
const showTime = readFlag(args, ['-T', '--show-time'])
|
|
260
|
-
const silent = readFlag(args, ['--silent'])
|
|
261
|
-
const startedAt = showTime && !silent ? process.hrtime.bigint() : undefined
|
|
262
|
-
|
|
263
|
-
emitWarnings = !silent
|
|
264
|
-
try {
|
|
265
|
-
await runBuildCommand(args)
|
|
266
|
-
reportShowTime(startedAt)
|
|
267
|
-
} finally {
|
|
268
|
-
emitWarnings = true
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async function runBuildCommand(args) {
|
|
273
|
-
const configPath = readOption(args, ['-c', '--config'])
|
|
274
|
-
const cache = readFlag(args, ['--cache'])
|
|
275
|
-
const noCache = readFlag(args, ['--no-cache'])
|
|
276
|
-
const noOriginal = readFlag(args, ['--no-original'])
|
|
277
|
-
readFlag(args, ['-d', '--deflate-woff'])
|
|
278
|
-
const cssGlyph = readFlag(args, ['--css-glyph'])
|
|
279
|
-
const cssUnicodeRanges = parseUnicodeRanges(
|
|
280
|
-
readOptions(args, ['--css-unicode-range']),
|
|
281
|
-
)
|
|
282
|
-
const deliverySlices = parseDeliverySlices(
|
|
283
|
-
readOptions(args, ['--delivery-slice']),
|
|
284
|
-
)
|
|
285
|
-
const outDir = readOption(args, ['-o', '--out-dir'])
|
|
286
|
-
const formats = readOption(args, ['--formats'])
|
|
287
|
-
const preset = readOption(args, ['--preset'])
|
|
288
|
-
const basicText = readFlag(args, ['-b', '--basic-text'])
|
|
289
|
-
const fontFamily = readOption(args, ['--font-family'])
|
|
290
|
-
const fontPath = readOption(args, ['--font-path'])
|
|
291
|
-
const variationCoordinates = parseVariations(
|
|
292
|
-
readOptions(args, ['--variation']),
|
|
293
|
-
)
|
|
294
|
-
const subsetOptions = await subsetOptionsFromArgs(args)
|
|
295
|
-
assertNoUnknownOptions(args)
|
|
296
|
-
|
|
297
|
-
if (cache && noCache) {
|
|
298
|
-
throw new Error('build accepts only one of --cache or --no-cache')
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const cacheOverride = cacheOverrideFromFlags(cache, noCache)
|
|
302
|
-
|
|
303
|
-
if (configPath !== undefined) {
|
|
304
|
-
if (isIconfontPreset(preset)) {
|
|
305
|
-
await buildIconfontConfigCommand(configPath, {
|
|
306
|
-
fontFamily,
|
|
307
|
-
fontPath,
|
|
308
|
-
formats,
|
|
309
|
-
inputs: [...args],
|
|
310
|
-
cacheOverride,
|
|
311
|
-
cssUnicodeRanges,
|
|
312
|
-
deliverySlices,
|
|
313
|
-
outDir,
|
|
314
|
-
variationCoordinates,
|
|
315
|
-
})
|
|
316
|
-
return
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
await buildConfigCommand(configPath, {
|
|
320
|
-
basicText,
|
|
321
|
-
cacheOverride,
|
|
322
|
-
cssGlyph,
|
|
323
|
-
cssUnicodeRanges,
|
|
324
|
-
deliverySlices,
|
|
325
|
-
fontFamily,
|
|
326
|
-
fontPath,
|
|
327
|
-
formats,
|
|
328
|
-
inputs: [...args],
|
|
329
|
-
noOriginal,
|
|
330
|
-
outDir,
|
|
331
|
-
preset,
|
|
332
|
-
subsetOptions,
|
|
333
|
-
variationCoordinates,
|
|
334
|
-
})
|
|
335
|
-
return
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (args.length === 0) {
|
|
339
|
-
const foundConfigPath = await findConfig()
|
|
340
|
-
|
|
341
|
-
if (isIconfontPreset(preset)) {
|
|
342
|
-
await buildIconfontConfigCommand(foundConfigPath, {
|
|
343
|
-
fontFamily,
|
|
344
|
-
fontPath,
|
|
345
|
-
formats,
|
|
346
|
-
cacheOverride,
|
|
347
|
-
cssUnicodeRanges,
|
|
348
|
-
deliverySlices,
|
|
349
|
-
outDir,
|
|
350
|
-
variationCoordinates,
|
|
351
|
-
})
|
|
352
|
-
return
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
await buildConfigCommand(foundConfigPath, {
|
|
356
|
-
basicText,
|
|
357
|
-
cacheOverride,
|
|
358
|
-
cssGlyph,
|
|
359
|
-
cssUnicodeRanges,
|
|
360
|
-
deliverySlices,
|
|
361
|
-
fontFamily,
|
|
362
|
-
fontPath,
|
|
363
|
-
formats,
|
|
364
|
-
noOriginal,
|
|
365
|
-
outDir,
|
|
366
|
-
preset,
|
|
367
|
-
subsetOptions,
|
|
368
|
-
variationCoordinates,
|
|
369
|
-
})
|
|
370
|
-
return
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const inputPatterns = [...args]
|
|
374
|
-
const [input] = inputPatterns
|
|
375
|
-
|
|
376
|
-
requireValue(input, 'build requires an input font')
|
|
377
|
-
requireValue(outDir, 'build requires -o, --out-dir')
|
|
378
|
-
|
|
379
|
-
if (isIconfontPreset(preset)) {
|
|
380
|
-
if (formats !== undefined) {
|
|
381
|
-
throw new Error('build accepts only one of --formats or --preset')
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const inputs = await expandInputPaths(inputPatterns, process.cwd())
|
|
385
|
-
const cacheOptions = normalizeCacheOptions(
|
|
386
|
-
undefined,
|
|
387
|
-
process.cwd(),
|
|
388
|
-
cacheOverride,
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
await buildIconfontCommand(
|
|
392
|
-
inputs,
|
|
393
|
-
outDir,
|
|
394
|
-
fontFamily,
|
|
395
|
-
fontPath ?? './',
|
|
396
|
-
[],
|
|
397
|
-
{},
|
|
398
|
-
cacheOptions,
|
|
399
|
-
cssUnicodeRanges,
|
|
400
|
-
)
|
|
401
|
-
return
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const inputs = await expandInputPaths(inputPatterns, process.cwd())
|
|
405
|
-
const outputFormats = filterOriginalOutput(
|
|
406
|
-
outputFormatsFromArgs(formats, preset),
|
|
407
|
-
noOriginal,
|
|
408
|
-
)
|
|
409
|
-
const cacheOptions = normalizeCacheOptions(
|
|
410
|
-
undefined,
|
|
411
|
-
process.cwd(),
|
|
412
|
-
cacheOverride,
|
|
413
|
-
)
|
|
414
|
-
if (outputFormats.length === 0) {
|
|
415
|
-
throw new Error('build requires at least one non-original output format')
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
await mkdir(outDir, { recursive: true })
|
|
419
|
-
|
|
420
|
-
for (const input of inputs) {
|
|
421
|
-
await buildDirectInput(input, outDir, {
|
|
422
|
-
basicText,
|
|
423
|
-
cacheOptions,
|
|
424
|
-
cssGlyph,
|
|
425
|
-
cssUnicodeRanges,
|
|
426
|
-
deliverySlices,
|
|
427
|
-
fontFamily,
|
|
428
|
-
fontPath,
|
|
429
|
-
outputFormats,
|
|
430
|
-
subsetOptions,
|
|
431
|
-
variationCoordinates,
|
|
432
|
-
})
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
async function buildDirectInput(
|
|
437
|
-
input,
|
|
438
|
-
outDir,
|
|
439
|
-
{
|
|
440
|
-
basicText,
|
|
441
|
-
cacheOptions,
|
|
442
|
-
cssGlyph,
|
|
443
|
-
cssUnicodeRanges,
|
|
444
|
-
deliverySlices,
|
|
445
|
-
fontFamily,
|
|
446
|
-
fontPath,
|
|
447
|
-
outputFormats,
|
|
448
|
-
subsetOptions,
|
|
449
|
-
variationCoordinates,
|
|
450
|
-
},
|
|
451
|
-
) {
|
|
452
|
-
const contents = await readFile(input)
|
|
453
|
-
const ttfContents = convertFont(contents, 'ttf', { variationCoordinates })
|
|
454
|
-
const baseName = basename(input, extname(input))
|
|
455
|
-
const cacheKey = cacheOptions.enabled
|
|
456
|
-
? cacheKeyForBuildInput({
|
|
457
|
-
contents,
|
|
458
|
-
input,
|
|
459
|
-
kind: 'direct',
|
|
460
|
-
options: {
|
|
461
|
-
basicText,
|
|
462
|
-
cssGlyph,
|
|
463
|
-
cssUnicodeRanges,
|
|
464
|
-
deliverySlices,
|
|
465
|
-
fontFamily,
|
|
466
|
-
fontPath,
|
|
467
|
-
outputFormats,
|
|
468
|
-
subset: subsetOptions,
|
|
469
|
-
variationCoordinates,
|
|
470
|
-
},
|
|
471
|
-
})
|
|
472
|
-
: undefined
|
|
473
|
-
const cachedOutputs =
|
|
474
|
-
cacheKey === undefined
|
|
475
|
-
? undefined
|
|
476
|
-
: await readCachedBuildOutputs(cacheOptions.dir, cacheKey)
|
|
477
|
-
|
|
478
|
-
if (cachedOutputs !== undefined) {
|
|
479
|
-
await writeBuildOutputs(outDir, cachedOutputs)
|
|
480
|
-
return
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const source =
|
|
484
|
-
subsetOptions.text === undefined &&
|
|
485
|
-
subsetOptions.unicodes.length === 0 &&
|
|
486
|
-
!basicText
|
|
487
|
-
? ttfContents
|
|
488
|
-
: subsetWithCoverage(ttfContents, {
|
|
489
|
-
basicText,
|
|
490
|
-
missingGlyphs: subsetOptions.missingGlyphs,
|
|
491
|
-
text: subsetOptions.text,
|
|
492
|
-
unicodes: subsetOptions.unicodes,
|
|
493
|
-
})
|
|
494
|
-
const cssGlyphs = cssGlyph ? cssGlyphsFromSubset(subsetOptions) : []
|
|
495
|
-
const fontSources =
|
|
496
|
-
deliverySlices.length === 0
|
|
497
|
-
? [{ baseName, contents: source }]
|
|
498
|
-
: deliverySlices.map(slice => ({
|
|
499
|
-
baseName: `${baseName}-${slice.name}`,
|
|
500
|
-
contents: subsetTtf(source, {
|
|
501
|
-
missingGlyphs: 'ignore',
|
|
502
|
-
unicodeRanges: slice.unicodeRanges,
|
|
503
|
-
}),
|
|
504
|
-
unicodeRanges: slice.unicodeRanges,
|
|
505
|
-
}))
|
|
506
|
-
const cssSources = []
|
|
507
|
-
const outputs = []
|
|
508
|
-
|
|
509
|
-
for (const fontSource of fontSources) {
|
|
510
|
-
for (const format of outputFormats.filter(format => format !== 'css')) {
|
|
511
|
-
const fileName = `${fontSource.baseName}.${format}`
|
|
512
|
-
const output = convertFont(fontSource.contents, format)
|
|
513
|
-
|
|
514
|
-
outputs.push({ contents: output, fileName })
|
|
515
|
-
cssSources.push({
|
|
516
|
-
...(cssGlyphs.length > 0 && { glyphs: cssGlyphs }),
|
|
517
|
-
...(fontSource.unicodeRanges !== undefined && {
|
|
518
|
-
unicodeRanges: fontSource.unicodeRanges,
|
|
519
|
-
}),
|
|
520
|
-
fileName,
|
|
521
|
-
format,
|
|
522
|
-
})
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
if (outputFormats.includes('css')) {
|
|
527
|
-
if (cssSources.length === 0) {
|
|
528
|
-
throw new Error('build CSS output requires at least one font format')
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
outputs.push({
|
|
532
|
-
contents: Buffer.from(
|
|
533
|
-
generateFontFaceCss(cssSources, {
|
|
534
|
-
fontFamily: fontFamily ?? baseName,
|
|
535
|
-
fontPath: fontPath ?? './',
|
|
536
|
-
glyph: cssGlyph,
|
|
537
|
-
unicodeRanges: cssUnicodeRanges,
|
|
538
|
-
}),
|
|
539
|
-
),
|
|
540
|
-
fileName: `${fontSources[0].baseName}.css`,
|
|
541
|
-
})
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
await writeBuildOutputs(outDir, outputs)
|
|
545
|
-
|
|
546
|
-
if (cacheKey !== undefined) {
|
|
547
|
-
await writeCachedBuildOutputs(cacheOptions.dir, cacheKey, outputs)
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function reportShowTime(startedAt) {
|
|
552
|
-
if (startedAt === undefined) {
|
|
553
|
-
return
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1_000_000
|
|
557
|
-
|
|
558
|
-
process.stdout.write(
|
|
559
|
-
`fontmin-rs build completed in ${Math.round(elapsedMs)} ms\n`,
|
|
560
|
-
)
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
async function buildIconfontCommand(
|
|
564
|
-
inputs,
|
|
565
|
-
outDir,
|
|
566
|
-
fontFamily,
|
|
567
|
-
fontPath,
|
|
568
|
-
outputs = [],
|
|
569
|
-
css = {},
|
|
570
|
-
cacheOptions = normalizeCacheOptions(undefined, process.cwd()),
|
|
571
|
-
cssUnicodeRanges = css.unicodeRanges ?? [],
|
|
572
|
-
) {
|
|
573
|
-
if (inputs.length === 0) {
|
|
574
|
-
throw new Error('build requires at least one input font')
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const family = fontFamily ?? 'iconfont'
|
|
578
|
-
const icons = await Promise.all(
|
|
579
|
-
inputs.map(async input => ({
|
|
580
|
-
contents: await readFile(input, 'utf8'),
|
|
581
|
-
name: basename(input, extname(input)),
|
|
582
|
-
})),
|
|
583
|
-
)
|
|
584
|
-
const cacheKey = cacheOptions.enabled
|
|
585
|
-
? cacheKeyForIconfontBuild({
|
|
586
|
-
icons,
|
|
587
|
-
inputs,
|
|
588
|
-
options: {
|
|
589
|
-
css,
|
|
590
|
-
fontFamily,
|
|
591
|
-
fontPath,
|
|
592
|
-
outputs,
|
|
593
|
-
},
|
|
594
|
-
})
|
|
595
|
-
: undefined
|
|
596
|
-
const cachedOutputs =
|
|
597
|
-
cacheKey === undefined
|
|
598
|
-
? undefined
|
|
599
|
-
: await readCachedBuildOutputs(cacheOptions.dir, cacheKey)
|
|
600
|
-
|
|
601
|
-
if (cachedOutputs !== undefined) {
|
|
602
|
-
await writeBuildOutputs(outDir, cachedOutputs)
|
|
603
|
-
return
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
const ttf = svgsToTtf(icons, {
|
|
607
|
-
fontName: family,
|
|
608
|
-
})
|
|
609
|
-
const fileName = outputFileName(outputs, 'ttf', 'iconfont.ttf')
|
|
610
|
-
const cssFileName = outputFileName(
|
|
611
|
-
outputs,
|
|
612
|
-
'css',
|
|
613
|
-
`${basename(fileName, extname(fileName))}.${css.target ?? 'css'}`,
|
|
614
|
-
)
|
|
615
|
-
const glyphs = icons.map((icon, index) => ({
|
|
616
|
-
name: icon.name,
|
|
617
|
-
unicode: 57_345 + index,
|
|
618
|
-
}))
|
|
619
|
-
const buildOutputs = [
|
|
620
|
-
{
|
|
621
|
-
contents: ttf,
|
|
622
|
-
fileName,
|
|
623
|
-
},
|
|
624
|
-
{
|
|
625
|
-
contents: Buffer.from(
|
|
626
|
-
generateFontFaceCss(
|
|
627
|
-
[
|
|
628
|
-
{
|
|
629
|
-
...(css.base64 === true && { contents: ttf }),
|
|
630
|
-
fileName,
|
|
631
|
-
format: 'ttf',
|
|
632
|
-
glyphs,
|
|
633
|
-
},
|
|
634
|
-
],
|
|
635
|
-
{
|
|
636
|
-
asFileName: css.asFileName ?? true,
|
|
637
|
-
base64: css.base64,
|
|
638
|
-
fontDisplay: css.fontDisplay,
|
|
639
|
-
fontFamily: family,
|
|
640
|
-
fontPath,
|
|
641
|
-
glyph: true,
|
|
642
|
-
iconPrefix: css.iconPrefix,
|
|
643
|
-
local: css.local,
|
|
644
|
-
unicodeRanges: cssUnicodeRanges,
|
|
645
|
-
},
|
|
646
|
-
),
|
|
647
|
-
),
|
|
648
|
-
fileName: cssFileName,
|
|
649
|
-
},
|
|
650
|
-
]
|
|
651
|
-
|
|
652
|
-
await writeBuildOutputs(outDir, buildOutputs)
|
|
653
|
-
|
|
654
|
-
if (cacheKey !== undefined) {
|
|
655
|
-
await writeCachedBuildOutputs(cacheOptions.dir, cacheKey, buildOutputs)
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
async function buildConfigCommand(
|
|
660
|
-
configPath,
|
|
661
|
-
{
|
|
662
|
-
basicText,
|
|
663
|
-
cacheOverride,
|
|
664
|
-
cssGlyph,
|
|
665
|
-
cssUnicodeRanges = [],
|
|
666
|
-
deliverySlices = [],
|
|
667
|
-
fontFamily,
|
|
668
|
-
fontPath,
|
|
669
|
-
formats,
|
|
670
|
-
inputs: inputOverrides = [],
|
|
671
|
-
noOriginal,
|
|
672
|
-
outDir: outDirOverride,
|
|
673
|
-
preset,
|
|
674
|
-
subsetOptions,
|
|
675
|
-
variationCoordinates = {},
|
|
676
|
-
} = {},
|
|
677
|
-
) {
|
|
678
|
-
const resolvedConfigPath = resolve(configPath)
|
|
679
|
-
const config = await readConfig(resolvedConfigPath)
|
|
680
|
-
const cwd =
|
|
681
|
-
typeof config.cwd === 'string'
|
|
682
|
-
? resolve(config.cwd)
|
|
683
|
-
: dirname(resolvedConfigPath)
|
|
684
|
-
const inputs =
|
|
685
|
-
inputOverrides.length > 0 ? inputOverrides : (config.input ?? [])
|
|
686
|
-
const outDir = resolve(cwd, outDirOverride ?? config.outDir ?? 'build')
|
|
687
|
-
const outputFormats = outputFormatsForConfig(config.outputs, {
|
|
688
|
-
formats,
|
|
689
|
-
noOriginal,
|
|
690
|
-
preset,
|
|
691
|
-
})
|
|
692
|
-
const cacheOptions = normalizeCacheOptions(config.cache, cwd, cacheOverride)
|
|
693
|
-
const resolvedVariationCoordinates = {
|
|
694
|
-
...config.otf?.variationCoordinates,
|
|
695
|
-
...variationCoordinates,
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (inputs.length === 0) {
|
|
699
|
-
throw new Error('build config requires at least one input')
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const inputPaths = await expandInputPaths(inputs, cwd)
|
|
703
|
-
|
|
704
|
-
if (
|
|
705
|
-
fontFamily !== undefined ||
|
|
706
|
-
fontPath !== undefined ||
|
|
707
|
-
cssGlyph === true ||
|
|
708
|
-
cssUnicodeRanges.length > 0
|
|
709
|
-
) {
|
|
710
|
-
config.css = {
|
|
711
|
-
...config.css,
|
|
712
|
-
...(cssGlyph === true && { glyph: true }),
|
|
713
|
-
...(cssUnicodeRanges.length > 0 && {
|
|
714
|
-
unicodeRanges: cssUnicodeRanges,
|
|
715
|
-
}),
|
|
716
|
-
...(fontFamily !== undefined && { fontFamily }),
|
|
717
|
-
...(fontPath !== undefined && { fontPath }),
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
if (deliverySlices.length > 0) {
|
|
721
|
-
config.delivery = { slices: deliverySlices }
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
applySubsetOverrides(config, { basicText, subsetOptions })
|
|
725
|
-
|
|
726
|
-
if (config.clean === true) {
|
|
727
|
-
await cleanOutputDirectory(cwd, outDir, [resolvedConfigPath, ...inputPaths])
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
await mkdir(outDir, { recursive: true })
|
|
731
|
-
|
|
732
|
-
for (const inputPath of inputPaths) {
|
|
733
|
-
await buildConfigInput(
|
|
734
|
-
inputPath,
|
|
735
|
-
outDir,
|
|
736
|
-
outputFormats,
|
|
737
|
-
config,
|
|
738
|
-
cwd,
|
|
739
|
-
cacheOptions,
|
|
740
|
-
resolvedVariationCoordinates,
|
|
741
|
-
)
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
async function buildIconfontConfigCommand(
|
|
746
|
-
configPath,
|
|
747
|
-
{
|
|
748
|
-
cacheOverride,
|
|
749
|
-
cssUnicodeRanges = [],
|
|
750
|
-
deliverySlices = [],
|
|
751
|
-
fontFamily,
|
|
752
|
-
fontPath,
|
|
753
|
-
formats,
|
|
754
|
-
inputs: inputOverrides = [],
|
|
755
|
-
outDir: outDirOverride,
|
|
756
|
-
},
|
|
757
|
-
) {
|
|
758
|
-
if (formats !== undefined) {
|
|
759
|
-
throw new Error('build accepts only one of --formats or --preset')
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
const resolvedConfigPath = resolve(configPath)
|
|
763
|
-
const config = await readConfig(resolvedConfigPath)
|
|
764
|
-
const cwd =
|
|
765
|
-
typeof config.cwd === 'string'
|
|
766
|
-
? resolve(config.cwd)
|
|
767
|
-
: dirname(resolvedConfigPath)
|
|
768
|
-
const outDir = resolve(cwd, outDirOverride ?? config.outDir ?? 'build')
|
|
769
|
-
const inputs = await expandConfigInputPaths(
|
|
770
|
-
inputOverrides.length > 0 ? inputOverrides : (config.input ?? []),
|
|
771
|
-
cwd,
|
|
772
|
-
)
|
|
773
|
-
const css = config.css ?? {}
|
|
774
|
-
const cacheOptions = normalizeCacheOptions(config.cache, cwd, cacheOverride)
|
|
775
|
-
|
|
776
|
-
if (cssUnicodeRanges.length > 0) {
|
|
777
|
-
css.unicodeRanges = cssUnicodeRanges
|
|
778
|
-
}
|
|
779
|
-
if (deliverySlices.length > 0) {
|
|
780
|
-
config.delivery = { slices: deliverySlices }
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
if (config.clean === true) {
|
|
784
|
-
await cleanOutputDirectory(cwd, outDir, [resolvedConfigPath, ...inputs])
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
await buildIconfontCommand(
|
|
788
|
-
inputs,
|
|
789
|
-
outDir,
|
|
790
|
-
fontFamily ?? css.fontFamily,
|
|
791
|
-
fontPath ?? css.fontPath ?? './',
|
|
792
|
-
config.outputs ?? [],
|
|
793
|
-
css,
|
|
794
|
-
cacheOptions,
|
|
795
|
-
css.unicodeRanges,
|
|
796
|
-
)
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
function expandConfigInputPaths(inputs, cwd) {
|
|
800
|
-
return expandInputPaths(inputs, cwd)
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
async function expandInputPaths(inputs, cwd) {
|
|
804
|
-
if (inputs.length === 0) {
|
|
805
|
-
throw new Error('build config requires at least one input')
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
const paths = []
|
|
809
|
-
|
|
810
|
-
for (const input of inputs) {
|
|
811
|
-
if (typeof input !== 'string') {
|
|
812
|
-
throw new TypeError('build config input entries must be file paths')
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
paths.push(...(await expandInputPath(input, cwd)))
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
return paths
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
function outputFileName(outputs, format, fallback) {
|
|
822
|
-
const output = outputs.find(output => output?.format === format)
|
|
823
|
-
|
|
824
|
-
if (typeof output?.fileName === 'string') {
|
|
825
|
-
return output.fileName
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
if (typeof output?.ext === 'string') {
|
|
829
|
-
return `${basename(fallback, extname(fallback))}.${normalizeExtension(output.ext)}`
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
return fallback
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
function applySubsetOverrides(config, { basicText, subsetOptions } = {}) {
|
|
836
|
-
if (
|
|
837
|
-
subsetOptions === undefined ||
|
|
838
|
-
(subsetOptions.text === undefined &&
|
|
839
|
-
subsetOptions.unicodes.length === 0 &&
|
|
840
|
-
basicText !== true &&
|
|
841
|
-
subsetOptions.missingGlyphs === undefined)
|
|
842
|
-
) {
|
|
843
|
-
return
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
config.subset = {
|
|
847
|
-
...config.subset,
|
|
848
|
-
...(subsetOptions.text !== undefined && { text: subsetOptions.text }),
|
|
849
|
-
...(subsetOptions.unicodes.length > 0 && {
|
|
850
|
-
unicodes: subsetOptions.unicodes,
|
|
851
|
-
}),
|
|
852
|
-
...(basicText === true && { basicText }),
|
|
853
|
-
...(subsetOptions.missingGlyphs !== undefined && {
|
|
854
|
-
missingGlyphs: subsetOptions.missingGlyphs,
|
|
855
|
-
}),
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
async function buildConfigInput(
|
|
860
|
-
input,
|
|
861
|
-
outDir,
|
|
862
|
-
outputFormats,
|
|
863
|
-
config,
|
|
864
|
-
cwd,
|
|
865
|
-
cacheOptions,
|
|
866
|
-
variationCoordinates,
|
|
867
|
-
) {
|
|
868
|
-
const contents = await readFile(input)
|
|
869
|
-
const ttfContents = convertFont(contents, 'ttf', { variationCoordinates })
|
|
870
|
-
const subset = await resolveSubsetTextFile(config.subset ?? {}, cwd)
|
|
871
|
-
const unicodes = subset.unicodes ?? []
|
|
872
|
-
const deliverySlices = normalizeDeliverySlices(config.delivery?.slices ?? [])
|
|
873
|
-
const cacheKey = cacheOptions.enabled
|
|
874
|
-
? cacheKeyForBuildInput({
|
|
875
|
-
contents,
|
|
876
|
-
input,
|
|
877
|
-
kind: 'config',
|
|
878
|
-
options: {
|
|
879
|
-
css: config.css,
|
|
880
|
-
deliverySlices,
|
|
881
|
-
outputFormats,
|
|
882
|
-
outputs: config.outputs,
|
|
883
|
-
subset,
|
|
884
|
-
variationCoordinates,
|
|
885
|
-
},
|
|
886
|
-
})
|
|
887
|
-
: undefined
|
|
888
|
-
const cachedOutputs =
|
|
889
|
-
cacheKey === undefined
|
|
890
|
-
? undefined
|
|
891
|
-
: await readCachedBuildOutputs(cacheOptions.dir, cacheKey)
|
|
892
|
-
|
|
893
|
-
if (cachedOutputs !== undefined) {
|
|
894
|
-
await writeBuildOutputs(outDir, cachedOutputs)
|
|
895
|
-
return
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
const source =
|
|
899
|
-
subset.text === undefined &&
|
|
900
|
-
unicodes.length === 0 &&
|
|
901
|
-
subset.basicText !== true
|
|
902
|
-
? ttfContents
|
|
903
|
-
: subsetWithCoverage(ttfContents, {
|
|
904
|
-
basicText: subset.basicText,
|
|
905
|
-
missingGlyphs: subset.missingGlyphs,
|
|
906
|
-
text: subset.text,
|
|
907
|
-
unicodes,
|
|
908
|
-
})
|
|
909
|
-
const baseName = basename(input, extname(input))
|
|
910
|
-
const css = config.css ?? {}
|
|
911
|
-
const cssGlyphs = css.glyph === true ? cssGlyphsFromSubset(subset) : []
|
|
912
|
-
const fontSources =
|
|
913
|
-
deliverySlices.length === 0
|
|
914
|
-
? [{ baseName, contents: source }]
|
|
915
|
-
: deliverySlices.map(slice => ({
|
|
916
|
-
baseName: `${baseName}-${slice.name}`,
|
|
917
|
-
contents: subsetTtf(source, {
|
|
918
|
-
missingGlyphs: 'ignore',
|
|
919
|
-
unicodeRanges: slice.unicodeRanges,
|
|
920
|
-
}),
|
|
921
|
-
unicodeRanges: slice.unicodeRanges,
|
|
922
|
-
}))
|
|
923
|
-
const cssSources = []
|
|
924
|
-
const outputs = config.outputs ?? []
|
|
925
|
-
const buildOutputs = []
|
|
926
|
-
|
|
927
|
-
for (const fontSource of fontSources) {
|
|
928
|
-
for (const format of outputFormats.filter(format => format !== 'css')) {
|
|
929
|
-
const fileName = outputFileName(
|
|
930
|
-
outputs,
|
|
931
|
-
format,
|
|
932
|
-
`${fontSource.baseName}.${format}`,
|
|
933
|
-
)
|
|
934
|
-
const output = convertFont(fontSource.contents, format)
|
|
935
|
-
|
|
936
|
-
buildOutputs.push({ contents: output, fileName })
|
|
937
|
-
cssSources.push({
|
|
938
|
-
...(css.base64 === true && { contents: output }),
|
|
939
|
-
...(cssGlyphs.length > 0 && { glyphs: cssGlyphs }),
|
|
940
|
-
...(fontSource.unicodeRanges !== undefined && {
|
|
941
|
-
unicodeRanges: fontSource.unicodeRanges,
|
|
942
|
-
}),
|
|
943
|
-
fileName,
|
|
944
|
-
format,
|
|
945
|
-
})
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
if (outputFormats.includes('css')) {
|
|
950
|
-
if (cssSources.length === 0) {
|
|
951
|
-
throw new Error('build CSS output requires at least one font format')
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
const firstCssSource = cssSources[0]
|
|
955
|
-
const cssFileName = outputFileName(
|
|
956
|
-
outputs,
|
|
957
|
-
'css',
|
|
958
|
-
`${basename(firstCssSource.fileName, extname(firstCssSource.fileName))}.${css.target ?? 'css'}`,
|
|
959
|
-
)
|
|
960
|
-
|
|
961
|
-
buildOutputs.push({
|
|
962
|
-
contents: Buffer.from(
|
|
963
|
-
generateFontFaceCss(cssSources, {
|
|
964
|
-
asFileName: css.asFileName,
|
|
965
|
-
base64: css.base64,
|
|
966
|
-
fontDisplay: css.fontDisplay,
|
|
967
|
-
fontFamily: css.fontFamily ?? baseName,
|
|
968
|
-
fontPath: css.fontPath ?? './',
|
|
969
|
-
glyph: css.glyph,
|
|
970
|
-
iconPrefix: css.iconPrefix,
|
|
971
|
-
local: css.local,
|
|
972
|
-
unicodeRanges: css.unicodeRanges,
|
|
973
|
-
}),
|
|
974
|
-
),
|
|
975
|
-
fileName: cssFileName,
|
|
976
|
-
})
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
await writeBuildOutputs(outDir, buildOutputs)
|
|
980
|
-
|
|
981
|
-
if (cacheKey !== undefined) {
|
|
982
|
-
await writeCachedBuildOutputs(cacheOptions.dir, cacheKey, buildOutputs)
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
function cssGlyphsFromSubset(subset) {
|
|
987
|
-
const seen = new Set()
|
|
988
|
-
const glyphs = []
|
|
989
|
-
|
|
990
|
-
for (const character of subset.text ?? '') {
|
|
991
|
-
const unicode = character.codePointAt(0)
|
|
992
|
-
|
|
993
|
-
if (unicode !== undefined && !seen.has(unicode)) {
|
|
994
|
-
seen.add(unicode)
|
|
995
|
-
glyphs.push({ unicode })
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
for (const unicode of subset.unicodes ?? []) {
|
|
1000
|
-
if (!seen.has(unicode)) {
|
|
1001
|
-
seen.add(unicode)
|
|
1002
|
-
glyphs.push({ unicode })
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
return glyphs
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
async function writeBuildOutputs(outDir, outputs) {
|
|
1010
|
-
await mkdir(outDir, { recursive: true })
|
|
1011
|
-
|
|
1012
|
-
for (const output of outputs) {
|
|
1013
|
-
const outputPath = resolveContainedPath(
|
|
1014
|
-
outDir,
|
|
1015
|
-
output.fileName,
|
|
1016
|
-
'output file name',
|
|
1017
|
-
)
|
|
1018
|
-
|
|
1019
|
-
await mkdir(dirname(outputPath), { recursive: true })
|
|
1020
|
-
await ensureRealPathContained(
|
|
1021
|
-
outDir,
|
|
1022
|
-
dirname(outputPath),
|
|
1023
|
-
'output file name',
|
|
1024
|
-
)
|
|
1025
|
-
await rejectSymbolicLink(outputPath)
|
|
1026
|
-
await writeFile(outputPath, output.contents)
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
async function readCachedBuildOutputs(cacheDir, key) {
|
|
1031
|
-
let manifest = null
|
|
1032
|
-
|
|
1033
|
-
try {
|
|
1034
|
-
manifest = JSON.parse(
|
|
1035
|
-
await readFile(cacheManifestPath(cacheDir, key), 'utf8'),
|
|
1036
|
-
)
|
|
1037
|
-
} catch {
|
|
1038
|
-
return
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
if (manifest.version !== CACHE_SCHEMA_VERSION || manifest.key !== key) {
|
|
1042
|
-
return
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
const entryDir = cacheEntryDir(cacheDir, key)
|
|
1046
|
-
const outputs = []
|
|
1047
|
-
|
|
1048
|
-
try {
|
|
1049
|
-
for (const record of manifest.outputs) {
|
|
1050
|
-
const cacheFile = resolveContainedPath(
|
|
1051
|
-
entryDir,
|
|
1052
|
-
record.cacheFileName,
|
|
1053
|
-
'cache file name',
|
|
1054
|
-
)
|
|
1055
|
-
|
|
1056
|
-
await ensureRealPathContained(entryDir, cacheFile, 'cache file name')
|
|
1057
|
-
outputs.push({
|
|
1058
|
-
contents: await readFile(cacheFile),
|
|
1059
|
-
fileName: record.fileName,
|
|
1060
|
-
})
|
|
1061
|
-
}
|
|
1062
|
-
} catch {
|
|
1063
|
-
return
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
return outputs
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
async function writeCachedBuildOutputs(cacheDir, key, outputs) {
|
|
1070
|
-
await withCacheLock(cacheRoot(cacheDir), async () => {
|
|
1071
|
-
const entryDir = cacheEntryDir(cacheDir, key)
|
|
1072
|
-
const records = []
|
|
1073
|
-
|
|
1074
|
-
await mkdir(entryDir, { recursive: true })
|
|
1075
|
-
|
|
1076
|
-
for (const [index, output] of outputs.entries()) {
|
|
1077
|
-
const extension = extname(output.fileName).replace(/^\./u, '') || 'bin'
|
|
1078
|
-
const cacheFileName = `${String(index).padStart(3, '0')}.${extension}`
|
|
1079
|
-
|
|
1080
|
-
await atomicWriteFile(join(entryDir, cacheFileName), output.contents)
|
|
1081
|
-
records.push({
|
|
1082
|
-
cacheFileName,
|
|
1083
|
-
fileName: output.fileName,
|
|
1084
|
-
})
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
await atomicWriteFile(
|
|
1088
|
-
cacheManifestPath(cacheDir, key),
|
|
1089
|
-
`${JSON.stringify(
|
|
1090
|
-
{
|
|
1091
|
-
key,
|
|
1092
|
-
outputs: records,
|
|
1093
|
-
version: CACHE_SCHEMA_VERSION,
|
|
1094
|
-
},
|
|
1095
|
-
undefined,
|
|
1096
|
-
2,
|
|
1097
|
-
)}\n`,
|
|
1098
|
-
)
|
|
1099
|
-
await updateBuildCacheIndex(cacheDir, key, records)
|
|
1100
|
-
})
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
async function updateBuildCacheIndex(cacheDir, key, outputs) {
|
|
1104
|
-
const indexPath = cacheIndexPath(cacheDir)
|
|
1105
|
-
let index = {
|
|
1106
|
-
entries: {},
|
|
1107
|
-
version: CACHE_SCHEMA_VERSION,
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
try {
|
|
1111
|
-
index = JSON.parse(await readFile(indexPath, 'utf8'))
|
|
1112
|
-
} catch {
|
|
1113
|
-
// A missing or corrupted cache index can be rebuilt from the next writes.
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
if (index.version !== CACHE_SCHEMA_VERSION) {
|
|
1117
|
-
index = {
|
|
1118
|
-
entries: {},
|
|
1119
|
-
version: CACHE_SCHEMA_VERSION,
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
index.entries[key] = {
|
|
1124
|
-
outputs: outputs.map(output => output.fileName),
|
|
1125
|
-
updatedAt: new Date().toISOString(),
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
await mkdir(dirname(indexPath), { recursive: true })
|
|
1129
|
-
await atomicWriteFile(indexPath, `${JSON.stringify(index, undefined, 2)}\n`)
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
function normalizeCacheOptions(options, cwd, override) {
|
|
1133
|
-
const configuredDir =
|
|
1134
|
-
typeof options === 'object' &&
|
|
1135
|
-
options !== null &&
|
|
1136
|
-
typeof options.dir === 'string'
|
|
1137
|
-
? options.dir
|
|
1138
|
-
: DEFAULT_CACHE_DIR
|
|
1139
|
-
|
|
1140
|
-
if (override === true) {
|
|
1141
|
-
return {
|
|
1142
|
-
dir: resolve(cwd, configuredDir),
|
|
1143
|
-
enabled: true,
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
if (override === false || options === undefined || options === false) {
|
|
1148
|
-
return {
|
|
1149
|
-
dir: resolve(cwd, configuredDir),
|
|
1150
|
-
enabled: false,
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
if (options === true) {
|
|
1155
|
-
return {
|
|
1156
|
-
dir: resolve(cwd, DEFAULT_CACHE_DIR),
|
|
1157
|
-
enabled: true,
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
return {
|
|
1162
|
-
dir: resolve(cwd, options.dir ?? DEFAULT_CACHE_DIR),
|
|
1163
|
-
enabled: options.enabled ?? true,
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
function cacheOverrideFromFlags(cache, noCache) {
|
|
1168
|
-
if (cache) {
|
|
1169
|
-
return true
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
if (noCache) {
|
|
1173
|
-
return false
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
function cacheKeyForBuildInput({ contents, input, kind, options }) {
|
|
1178
|
-
return sha256(
|
|
1179
|
-
stableStringify({
|
|
1180
|
-
fontminVersion: FONTMIN_VERSION,
|
|
1181
|
-
input: {
|
|
1182
|
-
hash: sha256(contents),
|
|
1183
|
-
path: input,
|
|
1184
|
-
},
|
|
1185
|
-
kind,
|
|
1186
|
-
options,
|
|
1187
|
-
schema: CACHE_SCHEMA_VERSION,
|
|
1188
|
-
}),
|
|
1189
|
-
)
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
function cacheKeyForIconfontBuild({ icons, inputs, options }) {
|
|
1193
|
-
return sha256(
|
|
1194
|
-
stableStringify({
|
|
1195
|
-
fontminVersion: FONTMIN_VERSION,
|
|
1196
|
-
icons: icons.map((icon, index) => ({
|
|
1197
|
-
hash: sha256(icon.contents),
|
|
1198
|
-
input: inputs[index],
|
|
1199
|
-
name: icon.name,
|
|
1200
|
-
})),
|
|
1201
|
-
kind: 'iconfont',
|
|
1202
|
-
options,
|
|
1203
|
-
schema: CACHE_SCHEMA_VERSION,
|
|
1204
|
-
}),
|
|
1205
|
-
)
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
function cacheRoot(cacheDir) {
|
|
1209
|
-
return join(cacheDir, CACHE_SCHEMA_VERSION)
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
function cacheIndexPath(cacheDir) {
|
|
1213
|
-
return join(cacheRoot(cacheDir), 'index.json')
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
function cacheEntryDir(cacheDir, key) {
|
|
1217
|
-
return join(cacheRoot(cacheDir), key)
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
function cacheManifestPath(cacheDir, key) {
|
|
1221
|
-
return join(cacheEntryDir(cacheDir, key), 'index.json')
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
function sha256(input) {
|
|
1225
|
-
return createHash('sha256').update(input).digest('hex')
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
function stableStringify(value) {
|
|
1229
|
-
if (value === null || typeof value !== 'object') {
|
|
1230
|
-
return JSON.stringify(value)
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
if (Array.isArray(value)) {
|
|
1234
|
-
return `[${value.map(item => stableStringify(item)).join(',')}]`
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const record = value
|
|
1238
|
-
const entries = Object.keys(record)
|
|
1239
|
-
.sort()
|
|
1240
|
-
.map(key => {
|
|
1241
|
-
return `${JSON.stringify(key)}:${stableStringify(record[key])}`
|
|
1242
|
-
})
|
|
1243
|
-
|
|
1244
|
-
return `{${entries.join(',')}}`
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
async function inspectCommand(args) {
|
|
1248
|
-
const json = readFlag(args, ['--json'])
|
|
1249
|
-
assertNoUnexpectedArgs(args)
|
|
1250
|
-
const [input] = args
|
|
1251
|
-
|
|
1252
|
-
requireValue(input, 'inspect requires an input font')
|
|
1253
|
-
|
|
1254
|
-
const contents = await readFile(input)
|
|
1255
|
-
const info = inspectFont(contents)
|
|
1256
|
-
|
|
1257
|
-
if (json) {
|
|
1258
|
-
console.log(JSON.stringify(info, undefined, 2))
|
|
1259
|
-
} else {
|
|
1260
|
-
console.log(
|
|
1261
|
-
`${input}: ${info.format}, ${info.size} bytes, ${info.metadata.glyphCount} glyphs`,
|
|
1262
|
-
)
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
async function initCommand() {
|
|
1267
|
-
try {
|
|
1268
|
-
await writeFile(INIT_CONFIG_FILE, DEFAULT_INIT_CONFIG, { flag: 'wx' })
|
|
1269
|
-
} catch (error) {
|
|
1270
|
-
if (hasErrorCode(error, 'EEXIST')) {
|
|
1271
|
-
throw new Error(`${INIT_CONFIG_FILE} already exists`, { cause: error })
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
throw error
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
process.stdout.write(`created ${INIT_CONFIG_FILE}\n`)
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
async function subsetOptionsFromArgs(args) {
|
|
1281
|
-
const subset = await resolveSubsetTextFile(
|
|
1282
|
-
{
|
|
1283
|
-
text: readOption(args, ['-t', '--text']),
|
|
1284
|
-
textFile: readOption(args, ['--text-file']),
|
|
1285
|
-
unicodes: parseUnicodes(readOption(args, ['--unicodes'])),
|
|
1286
|
-
},
|
|
1287
|
-
process.cwd(),
|
|
1288
|
-
)
|
|
1289
|
-
|
|
1290
|
-
return {
|
|
1291
|
-
...subset,
|
|
1292
|
-
missingGlyphs: parseMissingGlyphPolicy(
|
|
1293
|
-
readOption(args, ['--missing-glyphs']),
|
|
1294
|
-
),
|
|
1295
|
-
unicodes: subset.unicodes ?? [],
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
function subsetWithCoverage(contents, options) {
|
|
1300
|
-
const policy = options.missingGlyphs ?? 'warn'
|
|
1301
|
-
|
|
1302
|
-
if (policy === 'warn') {
|
|
1303
|
-
const report = analyzeCoverage(contents, options)
|
|
1304
|
-
const warning = missingGlyphMessage(report)
|
|
1305
|
-
|
|
1306
|
-
if (warning !== undefined && emitWarnings) {
|
|
1307
|
-
process.stderr.write(`warning: ${warning}\n`)
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
return subsetTtf(contents, {
|
|
1312
|
-
...options,
|
|
1313
|
-
missingGlyphs: policy === 'warn' ? 'ignore' : policy,
|
|
1314
|
-
})
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
function missingGlyphMessage(report) {
|
|
1318
|
-
if (report.missing.length === 0) {
|
|
1319
|
-
return
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
const visible = report.missing
|
|
1323
|
-
.slice(0, 16)
|
|
1324
|
-
.map(
|
|
1325
|
-
codePoint => `U+${codePoint.toString(16).toUpperCase().padStart(4, '0')}`,
|
|
1326
|
-
)
|
|
1327
|
-
.join(', ')
|
|
1328
|
-
const remaining = report.missing.length - 16
|
|
1329
|
-
|
|
1330
|
-
return `missing glyphs for requested Unicode code points: ${visible}${remaining > 0 ? `, and ${remaining} more` : ''}`
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
function parseMissingGlyphPolicy(value) {
|
|
1334
|
-
if (value === undefined) {
|
|
1335
|
-
return
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
const policy = value.trim().toLowerCase()
|
|
1339
|
-
|
|
1340
|
-
if (policy === 'ignore' || policy === 'warn' || policy === 'error') {
|
|
1341
|
-
return policy
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
throw new Error(
|
|
1345
|
-
`missing glyph policy must be \`ignore\`, \`warn\`, or \`error\`: ${value}`,
|
|
1346
|
-
)
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
async function resolveSubsetTextFile(subset, cwd) {
|
|
1350
|
-
if (subset.textFile === undefined) {
|
|
1351
|
-
return subset
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
const fileText = await readFile(resolve(cwd, subset.textFile), 'utf8')
|
|
1355
|
-
|
|
1356
|
-
return {
|
|
1357
|
-
...subset,
|
|
1358
|
-
text: subset.text === undefined ? fileText : `${subset.text}${fileText}`,
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
async function readConfig(configPath) {
|
|
1363
|
-
const contents = await readFile(configPath, 'utf8')
|
|
1364
|
-
const extension = extname(configPath)
|
|
1365
|
-
|
|
1366
|
-
if (extension === '.json') {
|
|
1367
|
-
return JSON.parse(contents)
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
if (extension === '.jsonc') {
|
|
1371
|
-
return parseJsonc(contents)
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
if (MODULE_CONFIG_EXTENSIONS.has(extension)) {
|
|
1375
|
-
const configModule = await import(pathToFileURL(configPath).href)
|
|
1376
|
-
const loadedConfig = configModule.default ?? configModule.config
|
|
1377
|
-
|
|
1378
|
-
if (loadedConfig === undefined) {
|
|
1379
|
-
throw new Error(`config file \`${configPath}\` does not export a config`)
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
return typeof loadedConfig === 'function'
|
|
1383
|
-
? await loadedConfig()
|
|
1384
|
-
: loadedConfig
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
throw new Error(`unsupported config extension \`${extension}\``)
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
async function findConfig(cwd = process.cwd()) {
|
|
1391
|
-
for (const fileName of DEFAULT_CONFIG_FILES) {
|
|
1392
|
-
const configPath = resolve(cwd, fileName)
|
|
1393
|
-
|
|
1394
|
-
if (await isFile(configPath)) {
|
|
1395
|
-
return configPath
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
throw new Error(`could not find fontmin config in ${cwd}`)
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
async function isFile(path) {
|
|
1403
|
-
try {
|
|
1404
|
-
const stats = await stat(path)
|
|
1405
|
-
return stats.isFile()
|
|
1406
|
-
} catch {
|
|
1407
|
-
return false
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
async function expandInputPath(input, cwd) {
|
|
1412
|
-
if (!isGlobPattern(input)) {
|
|
1413
|
-
return [resolve(cwd, input)]
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
const matches = await glob(input, {
|
|
1417
|
-
absolute: true,
|
|
1418
|
-
cwd,
|
|
1419
|
-
onlyFiles: true,
|
|
1420
|
-
})
|
|
1421
|
-
|
|
1422
|
-
if (matches.length === 0) {
|
|
1423
|
-
throw new Error(`fontmin-rs input glob matched no files: ${input}`)
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
return matches.sort((left, right) => left.localeCompare(right))
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
function isGlobPattern(path) {
|
|
1430
|
-
return /[*?[\]{}]/u.test(path)
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
function hasErrorCode(error, code) {
|
|
1434
|
-
return (
|
|
1435
|
-
typeof error === 'object' &&
|
|
1436
|
-
error !== null &&
|
|
1437
|
-
'code' in error &&
|
|
1438
|
-
error.code === code
|
|
1439
|
-
)
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
async function cleanOutputDirectory(cwd, outDir, protectedPaths) {
|
|
1443
|
-
const root = resolve(cwd)
|
|
1444
|
-
const target = resolve(outDir)
|
|
1445
|
-
const targetIsInsideRoot = pathContains(root, target) && target !== root
|
|
1446
|
-
const targetContainsInput = protectedPaths.some(path =>
|
|
1447
|
-
pathContains(target, resolve(path)),
|
|
1448
|
-
)
|
|
1449
|
-
|
|
1450
|
-
if (
|
|
1451
|
-
target === parse(target).root ||
|
|
1452
|
-
pathContains(target, root) ||
|
|
1453
|
-
targetContainsInput
|
|
1454
|
-
) {
|
|
1455
|
-
throw new Error(
|
|
1456
|
-
`refusing to clean output directory ${target} because it is the project directory, an input ancestor, or a filesystem root`,
|
|
1457
|
-
)
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
try {
|
|
1461
|
-
const [realRoot, realTarget, ...realProtectedPaths] = await Promise.all([
|
|
1462
|
-
realpath(root),
|
|
1463
|
-
realpath(target),
|
|
1464
|
-
...protectedPaths.map(path => realpath(resolve(path))),
|
|
1465
|
-
])
|
|
1466
|
-
|
|
1467
|
-
if (
|
|
1468
|
-
realTarget === parse(realTarget).root ||
|
|
1469
|
-
pathContains(realTarget, realRoot) ||
|
|
1470
|
-
realProtectedPaths.some(path => pathContains(realTarget, path)) ||
|
|
1471
|
-
(targetIsInsideRoot && !pathContains(realRoot, realTarget))
|
|
1472
|
-
) {
|
|
1473
|
-
throw new Error(
|
|
1474
|
-
`refusing to clean output directory ${target} because its resolved location is unsafe for project ${root}`,
|
|
1475
|
-
)
|
|
1476
|
-
}
|
|
1477
|
-
} catch (error) {
|
|
1478
|
-
if (!hasErrorCode(error, 'ENOENT')) {
|
|
1479
|
-
throw error
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
await rm(target, { recursive: true, force: true })
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
function pathContains(parent, candidate) {
|
|
1487
|
-
const childPath = relative(parent, candidate)
|
|
1488
|
-
|
|
1489
|
-
return (
|
|
1490
|
-
childPath === '' ||
|
|
1491
|
-
(childPath !== '..' &&
|
|
1492
|
-
!childPath.startsWith(`..${sep}`) &&
|
|
1493
|
-
!isAbsolute(childPath))
|
|
1494
|
-
)
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
function resolveContainedPath(root, path, label) {
|
|
1498
|
-
if (typeof path !== 'string' || path.length === 0 || isAbsolute(path)) {
|
|
1499
|
-
throw new Error(`${label} must be a non-empty relative path: ${path}`)
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
const resolvedRoot = resolve(root)
|
|
1503
|
-
const resolvedPath = resolve(resolvedRoot, path)
|
|
1504
|
-
const relativePath = relative(resolvedRoot, resolvedPath)
|
|
1505
|
-
|
|
1506
|
-
if (
|
|
1507
|
-
relativePath === '' ||
|
|
1508
|
-
relativePath === '..' ||
|
|
1509
|
-
relativePath.startsWith(`..${sep}`) ||
|
|
1510
|
-
isAbsolute(relativePath)
|
|
1511
|
-
) {
|
|
1512
|
-
throw new Error(
|
|
1513
|
-
`${label} must stay within its destination directory: ${path}`,
|
|
1514
|
-
)
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
return resolvedPath
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
async function ensureRealPathContained(root, path, label) {
|
|
1521
|
-
const [realRoot, realPath] = await Promise.all([
|
|
1522
|
-
realpath(root),
|
|
1523
|
-
realpath(path),
|
|
1524
|
-
])
|
|
1525
|
-
const relativePath = relative(realRoot, realPath)
|
|
1526
|
-
|
|
1527
|
-
if (
|
|
1528
|
-
relativePath === '..' ||
|
|
1529
|
-
relativePath.startsWith(`..${sep}`) ||
|
|
1530
|
-
isAbsolute(relativePath)
|
|
1531
|
-
) {
|
|
1532
|
-
throw new Error(`${label} resolves outside its destination directory`)
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
async function rejectSymbolicLink(path) {
|
|
1537
|
-
try {
|
|
1538
|
-
const metadata = await lstat(path)
|
|
1539
|
-
|
|
1540
|
-
if (metadata.isSymbolicLink()) {
|
|
1541
|
-
throw new Error(`refusing to write output through symbolic link: ${path}`)
|
|
1542
|
-
}
|
|
1543
|
-
} catch (error) {
|
|
1544
|
-
if (!hasErrorCode(error, 'ENOENT')) {
|
|
1545
|
-
throw error
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
function normalizeExtension(extension) {
|
|
1551
|
-
const normalized = extension.replace(/^\.+/u, '')
|
|
1552
|
-
|
|
1553
|
-
if (
|
|
1554
|
-
normalized.length === 0 ||
|
|
1555
|
-
normalized === '..' ||
|
|
1556
|
-
normalized.includes('/') ||
|
|
1557
|
-
normalized.includes('\\')
|
|
1558
|
-
) {
|
|
1559
|
-
throw new Error(`output extension must be a file extension: ${extension}`)
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
return normalized
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
async function atomicWriteFile(path, contents) {
|
|
1566
|
-
const temporaryPath = `${path}.${process.pid}.${temporaryFileCounter}.tmp`
|
|
1567
|
-
temporaryFileCounter += 1
|
|
1568
|
-
|
|
1569
|
-
try {
|
|
1570
|
-
await writeFile(temporaryPath, contents)
|
|
1571
|
-
await rename(temporaryPath, path)
|
|
1572
|
-
} finally {
|
|
1573
|
-
await rm(temporaryPath, { force: true })
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
function outputFormatsFromConfig(outputs) {
|
|
1578
|
-
if (outputs === undefined) {
|
|
1579
|
-
return ['eot', 'woff', 'woff2', 'svg', 'css']
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
const formats = outputs.map(output =>
|
|
1583
|
-
typeof output === 'string' ? output : output.format,
|
|
1584
|
-
)
|
|
1585
|
-
|
|
1586
|
-
return parseFormats(formats.join(','))
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
function outputFormatsForConfig(outputs, { formats, noOriginal, preset }) {
|
|
1590
|
-
const outputFormats =
|
|
1591
|
-
formats === undefined && preset === undefined
|
|
1592
|
-
? outputFormatsFromConfig(outputs)
|
|
1593
|
-
: outputFormatsFromArgs(formats, preset)
|
|
1594
|
-
const filtered = filterOriginalOutput(outputFormats, noOriginal)
|
|
1595
|
-
|
|
1596
|
-
if (filtered.length === 0) {
|
|
1597
|
-
throw new Error('build requires at least one non-original output format')
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
return filtered
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
function outputFormatsFromArgs(formats, preset) {
|
|
1604
|
-
if (formats !== undefined && preset !== undefined) {
|
|
1605
|
-
throw new Error('build accepts only one of --formats or --preset')
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
if (formats !== undefined) {
|
|
1609
|
-
return parseFormats(formats)
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
if (preset !== undefined) {
|
|
1613
|
-
return outputFormatsFromPreset(preset)
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
throw new Error('build requires --formats or --preset')
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
function outputFormatsFromPreset(value) {
|
|
1620
|
-
const preset = value.trim().toLowerCase()
|
|
1621
|
-
|
|
1622
|
-
if (preset === 'compat') {
|
|
1623
|
-
return ['eot', 'svg', 'woff', 'woff2', 'css']
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
if (preset === 'modern-web') {
|
|
1627
|
-
return ['woff2', 'woff', 'css']
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
if (preset === 'iconfont') {
|
|
1631
|
-
return ['ttf', 'css']
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
throw new Error(`unsupported preset \`${preset}\``)
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
function isIconfontPreset(value) {
|
|
1638
|
-
return value?.trim().toLowerCase() === 'iconfont'
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
function filterOriginalOutput(formats, noOriginal) {
|
|
1642
|
-
return noOriginal ? formats.filter(format => format !== 'ttf') : formats
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
function parseUnicodeRanges(values) {
|
|
1646
|
-
return values.map(value => parseUnicodeRange(value))
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
function formatUnicodeEndpoint(codePoint) {
|
|
1650
|
-
return codePoint.toString(16).toUpperCase().padStart(4, '0')
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
function parseUnicodeRange(value) {
|
|
1654
|
-
const match =
|
|
1655
|
-
/^u\+(?<start>[0-9a-f]{1,6})(?:-(?<end>[0-9a-f]{1,6}))?$/iu.exec(value)
|
|
1656
|
-
|
|
1657
|
-
if (match?.groups === undefined) {
|
|
1658
|
-
throw new Error(`invalid Unicode range: ${value}`)
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
const start = Number.parseInt(match.groups.start, 16)
|
|
1662
|
-
const end = Number.parseInt(match.groups.end ?? match.groups.start, 16)
|
|
1663
|
-
|
|
1664
|
-
if (start > end || end > 0x10_ff_ff || (start <= 0xdf_ff && end >= 0xd8_00)) {
|
|
1665
|
-
throw new Error(`invalid Unicode range: ${value}`)
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
return start === end
|
|
1669
|
-
? `U+${formatUnicodeEndpoint(start)}`
|
|
1670
|
-
: `U+${formatUnicodeEndpoint(start)}-${formatUnicodeEndpoint(end)}`
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
function parseDeliverySlices(values) {
|
|
1674
|
-
const slices = []
|
|
1675
|
-
|
|
1676
|
-
for (const value of values) {
|
|
1677
|
-
const separator = value.indexOf(':')
|
|
1678
|
-
|
|
1679
|
-
if (separator === -1) {
|
|
1680
|
-
throw new Error(`delivery slice must use NAME:RANGE[,RANGE...]: ${value}`)
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
const name = value.slice(0, separator)
|
|
1684
|
-
const unicodeRanges = value.slice(separator + 1).split(',')
|
|
1685
|
-
const existing = slices.find(slice => slice.name === name)
|
|
1686
|
-
|
|
1687
|
-
if (existing === undefined) {
|
|
1688
|
-
slices.push({ name, unicodeRanges })
|
|
1689
|
-
} else {
|
|
1690
|
-
existing.unicodeRanges.push(...unicodeRanges)
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
return normalizeDeliverySlices(slices)
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
function normalizeDeliverySlices(values) {
|
|
1698
|
-
if (!Array.isArray(values)) {
|
|
1699
|
-
throw new TypeError('delivery slices must be an array')
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
const names = new Set()
|
|
1703
|
-
|
|
1704
|
-
return values.map((value, index) => {
|
|
1705
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
1706
|
-
throw new Error(`delivery slice ${index + 1} must be an object`)
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
const { name, unicodeRanges } = value
|
|
1710
|
-
|
|
1711
|
-
if (
|
|
1712
|
-
typeof name !== 'string' ||
|
|
1713
|
-
name.length === 0 ||
|
|
1714
|
-
!/^[A-Za-z0-9_-]+$/u.test(name)
|
|
1715
|
-
) {
|
|
1716
|
-
throw new Error(`invalid delivery slice name: ${name}`)
|
|
1717
|
-
}
|
|
1718
|
-
if (names.has(name)) {
|
|
1719
|
-
throw new Error(`duplicate delivery slice name: ${name}`)
|
|
1720
|
-
}
|
|
1721
|
-
if (!Array.isArray(unicodeRanges) || unicodeRanges.length === 0) {
|
|
1722
|
-
throw new Error(
|
|
1723
|
-
`delivery slice \`${name}\` requires at least one Unicode range`,
|
|
1724
|
-
)
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
names.add(name)
|
|
1728
|
-
|
|
1729
|
-
return {
|
|
1730
|
-
name,
|
|
1731
|
-
unicodeRanges: parseUnicodeRanges(unicodeRanges),
|
|
1732
|
-
}
|
|
1733
|
-
})
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
function parseUnicodes(value) {
|
|
1737
|
-
if (value === undefined) {
|
|
1738
|
-
return []
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
const unicodes = value.split(',').map(item => parseUnicodeCodePoint(item))
|
|
1742
|
-
|
|
1743
|
-
if (unicodes.length === 0) {
|
|
1744
|
-
throw new Error('expected at least one unicode code point')
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
return unicodes
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
function parseUnicodeCodePoint(value) {
|
|
1751
|
-
const item = value.trim()
|
|
1752
|
-
|
|
1753
|
-
if (item.length === 0) {
|
|
1754
|
-
throw new Error('empty unicode code point in --unicodes')
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
let digits = item
|
|
1758
|
-
let radix = 10
|
|
1759
|
-
|
|
1760
|
-
if (/^(?:0x|u\+)/iu.test(item)) {
|
|
1761
|
-
digits = item.slice(2)
|
|
1762
|
-
radix = 16
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
const validDigits = radix === 16 ? /^[0-9a-f]+$/iu : /^[0-9]+$/u
|
|
1766
|
-
|
|
1767
|
-
if (!validDigits.test(digits)) {
|
|
1768
|
-
throw new Error(`invalid unicode code point \`${item}\``)
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
const codePoint = Number.parseInt(digits, radix)
|
|
1772
|
-
|
|
1773
|
-
if (!Number.isInteger(codePoint) || codePoint > 4_294_967_295) {
|
|
1774
|
-
throw new Error(`invalid unicode code point \`${item}\``)
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
return codePoint
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
function parseFormats(value) {
|
|
1781
|
-
const formats = value
|
|
1782
|
-
.split(',')
|
|
1783
|
-
.map(format => format.trim().toLowerCase())
|
|
1784
|
-
.filter(format => format.length > 0)
|
|
1785
|
-
|
|
1786
|
-
if (formats.length === 0) {
|
|
1787
|
-
throw new Error('expected at least one output format')
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
for (const format of formats) {
|
|
1791
|
-
if (
|
|
1792
|
-
format !== 'ttf' &&
|
|
1793
|
-
format !== 'woff' &&
|
|
1794
|
-
format !== 'woff2' &&
|
|
1795
|
-
format !== 'eot' &&
|
|
1796
|
-
format !== 'svg' &&
|
|
1797
|
-
format !== 'css'
|
|
1798
|
-
) {
|
|
1799
|
-
throw new Error(`unsupported output format \`${format}\``)
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
return formats
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
function convertFont(contents, format, options = {}) {
|
|
1807
|
-
const normalized = format.toLowerCase()
|
|
1808
|
-
|
|
1809
|
-
if (normalized === 'ttf') {
|
|
1810
|
-
if (isTtf(contents)) {
|
|
1811
|
-
return contents
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
if (isWoff(contents)) {
|
|
1815
|
-
return woffToTtf(contents)
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
if (isWoff2(contents)) {
|
|
1819
|
-
return woff2ToTtf(contents)
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
if (isEot(contents)) {
|
|
1823
|
-
return eotToTtf(contents)
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
if (isOtf(contents)) {
|
|
1827
|
-
return otfToTtf(contents, {
|
|
1828
|
-
variationCoordinates: options.variationCoordinates ?? {},
|
|
1829
|
-
})
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
throw new Error('unsupported input format for TTF conversion')
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
const ttf = convertFont(contents, 'ttf', options)
|
|
1836
|
-
|
|
1837
|
-
if (normalized === 'woff') {
|
|
1838
|
-
return ttfToWoff(ttf)
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
if (normalized === 'woff2') {
|
|
1842
|
-
return ttfToWoff2(ttf)
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
if (normalized === 'eot') {
|
|
1846
|
-
return ttfToEot(ttf)
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
if (normalized === 'svg') {
|
|
1850
|
-
return ttfToSvg(ttf)
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
throw new Error(`unsupported output format \`${format}\``)
|
|
1854
|
-
}
|
|
1855
|
-
|
|
1856
|
-
function isTtf(contents) {
|
|
1857
|
-
return (
|
|
1858
|
-
(contents[0] === 0x00 &&
|
|
1859
|
-
contents[1] === 0x01 &&
|
|
1860
|
-
contents[2] === 0x00 &&
|
|
1861
|
-
contents[3] === 0x00) ||
|
|
1862
|
-
contents.subarray(0, 4).toString('ascii') === 'true'
|
|
1863
|
-
)
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
function isOtf(contents) {
|
|
1867
|
-
return contents.subarray(0, 4).toString('ascii') === 'OTTO'
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
function isWoff(contents) {
|
|
1871
|
-
return contents.subarray(0, 4).toString('ascii') === 'wOFF'
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
function isWoff2(contents) {
|
|
1875
|
-
return contents.subarray(0, 4).toString('ascii') === 'wOF2'
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
function isEot(contents) {
|
|
1879
|
-
return (
|
|
1880
|
-
contents.byteLength >= 12 &&
|
|
1881
|
-
((contents[8] === 0x01 &&
|
|
1882
|
-
contents[9] === 0x00 &&
|
|
1883
|
-
contents[10] === 0x02 &&
|
|
1884
|
-
contents[11] === 0x00) ||
|
|
1885
|
-
(contents[8] === 0x02 &&
|
|
1886
|
-
contents[9] === 0x00 &&
|
|
1887
|
-
contents[10] === 0x02 &&
|
|
1888
|
-
contents[11] === 0x00))
|
|
1889
|
-
)
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
function readOption(args, names) {
|
|
1893
|
-
const index = args.findIndex(arg => names.includes(arg))
|
|
1894
|
-
|
|
1895
|
-
if (index === -1) {
|
|
1896
|
-
return
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
const [name] = args.splice(index, 1)
|
|
1900
|
-
const [value] = args.splice(index, 1)
|
|
1901
|
-
|
|
1902
|
-
if (value === undefined || value.startsWith('-')) {
|
|
1903
|
-
throw new Error(`${name} requires a value`)
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
return value
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
function readOptions(args, names) {
|
|
1910
|
-
const values = []
|
|
1911
|
-
|
|
1912
|
-
while (args.some(arg => names.includes(arg))) {
|
|
1913
|
-
values.push(readOption(args, names))
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
return values
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
function readFlag(args, names) {
|
|
1920
|
-
const index = args.findIndex(arg => names.includes(arg))
|
|
1921
|
-
|
|
1922
|
-
if (index === -1) {
|
|
1923
|
-
return false
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
args.splice(index, 1)
|
|
1927
|
-
return true
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
function assertNoUnknownOptions(args) {
|
|
1931
|
-
const option = args.find(arg => arg.startsWith('-'))
|
|
1932
|
-
|
|
1933
|
-
if (option !== undefined) {
|
|
1934
|
-
throw new Error(`unknown option \`${option}\``)
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
function assertNoUnexpectedArgs(args, maximumPositionals = 1) {
|
|
1939
|
-
assertNoUnknownOptions(args)
|
|
1940
|
-
|
|
1941
|
-
if (args.length > maximumPositionals) {
|
|
1942
|
-
throw new Error(`unexpected argument \`${args[maximumPositionals]}\``)
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
|
|
1946
|
-
function parseVariations(values) {
|
|
1947
|
-
const coordinates = {}
|
|
1948
|
-
|
|
1949
|
-
for (const value of values) {
|
|
1950
|
-
const separator = value.indexOf('=')
|
|
1951
|
-
|
|
1952
|
-
if (separator === -1) {
|
|
1953
|
-
throw new Error(`invalid variation \`${value}\`; expected TAG=VALUE`)
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
const tag = value.slice(0, separator)
|
|
1957
|
-
const rawNumber = value.slice(separator + 1)
|
|
1958
|
-
|
|
1959
|
-
if (
|
|
1960
|
-
tag.length !== 4 ||
|
|
1961
|
-
[...tag].some(character => (character.codePointAt(0) ?? 128) > 127)
|
|
1962
|
-
) {
|
|
1963
|
-
throw new Error(
|
|
1964
|
-
`invalid variation axis \`${tag}\`; expected four ASCII characters`,
|
|
1965
|
-
)
|
|
1966
|
-
}
|
|
1967
|
-
if (Object.hasOwn(coordinates, tag)) {
|
|
1968
|
-
throw new Error(`duplicate variation axis \`${tag}\``)
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
const number = Number(rawNumber)
|
|
1972
|
-
|
|
1973
|
-
if (rawNumber.length === 0 || !Number.isFinite(number)) {
|
|
1974
|
-
throw new Error(
|
|
1975
|
-
`invalid variation value \`${rawNumber}\` for axis \`${tag}\``,
|
|
1976
|
-
)
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
coordinates[tag] = number
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
return coordinates
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
function requireValue(value, message) {
|
|
1986
|
-
if (value === undefined || value.length === 0) {
|
|
1987
|
-
throw new Error(message)
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
async function writeOutput(output, contents) {
|
|
1992
|
-
await mkdir(dirname(output), { recursive: true })
|
|
1993
|
-
await writeFile(output, contents)
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
function usage(stream) {
|
|
1997
|
-
stream.write(`Usage:
|
|
1998
|
-
fontmin-rs subset <INPUT> -o|--output <OUTPUT> (-t|--text <TEXT> | --text-file <FILE> | --unicodes <LIST> | -b|--basic-text) [--missing-glyphs <ignore|warn|error>]
|
|
1999
|
-
fontmin-rs coverage <INPUT> (-t|--text <TEXT> | --text-file <FILE> | --unicodes <LIST> | -b|--basic-text) [--json]
|
|
2000
|
-
fontmin-rs convert <INPUT> -f|--format <ttf|woff|woff2|eot|svg> -o|--output <OUTPUT> [--variation <TAG=VALUE>]...
|
|
2001
|
-
fontmin-rs build <INPUT...> [-c|--config <CONFIG>] [-o|--out-dir <OUT_DIR>] [-t|--text <TEXT>] [--text-file <FILE>] [--unicodes <LIST>] [-b|--basic-text] [--missing-glyphs <ignore|warn|error>] [-d|--deflate-woff] [-T|--show-time] [--silent] [--cache] [--no-cache] [--css-glyph] [--css-unicode-range <RANGE>]... [--delivery-slice <NAME:RANGE[,RANGE...]>]... [--variation <TAG=VALUE>]... [--formats <FORMATS>] [--preset <compat|modern-web|iconfont>] [--no-original] [--font-family <FONT_FAMILY>] [--font-path <FONT_PATH>]
|
|
2002
|
-
fontmin-rs bench <INPUT> [-t|--text <TEXT>] [--text-file <FILE>] [--unicodes <LIST>] [-b|--basic-text] [--json]
|
|
2003
|
-
fontmin-rs inspect <INPUT> [--json]
|
|
2004
|
-
fontmin-rs init
|
|
2005
|
-
fontmin-rs doctor
|
|
2006
|
-
`)
|
|
2007
|
-
}
|
|
5
|
+
await runCli()
|