Journey: real train/bus pictograms instead of letter badges

Vendor Font Awesome Free Solid (fonts/fa-solid-900.ttf, SIL OFL
licensed -- see fonts/FONT-AWESOME-LICENSE.txt) for its actual
train/bus glyphs. Confirmed live these stay sharp and recognizable
even at the tiny size the detail line renders at (~20px), unlike a
hand-drawn shape would be, and unlike emoji (not in DejaVu Sans,
would've rendered as tofu).

render_utils.py gets a load_icon_font() alongside the existing
load_font(), plus named ICON_BUS/ICON_TRAIN codepoint constants so
screens don't need to embed raw Private-Use-Area unicode literals.
This commit is contained in:
Honey committed 2026-07-23 10:13:59 +02:00
1 parent 7354517b22
commit 4380a62524
4 files changed
+197 -15

No files matched your search

+16 -15
View File
@@ -21,7 +21,7 @@ import requests
from PIL import Image, ImageDraw
from screens.base import Screen
from screens.render_utils import load_font, text_width, truncate_to_width
from screens.render_utils import ICON_BUS, ICON_TRAIN, load_font, load_icon_font, text_width, truncate_to_width
logger = logging.getLogger(__name__)
@@ -350,17 +350,19 @@ class JourneyScreen(Screen):
draw.text((arrow_x + arrow_w + scale * 0.02, top_line_y), journey["arrival"], font=time_font, fill=BLACK)
# Detail line: a small badge per leg -- colored circle (blue =
# train, green = bus; pure panel colors, no dithering) with a
# "Z" (Zug) or "B" (Bus) letter in it, so the mode is legible at
# a glance rather than relying on color alone. Followed by the
# line name, then transfer/platform/notice info. Drawn as
# manually-placed segments instead of one joined string so each
# leg can carry its own badge.
# Detail line: a real train/bus pictogram per leg (Font Awesome
# Solid, vendored in fonts/fa-solid-900.ttf -- confirmed live to
# stay recognizable even at this render size, unlike a hand-drawn
# shape or emoji, which either look unclear that small or render
# as tofu since DejaVu Sans has no color-emoji glyphs). Blue for
# train, green for bus -- pure panel colors, no dithering.
# Followed by the line name, then transfer/platform/notice info.
# Drawn as manually-placed segments instead of one joined string
# so each leg can carry its own icon.
detail_y = top_line_y + time_font.size * 1.25
right_edge = width - pad
icon_d = detail_font.size * 0.9
icon_font = load_font(size=max(8, int(icon_d * 0.62)), bold=True)
icon_d = detail_font.size * 1.05
icon_font = load_icon_font(size=int(icon_d))
cursor_x = pad
def draw_segment(text, color=BLACK, avail=None):
@@ -373,12 +375,11 @@ class JourneyScreen(Screen):
def draw_mode_icon(x, mode):
color = BLUE if mode == "train" else GREEN
letter = "Z" if mode == "train" else "B"
glyph = ICON_TRAIN if mode == "train" else ICON_BUS
l, t, r, b = draw.textbbox((0, 0), glyph, font=icon_font)
gw, gh = r - l, b - t
top = detail_y + (detail_font.size - icon_d) / 2
draw.ellipse((x, top, x + icon_d, top + icon_d), fill=color)
l, t, r, b = draw.textbbox((0, 0), letter, font=icon_font)
lw, lh = r - l, b - t
draw.text((x + (icon_d - lw) / 2 - l, top + (icon_d - lh) / 2 - t), letter, font=icon_font, fill=(255, 255, 255))
draw.text((x - l, top + (icon_d - gh) / 2 - t), glyph, font=icon_font, fill=color)
for idx, leg in enumerate(journey["legs"]):
if cursor_x >= right_edge - icon_d: