<commit_before>/* vim: set sw=4: -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
   rsvg.c: SAX-based renderer for SVG files into a GdkPixbuf.

   Copyright (C) 2000 Eazel, Inc.
   Copyright (C) 2002 <NAME> <<EMAIL>>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this program; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Author: <NAME> <<EMAIL>>
*/

#include "config.h"

#include "rsvg.h"
#include "rsvg-private.h"
#include "rsvg-css.h"
#include "rsvg-styles.h"
#include "rsvg-shapes.h"
#include "rsvg-text.h"
#include "rsvg-filter.h"
#include "rsvg-mask.h"

#include <math.h>
#include <string.h>
#include <stdarg.h>

#include <libart_lgpl/art_affine.h>

#include "rsvg-bpath-util.h"
#include "rsvg-path.h"
#include "rsvg-paint-server.h"

/*
 * This is configurable at runtime
 */
#define RSVG_DEFAULT_DPI 90.0
static double internal_dpi = RSVG_DEFAULT_DPI;

static void
rsvg_ctx_free_helper (gpointer key, gpointer value, gpointer user_data)
{
	xmlEntityPtr entval = (xmlEntityPtr)value;
	
	/* key == entval->name, so it's implicitly freed below */
	
	g_free ((char *) entval->name);
	g_free ((char *) entval->ExternalID);
	g_free ((char *) entval->SystemID);
	xmlFree (entval->content);
	xmlFree (entval->orig);
	g_free (entval);
}

static void
rsvg_pixmap_destroy (guchar *pixels, gpointer data)
{
	g_free (pixels);
}

static void
rsvg_start_svg (RsvgHandle *ctx, RsvgPropertyBag *atts)
{
	int width = -1, height = -1, x = -1, y = -1;
	int rowstride;
	art_u8 *pixels;
	RsvgState *state;
	gint new_width, new_height;
	double x_zoom = 1.;
	double y_zoom = 1.;
	double affine[6];
	const char * value;

	double vbox_x = 0, vbox_y = 0, vbox_w = 0, vbox_h = 0;
	gboolean has_vbox = FALSE;

	if (rsvg_property_bag_size (atts))
		{
			/* x & y should be ignored since we should always be the outermost SVG,
			   at least for now, but i'll include them here anyway */
			if ((value = rsvg_property_bag_lookup (atts, "viewBox")))
				{
					has_vbox = rsvg_css_parse_vbox (value, &vbox_x, &vbox_y,
													&vbox_w, &vbox_h);
				}
			if ((value = rsvg_property_bag_lookup (atts, "width")))
				width = rsvg_css_parse_normalized_length (value, ctx->dpi, vbox_w, 1);
			if ((value = rsvg_property_bag_lookup (atts, "height")))
				height = rsvg_css_parse_normalized_length (value, ctx->dpi, vbox_h, 1);
			if ((value = rsvg_property_bag_lookup (atts, "x")))
				x = rsvg_css_parse_normalized_length (value, ctx->dpi, vbox_w, 1);
			if ((value = rsvg_property_bag_lookup (atts, "y")))
				y = rsvg_css_parse_normalized_length (value, ctx->dpi, vbox_h, 1);
	
			if (has_vbox && vbox_w > 0. && vbox_h > 0.)
				{
					new_width  = (int)floor (vbox_w);
					new_height = (int)floor (vbox_h);
					
					/* apply the sizing function on the *original* width and height
					   to acquire our real destination size. we'll scale it against
					   the viewBox's coordinates later */
					if (ctx->size_func)
						(* ctx->size_func) (&width, &height, ctx->user_data);
				}
			else
				{
					new_width  = width;
					new_height = height;

					/* bogus hack */
					if (new_width <= 0 || new_height <= 0)
						{
							g_warning (_("rsvg_start_svg: width and height not specified in the SVG"));
							if (new_width <= 0) {width = new_width = 512;}
							if (new_height <= 0) {height = new_height = 512;}
						}

					/* apply the sizing function to acquire our new width and height.
					   we'll scale this against the old values later */
					if (ctx->size_func)
						(* ctx->size_func) (&new_width, &new_height, ctx->user_data);
				}

			/* set these here because % are relative to viewbox */
			ctx->width = new_width;
			ctx->height = new_height;

			if (!has_vbox)
				{
					  x_zoom = (width < 0 || new_width < 0) ? 1 : (double) new_width / width;
					  y_zoom = (height < 0 || new_height < 0) ? 1 : (double) new_height / height;
				}
			else
				{
					x_zoom = (width < 0 || new_width < 0) ? 1 : (double) width / new_width;
					y_zoom = (height < 0 || new_height < 0) ? 1 : (double) height / new_height;

					/* reset these so that we get a properly sized SVG and not a huge one */
					new_width  = (width == -1 ? new_width : width);
					new_height = (height == -1 ? new_height : height);
				}

			/* Scale size of target pixbuf */
			state = rsvg_state_current (ctx);

			art_affine_identity (state->affine);

			if (has_vbox && (vbox_x != 0. || vbox_y != 0.))
				{
					art_affine_translate (affine, - vbox_x, - vbox_y);
					art_affine_multiply (state->affine, state->affine, affine);
				}

			art_affine_scale (affine, x_zoom, y_zoom);
			art_affine_multiply (state->affine, state->affine, affine);

			if (new_width <= 0 || new_height <= 0)
				{
					g_warning (_("rsvg_start_svg: width and height not specified in the SVG, nor supplied by the size callback"));
					if (new_width <= 0) new_width = 512;
					if (new_height <= 0) new_height = 512;
				}

			if (new_width >= INT_MAX / 4)
				{
					/* FIXME: GError here? */
					g_warning (_("rsvg_start_svg: width too large"));
					return;
				}
			rowstride = (new_width * 4 + 3) & ~3;
			if (rowstride > INT_MAX / new_height)
				{
					/* FIXME: GError here? */
					g_warning (_("rsvg_start_svg: width too large"));
					return;
				}

			/* FIXME: Add GError here if size is too big. */

			pixels = g_try_malloc (rowstride * new_height);
			if (pixels == NULL)
				{
					/* FIXME: GError here? */
					g_warning (_("rsvg_start_svg: dimensions too large"));
					return;
				}
			memset (pixels, 0, rowstride * new_height);
			ctx->pixbuf = gdk_pixbuf_new_from_data (pixels,
													GDK_COLORSPACE_RGB,
													TRUE, 8,
													new_width, new_height,
													rowstride,
													rsvg_pixmap_destroy,
													NULL);
		}
}

