nautilus/librsvg/rsvg.c
Andy Hertzfeld 1d10bfc8a0 fixed bug 6301, bad svg crashes Nautilus, by adding a check in
* librsvg/rsvg.c: (rsvg_render_svp):
	fixed bug 6301, bad svg crashes Nautilus, by adding a check
	in rsvg_render_svp to make sure a pixbuf has been allocated; if
	not, don't try to render.
2001-02-05 20:16:43 +00:00

1463 lines
36 KiB
C

/*
rsvg.c: SAX-based renderer for SVG files into a GdkPixbuf.
Copyright (C) 2000 Eazel, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU 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
General Public License for more details.
You should have received a copy of the GNU 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: Raph Levien <raph@artofcode.com>
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <glib.h>
#include <libart_lgpl/art_misc.h>
#include <libart_lgpl/art_filterlevel.h>
#include <libart_lgpl/art_affine.h>
#include <libart_lgpl/art_svp.h>
#include <libart_lgpl/art_bpath.h>
#include <libart_lgpl/art_vpath.h>
#include <libart_lgpl/art_vpath_bpath.h>
#include <libart_lgpl/art_rgb_svp.h>
#include <libart_lgpl/art_svp_vpath_stroke.h>
#include <libart_lgpl/art_svp_vpath.h>
#include <libart_lgpl/art_svp_wind.h>
#include "art_rgba.h"
#include "art_render.h"
#include "art_render_gradient.h"
#include "art_render_svp.h"
#include "art_render_mask.h"
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gnome-xml/SAX.h>
#include <gnome-xml/xmlmemory.h>
#include "rsvg-bpath-util.h"
#include "rsvg-defs.h"
#include "rsvg-path.h"
#include "rsvg-css.h"
#include "rsvg-paint-server.h"
#include "rsvg-ft.h"
#include "rsvg.h"
#define noVERBOSE
typedef struct _RsvgCtx RsvgCtx;
typedef struct _RsvgState RsvgState;
typedef struct _RsvgSaxHandler RsvgSaxHandler;
struct _RsvgCtx {
GdkPixbuf *pixbuf;
double zoom;
/* stack; there is a state for each element */
RsvgState *state;
int n_state;
int n_state_max;
RsvgDefs *defs;
RsvgSaxHandler *handler; /* should this be a handler stack? */
int handler_nest;
GHashTable *entities; /* g_malloc'd string -> xmlEntityPtr */
RsvgFTCtx *ft_ctx;
};
struct _RsvgState {
double affine[6];
gint opacity; /* 0..255 */
RsvgPaintServer *fill;
gint fill_opacity; /* 0..255 */
RsvgPaintServer *stroke;
gint stroke_opacity; /* 0..255 */
double stroke_width;
ArtPathStrokeCapType cap;
ArtPathStrokeJoinType join;
double font_size;
guint32 stop_color; /* rgb */
gint stop_opacity; /* 0..255 */
gboolean in_defs;
GdkPixbuf *save_pixbuf;
};
struct _RsvgSaxHandler {
void (*free) (RsvgSaxHandler *self);
void (*start_element) (RsvgSaxHandler *self, const xmlChar *name, const xmlChar **atts);
void (*end_element) (RsvgSaxHandler *self, const xmlChar *name);
void (*characters) (RsvgSaxHandler *self, const xmlChar *ch, int len);
};
static RsvgCtx *
rsvg_ctx_new (void)
{
RsvgCtx *result;
result = g_new (RsvgCtx, 1);
result->pixbuf = NULL;
result->zoom = 1.0;
result->n_state = 0;
result->n_state_max = 16;
result->state = g_new (RsvgState, result->n_state_max);
result->defs = rsvg_defs_new ();
result->handler = NULL;
result->handler_nest = 0;
result->entities = g_hash_table_new (g_str_hash, g_str_equal);
result->ft_ctx = NULL;
return result;
}
static void
rsvg_state_init (RsvgState *state)
{
art_affine_identity (state->affine);
state->opacity = 0xff;
state->fill = rsvg_paint_server_parse (NULL, "#000");
state->fill_opacity = 0xff;
state->stroke = NULL;
state->stroke_opacity = 0xff;
state->stroke_width = 1;
state->cap = ART_PATH_STROKE_CAP_BUTT;
state->join = ART_PATH_STROKE_JOIN_MITER;
state->stop_color = 0;
state->stop_opacity = 0xff;
state->in_defs = FALSE;
state->save_pixbuf = NULL;
}
static void
rsvg_state_clone (RsvgState *dst, const RsvgState *src)
{
*dst = *src;
rsvg_paint_server_ref (dst->fill);
rsvg_paint_server_ref (dst->stroke);
dst->save_pixbuf = NULL;
}
static void
rsvg_state_finalize (RsvgState *state)
{
rsvg_paint_server_unref (state->fill);
rsvg_paint_server_unref (state->stroke);
}
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 ((xmlChar *)entval->name);
g_free ((xmlChar *)entval->ExternalID);
g_free ((xmlChar *)entval->SystemID);
xmlFree (entval->content);
xmlFree (entval->orig);
g_free (entval);
}
/* does not destroy the pixbuf */
static void
rsvg_ctx_free (RsvgCtx *ctx)
{
int i;
if (ctx->ft_ctx != NULL)
rsvg_ft_ctx_done (ctx->ft_ctx);
rsvg_defs_free (ctx->defs);
for (i = 0; i < ctx->n_state; i++)
rsvg_state_finalize (&ctx->state[i]);
g_free (ctx->state);
g_hash_table_foreach (ctx->entities, rsvg_ctx_free_helper, NULL);
g_hash_table_destroy (ctx->entities);
g_free (ctx);
}
static void
rsvg_pixmap_destroy (guchar *pixels, gpointer data)
{
g_free (pixels);
}
static void
rsvg_start_svg (RsvgCtx *ctx, const xmlChar **atts)
{
int i;
int width = -1, height = -1;
int rowstride;
art_u8 *pixels;
gint fixed;
RsvgState *state;
gboolean has_alpha = 1;
if (atts != NULL)
{
for (i = 0; atts[i] != NULL; i += 2)
{
if (!strcmp ((char *)atts[i], "width"))
width = rsvg_css_parse_length ((char *)atts[i + 1], &fixed);
else if (!strcmp ((char *)atts[i], "height"))
height = rsvg_css_parse_length ((char *)atts[i + 1], &fixed);
}
#ifdef VERBOSE
fprintf (stdout, "rsvg_start_svg: width = %d, height = %d\n",
width, height);
#endif
if (width < 0 || height < 0)
{
g_warning ("rsvg_start_svg: width and height attributes are not present in SVG\n");
if (width < 0) width = 500;
if (height < 0) height = 500;
}
/* Scale size of target pixbuf */
width = ceil (width * ctx->zoom);
height = ceil (height * ctx->zoom);
state = &ctx->state[ctx->n_state - 1];
art_affine_scale (state->affine, ctx->zoom, ctx->zoom);
rowstride = (width * (has_alpha ? 4 : 3) + 3) & -4;
pixels = g_new (art_u8, rowstride * height);
memset (pixels, has_alpha ? 0 : 255, rowstride * height);
ctx->pixbuf = gdk_pixbuf_new_from_data (pixels,
GDK_COLORSPACE_RGB,
has_alpha, 8,
width, height,
rowstride,
rsvg_pixmap_destroy,
NULL);
}
}
/* Parse a CSS2 style argument, setting the SVG context attributes. */
static void
rsvg_parse_style_arg (RsvgCtx *ctx, RsvgState *state, const char *str)
{
int arg_off;
arg_off = rsvg_css_param_arg_offset (str);
if (rsvg_css_param_match (str, "opacity"))
{
state->opacity = rsvg_css_parse_opacity (str + arg_off);
}
else if (rsvg_css_param_match (str, "fill"))
{
rsvg_paint_server_unref (state->fill);
state->fill = rsvg_paint_server_parse (ctx->defs, str + arg_off);
}
else if (rsvg_css_param_match (str, "fill-opacity"))
{
state->fill_opacity = rsvg_css_parse_opacity (str + arg_off);
}
else if (rsvg_css_param_match (str, "stroke"))
{
rsvg_paint_server_unref (state->stroke);
state->stroke = rsvg_paint_server_parse (ctx->defs, str + arg_off);
}
else if (rsvg_css_param_match (str, "stroke-width"))
{
int fixed;
state->stroke_width = rsvg_css_parse_length (str + arg_off, &fixed);
}
else if (rsvg_css_param_match (str, "stroke-linecap"))
{
if (!strcmp (str + arg_off, "butt"))
state->cap = ART_PATH_STROKE_CAP_BUTT;
else if (!strcmp (str + arg_off, "round"))
state->cap = ART_PATH_STROKE_CAP_ROUND;
else if (!strcmp (str + arg_off, "square"))
state->cap = ART_PATH_STROKE_CAP_SQUARE;
else
g_warning ("unknown line cap style %s", str + arg_off);
}
else if (rsvg_css_param_match (str, "stroke-opacity"))
{
state->stroke_opacity = rsvg_css_parse_opacity (str + arg_off);
}
else if (rsvg_css_param_match (str, "stroke-linejoin"))
{
if (!strcmp (str + arg_off, "miter"))
state->join = ART_PATH_STROKE_JOIN_MITER;
else if (!strcmp (str + arg_off, "round"))
state->join = ART_PATH_STROKE_JOIN_ROUND;
else if (!strcmp (str + arg_off, "bevel"))
state->join = ART_PATH_STROKE_JOIN_BEVEL;
else
g_warning ("unknown line join style %s", str + arg_off);
}
else if (rsvg_css_param_match (str, "font-size"))
{
state->font_size = rsvg_css_parse_fontsize (str + arg_off);
}
else if (rsvg_css_param_match (str, "font-family"))
{
/* state->font_family = g_strdup (str + arg_off); */
}
else if (rsvg_css_param_match (str, "stop-color"))
{
state->stop_color = rsvg_css_parse_color (str + arg_off);
}
else if (rsvg_css_param_match (str, "stop-opacity"))
{
state->stop_opacity = rsvg_css_parse_opacity (str + arg_off);
}
}
/* Split a CSS2 style into individual style arguments, setting attributes
in the SVG context.
It's known that this is _way_ out of spec. A more complete CSS2
implementation will happen later.
*/
static void
rsvg_parse_style (RsvgCtx *ctx, RsvgState *state, const char *str)
{
int start, end;
char *arg;
start = 0;
while (str[start] != '\0')
{
for (end = start; str[end] != '\0' && str[end] != ';'; end++);
arg = g_new (char, 1 + end - start);
memcpy (arg, str + start, end - start);
arg[end - start] = '\0';
rsvg_parse_style_arg (ctx, state, arg);
g_free (arg);
start = end;
if (str[start] == ';') start++;
while (str[start] == ' ') start++;
}
}
/* Parse an SVG transform string into an affine matrix. Reference: SVG
working draft dated 1999-07-06, section 8.5. Return TRUE on
success. */
static gboolean
rsvg_parse_transform (double dst[6], const char *src)
{
int idx;
char keyword[32];
double args[6];
int n_args;
guint key_len;
double tmp_affine[6];
art_affine_identity (dst);
idx = 0;
while (src[idx])
{
/* skip initial whitespace */
while (isspace (src[idx]))
idx++;
/* parse keyword */
for (key_len = 0; key_len < sizeof (keyword); key_len++)
{
char c;
c = src[idx];
if (isalpha (c) || c == '-')
keyword[key_len] = src[idx++];
else
break;
}
if (key_len >= sizeof (keyword))
return FALSE;
keyword[key_len] = '\0';
/* skip whitespace */
while (isspace (src[idx]))
idx++;
if (src[idx] != '(')
return FALSE;
idx++;
for (n_args = 0; ; n_args++)
{
char c;
char *end_ptr;
/* skip whitespace */
while (isspace (src[idx]))
idx++;
c = src[idx];
if (isdigit (c) || c == '+' || c == '-' || c == '.')
{
if (n_args == sizeof(args) / sizeof(args[0]))
return FALSE; /* too many args */
args[n_args] = strtod (src + idx, &end_ptr);
idx = end_ptr - src;
while (isspace (src[idx]))
idx++;
/* skip optional comma */
if (src[idx] == ',')
idx++;
}
else if (c == ')')
break;
else
return FALSE;
}
idx++;
/* ok, have parsed keyword and args, now modify the transform */
if (!strcmp (keyword, "matrix"))
{
if (n_args != 6)
return FALSE;
art_affine_multiply (dst, args, dst);
}
else if (!strcmp (keyword, "translate"))
{
if (n_args == 1)
args[1] = 0;
else if (n_args != 2)
return FALSE;
art_affine_translate (tmp_affine, args[0], args[1]);
art_affine_multiply (dst, tmp_affine, dst);
}
else if (!strcmp (keyword, "scale"))
{
if (n_args == 1)
args[1] = args[0];
else if (n_args != 2)
return FALSE;
art_affine_scale (tmp_affine, args[0], args[1]);
art_affine_multiply (dst, tmp_affine, dst);
}
else if (!strcmp (keyword, "rotate"))
{
if (n_args != 1)
return FALSE;
art_affine_rotate (tmp_affine, args[0]);
art_affine_multiply (dst, tmp_affine, dst);
}
else if (!strcmp (keyword, "skewX"))
{
if (n_args != 1)
return FALSE;
art_affine_shear (tmp_affine, args[0]);
art_affine_multiply (dst, tmp_affine, dst);
}
else if (!strcmp (keyword, "skewY"))
{
if (n_args != 1)
return FALSE;
art_affine_shear (tmp_affine, args[0]);
/* transpose the affine, given that we know [1] is zero */
tmp_affine[1] = tmp_affine[2];
tmp_affine[2] = 0;
art_affine_multiply (dst, tmp_affine, dst);
}
else
return FALSE; /* unknown keyword */
}
return TRUE;
}
/**
* rsvg_parse_transform_attr: Parse transform attribute and apply to state.
* @ctx: Rsvg context.
* @state: State in which to apply the transform.
* @str: String containing transform.
*
* Parses the transform attribute in @str and applies it to @state.
**/
static void
rsvg_parse_transform_attr (RsvgCtx *ctx, RsvgState *state, const char *str)
{
double affine[6];
if (rsvg_parse_transform (affine, str))
{
art_affine_multiply (state->affine, affine, state->affine);
}
else
{
/* parse error for transform attribute. todo: report */
}
}
/**
* rsvg_parse_style_attrs: Parse style attribute.
* @ctx: Rsvg context.
* @atts: Attributes in SAX style.
*
* Parses style and transform attributes and modifies state at top of
* stack.
**/
static void
rsvg_parse_style_attrs (RsvgCtx *ctx, const xmlChar **atts)
{
int i;
if (atts != NULL)
{
for (i = 0; atts[i] != NULL; i += 2)
{
if (!strcmp ((char *)atts[i], "style"))
rsvg_parse_style (ctx, &ctx->state[ctx->n_state - 1],
(char *)atts[i + 1]);
else if (!strcmp ((char *)atts[i], "transform"))
rsvg_parse_transform_attr (ctx, &ctx->state[ctx->n_state - 1],
(char *)atts[i + 1]);
}
}
}
/**
* rsvg_push_opacity_group: Begin a new transparency group.
* @ctx: Context in which to push.
*
* Pushes a new transparency group onto the stack. The top of the stack
* is stored in the context, while the "saved" value is in the state
* stack.
**/
static void
rsvg_push_opacity_group (RsvgCtx *ctx)
{
RsvgState *state;
GdkPixbuf *pixbuf;
art_u8 *pixels;
int width, height, rowstride;
state = &ctx->state[ctx->n_state - 1];
pixbuf = ctx->pixbuf;
if (!gdk_pixbuf_get_has_alpha (pixbuf))
{
g_warning ("push/pop transparency group on non-alpha buffer nyi");
return;
}
state->save_pixbuf = pixbuf;
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
pixels = g_new (art_u8, rowstride * height);
memset (pixels, 0, rowstride * height);
pixbuf = gdk_pixbuf_new_from_data (pixels,
GDK_COLORSPACE_RGB,
TRUE,
gdk_pixbuf_get_bits_per_sample (pixbuf),
width,
height,
rowstride,
rsvg_pixmap_destroy,
NULL);
ctx->pixbuf = pixbuf;
}
/**
* rsvg_pop_opacity_group: End a transparency group.
* @ctx: Context in which to push.
* @opacity: Opacity for blending (0..255).
*
* Pops a new transparency group from the stack, recompositing with the
* next on stack.
**/
static void
rsvg_pop_opacity_group (RsvgCtx *ctx, int opacity)
{
RsvgState *state = &ctx->state[ctx->n_state - 1];
GdkPixbuf *tos, *nos;
art_u8 *tos_pixels, *nos_pixels;
int width;
int height;
int rowstride;
int x, y;
int tmp;
tos = ctx->pixbuf;
nos = state->save_pixbuf;
if (!gdk_pixbuf_get_has_alpha (nos))
{
g_warning ("push/pop transparency group on non-alpha buffer nyi");
return;
}
width = gdk_pixbuf_get_width (tos);
height = gdk_pixbuf_get_height (tos);
rowstride = gdk_pixbuf_get_rowstride (tos);
tos_pixels = gdk_pixbuf_get_pixels (tos);
nos_pixels = gdk_pixbuf_get_pixels (nos);
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
art_u8 r, g, b, a;
a = tos_pixels[4 * x + 3];
if (a)
{
r = tos_pixels[4 * x];
g = tos_pixels[4 * x + 1];
b = tos_pixels[4 * x + 2];
tmp = a * opacity + 0x80;
a = (tmp + (tmp >> 8)) >> 8;
art_rgba_run_alpha (nos_pixels + 4 * x, r, g, b, a, 1);
}
}
tos_pixels += rowstride;
nos_pixels += rowstride;
}
gdk_pixbuf_unref (tos);
ctx->pixbuf = nos;
}
static void
rsvg_start_g (RsvgCtx *ctx, const xmlChar **atts)
{
RsvgState *state = &ctx->state[ctx->n_state - 1];
rsvg_parse_style_attrs (ctx, atts);
if (state->opacity != 0xff)
rsvg_push_opacity_group (ctx);
}
static void
rsvg_end_g (RsvgCtx *ctx)
{
RsvgState *state = &ctx->state[ctx->n_state - 1];
if (state->opacity != 0xff)
rsvg_pop_opacity_group (ctx, state->opacity);
}
/**
* rsvg_close_vpath: Close a vector path.
* @src: Source vector path.
*
* Closes any open subpaths in the vector path.
*
* Return value: Closed vector path, allocated with g_new.
**/
static ArtVpath *
rsvg_close_vpath (const ArtVpath *src)
{
ArtVpath *result;
int n_result, n_result_max;
int src_ix;
double beg_x, beg_y;
gboolean open;
n_result = 0;
n_result_max = 16;
result = g_new (ArtVpath, n_result_max);
beg_x = 0;
beg_y = 0;
open = FALSE;
for (src_ix = 0; src[src_ix].code != ART_END; src_ix++)
{
if (n_result == n_result_max)
result = g_renew (ArtVpath, result, n_result_max <<= 1);
result[n_result].code = src[src_ix].code == ART_MOVETO_OPEN ?
ART_MOVETO : src[src_ix].code;
result[n_result].x = src[src_ix].x;
result[n_result].y = src[src_ix].y;
n_result++;
if (src[src_ix].code == ART_MOVETO_OPEN)
{
beg_x = src[src_ix].x;
beg_y = src[src_ix].y;
open = TRUE;
}
else if (src[src_ix + 1].code != ART_LINETO)
{
if (open && (beg_x != src[src_ix].x || beg_y != src[src_ix].y))
{
if (n_result == n_result_max)
result = g_renew (ArtVpath, result, n_result_max <<= 1);
result[n_result].code = ART_LINETO;
result[n_result].x = beg_x;
result[n_result].y = beg_y;
n_result++;
}
open = FALSE;
}
}
if (n_result == n_result_max)
result = g_renew (ArtVpath, result, n_result_max <<= 1);
result[n_result].code = ART_END;
result[n_result].x = 0.0;
result[n_result].y = 0.0;
return result;
}
/**
* rsvg_render_svp: Render an SVP.
* @ctx: Context in which to render.
* @svp: SVP to render.
* @ps: Paint server for rendering.
* @opacity: Opacity as 0..0xff.
*
* Renders the SVP over the pixbuf in @ctx.
**/
static void
rsvg_render_svp (RsvgCtx *ctx, const ArtSVP *svp,
RsvgPaintServer *ps, int opacity)
{
GdkPixbuf *pixbuf;
ArtRender *render;
gboolean has_alpha;
pixbuf = ctx->pixbuf;
/* if a pixbuf hasn't been allocated, the svg is probably misformed. Exit
* to avoid crashing.
*/
if (pixbuf == NULL) {
return;
}
has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
render = art_render_new (0, 0,
gdk_pixbuf_get_width (pixbuf),
gdk_pixbuf_get_height (pixbuf),
gdk_pixbuf_get_pixels (pixbuf),
gdk_pixbuf_get_rowstride (pixbuf),
gdk_pixbuf_get_n_channels (pixbuf) -
(has_alpha ? 1 : 0),
gdk_pixbuf_get_bits_per_sample (pixbuf),
has_alpha ? ART_ALPHA_SEPARATE : ART_ALPHA_NONE,
NULL);
art_render_svp (render, svp);
art_render_mask_solid (render, (opacity << 8) + opacity + (opacity >> 7));
rsvg_render_paint_server (render, ps, NULL); /* todo: paint server ctx */
art_render_invoke (render);
}
static void
rsvg_render_bpath (RsvgCtx *ctx, const ArtBpath *bpath)
{
RsvgState *state;
ArtBpath *affine_bpath;
ArtVpath *vpath;
ArtSVP *svp;
GdkPixbuf *pixbuf;
gboolean need_tmpbuf;
int opacity;
int tmp;
state = &ctx->state[ctx->n_state - 1];
pixbuf = ctx->pixbuf;
affine_bpath = art_bpath_affine_transform (bpath,
state->affine);
vpath = art_bez_path_to_vec (affine_bpath, 0.25);
art_free (affine_bpath);
need_tmpbuf = (state->fill != NULL) && (state->stroke != NULL) &&
state->opacity != 0xff;
if (need_tmpbuf)
rsvg_push_opacity_group (ctx);
if (state->fill != NULL)
{
ArtVpath *closed_vpath;
ArtVpath *perturbed_vpath;
ArtSVP *tmp_svp;
ArtWindRule art_wind;
closed_vpath = rsvg_close_vpath (vpath);
perturbed_vpath = art_vpath_perturb (closed_vpath);
g_free (closed_vpath);
svp = art_svp_from_vpath (perturbed_vpath);
art_free (perturbed_vpath);
tmp_svp = art_svp_uncross (svp);
art_svp_free (svp);
art_wind = ART_WIND_RULE_NONZERO; /* todo - get from state */
svp = art_svp_rewind_uncrossed (tmp_svp, art_wind);
art_svp_free (tmp_svp);
opacity = state->fill_opacity;
if (!need_tmpbuf && state->opacity != 0xff)
{
tmp = opacity * state->opacity + 0x80;
opacity = (tmp + (tmp >> 8)) >> 8;
}
rsvg_render_svp (ctx, svp, state->fill, opacity);
art_svp_free (svp);
}
if (state->stroke != NULL)
{
/* todo: libart doesn't yet implement anamorphic scaling of strokes */
double stroke_width = state->stroke_width *
art_affine_expansion (state->affine);
if (stroke_width < 0.25)
stroke_width = 0.25;
svp = art_svp_vpath_stroke (vpath, state->join, state->cap,
stroke_width, 4, 0.25);
opacity = state->stroke_opacity;
if (!need_tmpbuf && state->opacity != 0xff)
{
tmp = opacity * state->opacity + 0x80;
opacity = (tmp + (tmp >> 8)) >> 8;
}
rsvg_render_svp (ctx, svp, state->stroke, opacity);
art_svp_free (svp);
}
if (need_tmpbuf)
rsvg_pop_opacity_group (ctx, state->opacity);
art_free (vpath);
}
static void
rsvg_start_path (RsvgCtx *ctx, const xmlChar **atts)
{
int i;
char *d = NULL;
rsvg_parse_style_attrs (ctx, atts);
if (atts != NULL)
{
for (i = 0; atts[i] != NULL; i += 2)
{
if (!strcmp ((char *)atts[i], "d"))
d = (char *)atts[i + 1];
}
}
if (d != NULL)
{
RsvgBpathDef *bpath_def;
bpath_def = rsvg_parse_path (d);
rsvg_bpath_def_art_finish (bpath_def);
rsvg_render_bpath (ctx, bpath_def->bpath);
rsvg_bpath_def_free (bpath_def);
}
}
/* begin text - this should likely get split into its own .c file */
typedef struct _RsvgSaxHandlerText RsvgSaxHandlerText;
struct _RsvgSaxHandlerText {
RsvgSaxHandler super;
RsvgCtx *ctx;
double xpos;
double ypos;
};
static void
rsvg_text_handler_free (RsvgSaxHandler *self)
{
g_free (self);
}
static void
rsvg_text_handler_characters (RsvgSaxHandler *self, const xmlChar *ch, int len)
{
RsvgSaxHandlerText *z = (RsvgSaxHandlerText *)self;
RsvgCtx *ctx = z->ctx;
char *string;
int beg, end;
RsvgFTFontHandle fh;
RsvgFTGlyph *glyph;
int glyph_xy[2];
RsvgState *state;
ArtRender *render;
GdkPixbuf *pixbuf;
gboolean has_alpha;
static int count = 0;
int opacity;
count++;
if (count == 2)
{
count = 10;
}
/* Copy ch into string, chopping off leading and trailing whitespace */
for (beg = 0; beg < len; beg++)
if (!isspace (ch[beg]))
break;
for (end = len; end > beg; end--)
if (!isspace (ch[end - 1]))
break;
string = g_malloc (end - beg + 1);
memcpy (string, ch + beg, end - beg);
string[end - beg] = 0;
#ifdef VERBOSE
fprintf (stderr, "text characters(%s, %d)\n", string, len);
#endif
if (ctx->ft_ctx == NULL)
ctx->ft_ctx = rsvg_ft_ctx_new ();
/* FIXME bugzilla.eazel.com 3904: We need to make rsvg use the
* Nautilus font mapping stuff in NautilusScalableFont. See bug
* for details.
*/
fh = rsvg_ft_intern (ctx->ft_ctx,
NAUTILUS_DATADIR "/fonts/urw/n019003l.pfb");
rsvg_ft_font_attach (ctx->ft_ctx, fh,
NAUTILUS_DATADIR "/fonts/urw/n019003l.afm");
state = &ctx->state[ctx->n_state - 1];
if (state->fill != NULL)
{
pixbuf = ctx->pixbuf;
has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
render = art_render_new (0, 0,
gdk_pixbuf_get_width (pixbuf),
gdk_pixbuf_get_height (pixbuf),
gdk_pixbuf_get_pixels (pixbuf),
gdk_pixbuf_get_rowstride (pixbuf),
gdk_pixbuf_get_n_channels (pixbuf) -
(has_alpha ? 1 : 0),
gdk_pixbuf_get_bits_per_sample (pixbuf),
has_alpha ? ART_ALPHA_SEPARATE : ART_ALPHA_NONE,
NULL);
glyph = rsvg_ft_render_string (ctx->ft_ctx, fh,
string,
strlen (string),
state->font_size, state->font_size,
state->affine, glyph_xy);
rsvg_render_paint_server (render, state->fill, NULL); /* todo: paint server ctx */
opacity = state->fill_opacity * state->opacity;
opacity = opacity + (opacity >> 7) + (opacity >> 14);
#ifdef VERBOSE
fprintf (stderr, "opacity = %d\n", opacity);
#endif
art_render_mask_solid (render, opacity);
art_render_mask (render,
glyph_xy[0], glyph_xy[1],
glyph_xy[0] + glyph->width, glyph_xy[1] + glyph->height,
glyph->buf, glyph->rowstride);
art_render_invoke (render);
rsvg_ft_glyph_unref (glyph);
}
g_free (string);
}
static void
rsvg_start_text (RsvgCtx *ctx, const xmlChar **atts)
{
RsvgSaxHandlerText *handler = g_new0 (RsvgSaxHandlerText, 1);
handler->super.free = rsvg_text_handler_free;
handler->super.characters = rsvg_text_handler_characters;
handler->ctx = ctx;
/* todo: parse "x" and "y" attributes */
handler->xpos = 0;
handler->ypos = 0;
rsvg_parse_style_attrs (ctx, atts);
ctx->handler = &handler->super;
#ifdef VERBOSE
fprintf (stderr, "begin text!\n");
#endif
}
/* end text */
static void
rsvg_start_defs (RsvgCtx *ctx, const xmlChar **atts)
{
RsvgState *state = &ctx->state[ctx->n_state - 1];
state->in_defs = TRUE;
}
typedef struct _RsvgSaxHandlerGstops RsvgSaxHandlerGstops;
struct _RsvgSaxHandlerGstops {
RsvgSaxHandler super;
RsvgCtx *ctx;
RsvgGradientStops *stops;
};
static void
rsvg_gradient_stop_handler_free (RsvgSaxHandler *self)
{
g_free (self);
}
static void
rsvg_gradient_stop_handler_start (RsvgSaxHandler *self, const xmlChar *name,
const xmlChar **atts)
{
RsvgSaxHandlerGstops *z = (RsvgSaxHandlerGstops *)self;
RsvgGradientStops *stops = z->stops;
int i;
double offset = 0;
gboolean got_offset = FALSE;
gint fixed;
RsvgState state;
int n_stop;
if (strcmp ((char *)name, "stop"))
{
g_warning ("unexpected <%s> element in gradient\n", name);
return;
}
rsvg_state_init (&state);
if (atts != NULL)
{
for (i = 0; atts[i] != NULL; i += 2)
{
if (!strcmp ((char *)atts[i], "offset"))
{
offset = rsvg_css_parse_length ((char *)atts[i + 1], &fixed);
got_offset = TRUE;
}
else if (!strcmp ((char *)atts[i], "style"))
rsvg_parse_style (z->ctx, &state, (char *)atts[i + 1]);
}
}
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].rgba = (state.stop_color << 8) | state.stop_opacity;
}
static void
rsvg_gradient_stop_handler_end (RsvgSaxHandler *self, const xmlChar *name)
{
}
static RsvgSaxHandler *
rsvg_gradient_stop_handler_new (RsvgCtx *ctx, RsvgGradientStops **p_stops)
{
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;
stops->n_stop = 0;
stops->stop = NULL;
*p_stops = stops;
return &gstops->super;
}
static 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 (RsvgCtx *ctx, const xmlChar **atts)
{
RsvgState *state = &ctx->state[ctx->n_state - 1];
RsvgLinearGradient *grad;
int i;
char *id = NULL;
double x1 = 0, y1 = 0, x2 = 100, y2 = 0;
ArtGradientSpread spread = ART_GRADIENT_PAD;
/* todo: only handles numeric coordinates in gradientUnits = userSpace */
if (atts != NULL)
{
for (i = 0; atts[i] != NULL; i += 2)
{
if (!strcmp ((char *)atts[i], "id"))
id = (char *)atts[i + 1];
else if (!strcmp ((char *)atts[i], "x1"))
x1 = atof ((char *)atts[i + 1]);
else if (!strcmp ((char *)atts[i], "y1"))
y1 = atof ((char *)atts[i + 1]);
else if (!strcmp ((char *)atts[i], "x2"))
x2 = atof ((char *)atts[i + 1]);
else if (!strcmp ((char *)atts[i], "y2"))
y2 = atof ((char *)atts[i + 1]);
else if (!strcmp ((char *)atts[i], "spreadMethod"))
{
if (!strcmp ((char *)atts[i + 1], "pad"))
spread = ART_GRADIENT_PAD;
else if (!strcmp ((char *)atts[i + 1], "reflect"))
spread = ART_GRADIENT_REFLECT;
else if (!strcmp ((char *)atts[i + 1], "repeat"))
spread = ART_GRADIENT_REPEAT;
}
}
}
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);
rsvg_defs_set (ctx->defs, id, &grad->super);
for (i = 0; i < 6; i++)
grad->affine[i] = state->affine[i];
grad->x1 = x1;
grad->y1 = y1;
grad->x2 = x2;
grad->y2 = y2;
grad->spread = spread;
}
static void
rsvg_radial_gradient_free (RsvgDefVal *self)
{
RsvgRadialGradient *z = (RsvgRadialGradient *)self;
g_free (z->stops->stop);
g_free (z->stops);
g_free (self);
}
static void
rsvg_start_radial_gradient (RsvgCtx *ctx, const xmlChar **atts)
{
RsvgState *state = &ctx->state[ctx->n_state - 1];
RsvgRadialGradient *grad;
int i;
char *id = NULL;
double cx = 50, cy = 50, r = 50, fx = 50, fy = 50;
/* todo: only handles numeric coordinates in gradientUnits = userSpace */
if (atts != NULL)
{
for (i = 0; atts[i] != NULL; i += 2)
{
if (!strcmp ((char *)atts[i], "id"))
id = (char *)atts[i + 1];
else if (!strcmp ((char *)atts[i], "cx"))
cx = atof ((char *)atts[i + 1]);
else if (!strcmp ((char *)atts[i], "cy"))
cy = atof ((char *)atts[i + 1]);
else if (!strcmp ((char *)atts[i], "r"))
r = atof ((char *)atts[i + 1]);
else if (!strcmp ((char *)atts[i], "fx"))
fx = atof ((char *)atts[i + 1]);
else if (!strcmp ((char *)atts[i], "fy"))
fy = atof ((char *)atts[i + 1]);
}
}
grad = g_new (RsvgRadialGradient, 1);
grad->super.type = RSVG_DEF_RADGRAD;
grad->super.free = rsvg_radial_gradient_free;
ctx->handler = rsvg_gradient_stop_handler_new (ctx, &grad->stops);
rsvg_defs_set (ctx->defs, id, &grad->super);
for (i = 0; i < 6; i++)
grad->affine[i] = state->affine[i];
grad->cx = cx;
grad->cy = cy;
grad->r = r;
grad->fx = fx;
grad->fy = fy;
}
static void
rsvg_start_element (void *data, const xmlChar *name, const xmlChar **atts)
{
RsvgCtx *ctx = (RsvgCtx *)data;
#ifdef VERBOSE
int i;
#endif
#ifdef VERBOSE
fprintf (stdout, "SAX.startElement(%s", (char *) name);
if (atts != NULL) {
for (i = 0;(atts[i] != NULL);i++) {
fprintf (stdout, ", %s='", atts[i++]);
fprintf (stdout, "%s'", atts[i]);
}
}
fprintf (stdout, ")\n");
#endif
if (ctx->handler)
{
ctx->handler_nest++;
if (ctx->handler->start_element != NULL)
ctx->handler->start_element (ctx->handler, name, atts);
}
else
{
/* push the state stack */
if (ctx->n_state == ctx->n_state_max)
ctx->state = g_renew (RsvgState, ctx->state, ctx->n_state_max <<= 1);
if (ctx->n_state)
rsvg_state_clone (&ctx->state[ctx->n_state],
&ctx->state[ctx->n_state - 1]);
else
rsvg_state_init (ctx->state);
ctx->n_state++;
if (!strcmp ((char *)name, "svg"))
rsvg_start_svg (ctx, atts);
else if (!strcmp ((char *)name, "g"))
rsvg_start_g (ctx, atts);
else if (!strcmp ((char *)name, "path"))
rsvg_start_path (ctx, atts);
else if (!strcmp ((char *)name, "text"))
rsvg_start_text (ctx, atts);
else if (!strcmp ((char *)name, "defs"))
rsvg_start_defs (ctx, atts);
else if (!strcmp ((char *)name, "linearGradient"))
rsvg_start_linear_gradient (ctx, atts);
else if (!strcmp ((char *)name, "radialGradient"))
rsvg_start_radial_gradient (ctx, atts);
}
}
static void
rsvg_end_element (void *data, const xmlChar *name)
{
RsvgCtx *ctx = (RsvgCtx *)data;
if (ctx->handler_nest > 0)
{
if (ctx->handler->end_element != NULL)
ctx->handler->end_element (ctx->handler, name);
ctx->handler_nest--;
}
else
{
if (ctx->handler != NULL)
{
ctx->handler->free (ctx->handler);
ctx->handler = NULL;
}
if (!strcmp ((char *)name, "g"))
rsvg_end_g (ctx);
/* pop the state stack */
ctx->n_state--;
rsvg_state_finalize (&ctx->state[ctx->n_state]);
#ifdef VERBOSE
fprintf (stdout, "SAX.endElement(%s)\n", (char *) name);
#endif
}
}
static void
rsvg_characters (void *data, const xmlChar *ch, int len)
{
RsvgCtx *ctx = (RsvgCtx *)data;
if (ctx->handler && ctx->handler->characters != NULL)
ctx->handler->characters (ctx->handler, ch, len);
}
static xmlEntityPtr
rsvg_get_entity (void *data, const xmlChar *name)
{
RsvgCtx *ctx = (RsvgCtx *)data;
return (xmlEntityPtr)g_hash_table_lookup (ctx->entities, name);
}
static void
rsvg_entity_decl (void *data, const xmlChar *name, int type,
const xmlChar *publicId, const xmlChar *systemId, xmlChar *content)
{
RsvgCtx *ctx = (RsvgCtx *)data;
GHashTable *entities = ctx->entities;
xmlEntityPtr entity;
char *dupname;
entity = g_new (xmlEntity, 1);
entity->type = type;
entity->len = strlen (name);
dupname = g_strdup (name);
entity->name = dupname;
entity->ExternalID = g_strdup (publicId);
entity->SystemID = g_strdup (systemId);
entity->content = xmlMemStrdup (content);
entity->length = strlen (content);
entity->orig = NULL;
g_hash_table_insert (entities, dupname, entity);
}
static xmlSAXHandler rsvgSAXHandlerStruct = {
NULL, /* internalSubset */
NULL, /* isStandalone */
NULL, /* hasInternalSubset */
NULL, /* hasExternalSubset */
NULL, /* resolveEntity */
rsvg_get_entity, /* getEntity */
rsvg_entity_decl, /* entityDecl */
NULL, /* notationDecl */
NULL, /* attributeDecl */
NULL, /* elementDecl */
NULL, /* unparsedEntityDecl */
NULL, /* setDocumentLocator */
NULL, /* startDocument */
NULL, /* endDocument */
rsvg_start_element, /* startElement */
rsvg_end_element, /* endElement */
NULL, /* reference */
rsvg_characters, /* characters */
NULL, /* ignorableWhitespace */
NULL, /* processingInstruction */
NULL, /* comment */
NULL, /* xmlParserWarning */
NULL, /* xmlParserError */
NULL, /* xmlParserError */
NULL, /* getParameterEntity */
};
static xmlSAXHandlerPtr rsvgSAXHandler = &rsvgSAXHandlerStruct;
GdkPixbuf *
rsvg_render_file (FILE *f, double zoom)
{
int res;
char chars[10];
xmlParserCtxtPtr ctxt;
RsvgCtx *ctx;
GdkPixbuf *result;
ctx = rsvg_ctx_new ();
ctx->zoom = zoom;
res = fread(chars, 1, 4, f);
if (res > 0) {
ctxt = xmlCreatePushParserCtxt(rsvgSAXHandler, ctx,
chars, res, "filename XXX");
ctxt->replaceEntities = TRUE;
while ((res = fread(chars, 1, 3, f)) > 0) {
xmlParseChunk(ctxt, chars, res, 0);
}
xmlParseChunk(ctxt, chars, 0, 1);
xmlFreeParserCtxt(ctxt);
}
result = ctx->pixbuf;
rsvg_ctx_free (ctx);
return result;
}
#ifdef RSVG_MAIN
static void
write_pixbuf (ArtPixBuf *pixbuf)
{
int y;
printf ("P6\n%d %d\n255\n", pixbuf->width, pixbuf->height);
for (y = 0; y < pixbuf->height; y++)
fwrite (pixbuf->pixels + y * pixbuf->rowstride, 1, pixbuf->width * 3,
stdout);
}
int
main (int argc, char **argv)
{
FILE *f;
ArtPixBuf *pixbuf;
if (argc == 1)
f = stdin;
else
{
f = fopen (argv[1], "r");
if (f == NULL)
{
fprintf (stderr, "Error opening source file %s\n", argv[1]);
}
}
pixbuf = rsvg_render_file (f);
if (f != stdin)
fclose (f);
write_pixbuf (pixbuf);
return 0;
}
#endif