font-range 0.2.1 → 1.0.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.
Files changed (45) hide show
  1. package/.eslintrc.json +12 -1
  2. package/.gitattributes +3 -0
  3. package/.vim/coc-settings.json +6 -0
  4. package/.yarn/plugins/@yarnpkg/plugin-outdated.cjs +35 -0
  5. package/.yarn/plugins/@yarnpkg/plugin-typescript.cjs +9 -0
  6. package/.yarn/sdks/eslint/bin/eslint.js +20 -0
  7. package/.yarn/sdks/eslint/lib/api.js +20 -0
  8. package/.yarn/sdks/eslint/package.json +6 -0
  9. package/.yarn/sdks/integrations.yml +6 -0
  10. package/.yarn/sdks/prettier/index.js +20 -0
  11. package/.yarn/sdks/prettier/package.json +6 -0
  12. package/.yarn/sdks/typescript/bin/tsc +20 -0
  13. package/.yarn/sdks/typescript/bin/tsserver +20 -0
  14. package/.yarn/sdks/typescript/lib/tsc.js +20 -0
  15. package/.yarn/sdks/typescript/lib/tsserver.js +223 -0
  16. package/.yarn/sdks/typescript/lib/tsserverlibrary.js +223 -0
  17. package/.yarn/sdks/typescript/lib/typescript.js +20 -0
  18. package/.yarn/sdks/typescript/package.json +6 -0
  19. package/README.md +141 -43
  20. package/__tests__/css_load.test.ts +52 -0
  21. package/__tests__/font/NotoSansKR-Local.css +3 -2
  22. package/__tests__/font/subset_glyphs.txt +1 -0
  23. package/__tests__/main.test.ts +241 -52
  24. package/__tests__/preset.test.ts +64 -0
  25. package/__tests__/shared.ts +25 -0
  26. package/build/main.d.ts +49 -0
  27. package/build/main.js +363 -0
  28. package/build/worker.d.ts +5 -0
  29. package/build/worker.js +10 -0
  30. package/jest.config.js +5 -5
  31. package/package.json +29 -26
  32. package/requirements.txt +4 -4
  33. package/src/main.ts +356 -106
  34. package/src/{types.ts → types.d.ts} +0 -2
  35. package/src/worker.ts +12 -0
  36. package/tsconfig.json +1 -0
  37. package/tsconfig.release.json +3 -2
  38. package/.github/workflows/nodejs.yml +0 -36
  39. package/.github/workflows/npm-publish.yml +0 -106
  40. package/build/src/main.d.ts +0 -18
  41. package/build/src/main.js +0 -199
  42. package/build/src/main.js.map +0 -1
  43. package/build/src/types.d.ts +0 -4
  44. package/build/src/types.js +0 -3
  45. package/build/src/types.js.map +0 -1
