#!/usr/bin/env python3
"""Costruisce themes/tielogic-reference.docx applicando l'identità Tielogic
al reference.docx di default di Pandoc.
Pandoc usa il reference.docx come template di stili (Heading1, Heading2,
Normal, Table, ...) per l'output `-t docx`. Questo script:
1. estrae il reference.docx di default di Pandoc
2. modifica word/styles.xml: font Inter, colori Tielogic blu (#2767d8),
dimensioni e attributi paragrafo coerenti col theme CSS
3. modifica word/header*.xml e word/footer*.xml con testo Tielogic generico
4. riscrive lo zip in themes/tielogic-reference.docx
"""
from __future__ import annotations
import argparse
import io
import re
import shutil
import subprocess
import sys
import zipfile
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
DEFAULT_OUTPUT = REPO_ROOT / "themes" / "tielogic-reference.docx"
TIELOGIC_BLUE = "2767D8"
TIELOGIC_DARK = "0D1B2A"
INTER = "Inter"
INTER_MONO = "JetBrains Mono"
def get_pandoc_default_reference() -> bytes:
if shutil.which("pandoc") is None:
sys.exit("pandoc not found in PATH; install pandoc and rerun")
proc = subprocess.run(
["pandoc", "--print-default-data-file", "reference.docx"],
capture_output=True,
check=True,
)
return proc.stdout
def patch_styles_xml(xml: str) -> str:
"""Apply Tielogic visual identity to styles.xml.
Targets the most visible styles: Heading1, Heading2, Heading3, Normal,
plus the default Title/Subtitle. Operates with regex on the XML for
minimal dependency footprint (no python-docx required).
"""
out = xml
# Override the run-property defaults globally where possible.
# rFonts (font) — set ascii/hAnsi to Inter; cs to Inter; eastAsia kept.
out = re.sub(
r'',
lambda m: _patch_rfonts(m.group(0)),
out,
)
# For each heading style, enforce color + bold + Inter.
headings = {
"Heading1": {"size": "44", "color": TIELOGIC_DARK, "caps": False, "bold": True},
"Heading2": {"size": "28", "color": TIELOGIC_BLUE, "caps": True, "bold": True},
"Heading3": {"size": "23", "color": TIELOGIC_DARK, "caps": False, "bold": True},
"Title": {"size": "52", "color": TIELOGIC_DARK, "caps": True, "bold": True},
"Subtitle": {"size": "26", "color": "5A6478", "caps": True, "bold": False},
}
for style_id, attrs in headings.items():
out = _override_style(out, style_id, **attrs)
return out
def _patch_rfonts(tag: str) -> str:
"""Replace ascii/hAnsi font names with Inter, preserve other attrs."""
new = re.sub(r'w:ascii="[^"]*"', f'w:ascii="{INTER}"', tag)
new = re.sub(r'w:hAnsi="[^"]*"', f'w:hAnsi="{INTER}"', new)
new = re.sub(r'w:cs="[^"]*"', f'w:cs="{INTER}"', new)
if 'w:ascii=' not in new:
new = new.replace('/>', f' w:ascii="{INTER}" w:hAnsi="{INTER}"/>')
return new
def _override_style(xml: str, style_id: str, *, size: str, color: str, caps: bool, bold: bool) -> str:
"""Inject explicit rPr override for a named style, replacing any existing
color/sz/caps/font directive within that style's block."""
pattern = re.compile(
r'(]*w:styleId="' + re.escape(style_id) + r'"[^>]*>)(.*?)()',
re.DOTALL,
)
def repl(m: re.Match[str]) -> str:
head, body, tail = m.group(1), m.group(2), m.group(3)
rpr_block = (
""
f''
+ (f'' if bold else "")
+ (f'' if caps else "")
+ f''
f''
f''
""
)
if "" in body:
body = re.sub(r".*?", rpr_block, body, count=1, flags=re.DOTALL)
else:
# insert after pPr if present, else right after style head
if "" in body:
body = re.sub(r"()", r"\1" + rpr_block, body, count=1)
else:
body = rpr_block + body
return head + body + tail
return pattern.sub(repl, xml)
def patch_header_footer_xml(name: str, xml: str) -> str:
"""Generic Tielogic running header/footer text. We don't try to add
field codes for page numbers in headers — Pandoc emits its own with
PAGE field if the reference includes one; we leave that intact."""
# Replace any pre-existing visible body text inside ...
# with Tielogic placeholders. Keep formatting nodes.
if "header" in name:
replacement = "Tielogic — Soluzioni Software Industriali"
else:
replacement = "Tielogic — Soluzioni Software Industriali"
return re.sub(r"]*>[^<]*", f"{replacement}", xml)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
args = parser.parse_args()
output = args.output.resolve()
output.parent.mkdir(parents=True, exist_ok=True)
src_bytes = get_pandoc_default_reference()
out_buf = io.BytesIO()
with zipfile.ZipFile(io.BytesIO(src_bytes), "r") as zin:
with zipfile.ZipFile(out_buf, "w", zipfile.ZIP_DEFLATED) as zout:
for item in zin.infolist():
data = zin.read(item.filename)
if item.filename == "word/styles.xml":
text = data.decode("utf-8")
text = patch_styles_xml(text)
data = text.encode("utf-8")
elif re.match(r"word/(header|footer)\d*\.xml$", item.filename):
text = data.decode("utf-8")
text = patch_header_footer_xml(item.filename, text)
data = text.encode("utf-8")
zout.writestr(item, data)
output.write_bytes(out_buf.getvalue())
print(f"OK: {output} ({output.stat().st_size} bytes)")
if __name__ == "__main__":
main()