static void
rsvg_start_g (RsvgHandle *ctx, RsvgPropertyBag *atts)
{
	RsvgState *state = rsvg_state_current (ctx);
	const char * klazz = NULL, * id = NULL, *value;
	
	if (rsvg_property_bag_size (atts))
		{
			if ((value = rsvg_property_bag_lookup (atts, "class")))
				klazz = value;
			if ((value = rsvg_property_bag_lookup (atts, "id")))
				id = value;

			rsvg_parse_style_attrs (ctx, state, "g", klazz, id, atts);
		}	
  
	rsvg_push_def_group (ctx, id);
	if (!ctx->in_defs)	
		rsvg_push_discrete_layer (ctx);
}

static void
rsvg_end_g (RsvgHandle *ctx)
{
	rsvg_pop_def_group (ctx);
	if (!ctx->in_defs)
		rsvg_pop_discrete_layer (ctx);
}

typedef struct _RsvgSaxHandlerDefs {
	RsvgSaxHandler super;
	RsvgHandle *ctx;
} RsvgSaxHandlerDefs;

typedef struct _RsvgSaxHandlerStyle {
	RsvgSaxHandler super;
	RsvgSaxHandlerDefs *parent;
	RsvgHandle *ctx;
	GString *style;
} RsvgSaxHandlerStyle;

typedef struct _RsvgSaxHandlerGstops {
	RsvgSaxHandler super;
	RsvgSaxHandlerDefs *parent;
	RsvgHandle *ctx;
	RsvgGradientStops *stops;
	const char * parent_tag;
} RsvgSaxHandlerGstops;

/* hide this fact from the general public */
typedef RsvgSaxHandlerDefs RsvgSaxHandlerTitle;
typedef RsvgSaxHandlerDefs RsvgSaxHandlerDesc;

static void
rsvg_gradient_stop_handler_free (RsvgSaxHandler *self)
{
	g_free (self);
}