package/build/main.js ADDED
@@ -0,0 +1,363 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fontPipe = exports.fontSubset = exports.fontRange = exports.defaultArgs = exports.parseCSS = exports.targets = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path_1 = require("path");
6
+ const fs_1 = require("fs");
7
+ const promises_1 = require("fs/promises");
8
+ const piscina_1 = tslib_1.__importDefault(require("piscina"));
9
+ const node_fetch_1 = tslib_1.__importStar(require("@esm2cjs/node-fetch"));
10
+ const css_tree_1 = require("css-tree");
11
+ class Worker {
12
+ constructor() { }
13
+ static getInstance() {
14
+ if (!Worker.instance) {
15
+ Worker.instance = new piscina_1.default({
16
+ filename: (0, path_1.join)(__dirname, "../", "build", "worker.js")
17
+ });
18
+ }
19
+ return Worker.instance;
20
+ }
21
+ }
22
+ exports.targets = {
23
+ weston: "https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap",
24
+ korean: "https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap",
25
+ japanese: "https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap",
26
+ chinese: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC&display=swap",
27
+ chinese_traditional: "https://fonts.googleapis.com/css2?family=Noto+Sans+TC&display=swap",
28
+ };
29
+ function getFontName(url) {
30
+ const encodedURL = decodeURI(url);
31
+ const urlObj = new URL(encodedURL);
32
+ const urlParams = urlObj.searchParams;
33
+ const fontName = urlParams.get("family");
34
+ return fontName;
35
+ }
36
+ function validURL(str) {
37
+ const pattern = new RegExp("^(https?:\\/\\/)?" +
38
+ "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" +
39
+ "((\\d{1,3}\\.){3}\\d{1,3}))" +
40
+ "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" +
41
+ "(\\?[;&a-z\\d%_.~+=-]*)?" +
42
+ "(\\#[-a-z\\d_]*)?$", "i");
43
+ return !!pattern.test(str);
44
+ }
45
+ function getCSSPath(dirPath, url) {
46
+ if (validURL(url)) {
47
+ const fontName = getFontName(url);
48
+ const cssPath = (0, path_1.join)(dirPath, fontName + ".css");
49
+ return cssPath;
50
+ }
51
+ if (!(0, fs_1.existsSync)(url)) {
52
+ throw new Error("Not vaild URL or PATH: " + url);
53
+ }
54
+ return url;
55
+ }
56
+ function saveCSS(path, url) {
57
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
58
+ const version = "109.0";
59
+ const headers = new node_fetch_1.Headers({
60
+ "Accept": "text/html,application/xhtml+xml,application/xml;",
61
+ "User-Agent": `Mozilla/5.0 (Windows NT 10.0; rv:${version}) Gecko/20100101 Firefox/${version}`
62
+ });
63
+ const res = yield (0, node_fetch_1.default)(url, {
64
+ method: "GET",
65
+ headers: headers
66
+ });
67
+ if (res.status !== 200) {
68
+ throw new Error("Not vaild URL: " + url);
69
+ }
70
+ return new Promise((resolve, reject) => {
71
+ const fileStream = (0, fs_1.createWriteStream)(path);
72
+ res.body.pipe(fileStream);
73
+ res.body.on("error", (err) => {
74
+ console.log("File write Error.");
75
+ reject(err);
76
+ });
77
+ res.body.on("close", () => {
78
+ fileStream.close();
79
+ });
80
+ fileStream.on("close", () => {
81
+ resolve();
82
+ });
83
+ });
84
+ });
85
+ }
86
+ function readCSS(path) {
87
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
88
+ return new Promise((resolve, reject) => {
89
+ const readData = [];
90
+ (0, fs_1.createReadStream)(path)
91
+ .on("data", (data) => {
92
+ readData.push(data);
93
+ })
94
+ .on("end", () => {
95
+ const css = readData.join("");
96
+ resolve(css);
97
+ })
98
+ .on("error", reject);
99
+ });
100
+ });
101
+ }
102
+ const parseOptions = {
103
+ parseAtrulePrelude: false,
104
+ parseRulePrelude: false,
105
+ parseValue: false
106
+ };
107
+ function loadAST(dirPath, url = exports.targets.korean, parseOption = parseOptions) {
108
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
109
+ const cssPath = getCSSPath(dirPath, url);
110
+ if (!(0, fs_1.existsSync)(dirPath)) {
111
+ yield (0, promises_1.mkdir)(dirPath);
112
+ }
113
+ if (!(0, fs_1.existsSync)(cssPath)) {
114
+ yield saveCSS(cssPath, url);
115
+ }
116
+ const css = yield readCSS(cssPath);
117
+ return (0, css_tree_1.parse)(css, parseOption);
118
+ });
119
+ }
120
+ function setBlock(block, elem, property) {
121
+ if (elem.property === property) {
122
+ if (elem.value.type === "Raw") {
123
+ block[property] = elem.value.value;
124
+ }
125
+ }
126
+ }
127
+ function parseCSS(dirPath = "src", url = exports.targets.korean) {
128
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
129
+ const ast = yield loadAST(dirPath, url);
130
+ const parsed = [];
131
+ (0, css_tree_1.walk)(ast, { visit: "Atrule", enter(node) {
132
+ if (node.name === "font-face") {
133
+ const block = {
134
+ src: "",
135
+ unicodes: ""
136
+ };
137
+ (0, css_tree_1.walk)(node, { visit: "Declaration", enter(elem) {
138
+ setBlock(block, elem, "src");
139
+ setBlock(block, elem, "unicode-range");
140
+ } });
141
+ if (block.src !== "" || block.unicodes !== "") {
142
+ parsed.push(block);
143
+ }
144
+ }
145
+ } });
146
+ return parsed;
147
+ });
148
+ }
149
+ exports.parseCSS = parseCSS;
150
+ exports.defaultArgs = [
151
+ "--layout-features=*",
152
+ "--glyph-names",
153
+ "--symbol-cmap",
154
+ "--legacy-cmap",
155
+ "--notdef-glyph",
156
+ "--notdef-outline",
157
+ "--recommended-glyphs",
158
+ "--name-legacy",
159
+ "--drop-tables=",
160
+ "--name-IDs=*",
161
+ "--name-languages=*"
162
+ ];
163
+ function getDefaultOptions() {
164
+ return {
165
+ format: "woff2",
166
+ nameFormat: "{NAME}_{INDEX}{EXT}",
167
+ logFormat: "Convert {ORIGIN} -> {OUTPUT}",
168
+ defaultArgs: exports.defaultArgs,
169
+ etcArgs: []
170
+ };
171
+ }
172
+ function getFormat(format) {
173
+ switch (format) {
174
+ case "otf": return "otf";
175
+ case "ttf": return "ttf";
176
+ case "woff2": return "woff2";
177
+ case "woff": return "woff";
178
+ case "woff-zopfli": return "woff";
179
+ }
180
+ }
181
+ function formatOption(format) {
182
+ const formatName = getFormat(format);
183
+ if (format === "otf" || format === "ttf")
184
+ return [""];
185
+ return [
186
+ "--flavor=" + formatName,
187
+ (format === "woff-zopfli") ? "--with-zopfli" : ""
188
+ ];
189
+ }
190
+ function fileNameInit(nameFormat, fontName, fontExt) {
191
+ return nameFormat
192
+ .replace("{NAME}", fontName)
193
+ .replace("{EXT}", fontExt);
194
+ }
195
+ function getFileName(initName, index) {
196
+ return initName
197
+ .replace("{INDEX}", (typeof index === "number")
198
+ ? index.toString()
199
+ : (typeof index === "string")
200
+ ? index
201
+ : "");
202
+ }
203
+ function getConsoleLog(logFormat, origin, output) {
204
+ return logFormat
205
+ .replace("{ORIGIN}", origin)
206
+ .replace("{OUTPUT}", output);
207
+ }
208
+ function getOptionInfos(fontPath = "", fontOption, indexIndicate = "") {
209
+ const options = Object.assign({ fromCSS: "default" }, getDefaultOptions(), typeof (fontOption) === "string"
210
+ ? { saveDir: fontOption }
211
+ : fontOption);
212
+ const format = options.format;
213
+ const pathInfo = (0, path_1.parse)(fontPath);
214
+ const fontDir = pathInfo.dir;
215
+ const fontBase = pathInfo.base;
216
+ const fontName = pathInfo.name;
217
+ const fontExt = "." + getFormat(format);
218
+ const dirPath = Object.prototype.hasOwnProperty.call(options, "saveDir") ? options["saveDir"] : fontDir;
219
+ const nameFormat = options.nameFormat;
220
+ const logFormat = options.logFormat;
221
+ const initName = fileNameInit(nameFormat, fontName, fontExt);
222
+ const output = getFileName(initName, indexIndicate);
223
+ const logMsg = getConsoleLog(logFormat, fontBase, output);
224
+ const convertOption = formatOption(format);
225
+ const defaultOption = options.defaultArgs;
226
+ const etcOption = options.etcArgs;
227
+ const baseOption = [
228
+ ...convertOption,
229
+ ...defaultOption,
230
+ ...etcOption
231
+ ].filter(option => option !== "");
232
+ const worker = Worker.getInstance();
233
+ return {
234
+ dirPath,
235
+ initName,
236
+ logMsg,
237
+ baseOption,
238
+ worker,
239
+ fromCSS: options.fromCSS
240
+ };
241
+ }
242
+ function getSrcInfo(src) {
243
+ const first = src.split(",").find((str) => {
244
+ return str.indexOf("url(") === 0;
245
+ });
246
+ if (typeof first === "undefined")
247
+ return {
248
+ base: "",
249
+ index: 0
250
+ };
251
+ const reStr = "url\\(";
252
+ const reMdl = "(.+?)";
253
+ const reEnd = "\\)";
254
+ const quote = "(\\\\?['\"])?";
255
+ const regex = new RegExp(reStr + quote + reMdl + quote + reEnd);
256
+ const urlContent = first.match(regex)[2];
257
+ const parsedURL = (0, path_1.parse)(urlContent);
258
+ return {
259
+ base: parsedURL.base,
260
+ index: parseInt(parsedURL.name.split(".").pop())
261
+ };
262
+ }
263
+ function getSaveOption(dirPath, initName, index) {
264
+ const fileName = getFileName(initName, index);
265
+ return ("--output-file=" + (0, path_1.join)(dirPath, fileName));
266
+ }
267
+ function getSubsetOption(fontSubsetOption) {
268
+ if (typeof fontSubsetOption !== "undefined" &&
269
+ typeof fontSubsetOption !== "string") {
270
+ if ("textFile" in fontSubsetOption) {
271
+ return ("--text-file=" + fontSubsetOption.textFile);
272
+ }
273
+ if ("text" in fontSubsetOption) {
274
+ return ("--text=" + fontSubsetOption.text);
275
+ }
276
+ }
277
+ return "--glyphs=*";
278
+ }
279
+ function fontRange(fontPath = "", url = exports.targets.korean, fontRangeOption) {
280
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
281
+ const { dirPath, initName, logMsg, baseOption, worker, fromCSS } = getOptionInfos(fontPath, fontRangeOption, "n");
282
+ const ranges = yield parseCSS(dirPath, url);
283
+ const result = ranges.map(({ src, unicodes }, i) => tslib_1.__awaiter(this, void 0, void 0, function* () {
284
+ const srcInfo = getSrcInfo(src);
285
+ const saveOption = getSaveOption(dirPath, (fromCSS === "srcName" && srcInfo.base !== "")
286
+ ? srcInfo.base
287
+ : initName, (fromCSS === "srcIndex" && srcInfo.base !== "")
288
+ ? srcInfo.index
289
+ : i);
290
+ const unicodeRanges = unicodes.split(", ").join(",");
291
+ const unicodeOption = "--unicodes=" + unicodeRanges;
292
+ const options = [fontPath, saveOption, unicodeOption, ...baseOption];
293
+ const logInfo = (i === 0) ? logMsg : "";
294
+ const result = yield worker.run({ options, log: logInfo });
295
+ return result;
296
+ }));
297
+ return yield Promise.all(result);
298
+ });
299
+ }
300
+ exports.fontRange = fontRange;
301
+ function fontSubset(fontPath = "", fontSubsetOption) {
302
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
303
+ const { dirPath, initName, logMsg, baseOption, worker } = getOptionInfos(fontPath, fontSubsetOption);
304
+ if (!(0, fs_1.existsSync)(dirPath))
305
+ yield (0, promises_1.mkdir)(dirPath);
306
+ const subsetOption = getSubsetOption(fontSubsetOption);
307
+ const saveOption = getSaveOption(dirPath, initName);
308
+ const options = [fontPath, saveOption, subsetOption, ...baseOption];
309
+ const result = yield worker.run({ options, log: logMsg });
310
+ return result;
311
+ });
312
+ }
313
+ exports.fontSubset = fontSubset;
314
+ function fontPipeExec(subsetTarget) {
315
+ const { fontPath, option } = subsetTarget;
316
+ return ((typeof option !== "undefined") &&
317
+ (typeof option.cssFile !== "undefined"))
318
+ ? fontRange(fontPath, option.cssFile, option)
319
+ : fontSubset(fontPath, option);
320
+ }
321
+ function shardNum(shardStr, content) {
322
+ const num = Math.abs(parseInt(shardStr, 10));
323
+ if (isNaN(num) || num <= 0) {
324
+ throw new Error("<" + content + "> must be a positive number");
325
+ }
326
+ return num;
327
+ }
328
+ function getShardInfo(shardEnv) {
329
+ const [indexStr, totalStr] = shardEnv.split("/");
330
+ const index = shardNum(indexStr, "index");
331
+ const total = shardNum(totalStr, "total");
332
+ if (index > total) {
333
+ throw new Error("<index> must be less then <total>");
334
+ }
335
+ return [index, total];
336
+ }
337
+ function fontPipe(subsetList, shard) {
338
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
339
+ const shardEnv = (typeof shard === "object" && typeof shard.shard === "string")
340
+ ? shard.shard
341
+ : (typeof shard === "object" || typeof shard === "undefined")
342
+ ? process.env.SHARD || "1/1"
343
+ : shard;
344
+ const shardFormat = (typeof shard === "object" && typeof shard.shardFormat === "string")
345
+ ? shard.shardFormat
346
+ : "== {START}/{END} ==========";
347
+ const [index, total] = getShardInfo(shardEnv);
348
+ const shardSize = Math.ceil(subsetList.length / total);
349
+ const shardStart = shardSize * (index - 1);
350
+ const shardEnd = shardSize * index;
351
+ if (shardEnv !== "1/1") {
352
+ const shardMsg = shardFormat
353
+ .replace("{START}", index.toString())
354
+ .replace("{END}", total.toString());
355
+ console.log(shardMsg);
356
+ }
357
+ const result = subsetList
358
+ .slice(shardStart, shardEnd)
359
+ .map(fontPipeExec);
360
+ return yield Promise.all(result);
361
+ });
362
+ }
363
+ exports.fontPipe = fontPipe;
@@ -0,0 +1,5 @@
1
+ export interface SubsetI {
2
+ options: string[];
3
+ log?: string;
4
+ }
5
+ export default function subset({ options, log }: SubsetI): import("@esm2cjs/execa").ExecaSyncReturnValue<string>;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const execa_1 = require("@esm2cjs/execa");
4
+ function subset({ options, log = "" }) {
5
+ if (log !== "") {
6
+ console.log(log);
7
+ }
8
+ return (0, execa_1.execaSync)("pyftsubset", options);
9
+ }
10
+ exports.default = subset;
package/jest.config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2
2
  module.exports = {
3
- preset: 'ts-jest',
4
- testEnvironment: 'node',
3
+ preset: "ts-jest",
4
+ testEnvironment: "node",
5
5
  transform: {
6
6
  "^.+\\.tsx?$": "ts-jest"
7
7
  },
@@ -13,10 +13,10 @@ module.exports = {
13
13
  "json",
14
14
  "node",
15
15
  ],
16
- testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(ts|js)x?$',
16
+ testRegex: "/__tests__/.*\.(test|spec)\.(ts|js)x?$",
17
17
  coverageDirectory: 'coverage',
18
18
  collectCoverageFrom: [
19
- 'src/**/*.{ts,tsx,js,jsx}',
20
- '!src/**/*.d.ts',
19
+ "src/**/*.{ts,tsx,js,jsx}",
20
+ "!src/**/*.d.ts"
21
21
  ],
22
22
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "font-range",
3
3
  "description": " Font subset with google font's ML result.",
4
- "version": "0.2.1",
4
+ "version": "1.0.0",
5
5
  "author": "black7375 <alstjr7375@daum.net>",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -24,26 +24,27 @@
24
24
  },
