Skip to main content

rupdf

A fast, minimal PDF renderer in Rust with Python bindings. Takes pre-laid-out pages and renders them to PDF bytes.

Features

  • Text with TTF/OTF fonts, horizontal/vertical alignment, and colors
  • Font fallback chains — per-element list of fallback fonts for characters absent from the primary font's cmap (emoji, CJK, Arabic, etc.)
  • Rectangles with stroke, fill, and rounded corners
  • Lines with configurable width
  • Images (PNG, JPEG, WebP, SVG)
  • Barcodes (Code 128, GS1-128), Data Matrix (incl. GS1 DataMatrix), and QR codes
  • Font subsetting - embeds only used glyphs
  • Compression - optional zlib compression

Installation

pip install rupdf

Usage

import rupdf

doc = {
    "metadata": {
        "title": "My Document",
        "author": "Jane Doe"
    },
    "pages": [
        {
            "size": (612, 792),  # Letter size in points
            "background": (255, 255, 255, 255),
            "elements": [
                {
                    "type": "text",
                    "x": 72,
                    "y": 72,
                    "text": "Hello, World!",
                    "font": "main",
                    "size": 24,
                    "color": (0, 0, 0, 255)
                },
                {
                    "type": "rect",
                    "x": 72,
                    "y": 120,
                    "w": 200,
                    "h": 100,
                    "stroke": 1.0,
                    "stroke_color": (0, 0, 0, 255),
                    "fill_color": (240, 240, 240, 255)
                }
            ]
        }
    ],
    "resources": {
        "fonts": {
            "main": {"path": "/path/to/font.ttf"}
            # Or: "main": {"bytes": font_bytes}
        },
        "images": {
            "logo": {"path": "/path/to/logo.png"}
            # Or: "logo": {"bytes": image_bytes}
        }
    }
}

# Render to PDF bytes
pdf_bytes = rupdf.render_pdf(doc, compress=True)

# Write to file
with open("output.pdf", "wb") as f:
    f.write(pdf_bytes)

Coordinate System

  • Origin: top-left corner of the page
  • Units: points (1 point = 1/72 inch)
  • Y-axis: increases downward

Common page sizes:

  • Letter: 612 x 792 points
  • A4: 595 x 842 points

Element Types

Text

{
    "type": "text",
    "x": 72,
    "y": 72,
    "text": "Hello",
    "font": "font_ref",           # Reference to fonts in resources
    "font_fallback": [],          # Optional list of fallback font refs; see "Font fallback" below
    "missing_glyph_policy": "drop",  # "drop" (default) or "raise"
    "size": 12,                   # Font size in points
    "color": (0, 0, 0, 255),      # RGBA (optional, default black)
    "align": "left",              # "left", "center", or "right" (optional)
    "vertical_anchor": "baseline",# "baseline", "capline", or "center" (optional)
    "strikethrough": False        # Draw a line through the text (optional)
}

Positioning:

  • (x, y) specifies the anchor point of the text
  • align controls horizontal alignment relative to x:
    • "left" (default): text extends to the right of x
    • "center": text is centered on x
    • "right": text extends to the left of x
  • vertical_anchor controls vertical alignment relative to y:
    • "baseline" (default): y is the text baseline
    • "capline": y is the top of capital letters
    • "center": y is the vertical center of capital letters

Strikethrough: when "strikethrough": True, a horizontal line is drawn through the text at the font's strikeout position, spanning the exact rendered run width, in the text color. Position and thickness come from the font's metrics (with sensible fallbacks for fonts that lack them).

TextBox

Multi-line text with word wrapping, like Illustrator's "area type".

{
    "type": "textbox",
    "x": 72,
    "y": 72,
    "w": 200,
    "h": 100,
    "text": "Long text that will wrap within the box...",
    "font": "font_ref",
    "font_fallback": [],          # Optional list of fallback font refs; see "Font fallback" below
    "missing_glyph_policy": "drop",  # "drop" (default) or "raise"
    "size": 12,
    "line_height": 14.4,          # Optional, default = size * 1.2
    "color": (0, 0, 0, 255),      # Optional, default black

    # Box alignment (how the box is positioned relative to x, y)
    "box_align_x": "left",        # "left", "center", or "right" (optional)
    "box_align_y": "top",         # "top", "center", or "bottom" (optional)

    # Text alignment (how text is positioned inside the box)
    "text_align_x": "left",       # "left", "center", or "right" (optional)
    "text_align_y": "baseline"    # "top", "capline", "center", "baseline", or "bottom" (optional)
}