static void
rsvg_gradient_stop_handler_start (RsvgSaxHandler *self, const xmlChar *name,
								  RsvgPropertyBag *atts)
{
	RsvgSaxHandlerGstops *z = (RsvgSaxHandlerGstops *)self;
	RsvgGradientStops *stops = z->stops;
	double offset = 0;
	gboolean got_offset = FALSE;
	RsvgState state;
	int n_stop;
	gboolean is_current_color = FALSE;
	const char *value;
	
	if (strcmp ((char *)name, "stop"))
		return;
	
	rsvg_state_init (&state);
	
	if (rsvg_property_bag_size (atts))
		{
			if ((value = rsvg_property_bag_lookup (atts, "offset")))
				{
					/* either a number [0,1] or a percentage */
					offset = rsvg_css_parse_normalized_length (value, z->ctx->dpi, 1., 0.);
					
					if (offset < 0.)
						offset = 0.;
					else if (offset > 1.)
						offset = 1.;
					
					got_offset = TRUE;
				}
			if ((value = rsvg_property_bag_lookup (atts, "style")))
				rsvg_parse_style (z->ctx, &state, value);
			
			if ((value = rsvg_property_bag_lookup (atts, "stop-color")))
				if (!strcmp(value, "currentColor"))
					is_current_color = TRUE;
			rsvg_parse_style_pairs (z->ctx, &state, atts);
		}
	
	rsvg_state_finalize (&state);
	
	if (!got_offset)
		{
			g_warning (_("gradient stop must specify offset\n"));
			return;
		}
	
	n_stop = stops->n_stop++;
	if (n_stop == 0)
		stops->stop = g_new (RsvgGradientStop, 1);
	else if (!(n_stop & (n_stop - 1)))
		/* double the allocation if size is a power of two */
		stops->stop = g_renew (RsvgGradientStop, stops->stop, n_stop << 1);
	stops->stop[n_stop].offset = offset;
	stops->stop[n_stop].is_current_color = is_current_color;
	stops->stop[n_stop].rgba = (state.stop_color << 8) | state.stop_opacity;
}

static void
rsvg_gradient_stop_handler_end (RsvgSaxHandler *self, const xmlChar *name)
{
	RsvgSaxHandlerGstops *z = (RsvgSaxHandlerGstops *)self;
	RsvgHandle *ctx = z->ctx;
	RsvgSaxHandler *prev = &z->parent->super;
	
	if (!strcmp((char *)name, z->parent_tag))
		{
			if (ctx->handler != NULL)
				{
					ctx->handler->free (ctx->handler);
					ctx->handler = prev;
				}
		}
}

static RsvgSaxHandler *
rsvg_gradient_stop_handler_new_clone (RsvgHandle *ctx, RsvgGradientStops *stops, 
									  const char * parent)
{
	RsvgSaxHandlerGstops *gstops = g_new0 (RsvgSaxHandlerGstops, 1);
	
	gstops->super.free = rsvg_gradient_stop_handler_free;
	gstops->super.start_element = rsvg_gradient_stop_handler_start;
	gstops->super.end_element = rsvg_gradient_stop_handler_end;
	gstops->ctx = ctx;
	gstops->stops = stops;
	gstops->parent_tag = parent;
	
	gstops->parent = (RsvgSaxHandlerDefs*)ctx->handler;
	return &gstops->super;
}

static RsvgSaxHandler *
rsvg_gradient_stop_handler_new (RsvgHandle *ctx, RsvgGradientStops **p_stops,
								const char * parent)
{
	RsvgSaxHandlerGstops *gstops = g_new0 (RsvgSaxHandlerGstops, 1);
	RsvgGradientStops *stops = g_new (RsvgGradientStops, 1);
	
	gstops->super.free = rsvg_gradient_stop_handler_free;
	gstops->super.start_element = rsvg_gradient_stop_handler_start;
	gstops->super.end_element = rsvg_gradient_stop_handler_end;
	gstops->ctx = ctx;
	gstops->stops = stops;
	gstops->parent_tag = parent;
	
	stops->n_stop = 0;
	stops->stop = NULL;
	
	gstops->parent = (RsvgSaxHandlerDefs*)ctx->handler;
	*p_stops = stops;
	return &gstops->super;
}

/* exported to the paint server via rsvg-private.h */
void
rsvg_linear_gradient_free (RsvgDefVal *self)
{
	RsvgLinearGradient *z = (RsvgLinearGradient *)self;
	
	g_free (z->stops->stop);
	g_free (z->stops);
	g_free (self);
}

