"""Render README.md → a polished, visually-engaging README PDF.
Pure-Python: the `markdown-pdf` package (pymupdf-backed) — no system deps
like cairo/pango, so it runs anywhere without Homebrew/apt.
This builder does NOT alter the README's wording, ordering, details or
context. It only changes presentation: it prepends a cover page, inserts
an auto-generated clickable table of contents, starts every top-level
("## ") section on a fresh page, and applies a refined typographic theme
so the document reads like a professionally typeset report rather than a
raw markdown dump.
Run:
uv pip install --python ~/.cache/uv-venvs/insurance-sales-bot/bin/python markdown-pdf
python tools/build_readme_pdf.py
Output: ~/Desktop/Insurance-Sales-Bot-README.pdf (sharable artifact;
the HF Space repo rejects binaries, so it is never committed).
"""
from __future__ import annotations
import datetime as _dt
import re
from pathlib import Path
from markdown_pdf import MarkdownPdf, Section
ROOT = Path(__file__).resolve().parent.parent
README_PATH = ROOT / "README.md"
OUT_PATH = Path.home() / "Desktop" / "Insurance-Sales-Bot-README.pdf"
def strip_yaml_frontmatter(text: str) -> str:
"""HF Spaces' YAML metadata at the top of README is meaningless in the PDF."""
if not text.startswith("---"):
return text
end = text.find("\n---", 3)
if end == -1:
return text
return text[end + 4:].lstrip()
def strip_anchor_links(text: str) -> str:
"""Convert in-page `[Text](#anchor)` → `Text` so the pymupdf backend does
not choke resolving local targets. External `[Text](https://…)` links and
the auto-generated TOC stay fully clickable."""
return re.sub(r"\[([^\]]+)\]\(#[^)]+\)", r"\1", text)
def extract_title(text: str) -> tuple[str, str]:
"""Pull the first H1 as the cover title and the first non-empty line
after it as the subtitle, so the cover reflects the README verbatim
(no invented copy)."""
title, subtitle = "Insurance Sales Bot", ""
lines = text.splitlines()
for i, ln in enumerate(lines):
if ln.startswith("# "):
title = ln[2:].strip()
for nxt in lines[i + 1:]:
s = nxt.strip()
if s and not s.startswith("#"):
subtitle = re.sub(r"[*_`]", "", s)
break
break
return title, subtitle
# Cover page is pure presentation; it restates the README's own H1/subtitle.
COVER_TEMPLATE = """
PROJECT DOCUMENTATION
{title}
{subtitle}
Author · Rohit Saraf
Generated · {date}
"""
CSS = """
body { font-family: -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 10.8pt; color: #1f2937; line-height: 1.62; }
/* ---- Cover page ---- */
.cover { text-align: center; padding-top: 150px; page-break-after: always; }
.cover-kicker { font-size: 11pt; letter-spacing: 5px; color: #0f766e; font-weight: 700; }
.cover-title { font-size: 32pt; color: #0f172a; margin: 26px 40px 10px; border: none;
line-height: 1.18; }
.cover-sub { font-size: 13.5pt; color: #475569; margin: 0 60px; font-style: italic; }
.cover-rule { width: 90px; height: 4px; background: #0f766e; margin: 34px auto; border-radius: 2px; }
.cover-meta { font-size: 11pt; color: #334155; margin: 4px 0; }
.cover-foot { font-size: 9.5pt; color: #94a3b8; margin-top: 150px; }
/* ---- Headings: each top-level section on its own page ---- */
h1 { font-size: 21pt; color: #0f172a; margin: 0.4em 0 0.3em; border-bottom: 2px solid #0f766e;
padding-bottom: 5px; }
h2 { font-size: 16pt; color: #134e4a; margin: 1.3em 0 0.45em; border-bottom: 1px solid #cbd5e1;
padding-bottom: 4px; page-break-before: always; }
h3 { font-size: 12.8pt; color: #0f766e; margin: 1.15em 0 0.3em; }
h4 { font-size: 11.2pt; color: #1e3a8a; margin: 0.95em 0 0.3em; }
p { margin: 0.5em 0; }
a { color: #0d6efd; text-decoration: none; }
strong { color: #0f172a; }
em { color: #334155; }
code { font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace; font-size: 9pt;
background: #f1f5f9; padding: 1px 4px; border-radius: 3px; color: #b91c1c; }
pre { font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace; font-size: 8.8pt;
background: #f8fafc; border: 1px solid #e2e8f0; border-left: 3px solid #0f766e;
padding: 10px 12px; border-radius: 4px; line-height: 1.45; }
pre code { background: transparent; padding: 0; color: #0f172a; }
blockquote { border-left: 3px solid #0f766e; margin: 0.9em 0; padding: 6px 14px;
color: #475569; background: #f0fdfa; border-radius: 0 4px 4px 0; }
/* The README is pure markdown (no tables by design); style defensively anyway. */
table { border-collapse: collapse; width: 100%; margin: 0.7em 0; font-size: 9.6pt; }
th, td { border: 1px solid #cbd5e1; padding: 5px 8px; text-align: left; vertical-align: top; }
th { background: #f1f5f9; font-weight: 600; color: #0f172a; }
ul, ol { padding-left: 22px; margin: 0.45em 0; }
li { margin: 0.28em 0; }
hr { border: none; border-top: 1px solid #cbd5e1; margin: 1.6em 0; }
"""
def main() -> None:
raw = README_PATH.read_text(encoding="utf-8")
body = strip_yaml_frontmatter(raw)
title, subtitle = extract_title(body)
cleaned = strip_anchor_links(body)
cover = COVER_TEMPLATE.format(
title=title,
subtitle=subtitle or "Voice-first AI advisor for Indian health insurance",
date=_dt.date.today().strftime("%d %B %Y"),
)
pdf = MarkdownPdf(toc_level=3, optimize=True)
pdf.meta["title"] = f"{title} — README"
pdf.meta["author"] = "Rohit Saraf"
pdf.meta["subject"] = subtitle or "Insurance Sales Bot — documentation"
# Cover first (no TOC entry), then the README verbatim with a clickable TOC.
pdf.add_section(Section(cover, toc=False), user_css=CSS)
pdf.add_section(Section(cleaned, toc=True), user_css=CSS)
pdf.save(str(OUT_PATH))
size_kb = OUT_PATH.stat().st_size / 1024
print(f"wrote {OUT_PATH} ({size_kb:.1f} KB)")
if __name__ == "__main__":
main()