25
25
  "homepage": "https://github.com/black7375/font-range#readme",
26
26
  "engines": {
27
- "node": ">= 12.13"
27
+ "node": ">= 16.16"
28
28
  },
29
29
  "devDependencies": {
30
- "@types/jest": "^27.4.0",
31
- "@types/node": "^16.10.9",
32
- "@typescript-eslint/eslint-plugin": "^5.10.1",
33
- "@typescript-eslint/parser": "^5.10.1",
30
+ "@types/command-exists": "^1.2.0",
31
+ "@types/jest": "^29.2.5",
32
+ "@types/node": "^18.11.18",
33
+ "@typescript-eslint/eslint-plugin": "^5.48.0",
34
+ "@typescript-eslint/parser": "^5.48.0",
34
35
  "command-exists": "^1.2.9",
35
- "eslint": "~8.7.0",
36
- "eslint-config-prettier": "~8.3.0",
37
- "eslint-plugin-jest": "^26.0.0",
38
- "jest": "^27.4.7",
39
- "prettier": "^2.5.1",
40
- "rimraf": "~3.0.2",
41
- "ts-jest": "^27.1.3",
42
- "tsutils": "~3.21.0",
43
- "typescript": "^4.4.5"
36
+ "eslint": "^8.31.0",
37
+ "eslint-config-prettier": "^8.6.0",
38
+ "eslint-plugin-jest": "^27.2.1",
39
+ "jest": "^29.3.1",
40
+ "prettier": "^2.8.1",
41
+ "rimraf": "^3.0.2",
42
+ "ts-jest": "^29.0.3",
43
+ "tsutils": "^3.21.0",
44
+ "typescript": "^4.9.4"
44
45
  },
