22//JAVA 25
33//DEPS com.fasterxml.jackson.core:jackson-databind:2.18.3
44//DEPS com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.3
5+ //DEPS org.apache.xmlgraphics:batik-transcoder:1.18
6+ //DEPS org.apache.xmlgraphics:batik-codec:1.18
57
68import module java .base ;
79import com .fasterxml .jackson .databind .*;
810import com .fasterxml .jackson .dataformat .yaml .YAMLFactory ;
11+ import org .apache .batik .transcoder .TranscoderInput ;
12+ import org .apache .batik .transcoder .TranscoderOutput ;
13+ import org .apache .batik .transcoder .image .PNGTranscoder ;
914
1015/**
1116 * Generate Open Graph SVG cards (1200×630) for each pattern.
8893static final int MAX_CODE_FONT = 16 ;
8994
9095// ── Helpers ────────────────────────────────────────────────────────────
96+ static final Path FONT_CACHE = Path .of (System .getProperty ("user.home" ), ".cache" , "javaevolved-fonts" );
97+
98+ static final Map <String , String > FONT_URLS = Map .of (
99+ "Inter-Regular.ttf" ,
100+ "https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZg.ttf" ,
101+ "Inter-Medium.ttf" ,
102+ "https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuI6fMZg.ttf" ,
103+ "Inter-SemiBold.ttf" ,
104+ "https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuGKYMZg.ttf" ,
105+ "Inter-Bold.ttf" ,
106+ "https://fonts.gstatic.com/s/inter/v20/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuFuYMZg.ttf" ,
107+ "JetBrainsMono-Regular.ttf" ,
108+ "https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxjPQ.ttf" ,
109+ "JetBrainsMono-Medium.ttf" ,
110+ "https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8-qxjPQ.ttf"
111+ );
112+
113+ /** Download fonts to cache and register with Java's graphics environment. */
114+ static void ensureFonts () throws IOException {
115+ Files .createDirectories (FONT_CACHE );
116+ var ge = java .awt .GraphicsEnvironment .getLocalGraphicsEnvironment ();
117+ for (var entry : FONT_URLS .entrySet ()) {
118+ var file = FONT_CACHE .resolve (entry .getKey ());
119+ if (!Files .exists (file )) {
120+ IO .println ("Downloading %s..." .formatted (entry .getKey ()));
121+ try (var in = URI .create (entry .getValue ()).toURL ().openStream ()) {
122+ Files .copy (in , file );
123+ }
124+ }
125+ try {
126+ var font = java .awt .Font .createFont (java .awt .Font .TRUETYPE_FONT , file .toFile ());
127+ ge .registerFont (font );
128+ } catch (java .awt .FontFormatException e ) {
129+ IO .println ("[WARN] Could not register font %s: %s" .formatted (entry .getKey (), e .getMessage ()));
130+ }
131+ }
132+ }
133+
134+ /** Convert an SVG string to a PNG file using Batik. */
135+ static void svgToPng (String svgContent , Path pngPath ) throws Exception {
136+ var input = new TranscoderInput (new java .io .StringReader (svgContent ));
137+ try (var out = new java .io .BufferedOutputStream (Files .newOutputStream (pngPath ))) {
138+ var transcoder = new PNGTranscoder ();
139+ transcoder .addTranscodingHint (PNGTranscoder .KEY_WIDTH , (float ) W );
140+ transcoder .addTranscodingHint (PNGTranscoder .KEY_HEIGHT , (float ) H );
141+ transcoder .transcode (input , new TranscoderOutput (out ));
142+ }
143+ }
144+
91145static SequencedMap <String , String > loadProperties (String file ) {
92146 try {
93147 var map = new LinkedHashMap <String , String >();
@@ -240,7 +294,6 @@ static String generateSvg(Snippet s) {
240294<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d">
241295 <defs>
242296 <style>
243- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
244297 .title { font: 700 24px/1 'Inter', sans-serif; fill: %s; }
245298 .category { font: 600 13px/1 'Inter', sans-serif; fill: %s; }
246299 .label { font: 600 11px/1 'Inter', sans-serif; text-transform: uppercase; letter-spacing: 0.05em; }
@@ -326,7 +379,9 @@ static String generateSvg(Snippet s) {
326379}
327380
328381// ── Main ───────────────────────────────────────────────────────────────
329- void main (String ... args ) throws IOException {
382+ void main (String ... args ) throws Exception {
383+ ensureFonts ();
384+
330385 var allSnippets = loadAllSnippets ();
331386 IO .println ("Loaded %d snippets" .formatted (allSnippets .size ()));
332387
@@ -350,7 +405,8 @@ void main(String... args) throws IOException {
350405 Files .createDirectories (dir );
351406 var svg = generateSvg (s );
352407 Files .writeString (dir .resolve (s .slug () + ".svg" ), svg );
408+ svgToPng (svg , dir .resolve (s .slug () + ".png" ));
353409 count ++;
354410 }
355- IO .println ("Generated %d SVG card(s) in %s/" .formatted (count , OUTPUT_DIR ));
411+ IO .println ("Generated %d SVG+PNG card(s) in %s/" .formatted (count , OUTPUT_DIR ));
356412}
0 commit comments