Skip to content

Commit 0e3cf7f

Browse files
committed
fix: resolve font NameError, JPEG/webp mismatch, and duplicate Tower of Hanoi entry- Load fonts once at top of generate_banner() to fix NameError in matrix branch- Bundle DejaVu Sans Bold font for consistent cross-platform rendering- Warn via RuntimeWarning if no font is found instead of failing silently- Save banners as true WebP instead of JPEG bytes in a .webp file- Remove duplicate Tower of Hanoi entry that silently overwrote its own output
1 parent 6dd19a8 commit 0e3cf7f

2 files changed

Lines changed: 61 additions & 17 deletions

File tree

assets/fonts/DejaVuSans-Bold.ttf

688 KB
Binary file not shown.

web-app/generate_banners.py

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,44 @@
11
import os
22
import math
3+
import warnings
34
from PIL import Image, ImageDraw, ImageFont, ImageFilter
45

6+
7+
def load_fonts():
8+
"""Load title/subtitle fonts with a robust, OS-independent fallback chain.
9+
10+
Tries a bundled font first (works identically on every OS), then falls
11+
back to common Windows/Linux system fonts. If nothing is found, warns
12+
loudly instead of silently rendering unreadable bitmap text.
13+
"""
14+
script_dir = os.path.dirname(os.path.abspath(__file__))
15+
bundled_font = os.path.join(script_dir, "assets", "fonts", "DejaVuSans-Bold.ttf")
16+
17+
candidates = [
18+
bundled_font, # 1. Repo-bundled font (preferred)
19+
"segoeui.ttf", # 2. Windows
20+
"arial.ttf", # 3. Windows fallback
21+
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", # 4. Common Linux path
22+
]
23+
24+
for path in candidates:
25+
try:
26+
font_title = ImageFont.truetype(path, 36)
27+
font_subtitle = ImageFont.truetype(path, 16)
28+
return font_title, font_subtitle
29+
except IOError:
30+
continue
31+
32+
warnings.warn(
33+
"No usable .ttf font found (checked bundled, Windows, and common Linux paths). "
34+
"Falling back to PIL's default bitmap font - banner text will be unreadable. "
35+
"Add a .ttf to assets/fonts/ to fix this.",
36+
RuntimeWarning,
37+
stacklevel=2,
38+
)
39+
return ImageFont.load_default(), ImageFont.load_default()
40+
41+
542
def create_radial_gradient(color_center, color_edge):
643
"""Create a high-fidelity smooth radial gradient by resizing a eased 24x24 radial dot."""
744
w, h = 24, 24
@@ -44,6 +81,12 @@ def draw_perspective_grid(draw, color):
4481