static void
rsvg_start_linear_gradient (RsvgHandle *ctx, RsvgPropertyBag *atts)
{
	RsvgState *state = rsvg_state_current (ctx);
	RsvgLinearGradient *grad = NULL;
	const char *id = NULL, *value;
	double x1 = 0., y1 = 0., x2 = 0., y2 = 0.;
	ArtGradientSpread spread = ART_GRADIENT_PAD;
	const char * xlink_href = NULL;
	gboolean obj_bbox = TRUE;
	gboolean got_x1, got_x2, got_y1, got_y2, got_spread, got_transform, got_bbox, cloned, shallow_cloned;
	double affine[6];
	guint32 color = 0;
	gboolean got_color = FALSE;
	int i;

	got_x1 = got_x2 = got_y1 = got_y2 = got_spread = got_transform = got_bbox = cloned = shallow_cloned = FALSE;
		
	if (rsvg_property_bag_size (atts))
		{
			if ((value = rsvg_property_bag_lookup (atts, "id")))
				id = value;
			if ((value = rsvg_property_bag_lookup (atts, "x1"))) {
				x1 = rsvg_css_parse_normalized_length (value, ctx->dpi, 1, state->font_size);
				got_x1 = TRUE;
			}
			if ((value = rsvg_property_bag_lookup (atts, "y1"))) {
				y1 = rsvg_css_parse_normalized_length (value, ctx->dpi, 1, state->font_size);
				got_y1 = TRUE;
			}
			if ((value = rsvg_property_bag_lookup (atts, "x2"))) {
				x2 = rsvg_css_parse_normalized_length (value, ctx->dpi, 1, state->font_size);
				got_x2 = TRUE;
			}
			if ((value = rsvg_property_bag_lookup (atts, "y2"))) {
				y2 = rsvg_css_parse_normalized_length (value, ctx->dpi, 1, state->font_size);
				got_y2 = TRUE;
			}
			if ((value = rsvg_property_bag_lookup (atts, "spreadMethod")))
				{
					if (!strcmp (value, "pad")) {
						spread = ART_GRADIENT_PAD;
						got_spread = TRUE;
					}
					else if (!strcmp (value, "reflect")) {
						spread = ART_GRADIENT_REFLECT;
						got_spread = TRUE;
					}
					else if (!strcmp (value, "repeat")) {
						spread = ART_GRADIENT_REPEAT;
						got_spread = TRUE;
					}
				}
			if ((value = rsvg_property_bag_lookup (atts, "xlink:href")))
				xlink_href = value;
			if ((value = rsvg_property_bag_lookup (atts, "gradientTransform")))
				got_transform = rsvg_parse_transform (affine, value);
			if ((value = rsvg_property_bag_lookup (atts, "color")))
				{
					got_color = TRUE;
					color = rsvg_css_parse_color (value, 0);
				}
			if ((value = rsvg_property_bag_lookup (atts, "gradientUnits"))) {
				if (!strcmp (value, "userSpaceOnUse"))
					obj_bbox = FALSE;
				got_bbox = TRUE;
			}
		}
	
	/* set up 100% as the default if not gotten */
	if (!got_x2) {
		if (obj_bbox)
			x2 = 1.0;
		else
			x2 = rsvg_css_parse_normalized_length ("100%", ctx->dpi, (gdouble)ctx->width, state->font_size);
	}

	if (xlink_href != NULL)
		{
			RsvgLinearGradient * parent = (RsvgLinearGradient*)rsvg_defs_lookup (ctx->defs, xlink_href+1);
			if (parent != NULL)
				{
					cloned = TRUE;
					grad = rsvg_clone_linear_gradient (parent, &shallow_cloned); 
					ctx->handler = rsvg_gradient_stop_handler_new_clone (ctx, grad->stops, "linearGradient");
				}
		}
	
	if (!cloned)
		{
			grad = g_new (RsvgLinearGradient, 1);
			grad->super.type = RSVG_DEF_LINGRAD;
			grad->super.free = rsvg_linear_gradient_free;
			ctx->handler = rsvg_gradient_stop_handler_new (ctx, &grad->stops, "linearGradient");
		}
	
	rsvg_defs_set (ctx->defs, id, &grad->super);
	
	if (got_transform)
		for (i = 0; i < 6; i++)
			grad->affine[i] = affine[i];
	else
		art_affine_identity(grad->affine);

	if (got_color)
		{
			grad->current_color = color;
			grad->has_current_color = TRUE;
		}

	/* gradient inherits parent/cloned information unless it's explicity gotten */
	grad->obj_bbox = (cloned && !got_bbox) ? grad->obj_bbox : obj_bbox;
	if (!shallow_cloned)
		{
			grad->x1 = (cloned && !got_x1) ? grad->x1 : x1;
			grad->y1 = (cloned && !got_y1) ? grad->y1 : y1;
			grad->x2 = (cloned && !got_x2) ? grad->x2 : x2;
			grad->y2 = (cloned && !got_y2) ? grad->y1 : y2;
		}
	else
		{
			grad->x1 = x1;
			grad->y1 = y1;
			grad->x2 = x2;
			grad->y2 = y2;
		}
	grad->spread = (cloned && !got_spread) ? grad->spread : spread;
}

/* exported to the paint server via rsvg-private.h */
void
rsvg_radial_gradient_free (RsvgDefVal *self)
{
	RsvgRadialGradient *z = (RsvgRadialGradient *)self;
	
	g_free (z->stops->stop);
	g_free (z->stops);
	g_free (self);
}
<commit_msg>further gradient fixes<commit_after>/* vim: set sw=4: -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
   rsvg.c: SAX-based renderer for SVG files into a GdkPixbuf.

   Copyright (C) 2000 Eazel, Inc.
   Copyright (C) 2002 <NAME> <<EMAIL>>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this program; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Author: <NAME> <<EMAIL>>
*/