Two-Layer Alignment:

  1. Box alignment - positions the box relative to (x, y):

    • box_align_x: left=x is left edge, center=x is center, right=x is right edge
    • box_align_y: top=y is top edge, center=y is center, bottom=y is bottom edge
  2. Text alignment - positions text inside the box:

    • text_align_x: per-line horizontal alignment (left/center/right)
    • text_align_y: vertical alignment of the text block:
      • "top": ascender of first line at box top
      • "capline": cap height of first line at box top
      • "center": text block vertically centered
      • "baseline" (default): last line's baseline at box bottom
      • "bottom": descender of last line at box bottom

Notes:

  • Text wraps at word boundaries to fit within w
  • Overflow is clipped to box bounds
  • Explicit \n in text creates line breaks

Font fallback

Text and TextBox elements accept a font_fallback list of font aliases tried in order for any character the primary font's cmap doesn't cover. This is how you render emoji, CJK, Arabic, or any script outside your primary font's coverage without crashing or showing tofu.

doc = {
    "pages": [{
        "size": (612, 792),
        "elements": [
            {
                "type": "text",
                "x": 72, "y": 72,
                "text": "Tony ❤ 山田",
                "font": "body",
                "font_fallback": ["emoji", "body_jp"],
                "size": 12,
            },
        ],
    }],
    "resources": {
        "fonts": {
            "body":     {"path": "IBMPlexSans-Regular.otf"},
            "emoji":    {"path": "NotoEmoji-Regular.ttf"},
            "body_jp":  {"path": "IBMPlexSansJP-Regular.otf"},
        },
    },
}

Semantics:

  • For each character, rupdf walks [font, *font_fallback] and uses the first font whose cmap covers it.
  • The primary font drives line height, ascender, descender, baseline, and cap-height metrics. Fallback chars share that baseline so they don't shift layout.
  • Inside one text element, the PDF content stream switches fonts inline (via Tf) at each run boundary. Word-wrapping in TextBox honors per-character advances across fonts.
  • Fallback fonts not referenced by any character are not embedded.

missing_glyph_policy controls what happens when no font in the chain covers a character:

Value Behavior
"drop" (default) Character silently omitted. Surrounding spaces and layout are preserved.
"raise" RupdfError is raised, naming the primary font and the offending codepoint.

"drop" is the right default for user-supplied text (customer names, free-text fields) where rendering must not fail. Use "raise" in tests or pipelines that want to detect unsupported codepoints early.

Rectangle

{
    "type": "rect",
    "x": 72,
    "y": 72,
    "w": 100,
    "h": 50,
    "stroke": 1.0,                     # Optional, defaults to 0 (no stroke)
    "stroke_color": (0, 0, 0, 255),    # Optional, defaults to black
    "fill_color": (255, 255, 255, 255), # Optional
    "corner_radius": 10                # Optional, for rounded corners
}

Notes:

  • (x, y) is the top-left corner
  • corner_radius creates rounded corners; automatically clamped to half the smallest dimension

Line

{
    "type": "line",
    "x1": 72,
    "y1": 72,
    "x2": 200,
    "y2": 72,
    "stroke": 1.0,
    "color": (0, 0, 0, 255)
}

Image

{
    "type": "image",
    "x": 72,
    "y": 72,
    "w": 200,
    "h": 150,
    "image_ref": "logo"  # Reference to images in resources
}

Supported formats: PNG, JPEG, WebP (rasterized to 300 DPI), SVG (rendered as vectors).

Barcode (Code 128)

{
    "type": "barcode",
    "x": 72,
    "y": 72,
    "w": 200,
    "h": 60,
    "value": "ABC-123",
    "human_readable": True,  # Show text below barcode
    "font": "font_ref",      # Required if human_readable
    "font_size": 10
}

GS1-128

