Skip to main content

simpdf

simpdf is a small Python library for rendering Markdown into PDF with fpdf2, while keeping font handling explicit so Cyrillic and other non-Latin text work reliably with external TTF fonts.

The library is centered on a MarkdownPdfRenderer class. You give it a directory with TTF files, describe the font face to register, and then render Markdown into PDF bytes or directly into a file.

Features

  • Uses fpdf2 as the PDF backend
  • Supports external TTF fonts and Cyrillic-friendly fonts such as DejaVu Sans
  • Provides a helper to download DejaVu Sans fonts into a target directory
  • Supports markdown images from filesystem paths, data: URLs, remote URLs, and custom callbacks
  • Class-first API with optional convenience helpers
  • Minimal CLI for rendering Markdown and downloading DejaVu fonts
  • Supports these Markdown elements in v1:
    • headings
    • paragraphs
    • bold and italic text
    • inline code
    • ordered and unordered lists
    • tables
    • blockquotes
    • fenced code blocks
    • thematic breaks
    • clickable links
    • block images

Installation

pip install simpdf

For development and tests:

pip install -e .[dev]

Quick Start

from simpdf import FontFace, MarkdownPdfRenderer

renderer = MarkdownPdfRenderer(
    font_directory="fonts",
    font_face=FontFace.dejavu_sans(),
)

markdown_text = """
# Example

Привет, мир.

- one
- two
"""

pdf_bytes = renderer.render_to_bytes(markdown_text)

with open("output.pdf", "wb") as handle:
    handle.write(pdf_bytes)

Downloading DejaVu Fonts

The library does not bundle fonts inside the wheel. Use the download helper to populate your own font directory.

from simpdf import download_dejavu_fonts

downloaded = download_dejavu_fonts("fonts")
print([path.name for path in downloaded])

The helper downloads the DejaVu font files from public GitHub repository.

Using Custom Fonts

You can use any TTF family as long as you provide at least a regular face. Bold, italic, and bold-italic are optional; if omitted, the regular face is reused.

from simpdf import FontFace, MarkdownPdfRenderer

renderer = MarkdownPdfRenderer(
    font_directory="my-fonts",
    font_face=FontFace(
        family="NotoSansCustom",
        regular="NotoSans-Regular.ttf",
        bold="NotoSans-Bold.ttf",
        italic="NotoSans-Italic.ttf",
        bold_italic="NotoSans-BoldItalic.ttf",
    ),
)

Font file values may be file names relative to font_directory or absolute paths.

Formatting Options

Formatting is configured with a plain nested dictionary. Any omitted value falls back to the library defaults.

Example:

from simpdf import FontFace, MarkdownPdfRenderer

renderer = MarkdownPdfRenderer(
    font_directory="fonts",
    font_face=FontFace.dejavu_sans(),
    formatting_options={
        "text": {"font_size": 11},
        "headings": {
            "sizes": {1: 26, 2: 20, 3: 16},
        },
        "lists": {"indent": 9},
        "table": {"heading_font_size": 13},
    },
)

Supported option groups:

  • page: page size, orientation, and margins
  • text: base font size, line height multiplier, text color
  • headings: per-level sizes and spacing
  • paragraph: paragraph spacing
  • lists: indent, bullet symbol, list spacing
  • blockquote: indent, bar styling, text color
  • table: font sizes, padding, minimum column width, spacing
  • code_block: font size, padding, colors, spacing
  • inline_code: inline code text color
  • images: block image sizing, spacing, alignment, table-cell best-effort height
  • links: link color and underline toggle
  • thematic_break: rule color, width, spacing

Image Support

Markdown images use the normal syntax:

![alt text](path-or-url)

Supported image sources in 0.1.1:

  • custom callback
  • filesystem paths
  • data: URLs with embedded image data
  • remote http/https URLs

Filesystem Images

If you do nothing, filesystem images are resolved relative to the current working directory. If you want a different base directory, pass image_base_dir.