#include "config.h"

#include "rsvg.h"
#include "rsvg-private.h"
#include "rsvg-css.h"
#include "rsvg-styles.h"
#include "rsvg-shapes.h"
#include "rsvg-text.h"
#include "rsvg-filter.h"
#include "rsvg-mask.h"

#include <math.h>
#include <string.h>
#include <stdarg.h>

#include <libart_lgpl/art_affine.h>

#include "rsvg-bpath-util.h"
#include "rsvg-path.h"
#include "rsvg-paint-server.h"

/*
 * This is configurable at runtime
 */
#define RSVG_DEFAULT_DPI 90.0
static double internal_dpi = RSVG_DEFAULT_DPI;

static void
rsvg_ctx_free_helper (gpointer key, gpointer value, gpointer user_data)
{
	xmlEntityPtr entval = (xmlEntityPtr)value;
	
	/* key == entval->name, so it's implicitly freed below */
	
	g_free ((char *) entval->name);
	g_free ((char *) entval->ExternalID);
	g_free ((char *) entval->SystemID);
	xmlFree (entval->content);
	xmlFree (entval->orig);
	g_free (entval);
}

static void
rsvg_pixmap_destroy (guchar *pixels, gpointer data)
{
	g_free (pixels);
}

static void
rsvg_start_svg (RsvgHandle *ctx, RsvgPropertyBag *atts)
{
	int width = -1, height = -1, x = -1, y = -1;
	int rowstride;
	art_u8 *pixels;
	RsvgState *state;
	gint new_width, new_height;
	double x_zoom = 1.;
	double y_zoom = 1.;
	double affine[6];
	const char * value;

	double vbox_x = 0, vbox_y = 0, vbox_w = 0, vbox_h = 0;
	gboolean has_vbox = FALSE;

	if (rsvg_property_bag_size (atts))
		{
			/* x & y should be ignored since we should always be the outermost SVG,
			   at least for now, but i'll include them here anyway */
			if ((value = rsvg_property_bag_lookup (atts, "viewBox")))
				{
					has_vbox = rsvg_css_parse_vbox (value, &vbox_x, &vbox_y,
													&vbox_w, &vbox_h);
				}
			if ((value = rsvg_property_bag_lookup (atts, "width")))
				width = rsvg_css_parse_normalized_length (value, ctx->dpi, vbox_w, 1);
			if ((value = rsvg_property_bag_lookup (atts, "height")))
				height = rsvg_css_parse_normalized_length (value, ctx->dpi, vbox_h, 1);
			if ((value = rsvg_property_bag_lookup (atts, "x")))
				x = rsvg_css_parse_normalized_length (value, ctx->dpi, vbox_w, 1);
			if ((value = rsvg_property_bag_lookup (atts, "y")))
				y = rsvg_css_parse_normalized_length (value, ctx->dpi, vbox_h, 1);
	
			if (has_vbox && vbox_w > 0. && vbox_h > 0.)
				{
					new_width  = (int)floor (vbox_w);
					new_height = (int)floor (vbox_h);
					
					/* apply the sizing function on the *original* width and height
					   to acquire our real destination size. we'll scale it against
					   the viewBox's coordinates later */
					if (ctx->size_func)
						(* ctx->size_func) (&width, &height, ctx->user_data);
				}
			else
				{
					new_width  = width;
					new_height = height;

					/* bogus hack */
					if (new_width <= 0 || new_height <= 0)
						{
							g_warning (_("rsvg_start_svg: width and height not specified in the SVG"));
							if (new_width <= 0) {width = new_width = 512;}
							if (new_height <= 0) {height = new_height = 512;}
						}

					/* apply the sizing function to acquire our new width and height.
					   we'll scale this against the old values later */
					if (ctx->size_func)
						(* ctx->size_func) (&new_width, &new_height, ctx->user_data);
				}

			/* set these here because % are relative to viewbox */
			ctx->width = new_width;
			ctx->height = new_height;

			if (!has_vbox)
				{
					  x_zoom = (width < 0 || new_width < 0) ? 1 : (double) new_width / width;
					  y_zoom = (height < 0 || new_height < 0) ? 1 : (double) new_height / height;
				}
			else
				{
					x_zoom = (width < 0 || new_width < 0) ? 1 : (double) width / new_width;
					y_zoom = (height < 0 || new_height < 0) ? 1 : (double) height / new_height;

					/* reset these so that we get a properly sized SVG and not a huge one */
					new_width  = (width == -1 ? new_width : width);
					new_height = (height == -1 ? new_height : height);
				}

			/* Scale size of target pixbuf */
			state = rsvg_state_current (ctx);

			art_affine_identity (state->affine);

			if (has_vbox && (vbox_x != 0. || vbox_y != 0.))
				{
					art_affine_translate (affine, - vbox_x, - vbox_y);
					art_affine_multiply (state->affine, state->affine, affine);
				}

			art_affine_scale (affine, x_zoom, y_zoom);
			art_affine_multiply (state->affine, state->affine, affine);

			if (new_width <= 0 || new_height <= 0)
				{
					g_warning (_("rsvg_start_svg: width and height not specified in the SVG, nor supplied by the size callback"));
					if (new_width <= 0) new_width = 512;
					if (new_height <= 0) new_height = 512;
				}

			if (new_width >= INT_MAX / 4)
				{
					/* FIXME: GError here? */
					g_warning (_("rsvg_start_svg: width too large"));
					return;
				}
			rowstride = (new_width * 4 + 3) & ~3;
			if (rowstride > INT_MAX / new_height)
				{
					/* FIXME: GError here? */
					g_warning (_("rsvg_start_svg: width too large"));
					return;
				}

			/* FIXME: Add GError here if size is too big. */

			pixels = g_try_malloc (rowstride * new_height);
			if (pixels == NULL)
				{
					/* FIXME: GError here? */
					g_warning (_("rsvg_start_svg: dimensions too large"));
					return;
				}
			memset (pixels, 0, rowstride * new_height);
			ctx->pixbuf = gdk_pixbuf_new_from_data (pixels,
													GDK_COLORSPACE_RGB,
													TRUE, 8,
													new_width, new_height,
													rowstride,
													rsvg_pixmap_destroy,
													NULL);
		}
}

