djvudec
| Language | C (usable from C++) |
|---|---|
| License | MIT |
| Repository | github.com/kjk/djvudec |
| Documentation | src/djvu.h (API reference in comments) |
| Depends on | nothing |
| Read / write | yes / 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.
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.
#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:
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:
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:
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)#
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#
djvu_ctx_set_bgr(ctx, 1)makes color output B,G,R, ready for a Windows DIB, with no extra pass.djvu_page_render_info+djvu_page_render_intorender straight into your own buffer.djvu_page_get_typetells bitonal, photo and compound pages apart, so you can render bitonal pages as grayscale.- To render pages from several threads, pass lock and unlock callbacks to
djvu_ctx_new. To cancel a slow render, use adjvu_aborttoken withdjvu_page_render_abortable.