Skip to content

Commit 9e41164

Browse files
brunoborgesCopilot
andcommitted
Add PNG generation with Batik and auto-downloaded fonts
Uses Apache Batik to transcode each SVG to a 1200×630 PNG. Fonts (Inter + JetBrains Mono) are downloaded from Google Fonts on first run and cached in ~/.cache/javaevolved-fonts/. Fonts are registered with Java's GraphicsEnvironment at runtime so Batik renders them correctly without system-wide installation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 82fdff3 commit 9e41164

File tree

1 file changed

+59
-3
lines changed

1 file changed

+59
-3
lines changed

html-generators/generateog.java

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
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

68
import module java.base;
79
import com.fasterxml.jackson.databind.*;
810
import 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.
@@ -88,6 +93,55 @@
8893
static 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+
91145
static 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&amp;family=JetBrains+Mono:wght@400;500&amp;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

Comments
 (0)