<commit_before>/* GXPSPage
 *
 * Copyright (C) 2010  <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <config.h>

#include <stdlib.h>
#include <string.h>

#include "gxps-page-private.h"
#include "gxps-matrix.h"
#include "gxps-brush.h"
#include "gxps-path.h"
#include "gxps-glyphs.h"
#include "gxps-fonts.h"
#include "gxps-links.h"
#include "gxps-images.h"
#include "gxps-color.h"
#include "gxps-private.h"
#include "gxps-error.h"
#include "gxps-debug.h"

/**
 * SECTION:gxps-page
 * @Short_description: Page of XPS document
 * @Title: GXPSPage
 * @See_also: #GXPSDocument, #GXPSLink, #GXPSLinkTarget
 *
 * #GXPSPage represents a page in a XPS document. #GXPSPage<!-- -->s
 * can be rendered into a cairo context with gxps_page_render().
 * #GXPSPage objects can not be created directly, they are retrieved
 * from a #GXPSDocument with gxps_document_get_page().
 */

enum {
	PROP_0,
	PROP_ARCHIVE,
	PROP_SOURCE
};

static void render_start_element (GMarkupParseContext  *context,
				  const gchar          *element_name,
				  const gchar         **names,
				  const gchar         **values,
				  gpointer              user_data,
				  GError              **error);
static void render_end_element   (GMarkupParseContext  *context,
				  const gchar          *element_name,
				  gpointer              user_data,
				  GError              **error);
static void initable_iface_init  (GInitableIface       *initable_iface);

G_DEFINE_TYPE_WITH_CODE (GXPSPage, gxps_page, G_TYPE_OBJECT,
			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))

GQuark
gxps_page_error_quark (void)
{
	return g_quark_from_static_string ("gxps-page-error-quark");
}

/* Images */
cairo_surface_t *
gxps_page_get_image (GXPSPage    *page,
		     const gchar *image_uri,
		     GError     **error)
{
	cairo_surface_t *surface;

	if (page->priv->image_cache) {
		surface = g_hash_table_lookup (page->priv->image_cache,
					       image_uri);
		if (surface)
			return cairo_surface_reference (surface);
	}

	surface = gxps_images_get_image (page->priv->zip, image_uri, error);
	if (!surface)
		return NULL;

	if (!page->priv->image_cache) {
		page->priv->image_cache = g_hash_table_new_full (g_str_hash,
								 g_str_equal,
								 (GDestroyNotify)g_free,
								 (GDestroyNotify)cairo_surface_destroy);
	}

	g_hash_table_insert (page->priv->image_cache,
			     g_strdup (image_uri),
			     cairo_surface_reference (surface));
	return surface;
}

/* FixedPage parser */
static void
fixed_page_start_element (GMarkupParseContext  *context,
			  const gchar          *element_name,
			  const gchar         **names,
			  const gchar         **values,
			  gpointer              user_data,
			  GError              **error)
{
	GXPSPage *page = GXPS_PAGE (user_data);
	gint      i;

	if (strcmp (element_name, "FixedPage") == 0) {
		for (i = 0; names[i] != NULL; i++) {
			if (strcmp (names[i], "Width") == 0) {
				if (!gxps_value_get_double_positive (values[i], &page->priv->width)) {
                                        gxps_parse_error (context,
                                                          page->priv->source,
                                                          G_MARKUP_ERROR_MISSING_ATTRIBUTE,
                                                          element_name, "Width",
                                                          NULL, error);
                                        return;
                                }
			} else if (strcmp (names[i], "Height") == 0) {
				if (!gxps_value_get_double_positive (values[i], &page->priv->height)) {
                                        gxps_parse_error (context,
                                                          page->priv->source,
                                                          G_MARKUP_ERROR_MISSING_ATTRIBUTE,
                                                          element_name, "Height",
                                                          NULL, error);
                                        return;
                                }
			} else if (strcmp (names[i], "xml:lang") == 0) {
				page->priv->lang = g_strdup (values[i]);
			} else if (strcmp (names[i], "ContentBox") == 0) {
				/* TODO */
			} else if (strcmp (names[i], "BleedBox") == 0) {
				/* TODO */
			} else if (strcmp (names[i], "Name") == 0) {
				page->priv->name = g_strdup (values[i]);
			}
		}
	}
}

