font-range 0.1.2 → 0.2.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/.eslintignore +1 -0
- package/.github/workflows/npm-publish.yml +13 -4
- package/README.md +30 -6
- package/__tests__/font/NotoSansKR-Local.css +1080 -0
- package/__tests__/main.test.ts +37 -6
- package/build/src/main.d.ts +18 -0
- package/build/src/main.js +199 -0
- package/build/src/main.js.map +1 -0
- package/build/src/types.d.ts +4 -0
- package/build/src/types.js +3 -0
- package/build/src/types.js.map +1 -0
- package/package.json +16 -14
- package/requirements.txt +4 -1
- package/src/main.ts +81 -19
- package/src/types.ts +5 -0
- package/tsconfig.json +3 -0
- package/tsconfig.release.json +2 -1
package/__tests__/main.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { targets, getUnicodeRanges, fontRange } from '../src/main';
|
|
2
2
|
import { join, parse } from 'path';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
3
|
+
import { existsSync, unlink, NoParamCallback } from 'fs';
|
|
4
4
|
import fetch from 'node-fetch';
|
|
5
5
|
|
|
6
6
|
describe("Preset Check", () => {
|
|
@@ -22,11 +22,39 @@ describe("Preset Check", () => {
|
|
|
22
22
|
});
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const fontPath = join("__tests__", "font", "NotoSansKR-Regular.otf");
|
|
26
|
+
const fontInfo = parse(fontPath);
|
|
27
|
+
const fontDir = fontInfo.dir;
|
|
28
|
+
const fontName = fontInfo.name;
|
|
29
|
+
const errCallback: NoParamCallback = (err) => {
|
|
30
|
+
if(err) {
|
|
31
|
+
console.error(err);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("FontRange Offline Feature", () => {
|
|
37
|
+
const cssPath = join("__tests__", "font", "NotoSansKR-Local.css");
|
|
38
|
+
beforeAll(() => {
|
|
39
|
+
return fontRange(cssPath, fontPath, {
|
|
40
|
+
nameFormat: "{NAME}.subset.{INDEX}{EXT}"
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("Font Created Check", async () => {
|
|
45
|
+
const ranges = await getUnicodeRanges(fontDir, cssPath);
|
|
46
|
+
const rangesL = ranges.length;
|
|
47
|
+
for (let counts = 0; counts < rangesL; counts++) {
|
|
48
|
+
const eachFontPath = join(fontDir, fontName + ".subset." + counts + ".woff2");
|
|
49
|
+
expect(existsSync(eachFontPath)).toBe(true);
|
|
50
|
+
|
|
51
|
+
// Remove file
|
|
52
|
+
unlink(eachFontPath, errCallback);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("FontRange Online Feature", () => {
|
|
30
58
|
beforeAll(() => {
|
|
31
59
|
return fontRange(targets.korean, fontPath);
|
|
32
60
|
});
|
|
@@ -42,6 +70,9 @@ describe("FontRange Feature", () => {
|
|
|
42
70
|
for (let counts = 0; counts < rangesL; counts++) {
|
|
43
71
|
const eachFontPath = join(fontDir, fontName + "_" + counts + ".woff2");
|
|
44
72
|
expect(existsSync(eachFontPath)).toBe(true);
|
|
73
|
+
|
|
74
|
+
// Remove file
|
|
75
|
+
unlink(eachFontPath, errCallback);
|
|
45
76
|
}
|
|
46
77
|
});
|
|
47
78
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
export declare const targets: {
|
|
3
|
+
weston: string;
|
|
4
|
+
korean: string;
|
|
5
|
+
japanese: string;
|
|
6
|
+
chinese: string;
|
|
7
|
+
chinese_traditional: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function getUnicodeRanges(dirPath?: string, url?: string): Promise<string[]>;
|
|
10
|
+
interface fontRangeOptionI {
|
|
11
|
+
savePath: string;
|
|
12
|
+
format: string;
|
|
13
|
+
nameFormat: string;
|
|
14
|
+
defaultArgs: string;
|
|
15
|
+
etcArgs: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function fontRange(url?: string, fontPath?: string, fontRangeOption?: fontRangeOptionI['savePath'] | Partial<fontRangeOptionI>): Promise<Buffer[]>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fontRange = exports.getUnicodeRanges = exports.targets = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const node_fetch_1 = require("node-fetch");
|
|
8
|
+
const css_tree_1 = require("css-tree");
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
exports.targets = {
|
|
11
|
+
weston: "https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap",
|
|
12
|
+
korean: "https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap",
|
|
13
|
+
japanese: "https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap",
|
|
14
|
+
chinese: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC&display=swap",
|
|
15
|
+
chinese_traditional: "https://fonts.googleapis.com/css2?family=Noto+Sans+TC&display=swap",
|
|
16
|
+
};
|
|
17
|
+
function getFontName(url) {
|
|
18
|
+
const encodedURL = decodeURI(url);
|
|
19
|
+
const urlObj = new URL(encodedURL);
|
|
20
|
+
const urlParams = urlObj.searchParams;
|
|
21
|
+
const fontName = urlParams.get("family");
|
|
22
|
+
return fontName;
|
|
23
|
+
}
|
|
24
|
+
function validURL(str) {
|
|
25
|
+
const pattern = new RegExp("^(https?:\\/\\/)?" +
|
|
26
|
+
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" +
|
|
27
|
+
"((\\d{1,3}\\.){3}\\d{1,3}))" +
|
|
28
|
+
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" +
|
|
29
|
+
"(\\?[;&a-z\\d%_.~+=-]*)?" +
|
|
30
|
+
"(\\#[-a-z\\d_]*)?$", "i");
|
|
31
|
+
return !!pattern.test(str);
|
|
32
|
+
}
|
|
33
|
+
function getCSSPath(dirPath, url) {
|
|
34
|
+
if (validURL(url)) {
|
|
35
|
+
const fontName = getFontName(url);
|
|
36
|
+
const cssPath = (0, path_1.join)(dirPath, fontName + ".css");
|
|
37
|
+
return cssPath;
|
|
38
|
+
}
|
|
39
|
+
if (!(0, fs_1.existsSync)(url)) {
|
|
40
|
+
throw new Error(url + "Not vaild URL or PATH");
|
|
41
|
+
}
|
|
42
|
+
return url;
|
|
43
|
+
}
|
|
44
|
+
function saveCSS(path, url) {
|
|
45
|
+
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
|
|
46
|
+
const headers = new node_fetch_1.Headers({
|
|
47
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;",
|
|
48
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
|
|
49
|
+
});
|
|
50
|
+
const res = yield (0, node_fetch_1.default)(url, {
|
|
51
|
+
method: "GET",
|
|
52
|
+
headers: headers
|
|
53
|
+
});
|
|
54
|
+
const fileStream = (0, fs_1.createWriteStream)(path);
|
|
55
|
+
yield new Promise((resolve, reject) => {
|
|
56
|
+
res.body.pipe(fileStream);
|
|
57
|
+
res.body.on("error", (err) => {
|
|
58
|
+
console.log('File write Error.');
|
|
59
|
+
reject(err);
|
|
60
|
+
});
|
|
61
|
+
fileStream.on("finish", function () {
|
|
62
|
+
resolve();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function readCSS(path) {
|
|
68
|
+
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const readData = [];
|
|
71
|
+
(0, fs_1.createReadStream)(path)
|
|
72
|
+
.on('data', (data) => {
|
|
73
|
+
readData.push(data);
|
|
74
|
+
})
|
|
75
|
+
.on('end', () => (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
|
|
76
|
+
yield Promise.all(readData);
|
|
77
|
+
const css = readData.join('');
|
|
78
|
+
resolve(css);
|
|
79
|
+
}))
|
|
80
|
+
.on('error', reject);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const parseOptions = {
|
|
85
|
+
parseAtrulePrelude: false,
|
|
86
|
+
parseRulePrelude: false,
|
|
87
|
+
parseValue: false
|
|
88
|
+
};
|
|
89
|
+
function loadAST(dirPath, url = exports.targets.korean, parseOption = parseOptions) {
|
|
90
|
+
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
|
|
91
|
+
const cssPath = getCSSPath(dirPath, url);
|
|
92
|
+
if (!(0, fs_1.existsSync)(dirPath)) {
|
|
93
|
+
(0, fs_1.mkdirSync)(dirPath);
|
|
94
|
+
}
|
|
95
|
+
if (!(0, fs_1.existsSync)(cssPath)) {
|
|
96
|
+
yield saveCSS(cssPath, url);
|
|
97
|
+
}
|
|
98
|
+
const css = yield readCSS(cssPath);
|
|
99
|
+
const ast = yield (0, css_tree_1.parse)(css, parseOption);
|
|
100
|
+
return ast;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function parseUnicodeRanges(parsed) {
|
|
104
|
+
const fontFaceL = parsed.children;
|
|
105
|
+
const uniRangeL = fontFaceL.map((faceValObj => {
|
|
106
|
+
const faceBlockL = faceValObj.block.children;
|
|
107
|
+
const uniRangeBlockL = faceBlockL.filter(faceBlock => faceBlock.property === "unicode-range");
|
|
108
|
+
const uniRangeL = uniRangeBlockL.map(uniRangeBlock => uniRangeBlock.value.value);
|
|
109
|
+
return uniRangeL.head.data;
|
|
110
|
+
}));
|
|
111
|
+
const uniRanges = [];
|
|
112
|
+
uniRangeL.forEach(unicodeRange => {
|
|
113
|
+
uniRanges.push(unicodeRange);
|
|
114
|
+
});
|
|
115
|
+
return uniRanges;
|
|
116
|
+
}
|
|
117
|
+
function getUnicodeRanges(dirPath = "src", url = exports.targets.korean) {
|
|
118
|
+
return (0, tslib_1.__awaiter)(this, void 0, void 0, function* () {
|
|
119
|
+
const ast = yield loadAST(dirPath, url);
|
|
120
|
+
return parseUnicodeRanges(ast);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
exports.getUnicodeRanges = getUnicodeRanges;
|
|
124
|
+
function getDefaultOptions() {
|
|
125
|
+
return {
|
|
126
|
+
format: "woff2",
|
|
127
|
+
nameFormat: "{NAME}_{INDEX}{EXT}",
|
|
128
|
+
defaultArgs: "--layout-features='*' \
|
|
129
|
+
--glyph-names \
|
|
130
|
+
--symbol-cmap \
|
|
131
|
+
--legacy-cmap \
|
|
132
|
+
--notdef-glyph \
|
|
133
|
+
--notdef-outline \
|
|
134
|
+
--recommended-glyphs \
|
|
135
|
+
--name-legacy \
|
|
136
|
+
--drop-tables= \
|
|
137
|
+
--name-IDs='*' \
|
|
138
|
+
--name-languages='*'",
|
|
139
|
+
etcArgs: ""
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function getOption(options, key, alterValue) {
|
|
143
|
+
return Object.prototype.hasOwnProperty.call(options, key)
|
|
144
|
+
? options[key]
|
|
145
|
+
: alterValue;
|
|
146
|
+
}
|
|
147
|
+
function getName(nameFormat, fontName, index, fontExt) {
|
|
148
|
+
return nameFormat
|
|
149
|
+
.replace("{NAME}", fontName)
|
|
150
|
+
.replace("{INDEX}", index.toString())
|
|
151
|
+
.replace("{EXT}", fontExt);
|
|
152
|
+
}
|
|
153
|
+
function getFormat(format) {
|
|
154
|
+
switch (format) {
|
|
155
|
+
case "otf": return "otf";
|
|
156
|
+
case "ttf": return "ttf";
|
|
157
|
+
case "woff2": return "woff2";
|
|
158
|
+
case "woff": return "woff";
|
|
159
|
+
case "woff-zopfli": return "woff";
|
|
160
|
+
default: return "woff2";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function formatOption(format, ext = true) {
|
|
164
|
+
const formatName = getFormat(format);
|
|
165
|
+
if (ext)
|
|
166
|
+
return "." + formatName;
|
|
167
|
+
if (format === "otf" || format === "ttf")
|
|
168
|
+
return "";
|
|
169
|
+
return "--flavor='" + ((format === "woff-zopfli")
|
|
170
|
+
? formatName + "' --with-zopfli "
|
|
171
|
+
: formatName + "' ");
|
|
172
|
+
}
|
|
173
|
+
function fontRange(url = exports.targets.korean, fontPath = "", fontRangeOption) {
|
|
174
|
+
const options = Object.assign(getDefaultOptions(), typeof (fontRangeOption) === 'string'
|
|
175
|
+
? { savePath: fontRangeOption }
|
|
176
|
+
: fontRangeOption);
|
|
177
|
+
const format = options.format;
|
|
178
|
+
const pathInfo = (0, path_1.parse)(fontPath);
|
|
179
|
+
const fontDir = pathInfo.dir;
|
|
180
|
+
const fontName = pathInfo.name;
|
|
181
|
+
const fontExt = formatOption(format);
|
|
182
|
+
const dirPath = getOption(options, 'savePath', fontDir);
|
|
183
|
+
const ranges = getUnicodeRanges(dirPath, url);
|
|
184
|
+
const convertOption = formatOption(format, false);
|
|
185
|
+
const defaultOption = options.defaultArgs;
|
|
186
|
+
const etcOption = options.etcArgs;
|
|
187
|
+
const nameFormat = options.nameFormat;
|
|
188
|
+
return ranges.then(eachRanges => eachRanges.map((unicodes, i) => {
|
|
189
|
+
const saveOption = "--output-file='" +
|
|
190
|
+
(0, path_1.join)(dirPath, getName(nameFormat, fontName, i, fontExt)) + "' ";
|
|
191
|
+
const unicodeRanges = unicodes.split(', ').join(',');
|
|
192
|
+
const unicodeOption = "--unicodes='" + unicodeRanges + "' ";
|
|
193
|
+
const options = " '" + fontPath + "' " + saveOption + unicodeOption
|
|
194
|
+
+ convertOption + defaultOption + etcOption;
|
|
195
|
+
return (0, child_process_1.execSync)("pyftsubset" + options);
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
exports.fontRange = fontRange;
|
|
199
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
|
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.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"author": "black7375 <alstjr7375@daum.net>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -27,21 +27,23 @@
|
|
|
27
27
|
"node": ">= 12.13"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@types/jest": "^27.0
|
|
30
|
+
"@types/jest": "^27.4.0",
|
|
31
31
|
"@types/node": "^16.10.9",
|
|
32
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
33
|
-
"@typescript-eslint/parser": "^5.
|
|
32
|
+
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
|
33
|
+
"@typescript-eslint/parser": "^5.10.1",
|
|
34
34
|
"command-exists": "^1.2.9",
|
|
35
|
-
"eslint": "~7.
|
|
35
|
+
"eslint": "~8.7.0",
|
|
36
36
|
"eslint-config-prettier": "~8.3.0",
|
|
37
|
-
"eslint-plugin-jest": "^
|
|
38
|
-
"jest": "^27.
|
|
39
|
-
"prettier": "^2.
|
|
37
|
+
"eslint-plugin-jest": "^26.0.0",
|
|
38
|
+
"jest": "^27.4.7",
|
|
39
|
+
"prettier": "^2.5.1",
|
|
40
40
|
"rimraf": "~3.0.2",
|
|
41
|
-
"ts-jest": "^27.
|
|
41
|
+
"ts-jest": "^27.1.3",
|
|
42
42
|
"tsutils": "~3.21.0",
|
|
43
|
-
"typescript": "^4.4.
|
|
43
|
+
"typescript": "^4.4.5"
|
|
44
44
|
},
|
|
45
|
+
"main": "build/src/main.js",
|
|
46
|
+
"typings": "build/src/main.d.ts",
|
|
45
47
|
"scripts": {
|
|
46
48
|
"clean": "rimraf coverage build tmp",
|
|
47
49
|
"build": "tsc -p tsconfig.release.json",
|
|
@@ -51,14 +53,14 @@
|
|
|
51
53
|
"test:watch": "jest --watch"
|
|
52
54
|
},
|
|
53
55
|
"dependencies": {
|
|
54
|
-
"@types/css-tree": "^1.0.
|
|
56
|
+
"@types/css-tree": "^1.0.7",
|
|
55
57
|
"@types/node-fetch": "^2.5.12",
|
|
56
|
-
"css-tree": "^
|
|
57
|
-
"node-fetch": "^2.6.
|
|
58
|
+
"css-tree": "^2.0.4",
|
|
59
|
+
"node-fetch": "^2.6.7",
|
|
58
60
|
"tslib": "^2.3.1"
|
|
59
61
|
},
|
|
60
62
|
"volta": {
|
|
61
63
|
"node": "16.10.0",
|
|
62
|
-
"npm": "
|
|
64
|
+
"npm": "8.3.2"
|
|
63
65
|
}
|
|
64
66
|
}
|
package/requirements.txt
CHANGED
package/src/main.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { createReadStream, createWriteStream, existsSync, mkdirSync } from 'fs';
|
|
|
3
3
|
import fetch, { Headers } from 'node-fetch';
|
|
4
4
|
import { parse as parseCSS, ParseOptions } from 'css-tree';
|
|
5
5
|
import { execSync } from 'child_process';
|
|
6
|
+
import { RequiredByValueExcept, ValueOf } from './types';
|
|
6
7
|
|
|
7
8
|
// == Resouce Basics ==========================================================
|
|
8
9
|
export const targets = {
|
|
@@ -21,10 +22,28 @@ function getFontName(url: string) {
|
|
|
21
22
|
return fontName;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
// https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
|
|
26
|
+
function validURL(str: string) {
|
|
27
|
+
const pattern = new RegExp("^(https?:\\/\\/)?"+ // protocol
|
|
28
|
+
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|"+ // domain name
|
|
29
|
+
"((\\d{1,3}\\.){3}\\d{1,3}))"+ // OR ip (v4) address
|
|
30
|
+
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*"+ // port and path
|
|
31
|
+
"(\\?[;&a-z\\d%_.~+=-]*)?"+ // query string
|
|
32
|
+
"(\\#[-a-z\\d_]*)?$","i"); // fragment locator
|
|
33
|
+
return !!pattern.test(str);
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
function getCSSPath(dirPath: string, url: string) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
if (validURL(url)) {
|
|
38
|
+
const fontName = getFontName(url);
|
|
39
|
+
const cssPath = join(dirPath, fontName + ".css");
|
|
40
|
+
return cssPath;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!existsSync(url)) {
|
|
44
|
+
throw new Error(url + "Not vaild URL or PATH");
|
|
45
|
+
}
|
|
46
|
+
return url;
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
// == CSS I/O ==================================================================
|
|
@@ -112,6 +131,47 @@ export async function getUnicodeRanges(dirPath = "src", url = targets.korean): P
|
|
|
112
131
|
return parseUnicodeRanges(ast);
|
|
113
132
|
}
|
|
114
133
|
|
|
134
|
+
// == Options =================================================================
|
|
135
|
+
interface fontRangeOptionI {
|
|
136
|
+
savePath: string;
|
|
137
|
+
format: string;
|
|
138
|
+
nameFormat: string;
|
|
139
|
+
defaultArgs: string;
|
|
140
|
+
etcArgs: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getDefaultOptions(): RequiredByValueExcept<fontRangeOptionI, 'savePath'> {
|
|
144
|
+
return {
|
|
145
|
+
format: "woff2",
|
|
146
|
+
nameFormat: "{NAME}_{INDEX}{EXT}",
|
|
147
|
+
defaultArgs: "--layout-features='*' \
|
|
148
|
+
--glyph-names \
|
|
149
|
+
--symbol-cmap \
|
|
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: ""
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getOption(options: Partial<fontRangeOptionI>, key: keyof fontRangeOptionI, alterValue: ValueOf<fontRangeOptionI>) {
|
|
163
|
+
return Object.prototype.hasOwnProperty.call(options, key)
|
|
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
|
+
|
|
115
175
|
// == Main ====================================================================
|
|
116
176
|
function getFormat(format: string) {
|
|
117
177
|
switch(format) {
|
|
@@ -134,37 +194,39 @@ function formatOption(format: string, ext = true) {
|
|
|
134
194
|
: formatName + "' ");
|
|
135
195
|
}
|
|
136
196
|
|
|
137
|
-
export function fontRange(
|
|
138
|
-
|
|
197
|
+
export function fontRange(
|
|
198
|
+
url = targets.korean, fontPath = "",
|
|
199
|
+
fontRangeOption?: fontRangeOptionI['savePath'] | Partial<fontRangeOptionI>
|
|
200
|
+
): Promise<Buffer[]> {
|
|
201
|
+
const options = Object.assign(
|
|
202
|
+
getDefaultOptions(),
|
|
203
|
+
typeof(fontRangeOption) === 'string'
|
|
204
|
+
? { savePath: fontRangeOption }
|
|
205
|
+
: fontRangeOption
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const format = options.format;
|
|
139
209
|
const pathInfo = parse(fontPath);
|
|
140
210
|
const fontDir = pathInfo.dir;
|
|
141
211
|
const fontName = pathInfo.name;
|
|
142
212
|
const fontExt = formatOption(format);
|
|
143
213
|
|
|
144
|
-
const dirPath = (savePath
|
|
214
|
+
const dirPath = getOption(options, 'savePath', fontDir);
|
|
145
215
|
const ranges = getUnicodeRanges(dirPath, url);
|
|
146
216
|
|
|
147
217
|
const convertOption = formatOption(format, false);
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
--symbol-cmap \
|
|
151
|
-
--legacy-cmap \
|
|
152
|
-
--notdef-glyph \
|
|
153
|
-
--notdef-outline \
|
|
154
|
-
--recommended-glyphs \
|
|
155
|
-
--name-legacy \
|
|
156
|
-
--drop-tables= \
|
|
157
|
-
--name-IDs='*' \
|
|
158
|
-
--name-languages='*'";
|
|
218
|
+
const defaultOption = options.defaultArgs;
|
|
219
|
+
const etcOption = options.etcArgs;
|
|
159
220
|
|
|
221
|
+
const nameFormat = options.nameFormat;
|
|
160
222
|
return ranges.then(eachRanges => eachRanges.map((unicodes, i) => {
|
|
161
223
|
const saveOption = "--output-file='" +
|
|
162
|
-
join(dirPath, fontName
|
|
224
|
+
join(dirPath, getName(nameFormat, fontName, i, fontExt)) + "' ";
|
|
163
225
|
const unicodeRanges = unicodes.split(', ').join(',');
|
|
164
226
|
const unicodeOption = "--unicodes='" + unicodeRanges + "' ";
|
|
165
227
|
|
|
166
228
|
const options = " '" + fontPath + "' " + saveOption + unicodeOption
|
|
167
|
-
+ convertOption +
|
|
229
|
+
+ convertOption + defaultOption + etcOption;
|
|
168
230
|
return execSync("pyftsubset" + options);
|
|
169
231
|
}));
|
|
170
232
|
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// https://stackoverflow.com/questions/52703321/make-some-properties-optional-in-a-typescript-type
|
|
2
|
+
type Diff<T, U> = T extends U ? never : T;
|
|
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/tsconfig.json
CHANGED