import math from pathlib import Path from .assets import load_json, load_rgb from .settings import BOARD_DECK, DECKS_DIR, BuildConfig def board_target_mm_for_mode( board_path: Path, board_mode: str, board_custom_mm: tuple[float, float] | None = None, ) -> tuple[float, float]: if board_custom_mm is not None: return board_custom_mm ratio = board_aspect_ratio(board_path) if board_mode == "a4-fit": max_w, max_h = 210.0, 297.0 if ratio > max_w / max_h: return max_w, max_w / ratio return max_h * ratio, max_h if board_mode == "a5-fit": max_w, max_h = 148.0, 210.0 if ratio > max_w / max_h: return max_w, max_w / ratio return max_h * ratio, max_h # Default: preserve aspect ratio while using approximately A5 area, # capped to fit inside A4. a5_area = 148.0 * 210.0 width = math.sqrt(a5_area * ratio) height = math.sqrt(a5_area / ratio) if width > 210.0 or height > 297.0: scale = min(210.0 / width, 297.0 / height) width *= scale height *= scale return width, height def board_aspect_ratio(board_path: Path) -> float: deck_json = board_path.parent / "deck.json" if deck_json.exists(): meta = load_json(deck_json) if meta.get("cardWidth") and meta.get("cardHeight"): return float(meta["cardWidth"]) / float(meta["cardHeight"]) with load_rgb(board_path) as img: return img.width / img.height def board_target_mm(board_path: Path, config: BuildConfig) -> tuple[float, float]: return board_target_mm_for_mode(board_path, config.board_mode, config.board_custom_mm) def board_mode_fits_paper(board_mode: str, paper_mm: tuple[float, float], margin_mm: float) -> bool: usable_w = paper_mm[0] - 2 * margin_mm usable_h = paper_mm[1] - 2 * margin_mm board_paths = sorted((DECKS_DIR / BOARD_DECK).glob("*-jumbo-front.png")) if not board_paths: return True for board_path in board_paths: board_w, board_h = board_target_mm_for_mode(board_path, board_mode) fits_upright = board_w <= usable_w and board_h <= usable_h fits_rotated = board_h <= usable_w and board_w <= usable_h if not (fits_upright or fits_rotated): return False return True