static const GMarkupParser fixed_page_parser = {
	fixed_page_start_element,
	NULL,
	NULL,
	NULL
};

static gboolean
gxps_page_parse_fixed_page (GXPSPage *page,
			    GError  **error)
{
	GInputStream        *stream;
	GMarkupParseContext *ctx;

	stream = gxps_archive_open (page->priv->zip,
				    page->priv->source);
	if (!stream) {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_SOURCE_NOT_FOUND,
			     "Page source %s not found in archive",
			     page->priv->source);

		return FALSE;
	}

	ctx = g_markup_parse_context_new (&fixed_page_parser, 0, page, NULL);
	gxps_parse_stream (ctx, stream, error);
	g_object_unref (stream);
	g_markup_parse_context_free (ctx);

	return (*error != NULL) ? FALSE : TRUE;
}

/* Page Render Parser */
static GMarkupParser render_parser = {
	render_start_element,
	render_end_element,
	NULL,
	NULL
};

void
gxps_page_render_parser_push (GMarkupParseContext *context,
                              GXPSRenderContext   *ctx)
{
        g_markup_parse_context_push (context, &render_parser, ctx);
}

static gboolean
gxps_dash_array_parse (const gchar *dash,
		       gdouble    **dashes_out,
		       guint       *num_dashes_out)
{
	gchar  **items;
	guint    i;
        gdouble *dashes;
        guint    num_dashes;

	items = g_strsplit (dash, " ", -1);
	if (!items)
		return FALSE;

	num_dashes = g_strv_length (items);
	dashes = g_malloc (num_dashes * sizeof (gdouble));

	for (i = 0; i < num_dashes; i++) {
                if (!gxps_value_get_double (items[i], &dashes[i])) {
                        g_free (dashes);
                        g_strfreev (items);

                        return FALSE;
                }
        }

	g_strfreev (items);

        *dashes_out = dashes;
        *num_dashes_out = num_dashes;

	return TRUE;
}

static cairo_line_cap_t
gxps_line_cap_parse (const gchar *cap)
{
	if (strcmp (cap, "Flat") == 0)
		return CAIRO_LINE_CAP_BUTT;
	else if (strcmp (cap, "Round") == 0)
		return CAIRO_LINE_CAP_ROUND;
	else if (strcmp (cap, "Square") == 0)
		return CAIRO_LINE_CAP_SQUARE;
	else if (strcmp (cap, "Triangle") == 0)
		GXPS_DEBUG (g_debug ("Unsupported dash cap Triangle"));

	return CAIRO_LINE_CAP_BUTT;
}

static cairo_line_join_t
gxps_line_join_parse (const gchar *join)
{
	if (strcmp (join, "Miter") == 0)
		return CAIRO_LINE_JOIN_MITER;
	else if (strcmp (join, "Bevel") == 0)
		return CAIRO_LINE_JOIN_BEVEL;
	else if (strcmp (join, "Round") == 0)
		return CAIRO_LINE_JOIN_ROUND;
	return CAIRO_LINE_JOIN_MITER;
}

typedef struct {
	GXPSRenderContext *ctx;

	gdouble            opacity;
	cairo_pattern_t   *opacity_mask;
} GXPSCanvas;

static GXPSCanvas *
gxps_canvas_new (GXPSRenderContext *ctx)
{
	GXPSCanvas *canvas;

	canvas = g_slice_new0 (GXPSCanvas);
	canvas->ctx = ctx;

	/* Default values */
	canvas->opacity = 1.0;

	return canvas;
}

static void
gxps_canvas_free (GXPSCanvas *canvas)
{
	if (G_UNLIKELY (!canvas))
		return;

	cairo_pattern_destroy (canvas->opacity_mask);
	g_slice_free (GXPSCanvas, canvas);
}

static void
canvas_start_element (GMarkupParseContext  *context,
		      const gchar          *element_name,
		      const gchar         **names,
		      const gchar         **values,
		      gpointer              user_data,
		      GError              **error)
{
	GXPSCanvas *canvas = (GXPSCanvas *)user_data;

	if (strcmp (element_name, "Canvas.RenderTransform") == 0) {
		GXPSMatrix *matrix;

		matrix = gxps_matrix_new (canvas->ctx);
		gxps_matrix_parser_push (context, matrix);
	} else if (strcmp (element_name, "Canvas.OpacityMask") == 0) {
		GXPSBrush *brush;

		brush = gxps_brush_new (canvas->ctx);
		gxps_brush_parser_push (context, brush);
	} else {
		render_start_element (context,
				      element_name,
				      names,
				      values,
				      canvas->ctx,
				      error);
	}
}

