183 lines
7.1 KiB
Python
183 lines
7.1 KiB
Python
# 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
|