mrkeyoor.com_
Thu 06 Aug 07:43 UTC
PyPIUtilsupdated 06 Aug 2026

pypdfium2

pypdfium2 is a ctypes binding to PDFium, the C++ PDF engine that Chrome uses to display PDFs. The wheels ship a prebuilt pdfium shared library, so pip install gives you a working PDF renderer with no compiler and no system packages. There are two layers. The helper layer is normal Python: PdfDocument, PdfPage, PdfTextPage, PdfBitmap, with methods for rendering pages to images, pulling out text, reading the table of contents, adding and removing pages, and saving. Underneath, pypdfium2.raw exposes every PDFium C function directly through ctypes for the parts the helpers do not cover yet. The reason people reach for it over the alternatives is licensing: pypdfium2 is Apache-2.0 or BSD-3-Clause and PDFium is BSD-style, so you get Chrome-grade rendering speed without the AGPL obligations that come with PyMuPDF.

Verdict

The best answer when you need Chrome-quality PDF rendering under a permissive licence, and the wheel coverage means it usually just installs. Pair it with pikepdf or pypdf, because PDFium gives you pixels and characters but no way to edit the document structure underneath.

API stability3/5Helper APIs are versioned and changes are documented, but majors land often and they do break things: 5.10.1 removed PdfFormEnv.pdf, turned PdfFormEnv.close() into a deprecated no-op, and moved form lifetime onto PdfDocument. The setup layer is stated outright to have no stability promise at all
Docs4/5A Sphinx API reference on Read the Docs, a platform support table, and a README that is unusually honest about limitations including thread safety and object lifetime. The gap is the raw API: PDFium itself publishes no rendered docs, so anything past the helpers means reading C header comments
Maintenance4/5Pushed 2026-08-05 with only 2 open issues, releases roughly monthly to track pdfium-binaries, and a detailed changelog. The risk is concentration: it is essentially one maintainer with a stated policy that issues may be closed without feedback
Ecosystem4/5Sits underneath langchain, docling, pdfplumber, doctr, dify, and nv-ingest, which is most of why the download count is what it is. Direct users are fewer, and the wider Python PDF space is still split between PyMuPDF, pikepdf, pypdf, and pdfminer.six

Use it if

  • You need to rasterize PDF pages fast, for thumbnails, OCR preprocessing, or feeding page images to a vision model, and PDFium's renderer is the same one that draws PDFs in Chrome
  • You are shipping commercial or closed-source software and cannot take on PyMuPDF's AGPL terms, which is the single most common reason projects land here
  • You want a wide platform matrix without building anything: prebuilt wheels cover manylinux and musllinux on x86_64, i686, aarch64, armv7l, ppc64le, s390x and riscv64, plus Windows x86, x64 and arm64, macOS 13 and newer, and Android
  • You need PDFium APIs the helpers do not wrap yet, since pypdfium2.raw exposes the full C surface and helper objects auto-convert to their raw handles when passed to it
Skip it if

Setup reality

python -m pip install -U pypdfium2 normally grabs a py3-none wheel with the pdfium binary bundled inside, which takes seconds and needs no toolchain. That is the happy path and it covers most machines. If your platform is not in the wheel matrix, pip falls back to the sdist and pypdfium2's own setup.py runs: it wants a C preprocessor, git, the pypdfium2-team fork of ctypesgen, and it will either bind against a system pdfium or try to build pdfium from source with gn and ninja, which is a heavy checkout. Note also that the setup code is explicitly excluded from the project's API stability promises and is driven by environment variables such as PDFIUM_PLATFORM. There are no mandatory runtime dependencies, but bitmap.to_pil() needs Pillow and bitmap.to_numpy() needs NumPy, and neither is installed for you. On the usage side, the two things that bite first are that PDFium is not thread-safe, and that render() takes a scale factor rather than a DPI, so 200 DPI is scale=200/72.

Patterns

Open a PDF and read basic factsopen-document

import pypdfium2 as pdfium

pdf = pdfium.PdfDocument('report.pdf')
print(len(pdf), 'pages')
print('PDF version', pdf.get_version())
print(pdf.get_metadata_dict(skip_empty=True))

page = pdf[0]
width, height = page.get_size()
pdf.close()

The constructor takes a path, a pathlib.Path, bytes, or a byte stream, plus password= for encrypted files. PdfDocument is also a context manager, which is the safer form because closing releases the underlying C handle deterministically.

Render one page to a PNG at a chosen DPIrender-page-to-image

import pypdfium2 as pdfium

DPI = 200
with pdfium.PdfDocument('report.pdf') as pdf:
    page = pdf[0]
    bitmap = page.render(scale=DPI / 72)
    image = bitmap.to_pil()
    image.save('page1.png')

scale is pixels per PDF canvas unit, not DPI, and one canvas unit is 1/72 inch, so divide your DPI by 72. to_pil() needs Pillow installed; pypdfium2 declares no runtime dependencies, so pip will not have pulled it in.

Rasterize a whole documentrender-all-pages

import pypdfium2 as pdfium

with pdfium.PdfDocument('report.pdf') as pdf:
    for i, page in enumerate(pdf):
        bitmap = page.render(scale=2, draw_annots=False, fill_color=(255, 255, 255, 255))
        bitmap.to_pil().save(f'page-{i:03d}.png')

Iterating the document yields PdfPage objects in order. Do not spread this loop over a ThreadPoolExecutor: PDFium is not thread-safe, so use multiprocessing with one document opened per worker process.

Pull the text out of a pageextract-text

import pypdfium2 as pdfium