static void
canvas_end_element (GMarkupParseContext  *context,
		    const gchar          *element_name,
		    gpointer              user_data,
		    GError              **error)
{
	GXPSCanvas *canvas = (GXPSCanvas *)user_data;

	if (strcmp (element_name, "Canvas.RenderTransform") == 0) {
		GXPSMatrix *matrix;

		matrix = g_markup_parse_context_pop (context);
		GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
			      matrix->matrix.xx, matrix->matrix.yx,
			      matrix->matrix.xy, matrix->matrix.yy,
			      matrix->matrix.x0, matrix->matrix.y0));
		cairo_transform (canvas->ctx->cr, &matrix->matrix);
		gxps_matrix_free (matrix);
	} else if (strcmp (element_name, "Canvas.OpacityMask") == 0) {
		GXPSBrush *brush;

		brush = g_markup_parse_context_pop (context);
		if (!canvas->opacity_mask) {
			canvas->opacity_mask = cairo_pattern_reference (brush->pattern);
			cairo_push_group (canvas->ctx->cr);
		}
		gxps_brush_free (brush);
	} else {
		render_end_element (context,
				    element_name,
				    canvas->ctx,
				    error);
	}
}

static GMarkupParser canvas_parser = {
	canvas_start_element,
	canvas_end_element,
	NULL,
	NULL
};

static void
render_start_element (GMarkupParseContext  *context,
		      const gchar          *element_name,
		      const gchar         **names,
		      const gchar         **values,
		      gpointer              user_data,
		      GError              **error)
{
	GXPSRenderContext *ctx = (GXPSRenderContext *)user_data;
<commit_msg>Fixed memory leak when parsing fails for a child element of a Canvas<commit_after>/* GXPSPage
 *
 * Copyright (C) 2010  <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <config.h>

#include <stdlib.h>
#include <string.h>

#include "gxps-page-private.h"
#include "gxps-matrix.h"
#include "gxps-brush.h"
#include "gxps-path.h"
#include "gxps-glyphs.h"
#include "gxps-fonts.h"
#include "gxps-links.h"
#include "gxps-images.h"
#include "gxps-color.h"
#include "gxps-private.h"
#include "gxps-error.h"
#include "gxps-debug.h"

/**
 * SECTION:gxps-page
 * @Short_description: Page of XPS document
 * @Title: GXPSPage
 * @See_also: #GXPSDocument, #GXPSLink, #GXPSLinkTarget
 *
 * #GXPSPage represents a page in a XPS document. #GXPSPage<!-- -->s
 * can be rendered into a cairo context with gxps_page_render().
 * #GXPSPage objects can not be created directly, they are retrieved
 * from a #GXPSDocument with gxps_document_get_page().
 */

enum {
	PROP_0,
	PROP_ARCHIVE,
	PROP_SOURCE
};

static void render_start_element (GMarkupParseContext  *context,
				  const gchar          *element_name,
				  const gchar         **names,
				  const gchar         **values,
				  gpointer              user_data,
				  GError              **error);
static void render_end_element   (GMarkupParseContext  *context,
				  const gchar          *element_name,
				  gpointer              user_data,
				  GError              **error);
static void initable_iface_init  (GInitableIface       *initable_iface);

G_DEFINE_TYPE_WITH_CODE (GXPSPage, gxps_page, G_TYPE_OBJECT,
			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))

GQuark
gxps_page_error_quark (void)
{
	return g_quark_from_static_string ("gxps-page-error-quark");
}

/* Images */
cairo_surface_t *
gxps_page_get_image (GXPSPage    *page,
		     const gchar *image_uri,
		     GError     **error)
{
	cairo_surface_t *surface;

	if (page->priv->image_cache) {
		surface = g_hash_table_lookup (page->priv->image_cache,
					       image_uri);
		if (surface)
			return cairo_surface_reference (surface);
	}

	surface = gxps_images_get_image (page->priv->zip, image_uri, error);
	if (!surface)
		return NULL;

	if (!page->priv->image_cache) {
		page->priv->image_cache = g_hash_table_new_full (g_str_hash,
								 g_str_equal,
								 (GDestroyNotify)g_free,
								 (GDestroyNotify)cairo_surface_destroy);
	}

	g_hash_table_insert (page->priv->image_cache,
			     g_strdup (image_uri),
			     cairo_surface_reference (surface));
	return surface;
}

