diff --git a/amateurfunk_anki.py b/amateurfunk_anki.py
index 01d1007..215638a 100644
--- a/amateurfunk_anki.py
+++ b/amateurfunk_anki.py
@@ -165,6 +165,23 @@ TECHNISCHE_SHORT_TITLE = "Technische Kenntnisse"
# inside the package as a deck tree.
TOPIC_SPLIT_CLASSES = frozenset({"E", "A"})
+# The catalog's figures come in two kinds: vector SVGs (the bulk; mostly
+# small — well under ~200 px) and a handful of raster PNGs (all 1024×685). We
+# enlarge only the SVGs: they scale crisply, they're the ones that read tiny
+# on a wide desktop card, and the PNGs are already at roughly the size we're
+# aiming for. The enlargement is `MEDIA_SCALE`× the native size, with
+# `MEDIA_MAX_WIDTH` × `MEDIA_MAX_HEIGHT` acting as an *enlargement ceiling* —
+# it bounds how far a below-cap figure may grow (a figure already at/above a
+# cap is left at native size; we never shrink). The ceiling is the PNGs'
+# ballpark (1024×768) so the largest SVG schematics (up to 635×481 px in the
+# 3rd edition: the NG20x block diagrams, EB309/EB310) grow to a comparable
+# size rather than staying cramped, while folding in native *height* still
+# stops tall-but-narrow figures from growing off the bottom of the card.
+# See `MediaRegistry.image_html`.
+MEDIA_SCALE = 2
+MEDIA_MAX_WIDTH = 1024
+MEDIA_MAX_HEIGHT = 768
+
# Fallback build epoch if neither the manifest nor `--epoch` supplies
# one. Picked as 0 so missing-metadata builds are still deterministic
# and obviously wrong (timestamps would all show 1970).
@@ -837,6 +854,8 @@ class MediaRegistry:
self.used_paths = set()
# references that didn't resolve (recorded for diagnostics)
self.missing = []
+ # Path → intrinsic (w, h) in px (memoised; None when unreadable)
+ self._size_cache = {}
self._index_media_dir()
def _index_media_dir(self):
@@ -878,12 +897,43 @@ class MediaRegistry:
return None
def image_html(self, reference):
- """Return an `` tag for `reference`, or `""` if unresolved."""
+ """Return an `` tag for `reference`, or `""` if unresolved.
+
+ Most catalog SVGs carry small intrinsic dimensions, so on a wide
+ desktop card they render barely legible (a small fixed-px image
+ occupies only a sliver of the column; `max-width: 100%` only
+ ever shrinks, never enlarges). We raise the *preferred* width
+ via an explicit `width` attribute so the figure grows on every
+ viewport until the card's `max-width: 100%` cap engages.
+
+ Only SVGs are scaled — the catalog's PNGs are already large
+ (1024×685), so `_intrinsic_svg_size` returns `None` for them and
+ they fall through to native size. The enlargement factor is
+ `scale_for_size` — `MEDIA_SCALE`×, clamped at the
+ `MEDIA_MAX_WIDTH` × `MEDIA_MAX_HEIGHT` enlargement ceiling.
+ `height: auto` in the CSS keeps the aspect ratio. When the size
+ can't be read (non-SVG, or an unparseable SVG) we fall back to
+ native.
+ """
path = self.resolve(reference)
if path is None:
return ""
filename = html.escape(path.name, quote=True)
- return f''
+ size = self._intrinsic_svg_size(path)
+ if size is None:
+ return f''
+ width, height = size
+ scale = scale_for_size(width, height)
+ if scale <= 1.0:
+ return f''
+ scaled = round(width * scale)
+ return f''
+
+ def _intrinsic_svg_size(self, path):
+ """Return the SVG's `(width, height)` in px (memoised; SVG only)."""
+ if path not in self._size_cache:
+ self._size_cache[path] = intrinsic_svg_size(path)
+ return self._size_cache[path]
# ============================================================================
@@ -1110,6 +1160,74 @@ def media_bytes_for_package(path):
return svg_with_white_background(text).encode("utf-8")
+def scale_for_size(width, height):
+ """Return the display scale for a figure of native `width`×`height`.
+
+ `MEDIA_SCALE`× for legibility, but never so much that the enlarged
+ figure would grow past the `MEDIA_MAX_WIDTH` / `MEDIA_MAX_HEIGHT`
+ ceiling, and never below 1.0 (we only ever enlarge, never shrink —
+ shrinking is the CSS `max-width`'s job). A figure already at/above
+ either cap yields exactly 1.0, i.e. "render at native size" — so a
+ native figure larger than the ceiling is left as-is, not shrunk.
+ """
+ if width <= 0 or height <= 0:
+ return 1.0
+ scale = min(
+ MEDIA_SCALE,
+ MEDIA_MAX_WIDTH / width,
+ MEDIA_MAX_HEIGHT / height,
+ )
+ return max(scale, 1.0)
+
+
+def _svg_dimension(svg_tag, name):
+ """Parse a `width`/`height` length (px or unitless) off an `