PDF generation with a fluent API. Core is lean (reportlab + Pillow + svglib). Optional [md] extra adds full markdown-to-PDF rendering with banner headers, math, mermaid, syntax highlighting and more.
PDFMarQ wraps reportlab's stateful canvas into a fluent, cursor-based API. You describe document flow, not coordinates. Markdown rendering lives in a separate subpackage so the core stays installable without heavyweight dependencies.
- Fluent over imperative:
pdf.font("Helvetica", 12).text("Hi").enter().text("World")vscanvas.setFont() → canvas.drawString() → manual Y tracking - Cursor flows naturally: top-left origin,
ygrows down,enter()is a newline - One way per feature:
pdf.table(),pdf.image(),pdf.svg(),pdf.link(), no overloaded call signatures - Markdown is optional: core → 3 deps,
[md]adds the stack - Banner as contract: YAML frontmatter block becomes a styled banner with logo, status badge, version, dates, signature slot
- Lean output: no headless Chrome, no web stack, no React SSR. Pure Python + native PDF primitives. Files stay small, rendering stays fast, fonts are embedded properly, and the output opens clean in every PDF reader
Trade-offs:
- Cursor mutation is a state machine. Great for linear documents, awkward for complex grid layouts. For those, drop into raw reportlab via
pdf._canvas. - Markdown rendering estimates heights analytically to decide page breaks. Good enough for 95% of content. Edge cases with math + wide tables may push onto the next page more aggressively than necessary.
- Installing Python + a stack of deps is a barrier for non-technical users. If you're building a tool end-users will actually touch, put PDFMarQ behind a backend service (FastAPI endpoint, CLI wrapper, desktop app) rather than asking them to
pip installanything.
pip install pdfmarq # core: reportlab, Pillow, svglib
pip install pdfmarq[md] # + markdown rendering stackfrom pdfmarq import PDF
# Fluent core API
with PDF("report.pdf") as pdf:
pdf.font("Helvetica", 20, "Bold").text("Quarterly Report")
pdf.enter().font(size=12, mode="Regular")
pdf.text("Revenue up 23% year-over-year.")
pdf.table(
[["Q1", "120k"], ["Q2", "148k"], ["Q3", "172k"]],
header=["Quarter", "Revenue"],
sizes=[1, 2], aligns=["C", "R"],
)
pdf.image("chart.png", 180, 80)
pdf.link("https://xaeian.com", 40, 5)from pdfmarq.md import md_to_pdf, MarkdownStyle
# Markdown to PDF
style = MarkdownStyle(
body_family="IBMPlexSans",
head_family="Sora",
page_number_label="Page", # "Page 1/5" in footer
)
md_to_pdf(open("doc.md").read(), "doc.pdf", style=style, font_dir="./fonts")- GitHub-flavored markdown (tables, fenced code, lists, strikethrough)
- YAML frontmatter rendered as a styled banner (logo, status badge, version, sign block, landscape flag)
- Mini-banner on continuation pages with aspect-aware logo (width + height caps)
- Page numbering
Page N/Mvia deferred canvas rendering (configurable) - Built-in language presets (en/pl/de/fr/es/it/cs/sk) via
lang_style(): covers banner, callouts, date format, page numbers - Skip-duplicate-title: drops
# Xwhen it matches frontmattertitle - Auto-slugged headings with clickable
[text](#anchor)internal links (unicode-aware, broken targets degrade to plain text) - Local-path links configurable via
link_root+link_base(or per-doc YAMLbase:) - Syntax highlighting (Pygments)
- Math formulas inline
$x^2$and block$$...$$(matplotlib) - Mermaid diagrams via
mermaid-cli(local) ormermaid.ink(network fallback), capped at a configurable max height - Footnotes, emoji shortcodes
:rocket:, nested lists, blockquotes, GitHub callouts (> [!NOTE],> [!WARNING], …) - Zebra-striped tables (subtle, readability without noise)
- Smart page breaks for paragraphs, tables, lists, and blockquotes (pre-measured, no orphans)
- Basic inline HTML pass-through (
<b>,<i>,<code>,<br>,<hr>)
| Module | Description | Docs |
|---|---|---|
pdfmarq |
Core PDF API (fluent cursor-based drawing) | pdfmarq/readme.md |
pdfmarq.md |
Markdown-to-PDF renderer (optional [md] extra) |
pdfmarq/md/readme.md |