45
- "main": "build/src/main.js",
46
- "typings": "build/src/main.d.ts",
46
+ "main": "build/main.js",
47
+ "typings": "build/main.d.ts",
47
48
  "scripts": {
48
49
  "clean": "rimraf coverage build tmp",
49
50
  "build": "tsc -p tsconfig.release.json",
@@ -53,14 +54,16 @@
53
54
  "test:watch": "jest --watch"
54
55
  },
55
56
  "dependencies": {
56
- "@types/css-tree": "^1.0.7",
57
- "@types/node-fetch": "^2.5.12",
58
- "css-tree": "^2.0.4",
59
- "node-fetch": "^2.6.7",
60
- "tslib": "^2.3.1"
57
+ "@esm2cjs/execa": "^6.1.1-cjs.1",
58
+ "@esm2cjs/node-fetch": "^3.2.10",
59
+ "@types/css-tree": "^2.0.1",
60
+ "css-tree": "^2.3.1",
61
+ "piscina": "^3.2.0",
62
+ "tslib": "^2.4.1"
61
63
  },
62
64
  "volta": {
63
- "node": "16.10.0",
64
- "npm": "8.3.2"
65
- }
66
- }
65
+ "node": "16.16.0",
66
+ "npm": "9.2.0"
67
+ },
68
+ "packageManager": "yarn@3.3.1"
69
+ }
package/requirements.txt CHANGED
@@ -2,9 +2,9 @@
2
2
  # https://pypi.org/project/pip-outdated/
3
3
 
4
4
  appdirs~=1.4.4
5
- six~=1.10
5
+ six~=1.16.0
6
6
  fs<3,>=2.2.0
7
- pytz~=2021.1
8
- fonttools~=4.29.0
7
+ pytz~=2022.7
8
+ fonttools~=4.38.0
9
9
  Brotli~=1.0.9
10
- zopfli~=0.1.8
10
+ zopfli~=0.2.2