/* FixedPage parser */
static void
fixed_page_start_element (GMarkupParseContext  *context,
			  const gchar          *element_name,
			  const gchar         **names,
			  const gchar         **values,
			  gpointer              user_data,
			  GError              **error)
{
	GXPSPage *page = GXPS_PAGE (user_data);
	gint      i;

	if (strcmp (element_name, "FixedPage") == 0) {
		for (i = 0; names[i] != NULL; i++) {
			if (strcmp (names[i], "Width") == 0) {
				if (!gxps_value_get_double_positive (values[i], &page->priv->width)) {
                                        gxps_parse_error (context,
                                                          page->priv->source,
                                                          G_MARKUP_ERROR_MISSING_ATTRIBUTE,
                                                          element_name, "Width",
                                                          NULL, error);
                                        return;
                                }
			} else if (strcmp (names[i], "Height") == 0) {
				if (!gxps_value_get_double_positive (values[i], &page->priv->height)) {
                                        gxps_parse_error (context,
                                                          page->priv->source,
                                                          G_MARKUP_ERROR_MISSING_ATTRIBUTE,
                                                          element_name, "Height",
                                                          NULL, error);
                                        return;
                                }
			} else if (strcmp (names[i], "xml:lang") == 0) {
				page->priv->lang = g_strdup (values[i]);
			} else if (strcmp (names[i], "ContentBox") == 0) {
				/* TODO */
			} else if (strcmp (names[i], "BleedBox") == 0) {
				/* TODO */
			} else if (strcmp (names[i], "Name") == 0) {
				page->priv->name = g_strdup (values[i]);
			}
		}
	}
}

static const GMarkupParser fixed_page_parser = {
	fixed_page_start_element,
	NULL,
	NULL,
	NULL
};

static gboolean
gxps_page_parse_fixed_page (GXPSPage *page,
			    GError  **error)
{
	GInputStream        *stream;
	GMarkupParseContext *ctx;

	stream = gxps_archive_open (page->priv->zip,
				    page->priv->source);
	if (!stream) {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_SOURCE_NOT_FOUND,
			     "Page source %s not found in archive",
			     page->priv->source);

		return FALSE;
	}

	ctx = g_markup_parse_context_new (&fixed_page_parser, 0, page, NULL);
	gxps_parse_stream (ctx, stream, error);
	g_object_unref (stream);
	g_markup_parse_context_free (ctx);

	return (*error != NULL) ? FALSE : TRUE;
}

/* Page Render Parser */
static GMarkupParser render_parser = {
	render_start_element,
	render_end_element,
	NULL,
	NULL
};

void
gxps_page_render_parser_push (GMarkupParseContext *context,
                              GXPSRenderContext   *ctx)
{
        g_markup_parse_context_push (context, &render_parser, ctx);
}

static gboolean
gxps_dash_array_parse (const gchar *dash,
		       gdouble    **dashes_out,
		       guint       *num_dashes_out)
{
	gchar  **items;
	guint    i;
        gdouble *dashes;
        guint    num_dashes;

	items = g_strsplit (dash, " ", -1);
	if (!items)
		return FALSE;

	num_dashes = g_strv_length (items);
	dashes = g_malloc (num_dashes * sizeof (gdouble));

	for (i = 0; i < num_dashes; i++) {
                if (!gxps_value_get_double (items[i], &dashes[i])) {
                        g_free (dashes);
                        g_strfreev (items);

                        return FALSE;
                }
        }

	g_strfreev (items);

        *dashes_out = dashes;
        *num_dashes_out = num_dashes;

	return TRUE;
}

static cairo_line_cap_t
gxps_line_cap_parse (const gchar *cap)
{
	if (strcmp (cap, "Flat") == 0)
		return CAIRO_LINE_CAP_BUTT;
	else if (strcmp (cap, "Round") == 0)
		return CAIRO_LINE_CAP_ROUND;
	else if (strcmp (cap, "Square") == 0)
		return CAIRO_LINE_CAP_SQUARE;
	else if (strcmp (cap, "Triangle") == 0)
		GXPS_DEBUG (g_debug ("Unsupported dash cap Triangle"));

	return CAIRO_LINE_CAP_BUTT;
}

