Setting the file. One moment.
Inject Fonts · Embedded Captions · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page 14.42
Hershey Script1
Script Lib Dna
scripts/ inject-fonts.cjs
JavaScript · 169 lines · 6 KB
15 * base64 woff2, no sub-resources), finds which of those families the HTML uses,
16 * and inlines just those @font-face blocks into a <style id="hf-embedded-fonts">.
17 * Idempotent (re-running replaces the block). Families already declared in the
18 * HTML, hyperframes-canonical families, and CSS generics are left untouched.
19 *
20 * Runs BEFORE the layout gates + render so measure-layout and the capture both
21 * see the real glyph metrics, never a fallback.
22 */
23 const fs = require ( "fs" );
24 const path = require ( "path" );
25
26 const SKILL_ROOT = path. resolve (__dirname, ".." );
27 const FONTS_CSS = path. join ( SKILL_ROOT , "modes" , "standard" , "fonts" , "fonts.css" );
28 const MARKER = "hf-embedded-fonts" ;
29
30 const GENERIC = new Set ([
31 "serif" ,
32 "sans-serif" ,
33 "monospace" ,
34 "cursive" ,
35 "fantasy" ,
36 "system-ui" ,
37 "ui-serif" ,
38 "ui-sans-serif" ,
39 "ui-monospace" ,
40 "ui-rounded" ,
41 "math" ,
42 "emoji" ,
43 "fangsong" ,
44 "inherit" ,
45 "initial" ,
46 "unset" ,
47 "revert" ,
48 "revert-layer" ,
49 ]);
50
51 // Parse fonts.css → Map<lowercased family, [ @font-face block strings ]>.
52 function loadFontLibrary () {
53 let css;
54 try {
55 css = fs. readFileSync ( FONTS_CSS , "utf8" );
56 } catch {
57 console. error (
58 `[inject-fonts] missing ${ FONTS_CSS } — run modes/standard/fonts/build-fonts-css.cjs` ,
59 );
60 process. exit ( 2 );
61 }
62 const lib = new Map ();
63 // base64 data-URIs contain no "}", so [^}]* safely bounds a block.
64 const blockRe = /@font-face \s * \{ [ ^ }] * \} / gi ;
65 const famRe = /font-family \s * : \s * ( ['"] ? )( [ ^ ;'"] + ) \1 / i ;
66 let m;
67 while ((m = blockRe. exec (css)) !== null ) {
68 const fam = m[ 0 ]. match (famRe);
69 if ( ! fam) continue ;
70 const key = fam[ 2 ]. trim (). toLowerCase ();
71 if ( ! lib. has (key)) lib. set (key, []);
72 lib. get (key). push (m[ 0 ]);
73 }
74 return lib;
75 }
76
77 // Families referenced anywhere in the HTML: CSS `font-family:` (in <style> or
78 // inline style="") and the data-font-family attribute hyperframes also honors.
79 function usedFamilies ( html ) {
80 const out = new Set ();
81 const add = ( stack ) => {
82 for ( const part of String (stack). split ( "," )) {
83 const name = part
84 . trim ()
85 . replace ( / ^ ['"] | ['"] $ / g , "" )
86 . trim ()
87 . toLowerCase ();
88 if (name && ! GENERIC . has (name) && ! / ^ var \( / . test (name)) out. add (name);
89 }
90 };
91 // strip existing @font-face so we don't re-list the family it declares
92 const body = html. replace ( /@font-face \s * \{ [ ^ }] * \} / gi , "" );
93 for ( const mm of body. matchAll ( /font-family \s * : \s * ( [ ^ ;}{] + )/ gi )) add (mm[ 1 ]);
94 for ( const mm of html. matchAll ( /data-font-family \s * = \s * ["'] ( [ ^ "'] + ) ["'] / gi )) add (mm[ 1 ]);
95 return out;
96 }
97
98 // Families the HTML ALREADY declares via @font-face — leave those alone.
99 function declaredFamilies ( html ) {
100 const out = new Set ();
101 const famRe = /font-family \s * : \s * ( ['"] ? )( [ ^ ;'"] + ) \1 / i ;
102 for ( const mm of html. matchAll ( /@font-face \s * \{ [ ^ }] * \} / gi )) {
103 const f = mm[ 0 ]. match (famRe);
104 if (f) out. add (f[ 2 ]. trim (). toLowerCase ());
105 }
106 return out;
107 }
108
109 function injectInto ( file , lib ) {
110 let html;
111 try {
112 html = fs. readFileSync (file, "utf8" );
113 } catch {
114 return null ;
115 }
116 // drop any previous injection so this is idempotent
117 html = html. replace ( new RegExp ( ` \\ s*<style id="${ MARKER }">[ \\ s \\ S]*?</style>` , "i" ), "" );
118
119 const used = usedFamilies (html);
120 const already = declaredFamilies (html);
121 const blocks = [];
122 const inlined = [];
123 for ( const fam of used) {
124 if (already. has (fam)) continue ;
125 const lb = lib. get (fam);
126 if ( ! lb) continue ; // canonical / system / unknown — not ours to supply
127 blocks. push ( ... lb);
128 inlined. push (fam);
129 }
130 if ( ! blocks. length ) {
131 fs. writeFileSync (file, html);
132 return { file: path. basename (file), inlined: [] };
133 }
134 const style = `<style id="${ MARKER }"> \n /* auto-embedded by inject-fonts.cjs — deterministic template fonts */ \n ${ blocks . join ( " \n " ) } \n </style>` ;
135 // Prefer right after <head...>; fall back to before </head>, then <html>, then prepend.
136 if ( /<head [ ^ >] * >/ i . test (html)) html = html. replace ( /<head [ ^ >] * >/ i , ( m0 ) => `${ m0 } \n ${ style }` );
137 else if ( /< \/ head>/ i . test (html)) html = html. replace ( /< \/ head>/ i , `${ style } \n </head>` );
138 else if ( /<html [ ^ >] * >/ i . test (html))
139 html = html. replace ( /<html [ ^ >] * >/ i , ( m0 ) => `${ m0 } \n ${ style }` );
140 else html = style + " \n " + html;
141
142 fs. writeFileSync (file, html);
143 return { file: path. basename (file), inlined };
144 }
145
146 function main () {
147 const project = path. resolve (process.argv[ 2 ] || "" );
148 if ( ! process.argv[ 2 ]) {
149 console. error ( "usage: inject-fonts.cjs <project-dir> [file.html ...]" );
150 process. exit ( 1 );
151 }
152 const lib = loadFontLibrary ();
153 let files = process.argv. slice ( 3 );
154 if ( ! files. length ) files = [ "index.html" , "rail.html" , "index_fg.html" ];
155 files = files. map (( f ) => (path. isAbsolute (f) ? f : path. join (project, f)));
156
157 let touched = 0 ;
158 for ( const f of files) {
159 const r = injectInto (f, lib);
160 if (r == null ) continue ;
161 if (r.inlined. length ) {
162 console. log ( `[inject-fonts] ${ r . file }: embedded ${ r . inlined . join ( ", " ) }` );
163 touched ++ ;
164 } else console. log ( `[inject-fonts] ${ r . file }: no non-canonical fonts to embed` );
165 }
166 if ( ! touched)
167 console. log ( `[inject-fonts] nothing embedded (all fonts canonical/system or already declared)` );
168 }
169 main ();