1#include "mupdf/fitz.h"
2#include "xps-imp.h"
3
4#include <math.h>
5#include <string.h>
6#include <stdlib.h>
7
8static char *
9xps_parse_float_array(fz_context *ctx, xps_document *doc, char *s, int num, float *x)
10{
11 int k = 0;
12
13 if (s == NULL || *s == 0)
14 return NULL;
15
16 while (*s)
17 {
18 while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
19 s++;
20 x[k] = fz_strtof(s, &s);
21 while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a)
22 s++;
23 if (*s == ',')
24 s++;
25 if (++k == num)
26 break;
27 }
28 return s;
29}
30
31char *
32xps_parse_point(fz_context *ctx, xps_document *doc, char *s_in, float *x, float *y)
33{
34 char *s_out = s_in;
35 float xy[2];
36
37 s_out = xps_parse_float_array(ctx, doc, s_out, 2, &xy[0]);
38 *x = xy[0];
39 *y = xy[1];
40 return s_out;
41}
42
43/* Draw an arc segment transformed by the matrix, we approximate with straight
44 * line segments. We cannot use the fz_arc function because they only draw
45 * circular arcs, we need to transform the line to make them elliptical but
46 * without transforming the line width.
47 *
48 * We are guaranteed that on entry the point is at the point that would be
49 * calculated by th0, and on exit, a point is generated for us at th0.
50 */
51static void
52xps_draw_arc_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_matrix mtx, float th0, float th1, int iscw)
53{
54 float t, d;
55 fz_point p;
56
57 while (th1 < th0)
58 th1 += FZ_PI * 2;
59
60 d = FZ_PI / 180; /* 1-degree precision */
61
62 if (iscw)
63 {
64 for (t = th0 + d; t < th1 - d/2; t += d)
65 {
66 p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
67 fz_lineto(ctx, path, p.x, p.y);
68 }
69 }
70 else
71 {
72 th0 += FZ_PI * 2;
73 for (t = th0 - d; t > th1 + d/2; t -= d)
74 {
75 p = fz_transform_point_xy(cosf(t), sinf(t), mtx);
76 fz_lineto(ctx, path, p.x, p.y);
77 }
78 }
79}
80
81/* Given two vectors find the angle between them. */
82static float
83angle_between(fz_point u, fz_point v)
84{
85 float det = u.x * v.y - u.y * v.x;
86 float sign = (det < 0 ? -1 : 1);
87 float magu = u.x * u.x + u.y * u.y;
88 float magv = v.x * v.x + v.y * v.y;
89 float udotv = u.x * v.x + u.y * v.y;
90 float t = udotv / (magu * magv);
91 /* guard against rounding errors when near |1| (where acos will return NaN) */
92 if (t < -1) t = -1;
93 if (t > 1) t = 1;
94 return sign * acosf(t);
95}
96
97/*
98 Some explanation of the parameters here is warranted. See:
99
100 http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
101
102 Add an arc segment to path, that describes a section of an elliptical
103 arc from the current point of path to (point_x,point_y), such that:
104
105 The arc segment is taken from an elliptical arc of semi major radius
106 size_x, semi minor radius size_y, where the semi major axis of the
107 ellipse is rotated by rotation_angle.
108
109 If is_large_arc, then the arc segment is selected to be > 180 degrees.
110
111 If is_clockwise, then the arc sweeps clockwise.
112*/
113static void
114xps_draw_arc(fz_context *ctx, xps_document *doc, fz_path *path,
115 float size_x, float size_y, float rotation_angle,
116 int is_large_arc, int is_clockwise,
117 float point_x, float point_y)
118{
119 fz_matrix rotmat, revmat;
120 fz_matrix mtx;
121 fz_point pt;
122 float rx, ry;
123 float x1, y1, x2, y2;
124 float x1t, y1t;
125 float cxt, cyt, cx, cy;
126 float t1, t2, t3;
127 float sign;
128 float th1, dth;
129
130 pt = fz_currentpoint(ctx, path);
131 x1 = pt.x;
132 y1 = pt.y;
133 x2 = point_x;
134 y2 = point_y;
135 rx = size_x;
136 ry = size_y;
137
138 if (is_clockwise != is_large_arc)
139 sign = 1;
140 else
141 sign = -1;
142
143 rotmat = fz_rotate(rotation_angle);
144 revmat = fz_rotate(-rotation_angle);
145
146 /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
147 /* Conversion from endpoint to center parameterization */
148
149 /* F.6.6.1 -- ensure radii are positive and non-zero */
150 rx = fabsf(rx);
151 ry = fabsf(ry);
152 if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
153 {
154 fz_lineto(ctx, path, x2, y2);
155 return;
156 }
157
158 /* F.6.5.1 */
159 pt.x = (x1 - x2) / 2;
160 pt.y = (y1 - y2) / 2;
161 pt = fz_transform_vector(pt, revmat);
162 x1t = pt.x;
163 y1t = pt.y;
164
165 /* F.6.6.2 -- ensure radii are large enough */
166 t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
167 if (t1 > 1)
168 {
169 rx = rx * sqrtf(t1);
170 ry = ry * sqrtf(t1);
171 }
172
173 /* F.6.5.2 */
174 t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
175 t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
176 t3 = t1 / t2;
177 /* guard against rounding errors; sqrt of negative numbers is bad for your health */
178 if (t3 < 0) t3 = 0;
179 t3 = sqrtf(t3);
180
181 cxt = sign * t3 * (rx * y1t) / ry;
182 cyt = sign * t3 * -(ry * x1t) / rx;
183
184 /* F.6.5.3 */
185 pt.x = cxt;
186 pt.y = cyt;
187 pt = fz_transform_vector(pt, rotmat);
188 cx = pt.x + (x1 + x2) / 2;
189 cy = pt.y + (y1 + y2) / 2;
190
191 /* F.6.5.4 */
192 {
193 fz_point coord1, coord2, coord3, coord4;
194 coord1.x = 1;
195 coord1.y = 0;
196 coord2.x = (x1t - cxt) / rx;
197 coord2.y = (y1t - cyt) / ry;
198 coord3.x = (x1t - cxt) / rx;
199 coord3.y = (y1t - cyt) / ry;
200 coord4.x = (-x1t - cxt) / rx;
201 coord4.y = (-y1t - cyt) / ry;
202 th1 = angle_between(coord1, coord2);
203 dth = angle_between(coord3, coord4);
204 if (dth < 0 && !is_clockwise)
205 dth += ((FZ_PI / 180) * 360);
206 if (dth > 0 && is_clockwise)
207 dth -= ((FZ_PI / 180) * 360);
208 }
209
210 mtx = fz_pre_scale(fz_pre_rotate(fz_translate(cx, cy), rotation_angle), rx, ry);
211 xps_draw_arc_segment(ctx, doc, path, mtx, th1, th1 + dth, is_clockwise);
212
213 fz_lineto(ctx, path, point_x, point_y);
214}
215
216/*
217 * Parse an abbreviated geometry string, and call
218 * ghostscript moveto/lineto/curveto functions to
219 * build up a path.
220 */
221
222fz_path *
223xps_parse_abbreviated_geometry(fz_context *ctx, xps_document *doc, char *geom, int *fill_rule)
224{
225 fz_path *path;
226 char **args = NULL;
227 char **pargs;
228 char *s = geom;
229 fz_point pt;
230 int i, n;
231 int cmd, old;
232 float x1, y1, x2, y2, x3, y3;
233 float smooth_x, smooth_y; /* saved cubic bezier control point for smooth curves */
234 int reset_smooth;
235
236 fz_var(args);
237
238 path = fz_new_path(ctx);
239
240 fz_try(ctx)
241 {
242 args = fz_malloc_array(ctx, strlen(geom) + 1, char*);
243 pargs = args;
244
245 while (*s)
246 {
247 if ((*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z'))
248 {
249 *pargs++ = s++;
250 }
251 else if ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
252 {
253 *pargs++ = s;
254 while ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E')
255 s ++;
256 }
257 else
258 {
259 s++;
260 }
261 }
262
263 *pargs = s;
264
265 n = pargs - args;
266 i = 0;
267
268 old = 0;
269
270 reset_smooth = 1;
271 smooth_x = 0;
272 smooth_y = 0;
273
274 while (i < n)
275 {
276 cmd = args[i][0];
277 if (cmd == '+' || cmd == '.' || cmd == '-' || (cmd >= '0' && cmd <= '9'))
278 cmd = old; /* it's a number, repeat old command */
279 else
280 i ++;
281
282 if (reset_smooth)
283 {
284 smooth_x = 0;
285 smooth_y = 0;
286 }
287
288 reset_smooth = 1;
289
290 switch (cmd)
291 {
292 case 'F':
293 if (i >= n) break;
294 *fill_rule = atoi(args[i]);
295 i ++;
296 break;
297
298 case 'M':
299 if (i + 1 >= n) break;
300 fz_moveto(ctx, path, fz_atof(args[i]), fz_atof(args[i+1]));
301 i += 2;
302 break;
303 case 'm':
304 if (i + 1 >= n) break;
305 pt = fz_currentpoint(ctx, path);
306 fz_moveto(ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1]));
307 i += 2;
308 break;
309
310 case 'L':
311 if (i + 1 >= n) break;
312 fz_lineto(ctx, path, fz_atof(args[i]), fz_atof(args[i+1]));
313 i += 2;
314 break;
315 case 'l':
316 if (i + 1 >= n) break;
317 pt = fz_currentpoint(ctx, path);
318 fz_lineto(ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1]));
319 i += 2;
320 break;
321
322 case 'H':
323 if (i >= n) break;
324 pt = fz_currentpoint(ctx, path);
325 fz_lineto(ctx, path, fz_atof(args[i]), pt.y);
326 i += 1;
327 break;
328 case 'h':
329 if (i >= n) break;
330 pt = fz_currentpoint(ctx, path);
331 fz_lineto(ctx, path, pt.x + fz_atof(args[i]), pt.y);
332 i += 1;
333 break;
334
335 case 'V':
336 if (i >= n) break;
337 pt = fz_currentpoint(ctx, path);
338 fz_lineto(ctx, path, pt.x, fz_atof(args[i]));
339 i += 1;
340 break;
341 case 'v':
342 if (i >= n) break;
343 pt = fz_currentpoint(ctx, path);
344 fz_lineto(ctx, path, pt.x, pt.y + fz_atof(args[i]));
345 i += 1;
346 break;
347
348 case 'C':
349 if (i + 5 >= n) break;
350 x1 = fz_atof(args[i+0]);
351 y1 = fz_atof(args[i+1]);
352 x2 = fz_atof(args[i+2]);
353 y2 = fz_atof(args[i+3]);
354 x3 = fz_atof(args[i+4]);
355 y3 = fz_atof(args[i+5]);
356 fz_curveto(ctx, path, x1, y1, x2, y2, x3, y3);
357 i += 6;
358 reset_smooth = 0;
359 smooth_x = x3 - x2;
360 smooth_y = y3 - y2;
361 break;
362
363 case 'c':
364 if (i + 5 >= n) break;
365 pt = fz_currentpoint(ctx, path);
366 x1 = fz_atof(args[i+0]) + pt.x;
367 y1 = fz_atof(args[i+1]) + pt.y;
368 x2 = fz_atof(args[i+2]) + pt.x;
369 y2 = fz_atof(args[i+3]) + pt.y;
370 x3 = fz_atof(args[i+4]) + pt.x;
371 y3 = fz_atof(args[i+5]) + pt.y;
372 fz_curveto(ctx, path, x1, y1, x2, y2, x3, y3);
373 i += 6;
374 reset_smooth = 0;
375 smooth_x = x3 - x2;
376 smooth_y = y3 - y2;
377 break;
378
379 case 'S':
380 if (i + 3 >= n) break;
381 pt = fz_currentpoint(ctx, path);
382 x1 = fz_atof(args[i+0]);
383 y1 = fz_atof(args[i+1]);
384 x2 = fz_atof(args[i+2]);
385 y2 = fz_atof(args[i+3]);
386 fz_curveto(ctx, path, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
387 i += 4;
388 reset_smooth = 0;
389 smooth_x = x2 - x1;
390 smooth_y = y2 - y1;
391 break;
392
393 case 's':
394 if (i + 3 >= n) break;
395 pt = fz_currentpoint(ctx, path);
396 x1 = fz_atof(args[i+0]) + pt.x;
397 y1 = fz_atof(args[i+1]) + pt.y;
398 x2 = fz_atof(args[i+2]) + pt.x;
399 y2 = fz_atof(args[i+3]) + pt.y;
400 fz_curveto(ctx, path, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2);
401 i += 4;
402 reset_smooth = 0;
403 smooth_x = x2 - x1;
404 smooth_y = y2 - y1;
405 break;
406
407 case 'Q':
408 if (i + 3 >= n) break;
409 x1 = fz_atof(args[i+0]);
410 y1 = fz_atof(args[i+1]);
411 x2 = fz_atof(args[i+2]);
412 y2 = fz_atof(args[i+3]);
413 fz_quadto(ctx, path, x1, y1, x2, y2);
414 i += 4;
415 break;
416 case 'q':
417 if (i + 3 >= n) break;
418 pt = fz_currentpoint(ctx, path);
419 x1 = fz_atof(args[i+0]) + pt.x;
420 y1 = fz_atof(args[i+1]) + pt.y;
421 x2 = fz_atof(args[i+2]) + pt.x;
422 y2 = fz_atof(args[i+3]) + pt.y;
423 fz_quadto(ctx, path, x1, y1, x2, y2);
424 i += 4;
425 break;
426
427 case 'A':
428 if (i + 6 >= n) break;
429 xps_draw_arc(ctx, doc, path,
430 fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]),
431 atoi(args[i+3]), atoi(args[i+4]),
432 fz_atof(args[i+5]), fz_atof(args[i+6]));
433 i += 7;
434 break;
435 case 'a':
436 if (i + 6 >= n) break;
437 pt = fz_currentpoint(ctx, path);
438 xps_draw_arc(ctx, doc, path,
439 fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]),
440 atoi(args[i+3]), atoi(args[i+4]),
441 fz_atof(args[i+5]) + pt.x, fz_atof(args[i+6]) + pt.y);
442 i += 7;
443 break;
444
445 case 'Z':
446 case 'z':
447 fz_closepath(ctx, path);
448 break;
449
450 default:
451 fz_warn(ctx, "ignoring invalid command '%c'", cmd);
452 if (old == cmd) /* avoid infinite loop */
453 i++;
454 break;
455 }
456
457 old = cmd;
458 }
459 }
460 fz_always(ctx)
461 fz_free(ctx, args);
462 fz_catch(ctx)
463 {
464 fz_drop_path(ctx, path);
465 fz_rethrow(ctx);
466 }
467
468 return path;
469}
470
471static void
472xps_parse_arc_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
473{
474 /* ArcSegment pretty much follows the SVG algorithm for converting an
475 * arc in endpoint representation to an arc in centerpoint
476 * representation. Once in centerpoint it can be given to the
477 * graphics library in the form of a postscript arc. */
478
479 float rotation_angle;
480 int is_large_arc, is_clockwise;
481 float point_x, point_y;
482 float size_x, size_y;
483 int is_stroked;
484
485 char *point_att = fz_xml_att(root, "Point");
486 char *size_att = fz_xml_att(root, "Size");
487 char *rotation_angle_att = fz_xml_att(root, "RotationAngle");
488 char *is_large_arc_att = fz_xml_att(root, "IsLargeArc");
489 char *sweep_direction_att = fz_xml_att(root, "SweepDirection");
490 char *is_stroked_att = fz_xml_att(root, "IsStroked");
491
492 if (!point_att || !size_att || !rotation_angle_att || !is_large_arc_att || !sweep_direction_att)
493 {
494 fz_warn(ctx, "ArcSegment element is missing attributes");
495 return;
496 }
497
498 is_stroked = 1;
499 if (is_stroked_att && !strcmp(is_stroked_att, "false"))
500 is_stroked = 0;
501 if (!is_stroked)
502 *skipped_stroke = 1;
503
504 point_x = point_y = 0;
505 size_x = size_y = 0;
506
507 xps_parse_point(ctx, doc, point_att, &point_x, &point_y);
508 xps_parse_point(ctx, doc, size_att, &size_x, &size_y);
509 rotation_angle = fz_atof(rotation_angle_att);
510 is_large_arc = !strcmp(is_large_arc_att, "true");
511 is_clockwise = !strcmp(sweep_direction_att, "Clockwise");
512
513 if (stroking && !is_stroked)
514 {
515 fz_moveto(ctx, path, point_x, point_y);
516 return;
517 }
518
519 xps_draw_arc(ctx, doc, path, size_x, size_y, rotation_angle, is_large_arc, is_clockwise, point_x, point_y);
520}
521
522static void
523xps_parse_poly_quadratic_bezier_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
524{
525 char *points_att = fz_xml_att(root, "Points");
526 char *is_stroked_att = fz_xml_att(root, "IsStroked");
527 float x[2], y[2];
528 int is_stroked;
529 fz_point pt;
530 char *s;
531 int n;
532
533 if (!points_att)
534 {
535 fz_warn(ctx, "PolyQuadraticBezierSegment element has no points");
536 return;
537 }
538
539 is_stroked = 1;
540 if (is_stroked_att && !strcmp(is_stroked_att, "false"))
541 is_stroked = 0;
542 if (!is_stroked)
543 *skipped_stroke = 1;
544
545 s = points_att;
546 n = 0;
547 while (*s != 0)
548 {
549 while (*s == ' ') s++;
550 s = xps_parse_point(ctx, doc, s, &x[n], &y[n]);
551 n ++;
552 if (n == 2)
553 {
554 if (stroking && !is_stroked)
555 {
556 fz_moveto(ctx, path, x[1], y[1]);
557 }
558 else
559 {
560 pt = fz_currentpoint(ctx, path);
561 fz_curveto(ctx, path,
562 (pt.x + 2 * x[0]) / 3, (pt.y + 2 * y[0]) / 3,
563 (x[1] + 2 * x[0]) / 3, (y[1] + 2 * y[0]) / 3,
564 x[1], y[1]);
565 }
566 n = 0;
567 }
568 }
569}
570
571static void
572xps_parse_poly_bezier_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
573{
574 char *points_att = fz_xml_att(root, "Points");
575 char *is_stroked_att = fz_xml_att(root, "IsStroked");
576 float x[3], y[3];
577 int is_stroked;
578 char *s;
579 int n;
580
581 if (!points_att)
582 {
583 fz_warn(ctx, "PolyBezierSegment element has no points");
584 return;
585 }
586
587 is_stroked = 1;
588 if (is_stroked_att && !strcmp(is_stroked_att, "false"))
589 is_stroked = 0;
590 if (!is_stroked)
591 *skipped_stroke = 1;
592
593 s = points_att;
594 n = 0;
595 while (*s != 0)
596 {
597 while (*s == ' ') s++;
598 s = xps_parse_point(ctx, doc, s, &x[n], &y[n]);
599 n ++;
600 if (n == 3)
601 {
602 if (stroking && !is_stroked)
603 fz_moveto(ctx, path, x[2], y[2]);
604 else
605 fz_curveto(ctx, path, x[0], y[0], x[1], y[1], x[2], y[2]);
606 n = 0;
607 }
608 }
609}
610
611static void
612xps_parse_poly_line_segment(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke)
613{
614 char *points_att = fz_xml_att(root, "Points");
615 char *is_stroked_att = fz_xml_att(root, "IsStroked");
616 int is_stroked;
617 float x, y;
618 char *s;
619
620 if (!points_att)
621 {
622 fz_warn(ctx, "PolyLineSegment element has no points");
623 return;
624 }
625
626 is_stroked = 1;
627 if (is_stroked_att && !strcmp(is_stroked_att, "false"))
628 is_stroked = 0;
629 if (!is_stroked)
630 *skipped_stroke = 1;
631
632 s = points_att;
633 while (*s != 0)
634 {
635 while (*s == ' ') s++;
636 s = xps_parse_point(ctx, doc, s, &x, &y);
637 if (stroking && !is_stroked)
638 fz_moveto(ctx, path, x, y);
639 else
640 fz_lineto(ctx, path, x, y);
641 }
642}
643
644static void
645xps_parse_path_figure(fz_context *ctx, xps_document *doc, fz_path *path, fz_xml *root, int stroking)
646{
647 fz_xml *node;
648
649 char *is_closed_att;
650 char *start_point_att;
651 char *is_filled_att;
652
653 int is_closed = 0;
654 int is_filled = 1;
655 float start_x = 0;
656 float start_y = 0;
657
658 int skipped_stroke = 0;
659
660 is_closed_att = fz_xml_att(root, "IsClosed");
661 start_point_att = fz_xml_att(root, "StartPoint");
662 is_filled_att = fz_xml_att(root, "IsFilled");
663
664 if (is_closed_att)
665 is_closed = !strcmp(is_closed_att, "true");
666 if (is_filled_att)
667 is_filled = !strcmp(is_filled_att, "true");
668 if (start_point_att)
669 xps_parse_point(ctx, doc, start_point_att, &start_x, &start_y);
670
671 if (!stroking && !is_filled) /* not filled, when filling */
672 return;
673
674 fz_moveto(ctx, path, start_x, start_y);
675
676 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
677 {
678 if (fz_xml_is_tag(node, "ArcSegment"))
679 xps_parse_arc_segment(ctx, doc, path, node, stroking, &skipped_stroke);
680 if (fz_xml_is_tag(node, "PolyBezierSegment"))
681 xps_parse_poly_bezier_segment(ctx, doc, path, node, stroking, &skipped_stroke);
682 if (fz_xml_is_tag(node, "PolyLineSegment"))
683 xps_parse_poly_line_segment(ctx, doc, path, node, stroking, &skipped_stroke);
684 if (fz_xml_is_tag(node, "PolyQuadraticBezierSegment"))
685 xps_parse_poly_quadratic_bezier_segment(ctx, doc, path, node, stroking, &skipped_stroke);
686 }
687
688 if (is_closed)
689 {
690 if (stroking && skipped_stroke)
691 fz_lineto(ctx, path, start_x, start_y); /* we've skipped using fz_moveto... */
692 else
693 fz_closepath(ctx, path); /* no skipped segments, safe to closepath properly */
694 }
695}
696
697fz_path *
698xps_parse_path_geometry(fz_context *ctx, xps_document *doc, xps_resource *dict, fz_xml *root, int stroking, int *fill_rule)
699{
700 fz_xml *node;
701
702 char *figures_att;
703 char *fill_rule_att;
704 char *transform_att;
705
706 fz_xml *transform_tag = NULL;
707 fz_xml *figures_tag = NULL; /* only used by resource */
708
709 fz_matrix transform;
710 fz_path *path;
711
712 figures_att = fz_xml_att(root, "Figures");
713 fill_rule_att = fz_xml_att(root, "FillRule");
714 transform_att = fz_xml_att(root, "Transform");
715
716 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
717 {
718 if (fz_xml_is_tag(node, "PathGeometry.Transform"))
719 transform_tag = fz_xml_down(node);
720 }
721
722 xps_resolve_resource_reference(ctx, doc, dict, &transform_att, &transform_tag, NULL);
723 xps_resolve_resource_reference(ctx, doc, dict, &figures_att, &figures_tag, NULL);
724
725 if (fill_rule_att)
726 {
727 if (!strcmp(fill_rule_att, "NonZero"))
728 *fill_rule = 1;
729 if (!strcmp(fill_rule_att, "EvenOdd"))
730 *fill_rule = 0;
731 }
732
733 transform = xps_parse_transform(ctx, doc, transform_att, transform_tag, fz_identity);
734
735 if (figures_att)
736 path = xps_parse_abbreviated_geometry(ctx, doc, figures_att, fill_rule);
737 else
738 path = fz_new_path(ctx);
739
740 fz_try(ctx)
741 {
742 if (figures_tag)
743 xps_parse_path_figure(ctx, doc, path, figures_tag, stroking);
744
745 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
746 {
747 if (fz_xml_is_tag(node, "PathFigure"))
748 xps_parse_path_figure(ctx, doc, path, node, stroking);
749 }
750
751 if (transform_att || transform_tag)
752 fz_transform_path(ctx, path, transform);
753 }
754 fz_catch(ctx)
755 {
756 fz_drop_path(ctx, path);
757 fz_rethrow(ctx);
758 }
759
760 return path;
761}
762
763static int
764xps_parse_line_cap(char *attr)
765{
766 if (attr)
767 {
768 if (!strcmp(attr, "Flat")) return 0;
769 if (!strcmp(attr, "Round")) return 1;
770 if (!strcmp(attr, "Square")) return 2;
771 if (!strcmp(attr, "Triangle")) return 3;
772 }
773 return 0;
774}
775
776void
777xps_clip(fz_context *ctx, xps_document *doc, fz_matrix ctm, xps_resource *dict, char *clip_att, fz_xml *clip_tag)
778{
779 fz_device *dev = doc->dev;
780 fz_path *path;
781 int fill_rule = 0;
782
783 if (clip_att)
784 path = xps_parse_abbreviated_geometry(ctx, doc, clip_att, &fill_rule);
785 else if (clip_tag)
786 path = xps_parse_path_geometry(ctx, doc, dict, clip_tag, 0, &fill_rule);
787 else
788 path = fz_new_path(ctx);
789 fz_clip_path(ctx, dev, path, fill_rule == 0, ctm, fz_infinite_rect);
790 fz_drop_path(ctx, path);
791}
792
793/*
794 * Parse an XPS <Path> element, and call relevant ghostscript
795 * functions for drawing and/or clipping the child elements.
796 */
797
798void
799xps_parse_path(fz_context *ctx, xps_document *doc, fz_matrix ctm, char *base_uri, xps_resource *dict, fz_xml *root)
800{
801 fz_device *dev = doc->dev;
802
803 fz_xml *node;
804
805 char *fill_uri;
806 char *stroke_uri;
807 char *opacity_mask_uri;
808
809 char *transform_att;
810 char *clip_att;
811 char *data_att;
812 char *fill_att;
813 char *stroke_att;
814 char *opacity_att;
815 char *opacity_mask_att;
816
817 fz_xml *transform_tag = NULL;
818 fz_xml *clip_tag = NULL;
819 fz_xml *data_tag = NULL;
820 fz_xml *fill_tag = NULL;
821 fz_xml *stroke_tag = NULL;
822 fz_xml *opacity_mask_tag = NULL;
823
824 char *fill_opacity_att = NULL;
825 char *stroke_opacity_att = NULL;
826
827 char *stroke_dash_array_att;
828 char *stroke_dash_cap_att;
829 char *stroke_dash_offset_att;
830 char *stroke_end_line_cap_att;
831 char *stroke_start_line_cap_att;
832 char *stroke_line_join_att;
833 char *stroke_miter_limit_att;
834 char *stroke_thickness_att;
835
836 fz_stroke_state *stroke = NULL;
837 float samples[FZ_MAX_COLORS];
838 fz_colorspace *colorspace;
839 fz_path *path = NULL;
840 fz_path *stroke_path = NULL;
841 fz_rect area;
842 int fill_rule;
843 int dash_len = 0;
844
845 /*
846 * Extract attributes and extended attributes.
847 */
848
849 transform_att = fz_xml_att(root, "RenderTransform");
850 clip_att = fz_xml_att(root, "Clip");
851 data_att = fz_xml_att(root, "Data");
852 fill_att = fz_xml_att(root, "Fill");
853 stroke_att = fz_xml_att(root, "Stroke");
854 opacity_att = fz_xml_att(root, "Opacity");
855 opacity_mask_att = fz_xml_att(root, "OpacityMask");
856
857 stroke_dash_array_att = fz_xml_att(root, "StrokeDashArray");
858 stroke_dash_cap_att = fz_xml_att(root, "StrokeDashCap");
859 stroke_dash_offset_att = fz_xml_att(root, "StrokeDashOffset");
860 stroke_end_line_cap_att = fz_xml_att(root, "StrokeEndLineCap");
861 stroke_start_line_cap_att = fz_xml_att(root, "StrokeStartLineCap");
862 stroke_line_join_att = fz_xml_att(root, "StrokeLineJoin");
863 stroke_miter_limit_att = fz_xml_att(root, "StrokeMiterLimit");
864 stroke_thickness_att = fz_xml_att(root, "StrokeThickness");
865
866 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
867 {
868 if (fz_xml_is_tag(node, "Path.RenderTransform"))
869 transform_tag = fz_xml_down(node);
870 if (fz_xml_is_tag(node, "Path.OpacityMask"))
871 opacity_mask_tag = fz_xml_down(node);
872 if (fz_xml_is_tag(node, "Path.Clip"))
873 clip_tag = fz_xml_down(node);
874 if (fz_xml_is_tag(node, "Path.Fill"))
875 fill_tag = fz_xml_down(node);
876 if (fz_xml_is_tag(node, "Path.Stroke"))
877 stroke_tag = fz_xml_down(node);
878 if (fz_xml_is_tag(node, "Path.Data"))
879 data_tag = fz_xml_down(node);
880 }
881
882 fill_uri = base_uri;
883 stroke_uri = base_uri;
884 opacity_mask_uri = base_uri;
885
886 xps_resolve_resource_reference(ctx, doc, dict, &data_att, &data_tag, NULL);
887 xps_resolve_resource_reference(ctx, doc, dict, &clip_att, &clip_tag, NULL);
888 xps_resolve_resource_reference(ctx, doc, dict, &transform_att, &transform_tag, NULL);
889 xps_resolve_resource_reference(ctx, doc, dict, &fill_att, &fill_tag, &fill_uri);
890 xps_resolve_resource_reference(ctx, doc, dict, &stroke_att, &stroke_tag, &stroke_uri);
891 xps_resolve_resource_reference(ctx, doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri);
892
893 /*
894 * Act on the information we have gathered:
895 */
896
897 if (!data_att && !data_tag)
898 return;
899
900 if (fz_xml_is_tag(fill_tag, "SolidColorBrush"))
901 {
902 fill_opacity_att = fz_xml_att(fill_tag, "Opacity");
903 fill_att = fz_xml_att(fill_tag, "Color");
904 fill_tag = NULL;
905 }
906
907 if (fz_xml_is_tag(stroke_tag, "SolidColorBrush"))
908 {
909 stroke_opacity_att = fz_xml_att(stroke_tag, "Opacity");
910 stroke_att = fz_xml_att(stroke_tag, "Color");
911 stroke_tag = NULL;
912 }
913
914 if (stroke_att || stroke_tag)
915 {
916 if (stroke_dash_array_att)
917 {
918 char *s = stroke_dash_array_att;
919
920 while (*s)
921 {
922 while (*s == ' ')
923 s++;
924 if (*s) /* needed in case of a space before the last quote */
925 dash_len++;
926
927 while (*s && *s != ' ')
928 s++;
929 }
930 }
931 stroke = fz_new_stroke_state_with_dash_len(ctx, dash_len);
932 stroke->start_cap = xps_parse_line_cap(stroke_start_line_cap_att);
933 stroke->dash_cap = xps_parse_line_cap(stroke_dash_cap_att);
934 stroke->end_cap = xps_parse_line_cap(stroke_end_line_cap_att);
935
936 stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
937 if (stroke_line_join_att)
938 {
939 if (!strcmp(stroke_line_join_att, "Miter")) stroke->linejoin = FZ_LINEJOIN_MITER_XPS;
940 if (!strcmp(stroke_line_join_att, "Round")) stroke->linejoin = FZ_LINEJOIN_ROUND;
941 if (!strcmp(stroke_line_join_att, "Bevel")) stroke->linejoin = FZ_LINEJOIN_BEVEL;
942 }
943
944 stroke->miterlimit = 10;
945 if (stroke_miter_limit_att)
946 stroke->miterlimit = fz_atof(stroke_miter_limit_att);
947
948 stroke->linewidth = 1;
949 if (stroke_thickness_att)
950 stroke->linewidth = fz_atof(stroke_thickness_att);
951
952 stroke->dash_phase = 0;
953 stroke->dash_len = 0;
954 if (stroke_dash_array_att)
955 {
956 char *s = stroke_dash_array_att;
957
958 if (stroke_dash_offset_att)
959 stroke->dash_phase = fz_atof(stroke_dash_offset_att) * stroke->linewidth;
960
961 while (*s)
962 {
963 while (*s == ' ')
964 s++;
965 if (*s) /* needed in case of a space before the last quote */
966 stroke->dash_list[stroke->dash_len++] = fz_atof(s) * stroke->linewidth;
967 while (*s && *s != ' ')
968 s++;
969 }
970 if (dash_len > 0)
971 {
972 /* fz_stroke_path doesn't draw non-empty paths with phase length zero */
973 float phase_len = 0;
974 int i;
975 for (i = 0; i < dash_len; i++)
976 phase_len += stroke->dash_list[i];
977 if (phase_len == 0)
978 dash_len = 0;
979 }
980 stroke->dash_len = dash_len;
981 }
982 }
983
984 ctm = xps_parse_transform(ctx, doc, transform_att, transform_tag, ctm);
985
986 if (clip_att || clip_tag)
987 xps_clip(ctx, doc, ctm, dict, clip_att, clip_tag);
988
989 fz_try(ctx)
990 {
991 fill_rule = 0;
992 if (data_att)
993 path = xps_parse_abbreviated_geometry(ctx, doc, data_att, &fill_rule);
994 else if (data_tag)
995 {
996 path = xps_parse_path_geometry(ctx, doc, dict, data_tag, 0, &fill_rule);
997 // /home/sebras/src/jxr/fts_06xx.xps
998 if (stroke_att || stroke_tag)
999 stroke_path = xps_parse_path_geometry(ctx, doc, dict, data_tag, 1, &fill_rule);
1000 }
1001 if (!stroke_path)
1002 stroke_path = path;
1003
1004 if (stroke_att || stroke_tag)
1005 {
1006 area = fz_bound_path(ctx, stroke_path, stroke, ctm);
1007 if (stroke_path != path && (fill_att || fill_tag)) {
1008 fz_rect bounds = fz_bound_path(ctx, path, NULL, ctm);
1009 area = fz_union_rect(area, bounds);
1010 }
1011 }
1012 else
1013 area = fz_bound_path(ctx, path, NULL, ctm);
1014
1015 xps_begin_opacity(ctx, doc, ctm, area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
1016
1017 if (fill_att)
1018 {
1019 xps_parse_color(ctx, doc, base_uri, fill_att, &colorspace, samples);
1020 if (fill_opacity_att)
1021 samples[0] *= fz_atof(fill_opacity_att);
1022 xps_set_color(ctx, doc, colorspace, samples);
1023 fz_fill_path(ctx, dev, path, fill_rule == 0, ctm,
1024 doc->colorspace, doc->color, doc->alpha, fz_default_color_params);
1025 }
1026
1027 if (fill_tag)
1028 {
1029 fz_clip_path(ctx, dev, path, fill_rule == 0, ctm, area);
1030 xps_parse_brush(ctx, doc, ctm, area, fill_uri, dict, fill_tag);
1031 fz_pop_clip(ctx, dev);
1032 }
1033
1034 if (stroke_att)
1035 {
1036 xps_parse_color(ctx, doc, base_uri, stroke_att, &colorspace, samples);
1037 if (stroke_opacity_att)
1038 samples[0] *= fz_atof(stroke_opacity_att);
1039 xps_set_color(ctx, doc, colorspace, samples);
1040 fz_stroke_path(ctx, dev, stroke_path, stroke, ctm,
1041 doc->colorspace, doc->color, doc->alpha, fz_default_color_params);
1042 }
1043
1044 if (stroke_tag)
1045 {
1046 fz_clip_stroke_path(ctx, dev, stroke_path, stroke, ctm, area);
1047 xps_parse_brush(ctx, doc, ctm, area, stroke_uri, dict, stroke_tag);
1048 fz_pop_clip(ctx, dev);
1049 }
1050
1051 xps_end_opacity(ctx, doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag);
1052 }
1053 fz_always(ctx)
1054 {
1055 if (stroke_path != path)
1056 fz_drop_path(ctx, stroke_path);
1057 fz_drop_path(ctx, path);
1058 fz_drop_stroke_state(ctx, stroke);
1059 }
1060 fz_catch(ctx)
1061 fz_rethrow(ctx);
1062
1063 if (clip_att || clip_tag)
1064 fz_pop_clip(ctx, dev);
1065}
1066