static void
rsvg_start_g (RsvgHandle *ctx, RsvgPropertyBag *atts)
{
	RsvgState *state = rsvg_state_current (ctx);
	const char * klazz = NULL, * id = NULL, *value;
	
	if (rsvg_property_bag_size (atts))
		{
			if ((value = rsvg_property_bag_lookup (atts, "class")))
				klazz = value;
			if ((value = rsvg_property_bag_lookup (atts, "id")))
				id = value;

			rsvg_parse_style_attrs (ctx, state, "g", klazz, id, atts);
		}	
  
	rsvg_push_def_group (ctx, id);
	if (!ctx->in_defs)	
		rsvg_push_discrete_layer (ctx);
}

static void
rsvg_end_g (RsvgHandle *ctx)
{
	rsvg_pop_def_group (ctx);
	if (!ctx->in_defs)
		rsvg_pop_discrete_layer (ctx);
}

typedef struct _RsvgSaxHandlerDefs {
	RsvgSaxHandler super;
	RsvgHandle *ctx;
} RsvgSaxHandlerDefs;

typedef struct _RsvgSaxHandlerStyle {
	RsvgSaxHandler super;
	RsvgSaxHandlerDefs *parent;
	RsvgHandle *ctx;
	GString *style;
} RsvgSaxHandlerStyle;

typedef struct _RsvgSaxHandlerGstops {
	RsvgSaxHandler super;
	RsvgSaxHandlerDefs *parent;
	RsvgHandle *ctx;
	RsvgGradientStops *stops;
	const char * parent_tag;
} RsvgSaxHandlerGstops;

/* hide this fact from the general public */
typedef RsvgSaxHandlerDefs RsvgSaxHandlerTitle;
typedef RsvgSaxHandlerDefs RsvgSaxHandlerDesc;

static void
rsvg_gradient_stop_handler_free (RsvgSaxHandler *self)
{
	g_free (self);
}

