1 /* GXPSPath
2 *
3 * Copyright (C) 2011 Carlos Garcia Campos <carlosgc@gnome.org>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include <config.h>
21
22 #include <string.h>
23
24 #include "gxps-path.h"
25 #include "gxps-matrix.h"
26 #include "gxps-brush.h"
27 #include "gxps-parse-utils.h"
28 #include "gxps-debug.h"
29
30 typedef enum {
31 PD_TOKEN_INVALID,
32 PD_TOKEN_NUMBER,
33 PD_TOKEN_COMMA,
34 PD_TOKEN_COMMAND,
35 PD_TOKEN_EOF
36 } PathDataTokenType;
37
38 typedef struct {
39 gchar *iter;
40 gchar *end;
41 PathDataTokenType type;
42 gdouble number;
43 gchar command;
44 } PathDataToken;
45
46 GXPSPath *
gxps_path_new(GXPSRenderContext * ctx)47 gxps_path_new (GXPSRenderContext *ctx)
48 {
49 GXPSPath *path;
50
51 path = g_slice_new0 (GXPSPath);
52 path->ctx = ctx;
53
54 /* Default values */
55 path->fill_rule = CAIRO_FILL_RULE_EVEN_ODD;
56 path->line_width = 1.0;
57 path->line_cap = CAIRO_LINE_CAP_BUTT;
58 path->line_join = CAIRO_LINE_JOIN_MITER;
59 path->miter_limit = 10.0;
60 path->opacity = 1.0;
61 path->is_filled = TRUE;
62 path->is_stroked = TRUE;
63
64 return path;
65 }
66
67 void
gxps_path_free(GXPSPath * path)68 gxps_path_free (GXPSPath *path)
69 {
70 if (G_UNLIKELY (!path))
71 return;
72
73 g_free (path->data);
74 g_free (path->clip_data);
75 cairo_pattern_destroy (path->fill_pattern);
76 cairo_pattern_destroy (path->stroke_pattern);
77 cairo_pattern_destroy (path->opacity_mask);
78 g_free (path->dash);
79
80 g_slice_free (GXPSPath, path);
81 }
82
83 static const gchar *
path_data_token_type_to_string(PathDataTokenType type)84 path_data_token_type_to_string (PathDataTokenType type)
85 {
86 switch (type) {
87 case PD_TOKEN_INVALID:
88 return "Invalid";
89 case PD_TOKEN_NUMBER:
90 return "Number";
91 case PD_TOKEN_COMMA:
92 return "Comma";
93 case PD_TOKEN_COMMAND:
94 return "Command";
95 case PD_TOKEN_EOF:
96 return "Eof";
97 default:
98 g_assert_not_reached ();
99 }
100
101 return NULL;
102 }
103
104 #ifdef GXPS_ENABLE_DEBUG
105 static void
print_token(PathDataToken * token)106 print_token (PathDataToken *token)
107 {
108 switch (token->type) {
109 case PD_TOKEN_INVALID:
110 g_debug ("Invalid token");
111 break;
112 case PD_TOKEN_NUMBER:
113 g_debug ("Token number: %f", token->number);
114 break;
115 case PD_TOKEN_COMMA:
116 g_debug ("Token comma");
117 break;
118 case PD_TOKEN_COMMAND:
119 g_debug ("Token command %c", token->command);
120 break;
121 case PD_TOKEN_EOF:
122 g_debug ("Token EOF");
123 break;
124 default:
125 g_assert_not_reached ();
126 }
127 }
128 #endif /* GXPS_ENABLE_DEBUG */
129
130 static inline gboolean
advance_char(PathDataToken * token)131 advance_char (PathDataToken *token)
132 {
133 token->iter++;
134
135 if (G_UNLIKELY (token->iter == token->end))
136 return FALSE;
137
138 return TRUE;
139 }
140
141 static inline gboolean
_isspace(char c)142 _isspace (char c)
143 {
144 return c == ' ' || c == '\t';
145 }
146
147 static void
skip_spaces(PathDataToken * token)148 skip_spaces (PathDataToken *token)
149 {
150 do {
151 if (!_isspace (*token->iter))
152 return;
153 } while (advance_char (token));
154 }
155
156 static gboolean
path_data_iter_next(PathDataToken * token,GError ** error)157 path_data_iter_next (PathDataToken *token,
158 GError **error)
159 {
160 gchar c;
161
162 skip_spaces (token);
163
164 if (token->iter == token->end) {
165 token->type = PD_TOKEN_EOF;
166 GXPS_DEBUG (print_token (token));
167
168 return TRUE;
169 }
170
171 c = *token->iter;
172
173 if (g_ascii_isdigit (c) || c == '+' || c == '-') {
174 gchar *start;
175 gchar *str;
176
177 start = token->iter;
178 gxps_parse_skip_number (&token->iter, token->end);
179 str = g_strndup (start, token->iter - start);
180 if (!gxps_value_get_double (str, &token->number)) {
181 g_set_error (error,
182 GXPS_PAGE_ERROR,
183 GXPS_PAGE_ERROR_RENDER,
184 "Error parsing abreviated path: error converting token %s (%s) to double at %s",
185 path_data_token_type_to_string (token->type),
186 str, token->iter);
187 g_free (str);
188
189 return FALSE;
190 }
191 g_free (str);
192 token->type = PD_TOKEN_NUMBER;
193 } else if (c == ',') {
194 token->type = PD_TOKEN_COMMA;
195 token->iter++;
196 } else if (g_ascii_isalpha (c)) {
197 token->command = c;
198 token->type = PD_TOKEN_COMMAND;
199 token->iter++;
200 } else {
201 token->type = PD_TOKEN_INVALID;
202 token->iter++;
203 }
204
205 GXPS_DEBUG (print_token (token));
206
207 return TRUE;
208 }
209
210 static void
path_data_parse_error(PathDataToken * token,PathDataTokenType expected,GError ** error)211 path_data_parse_error (PathDataToken *token,
212 PathDataTokenType expected,
213 GError **error)
214 {
215 if (expected == PD_TOKEN_INVALID)
216 g_set_error (error,
217 GXPS_PAGE_ERROR,
218 GXPS_PAGE_ERROR_RENDER,
219 "Error parsing abreviated path: unexpected token %s at %s",
220 path_data_token_type_to_string (token->type),
221 token->iter);
222 else
223 g_set_error (error,
224 GXPS_PAGE_ERROR,
225 GXPS_PAGE_ERROR_RENDER,
226 "Error parsing abreviated path: expected token %s, but %s found at %s",
227 path_data_token_type_to_string (token->type),
228 path_data_token_type_to_string (expected),
229 token->iter);
230 }
231
232 static gboolean
path_data_get_point(PathDataToken * token,gdouble * x,gdouble * y,GError ** error)233 path_data_get_point (PathDataToken *token,
234 gdouble *x,
235 gdouble *y,
236 GError **error)
237 {
238 *x = token->number;
239
240 if (!path_data_iter_next (token, error))
241 return FALSE;
242 if (token->type != PD_TOKEN_COMMA) {
243 path_data_parse_error (token, PD_TOKEN_COMMA, error);
244 return FALSE;
245 }
246
247 if (!path_data_iter_next (token, error))
248 return FALSE;
249 if (token->type != PD_TOKEN_NUMBER) {
250 path_data_parse_error (token, PD_TOKEN_NUMBER, error);
251 return FALSE;
252 }
253 *y = token->number;
254
255 return TRUE;
256 }
257
258 gboolean
gxps_path_parse(const gchar * data,cairo_t * cr,GError ** error)259 gxps_path_parse (const gchar *data,
260 cairo_t *cr,
261 GError **error)
262 {
263 PathDataToken token;
264 gdouble control_point_x;
265 gdouble control_point_y;
266
267 token.iter = (gchar *)data;
268 token.end = token.iter + strlen (data);
269
270 if (!path_data_iter_next (&token, error))
271 return FALSE;
272 if (G_UNLIKELY (token.type != PD_TOKEN_COMMAND))
273 return TRUE;
274
275 control_point_x = control_point_y = 0;
276
277 do {
278 gchar command = token.command;
279 gboolean is_rel = FALSE;
280
281 if (!path_data_iter_next (&token, error))
282 return FALSE;
283
284 switch (command) {
285 /* Move */
286 case 'm':
287 is_rel = TRUE;
288 case 'M':
289 while (token.type == PD_TOKEN_NUMBER) {
290 gdouble x, y;
291
292 if (!path_data_get_point (&token, &x, &y, error))
293 return FALSE;
294
295 GXPS_DEBUG (g_message ("%s (%f, %f)", is_rel ? "rel_move_to" : "move_to", x, y));
296
297 if (is_rel)
298 cairo_rel_move_to (cr, x, y);
299 else
300 cairo_move_to (cr, x, y);
301
302 if (!path_data_iter_next (&token, error))
303 return FALSE;
304 }
305 control_point_x = control_point_y = 0;
306 break;
307 /* Line */
308 case 'l':
309 is_rel = TRUE;
310 case 'L':
311 while (token.type == PD_TOKEN_NUMBER) {
312 gdouble x, y;
313
314 if (!path_data_get_point (&token, &x, &y, error))
315 return FALSE;
316
317 GXPS_DEBUG (g_message ("%s (%f, %f)", is_rel ? "rel_line_to" : "line_to", x, y));
318
319 if (is_rel)
320 cairo_rel_line_to (cr, x, y);
321 else
322 cairo_line_to (cr, x, y);
323
324 if (!path_data_iter_next (&token, error))
325 return FALSE;
326 }
327 control_point_x = control_point_y = 0;
328 break;
329 /* Horizontal Line */
330 case 'h':
331 is_rel = TRUE;
332 case 'H':
333 while (token.type == PD_TOKEN_NUMBER) {
334 gdouble x, y;
335 gdouble offset;
336
337 offset = token.number;
338
339 GXPS_DEBUG (g_message ("%s (%f)", is_rel ? "rel_hline_to" : "hline_to", offset));
340
341 cairo_get_current_point (cr, &x, &y);
342 x = is_rel ? x + offset : offset;
343 cairo_line_to (cr, x, y);
344
345 if (!path_data_iter_next (&token, error))
346 return FALSE;
347 }
348 control_point_x = control_point_y = 0;
349 break;
350 /* Vertical Line */
351 case 'v':
352 is_rel = TRUE;
353 case 'V':
354 while (token.type == PD_TOKEN_NUMBER) {
355 gdouble x, y;
356 gdouble offset;
357
358 offset = token.number;
359
360 GXPS_DEBUG (g_message ("%s (%f)", is_rel ? "rel_vline_to" : "vline_to", offset));
361
362 cairo_get_current_point (cr, &x, &y);
363 y = is_rel ? y + offset : offset;
364 cairo_line_to (cr, x, y);
365
366 if (!path_data_iter_next (&token, error))
367 return FALSE;
368 }
369 control_point_x = control_point_y = 0;
370 break;
371 /* Cubic Bézier curve */
372 case 'c':
373 is_rel = TRUE;
374 case 'C':
375 while (token.type == PD_TOKEN_NUMBER) {
376 gdouble x1, y1, x2, y2, x3, y3;
377
378 if (!path_data_get_point (&token, &x1, &y1, error))
379 return FALSE;
380
381 if (!path_data_iter_next (&token, error))
382 return FALSE;
383 if (!path_data_get_point (&token, &x2, &y2, error))
384 return FALSE;
385
386 if (!path_data_iter_next (&token, error))
387 return FALSE;
388 if (!path_data_get_point (&token, &x3, &y3, error))
389 return FALSE;
390
391 GXPS_DEBUG (g_message ("%s (%f, %f, %f, %f, %f, %f)", is_rel ? "rel_curve_to" : "curve_to",
392 x1, y1, x2, y2, x3, y3));
393
394 if (is_rel)
395 cairo_rel_curve_to (cr, x1, y1, x2, y2, x3, y3);
396 else
397 cairo_curve_to (cr, x1, y1, x2, y2, x3, y3);
398
399 control_point_x = x3 - x2;
400 control_point_y = y3 - y2;
401
402 if (!path_data_iter_next (&token, error))
403 return FALSE;
404 }
405 break;
406 /* Quadratic Bézier curve */
407 case 'q':
408 is_rel = TRUE;
409 case 'Q':
410 while (token.type == PD_TOKEN_NUMBER) {
411 gdouble x1, y1, x2, y2;
412 gdouble x, y;
413
414 if (!path_data_get_point (&token, &x1, &y1, error))
415 return FALSE;
416
417 if (!path_data_iter_next (&token, error))
418 return FALSE;
419 if (!path_data_get_point (&token, &x2, &y2, error))
420 return FALSE;
421
422 GXPS_DEBUG (g_message ("%s (%f, %f, %f, %f)", is_rel ? "rel_quad_curve_to" : "quad_curve_to",
423 x1, y1, x2, y2));
424
425 cairo_get_current_point (cr, &x, &y);
426 x1 += is_rel ? x : 0;
427 y1 += is_rel ? y : 0;
428 x2 += is_rel ? x : 0;
429 y2 += is_rel ? y : 0;
430 cairo_curve_to (cr,
431 2.0 / 3.0 * x1 + 1.0 / 3.0 * x,
432 2.0 / 3.0 * y1 + 1.0 / 3.0 * y,
433 2.0 / 3.0 * x1 + 1.0 / 3.0 * x2,
434 2.0 / 3.0 * y1 + 1.0 / 3.0 * y2,
435 x2, y2);
436
437 if (!path_data_iter_next (&token, error))
438 return FALSE;
439 }
440 control_point_x = control_point_y = 0;
441 break;
442 /* Smooth Cubic Bézier curve */
443 case 's':
444 is_rel = TRUE;
445 case 'S':
446 while (token.type == PD_TOKEN_NUMBER) {
447 gdouble x2, y2, x3, y3;
448
449 if (!path_data_get_point (&token, &x2, &y2, error))
450 return FALSE;
451
452 if (!path_data_iter_next (&token, error))
453 return FALSE;
454 if (!path_data_get_point (&token, &x3, &y3, error))
455 return FALSE;
456
457 GXPS_DEBUG (g_message ("%s (%f, %f, %f, %f, %f, %f)", is_rel ? "rel_smooth_curve_to" : "smooth_curve_to",
458 control_point_x, control_point_y, x2, y2, x3, y3));
459
460 if (is_rel) {
461 cairo_rel_curve_to (cr, control_point_x, control_point_y, x2, y2, x3, y3);
462 } else {
463 gdouble x, y;
464
465 cairo_get_current_point (cr, &x, &y);
466 cairo_curve_to (cr, x + control_point_x, y + control_point_y, x2, y2, x3, y3);
467 }
468
469 control_point_x = x3 - x2;
470 control_point_y = y3 - y2;
471
472 if (!path_data_iter_next (&token, error))
473 return FALSE;
474 }
475 break;
476 /* Elliptical Arc */
477 case 'a':
478 is_rel = TRUE;
479 case 'A':
480 while (token.type == PD_TOKEN_NUMBER) {
481 gdouble xr, yr, x, y;
482 #ifdef GXPS_ENABLE_DEBUG
483 /* TODO: for now these variables are only used
484 * in debug mode.
485 */
486 gdouble rx, farc, fsweep;
487 #endif
488
489 if (!path_data_get_point (&token, &xr, &yr, error))
490 return FALSE;
491
492 if (!path_data_iter_next (&token, error))
493 return FALSE;
494 if (token.type != PD_TOKEN_NUMBER) {
495 path_data_parse_error (&token, PD_TOKEN_NUMBER, error);
496 return FALSE;
497 }
498 #ifdef GXPS_ENABLE_DEBUG
499 rx = token.number;
500 #endif
501
502 if (!path_data_iter_next (&token, error))
503 return FALSE;
504 if (token.type != PD_TOKEN_NUMBER) {
505 path_data_parse_error (&token, PD_TOKEN_NUMBER, error);
506 return FALSE;
507 }
508 #ifdef GXPS_ENABLE_DEBUG
509 farc = token.number;
510 #endif
511
512 if (!path_data_iter_next (&token, error))
513 return FALSE;
514 if (token.type != PD_TOKEN_NUMBER) {
515 path_data_parse_error (&token, PD_TOKEN_NUMBER, error);
516 return FALSE;
517 }
518 #ifdef GXPS_ENABLE_DEBUG
519 fsweep = token.number;
520 #endif
521
522 if (!path_data_iter_next (&token, error))
523 return FALSE;
524 if (!path_data_get_point (&token, &x, &y, error))
525 return FALSE;
526
527 GXPS_DEBUG (g_message ("%s (%f, %f, %f, %f, %f, %f, %f)", is_rel ? "rel_arc" : "arc",
528 xr, yr, rx, farc, fsweep, x, y));
529 GXPS_DEBUG (g_debug ("Unsupported command in path: %c", command));
530
531 if (!path_data_iter_next (&token, error))
532 return FALSE;
533 }
534 control_point_x = control_point_y = 0;
535 break;
536 /* Close */
537 case 'z':
538 is_rel = TRUE;
539 case 'Z':
540 cairo_close_path (cr);
541 GXPS_DEBUG (g_message ("close_path"));
542 control_point_x = control_point_y = 0;
543 break;
544 /* Fill Rule */
545 case 'F': {
546 gint fill_rule;
547
548 fill_rule = (gint)token.number;
549 cairo_set_fill_rule (cr,
550 (fill_rule == 0) ?
551 CAIRO_FILL_RULE_EVEN_ODD :
552 CAIRO_FILL_RULE_WINDING);
553 GXPS_DEBUG (g_message ("set_fill_rule (%s)", (fill_rule == 0) ? "EVEN_ODD" : "WINDING"));
554
555 if (!path_data_iter_next (&token, error))
556 return FALSE;
557 }
558 control_point_x = control_point_y = 0;
559 break;
560 default:
561 g_assert_not_reached ();
562 }
563 } while (token.type == PD_TOKEN_COMMAND);
564
565 return TRUE;
566 }
567
568 static gboolean
gxps_points_parse(const gchar * points,gdouble ** coords,guint * n_points)569 gxps_points_parse (const gchar *points,
570 gdouble **coords,
571 guint *n_points)
572 {
573 gchar **items;
574 guint i, j = 0;
575 gboolean retval = TRUE;
576
577 *n_points = 0;
578 items = g_strsplit (points, " ", -1);
579 if (!items)
580 return FALSE;
581
582 for (i = 0; items[i] != NULL; i++) {
583 if (*items[i] != '\0') /* don't count empty string */
584 (*n_points)++;
585 }
586
587 if (*n_points == 0)
588 return FALSE;
589
590 *coords = g_malloc (*n_points * 2 * sizeof (gdouble));
591
592 for (i = 0; items[i] != NULL; i++) {
593 gdouble x, y;
594
595 if (*items[i] == '\0')
596 continue;
597
598 if (!gxps_point_parse (items[i], &x, &y)) {
599 g_free (*coords);
600 retval = FALSE;
601 break;
602 }
603
604 coords[0][j++] = x;
605 coords[0][j++] = y;
606 }
607
608 g_strfreev (items);
609
610 return retval;
611 }
612
613 static void
path_geometry_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)614 path_geometry_start_element (GMarkupParseContext *context,
615 const gchar *element_name,
616 const gchar **names,
617 const gchar **values,
618 gpointer user_data,
619 GError **error)
620 {
621 GXPSPath *path = (GXPSPath *)user_data;
622
623 if (strcmp (element_name, "PathGeometry.Transform") == 0) {
624 GXPSMatrix *matrix;
625
626 matrix = gxps_matrix_new (path->ctx);
627 gxps_matrix_parser_push (context, matrix);
628 } else if (strcmp (element_name, "PathFigure") == 0) {
629 gint i;
630 gboolean has_start_point = FALSE;
631
632 for (i = 0; names[i] != NULL; i++) {
633 if (strcmp (names[i], "StartPoint") == 0) {
634 gdouble x, y;
635
636 if (!gxps_point_parse (values[i], &x, &y)) {
637 gxps_parse_error (context,
638 path->ctx->page->priv->source,
639 G_MARKUP_ERROR_INVALID_CONTENT,
640 "PathFigure", "StartPoint",
641 values[i], error);
642 return;
643 }
644
645 GXPS_DEBUG (g_message ("move_to (%f, %f)", x, y));
646 cairo_move_to (path->ctx->cr, x, y);
647 has_start_point = TRUE;
648 } else if (strcmp (names[i], "IsClosed") == 0) {
649 gboolean is_closed;
650
651 if (!gxps_value_get_boolean (values[i], &is_closed)) {
652 gxps_parse_error (context,
653 path->ctx->page->priv->source,
654 G_MARKUP_ERROR_INVALID_CONTENT,
655 "PathFigure", "IsClosed",
656 values[i], error);
657 return;
658 }
659 path->is_closed = is_closed;
660 } else if (strcmp (names[i], "IsFilled") == 0) {
661 gboolean is_filled;
662
663 if (!gxps_value_get_boolean (values[i], &is_filled)) {
664 gxps_parse_error (context,
665 path->ctx->page->priv->source,
666 G_MARKUP_ERROR_INVALID_CONTENT,
667 "PathFigure", "IsFilled",
668 values[i], error);
669 return;
670 }
671 path->is_filled = is_filled;
672 }
673 }
674
675 if (!has_start_point) {
676 gxps_parse_error (context,
677 path->ctx->page->priv->source,
678 G_MARKUP_ERROR_MISSING_ATTRIBUTE,
679 "PathFigure", "StartPoint",
680 NULL, error);
681 return;
682 }
683 } else if (strcmp (element_name, "PolyLineSegment") == 0) {
684 gint i, j;
685 const gchar *points_str = NULL;
686 gdouble *points = NULL;
687 guint n_points;
688 gboolean is_stroked = TRUE;
689
690 for (i = 0; names[i] != NULL; i++) {
691 if (strcmp (names[i], "Points") == 0) {
692 points_str = values[i];
693 } else if (strcmp (names[i], "IsStroked") == 0) {
694 if (!gxps_value_get_boolean (values[i], &is_stroked)) {
695 gxps_parse_error (context,
696 path->ctx->page->priv->source,
697 G_MARKUP_ERROR_INVALID_CONTENT,
698 "PolyLineSegment", "IsStroked",
699 points_str, error);
700 return;
701 }
702 }
703 }
704
705 if (!is_stroked)
706 return;
707
708 if (!points_str) {
709 gxps_parse_error (context,
710 path->ctx->page->priv->source,
711 G_MARKUP_ERROR_MISSING_ATTRIBUTE,
712 "PolyLineSegment", "Points",
713 NULL, error);
714 return;
715 }
716
717 if (!gxps_points_parse (points_str, &points, &n_points)) {
718 gxps_parse_error (context,
719 path->ctx->page->priv->source,
720 G_MARKUP_ERROR_INVALID_CONTENT,
721 "PolyLineSegment", "Points",
722 points_str, error);
723 return;
724 }
725
726 for (j = 0; j < n_points * 2; j += 2) {
727 GXPS_DEBUG (g_message ("line_to (%f, %f)", points[j], points[j + 1]));
728 cairo_line_to (path->ctx->cr, points[j], points[j + 1]);
729 }
730
731 g_free (points);
732 } else if (strcmp (element_name, "PolyBezierSegment") == 0) {
733 gint i, j;
734 const gchar *points_str = NULL;
735 gdouble *points = NULL;
736 guint n_points;
737 gboolean is_stroked = TRUE;
738
739 for (i = 0; names[i] != NULL; i++) {
740 if (strcmp (names[i], "Points") == 0) {
741 points_str = values[i];
742
743 } else if (strcmp (names[i], "IsStroked") == 0) {
744 if (!gxps_value_get_boolean (values[i], &is_stroked)) {
745 gxps_parse_error (context,
746 path->ctx->page->priv->source,
747 G_MARKUP_ERROR_INVALID_CONTENT,
748 "PolyBezierSegment", "IsStroked",
749 points_str, error);
750 return;
751 }
752 }
753 }
754
755 if (!is_stroked)
756 return;
757
758 if (!points_str) {
759 gxps_parse_error (context,
760 path->ctx->page->priv->source,
761 G_MARKUP_ERROR_MISSING_ATTRIBUTE,
762 "PolyBezierSegment", "Points",
763 NULL, error);
764 return;
765 }
766
767 if (!gxps_points_parse (points_str, &points, &n_points)) {
768 gxps_parse_error (context,
769 path->ctx->page->priv->source,
770 G_MARKUP_ERROR_INVALID_CONTENT,
771 "PolyBezierSegment", "Points",
772 points_str, error);
773 return;
774 }
775
776 for (j = 0; j < n_points * 2; j += 6) {
777 GXPS_DEBUG (g_message ("curve_to (%f, %f, %f, %f, %f, %f)",
778 points[j], points[j + 1],
779 points[j + 2], points[j + 3],
780 points[j + 4], points[j + 5]));
781 cairo_curve_to (path->ctx->cr,
782 points[j], points[j + 1],
783 points[j + 2], points[j + 3],
784 points[j + 4], points[j + 5]);
785 }
786
787 g_free (points);
788 } else if (strcmp (element_name, "PolyQuadraticBezierSegment") == 0) {
789 gint i, j;
790 const gchar *points_str = NULL;
791 gdouble *points = NULL;
792 guint n_points;
793 gboolean is_stroked = TRUE;
794
795 for (i = 0; names[i] != NULL; i++) {
796 if (strcmp (names[i], "Points") == 0) {
797 points_str = values[i];
798
799 } else if (strcmp (names[i], "IsStroked") == 0) {
800 if (!gxps_value_get_boolean (values[i], &is_stroked)) {
801 gxps_parse_error (context,
802 path->ctx->page->priv->source,
803 G_MARKUP_ERROR_INVALID_CONTENT,
804 "PolyQuadraticBezierSegment", "IsStroked",
805 points_str, error);
806 return;
807 }
808 }
809 }
810
811 if (!is_stroked)
812 return;
813
814 if (!points_str) {
815 gxps_parse_error (context,
816 path->ctx->page->priv->source,
817 G_MARKUP_ERROR_MISSING_ATTRIBUTE,
818 "PolyQuadraticBezierSegment", "Points",
819 NULL, error);
820 return;
821 }
822
823 if (!gxps_points_parse (points_str, &points, &n_points)) {
824 gxps_parse_error (context,
825 path->ctx->page->priv->source,
826 G_MARKUP_ERROR_INVALID_CONTENT,
827 "PolyQuadraticBezierSegment", "Points",
828 points_str, error);
829 return;
830 }
831
832 for (j = 0; j < n_points * 2; j += 4) {
833 gdouble x1, y1, x2, y2;
834 gdouble x, y;
835
836 x1 = points[j];
837 y1 = points[j + 1];
838 x2 = points[j + 2];
839 y2 = points[j + 3];
840
841 GXPS_DEBUG (g_message ("quad_curve_to (%f, %f, %f, %f)", x1, y1, x2, y2));
842 cairo_get_current_point (path->ctx->cr, &x, &y);
843 cairo_curve_to (path->ctx->cr,
844 2.0 / 3.0 * x1 + 1.0 / 3.0 * x,
845 2.0 / 3.0 * y1 + 1.0 / 3.0 * y,
846 2.0 / 3.0 * x1 + 1.0 / 3.0 * x2,
847 2.0 / 3.0 * y1 + 1.0 / 3.0 * y2,
848 x2, y2);
849 }
850
851 g_free (points);
852 } else if (strcmp (element_name, "ArcSegment") == 0) {
853 GXPS_DEBUG (g_debug ("Unsupported PathGeometry: ArcSegment"));
854 }
855 }
856
857 static void
path_geometry_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)858 path_geometry_end_element (GMarkupParseContext *context,
859 const gchar *element_name,
860 gpointer user_data,
861 GError **error)
862 {
863 GXPSPath *path = (GXPSPath *)user_data;
864
865 if (strcmp (element_name, "PathGeometry.Transform") == 0) {
866 GXPSMatrix *matrix;
867
868 matrix = g_markup_parse_context_pop (context);
869 GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
870 matrix->matrix.xx, matrix->matrix.yx,
871 matrix->matrix.xy, matrix->matrix.yy,
872 matrix->matrix.x0, matrix->matrix.y0));
873 cairo_transform (path->ctx->cr, &matrix->matrix);
874
875 gxps_matrix_free (matrix);
876 } else if (strcmp (element_name, "PathFigure") == 0) {
877 if (path->is_closed) {
878 GXPS_DEBUG (g_message ("close_path"));
879 cairo_close_path (path->ctx->cr);
880 }
881
882 if (path->stroke_pattern) {
883 cairo_set_line_width (path->ctx->cr, path->line_width);
884 if (path->dash && path->dash_len > 0)
885 cairo_set_dash (path->ctx->cr, path->dash, path->dash_len, path->dash_offset);
886 cairo_set_line_join (path->ctx->cr, path->line_join);
887 cairo_set_miter_limit (path->ctx->cr, path->miter_limit);
888 }
889
890 if (path->opacity_mask) {
891 gdouble x1 = 0, y1 = 0, x2 = 0, y2 = 0;
892 cairo_path_t *cairo_path;
893
894 if (path->stroke_pattern)
895 cairo_stroke_extents (path->ctx->cr, &x1, &y1, &x2, &y2);
896 else if (path->fill_pattern)
897 cairo_fill_extents (path->ctx->cr, &x1, &y1, &x2, &y2);
898
899 cairo_path = cairo_copy_path (path->ctx->cr);
900 cairo_new_path (path->ctx->cr);
901 cairo_rectangle (path->ctx->cr, x1, y1, x2 - x1, y2 - y1);
902 cairo_clip (path->ctx->cr);
903 cairo_push_group (path->ctx->cr);
904 cairo_append_path (path->ctx->cr, cairo_path);
905 cairo_path_destroy (cairo_path);
906 }
907
908 if (path->is_filled && path->fill_pattern) {
909 GXPS_DEBUG (g_message ("fill"));
910 cairo_set_source (path->ctx->cr, path->fill_pattern);
911 if (path->is_stroked && path->stroke_pattern)
912 cairo_fill_preserve (path->ctx->cr);
913 else
914 cairo_fill (path->ctx->cr);
915 }
916
917 if (path->stroke_pattern) {
918 GXPS_DEBUG (g_message ("stroke"));
919 cairo_set_source (path->ctx->cr, path->stroke_pattern);
920 cairo_stroke (path->ctx->cr);
921 }
922
923 if (path->opacity_mask) {
924 cairo_pop_group_to_source (path->ctx->cr);
925 cairo_mask (path->ctx->cr, path->opacity_mask);
926 }
927 }
928 }
929
930 static GMarkupParser path_geometry_parser = {
931 path_geometry_start_element,
932 path_geometry_end_element,
933 NULL,
934 NULL
935 };
936
937 static cairo_fill_rule_t
gxps_fill_rule_parse(const gchar * rule)938 gxps_fill_rule_parse (const gchar *rule)
939 {
940 if (strcmp (rule, "EvenOdd") == 0)
941 return CAIRO_FILL_RULE_EVEN_ODD;
942 else if (strcmp (rule, "NonZero") == 0)
943 return CAIRO_FILL_RULE_WINDING;
944 return CAIRO_FILL_RULE_EVEN_ODD;
945 }
946
947 static void
path_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)948 path_start_element (GMarkupParseContext *context,
949 const gchar *element_name,
950 const gchar **names,
951 const gchar **values,
952 gpointer user_data,
953 GError **error)
954 {
955 GXPSPath *path = (GXPSPath *)user_data;
956
957 if (strcmp (element_name, "Path.Fill") == 0) {
958 GXPSBrush *brush;
959
960 brush = gxps_brush_new (path->ctx);
961 gxps_brush_parser_push (context, brush);
962 } else if (strcmp (element_name, "Path.Stroke") == 0) {
963 GXPSBrush *brush;
964
965 brush = gxps_brush_new (path->ctx);
966 gxps_brush_parser_push (context, brush);
967 } else if (strcmp (element_name, "Path.Data") == 0) {
968 } else if (strcmp (element_name, "PathGeometry") == 0) {
969 gint i;
970
971 for (i = 0; names[i] != NULL; i++) {
972 if (strcmp (names[i], "Figures") == 0) {
973 path->data = g_strdup (values[i]);
974 } else if (strcmp (names[i], "FillRule") == 0) {
975 path->fill_rule = gxps_fill_rule_parse (values[i]);
976 GXPS_DEBUG (g_message ("set_fill_rule (%s)", values[i]));
977 } else if (strcmp (names[i], "Transform") == 0) {
978 cairo_matrix_t matrix;
979
980 if (!gxps_matrix_parse (values[i], &matrix)) {
981 gxps_parse_error (context,
982 path->ctx->page->priv->source,
983 G_MARKUP_ERROR_INVALID_CONTENT,
984 "PathGeometry", "Transform",
985 values[i], error);
986 return;
987 }
988 GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
989 matrix.xx, matrix.yx,
990 matrix.xy, matrix.yy,
991 matrix.x0, matrix.y0));
992 cairo_transform (path->ctx->cr, &matrix);
993 }
994 }
995
996 if (!path->data) {
997 cairo_set_fill_rule (path->ctx->cr, path->fill_rule);
998 if (path->clip_data) {
999 if (!gxps_path_parse (path->clip_data, path->ctx->cr, error))
1000 return;
1001 GXPS_DEBUG (g_message ("clip"));
1002 cairo_clip (path->ctx->cr);
1003 }
1004 g_markup_parse_context_push (context, &path_geometry_parser, path);
1005 }
1006 } else if (strcmp (element_name, "Path.RenderTransform") == 0) {
1007 GXPSMatrix *matrix;
1008
1009 matrix = gxps_matrix_new (path->ctx);
1010 gxps_matrix_parser_push (context, matrix);
1011 } else if (strcmp (element_name, "Path.OpacityMask") == 0) {
1012 GXPSBrush *brush;
1013
1014 brush = gxps_brush_new (path->ctx);
1015 gxps_brush_parser_push (context, brush);
1016 } else {
1017 GXPS_DEBUG (g_debug ("Unsupported path child %s", element_name));
1018 }
1019 }
1020
1021 static void
path_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)1022 path_end_element (GMarkupParseContext *context,
1023 const gchar *element_name,
1024 gpointer user_data,
1025 GError **error)
1026 {
1027 GXPSPath *path = (GXPSPath *)user_data;
1028
1029 if (strcmp (element_name, "Path.Fill") == 0) {
1030 GXPSBrush *brush;
1031
1032 brush = g_markup_parse_context_pop (context);
1033 path->fill_pattern = cairo_pattern_reference (brush->pattern);
1034 gxps_brush_free (brush);
1035 } else if (strcmp (element_name, "Path.Stroke") == 0) {
1036 GXPSBrush *brush;
1037
1038 brush = g_markup_parse_context_pop (context);
1039 path->stroke_pattern = cairo_pattern_reference (brush->pattern);
1040 gxps_brush_free (brush);
1041 } else if (strcmp (element_name, "Path.Data") == 0) {
1042 } else if (strcmp (element_name, "PathGeometry") == 0) {
1043 if (!path->data)
1044 g_markup_parse_context_pop (context);
1045 } else if (strcmp (element_name, "Path.RenderTransform") == 0) {
1046 GXPSMatrix *matrix;
1047
1048 matrix = g_markup_parse_context_pop (context);
1049 GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
1050 matrix->matrix.xx, matrix->matrix.yx,
1051 matrix->matrix.xy, matrix->matrix.yy,
1052 matrix->matrix.x0, matrix->matrix.y0));
1053 cairo_transform (path->ctx->cr, &matrix->matrix);
1054
1055 gxps_matrix_free (matrix);
1056 } else if (strcmp (element_name, "Path.OpacityMask") == 0) {
1057 GXPSBrush *brush;
1058
1059 brush = g_markup_parse_context_pop (context);
1060 if (!path->opacity_mask)
1061 path->opacity_mask = cairo_pattern_reference (brush->pattern);
1062 gxps_brush_free (brush);
1063 } else {
1064
1065 }
1066 }
1067
1068 static void
path_error(GMarkupParseContext * context,GError * error,gpointer user_data)1069 path_error (GMarkupParseContext *context,
1070 GError *error,
1071 gpointer user_data)
1072 {
1073 GXPSPath *path = (GXPSPath *)user_data;
1074 gxps_path_free (path);
1075 }
1076
1077 static GMarkupParser path_parser = {
1078 path_start_element,
1079 path_end_element,
1080 NULL,
1081 NULL,
1082 path_error
1083 };
1084
1085 void
gxps_path_parser_push(GMarkupParseContext * context,GXPSPath * path)1086 gxps_path_parser_push (GMarkupParseContext *context,
1087 GXPSPath *path)
1088 {
1089 g_markup_parse_context_push (context, &path_parser, path);
1090 }
1091