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.
@@ -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
- describe("FontRange Feature", () => {
26
- const fontPath = join("__tests__", "font", "NotoSansKR-Regular.otf");
27
- const fontInfo = parse(fontPath);
28
- const fontDir = fontInfo.dir;
29
- const fontName = fontInfo.name;
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,4 @@
1
+ declare type Diff<T, U> = T extends U ? never : T;
2
+ export declare type RequiredByValueExcept<T, TOptional extends keyof T> = Pick<T, Diff<keyof T, TOptional>> & Partial<T>;
3
+ export declare type ValueOf<T> = T[keyof T];
4
+ export {};
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -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.2",
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.2",
30
+ "@types/jest": "^27.4.0",
31
31
  "@types/node": "^16.10.9",
32
- "@typescript-eslint/eslint-plugin": "^5.0.0",
33
- "@typescript-eslint/parser": "^5.0.0",
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.31.0",
35
+ "eslint": "~8.7.0",
36
36
  "eslint-config-prettier": "~8.3.0",
37
- "eslint-plugin-jest": "^25.0.5",
38
- "jest": "^27.2.5",
39
- "prettier": "^2.4.1",
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.0.5",
41
+ "ts-jest": "^27.1.3",
42
42
  "tsutils": "~3.21.0",
43
- "typescript": "^4.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.6",
56
+ "@types/css-tree": "^1.0.7",
55
57
  "@types/node-fetch": "^2.5.12",
56
- "css-tree": "^1.1.3",
57
- "node-fetch": "^2.6.1",
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": "7.24.1"
64
+ "npm": "8.3.2"
63
65
  }
64
66
  }
package/requirements.txt CHANGED
@@ -1,7 +1,10 @@
1
+ ## pip-outdated requirements.txt
2
+ # https://pypi.org/project/pip-outdated/
3
+
1
4
  appdirs~=1.4.4
2
5
  six~=1.10
3
6
  fs<3,>=2.2.0
4
7
  pytz~=2021.1
5
- fonttools~=4.25.2
8
+ fonttools~=4.29.0
6
9
  Brotli~=1.0.9
7
10
  zopfli~=0.1.8
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
- const fontName = getFontName(url);
26
- const cssPath = join(dirPath, fontName + ".css");
27
- return cssPath;
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(url = targets.korean, fontPath = "", savePath?: string,
138
- format = "woff2"): Promise<Buffer[]> {
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 === undefined) ? fontDir : 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 defautOptions = "--layout-features='*' \
149
- --glyph-names \
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 + "_" + i + fontExt) + "' ";
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 + defautOptions;
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
@@ -24,5 +24,8 @@
24
24
  "include": [
25
25
  "src/**/*",
26
26
  "__tests__/**/*"
27
+ ],
28
+ "exclude": [
29
+ "build/**/*"
27
30
  ]
28
31
  }
@@ -3,7 +3,8 @@
3
3
  "compilerOptions": {
4
4
  "rootDir": ".",
5
5
  "outDir": "build",
6
- "removeComments": true
6
+ "removeComments": true,
7
+ "declaration": true
7
8
  },
8
9
  "include": [
9
10
  "src/**/*"