diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/source/cbz/mucbz.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,391 @@
+// Copyright (C) 2004-2024 Artifex Software, Inc.
+//
+// This file is part of MuPDF.
+//
+// MuPDF is free software: you can redistribute it and/or modify it under the
+// terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
+//
+// Alternative licensing terms are available from the licensor.
+// For commercial licensing, see <https://www.artifex.com/> or contact
+// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
+// CA 94129, USA, for further information.
+
+#include "mupdf/fitz.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#define DPI 72.0f
+
+static const char *cbz_ext_list[] = {
+	".bmp",
+	".gif",
+	".hdp",
+	".j2k",
+	".jb2",
+	".jbig2",
+	".jp2",
+	".jpeg",
+	".jpg",
+	".jpx",
+	".jxr",
+	".pam",
+	".pbm",
+	".pgm",
+	".pkm",
+	".png",
+	".pnm",
+	".ppm",
+	".tif",
+	".tiff",
+	".wdp",
+	NULL
+};
+
+typedef struct
+{
+	fz_page super;
+	fz_image *image;
+} cbz_page;
+
+typedef struct
+{
+	fz_document super;
+	fz_archive *arch;
+	int page_count;
+	const char **page;
+} cbz_document;
+
+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 *arch = doc->arch;
+	int i, k, count;
+
+	count = fz_count_archive_entries(ctx, arch);
+
+	doc->page_count = 0;
+	doc->page = fz_malloc_array(ctx, count, const char *);
+
+	for (i = 0; i < count; i++)
+	{
+		const char *name = fz_list_archive_entry(ctx, arch, i);
+		const char *ext = name ? strrchr(name, '.') : NULL;
+		for (k = 0; cbz_ext_list[k]; k++)
+		{
+			if (ext && !fz_strcasecmp(ext, 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_drop_document(fz_context *ctx, fz_document *doc_)
+{
+	cbz_document *doc = (cbz_document*)doc_;
+	fz_drop_archive(ctx, doc->arch);
+	fz_free(ctx, (char **)doc->page);
+}
+
+static int
+cbz_count_pages(fz_context *ctx, fz_document *doc_, int chapter)
+{
+	cbz_document *doc = (cbz_document*)doc_;
+	return doc->page_count;
+}
+
+static fz_rect
+cbz_bound_page(fz_context *ctx, fz_page *page_, fz_box_type box)
+{
+	cbz_page *page = (cbz_page*)page_;
+	fz_image *image = page->image;
+	int xres, yres;
+	fz_rect bbox = fz_empty_rect;
+	uint8_t orientation;
+
+	if (image)
+	{
+		fz_image_resolution(image, &xres, &yres);
+		bbox.x0 = bbox.y0 = 0;
+		orientation = fz_image_orientation(ctx, image);
+		if (orientation == 0 || (orientation & 1) == 1)
+		{
+			bbox.x1 = image->w * DPI / xres;
+			bbox.y1 = image->h * DPI / yres;
+		}
+		else
+		{
+			bbox.y1 = image->w * DPI / xres;
+			bbox.x1 = image->h * DPI / yres;
+		}
+	}
+	return bbox;
+}
+
+static void
+cbz_run_page(fz_context *ctx, fz_page *page_, fz_device *dev, fz_matrix ctm, fz_cookie *cookie)
+{
+	cbz_page *page = (cbz_page*)page_;
+	fz_image *image = page->image;
+	int xres, yres;
+	float w, h;
+	uint8_t orientation;
+	fz_matrix immat;
+
+	if (image)
+	{
+		fz_try(ctx)
+		{
+			fz_image_resolution(image, &xres, &yres);
+			orientation = fz_image_orientation(ctx, image);
+			if (orientation == 0 || (orientation & 1) == 1)
+			{
+				w = image->w * DPI / xres;
+				h = image->h * DPI / yres;
+			}
+			else
+			{
+				h = image->w * DPI / xres;
+				w = image->h * DPI / yres;
+			}
+			immat = fz_image_orientation_matrix(ctx, image);
+			immat = fz_post_scale(immat, w, h);
+			ctm = fz_concat(immat, ctm);
+			fz_fill_image(ctx, dev, image, ctm, 1, fz_default_color_params);
+		}
+		fz_catch(ctx)
+		{
+			fz_report_error(ctx);
+			fz_warn(ctx, "cannot render image on page");
+		}
+	}
+}
+
+static void
+cbz_drop_page(fz_context *ctx, fz_page *page_)
+{
+	cbz_page *page = (cbz_page*)page_;
+	fz_drop_image(ctx, page->image);
+}
+
+static fz_page *
+cbz_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number)
+{
+	cbz_document *doc = (cbz_document*)doc_;
+	cbz_page *page = NULL;
+	fz_buffer *buf = NULL;
+
+	if (number < 0 || number >= doc->page_count)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "invalid page number %d", number);
+
+	fz_var(page);
+
+	page = fz_new_derived_page(ctx, cbz_page, doc_);
+	page->super.bound_page = cbz_bound_page;
+	page->super.run_page_contents = cbz_run_page;
+	page->super.drop_page = cbz_drop_page;
+
+	fz_try(ctx)
+	{
+		buf = fz_read_archive_entry(ctx, doc->arch, doc->page[number]);
+		page->image = fz_new_image_from_buffer(ctx, buf);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_buffer(ctx, buf);
+	}
+	fz_catch(ctx)
+	{
+		fz_report_error(ctx);
+		fz_warn(ctx, "cannot decode image on page, leaving it blank");
+	}
+
+	return (fz_page*)page;
+}
+
+static int
+cbz_lookup_metadata(fz_context *ctx, fz_document *doc_, const char *key, char *buf, size_t size)
+{
+	cbz_document *doc = (cbz_document*)doc_;
+	if (!strcmp(key, FZ_META_FORMAT))
+		return 1 + (int) fz_strlcpy(buf, fz_archive_format(ctx, doc->arch), size);
+	return -1;
+}
+
+static fz_document *
+cbz_open_document(fz_context *ctx, const fz_document_handler *handler, fz_stream *file, fz_stream *accel, fz_archive *dir, void *state)
+{
+	cbz_document *doc = fz_new_derived_document(ctx, cbz_document);
+
+	doc->super.drop_document = cbz_drop_document;
+	doc->super.count_pages = cbz_count_pages;
+	doc->super.load_page = cbz_load_page;
+	doc->super.lookup_metadata = cbz_lookup_metadata;
+
+	fz_try(ctx)
+	{
+		if (file)
+			doc->arch = fz_open_archive_with_stream(ctx, file);
+		else
+			doc->arch = fz_keep_archive(ctx, dir);
+		cbz_create_page_list(ctx, doc);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_document(ctx, (fz_document*)doc);
+		fz_rethrow(ctx);
+	}
+	return (fz_document*)doc;
+}
+
+static const char *cbz_extensions[] =
+{
+#ifdef HAVE_LIBARCHIVE
+	"cbr",
+#endif
+	"cbt",
+	"cbz",
+	"tar",
+	"zip",
+	NULL
+};
+
+static const char *cbz_mimetypes[] =
+{
+#ifdef HAVE_LIBARCHIVE
+	"application/vnd.comicbook-rar",
+#endif
+	"application/vnd.comicbook+zip",
+#ifdef HAVE_LIBARCHIVE
+	"application/x-cbr",
+#endif
+	"application/x-cbt",
+	"application/x-cbz",
+	"application/x-tar",
+	"application/zip",
+	NULL
+};
+
+static int
+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)
+{
+	fz_archive *arch = NULL;
+	int ret = 0;
+	int i, k, count;
+
+	fz_var(arch);
+	fz_var(ret);
+
+	fz_try(ctx)
+	{
+		if (stream == NULL)
+			arch = fz_keep_archive(ctx, dir);
+		else
+		{
+			arch = fz_try_open_archive_with_stream(ctx, stream);
+			if (arch == NULL)
+				break;
+		}
+
+		/* If it's an archive, and we can find at least one plausible page
+		 * then we can open it as a cbz. */
+		count = fz_count_archive_entries(ctx, arch);
+		for (i = 0; i < count && ret == 0; i++)
+		{
+			const char *name = fz_list_archive_entry(ctx, arch, i);
+			const char *ext;
+			if (name == NULL)
+				continue;
+			ext = strrchr(name, '.');
+			if (ext)
+			{
+				for (k = 0; cbz_ext_list[k]; k++)
+				{
+					if (!fz_strcasecmp(ext, cbz_ext_list[k]))
+					{
+						ret = 25;
+						break;
+					}
+				}
+			}
+		}
+	}
+	fz_always(ctx)
+		fz_drop_archive(ctx, arch);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+
+	return ret;
+}
+
+fz_document_handler cbz_document_handler =
+{
+	NULL,
+	cbz_open_document,
+	cbz_extensions,
+	cbz_mimetypes,
+	cbz_recognize_doc_content
+};