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.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 rupdf 0.4.1
File Size Uploaded Provenance Actions
rupdf-0.4.1.tar.gz 2.3 MB Without provenance Details for rupdf-0.4.1.tar.gz

Built distributions (wheels)

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

Total release size: 13.3 MB

Release files / rupdf-0.4.1.tar.gz

Download URL rupdf-0.4.1.tar.gz
Size 2.3 MB
Tags Source
SHA-256 checksum
How to use checksums
f779aee1c66c3bfbcacfb01bba4b1b4109e1cec3d37433cbd584e571e973e80b
BLAKE2b-256 checksum
How to use checksums
e0d34fa8df66f0474e8219056c13ec252a29ecd1817191447a262bf888555c64
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.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL rupdf-0.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.3 MB
Tags CPython 3.13 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
597f0f922a42b4ac50623ef867321fac03fac8c1d87f66b1a410fa056ef2ebd2
BLAKE2b-256 checksum
How to use checksums
7c99d8a2a8b5a3de1720c2f289947a38773941826663639ceb48422d01a8a4de
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.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL rupdf-0.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.2 MB
Tags CPython 3.13 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
57d679dd0620220ccbb0e603f7cfec6d300a45a6fffc45a9f8d85064514d7309
BLAKE2b-256 checksum
How to use checksums
79615c218dd4efa03a0e3321671afccc09a0ad7a73ac0c26a370c995722ebc5b
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.1-cp313-cp313-macosx_11_0_arm64.whl

Download URL rupdf-0.4.1-cp313-cp313-macosx_11_0_arm64.whl
Size 1.1 MB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
14389a8a98b27811da54e16012f7b63ba1703b70e2583f7c63b65d5ffecac903
BLAKE2b-256 checksum
How to use checksums
573b7c38bff18f0202ec70f0dd0d9edae88ffa63c4c3679021419a0fa28d4f71
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.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL rupdf-0.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.3 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
54218d4bc6f2f605fc0f4435bde343772dce4a44969af7d685e284543b7f2744
BLAKE2b-256 checksum
How to use checksums
57f18dd7bf3eed2e0132615226d82afec85dd841628ed911e6b8bf140cec72ba
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.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL rupdf-0.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.2 MB
Tags CPython 3.12 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
910c9f77dec1789f1a269d0a7a7467d314ce927297fee73789988a9b52921341
BLAKE2b-256 checksum
How to use checksums
0d7d2e6310f5b38aa0436b1268c82b0c3d8941a35cd5ee244a896583e0235ec7
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.1-cp312-cp312-macosx_11_0_arm64.whl

Download URL rupdf-0.4.1-cp312-cp312-macosx_11_0_arm64.whl
Size 1.1 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
1daabc3e0d84c33d29ee5cb585141e22ece7dc0a747fc6888ef87d5e8aa5b148
BLAKE2b-256 checksum
How to use checksums
c4e3fa78cf5a64f348f7f00bf47f6228d735b7b5582fd0f2a48f4a77ec02d044
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.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL rupdf-0.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.3 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
db839167849e3cebbb3ba91789132ba5b67a726a8e77133c7bc7225df75cda12
BLAKE2b-256 checksum
How to use checksums
385921c028c7fadbb2e8a46927b6780e8e9a5bb32fa6146f87fec95b87f5841a
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.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL rupdf-0.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.2 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
5af3bdf4d03c8f9682c201996dce85af22ce65ef4a31cd6523cee266499f7fd3
BLAKE2b-256 checksum
How to use checksums
7ee43b146a4541b5707b603008927ff6d83973ec24bf7a1e1f24f443968a98ce
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.1-cp311-cp311-macosx_11_0_arm64.whl

Download URL rupdf-0.4.1-cp311-cp311-macosx_11_0_arm64.whl
Size 1.1 MB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
df46975f9412bee767e9566d285f74584a9d40f65b82a2393644faa4b79cf1bb
BLAKE2b-256 checksum
How to use checksums
796cc5d2ac6274fd04a827a2214ec12e1826e47ac7341ca481860eb74a0ff392
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.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.

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