QPDF
| Language | C++ (also a C API) |
|---|---|
| License | Apache-2.0 |
| Repository | github.com/qpdf/qpdf |
| Documentation | qpdf.readthedocs.io |
| Depends on | zlib, libjpeg; OpenSSL or GnuTLS (optional, a native crypto provider is built in) |
| Read / write | yes / 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#
# Debian / Ubuntu
sudo apt install libqpdf-dev qpdf
# Fedora
sudo dnf install qpdf-devel qpdf
# macOS
brew install qpdf
# Windows (vcpkg)
vcpkg install qpdf
find_package(qpdf REQUIRED)
target_link_libraries(myapp PRIVATE qpdf::libqpdf)
Basic usage: merge PDF files#
#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#
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#
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#
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#
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#
qpdf --check in.pdfreports structural problems;qpdf in.pdf out.pdfrewrites a damaged file with a rebuilt xref.w.setObjectStreamMode(qpdf_o_generate)writes object streams and an xref stream for smaller files;w.setLinearization(true)makes a fast-web-view file.qpdf --json in.pdfdumps the whole object structure as JSON, and--json-input/--update-from-jsonapply edits from JSON.- The
QPDFJobclass runs any command line from C++ code:QPDFJob j; j.initializeFromArgv(argv); j.run();.