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