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