1- /* eslint-disable unicorn/prefer-module */
2- import { webfont } from 'webfont'
31import {
42 writeFile ,
53 ensureFile ,
4+ createWriteStream ,
5+ createReadStream ,
6+ readFile ,
67} from 'fs-extra'
78import {
89 resolve ,
@@ -11,74 +12,157 @@ import {
1112 basename ,
1213} from 'node:path'
1314import { createHash } from 'node:crypto'
14- import type { Result } from 'webfont/dist/src/types/Result'
1515import { 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+ }
1634
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 ( )
3370 } )
3471
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 ) )
3974
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' ) )
4476
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+ }
4979
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 )
5483
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 )
5986
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+ } )
61136}
62137
63138function hash ( buffer : Buffer | string , length = 4 ) {
64139 return createHash ( 'shake256' , { outputLength : length } )
65- . update ( buffer . toString ( ) )
140+ . update ( buffer )
66141 . digest ( 'hex' )
67142}
68143
69144function toCode ( unicode : string ) {
70145 return unicode . codePointAt ( 0 ) ?. toString ( 16 ) . padStart ( 4 , '0' ) ?? ''
71146}
72147
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 ) {
74158 let css = `@font-face {
75159 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');
82166 font-weight: normal;
83167 font-style: normal;
84168}
@@ -99,9 +183,9 @@ async function createCss (result: Result) {
99183 -moz-osx-font-smoothing: grayscale;
100184}${ EOL } ${ EOL } `
101185
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 )
105189
106190 if ( name && content )
107191 css += `.pi-${ name } ::before { content: '\\${ toCode ( content ) } ' }${ EOL } `
0 commit comments