Add a per-widget text-size picker for calendar/tasks legibility
Calendar and tasks pack the most body text at the smallest default sizes, so those two gear-icon dialogs get a "Text size" card (Normal/ Large/X-Large) alongside the existing Border card -- a new Widget-level font_scale column with its own POST .../font-scale endpoint, same Widget-property-not-config-field shape as border_style. Threaded through every classic (calendar_render.py) and modern (html_render.py/ calendar_html_render.py) size calc via one shared panel_style. scaled_size() so row heights/max_rows already derived from font size re-fit around the bigger text automatically.
This commit is contained in:
@@ -512,10 +512,12 @@ _AGENDA_FONTS = {"large": (34, 22, 20), "medium": (24, 22, 16), "small": (18, 16
|
||||
|
||||
def _build_agenda(events: list[dict], browse_offset: int, target_w: int, target_h: int, tz: ZoneInfo,
|
||||
palette_rgb: list | None = None, weather_cities: list[dict] | None = None,
|
||||
weather_units: str = "fahrenheit") -> Image.Image:
|
||||
weather_units: str = "fahrenheit", font_scale: float = 1.0) -> Image.Image:
|
||||
img, draw, region = panel_style.card_canvas(target_w, target_h)
|
||||
|
||||
title_size, body_size, weather_size = _AGENDA_FONTS[_size_tier(target_w, target_h)]
|
||||
title_size, body_size, weather_size = (
|
||||
panel_style.scaled_size(v, font_scale) for v in _AGENDA_FONTS[_size_tier(target_w, target_h)]
|
||||
)
|
||||
title_font = panel_style.font_bold(title_size)
|
||||
body_font = panel_style.font_regular(body_size)
|
||||
weather_font = panel_style.font_regular(weather_size)
|
||||
@@ -533,7 +535,7 @@ _TODAY_TOMORROW_FONTS = {"large": (26, 18, 16), "medium": (20, 15, 13), "small":
|
||||
|
||||
def _build_today_tomorrow(events: list[dict], browse_offset: int, target_w: int, target_h: int, tz: ZoneInfo,
|
||||
palette_rgb: list | None = None, weather_cities: list[dict] | None = None,
|
||||
weather_units: str = "fahrenheit") -> Image.Image:
|
||||
weather_units: str = "fahrenheit", font_scale: float = 1.0) -> Image.Image:
|
||||
"""Two _draw_agenda_day sections stacked vertically (below each other
|
||||
rather than side-by-side -- narrower than tall doesn't leave enough
|
||||
width per day for the event-row text at smaller sizes). browse_offset
|
||||
@@ -542,7 +544,9 @@ def _build_today_tomorrow(events: list[dict], browse_offset: int, target_w: int,
|
||||
both views."""
|
||||
img, draw, (cx0, cy0, cw, ch) = panel_style.card_canvas(target_w, target_h)
|
||||
|
||||
title_size, body_size, weather_size = _TODAY_TOMORROW_FONTS[_size_tier(target_w, target_h)]
|
||||
title_size, body_size, weather_size = (
|
||||
panel_style.scaled_size(v, font_scale) for v in _TODAY_TOMORROW_FONTS[_size_tier(target_w, target_h)]
|
||||
)
|
||||
title_font = panel_style.font_bold(title_size)
|
||||
body_font = panel_style.font_regular(body_size)
|
||||
weather_font = panel_style.font_regular(weather_size)
|
||||
@@ -572,7 +576,7 @@ def _build_week(events: list[dict], browse_offset: int, target_w: int, target_h:
|
||||
week_start: int, palette_rgb: list | None = None,
|
||||
weather_cities: list[dict] | None = None, weather_units: str = "fahrenheit",
|
||||
days: int = 7, layout: str = "horizontal",
|
||||
start_offset: int = 0) -> Image.Image:
|
||||
start_offset: int = 0, font_scale: float = 1.0) -> Image.Image:
|
||||
"""`days` (2-10, see routers/api_widgets.py's clamp) side-by-side
|
||||
columns (layout="horizontal", the original fixed-at-7 behavior
|
||||
generalized) or stacked bands (layout="vertical", reusing
|
||||
@@ -597,9 +601,9 @@ def _build_week(events: list[dict], browse_offset: int, target_w: int, target_h:
|
||||
|
||||
if layout == "vertical":
|
||||
title_base, body_base, weather_base = _WEEK_VERTICAL_FONTS[tier]
|
||||
title_font = panel_style.font_bold(max(14, title_base - days))
|
||||
body_font = panel_style.font_regular(max(11, body_base - days))
|
||||
weather_font = panel_style.font_regular(max(9, weather_base - days))
|
||||
title_font = panel_style.font_bold(panel_style.scaled_size(max(14, title_base - days), font_scale))
|
||||
body_font = panel_style.font_regular(panel_style.scaled_size(max(11, body_base - days), font_scale))
|
||||
weather_font = panel_style.font_regular(panel_style.scaled_size(max(9, weather_base - days), font_scale))
|
||||
section_h = ch // days
|
||||
for i in range(days):
|
||||
section_y0 = cy0 + i * section_h
|
||||
@@ -611,7 +615,9 @@ def _build_week(events: list[dict], browse_offset: int, target_w: int, target_h:
|
||||
weather_cities, weather_font, weather_units)
|
||||
return img
|
||||
|
||||
header_size, chip_size, weather_size = _WEEK_HORIZONTAL_FONTS[tier]
|
||||
header_size, chip_size, weather_size = (
|
||||
panel_style.scaled_size(v, font_scale) for v in _WEEK_HORIZONTAL_FONTS[tier]
|
||||
)
|
||||
header_font = panel_style.font_bold(header_size)
|
||||
chip_font = panel_style.font_regular(chip_size)
|
||||
weather_font = panel_style.font_regular(weather_size)
|
||||
@@ -667,7 +673,7 @@ _MONTH_FONTS = {"large": (16, 18), "medium": (12, 13), "small": (12, 13)}
|
||||
|
||||
|
||||
def _build_month(events: list[dict], browse_offset: int, target_w: int, target_h: int, tz: ZoneInfo,
|
||||
week_start: int, palette_rgb: list | None = None) -> Image.Image:
|
||||
week_start: int, palette_rgb: list | None = None, font_scale: float = 1.0) -> Image.Image:
|
||||
"""Density dots per day, not literal event text -- real text at
|
||||
typical month-cell size (~100x70px) is close to unreadable on a
|
||||
6-color dithered e-ink panel. Capped at 4 visible dots, "+N" beyond.
|
||||
@@ -677,7 +683,9 @@ def _build_month(events: list[dict], browse_offset: int, target_w: int, target_h
|
||||
comment above MARGIN/BG/FG."""
|
||||
img, draw, (cx0, cy0, cw, ch) = panel_style.card_canvas(target_w, target_h)
|
||||
|
||||
header_size, day_size = _MONTH_FONTS[_size_tier(target_w, target_h)]
|
||||
header_size, day_size = (
|
||||
panel_style.scaled_size(v, font_scale) for v in _MONTH_FONTS[_size_tier(target_w, target_h)]
|
||||
)
|
||||
header_font = panel_style.font_bold(header_size)
|
||||
day_font_in_month = panel_style.font_bold(day_size)
|
||||
day_font_out_of_month = panel_style.font_regular(day_size)
|
||||
@@ -750,7 +758,7 @@ def _build(events: list[dict], view: str, browse_offset: int, target_w: int, tar
|
||||
fetch_summary: str, week_start: int, palette_rgb: list | None = None,
|
||||
weather_cities: list[dict] | None = None, weather_units: str = "fahrenheit",
|
||||
week_days: int = 7, week_layout: str = "horizontal",
|
||||
week_start_offset: int = 0) -> Image.Image:
|
||||
week_start_offset: int = 0, font_scale: float = 1.0) -> Image.Image:
|
||||
tz = ZoneInfo(timezone) if timezone else ZoneInfo("UTC")
|
||||
effective_view = view
|
||||
if view == "month" and not _month_view_fits(target_w, target_h):
|
||||
@@ -758,13 +766,13 @@ def _build(events: list[dict], view: str, browse_offset: int, target_w: int, tar
|
||||
|
||||
if effective_view == "agenda":
|
||||
img = _build_agenda(events, browse_offset, target_w, target_h, tz, palette_rgb,
|
||||
weather_cities, weather_units)
|
||||
weather_cities, weather_units, font_scale)
|
||||
elif effective_view == "today_tomorrow":
|
||||
img = _build_today_tomorrow(events, browse_offset, target_w, target_h, tz, palette_rgb,
|
||||
weather_cities, weather_units)
|
||||
weather_cities, weather_units, font_scale)
|
||||
elif effective_view == "week":
|
||||
img = _build_week(events, browse_offset, target_w, target_h, tz, week_start, palette_rgb,
|
||||
weather_cities, weather_units, week_days, week_layout, week_start_offset)
|
||||
weather_cities, weather_units, week_days, week_layout, week_start_offset, font_scale)
|
||||
elif effective_view == "month":
|
||||
# Never given weather -- no room for it at typical month-cell
|
||||
# size, same reasoning that already keeps this view to density
|
||||
@@ -772,10 +780,10 @@ def _build(events: list[dict], view: str, browse_offset: int, target_w: int, tar
|
||||
# docstring). Colors are still passed through, though -- that's
|
||||
# a different concern (legibility of individual events) than
|
||||
# needing a whole extra strip of content.
|
||||
img = _build_month(events, browse_offset, target_w, target_h, tz, week_start, palette_rgb)
|
||||
img = _build_month(events, browse_offset, target_w, target_h, tz, week_start, palette_rgb, font_scale)
|
||||
else:
|
||||
img = _build_agenda(events, browse_offset, target_w, target_h, tz, palette_rgb,
|
||||
weather_cities, weather_units)
|
||||
weather_cities, weather_units, font_scale)
|
||||
|
||||
if fetch_summary:
|
||||
# Drawn as a final overlay onto the already-composited img (not
|
||||
@@ -814,13 +822,13 @@ def render_calendar_preview_png(events: list[dict], view: str, browse_offset: in
|
||||
fetch_summary: str = "", manage: dict | None = None, week_start: int = 0,
|
||||
weather_cities: list[dict] | None = None, weather_units: str = "fahrenheit",
|
||||
week_days: int = 7, week_layout: str = "horizontal",
|
||||
week_start_offset: int = 0) -> bytes:
|
||||
week_start_offset: int = 0, font_scale: float = 1.0) -> bytes:
|
||||
"""Same pipeline as render_calendar, but a normal browser-viewable
|
||||
PNG in logical (upright) orientation -- mirrors
|
||||
image_pipeline.render_preview_png's relationship to render_frame."""
|
||||
target_w, target_h = logical_render_size(orientation)
|
||||
img = _build(events, view, browse_offset, target_w, target_h, timezone, fetch_summary, week_start,
|
||||
palette_rgb, weather_cities, weather_units, week_days, week_layout, week_start_offset)
|
||||
palette_rgb, weather_cities, weather_units, week_days, week_layout, week_start_offset, font_scale)
|
||||
img = _apply_manage_overlay(img, manage)
|
||||
quantized = _quantize(img, palette_rgb, dither_strength=1.0)
|
||||
buf = io.BytesIO()
|
||||
@@ -835,12 +843,14 @@ _TASKS_FONTS = {"large": (24, 18), "medium": (20, 16), "small": (16, 13)}
|
||||
|
||||
|
||||
def _build_tasks(tasks: list[dict], target_w: int, target_h: int, palette_rgb: list | None = None,
|
||||
title: str = "Tasks") -> Image.Image:
|
||||
title: str = "Tasks", font_scale: float = 1.0) -> Image.Image:
|
||||
"""A tasks widget's entire region is the checklist -- unlike the old
|
||||
week-view slot, there's no day columns/header to share space with,
|
||||
so this is just _draw_tasks over the whole box."""
|
||||
img, draw, region = panel_style.card_canvas(target_w, target_h)
|
||||
title_size, body_size = _TASKS_FONTS[_size_tier(target_w, target_h)]
|
||||
title_size, body_size = (
|
||||
panel_style.scaled_size(v, font_scale) for v in _TASKS_FONTS[_size_tier(target_w, target_h)]
|
||||
)
|
||||
title_font = panel_style.font_bold(title_size)
|
||||
body_font = panel_style.font_regular(body_size)
|
||||
_draw_tasks(img, draw, region, tasks, title_font, body_font, palette_rgb, title)
|
||||
@@ -860,12 +870,12 @@ def render_tasks(tasks: list[dict], orientation: str, palette_rgb: list | None,
|
||||
|
||||
|
||||
def render_tasks_preview_png(tasks: list[dict], orientation: str, palette_rgb: list | None,
|
||||
manage: dict | None = None, title: str = "Tasks") -> bytes:
|
||||
manage: dict | None = None, title: str = "Tasks", font_scale: float = 1.0) -> bytes:
|
||||
"""Same pipeline as render_tasks, but a normal browser-viewable PNG
|
||||
in logical (upright) orientation -- mirrors render_calendar_preview_
|
||||
png's relationship to render_calendar."""
|
||||
target_w, target_h = logical_render_size(orientation)
|
||||
img = _build_tasks(tasks, target_w, target_h, palette_rgb, title)
|
||||
img = _build_tasks(tasks, target_w, target_h, palette_rgb, title, font_scale=font_scale)
|
||||
img = _apply_manage_overlay(img, manage)
|
||||
quantized = _quantize(img, palette_rgb, dither_strength=1.0)
|
||||
buf = io.BytesIO()
|
||||
|
||||
Reference in New Issue
Block a user