1#include "mupdf/fitz.h"
2#include "xps-imp.h"
3
4#include <string.h>
5#include <math.h>
6#include <float.h>
7#include <stdlib.h>
8
9#define MAX_STOPS 256
10
11enum { SPREAD_PAD, SPREAD_REPEAT, SPREAD_REFLECT };
12
13/*
14 * Parse a list of GradientStop elements.
15 * Fill the offset and color arrays, and
16 * return the number of stops parsed.
17 */
18
19struct stop
20{
21 float offset;
22 float r, g, b, a;
23 int index;
24};
25
26static int cmp_stop(const void *a, const void *b)
27{
28 const struct stop *astop = a;
29 const struct stop *bstop = b;
30 float diff = astop->offset - bstop->offset;
31 if (diff < 0)
32 return -1;
33 if (diff > 0)
34 return 1;
35 return astop->index - bstop->index;
36}
37
38static inline float lerp(float a, float b, float x)
39{
40 return a + (b - a) * x;
41}
42
43static int
44xps_parse_gradient_stops(fz_context *ctx, xps_document *doc, char *base_uri, fz_xml *node,
45 struct stop *stops, int maxcount)
46{
47 fz_colorspace *colorspace;
48 float sample[FZ_MAX_COLORS];
49 float rgb[3];
50 int before, after;
51 int count;
52 int i;
53
54 /* We may have to insert 2 extra stops when postprocessing */
55 maxcount -= 2;
56
57 count = 0;
58 while (node && count < maxcount)
59 {
60 if (fz_xml_is_tag(node, "GradientStop"))
61 {
62 char *offset = fz_xml_att(node, "Offset");
63 char *color = fz_xml_att(node, "Color");
64 if (offset && color)
65 {
66 stops[count].offset = fz_atof(offset);
67 stops[count].index = count;
68
69 xps_parse_color(ctx, doc, base_uri, color, &colorspace, sample);
70
71 fz_convert_color(ctx, colorspace, sample+1, fz_device_rgb(ctx), rgb, NULL, fz_default_color_params);
72
73 stops[count].r = rgb[0];
74 stops[count].g = rgb[1];
75 stops[count].b = rgb[2];
76 stops[count].a = sample[0];
77
78 count ++;
79 }
80 }
81 node = fz_xml_next(node);
82 }
83
84 if (count == 0)
85 {
86 fz_warn(ctx, "gradient brush has no gradient stops");
87 stops[0].offset = 0;
88 stops[0].r = 0;
89 stops[0].g = 0;
90 stops[0].b = 0;
91 stops[0].a = 1;
92 stops[1].offset = 1;
93 stops[1].r = 1;
94 stops[1].g = 1;
95 stops[1].b = 1;
96 stops[1].a = 1;
97 return 2;
98 }
99
100 if (count == maxcount)
101 fz_warn(ctx, "gradient brush exceeded maximum number of gradient stops");
102
103 /* Postprocess to make sure the range of offsets is 0.0 to 1.0 */
104
105 qsort(stops, count, sizeof(struct stop), cmp_stop);
106
107 before = -1;
108 after = -1;
109
110 for (i = 0; i < count; i++)
111 {
112 if (stops[i].offset < 0)
113 before = i;
114 if (stops[i].offset > 1)
115 {
116 after = i;
117 break;
118 }
119 }
120
121 /* Remove all stops < 0 except the largest one */
122 if (before > 0)
123 {
124 memmove(stops, stops + before, (count - before) * sizeof(struct stop));
125 count -= before;
126 }
127
128 /* Remove all stops > 1 except the smallest one */
129 if (after >= 0)
130 count = after + 1;
131
132 /* Expand single stop to 0 .. 1 */
133 if (count == 1)
134 {
135 stops[1] = stops[0];
136 stops[0].offset = 0;
137 stops[1].offset = 1;
138 return 2;
139 }
140
141 /* First stop < 0 -- interpolate value to 0 */
142 if (stops[0].offset < 0)
143 {
144 float d = -stops[0].offset / (stops[1].offset - stops[0].offset);
145 stops[0].offset = 0;
146 stops[0].r = lerp(stops[0].r, stops[1].r, d);
147 stops[0].g = lerp(stops[0].g, stops[1].g, d);
148 stops[0].b = lerp(stops[0].b, stops[1].b, d);
149 stops[0].a = lerp(stops[0].a, stops[1].a, d);
150 }
151
152 /* Last stop > 1 -- interpolate value to 1 */
153 if (stops[count-1].offset > 1)
154 {
155 float d = (1 - stops[count-2].offset) / (stops[count-1].offset - stops[count-2].offset);
156 stops[count-1].offset = 1;
157 stops[count-1].r = lerp(stops[count-2].r, stops[count-1].r, d);
158 stops[count-1].g = lerp(stops[count-2].g, stops[count-1].g, d);
159 stops[count-1].b = lerp(stops[count-2].b, stops[count-1].b, d);
160 stops[count-1].a = lerp(stops[count-2].a, stops[count-1].a, d);
161 }
162
163 /* First stop > 0 -- insert a duplicate at 0 */
164 if (stops[0].offset > 0)
165 {
166 memmove(stops + 1, stops, count * sizeof(struct stop));
167 stops[0] = stops[1];
168 stops[0].offset = 0;
169 count++;
170 }
171
172 /* Last stop < 1 -- insert a duplicate at 1 */
173 if (stops[count-1].offset < 1)
174 {
175 stops[count] = stops[count-1];
176 stops[count].offset = 1;
177 count++;
178 }
179
180 return count;
181}
182
183static void
184xps_sample_gradient_stops(fz_context *ctx, xps_document *doc, fz_shade *shade, struct stop *stops, int count)
185{
186 float offset, d;
187 int i, k;
188
189 k = 0;
190 for (i = 0; i < 256; i++)
191 {
192 offset = i / 255.0f;
193 while (k + 1 < count && offset > stops[k+1].offset)
194 k++;
195
196 d = (offset - stops[k].offset) / (stops[k+1].offset - stops[k].offset);
197
198 shade->function[i][0] = lerp(stops[k].r, stops[k+1].r, d);
199 shade->function[i][1] = lerp(stops[k].g, stops[k+1].g, d);
200 shade->function[i][2] = lerp(stops[k].b, stops[k+1].b, d);
201 shade->function[i][3] = lerp(stops[k].a, stops[k+1].a, d);
202 }
203}
204
205/*
206 * Radial gradients map more or less to Radial shadings.
207 * The inner circle is always a point.
208 * The outer circle is actually an ellipse,
209 * mess with the transform to squash the circle into the right aspect.
210 */
211
212static void
213xps_draw_one_radial_gradient(fz_context *ctx, xps_document *doc, fz_matrix ctm,
214 struct stop *stops, int count,
215 int extend,
216 float x0, float y0, float r0,
217 float x1, float y1, float r1)
218{
219 fz_device *dev = doc->dev;
220 fz_shade *shade;
221
222 shade = fz_malloc_struct(ctx, fz_shade);
223 FZ_INIT_STORABLE(shade, 1, fz_drop_shade_imp);
224 shade->colorspace = fz_keep_colorspace(ctx, fz_device_rgb(ctx));
225 shade->bbox = fz_infinite_rect;
226 shade->matrix = fz_identity;
227 shade->use_background = 0;
228 shade->use_function = 1;
229 shade->type = FZ_RADIAL;
230 shade->u.l_or_r.extend[0] = extend;
231 shade->u.l_or_r.extend[1] = extend;
232
233 shade->u.l_or_r.coords[0][0] = x0;
234 shade->u.l_or_r.coords[0][1] = y0;
235 shade->u.l_or_r.coords[0][2] = r0;
236 shade->u.l_or_r.coords[1][0] = x1;
237 shade->u.l_or_r.coords[1][1] = y1;
238 shade->u.l_or_r.coords[1][2] = r1;
239
240 fz_try(ctx)
241 {
242 xps_sample_gradient_stops(ctx, doc, shade, stops, count);
243 fz_fill_shade(ctx, dev, shade, ctm, 1, fz_default_color_params);
244 }
245 fz_always(ctx)
246 fz_drop_shade(ctx, shade);
247 fz_catch(ctx)
248 fz_rethrow(ctx);
249}
250
251/*
252 * Linear gradients.
253 */
254
255static void
256xps_draw_one_linear_gradient(fz_context *ctx, xps_document *doc, fz_matrix ctm,
257 struct stop *stops, int count,
258 int extend,
259 float x0, float y0, float x1, float y1)
260{
261 fz_device *dev = doc->dev;
262 fz_shade *shade;
263
264 shade = fz_malloc_struct(ctx, fz_shade);
265 FZ_INIT_STORABLE(shade, 1, fz_drop_shade_imp);
266 shade->colorspace = fz_keep_colorspace(ctx, fz_device_rgb(ctx));
267 shade->bbox = fz_infinite_rect;
268 shade->matrix = fz_identity;
269 shade->use_background = 0;
270 shade->use_function = 1;
271 shade->type = FZ_LINEAR;
272 shade->u.l_or_r.extend[0] = extend;
273 shade->u.l_or_r.extend[1] = extend;
274
275 shade->u.l_or_r.coords[0][0] = x0;
276 shade->u.l_or_r.coords[0][1] = y0;
277 shade->u.l_or_r.coords[0][2] = 0;
278 shade->u.l_or_r.coords[1][0] = x1;
279 shade->u.l_or_r.coords[1][1] = y1;
280 shade->u.l_or_r.coords[1][2] = 0;
281
282 fz_try(ctx)
283 {
284 xps_sample_gradient_stops(ctx, doc, shade, stops, count);
285 fz_fill_shade(ctx, dev, shade, ctm, doc->opacity[doc->opacity_top], fz_default_color_params);
286 }
287 fz_always(ctx)
288 fz_drop_shade(ctx, shade);
289 fz_catch(ctx)
290 fz_rethrow(ctx);
291}
292
293/*
294 * We need to loop and create many shading objects to account
295 * for the Repeat and Reflect SpreadMethods.
296 * I'm not smart enough to calculate this analytically
297 * so we iterate and check each object until we
298 * reach a reasonable limit for infinite cases.
299 */
300
301static void
302xps_draw_radial_gradient(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
303 struct stop *stops, int count,
304 fz_xml *root, int spread)
305{
306 float x0, y0, r0;
307 float x1, y1, r1;
308 float xrad = 1;
309 float yrad = 1;
310 float invscale;
311 int i, ma = 1;
312 fz_matrix inv;
313
314 char *center_att = fz_xml_att(root, "Center");
315 char *origin_att = fz_xml_att(root, "GradientOrigin");
316 char *radius_x_att = fz_xml_att(root, "RadiusX");
317 char *radius_y_att = fz_xml_att(root, "RadiusY");
318
319 x0 = y0 = 0.0f;
320 x1 = y1 = 1.0f;
321 xrad = 1.0f;
322 yrad = 1.0f;
323
324 if (origin_att)
325 xps_parse_point(ctx, doc, origin_att, &x0, &y0);
326 if (center_att)
327 xps_parse_point(ctx, doc, center_att, &x1, &y1);
328 if (radius_x_att)
329 xrad = fz_atof(radius_x_att);
330 if (radius_y_att)
331 yrad = fz_atof(radius_y_att);
332
333 xrad = fz_max(0.01f, xrad);
334 yrad = fz_max(0.01f, yrad);
335
336 /* scale the ctm to make ellipses */
337 if (fz_abs(xrad) > FLT_EPSILON)
338 {
339 ctm = fz_pre_scale(ctm, 1, yrad/xrad);
340 }
341
342 if (yrad != 0.0f)
343 {
344 invscale = xrad / yrad;
345 y0 = y0 * invscale;
346 y1 = y1 * invscale;
347 }
348
349 r0 = 0;
350 r1 = xrad;
351
352 inv = fz_invert_matrix(ctm);
353 area = fz_transform_rect(area, inv);
354 ma = fz_maxi(ma, ceilf(hypotf(area.x0 - x0, area.y0 - y0) / xrad));
355 ma = fz_maxi(ma, ceilf(hypotf(area.x1 - x0, area.y0 - y0) / xrad));
356 ma = fz_maxi(ma, ceilf(hypotf(area.x0 - x0, area.y1 - y0) / xrad));
357 ma = fz_maxi(ma, ceilf(hypotf(area.x1 - x0, area.y1 - y0) / xrad));
358
359 if (spread == SPREAD_REPEAT)
360 {
361 for (i = ma - 1; i >= 0; i--)
362 xps_draw_one_radial_gradient(ctx, doc, ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
363 }
364 else if (spread == SPREAD_REFLECT)
365 {
366 if ((ma % 2) != 0)
367 ma++;
368 for (i = ma - 2; i >= 0; i -= 2)
369 {
370 xps_draw_one_radial_gradient(ctx, doc, ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
371 xps_draw_one_radial_gradient(ctx, doc, ctm, stops, count, 0, x0, y0, r0 + (i + 2) * xrad, x1, y1, r1 + i * xrad);
372 }
373 }
374 else
375 {
376 xps_draw_one_radial_gradient(ctx, doc, ctm, stops, count, 1, x0, y0, r0, x1, y1, r1);
377 }
378}
379
380/*
381 * Calculate how many iterations are needed to cover
382 * the bounding box.
383 */
384
385static void
386xps_draw_linear_gradient(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
387 struct stop *stops, int count,
388 fz_xml *root, int spread)
389{
390 float x0, y0, x1, y1;
391 int i, mi, ma;
392 float dx, dy, x, y, k;
393 fz_point p1, p2;
394 fz_matrix inv;
395
396 char *start_point_att = fz_xml_att(root, "StartPoint");
397 char *end_point_att = fz_xml_att(root, "EndPoint");
398
399 x0 = y0 = 0;
400 x1 = y1 = 1;
401
402 if (start_point_att)
403 xps_parse_point(ctx, doc, start_point_att, &x0, &y0);
404 if (end_point_att)
405 xps_parse_point(ctx, doc, end_point_att, &x1, &y1);
406
407 p1.x = x0; p1.y = y0; p2.x = x1; p2.y = y1;
408 inv = fz_invert_matrix(ctm);
409 area = fz_transform_rect(area, inv);
410 x = p2.x - p1.x; y = p2.y - p1.y;
411 k = ((area.x0 - p1.x) * x + (area.y0 - p1.y) * y) / (x * x + y * y);
412 mi = floorf(k); ma = ceilf(k);
413 k = ((area.x1 - p1.x) * x + (area.y0 - p1.y) * y) / (x * x + y * y);
414 mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
415 k = ((area.x0 - p1.x) * x + (area.y1 - p1.y) * y) / (x * x + y * y);
416 mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
417 k = ((area.x1 - p1.x) * x + (area.y1 - p1.y) * y) / (x * x + y * y);
418 mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
419 dx = x1 - x0; dy = y1 - y0;
420
421 if (spread == SPREAD_REPEAT)
422 {
423 for (i = mi; i < ma; i++)
424 xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
425 }
426 else if (spread == SPREAD_REFLECT)
427 {
428 if ((mi % 2) != 0)
429 mi--;
430 for (i = mi; i < ma; i += 2)
431 {
432 xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
433 xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 0, x0 + (i + 2) * dx, y0 + (i + 2) * dy, x1 + i * dx, y1 + i * dy);
434 }
435 }
436 else
437 {
438 xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 1, x0, y0, x1, y1);
439 }
440}
441
442/*
443 * Parse XML tag and attributes for a gradient brush, create color/opacity
444 * function objects and call gradient drawing primitives.
445 */
446
447static void
448xps_parse_gradient_brush(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
449 char *base_uri, xps_resource *dict, fz_xml *root,
450 void (*draw)(fz_context *ctx, xps_document *, fz_matrix, fz_rect, struct stop *, int, fz_xml *, int))
451{
452 fz_xml *node;
453
454 char *opacity_att;
455 char *spread_att;
456 char *transform_att;
457
458 fz_xml *transform_tag = NULL;
459 fz_xml *stop_tag = NULL;
460
461 struct stop stop_list[MAX_STOPS];
462 int stop_count;
463 int spread_method;
464
465 opacity_att = fz_xml_att(root, "Opacity");
466 spread_att = fz_xml_att(root, "SpreadMethod");
467 transform_att = fz_xml_att(root, "Transform");
468
469 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
470 {
471 if (fz_xml_is_tag(node, "LinearGradientBrush.Transform"))
472 transform_tag = fz_xml_down(node);
473 if (fz_xml_is_tag(node, "RadialGradientBrush.Transform"))
474 transform_tag = fz_xml_down(node);
475 if (fz_xml_is_tag(node, "LinearGradientBrush.GradientStops"))
476 stop_tag = fz_xml_down(node);
477 if (fz_xml_is_tag(node, "RadialGradientBrush.GradientStops"))
478 stop_tag = fz_xml_down(node);
479 }
480
481 xps_resolve_resource_reference(ctx, doc, dict, &transform_att, &transform_tag, NULL);
482
483 spread_method = SPREAD_PAD;
484 if (spread_att)
485 {
486 if (!strcmp(spread_att, "Pad"))
487 spread_method = SPREAD_PAD;
488 if (!strcmp(spread_att, "Reflect"))
489 spread_method = SPREAD_REFLECT;
490 if (!strcmp(spread_att, "Repeat"))
491 spread_method = SPREAD_REPEAT;
492 }
493
494 ctm = xps_parse_transform(ctx, doc, transform_att, transform_tag, ctm);
495
496 if (!stop_tag) {
497 fz_warn(ctx, "missing gradient stops tag");
498 return;
499 }
500
501 stop_count = xps_parse_gradient_stops(ctx, doc, base_uri, stop_tag, stop_list, MAX_STOPS);
502 if (stop_count == 0)
503 {
504 fz_warn(ctx, "no gradient stops found");
505 return;
506 }
507
508 xps_begin_opacity(ctx, doc, ctm, area, base_uri, dict, opacity_att, NULL);
509
510 draw(ctx, doc, ctm, area, stop_list, stop_count, root, spread_method);
511
512 xps_end_opacity(ctx, doc, base_uri, dict, opacity_att, NULL);
513}
514
515void
516xps_parse_linear_gradient_brush(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
517 char *base_uri, xps_resource *dict, fz_xml *root)
518{
519 xps_parse_gradient_brush(ctx, doc, ctm, area, base_uri, dict, root, xps_draw_linear_gradient);
520}
521
522void
523xps_parse_radial_gradient_brush(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
524 char *base_uri, xps_resource *dict, fz_xml *root)
525{
526 xps_parse_gradient_brush(ctx, doc, ctm, area, base_uri, dict, root, xps_draw_radial_gradient);
527}
528