All formats

djvudec

LanguageC (usable from C++)
LicenseMIT
Repositorygithub.com/kjk/djvudec
Documentationsrc/djvu.h (API reference in comments)
Depends onnothing
Read / writeyes / no (decoder only)

djvudec is a DjVu decoder in plain C: render pages to grayscale or RGB pixels, extract the hidden text layer (plain or with word and line bounding boxes), read the outline (bookmarks) and page hyperlinks. It’s a port of the C# DjvuNet decoder, tested bit for bit against DjVuLibre on thousands of files. SumatraPDF uses it.

Compared with DjVuLibre: much less code (about 220 KiB of machine code against 1.2 MiB, measured by the project with clang -O3), C instead of C++, a smaller API, and the MIT license instead of GPL. It doesn’t encode DjVu files and doesn’t do incremental loading: you pass the whole file as a memory buffer.

Installation#

Copy dist/djvu.h and dist/djvu.c from the repository into your project and compile djvu.c with the rest of your code. The dist/ files are the whole library amalgamated into one translation unit, like SQLite.

Shell
git clone https://github.com/kjk/djvudec
cp djvudec/dist/djvu.h djvudec/dist/djvu.c myproject/

Basic usage: render a page#

The file must stay in memory as long as the document is open. Errors go to an optional callback.

C
#include "djvu.h"
#include <stdio.h>
#include <stdlib.h>

static void on_error(void *user, djvu_severity sev, const char *msg) {
    if (sev >= DJVU_SEVERITY_WARNING) fprintf(stderr, "djvu: %s\n", msg);
}

static uint8_t *read_file(const char *path, size_t *len) {
    FILE *f = fopen(path, "rb");
    if (!f) return NULL;
    fseek(f, 0, SEEK_END);
    *len = (size_t)ftell(f);
    fseek(f, 0, SEEK_SET);
    uint8_t *d = malloc(*len);
    if (d && fread(d, 1, *len, f) != *len) { free(d); d = NULL; }
    fclose(f);
    return d;
}

int main(void) {
    size_t len;
    uint8_t *data = read_file("book.djvu", &len);
    if (!data) return 1;

    djvu_init();  /* once per process, before using threads */
    djvu_ctx *ctx = djvu_ctx_new(NULL, NULL, NULL, NULL, on_error, NULL);
    djvu_doc *doc = djvu_doc_open(ctx, data, len);
    if (!doc) return 1;

    printf("%d pages\n", djvu_doc_page_count(doc));
    djvu_page_info info;
    if (djvu_doc_page_info(doc, 0, &info) == 0)
        printf("page 1: %d x %d at %d dpi\n", info.width, info.height, info.dpi);

    djvu_image *img = djvu_page_render(doc, 0, 1);  /* page 0, full resolution */
    if (img) {
        /* img->data: top-down rows of img->stride bytes;
           DJVU_FORMAT_GRAY8 = 1 byte/pixel, DJVU_FORMAT_RGB24 = 3 bytes/pixel */
        printf("rendered %d x %d, format %d\n", img->width, img->height, img->format);
        djvu_image_destroy(ctx, img);
    }

    djvu_doc_close(doc);
    djvu_ctx_free(ctx);
    free(data);
    return 0;
}

How to extract text from a DjVu file#

DjVu files from OCR have a hidden text layer. djvu_page_text returns it as UTF-8, or NULL if the page has none:

C
int n = djvu_doc_page_count(doc);
for (int i = 0; i < n; i++) {
    char *text = djvu_page_text(doc, i);
    if (text) {
        printf("--- page %d\n%s\n", i + 1, text);
        djvu_text_destroy(ctx, text);
    }
}

How to get word positions#

djvu_page_text_get_zones returns the text as a tree (page, column, region, paragraph, line, word, character) with bounding boxes in page pixels, measured from the top-left:

C
static void print_words(const djvu_text_zone *z) {
    if (z->type == DJVU_ZONE_WORD) {
        printf("%-20s x=%d y=%d w=%d h=%d\n", z->text, z->x, z->y, z->w, z->h);
        return;
    }
    for (int i = 0; i < z->nchildren; i++) print_words(&z->children[i]);
}

djvu_page_text_zones *zones = djvu_page_text_get_zones(doc, 0);
if (zones && zones->root) print_words(zones->root);
djvu_text_zones_destroy(ctx, zones);

How to make a thumbnail#

The subsample argument reduces resolution by an integer factor during decoding, which is much faster than rendering at full size and scaling down:

C
djvu_page_info info;
djvu_doc_page_info(doc, 0, &info);
int factor = info.width / 200;            /* about 200 pixels wide */
if (factor < 1) factor = 1;
djvu_image *thumb = djvu_page_render(doc, 0, factor);

How to read the outline (bookmarks)#

C
static void print_outline(const djvu_outline_item *it, int depth) {
    for (int i = 0; i < it->nchildren; i++) {
        const djvu_outline_item *c = &it->children[i];
        printf("%*s%s -> page %d\n", depth * 2, "", c->title, c->page_no + 1);
        print_outline(c, depth + 1);
    }
}

djvu_outline_item *root = djvu_doc_outline(doc);  /* NULL if there's no outline */
if (root) {
    print_outline(root, 0);
    djvu_outline_destroy(ctx, root);
}

Tips#