A Code 128 variant with an FNC1 designator and Application Identifiers (AIs). The value is a parenthesized string; FNC1 separators are inserted automatically after variable-length fields, and fixed-length AIs (00, 01-04, 11-19, 20, 31xx-36xx, 41) have their data length validated.

{
    "type": "gs1_128",       # also "gs1-128" or "gs1"
    "x": 72,
    "y": 72,
    "w": 300,
    "h": 60,
    "value": "(01)12345678901234(17)260101(10)BATCH123",
    "human_readable": True,  # renders the parenthesized form below the bars
    "font": "font_ref",
    "font_size": 9
}

Data Matrix (incl. GS1 DataMatrix)

# Plain ECC 200 Data Matrix
{
    "type": "datamatrix",
    "x": 72,
    "y": 72,
    "size": 80,                # bounding-box dimension
    "value": "UNIT-42",
    "color": (0, 0, 0, 255),         # optional
    "background": (255, 255, 255, 255)  # optional
}

# GS1 Data Matrix — same parenthesized (AI)data form as GS1-128
{
    "type": "gs1_datamatrix",  # also "gs1-datamatrix"
    "x": 72,
    "y": 72,
    "size": 80,
    "value": "(01)12345678901234(17)260101(10)BATCH123",
    "shape": "square"  # "any" (default), "square", or "rectangular"
}

Both square and rectangular shapes are valid GS1 Data Matrix per the GS1 General Specifications. "any" (the default) lets the encoder pick the smallest-area symbol for the payload, which often turns out rectangular. Pick "square" if you need the conventional square shape.

QR Code

{
    "type": "qrcode",
    "x": 72,
    "y": 72,
    "size": 100,             # QR codes are square
    "value": "https://example.com",
    "color": (0, 0, 0, 255),       # Foreground (dark modules)
    "background": (255, 255, 255, 255)  # Background (light modules)
}

Error Handling

try:
    pdf = rupdf.render_pdf(doc)
except rupdf.RupdfError as e:
    print(f"Failed to render: {e}")

Common errors:

  • Missing font or image reference
  • Invalid page dimensions
  • Missing required element fields
  • Character not found in font

Performance

Benchmarks comparing rupdf to ReportLab (10 iterations each):

Benchmark rupdf ReportLab Speedup
Empty page 0.02ms 0.27ms 13x
50 text lines 0.82ms 0.82ms 1x
100 rectangles 0.19ms 1.02ms 5x
10 pages 1.62ms 3.80ms 2x

Development

# Build
maturin develop

# Run tests
cargo test                    # Rust unit tests
pytest python/tests/ -v       # Python tests

# Generate test PDF with all element types
python scripts/generate_test_pdf.py                          # Without images
python scripts/generate_test_pdf.py --svg logo.svg --png photo.png  # With images

# Benchmarks
python benchmarks/run_benchmark.py

License

MIT

Metadata

Release files for rupdf 0.4.3

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

Source distribution (sdist)

Source distribution for rupdf 0.4.3
File Size Uploaded Provenance Actions
rupdf-0.4.3.tar.gz 2.3 MB Without provenance Details for rupdf-0.4.3.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for rupdf 0.4.3
File Actions
rupdf-0.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.17+ x86-64 Details for rupdf-0.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
rupdf-0.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl CPython 3.13 CPython 3.13 Linux glibc 2.17+ ARM64 Details for rupdf-0.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
rupdf-0.4.3-cp313-cp313-macosx_11_0_arm64.whl CPython 3.13 CPython 3.13 macOS 11.0+ ARM64 Details for rupdf-0.4.3-cp313-cp313-macosx_11_0_arm64.whl
rupdf-0.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ x86-64 Details for rupdf-0.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
rupdf-0.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ ARM64 Details for rupdf-0.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
rupdf-0.4.3-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details for rupdf-0.4.3-cp312-cp312-macosx_11_0_arm64.whl
rupdf-0.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.17+ x86-64 Details for rupdf-0.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
rupdf-0.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl CPython 3.11 CPython 3.11 Linux glibc 2.17+ ARM64 Details for rupdf-0.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
rupdf-0.4.3-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details for rupdf-0.4.3-cp311-cp311-macosx_11_0_arm64.whl

