mucbz.c 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. // Copyright (C) 2004-2024 Artifex Software, Inc.
  2. //
  3. // This file is part of MuPDF.
  4. //
  5. // MuPDF is free software: you can redistribute it and/or modify it under the
  6. // terms of the GNU Affero General Public License as published by the Free
  7. // Software Foundation, either version 3 of the License, or (at your option)
  8. // any later version.
  9. //
  10. // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
  11. // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. // details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
  17. //
  18. // Alternative licensing terms are available from the licensor.
  19. // For commercial licensing, see <https://www.artifex.com/> or contact
  20. // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
  21. // CA 94129, USA, for further information.
  22. #include "mupdf/fitz.h"
  23. #include <string.h>
  24. #include <stdlib.h>
  25. #define DPI 72.0f
  26. static const char *cbz_ext_list[] = {
  27. ".bmp",
  28. ".gif",
  29. ".hdp",
  30. ".j2k",
  31. ".jb2",
  32. ".jbig2",
  33. ".jp2",
  34. ".jpeg",
  35. ".jpg",
  36. ".jpx",
  37. ".jxr",
  38. ".pam",
  39. ".pbm",
  40. ".pgm",
  41. ".pkm",
  42. ".png",
  43. ".pnm",
  44. ".ppm",
  45. ".tif",
  46. ".tiff",
  47. ".wdp",
  48. NULL
  49. };
  50. typedef struct
  51. {
  52. fz_page super;
  53. fz_image *image;
  54. } cbz_page;
  55. typedef struct
  56. {
  57. fz_document super;
  58. fz_archive *arch;
  59. int page_count;
  60. const char **page;
  61. } cbz_document;
  62. static inline int cbz_isdigit(int c)
  63. {
  64. return c >= '0' && c <= '9';
  65. }
  66. static inline int cbz_toupper(int c)
  67. {
  68. if (c >= 'a' && c <= 'z')
  69. return c - 'a' + 'A';
  70. return c;
  71. }
  72. static inline int
  73. cbz_strnatcmp(const char *a, const char *b)
  74. {
  75. int x, y;
  76. while (*a || *b)
  77. {
  78. if (cbz_isdigit(*a) && cbz_isdigit(*b))
  79. {
  80. x = *a++ - '0';
  81. while (cbz_isdigit(*a))
  82. x = x * 10 + *a++ - '0';
  83. y = *b++ - '0';
  84. while (cbz_isdigit(*b))
  85. y = y * 10 + *b++ - '0';
  86. }
  87. else
  88. {
  89. x = cbz_toupper(*a++);
  90. y = cbz_toupper(*b++);
  91. }
  92. if (x < y)
  93. return -1;
  94. if (x > y)
  95. return 1;
  96. }
  97. return 0;
  98. }
  99. static int
  100. cbz_compare_page_names(const void *a, const void *b)
  101. {
  102. return cbz_strnatcmp(*(const char **)a, *(const char **)b);
  103. }
  104. static void
  105. cbz_create_page_list(fz_context *ctx, cbz_document *doc)
  106. {
  107. fz_archive *arch = doc->arch;
  108. int i, k, count;
  109. count = fz_count_archive_entries(ctx, arch);
  110. doc->page_count = 0;
  111. doc->page = fz_malloc_array(ctx, count, const char *);
  112. for (i = 0; i < count; i++)
  113. {
  114. const char *name = fz_list_archive_entry(ctx, arch, i);
  115. const char *ext = name ? strrchr(name, '.') : NULL;
  116. for (k = 0; cbz_ext_list[k]; k++)
  117. {
  118. if (ext && !fz_strcasecmp(ext, cbz_ext_list[k]))
  119. {
  120. doc->page[doc->page_count++] = name;
  121. break;
  122. }
  123. }
  124. }
  125. qsort((char **)doc->page, doc->page_count, sizeof *doc->page, cbz_compare_page_names);
  126. }
  127. static void
  128. cbz_drop_document(fz_context *ctx, fz_document *doc_)
  129. {
  130. cbz_document *doc = (cbz_document*)doc_;
  131. fz_drop_archive(ctx, doc->arch);
  132. fz_free(ctx, (char **)doc->page);
  133. }
  134. static int
  135. cbz_count_pages(fz_context *ctx, fz_document *doc_, int chapter)
  136. {
  137. cbz_document *doc = (cbz_document*)doc_;
  138. return doc->page_count;
  139. }
  140. static fz_rect
  141. cbz_bound_page(fz_context *ctx, fz_page *page_, fz_box_type box)
  142. {
  143. cbz_page *page = (cbz_page*)page_;
  144. fz_image *image = page->image;
  145. int xres, yres;
  146. fz_rect bbox = fz_empty_rect;
  147. uint8_t orientation;
  148. if (image)
  149. {
  150. fz_image_resolution(image, &xres, &yres);
  151. bbox.x0 = bbox.y0 = 0;
  152. orientation = fz_image_orientation(ctx, image);
  153. if (orientation == 0 || (orientation & 1) == 1)
  154. {
  155. bbox.x1 = image->w * DPI / xres;
  156. bbox.y1 = image->h * DPI / yres;
  157. }
  158. else
  159. {
  160. bbox.y1 = image->w * DPI / xres;
  161. bbox.x1 = image->h * DPI / yres;
  162. }
  163. }
  164. return bbox;
  165. }
  166. static void
  167. cbz_run_page(fz_context *ctx, fz_page *page_, fz_device *dev, fz_matrix ctm, fz_cookie *cookie)
  168. {
  169. cbz_page *page = (cbz_page*)page_;
  170. fz_image *image = page->image;
  171. int xres, yres;
  172. float w, h;
  173. uint8_t orientation;
  174. fz_matrix immat;
  175. if (image)
  176. {
  177. fz_try(ctx)
  178. {
  179. fz_image_resolution(image, &xres, &yres);
  180. orientation = fz_image_orientation(ctx, image);
  181. if (orientation == 0 || (orientation & 1) == 1)
  182. {
  183. w = image->w * DPI / xres;
  184. h = image->h * DPI / yres;
  185. }
  186. else
  187. {
  188. h = image->w * DPI / xres;
  189. w = image->h * DPI / yres;
  190. }
  191. immat = fz_image_orientation_matrix(ctx, image);
  192. immat = fz_post_scale(immat, w, h);
  193. ctm = fz_concat(immat, ctm);
  194. fz_fill_image(ctx, dev, image, ctm, 1, fz_default_color_params);
  195. }
  196. fz_catch(ctx)
  197. {
  198. fz_report_error(ctx);
  199. fz_warn(ctx, "cannot render image on page");
  200. }
  201. }
  202. }
  203. static void
  204. cbz_drop_page(fz_context *ctx, fz_page *page_)
  205. {
  206. cbz_page *page = (cbz_page*)page_;
  207. fz_drop_image(ctx, page->image);
  208. }
  209. static fz_page *
  210. cbz_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number)
  211. {
  212. cbz_document *doc = (cbz_document*)doc_;
  213. cbz_page *page = NULL;
  214. fz_buffer *buf = NULL;
  215. if (number < 0 || number >= doc->page_count)
  216. fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid page number %d", number);
  217. fz_var(page);
  218. page = fz_new_derived_page(ctx, cbz_page, doc_);
  219. page->super.bound_page = cbz_bound_page;
  220. page->super.run_page_contents = cbz_run_page;
  221. page->super.drop_page = cbz_drop_page;
  222. fz_try(ctx)
  223. {
  224. buf = fz_read_archive_entry(ctx, doc->arch, doc->page[number]);
  225. page->image = fz_new_image_from_buffer(ctx, buf);
  226. }
  227. fz_always(ctx)
  228. {
  229. fz_drop_buffer(ctx, buf);
  230. }
  231. fz_catch(ctx)
  232. {
  233. fz_report_error(ctx);
  234. fz_warn(ctx, "cannot decode image on page, leaving it blank");
  235. }
  236. return (fz_page*)page;
  237. }
  238. static int
  239. cbz_lookup_metadata(fz_context *ctx, fz_document *doc_, const char *key, char *buf, size_t size)
  240. {
  241. cbz_document *doc = (cbz_document*)doc_;
  242. if (!strcmp(key, FZ_META_FORMAT))
  243. return 1 + (int) fz_strlcpy(buf, fz_archive_format(ctx, doc->arch), size);
  244. return -1;
  245. }
  246. static fz_document *
  247. cbz_open_document(fz_context *ctx, const fz_document_handler *handler, fz_stream *file, fz_stream *accel, fz_archive *dir, void *state)
  248. {
  249. cbz_document *doc = fz_new_derived_document(ctx, cbz_document);
  250. doc->super.drop_document = cbz_drop_document;
  251. doc->super.count_pages = cbz_count_pages;
  252. doc->super.load_page = cbz_load_page;
  253. doc->super.lookup_metadata = cbz_lookup_metadata;
  254. fz_try(ctx)
  255. {
  256. if (file)
  257. doc->arch = fz_open_archive_with_stream(ctx, file);
  258. else
  259. doc->arch = fz_keep_archive(ctx, dir);
  260. cbz_create_page_list(ctx, doc);
  261. }
  262. fz_catch(ctx)
  263. {
  264. fz_drop_document(ctx, (fz_document*)doc);
  265. fz_rethrow(ctx);
  266. }
  267. return (fz_document*)doc;
  268. }
  269. static const char *cbz_extensions[] =
  270. {
  271. #ifdef HAVE_LIBARCHIVE
  272. "cbr",
  273. #endif
  274. "cbt",
  275. "cbz",
  276. "tar",
  277. "zip",
  278. NULL
  279. };
  280. static const char *cbz_mimetypes[] =
  281. {
  282. #ifdef HAVE_LIBARCHIVE
  283. "application/vnd.comicbook-rar",
  284. #endif
  285. "application/vnd.comicbook+zip",
  286. #ifdef HAVE_LIBARCHIVE
  287. "application/x-cbr",
  288. #endif
  289. "application/x-cbt",
  290. "application/x-cbz",
  291. "application/x-tar",
  292. "application/zip",
  293. NULL
  294. };
  295. static int
  296. cbz_recognize_doc_content(fz_context *ctx, const fz_document_handler *handler, fz_stream *stream, fz_archive *dir, void **state, fz_document_recognize_state_free_fn **freestate)
  297. {
  298. fz_archive *arch = NULL;
  299. int ret = 0;
  300. int i, k, count;
  301. fz_var(arch);
  302. fz_var(ret);
  303. fz_try(ctx)
  304. {
  305. if (stream == NULL)
  306. arch = fz_keep_archive(ctx, dir);
  307. else
  308. {
  309. arch = fz_try_open_archive_with_stream(ctx, stream);
  310. if (arch == NULL)
  311. break;
  312. }
  313. /* If it's an archive, and we can find at least one plausible page
  314. * then we can open it as a cbz. */
  315. count = fz_count_archive_entries(ctx, arch);
  316. for (i = 0; i < count && ret == 0; i++)
  317. {
  318. const char *name = fz_list_archive_entry(ctx, arch, i);
  319. const char *ext;
  320. if (name == NULL)
  321. continue;
  322. ext = strrchr(name, '.');
  323. if (ext)
  324. {
  325. for (k = 0; cbz_ext_list[k]; k++)
  326. {
  327. if (!fz_strcasecmp(ext, cbz_ext_list[k]))
  328. {
  329. ret = 25;
  330. break;
  331. }
  332. }
  333. }
  334. }
  335. }
  336. fz_always(ctx)
  337. fz_drop_archive(ctx, arch);
  338. fz_catch(ctx)
  339. fz_rethrow(ctx);
  340. return ret;
  341. }
  342. fz_document_handler cbz_document_handler =
  343. {
  344. NULL,
  345. cbz_open_document,
  346. cbz_extensions,
  347. cbz_mimetypes,
  348. cbz_recognize_doc_content
  349. };