comparison 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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2021 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 #include "xps-imp.h"
25
26 #include <string.h>
27 #include <stdio.h> /* for sscanf */
28 #include <math.h> /* for pow */
29
30 static inline int unhex(int a)
31 {
32 if (a >= 'A' && a <= 'F') return a - 'A' + 0xA;
33 if (a >= 'a' && a <= 'f') return a - 'a' + 0xA;
34 if (a >= '0' && a <= '9') return a - '0';
35 return 0;
36 }
37
38 fz_xml *
39 xps_lookup_alternate_content(fz_context *ctx, xps_document *doc, fz_xml *node)
40 {
41 for (node = fz_xml_down(node); node; node = fz_xml_next(node))
42 {
43 if (fz_xml_is_tag(node, "Choice") && fz_xml_att(node, "Requires"))
44 {
45 char list[64];
46 char *next = list, *item;
47 fz_strlcpy(list, fz_xml_att(node, "Requires"), sizeof(list));
48 while ((item = fz_strsep(&next, " \t\r\n")) != NULL && (!*item || !strcmp(item, "xps")));
49 if (!item)
50 return fz_xml_down(node);
51 }
52 else if (fz_xml_is_tag(node, "Fallback"))
53 return fz_xml_down(node);
54 }
55 return NULL;
56 }
57
58 void
59 xps_parse_brush(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, fz_xml *node)
60 {
61 if (doc->cookie && doc->cookie->abort)
62 return;
63 /* SolidColorBrushes are handled in a special case and will never show up here */
64 if (fz_xml_is_tag(node, "ImageBrush"))
65 xps_parse_image_brush(ctx, doc, ctm, area, base_uri, dict, node);
66 else if (fz_xml_is_tag(node, "VisualBrush"))
67 xps_parse_visual_brush(ctx, doc, ctm, area, base_uri, dict, node);
68 else if (fz_xml_is_tag(node, "LinearGradientBrush"))
69 xps_parse_linear_gradient_brush(ctx, doc, ctm, area, base_uri, dict, node);
70 else if (fz_xml_is_tag(node, "RadialGradientBrush"))
71 xps_parse_radial_gradient_brush(ctx, doc, ctm, area, base_uri, dict, node);
72 else
73 fz_warn(ctx, "unknown brush tag");
74 }
75
76 void
77 xps_parse_element(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area, char *base_uri, xps_resource *dict, fz_xml *node)
78 {
79 if (doc->cookie && doc->cookie->abort)
80 return;
81 if (fz_xml_is_tag(node, "Path"))
82 xps_parse_path(ctx, doc, ctm, base_uri, dict, node);
83 if (fz_xml_is_tag(node, "Glyphs"))
84 xps_parse_glyphs(ctx, doc, ctm, base_uri, dict, node);
85 if (fz_xml_is_tag(node, "Canvas"))
86 xps_parse_canvas(ctx, doc, ctm, area, base_uri, dict, node);
87 if (fz_xml_is_tag(node, "AlternateContent"))
88 {
89 node = xps_lookup_alternate_content(ctx, doc, node);
90 if (node)
91 xps_parse_element(ctx, doc, ctm, area, base_uri, dict, node);
92 }
93 /* skip unknown tags (like Foo.Resources and similar) */
94 }
95
96 void
97 xps_begin_opacity(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
98 char *base_uri, xps_resource *dict,
99 char *opacity_att, fz_xml *opacity_mask_tag)
100 {
101 fz_device *dev = doc->dev;
102 float opacity;
103
104 if (!opacity_att && !opacity_mask_tag)
105 return;
106
107 opacity = 1;
108 if (opacity_att)
109 opacity = fz_atof(opacity_att);
110
111 if (fz_xml_is_tag(opacity_mask_tag, "SolidColorBrush"))
112 {
113 char *scb_opacity_att = fz_xml_att(opacity_mask_tag, "Opacity");
114 char *scb_color_att = fz_xml_att(opacity_mask_tag, "Color");
115 if (scb_opacity_att)
116 opacity = opacity * fz_atof(scb_opacity_att);
117 if (scb_color_att)
118 {
119 fz_colorspace *colorspace;
120 float samples[FZ_MAX_COLORS];
121 xps_parse_color(ctx, doc, base_uri, scb_color_att, &colorspace, samples);
122 opacity = opacity * samples[0];
123 }
124 opacity_mask_tag = NULL;
125 }
126
127 if (doc->opacity_top + 1 < (int)nelem(doc->opacity))
128 {
129 doc->opacity[doc->opacity_top + 1] = doc->opacity[doc->opacity_top] * opacity;
130 doc->opacity_top++;
131 }
132
133 if (opacity_mask_tag)
134 {
135 fz_begin_mask(ctx, dev, area, 0, NULL, NULL, fz_default_color_params);
136 xps_parse_brush(ctx, doc, ctm, area, base_uri, dict, opacity_mask_tag);
137 fz_end_mask(ctx, dev);
138 }
139 }
140
141 void
142 xps_end_opacity(fz_context *ctx, xps_document *doc, char *base_uri, xps_resource *dict,
143 char *opacity_att, fz_xml *opacity_mask_tag)
144 {
145 fz_device *dev = doc->dev;
146
147 if (!opacity_att && !opacity_mask_tag)
148 return;
149
150 if (doc->opacity_top > 0)
151 doc->opacity_top--;
152
153 if (opacity_mask_tag)
154 {
155 if (!fz_xml_is_tag(opacity_mask_tag, "SolidColorBrush"))
156 fz_pop_clip(ctx, dev);
157 }
158 }
159
160 static fz_matrix
161 xps_parse_render_transform(fz_context *ctx, xps_document *doc, char *transform)
162 {
163 fz_matrix matrix;
164 float args[6];
165 char *s = transform;
166 int i;
167
168 args[0] = 1; args[1] = 0;
169 args[2] = 0; args[3] = 1;
170 args[4] = 0; args[5] = 0;
171
172 for (i = 0; i < 6 && *s; i++)
173 {
174 args[i] = fz_atof(s);
175 while (*s && *s != ',')
176 s++;
177 if (*s == ',')
178 s++;
179 }
180
181 matrix.a = args[0]; matrix.b = args[1];
182 matrix.c = args[2]; matrix.d = args[3];
183 matrix.e = args[4]; matrix.f = args[5];
184 return matrix;
185 }
186
187 static fz_matrix
188 xps_parse_matrix_transform(fz_context *ctx, xps_document *doc, fz_xml *root)
189 {
190 if (fz_xml_is_tag(root, "MatrixTransform"))
191 {
192 char *transform = fz_xml_att(root, "Matrix");
193 if (transform)
194 return xps_parse_render_transform(ctx, doc, transform);
195 }
196 return fz_identity;
197 }
198
199 fz_matrix
200 xps_parse_transform(fz_context *ctx, xps_document *doc, char *att, fz_xml *tag, fz_matrix ctm)
201 {
202 if (att)
203 return fz_concat(xps_parse_render_transform(ctx, doc, att), ctm);
204 if (tag)
205 return fz_concat(xps_parse_matrix_transform(ctx, doc, tag), ctm);
206 return ctm;
207 }
208
209 fz_rect
210 xps_parse_rectangle(fz_context *ctx, xps_document *doc, char *text)
211 {
212 fz_rect rect;
213 float args[4];
214 char *s = text;
215 int i;
216
217 args[0] = 0; args[1] = 0;
218 args[2] = 1; args[3] = 1;
219
220 for (i = 0; i < 4 && *s; i++)
221 {
222 args[i] = fz_atof(s);
223 while (*s && *s != ',')
224 s++;
225 if (*s == ',')
226 s++;
227 }
228
229 rect.x0 = args[0];
230 rect.y0 = args[1];
231 rect.x1 = args[0] + args[2];
232 rect.y1 = args[1] + args[3];
233 return rect;
234 }
235
236 static int count_commas(char *s)
237 {
238 int n = 0;
239 while (*s)
240 {
241 if (*s == ',')
242 n ++;
243 s ++;
244 }
245 return n;
246 }
247
248 static float sRGB_from_scRGB(float x)
249 {
250 if (x < 0.0031308f)
251 return 12.92f * x;
252 return 1.055f * powf(x, 1/2.4f) - 0.055f;
253 }
254
255 void
256 xps_parse_color(fz_context *ctx, xps_document *doc, char *base_uri, char *string,
257 fz_colorspace **csp, float *samples)
258 {
259 char *p;
260 int i, n;
261 char buf[1024];
262 char *profile;
263
264 *csp = fz_device_rgb(ctx);
265
266 samples[0] = 1;
267 samples[1] = 0;
268 samples[2] = 0;
269 samples[3] = 0;
270
271 if (string[0] == '#')
272 {
273 size_t z = strlen(string);
274 if (z == 9)
275 {
276 samples[0] = unhex(string[1]) * 16 + unhex(string[2]);
277 samples[1] = unhex(string[3]) * 16 + unhex(string[4]);
278 samples[2] = unhex(string[5]) * 16 + unhex(string[6]);
279 samples[3] = unhex(string[7]) * 16 + unhex(string[8]);
280 }
281 else
282 {
283 samples[0] = 255;
284 /* Use a macro to protect against overrunning the string. */
285 #define UNHEX(idx) (idx < z ? unhex(string[idx]) : 0)
286 samples[1] = UNHEX(1) * 16 + UNHEX(2);
287 samples[2] = UNHEX(3) * 16 + UNHEX(4);
288 samples[3] = UNHEX(5) * 16 + UNHEX(6);
289 #undef UNHEX
290 }
291
292 samples[0] /= 255;
293 samples[1] /= 255;
294 samples[2] /= 255;
295 samples[3] /= 255;
296 }
297
298 else if (string[0] == 's' && string[1] == 'c' && string[2] == '#')
299 {
300 if (count_commas(string) == 2)
301 sscanf(string, "sc#%g,%g,%g", samples + 1, samples + 2, samples + 3);
302 if (count_commas(string) == 3)
303 sscanf(string, "sc#%g,%g,%g,%g", samples, samples + 1, samples + 2, samples + 3);
304
305 /* Convert from scRGB gamma 1.0 to sRGB gamma */
306 samples[1] = sRGB_from_scRGB(samples[1]);
307 samples[2] = sRGB_from_scRGB(samples[2]);
308 samples[3] = sRGB_from_scRGB(samples[3]);
309 }
310
311 else if (strstr(string, "ContextColor ") == string)
312 {
313 /* Crack the string for profile name and sample values */
314 fz_strlcpy(buf, string, sizeof buf);
315
316 profile = strchr(buf, ' ');
317 if (!profile)
318 {
319 fz_warn(ctx, "cannot find icc profile uri in '%s'", string);
320 return;
321 }
322
323 *profile++ = 0;
324 p = strchr(profile, ' ');
325 if (!p)
326 {
327 fz_warn(ctx, "cannot find component values in '%s'", profile);
328 return;
329 }
330
331 *p++ = 0;
332 n = count_commas(p) + 1;
333 if (n > FZ_MAX_COLORS)
334 {
335 fz_warn(ctx, "ignoring %d color components (max %d allowed)", n - FZ_MAX_COLORS, FZ_MAX_COLORS);
336 n = FZ_MAX_COLORS;
337 }
338 i = 0;
339 while (i < n)
340 {
341 samples[i++] = fz_atof(p);
342 p = strchr(p, ',');
343 if (!p)
344 break;
345 p ++;
346 if (*p == ' ')
347 p ++;
348 }
349 while (i < n)
350 {
351 samples[i++] = 0;
352 }
353
354 /* TODO: load ICC profile */
355 switch (n)
356 {
357 case 2: *csp = fz_device_gray(ctx); break;
358 case 4: *csp = fz_device_rgb(ctx); break;
359 case 5: *csp = fz_device_cmyk(ctx); break;
360 default: *csp = fz_device_gray(ctx); break;
361 }
362 }
363 }
364
365 void
366 xps_set_color(fz_context *ctx, xps_document *doc, fz_colorspace *colorspace, float *samples)
367 {
368 int i;
369 int n = fz_colorspace_n(ctx, colorspace);
370 doc->colorspace = colorspace;
371 for (i = 0; i < n; i++)
372 doc->color[i] = samples[i + 1];
373 doc->alpha = samples[0] * doc->opacity[doc->opacity_top];
374 }