All formats

MuPDF

LanguageC (C++ and Java bindings included; Python via PyMuPDF)
LicenseAGPL-3.0 or commercial license from Artifex
Repositorygithub.com/ArtifexSoftware/mupdf
Documentationmupdf.readthedocs.io
Depends onbundled third-party libraries (zlib, freetype, harfbuzz, libjpeg, openjpeg, jbig2dec, lcms2, gumbo)
Read / writeyes / 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#

Shell
# 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#

C
#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:

C
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#

C
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#

C
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):

C
#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#

C
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#