Total release size: 19.0 MB

Release files / rupdf-0.4.3.tar.gz

Download URL rupdf-0.4.3.tar.gz
Size 2.3 MB
Tags Source
SHA-256 checksum
How to use checksums
20fdec2762452ad35c6b17e0ca66acf14b3476015a0de19c484b9d3446b2b587
BLAKE2b-256 checksum
How to use checksums
48dad8937c6669bc53baa8bbeb633dac4e65ebdcdf6c344dc964474b6997d91c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / rupdf-0.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL rupdf-0.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 2.3 MB
Tags CPython 3.13 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
726d46fc3c5690f34000547c79e94c0776d691f807559222f7aef78bd95697a5
BLAKE2b-256 checksum
How to use checksums
d26f0c6a1a92e45089a25168f68e8d8ff8cd4e56642d1bbf805a8c6c78d879df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / rupdf-0.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl

Download URL rupdf-0.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Size 2.1 MB
Tags CPython 3.13 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
4512572b497d1a31ce2ba0cfee36cc81fcf5feb598ecd47bd34f3ec59f3a1b3e
BLAKE2b-256 checksum
How to use checksums
3e38fe0b630f78a90519944c1812548aab59506ae76a8716cc7b5835ad16b07e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / rupdf-0.4.3-cp313-cp313-macosx_11_0_arm64.whl

Download URL rupdf-0.4.3-cp313-cp313-macosx_11_0_arm64.whl
Size 1.2 MB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
65f0feb2750739edcca3e0cc3619cb4ef948ccd2cc0a08b2a1b7de26a32759a9
BLAKE2b-256 checksum
How to use checksums
a2832cd63af294ab2fed784f7317e67374ea2902ff2ed5b44e332692e126de52
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / rupdf-0.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL rupdf-0.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 2.3 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
590d9142690201e39df1707dd3e6e39959033af98fcd6c125bd6c923b3fdefeb
BLAKE2b-256 checksum
How to use checksums
141f2880c6694654b4f8a67fad2a2003c7663a141cf21bcd62509cb28021a576
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / rupdf-0.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl

Download URL rupdf-0.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Size 2.1 MB
Tags CPython 3.12 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
f56e35d6ee25a855d55dccbc5912e457d0b26d6bba108987293af25610088e90
BLAKE2b-256 checksum
How to use checksums
d2824148a1ee1449ad9bfc9a3d67f53336ba93d2995857178e622c50ba3e5991
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / rupdf-0.4.3-cp312-cp312-macosx_11_0_arm64.whl

Download URL rupdf-0.4.3-cp312-cp312-macosx_11_0_arm64.whl
Size 1.2 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
f8a567fe94e2854fd40bc5a465a258efaedea61ae59e1cfcd49ca612ea420cef
BLAKE2b-256 checksum
How to use checksums
4590cd23c057bfe4a77ea5b8d89627d56784f8db171cfa04e8e2e94ddb4e34da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / rupdf-0.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL rupdf-0.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 2.3 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
f4b63d62165a9ecc79b8637c19230e5c0e7d7b94bd3023cd2c7ea635d85368ab
BLAKE2b-256 checksum
How to use checksums
2141b28ccbb1ae55a543714ba28bffad96671d37177033f7ad81c30c3f1c4190
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / rupdf-0.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl

Download URL rupdf-0.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Size 2.1 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
22036c969cc99db65b8b4ba6e7da3bff2361b09e852cc30950fcdb53d9ceaee0
BLAKE2b-256 checksum
How to use checksums
4228042f256d38eaa747561e6e90f8f374520deb2287f0001858156849ffa5ea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / rupdf-0.4.3-cp311-cp311-macosx_11_0_arm64.whl

Download URL rupdf-0.4.3-cp311-cp311-macosx_11_0_arm64.whl
Size 1.2 MB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
1523269acd9f1b126aa2e74cb766f69de8f73fc55c5ecceedaeb94aa2f16f632
BLAKE2b-256 checksum
How to use checksums
e8406b4550b0f0ee36a994b6458d36cff2ae17094fc6417e77149cc74e3111f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Security for rupdf 0.4.3

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.

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