11/**
2- * This script is responsible for generating a JSON file containing the fonts served by Google Fonts.
3- * The JSON file will be used to populate the typography options in the resume builder.
2+ * This script generates a JSON file containing the fonts served by Google Fonts,
3+ * and also injects the Computer Modern font families as served by https://github.com/bitmaks/cm-web-fonts
4+ * to ensure they appear in the application's typography options.
45 *
5- * Information about the Google Fonts Developer API can be found here: https://developers.google.com/fonts/docs/developer_api
6+ * See:
7+ * - Google Fonts API: https://developers.google.com/fonts/docs/developer_api
8+ * - Computer Modern Web Fonts: https://github.com/bitmaks/cm-web-fonts
69 */
710
811import { mkdir , readFile , writeFile } from "node:fs/promises" ;
@@ -19,6 +22,7 @@ const FONTS_DIR = "./scripts/fonts";
1922const RESPONSE_FILE = `${ FONTS_DIR } /response.json` ;
2023const WEBFONTLIST_FILE = `${ FONTS_DIR } /webfontlist.json` ;
2124
25+ /** Returns JSON from Google Fonts API or (unless --force) from local cache if it exists */
2226async function getGoogleFontsJSON ( ) {
2327 let contents : string | null = null ;
2428
@@ -44,21 +48,99 @@ async function getGoogleFontsJSON() {
4448 return data ;
4549}
4650
51+ /** Map Google Fonts API variant strings to simple weights */
4752function variantToWeight ( variant : Variant ) : Weight | null {
4853 if ( [ "100" , "200" , "300" , "500" , "600" , "700" , "800" , "900" ] . includes ( variant ) ) return variant as Weight ;
4954 if ( variant === "regular" ) return "400" ;
5055 return null ;
5156}
5257
58+ /**
59+ * Helper: Get additional Computer Modern webfonts (manually curated from https://github.com/bitmaks/cm-web-fonts)
60+ * These do NOT come from Google Fonts but should appear in webfontlist.json output.
61+ * Files are delivered via jsDelivr CDN.
62+ */
63+ function getComputerModernWebFonts ( ) : WebFont [ ] {
64+ const CDN = "https://cdn.jsdelivr.net/gh/bitmaks/cm-web-fonts@latest/font" ;
65+
66+ return [
67+ {
68+ type : "web" ,
69+ category : "display" ,
70+ family : "Computer Modern Bright" ,
71+ weights : [ "400" , "700" ] ,
72+ preview : `${ CDN } /Bright/cmunbmr.woff` ,
73+ files : {
74+ "400" : `${ CDN } /Bright/cmunbmr.woff` ,
75+ "400italic" : `${ CDN } /Bright/cmunbmo.woff` ,
76+ "700" : `${ CDN } /Bright/cmunbbx.woff` ,
77+ "700italic" : `${ CDN } /Bright/cmunbxo.woff` ,
78+ } ,
79+ } ,
80+ {
81+ type : "web" ,
82+ category : "serif" ,
83+ family : "Computer Modern Concrete" ,
84+ weights : [ "400" , "700" ] ,
85+ preview : `${ CDN } /Concrete/cmunorm.woff` ,
86+ files : {
87+ "400" : `${ CDN } /Concrete/cmunorm.woff` ,
88+ "400italic" : `${ CDN } /Concrete/cmunobi.woff` ,
89+ "700" : `${ CDN } /Concrete/cmunobx.woff` ,
90+ "700italic" : `${ CDN } /Concrete/cmunoti.woff` ,
91+ } ,
92+ } ,
93+ {
94+ type : "web" ,
95+ category : "sans-serif" ,
96+ family : "Computer Modern Sans" ,
97+ weights : [ "400" , "700" ] ,
98+ preview : `${ CDN } /Sans/cmunss.woff` ,
99+ files : {
100+ "400" : `${ CDN } /Sans/cmunss.woff` ,
101+ "400italic" : `${ CDN } /Sans/cmunsl.woff` ,
102+ "700" : `${ CDN } /Sans/cmunsx.woff` ,
103+ "700italic" : `${ CDN } /Sans/cmunsi.woff` ,
104+ } ,
105+ } ,
106+ {
107+ type : "web" ,
108+ category : "serif" ,
109+ family : "Computer Modern Serif" ,
110+ weights : [ "400" , "700" ] ,
111+ preview : `${ CDN } /Serif/cmunrm.woff` ,
112+ files : {
113+ "400" : `${ CDN } /Serif/cmunrm.woff` ,
114+ "400italic" : `${ CDN } /Serif/cmunti.woff` ,
115+ "700" : `${ CDN } /Serif/cmunbx.woff` ,
116+ "700italic" : `${ CDN } /Serif/cmunbi.woff` ,
117+ } ,
118+ } ,
119+ {
120+ type : "web" ,
121+ category : "monospace" ,
122+ family : "Computer Modern Typewriter" ,
123+ weights : [ "400" , "700" ] ,
124+ preview : `${ CDN } /Typewriter/cmuntt.woff` ,
125+ files : {
126+ "400" : `${ CDN } /Typewriter/cmuntt.woff` ,
127+ "400italic" : `${ CDN } /Typewriter/cmunit.woff` ,
128+ "700" : `${ CDN } /Typewriter/cmuntx.woff` ,
129+ "700italic" : `${ CDN } /Typewriter/cmuntb.woff` ,
130+ } ,
131+ } ,
132+ ] ;
133+ }
134+
53135export async function generateFonts ( ) {
54136 const response = await getGoogleFontsJSON ( ) ;
55- console . log ( `Found ${ response . items . length } fonts in total.` ) ;
137+ console . log ( `Found ${ response . items . length } fonts in total (Google Fonts) .` ) ;
56138
57139 const filteredItems = response . items . filter (
58140 ( item ) => ! skippedFamilies . some ( ( family ) => item . family . includes ( family ) ) ,
59141 ) ;
60142
61- const result : WebFont [ ] = filteredItems . slice ( 0 , argLimit ) . map ( ( item ) => {
143+ const googleFontResults : WebFont [ ] = filteredItems . slice ( 0 , argLimit ) . map ( ( item ) => {
62144 // 1. weights: Only non-italic, convert "regular" to "400"
63145 const weights : Weight [ ] = item . variants . map ( ( v ) => variantToWeight ( v ) ) . filter ( ( w ) : w is Weight => ! ! w ) ;
64146
@@ -82,11 +164,19 @@ export async function generateFonts() {
82164 } satisfies WebFont ;
83165 } ) ;
84166
85- const jsonString = argCompress ? JSON . stringify ( result ) : JSON . stringify ( result , null , 2 ) ;
167+ // Manually append Computer Modern web fonts
168+ const computerModernFonts = getComputerModernWebFonts ( ) ;
169+ const allWebFonts : WebFont [ ] = [ ...computerModernFonts , ...googleFontResults ] ;
170+
171+ console . log (
172+ `Added ${ computerModernFonts . length } Computer Modern Web Fonts. Total output: ${ allWebFonts . length } web fonts.` ,
173+ ) ;
174+
175+ const jsonString = argCompress ? JSON . stringify ( allWebFonts ) : JSON . stringify ( allWebFonts , null , 2 ) ;
86176 await mkdir ( FONTS_DIR , { recursive : true } ) ;
87177 await writeFile ( WEBFONTLIST_FILE , jsonString , "utf-8" ) ;
88178
89- console . log ( `Generated ${ result . length } fonts in the list.` ) ;
179+ console . log ( `Generated ${ allWebFonts . length } fonts in the list (including Computer Modern web fonts) .` ) ;
90180}
91181
92182if ( import . meta. main ) {
0 commit comments