static void
rsvg_gradient_stop_handler_start (RsvgSaxHandler *self, const xmlChar *name,
								  RsvgPropertyBag *atts)
{
	RsvgSaxHandlerGstops *z = (RsvgSaxHandlerGstops *)self;
	RsvgGradientStops *stops = z->stops;
	double offset = 0;
	gboolean got_offset = FALSE;
	RsvgState state;
	int n_stop;
	gboolean is_current_color = FALSE;
	const char *value;
	
	if (strcmp ((char *)name, "stop"))
		return;
	
	rsvg_state_init (&state);
	
	if (rsvg_property_bag_size (atts))
		{
			if ((value = rsvg_property_bag_lookup (atts, "offset")))
				{
					/* either a number [0,1] or a percentage */
					offset = rsvg_css_parse_normalized_length (value, z->ctx->dpi, 1., 0.);
					
					if (offset < 0.)
						offset = 0.;
					else if (offset > 1.)
						offset = 1.;
					
					got_offset = TRUE;
				}
			if ((value = rsvg_property_bag_lookup (atts, "style")))
				rsvg_parse_style (z->ctx, &state, value);
			
			if ((value = rsvg_property_bag_lookup (atts, "stop-color")))
				if (!strcmp(value, "currentColor"))
					is_current_color = TRUE;
			rsvg_parse_style_pairs (z->ctx, &state, atts);
		}
	
	rsvg_state_finalize (&state);
	
	if (!got_offset)
		{
			g_warning (_("gradient stop must specify offset\n"));
			return;
		}
	
	n_stop = stops->n_stop++;
	if (n_stop == 0)
		stops->stop = g_new (RsvgGradientStop, 1);
	else if (!(n_stop & (n_stop - 1)))
		/* double the allocation if size is a power of two */
		stops->stop = g_renew (RsvgGradientStop, stops->stop, n_stop << 1);
	stops->stop[n_stop].offset = offset;
	stops->stop[n_stop].is_current_color = is_current_color;
	stops->stop[n_stop].rgba = (state.stop_color << 8) | state.stop_opacity;
}

static void
rsvg_gradient_stop_handler_end (RsvgSaxHandler *self, const xmlChar *name)
{
	RsvgSaxHandlerGstops *z = (RsvgSaxHandlerGstops *)self;
	RsvgHandle *ctx = z->ctx;
	RsvgSaxHandler *prev = &z->parent->super;
	
	if (!strcmp((char *)name, z->parent_tag))
		{
			if (ctx->handler != NULL)
				{
					ctx->handler->free (ctx->handler);
					ctx->handler = prev;
				}
		}
}

static RsvgSaxHandler *
rsvg_gradient_stop_handler_new_clone (RsvgHandle *ctx, RsvgGradientStops *stops, 
									  const char * parent)
{
	RsvgSaxHandlerGstops *gstops = g_new0 (RsvgSaxHandlerGstops, 1);
	
	gstops->super.free = rsvg_gradient_stop_handler_free;
	gstops->super.start_element = rsvg_gradient_stop_handler_start;
	gstops->super.end_element = rsvg_gradient_stop_handler_end;
	gstops->ctx = ctx;
	gstops->stops = stops;
	gstops->parent_tag = parent;
	
	gstops->parent = (RsvgSaxHandlerDefs*)ctx->handler;
	return &gstops->super;
}

static RsvgSaxHandler *
rsvg_gradient_stop_handler_new (RsvgHandle *ctx, RsvgGradientStops **p_stops,
								const char * parent)
{
	RsvgSaxHandlerGstops *gstops = g_new0 (RsvgSaxHandlerGstops, 1);
	RsvgGradientStops *stops = g_new (RsvgGradientStops, 1);
	
	gstops->super.free = rsvg_gradient_stop_handler_free;
	gstops->super.start_element = rsvg_gradient_stop_handler_start;
	gstops->super.end_element = rsvg_gradient_stop_handler_end;
	gstops->ctx = ctx;
	gstops->stops = stops;
	gstops->parent_tag = parent;
	
	stops->n_stop = 0;
	stops->stop = NULL;
	
	gstops->parent = (RsvgSaxHandlerDefs*)ctx->handler;
	*p_stops = stops;
	return &gstops->super;
}

/* exported to the paint server via rsvg-private.h */
void
rsvg_linear_gradient_free (RsvgDefVal *self)
{
	RsvgLinearGradient *z = (RsvgLinearGradient *)self;
	
	g_free (z->stops->stop);
	g_free (z->stops);
	g_free (self);
}

