Skip to main content

pdf-studio

Three lines of code for a PDF with a table, a chart, and a header.

License Python ≥3.10 Build Developer Support


Why

Every Python developer hits the wall where they need a PDF with actual content — a table, a chart, a header with page numbers — and the options are:

  • ReportLab — powerful but 300+ lines of TableStyle boilerplate for a simple table
  • matplotlib — great for charts, zero for document layout
  • fpdf2/WeasyPrint — different tradeoffs, same boilerplate problem
  • Typst — not Python, not embeddable

pdf-studio bridges the gap. It's a thin wrapper around ReportLab that gives you a clean API for the 80% use case: a document with headings, paragraphs, tables, charts, and a running header — in three method calls.

Architecture

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│  Document   │ ──→ │  render.py   │ ──→ │  ReportLab  │
│  (model)    │     │  (builder)   │     │  (engine)   │
└─────────────┘     └──────────────┘     └─────────────┘
       │                     │
       ▼                     ▼
  Style / Font          SVG → PDF
  (dataclasses)         (svglib)

The library has three layers:

  1. Document model (Document, Style, Font) — pure Python dataclasses that describe what goes in the PDF
  2. Render engine (render.py) — converts the document model into ReportLab flowables, handles font registration, table styling, chart conversion (matplotlib SVG → vector PDF via svglib)
  3. ReportLab — the actual PDF generation. pdf-studio is a 300-line wrapper, not a replacement.

The document is rendered in two passes: the first pass counts pages (for {total} in headers), the second pass builds the real PDF with resolved page numbers.

Quick Start

pip install pdf-studio reportlab matplotlib pandas svglib
from pdf_studio import Document

doc = Document(theme="ledger")
doc.set_header("Report | Page {page} of {total}")
doc.add_heading("Q3 Results", level=0)
doc.add_paragraph("Revenue grew 12% year-over-year.")

# Charts from matplotlib (inline vector, no temp files)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.bar(["Apr", "May", "Jun"], [100, 120, 150])
doc.add_chart(fig)

# Tables from pandas or list-of-lists
import pandas as pd
doc.add_table(pd.DataFrame({
    "City": ["Delhi", "Mumbai", "Bangalore"],
    "Revenue": [450, 620, 380],
}), caption="Q3 Revenue by City")

doc.render("report.pdf")   # ✅ 3 methods, one file

That's it. Three methods and one render() call produces a multi-page PDF with a running header, a chart, and a styled table.

Theme Showcase

pdf-studio ships with three research-backed, WCAG-AA verified themes. One Theme object recolors the entire document — headings, tables, charts, KPIs, bullets — without touching your code.

cypher (default) ledger (finance) slate (approachable)
cypher ledger slate

Full showcase PDF: pdf-studio-showcase.pdf — 9 pages, all features across all three themes (tables, charts, KPIs, typography, templates).

Usage:

from pdf_studio import Document

# Single string — theme applied to everything
doc = Document(theme="ledger")   # or "cypher", "slate"
doc.add_heading("Portfolio Statement", level=0)
doc.add_kpi_row([{"label": "AUM", "value": "₹1.2L", "delta": "+4.1%"}])
doc.add_table([["Holding", "Value"], ["VEDL", "₹15,600"]])
doc.render("report.pdf")

Each theme is a complete visual language:

  • cypher — navy foundation #0B1121, teal accent #2DD4BF. The brand default.
  • ledger — deep-green foundation #064E3B, gold accent #B45309. Finance-optimised; green = growth, gold = premium.
  • slate — indigo foundation #312E81, amber accent #D97706. Contemporary, warm, distinct from the navy/teal brand.

All foundations pass WCAG-AA on white (≥4.5:1). Accents are fill/bullet colours only — never used as text.

Declarative Templates

For recurring reports, skip the builder API entirely:

from pdf_studio import Document

data = {
    "title": "Q3 Portfolio",
    "kpis": [{"label": "AUM", "value": "₹1.2L", "delta": "+4.1%"}],
    "composition": (["Equity", "Cash"], [70, 30]),
    "table": [["Holding", "Value"], ["VEDL", "₹15,600"]],
    "right_align_cols": [1],
}
doc = Document.from_template("financial_statement", data)
doc.render("q3.pdf")

One call — structured data → fully styled PDF (theme, KPIs, donut chart, table). The template registry is extensible for your own report types.

API Reference

Document

Method Parameters Description
Document(page_size="A4", margins="32pt", theme=None) theme: "cypher" | "ledger" | "slate" | Theme object Create a document with an optional theme
add_heading(text, level=0) level: 0=title, 1=h1, 2=h2 Add a heading
add_paragraph(text, style=None) style: optional Style Add body text
add_table(data, caption=None, right_align_cols=None) data: DataFrame or list[list] Add a styled table with alternating rows
add_chart(figure, width=None, height=None, close_figure=True) matplotlib Figure Convert chart to inline vector PDF
add_chart_row(figures, space_after=6) list[Figure] Lay out 2 charts side-by-side
add_kpi_row(cards) list[dict] with label, value, delta? Add a row of KPI summary cards
add_bullet(text, style=None) — Add a bullet-pointed item
add_page_break() — Force a new page
set_header(text) supports {page} and {total} Running header on every page
render(path) file path Build the PDF file
from_template(name, data, **kwargs) name: template name, data: dict Class method: build document from template

Convenience Chart Methods

Method Parameters Description
add_bar_chart(labels, values, title=None, horizontal=False, space_after=6) — Brand-styled single-series bar chart
add_line_chart(x, series, title=None, space_after=6) series: dict[name -> list[float]] Brand-styled multi-series line chart
add_donut_chart(labels, values, title=None, space_after=6) — Brand-styled composition donut
add_heatmap(matrix, labels, title=None, diverging=True, space_after=6) — Brand-styled correlation heatmap

