diff mupdf-source/source/pdf/pdf-run.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/pdf/pdf-run.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,694 @@
+// Copyright (C) 2004-2025 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 "pdf-annot-imp.h"
+
+static void
+pdf_run_annot_with_usage(fz_context *ctx, pdf_document *doc, pdf_page *page, pdf_annot *annot, fz_device *dev, fz_matrix ctm, const char *usage, fz_cookie *cookie)
+{
+	fz_matrix page_ctm;
+	fz_rect mediabox;
+	pdf_processor *proc = NULL;
+	fz_default_colorspaces *default_cs = NULL;
+	int flags;
+	int resources_pushed = 0;
+	int struct_parent_num;
+	pdf_obj *struct_parent;
+
+	fz_var(proc);
+	fz_var(default_cs);
+	fz_var(resources_pushed);
+
+	if (cookie && page->super.incomplete)
+		cookie->incomplete = 1;
+
+	pdf_annot_push_local_xref(ctx, annot);
+
+	/* Widgets only get displayed if they have both a T and a TF flag,
+	 * apparently */
+	if (pdf_name_eq(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype)), PDF_NAME(Widget)))
+	{
+		pdf_obj *ft = pdf_dict_get_inheritable(ctx, annot->obj, PDF_NAME(FT));
+		pdf_obj *t = pdf_dict_get_inheritable(ctx, annot->obj, PDF_NAME(T));
+
+		if (ft == NULL || t == NULL)
+		{
+			pdf_annot_pop_local_xref(ctx, annot);
+			return;
+		}
+	}
+
+	fz_try(ctx)
+	{
+		default_cs = pdf_load_default_colorspaces(ctx, doc, page);
+		if (default_cs)
+			fz_set_default_colorspaces(ctx, dev, default_cs);
+
+		pdf_page_transform(ctx, page, &mediabox, &page_ctm);
+
+		flags = pdf_dict_get_int(ctx, annot->obj, PDF_NAME(F));
+		if (flags & PDF_ANNOT_IS_NO_ROTATE)
+		{
+			int rotate = pdf_dict_get_inheritable_int(ctx, page->obj, PDF_NAME(Rotate));
+			fz_rect rect = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
+			fz_point tp = fz_transform_point_xy(rect.x0, rect.y1, page_ctm);
+			page_ctm = fz_concat(page_ctm, fz_translate(-tp.x, -tp.y));
+			page_ctm = fz_concat(page_ctm, fz_rotate(-rotate));
+			page_ctm = fz_concat(page_ctm, fz_translate(tp.x, tp.y));
+		}
+
+		ctm = fz_concat(page_ctm, ctm);
+
+		struct_parent = pdf_dict_getl(ctx, page->obj, PDF_NAME(StructParent), NULL);
+		struct_parent_num = pdf_to_int_default(ctx, struct_parent, -1);
+
+		proc = pdf_new_run_processor(ctx, page->doc, dev, ctm, struct_parent_num, usage, NULL, default_cs, cookie, NULL, NULL);
+		pdf_processor_push_resources(ctx, proc, pdf_page_resources(ctx, annot->page));
+		resources_pushed = 1;
+		pdf_process_annot(ctx, proc, annot, cookie);
+		pdf_close_processor(ctx, proc);
+	}
+	fz_always(ctx)
+	{
+		if (resources_pushed)
+			pdf_processor_pop_resources(ctx, proc);
+		pdf_drop_processor(ctx, proc);
+		fz_drop_default_colorspaces(ctx, default_cs);
+		pdf_annot_pop_local_xref(ctx, annot);
+	}
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}
+
+static fz_rect pdf_page_cropbox(fz_context *ctx, pdf_page *page)
+{
+	pdf_obj *obj = pdf_dict_get_inheritable(ctx, page->obj, PDF_NAME(CropBox));
+	if (!obj)
+		obj = pdf_dict_get_inheritable(ctx, page->obj, PDF_NAME(MediaBox));
+	return pdf_to_rect(ctx, obj);
+}
+
+static fz_rect pdf_page_mediabox(fz_context *ctx, pdf_page *page)
+{
+	return pdf_dict_get_inheritable_rect(ctx, page->obj, PDF_NAME(MediaBox));
+}
+
+static void
+pdf_run_page_contents_with_usage_imp(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_device *dev, fz_matrix ctm, const char *usage, fz_cookie *cookie)
+{
+	fz_matrix page_ctm;
+	pdf_obj *resources;
+	pdf_obj *contents;
+	fz_rect fitzbox;
+	fz_rect mediabox, cropbox;
+	pdf_processor *proc = NULL;
+	fz_default_colorspaces *default_cs = NULL;
+	fz_colorspace *colorspace = NULL;
+	fz_path *path = NULL;
+	int struct_parent_num;
+	pdf_obj *struct_parent;
+
+	fz_var(proc);
+	fz_var(colorspace);
+	fz_var(default_cs);
+	fz_var(path);
+
+	if (cookie && page->super.incomplete)
+		cookie->incomplete = 1;
+
+	fz_try(ctx)
+	{
+		default_cs = pdf_load_default_colorspaces(ctx, doc, page);
+		if (default_cs)
+			fz_set_default_colorspaces(ctx, dev, default_cs);
+
+		pdf_page_transform(ctx, page, &fitzbox, &page_ctm);
+		ctm = fz_concat(page_ctm, ctm);
+		fitzbox = fz_transform_rect(fitzbox, ctm);
+
+		resources = pdf_page_resources(ctx, page);
+		contents = pdf_page_contents(ctx, page);
+
+		mediabox = pdf_page_mediabox(ctx, page);
+		cropbox = pdf_page_cropbox(ctx, page);
+
+		if (page->transparency)
+		{
+			pdf_obj *group = pdf_page_group(ctx, page);
+
+			if (group)
+			{
+				pdf_obj *cs = pdf_dict_get(ctx, group, PDF_NAME(CS));
+				if (cs)
+				{
+					fz_try(ctx)
+						colorspace = pdf_load_colorspace(ctx, cs);
+					fz_catch(ctx)
+					{
+						fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
+						fz_rethrow_if(ctx, FZ_ERROR_SYSTEM);
+						fz_report_error(ctx);
+						fz_warn(ctx, "Ignoring Page blending colorspace.");
+					}
+					if (!fz_is_valid_blend_colorspace(ctx, colorspace))
+					{
+						fz_warn(ctx, "Ignoring invalid Page blending colorspace: %s.", colorspace->name);
+						fz_drop_colorspace(ctx, colorspace);
+						colorspace = NULL;
+					}
+				}
+			}
+			else
+				colorspace = fz_keep_colorspace(ctx, fz_default_output_intent(ctx, default_cs));
+
+			fz_begin_group(ctx, dev, fitzbox, colorspace, 1, 0, 0, 1);
+		}
+
+		struct_parent = pdf_dict_get(ctx, page->obj, PDF_NAME(StructParents));
+		struct_parent_num = pdf_to_int_default(ctx, struct_parent, -1);
+
+		/* Clip content to CropBox if it is smaller than the MediaBox */
+		if (cropbox.x0 > mediabox.x0 || cropbox.x1 < mediabox.x1 || cropbox.y0 > mediabox.y0 || cropbox.y1 < mediabox.y1)
+		{
+			path = fz_new_path(ctx);
+			fz_rectto(ctx, path, cropbox.x0, cropbox.y0, cropbox.x1, cropbox.y1);
+			fz_clip_path(ctx, dev, path, 1, ctm, fz_infinite_rect);
+		}
+
+		proc = pdf_new_run_processor(ctx, page->doc, dev, ctm, struct_parent_num, usage, NULL, default_cs, cookie, NULL, NULL);
+		pdf_process_contents(ctx, proc, doc, resources, contents, cookie, NULL);
+		pdf_close_processor(ctx, proc);
+
+		if (cropbox.x0 > mediabox.x0 || cropbox.x1 < mediabox.x1 || cropbox.y0 > mediabox.y0 || cropbox.y1 < mediabox.y1)
+		{
+			fz_pop_clip(ctx, dev);
+		}
+
+		if (page->transparency)
+		{
+			fz_end_group(ctx, dev);
+		}
+	}
+	fz_always(ctx)
+	{
+		fz_drop_path(ctx, path);
+		pdf_drop_processor(ctx, proc);
+		fz_drop_colorspace(ctx, colorspace);
+		fz_drop_default_colorspaces(ctx, default_cs);
+	}
+	fz_catch(ctx)
+	{
+		fz_rethrow(ctx);
+	}
+}
+
+void pdf_run_page_contents_with_usage(fz_context *ctx, pdf_page *page, fz_device *dev, fz_matrix ctm, const char *usage, fz_cookie *cookie)
+{
+	pdf_document *doc = page->doc;
+	int nocache;
+
+	nocache = !!(dev->hints & FZ_NO_CACHE);
+	if (nocache)
+		pdf_mark_xref(ctx, doc);
+
+	fz_try(ctx)
+	{
+		pdf_run_page_contents_with_usage_imp(ctx, doc, page, dev, ctm, usage, cookie);
+	}
+	fz_always(ctx)
+	{
+		if (nocache)
+			pdf_clear_xref_to_mark(ctx, doc);
+	}
+	fz_catch(ctx)
+	{
+		fz_rethrow(ctx);
+	}
+}
+
+void pdf_run_page_contents(fz_context *ctx, pdf_page *page, fz_device *dev, fz_matrix ctm, fz_cookie *cookie)
+{
+	pdf_run_page_contents_with_usage(ctx, page, dev, ctm, "View", cookie);
+}
+
+void pdf_run_annot(fz_context *ctx, pdf_annot *annot, fz_device *dev, fz_matrix ctm, fz_cookie *cookie)
+{
+	pdf_page *page = annot->page;
+	pdf_document *doc;
+	int nocache;
+
+	if (!page)
+		fz_throw(ctx, FZ_ERROR_ARGUMENT, "annotation not bound to any page");
+
+	doc = page->doc;
+
+	nocache = !!(dev->hints & FZ_NO_CACHE);
+	if (nocache)
+		pdf_mark_xref(ctx, doc);
+	fz_try(ctx)
+	{
+		pdf_run_annot_with_usage(ctx, doc, page, annot, dev, ctm, "View", cookie);
+	}
+	fz_always(ctx)
+	{
+		if (nocache)
+			pdf_clear_xref_to_mark(ctx, doc);
+	}
+	fz_catch(ctx)
+	{
+		fz_rethrow(ctx);
+	}
+}
+
+static void
+pdf_run_page_widgets_with_usage_imp(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_device *dev, fz_matrix ctm, const char *usage, fz_cookie *cookie)
+{
+	pdf_annot *widget;
+
+	if (cookie && cookie->progress_max != (size_t)-1)
+	{
+		int count = 1;
+		for (widget = page->widgets; widget; widget = widget->next)
+			count++;
+		cookie->progress_max += count;
+	}
+
+	for (widget = page->widgets; widget; widget = widget->next)
+	{
+		/* Check the cookie for aborting */
+		if (cookie)
+		{
+			if (cookie->abort)
+				break;
+			cookie->progress++;
+		}
+
+		pdf_run_annot_with_usage(ctx, doc, page, widget, dev, ctm, usage, cookie);
+	}
+}
+
+static void
+pdf_run_page_annots_with_usage_imp(fz_context *ctx, pdf_document *doc, pdf_page *page, fz_device *dev, fz_matrix ctm, const char *usage, fz_cookie *cookie)
+{
+	pdf_annot *annot;
+
+	if (cookie && cookie->progress_max != (size_t)-1)
+	{
+		int count = 1;
+		for (annot = page->annots; annot; annot = annot->next)
+			count++;
+		cookie->progress_max += count;
+	}
+
+	for (annot = page->annots; annot; annot = annot->next)
+	{
+		/* Check the cookie for aborting */
+		if (cookie)
+		{
+			if (cookie->abort)
+				break;
+			cookie->progress++;
+		}
+
+		pdf_run_annot_with_usage(ctx, doc, page, annot, dev, ctm, usage, cookie);
+	}
+}
+
+void pdf_run_page_annots_with_usage(fz_context *ctx, pdf_page *page, fz_device *dev, fz_matrix ctm, const char *usage, fz_cookie *cookie)
+{
+	pdf_document *doc = page->doc;
+	int nocache;
+
+	nocache = !!(dev->hints & FZ_NO_CACHE);
+	if (nocache)
+		pdf_mark_xref(ctx, doc);
+
+	fz_try(ctx)
+	{
+		pdf_run_page_annots_with_usage_imp(ctx, doc, page, dev, ctm, usage, cookie);
+	}
+	fz_always(ctx)
+	{
+		if (nocache)
+			pdf_clear_xref_to_mark(ctx, doc);
+	}
+	fz_catch(ctx)
+	{
+		fz_rethrow(ctx);
+	}
+}
+
+void pdf_run_page_annots(fz_context *ctx, pdf_page *page, fz_device *dev, fz_matrix ctm, fz_cookie *cookie)
+{
+	pdf_run_page_annots_with_usage(ctx, page, dev, ctm, "View", cookie);
+}
+
+void pdf_run_page_widgets_with_usage(fz_context *ctx, pdf_page *page, fz_device *dev, fz_matrix ctm, const char *usage, fz_cookie *cookie)
+{
+	pdf_document *doc = page->doc;
+	int nocache;
+
+	nocache = !!(dev->hints & FZ_NO_CACHE);
+	if (nocache)
+		pdf_mark_xref(ctx, doc);
+
+	fz_try(ctx)
+	{
+		pdf_run_page_widgets_with_usage_imp(ctx, doc, page, dev, ctm, usage, cookie);
+	}
+	fz_always(ctx)
+	{
+		if (nocache)
+			pdf_clear_xref_to_mark(ctx, doc);
+	}
+	fz_catch(ctx)
+	{
+		fz_rethrow(ctx);
+	}
+}
+
+void pdf_run_page_widgets(fz_context *ctx, pdf_page *page, fz_device *dev, fz_matrix ctm, fz_cookie *cookie)
+{
+	pdf_run_page_widgets_with_usage(ctx, page, dev, ctm, "View", cookie);
+}
+
+void
+pdf_run_page_with_usage(fz_context *ctx, pdf_page *page, fz_device *dev, fz_matrix ctm, const char *usage, fz_cookie *cookie)
+{
+	pdf_document *doc = page->doc;
+	int nocache = !!(dev->hints & FZ_NO_CACHE);
+
+	if (nocache)
+		pdf_mark_xref(ctx, doc);
+	fz_try(ctx)
+	{
+		pdf_run_page_contents_with_usage_imp(ctx, doc, page, dev, ctm, usage, cookie);
+		pdf_run_page_annots_with_usage_imp(ctx, doc, page, dev, ctm, usage, cookie);
+		pdf_run_page_widgets_with_usage_imp(ctx, doc, page, dev, ctm, usage, cookie);
+	}
+	fz_always(ctx)
+	{
+		if (nocache)
+			pdf_clear_xref_to_mark(ctx, doc);
+	}
+	fz_catch(ctx)
+	{
+		fz_rethrow(ctx);
+	}
+}
+
+void
+pdf_run_page(fz_context *ctx, pdf_page *page, fz_device *dev, fz_matrix ctm, fz_cookie *cookie)
+{
+	pdf_run_page_with_usage(ctx, page, dev, ctm, "View", cookie);
+}
+
+void
+pdf_run_glyph(fz_context *ctx, pdf_document *doc, pdf_obj *resources, fz_buffer *contents, fz_device *dev, fz_matrix ctm, void *gstate, fz_default_colorspaces *default_cs, void *fill_gstate, void *stroke_gstate)
+{
+	pdf_processor *proc;
+
+	proc = pdf_new_run_processor(ctx, doc, dev, ctm, -1, "View", gstate, default_cs, NULL, fill_gstate, stroke_gstate);
+	fz_try(ctx)
+	{
+		pdf_process_glyph(ctx, proc, doc, resources, contents);
+		pdf_close_processor(ctx, proc);
+	}
+	fz_always(ctx)
+		pdf_drop_processor(ctx, proc);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}
+
+fz_structure
+pdf_structure_type(fz_context *ctx, pdf_obj *role_map, pdf_obj *tag)
+{
+	/* Perform Structure mapping to go from tag to standard. */
+	if (role_map)
+	{
+		pdf_obj *o = pdf_dict_get(ctx, role_map, tag);
+		if (o)
+			tag = o;
+	}
+
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Document)))
+		return FZ_STRUCTURE_DOCUMENT;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Part)))
+		return FZ_STRUCTURE_PART;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Art)))
+		return FZ_STRUCTURE_ART;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Sect)))
+		return FZ_STRUCTURE_SECT;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Div)))
+		return FZ_STRUCTURE_DIV;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(BlockQuote)))
+		return FZ_STRUCTURE_BLOCKQUOTE;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Caption)))
+		return FZ_STRUCTURE_CAPTION;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(TOC)))
+		return FZ_STRUCTURE_TOC;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(TOCI)))
+		return FZ_STRUCTURE_TOCI;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Index)))
+		return FZ_STRUCTURE_INDEX;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(NonStruct)))
+		return FZ_STRUCTURE_NONSTRUCT;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Private)))
+		return FZ_STRUCTURE_PRIVATE;
+	/* Grouping elements (PDF 2.0 - Table 364) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(DocumentFragment)))
+		return FZ_STRUCTURE_DOCUMENTFRAGMENT;
+	/* Grouping elements (PDF 2.0 - Table 365) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Aside)))
+		return FZ_STRUCTURE_ASIDE;
+	/* Grouping elements (PDF 2.0 - Table 366) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Title)))
+		return FZ_STRUCTURE_TITLE;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(FENote)))
+		return FZ_STRUCTURE_FENOTE;
+	/* Grouping elements (PDF 2.0 - Table 367) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Sub)))
+		return FZ_STRUCTURE_SUB;
+
+	/* Paragraphlike elements (PDF 1.7 - Table 10.21) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(P)))
+		return FZ_STRUCTURE_P;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(H)))
+		return FZ_STRUCTURE_H;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(H1)))
+		return FZ_STRUCTURE_H1;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(H2)))
+		return FZ_STRUCTURE_H2;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(H3)))
+		return FZ_STRUCTURE_H3;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(H4)))
+		return FZ_STRUCTURE_H4;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(H5)))
+		return FZ_STRUCTURE_H5;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(H6)))
+		return FZ_STRUCTURE_H6;
+
+	/* List elements (PDF 1.7 - Table 10.23) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(L)))
+		return FZ_STRUCTURE_LIST;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(LI)))
+		return FZ_STRUCTURE_LISTITEM;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Lbl)))
+		return FZ_STRUCTURE_LABEL;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(LBody)))
+		return FZ_STRUCTURE_LISTBODY;
+
+	/* Table elements (PDF 1.7 - Table 10.24) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Table)))
+		return FZ_STRUCTURE_TABLE;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(TR)))
+		return FZ_STRUCTURE_TR;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(TH)))
+		return FZ_STRUCTURE_TH;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(TD)))
+		return FZ_STRUCTURE_TD;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(THead)))
+		return FZ_STRUCTURE_THEAD;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(TBody)))
+		return FZ_STRUCTURE_TBODY;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(TFoot)))
+		return FZ_STRUCTURE_TFOOT;
+
+	/* Inline elements (PDF 1.7 - Table 10.25) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Span)))
+		return FZ_STRUCTURE_SPAN;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Quote)))
+		return FZ_STRUCTURE_QUOTE;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Note)))
+		return FZ_STRUCTURE_NOTE;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Reference)))
+		return FZ_STRUCTURE_REFERENCE;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(BibEntry)))
+		return FZ_STRUCTURE_BIBENTRY;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Code)))
+		return FZ_STRUCTURE_CODE;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Link)))
+		return FZ_STRUCTURE_LINK;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Annot)))
+		return FZ_STRUCTURE_ANNOT;
+	/* Inline elements (PDF 2.0 - Table 368) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Em)))
+		return FZ_STRUCTURE_EM;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Strong)))
+		return FZ_STRUCTURE_STRONG;
+
+	/* Ruby inline element (PDF 1.7 - Table 10.26) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Ruby)))
+		return FZ_STRUCTURE_RUBY;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(RB)))
+		return FZ_STRUCTURE_RB;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(RT)))
+		return FZ_STRUCTURE_RT;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(RP)))
+		return FZ_STRUCTURE_RP;
+
+	/* Warichu inline element (PDF 1.7 - Table 10.26) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Warichu)))
+		return FZ_STRUCTURE_WARICHU;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(WT)))
+		return FZ_STRUCTURE_WT;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(WP)))
+		return FZ_STRUCTURE_WP;
+
+	/* Illustration elements (PDF 1.7 - Table 10.27) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Figure)))
+		return FZ_STRUCTURE_FIGURE;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Formula)))
+		return FZ_STRUCTURE_FORMULA;
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Form)))
+		return FZ_STRUCTURE_FORM;
+
+	/* Artifact structure type (PDF 2.0 - Table 375) */
+	if (pdf_name_eq(ctx, tag, PDF_NAME(Artifact)))
+		return FZ_STRUCTURE_ARTIFACT;
+
+	return FZ_STRUCTURE_INVALID;
+}
+
+/* The recursive descent of the structure tree uses an fz_try at each level.
+ * At the risk of creating a foot cannon... "no one will need more than ~64
+ * levels of structure tree". */
+static void
+run_ds(fz_context *ctx, fz_device *dev, pdf_obj *role_map, pdf_obj *obj, int idx, fz_cookie *cookie)
+{
+	pdf_obj *k;
+	int i, n;
+
+	/* Check the cookie for aborting */
+	if (cookie)
+	{
+		if (cookie->abort)
+			return;
+		cookie->progress++;
+	}
+
+	if (pdf_is_number(ctx, obj))
+	{
+		/* A marked-content identifier denoting a marked content sequence. WHAT? */
+		return;
+	}
+
+	if (pdf_mark_obj(ctx, obj))
+		return;
+
+	fz_try(ctx)
+	{
+		fz_structure standard;
+		pdf_obj *tag = pdf_dict_get(ctx, obj, PDF_NAME(S));
+		if (!tag)
+			break;
+
+		standard = pdf_structure_type(ctx, role_map, tag);
+		if (standard == FZ_STRUCTURE_INVALID)
+			break;
+		fz_begin_structure(ctx, dev, standard, pdf_to_name(ctx, tag), idx);
+		k = pdf_dict_get(ctx, obj, PDF_NAME(K));
+		if (k)
+		{
+			n = pdf_array_len(ctx, k);
+			if (n == 0)
+				run_ds(ctx, dev, role_map, k, 0, cookie);
+			else
+			{
+				for (i = 0; i < n; i++)
+					run_ds(ctx, dev, role_map, pdf_array_get(ctx, k, i), i, cookie);
+			}
+		}
+		fz_end_structure(ctx, dev);
+	}
+	fz_always(ctx)
+		pdf_unmark_obj(ctx, obj);
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}
+
+void pdf_run_document_structure(fz_context *ctx, pdf_document *doc, fz_device *dev, fz_cookie *cookie)
+{
+	int nocache;
+	int marked = 0;
+	pdf_obj *st, *rm, *k;
+
+	fz_var(marked);
+
+	nocache = !!(dev->hints & FZ_NO_CACHE);
+	if (nocache)
+		pdf_mark_xref(ctx, doc);
+
+	fz_try(ctx)
+	{
+		st = pdf_dict_get(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Root)), PDF_NAME(StructTreeRoot));
+		rm = pdf_dict_get(ctx, st, PDF_NAME(RoleMap));
+
+		if (pdf_mark_obj(ctx, st))
+			break;
+		marked = 1;
+
+		k = pdf_dict_get(ctx, st, PDF_NAME(K));
+		if (k)
+		{
+			int n = pdf_array_len(ctx, k);
+			if (n == 0)
+				run_ds(ctx, dev, rm, k, 0, cookie);
+			else
+			{
+				int i;
+				for (i = 0; i < n; i++)
+					run_ds(ctx, dev, rm, pdf_array_get(ctx, k, i), i, cookie);
+			}
+		}
+	}
+	fz_always(ctx)
+	{
+		if (marked)
+			pdf_unmark_obj(ctx, st);
+		if (nocache)
+			pdf_clear_xref_to_mark(ctx, doc);
+	}
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}