comparison mupdf-source/source/xps/xps-path.c @ 3:2c135c81b16c

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