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,                     # Stroke width (0 for no stroke)
    "stroke_color": (0, 0, 0, 255),    # Optional
    "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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rupdf-0.2.2.tar.gz (2.3 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

rupdf-0.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

rupdf-0.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.2 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ ARM64

rupdf-0.2.2-cp313-cp313-macosx_11_0_arm64.whl (1.1 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

rupdf-0.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

rupdf-0.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.2 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64

rupdf-0.2.2-cp312-cp312-macosx_11_0_arm64.whl (1.1 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

rupdf-0.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

rupdf-0.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.2 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ ARM64

rupdf-0.2.2-cp311-cp311-macosx_11_0_arm64.whl (1.1 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

File details

Details for the file rupdf-0.2.2.tar.gz.

File metadata

  • Download URL: rupdf-0.2.2.tar.gz
  • Upload date:
  • Size: 2.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.13

File hashes

Hashes for rupdf-0.2.2.tar.gz
Algorithm Hash digest
SHA256 ef322ddaee8211d94eccc003237be318bd04c0a724fa772b00530a9fb90a417b
MD5 f01477e5628da74f4410397ab6aab62e
BLAKE2b-256 6ae4d7a4bbb7bdb582c99f0a41d664df44eabe9f74233d645e28b56276d5ec80

See more details on using hashes here.

File details

Details for the file rupdf-0.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rupdf-0.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 07d3aa4beb315e7c8f9acb9cd43b3a3ba1edb1be87941afae44389a4fe47e092
MD5 b4ce04ceb07a50a0dfcd27df378a9781
BLAKE2b-256 71c6b7532f6e8db9ed9447319b9f3e85d02d89d110fc391fc25ea8a82928f9dd

See more details on using hashes here.

File details

Details for the file rupdf-0.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rupdf-0.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 28565c93689fda6a2392ca672ca37dbdb3449f4a4ae01fa220f081ebee7eca9b
MD5 a34916a39827219a383ddb10120792c3
BLAKE2b-256 8aa77278e8f8aa16d073dd809809256ab0fc4e2bf051fef3291c432db580ee67

See more details on using hashes here.

File details

Details for the file rupdf-0.2.2-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rupdf-0.2.2-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2312d7675f8ef99cd424198f1586480232ebf250da11e0f2744017be74d36f6d
MD5 5133d4b6ef6bb127351e83351b2ca0ab
BLAKE2b-256 d40c15f620454ceb6403b09f0efb3274c4993454d7d78a8536914d3878b49dbe

See more details on using hashes here.

File details

Details for the file rupdf-0.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rupdf-0.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9bc709d554a1f7fd8e00a7de28b6f8d2e0df02f7a8e1879705af91fcd0926e0c
MD5 72e00d5187e267d1adc8f6c0e8840fd8
BLAKE2b-256 225b13e4dca8733dbe8fd3051efbd8057d23783edd674526931cd5d327febe90

See more details on using hashes here.

File details

Details for the file rupdf-0.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rupdf-0.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 8ad23aaea92ea0951951e48b157a0280fdf2ed78694eceed532aaa85650ccc21
MD5 ed9319e1fc971473612b48b58a79d06e
BLAKE2b-256 3513b476da1bf05c281a4f42a5c3b1d9dc9b9fee134883a57b339c29fc98a503

See more details on using hashes here.

File details

Details for the file rupdf-0.2.2-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rupdf-0.2.2-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1c4cbe0a3b5414cfdb9d2d4664af29b2362ba74497cc910f287a21ef94d4ec3d
MD5 c6657737ef6d788d5b902a24b826f78f
BLAKE2b-256 96f85fabcec2d3a2772f99901e717f221180bceccff5665c56cf836f166f7770

See more details on using hashes here.

File details

Details for the file rupdf-0.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rupdf-0.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d2e3265fb2a4a919ab5d94bed7f8708ba64be48ea2f0537a10b58aef71cddc1c
MD5 64796fdd2b0fd4c3cfd46f267ab9c2c7
BLAKE2b-256 c7bb5cd0a23014bc179174af830ab16162555e02e7979a7a4deee64afd21ca6f

See more details on using hashes here.

File details

Details for the file rupdf-0.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rupdf-0.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 4a1a2dacab5b1fa5cf21ba4a115a220cbb58fd67612d499daaf90f06f31097e6
MD5 34d8aba8ed223f5ee117476343e08bcc
BLAKE2b-256 f82aee1955ba4bb7b073569cfee1ad9f7b0a04792efdcffb8eea60c60526282d

See more details on using hashes here.

File details

Details for the file rupdf-0.2.2-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rupdf-0.2.2-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2070a3e417e8e4ee29e18a49751a9b59f0334cd007bffa770e73561686f49bc8
MD5 27a44a56ee3f7f67393e11aea2c221ba
BLAKE2b-256 2c7984865fac048a83410f60fd2e795bc0dc5ba71c5713a8e2c785cdfeac5254

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.1

10 files

0.4.0

10 files

0.3.0

10 files

This release

0.2.2 This release

10 files

0.2.1

10 files

0.2.0

10 files

0.1.8

5 files

0.1.7

7 files

0.1.6

5 files

0.1.5

2 files

0.1.4

4 files

0.1.3

4 files

0.1.2

5 files

0.1.1

4 files

0.1.0

2 files

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