/* 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 */ #include #include "rsvg.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "art_rgba.h" #include "art_render.h" #include "art_render_gradient.h" #include "art_render_svp.h" #include "art_render_mask.h" #include #include #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" #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); }; char *fonts_dir; 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) { memset (state, 0, sizeof (*state)); art_affine_identity (state->affine); state->opacity = 0xff; state->fill = rsvg_paint_server_parse (NULL, "#000"); state->fill_opacity = 0xff; state->stroke_opacity = 0xff; state->stroke_width = 1; state->cap = ART_PATH_STROKE_CAP_BUTT; state->join = ART_PATH_STROKE_JOIN_MITER; state->stop_opacity = 0xff; } 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); } /* art_affine_expansion is missing the fabs call */ static double rsvg_affine_expansion (const double src[6]) { return sqrt (fabs (src[0] * src[3] - src[1] * src[2])); } 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 * rsvg_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; int opacity; const char *dir; char *path; /* 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 something * like the Nautilus font mapping stuff in NautilusScalableFont. See * bug for details. */ if (fonts_dir == NULL) { dir = DATADIR "/eel/fonts"; } else { dir = fonts_dir; } path = g_strconcat (dir, "/urw/n019003l.pfb", NULL); fh = rsvg_ft_intern (ctx->ft_ctx, path); g_free (path); path = g_strconcat (dir, "/urw/n019003l.afm", NULL); rsvg_ft_font_attach (ctx->ft_ctx, fh, path); g_free (path); state = &ctx->state[ctx->n_state - 1]; if (state->fill != NULL && state->font_size > 0) { 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); if (glyph == NULL) { } else { 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_new0 (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); if (content) { entity->content = xmlMemStrdup (content); entity->length = strlen (content); } 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; void rsvg_set_fonts_dir (const char *new_fonts_dir) { g_free (fonts_dir); fonts_dir = g_strdup (new_fonts_dir); } 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