4582
def generate_banner(name, category, filename):
4683
"""Generate a highly customized, vector-styled banner for the card."""
84+
# Load fonts once, up front, so they're available to every branch below
85+
# (previously font_title was only loaded near the end, which crashed
86+
# the "matrix" branch with a NameError since it referenced font_title
87+
# before it existed).
88+
font_title, font_subtitle = load_fonts()
89+
4790
# Tailor color scheme by category
4891
if category == "games":
4992
color_center = (25, 10, 55)
@@ -485,6 +528,15 @@ def draw_o(ox, oy):
485528
v_draw.ellipse([cx-35, cy-40, cx-15, cy-20], fill=color_accent) # toe 1
486529
v_draw.ellipse([cx-10, cy-50, cx+10, cy-30], fill=color_accent) # toe 2
487530
v_draw.ellipse([cx+15, cy-40, cx+35, cy-20], fill=color_accent) # toe 3
531+
elif "budget" in n_lower:
532+
# Wallet + bar chart for budget tracker
533+
cx, cy = 400, 225
534+
v_draw.rounded_rectangle([cx-90, cy-50, cx+90, cy+50], radius=16, fill=(255,255,255,12), outline=color_accent, width=3)
535+
v_draw.rounded_rectangle([cx+40, cy-15, cx+90, cy+15], radius=8, fill=color_accent)
536+
bar_heights = [30, 55, 40, 70]
537+
for i, h in enumerate(bar_heights):
538+
x = 230 + i * 50
539+
v_draw.rectangle([x, 320 - h, x + 30, 320], fill=color_accent)
488540
else:
489541
# Default nice abstract waves
490542
points = []
@@ -502,18 +554,6 @@ def draw_o(ox, oy):
502554
glass_layer = Image.new("RGBA", (800, 450))
503555
gl_draw = ImageDraw.Draw(glass_layer)
504556
gl_draw.rounded_rectangle([150, 110, 650, 340], radius=24, fill=(255, 255, 255, 14), outline=(255, 255, 255, 45), width=2)
505-
506-
# Load fonts cleanly
507-
try:
508-
font_title = ImageFont.truetype("segoeui.ttf", 36)
509-
font_subtitle = ImageFont.truetype("segoeui.ttf", 16)
510-
except IOError:
511-
try:
512-
font_title = ImageFont.truetype("arial.ttf", 36)
513-
font_subtitle = ImageFont.truetype("arial.ttf", 16)
514-
except IOError:
515-
font_title = ImageFont.load_default()
516-
font_subtitle = ImageFont.load_default()
517557

518558
# Draw Text Labels
519559
# Shadow text
@@ -527,9 +567,10 @@ def draw_o(ox, oy):
527567

528568
final_img = Image.alpha_composite(composite, glass_layer).convert("RGB")
529569

530-
# Save Image
570+
# Save Image (true WebP encoding — filenames use .webp, so save as WebP,
571+
# not JPEG bytes inside a .webp container)
531572
os.makedirs(os.path.dirname(filename), exist_ok=True)
532-
final_img.save(filename, "JPEG", quality=90)
573+
final_img.save(filename, "WEBP", quality=90, method=6)
533574
print(f"Generated HD banner: {filename}")
534575

535576
# Project category directory mappings
@@ -540,7 +581,7 @@ def draw_o(ox, oy):
540581
("Coin Flip", "games", "coin-flip.webp"),
541582
("Dice Rolling", "games", "dice-rolling.webp"),
542583
("Dots & Boxes AI", "games", "dots-boxes.webp"),
543-
("Emoji Memory Game", "games", "emoji-memory-game.webp"), # FIXED filename
584+
("Emoji Memory Game", "games", "emoji-memory-game.webp"),
544585
("FLAMES Game", "games", "flames.webp"),
545586
("Flappy Game", "games", "flappy-game.webp"),
546587
("Hangman", "games", "hangman.webp"),
@@ -579,9 +620,12 @@ def draw_o(ox, oy):
579620
("Fourier Series", "math", "fourier-series.webp"),
580621

581622
# UTILITIES
623+
# NOTE: "Tower of Hanoi" under utilities removed — it was a duplicate of
624+
# the math entry above and wrote to the same "tower-of-hanoi.webp" path,
625+
# silently overwriting it. If a utilities-specific banner is wanted,
626+
# give it a distinct filename instead (e.g. "tower-of-hanoi-util.webp").
582627
("Morse Code", "utilities", "morse-code.webp"),
583628
("Number Converter", "utilities", "number-converter.webp"),
584-
("Tower of Hanoi", "utilities", "tower-of-hanoi.webp"),
585629
("Typing Speed Tester", "utilities", "typing-speed-tester.webp"),
586630
("Color Palette Suggestor", "utilities", "color-palette.webp"),
587631
("AI Resume Analyzer", "utilities", "resume-analyzer.webp"),
@@ -597,4 +641,4 @@ def draw_o(ox, oy):
597641
dest_path = os.path.join(banners_dir, filename)
598642
generate_banner(name, cat, dest_path)
599643

600-
print("All HD banner images generated successfully!")
644+
print("All HD banner images generated successfully!")

0 commit comments

Comments
 (0)