# Copyright (c) 2025 Patrick Motsch # All rights reserved. """Default style definitions and style resolution for document rendering.""" from typing import Any, Dict DEFAULT_STYLE: Dict[str, Any] = { "fonts": { "primary": "Calibri", "monospace": "Consolas", }, "colors": { "primary": "#1F3864", "secondary": "#2C3E50", "accent": "#2980B9", "background": "#FFFFFF", }, "documentTitle": { "sizePt": 28, "weight": "bold", "color": "#1F3864", "spaceBeforePt": 0, "spaceAfterPt": 18, "align": "center", }, "headings": { "h1": {"sizePt": 22, "weight": "bold", "color": "#1F3864", "spaceBeforePt": 22, "spaceAfterPt": 8}, "h2": {"sizePt": 18, "weight": "bold", "color": "#1F3864", "spaceBeforePt": 20, "spaceAfterPt": 6}, "h3": {"sizePt": 14, "weight": "bold", "color": "#2C3E50", "spaceBeforePt": 16, "spaceAfterPt": 4}, "h4": {"sizePt": 12, "weight": "bold", "color": "#2C3E50", "spaceBeforePt": 12, "spaceAfterPt": 3}, }, "paragraph": {"sizePt": 11, "lineSpacing": 1.15, "color": "#333333"}, "table": { "headerBg": "#1F3864", "headerFg": "#FFFFFF", "headerSizePt": 10, "bodySizePt": 10, "rowBandingEven": "#F2F6FC", "rowBandingOdd": "#FFFFFF", "borderColor": "#CBD5E1", "borderWidthPt": 0.5, }, "list": {"bulletChar": "\u2022", "indentPt": 18, "sizePt": 11}, "image": {"defaultWidthPt": 480, "maxWidthPt": 800, "alignment": "center"}, "codeBlock": {"fontSizePt": 9, "background": "#F8F9FA", "borderColor": "#E2E8F0"}, "page": { "format": "A4", "marginsPt": {"top": 60, "bottom": 60, "left": 60, "right": 60}, "showPageNumbers": True, "headerHeight": 30, "footerHeight": 30, "headerLogo": None, "headerText": "", "footerText": "", }, } # ------------------------------------------------------------------ # Theme presets (A3): named, purpose-specific style overrides that are # deep-merged onto DEFAULT_STYLE. A preset only declares the keys it changes; # everything else inherits the default. Explicit per-call `style` overrides # always win over the preset. # ------------------------------------------------------------------ THEME_PRESETS: Dict[str, Dict[str, Any]] = { # "general" intentionally empty -> identical to DEFAULT_STYLE. "general": {}, "finance": { "fonts": {"primary": "Calibri"}, "colors": {"primary": "#0B3D2E", "secondary": "#14532D", "accent": "#047857"}, "documentTitle": {"color": "#0B3D2E", "align": "left"}, "headings": { "h1": {"color": "#0B3D2E"}, "h2": {"color": "#0B3D2E"}, "h3": {"color": "#14532D"}, "h4": {"color": "#14532D"}, }, "table": {"headerBg": "#0B3D2E", "rowBandingEven": "#ECFDF5"}, }, "legal": { # Serif, sober, single-column, justified body, no logo banner. "fonts": {"primary": "Times New Roman"}, "colors": {"primary": "#1A1A1A", "secondary": "#333333", "accent": "#5A5A5A"}, "documentTitle": {"color": "#1A1A1A", "align": "center", "sizePt": 20}, "headings": { "h1": {"color": "#1A1A1A", "sizePt": 16}, "h2": {"color": "#1A1A1A", "sizePt": 14}, "h3": {"color": "#333333", "sizePt": 12}, "h4": {"color": "#333333", "sizePt": 11}, }, "paragraph": {"sizePt": 11, "lineSpacing": 1.5, "color": "#1A1A1A", "align": "justify"}, "table": {"headerBg": "#333333", "rowBandingEven": "#F5F5F5", "borderColor": "#999999"}, "page": {"showPageNumbers": True}, }, "technical": { "fonts": {"primary": "Arial", "monospace": "Consolas"}, "colors": {"primary": "#0F172A", "secondary": "#1E293B", "accent": "#2563EB"}, "documentTitle": {"color": "#0F172A", "align": "left"}, "headings": { "h1": {"color": "#0F172A"}, "h2": {"color": "#1E293B"}, "h3": {"color": "#1E293B"}, "h4": {"color": "#334155"}, }, "paragraph": {"sizePt": 10, "lineSpacing": 1.2}, "codeBlock": {"fontSizePt": 9, "background": "#0F172A"}, "table": {"headerBg": "#1E293B", "rowBandingEven": "#EEF2FF"}, }, "hr": { "fonts": {"primary": "Calibri"}, "colors": {"primary": "#5B21B6", "secondary": "#6D28D9", "accent": "#9333EA"}, "documentTitle": {"color": "#5B21B6", "align": "center"}, "headings": { "h1": {"color": "#5B21B6"}, "h2": {"color": "#6D28D9"}, "h3": {"color": "#7C3AED"}, "h4": {"color": "#7C3AED"}, }, "table": {"headerBg": "#5B21B6", "rowBandingEven": "#F5F3FF"}, }, "marketing": { # Bold, image-friendly, generous spacing, larger title. "fonts": {"primary": "Verdana"}, "colors": {"primary": "#BE123C", "secondary": "#E11D48", "accent": "#F59E0B"}, "documentTitle": {"color": "#BE123C", "sizePt": 34, "align": "center", "spaceAfterPt": 24}, "headings": { "h1": {"color": "#BE123C", "sizePt": 24}, "h2": {"color": "#E11D48", "sizePt": 19}, "h3": {"color": "#E11D48", "sizePt": 15}, "h4": {"color": "#9F1239", "sizePt": 13}, }, "paragraph": {"sizePt": 12, "lineSpacing": 1.3}, "image": {"defaultWidthPt": 540, "maxWidthPt": 900, "alignment": "center"}, "table": {"headerBg": "#BE123C", "rowBandingEven": "#FFF1F2"}, }, } def resolveTheme(themeName: str | None) -> Dict[str, Any]: """Return the partial style override for a named theme preset. Unknown / empty names fall back to ``{}`` (i.e. plain DEFAULT_STYLE). The lookup is case-insensitive. """ if not themeName: return {} return dict(THEME_PRESETS.get(str(themeName).strip().lower(), {})) def _deepMerge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]: """Recursively merge override into base. Both dicts left unchanged; returns new dict.""" result = {} for key in base: if key in override: baseVal = base[key] overVal = override[key] if isinstance(baseVal, dict) and isinstance(overVal, dict): result[key] = _deepMerge(baseVal, overVal) else: result[key] = overVal else: result[key] = base[key] for key in override: if key not in base: result[key] = override[key] return result def resolveStyle(agentStyle: dict | None, documentTheme: str | None = None) -> Dict[str, Any]: """Resolve the effective style: ``DEFAULT_STYLE <- themePreset <- agentStyle``. Precedence (lowest to highest): platform defaults, the named ``documentTheme`` preset, then any explicit per-call ``agentStyle`` override. With no theme and no override this returns plain :data:`DEFAULT_STYLE`. """ resolved = dict(DEFAULT_STYLE) themeOverride = resolveTheme(documentTheme) if themeOverride: resolved = _deepMerge(resolved, themeOverride) if agentStyle: resolved = _deepMerge(resolved, agentStyle) return resolved