All chart methods accept the document's theme automatically.

Style

Field Default Description
font Font() Font specification
leading 1.4 Line-height multiplier
alignment "left" One of: left, center, right, justify
space_before 0 Points before paragraph
space_after 6 Points after paragraph

Font

Field Default Description
family "Inter" One of bundled fonts or your own TTF name
size 11 Point size
bold False Style flag
italic False Style flag
color "#1a1a1a" Hex color string

Theme

Field Default Description
name "cypher" Preset name: cypher, ledger, slate
foundation "#0B1121" Deep structural colour (headings, table header)
surface "#F8FAFC" Light card / zebra background
body_text "#1F2937" Paragraph text
muted_text "#64748B" Captions, KPI labels, axis ticks
accent "#2DD4BF" Single confident highlight (fills, rules, bullets — not text)
good / bad "#047857" / "#B91C1C" Semantic delta colours (KPI up/down)
grid "#E2E8F0" Chart gridlines and table borders
series [...] Ordered chart series palette
h0 / h1 / h2 — Heading colours by level

Use Theme.get("cypher"), Theme.get("ledger"), Theme.get("slate"), or pass a custom Theme object to Document(theme=...).

Bundled Fonts

Three open-source fonts ship with the library — zero system font dependencies:

  • Inter (sans-serif, body text) — Regular, Bold
  • Lora (serif, headings / long-form reading) — Regular, Bold, Italic
  • JetBrains Mono (monospace, code / data) — Regular, Bold

All weights/styles are real TTF files. <b>/<i> tags and Font(bold=True/italic=True) resolve to the correct weight — no synthetic rendering.

Dependencies

Package Required Why
reportlab ✅ PDF generation engine
matplotlib ❌ Charts (only needed if you use add_chart())
pandas ❌ Tables from DataFrames (only needed if you pass DataFrames)
svglib ❌ SVG→PDF conversion (only needed if you use add_chart())

Core dependency is one package: reportlab. Chart and DataFrame features are optional.

Setup

Install from PyPI

pip install pdf-studio

Install from source

git clone https://github.com/AshayK003/pdf-studio.git
cd pdf-studio
pip install -e .

For development (includes test deps):

pip install -e ".[dev]"

Verify installation

python _demo.py
# ✅ pdf-studio v0.1.0 smoke test passed

Testing

# Run all tests
pytest

# Run with coverage
pytest --cov=pdf_studio

# Run specific test file
pytest tests/test_render.py -v

Tests use pytest with standard Python temp directories. No external services, no network calls, no fixtures beyond what pytest provides. 39 tests, ~2.5s runtime.

Troubleshooting

Problem Likely Cause Fix
ModuleNotFoundError: No module named 'reportlab' Missing dependency pip install reportlab
ModuleNotFoundError: No module named 'svglib' Need chart support pip install svglib
Chart renders at wrong size / always 1.0 scale Using patched render.py before PR #4 Update to latest master — the bug was that drawing.width was overwritten before the scale ratio was computed
font not found: Inter Font file missing Reinstall with pip install -e . — bundled fonts are in pdf_studio/fonts/
pdf_studio not found after pip install -e . Build backend needs setuptools pip install setuptools

Production Notes

  • pdf-studio is a library, not a service. It generates PDFs synchronously on the calling thread. For high-throughput PDF generation, wrap calls in a thread pool.
  • The two-pass render is intentional: {total} in headers needs the final page count. If you don't use {total}, the first pass still runs (minor overhead, ~50ms on a 10-page doc).
  • Thread-safe within a single Document instance. Concurrent calls to render() on the same doc are not safe (it mutates internal state).
  • ReportLab is C-extended where it matters (PDF serialization) and pure Python for layout. Expect ~100ms for a 5-page document on modern hardware.

Contributing

See CONTRIBUTING.md for the full guide.

High-level principles:

  • One change per PR. Small, focused diffs are easier to review and less likely to conflict.
  • Tests required. Every new feature or bug fix includes tests. pytest must pass.
  • Keep it simple. This is a 300-line library with 3 bundled fonts. Adding a dependency or a new abstraction needs a strong justification — the library's value is its simplicity.
  • Backend-optional. Chart/matplotlib features use lazy imports. Don't make them hard requirements.

License

AGPL v3 — see LICENSE. Free to use, share, and modify. If you build a closed-source application on top of it, you must release your changes under AGPL as well.


Support the developer with a chai ☕

Metadata

Release files for pdf-studio-py 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pdf-studio-py 0.1.1
File Size Uploaded
pdf_studio_py-0.1.1.tar.gz 898.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pdf-studio-py 0.1.1
File Interpreter ABI Platform
pdf_studio_py-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 1.8 MB

Release files / pdf_studio_py-0.1.1.tar.gz

Download URL pdf_studio_py-0.1.1.tar.gz
Size 898.2 kB
Tags Source
SHA-256 checksum
How to use checksums
15c26587282e8c80a3808982a3c0b1271f9ddb2f912f02eaab0cb6c462213e94
BLAKE2b-256 checksum
How to use checksums
67b48ad8135011eb163a2059c4f637f5dff18e684ceac858797a4ae118459b2e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release files / pdf_studio_py-0.1.1-py3-none-any.whl

Download URL pdf_studio_py-0.1.1-py3-none-any.whl
Size 888.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
298f343b3766f5360ee498205d9b333ce73351f78b7b03f639b77fff6af92c72
BLAKE2b-256 checksum
How to use checksums
3982a0d25a7f260469156e55dbb6b487816c1707a63823dfb082ca35983ccb4d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release history Release notifications | RSS feed

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page