1 /* GXPSPage
2  *
3  * Copyright (C) 2010  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 <stdlib.h>
23 #include <string.h>
24 
25 #include "gxps-page-private.h"
26 #include "gxps-matrix.h"
27 #include "gxps-brush.h"
28 #include "gxps-path.h"
29 #include "gxps-glyphs.h"
30 #include "gxps-fonts.h"
31 #include "gxps-links.h"
32 #include "gxps-images.h"
33 #include "gxps-color.h"
34 #include "gxps-private.h"
35 #include "gxps-error.h"
36 #include "gxps-debug.h"
37 
38 /**
39  * SECTION:gxps-page
40  * @Short_description: Page of XPS document
41  * @Title: GXPSPage
42  * @See_also: #GXPSDocument, #GXPSLink, #GXPSLinkTarget
43  *
44  * #GXPSPage represents a page in a XPS document. #GXPSPage<!-- -->s
45  * can be rendered into a cairo context with gxps_page_render().
46  * #GXPSPage objects can not be created directly, they are retrieved
47  * from a #GXPSDocument with gxps_document_get_page().
48  */
49 
50 enum {
51 	PROP_0,
52 	PROP_ARCHIVE,
53 	PROP_SOURCE
54 };
55 
56 static void render_start_element (GMarkupParseContext  *context,
57 				  const gchar          *element_name,
58 				  const gchar         **names,
59 				  const gchar         **values,
60 				  gpointer              user_data,
61 				  GError              **error);
62 static void render_end_element   (GMarkupParseContext  *context,
63 				  const gchar          *element_name,
64 				  gpointer              user_data,
65 				  GError              **error);
66 static void initable_iface_init  (GInitableIface       *initable_iface);
67 
G_DEFINE_TYPE_WITH_CODE(GXPSPage,gxps_page,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,initable_iface_init))68 G_DEFINE_TYPE_WITH_CODE (GXPSPage, gxps_page, G_TYPE_OBJECT,
69 			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
70 
71 GQuark
72 gxps_page_error_quark (void)
73 {
74 	return g_quark_from_static_string ("gxps-page-error-quark");
75 }
76 
77 /* Images */
78 GXPSImage *
gxps_page_get_image(GXPSPage * page,const gchar * image_uri,GError ** error)79 gxps_page_get_image (GXPSPage    *page,
80 		     const gchar *image_uri,
81 		     GError     **error)
82 {
83 	GXPSImage *image;
84 
85 	if (page->priv->image_cache) {
86 		image = g_hash_table_lookup (page->priv->image_cache,
87 					     image_uri);
88 		if (image)
89 			return image;
90 	}
91 
92 	image = gxps_images_get_image (page->priv->zip, image_uri, error);
93 	if (!image)
94 		return NULL;
95 
96 	if (!page->priv->image_cache) {
97 		page->priv->image_cache = g_hash_table_new_full (g_str_hash,
98 								 g_str_equal,
99 								 (GDestroyNotify)g_free,
100 								 (GDestroyNotify)gxps_image_free);
101 	}
102 
103 	g_hash_table_insert (page->priv->image_cache,
104 			     g_strdup (image_uri),
105 			     image);
106 	return image;
107 }
108 
109 /* FixedPage parser */
110 static void
fixed_page_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)111 fixed_page_start_element (GMarkupParseContext  *context,
112 			  const gchar          *element_name,
113 			  const gchar         **names,
114 			  const gchar         **values,
115 			  gpointer              user_data,
116 			  GError              **error)
117 {
118 	GXPSPage *page = GXPS_PAGE (user_data);
119 	gint      i;
120 
121 	if (strcmp (element_name, "FixedPage") == 0) {
122 		for (i = 0; names[i] != NULL; i++) {
123 			if (strcmp (names[i], "Width") == 0) {
124 				if (!gxps_value_get_double_positive (values[i], &page->priv->width)) {
125                                         gxps_parse_error (context,
126                                                           page->priv->source,
127                                                           G_MARKUP_ERROR_MISSING_ATTRIBUTE,
128                                                           element_name, "Width",
129                                                           NULL, error);
130                                         return;
131                                 }
132 			} else if (strcmp (names[i], "Height") == 0) {
133 				if (!gxps_value_get_double_positive (values[i], &page->priv->height)) {
134                                         gxps_parse_error (context,
135                                                           page->priv->source,
136                                                           G_MARKUP_ERROR_MISSING_ATTRIBUTE,
137                                                           element_name, "Height",
138                                                           NULL, error);
139                                         return;
140                                 }
141 			} else if (strcmp (names[i], "xml:lang") == 0) {
142 				page->priv->lang = g_strdup (values[i]);
143 			} else if (strcmp (names[i], "ContentBox") == 0) {
144 				/* TODO */
145 			} else if (strcmp (names[i], "BleedBox") == 0) {
146 				/* TODO */
147 			} else if (strcmp (names[i], "Name") == 0) {
148 				page->priv->name = g_strdup (values[i]);
149 			}
150 		}
151 	}
152 }
153 
154 static const GMarkupParser fixed_page_parser = {
155 	fixed_page_start_element,
156 	NULL,
157 	NULL,
158 	NULL
159 };
160 
161 static gboolean
gxps_page_parse_fixed_page(GXPSPage * page,GError ** error)162 gxps_page_parse_fixed_page (GXPSPage *page,
163 			    GError  **error)
164 {
165 	GInputStream        *stream;
166 	GMarkupParseContext *ctx;
167 
168 	stream = gxps_archive_open (page->priv->zip,
169 				    page->priv->source);
170 	if (!stream) {
171 		g_set_error (error,
172 			     GXPS_ERROR,
173 			     GXPS_ERROR_SOURCE_NOT_FOUND,
174 			     "Page source %s not found in archive",
175 			     page->priv->source);
176 
177 		return FALSE;
178 	}
179 
180 	ctx = g_markup_parse_context_new (&fixed_page_parser, 0, page, NULL);
181 	gxps_parse_stream (ctx, stream, error);
182 	g_object_unref (stream);
183 	g_markup_parse_context_free (ctx);
184 
185 	return (*error != NULL) ? FALSE : TRUE;
186 }
187 
188 /* Page Render Parser */
189 static GMarkupParser render_parser = {
190 	render_start_element,
191 	render_end_element,
192 	NULL,
193 	NULL
194 };
195 
196 void
gxps_page_render_parser_push(GMarkupParseContext * context,GXPSRenderContext * ctx)197 gxps_page_render_parser_push (GMarkupParseContext *context,
198                               GXPSRenderContext   *ctx)
199 {
200         g_markup_parse_context_push (context, &render_parser, ctx);
201 }
202 
203 static gboolean
gxps_dash_array_parse(const gchar * dash,gdouble ** dashes_out,guint * num_dashes_out)204 gxps_dash_array_parse (const gchar *dash,
205 		       gdouble    **dashes_out,
206 		       guint       *num_dashes_out)
207 {
208 	gchar  **items;
209         gchar   *stripped_dash;
210 	guint    i;
211         gdouble *dashes;
212         guint    num_dashes;
213 
214         stripped_dash = g_strstrip (g_strdup (dash));
215 	items = g_strsplit (stripped_dash, " ", -1);
216         g_free (stripped_dash);
217 	if (!items)
218 		return FALSE;
219 
220 	num_dashes = g_strv_length (items);
221         if (num_dashes % 2 != 0) {
222                 g_strfreev (items);
223 
224                 return FALSE;
225         }
226 
227 	dashes = g_malloc (num_dashes * sizeof (gdouble));
228 	for (i = 0; i < num_dashes; i++) {
229                 if (!gxps_value_get_double_non_negative (items[i], &dashes[i])) {
230                         g_free (dashes);
231                         g_strfreev (items);
232 
233                         return FALSE;
234                 }
235         }
236 
237 	g_strfreev (items);
238 
239         *dashes_out = dashes;
240         *num_dashes_out = num_dashes;
241 
242 	return TRUE;
243 }
244 
245 static cairo_line_cap_t
gxps_line_cap_parse(const gchar * cap)246 gxps_line_cap_parse (const gchar *cap)
247 {
248 	if (strcmp (cap, "Flat") == 0)
249 		return CAIRO_LINE_CAP_BUTT;
250 	else if (strcmp (cap, "Round") == 0)
251 		return CAIRO_LINE_CAP_ROUND;
252 	else if (strcmp (cap, "Square") == 0)
253 		return CAIRO_LINE_CAP_SQUARE;
254 	else if (strcmp (cap, "Triangle") == 0)
255 		GXPS_DEBUG (g_debug ("Unsupported dash cap Triangle"));
256 
257 	return CAIRO_LINE_CAP_BUTT;
258 }
259 
260 static cairo_line_join_t
gxps_line_join_parse(const gchar * join)261 gxps_line_join_parse (const gchar *join)
262 {
263 	if (strcmp (join, "Miter") == 0)
264 		return CAIRO_LINE_JOIN_MITER;
265 	else if (strcmp (join, "Bevel") == 0)
266 		return CAIRO_LINE_JOIN_BEVEL;
267 	else if (strcmp (join, "Round") == 0)
268 		return CAIRO_LINE_JOIN_ROUND;
269 	return CAIRO_LINE_JOIN_MITER;
270 }
271 
272 typedef struct {
273 	GXPSRenderContext *ctx;
274 
275 	gdouble            opacity;
276 	cairo_pattern_t   *opacity_mask;
277 	gboolean           pop_resource_dict;
278 } GXPSCanvas;
279 
280 static GXPSCanvas *
gxps_canvas_new(GXPSRenderContext * ctx)281 gxps_canvas_new (GXPSRenderContext *ctx)
282 {
283 	GXPSCanvas *canvas;
284 
285 	canvas = g_slice_new0 (GXPSCanvas);
286 	canvas->ctx = ctx;
287 
288 	/* Default values */
289 	canvas->opacity = 1.0;
290 	canvas->pop_resource_dict = FALSE;
291 
292 	return canvas;
293 }
294 
295 static void
gxps_canvas_free(GXPSCanvas * canvas)296 gxps_canvas_free (GXPSCanvas *canvas)
297 {
298 	if (G_UNLIKELY (!canvas))
299 		return;
300 
301 	cairo_pattern_destroy (canvas->opacity_mask);
302 	g_slice_free (GXPSCanvas, canvas);
303 }
304 
305 static void
canvas_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)306 canvas_start_element (GMarkupParseContext  *context,
307 		      const gchar          *element_name,
308 		      const gchar         **names,
309 		      const gchar         **values,
310 		      gpointer              user_data,
311 		      GError              **error)
312 {
313 	GXPSCanvas *canvas = (GXPSCanvas *)user_data;
314 
315 	if (strcmp (element_name, "Canvas.RenderTransform") == 0) {
316 		GXPSMatrix *matrix;
317 
318 		matrix = gxps_matrix_new (canvas->ctx);
319 		gxps_matrix_parser_push (context, matrix);
320 	} else if (strcmp (element_name, "Canvas.OpacityMask") == 0) {
321 		GXPSBrush *brush;
322 
323 		brush = gxps_brush_new (canvas->ctx);
324 		gxps_brush_parser_push (context, brush);
325 	} else if (strcmp (element_name, "Canvas.Resources") == 0) {
326 		GXPSResources *resources;
327 
328 		if (canvas->pop_resource_dict) {
329 			gxps_parse_error (context,
330 					  canvas->ctx->page->priv->source,
331 					  G_MARKUP_ERROR_UNKNOWN_ELEMENT,
332 					  element_name, NULL, NULL, error);
333 			return;
334 		}
335 
336 		resources = gxps_archive_get_resources (canvas->ctx->page->priv->zip);
337 		gxps_resources_push_dict (resources);
338 		canvas->pop_resource_dict = TRUE;
339 		gxps_resources_parser_push (context, resources,
340 		                            canvas->ctx->page->priv->source);
341 	} else {
342 		render_start_element (context,
343 				      element_name,
344 				      names,
345 				      values,
346 				      canvas->ctx,
347 				      error);
348 	}
349 }
350 
351 static void
canvas_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)352 canvas_end_element (GMarkupParseContext  *context,
353 		    const gchar          *element_name,
354 		    gpointer              user_data,
355 		    GError              **error)
356 {
357 	GXPSCanvas *canvas = (GXPSCanvas *)user_data;
358 
359 	if (strcmp (element_name, "Canvas.RenderTransform") == 0) {
360 		GXPSMatrix *matrix;
361 
362 		matrix = g_markup_parse_context_pop (context);
363 		GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
364 			      matrix->matrix.xx, matrix->matrix.yx,
365 			      matrix->matrix.xy, matrix->matrix.yy,
366 			      matrix->matrix.x0, matrix->matrix.y0));
367 		cairo_transform (canvas->ctx->cr, &matrix->matrix);
368 		gxps_matrix_free (matrix);
369 	} else if (strcmp (element_name, "Canvas.OpacityMask") == 0) {
370 		GXPSBrush *brush;
371 
372 		brush = g_markup_parse_context_pop (context);
373 		if (!canvas->opacity_mask) {
374 			canvas->opacity_mask = cairo_pattern_reference (brush->pattern);
375 			cairo_push_group (canvas->ctx->cr);
376 		}
377 		gxps_brush_free (brush);
378 	} else if (strcmp (element_name, "Canvas.Resources") == 0) {
379 		gxps_resources_parser_pop (context);
380 	} else {
381 		render_end_element (context,
382 				    element_name,
383 				    canvas->ctx,
384 				    error);
385 	}
386 }
387 
388 static void
canvas_error(GMarkupParseContext * context,GError * error,gpointer user_data)389 canvas_error (GMarkupParseContext *context,
390               GError              *error,
391               gpointer             user_data)
392 {
393 	GXPSCanvas *canvas = (GXPSCanvas *)user_data;
394 	gxps_canvas_free (canvas);
395 }
396 
397 static GMarkupParser canvas_parser = {
398 	canvas_start_element,
399 	canvas_end_element,
400 	NULL,
401 	NULL,
402 	canvas_error
403 };
404 
405 static void
resource_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)406 resource_start_element (GMarkupParseContext  *context,
407                         const gchar          *element_name,
408                         const gchar         **names,
409                         const gchar         **values,
410                         gpointer              user_data,
411                         GError              **error)
412 {
413 	if (strcmp (element_name, "PathGeometry") == 0) {
414 		GXPSPath *path = (GXPSPath *)user_data;
415 
416 		gxps_path_parser_push (context, path);
417 	} else if (g_str_has_suffix (element_name, "Brush")) {
418 		GXPSPath *path = (GXPSPath *)user_data;
419 		GXPSBrush *brush;
420 
421 		brush = gxps_brush_new (path->ctx);
422 		gxps_brush_parser_push (context, brush);
423 	}
424 }
425 
426 static void
resource_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)427 resource_end_element (GMarkupParseContext  *context,
428 		      const gchar          *element_name,
429 		      gpointer              user_data,
430 		      GError              **error)
431 {
432 	if (strcmp (element_name, "PathGeometry") == 0) {
433 		g_markup_parse_context_pop (context);
434 	} else if (g_str_has_suffix (element_name, "Brush")) {
435 		GXPSPath *path = (GXPSPath *)user_data;
436 		GXPSBrush *brush = g_markup_parse_context_pop (context);
437 
438 		path->fill_pattern = cairo_pattern_reference (brush->pattern);
439 		gxps_brush_free (brush);
440 	}
441 }
442 
443 static GMarkupParser resource_parser = {
444 	resource_start_element,
445 	resource_end_element,
446 	NULL,
447 	NULL,
448 	NULL
449 };
450 
451 static gboolean
expand_resource(GXPSPage * page,const gchar * data,gpointer user_data)452 expand_resource (GXPSPage    *page,
453                  const gchar *data,
454                  gpointer     user_data)
455 {
456 	gchar *resource_key;
457 	gchar *p;
458 	gsize len;
459 	GXPSResources *resources;
460 	const gchar *resource;
461 	GMarkupParseContext *context;
462 	gboolean ret = TRUE;
463 
464 	if (!g_str_has_prefix (data, "{StaticResource "))
465 		return FALSE;
466 
467 	p = strstr (data, "}");
468 	if (p == NULL)
469 		return FALSE;
470 
471 	len = strlen ("{StaticResource ");
472 	resource_key = g_strndup (data + len, p - (data + len));
473 
474 	if (!resource_key || *resource_key == '\0') {
475 		g_free (resource_key);
476 		return FALSE;
477 	}
478 
479 	resources = gxps_archive_get_resources (page->priv->zip);
480 	resource = gxps_resources_get_resource (resources, resource_key);
481 	g_free (resource_key);
482 	if (!resource)
483 		return FALSE;
484 
485 	context = g_markup_parse_context_new (&resource_parser, 0, user_data, NULL);
486 
487 	ret = g_markup_parse_context_parse (context, resource, strlen (resource), NULL) &&
488 	      g_markup_parse_context_end_parse (context, NULL);
489 	g_markup_parse_context_free (context);
490 
491 	return ret;
492 }
493 
494 static void
render_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)495 render_start_element (GMarkupParseContext  *context,
496 		      const gchar          *element_name,
497 		      const gchar         **names,
498 		      const gchar         **values,
499 		      gpointer              user_data,
500 		      GError              **error)
501 {
502 	GXPSRenderContext *ctx = (GXPSRenderContext *)user_data;
503 
504 	if (strcmp (element_name, "Path") == 0) {
505 		GXPSPath *path;
506 		gint      i;
507 
508 		GXPS_DEBUG (g_message ("save"));
509 		cairo_save (ctx->cr);
510 
511 		path = gxps_path_new (ctx);
512 
513 		for (i = 0; names[i] != NULL; i++) {
514 			/* FIXME: if the resource gets expanded, that specific
515 			 * resource will be already handled leading to a different
516 			 * behavior of what we are actually doing without resources.
517 			 * In an ideal world we would handle the resource without
518 			 * special casing
519 			 */
520 			if (expand_resource (ctx->page, values[i], path)) {
521 				GXPS_DEBUG (g_message ("expanded resource: %s", names[i]));
522 			} else if (strcmp (names[i], "Data") == 0) {
523 				path->data = g_strdup (values[i]);
524 			} else if (strcmp (names[i], "RenderTransform") == 0) {
525 				cairo_matrix_t matrix;
526 
527 				if (!gxps_matrix_parse (values[i], &matrix)) {
528 					gxps_parse_error (context,
529 							  ctx->page->priv->source,
530 							  G_MARKUP_ERROR_INVALID_CONTENT,
531 							  "Path", "RenderTransform", values[i], error);
532 					gxps_path_free (path);
533 					return;
534 				}
535 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
536 					      matrix.xx, matrix.yx,
537 					      matrix.xy, matrix.yy,
538 					      matrix.x0, matrix.y0));
539 				cairo_transform (ctx->cr, &matrix);
540 			} else if (strcmp (names[i], "Clip") == 0) {
541 				path->clip_data = g_strdup (values[i]);
542 			} else if (strcmp (names[i], "Fill") == 0) {
543 				if (!gxps_brush_solid_color_parse (values[i], ctx->page->priv->zip, 1., &path->fill_pattern)) {
544 					gxps_parse_error (context,
545 							  ctx->page->priv->source,
546 							  G_MARKUP_ERROR_INVALID_CONTENT,
547 							  "Path", "Fill", values[i], error);
548 					gxps_path_free (path);
549 					return;
550 				}
551 				GXPS_DEBUG (g_message ("set_fill_pattern (solid)"));
552 			} else if (strcmp (names[i], "Stroke") == 0) {
553 				GXPS_DEBUG (g_message ("set_stroke_pattern (solid)"));
554                                 if (!gxps_brush_solid_color_parse (values[i], ctx->page->priv->zip, 1., &path->stroke_pattern)) {
555 					gxps_parse_error (context,
556 							  ctx->page->priv->source,
557 							  G_MARKUP_ERROR_INVALID_CONTENT,
558 							  "Path", "Stroke", values[i], error);
559 					gxps_path_free (path);
560 					return;
561 				}
562 			} else if (strcmp (names[i], "StrokeThickness") == 0) {
563                                 if (!gxps_value_get_double (values[i], &path->line_width)) {
564                                         gxps_parse_error (context,
565                                                           ctx->page->priv->source,
566                                                           G_MARKUP_ERROR_INVALID_CONTENT,
567                                                           "Path", "StrokeThickness", values[i], error);
568                                         gxps_path_free (path);
569                                         return;
570                                 }
571 				GXPS_DEBUG (g_message ("set_line_width (%f)", path->line_width));
572 			} else if (strcmp (names[i], "StrokeDashArray") == 0) {
573 				if (!gxps_dash_array_parse (values[i], &path->dash, &path->dash_len)) {
574 					gxps_parse_error (context,
575 							  ctx->page->priv->source,
576 							  G_MARKUP_ERROR_INVALID_CONTENT,
577 							  "Path", "StrokeDashArray", values[i], error);
578 					gxps_path_free (path);
579 					return;
580 				}
581 				GXPS_DEBUG (g_message ("set_dash"));
582 			} else if (strcmp (names[i], "StrokeDashOffset") == 0) {
583                                 if (!gxps_value_get_double (values[i], &path->dash_offset)) {
584                                         gxps_parse_error (context,
585                                                           ctx->page->priv->source,
586                                                           G_MARKUP_ERROR_INVALID_CONTENT,
587                                                           "Path", "StrokeDashOffset", values[i], error);
588                                         gxps_path_free (path);
589                                         return;
590                                 }
591 				GXPS_DEBUG (g_message ("set_dash_offset (%f)", path->dash_offset));
592 			} else if (strcmp (names[i], "StrokeDashCap") == 0) {
593 				path->line_cap = gxps_line_cap_parse (values[i]);
594 				GXPS_DEBUG (g_message ("set_line_cap (%s)", values[i]));
595 			} else if (strcmp (names[i], "StrokeLineJoin") == 0) {
596 				path->line_join = gxps_line_join_parse (values[i]);
597 				GXPS_DEBUG (g_message ("set_line_join (%s)", values[i]));
598 			} else if (strcmp (names[i], "StrokeMiterLimit") == 0) {
599                                 if (!gxps_value_get_double (values[i], &path->miter_limit)) {
600                                         gxps_parse_error (context,
601                                                           ctx->page->priv->source,
602                                                           G_MARKUP_ERROR_INVALID_CONTENT,
603                                                           "Path", "StrokeMiterLimit", values[i], error);
604                                         gxps_path_free (path);
605                                         return;
606                                 }
607 				GXPS_DEBUG (g_message ("set_miter_limit (%f)", path->miter_limit));
608 			} else if (strcmp (names[i], "Opacity") == 0) {
609                                 if (!gxps_value_get_double (values[i], &path->opacity)) {
610                                         gxps_parse_error (context,
611                                                           ctx->page->priv->source,
612                                                           G_MARKUP_ERROR_INVALID_CONTENT,
613                                                           "Path", "Opacity", values[i], error);
614                                         gxps_path_free (path);
615                                         return;
616                                 }
617 				GXPS_DEBUG (g_message ("set_opacity (%f)", path->opacity));
618 			}
619 		}
620 
621 		if (path->opacity != 1.0)
622 			cairo_push_group (ctx->cr);
623 		gxps_path_parser_push (context, path);
624 	} else if (strcmp (element_name, "Glyphs") == 0) {
625 		GXPSGlyphs  *glyphs;
626 		gchar       *font_uri = NULL;
627 		gdouble      font_size = -1;
628 		gdouble      x = -1, y = -1;
629 		const gchar *text = NULL;
630 		const gchar *fill_color = NULL;
631 		const gchar *indices = NULL;
632 		const gchar *clip_data = NULL;
633                 gint         bidi_level = 0;
634                 gboolean     is_sideways = FALSE;
635                 gboolean     italic = FALSE;
636 		gdouble      opacity = 1.0;
637 		gint         i;
638 
639 		GXPS_DEBUG (g_message ("save"));
640 		cairo_save (ctx->cr);
641 
642 		for (i = 0; names[i] != NULL; i++) {
643 			if (strcmp (names[i], "FontRenderingEmSize") == 0) {
644                                 if (!gxps_value_get_double (values[i], &font_size)) {
645                                         gxps_parse_error (context,
646                                                           ctx->page->priv->source,
647                                                           G_MARKUP_ERROR_INVALID_CONTENT,
648                                                           "Glyphs", "FontRenderingEmSize",
649                                                           values[i], error);
650                                         g_free (font_uri);
651                                         return;
652                                 }
653 			} else if (strcmp (names[i], "FontUri") == 0) {
654 				font_uri = gxps_resolve_relative_path (ctx->page->priv->source,
655 								       values[i]);
656 			} else if (strcmp (names[i], "OriginX") == 0) {
657                                 if (!gxps_value_get_double (values[i], &x)) {
658                                         gxps_parse_error (context,
659                                                           ctx->page->priv->source,
660                                                           G_MARKUP_ERROR_INVALID_CONTENT,
661                                                           "Glyphs", "OriginX",
662                                                           values[i], error);
663                                         g_free (font_uri);
664                                         return;
665                                 }
666 			} else if (strcmp (names[i], "OriginY") == 0) {
667                                 if (!gxps_value_get_double (values[i], &y)) {
668                                         gxps_parse_error (context,
669                                                           ctx->page->priv->source,
670                                                           G_MARKUP_ERROR_INVALID_CONTENT,
671                                                           "Glyphs", "OriginY",
672                                                           values[i], error);
673                                         g_free (font_uri);
674                                         return;
675                                 }
676 			} else if (strcmp (names[i], "UnicodeString") == 0) {
677 				text = values[i];
678 			} else if (strcmp (names[i], "Fill") == 0) {
679 				fill_color = values[i];
680 			} else if (strcmp (names[i], "Indices") == 0) {
681 				indices = values[i];
682 			} else if (strcmp (names[i], "RenderTransform") == 0) {
683 				cairo_matrix_t matrix;
684 
685 				if (!gxps_matrix_parse (values[i], &matrix)) {
686 					gxps_parse_error (context,
687 							  ctx->page->priv->source,
688 							  G_MARKUP_ERROR_INVALID_CONTENT,
689 							  "Glyphs", "RenderTransform",
690 							  values[i], error);
691 					g_free (font_uri);
692 					return;
693 				}
694 
695 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
696 					      matrix.xx, matrix.yx,
697 					      matrix.xy, matrix.yy,
698 					      matrix.x0, matrix.y0));
699 				cairo_transform (ctx->cr, &matrix);
700 			} else if (strcmp (names[i], "Clip") == 0) {
701 				clip_data = values[i];
702                         } else if (strcmp (names[i], "BidiLevel") == 0) {
703                                 if (!gxps_value_get_int (values[i], &bidi_level)) {
704                                         gxps_parse_error (context,
705                                                           ctx->page->priv->source,
706                                                           G_MARKUP_ERROR_INVALID_CONTENT,
707                                                           "Glyphs", "BidiLevel",
708                                                           values[i], error);
709                                         g_free (font_uri);
710                                         return;
711                                 }
712                         } else if (strcmp (names[i], "IsSideways") == 0) {
713                                 if (!gxps_value_get_boolean (values[i], &is_sideways)) {
714                                         gxps_parse_error (context,
715                                                           ctx->page->priv->source,
716                                                           G_MARKUP_ERROR_INVALID_CONTENT,
717                                                           "Glyphs", "IsSideways",
718                                                           values[i], error);
719                                         g_free (font_uri);
720                                         return;
721                                 }
722 			} else if (strcmp (names[i], "Opacity") == 0) {
723                                 if (!gxps_value_get_double (values[i], &opacity)) {
724                                         gxps_parse_error (context,
725                                                           ctx->page->priv->source,
726                                                           G_MARKUP_ERROR_INVALID_CONTENT,
727                                                           "Glyphs", "Opacity",
728                                                           values[i], error);
729                                         g_free (font_uri);
730                                         return;
731                                 }
732                         } else if (strcmp (names[i], "StyleSimulations") == 0) {
733                                 if (strcmp (values[i], "ItalicSimulation") == 0) {
734                                         italic = TRUE;
735                                 }
736 			}
737 		}
738 
739 		if (!font_uri || font_size == -1 || x == -1 || y == -1) {
740 			if (!font_uri) {
741 				gxps_parse_error (context,
742 						  ctx->page->priv->source,
743 						  G_MARKUP_ERROR_MISSING_ATTRIBUTE,
744 						  element_name, "FontUri", NULL, error);
745 			} else if (font_size == -1) {
746 				gxps_parse_error (context,
747 						  ctx->page->priv->source,
748 						  G_MARKUP_ERROR_MISSING_ATTRIBUTE,
749 						  element_name, "FontRenderingEmSize", NULL, error);
750 			} else if (x == -1 || y == -1) {
751 				gxps_parse_error (context,
752 						  ctx->page->priv->source,
753 						  G_MARKUP_ERROR_MISSING_ATTRIBUTE,
754 						  element_name,
755 						  (x == -1) ? "OriginX" : "OriginY", NULL, error);
756 			}
757 
758 			g_free (font_uri);
759 			return;
760 		}
761 
762 		/* GXPSGlyphs takes ownership of font_uri */
763 		glyphs = gxps_glyphs_new (ctx, font_uri, font_size, x, y);
764 		glyphs->text = g_strdup (text);
765 		glyphs->indices = g_strdup (indices);
766 		glyphs->clip_data = g_strdup (clip_data);
767                 glyphs->bidi_level = bidi_level;
768                 glyphs->is_sideways = is_sideways;
769                 glyphs->italic = italic;
770 		glyphs->opacity = opacity;
771 		if (fill_color) {
772 			GXPS_DEBUG (g_message ("set_fill_pattern (solid)"));
773                         gxps_brush_solid_color_parse (fill_color, ctx->page->priv->zip, 1., &glyphs->fill_pattern);
774 		}
775 
776 		if (glyphs->opacity != 1.0)
777 			cairo_push_group (glyphs->ctx->cr);
778 		gxps_glyphs_parser_push (context, glyphs);
779 	} else if (strcmp (element_name, "Canvas") == 0) {
780 		GXPSCanvas *canvas;
781 		gint i;
782 
783 		GXPS_DEBUG (g_message ("save"));
784 		cairo_save (ctx->cr);
785 
786 		canvas = gxps_canvas_new (ctx);
787 
788 		for (i = 0; names[i] != NULL; i++) {
789 			if (strcmp (names[i], "RenderTransform") == 0) {
790 				cairo_matrix_t matrix;
791 
792 				if (!gxps_matrix_parse (values[i], &matrix)) {
793 					gxps_parse_error (context,
794 							  ctx->page->priv->source,
795 							  G_MARKUP_ERROR_INVALID_CONTENT,
796 							  "Canvas", "RenderTransform", values[i], error);
797 					gxps_canvas_free (canvas);
798 					return;
799 				}
800 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
801 					      matrix.xx, matrix.yx,
802 					      matrix.xy, matrix.yy,
803 					      matrix.x0, matrix.y0));
804 				cairo_transform (ctx->cr, &matrix);
805 			} else if (strcmp (names[i], "Opacity") == 0) {
806                                 if (!gxps_value_get_double (values[i], &canvas->opacity)) {
807                                         gxps_parse_error (context,
808                                                           ctx->page->priv->source,
809                                                           G_MARKUP_ERROR_INVALID_CONTENT,
810                                                           "Canvas", "Opacity", values[i], error);
811                                         gxps_canvas_free (canvas);
812                                         return;
813                                 }
814 				GXPS_DEBUG (g_message ("set_opacity (%f)", canvas->opacity));
815 			} else if (strcmp (names[i], "Clip") == 0) {
816 				if (!gxps_path_parse (values[i], ctx->cr, error)) {
817 					gxps_parse_error (context,
818 							  ctx->page->priv->source,
819 							  G_MARKUP_ERROR_INVALID_CONTENT,
820 							  "Canvas", "Clip", values[i], error);
821 					gxps_canvas_free (canvas);
822 					return;
823 				}
824 				GXPS_DEBUG (g_message ("clip"));
825 				cairo_clip (ctx->cr);
826 			}
827 		}
828 		if (canvas->opacity != 1.0)
829 			cairo_push_group (canvas->ctx->cr);
830 		g_markup_parse_context_push (context, &canvas_parser, canvas);
831 	} else if (strcmp (element_name, "FixedPage.Resources") == 0) {
832 		GXPSResources *resources;
833 
834 		resources = gxps_archive_get_resources (ctx->page->priv->zip);
835 		gxps_resources_parser_push (context, resources,
836 		                            ctx->page->priv->source);
837 	} else if (strcmp (element_name, "FixedPage") == 0) {
838 		/* Do Nothing */
839 	} else {
840 		/* TODO: error */
841 	}
842 }
843 
844 static void
render_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)845 render_end_element (GMarkupParseContext  *context,
846 		    const gchar          *element_name,
847 		    gpointer              user_data,
848 		    GError              **error)
849 {
850 	GXPSRenderContext *ctx = (GXPSRenderContext *)user_data;
851 
852 	if (strcmp (element_name, "Path") == 0) {
853 		GXPSPath *path;
854 
855 		path = g_markup_parse_context_pop (context);
856 
857 		if (!path->data) {
858 			GXPS_DEBUG (g_message ("restore"));
859 			/* Something may have been drawn in a PathGeometry */
860 			if (path->opacity != 1.0) {
861 				cairo_pop_group_to_source (ctx->cr);
862 				cairo_paint_with_alpha (ctx->cr, path->opacity);
863 			}
864 			cairo_restore (ctx->cr);
865 			gxps_path_free (path);
866 			return;
867 		}
868 
869 		cairo_set_fill_rule (ctx->cr, path->fill_rule);
870 
871 		if (path->clip_data) {
872 			if (!gxps_path_parse (path->clip_data, ctx->cr, error)) {
873 				if (path->opacity != 1.0)
874 					cairo_pattern_destroy (cairo_pop_group (ctx->cr));
875 				gxps_path_free (path);
876 				return;
877 			}
878 			GXPS_DEBUG (g_message ("clip"));
879 			cairo_clip (ctx->cr);
880 		}
881 
882 		if (!gxps_path_parse (path->data, ctx->cr, error)) {
883 			if (path->opacity != 1.0)
884 				cairo_pattern_destroy (cairo_pop_group (ctx->cr));
885 			gxps_path_free (path);
886 			return;
887 		}
888 
889 		if (path->stroke_pattern) {
890 			cairo_set_line_width (ctx->cr, path->line_width);
891 			if (path->dash && path->dash_len > 0)
892 				cairo_set_dash (ctx->cr, path->dash, path->dash_len, path->dash_offset);
893 			/* FIXME: square cap doesn't work with dashed lines */
894 //					cairo_set_line_cap (ctx->cr, path->line_cap);
895 			cairo_set_line_join (ctx->cr, path->line_join);
896 			cairo_set_miter_limit (ctx->cr, path->miter_limit);
897 		}
898 
899 		if (path->opacity_mask) {
900 			gdouble x1 = 0, y1 = 0, x2 = 0, y2 = 0;
901 			cairo_path_t *cairo_path;
902 
903 			if (path->stroke_pattern)
904 				cairo_stroke_extents (ctx->cr, &x1, &y1, &x2, &y2);
905 			else if (path->fill_pattern)
906 				cairo_fill_extents (ctx->cr, &x1, &y1, &x2, &y2);
907 
908 			cairo_path = cairo_copy_path (ctx->cr);
909 			cairo_new_path (ctx->cr);
910 			cairo_rectangle (ctx->cr, x1, y1, x2 - x1, y2 - y1);
911 			cairo_clip (ctx->cr);
912 			cairo_push_group (ctx->cr);
913 			cairo_append_path (ctx->cr, cairo_path);
914 			cairo_path_destroy (cairo_path);
915 		}
916 
917 		if (path->fill_pattern) {
918 			GXPS_DEBUG (g_message ("fill"));
919 
920 			cairo_set_source (ctx->cr, path->fill_pattern);
921 			if (path->stroke_pattern)
922 				cairo_fill_preserve (ctx->cr);
923 			else
924 				cairo_fill (ctx->cr);
925 		}
926 
927 		if (path->stroke_pattern) {
928 			GXPS_DEBUG (g_message ("stroke"));
929 			cairo_set_source (ctx->cr, path->stroke_pattern);
930 			cairo_stroke (ctx->cr);
931 		}
932 
933 		if (path->opacity_mask) {
934 			cairo_pop_group_to_source (ctx->cr);
935 			cairo_mask (ctx->cr, path->opacity_mask);
936 		}
937 
938 		if (path->opacity != 1.0) {
939 			cairo_pop_group_to_source (ctx->cr);
940 			cairo_paint_with_alpha (ctx->cr, path->opacity);
941 		}
942 		gxps_path_free (path);
943 
944 		GXPS_DEBUG (g_message ("restore"));
945 		cairo_restore (ctx->cr);
946 	} else if (strcmp (element_name, "Glyphs") == 0) {
947 		GXPSGlyphs           *glyphs;
948 		gchar                *utf8;
949 		cairo_text_cluster_t *cluster_list = NULL;
950 		gint                  num_clusters;
951 		cairo_glyph_t        *glyph_list = NULL;
952 		gint                  num_glyphs;
953 		cairo_matrix_t        ctm, font_matrix;
954 		cairo_font_face_t    *font_face;
955 		cairo_font_options_t *font_options;
956 		cairo_scaled_font_t  *scaled_font;
957                 gboolean              use_show_text_glyphs;
958                 gboolean              success;
959 
960 		glyphs = g_markup_parse_context_pop (context);
961 
962 		font_face = gxps_fonts_get_font (ctx->page->priv->zip, glyphs->font_uri, error);
963 		if (!font_face) {
964 			if (glyphs->opacity_mask)
965 				cairo_pattern_destroy (cairo_pop_group (ctx->cr));
966 			if (glyphs->opacity != 1.0)
967 				cairo_pattern_destroy (cairo_pop_group (ctx->cr));
968 			gxps_glyphs_free (glyphs);
969 
970 			GXPS_DEBUG (g_message ("restore"));
971 			cairo_restore (ctx->cr);
972 			return;
973 		}
974 
975 		if (glyphs->clip_data) {
976 			if (!gxps_path_parse (glyphs->clip_data, ctx->cr, error)) {
977 				if (glyphs->opacity_mask)
978 					cairo_pattern_destroy (cairo_pop_group (ctx->cr));
979 				if (glyphs->opacity != 1.0)
980 					cairo_pattern_destroy (cairo_pop_group (ctx->cr));
981 				gxps_glyphs_free (glyphs);
982 				GXPS_DEBUG (g_message ("restore"));
983 				cairo_restore (ctx->cr);
984 				return;
985 			}
986 			GXPS_DEBUG (g_message ("clip"));
987 			cairo_clip (ctx->cr);
988 		}
989 
990 		font_options = cairo_font_options_create ();
991 		cairo_get_font_options (ctx->cr, font_options);
992 		cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
993 
994 		cairo_matrix_init_identity (&font_matrix);
995 		cairo_matrix_scale (&font_matrix, glyphs->em_size, glyphs->em_size);
996 		cairo_get_matrix (ctx->cr, &ctm);
997 
998                 /* italics is 20 degrees slant.  0.342 = sin(20 deg) */
999                 if (glyphs->italic)
1000                         font_matrix.xy = glyphs->em_size * -0.342;
1001 
1002                 if (glyphs->is_sideways)
1003                         cairo_matrix_rotate (&font_matrix, -G_PI_2);
1004 
1005 		scaled_font = cairo_scaled_font_create (font_face,
1006 							&font_matrix,
1007 							&ctm,
1008 							font_options);
1009 
1010 		cairo_font_options_destroy (font_options);
1011 
1012                 /* UnicodeString may begin with escape sequence "{}" */
1013                 utf8 = glyphs->text;
1014                 if (utf8 && g_str_has_prefix (utf8, "{}"))
1015                         utf8 += 2;
1016 
1017                 use_show_text_glyphs = cairo_surface_has_show_text_glyphs (cairo_get_target (ctx->cr));
1018 
1019 		success = gxps_glyphs_to_cairo_glyphs (glyphs, scaled_font, utf8,
1020 						       &glyph_list, &num_glyphs,
1021 						       use_show_text_glyphs ? &cluster_list : NULL,
1022                                                        use_show_text_glyphs ? &num_clusters : NULL,
1023 						       error);
1024 		if (!success) {
1025 			if (glyphs->opacity_mask)
1026 				cairo_pattern_destroy (cairo_pop_group (ctx->cr));
1027 			if (glyphs->opacity != 1.0)
1028 				cairo_pattern_destroy (cairo_pop_group (ctx->cr));
1029 			gxps_glyphs_free (glyphs);
1030 			cairo_scaled_font_destroy (scaled_font);
1031 			GXPS_DEBUG (g_message ("restore"));
1032 			cairo_restore (ctx->cr);
1033 			return;
1034 		}
1035 
1036 		if (glyphs->fill_pattern)
1037 			cairo_set_source (ctx->cr, glyphs->fill_pattern);
1038 
1039 		GXPS_DEBUG (g_message ("show_text (%s)", glyphs->text));
1040 
1041 		cairo_set_scaled_font (ctx->cr, scaled_font);
1042                 if (use_show_text_glyphs) {
1043                         cairo_show_text_glyphs (ctx->cr, utf8, -1,
1044                                                 glyph_list, num_glyphs,
1045                                                 cluster_list, num_clusters,
1046                                                 0);
1047                         g_free (cluster_list);
1048                 } else {
1049                         cairo_show_glyphs (ctx->cr, glyph_list, num_glyphs);
1050                 }
1051 
1052 		if (glyphs->opacity_mask) {
1053 			cairo_pop_group_to_source (ctx->cr);
1054 			cairo_mask (ctx->cr, glyphs->opacity_mask);
1055 		}
1056 		if (glyphs->opacity != 1.0) {
1057 			cairo_pop_group_to_source (ctx->cr);
1058 			cairo_paint_with_alpha (ctx->cr, glyphs->opacity);
1059 		}
1060 		g_free (glyph_list);
1061 		gxps_glyphs_free (glyphs);
1062 		cairo_scaled_font_destroy (scaled_font);
1063 
1064 		GXPS_DEBUG (g_message ("restore"));
1065 		cairo_restore (ctx->cr);
1066 	} else if (strcmp (element_name, "Canvas") == 0) {
1067 		GXPSCanvas *canvas;
1068 
1069 		canvas = g_markup_parse_context_pop (context);
1070 
1071 		if (canvas->opacity_mask) {
1072 			cairo_pop_group_to_source (ctx->cr);
1073 			cairo_mask (ctx->cr, canvas->opacity_mask);
1074 		}
1075 		if (canvas->opacity != 1.0) {
1076 			cairo_pop_group_to_source (ctx->cr);
1077 			cairo_paint_with_alpha (ctx->cr, canvas->opacity);
1078 		}
1079 		cairo_restore (ctx->cr);
1080 		GXPS_DEBUG (g_message ("restore"));
1081 		if (canvas->pop_resource_dict) {
1082 			GXPSResources *resources;
1083 
1084 			resources = gxps_archive_get_resources (ctx->page->priv->zip);
1085 			gxps_resources_pop_dict (resources);
1086 		}
1087 		gxps_canvas_free (canvas);
1088 	} else if (strcmp (element_name, "FixedPage.Resources") == 0) {
1089 		gxps_resources_parser_pop (context);
1090 	} else if (strcmp (element_name, "FixedPage") == 0) {
1091 		/* Do Nothing */
1092 	} else {
1093 		/* TODO: error */
1094 	}
1095 }
1096 
1097 static gboolean
gxps_page_parse_for_rendering(GXPSPage * page,cairo_t * cr,GError ** error)1098 gxps_page_parse_for_rendering (GXPSPage *page,
1099 			       cairo_t  *cr,
1100 			       GError  **error)
1101 {
1102 	GInputStream        *stream;
1103 	GMarkupParseContext *context;
1104 	GXPSRenderContext    ctx;
1105 	GError              *err = NULL;
1106 
1107 	stream = gxps_archive_open (page->priv->zip,
1108 				    page->priv->source);
1109 	if (!stream) {
1110 		g_set_error (error,
1111 			     GXPS_ERROR,
1112 			     GXPS_ERROR_SOURCE_NOT_FOUND,
1113 			     "Page source %s not found in archive",
1114 			     page->priv->source);
1115 		return FALSE;
1116 	}
1117 
1118 	ctx.page = page;
1119 	ctx.cr = cr;
1120 
1121 	context = g_markup_parse_context_new (&render_parser, 0, &ctx, NULL);
1122 	gxps_parse_stream (context, stream, &err);
1123 	g_object_unref (stream);
1124 	g_markup_parse_context_free (context);
1125 
1126 
1127 	if (g_error_matches (err, GXPS_PAGE_ERROR, GXPS_PAGE_ERROR_RENDER)) {
1128 		g_propagate_error (error, err);
1129 	} else if (err) {
1130 		g_set_error (error,
1131 			     GXPS_PAGE_ERROR,
1132 			     GXPS_PAGE_ERROR_RENDER,
1133 			     "Error rendering page %s: %s",
1134 			     page->priv->source, err->message);
1135 		g_error_free (err);
1136 	}
1137 
1138 	return (*error != NULL) ? FALSE : TRUE;
1139 }
1140 
1141 /* Links */
1142 typedef struct {
1143 	GXPSPage *page;
1144 	cairo_t  *cr;
1145 
1146 	GList    *st;
1147 	GList    *links;
1148 	gboolean  do_transform;
1149 } GXPSLinksContext;
1150 
1151 typedef struct {
1152 	gchar *data;
1153 	gchar *uri;
1154 } GXPSPathLink;
1155 
1156 static void
links_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)1157 links_start_element (GMarkupParseContext  *context,
1158 		     const gchar          *element_name,
1159 		     const gchar         **names,
1160 		     const gchar         **values,
1161 		     gpointer              user_data,
1162 		     GError              **error)
1163 {
1164 	GXPSLinksContext *ctx = (GXPSLinksContext *)user_data;
1165 
1166 	if (strcmp (element_name, "Canvas") == 0) {
1167 		gint i;
1168 
1169 		GXPS_DEBUG (g_message ("save"));
1170 		cairo_save (ctx->cr);
1171 
1172 		for (i = 0; names[i] != NULL; i++) {
1173 			if (strcmp (names[i], "RenderTransform") == 0) {
1174 				cairo_matrix_t matrix;
1175 
1176 				if (!gxps_matrix_parse (values[i], &matrix)) {
1177 					gxps_parse_error (context,
1178 							  ctx->page->priv->source,
1179 							  G_MARKUP_ERROR_INVALID_CONTENT,
1180 							  "Canvas", "RenderTransform", values[i], error);
1181 					return;
1182 				}
1183 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
1184 					      matrix.xx, matrix.yx,
1185 					      matrix.xy, matrix.yy,
1186 					      matrix.x0, matrix.y0));
1187 				cairo_transform (ctx->cr, &matrix);
1188 
1189 				return;
1190 			} else if (strcmp (names[i], "Clip") == 0) {
1191 				/* FIXME: do we really need clips? */
1192 				if (!gxps_path_parse (values[i], ctx->cr, error))
1193 					return;
1194 				GXPS_DEBUG (g_message ("clip"));
1195 				cairo_clip (ctx->cr);
1196 			}
1197 		}
1198 	} else if (strcmp (element_name, "Path") == 0) {
1199 		gint i;
1200 		GXPSPathLink *path_link;
1201 		const gchar *data = NULL;
1202 		const gchar *link_uri = NULL;
1203 
1204 		GXPS_DEBUG (g_message ("save"));
1205 		cairo_save (ctx->cr);
1206 
1207 		for (i = 0; names[i] != NULL; i++) {
1208 			if (strcmp (names[i], "Data") == 0) {
1209 				data = values[i];
1210 			} else if (strcmp (names[i], "RenderTransform") == 0) {
1211 				cairo_matrix_t matrix;
1212 
1213 				if (!gxps_matrix_parse (values[i], &matrix)) {
1214 					gxps_parse_error (context,
1215 							  ctx->page->priv->source,
1216 							  G_MARKUP_ERROR_INVALID_CONTENT,
1217 							  "Path", "RenderTransform", values[i], error);
1218 					return;
1219 				}
1220 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
1221 					      matrix.xx, matrix.yx,
1222 					      matrix.xy, matrix.yy,
1223 					      matrix.x0, matrix.y0));
1224 				cairo_transform (ctx->cr, &matrix);
1225 			} else if (strcmp (names[i], "FixedPage.NavigateUri") == 0) {
1226 				link_uri = values[i];
1227 			}
1228 		}
1229 
1230 		path_link = g_slice_new0 (GXPSPathLink);
1231 		if (link_uri) {
1232 			path_link->data = data ? g_strdup (data) : NULL;
1233 			path_link->uri = gxps_resolve_relative_path (ctx->page->priv->source, link_uri);
1234 		}
1235 
1236 		ctx->st = g_list_prepend (ctx->st, path_link);
1237 	} else if (strcmp (element_name, "Glyphs") == 0) {
1238 		gint i;
1239 
1240 		GXPS_DEBUG (g_message ("save"));
1241 		cairo_save (ctx->cr);
1242 
1243 		for (i = 0; names[i] != NULL; i++) {
1244 			if (strcmp (names[i], "RenderTransform") == 0) {
1245 				cairo_matrix_t matrix;
1246 
1247 				if (!gxps_matrix_parse (values[i], &matrix)) {
1248 					gxps_parse_error (context,
1249 							  ctx->page->priv->source,
1250 							  G_MARKUP_ERROR_INVALID_CONTENT,
1251 							  "Glyphs", "RenderTransform", values[i], error);
1252 					return;
1253 				}
1254 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
1255 					      matrix.xx, matrix.yx,
1256 					      matrix.xy, matrix.yy,
1257 					      matrix.x0, matrix.y0));
1258 				cairo_transform (ctx->cr, &matrix);
1259 			} else if (strcmp (names[i], "FixedPage.NavigateUri") == 0) {
1260 				/* TODO */
1261 			}
1262 		}
1263 	} else if (strcmp (element_name, "Canvas.RenderTransform") == 0 ||
1264 		   strcmp (element_name, "Path.RenderTransform") == 0 ||
1265 		   strcmp (element_name, "Glyphs.RenderTransform") == 0 ) {
1266 		ctx->do_transform = TRUE;
1267 	} else if (strcmp (element_name, "MatrixTransform") == 0) {
1268 		gint i;
1269 
1270 		if (!ctx->do_transform) {
1271 			return;
1272 		}
1273 
1274 		for (i = 0; names[i] != NULL; i++) {
1275 			if (strcmp (names[i], "Matrix") == 0) {
1276 				cairo_matrix_t matrix;
1277 
1278 				if (!gxps_matrix_parse (values[i], &matrix)) {
1279 					gxps_parse_error (context,
1280 							  ctx->page->priv->source,
1281 							  G_MARKUP_ERROR_INVALID_CONTENT,
1282 							  "MatrixTransform", "Matrix",
1283 							  values[i], error);
1284 					return;
1285 				}
1286 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
1287 					      matrix.xx, matrix.yx,
1288 					      matrix.xy, matrix.yy,
1289 					      matrix.x0, matrix.y0));
1290 				cairo_transform (ctx->cr, &matrix);
1291 				return;
1292 			}
1293 		}
1294 	}
1295 }
1296 
1297 static void
links_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)1298 links_end_element (GMarkupParseContext  *context,
1299 		   const gchar          *element_name,
1300 		   gpointer              user_data,
1301 		   GError              **error)
1302 {
1303 	GXPSLinksContext *ctx = (GXPSLinksContext *)user_data;
1304 
1305 	if (strcmp (element_name, "Canvas") == 0) {
1306 		GXPS_DEBUG (g_message ("restore"));
1307 		cairo_restore (ctx->cr);
1308 	} else if (strcmp (element_name, "Path") == 0) {
1309 		GXPSPathLink *path_link;
1310 
1311 		path_link = (GXPSPathLink *)ctx->st->data;
1312 		ctx->st = g_list_delete_link (ctx->st, ctx->st);
1313 		if (path_link->uri) {
1314 			GXPSLink         *link;
1315 			gdouble           x1, y1, x2, y2;
1316 			cairo_rectangle_t area;
1317 
1318 			if (path_link->data)
1319 				gxps_path_parse (path_link->data, ctx->cr, error);
1320 
1321 			cairo_path_extents (ctx->cr, &x1, &y1, &x2, &y2);
1322 			cairo_user_to_device (ctx->cr, &x1, &y1);
1323 			cairo_user_to_device (ctx->cr, &x2, &y2);
1324 
1325 			area.x = x1;
1326 			area.y = y1;
1327 			area.width = x2 - x1;
1328 			area.height = y2 - y1;
1329 			link = _gxps_link_new (ctx->page->priv->zip, &area, path_link->uri);
1330 			ctx->links = g_list_prepend (ctx->links, link);
1331 			g_free (path_link->uri);
1332 		}
1333 		g_free (path_link->data);
1334 		g_slice_free (GXPSPathLink, path_link);
1335 		cairo_new_path (ctx->cr);
1336 		GXPS_DEBUG (g_message ("restore"));
1337 		cairo_restore (ctx->cr);
1338 	} else if (strcmp (element_name, "Glyphs") == 0) {
1339 		GXPS_DEBUG (g_message ("restore"));
1340 		cairo_restore (ctx->cr);
1341 	} else if (strcmp (element_name, "Canvas.RenderTransform") == 0 ||
1342 		   strcmp (element_name, "Path.RenderTransform") == 0 ||
1343 		   strcmp (element_name, "Glyphs.RenderTransform") == 0 ) {
1344 		ctx->do_transform = FALSE;
1345 	}
1346 }
1347 
1348 static const GMarkupParser links_parser = {
1349 	links_start_element,
1350 	links_end_element,
1351 	NULL,
1352 	NULL,
1353 	NULL
1354 };
1355 
1356 static GList *
gxps_page_parse_links(GXPSPage * page,cairo_t * cr,GError ** error)1357 gxps_page_parse_links (GXPSPage *page,
1358 		       cairo_t  *cr,
1359 		       GError  **error)
1360 {
1361 	GInputStream        *stream;
1362 	GXPSLinksContext     ctx;
1363 	GMarkupParseContext *context;
1364 
1365 	stream = gxps_archive_open (page->priv->zip,
1366 				    page->priv->source);
1367 	if (!stream) {
1368 		g_set_error (error,
1369 			     GXPS_ERROR,
1370 			     GXPS_ERROR_SOURCE_NOT_FOUND,
1371 			     "Page source %s not found in archive",
1372 			     page->priv->source);
1373 		return FALSE;
1374 	}
1375 
1376 	ctx.cr = cr;
1377 	ctx.page = page;
1378 	ctx.st = NULL;
1379 	ctx.links = NULL;
1380 
1381 	context = g_markup_parse_context_new (&links_parser, 0, &ctx, NULL);
1382 	gxps_parse_stream (context, stream, error);
1383 	g_object_unref (stream);
1384 	g_markup_parse_context_free (context);
1385 
1386 	return ctx.links;
1387 }
1388 
1389 typedef struct {
1390 	GXPSPage   *page;
1391 	cairo_t    *cr;
1392 
1393 	GList      *st;
1394 	GHashTable *anchors;
1395 	gboolean    do_transform;
1396 } GXPSAnchorsContext;
1397 
1398 typedef struct {
1399 	gchar *data;
1400 	gchar *name;
1401 } GXPSPathAnchor;
1402 
1403 static void
anchors_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)1404 anchors_start_element (GMarkupParseContext  *context,
1405 		       const gchar          *element_name,
1406 		       const gchar         **names,
1407 		       const gchar         **values,
1408 		       gpointer              user_data,
1409 		       GError              **error)
1410 {
1411 	GXPSAnchorsContext *ctx = (GXPSAnchorsContext *)user_data;
1412 
1413 	if (strcmp (element_name, "Canvas") == 0) {
1414 		gint i;
1415 
1416 		GXPS_DEBUG (g_message ("save"));
1417 		cairo_save (ctx->cr);
1418 
1419 		for (i = 0; names[i] != NULL; i++) {
1420 			if (strcmp (names[i], "RenderTransform") == 0) {
1421 				cairo_matrix_t matrix;
1422 
1423 				if (!gxps_matrix_parse (values[i], &matrix)) {
1424 					gxps_parse_error (context,
1425 							  ctx->page->priv->source,
1426 							  G_MARKUP_ERROR_INVALID_CONTENT,
1427 							  "Canvas", "RenderTransform", values[i], error);
1428 					return;
1429 				}
1430 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
1431 					      matrix.xx, matrix.yx,
1432 					      matrix.xy, matrix.yy,
1433 					      matrix.x0, matrix.y0));
1434 				cairo_transform (ctx->cr, &matrix);
1435 
1436 				return;
1437 			}
1438 		}
1439 	} else if (strcmp (element_name, "Path") == 0) {
1440 		gint i;
1441 		GXPSPathAnchor *path_anchor;
1442 		const gchar *data = NULL;
1443 		const gchar *name = NULL;
1444 
1445 		GXPS_DEBUG (g_message ("save"));
1446 		cairo_save (ctx->cr);
1447 
1448 		for (i = 0; names[i] != NULL; i++) {
1449 			if (strcmp (names[i], "Data") == 0) {
1450 				data = values[i];
1451 			} else if (strcmp (names[i], "RenderTransform") == 0) {
1452 				cairo_matrix_t matrix;
1453 
1454 				if (!gxps_matrix_parse (values[i], &matrix)) {
1455 					gxps_parse_error (context,
1456 							  ctx->page->priv->source,
1457 							  G_MARKUP_ERROR_INVALID_CONTENT,
1458 							  "Path", "RenderTransform", values[i], error);
1459 					return;
1460 				}
1461 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
1462 					      matrix.xx, matrix.yx,
1463 					      matrix.xy, matrix.yy,
1464 					      matrix.x0, matrix.y0));
1465 				cairo_transform (ctx->cr, &matrix);
1466 			} else if (strcmp (names[i], "Name") == 0) {
1467 				name = values[i];
1468 			}
1469 		}
1470 
1471 		path_anchor = g_slice_new0 (GXPSPathAnchor);
1472 		if (name) {
1473 			path_anchor->data = data ? g_strdup (data) : NULL;
1474 			path_anchor->name = g_strdup (name);
1475 		}
1476 
1477 		ctx->st = g_list_prepend (ctx->st, path_anchor);
1478 	} else if (strcmp (element_name, "Glyphs") == 0) {
1479 		gint i;
1480 
1481 		GXPS_DEBUG (g_message ("save"));
1482 		cairo_save (ctx->cr);
1483 
1484 		for (i = 0; names[i] != NULL; i++) {
1485 			if (strcmp (names[i], "RenderTransform") == 0) {
1486 				cairo_matrix_t matrix;
1487 
1488 				if (!gxps_matrix_parse (values[i], &matrix)) {
1489 					gxps_parse_error (context,
1490 							  ctx->page->priv->source,
1491 							  G_MARKUP_ERROR_INVALID_CONTENT,
1492 							  "Glyphs", "RenderTransform", values[i], error);
1493 					return;
1494 				}
1495 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
1496 					      matrix.xx, matrix.yx,
1497 					      matrix.xy, matrix.yy,
1498 					      matrix.x0, matrix.y0));
1499 				cairo_transform (ctx->cr, &matrix);
1500 			} else if (strcmp (names[i], "Name") == 0) {
1501 				/* TODO */
1502 			}
1503 		}
1504 	} else if (strcmp (element_name, "Canvas.RenderTransform") == 0 ||
1505 		   strcmp (element_name, "Path.RenderTransform") == 0 ||
1506 		   strcmp (element_name, "Glyphs.RenderTransform") == 0 ) {
1507 		ctx->do_transform = TRUE;
1508 	} else if (strcmp (element_name, "MatrixTransform") == 0) {
1509 		gint i;
1510 
1511 		if (!ctx->do_transform) {
1512 			return;
1513 		}
1514 
1515 		for (i = 0; names[i] != NULL; i++) {
1516 			if (strcmp (names[i], "Matrix") == 0) {
1517 				cairo_matrix_t matrix;
1518 
1519 				if (!gxps_matrix_parse (values[i], &matrix)) {
1520 					gxps_parse_error (context,
1521 							  ctx->page->priv->source,
1522 							  G_MARKUP_ERROR_INVALID_CONTENT,
1523 							  "MatrixTransform", "Matrix",
1524 							  values[i], error);
1525 					return;
1526 				}
1527 				GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
1528 					      matrix.xx, matrix.yx,
1529 					      matrix.xy, matrix.yy,
1530 					      matrix.x0, matrix.y0));
1531 				cairo_transform (ctx->cr, &matrix);
1532 				return;
1533 			}
1534 		}
1535 	}
1536 }
1537 
1538 static void
anchors_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)1539 anchors_end_element (GMarkupParseContext  *context,
1540 		     const gchar          *element_name,
1541 		     gpointer              user_data,
1542 		     GError              **error)
1543 {
1544 	GXPSAnchorsContext *ctx = (GXPSAnchorsContext *)user_data;
1545 
1546 	if (strcmp (element_name, "Canvas") == 0) {
1547 		GXPS_DEBUG (g_message ("restore"));
1548 		cairo_restore (ctx->cr);
1549 	} else if (strcmp (element_name, "Path") == 0) {
1550 		GXPSPathAnchor *path_anchor;
1551 
1552 		path_anchor = (GXPSPathAnchor *)ctx->st->data;
1553 		ctx->st = g_list_delete_link (ctx->st, ctx->st);
1554 		if (path_anchor->name) {
1555 			gdouble x1, y1, x2, y2;
1556 			cairo_rectangle_t *rect;
1557 
1558 			if (path_anchor->data)
1559 				gxps_path_parse (path_anchor->data, ctx->cr, error);
1560 
1561 			cairo_path_extents (ctx->cr, &x1, &y1, &x2, &y2);
1562 			cairo_user_to_device (ctx->cr, &x1, &y1);
1563 			cairo_user_to_device (ctx->cr, &x2, &y2);
1564 
1565 			rect = g_slice_new (cairo_rectangle_t);
1566 			rect->x = x1;
1567 			rect->y = y1;
1568 			rect->width = x2 - x1;
1569 			rect->height = y2 - y1;
1570 			g_hash_table_insert (ctx->anchors, path_anchor->name, rect);
1571 		}
1572 		g_free (path_anchor->data);
1573 		g_slice_free (GXPSPathAnchor, path_anchor);
1574 		cairo_new_path (ctx->cr);
1575 		GXPS_DEBUG (g_message ("restore"));
1576 		cairo_restore (ctx->cr);
1577 	} else if (strcmp (element_name, "Glyphs") == 0) {
1578 		GXPS_DEBUG (g_message ("restore"));
1579 		cairo_restore (ctx->cr);
1580 	} else if (strcmp (element_name, "Canvas.RenderTransform") == 0 ||
1581 		   strcmp (element_name, "Path.RenderTransform") == 0 ||
1582 		   strcmp (element_name, "Glyphs.RenderTransform") == 0 ) {
1583 		ctx->do_transform = FALSE;
1584 	}
1585 }
1586 
1587 static const GMarkupParser anchors_parser = {
1588 	anchors_start_element,
1589 	anchors_end_element,
1590 	NULL,
1591 	NULL,
1592 	NULL
1593 };
1594 
1595 static void
anchor_area_free(cairo_rectangle_t * area)1596 anchor_area_free (cairo_rectangle_t *area)
1597 {
1598 	g_slice_free (cairo_rectangle_t, area);
1599 }
1600 
1601 static gboolean
gxps_page_parse_anchors(GXPSPage * page,cairo_t * cr,GError ** error)1602 gxps_page_parse_anchors (GXPSPage *page,
1603 			 cairo_t  *cr,
1604 			 GError  **error)
1605 {
1606 	GInputStream        *stream;
1607 	GXPSAnchorsContext   ctx;
1608 	GMarkupParseContext *context;
1609 
1610 	stream = gxps_archive_open (page->priv->zip,
1611 				    page->priv->source);
1612 	if (!stream) {
1613 		g_set_error (error,
1614 			     GXPS_ERROR,
1615 			     GXPS_ERROR_SOURCE_NOT_FOUND,
1616 			     "Page source %s not found in archive",
1617 			     page->priv->source);
1618 		return FALSE;
1619 	}
1620 
1621 	ctx.cr = cr;
1622 	ctx.page = page;
1623 	ctx.st = NULL;
1624 	ctx.anchors = g_hash_table_new_full (g_str_hash,
1625 					     g_str_equal,
1626 					     (GDestroyNotify)g_free,
1627 					     (GDestroyNotify)anchor_area_free);
1628 
1629 	context = g_markup_parse_context_new (&anchors_parser, 0, &ctx, NULL);
1630 	gxps_parse_stream (context, stream, error);
1631 	g_object_unref (stream);
1632 	g_markup_parse_context_free (context);
1633 
1634 	if (g_hash_table_size (ctx.anchors) > 0) {
1635 		page->priv->has_anchors = TRUE;
1636 		page->priv->anchors = ctx.anchors;
1637 	} else {
1638 		page->priv->has_anchors = FALSE;
1639 		g_hash_table_destroy (ctx.anchors);
1640 	}
1641 
1642 	return TRUE;
1643 }
1644 
1645 static void
gxps_page_finalize(GObject * object)1646 gxps_page_finalize (GObject *object)
1647 {
1648 	GXPSPage *page = GXPS_PAGE (object);
1649 
1650 	g_clear_object (&page->priv->zip);
1651 	g_clear_pointer (&page->priv->source, g_free);
1652 	g_clear_error (&page->priv->init_error);
1653 	g_clear_pointer (&page->priv->lang, g_free);
1654 	g_clear_pointer (&page->priv->name, g_free);
1655 	g_clear_pointer (&page->priv->image_cache, g_hash_table_destroy);
1656 	g_clear_pointer (&page->priv->anchors, g_hash_table_destroy);
1657 	page->priv->has_anchors = FALSE;
1658 
1659 	G_OBJECT_CLASS (gxps_page_parent_class)->finalize (object);
1660 }
1661 
1662 static void
gxps_page_init(GXPSPage * page)1663 gxps_page_init (GXPSPage *page)
1664 {
1665 	page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page,
1666 						  GXPS_TYPE_PAGE,
1667 						  GXPSPagePrivate);
1668 	page->priv->has_anchors = TRUE;
1669 }
1670 
1671 static void
gxps_page_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1672 gxps_page_set_property (GObject      *object,
1673 			guint         prop_id,
1674 			const GValue *value,
1675 			GParamSpec   *pspec)
1676 {
1677 	GXPSPage *page = GXPS_PAGE (object);
1678 
1679 	switch (prop_id) {
1680 	case PROP_ARCHIVE:
1681 		page->priv->zip = g_value_dup_object (value);
1682 		break;
1683 	case PROP_SOURCE:
1684 		page->priv->source = g_value_dup_string (value);
1685 		break;
1686 	default:
1687 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1688 		break;
1689 	}
1690 }
1691 
1692 static void
gxps_page_class_init(GXPSPageClass * klass)1693 gxps_page_class_init (GXPSPageClass *klass)
1694 {
1695 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
1696 
1697 	object_class->set_property = gxps_page_set_property;
1698 	object_class->finalize = gxps_page_finalize;
1699 
1700 	g_object_class_install_property (object_class,
1701 					 PROP_ARCHIVE,
1702 					 g_param_spec_object ("archive",
1703 							      "Archive",
1704 							      "The document archive",
1705 							      GXPS_TYPE_ARCHIVE,
1706 							      G_PARAM_WRITABLE |
1707 							      G_PARAM_CONSTRUCT_ONLY));
1708 	g_object_class_install_property (object_class,
1709 					 PROP_SOURCE,
1710 					 g_param_spec_string ("source",
1711 							      "Source",
1712 							      "The Page Source File",
1713 							      NULL,
1714 							      G_PARAM_WRITABLE |
1715 							      G_PARAM_CONSTRUCT_ONLY));
1716 
1717 	g_type_class_add_private (klass, sizeof (GXPSPagePrivate));
1718 }
1719 
1720 static gboolean
gxps_page_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)1721 gxps_page_initable_init (GInitable     *initable,
1722 			 GCancellable  *cancellable,
1723 			 GError       **error)
1724 {
1725 	GXPSPage *page = GXPS_PAGE (initable);
1726 
1727 	if (page->priv->initialized) {
1728 		if (page->priv->init_error) {
1729 			g_propagate_error (error, g_error_copy (page->priv->init_error));
1730 
1731 			return FALSE;
1732 		}
1733 		return TRUE;
1734 	}
1735 
1736 	page->priv->initialized = TRUE;
1737 
1738 	if (!gxps_page_parse_fixed_page (page, &page->priv->init_error)) {
1739 		g_propagate_error (error, g_error_copy (page->priv->init_error));
1740 		return FALSE;
1741 	}
1742 
1743 	if (!page->priv->lang || page->priv->width == -1 || page->priv->height == -1) {
1744 		if (!page->priv->lang) {
1745 			g_set_error_literal (&page->priv->init_error,
1746 					     GXPS_PAGE_ERROR,
1747 					     GXPS_PAGE_ERROR_INVALID,
1748 					     "Missing required attribute xml:lang");
1749 		} else {
1750 			g_set_error_literal (&page->priv->init_error,
1751 					     GXPS_PAGE_ERROR,
1752 					     GXPS_PAGE_ERROR_INVALID,
1753 					     "Missing page size");
1754 		}
1755 
1756 		g_propagate_error (error, g_error_copy (page->priv->init_error));
1757 
1758 		return FALSE;
1759 	}
1760 
1761 	return TRUE;
1762 }
1763 
1764 static void
initable_iface_init(GInitableIface * initable_iface)1765 initable_iface_init (GInitableIface *initable_iface)
1766 {
1767 	initable_iface->init = gxps_page_initable_init;
1768 }
1769 
1770 GXPSPage *
_gxps_page_new(GXPSArchive * zip,const gchar * source,GError ** error)1771 _gxps_page_new (GXPSArchive *zip,
1772 		const gchar *source,
1773 		GError     **error)
1774 {
1775 	return g_initable_new (GXPS_TYPE_PAGE,
1776 			       NULL, error,
1777 			       "archive", zip,
1778 			       "source", source,
1779 			       NULL);
1780 }
1781 
1782 /**
1783  * gxps_page_get_size:
1784  * @page: a #GXPSPage
1785  * @width: (out) (allow-none): return location for the page width
1786  * @height: (out) (allow-none): return location for the page height
1787  *
1788  * Gets the size of the page.
1789  */
1790 void
gxps_page_get_size(GXPSPage * page,gdouble * width,gdouble * height)1791 gxps_page_get_size (GXPSPage *page,
1792 		    gdouble  *width,
1793 		    gdouble  *height)
1794 {
1795 	g_return_if_fail (GXPS_IS_PAGE (page));
1796 
1797 	if (width)
1798 		*width = page->priv->width;
1799 	if (height)
1800 		*height = page->priv->height;
1801 }
1802 
1803 /**
1804  * gxps_page_render:
1805  * @page: a #GXPSPage
1806  * @cr: a cairo context to render to
1807  * @error: #GError for error reporting, or %NULL to ignore
1808  *
1809  * Render the page to the given cairo context. In case of
1810  * error, %FALSE is returned and @error is filled with
1811  * information about error.
1812  *
1813  * Returns: %TRUE if page was successfully rendered,
1814  *     %FALSE otherwise.
1815  */
1816 gboolean
gxps_page_render(GXPSPage * page,cairo_t * cr,GError ** error)1817 gxps_page_render (GXPSPage *page,
1818 		  cairo_t  *cr,
1819 		  GError  **error)
1820 {
1821 	g_return_val_if_fail (GXPS_IS_PAGE (page), FALSE);
1822 	g_return_val_if_fail (cr != NULL, FALSE);
1823 
1824 	return gxps_page_parse_for_rendering (page, cr, error);
1825 }
1826 
1827 /**
1828  * gxps_page_get_links:
1829  * @page: a #GXPSPage
1830  * @error: #GError for error reporting, or %NULL to ignore
1831  *
1832  * Gets a list of #GXPSLink items that map from a location
1833  * in @page to a #GXPSLinkTarget. Items in the list should
1834  * be freed with gxps_link_free() and the list itself with
1835  * g_list_free() when done.
1836  *
1837  * Returns: (element-type GXPS.Link) (transfer full):  a #GList
1838  *     of #GXPSLink items.
1839  */
1840 GList *
gxps_page_get_links(GXPSPage * page,GError ** error)1841 gxps_page_get_links (GXPSPage *page,
1842 		     GError  **error)
1843 {
1844 	cairo_surface_t  *surface;
1845 	cairo_t          *cr;
1846 	GList            *links;
1847 	cairo_rectangle_t extents;
1848 
1849         g_return_val_if_fail (GXPS_IS_PAGE (page), NULL);
1850 
1851 	extents.x = extents.y = 0;
1852 	extents.width = page->priv->width;
1853 	extents.height = page->priv->height;
1854 
1855 	surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR, &extents);
1856 	cr = cairo_create (surface);
1857 	cairo_surface_destroy (surface);
1858 
1859 	links = gxps_page_parse_links (page, cr, error);
1860 	cairo_destroy (cr);
1861 
1862 	return links;
1863 }
1864 
1865 /**
1866  * gxps_page_get_anchor_destination:
1867  * @page: a #GXPSPage
1868  * @anchor: the name of an anchor in @page
1869  * @area: (out): return location for page area of @anchor
1870  * @error: #GError for error reporting, or %NULL to ignore
1871  *
1872  * Gets the rectangle of @page corresponding to the destination
1873  * of the given anchor. If @anchor is not found in @page, %FALSE
1874  * will be returned and @error will contain %GXPS_PAGE_ERROR_INVALID_ANCHOR
1875  *
1876  * Returns: %TRUE if the destination for the anchor was found in page
1877  *     and @area contains the rectangle, %FALSE otherwise.
1878  */
1879 gboolean
gxps_page_get_anchor_destination(GXPSPage * page,const gchar * anchor,cairo_rectangle_t * area,GError ** error)1880 gxps_page_get_anchor_destination (GXPSPage          *page,
1881 				  const gchar       *anchor,
1882 				  cairo_rectangle_t *area,
1883 				  GError           **error)
1884 {
1885 	cairo_rectangle_t *anchor_area;
1886 
1887         g_return_val_if_fail (GXPS_IS_PAGE (page), FALSE);
1888         g_return_val_if_fail (anchor != NULL, FALSE);
1889         g_return_val_if_fail (area != NULL, FALSE);
1890 
1891 	if (!page->priv->has_anchors)
1892 		return FALSE;
1893 
1894 	if (!page->priv->anchors) {
1895 		cairo_surface_t  *surface;
1896 		cairo_t          *cr;
1897 		cairo_rectangle_t extents;
1898 		gboolean          success;
1899 
1900 		extents.x = extents.y = 0;
1901 		extents.width = page->priv->width;
1902 		extents.height = page->priv->height;
1903 
1904 		surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR, &extents);
1905 		cr = cairo_create (surface);
1906 		cairo_surface_destroy (surface);
1907 
1908 		success = gxps_page_parse_anchors (page, cr, error);
1909 		cairo_destroy (cr);
1910 		if (!success)
1911 			return FALSE;
1912 	}
1913 
1914 	anchor_area = g_hash_table_lookup (page->priv->anchors, anchor);
1915 	if (!anchor_area) {
1916 		g_set_error (error,
1917 			     GXPS_PAGE_ERROR,
1918 			     GXPS_PAGE_ERROR_INVALID_ANCHOR,
1919 			     "Invalid anchor '%s' for page", anchor);
1920 		return FALSE;
1921 	}
1922 
1923 	*area = *anchor_area;
1924 
1925 	return TRUE;
1926 }
1927