platform-core/modules/serviceCenter/services/serviceGeneration/styleDefaults.py
ValueOn AG d61e29bcac
Some checks failed
Deploy Plattform-Core (Int) / test (push) Failing after 24s
Deploy Plattform-Core (Int) / deploy (push) Has been skipped
fixes private model and udb scoping sources
2026-06-03 09:37:03 +02:00

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