All formats

QPDF

LanguageC++ (also a C API)
LicenseApache-2.0
Repositorygithub.com/qpdf/qpdf
Documentationqpdf.readthedocs.io
Depends onzlib, libjpeg; OpenSSL or GnuTLS (optional, a native crypto provider is built in)
Read / writeyes / yes: object-level reading and writing, pages, encryption, linearization, repair; no rendering or text extraction

QPDF reads a PDF into an object model and writes it back out: it merges and splits files, rotates pages, encrypts and decrypts, linearizes for fast web view, converts to and from object and xref streams, and repairs damaged files. It doesn’t render pages or lay out text. The qpdf command line exposes nearly everything, and its --qdf mode writes a normalized, uncompressed file that’s easy to read in a text editor. Python’s pikepdf is a binding to QPDF.

QPDF handles every encryption revision, including AES-256, and preserves content it doesn’t understand. Use it with a renderer (MuPDF, PDFium, Poppler) when you also need images or text.

Installation#

Shell
# Debian / Ubuntu
sudo apt install libqpdf-dev qpdf
# Fedora
sudo dnf install qpdf-devel qpdf
# macOS
brew install qpdf
# Windows (vcpkg)
vcpkg install qpdf
cmake
find_package(qpdf REQUIRED)
target_link_libraries(myapp PRIVATE qpdf::libqpdf)

Basic usage: merge PDF files#

C++
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFPageDocumentHelper.hh>
#include <qpdf/QPDFWriter.hh>
#include <iostream>
#include <vector>

int main(int argc, char *argv[]) {
    if (argc < 3) {
        std::cerr << "usage: merge out.pdf in1.pdf in2.pdf ...\n";
        return 2;
    }
    try {
        QPDF out;
        out.emptyPDF();
        QPDFPageDocumentHelper out_pages(out);

        // inputs must stay alive until the output is written
        std::vector<std::unique_ptr<QPDF>> inputs;
        for (int i = 2; i < argc; i++) {
            auto in = std::make_unique<QPDF>();
            in->processFile(argv[i]);
            for (auto &page : QPDFPageDocumentHelper(*in).getAllPages()) {
                out_pages.addPage(page, false /*append*/);
            }
            inputs.push_back(std::move(in));
        }
        QPDFWriter w(out, argv[1]);
        w.write();
    } catch (std::exception &e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    }
    return 0;
}

The command line does the same with qpdf --empty --pages a.pdf b.pdf -- merged.pdf.

How to split a PDF into pages#

C++
QPDF in;
in.processFile("in.pdf");
int n = 0;
for (auto &page : QPDFPageDocumentHelper(in).getAllPages()) {
    QPDF out;
    out.emptyPDF();
    QPDFPageDocumentHelper(out).addPage(page, false);
    std::string name = "page-" + std::to_string(++n) + ".pdf";
    QPDFWriter w(out, name.c_str());
    w.write();
}

Command line: qpdf --split-pages in.pdf page-%d.pdf, or extract a range with qpdf in.pdf --pages . 1-5 -- out.pdf.

How to rotate pages#

C++
QPDF pdf;
pdf.processFile("in.pdf");
for (auto &page : QPDFPageDocumentHelper(pdf).getAllPages()) {
    page.rotatePage(90, true /*relative to current rotation*/);
}
QPDFWriter w(pdf, "rotated.pdf");
w.write();

Command line: qpdf in.pdf --rotate=+90:1-3 out.pdf.

How to encrypt and decrypt a PDF#

C++
QPDF pdf;
pdf.processFile("in.pdf");
QPDFWriter w(pdf, "encrypted.pdf");
w.setR6EncryptionParameters(
    "user", "owner",
    true,   // allow accessibility
    true,   // allow text and image extraction
    false,  // allow assembling (insert, rotate, delete pages)
    false,  // allow annotations and forms
    true,   // allow filling forms
    false,  // allow other modifications
    qpdf_r3p_full,  // printing: full, low or none
    true);  // encrypt metadata
w.write();

// decrypt: open with the password and write without encryption
QPDF secret;
secret.processFile("encrypted.pdf", "owner");
QPDFWriter plain(secret, "decrypted.pdf");
plain.setPreserveEncryption(false);
plain.write();

Command line: qpdf --encrypt user owner 256 -- in.pdf out.pdf and qpdf --password=owner --decrypt in.pdf out.pdf.

How to read PDF metadata#

C++
QPDF pdf;
pdf.processFile("in.pdf");
std::cout << "PDF " << pdf.getPDFVersion() << "\n";
QPDFObjectHandle info = pdf.getTrailer().getKey("/Info");
if (info.isDictionary()) {
    for (auto const &key : {"/Title", "/Author", "/Producer"}) {
        QPDFObjectHandle v = info.getKey(key);
        if (v.isString()) std::cout << key << ": " << v.getUTF8Value() << "\n";
    }
}
std::cout << QPDFPageDocumentHelper(pdf).getAllPages().size() << " pages\n";

Tips#