static void
rsvg_start_linear_gradient (RsvgHandle *ctx, RsvgPropertyBag *atts)
{
	RsvgState *state = rsvg_state_current (ctx);
	RsvgLinearGradient *grad = NULL;
	const char *id = NULL, *value;
	double x1 = 0., y1 = 0., x2 = 0., y2 = 0.;
	ArtGradientSpread spread = ART_GRADIENT_PAD;
	const char * xlink_href = NULL;
	gboolean obj_bbox = TRUE;
	gboolean got_x1, got_x2, got_y1, got_y2, got_spread, got_transform, got_bbox, cloned, shallow_cloned;
	double affine[6];
	guint32 color = 0;
	gboolean got_color = FALSE;
	int i;

	got_x1 = got_x2 = got_y1 = got_y2 = got_spread = got_transform = got_bbox = cloned = shallow_cloned = FALSE;
		
	if (rsvg_property_bag_size (atts))
		{
			if ((value = rsvg_property_bag_lookup (atts, "id")))
				id = value;
			if ((value = rsvg_property_bag_lookup (atts, "x1"))) {
				x1 = rsvg_css_parse_normalized_length (value, ctx->dpi, 1, state->font_size);
				got_x1 = TRUE;
			}
			if ((value = rsvg_property_bag_lookup (atts, "y1"))) {
				y1 = rsvg_css_parse_normalized_length (value, ctx->dpi, 1, state->font_size);
				got_y1 = TRUE;
			}
			if ((value = rsvg_property_bag_lookup (atts, "x2"))) {
				x2 = rsvg_css_parse_normalized_length (value, ctx->dpi, 1, state->font_size);
				got_x2 = TRUE;
			}
			if ((value = rsvg_property_bag_lookup (atts, "y2"))) {
				y2 = rsvg_css_parse_normalized_length (value, ctx->dpi, 1, state->font_size);
				got_y2 = TRUE;
			}
			if ((value = rsvg_property_bag_lookup (atts, "spreadMethod")))
				{
					if (!strcmp (value, "pad")) {
						spread = ART_GRADIENT_PAD;
						got_spread = TRUE;
					}
					else if (!strcmp (value, "reflect")) {
						spread = ART_GRADIENT_REFLECT;
						got_spread = TRUE;
					}
					else if (!strcmp (value, "repeat")) {
						spread = ART_GRADIENT_REPEAT;
						got_spread = TRUE;
					}
				}
			if ((value = rsvg_property_bag_lookup (atts, "xlink:href")))
				xlink_href = value;
			if ((value = rsvg_property_bag_lookup (atts, "gradientTransform")))
				got_transform = rsvg_parse_transform (affine, value);
			if ((value = rsvg_property_bag_lookup (atts, "color")))
				{
					got_color = TRUE;
					color = rsvg_css_parse_color (value, 0);
				}
			if ((value = rsvg_property_bag_lookup (atts, "gradientUnits"))) {
				if (!strcmp (value, "userSpaceOnUse"))
					obj_bbox = FALSE;
				got_bbox = TRUE;
			}
		}
	
	/* set up 100% as the default if not gotten */
	if (!got_x2) {
		if (obj_bbox)
			x2 = 1.0;
		else
			x2 = rsvg_css_parse_normalized_length ("100%", ctx->dpi, (gdouble)ctx->width, state->font_size);
	}

	if (xlink_href != NULL)
		{
			RsvgLinearGradient * parent = (RsvgLinearGradient*)rsvg_defs_lookup (ctx->defs, xlink_href+1);
			if (parent != NULL)
				{
					cloned = TRUE;
					grad = rsvg_clone_linear_gradient (parent, &shallow_cloned); 
					ctx->handler = rsvg_gradient_stop_handler_new_clone (ctx, grad->stops, "linearGradient");
				}
		}
	
	if (!cloned)
		{
			grad = g_new (RsvgLinearGradient, 1);
			grad->super.type = RSVG_DEF_LINGRAD;
			grad->super.free = rsvg_linear_gradient_free;
			ctx->handler = rsvg_gradient_stop_handler_new (ctx, &grad->stops, "linearGradient");
		}
	
	rsvg_defs_set (ctx->defs, id, &grad->super);
	
	if (got_transform)
		for (i = 0; i < 6; i++)
			grad->affine[i] = affine[i];
	else
		art_affine_identity(grad->affine);

	if (got_color)
		{
			grad->current_color = color;
			grad->has_current_color = TRUE;
		}

	/* gradient inherits parent/cloned information unless it's explicity gotten */
	grad->obj_bbox = (cloned && !got_bbox) ? grad->obj_bbox : obj_bbox;
	if (!shallow_cloned)
		{
			grad->x1 = (cloned && !got_x1) ? grad->x1 : x1;
			grad->y1 = (cloned && !got_y1) ? grad->y1 : y1;
			grad->x2 = (cloned && !got_x2) ? grad->x2 : x2;
			grad->y2 = (cloned && !got_y2) ? grad->y2 : y2;
		}
	else
		{
			grad->x1 = x1;
			grad->y1 = y1;
			grad->x2 = x2;
			grad->y2 = y2;
		}
	grad->spread = (cloned && !got_spread) ? grad->spread : spread;
}

/* exported to the paint server via rsvg-private.h */
void
rsvg_radial_gradient_free (RsvgDefVal *self)
{
	RsvgRadialGradient *z = (RsvgRadialGradient *)self;
	
	g_free (z->stops->stop);
	g_free (z->stops);
	g_free (self);
}

{'fraction_non_alphanumeric': 0.12961936136207966, 'fraction_numerical': 0.012068234449916827, 'mean_word_length': 3.505510653930933, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 0, '>': 259, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 12}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2023014', 'n_tokens_mistral': 13601, 'n_tokens_neox': 11482, 'n_words': 2834}