DjVuLibre
| Language | C++ with a C API (ddjvuapi.h) |
|---|---|
| License | GPL-2.0-or-later |
| Repository | sourceforge.net/projects/djvu (GitHub mirror: barak/djvulibre) |
| Documentation | comments in ddjvuapi.h, tool man pages |
| Depends on | optional libjpeg and libtiff |
| Read / write | yes / 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#
# 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:
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:
#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:
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:
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"):
#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#
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:
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#
- Every
get_*call that returnsminiexp_dummyor a status belowDDJVU_JOB_OKmeans “not ready yet”: process messages and call it again. ddjvu_page_get_initial_rotationreturns the rotation stored inINFO;ddjvu_page_set_rotationchanges how the page renders.- For a quick look at a file’s chunks, run
djvudump file.djvu. - The GPL applies to programs that link DjVuLibre. Running the command-line tools as separate processes keeps your program’s license independent.