from simpdf import FontFace, MarkdownPdfRenderer

renderer = MarkdownPdfRenderer(
    font_directory="fonts",
    font_face=FontFace.dejavu_sans(),
    image_base_dir="docs-assets",
)

markdown_text = "![Logo](logo.png)"
pdf_bytes = renderer.render_to_bytes(markdown_text)

Custom Callback

The callback receives (alt_text, source) and may return raw bytes, a binary stream, or a PIL.Image.Image when Pillow is installed. Returning None falls through to the built-in resolvers.

from io import BytesIO
from simpdf import FontFace, MarkdownPdfRenderer

def image_callback(alt_text: str, source: str):
    if source == "custom://badge":
        return BytesIO(open("badge.png", "rb").read())
    return None

renderer = MarkdownPdfRenderer(
    font_directory="fonts",
    font_face=FontFace.dejavu_sans(),
    image_resolver=image_callback,
)

Layout Behavior

  • Images render as standalone block elements.
  • If a paragraph mixes text and images, the renderer outputs the text and image blocks in order.
  • Images inside table cells are best-effort in 0.1.1.
    • image-only cells are rendered as images
    • mixed text+image cells fall back to text rendering using the image alt text

Convenience Helpers

If you prefer a functional call site, simpdf also exports:

  • render_markdown_to_pdf_bytes(...)
  • render_markdown_to_pdf_file(...)

These helpers internally construct MarkdownPdfRenderer.

CLI Usage

Render Markdown into PDF:

simpdf render input.md output.pdf \
  --fonts-dir ./fonts \
  --family-name DejaVuSans \
  --font-regular DejaVuSans.ttf \
  --font-bold DejaVuSans-Bold.ttf \
  --font-italic DejaVuSans-Oblique.ttf \
  --font-bold-italic DejaVuSans-BoldOblique.ttf \
  --image-base-dir ./images

Download DejaVu fonts:

simpdf download-dejavu ./fonts

If you want custom formatting from the CLI, pass a JSON file with --options-file.

Examples

See examples/basic_render.py, examples/custom_font_and_style.py, examples/rich_markdown.py, examples/images_from_filesystem.py, and examples/images_custom_callback.py.

Tests

The repo now contains a pytest suite that covers:

  • font config validation
  • DejaVu download helper behavior
  • markdown token flattening and table extraction
  • image resolvers and image rendering
  • PDF rendering with Cyrillic content
  • formatting overrides
  • CLI render and download flows

Run tests with:

pytest

Compatibility Note

For older code that imported simpdf.pdfgen, a small compatibility wrapper is still present. The recommended API is the class-based renderer from simpdf.

Metadata

Release files for simpdf 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 simpdf 0.1.1
File Size Uploaded Provenance Actions
simpdf-0.1.1.tar.gz 22.7 kB Without provenance Details for simpdf-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for simpdf 0.1.1
File Interpreter ABI Platform Actions
simpdf-0.1.1-py3-none-any.whl Python 3 none any Details for simpdf-0.1.1-py3-none-any.whl

Total release size: 41.1 kB

Release files / simpdf-0.1.1.tar.gz

Download URL simpdf-0.1.1.tar.gz
Size 22.7 kB
Tags Source
SHA-256 checksum
How to use checksums
1b520f7d80fcf3a63767674cb653e54ecf25f764e621fce9c99893e1cffae499
BLAKE2b-256 checksum
How to use checksums
3c1ea4ada1db913d88a22ca15fe12d72e5b3495203f826bdc641f2001d1db54f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.0

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

Download URL simpdf-0.1.1-py3-none-any.whl
Size 18.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e2276e69293647b04db11db7277879e92b39a9b7e09028f2793fdf6d0f22d8c1
BLAKE2b-256 checksum
How to use checksums
459e6dbb641ba55a0ede4543d36d95b4a97645fde1a1b979201cac289371193d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.0

Security for simpdf 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

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