@ship-ui/core 0.18.13 → 0.19.2
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/assets/mcp/components.json +57 -34
- package/bin/ship-fg-scanner +0 -0
- package/bin/ship-fg.mjs +3588 -0
- package/bin/ship-fg.ts +1 -2
- package/bin/src/scanner.zig +322 -0
- package/bin/src/ship-fg.ts +101 -183
- package/bin/src/subset.ts +97 -0
- package/bin/src/utilities.js +19 -19
- package/bin/src/utilities.ts +19 -19
- package/fesm2022/ship-ui-core.mjs +520 -334
- package/fesm2022/ship-ui-core.mjs.map +1 -1
- package/package.json +6 -5
- package/snippets/ship-ui.code-snippets +0 -7
- package/styles/components/ship-sidenav.scss +1 -1
- package/styles/components/ship-sortable.scss +13 -22
- package/types/ship-ui-core.d.ts +78 -46
- package/bin/ship-fg-node +0 -40
- package/bin/src/ship-fg-node.js +0 -373
package/bin/src/ship-fg.ts
CHANGED
|
@@ -1,142 +1,71 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { spawnSync } from 'child_process';
|
|
4
4
|
import { FSWatcher, watch } from 'fs';
|
|
5
|
-
import
|
|
5
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
6
|
+
import { dirname, join, resolve } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { gzipSync } from 'zlib';
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
const _dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
11
|
|
|
9
|
-
import
|
|
12
|
+
import subsetFont from './subset';
|
|
13
|
+
|
|
14
|
+
import { formatFileSize, InputArguments, SupportedFontTypes } from './utilities';
|
|
15
|
+
|
|
16
|
+
const CWD_PATH = process.cwd();
|
|
17
|
+
const PHOSPHOR_SRC_PATH = resolve(CWD_PATH, 'node_modules', '@phosphor-icons', 'web', 'src');
|
|
10
18
|
|
|
11
19
|
let writtenCssSize = 0;
|
|
12
20
|
let compressedCssSize = 0;
|
|
21
|
+
let watchers: FSWatcher[] = [];
|
|
13
22
|
|
|
14
23
|
const run = async (
|
|
15
24
|
PROJECT_SRC: string,
|
|
16
|
-
LIB_ICONS: string[],
|
|
17
25
|
PROJECT_PUBLIC: string,
|
|
18
|
-
GLYPH_MAP: Record<string, [string, string]>,
|
|
19
26
|
TARGET_FONT_TYPE: SupportedFontTypes,
|
|
20
27
|
values: InputArguments
|
|
21
28
|
) => {
|
|
22
29
|
const startTime = performance.now();
|
|
30
|
+
const actualDir = _dirname;
|
|
31
|
+
const scannerPath = resolve(actualDir, './ship-fg-scanner');
|
|
32
|
+
const shipUiDir = resolve(actualDir, '../');
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const regex = /<sh-icon[^>]*>\s*((?!{{.*?}})[^<]*?)\s*<\/sh-icon>/g;
|
|
27
|
-
const regex2 = /shicon:([^']+)/g;
|
|
28
|
-
const iconsFound = new Set<string>(LIB_ICONS);
|
|
29
|
-
const missingIcons = new Set<string>();
|
|
30
|
-
|
|
31
|
-
for await (const file of glob.scan(`${PROJECT_SRC}`)) {
|
|
32
|
-
const fileText = await Bun.file(`${PROJECT_SRC}/${file}`).text();
|
|
33
|
-
const matches = Array.from((fileText as any).matchAll(regex), (m: string) => m[1]);
|
|
34
|
-
|
|
35
|
-
if (matches?.length) {
|
|
36
|
-
for (let index = 0; index < matches.length; index++) {
|
|
37
|
-
const match = matches[index];
|
|
34
|
+
let groupedIcons: { [key: string]: [string, string][] } = {};
|
|
35
|
+
let missingIconsArray: string[] = [];
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
try {
|
|
38
|
+
const proc = spawnSync(scannerPath, [PROJECT_SRC, shipUiDir, CWD_PATH]);
|
|
39
|
+
if (proc.error || proc.status !== 0) {
|
|
40
|
+
console.error('Error running scanner:', proc.stderr?.toString() || proc.error);
|
|
41
|
+
throw new Error('Native scanner failed');
|
|
41
42
|
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for await (const file of tsGlob.scan(`${PROJECT_SRC}`)) {
|
|
45
|
-
const fileText = await Bun.file(`${PROJECT_SRC}/${file}`).text();
|
|
46
|
-
const matches = Array.from((fileText as any).matchAll(regex2), (m: string) => m[1]);
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
44
|
+
const parsed = JSON.parse(proc.stdout?.toString() || '{}');
|
|
45
|
+
groupedIcons = parsed;
|
|
46
|
+
missingIconsArray = parsed.missing || [];
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error('Failed to run high-performance zig scanner:', err);
|
|
49
|
+
throw err;
|
|
55
50
|
}
|
|
56
51
|
|
|
57
|
-
const groupedIcons = Array.from(iconsFound).reduce(
|
|
58
|
-
(acc, icon) => {
|
|
59
|
-
const bold = icon.endsWith('-bold');
|
|
60
|
-
const thin = icon.endsWith('-thin');
|
|
61
|
-
const light = icon.endsWith('-light');
|
|
62
|
-
const fill = icon.endsWith('-fill');
|
|
63
|
-
const duotone = icon.endsWith('-duotone');
|
|
64
|
-
const regular = !bold && !thin && !light && !fill && !duotone;
|
|
65
|
-
const glyph = GLYPH_MAP[icon];
|
|
66
|
-
|
|
67
|
-
if (!glyph) {
|
|
68
|
-
missingIcons.add(icon);
|
|
69
|
-
return acc;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (bold) {
|
|
73
|
-
acc['bold'].push([icon, '']);
|
|
74
|
-
acc['bold'].push(glyph);
|
|
75
|
-
}
|
|
76
|
-
if (thin) {
|
|
77
|
-
acc['thin'].push([icon, '']);
|
|
78
|
-
acc['thin'].push(glyph);
|
|
79
|
-
}
|
|
80
|
-
if (light) {
|
|
81
|
-
acc['light'].push([icon, '']);
|
|
82
|
-
acc['light'].push(glyph);
|
|
83
|
-
}
|
|
84
|
-
if (fill) {
|
|
85
|
-
acc['fill'].push([icon, '']);
|
|
86
|
-
acc['fill'].push(glyph);
|
|
87
|
-
}
|
|
88
|
-
if (regular) {
|
|
89
|
-
acc['regular'].push([icon, '']);
|
|
90
|
-
acc['regular'].push(glyph);
|
|
91
|
-
}
|
|
92
|
-
if (duotone) {
|
|
93
|
-
acc['duotone'].push([icon, '']);
|
|
94
|
-
acc['duotone'].push(glyph);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return acc;
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
bold: [],
|
|
101
|
-
thin: [],
|
|
102
|
-
light: [],
|
|
103
|
-
fill: [],
|
|
104
|
-
regular: [],
|
|
105
|
-
duotone: [],
|
|
106
|
-
text: [],
|
|
107
|
-
} as {
|
|
108
|
-
[key: string]: [string, string][];
|
|
109
|
-
}
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
const missingIconsArray = Array.from(missingIcons);
|
|
113
|
-
|
|
114
52
|
if (missingIconsArray.length) {
|
|
115
|
-
console.log('Following icons does not exist in font: \n ',
|
|
53
|
+
console.log('Following icons does not exist in font: \n ', missingIconsArray);
|
|
116
54
|
}
|
|
117
55
|
|
|
118
56
|
writeCssFile(PROJECT_PUBLIC, values, groupedIcons, TARGET_FONT_TYPE);
|
|
119
57
|
|
|
120
58
|
const fontTypes = ['bold', 'thin', 'light', 'fill', 'regular'].filter((x) => groupedIcons[x].length > 0);
|
|
121
|
-
const targetFormat = (TARGET_FONT_TYPE as SupportedFontTypes) === 'ttf' ? 'truetype' : TARGET_FONT_TYPE;
|
|
122
59
|
const fonts = fontTypes.map(async (fontType) => {
|
|
123
60
|
const glyphs = uniqueString(groupedIcons[fontType].map((icon) => icon[0]).join(''));
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
process.cwd(),
|
|
127
|
-
'node_modules',
|
|
128
|
-
'@phosphor-icons',
|
|
129
|
-
'web',
|
|
130
|
-
'src',
|
|
61
|
+
const fullPath = join(
|
|
62
|
+
PHOSPHOR_SRC_PATH,
|
|
131
63
|
fontType,
|
|
132
|
-
|
|
64
|
+
`Phosphor${fontType === 'regular' ? '' : '-' + capitalize(fontType)}.ttf`
|
|
133
65
|
);
|
|
134
|
-
|
|
135
|
-
const arrayBuffer =
|
|
136
|
-
const subsetBuffer = await subsetFont(Buffer.from(arrayBuffer), glyphs
|
|
137
|
-
targetFormat,
|
|
138
|
-
noLayoutClosure: true,
|
|
139
|
-
} as any);
|
|
66
|
+
const fileBuffer = await readFile(fullPath);
|
|
67
|
+
const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength);
|
|
68
|
+
const subsetBuffer = await subsetFont(Buffer.from(arrayBuffer), glyphs);
|
|
140
69
|
|
|
141
70
|
return subsetBuffer;
|
|
142
71
|
});
|
|
@@ -148,12 +77,11 @@ const run = async (
|
|
|
148
77
|
for (let i = 0; i < _fonts.length; i++) {
|
|
149
78
|
const subsetBuffer = _fonts[i];
|
|
150
79
|
const fontType = fontTypes[i];
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
);
|
|
80
|
+
const fontPath = `${PROJECT_PUBLIC}/sh${fontType === 'regular' ? '' : '-' + fontType}.${TARGET_FONT_TYPE}`;
|
|
81
|
+
await writeFile(fontPath, subsetBuffer);
|
|
82
|
+
const fontWrites = subsetBuffer.byteLength;
|
|
155
83
|
|
|
156
|
-
const compressedFont =
|
|
84
|
+
const compressedFont = gzipSync(subsetBuffer);
|
|
157
85
|
const iconsOnGroup = groupedIcons[fontType].filter((icon) => icon[1] === '').map((icon) => icon[0]);
|
|
158
86
|
|
|
159
87
|
totalFontSize += fontWrites;
|
|
@@ -249,8 +177,9 @@ sh-icon {
|
|
|
249
177
|
-webkit-font-smoothing: antialiased;
|
|
250
178
|
-moz-osx-font-smoothing: grayscale;
|
|
251
179
|
}`;
|
|
252
|
-
|
|
253
|
-
const
|
|
180
|
+
await writeFile(`${PROJECT_PUBLIC}/ship.css`, cssFileContent);
|
|
181
|
+
const cssWrites = Buffer.byteLength(cssFileContent);
|
|
182
|
+
const compressedCss = gzipSync(cssFileContent);
|
|
254
183
|
|
|
255
184
|
writtenCssSize = cssWrites;
|
|
256
185
|
compressedCssSize = compressedCss.length;
|
|
@@ -274,7 +203,7 @@ const textMateSnippet = async (GLYPH_MAP: Record<string, [string, string]>) => {
|
|
|
274
203
|
}
|
|
275
204
|
`;
|
|
276
205
|
|
|
277
|
-
await
|
|
206
|
+
await writeFile('./.vscode/html.code-snippets', iconsSnippetContent);
|
|
278
207
|
};
|
|
279
208
|
|
|
280
209
|
function capitalize(str: string) {
|
|
@@ -283,46 +212,25 @@ function capitalize(str: string) {
|
|
|
283
212
|
|
|
284
213
|
export const main = async (values: InputArguments) => {
|
|
285
214
|
const TARGET_FONT_TYPE: SupportedFontTypes = 'woff2' as SupportedFontTypes;
|
|
286
|
-
const
|
|
287
|
-
const
|
|
215
|
+
const actualDir = _dirname;
|
|
216
|
+
const packageJsonPath = resolve(actualDir, '../package.json');
|
|
288
217
|
const PROJECT_SRC = values.src;
|
|
289
218
|
const PROJECT_PUBLIC = values.out;
|
|
290
|
-
const fontVariants = ['bold', 'thin', 'light', 'fill', 'regular'];
|
|
291
|
-
|
|
292
|
-
const GLYPH_MAPS = await Promise.all(
|
|
293
|
-
fontVariants.map(async (fontVariant) => {
|
|
294
|
-
const selectionJsonFullPath = path.resolve(
|
|
295
|
-
process.cwd(),
|
|
296
|
-
'node_modules',
|
|
297
|
-
'@phosphor-icons',
|
|
298
|
-
'web',
|
|
299
|
-
'src',
|
|
300
|
-
fontVariant,
|
|
301
|
-
'selection.json'
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
const selectionJson = await Bun.file(selectionJsonFullPath).json();
|
|
305
|
-
const unicodeObj = getUnicodeObject(selectionJson.icons, fontVariant === 'duotone');
|
|
306
219
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return
|
|
314
|
-
...acc,
|
|
315
|
-
...curr,
|
|
316
|
-
};
|
|
220
|
+
const selectionJsonPath = resolve(PHOSPHOR_SRC_PATH, 'regular', 'selection.json');
|
|
221
|
+
const selectionData = await readFile(selectionJsonPath, 'utf8');
|
|
222
|
+
const GLYPH_MAP = JSON.parse(selectionData).icons.reduce(
|
|
223
|
+
(acc: Record<string, [string, string]>, iconWrapper: any) => {
|
|
224
|
+
const name = iconWrapper.properties.name;
|
|
225
|
+
acc[name] = [name, ''];
|
|
226
|
+
return acc;
|
|
317
227
|
},
|
|
318
228
|
{} as Record<string, [string, string]>
|
|
319
229
|
);
|
|
320
230
|
|
|
321
|
-
let LIB_ICONS = packageJson.libraryIcons as string[];
|
|
322
|
-
|
|
323
231
|
try {
|
|
324
232
|
await textMateSnippet(GLYPH_MAP);
|
|
325
|
-
await run(PROJECT_SRC,
|
|
233
|
+
await run(PROJECT_SRC, PROJECT_PUBLIC, TARGET_FONT_TYPE, values);
|
|
326
234
|
} catch (error) {
|
|
327
235
|
console.error('An error occurred during the initial run:', error);
|
|
328
236
|
if (!values.watch && !values.watchLib) {
|
|
@@ -335,52 +243,62 @@ export const main = async (values: InputArguments) => {
|
|
|
335
243
|
}
|
|
336
244
|
|
|
337
245
|
console.log('\nWatching for file changes. Press Cmd+C to stop.');
|
|
338
|
-
let watchers: FSWatcher[] = [];
|
|
339
|
-
|
|
340
|
-
function killWatchers() {
|
|
341
|
-
console.log(`\n✅ The icon font generation watch process has been stopped.`);
|
|
342
|
-
|
|
343
|
-
for (let index = 0; index < watchers.length; index++) {
|
|
344
|
-
const watcher = watchers[index];
|
|
345
|
-
watcher.close();
|
|
346
|
-
watcher.removeAllListeners();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
process.exit(0);
|
|
350
|
-
}
|
|
351
246
|
|
|
352
247
|
process.on('SIGINT', killWatchers);
|
|
353
248
|
process.on('SIGTERM', killWatchers);
|
|
354
249
|
process.on('SIGBREAK', killWatchers);
|
|
355
250
|
|
|
251
|
+
const debouncedRun = debounce(async (triggerName: string | null) => {
|
|
252
|
+
console.log(`Change detected (${triggerName}), regenerating...`);
|
|
253
|
+
try {
|
|
254
|
+
await run(PROJECT_SRC, PROJECT_PUBLIC, TARGET_FONT_TYPE, values);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error('Error during watched run:', error);
|
|
257
|
+
}
|
|
258
|
+
}, 50);
|
|
259
|
+
|
|
356
260
|
if (values.watch) {
|
|
357
261
|
const excludeFolders = ['node_modules', '.git', '.vscode', 'bin', 'assets'].concat([PROJECT_PUBLIC]);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
await run(PROJECT_SRC, LIB_ICONS, PROJECT_PUBLIC, GLYPH_MAP, TARGET_FONT_TYPE, values);
|
|
363
|
-
} catch (error) {
|
|
364
|
-
console.error('Error during watched run:', error);
|
|
262
|
+
watchers.push(
|
|
263
|
+
watch(PROJECT_SRC, { recursive: true }, (_, filename) => {
|
|
264
|
+
if (filename && !excludeFolders.some((folder) => resolve(join(PROJECT_SRC, filename)).includes(folder))) {
|
|
265
|
+
debouncedRun(filename);
|
|
365
266
|
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
watchers.push(watcher);
|
|
267
|
+
})
|
|
268
|
+
);
|
|
370
269
|
}
|
|
371
270
|
|
|
372
271
|
if (values.watchLib) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
await run(PROJECT_SRC, LIB_ICONS, PROJECT_PUBLIC, GLYPH_MAP, TARGET_FONT_TYPE, values);
|
|
379
|
-
} catch (error) {
|
|
380
|
-
console.error('Error during package.json watched run:', error);
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
watchers.push(watcher);
|
|
272
|
+
watchers.push(
|
|
273
|
+
watch(packageJsonPath, {}, (_, filename) => {
|
|
274
|
+
debouncedRun(filename || 'package.json');
|
|
275
|
+
})
|
|
276
|
+
);
|
|
385
277
|
}
|
|
386
278
|
};
|
|
279
|
+
|
|
280
|
+
function debounce<T extends (...args: any[]) => void>(func: T, wait: number): T {
|
|
281
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
282
|
+
return ((...args: any[]) => {
|
|
283
|
+
if (timeout) clearTimeout(timeout);
|
|
284
|
+
timeout = setTimeout(() => func(...args), wait);
|
|
285
|
+
}) as T;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function killWatchers() {
|
|
289
|
+
console.log(`\n✅ The icon font generation watch process has been stopped.`);
|
|
290
|
+
|
|
291
|
+
for (let index = 0; index < watchers.length; index++) {
|
|
292
|
+
try {
|
|
293
|
+
const watcher = watchers[index];
|
|
294
|
+
watcher.close();
|
|
295
|
+
if (typeof watcher.removeAllListeners === 'function') {
|
|
296
|
+
watcher.removeAllListeners();
|
|
297
|
+
}
|
|
298
|
+
} catch (e) {
|
|
299
|
+
// Ignore native watcher cleanup errors
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
process.exit(0);
|
|
304
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
import wawoff2 from 'wawoff2';
|
|
8
|
+
|
|
9
|
+
let harfbuzzInst: { harfbuzzJsWasm: any; heapu8: Uint8Array } | null = null;
|
|
10
|
+
|
|
11
|
+
async function loadAndInitializeHarfbuzz() {
|
|
12
|
+
if (harfbuzzInst) return harfbuzzInst;
|
|
13
|
+
|
|
14
|
+
const wasmPath = require.resolve('harfbuzzjs/hb-subset.wasm');
|
|
15
|
+
const wasmBuffer = await readFile(wasmPath);
|
|
16
|
+
const { instance } = await WebAssembly.instantiate(wasmBuffer);
|
|
17
|
+
const harfbuzzJsWasm = instance.exports as any;
|
|
18
|
+
const heapu8 = new Uint8Array(harfbuzzJsWasm.memory.buffer);
|
|
19
|
+
|
|
20
|
+
harfbuzzInst = { harfbuzzJsWasm, heapu8 };
|
|
21
|
+
return harfbuzzInst;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function subsetFont(originalFont: Buffer, text: string): Promise<Buffer> {
|
|
25
|
+
if (typeof text !== 'string') {
|
|
26
|
+
throw new Error('The subset text must be given as a string');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { harfbuzzJsWasm, heapu8 } = await loadAndInitializeHarfbuzz();
|
|
30
|
+
|
|
31
|
+
const input = harfbuzzJsWasm.hb_subset_input_create_or_fail();
|
|
32
|
+
if (input === 0) {
|
|
33
|
+
throw new Error('hb_subset_input_create_or_fail (harfbuzz) returned zero');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fontBuffer = harfbuzzJsWasm.malloc(originalFont.byteLength);
|
|
37
|
+
heapu8.set(new Uint8Array(originalFont), fontBuffer);
|
|
38
|
+
|
|
39
|
+
const blob = harfbuzzJsWasm.hb_blob_create(fontBuffer, originalFont.byteLength, 2, 0, 0);
|
|
40
|
+
const face = harfbuzzJsWasm.hb_face_create(blob, 0);
|
|
41
|
+
harfbuzzJsWasm.hb_blob_destroy(blob);
|
|
42
|
+
|
|
43
|
+
// equivalent of --font-features=*
|
|
44
|
+
const layoutFeatures = harfbuzzJsWasm.hb_subset_input_set(input, 6); // HB_SUBSET_SETS_LAYOUT_FEATURE_TAG
|
|
45
|
+
harfbuzzJsWasm.hb_set_clear(layoutFeatures);
|
|
46
|
+
harfbuzzJsWasm.hb_set_invert(layoutFeatures);
|
|
47
|
+
|
|
48
|
+
// equivalent of noLayoutClosure: true
|
|
49
|
+
harfbuzzJsWasm.hb_subset_input_set_flags(
|
|
50
|
+
input,
|
|
51
|
+
harfbuzzJsWasm.hb_subset_input_get_flags(input) | 0x00000200
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Add mapped unicode indices
|
|
55
|
+
const inputUnicodes = harfbuzzJsWasm.hb_subset_input_unicode_set(input);
|
|
56
|
+
for (const c of text) {
|
|
57
|
+
const cp = c.codePointAt(0);
|
|
58
|
+
if (cp) {
|
|
59
|
+
harfbuzzJsWasm.hb_set_add(inputUnicodes, cp);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let subset;
|
|
64
|
+
try {
|
|
65
|
+
subset = harfbuzzJsWasm.hb_subset_or_fail(face, input);
|
|
66
|
+
if (subset === 0) {
|
|
67
|
+
harfbuzzJsWasm.hb_face_destroy(face);
|
|
68
|
+
harfbuzzJsWasm.free(fontBuffer);
|
|
69
|
+
throw new Error('hb_subset_or_fail returned zero. Input corrupted?');
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
harfbuzzJsWasm.hb_subset_input_destroy(input);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const result = harfbuzzJsWasm.hb_face_reference_blob(subset);
|
|
76
|
+
const offset = harfbuzzJsWasm.hb_blob_get_data(result, 0);
|
|
77
|
+
const subsetByteLength = harfbuzzJsWasm.hb_blob_get_length(result);
|
|
78
|
+
|
|
79
|
+
if (subsetByteLength === 0) {
|
|
80
|
+
harfbuzzJsWasm.hb_blob_destroy(result);
|
|
81
|
+
harfbuzzJsWasm.hb_face_destroy(subset);
|
|
82
|
+
harfbuzzJsWasm.hb_face_destroy(face);
|
|
83
|
+
harfbuzzJsWasm.free(fontBuffer);
|
|
84
|
+
throw new Error('Failed to extract subset TTF blob from harfbuzz');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const subsetFontBuffer = Buffer.from(heapu8.subarray(offset, offset + subsetByteLength));
|
|
88
|
+
|
|
89
|
+
harfbuzzJsWasm.hb_blob_destroy(result);
|
|
90
|
+
harfbuzzJsWasm.hb_face_destroy(subset);
|
|
91
|
+
harfbuzzJsWasm.hb_face_destroy(face);
|
|
92
|
+
harfbuzzJsWasm.free(fontBuffer);
|
|
93
|
+
|
|
94
|
+
// Direct compression from TTF -> WOFF2 internally
|
|
95
|
+
const woff2Buffer = await wawoff2.compress(subsetFontBuffer);
|
|
96
|
+
return Buffer.from(woff2Buffer);
|
|
97
|
+
}
|
package/bin/src/utilities.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
export function createNameCodeObject(jsonData) {
|
|
2
|
-
|
|
1
|
+
// export function createNameCodeObject(jsonData) {
|
|
2
|
+
// const nameCodeObject = {};
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
// for (let i = 0; i < jsonData.length; i++) {
|
|
5
|
+
// const item = jsonData[i];
|
|
6
|
+
// const hexCode = item.properties.code.toString(16);
|
|
7
|
+
// const codePoint = parseInt(hexCode, 16);
|
|
8
|
+
// const glyph = String.fromCodePoint(codePoint);
|
|
9
|
+
// nameCodeObject[item.properties.ligatures] = glyph;
|
|
10
|
+
// }
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
}
|
|
12
|
+
// return nameCodeObject;
|
|
13
|
+
// }
|
|
14
14
|
|
|
15
15
|
export const getUnicodeObject = (jsonData, isDuotone) => {
|
|
16
16
|
const nameCodeObject = {};
|
|
@@ -32,17 +32,17 @@ export const getUnicodeObject = (jsonData, isDuotone) => {
|
|
|
32
32
|
return nameCodeObject;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export const createCodepointObject = (jsonData) => {
|
|
36
|
-
|
|
35
|
+
// export const createCodepointObject = (jsonData) => {
|
|
36
|
+
// const nameCodeObject = {};
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// for (let i = 0; i < jsonData.length; i++) {
|
|
39
|
+
// const item = jsonData[i];
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// nameCodeObject[item.properties.ligatures] = item.properties.code;
|
|
42
|
+
// }
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
};
|
|
44
|
+
// return nameCodeObject;
|
|
45
|
+
// };
|
|
46
46
|
|
|
47
47
|
export function formatFileSize(bytes, dm = 2) {
|
|
48
48
|
if (bytes == 0) return '0 Bytes';
|
package/bin/src/utilities.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
export function createNameCodeObject(jsonData: Item[]): Record<string, string> {
|
|
2
|
-
|
|
1
|
+
// export function createNameCodeObject(jsonData: Item[]): Record<string, string> {
|
|
2
|
+
// const nameCodeObject: Record<string, string> = {};
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
// for (let i = 0; i < jsonData.length; i++) {
|
|
5
|
+
// const item = jsonData[i];
|
|
6
|
+
// const hexCode = item.properties.code.toString(16); // Convert decimal to hex
|
|
7
|
+
// const codePoint = parseInt(hexCode, 16); // Parse hex to integer code point
|
|
8
|
+
// const glyph = String.fromCodePoint(codePoint); // Create the glyph
|
|
9
|
+
// nameCodeObject[item.properties.ligatures] = glyph;
|
|
10
|
+
// }
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
}
|
|
12
|
+
// return nameCodeObject;
|
|
13
|
+
// }
|
|
14
14
|
|
|
15
15
|
export const getUnicodeObject = (jsonData: Item[], isDuotone = false): Record<string, [string, string]> => {
|
|
16
16
|
const nameCodeObject: Record<string, [string, string]> = {};
|
|
@@ -32,17 +32,17 @@ export const getUnicodeObject = (jsonData: Item[], isDuotone = false): Record<st
|
|
|
32
32
|
return nameCodeObject;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export const createCodepointObject = (jsonData: Item[]): Record<string, number> => {
|
|
36
|
-
|
|
35
|
+
// export const createCodepointObject = (jsonData: Item[]): Record<string, number> => {
|
|
36
|
+
// const nameCodeObject: Record<string, number> = {};
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// for (let i = 0; i < jsonData.length; i++) {
|
|
39
|
+
// const item = jsonData[i];
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// nameCodeObject[item.properties.ligatures] = item.properties.code;
|
|
42
|
+
// }
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
};
|
|
44
|
+
// return nameCodeObject;
|
|
45
|
+
// };
|
|
46
46
|
|
|
47
47
|
export function formatFileSize(bytes: number, dm = 2) {
|
|
48
48
|
if (bytes == 0) return '0 Bytes';
|