From fa78fc9a664c5d5a87c88f16544ef186ce32090f Mon Sep 17 00:00:00 2001 From: The_miro Date: Tue, 21 Jul 2026 13:32:10 +0200 Subject: [PATCH] Add md-to-pdf.sh: convert Markdown to PDF via python-markdown + weasyprint Companion to docs/md-to-html.sh, for cases needing a standalone PDF rather than a themed HTML page. --- docs/md-to-pdf.sh | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100755 docs/md-to-pdf.sh diff --git a/docs/md-to-pdf.sh b/docs/md-to-pdf.sh new file mode 100755 index 0000000..a317b9d --- /dev/null +++ b/docs/md-to-pdf.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# md-to-pdf.sh — Convert a Markdown file to PDF. +# +# Usage: +# md-to-pdf.sh input.md [output.pdf] +# +# output.pdf defaults to input's basename with a .pdf extension, written +# next to input.md. +# +# Requires: python3 with the 'markdown' and 'weasyprint' packages +# sudo pacman -S python-markdown python-weasyprint + +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 input.md [output.pdf]" >&2 + exit 1 +fi + +src="$1" +[[ -f "$src" ]] || { echo "md-to-pdf: file not found: $src" >&2; exit 1; } + +out="${2:-${src%.*}.pdf}" + +for mod in markdown weasyprint; do + python3 -c "import $mod" 2>/dev/null || { + echo "md-to-pdf: missing python module '$mod' — install with:" >&2 + echo " sudo pacman -S python-markdown python-weasyprint" >&2 + exit 1 + } +done + +title=$(grep -m1 '^# ' "$src" | sed 's/^# //' || true) +[[ -z "$title" ]] && title="$(basename "${src%.*}")" + +python3 - "$src" "$out" "$title" <<'PYEOF' +import sys +import markdown +from weasyprint import HTML + +src, out, title = sys.argv[1], sys.argv[2], sys.argv[3] + +with open(src, encoding="utf-8") as fh: + text = fh.read() + +body_html = markdown.markdown( + text, + extensions=["tables", "fenced_code", "toc", "def_list"], +) + +css = """ +@page { size: A4; margin: 2cm; } +body { + font-family: 'DejaVu Sans', sans-serif; + font-size: 11pt; + line-height: 1.5; + color: #1a1a1a; +} +h1, h2, h3, h4 { font-weight: 700; line-height: 1.25; margin: 1.4em 0 0.5em; } +h1 { font-size: 1.9em; border-bottom: 2px solid #333; padding-bottom: 0.2em; } +h2 { font-size: 1.4em; border-bottom: 1px solid #999; padding-bottom: 0.15em; } +h3 { font-size: 1.15em; } +p, li { margin: 0.4em 0; } +code, pre { + font-family: 'DejaVu Sans Mono', monospace; + font-size: 0.85em; + background: #f2f2f2; + border-radius: 3px; +} +code { padding: 0.1em 0.35em; } +pre { padding: 0.8em 1em; overflow-x: auto; white-space: pre-wrap; } +pre code { padding: 0; background: none; } +table { width: 100%; border-collapse: collapse; margin: 1em 0; font-size: 0.85em; } +th, td { border: 1px solid #999; padding: 0.4em 0.6em; text-align: left; vertical-align: top; } +th { background: #e5e5e5; } +tr:nth-child(even) td { background: #fafafa; } +blockquote { border-left: 4px solid #999; margin: 1em 0; padding: 0.3em 1em; color: #444; } +a { color: #1a4fa0; } +""" + +html = f"{title}" \ + f"{body_html}" + +HTML(string=html, base_url=src).write_pdf(out) +print(f"Wrote {out}") +PYEOF