font-range 0.2.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +12 -1
- package/.gitattributes +3 -0
- package/.vim/coc-settings.json +6 -0
- package/.yarn/plugins/@yarnpkg/plugin-outdated.cjs +35 -0
- package/.yarn/plugins/@yarnpkg/plugin-typescript.cjs +9 -0
- package/.yarn/sdks/eslint/bin/eslint.js +20 -0
- package/.yarn/sdks/eslint/lib/api.js +20 -0
- package/.yarn/sdks/eslint/package.json +6 -0
- package/.yarn/sdks/integrations.yml +6 -0
- package/.yarn/sdks/prettier/index.js +20 -0
- package/.yarn/sdks/prettier/package.json +6 -0
- package/.yarn/sdks/typescript/bin/tsc +20 -0
- package/.yarn/sdks/typescript/bin/tsserver +20 -0
- package/.yarn/sdks/typescript/lib/tsc.js +20 -0
- package/.yarn/sdks/typescript/lib/tsserver.js +223 -0
- package/.yarn/sdks/typescript/lib/tsserverlibrary.js +223 -0
- package/.yarn/sdks/typescript/lib/typescript.js +20 -0
- package/.yarn/sdks/typescript/package.json +6 -0
- package/README.md +141 -43
- package/__tests__/css_load.test.ts +52 -0
- package/__tests__/font/NotoSansKR-Local.css +3 -2
- package/__tests__/font/subset_glyphs.txt +1 -0
- package/__tests__/main.test.ts +241 -52
- package/__tests__/preset.test.ts +64 -0
- package/__tests__/shared.ts +25 -0
- package/build/main.d.ts +49 -0
- package/build/main.js +363 -0
- package/build/worker.d.ts +5 -0
- package/build/worker.js +10 -0
- package/jest.config.js +5 -5
- package/package.json +29 -26
- package/requirements.txt +4 -4
- package/src/main.ts +356 -106
- package/src/{types.ts → types.d.ts} +0 -2
- package/src/worker.ts +12 -0
- package/tsconfig.json +1 -0
- package/tsconfig.release.json +3 -2
- package/.github/workflows/nodejs.yml +0 -36
- package/.github/workflows/npm-publish.yml +0 -106
- package/build/src/main.d.ts +0 -18
- package/build/src/main.js +0 -199
- package/build/src/main.js.map +0 -1
- package/build/src/types.d.ts +0 -4
- package/build/src/types.js +0 -3
- package/build/src/types.js.map +0 -1
package/src/main.ts
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
|
-
import { join, parse } from
|
|
2
|
-
import { createReadStream, createWriteStream, existsSync
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { join, parse } from "path";
|
|
2
|
+
import { createReadStream, createWriteStream, existsSync } from "fs";
|
|
3
|
+
import { mkdir } from "fs/promises";
|
|
4
|
+
|
|
5
|
+
import Piscina from "piscina";
|
|
6
|
+
import fetch, { Headers } from "@esm2cjs/node-fetch";
|
|
7
|
+
import { parse as cssAST, ParseOptions, walk } from "css-tree";
|
|
8
|
+
import type { Declaration } from "css-tree";
|
|
9
|
+
|
|
10
|
+
import type WorkerFn from "./worker";
|
|
11
|
+
import type { RequiredByValueExcept } from "./types";
|
|
12
|
+
|
|
13
|
+
// == Worker ===================================================================
|
|
14
|
+
type WorkerRT = ReturnType<typeof WorkerFn>;
|
|
15
|
+
|
|
16
|
+
class Worker {
|
|
17
|
+
private static instance: Piscina;
|
|
18
|
+
private constructor() { }
|
|
19
|
+
|
|
20
|
+
public static getInstance(): Piscina {
|
|
21
|
+
if(!Worker.instance) {
|
|
22
|
+
Worker.instance = new Piscina({
|
|
23
|
+
filename: join(__dirname, "../", "build", "worker.js")
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return Worker.instance;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// == Resouce Basics ===========================================================
|
|
9
31
|
export const targets = {
|
|
10
32
|
weston: "https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap",
|
|
11
33
|
korean: "https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap",
|
|
@@ -41,7 +63,7 @@ function getCSSPath(dirPath: string, url: string) {
|
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
if (!existsSync(url)) {
|
|
44
|
-
throw new Error(
|
|
66
|
+
throw new Error("Not vaild URL or PATH: " + url);
|
|
45
67
|
}
|
|
46
68
|
return url;
|
|
47
69
|
}
|
|
@@ -49,24 +71,31 @@ function getCSSPath(dirPath: string, url: string) {
|
|
|
49
71
|
// == CSS I/O ==================================================================
|
|
50
72
|
async function saveCSS(path: string, url: string) {
|
|
51
73
|
// Fake header
|
|
74
|
+
const version = "109.0";
|
|
52
75
|
const headers = new Headers({
|
|
53
76
|
"Accept": "text/html,application/xhtml+xml,application/xml;",
|
|
54
|
-
"User-Agent":
|
|
77
|
+
"User-Agent": `Mozilla/5.0 (Windows NT 10.0; rv:${ version }) Gecko/20100101 Firefox/${ version }`
|
|
55
78
|
});
|
|
56
79
|
|
|
57
80
|
const res = await fetch(url, {
|
|
58
81
|
method: "GET",
|
|
59
82
|
headers: headers
|
|
60
83
|
});
|
|
61
|
-
|
|
84
|
+
if(res.status !== 200) {
|
|
85
|
+
throw new Error("Not vaild URL: " + url);
|
|
86
|
+
}
|
|
62
87
|
|
|
63
|
-
|
|
88
|
+
return new Promise<void>((resolve, reject) => {
|
|
89
|
+
const fileStream = createWriteStream(path);
|
|
64
90
|
res.body.pipe(fileStream);
|
|
65
91
|
res.body.on("error", (err) => {
|
|
66
|
-
console.log(
|
|
92
|
+
console.log("File write Error.");
|
|
67
93
|
reject(err);
|
|
68
94
|
});
|
|
69
|
-
|
|
95
|
+
res.body.on("close", () => {
|
|
96
|
+
fileStream.close();
|
|
97
|
+
});
|
|
98
|
+
fileStream.on("close", () => {
|
|
70
99
|
resolve();
|
|
71
100
|
});
|
|
72
101
|
});
|
|
@@ -76,20 +105,19 @@ async function readCSS(path: string) {
|
|
|
76
105
|
return new Promise<string>((resolve, reject) => {
|
|
77
106
|
const readData: (string | Buffer)[] = [];
|
|
78
107
|
createReadStream(path)
|
|
79
|
-
.on(
|
|
108
|
+
.on("data", (data) => {
|
|
80
109
|
readData.push(data);
|
|
81
110
|
})
|
|
82
|
-
.on(
|
|
83
|
-
|
|
84
|
-
const css = readData.join('');
|
|
111
|
+
.on("end", () => {
|
|
112
|
+
const css = readData.join("");
|
|
85
113
|
|
|
86
114
|
resolve(css);
|
|
87
115
|
})
|
|
88
|
-
.on(
|
|
116
|
+
.on("error", reject);
|
|
89
117
|
});
|
|
90
118
|
}
|
|
91
119
|
|
|
92
|
-
// == CSS Parse
|
|
120
|
+
// == CSS Parse ================================================================
|
|
93
121
|
const parseOptions: ParseOptions = {
|
|
94
122
|
parseAtrulePrelude: false,
|
|
95
123
|
parseRulePrelude: false,
|
|
@@ -99,134 +127,356 @@ const parseOptions: ParseOptions = {
|
|
|
99
127
|
async function loadAST(dirPath: string, url = targets.korean, parseOption = parseOptions) {
|
|
100
128
|
const cssPath = getCSSPath(dirPath, url);
|
|
101
129
|
if (!existsSync(dirPath)) {
|
|
102
|
-
|
|
130
|
+
await mkdir(dirPath);
|
|
103
131
|
}
|
|
104
132
|
if (!existsSync(cssPath)) {
|
|
105
133
|
await saveCSS(cssPath, url);
|
|
106
134
|
}
|
|
107
135
|
|
|
108
136
|
const css = await readCSS(cssPath);
|
|
109
|
-
|
|
110
|
-
return ast;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function parseUnicodeRanges(parsed) {
|
|
114
|
-
const fontFaceL = parsed.children;
|
|
115
|
-
const uniRangeL = fontFaceL.map((faceValObj => {
|
|
116
|
-
const faceBlockL = faceValObj.block.children;
|
|
117
|
-
const uniRangeBlockL = faceBlockL.filter(faceBlock => faceBlock.property === "unicode-range");
|
|
118
|
-
const uniRangeL = uniRangeBlockL.map(uniRangeBlock => uniRangeBlock.value.value);
|
|
119
|
-
return uniRangeL.head.data;
|
|
120
|
-
}));
|
|
121
|
-
|
|
122
|
-
const uniRanges: string[] = [];
|
|
123
|
-
uniRangeL.forEach(unicodeRange => {
|
|
124
|
-
uniRanges.push(unicodeRange);
|
|
125
|
-
});
|
|
126
|
-
return uniRanges;
|
|
137
|
+
return cssAST(css, parseOption);
|
|
127
138
|
}
|
|
128
139
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
interface BlockI {
|
|
141
|
+
src: string;
|
|
142
|
+
unicodes: string;
|
|
143
|
+
}
|
|
144
|
+
function setBlock(block: BlockI, elem: Declaration, blockProp: keyof BlockI, elemProp: string) {
|
|
145
|
+
if(elem.property === elemProp) {
|
|
146
|
+
if(elem.value.type === "Raw") {
|
|
147
|
+
block[blockProp] = elem.value.value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function parseCSS(dirPath = "src", url = targets.korean) {
|
|
153
|
+
const ast = await loadAST(dirPath, url);
|
|
154
|
+
const parsed = [] as BlockI[];
|
|
155
|
+
|
|
156
|
+
walk(ast, { visit: "Atrule", enter(node) {
|
|
157
|
+
if(node.name === "font-face") {
|
|
158
|
+
const block = {
|
|
159
|
+
src: "",
|
|
160
|
+
unicodes: ""
|
|
161
|
+
};
|
|
162
|
+
walk(node, { visit: "Declaration", enter(elem) {
|
|
163
|
+
setBlock(block, elem, "src", "src");
|
|
164
|
+
setBlock(block, elem, "unicodes", "unicode-range");
|
|
165
|
+
}});
|
|
166
|
+
|
|
167
|
+
if(block.src !== "" || block.unicodes !== "") {
|
|
168
|
+
parsed.push(block);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}});
|
|
172
|
+
return parsed;
|
|
132
173
|
}
|
|
133
174
|
|
|
134
|
-
// == Options
|
|
135
|
-
interface
|
|
136
|
-
|
|
137
|
-
format:
|
|
175
|
+
// == Options - Basics =========================================================
|
|
176
|
+
export interface FontDefaultOptionI {
|
|
177
|
+
saveDir: string;
|
|
178
|
+
format: Format;
|
|
138
179
|
nameFormat: string;
|
|
139
|
-
|
|
140
|
-
|
|
180
|
+
logFormat: string;
|
|
181
|
+
defaultArgs: string[];
|
|
182
|
+
etcArgs: string[];
|
|
141
183
|
}
|
|
184
|
+
export interface FontRangeOptionI extends FontDefaultOptionI {
|
|
185
|
+
fromCSS: "default" | "srcIndex" | "srcName";
|
|
186
|
+
}
|
|
187
|
+
export interface FontSubsetOptionI extends FontDefaultOptionI {
|
|
188
|
+
textFile: string;
|
|
189
|
+
text: string;
|
|
190
|
+
}
|
|
191
|
+
export interface FontPipeOptionI extends FontRangeOptionI, FontSubsetOptionI {
|
|
192
|
+
cssFile: string;
|
|
193
|
+
}
|
|
194
|
+
type ArgOptionT<I> = FontDefaultOptionI["saveDir"] | Partial<I>;
|
|
195
|
+
type FontRangeOptionT = ArgOptionT<FontRangeOptionI>;
|
|
196
|
+
type FontSubsetOptionT = ArgOptionT<FontSubsetOptionI>;
|
|
197
|
+
type FontPipeOptionT = Partial<FontPipeOptionI>;
|
|
198
|
+
type ArgOptionsT = FontRangeOptionT | FontSubsetOptionT | FontPipeOptionT;
|
|
199
|
+
type Format = "otf" | "ttf" | "woff2" | "woff" | "woff-zopfli";
|
|
142
200
|
|
|
143
|
-
|
|
201
|
+
export const defaultArgs = [
|
|
202
|
+
"--layout-features=*",
|
|
203
|
+
"--glyph-names",
|
|
204
|
+
"--symbol-cmap",
|
|
205
|
+
"--legacy-cmap",
|
|
206
|
+
"--notdef-glyph",
|
|
207
|
+
"--notdef-outline",
|
|
208
|
+
"--recommended-glyphs",
|
|
209
|
+
"--name-legacy",
|
|
210
|
+
"--drop-tables=",
|
|
211
|
+
"--name-IDs=*",
|
|
212
|
+
"--name-languages=*"
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
function getDefaultOptions(): RequiredByValueExcept<FontDefaultOptionI, "saveDir"> {
|
|
144
216
|
return {
|
|
145
217
|
format: "woff2",
|
|
146
218
|
nameFormat: "{NAME}_{INDEX}{EXT}",
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
--legacy-cmap \
|
|
151
|
-
--notdef-glyph \
|
|
152
|
-
--notdef-outline \
|
|
153
|
-
--recommended-glyphs \
|
|
154
|
-
--name-legacy \
|
|
155
|
-
--drop-tables= \
|
|
156
|
-
--name-IDs='*' \
|
|
157
|
-
--name-languages='*'",
|
|
158
|
-
etcArgs: ""
|
|
219
|
+
logFormat: "Convert {ORIGIN} -> {OUTPUT}",
|
|
220
|
+
defaultArgs: defaultArgs,
|
|
221
|
+
etcArgs: []
|
|
159
222
|
};
|
|
160
223
|
}
|
|
161
224
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
? options[key]
|
|
165
|
-
: alterValue;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function getName(nameFormat: string, fontName: string, index: number, fontExt: string) {
|
|
169
|
-
return nameFormat
|
|
170
|
-
.replace( "{NAME}", fontName)
|
|
171
|
-
.replace("{INDEX}", index.toString())
|
|
172
|
-
.replace( "{EXT}", fontExt);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// == Main ====================================================================
|
|
176
|
-
function getFormat(format: string) {
|
|
225
|
+
// == Options - Get Info =======================================================
|
|
226
|
+
function getFormat(format: Format) {
|
|
177
227
|
switch(format) {
|
|
178
228
|
case "otf": return "otf";
|
|
179
229
|
case "ttf": return "ttf";
|
|
180
230
|
case "woff2": return "woff2";
|
|
181
231
|
case "woff": return "woff";
|
|
182
232
|
case "woff-zopfli": return "woff";
|
|
183
|
-
default: return "woff2";
|
|
184
233
|
}
|
|
185
234
|
}
|
|
186
235
|
|
|
187
|
-
function formatOption(format:
|
|
236
|
+
function formatOption(format: Format) {
|
|
188
237
|
const formatName = getFormat(format);
|
|
189
|
-
if(ext) return "." + formatName;
|
|
190
238
|
|
|
191
|
-
if(format === "otf" || format === "ttf") return "";
|
|
192
|
-
return
|
|
193
|
-
|
|
194
|
-
|
|
239
|
+
if(format === "otf" || format === "ttf") return [""];
|
|
240
|
+
return [
|
|
241
|
+
"--flavor=" + formatName,
|
|
242
|
+
(format === "woff-zopfli") ? "--with-zopfli" : ""
|
|
243
|
+
];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function fileNameInit(nameFormat: string, fontName: string, fontExt: string) {
|
|
247
|
+
return nameFormat
|
|
248
|
+
.replace( "{NAME}", fontName)
|
|
249
|
+
.replace( "{EXT}", fontExt );
|
|
250
|
+
}
|
|
251
|
+
function getFileName(initName: string, index?: number | string) {
|
|
252
|
+
return initName
|
|
253
|
+
.replace("{INDEX}",
|
|
254
|
+
(typeof index === "number")
|
|
255
|
+
? index.toString()
|
|
256
|
+
: (typeof index === "string")
|
|
257
|
+
? index
|
|
258
|
+
: ""
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
function getConsoleLog(logFormat: string, origin: string, output: string) {
|
|
262
|
+
return logFormat
|
|
263
|
+
.replace("{ORIGIN}", origin)
|
|
264
|
+
.replace("{OUTPUT}", output);
|
|
195
265
|
}
|
|
196
266
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
): Promise<Buffer[]> {
|
|
201
|
-
const options = Object.assign(
|
|
267
|
+
function getOptionInfos(fontPath = "", fontOption?: ArgOptionsT, indexIndicate = "") {
|
|
268
|
+
const options: FontPipeOptionT = Object.assign(
|
|
269
|
+
{ fromCSS: "default" } satisfies Partial<FontRangeOptionI>,
|
|
202
270
|
getDefaultOptions(),
|
|
203
|
-
typeof(
|
|
204
|
-
? {
|
|
205
|
-
:
|
|
271
|
+
typeof(fontOption) === "string"
|
|
272
|
+
? { saveDir: fontOption }
|
|
273
|
+
: fontOption
|
|
206
274
|
);
|
|
207
275
|
|
|
208
276
|
const format = options.format;
|
|
209
277
|
const pathInfo = parse(fontPath);
|
|
210
278
|
const fontDir = pathInfo.dir;
|
|
279
|
+
const fontBase = pathInfo.base;
|
|
211
280
|
const fontName = pathInfo.name;
|
|
212
|
-
const fontExt =
|
|
281
|
+
const fontExt = "." + getFormat(format);
|
|
213
282
|
|
|
214
|
-
const dirPath
|
|
215
|
-
const
|
|
283
|
+
const dirPath = Object.prototype.hasOwnProperty.call(options, "saveDir") ? options["saveDir"] : fontDir;
|
|
284
|
+
const nameFormat = options.nameFormat;
|
|
285
|
+
const logFormat = options.logFormat;
|
|
286
|
+
|
|
287
|
+
const initName = fileNameInit(nameFormat, fontName, fontExt);
|
|
288
|
+
const output = getFileName(initName, indexIndicate);
|
|
289
|
+
const logMsg = getConsoleLog(logFormat, fontBase, output);
|
|
216
290
|
|
|
217
|
-
const convertOption = formatOption(format
|
|
291
|
+
const convertOption = formatOption(format);
|
|
218
292
|
const defaultOption = options.defaultArgs;
|
|
219
293
|
const etcOption = options.etcArgs;
|
|
294
|
+
const baseOption = [
|
|
295
|
+
...convertOption,
|
|
296
|
+
...defaultOption,
|
|
297
|
+
...etcOption
|
|
298
|
+
].filter(option => option !== "");
|
|
220
299
|
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
300
|
+
const worker = Worker.getInstance();
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
dirPath,
|
|
304
|
+
initName,
|
|
305
|
+
logMsg,
|
|
306
|
+
|
|
307
|
+
baseOption,
|
|
308
|
+
worker,
|
|
309
|
+
|
|
310
|
+
fromCSS: options.fromCSS
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// == Options - Others =========================================================
|
|
315
|
+
function getSrcInfo(src: string) {
|
|
316
|
+
const first = src.split(",").find((str) => {
|
|
317
|
+
return str.indexOf("url(") === 0;
|
|
318
|
+
});
|
|
319
|
+
if(typeof first === "undefined") return {
|
|
320
|
+
base: "",
|
|
321
|
+
index: 0
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const reStr = "url\\(";
|
|
325
|
+
const reMdl = "(.+?)";
|
|
326
|
+
const reEnd = "\\)";
|
|
327
|
+
const quote = "(\\\\?['\"])?";
|
|
328
|
+
const regex = new RegExp(
|
|
329
|
+
reStr + quote + reMdl + quote + reEnd
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const urlContent = first.match(regex)[2];
|
|
333
|
+
const parsedURL = parse(urlContent);
|
|
334
|
+
return {
|
|
335
|
+
base: parsedURL.base,
|
|
336
|
+
index: parseInt(
|
|
337
|
+
parsedURL.name.split(".").pop() // google font index at latest
|
|
338
|
+
)
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function getSaveOption(dirPath: string, initName: string, index?: number) {
|
|
343
|
+
const fileName = getFileName(initName, index);
|
|
344
|
+
return ("--output-file=" + join(dirPath, fileName));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function getSubsetOption(fontSubsetOption?: FontSubsetOptionT) {
|
|
348
|
+
if(
|
|
349
|
+
typeof fontSubsetOption !== "undefined" &&
|
|
350
|
+
typeof fontSubsetOption !== "string"
|
|
351
|
+
) {
|
|
352
|
+
if("textFile" in fontSubsetOption) {
|
|
353
|
+
return ("--text-file=" + fontSubsetOption.textFile);
|
|
354
|
+
}
|
|
355
|
+
if("text" in fontSubsetOption) {
|
|
356
|
+
return ("--text=" + fontSubsetOption.text );
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return "--glyphs=*";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// == Main =====================================================================
|
|
363
|
+
export async function fontRange(fontPath = "", url = targets.korean, fontRangeOption?: FontRangeOptionT) {
|
|
364
|
+
const {
|
|
365
|
+
dirPath,
|
|
366
|
+
initName,
|
|
367
|
+
logMsg,
|
|
368
|
+
|
|
369
|
+
baseOption,
|
|
370
|
+
worker,
|
|
371
|
+
|
|
372
|
+
fromCSS
|
|
373
|
+
} = getOptionInfos(fontPath, fontRangeOption, "n");
|
|
374
|
+
|
|
375
|
+
const ranges = await parseCSS(dirPath, url);
|
|
376
|
+
const result = ranges.map(async ({src, unicodes}, i) => {
|
|
377
|
+
const srcInfo = getSrcInfo(src);
|
|
378
|
+
const saveOption = getSaveOption(
|
|
379
|
+
dirPath,
|
|
380
|
+
(fromCSS === "srcName" && srcInfo.base !== "")
|
|
381
|
+
? srcInfo.base
|
|
382
|
+
: initName,
|
|
383
|
+
(fromCSS === "srcIndex" && srcInfo.base !== "")
|
|
384
|
+
? srcInfo.index
|
|
385
|
+
: i
|
|
386
|
+
);
|
|
387
|
+
const unicodeRanges = unicodes.split(", ").join(",");
|
|
388
|
+
const unicodeOption = "--unicodes=" + unicodeRanges;
|
|
389
|
+
|
|
390
|
+
const options = [fontPath, saveOption, unicodeOption, ...baseOption];
|
|
391
|
+
const logInfo = (i === 0) ? logMsg : "";
|
|
392
|
+
const result: WorkerRT = await worker.run({ options, log: logInfo});
|
|
393
|
+
return result;
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
return await Promise.all(result);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export async function fontSubset(fontPath = "", fontSubsetOption?: FontSubsetOptionT) {
|
|
400
|
+
const {
|
|
401
|
+
dirPath,
|
|
402
|
+
initName,
|
|
403
|
+
logMsg,
|
|
404
|
+
|
|
405
|
+
baseOption,
|
|
406
|
+
worker
|
|
407
|
+
} = getOptionInfos(fontPath, fontSubsetOption);
|
|
408
|
+
if(!existsSync(dirPath)) await mkdir(dirPath);
|
|
409
|
+
|
|
410
|
+
const subsetOption = getSubsetOption(fontSubsetOption);
|
|
411
|
+
const saveOption = getSaveOption(dirPath, initName);
|
|
412
|
+
|
|
413
|
+
const options = [fontPath, saveOption, subsetOption, ...baseOption];
|
|
414
|
+
const result: WorkerRT = await worker.run({ options, log: logMsg});
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// == Pipeline =================================================================
|
|
419
|
+
export interface FontPipeI {
|
|
420
|
+
fontPath: string;
|
|
421
|
+
option?: FontPipeOptionT;
|
|
422
|
+
}
|
|
423
|
+
function fontPipeExec(subsetTarget: FontPipeI) {
|
|
424
|
+
const { fontPath, option } = subsetTarget;
|
|
425
|
+
|
|
426
|
+
return ((typeof option !== "undefined") &&
|
|
427
|
+
(typeof option.cssFile !== "undefined"))
|
|
428
|
+
? fontRange(fontPath, option.cssFile, option)
|
|
429
|
+
: fontSubset(fontPath, option);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function shardNum(shardStr: string, content: string) {
|
|
433
|
+
const num = Math.abs(parseInt(shardStr, 10));
|
|
434
|
+
|
|
435
|
+
if(isNaN(num) || num <= 0) {
|
|
436
|
+
throw new Error("<" + content + "> must be a positive number");
|
|
437
|
+
}
|
|
438
|
+
return num;
|
|
439
|
+
}
|
|
440
|
+
function getShardInfo(shardEnv: string) {
|
|
441
|
+
const [indexStr, totalStr] = shardEnv.split("/");
|
|
442
|
+
const index = shardNum(indexStr, "index");
|
|
443
|
+
const total = shardNum(totalStr, "total");
|
|
444
|
+
|
|
445
|
+
if(index > total) {
|
|
446
|
+
throw new Error("<index> must be less then <total>")
|
|
447
|
+
}
|
|
448
|
+
return [index, total] as const;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
interface ShardI {
|
|
452
|
+
shard: string;
|
|
453
|
+
shardFormat: string;
|
|
454
|
+
}
|
|
455
|
+
type ShardT = ShardI["shard"] | Partial<ShardI>
|
|
456
|
+
export async function fontPipe(subsetList: FontPipeI[], shard?: ShardT) {
|
|
457
|
+
const shardEnv = (typeof shard === "object" && typeof shard.shard === "string")
|
|
458
|
+
? shard.shard
|
|
459
|
+
: (typeof shard === "object" || typeof shard === "undefined")
|
|
460
|
+
? process.env.SHARD || "1/1"
|
|
461
|
+
: shard;
|
|
462
|
+
const shardFormat = (typeof shard === "object" && typeof shard.shardFormat === "string")
|
|
463
|
+
? shard.shardFormat
|
|
464
|
+
: "== {START}/{END} ==========";
|
|
465
|
+
|
|
466
|
+
const [index, total] = getShardInfo(shardEnv);
|
|
467
|
+
const shardSize = Math.ceil(subsetList.length / total);
|
|
468
|
+
const shardStart = shardSize * (index - 1);
|
|
469
|
+
const shardEnd = shardSize * index;
|
|
470
|
+
|
|
471
|
+
if(shardEnv !== "1/1") {
|
|
472
|
+
const shardMsg = shardFormat
|
|
473
|
+
.replace("{START}", index.toString())
|
|
474
|
+
.replace("{END}", total.toString());
|
|
475
|
+
console.log(shardMsg);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const result = subsetList
|
|
479
|
+
.slice(shardStart, shardEnd)
|
|
480
|
+
.map(fontPipeExec);
|
|
481
|
+
return await Promise.all(result);
|
|
232
482
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
// https://stackoverflow.com/questions/52703321/make-some-properties-optional-in-a-typescript-type
|
|
2
2
|
type Diff<T, U> = T extends U ? never : T;
|
|
3
3
|
export type RequiredByValueExcept<T, TOptional extends keyof T> = Pick<T, Diff<keyof T, TOptional>> & Partial<T>;
|
|
4
|
-
|
|
5
|
-
export type ValueOf<T> = T[keyof T];
|
package/src/worker.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { execaSync } from "@esm2cjs/execa";
|
|
2
|
+
|
|
3
|
+
export interface SubsetI {
|
|
4
|
+
options: string[],
|
|
5
|
+
log?: string
|
|
6
|
+
}
|
|
7
|
+
export default function subset({options, log = ""}: SubsetI) {
|
|
8
|
+
if(log !== "") {
|
|
9
|
+
console.log(log);
|
|
10
|
+
}
|
|
11
|
+
return execaSync("pyftsubset", options);
|
|
12
|
+
}
|
package/tsconfig.json
CHANGED
package/tsconfig.release.json
CHANGED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
name: Node.js CI
|
|
2
|
-
|
|
3
|
-
on: [push, pull_request]
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
steps:
|
|
9
|
-
- uses: actions/checkout@v2
|
|
10
|
-
- uses: volta-cli/action@v1
|
|
11
|
-
- name: pip cache
|
|
12
|
-
id: pip-cache
|
|
13
|
-
uses: actions/cache@v2
|
|
14
|
-
with:
|
|
15
|
-
path: ~/.cache/pip
|
|
16
|
-
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
|
17
|
-
restore-keys: |
|
|
18
|
-
${{ runner.os }}-pip-
|
|
19
|
-
- name: node cache
|
|
20
|
-
id: node-cache
|
|
21
|
-
uses: actions/cache@v2
|
|
22
|
-
with:
|
|
23
|
-
path: node_modules
|
|
24
|
-
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
|
25
|
-
restore-keys: |
|
|
26
|
-
${{ runner.os }}-node-
|
|
27
|
-
- run: pip install -r ./requirements.txt
|
|
28
|
-
- name: npm install
|
|
29
|
-
if: steps.node-cache.outputs.cache-hit != 'true'
|
|
30
|
-
run: npm install
|
|
31
|
-
- run: npm ci --no-audit
|
|
32
|
-
- run: npm run lint --if-present
|
|
33
|
-
- run: npm test
|
|
34
|
-
- run: npm run build --if-present
|
|
35
|
-
env:
|
|
36
|
-
CI: true
|