#include "mupdf/fitz.h" #define DPI 72.0f typedef struct cbz_document_s cbz_document; typedef struct cbz_page_s cbz_page; static const char *cbz_ext_list[] = { ".jpg", ".jpeg", ".png", ".JPG", ".JPEG", ".PNG", NULL }; struct cbz_page_s { fz_page super; fz_image *image; }; struct cbz_document_s { fz_document super; fz_archive *zip; int page_count; const char **page; }; static inline int cbz_isdigit(int c) { return c >= '0' && c <= '9'; } static inline int cbz_toupper(int c) { if (c >= 'a' && c <= 'z') return c - 'a' + 'A'; return c; } static inline int cbz_strnatcmp(const char *a, const char *b) { int x, y; while (*a || *b) { if (cbz_isdigit(*a) && cbz_isdigit(*b)) { x = *a++ - '0'; while (cbz_isdigit(*a)) x = x * 10 + *a++ - '0'; y = *b++ - '0'; while (cbz_isdigit(*b)) y = y * 10 + *b++ - '0'; } else { x = cbz_toupper(*a++); y = cbz_toupper(*b++); } if (x < y) return -1; if (x > y) return 1; } return 0; } static int cbz_compare_page_names(const void *a, const void *b) { return cbz_strnatcmp(*(const char **)a, *(const char **)b); } static void cbz_create_page_list(fz_context *ctx, cbz_document *doc) { fz_archive *zip = doc->zip; int i, k, count; count = fz_count_archive_entries(ctx, zip); doc->page_count = 0; doc->page = fz_malloc_array(ctx, count, sizeof *doc->page); for (i = 0; i < count; i++) { for (k = 0; cbz_ext_list[k]; k++) { const char *name = fz_list_archive_entry(ctx, zip, i); if (strstr(name, cbz_ext_list[k])) { doc->page[doc->page_count++] = name; break; } } } qsort((char **)doc->page, doc->page_count, sizeof *doc->page, cbz_compare_page_names); } static void cbz_close_document(fz_context *ctx, cbz_document *doc) { fz_drop_archive(ctx, doc->zip); fz_free(ctx, (char **)doc->page); fz_free(ctx, doc); } static int cbz_count_pages(fz_context *ctx, cbz_document *doc) { return doc->page_count; } static fz_rect * cbz_bound_page(fz_context *ctx, cbz_page *page, fz_rect *bbox) { fz_image *image = page->image; int xres, yres; fz_image_get_sanitised_res(image, &xres, &yres); bbox->x0 = bbox->y0 = 0; bbox->x1 = image->w * DPI / xres; bbox->y1 = image->h * DPI / yres; return bbox; } static void cbz_run_page(fz_context *ctx, cbz_page *page, fz_device *dev, const fz_matrix *ctm, fz_cookie *cookie) { fz_matrix local_ctm = *ctm; fz_image *image = page->image; int xres, yres; float w, h; fz_image_get_sanitised_res(image, &xres, &yres); w = image->w * DPI / xres; h = image->h * DPI / yres; fz_pre_scale(&local_ctm, w, h); fz_fill_image(ctx, dev, image, &local_ctm, 1); } static void cbz_drop_page_imp(fz_context *ctx, cbz_page *page) { if (!page) return; fz_drop_image(ctx, page->image); } static cbz_page * cbz_load_page(fz_context *ctx, cbz_document *doc, int number) { unsigned char *data = NULL; cbz_page *page = NULL; fz_buffer *buf; if (number < 0 || number >= doc->page_count) return NULL; fz_var(data); fz_var(page); buf = fz_read_archive_entry(ctx, doc->zip, doc->page[number]); fz_try(ctx) { page = fz_new_page(ctx, sizeof *page); page->super.bound_page = (fz_page_bound_page_fn *)cbz_bound_page; page->super.run_page_contents = (fz_page_run_page_contents_fn *)cbz_run_page; page->super.drop_page_imp = (fz_page_drop_page_imp_fn *)cbz_drop_page_imp; page->image = fz_new_image_from_buffer(ctx, buf); } fz_always(ctx) { fz_drop_buffer(ctx, buf); } fz_catch(ctx) { fz_free(ctx, data); cbz_drop_page_imp(ctx, page); fz_rethrow(ctx); } return page; } static int cbz_meta(fz_context *ctx, cbz_document *doc, int key, void *ptr, int size) { switch (key) { case FZ_META_FORMAT_INFO: sprintf((char *)ptr, "CBZ"); return FZ_META_OK; default: return FZ_META_UNKNOWN_KEY; } } static cbz_document * cbz_open_document_with_stream(fz_context *ctx, fz_stream *file) { cbz_document *doc = fz_new_document(ctx, sizeof *doc); doc->super.close = (fz_document_close_fn *)cbz_close_document; doc->super.count_pages = (fz_document_count_pages_fn *)cbz_count_pages; doc->super.load_page = (fz_document_load_page_fn *)cbz_load_page; doc->super.meta = (fz_document_meta_fn *)cbz_meta; fz_try(ctx) { doc->zip = fz_open_archive_with_stream(ctx, file); cbz_create_page_list(ctx, doc); } fz_catch(ctx) { cbz_close_document(ctx, doc); fz_rethrow(ctx); } return doc; } static cbz_document * cbz_open_document(fz_context *ctx, const char *filename) { fz_stream *file; cbz_document *doc; file = fz_open_file(ctx, filename); fz_try(ctx) doc = cbz_open_document_with_stream(ctx, file); fz_always(ctx) fz_drop_stream(ctx, file); fz_catch(ctx) fz_rethrow(ctx); return doc; } static int cbz_recognize(fz_context *doc, const char *magic) { char *ext = strrchr(magic, '.'); if (ext) { if (!fz_strcasecmp(ext, ".cbz") || !fz_strcasecmp(ext, ".zip")) return 100; } if (!strcmp(magic, "cbz") || !strcmp(magic, "application/x-cbz")) return 100; return 0; } fz_document_handler cbz_document_handler = { (fz_document_recognize_fn *)&cbz_recognize, (fz_document_open_fn *)&cbz_open_document, (fz_document_open_with_stream_fn *)&cbz_open_document_with_stream };