Mercurial > hgrepos > Python2 > PyMuPDF
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 } |