with pdfium.PdfDocument('report.pdf') as pdf:
    page = pdf[0]
    textpage = page.get_textpage()
    all_text = textpage.get_text_bounded()
    first_50 = textpage.get_text_range(index=0, count=50)
    print(textpage.count_chars(), 'characters')

get_text_bounded() with no arguments covers the whole page; pass left, bottom, right, top in canvas units to restrict it to a region. Order follows the content stream, so a two-column layout comes back interleaved unless you extract each column by its own bounding box.

Find every occurrence of a phrase on a pagesearch-text

import pypdfium2 as pdfium

with pdfium.PdfDocument('report.pdf') as pdf:
    textpage = pdf[0].get_textpage()
    searcher = textpage.search('net revenue', match_case=False, match_whole_word=False)
    while (match := searcher.get_next()) is not None:
        index, count = match
        print(index, textpage.get_text_range(index, count))

get_next() returns a (char_index, char_count) tuple or None when there is nothing left, and the searcher keeps position between calls so you loop rather than re-search. get_prev() walks backwards from wherever you are.

Walk the table of contentsread-toc

import pypdfium2 as pdfium

with pdfium.PdfDocument('book.pdf') as pdf:
    for bookmark in pdf.get_toc(max_depth=15):
        dest = bookmark.get_dest()
        page_index = dest.get_index() if dest else None
        print('  ' * bookmark.level, bookmark.get_title(), '->', page_index)

get_toc() is a generator that flattens the tree, so use bookmark.level for indentation. get_dest() returns None for bookmarks that point at an action rather than a page, and get_index() can be None when the destination does not resolve.

Build a new document from scratchcreate-pdf

import pypdfium2 as pdfium

pdf = pdfium.PdfDocument.new()
A4_WIDTH, A4_HEIGHT = 595, 842
page = pdf.new_page(A4_WIDTH, A4_HEIGHT)
pdf.save('blank.pdf', version=17)
pdf.close()

Page dimensions are in canvas units, so A4 is 595 by 842, not millimetres. version=17 writes a PDF 1.7 header. There is no text drawing helper, so for generating text-heavy documents use ReportLab and keep pypdfium2 for rendering.

Place a JPEG on a pageinsert-image

import pypdfium2 as pdfium

pdf = pdfium.PdfDocument.new()
image = pdfium.PdfImage.new(pdf)
image.load_jpeg('photo.jpg')
width, height = image.get_px_size()

image.set_matrix(pdfium.PdfMatrix().scale(width, height))
page = pdf.new_page(width, height)
page.insert_obj(image)
page.gen_content()
pdf.save('photo.pdf')

A page object with no matrix renders at one canvas unit square, so the scale() call is what makes the image visible at all. gen_content() must be called after adding objects or the changes never reach the saved file.

Combine several PDFs into onemerge-documents

import pypdfium2 as pdfium

sources = [pdfium.PdfDocument(p) for p in ('a.pdf', 'b.pdf', 'c.pdf')]
merged = pdfium.PdfDocument.new()
for src in sources:
    merged.import_pages(src)
merged.save('merged.pdf')
merged.close()
for src in sources:
    src.close()

The source documents have to stay alive and open until after save(), because imported pages still reference their originals. Building the list up front rather than opening inside the loop is what keeps them from being garbage collected mid-merge. Pass pages=[0, 2, 4] to import a subset.

Drop pages and write the resultdelete-and-reorder-pages

import pypdfium2 as pdfium

with pdfium.PdfDocument('scan.pdf') as pdf:
    for index in sorted([1, 4, 7], reverse=True):
        del pdf[index]
    pdf.save('trimmed.pdf')

Deleting shifts every later index down, which is why the loop runs in reverse. There is no move-page API, so reordering means creating a new document and calling import_pages with the page order you want.

Get a zero-copy array for OpenCV or OCRbitmap-to-numpy

import pypdfium2 as pdfium
import pypdfium2.raw as pdfium_c

with pdfium.PdfDocument('scan.pdf') as pdf:
    bitmap = pdf[0].render(
        scale=300 / 72,
        grayscale=True,
        force_bitmap_format=pdfium_c.FPDFBitmap_BGRA,
        rev_byteorder=True,
    )
    array = bitmap.to_numpy()
    print(array.shape, array.dtype)

to_numpy() is a view over PDFium's buffer, not a copy, so the array is only valid while the bitmap object is alive. Forcing BGRA plus rev_byteorder avoids a format conversion and keeps rendering off PDFium's slower transparency path.

Call a PDFium function the helpers do not wrapraw-c-api

import ctypes
import pypdfium2 as pdfium
import pypdfium2.raw as pdfium_c

with pdfium.PdfDocument('locked.pdf') as pdf:
    flags = pdfium_c.FPDF_GetDocPermission(pdf)  # helper auto-casts to pdf.raw

    c_version = ctypes.c_int()
    ok = pdfium_c.FPDF_GetFileVersion(pdf, c_version)
    version = c_version.value if ok else None

Raw calls take positional arguments only, with no defaults and no keywords. Output parameters are ctypes objects you allocate and then read via .value, and any Python buffer you pass must stay referenced for as long as PDFium holds it or you get a crash instead of an error.

Alternatives

PackageRegistryPick it when
pymupdfPyPIYou want the richest feature set and the fastest text extraction in one package and the AGPL or a commercial licence is acceptable for your project
pikepdfPyPIYou need real access to PDF objects: editing dictionaries and streams, repairing damaged files, linearizing, or encrypting, none of which PDFium exposes
pypdfPyPIYou only need to split, merge, rotate, or read metadata and want a pure-Python dependency with no bundled binary
pdfplumberPyPIThe job is pulling structured text, words with coordinates, or tables out of a page rather than drawing it