MuPDF
| Language | C (C++ and Java bindings included; Python via PyMuPDF) |
|---|---|
| License | AGPL-3.0 or commercial license from Artifex |
| Repository | github.com/ArtifexSoftware/mupdf |
| Documentation | mupdf.readthedocs.io |
| Depends on | bundled third-party libraries (zlib, freetype, harfbuzz, libjpeg, openjpeg, jbig2dec, lcms2, gumbo) |
| Read / write | yes / yes: render, extract text and images, edit, merge, encrypt, annotations, forms |
MuPDF is a small, fast PDF, XPS, EPUB, MOBI, FB2, CBZ and image renderer from Artifex, the makers of Ghostscript. It’s the engine of SumatraPDF, and PyMuPDF and go-fitz wrap it. It renders accurately, extracts text with positions (“structured text”), and its pdf_* API edits PDFs at the object level: merge pages, add annotations, fill forms, redact, encrypt, clean and compress.
The license is the main limitation: MuPDF is AGPL-3.0. If you distribute a program that uses it, or run it in a network service, you must release your source code under the AGPL, or buy a commercial license.
The library has two layers: fz_* functions (the “fitz” API) work on any document type, pdf_* functions are PDF-specific. Errors are reported with fz_try / fz_catch macros that use setjmp.
Installation#
# Debian / Ubuntu
sudo apt install libmupdf-dev mupdf-tools
# macOS
brew install mupdf
# from source: builds the library and the bundled dependencies
git clone --recursive https://github.com/ArtifexSoftware/mupdf.git
cd mupdf && make HAVE_X11=no HAVE_GLUT=no prefix=/usr/local install
The mutool command line covers many tasks without code: mutool draw -o page%d.png -r 150 in.pdf, mutool convert -F text -o out.txt in.pdf, mutool merge -o out.pdf a.pdf b.pdf, mutool clean -e aes-256 -U user -O owner in.pdf out.pdf.
Basic usage: render a page to PNG#
#include <mupdf/fitz.h>
#include <stdio.h>
int main(int argc, char **argv) {
const char *path = argc > 1 ? argv[1] : "in.pdf";
fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
if (!ctx) return 1;
fz_document *doc = NULL;
fz_pixmap *pix = NULL;
int ret = 0;
fz_try(ctx) {
fz_register_document_handlers(ctx);
doc = fz_open_document(ctx, path);
printf("%d pages\n", fz_count_pages(ctx, doc));
// 150 dpi: PDF units are 1/72 inch
fz_matrix ctm = fz_scale(150.0f / 72, 150.0f / 72);
pix = fz_new_pixmap_from_page_number(ctx, doc, 0, ctm, fz_device_rgb(ctx), 0);
fz_save_pixmap_as_png(ctx, pix, "page1.png");
}
fz_always(ctx) {
fz_drop_pixmap(ctx, pix);
fz_drop_document(ctx, doc);
}
fz_catch(ctx) {
fprintf(stderr, "error: %s\n", fz_caught_message(ctx));
ret = 1;
}
fz_drop_context(ctx);
return ret;
}
Link with -lmupdf -lmupdf-third -lm (or pkg-config --libs mupdf).
How to extract text from a PDF#
fz_new_buffer_from_page_number returns the text of a page as UTF-8 in reading order of the content:
fz_buffer *buf = NULL;
fz_var(buf);
fz_try(ctx) {
int n = fz_count_pages(ctx, doc);
for (int i = 0; i < n; i++) {
buf = fz_new_buffer_from_page_number(ctx, doc, i, NULL);
printf("--- page %d ---\n%s\n", i + 1, fz_string_from_buffer(ctx, buf));
fz_drop_buffer(ctx, buf);
buf = NULL;
}
}
fz_catch(ctx) {
fz_drop_buffer(ctx, buf);
fprintf(stderr, "error: %s\n", fz_caught_message(ctx));
}
For positions of blocks, lines and characters, build an fz_stext_page with fz_new_stext_page_from_page_number and walk its blocks.
How to read PDF metadata#
char buf[256];
if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_TITLE, buf, sizeof buf) > 0)
printf("Title: %s\n", buf);
if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_AUTHOR, buf, sizeof buf) > 0)
printf("Author: %s\n", buf);
if (fz_lookup_metadata(ctx, doc, FZ_META_FORMAT, buf, sizeof buf) > 0)
printf("Format: %s\n", buf); // e.g. "PDF 1.7"
How to open a password-protected PDF#
doc = fz_open_document(ctx, "secret.pdf");
if (fz_needs_password(ctx, doc) && !fz_authenticate_password(ctx, doc, "password")) {
fprintf(stderr, "wrong password\n");
}
How to merge PDF files#
Copy pages between documents with pdf_graft_page (page -1 appends at the end):
#include <mupdf/fitz.h>
#include <mupdf/pdf.h>
void merge(fz_context *ctx, const char *out, const char **inputs, int count) {
pdf_document *dst = pdf_create_document(ctx);
for (int i = 0; i < count; i++) {
pdf_document *src = pdf_open_document(ctx, inputs[i]);
int n = pdf_count_pages(ctx, src);
for (int p = 0; p < n; p++)
pdf_graft_page(ctx, dst, -1, src, p);
pdf_drop_document(ctx, src);
}
pdf_write_options opts = pdf_default_write_options;
opts.do_compress = 1;
opts.do_garbage = 1; // remove unused objects
pdf_save_document(ctx, dst, out, &opts);
pdf_drop_document(ctx, dst);
}
Call it inside fz_try so errors are caught.
How to rotate pages and encrypt a PDF#
pdf_document *doc = pdf_open_document(ctx, "in.pdf");
int n = pdf_count_pages(ctx, doc);
for (int i = 0; i < n; i++) {
pdf_obj *page = pdf_lookup_page_obj(ctx, doc, i);
pdf_dict_put_int(ctx, page, PDF_NAME(Rotate), 90);
}
pdf_write_options opts = pdf_default_write_options;
opts.do_encrypt = PDF_ENCRYPT_AES_256;
fz_strlcpy(opts.upwd_utf8, "user", sizeof opts.upwd_utf8);
fz_strlcpy(opts.opwd_utf8, "owner", sizeof opts.opwd_utf8);
opts.permissions = ~0; // all permissions
pdf_save_document(ctx, doc, "out.pdf", &opts);
pdf_drop_document(ctx, doc);
Use opts.do_encrypt = PDF_ENCRYPT_NONE to save a decrypted copy of a file opened with its password.
Tips#
- A
fz_contextisn’t thread-safe. For multi-threaded rendering pass locking functions tofz_new_contextand clone the context per thread withfz_clone_context. fz_set_aa_level(ctx, 0)turns off anti-aliasing for faster rendering of large batches.- The C++ bindings (
mupdf::FzDocument, generated from the C API) throw exceptions instead of usingfz_try.