feat(okr): /okr:export PDF via weasyprint + scripts/export-pdf.py (SC5)
This commit is contained in:
parent
5890d09ac9
commit
a0e533a856
2 changed files with 192 additions and 0 deletions
108
scripts/export-pdf.py
Normal file
108
scripts/export-pdf.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Render an OKR Markdown document to a print-ready A4 PDF.
|
||||
|
||||
Usage:
|
||||
python3 export-pdf.py <input.md> <output.pdf>
|
||||
|
||||
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 (
|
||||
"<!DOCTYPE html><html><head><meta charset=\"utf-8\">"
|
||||
"<style>" + PAGE_CSS + "</style></head><body>" + body + "</body></html>"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
sys.stderr.write("Bruk: python3 export-pdf.py <input.md> <output.pdf>\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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue