diff mupdf-source/source/fitz/util.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/fitz/util.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,984 @@
+// Copyright (C) 2004-2022 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 <float.h>
+
+fz_display_list *
+fz_new_display_list_from_page(fz_context *ctx, fz_page *page)
+{
+	fz_display_list *list;
+	fz_device *dev = NULL;
+
+	fz_var(dev);
+
+	list = fz_new_display_list(ctx, fz_bound_page(ctx, page));
+	fz_try(ctx)
+	{
+		dev = fz_new_list_device(ctx, list);
+		fz_run_page(ctx, page, dev, fz_identity, NULL);
+		fz_close_device(ctx, dev);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_device(ctx, dev);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_display_list(ctx, list);
+		fz_rethrow(ctx);
+	}
+
+	return list;
+}
+
+fz_display_list *
+fz_new_display_list_from_page_number(fz_context *ctx, fz_document *doc, int number)
+{
+	fz_page *page;
+	fz_display_list *list = NULL;
+
+	page = fz_load_page(ctx, doc, number);
+	fz_try(ctx)
+		list = fz_new_display_list_from_page(ctx, page);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return list;
+}
+
+fz_display_list *
+fz_new_display_list_from_page_contents(fz_context *ctx, fz_page *page)
+{
+	fz_display_list *list;
+	fz_device *dev = NULL;
+
+	fz_var(dev);
+
+	list = fz_new_display_list(ctx, fz_bound_page(ctx, page));
+	fz_try(ctx)
+	{
+		dev = fz_new_list_device(ctx, list);
+		fz_run_page_contents(ctx, page, dev, fz_identity, NULL);
+		fz_close_device(ctx, dev);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_device(ctx, dev);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_display_list(ctx, list);
+		fz_rethrow(ctx);
+	}
+
+	return list;
+}
+
+fz_pixmap *
+fz_new_pixmap_from_display_list(fz_context *ctx, fz_display_list *list, fz_matrix ctm, fz_colorspace *cs, int alpha)
+{
+	return fz_new_pixmap_from_display_list_with_separations(ctx, list, ctm, cs, NULL, alpha);
+}
+
+fz_pixmap *
+fz_new_pixmap_from_display_list_with_separations(fz_context *ctx, fz_display_list *list, fz_matrix ctm, fz_colorspace *cs, fz_separations *seps, int alpha)
+{
+	fz_rect rect;
+	fz_irect bbox;
+	fz_pixmap *pix;
+
+	rect = fz_bound_display_list(ctx, list);
+	rect = fz_transform_rect(rect, ctm);
+	bbox = fz_round_rect(rect);
+
+	pix = fz_new_pixmap_with_bbox(ctx, cs, bbox, seps, alpha);
+	if (alpha)
+		fz_clear_pixmap(ctx, pix);
+	else
+		fz_clear_pixmap_with_value(ctx, pix, 0xFF);
+
+	return fz_fill_pixmap_from_display_list(ctx, list, ctm, pix);
+}
+
+fz_pixmap *
+fz_fill_pixmap_from_display_list(fz_context *ctx, fz_display_list *list, fz_matrix ctm, fz_pixmap *pix)
+{
+	fz_device *dev = NULL;
+
+	fz_var(dev);
+
+	fz_try(ctx)
+	{
+		dev = fz_new_draw_device(ctx, ctm, pix);
+		fz_run_display_list(ctx, list, dev, fz_identity, fz_infinite_rect, NULL);
+		fz_close_device(ctx, dev);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_device(ctx, dev);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_pixmap(ctx, pix);
+		fz_rethrow(ctx);
+	}
+
+	return pix;
+}
+
+fz_pixmap *
+fz_new_pixmap_from_page_contents(fz_context *ctx, fz_page *page, fz_matrix ctm, fz_colorspace *cs, int alpha)
+{
+	return fz_new_pixmap_from_page_contents_with_separations(ctx, page, ctm, cs, NULL, alpha);
+}
+
+fz_pixmap *
+fz_new_pixmap_from_page_contents_with_separations(fz_context *ctx, fz_page *page, fz_matrix ctm, fz_colorspace *cs, fz_separations *seps, int alpha)
+{
+	fz_rect rect;
+	fz_irect bbox;
+	fz_pixmap *pix;
+	fz_device *dev = NULL;
+
+	fz_var(dev);
+
+	rect = fz_bound_page(ctx, page);
+	rect = fz_transform_rect(rect, ctm);
+	bbox = fz_round_rect(rect);
+
+	pix = fz_new_pixmap_with_bbox(ctx, cs, bbox, seps, alpha);
+	if (alpha)
+		fz_clear_pixmap(ctx, pix);
+	else
+		fz_clear_pixmap_with_value(ctx, pix, 0xFF);
+
+	fz_try(ctx)
+	{
+		dev = fz_new_draw_device(ctx, ctm, pix);
+		fz_run_page_contents(ctx, page, dev, fz_identity, NULL);
+		fz_close_device(ctx, dev);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_device(ctx, dev);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_pixmap(ctx, pix);
+		fz_rethrow(ctx);
+	}
+
+	return pix;
+}
+
+fz_pixmap *
+fz_new_pixmap_from_page(fz_context *ctx, fz_page *page, fz_matrix ctm, fz_colorspace *cs, int alpha)
+{
+	return fz_new_pixmap_from_page_with_separations(ctx, page, ctm, cs, NULL, alpha);
+}
+
+fz_pixmap *
+fz_new_pixmap_from_page_with_separations(fz_context *ctx, fz_page *page, fz_matrix ctm, fz_colorspace *cs, fz_separations *seps, int alpha)
+{
+	fz_rect rect;
+	fz_irect bbox;
+	fz_pixmap *pix;
+	fz_device *dev = NULL;
+
+	fz_var(dev);
+
+	rect = fz_bound_page(ctx, page);
+	rect = fz_transform_rect(rect, ctm);
+	bbox = fz_round_rect(rect);
+
+	pix = fz_new_pixmap_with_bbox(ctx, cs, bbox, seps, alpha);
+
+	fz_try(ctx)
+	{
+		if (alpha)
+			fz_clear_pixmap(ctx, pix);
+		else
+			fz_clear_pixmap_with_value(ctx, pix, 0xFF);
+
+		dev = fz_new_draw_device(ctx, ctm, pix);
+		fz_run_page(ctx, page, dev, fz_identity, NULL);
+		fz_close_device(ctx, dev);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_device(ctx, dev);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_pixmap(ctx, pix);
+		fz_rethrow(ctx);
+	}
+
+	return pix;
+}
+
+fz_pixmap *
+fz_new_pixmap_from_page_number(fz_context *ctx, fz_document *doc, int number, fz_matrix ctm, fz_colorspace *cs, int alpha)
+{
+	return fz_new_pixmap_from_page_number_with_separations(ctx, doc, number, ctm, cs, NULL, alpha);
+}
+
+fz_pixmap *
+fz_new_pixmap_from_page_number_with_separations(fz_context *ctx, fz_document *doc, int number, fz_matrix ctm, fz_colorspace *cs, fz_separations *seps, int alpha)
+{
+	fz_page *page;
+	fz_pixmap *pix = NULL;
+
+	page = fz_load_page(ctx, doc, number);
+	fz_try(ctx)
+		pix = fz_new_pixmap_from_page_with_separations(ctx, page, ctm, cs, seps, alpha);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return pix;
+}
+
+fz_stext_page *
+fz_new_stext_page_from_display_list(fz_context *ctx, fz_display_list *list, const fz_stext_options *options)
+{
+	fz_stext_page *text;
+	fz_device *dev = NULL;
+
+	fz_var(dev);
+
+	if (list == NULL)
+		return NULL;
+
+	text = fz_new_stext_page(ctx, fz_bound_display_list(ctx, list));
+	fz_try(ctx)
+	{
+		dev = fz_new_stext_device(ctx, text, options);
+		fz_run_display_list(ctx, list, dev, fz_identity, fz_infinite_rect, NULL);
+		fz_close_device(ctx, dev);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_device(ctx, dev);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_stext_page(ctx, text);
+		fz_rethrow(ctx);
+	}
+
+	return text;
+}
+
+fz_stext_page *
+fz_new_stext_page_from_page(fz_context *ctx, fz_page *page, const fz_stext_options *options)
+{
+	fz_stext_page *text;
+	fz_device *dev = NULL;
+
+	fz_var(dev);
+
+	if (page == NULL)
+		return NULL;
+
+	text = fz_new_stext_page(ctx, fz_bound_page(ctx, page));
+	fz_try(ctx)
+	{
+		dev = fz_new_stext_device(ctx, text, options);
+		fz_run_page_contents(ctx, page, dev, fz_identity, NULL);
+		fz_close_device(ctx, dev);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_device(ctx, dev);
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_stext_page(ctx, text);
+		fz_rethrow(ctx);
+	}
+
+	return text;
+}
+
+fz_stext_page *
+fz_new_stext_page_from_page_number(fz_context *ctx, fz_document *doc, int number, const fz_stext_options *options)
+{
+	fz_page *page;
+	fz_stext_page *text = NULL;
+
+	page = fz_load_page(ctx, doc, number);
+	fz_try(ctx)
+		text = fz_new_stext_page_from_page(ctx, page, options);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return text;
+}
+
+fz_stext_page *
+fz_new_stext_page_from_chapter_page_number(fz_context *ctx, fz_document *doc, int chapter, int number, const fz_stext_options *options)
+{
+	fz_page *page;
+	fz_stext_page *text = NULL;
+
+	page = fz_load_chapter_page(ctx, doc, chapter, number);
+	fz_try(ctx)
+		text = fz_new_stext_page_from_page(ctx, page, options);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return text;
+}
+
+int
+fz_search_display_list(fz_context *ctx, fz_display_list *list, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max)
+{
+	fz_stext_page *text;
+	int count = 0;
+
+	text = fz_new_stext_page_from_display_list(ctx, list, NULL);
+	fz_try(ctx)
+		count = fz_search_stext_page(ctx, text, needle, hit_mark, hit_bbox, hit_max);
+	fz_always(ctx)
+		fz_drop_stext_page(ctx, text);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return count;
+}
+
+int
+fz_search_display_list_cb(fz_context *ctx, fz_display_list *list, const char *needle, fz_search_callback_fn *cb, void *opaque)
+{
+	fz_stext_page *text;
+	int count = 0;
+
+	text = fz_new_stext_page_from_display_list(ctx, list, NULL);
+	fz_try(ctx)
+		count = fz_search_stext_page_cb(ctx, text, needle, cb, opaque);
+	fz_always(ctx)
+		fz_drop_stext_page(ctx, text);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return count;
+}
+
+int
+fz_search_page(fz_context *ctx, fz_page *page, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max)
+{
+	fz_stext_options opts = { FZ_STEXT_DEHYPHENATE };
+	fz_stext_page *text;
+	int count = 0;
+
+	text = fz_new_stext_page_from_page(ctx, page, &opts);
+	fz_try(ctx)
+		count = fz_search_stext_page(ctx, text, needle, hit_mark, hit_bbox, hit_max);
+	fz_always(ctx)
+		fz_drop_stext_page(ctx, text);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return count;
+}
+
+int
+fz_search_page_cb(fz_context *ctx, fz_page *page, const char *needle, fz_search_callback_fn *cb, void *opaque)
+{
+	fz_stext_options opts = { FZ_STEXT_DEHYPHENATE };
+	fz_stext_page *text;
+	int count = 0;
+
+	text = fz_new_stext_page_from_page(ctx, page, &opts);
+	fz_try(ctx)
+		count = fz_search_stext_page_cb(ctx, text, needle, cb, opaque);
+	fz_always(ctx)
+		fz_drop_stext_page(ctx, text);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return count;
+}
+
+int
+fz_search_page_number(fz_context *ctx, fz_document *doc, int number, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max)
+{
+	fz_page *page;
+	int count = 0;
+
+	page = fz_load_page(ctx, doc, number);
+	fz_try(ctx)
+		count = fz_search_page(ctx, page, needle, hit_mark, hit_bbox, hit_max);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return count;
+}
+
+int
+fz_search_page_number_cb(fz_context *ctx, fz_document *doc, int number, const char *needle, fz_search_callback_fn *cb, void *opaque)
+{
+	fz_page *page;
+	int count = 0;
+
+	page = fz_load_page(ctx, doc, number);
+	fz_try(ctx)
+		count = fz_search_page_cb(ctx, page, needle, cb, opaque);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return count;
+}
+
+int
+fz_search_chapter_page_number(fz_context *ctx, fz_document *doc, int chapter, int number, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max)
+{
+	fz_page *page;
+	int count = 0;
+
+	page = fz_load_chapter_page(ctx, doc, chapter, number);
+	fz_try(ctx)
+		count = fz_search_page(ctx, page, needle, hit_mark, hit_bbox, hit_max);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return count;
+}
+
+int
+fz_search_chapter_page_number_cb(fz_context *ctx, fz_document *doc, int chapter, int number, const char *needle, fz_search_callback_fn *cb, void *opaque)
+{
+	fz_page *page;
+	int count = 0;
+
+	page = fz_load_chapter_page(ctx, doc, chapter, number);
+	fz_try(ctx)
+		count = fz_search_page_cb(ctx, page, needle, cb, opaque);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return count;
+}
+
+fz_buffer *
+fz_new_buffer_from_stext_page(fz_context *ctx, fz_stext_page *page)
+{
+	fz_stext_block *block;
+	fz_stext_line *line;
+	fz_stext_char *ch;
+	fz_buffer *buf;
+
+	buf = fz_new_buffer(ctx, 256);
+	fz_try(ctx)
+	{
+		for (block = page->first_block; block; block = block->next)
+		{
+			if (block->type == FZ_STEXT_BLOCK_TEXT)
+			{
+				for (line = block->u.t.first_line; line; line = line->next)
+				{
+					for (ch = line->first_char; ch; ch = ch->next)
+						fz_append_rune(ctx, buf, ch->c);
+					fz_append_byte(ctx, buf, '\n');
+				}
+				fz_append_byte(ctx, buf, '\n');
+			}
+		}
+	}
+	fz_catch(ctx)
+	{
+		fz_drop_buffer(ctx, buf);
+		fz_rethrow(ctx);
+	}
+
+	return buf;
+}
+
+fz_buffer *
+fz_new_buffer_from_display_list(fz_context *ctx, fz_display_list *list, const fz_stext_options *options)
+{
+	fz_stext_page *text;
+	fz_buffer *buf = NULL;
+
+	text = fz_new_stext_page_from_display_list(ctx, list, options);
+	fz_try(ctx)
+		buf = fz_new_buffer_from_stext_page(ctx, text);
+	fz_always(ctx)
+		fz_drop_stext_page(ctx, text);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return buf;
+}
+
+fz_buffer *
+fz_new_buffer_from_page(fz_context *ctx, fz_page *page, const fz_stext_options *options)
+{
+	fz_stext_page *text;
+	fz_buffer *buf = NULL;
+
+	text = fz_new_stext_page_from_page(ctx, page, options);
+	fz_try(ctx)
+		buf = fz_new_buffer_from_stext_page(ctx, text);
+	fz_always(ctx)
+		fz_drop_stext_page(ctx, text);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return buf;
+}
+
+fz_buffer *
+fz_new_buffer_from_page_number(fz_context *ctx, fz_document *doc, int number, const fz_stext_options *options)
+{
+	fz_page *page;
+	fz_buffer *buf = NULL;
+
+	page = fz_load_page(ctx, doc, number);
+	fz_try(ctx)
+		buf = fz_new_buffer_from_page(ctx, page, options);
+	fz_always(ctx)
+		fz_drop_page(ctx, page);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+	return buf;
+}
+
+void
+fz_write_image_as_data_uri(fz_context *ctx, fz_output *out, fz_image *image)
+{
+	fz_compressed_buffer *cbuf;
+	fz_buffer *buf;
+
+	cbuf = fz_compressed_image_buffer(ctx, image);
+
+	if (cbuf && cbuf->params.type == FZ_IMAGE_JPEG)
+	{
+		int type = fz_colorspace_type(ctx, image->colorspace);
+		if (type == FZ_COLORSPACE_GRAY || type == FZ_COLORSPACE_RGB)
+		{
+			fz_write_string(ctx, out, "data:image/jpeg;base64,");
+			fz_write_base64_buffer(ctx, out, cbuf->buffer, 1);
+			return;
+		}
+	}
+	if (cbuf && cbuf->params.type == FZ_IMAGE_PNG)
+	{
+		fz_write_string(ctx, out, "data:image/png;base64,");
+		fz_write_base64_buffer(ctx, out, cbuf->buffer, 1);
+		return;
+	}
+
+	buf = fz_new_buffer_from_image_as_png(ctx, image, fz_default_color_params);
+	fz_try(ctx)
+	{
+		fz_write_string(ctx, out, "data:image/png;base64,");
+		fz_write_base64_buffer(ctx, out, buf, 1);
+	}
+	fz_always(ctx)
+		fz_drop_buffer(ctx, buf);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}
+
+static uint32_t read16(const uint8_t *d, size_t *pos, size_t len, int order)
+{
+	size_t p = *pos;
+	uint32_t v;
+
+	if (p+1 >= len)
+	{
+		*pos = len;
+		return 0;
+	}
+
+	if (order)
+	{
+		v = d[p++]<<8; /* BE */
+		v |= d[p++];
+	}
+	else
+	{
+		v = d[p++]; /* LE */
+		v |= d[p++]<<8;
+	}
+
+	*pos = p;
+
+	return v;
+}
+
+static uint32_t read32(const uint8_t *d, size_t *pos, size_t len, int order)
+{
+	size_t p = *pos;
+	uint32_t v;
+
+	if (p+3 >= len)
+	{
+		*pos = len;
+		return 0;
+	}
+
+	if (order)
+	{
+		v = d[p++]<<24; /* BE */
+		v |= d[p++]<<16;
+		v |= d[p++]<<8;
+		v |= d[p++];
+	}
+	else
+	{
+		v = d[p++];
+		v |= d[p++]<<8; /* LE */
+		v |= d[p++]<<16;
+		v |= d[p++]<<24;
+	}
+
+	*pos = p;
+
+	return v;
+}
+
+static void write16(uint8_t *d, size_t *pos, size_t len, int order, uint32_t v)
+{
+	size_t p = *pos;
+
+	if (p+1 >= len)
+	{
+		*pos = len;
+		return;
+	}
+
+	if (order)
+	{
+		d[p++] = (v>>8);
+		d[p++] = v;
+	}
+	else
+	{
+		d[p++] = v;
+		d[p++] = (v>>8);
+	}
+
+	*pos = p;
+}
+
+static void write32( uint8_t *d, size_t *pos, size_t len, int order, uint32_t v)
+{
+	size_t p = *pos;
+
+	if (p+3 >= len)
+	{
+		*pos = len;
+		return;
+	}
+
+	if (order)
+	{
+		d[p++] = (v>>24);
+		d[p++] = (v>>16);
+		d[p++] = (v>>8);
+		d[p++] = v;
+	}
+	else
+	{
+		d[p++] = v;
+		d[p++] = (v>>8);
+		d[p++] = (v>>16);
+		d[p++] = (v>>24);
+	}
+
+	*pos = p;
+}
+
+fz_buffer *
+fz_sanitize_jpeg_buffer(fz_context *ctx, fz_buffer *in)
+{
+	fz_buffer *out = fz_clone_buffer(ctx, in);
+	size_t len = out->len;
+	size_t pos = 0;
+	uint8_t *d = out->data;
+
+	/* We need at least 4 data bytes. */
+	while (pos+4 < len)
+	{
+		uint8_t m;
+		/* We should be on a marker. If not, inch forwards until we are. */
+		if (d[pos++] != 0xff)
+			continue;
+		m = d[pos++];
+		if (m == 0xDA)
+			break; /* Start Of Scan. All our rewriting happens before this. */
+		if (m == 0xE1)
+		{
+			uint8_t order;
+			uint32_t tmp;
+			size_t body_start;
+			/* APP1 tag. This is where the EXIF data lives. */
+			/* Read and discard the marker length. We're not continuing after this anyway. */
+			(void)read16(d, &pos, len, 0);
+			tmp = read32(d, &pos, len, 0);
+			if (tmp != 0x66697845) /* Exif */
+				break; /* Not exif - nothing to rewrite. */
+			tmp = read16(d, &pos, len, 0);
+			if (tmp != 0) /* Terminator + Pad */
+				break; /* Not exif - nothing to rewrite. */
+			/* Now we're at the APP1 Body. */
+			body_start = pos;
+			tmp = read16(d, &pos, len, 0);
+			if (tmp == 0x4949)
+				order = 0; /* LE */
+			else if (tmp == 0x4d4d)
+				order = 1; /* BE */
+			else
+				break; /* Bad TIFF type. Bale. */
+			tmp = read16(d, &pos, len, order);
+			if (tmp != 0x002a) /* 42 */
+				break; /* Bad version field.  Bale. */
+			do
+			{
+				uint32_t i, n;
+				tmp = read32(d, &pos, len, order);
+				pos = body_start + tmp;
+				if (tmp == 0 || pos >= len)
+					break;
+				n = read16(d, &pos, len, order);
+				for (i = 0; i < n; i++)
+				{
+					if (read16(d, &pos, len, order) == 0x112)
+					{
+						/* Orientation tag! */
+						write16(d, &pos, len, order, 3); /* 3 = short */
+						write32(d, &pos, len, order, 1); /* Count = 1 */
+						write16(d, &pos, len, order, 1); /* Value = 1 */
+						write16(d, &pos, len, order, 0); /* padding */
+						i = n;
+						pos = len; /* Done! */
+					}
+					else
+						pos += 10;
+				}
+			}
+			while (pos+4 < len);
+			break;
+		}
+		else if (m >= 0xD0 && m <= 0xD7)
+		{
+			/* RSTm - no length code. But we shouldn't hit this! */
+		}
+		else if (m == 0x01)
+		{
+			/* TEM - temporary private use in arithmetic coding - shouldn't hit this either. */
+		}
+		else if (m == 0xD8)
+		{
+			/* SOI - start of image. */
+		}
+		else if (m == 0x01)
+		{
+			/* EOI - end of image. */
+		}
+		else
+		{
+			/* All other markers have a length. */
+			size_t marker_len = d[pos]*256 + d[pos+1];
+			pos += marker_len; /* The 2 length bytes are included in the marker_len */
+		}
+	}
+
+	return out;
+}
+
+void
+fz_append_image_as_data_uri(fz_context *ctx, fz_buffer *out, fz_image *image)
+{
+	fz_compressed_buffer *cbuf;
+	fz_buffer *buf;
+	const char *mime;
+
+	cbuf = fz_compressed_image_buffer(ctx, image);
+
+	if (cbuf && cbuf->params.type == FZ_IMAGE_JPEG)
+	{
+		int type = fz_colorspace_type(ctx, image->colorspace);
+		if (type == FZ_COLORSPACE_GRAY || type == FZ_COLORSPACE_RGB)
+		{
+			fz_buffer *new_buf = fz_sanitize_jpeg_buffer(ctx, cbuf->buffer);
+			fz_append_string(ctx, out, "data:image/jpeg;base64,");
+			fz_try(ctx)
+				fz_append_base64_buffer(ctx, out, new_buf, 1);
+			fz_always(ctx)
+				fz_drop_buffer(ctx, new_buf);
+			fz_catch(ctx)
+				fz_rethrow(ctx);
+			return;
+		}
+	}
+
+	if (cbuf && cbuf->params.type == FZ_IMAGE_PNG)
+	{
+		fz_append_string(ctx, out, "data:image/png;base64,");
+		fz_append_base64_buffer(ctx, out, cbuf->buffer, 1);
+		return;
+	}
+
+	if (fz_is_lossy_image(ctx, image))
+	{
+		/* Convert lossy image formats to JPEG */
+		buf = fz_new_buffer_from_image_as_jpeg(ctx, image, fz_default_color_params, 90, 0);
+		mime = "data:image/jpeg;base64,";
+	}
+	else
+	{
+		buf = fz_new_buffer_from_image_as_png(ctx, image, fz_default_color_params);
+		mime = "data:image/png;base64,";
+	}
+
+	fz_try(ctx)
+	{
+		fz_append_string(ctx, out, mime);
+		fz_append_base64_buffer(ctx, out, buf, 1);
+	}
+	fz_always(ctx)
+		fz_drop_buffer(ctx, buf);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}
+
+void
+fz_write_pixmap_as_data_uri(fz_context *ctx, fz_output *out, fz_pixmap *pixmap)
+{
+	fz_buffer *buf = fz_new_buffer_from_pixmap_as_png(ctx, pixmap, fz_default_color_params);
+	fz_try(ctx)
+	{
+		fz_write_string(ctx, out, "data:image/png;base64,");
+		fz_write_base64_buffer(ctx, out, buf, 1);
+	}
+	fz_always(ctx)
+		fz_drop_buffer(ctx, buf);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}
+
+void
+fz_append_pixmap_as_data_uri(fz_context *ctx, fz_buffer *out, fz_pixmap *pixmap)
+{
+	fz_buffer *buf = fz_new_buffer_from_pixmap_as_png(ctx, pixmap, fz_default_color_params);
+	fz_try(ctx)
+	{
+		fz_append_string(ctx, out, "data:image/png;base64,");
+		fz_append_base64_buffer(ctx, out, buf, 1);
+	}
+	fz_always(ctx)
+		fz_drop_buffer(ctx, buf);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}
+
+fz_document *
+fz_new_xhtml_document_from_document(fz_context *ctx, fz_document *old_doc, const fz_stext_options *opts)
+{
+	fz_stext_options default_opts = { FZ_STEXT_PRESERVE_IMAGES | FZ_STEXT_DEHYPHENATE };
+	fz_document *new_doc;
+	fz_buffer *buf = NULL;
+	fz_output *out = NULL;
+	fz_stream *stm = NULL;
+	fz_stext_page *text = NULL;
+	int i;
+
+	fz_var(buf);
+	fz_var(out);
+	fz_var(stm);
+	fz_var(text);
+
+	if (!opts)
+		opts = &default_opts;
+
+	fz_try(ctx)
+	{
+		buf = fz_new_buffer(ctx, 8192);
+		out = fz_new_output_with_buffer(ctx, buf);
+		fz_print_stext_header_as_xhtml(ctx, out);
+
+		for (i = 0; i < fz_count_pages(ctx, old_doc); ++i)
+		{
+			text = fz_new_stext_page_from_page_number(ctx, old_doc, i, opts);
+			fz_print_stext_page_as_xhtml(ctx, out, text, i+1);
+			fz_drop_stext_page(ctx, text);
+			text = NULL;
+		}
+
+		fz_print_stext_trailer_as_xhtml(ctx, out);
+		fz_close_output(ctx, out);
+		fz_terminate_buffer(ctx, buf);
+
+		stm = fz_open_buffer(ctx, buf);
+		new_doc = fz_open_document_with_stream(ctx, "application/xhtml+xml", stm);
+	}
+	fz_always(ctx)
+	{
+		fz_drop_stream(ctx, stm);
+		fz_drop_buffer(ctx, buf);
+		fz_drop_output(ctx, out);
+		fz_drop_stext_page(ctx, text);
+	}
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+
+	return new_doc;
+}
+
+fz_buffer *
+fz_new_buffer_from_page_with_format(fz_context *ctx, fz_page *page, const char *format, const char *options, fz_matrix transform, fz_cookie *cookie)
+{
+	fz_buffer *buf = NULL;
+	fz_output *out;
+	fz_document_writer *writer = NULL;
+	fz_device *dev = NULL;
+
+	fz_var(buf);
+	fz_var(writer);
+	fz_var(dev);
+
+	fz_try(ctx)
+	{
+		buf = fz_new_buffer(ctx, 0);
+		out = fz_new_output_with_buffer(ctx, buf);
+		writer = fz_new_document_writer_with_output(ctx, out, format, options);
+		dev = fz_begin_page(ctx, writer, fz_bound_page(ctx, page));
+		fz_run_page(ctx, page, dev, transform, cookie);
+		fz_end_page(ctx, writer);
+		fz_close_document_writer(ctx, writer);
+	}
+	fz_always(ctx)
+		fz_drop_document_writer(ctx, writer);
+	fz_catch(ctx)
+	{
+		fz_drop_buffer(ctx, buf);
+		fz_rethrow(ctx);
+	}
+	return buf;
+}