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

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.4.1.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.4.1-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.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.2 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ ARM64

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

Uploaded CPython 3.13macOS 11.0+ ARM64

rupdf-0.4.1-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.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.2 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64

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

Uploaded CPython 3.12macOS 11.0+ ARM64

rupdf-0.4.1-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.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.2 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ ARM64

rupdf-0.4.1-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.4.1.tar.gz.

File metadata

  • Download URL: rupdf-0.4.1.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.4.1.tar.gz
Algorithm Hash digest
SHA256 f779aee1c66c3bfbcacfb01bba4b1b4109e1cec3d37433cbd584e571e973e80b
MD5 1b4926b4b5c97fd62c3aaaedc88c61e1
BLAKE2b-256 e0d34fa8df66f0474e8219056c13ec252a29ecd1817191447a262bf888555c64

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rupdf-0.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 597f0f922a42b4ac50623ef867321fac03fac8c1d87f66b1a410fa056ef2ebd2
MD5 cc64459d246a650698293fe845519791
BLAKE2b-256 7c99d8a2a8b5a3de1720c2f289947a38773941826663639ceb48422d01a8a4de

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rupdf-0.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 57d679dd0620220ccbb0e603f7cfec6d300a45a6fffc45a9f8d85064514d7309
MD5 7c0e7ec469837464aba7f26d0d0fb74f
BLAKE2b-256 79615c218dd4efa03a0e3321671afccc09a0ad7a73ac0c26a370c995722ebc5b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rupdf-0.4.1-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 14389a8a98b27811da54e16012f7b63ba1703b70e2583f7c63b65d5ffecac903
MD5 0b1e04446ba4f7ac57f385d71ec4a4c0
BLAKE2b-256 573b7c38bff18f0202ec70f0dd0d9edae88ffa63c4c3679021419a0fa28d4f71

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rupdf-0.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 54218d4bc6f2f605fc0f4435bde343772dce4a44969af7d685e284543b7f2744
MD5 bd7c10a5ed28797b7154f8273f8eceb7
BLAKE2b-256 57f18dd7bf3eed2e0132615226d82afec85dd841628ed911e6b8bf140cec72ba

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rupdf-0.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 910c9f77dec1789f1a269d0a7a7467d314ce927297fee73789988a9b52921341
MD5 3c60ae35e828a8de190354557b0c5bfd
BLAKE2b-256 0d7d2e6310f5b38aa0436b1268c82b0c3d8941a35cd5ee244a896583e0235ec7

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rupdf-0.4.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1daabc3e0d84c33d29ee5cb585141e22ece7dc0a747fc6888ef87d5e8aa5b148
MD5 81aa621a28c8ca4ce6ec2eb290dd856c
BLAKE2b-256 c4e3fa78cf5a64f348f7f00bf47f6228d735b7b5582fd0f2a48f4a77ec02d044

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rupdf-0.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 db839167849e3cebbb3ba91789132ba5b67a726a8e77133c7bc7225df75cda12
MD5 da67318e5a480b02a17e935e17e6838a
BLAKE2b-256 385921c028c7fadbb2e8a46927b6780e8e9a5bb32fa6146f87fec95b87f5841a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rupdf-0.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 5af3bdf4d03c8f9682c201996dce85af22ce65ef4a31cd6523cee266499f7fd3
MD5 606f6659f0790fe48aef0f662de5a788
BLAKE2b-256 7ee43b146a4541b5707b603008927ff6d83973ec24bf7a1e1f24f443968a98ce

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for rupdf-0.4.1-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 df46975f9412bee767e9566d285f74584a9d40f65b82a2393644faa4b79cf1bb
MD5 f28300ea3e074c81ad7df23ef9c30aae
BLAKE2b-256 796cc5d2ac6274fd04a827a2214ec12e1826e47ac7341ca481860eb74a0ff392

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.4.1 This release

10 files

0.4.0

10 files

0.3.0

10 files

0.2.2

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