static cairo_line_join_t
gxps_line_join_parse (const gchar *join)
{
	if (strcmp (join, "Miter") == 0)
		return CAIRO_LINE_JOIN_MITER;
	else if (strcmp (join, "Bevel") == 0)
		return CAIRO_LINE_JOIN_BEVEL;
	else if (strcmp (join, "Round") == 0)
		return CAIRO_LINE_JOIN_ROUND;
	return CAIRO_LINE_JOIN_MITER;
}

typedef struct {
	GXPSRenderContext *ctx;

	gdouble            opacity;
	cairo_pattern_t   *opacity_mask;
} GXPSCanvas;

static GXPSCanvas *
gxps_canvas_new (GXPSRenderContext *ctx)
{
	GXPSCanvas *canvas;

	canvas = g_slice_new0 (GXPSCanvas);
	canvas->ctx = ctx;

	/* Default values */
	canvas->opacity = 1.0;

	return canvas;
}

static void
gxps_canvas_free (GXPSCanvas *canvas)
{
	if (G_UNLIKELY (!canvas))
		return;

	cairo_pattern_destroy (canvas->opacity_mask);
	g_slice_free (GXPSCanvas, canvas);
}

static void
canvas_start_element (GMarkupParseContext  *context,
		      const gchar          *element_name,
		      const gchar         **names,
		      const gchar         **values,
		      gpointer              user_data,
		      GError              **error)
{
	GXPSCanvas *canvas = (GXPSCanvas *)user_data;

	if (strcmp (element_name, "Canvas.RenderTransform") == 0) {
		GXPSMatrix *matrix;

		matrix = gxps_matrix_new (canvas->ctx);
		gxps_matrix_parser_push (context, matrix);
	} else if (strcmp (element_name, "Canvas.OpacityMask") == 0) {
		GXPSBrush *brush;

		brush = gxps_brush_new (canvas->ctx);
		gxps_brush_parser_push (context, brush);
	} else {
		render_start_element (context,
				      element_name,
				      names,
				      values,
				      canvas->ctx,
				      error);
	}
}

static void
canvas_end_element (GMarkupParseContext  *context,
		    const gchar          *element_name,
		    gpointer              user_data,
		    GError              **error)
{
	GXPSCanvas *canvas = (GXPSCanvas *)user_data;

	if (strcmp (element_name, "Canvas.RenderTransform") == 0) {
		GXPSMatrix *matrix;

		matrix = g_markup_parse_context_pop (context);
		GXPS_DEBUG (g_message ("transform (%f, %f, %f, %f) [%f, %f]",
			      matrix->matrix.xx, matrix->matrix.yx,
			      matrix->matrix.xy, matrix->matrix.yy,
			      matrix->matrix.x0, matrix->matrix.y0));
		cairo_transform (canvas->ctx->cr, &matrix->matrix);
		gxps_matrix_free (matrix);
	} else if (strcmp (element_name, "Canvas.OpacityMask") == 0) {
		GXPSBrush *brush;

		brush = g_markup_parse_context_pop (context);
		if (!canvas->opacity_mask) {
			canvas->opacity_mask = cairo_pattern_reference (brush->pattern);
			cairo_push_group (canvas->ctx->cr);
		}
		gxps_brush_free (brush);
	} else {
		render_end_element (context,
				    element_name,
				    canvas->ctx,
				    error);
	}
}

static void
canvas_error (GMarkupParseContext *context,
              GError              *error,
              gpointer             user_data)
{
	GXPSCanvas *canvas = (GXPSCanvas *)user_data;
	gxps_canvas_free (canvas);
}

static GMarkupParser canvas_parser = {
	canvas_start_element,
	canvas_end_element,
	NULL,
	NULL,
	canvas_error
};

static void
render_start_element (GMarkupParseContext  *context,
		      const gchar          *element_name,
		      const gchar         **names,
		      const gchar         **values,
		      gpointer              user_data,
		      GError              **error)
{
	GXPSRenderContext *ctx = (GXPSRenderContext *)user_data;

{'fraction_non_alphanumeric': 0.10718347232752085, 'fraction_numerical': 0.004264594389689159, 'mean_word_length': 2.441219631501712, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 119, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 40, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3470786', 'n_tokens_mistral': 7688, 'n_tokens_neox': 6974, 'n_words': 1656}