comparison mupdf-source/source/cbz/mucbz.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
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
23 #include "mupdf/fitz.h"
24
25 #include <string.h>
26 #include <stdlib.h>
27
28 #define DPI 72.0f
29
30 static const char *cbz_ext_list[] = {
31 ".bmp",
32 ".gif",
33 ".hdp",
34 ".j2k",
35 ".jb2",
36 ".jbig2",
37 ".jp2",
38 ".jpeg",
39 ".jpg",
40 ".jpx",
41 ".jxr",
42 ".pam",
43 ".pbm",
44 ".pgm",
45 ".pkm",
46 ".png",
47 ".pnm",
48 ".ppm",
49 ".tif",
50 ".tiff",
51 ".wdp",
52 NULL
53 };
54
55 typedef struct
56 {
57 fz_page super;
58 fz_image *image;
59 } cbz_page;
60
61 typedef struct
62 {
63 fz_document super;
64 fz_archive *arch;
65 int page_count;
66 const char **page;
67 } cbz_document;
68
69 static inline int cbz_isdigit(int c)
70 {
71 return c >= '0' && c <= '9';
72 }
73
74 static inline int cbz_toupper(int c)
75 {
76 if (c >= 'a' && c <= 'z')
77 return c - 'a' + 'A';
78 return c;
79 }
80
81 static inline int
82 cbz_strnatcmp(const char *a, const char *b)
83 {
84 int x, y;
85
86 while (*a || *b)
87 {
88 if (cbz_isdigit(*a) && cbz_isdigit(*b))
89 {
90 x = *a++ - '0';
91 while (cbz_isdigit(*a))
92 x = x * 10 + *a++ - '0';
93 y = *b++ - '0';
94 while (cbz_isdigit(*b))
95 y = y * 10 + *b++ - '0';
96 }
97 else
98 {
99 x = cbz_toupper(*a++);
100 y = cbz_toupper(*b++);
101 }
102 if (x < y)
103 return -1;
104 if (x > y)
105 return 1;
106 }
107
108 return 0;
109 }
110
111 static int
112 cbz_compare_page_names(const void *a, const void *b)
113 {
114 return cbz_strnatcmp(*(const char **)a, *(const char **)b);
115 }
116
117 static void
118 cbz_create_page_list(fz_context *ctx, cbz_document *doc)
119 {
120 fz_archive *arch = doc->arch;
121 int i, k, count;
122
123 count = fz_count_archive_entries(ctx, arch);
124
125 doc->page_count = 0;
126 doc->page = fz_malloc_array(ctx, count, const char *);
127
128 for (i = 0; i < count; i++)
129 {
130 const char *name = fz_list_archive_entry(ctx, arch, i);
131 const char *ext = name ? strrchr(name, '.') : NULL;
132 for (k = 0; cbz_ext_list[k]; k++)
133 {
134 if (ext && !fz_strcasecmp(ext, cbz_ext_list[k]))
135 {
136 doc->page[doc->page_count++] = name;
137 break;
138 }
139 }
140 }
141
142 qsort((char **)doc->page, doc->page_count, sizeof *doc->page, cbz_compare_page_names);
143 }
144
145 static void
146 cbz_drop_document(fz_context *ctx, fz_document *doc_)
147 {
148 cbz_document *doc = (cbz_document*)doc_;
149 fz_drop_archive(ctx, doc->arch);
150 fz_free(ctx, (char **)doc->page);
151 }
152
153 static int
154 cbz_count_pages(fz_context *ctx, fz_document *doc_, int chapter)
155 {
156 cbz_document *doc = (cbz_document*)doc_;
157 return doc->page_count;
158 }
159
160 static fz_rect
161 cbz_bound_page(fz_context *ctx, fz_page *page_, fz_box_type box)
162 {
163 cbz_page *page = (cbz_page*)page_;
164 fz_image *image = page->image;
165 int xres, yres;
166 fz_rect bbox = fz_empty_rect;
167 uint8_t orientation;
168
169 if (image)
170 {
171 fz_image_resolution(image, &xres, &yres);
172 bbox.x0 = bbox.y0 = 0;
173 orientation = fz_image_orientation(ctx, image);
174 if (orientation == 0 || (orientation & 1) == 1)
175 {
176 bbox.x1 = image->w * DPI / xres;
177 bbox.y1 = image->h * DPI / yres;
178 }
179 else
180 {
181 bbox.y1 = image->w * DPI / xres;
182 bbox.x1 = image->h * DPI / yres;
183 }
184 }
185 return bbox;
186 }
187
188 static void
189 cbz_run_page(fz_context *ctx, fz_page *page_, fz_device *dev, fz_matrix ctm, fz_cookie *cookie)
190 {
191 cbz_page *page = (cbz_page*)page_;
192 fz_image *image = page->image;
193 int xres, yres;
194 float w, h;
195 uint8_t orientation;
196 fz_matrix immat;
197
198 if (image)
199 {
200 fz_try(ctx)
201 {
202 fz_image_resolution(image, &xres, &yres);
203 orientation = fz_image_orientation(ctx, image);
204 if (orientation == 0 || (orientation & 1) == 1)
205 {
206 w = image->w * DPI / xres;
207 h = image->h * DPI / yres;
208 }
209 else
210 {
211 h = image->w * DPI / xres;
212 w = image->h * DPI / yres;
213 }
214 immat = fz_image_orientation_matrix(ctx, image);
215 immat = fz_post_scale(immat, w, h);
216 ctm = fz_concat(immat, ctm);
217 fz_fill_image(ctx, dev, image, ctm, 1, fz_default_color_params);
218 }
219 fz_catch(ctx)
220 {
221 fz_report_error(ctx);
222 fz_warn(ctx, "cannot render image on page");
223 }
224 }
225 }
226
227 static void
228 cbz_drop_page(fz_context *ctx, fz_page *page_)
229 {
230 cbz_page *page = (cbz_page*)page_;
231 fz_drop_image(ctx, page->image);
232 }
233
234 static fz_page *
235 cbz_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number)
236 {
237 cbz_document *doc = (cbz_document*)doc_;
238 cbz_page *page = NULL;
239 fz_buffer *buf = NULL;
240
241 if (number < 0 || number >= doc->page_count)
242 fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid page number %d", number);
243
244 fz_var(page);
245
246 page = fz_new_derived_page(ctx, cbz_page, doc_);
247 page->super.bound_page = cbz_bound_page;
248 page->super.run_page_contents = cbz_run_page;
249 page->super.drop_page = cbz_drop_page;
250
251 fz_try(ctx)
252 {
253 buf = fz_read_archive_entry(ctx, doc->arch, doc->page[number]);
254 page->image = fz_new_image_from_buffer(ctx, buf);
255 }
256 fz_always(ctx)
257 {
258 fz_drop_buffer(ctx, buf);
259 }
260 fz_catch(ctx)
261 {
262 fz_report_error(ctx);
263 fz_warn(ctx, "cannot decode image on page, leaving it blank");
264 }
265
266 return (fz_page*)page;
267 }
268
269 static int
270 cbz_lookup_metadata(fz_context *ctx, fz_document *doc_, const char *key, char *buf, size_t size)
271 {
272 cbz_document *doc = (cbz_document*)doc_;
273 if (!strcmp(key, FZ_META_FORMAT))
274 return 1 + (int) fz_strlcpy(buf, fz_archive_format(ctx, doc->arch), size);
275 return -1;
276 }
277
278 static fz_document *
279 cbz_open_document(fz_context *ctx, const fz_document_handler *handler, fz_stream *file, fz_stream *accel, fz_archive *dir, void *state)
280 {
281 cbz_document *doc = fz_new_derived_document(ctx, cbz_document);
282
283 doc->super.drop_document = cbz_drop_document;
284 doc->super.count_pages = cbz_count_pages;
285 doc->super.load_page = cbz_load_page;
286 doc->super.lookup_metadata = cbz_lookup_metadata;
287
288 fz_try(ctx)
289 {
290 if (file)
291 doc->arch = fz_open_archive_with_stream(ctx, file);
292 else
293 doc->arch = fz_keep_archive(ctx, dir);
294 cbz_create_page_list(ctx, doc);
295 }
296 fz_catch(ctx)
297 {
298 fz_drop_document(ctx, (fz_document*)doc);
299 fz_rethrow(ctx);
300 }
301 return (fz_document*)doc;
302 }
303
304 static const char *cbz_extensions[] =
305 {
306 #ifdef HAVE_LIBARCHIVE
307 "cbr",
308 #endif
309 "cbt",
310 "cbz",
311 "tar",
312 "zip",
313 NULL
314 };
315
316 static const char *cbz_mimetypes[] =
317 {
318 #ifdef HAVE_LIBARCHIVE
319 "application/vnd.comicbook-rar",
320 #endif
321 "application/vnd.comicbook+zip",
322 #ifdef HAVE_LIBARCHIVE
323 "application/x-cbr",
324 #endif
325 "application/x-cbt",
326 "application/x-cbz",
327 "application/x-tar",
328 "application/zip",
329 NULL
330 };
331
332 static int
333 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)
334 {
335 fz_archive *arch = NULL;
336 int ret = 0;
337 int i, k, count;
338
339 fz_var(arch);
340 fz_var(ret);
341
342 fz_try(ctx)
343 {
344 if (stream == NULL)
345 arch = fz_keep_archive(ctx, dir);
346 else
347 {
348 arch = fz_try_open_archive_with_stream(ctx, stream);
349 if (arch == NULL)
350 break;
351 }
352
353 /* If it's an archive, and we can find at least one plausible page
354 * then we can open it as a cbz. */
355 count = fz_count_archive_entries(ctx, arch);
356 for (i = 0; i < count && ret == 0; i++)
357 {
358 const char *name = fz_list_archive_entry(ctx, arch, i);
359 const char *ext;
360 if (name == NULL)
361 continue;
362 ext = strrchr(name, '.');
363 if (ext)
364 {
365 for (k = 0; cbz_ext_list[k]; k++)
366 {
367 if (!fz_strcasecmp(ext, cbz_ext_list[k]))
368 {
369 ret = 25;
370 break;
371 }
372 }
373 }
374 }
375 }
376 fz_always(ctx)
377 fz_drop_archive(ctx, arch);
378 fz_catch(ctx)
379 fz_rethrow(ctx);
380
381 return ret;
382 }
383
384 fz_document_handler cbz_document_handler =
385 {
386 NULL,
387 cbz_open_document,
388 cbz_extensions,
389 cbz_mimetypes,
390 cbz_recognize_doc_content
391 };