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