Coverage Report

Created: 2025-12-03 07:00

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