diff --git a/api/advent22_api/core/config.py b/api/advent22_api/core/config.py index 0c873e6..1f6ba42 100644 --- a/api/advent22_api/core/config.py +++ b/api/advent22_api/core/config.py @@ -92,14 +92,6 @@ class Puzzle(BaseModel): close_after: int = 90 -class TTFont(BaseModel): - # Dateiname (in "/files") - file: str - - # Schriftgröße für den Font - size: int = 50 - - class Image(BaseModel): # Quadrat, Seitenlänge in px size: int = 1000 @@ -107,10 +99,6 @@ class Image(BaseModel): # Rand in px, wo keine Buchstaben untergebracht werden border: int = 60 - # Schriftarten - # TODO - fonts: list[TTFont] - class Config(BaseModel): # Login-Daten für Admin-Modus diff --git a/api/advent22_api/core/depends.py b/api/advent22_api/core/depends.py index 5d17664..2b3ba0e 100644 --- a/api/advent22_api/core/depends.py +++ b/api/advent22_api/core/depends.py @@ -1,4 +1,5 @@ import re +from dataclasses import dataclass from datetime import date from io import BytesIO from typing import cast @@ -11,8 +12,10 @@ from .calendar_config import CalendarConfig, get_calendar_config from .config import Config, get_config from .dav.webdav import WebDAV from .helpers import ( + RE_TTF, EventDates, Random, + list_fonts, list_images_auto, list_images_manual, load_image, @@ -109,11 +112,46 @@ async def get_all_image_names( return auto_image_names +@dataclass(slots=True, frozen=True) +class TTFont: + # Dateiname + file_name: str + + # Schriftgröße für den Font + size: int = 50 + + @property + async def font(self) -> "ImageFont._Font": + return ImageFont.truetype( + font=BytesIO(await WebDAV.read_bytes(self.file_name)), + size=100, + ) + + +async def get_all_ttfonts( + font_names: list[str] = Depends(list_fonts), +) -> list[TTFont]: + result = [] + + for name in font_names: + assert (size_match := RE_TTF.search(name)) is not None + + result.append( + TTFont( + file_name=name, + size=int(size_match.group(1)), + ) + ) + + return result + + async def gen_day_auto_image( day: int, cfg: Config, auto_image_names: dict[int, str], day_parts: dict[int, str], + ttfonts: list[TTFont], ) -> Image.Image: """ Automatisch generiertes Bild erstellen @@ -125,18 +163,14 @@ async def gen_day_auto_image( rnd = await Random.get(day) - font = ImageFont.truetype( - font=BytesIO(await WebDAV.read_bytes("files/Lena.ttf")), # TODO - size=100, - ) - # Buchstaben verstecken for letter in day_parts[day]: xy_range = range(cfg.image.border, (cfg.image.size - cfg.image.border)) + await image.hide_text( xy=cast(_XY, tuple(rnd.choices(xy_range, k=2))), text=letter, - font=font, + font=await rnd.choice(ttfonts).font, ) return image.img @@ -148,6 +182,7 @@ async def get_day_image( cfg: Config = Depends(get_config), auto_image_names: dict[int, str] = Depends(get_all_auto_image_names), day_parts: dict[int, str] = Depends(get_all_parts), + ttfonts: list[TTFont] = Depends(get_all_ttfonts), ) -> Image.Image | None: """ Bild für einen Tag abrufen @@ -167,5 +202,9 @@ async def get_day_image( except RuntimeError: # Erstelle automatisch generiertes Bild return await gen_day_auto_image( - day=day, cfg=cfg, auto_image_names=auto_image_names, day_parts=day_parts + day=day, + cfg=cfg, + auto_image_names=auto_image_names, + day_parts=day_parts, + ttfonts=ttfonts, ) diff --git a/api/advent22_api/core/helpers.py b/api/advent22_api/core/helpers.py index 4472cdf..7bd3f2f 100644 --- a/api/advent22_api/core/helpers.py +++ b/api/advent22_api/core/helpers.py @@ -13,7 +13,7 @@ from .dav.webdav import WebDAV T = TypeVar("T") RE_IMG = re.compile(r"\.(gif|jpe?g|tiff?|png|bmp)$", flags=re.IGNORECASE) -RE_TTF = re.compile(r"\.(ttf)$", flags=re.IGNORECASE) +RE_TTF = re.compile(r"_(\d+)\.ttf$", flags=re.IGNORECASE) class Random(random.Random): diff --git a/api/advent22_api/routers/admin.py b/api/advent22_api/routers/admin.py index e3cc78b..979f66b 100644 --- a/api/advent22_api/routers/admin.py +++ b/api/advent22_api/routers/admin.py @@ -7,7 +7,13 @@ from advent22_api.core.helpers import EventDates from ..core.calendar_config import CalendarConfig, DoorsSaved, get_calendar_config from ..core.config import Config, Image, get_config -from ..core.depends import get_all_event_dates, get_all_image_names, get_all_parts +from ..core.depends import ( + TTFont, + get_all_event_dates, + get_all_image_names, + get_all_parts, + get_all_ttfonts, +) from ..core.settings import SETTINGS, RedisSettings from ._security import require_admin, user_is_admin @@ -39,6 +45,10 @@ class ConfigModel(BaseModel): config_file: str background: str + class __Font(BaseModel): + file: str + size: int + class __WebDAV(BaseModel): url: str cache_ttl: int @@ -48,6 +58,7 @@ class ConfigModel(BaseModel): puzzle: __Puzzle calendar: __Calendar image: Image + fonts: list[__Font] redis: RedisSettings webdav: __WebDAV @@ -58,6 +69,7 @@ async def get_config_model( cfg: Config = Depends(get_config), cal_cfg: CalendarConfig = Depends(get_calendar_config), event_dates: EventDates = Depends(get_all_event_dates), + ttfonts: list[TTFont] = Depends(get_all_ttfonts), ) -> ConfigModel: """ Kombiniert aus privaten `settings`, `config` und `calendar_config` @@ -83,6 +95,9 @@ async def get_config_model( "background": cal_cfg.background, }, "image": cfg.image, + "fonts": [ + {"file": ttfont.file_name, "size": ttfont.size} for ttfont in ttfonts + ], "redis": SETTINGS.redis, "webdav": { "url": SETTINGS.webdav.url, diff --git a/ui/src/components/admin/ConfigView.vue b/ui/src/components/admin/ConfigView.vue index ba5e44a..d6a00d6 100644 --- a/ui/src/components/admin/ConfigView.vue +++ b/ui/src/components/admin/ConfigView.vue @@ -101,10 +101,10 @@