1
- /* eslint-disable unicorn/prefer-module */
2
- import { webfont } from 'webfont'
3
1
import {
4
2
writeFile ,
5
3
ensureFile ,
4
+ createWriteStream ,
5
+ createReadStream ,
6
+ readFile ,
6
7
} from 'fs-extra'
7
8
import {
8
9
resolve ,
@@ -11,74 +12,157 @@ import {
11
12
basename ,
12
13
} from 'node:path'
13
14
import { createHash } from 'node:crypto'
14
- import type { Result } from 'webfont/dist/src/types/Result'
15
15
import { EOL } from 'node:os'
16
+ import { fileURLToPath } from 'node:url'
17
+ import glob from 'fast-glob'
18
+ import type { Glyph , SVGIconStream } from 'svgicons2svgfont'
19
+ import { SVGIcons2SVGFontStream } from 'svgicons2svgfont'
20
+ import svg2ttf from 'svg2ttf'
21
+ import ttf2woff from 'ttf2woff'
22
+ import ttf2woff2 from 'ttf2woff2'
23
+ import ttf2eot from 'ttf2eot'
24
+
25
+ const SVG_DIR = fileURLToPath ( new URL ( '../svg/' , import . meta. url ) )
26
+ const FONT_DIR = fileURLToPath ( new URL ( '../font/' , import . meta. url ) )
27
+
28
+ function withDirname ( a : string , b : string ) {
29
+ const dirA = basename ( dirname ( a ) )
30
+ const dirB = basename ( dirname ( b ) )
31
+
32
+ return dirA . localeCompare ( dirB )
33
+ }
16
34
17
- const SVG_DIR = resolve ( __dirname , '../svg/' )
18
- const FONT_DIR = resolve ( __dirname , '../font/' )
19
-
20
- export async function createFont ( ) {
21
- const result = await webfont ( {
22
- files : join ( SVG_DIR , '**' , '16.svg' ) ,
23
- fontName : 'Persona Icon' ,
24
- templateClassName : 'pi' ,
25
- fixedWidth : true ,
26
- normalize : true ,
27
- fontHeight : 1000 ,
28
- round : 10e12 ,
29
- sort : true ,
30
- glyphTransformFn ( obj ) {
31
- return Object . assign ( obj , { name : basename ( dirname ( ( obj as any ) . path ) ) } )
32
- } ,
35
+ async function createSVG ( ) {
36
+ await ensureFile ( join ( FONT_DIR , 'persona-icon.svg' ) )
37
+
38
+ const icons = await glob ( './**/16.svg' , { cwd : SVG_DIR } )
39
+ const glyphs = await new Promise < Glyph [ ] > ( ( resolve , reject ) => {
40
+ const fontStream = new SVGIcons2SVGFontStream ( {
41
+ fontName : 'Persona Icon' ,
42
+ fixedWidth : true ,
43
+ normalize : true ,
44
+ fontHeight : 1000 ,
45
+ round : 10e12 ,
46
+ } )
47
+
48
+ fontStream . pipe ( createWriteStream ( join ( FONT_DIR , 'persona-icon.svg' ) ) )
49
+ . on ( 'finish' , ( ) => {
50
+ resolve ( fontStream . glyphs )
51
+ } )
52
+ . on ( 'error' , ( error ) => {
53
+ reject ( error )
54
+ } )
55
+
56
+ let unicode = 0xEA_00
57
+
58
+ for ( const icon of icons . toSorted ( withDirname ) ) {
59
+ const glyph : SVGIconStream = createReadStream ( join ( SVG_DIR , icon ) ) as any
60
+
61
+ glyph . metadata = {
62
+ name : basename ( dirname ( icon ) ) ,
63
+ unicode : [ String . fromCodePoint ( ++ unicode ) ] ,
64
+ }
65
+
66
+ fontStream . write ( glyph )
67
+ }
68
+
69
+ fontStream . end ( )
33
70
} )
34
71
35
- if ( result . ttf ) {
36
- await ensureFile ( resolve ( FONT_DIR , 'persona-icon.ttf' ) )
37
- await writeFile ( resolve ( FONT_DIR , 'persona-icon.ttf' ) , result . ttf . toString ( ) )
38
- }
72
+ await ensureFile ( join ( FONT_DIR , 'persona-icon.json' ) )
73
+ await writeFile ( join ( FONT_DIR , 'persona-icon.json' ) , JSON . stringify ( glyphs , undefined , 2 ) )
39
74
40
- if ( result . woff ) {
41
- await ensureFile ( resolve ( FONT_DIR , 'persona-icon.woff' ) )
42
- await writeFile ( resolve ( FONT_DIR , 'persona-icon.woff' ) , result . woff . toString ( ) )
43
- }
75
+ const buffer = await readFile ( join ( FONT_DIR , 'persona-icon.svg' ) )
44
76
45
- if ( result . woff2 ) {
46
- await ensureFile ( resolve ( FONT_DIR , 'persona-icon.woff2' ) )
47
- await writeFile ( resolve ( FONT_DIR , 'persona-icon.woff2' ) , result . woff2 . toString ( ) )
48
- }
77
+ return { glyphs, buffer }
78
+ }
49
79
50
- if ( result . eot ) {
51
- await ensureFile ( resolve ( FONT_DIR , 'persona-icon.eot' ) )
52
- await writeFile ( resolve ( FONT_DIR , 'persona-icon.eot' ) , result . eot . toString ( ) )
53
- }
80
+ async function createTtf ( svg : Buffer ) {
81
+ const result = svg2ttf ( svg . toString ( 'utf8' ) )
82
+ const output = Buffer . from ( result . buffer )
54
83
55
- if ( result . svg ) {
56
- await ensureFile ( resolve ( FONT_DIR , 'persona-icon.svg' ) )
57
- await writeFile ( resolve ( FONT_DIR , 'persona-icon.svg' ) , result . svg . toString ( ) )
58
- }
84
+ await ensureFile ( resolve ( FONT_DIR , 'persona-icon.ttf' ) )
85
+ await writeFile ( resolve ( FONT_DIR , 'persona-icon.ttf' ) , output )
59
86
60
- await createCss ( result )
87
+ return output
88
+ }
89
+
90
+ async function createWoff ( ttf : Buffer ) {
91
+ const result = ttf2woff ( ttf )
92
+
93
+ await ensureFile ( resolve ( FONT_DIR , 'persona-icon.woff' ) )
94
+ await writeFile ( resolve ( FONT_DIR , 'persona-icon.woff' ) , result )
95
+
96
+ return result
97
+ }
98
+
99
+ async function createWoff2 ( ttf : Buffer ) {
100
+ const result = ttf2woff2 ( ttf )
101
+
102
+ await ensureFile ( resolve ( FONT_DIR , 'persona-icon.woff2' ) )
103
+ await writeFile ( resolve ( FONT_DIR , 'persona-icon.woff2' ) , result )
104
+
105
+ return result
106
+ }
107
+
108
+ async function createEot ( ttf : Buffer ) {
109
+ const result = ttf2eot ( ttf )
110
+
111
+ await ensureFile ( resolve ( FONT_DIR , 'persona-icon.eot' ) )
112
+ await writeFile ( resolve ( FONT_DIR , 'persona-icon.eot' ) , result )
113
+
114
+ return result
115
+ }
116
+
117
+ export async function createFont ( ) {
118
+ const {
119
+ glyphs,
120
+ buffer : svg ,
121
+ } = await createSVG ( )
122
+
123
+ const ttf = await createTtf ( svg )
124
+ const woff = await createWoff ( ttf )
125
+ const woff2 = await createWoff2 ( ttf )
126
+ const eot = await createEot ( ttf )
127
+
128
+ await createCss ( {
129
+ glyphs,
130
+ svg,
131
+ ttf,
132
+ eot,
133
+ woff,
134
+ woff2,
135
+ } )
61
136
}
62
137
63
138
function hash ( buffer : Buffer | string , length = 4 ) {
64
139
return createHash ( 'shake256' , { outputLength : length } )
65
- . update ( buffer . toString ( ) )
140
+ . update ( buffer )
66
141
. digest ( 'hex' )
67
142
}
68
143
69
144
function toCode ( unicode : string ) {
70
145
return unicode . codePointAt ( 0 ) ?. toString ( 16 ) . padStart ( 4 , '0' ) ?? ''
71
146
}
72
147
73
- async function createCss ( result : Result ) {
148
+ interface CreateCSS {
149
+ glyphs : Glyph [ ] ,
150
+ svg : Buffer ,
151
+ ttf : Buffer ,
152
+ eot : Buffer ,
153
+ woff : Buffer ,
154
+ woff2 : Buffer ,
155
+ }
156
+
157
+ async function createCss ( input : CreateCSS ) {
74
158
let css = `@font-face {
75
159
font-family: 'Persona Icon';
76
- src: url('../font/persona-icon.eot?${ hash ( result . eot ?? '' ) } ');
77
- src: url('../font/persona-icon.eot?${ hash ( result . eot ?? '' ) } #iefix') format('embedded-opentype'),
78
- url('../font/persona-icon.woff2?${ hash ( result . woff2 ?? '' ) } ') format('woff2'),
79
- url('../font/persona-icon.woff?${ hash ( result . woff ?? '' ) } ') format('woff'),
80
- url('../font/persona-icon.ttf?${ hash ( result . ttf ?? '' ) } ') format('truetype'),
81
- url('../font/persona-icon.svg?${ hash ( result . svg ?? '' ) } #${ encodeURIComponent ( 'Persona Icon' ) } ') format('svg');
160
+ src: url('../font/persona-icon.eot?${ hash ( input . eot ) } ');
161
+ src: url('../font/persona-icon.eot?${ hash ( input . eot ) } #iefix') format('embedded-opentype'),
162
+ url('../font/persona-icon.woff2?${ hash ( input . woff2 ) } ') format('woff2'),
163
+ url('../font/persona-icon.woff?${ hash ( input . woff ) } ') format('woff'),
164
+ url('../font/persona-icon.ttf?${ hash ( input . ttf ) } ') format('truetype'),
165
+ url('../font/persona-icon.svg?${ hash ( input . svg ?? '' ) } #${ encodeURIComponent ( 'Persona Icon' ) } ') format('svg');
82
166
font-weight: normal;
83
167
font-style: normal;
84
168
}
@@ -99,9 +183,9 @@ async function createCss (result: Result) {
99
183
-moz-osx-font-smoothing: grayscale;
100
184
}${ EOL } ${ EOL } `
101
185
102
- for ( const glyph of ( result . glyphsData ?? [ ] ) ) {
103
- const name = glyph . metadata ?. name
104
- const content = glyph . metadata ?. unicode ?. at ( 0 )
186
+ for ( const glyph of ( input . glyphs ?? [ ] ) ) {
187
+ const name = glyph . name
188
+ const content = glyph . unicode ?. at ( 0 )
105
189
106
190
if ( name && content )
107
191
css += `.pi-${ name } ::before { content: '\\${ toCode ( content ) } ' }${ EOL } `
0 commit comments