#!/usr/bin/env python3 """Render an OKR Markdown document to a print-ready A4 PDF. Usage: python3 export-pdf.py Dependencies (documented prerequisite, not bundled): pip install markdown weasyprint # native libs on macOS: brew install pango The script degrades gracefully: a missing Python package exits 1 with a pip hint; a missing native library (Pango/cairo/gdk-pixbuf) surfaces at render time as an OSError and exits 1 with a brew hint. Neither path leaks a traceback. """ import sys PIP_HINT = ( "Mangler Python-avhengigheter for PDF-eksport.\n" "Installer dem (dokumentert forutsetning, ikke bundlet):\n" " pip install markdown weasyprint\n" ) PANGO_HINT = ( "Klarte ikke aa rendre PDF: native bibliotek mangler " "(Pango/cairo/gdk-pixbuf).\n" "Installer dem (macOS):\n" " brew install pango\n" "Se: https://doc.courtbouillon.org/weasyprint/stable/first_steps.html\n" ) try: import markdown import weasyprint except ImportError: sys.stderr.write(PIP_HINT) sys.exit(1) # A4 page geometry + table striping + traffic-light score classes. # attr_list lets the Markdown author tag cells: `{: .score-green}` etc. PAGE_CSS = """ @page { size: A4; margin: 20mm 18mm; @bottom-right { content: counter(page) " / " counter(pages); font-size: 9pt; color: #666; } } body { font-family: "Helvetica Neue", Arial, sans-serif; font-size: 10.5pt; line-height: 1.45; color: #1a1a1a; } h1 { font-size: 20pt; border-bottom: 2px solid #2a4d69; padding-bottom: 4px; } h2 { font-size: 15pt; color: #2a4d69; margin-top: 1.4em; } h3 { font-size: 12.5pt; color: #4a6d89; } table { border-collapse: collapse; width: 100%; margin: 1em 0; font-size: 9.5pt; } th, td { border: 1px solid #ccc; padding: 5px 8px; text-align: left; vertical-align: top; } th { background: #2a4d69; color: #fff; font-weight: 600; } tbody tr:nth-child(even) { background: #f3f6f9; } code { font-family: "SF Mono", Menlo, monospace; font-size: 9pt; background: #f0f0f0; padding: 1px 4px; border-radius: 3px; } pre { background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; padding: 10px; overflow-x: auto; } pre code { background: none; padding: 0; } .score-green { background: #d6efd6 !important; color: #1a5c1a; font-weight: 600; } .score-yellow { background: #fbf3cf !important; color: #7a5c00; font-weight: 600; } .score-red { background: #f6d6d6 !important; color: #8c1a1a; font-weight: 600; } """ def build_html(markdown_text): """Convert Markdown to a full standalone HTML document with embedded CSS.""" body = markdown.markdown( markdown_text, extensions=["tables", "fenced_code", "codehilite", "toc", "attr_list"], ) return ( "" "" + body + "" ) def main(): if len(sys.argv) != 3: sys.stderr.write("Bruk: python3 export-pdf.py \n") sys.exit(2) in_path, out_path = sys.argv[1], sys.argv[2] try: with open(in_path, "r", encoding="utf-8") as handle: markdown_text = handle.read() except OSError as err: sys.stderr.write("Kunne ikke lese inputfil '%s': %s\n" % (in_path, err)) sys.exit(1) html_doc = build_html(markdown_text) try: # base_url anchors relative asset paths to the input file's directory. weasyprint.HTML(string=html_doc, base_url=in_path).write_pdf(out_path) except OSError: sys.stderr.write(PANGO_HINT) sys.exit(1) sys.stderr.write("Skrev PDF: %s\n" % out_path) if __name__ == "__main__": main()