"""Shared visual language for everything drawn onto the e-ink panel (excluding widgets/text.py, which already has its own richer multi- family font picker and is left alone) -- spacing, ink-color resolution, Inter font loading, and the small set of drawing primitives (header bar, color chip, battery icon) more than one render module needs. Centralizes what used to be independently redefined per render file (calendar_render.py/weather_render.py each had their own MARGIN/BG/FG/ RULE, widgets/battery.py and manage_overlay.py each had their own battery-glyph-drawing code) so the panel reads as one consistent system instead of N separately-styled widgets. Still bound by the same hard constraints as everything else that draws before the single whole-canvas quantize/dither pass (see image_pipeline.py's module docstring/draw_text): every fill here is one of DEFAULT_PALETTE_RGB's 6 exact colors, and text always routes through image_pipeline.draw_text. """ from __future__ import annotations from functools import lru_cache from pathlib import Path from PIL import Image, ImageDraw, ImageFont from .image_pipeline import DEFAULT_PALETTE_RGB # Spacing scale. CONTENT_MARGIN carries over calendar_render.py/ # weather_render.py's own long-tuned MARGIN=20 value unchanged (not # re-tuned -- every wrap/truncation-width calc in those modules was # measured against it). GUTTER is new: the inset every widget applies # within its own target_w x target_h box (see card_canvas) to get a # visible seam between adjacent widgets without touching grid.py's # zero-gap cell math. GUTTER = 6 CONTENT_MARGIN = 20 CARD_RADIUS = 12 CHIP_RADIUS = 4 # models.Widget.font_scale's allowed values -- a per-widget text-size # multiplier (the Layout dialog's "Text size" picker), same "reject # invalid, don't silently coerce" posture as border_style. Deliberately a # small fixed set (a