diff mupdf-source/source/xps/xps-common.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/xps/xps-common.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,374 @@
+// Copyright (C) 2004-2021 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 "xps-imp.h"
+
+#include <string.h>
+#include <stdio.h> /* for sscanf */
+#include <math.h> /* for pow */
+
+static inline int unhex(int a)
+{
+	if (a >= 'A' && a <= 'F') return a - 'A' + 0xA;
+	if (a >= 'a' && a <= 'f') return a - 'a' + 0xA;
+	if (a >= '0' && a <= '9') return a - '0';
+	return 0;
+}
+
+fz_xml *
+xps_lookup_alternate_content(fz_context *ctx, xps_document *doc, fz_xml *node)
+{
+	for (node = fz_xml_down(node); node; node = fz_xml_next(node))
+	{
+		if (fz_xml_is_tag(node, "Choice") && fz_xml_att(node, "Requires"))
+		{
+			char list[64];
+			char *next = list, *item;
+			fz_strlcpy(list, fz_xml_att(node, "Requires"), sizeof(list));
+			while ((item = fz_strsep(&next, " \t\r\n")) != NULL && (!*item || !strcmp(item, "xps")));
+			if (!item)
+				return fz_xml_down(node);
+		}
+		else if (fz_xml_is_tag(node, "Fallback"))
+			return fz_xml_down(node);
+	}
+	return NULL;
+}
+
+void
+xps_parse_brush(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, fz_xml *node)
+{
+	if (doc->cookie && doc->cookie->abort)
+		return;
+	/* SolidColorBrushes are handled in a special case and will never show up here */
+	if (fz_xml_is_tag(node, "ImageBrush"))
+		xps_parse_image_brush(ctx, doc, ctm, area, base_uri, dict, node);
+	else if (fz_xml_is_tag(node, "VisualBrush"))
+		xps_parse_visual_brush(ctx, doc, ctm, area, base_uri, dict, node);
+	else if (fz_xml_is_tag(node, "LinearGradientBrush"))
+		xps_parse_linear_gradient_brush(ctx, doc, ctm, area, base_uri, dict, node);
+	else if (fz_xml_is_tag(node, "RadialGradientBrush"))
+		xps_parse_radial_gradient_brush(ctx, doc, ctm, area, base_uri, dict, node);
+	else
+		fz_warn(ctx, "unknown brush tag");
+}
+
+void
+xps_parse_element(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, fz_xml *node)
+{
+	if (doc->cookie && doc->cookie->abort)
+		return;
+	if (fz_xml_is_tag(node, "Path"))
+		xps_parse_path(ctx, doc, ctm, base_uri, dict, node);
+	if (fz_xml_is_tag(node, "Glyphs"))
+		xps_parse_glyphs(ctx, doc, ctm, base_uri, dict, node);
+	if (fz_xml_is_tag(node, "Canvas"))
+		xps_parse_canvas(ctx, doc, ctm, area, base_uri, dict, node);
+	if (fz_xml_is_tag(node, "AlternateContent"))
+	{
+		node = xps_lookup_alternate_content(ctx, doc, node);
+		if (node)
+			xps_parse_element(ctx, doc, ctm, area, base_uri, dict, node);
+	}
+	/* skip unknown tags (like Foo.Resources and similar) */
+}
+
+void
+xps_begin_opacity(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
+	char *base_uri, xps_resource *dict,
+	char *opacity_att, fz_xml *opacity_mask_tag)
+{
+	fz_device *dev = doc->dev;
+	float opacity;
+
+	if (!opacity_att && !opacity_mask_tag)
+		return;
+
+	opacity = 1;
+	if (opacity_att)
+		opacity = fz_atof(opacity_att);
+
+	if (fz_xml_is_tag(opacity_mask_tag, "SolidColorBrush"))
+	{
+		char *scb_opacity_att = fz_xml_att(opacity_mask_tag, "Opacity");
+		char *scb_color_att = fz_xml_att(opacity_mask_tag, "Color");
+		if (scb_opacity_att)
+			opacity = opacity * fz_atof(scb_opacity_att);
+		if (scb_color_att)
+		{
+			fz_colorspace *colorspace;
+			float samples[FZ_MAX_COLORS];
+			xps_parse_color(ctx, doc, base_uri, scb_color_att, &colorspace, samples);
+			opacity = opacity * samples[0];
+		}
+		opacity_mask_tag = NULL;
+	}
+
+	if (doc->opacity_top + 1 < (int)nelem(doc->opacity))
+	{
+		doc->opacity[doc->opacity_top + 1] = doc->opacity[doc->opacity_top] * opacity;
+		doc->opacity_top++;
+	}
+
+	if (opacity_mask_tag)
+	{
+		fz_begin_mask(ctx, dev, area, 0, NULL, NULL, fz_default_color_params);
+		xps_parse_brush(ctx, doc, ctm, area, base_uri, dict, opacity_mask_tag);
+		fz_end_mask(ctx, dev);
+	}
+}
+
+void
+xps_end_opacity(fz_context *ctx, xps_document *doc, char *base_uri, xps_resource *dict,
+	char *opacity_att, fz_xml *opacity_mask_tag)
+{
+	fz_device *dev = doc->dev;
+
+	if (!opacity_att && !opacity_mask_tag)
+		return;
+
+	if (doc->opacity_top > 0)
+		doc->opacity_top--;
+
+	if (opacity_mask_tag)
+	{
+		if (!fz_xml_is_tag(opacity_mask_tag, "SolidColorBrush"))
+			fz_pop_clip(ctx, dev);
+	}
+}
+
+static fz_matrix
+xps_parse_render_transform(fz_context *ctx, xps_document *doc, char *transform)
+{
+	fz_matrix matrix;
+	float args[6];
+	char *s = transform;
+	int i;
+
+	args[0] = 1; args[1] = 0;
+	args[2] = 0; args[3] = 1;
+	args[4] = 0; args[5] = 0;
+
+	for (i = 0; i < 6 && *s; i++)
+	{
+		args[i] = fz_atof(s);
+		while (*s && *s != ',')
+			s++;
+		if (*s == ',')
+			s++;
+	}
+
+	matrix.a = args[0]; matrix.b = args[1];
+	matrix.c = args[2]; matrix.d = args[3];
+	matrix.e = args[4]; matrix.f = args[5];
+	return matrix;
+}
+
+static fz_matrix
+xps_parse_matrix_transform(fz_context *ctx, xps_document *doc, fz_xml *root)
+{
+	if (fz_xml_is_tag(root, "MatrixTransform"))
+	{
+		char *transform = fz_xml_att(root, "Matrix");
+		if (transform)
+			return xps_parse_render_transform(ctx, doc, transform);
+	}
+	return fz_identity;
+}
+
+fz_matrix
+xps_parse_transform(fz_context *ctx, xps_document *doc, char *att, fz_xml *tag, fz_matrix ctm)
+{
+	if (att)
+		return fz_concat(xps_parse_render_transform(ctx, doc, att), ctm);
+	if (tag)
+		return fz_concat(xps_parse_matrix_transform(ctx, doc, tag), ctm);
+	return ctm;
+}
+
+fz_rect
+xps_parse_rectangle(fz_context *ctx, xps_document *doc, char *text)
+{
+	fz_rect rect;
+	float args[4];
+	char *s = text;
+	int i;
+
+	args[0] = 0; args[1] = 0;
+	args[2] = 1; args[3] = 1;
+
+	for (i = 0; i < 4 && *s; i++)
+	{
+		args[i] = fz_atof(s);
+		while (*s && *s != ',')
+			s++;
+		if (*s == ',')
+			s++;
+	}
+
+	rect.x0 = args[0];
+	rect.y0 = args[1];
+	rect.x1 = args[0] + args[2];
+	rect.y1 = args[1] + args[3];
+	return rect;
+}
+
+static int count_commas(char *s)
+{
+	int n = 0;
+	while (*s)
+	{
+		if (*s == ',')
+			n ++;
+		s ++;
+	}
+	return n;
+}
+
+static float sRGB_from_scRGB(float x)
+{
+	if (x < 0.0031308f)
+		return 12.92f * x;
+	return 1.055f * powf(x, 1/2.4f) - 0.055f;
+}
+
+void
+xps_parse_color(fz_context *ctx, xps_document *doc, char *base_uri, char *string,
+		fz_colorspace **csp, float *samples)
+{
+	char *p;
+	int i, n;
+	char buf[1024];
+	char *profile;
+
+	*csp = fz_device_rgb(ctx);
+
+	samples[0] = 1;
+	samples[1] = 0;
+	samples[2] = 0;
+	samples[3] = 0;
+
+	if (string[0] == '#')
+	{
+		size_t z = strlen(string);
+		if (z == 9)
+		{
+			samples[0] = unhex(string[1]) * 16 + unhex(string[2]);
+			samples[1] = unhex(string[3]) * 16 + unhex(string[4]);
+			samples[2] = unhex(string[5]) * 16 + unhex(string[6]);
+			samples[3] = unhex(string[7]) * 16 + unhex(string[8]);
+		}
+		else
+		{
+			samples[0] = 255;
+/* Use a macro to protect against overrunning the string. */
+#define UNHEX(idx) (idx < z ? unhex(string[idx]) : 0)
+			samples[1] = UNHEX(1) * 16 + UNHEX(2);
+			samples[2] = UNHEX(3) * 16 + UNHEX(4);
+			samples[3] = UNHEX(5) * 16 + UNHEX(6);
+#undef UNHEX
+		}
+
+		samples[0] /= 255;
+		samples[1] /= 255;
+		samples[2] /= 255;
+		samples[3] /= 255;
+	}
+
+	else if (string[0] == 's' && string[1] == 'c' && string[2] == '#')
+	{
+		if (count_commas(string) == 2)
+			sscanf(string, "sc#%g,%g,%g", samples + 1, samples + 2, samples + 3);
+		if (count_commas(string) == 3)
+			sscanf(string, "sc#%g,%g,%g,%g", samples, samples + 1, samples + 2, samples + 3);
+
+		/* Convert from scRGB gamma 1.0 to sRGB gamma */
+		samples[1] = sRGB_from_scRGB(samples[1]);
+		samples[2] = sRGB_from_scRGB(samples[2]);
+		samples[3] = sRGB_from_scRGB(samples[3]);
+	}
+
+	else if (strstr(string, "ContextColor ") == string)
+	{
+		/* Crack the string for profile name and sample values */
+		fz_strlcpy(buf, string, sizeof buf);
+
+		profile = strchr(buf, ' ');
+		if (!profile)
+		{
+			fz_warn(ctx, "cannot find icc profile uri in '%s'", string);
+			return;
+		}
+
+		*profile++ = 0;
+		p = strchr(profile, ' ');
+		if (!p)
+		{
+			fz_warn(ctx, "cannot find component values in '%s'", profile);
+			return;
+		}
+
+		*p++ = 0;
+		n = count_commas(p) + 1;
+		if (n > FZ_MAX_COLORS)
+		{
+			fz_warn(ctx, "ignoring %d color components (max %d allowed)", n - FZ_MAX_COLORS, FZ_MAX_COLORS);
+			n = FZ_MAX_COLORS;
+		}
+		i = 0;
+		while (i < n)
+		{
+			samples[i++] = fz_atof(p);
+			p = strchr(p, ',');
+			if (!p)
+				break;
+			p ++;
+			if (*p == ' ')
+				p ++;
+		}
+		while (i < n)
+		{
+			samples[i++] = 0;
+		}
+
+		/* TODO: load ICC profile */
+		switch (n)
+		{
+		case 2: *csp = fz_device_gray(ctx); break;
+		case 4: *csp = fz_device_rgb(ctx); break;
+		case 5: *csp = fz_device_cmyk(ctx); break;
+		default: *csp = fz_device_gray(ctx); break;
+		}
+	}
+}
+
+void
+xps_set_color(fz_context *ctx, xps_document *doc, fz_colorspace *colorspace, float *samples)
+{
+	int i;
+	int n = fz_colorspace_n(ctx, colorspace);
+	doc->colorspace = colorspace;
+	for (i = 0; i < n; i++)
+		doc->color[i] = samples[i + 1];
+	doc->alpha = samples[0] * doc->opacity[doc->opacity_top];
+}