All formats

DjVuLibre

LanguageC++ with a C API (ddjvuapi.h)
LicenseGPL-2.0-or-later
Repositorysourceforge.net/projects/djvu (GitHub mirror: barak/djvulibre)
Documentationcomments in ddjvuapi.h, tool man pages
Depends onoptional libjpeg and libtiff
Read / writeyes / yes (writing through the C++ classes and the command-line tools)

DjVuLibre is the open-source DjVu implementation that came from the original AT&T code. Almost every DjVu viewer uses it: DjView, WinDjView, Okular, Evince, Zathura and SumatraPDF. It decodes every kind of DjVu file, including indirect documents and old formats.

The library has a C API, ddjvuapi, built for viewers: it decodes in background threads and reports progress through a message queue, so you must process messages in a loop until a job finishes. Text, annotations and the outline come back as Lisp-like s-expressions (miniexp_t). Encoding (IW44, JB2) is only in the C++ classes, without a stable API; most programs run the c44, cjb2, djvm and djvused tools instead.

Installation#

Shell
# Debian / Ubuntu
sudo apt install libdjvulibre-dev djvulibre-bin
# Fedora
sudo dnf install djvulibre-devel djvulibre
# macOS
brew install djvulibre

On Windows, use MSYS2 (pacman -S mingw-w64-x86_64-djvulibre) or build from source. Compile with pkg-config:

Shell
cc app.c $(pkg-config --cflags --libs ddjvuapi) -o app

Basic usage: open a document and render a page#

The program opens a document, waits for the header, renders page 1 at its native resolution as 24-bit RGB and writes a PPM file:

C
#include <libdjvu/ddjvuapi.h>
#include <stdio.h>
#include <stdlib.h>

/* Messages must be processed, or decoding never finishes. */
static void handle_messages(ddjvu_context_t *ctx, int wait) {
    const ddjvu_message_t *msg;
    if (wait)
        ddjvu_message_wait(ctx);
    while ((msg = ddjvu_message_peek(ctx))) {
        if (msg->m_any.tag == DDJVU_ERROR)
            fprintf(stderr, "djvu: %s\n", msg->m_error.message);
        ddjvu_message_pop(ctx);
    }
}

int main(void) {
    ddjvu_context_t *ctx = ddjvu_context_create("example");
    ddjvu_document_t *doc = ddjvu_document_create_by_filename_utf8(ctx, "book.djvu", 1);
    if (!doc) {
        fprintf(stderr, "cannot open file\n");
        return 1;
    }
    while (!ddjvu_document_decoding_done(doc))
        handle_messages(ctx, 1);
    if (ddjvu_document_decoding_error(doc)) {
        fprintf(stderr, "cannot decode document\n");
        return 1;
    }
    printf("%d pages\n", ddjvu_document_get_pagenum(doc));

    ddjvu_page_t *page = ddjvu_page_create_by_pageno(doc, 0);  /* pages are 0-based */
    while (!ddjvu_page_decoding_done(page))
        handle_messages(ctx, 1);
    if (ddjvu_page_decoding_error(page)) {
        fprintf(stderr, "cannot decode page\n");
        return 1;
    }
    int w = ddjvu_page_get_width(page);
    int h = ddjvu_page_get_height(page);
    printf("%d x %d at %d dpi\n", w, h, ddjvu_page_get_resolution(page));

    ddjvu_format_t *fmt = ddjvu_format_create(DDJVU_FORMAT_RGB24, 0, NULL);
    ddjvu_format_set_row_order(fmt, 1);  /* top row first */
    ddjvu_rect_t rect = {0, 0, (unsigned)w, (unsigned)h};
    unsigned long stride = (unsigned long)w * 3;
    char *pixels = malloc(stride * h);
    if (pixels && ddjvu_page_render(page, DDJVU_RENDER_COLOR, &rect, &rect, fmt, stride, pixels)) {
        FILE *f = fopen("page1.ppm", "wb");
        fprintf(f, "P6\n%d %d\n255\n", w, h);
        fwrite(pixels, 1, stride * h, f);
        fclose(f);
    }
    free(pixels);
    ddjvu_format_release(fmt);
    ddjvu_page_release(page);
    ddjvu_document_release(doc);
    ddjvu_context_release(ctx);
    return 0;
}

How to render a page at a lower resolution#

ddjvu_page_render scales the page to the size of pagerect. To render at 100 dpi, scale the native size:

C
int dpi = ddjvu_page_get_resolution(page);
unsigned tw = (unsigned)(ddjvu_page_get_width(page) * 100 / dpi);
unsigned th = (unsigned)(ddjvu_page_get_height(page) * 100 / dpi);
ddjvu_rect_t full = {0, 0, tw, th};
/* render all of it; use a smaller renderrect to get a tile */
ddjvu_page_render(page, DDJVU_RENDER_COLOR, &full, &full, fmt, tw * 3, pixels);

How to get page size without decoding the page#

ddjvu_document_get_pageinfo reads only the INFO chunk:

C
ddjvu_pageinfo_t info;
ddjvu_status_t r;
while ((r = ddjvu_document_get_pageinfo(doc, 0, &info)) < DDJVU_JOB_OK)
    handle_messages(ctx, 1);
if (r == DDJVU_JOB_OK)
    printf("%d x %d, %d dpi, rotation %d\n", info.width, info.height, info.dpi, info.rotation);

How to extract text from a DjVu page#

The text layer comes back as an s-expression. With detail level "page" it has the form (page x0 y0 x1 y1 "text"):

C
#include <libdjvu/miniexp.h>

miniexp_t r;
while ((r = ddjvu_document_get_pagetext(doc, 0, "page")) == miniexp_dummy)
    handle_messages(ctx, 1);
if (miniexp_consp(r)) {
    miniexp_t s = miniexp_nth(5, r);
    if (miniexp_stringp(s))
        printf("%s\n", miniexp_to_str(s));  /* UTF-8 */
}
ddjvu_miniexp_release(doc, r);

Pass "word" or "line" instead of "page" to get nested zones with coordinates for every word or line. A page without OCR returns miniexp_nil.

How to read the table of contents#

C
miniexp_t outline;
while ((outline = ddjvu_document_get_outline(doc)) == miniexp_dummy)
    handle_messages(ctx, 1);
/* (bookmarks ("Chapter 1" "#5" ...) ...) or miniexp_nil */
miniexp_pprint(outline, 72);
ddjvu_miniexp_release(doc, outline);

How to save some pages to a new file#

ddjvu_document_save writes a bundled document. The -pages option uses the same syntax as the ddjvu tool:

C
FILE *out = fopen("part.djvu", "wb");
const char *opts[] = {"-pages=1-10"};
ddjvu_job_t *job = ddjvu_document_save(doc, out, 1, opts);
while (!ddjvu_job_done(job))
    handle_messages(ctx, 1);
ddjvu_job_release(job);
fclose(out);

Tips#