source: trunk/third/librsvg/rsvg.c @ 18275

Revision 18275, 84.6 KB checked in by ghudson, 21 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18274, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2   rsvg.c: SAX-based renderer for SVG files into a GdkPixbuf.
3
4   Copyright (C) 2000 Eazel, Inc.
5   Copyright (C) 2002 Dom Lachowicz <cinamod@hotmail.com>
6
7   This program is free software; you can redistribute it and/or
8   modify it under the terms of the GNU Library General Public License as
9   published by the Free Software Foundation; either version 2 of the
10   License, or (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   Library General Public License for more details.
16
17   You should have received a copy of the GNU Library General Public
18   License along with this program; if not, write to the
19   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20   Boston, MA 02111-1307, USA.
21
22   Author: Raph Levien <raph@artofcode.com>
23*/
24
25#include "config.h"
26#include "rsvg.h"
27
28#include <math.h>
29#include <string.h>
30#include <stdarg.h>
31
32#include <libart_lgpl/art_affine.h>
33#include <libart_lgpl/art_vpath_bpath.h>
34#include <libart_lgpl/art_vpath_dash.h>
35#include <libart_lgpl/art_svp_vpath_stroke.h>
36#include <libart_lgpl/art_svp_vpath.h>
37#include <libart_lgpl/art_svp_intersect.h>
38#include <libart_lgpl/art_render_mask.h>
39#include <libart_lgpl/art_render_svp.h>
40#include <libart_lgpl/art_rgba.h>
41#include <libart_lgpl/art_rgb_affine.h>
42#include <libart_lgpl/art_rgb_rgba_affine.h>
43
44#include <libxml/SAX.h>
45#include <libxml/xmlmemory.h>
46
47#include <pango/pangoft2.h>
48
49#if ENABLE_GNOME_VFS
50#include <libgnomevfs/gnome-vfs.h>
51#endif
52
53#include "rsvg-bpath-util.h"
54#include "rsvg-path.h"
55#include "rsvg-css.h"
56#include "rsvg-paint-server.h"
57
58#define SVG_BUFFER_SIZE (1024 * 8)
59
60/* 4/3 * (1-cos 45ƒ)/sin 45ƒ = 4/3 * sqrt(2) - 1 */
61#define RSVG_ARC_MAGIC ((double) 0.5522847498)
62
63/*
64 * This is configurable at runtime
65 */
66#define RSVG_DEFAULT_DPI 90.0
67static double internal_dpi = RSVG_DEFAULT_DPI;
68
69typedef struct {
70  double affine[6];
71
72  gint opacity; /* 0..255 */
73
74  RsvgPaintServer *fill;
75  gint fill_opacity; /* 0..255 */
76
77  RsvgPaintServer *stroke;
78  gint stroke_opacity; /* 0..255 */
79  double stroke_width;
80  double miter_limit;
81
82  ArtPathStrokeCapType cap;
83  ArtPathStrokeJoinType join;
84
85  double font_size;
86  char *font_family;
87  guint text_offset;
88
89  guint32 stop_color; /* rgb */
90  gint stop_opacity; /* 0..255 */
91
92  ArtVpathDash dash;
93
94  GdkPixbuf *save_pixbuf;
95} RsvgState;
96
97typedef struct RsvgSaxHandler RsvgSaxHandler;
98
99struct RsvgSaxHandler {
100  void (*free) (RsvgSaxHandler *self);
101  void (*start_element) (RsvgSaxHandler *self, const xmlChar *name, const xmlChar **atts);
102  void (*end_element) (RsvgSaxHandler *self, const xmlChar *name);
103  void (*characters) (RsvgSaxHandler *self, const xmlChar *ch, int len);
104};
105
106struct RsvgHandle {
107  RsvgSizeFunc size_func;
108  gpointer user_data;
109  GDestroyNotify user_data_destroy;
110  GdkPixbuf *pixbuf;
111
112  /* stack; there is a state for each element */
113  RsvgState *state;
114  int n_state;
115  int n_state_max;
116
117  RsvgDefs *defs;
118  GHashTable *css_props;
119
120  /* not a handler stack. each nested handler keeps
121   * track of its parent
122   */
123  RsvgSaxHandler *handler;
124  int handler_nest;
125
126  GHashTable *entities; /* g_malloc'd string -> xmlEntityPtr */
127
128  PangoContext *pango_context;
129  xmlParserCtxtPtr ctxt;
130  GError **error;
131
132  int width;
133  int height;
134  double dpi;
135};
136
137static gdouble
138rsvg_viewport_percentage (gdouble width, gdouble height)
139{
140  return ((width * width) + (height * height)) / M_SQRT2;
141}
142
143static void
144rsvg_state_init (RsvgState *state)
145{
146  memset (state, 0, sizeof (*state));
147
148  art_affine_identity (state->affine);
149
150  state->opacity = 0xff;
151  state->fill = rsvg_paint_server_parse (NULL, "#000");
152  state->fill_opacity = 0xff;
153  state->stroke_opacity = 0xff;
154  state->stroke_width = 1;
155  state->miter_limit = 4;
156  state->cap = ART_PATH_STROKE_CAP_BUTT;
157  state->join = ART_PATH_STROKE_JOIN_MITER;
158  state->stop_opacity = 0xff;
159}
160
161static void
162rsvg_state_clone (RsvgState *dst, const RsvgState *src)
163{
164  gint i;
165
166  *dst = *src;
167  dst->font_family = g_strdup (src->font_family);
168  rsvg_paint_server_ref (dst->fill);
169  rsvg_paint_server_ref (dst->stroke);
170  dst->save_pixbuf = NULL;
171
172  if (src->dash.n_dash > 0)
173    {
174      dst->dash.dash = g_new (gdouble, src->dash.n_dash);
175      for (i = 0; i < src->dash.n_dash; i++)
176        dst->dash.dash[i] = src->dash.dash[i];
177    }
178}
179
180static void
181rsvg_state_finalize (RsvgState *state)
182{
183  g_free (state->font_family);
184  rsvg_paint_server_unref (state->fill);
185  rsvg_paint_server_unref (state->stroke);
186
187  if (state->dash.n_dash != 0)
188    g_free (state->dash.dash);
189}
190
191static void
192rsvg_ctx_free_helper (gpointer key, gpointer value, gpointer user_data)
193{
194  xmlEntityPtr entval = (xmlEntityPtr)value;
195
196  /* key == entval->name, so it's implicitly freed below */
197
198  g_free ((char *) entval->name);
199  g_free ((char *) entval->ExternalID);
200  g_free ((char *) entval->SystemID);
201  xmlFree (entval->content);
202  xmlFree (entval->orig);
203  g_free (entval);
204}
205
206static void
207rsvg_pixmap_destroy (guchar *pixels, gpointer data)
208{
209  g_free (pixels);
210}
211
212static void
213rsvg_start_svg (RsvgHandle *ctx, const xmlChar **atts)
214{
215  int i;
216  int width = -1, height = -1, x = -1, y = -1;
217  int rowstride;
218  art_u8 *pixels;
219  gint percent, em, ex;
220  RsvgState *state;
221  gboolean has_alpha = TRUE;
222  gint new_width, new_height;
223  double x_zoom = 1.;
224  double y_zoom = 1.;
225
226  double vbox_x = 0, vbox_y = 0, vbox_w = 0, vbox_h = 0;
227  gboolean has_vbox = TRUE;
228
229  if (atts != NULL)
230    {
231      for (i = 0; atts[i] != NULL; i += 2)
232        {
233          /* x & y should be ignored since we should always be the outermost SVG,
234             at least for now, but i'll include them here anyway */
235          if (!strcmp ((char *)atts[i], "width"))
236            width = rsvg_css_parse_length ((char *)atts[i + 1], ctx->dpi, &percent, &em, &ex);
237          else if (!strcmp ((char *)atts[i], "height"))     
238            height = rsvg_css_parse_length ((char *)atts[i + 1], ctx->dpi, &percent, &em, &ex);
239          else if (!strcmp ((char *)atts[i], "x"))         
240            x = rsvg_css_parse_length ((char *)atts[i + 1], ctx->dpi, &percent, &em, &ex);
241          else if (!strcmp ((char *)atts[i], "y"))         
242            y = rsvg_css_parse_length ((char *)atts[i + 1], ctx->dpi, &percent, &em, &ex);
243          else if (!strcmp ((char *)atts[i], "viewBox"))
244            {
245              /* todo: viewbox can have whitespace and/or comma but we're only likely to see
246                 these 2 combinations */
247              if (4 == sscanf ((char *)atts[i + 1], " %lf %lf %lf %lf ", &vbox_x, &vbox_y, &vbox_w, &vbox_h) ||
248                  4 == sscanf ((char *)atts[i + 1], " %lf , %lf , %lf , %lf ", &vbox_x, &vbox_y, &vbox_w, &vbox_h))
249                has_vbox = TRUE;
250            }
251        }
252
253      if (has_vbox && vbox_w > 0. && vbox_h > 0.)
254        {
255          new_width  = (int)floor (vbox_w);
256          new_height = (int)floor (vbox_h);
257
258          /* apply the sizing function on the *original* width and height
259             to acquire our real destination size. we'll scale it against
260             the viewBox's coordinates later */
261          if (ctx->size_func)
262            (* ctx->size_func) (&width, &height, ctx->user_data);
263        }
264      else
265        {
266          new_width  = width;
267          new_height = height;
268
269          /* apply the sizing function to acquire our new width and height.
270             we'll scale this against the old values later */
271          if (ctx->size_func)
272            (* ctx->size_func) (&new_width, &new_height, ctx->user_data);
273        }
274
275      /* set these here because % are relative to viewbox */
276      ctx->width = new_width;
277      ctx->height = new_height;
278
279      if (!has_vbox)
280        {
281          x_zoom = (width < 0 || new_width < 0) ? 1 : (double) new_width / width;
282          y_zoom = (height < 0 || new_height < 0) ? 1 : (double) new_height / height;
283        }
284      else
285        {
286#if 1
287          x_zoom = (width < 0 || new_width < 0) ? 1 : (double) width / new_width;
288          y_zoom = (height < 0 || new_height < 0) ? 1 : (double) height / new_height;
289#else
290          x_zoom = (width < 0 || new_width < 0) ? 1 : (double) new_width / width;
291          y_zoom = (height < 0 || new_height < 0) ? 1 : (double) new_height / height;     
292#endif
293
294          /* reset these so that we get a properly sized SVG and not a huge one */
295          new_width  = (width == -1 ? new_width : width);
296          new_height = (height == -1 ? new_height : height);
297        }
298
299      /* Scale size of target pixbuf */
300      state = &ctx->state[ctx->n_state - 1];
301      art_affine_scale (state->affine, x_zoom, y_zoom);
302
303#if 0
304      if (vbox_x != 0. || vbox_y != 0.)
305        {
306          double affine[6];
307          art_affine_translate (affine, vbox_x, vbox_y);
308          art_affine_multiply (state->affine, affine, state->affine);
309        }
310#endif
311
312      if (new_width < 0 || new_height < 0)
313        {
314          g_warning ("rsvg_start_svg: width and height not specified in the SVG, nor supplied by the size callback");
315          if (new_width < 0) new_width = 500;
316          if (new_height < 0) new_height = 500;
317        }
318
319      if (new_width >= INT_MAX / 4)
320        {
321          /* FIXME: GError here? */
322          g_warning ("rsvg_start_svg: width too large");
323          return;
324        }
325      rowstride = (new_width * (has_alpha ? 4 : 3) + 3) & ~3;
326      if (rowstride > INT_MAX / new_height)
327        {
328          /* FIXME: GError here? */
329          g_warning ("rsvg_start_svg: width too large");
330          return;
331        }
332
333      /* FIXME: Add GError here if size is too big. */
334
335      pixels = g_try_malloc (rowstride * new_height);
336      if (pixels == NULL)
337        {
338          /* FIXME: GError here? */
339          g_warning ("rsvg_start_svg: dimensions too large");
340          return;
341        }
342      memset (pixels, has_alpha ? 0 : 255, rowstride * new_height);
343      ctx->pixbuf = gdk_pixbuf_new_from_data (pixels,
344                                              GDK_COLORSPACE_RGB,
345                                              has_alpha, 8,
346                                              new_width, new_height,
347                                              rowstride,
348                                              rsvg_pixmap_destroy,
349                                              NULL);
350    }
351}
352
353/* Parse a CSS2 style argument, setting the SVG context attributes. */
354static void
355rsvg_parse_style_arg (RsvgHandle *ctx, RsvgState *state, const char *str)
356{
357  int arg_off;
358
359  arg_off = rsvg_css_param_arg_offset (str);
360  if (rsvg_css_param_match (str, "opacity"))
361    {
362      state->opacity = rsvg_css_parse_opacity (str + arg_off);
363    }
364  else if (rsvg_css_param_match (str, "fill"))
365    {
366      rsvg_paint_server_unref (state->fill);
367      state->fill = rsvg_paint_server_parse (ctx->defs, str + arg_off);
368    }
369  else if (rsvg_css_param_match (str, "fill-opacity"))
370    {
371      state->fill_opacity = rsvg_css_parse_opacity (str + arg_off);
372    }
373  else if (rsvg_css_param_match (str, "stroke"))
374    {
375      rsvg_paint_server_unref (state->stroke);
376      state->stroke = rsvg_paint_server_parse (ctx->defs, str + arg_off);
377    }
378  else if (rsvg_css_param_match (str, "stroke-width"))
379    {
380      state->stroke_width = rsvg_css_parse_normalized_length (str + arg_off, ctx->dpi,
381                                                              (gdouble)ctx->height, state->font_size, 0.);
382    }
383  else if (rsvg_css_param_match (str, "stroke-linecap"))
384    {
385      if (!strcmp (str + arg_off, "butt"))
386        state->cap = ART_PATH_STROKE_CAP_BUTT;
387      else if (!strcmp (str + arg_off, "round"))
388        state->cap = ART_PATH_STROKE_CAP_ROUND;
389      else if (!strcmp (str + arg_off, "square"))
390        state->cap = ART_PATH_STROKE_CAP_SQUARE;
391      else
392        g_warning ("unknown line cap style %s", str + arg_off);
393    }
394  else if (rsvg_css_param_match (str, "stroke-opacity"))
395    {
396      state->stroke_opacity = rsvg_css_parse_opacity (str + arg_off);
397    }
398  else if (rsvg_css_param_match (str, "stroke-linejoin"))
399    {
400      if (!strcmp (str + arg_off, "miter"))
401        state->join = ART_PATH_STROKE_JOIN_MITER;
402      else if (!strcmp (str + arg_off, "round"))
403        state->join = ART_PATH_STROKE_JOIN_ROUND;
404      else if (!strcmp (str + arg_off, "bevel"))
405        state->join = ART_PATH_STROKE_JOIN_BEVEL;
406      else
407        g_warning ("unknown line join style %s", str + arg_off);
408    }
409  else if (rsvg_css_param_match (str, "font-size"))
410    {
411      state->font_size = rsvg_css_parse_normalized_length (str + arg_off, ctx->dpi,
412                                                           (gdouble)ctx->height, state->font_size, 0.);
413    }
414  else if (rsvg_css_param_match (str, "font-family"))
415    {
416      g_free (state->font_family);
417      state->font_family = g_strdup (str + arg_off);
418    }
419  else if (rsvg_css_param_match (str, "stop-color"))
420    {
421      state->stop_color = rsvg_css_parse_color (str + arg_off);
422    }
423  else if (rsvg_css_param_match (str, "stop-opacity"))
424    {
425      state->stop_opacity = rsvg_css_parse_opacity (str + arg_off);
426    }
427  else if (rsvg_css_param_match (str, "stroke-miterlimit"))
428    {
429      state->miter_limit = g_ascii_strtod (str + arg_off, NULL);
430    }
431  else if (rsvg_css_param_match (str, "stroke-dashoffset"))
432    {
433      state->dash.offset = rsvg_css_parse_normalized_length (str + arg_off, ctx->dpi,
434                                                             rsvg_viewport_percentage((gdouble)ctx->width, (gdouble)ctx->height), state->font_size, 0.);
435      if (state->dash.offset < 0.)
436        state->dash.offset = 0.;
437    }
438  else if (rsvg_css_param_match (str, "stroke-dasharray"))
439    {
440      if(!strcmp(str + arg_off, "none"))
441        {
442            if (state->dash.n_dash != 0)
443            {
444                /* free any cloned dash data */
445                g_free (state->dash.dash);
446                state->dash.n_dash = 0;
447            }
448        }
449      else
450        {
451          gchar ** dashes = g_strsplit (str + arg_off, ",", -1);
452          if (NULL != dashes)
453            {
454              gint n_dashes, i;
455              gboolean is_even = FALSE ;
456
457              /* count the #dashes */
458              for (n_dashes = 0; dashes[n_dashes] != NULL; n_dashes++)
459                ;
460
461              is_even = (n_dashes % 2 == 0);
462              state->dash.n_dash = (is_even ? n_dashes : n_dashes * 2);
463              state->dash.dash = g_new (double, state->dash.n_dash);
464
465              /* TODO: handle negative value == error case */
466
467              /* the even and base case */
468              for (i = 0; i < n_dashes; i++)
469                state->dash.dash[i] = g_ascii_strtod (dashes[i], NULL);
470
471              /* if an odd number of dashes is found, it gets repeated */
472              if (!is_even)
473                for (; i < state->dash.n_dash; i++)
474                  state->dash.dash[i] = g_ascii_strtod (dashes[i - n_dashes], NULL);
475
476              g_strfreev (dashes) ;
477            }
478        }
479    }
480}
481
482/* tell whether @str is a supported style argument
483   whenever something gets added to parse_arg, please
484   remember to add it here too
485*/
486static gboolean
487rsvg_is_style_arg(const char *str)
488{
489  static GHashTable *styles = NULL;
490  if (!styles)
491    {
492      styles = g_hash_table_new (g_str_hash, g_str_equal);
493     
494      g_hash_table_insert (styles, "fill",              GINT_TO_POINTER (TRUE));
495      g_hash_table_insert (styles, "fill-opacity",      GINT_TO_POINTER (TRUE));
496      g_hash_table_insert (styles, "font-family",       GINT_TO_POINTER (TRUE));
497      g_hash_table_insert (styles, "font-size",         GINT_TO_POINTER (TRUE));
498      g_hash_table_insert (styles, "opacity",           GINT_TO_POINTER (TRUE));
499      g_hash_table_insert (styles, "stop-color",        GINT_TO_POINTER (TRUE));
500      g_hash_table_insert (styles, "stop-opacity",      GINT_TO_POINTER (TRUE));
501      g_hash_table_insert (styles, "stroke",            GINT_TO_POINTER (TRUE));
502      g_hash_table_insert (styles, "stroke-dasharray",  GINT_TO_POINTER (TRUE));
503      g_hash_table_insert (styles, "stroke-dashoffset", GINT_TO_POINTER (TRUE));
504      g_hash_table_insert (styles, "stroke-linecap",    GINT_TO_POINTER (TRUE));
505      g_hash_table_insert (styles, "stroke-linejoin",   GINT_TO_POINTER (TRUE));
506      g_hash_table_insert (styles, "stroke-miterlimit", GINT_TO_POINTER (TRUE));
507      g_hash_table_insert (styles, "stroke-opacity",    GINT_TO_POINTER (TRUE));
508      g_hash_table_insert (styles, "stroke-width",      GINT_TO_POINTER (TRUE));
509    }
510 
511  /* this will default to 0 (FALSE) on a failed lookup */
512  return GPOINTER_TO_INT (g_hash_table_lookup (styles, str));
513}
514
515/* take a pair of the form (fill="#ff00ff") and parse it as a style */
516static void
517rsvg_parse_style_pair (RsvgHandle *ctx, RsvgState *state,
518                       const char *key, const char *val)
519{
520  gchar * str = g_strdup_printf ("%s:%s", key, val);
521  rsvg_parse_style_arg (ctx, state, str);
522  g_free (str);
523}
524
525/* Split a CSS2 style into individual style arguments, setting attributes
526   in the SVG context.
527
528   It's known that this is _way_ out of spec. A more complete CSS2
529   implementation will happen later.
530*/
531static void
532rsvg_parse_style (RsvgHandle *ctx, RsvgState *state, const char *str)
533{
534  int start, end;
535  char *arg;
536
537  start = 0;
538  while (str[start] != '\0')
539    {
540      for (end = start; str[end] != '\0' && str[end] != ';'; end++);
541      arg = g_new (char, 1 + end - start);
542      memcpy (arg, str + start, end - start);
543      arg[end - start] = '\0';
544      rsvg_parse_style_arg (ctx, state, arg);
545      g_free (arg);
546      start = end;
547      if (str[start] == ';') start++;
548      while (str[start] == ' ') start++;
549    }
550}
551
552/*
553 * Extremely poor man's CSS parser. Not robust. Not compliant.
554 * Should work well enough for our needs ;-)
555 */
556static void
557rsvg_parse_cssbuffer (RsvgHandle *ctx, const char * buff, size_t buflen)
558{
559  size_t loc = 0;
560 
561  while (loc < buflen)
562    {
563      GString * style_name = g_string_new (NULL);
564      GString * style_props = g_string_new (NULL);
565
566      /* advance to the style's name */
567      while (loc < buflen && g_ascii_isspace (buff[loc]))
568        loc++;
569
570      while (loc < buflen && !g_ascii_isspace (buff[loc]))
571        g_string_append_c (style_name, buff[loc++]);
572
573      /* advance to the first { that defines the style's properties */
574      while (loc < buflen && buff[loc++] != '{' )
575        ;
576
577      while (loc < buflen && g_ascii_isspace (buff[loc]))
578        loc++;
579
580      while (buff[loc] != '}')
581        {
582          /* suck in and append our property */
583          while (loc < buflen && buff[loc] != ';' && buff[loc] != '}' )
584            g_string_append_c (style_props, buff[loc++]);
585
586          if (buff[loc] == '}')
587            break;
588          else
589            {
590              g_string_append_c (style_props, ';');
591             
592              /* advance to the next property */
593              loc++;
594              while (g_ascii_isspace (buff[loc]) && loc < buflen)
595                loc++;
596            }
597        }
598
599      /* push name/style pair into HT */
600      g_hash_table_insert (ctx->css_props, style_name->str, style_props->str);
601     
602      g_string_free (style_name, FALSE);
603      g_string_free (style_props, FALSE);
604
605      loc++;
606      while (g_ascii_isspace (buff[loc]) && loc < buflen)
607        loc++;
608    }
609}
610
611/* Parse an SVG transform string into an affine matrix. Reference: SVG
612   working draft dated 1999-07-06, section 8.5. Return TRUE on
613   success. */
614static gboolean
615rsvg_parse_transform (double dst[6], const char *src)
616{
617  int idx;
618  char keyword[32];
619  double args[6];
620  int n_args;
621  guint key_len;
622  double tmp_affine[6];
623
624  art_affine_identity (dst);
625
626  idx = 0;
627  while (src[idx])
628    {
629      /* skip initial whitespace */
630      while (g_ascii_isspace (src[idx]))
631        idx++;
632
633      /* parse keyword */
634      for (key_len = 0; key_len < sizeof (keyword); key_len++)
635        {
636          char c;
637
638          c = src[idx];
639          if (g_ascii_isalpha (c) || c == '-')
640            keyword[key_len] = src[idx++];
641          else
642            break;
643        }
644      if (key_len >= sizeof (keyword))
645        return FALSE;
646      keyword[key_len] = '\0';
647
648      /* skip whitespace */
649      while (g_ascii_isspace (src[idx]))
650        idx++;
651
652      if (src[idx] != '(')
653        return FALSE;
654      idx++;
655
656      for (n_args = 0; ; n_args++)
657        {
658          char c;
659          char *end_ptr;
660
661          /* skip whitespace */
662          while (g_ascii_isspace (src[idx]))
663            idx++;
664          c = src[idx];
665          if (g_ascii_isdigit (c) || c == '+' || c == '-' || c == '.')
666            {
667              if (n_args == sizeof(args) / sizeof(args[0]))
668                return FALSE; /* too many args */
669              args[n_args] = g_ascii_strtod (src + idx, &end_ptr);
670              idx = end_ptr - src;
671
672              while (g_ascii_isspace (src[idx]))
673                idx++;
674
675              /* skip optional comma */
676              if (src[idx] == ',')
677                idx++;
678            }
679          else if (c == ')')
680            break;
681          else
682            return FALSE;
683        }
684      idx++;
685
686      /* ok, have parsed keyword and args, now modify the transform */
687      if (!strcmp (keyword, "matrix"))
688        {
689          if (n_args != 6)
690            return FALSE;
691          art_affine_multiply (dst, args, dst);
692        }
693      else if (!strcmp (keyword, "translate"))
694        {
695          if (n_args == 1)
696            args[1] = 0;
697          else if (n_args != 2)
698            return FALSE;
699          art_affine_translate (tmp_affine, args[0], args[1]);
700          art_affine_multiply (dst, tmp_affine, dst);
701        }
702      else if (!strcmp (keyword, "scale"))
703        {
704          if (n_args == 1)
705            args[1] = args[0];
706          else if (n_args != 2)
707            return FALSE;
708          art_affine_scale (tmp_affine, args[0], args[1]);
709          art_affine_multiply (dst, tmp_affine, dst);
710        }
711      else if (!strcmp (keyword, "rotate"))
712        {
713          if (n_args != 1)
714            return FALSE;
715          art_affine_rotate (tmp_affine, args[0]);
716          art_affine_multiply (dst, tmp_affine, dst);
717        }
718      else if (!strcmp (keyword, "skewX"))
719        {
720          if (n_args != 1)
721            return FALSE;
722          art_affine_shear (tmp_affine, args[0]);
723          art_affine_multiply (dst, tmp_affine, dst);
724        }
725      else if (!strcmp (keyword, "skewY"))
726        {
727          if (n_args != 1)
728            return FALSE;
729          art_affine_shear (tmp_affine, args[0]);
730          /* transpose the affine, given that we know [1] is zero */
731          tmp_affine[1] = tmp_affine[2];
732          tmp_affine[2] = 0;
733          art_affine_multiply (dst, tmp_affine, dst);
734        }
735      else
736        return FALSE; /* unknown keyword */
737    }
738  return TRUE;
739}
740
741/**
742 * rsvg_parse_transform_attr: Parse transform attribute and apply to state.
743 * @ctx: Rsvg context.
744 * @state: State in which to apply the transform.
745 * @str: String containing transform.
746 *
747 * Parses the transform attribute in @str and applies it to @state.
748 **/
749static void
750rsvg_parse_transform_attr (RsvgHandle *ctx, RsvgState *state, const char *str)
751{
752  double affine[6];
753
754  if (rsvg_parse_transform (affine, str))
755    {
756      art_affine_multiply (state->affine, affine, state->affine);
757    }
758  else
759    {
760      /* parse error for transform attribute. todo: report */
761    }
762}
763
764static gboolean
765rsvg_lookup_apply_css_style (RsvgHandle *ctx, const char * target)
766{
767  const char * value = (const char *)g_hash_table_lookup (ctx->css_props, target);
768
769  if (value != NULL)
770    {
771      rsvg_parse_style (ctx, &ctx->state[ctx->n_state - 1],
772                        value);
773      return TRUE;
774    }
775  return FALSE;
776}
777
778/**
779 * rsvg_parse_style_attrs: Parse style attribute.
780 * @ctx: Rsvg context.
781 * @tag: The SVG tag we're processing (eg: circle, ellipse), optionally %NULL
782 * @klazz: The space delimited class list, optionally %NULL
783 * @atts: Attributes in SAX style.
784 *
785 * Parses style and transform attributes and modifies state at top of
786 * stack.
787 **/
788static void
789rsvg_parse_style_attrs (RsvgHandle *ctx,
790                        const char * tag,
791                        const char * klazz,
792                        const xmlChar **atts)
793{
794  int i = 0, j = 0;
795  char * target = NULL;
796  gboolean found = FALSE;
797  GString * klazz_list = NULL;
798
799  if (tag != NULL && klazz != NULL)
800    {
801      target = g_strdup_printf ("%s.%s", tag, klazz);
802      found = rsvg_lookup_apply_css_style (ctx, target);
803      g_free (target);
804    }
805 
806  if (found == FALSE)
807    {
808      if (tag != NULL)
809        rsvg_lookup_apply_css_style (ctx, tag);
810
811      if (klazz != NULL)
812        {
813          i = strlen (klazz);
814          while (j < i)
815            {
816              klazz_list = g_string_new (".");
817
818              while (j < i && g_ascii_isspace(klazz[j]))
819                j++;
820
821              while (j < i && !g_ascii_isspace(klazz[j]))
822                g_string_append_c (klazz_list, klazz[j++]);
823
824              rsvg_lookup_apply_css_style (ctx, klazz_list->str);
825              g_string_free (klazz_list, TRUE);
826            }
827        }
828    }
829
830  if (atts != NULL)
831    {
832      for (i = 0; atts[i] != NULL; i += 2)
833        {
834          if (!strcmp ((char *)atts[i], "style"))
835            rsvg_parse_style (ctx, &ctx->state[ctx->n_state - 1],
836                              (char *)atts[i + 1]);
837          else if (!strcmp ((char *)atts[i], "transform"))
838            rsvg_parse_transform_attr (ctx, &ctx->state[ctx->n_state - 1],
839                                       (char *)atts[i + 1]);
840          else if (rsvg_is_style_arg ((char *)atts[i]))
841            rsvg_parse_style_pair (ctx, &ctx->state[ctx->n_state - 1],
842                                   (char *)atts[i], (char *)atts[i + 1]);
843        }
844    }
845}
846
847/**
848 * rsvg_push_opacity_group: Begin a new transparency group.
849 * @ctx: Context in which to push.
850 *
851 * Pushes a new transparency group onto the stack. The top of the stack
852 * is stored in the context, while the "saved" value is in the state
853 * stack.
854 **/
855static void
856rsvg_push_opacity_group (RsvgHandle *ctx)
857{
858  RsvgState *state;
859  GdkPixbuf *pixbuf;
860  art_u8 *pixels;
861  int width, height, rowstride;
862
863  state = &ctx->state[ctx->n_state - 1];
864  pixbuf = ctx->pixbuf;
865
866  state->save_pixbuf = pixbuf;
867
868  if (pixbuf == NULL)
869    {
870      /* FIXME: What warning/GError here? */
871      return;
872    }
873
874  if (!gdk_pixbuf_get_has_alpha (pixbuf))
875    {
876      g_warning ("push/pop transparency group on non-alpha buffer nyi");
877      return;
878    }
879
880  width = gdk_pixbuf_get_width (pixbuf);
881  height = gdk_pixbuf_get_height (pixbuf);
882  rowstride = gdk_pixbuf_get_rowstride (pixbuf);
883  pixels = g_new (art_u8, rowstride * height);
884  memset (pixels, 0, rowstride * height);
885
886  pixbuf = gdk_pixbuf_new_from_data (pixels,
887                                     GDK_COLORSPACE_RGB,
888                                     TRUE,
889                                     gdk_pixbuf_get_bits_per_sample (pixbuf),
890                                     width,
891                                     height,
892                                     rowstride,
893                                     rsvg_pixmap_destroy,
894                                     NULL);
895  ctx->pixbuf = pixbuf;
896}
897
898/**
899 * rsvg_pop_opacity_group: End a transparency group.
900 * @ctx: Context in which to push.
901 * @opacity: Opacity for blending (0..255).
902 *
903 * Pops a new transparency group from the stack, recompositing with the
904 * next on stack.
905 **/
906static void
907rsvg_pop_opacity_group (RsvgHandle *ctx, int opacity)
908{
909  RsvgState *state = &ctx->state[ctx->n_state - 1];
910  GdkPixbuf *tos, *nos;
911  art_u8 *tos_pixels, *nos_pixels;
912  int width;
913  int height;
914  int rowstride;
915  int x, y;
916  int tmp;
917
918  tos = ctx->pixbuf;
919  nos = state->save_pixbuf;
920
921  if (tos == NULL || nos == NULL)
922    {
923      /* FIXME: What warning/GError here? */
924      return;
925    }
926
927  if (!gdk_pixbuf_get_has_alpha (nos))
928    {
929      g_warning ("push/pop transparency group on non-alpha buffer nyi");
930      return;
931    }
932
933  width = gdk_pixbuf_get_width (tos);
934  height = gdk_pixbuf_get_height (tos);
935  rowstride = gdk_pixbuf_get_rowstride (tos);
936
937  tos_pixels = gdk_pixbuf_get_pixels (tos);
938  nos_pixels = gdk_pixbuf_get_pixels (nos);
939
940  for (y = 0; y < height; y++)
941    {
942      for (x = 0; x < width; x++)
943        {
944          art_u8 r, g, b, a;
945          a = tos_pixels[4 * x + 3];
946          if (a)
947            {
948              r = tos_pixels[4 * x];
949              g = tos_pixels[4 * x + 1];
950              b = tos_pixels[4 * x + 2];
951              tmp = a * opacity + 0x80;
952              a = (tmp + (tmp >> 8)) >> 8;
953              art_rgba_run_alpha (nos_pixels + 4 * x, r, g, b, a, 1);
954            }
955        }
956      tos_pixels += rowstride;
957      nos_pixels += rowstride;
958    }
959
960  g_object_unref (tos);
961  ctx->pixbuf = nos;
962}
963
964static void
965rsvg_start_g (RsvgHandle *ctx, const xmlChar **atts)
966{
967  RsvgState *state = &ctx->state[ctx->n_state - 1];
968  const char * klazz = NULL;
969  int i;
970
971  if (atts != NULL)
972    {
973      for (i = 0; atts[i] != NULL; i += 2)
974        {
975          if (!strcmp ((char *)atts[i], "class"))
976            klazz = (const char *)atts[i + 1];
977        }
978    }
979
980  rsvg_parse_style_attrs (ctx, "g", klazz, atts);
981  if (state->opacity != 0xff)
982    rsvg_push_opacity_group (ctx);
983}
984
985static void
986rsvg_end_g (RsvgHandle *ctx)
987{
988  RsvgState *state = &ctx->state[ctx->n_state - 1];
989
990  if (state->opacity != 0xff)
991    rsvg_pop_opacity_group (ctx, state->opacity);
992}
993
994/**
995 * rsvg_close_vpath: Close a vector path.
996 * @src: Source vector path.
997 *
998 * Closes any open subpaths in the vector path.
999 *
1000 * Return value: Closed vector path, allocated with g_new.
1001 **/
1002static ArtVpath *
1003rsvg_close_vpath (const ArtVpath *src)
1004{
1005  ArtVpath *result;
1006  int n_result, n_result_max;
1007  int src_ix;
1008  double beg_x, beg_y;
1009  gboolean open;
1010
1011  n_result = 0;
1012  n_result_max = 16;
1013  result = g_new (ArtVpath, n_result_max);
1014
1015  beg_x = 0;
1016  beg_y = 0;
1017  open = FALSE;
1018
1019  for (src_ix = 0; src[src_ix].code != ART_END; src_ix++)
1020    {
1021      if (n_result == n_result_max)
1022        result = g_renew (ArtVpath, result, n_result_max <<= 1);
1023      result[n_result].code = src[src_ix].code == ART_MOVETO_OPEN ?
1024        ART_MOVETO : src[src_ix].code;
1025      result[n_result].x = src[src_ix].x;
1026      result[n_result].y = src[src_ix].y;
1027      n_result++;
1028      if (src[src_ix].code == ART_MOVETO_OPEN)
1029        {
1030          beg_x = src[src_ix].x;
1031          beg_y = src[src_ix].y;
1032          open = TRUE;
1033        }
1034      else if (src[src_ix + 1].code != ART_LINETO)
1035        {
1036          if (open && (beg_x != src[src_ix].x || beg_y != src[src_ix].y))
1037            {
1038              if (n_result == n_result_max)
1039                result = g_renew (ArtVpath, result, n_result_max <<= 1);
1040              result[n_result].code = ART_LINETO;
1041              result[n_result].x = beg_x;
1042              result[n_result].y = beg_y;
1043              n_result++;
1044            }
1045          open = FALSE;
1046        }
1047    }
1048  if (n_result == n_result_max)
1049    result = g_renew (ArtVpath, result, n_result_max <<= 1);
1050  result[n_result].code = ART_END;
1051  result[n_result].x = 0.0;
1052  result[n_result].y = 0.0;
1053  return result;
1054}
1055
1056/**
1057 * rsvg_render_svp: Render an SVP.
1058 * @ctx: Context in which to render.
1059 * @svp: SVP to render.
1060 * @ps: Paint server for rendering.
1061 * @opacity: Opacity as 0..0xff.
1062 *
1063 * Renders the SVP over the pixbuf in @ctx.
1064 **/
1065static void
1066rsvg_render_svp (RsvgHandle *ctx, const ArtSVP *svp,
1067                 RsvgPaintServer *ps, int opacity)
1068{
1069  GdkPixbuf *pixbuf;
1070  ArtRender *render;
1071  gboolean has_alpha;
1072
1073  pixbuf = ctx->pixbuf;
1074  if (pixbuf == NULL)
1075    {
1076      /* FIXME: What warning/GError here? */
1077      return;
1078    }
1079
1080  has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
1081
1082  render = art_render_new (0, 0,
1083                           gdk_pixbuf_get_width (pixbuf),
1084                           gdk_pixbuf_get_height (pixbuf),
1085                           gdk_pixbuf_get_pixels (pixbuf),
1086                           gdk_pixbuf_get_rowstride (pixbuf),
1087                           gdk_pixbuf_get_n_channels (pixbuf) -
1088                           (has_alpha ? 1 : 0),
1089                           gdk_pixbuf_get_bits_per_sample (pixbuf),
1090                           has_alpha ? ART_ALPHA_SEPARATE : ART_ALPHA_NONE,
1091                           NULL);
1092
1093  art_render_svp (render, svp);
1094  art_render_mask_solid (render, (opacity << 8) + opacity + (opacity >> 7));
1095  rsvg_render_paint_server (render, ps, NULL); /* todo: paint server ctx */
1096  art_render_invoke (render);
1097}
1098
1099static void
1100rsvg_render_bpath (RsvgHandle *ctx, const ArtBpath *bpath)
1101{
1102  RsvgState *state;
1103  ArtBpath *affine_bpath;
1104  ArtVpath *vpath;
1105  ArtSVP *svp;
1106  GdkPixbuf *pixbuf;
1107  gboolean need_tmpbuf;
1108  int opacity;
1109  int tmp;
1110
1111  pixbuf = ctx->pixbuf;
1112  if (pixbuf == NULL)
1113    {
1114      /* FIXME: What warning/GError here? */
1115      return;
1116    }
1117
1118  state = &ctx->state[ctx->n_state - 1];
1119  affine_bpath = art_bpath_affine_transform (bpath,
1120                                             state->affine);
1121
1122  vpath = art_bez_path_to_vec (affine_bpath, 0.25);
1123  art_free (affine_bpath);
1124
1125  need_tmpbuf = (state->fill != NULL) && (state->stroke != NULL) &&
1126    state->opacity != 0xff;
1127
1128  if (need_tmpbuf)
1129    rsvg_push_opacity_group (ctx);
1130
1131  if (state->fill != NULL)
1132    {
1133      ArtVpath *closed_vpath;
1134      ArtSVP *svp2;
1135      ArtSvpWriter *swr;
1136
1137      closed_vpath = rsvg_close_vpath (vpath);
1138      svp = art_svp_from_vpath (closed_vpath);
1139      g_free (closed_vpath);
1140     
1141      swr = art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO);
1142      art_svp_intersector (svp, swr);
1143
1144      svp2 = art_svp_writer_rewind_reap (swr);
1145      art_svp_free (svp);
1146
1147      opacity = state->fill_opacity;
1148      if (!need_tmpbuf && state->opacity != 0xff)
1149        {
1150          tmp = opacity * state->opacity + 0x80;
1151          opacity = (tmp + (tmp >> 8)) >> 8;
1152        }
1153      rsvg_render_svp (ctx, svp2, state->fill, opacity);
1154      art_svp_free (svp2);
1155    }
1156
1157  if (state->stroke != NULL)
1158    {
1159      /* todo: libart doesn't yet implement anamorphic scaling of strokes */
1160      double stroke_width = state->stroke_width *
1161        art_affine_expansion (state->affine);
1162
1163      if (stroke_width < 0.25)
1164        stroke_width = 0.25;
1165
1166      /* if the path is dashed, stroke it */
1167      if (state->dash.n_dash > 0)
1168        {
1169          ArtVpath * dashed_vpath = art_vpath_dash (vpath, &state->dash);
1170          art_free (vpath);
1171          vpath = dashed_vpath;
1172        }
1173
1174      svp = art_svp_vpath_stroke (vpath, state->join, state->cap,
1175                                  stroke_width, state->miter_limit, 0.25);
1176      opacity = state->stroke_opacity;
1177      if (!need_tmpbuf && state->opacity != 0xff)
1178        {
1179          tmp = opacity * state->opacity + 0x80;
1180          opacity = (tmp + (tmp >> 8)) >> 8;
1181        }
1182      rsvg_render_svp (ctx, svp, state->stroke, opacity);
1183      art_svp_free (svp);
1184    }
1185
1186  if (need_tmpbuf)
1187    rsvg_pop_opacity_group (ctx, state->opacity);
1188
1189  art_free (vpath);
1190}
1191
1192static void
1193rsvg_render_path(RsvgHandle *ctx, const char *d)
1194{
1195  RsvgBpathDef *bpath_def;
1196 
1197  bpath_def = rsvg_parse_path (d);
1198  rsvg_bpath_def_art_finish (bpath_def);
1199 
1200  rsvg_render_bpath (ctx, bpath_def->bpath);
1201 
1202  rsvg_bpath_def_free (bpath_def);
1203}
1204
1205static void
1206rsvg_start_path (RsvgHandle *ctx, const xmlChar **atts)
1207{
1208  int i;
1209  char *d = NULL;
1210  const char * klazz = NULL;
1211
1212  if (atts != NULL)
1213    {
1214      for (i = 0; atts[i] != NULL; i += 2)
1215        {
1216          if (!strcmp ((char *)atts[i], "d"))
1217            d = (char *)atts[i + 1];
1218          else if (!strcmp ((char *)atts[i], "class"))
1219            klazz = (char *)atts[i + 1];
1220        }
1221    }
1222
1223  if (d == NULL)
1224    return;
1225
1226  rsvg_parse_style_attrs (ctx, "path", klazz, atts);
1227  rsvg_render_path (ctx, d);
1228}
1229
1230/* begin text - this should likely get split into its own .c file */
1231
1232static char *
1233make_valid_utf8 (const char *str)
1234{
1235  GString *string;
1236  const char *remainder, *invalid;
1237  int remaining_bytes, valid_bytes;
1238 
1239  string = NULL;
1240  remainder = str;
1241  remaining_bytes = strlen (str);
1242 
1243  while (remaining_bytes != 0)
1244    {
1245      if (g_utf8_validate (remainder, remaining_bytes, &invalid))
1246        break;
1247      valid_bytes = invalid - remainder;
1248     
1249      if (string == NULL)
1250        string = g_string_sized_new (remaining_bytes);
1251     
1252      g_string_append_len (string, remainder, valid_bytes);
1253      g_string_append_c (string, '?');
1254
1255      remaining_bytes -= valid_bytes + 1;
1256      remainder = invalid + 1;
1257    }
1258
1259  if (string == NULL)
1260    return g_strdup (str);
1261 
1262  g_string_append (string, remainder);
1263       
1264  return g_string_free (string, FALSE);
1265}
1266
1267
1268typedef struct _RsvgSaxHandlerText {
1269  RsvgSaxHandler super;
1270  RsvgSaxHandler *parent;
1271  RsvgHandle *ctx;
1272} RsvgSaxHandlerText;
1273
1274static void
1275rsvg_text_handler_free (RsvgSaxHandler *self)
1276{
1277  g_free (self);
1278}
1279
1280static void
1281rsvg_text_handler_characters (RsvgSaxHandler *self, const xmlChar *ch, int len)
1282{
1283  RsvgSaxHandlerText *z = (RsvgSaxHandlerText *)self;
1284  RsvgHandle *ctx = z->ctx;
1285  char *string, *tmp;
1286  int beg, end;
1287  RsvgState *state;
1288  ArtRender *render;
1289  GdkPixbuf *pixbuf;
1290  gboolean has_alpha;
1291  int opacity;
1292  PangoLayout *layout;
1293  PangoFontDescription *font;
1294  PangoLayoutLine *line;
1295  PangoRectangle ink_rect, line_ink_rect;
1296  FT_Bitmap bitmap;
1297
1298  state = &ctx->state[ctx->n_state - 1];
1299  if (state->fill == NULL && state->font_size <= 0)
1300    {
1301      return;
1302    }
1303
1304  pixbuf = ctx->pixbuf;
1305  if (pixbuf == NULL)
1306    {
1307      /* FIXME: What warning/GError here? */
1308      return;
1309    }
1310
1311  /* Copy ch into string, chopping off leading and trailing whitespace */
1312  for (beg = 0; beg < len; beg++)
1313    if (!g_ascii_isspace (ch[beg]))
1314      break;
1315 
1316  for (end = len; end > beg; end--)
1317    if (!g_ascii_isspace (ch[end - 1]))
1318      break;
1319 
1320  if (end - beg == 0)
1321    {
1322      /* TODO: be smarter with some "last was space" logic */
1323      end = 1; beg = 0;
1324      string = g_strdup (" ");
1325    }
1326  else
1327    {
1328      string = g_malloc (end - beg + 1);
1329      memcpy (string, ch + beg, end - beg);
1330      string[end - beg] = 0;
1331    }
1332
1333  if (!g_utf8_validate (string, -1, NULL))
1334    {
1335      tmp = make_valid_utf8 (string);
1336      g_free (string);
1337      string = tmp;
1338    }
1339 
1340  if (ctx->pango_context == NULL)
1341    ctx->pango_context = pango_ft2_get_context ((guint)ctx->dpi, (guint)ctx->dpi);
1342
1343  has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
1344 
1345  render = art_render_new (0, 0,
1346                           gdk_pixbuf_get_width (pixbuf),
1347                           gdk_pixbuf_get_height (pixbuf),
1348                           gdk_pixbuf_get_pixels (pixbuf),
1349                           gdk_pixbuf_get_rowstride (pixbuf),
1350                           gdk_pixbuf_get_n_channels (pixbuf) -
1351                           (has_alpha ? 1 : 0),
1352                           gdk_pixbuf_get_bits_per_sample (pixbuf),
1353                           has_alpha ? ART_ALPHA_SEPARATE : ART_ALPHA_NONE,
1354                           NULL);
1355 
1356  layout = pango_layout_new (ctx->pango_context);
1357  pango_layout_set_text (layout, string, end - beg);
1358  font = pango_font_description_copy (pango_context_get_font_description (ctx->pango_context));
1359  if (state->font_family)
1360    pango_font_description_set_family_static (font, state->font_family);
1361
1362  /* we need to resize the font by our X or Y scale (ideally could stretch in both directions...)
1363     which, though? Y for now */
1364  pango_font_description_set_size (font, state->font_size * PANGO_SCALE * state->affine[3]);
1365  pango_layout_set_font_description (layout, font);
1366  pango_font_description_free (font);
1367 
1368  pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
1369 
1370  line = pango_layout_get_line (layout, 0);
1371  if (line == NULL)
1372    line_ink_rect = ink_rect; /* nothing to draw anyway */
1373  else
1374    pango_layout_line_get_pixel_extents (line, &line_ink_rect, NULL);
1375 
1376  bitmap.rows = ink_rect.height;
1377  bitmap.width = ink_rect.width;
1378  bitmap.pitch = (bitmap.width + 3) & ~3;
1379  bitmap.buffer = g_malloc0 (bitmap.rows * bitmap.pitch);
1380  bitmap.num_grays = 0x100;
1381  bitmap.pixel_mode = ft_pixel_mode_grays;
1382 
1383  pango_ft2_render_layout (&bitmap, layout, -ink_rect.x, -ink_rect.y);
1384 
1385  g_object_unref (layout);
1386 
1387  rsvg_render_paint_server (render, state->fill, NULL); /* todo: paint server ctx */
1388  opacity = state->fill_opacity * state->opacity;
1389  opacity = opacity + (opacity >> 7) + (opacity >> 14);
1390
1391  art_render_mask_solid (render, opacity);
1392  art_render_mask (render,
1393                   state->affine[4] + line_ink_rect.x + state->text_offset,
1394                   state->affine[5] + line_ink_rect.y,
1395                   state->affine[4] + line_ink_rect.x + bitmap.width + state->text_offset,
1396                   state->affine[5] + line_ink_rect.y + bitmap.rows,
1397                   bitmap.buffer, bitmap.pitch);
1398  art_render_invoke (render);
1399
1400  g_free (bitmap.buffer);
1401  g_free (string);
1402
1403  state->text_offset += line_ink_rect.width;
1404}
1405
1406static void
1407rsvg_start_tspan (RsvgHandle *ctx, const xmlChar **atts)
1408{
1409  int i;
1410  double affine[6] ;
1411  double x, y, dx, dy;
1412  RsvgState *state;
1413  const char * klazz = NULL;
1414  x = y = dx = dy = 0.;
1415
1416  state = &ctx->state[ctx->n_state - 1];
1417
1418  if (atts != NULL)
1419    {
1420      for (i = 0; atts[i] != NULL; i += 2)
1421        {
1422          if (!strcmp ((char *)atts[i], "x"))
1423            x = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1424          else if (!strcmp ((char *)atts[i], "y"))
1425            y = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
1426          else if (!strcmp ((char *)atts[i], "dx"))
1427            dx = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1428          else if (!strcmp ((char *)atts[i], "dy"))
1429            dy = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
1430          else if (!strcmp ((char *)atts[i], "class"))
1431            klazz = (const char *)atts[i + 1];
1432        }
1433    }
1434 
1435  /* todo: transform() is illegal here */
1436  x += dx ;
1437  y += dy ;
1438 
1439  if (x > 0 && y > 0)
1440    {
1441      art_affine_translate (affine, x, y);
1442      art_affine_multiply (state->affine, affine, state->affine);
1443    }
1444  rsvg_parse_style_attrs (ctx, "tspan", klazz, atts);
1445}
1446
1447static void
1448rsvg_text_handler_start (RsvgSaxHandler *self, const xmlChar *name,
1449                         const xmlChar **atts)
1450{
1451  RsvgSaxHandlerText *z = (RsvgSaxHandlerText *)self;
1452  RsvgHandle *ctx = z->ctx;
1453
1454  /* push the state stack */
1455  if (ctx->n_state == ctx->n_state_max)
1456    ctx->state = g_renew (RsvgState, ctx->state, ctx->n_state_max <<= 1);
1457  if (ctx->n_state)
1458    rsvg_state_clone (&ctx->state[ctx->n_state],
1459                      &ctx->state[ctx->n_state - 1]);
1460  else
1461    rsvg_state_init (ctx->state);
1462  ctx->n_state++;
1463 
1464  /* this should be the only thing starting inside of text */
1465  if (!strcmp ((char *)name, "tspan"))
1466    rsvg_start_tspan (ctx, atts);
1467}
1468
1469static void
1470rsvg_text_handler_end (RsvgSaxHandler *self, const xmlChar *name)
1471{
1472  RsvgSaxHandlerText *z = (RsvgSaxHandlerText *)self;
1473  RsvgHandle *ctx = z->ctx;
1474
1475  if (!strcmp ((char *)name, "tspan"))
1476    {
1477      /* advance the text offset */
1478      RsvgState *tspan = &ctx->state[ctx->n_state - 1];
1479      RsvgState *text  = &ctx->state[ctx->n_state - 2];
1480      text->text_offset += (tspan->text_offset - text->text_offset);
1481    }
1482  else if (!strcmp ((char *)name, "text"))
1483    {
1484      if (ctx->handler != NULL)
1485        {
1486          ctx->handler->free (ctx->handler);
1487          ctx->handler = z->parent;
1488        }
1489    }
1490
1491  /* pop the state stack */
1492  ctx->n_state--;
1493  rsvg_state_finalize (&ctx->state[ctx->n_state]);
1494}
1495
1496static void
1497rsvg_start_text (RsvgHandle *ctx, const xmlChar **atts)
1498{
1499  int i;
1500  double affine[6] ;
1501  double x, y, dx, dy;
1502  const char * klazz = NULL;
1503  RsvgState *state;
1504
1505  RsvgSaxHandlerText *handler = g_new0 (RsvgSaxHandlerText, 1);
1506  handler->super.free = rsvg_text_handler_free;
1507  handler->super.characters = rsvg_text_handler_characters;
1508  handler->super.start_element = rsvg_text_handler_start;
1509  handler->super.end_element   = rsvg_text_handler_end;
1510  handler->ctx = ctx;
1511
1512  x = y = dx = dy = 0.;
1513
1514  state = &ctx->state[ctx->n_state - 1];
1515
1516  if (atts != NULL)
1517    {
1518      for (i = 0; atts[i] != NULL; i += 2)
1519        {
1520          if (!strcmp ((char *)atts[i], "x"))
1521            x = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1522          else if (!strcmp ((char *)atts[i], "y"))
1523            y = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
1524          else if (!strcmp ((char *)atts[i], "dx"))
1525            dx = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1526          else if (!strcmp ((char *)atts[i], "dy"))
1527            dy = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
1528          else if (!strcmp ((char *)atts[i], "class"))
1529            klazz = (const char *)atts[i + 1];
1530        }
1531    }
1532
1533  x += dx ;
1534  y += dy ;
1535 
1536  art_affine_translate (affine, x, y);
1537  art_affine_multiply (state->affine, affine, state->affine);
1538 
1539  rsvg_parse_style_attrs (ctx, "text", klazz, atts);
1540
1541  handler->parent = ctx->handler;
1542  ctx->handler = &handler->super;
1543}
1544
1545/* end text */
1546
1547typedef struct _RsvgSaxHandlerDefs {
1548  RsvgSaxHandler super;
1549  RsvgHandle *ctx;
1550} RsvgSaxHandlerDefs;
1551
1552typedef struct _RsvgSaxHandlerStyle {
1553  RsvgSaxHandler super;
1554  RsvgSaxHandlerDefs *parent;
1555  RsvgHandle *ctx;
1556  GString *style;
1557} RsvgSaxHandlerStyle;
1558
1559typedef struct _RsvgSaxHandlerGstops {
1560  RsvgSaxHandler super;
1561  RsvgSaxHandlerDefs *parent;
1562  RsvgHandle *ctx;
1563  RsvgGradientStops *stops;
1564  const char * parent_tag;
1565} RsvgSaxHandlerGstops;
1566
1567static void
1568rsvg_gradient_stop_handler_free (RsvgSaxHandler *self)
1569{
1570  g_free (self);
1571}
1572
1573static void
1574rsvg_gradient_stop_handler_start (RsvgSaxHandler *self, const xmlChar *name,
1575                                  const xmlChar **atts)
1576{
1577  RsvgSaxHandlerGstops *z = (RsvgSaxHandlerGstops *)self;
1578  RsvgGradientStops *stops = z->stops;
1579  int i;
1580  double offset = 0;
1581  gboolean got_offset = FALSE;
1582  RsvgState state;
1583  int n_stop;
1584
1585  if (strcmp ((char *)name, "stop"))
1586    {
1587      g_warning ("unexpected <%s> element in gradient\n", name);
1588      return;
1589    }
1590
1591  rsvg_state_init (&state);
1592
1593  if (atts != NULL)
1594    {
1595      for (i = 0; atts[i] != NULL; i += 2)
1596        {
1597          if (!strcmp ((char *)atts[i], "offset"))
1598            {
1599              /* either a number [0,1] or a percentage */
1600              offset = rsvg_css_parse_normalized_length ((char *)atts[i + 1], z->ctx->dpi, 1., 0., 0.);
1601
1602              if (offset < 0.)
1603                offset = 0.;
1604              else if (offset > 1.)
1605                offset = 1.;
1606
1607              got_offset = TRUE;
1608            }
1609          else if (!strcmp ((char *)atts[i], "style"))
1610            rsvg_parse_style (z->ctx, &state, (char *)atts[i + 1]);
1611          else if (rsvg_is_style_arg ((char *)atts[i]))
1612            rsvg_parse_style_pair (z->ctx, &state,
1613                                   (char *)atts[i], (char *)atts[i + 1]);
1614        }
1615    }
1616
1617  rsvg_state_finalize (&state);
1618
1619  if (!got_offset)
1620    {
1621      g_warning ("gradient stop must specify offset\n");
1622      return;
1623    }
1624
1625  n_stop = stops->n_stop++;
1626  if (n_stop == 0)
1627    stops->stop = g_new (RsvgGradientStop, 1);
1628  else if (!(n_stop & (n_stop - 1)))
1629    /* double the allocation if size is a power of two */
1630    stops->stop = g_renew (RsvgGradientStop, stops->stop, n_stop << 1);
1631  stops->stop[n_stop].offset = offset;
1632  stops->stop[n_stop].rgba = (state.stop_color << 8) | state.stop_opacity;
1633}
1634
1635static void
1636rsvg_gradient_stop_handler_end (RsvgSaxHandler *self, const xmlChar *name)
1637{
1638  RsvgSaxHandlerGstops *z = (RsvgSaxHandlerGstops *)self;
1639  RsvgHandle *ctx = z->ctx;
1640
1641  if (!strcmp((char *)name, z->parent_tag))
1642    {
1643      if (ctx->handler != NULL)
1644        {
1645          ctx->handler->free (ctx->handler);
1646          ctx->handler = &z->parent->super;
1647        }
1648    }
1649}
1650
1651static RsvgSaxHandler *
1652rsvg_gradient_stop_handler_new_clone (RsvgHandle *ctx, RsvgGradientStops *stops,
1653                                      const char * parent)
1654{
1655  RsvgSaxHandlerGstops *gstops = g_new0 (RsvgSaxHandlerGstops, 1);
1656
1657  gstops->super.free = rsvg_gradient_stop_handler_free;
1658  gstops->super.start_element = rsvg_gradient_stop_handler_start;
1659  gstops->super.end_element = rsvg_gradient_stop_handler_end;
1660  gstops->ctx = ctx;
1661  gstops->stops = stops;
1662  gstops->parent_tag = parent;
1663
1664  gstops->parent = (RsvgSaxHandlerDefs*)ctx->handler;
1665  return &gstops->super;
1666}
1667
1668static RsvgSaxHandler *
1669rsvg_gradient_stop_handler_new (RsvgHandle *ctx, RsvgGradientStops **p_stops,
1670                                const char * parent)
1671{
1672  RsvgSaxHandlerGstops *gstops = g_new0 (RsvgSaxHandlerGstops, 1);
1673  RsvgGradientStops *stops = g_new (RsvgGradientStops, 1);
1674
1675  gstops->super.free = rsvg_gradient_stop_handler_free;
1676  gstops->super.start_element = rsvg_gradient_stop_handler_start;
1677  gstops->super.end_element = rsvg_gradient_stop_handler_end;
1678  gstops->ctx = ctx;
1679  gstops->stops = stops;
1680  gstops->parent_tag = parent;
1681
1682  stops->n_stop = 0;
1683  stops->stop = NULL;
1684
1685  gstops->parent = (RsvgSaxHandlerDefs*)ctx->handler;
1686  *p_stops = stops;
1687  return &gstops->super;
1688}
1689
1690static void
1691rsvg_linear_gradient_free (RsvgDefVal *self)
1692{
1693  RsvgLinearGradient *z = (RsvgLinearGradient *)self;
1694
1695  g_free (z->stops->stop);
1696  g_free (z->stops);
1697  g_free (self);
1698}
1699
1700static void
1701rsvg_start_linear_gradient (RsvgHandle *ctx, const xmlChar **atts)
1702{
1703  RsvgState *state = &ctx->state[ctx->n_state - 1];
1704  RsvgLinearGradient *grad = NULL;
1705  int i;
1706  char *id = NULL;
1707  double x1 = 0., y1 = 0., x2 = 0., y2 = 0.;
1708  ArtGradientSpread spread = ART_GRADIENT_PAD;
1709  const char * xlink_href = NULL;
1710  gboolean got_x1, got_x2, got_y1, got_y2, got_spread, cloned;
1711
1712  got_x1 = got_x2 = got_y1 = got_y2 = got_spread = cloned = FALSE;
1713
1714  /* 100% is the default */
1715  x2 = rsvg_css_parse_normalized_length ("100%", ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1716
1717  /* todo: only handles numeric coordinates in gradientUnits = userSpace */
1718  if (atts != NULL)
1719    {
1720      for (i = 0; atts[i] != NULL; i += 2)
1721        {
1722          if (!strcmp ((char *)atts[i], "id"))
1723            id = (char *)atts[i + 1];
1724          else if (!strcmp ((char *)atts[i], "x1"))
1725            x1 = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1726          else if (!strcmp ((char *)atts[i], "y1"))
1727            y1 = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
1728          else if (!strcmp ((char *)atts[i], "x2"))
1729            x2 = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1730          else if (!strcmp ((char *)atts[i], "y2"))
1731            y2 = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
1732          else if (!strcmp ((char *)atts[i], "spreadMethod"))
1733            {
1734              if (!strcmp ((char *)atts[i + 1], "pad"))
1735                spread = ART_GRADIENT_PAD;
1736              else if (!strcmp ((char *)atts[i + 1], "reflect"))
1737                spread = ART_GRADIENT_REFLECT;
1738              else if (!strcmp ((char *)atts[i + 1], "repeat"))
1739                spread = ART_GRADIENT_REPEAT;
1740            }
1741          else if (!strcmp ((char *)atts[i], "xlink:href"))
1742            xlink_href = (const char *)atts[i + 1];
1743        }
1744    }
1745 
1746  if (xlink_href != NULL)
1747    {
1748      RsvgLinearGradient * parent = (RsvgLinearGradient*)rsvg_defs_lookup (ctx->defs, xlink_href+1);
1749      if (parent != NULL)
1750        {
1751          cloned = TRUE;
1752          grad = rsvg_clone_linear_gradient (parent);
1753          ctx->handler = rsvg_gradient_stop_handler_new_clone (ctx, grad->stops, "linearGradient");
1754        }
1755    }
1756
1757  if (!cloned)
1758    {
1759      grad = g_new (RsvgLinearGradient, 1);
1760      grad->super.type = RSVG_DEF_LINGRAD;
1761      grad->super.free = rsvg_linear_gradient_free;
1762      ctx->handler = rsvg_gradient_stop_handler_new (ctx, &grad->stops, "linearGradient");
1763    }
1764
1765  rsvg_defs_set (ctx->defs, id, &grad->super);
1766
1767  for (i = 0; i < 6; i++)
1768    grad->affine[i] = state->affine[i];
1769
1770  /* state inherits parent/cloned information unless it's explicity gotten */
1771  grad->x1 = (cloned && !got_x1) ? grad->x1 : x1;
1772  grad->y1 = (cloned && !got_y1) ? grad->y1 : y1;
1773  grad->x2 = (cloned && !got_x2) ? grad->x2 : x2;
1774  grad->y2 = (cloned && !got_y2) ? grad->y1 : y2;
1775  grad->spread = (cloned && !got_spread) ? grad->spread : spread;
1776}
1777
1778static void
1779rsvg_radial_gradient_free (RsvgDefVal *self)
1780{
1781  RsvgRadialGradient *z = (RsvgRadialGradient *)self;
1782
1783  g_free (z->stops->stop);
1784  g_free (z->stops);
1785  g_free (self);
1786}
1787
1788static void
1789rsvg_start_radial_gradient (RsvgHandle *ctx, const xmlChar **atts)
1790{
1791  RsvgState *state = &ctx->state[ctx->n_state - 1];
1792  RsvgRadialGradient *grad = NULL;
1793  int i;
1794  char *id = NULL;
1795  double cx = 0., cy = 0., r = 0., fx = 0., fy = 0.; 
1796  const char * xlink_href = NULL;
1797  gboolean got_cx, got_cy, got_r, got_fx, got_fy, cloned;
1798
1799  got_cx = got_cy = got_r = got_fx = got_fy = cloned = FALSE;
1800
1801  /* setup defaults */
1802  cx = rsvg_css_parse_normalized_length ("50%", ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1803  cy = rsvg_css_parse_normalized_length ("50%", ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
1804  r  = rsvg_css_parse_normalized_length ("50%", ctx->dpi, rsvg_viewport_percentage((gdouble)ctx->width, (gdouble)ctx->height), state->font_size, 0.);
1805
1806  /* todo: only handles numeric coordinates in gradientUnits = userSpace */
1807  if (atts != NULL)
1808    {
1809      for (i = 0; atts[i] != NULL; i += 2)
1810        {
1811          if (!strcmp ((char *)atts[i], "id"))
1812            id = (char *)atts[i + 1];
1813          else if (!strcmp ((char *)atts[i], "cx")) {
1814            cx = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1815            got_cx = TRUE;
1816          }
1817          else if (!strcmp ((char *)atts[i], "cy")) {
1818            cy = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
1819            got_cy = TRUE;
1820          }
1821          else if (!strcmp ((char *)atts[i], "r")) {
1822            r = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi,
1823                                                  rsvg_viewport_percentage((gdouble)ctx->width, (gdouble)ctx->height),
1824                                                  state->font_size, 0.);
1825            got_r = TRUE;
1826          }
1827          else if (!strcmp ((char *)atts[i], "fx")) {
1828            fx = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
1829            got_fx = TRUE;
1830          }
1831          else if (!strcmp ((char *)atts[i], "fy")) {
1832            fy = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
1833            got_fy = TRUE;
1834          }
1835          else if (!strcmp ((char *)atts[i], "xlink:href"))
1836            xlink_href = (const char *)atts[i + 1];
1837        }
1838    }
1839
1840  if (xlink_href != NULL)
1841    {
1842      RsvgRadialGradient * parent = (RsvgRadialGradient*)rsvg_defs_lookup (ctx->defs, xlink_href+1);
1843      if (parent != NULL)
1844        {
1845          cloned = TRUE;
1846          grad = rsvg_clone_radial_gradient (parent);
1847          ctx->handler = rsvg_gradient_stop_handler_new_clone (ctx, grad->stops, "radialGradient");
1848        }
1849    }
1850  if (!cloned)
1851    {
1852      grad = g_new (RsvgRadialGradient, 1);
1853      grad->super.type = RSVG_DEF_RADGRAD;
1854      grad->super.free = rsvg_radial_gradient_free;
1855      ctx->handler = rsvg_gradient_stop_handler_new (ctx, &grad->stops, "radialGradient");
1856
1857      if (!got_fx)
1858        fx = cx;
1859      if (!got_fy)
1860        fy = cy;
1861    }
1862
1863  rsvg_defs_set (ctx->defs, id, &grad->super);
1864
1865  for (i = 0; i < 6; i++)
1866    grad->affine[i] = state->affine[i];
1867
1868  /* state inherits parent/cloned information unless it's explicity gotten */
1869  grad->cx = (cloned && !got_cx) ? grad->cx: cx;
1870  grad->cy = (cloned && !got_cy) ? grad->cy: cy;
1871  grad->r =  (cloned && !got_r) ? grad->r : r;
1872  grad->fx = (cloned && !got_fx) ? grad->fx : fx;
1873  grad->fy = (cloned && !got_fy) ? grad->fy : fy;
1874}
1875
1876/* end gradients */
1877
1878static void
1879rsvg_style_handler_free (RsvgSaxHandler *self)
1880{
1881  RsvgSaxHandlerStyle *z = (RsvgSaxHandlerStyle *)self;
1882  RsvgHandle *ctx = z->ctx;
1883
1884  rsvg_parse_cssbuffer (ctx, z->style->str, z->style->len);
1885
1886  g_string_free (z->style, TRUE);
1887  g_free (z);
1888}
1889
1890static void
1891rsvg_style_handler_characters (RsvgSaxHandler *self, const xmlChar *ch, int len)
1892{
1893  RsvgSaxHandlerStyle *z = (RsvgSaxHandlerStyle *)self;
1894  g_string_append_len (z->style, (const char *)ch, len);
1895}
1896
1897static void
1898rsvg_style_handler_start (RsvgSaxHandler *self, const xmlChar *name,
1899                          const xmlChar **atts)
1900{
1901}
1902
1903static void
1904rsvg_style_handler_end (RsvgSaxHandler *self, const xmlChar *name)
1905{
1906  RsvgSaxHandlerStyle *z = (RsvgSaxHandlerStyle *)self;
1907  RsvgHandle *ctx = z->ctx;
1908 
1909  if (!strcmp ((char *)name, "style"))
1910  {
1911    if (ctx->handler != NULL)
1912      {
1913        ctx->handler->free (ctx->handler);
1914        ctx->handler = &z->parent->super;
1915      }
1916  }
1917}
1918
1919static void
1920rsvg_start_style (RsvgHandle *ctx, const xmlChar **atts)
1921{
1922  RsvgSaxHandlerStyle *handler = g_new0 (RsvgSaxHandlerStyle, 1);
1923
1924  handler->super.free = rsvg_style_handler_free;
1925  handler->super.characters = rsvg_style_handler_characters;
1926  handler->super.start_element = rsvg_style_handler_start;
1927  handler->super.end_element   = rsvg_style_handler_end;
1928  handler->ctx = ctx;
1929
1930  handler->style = g_string_new (NULL);
1931
1932  handler->parent = (RsvgSaxHandlerDefs*)ctx->handler;
1933  ctx->handler = &handler->super;
1934}
1935
1936/* */
1937
1938static void
1939rsvg_defs_handler_free (RsvgSaxHandler *self)
1940{
1941  g_free (self);
1942}
1943
1944static void
1945rsvg_defs_handler_characters (RsvgSaxHandler *self, const xmlChar *ch, int len)
1946{
1947}
1948
1949static void
1950rsvg_defs_handler_start (RsvgSaxHandler *self, const xmlChar *name,
1951                         const xmlChar **atts)
1952{
1953  RsvgSaxHandlerDefs *z = (RsvgSaxHandlerDefs *)self;
1954  RsvgHandle *ctx = z->ctx;
1955
1956  /* push the state stack */
1957  if (ctx->n_state == ctx->n_state_max)
1958    ctx->state = g_renew (RsvgState, ctx->state, ctx->n_state_max <<= 1);
1959  if (ctx->n_state)
1960    rsvg_state_clone (&ctx->state[ctx->n_state],
1961                      &ctx->state[ctx->n_state - 1]);
1962  else
1963    rsvg_state_init (ctx->state);
1964  ctx->n_state++;
1965 
1966  if (!strcmp ((char *)name, "linearGradient"))
1967    rsvg_start_linear_gradient (ctx, atts);
1968  else if (!strcmp ((char *)name, "radialGradient"))
1969    rsvg_start_radial_gradient (ctx, atts);
1970  else if (!strcmp ((char *)name, "style"))
1971    rsvg_start_style (ctx, atts);
1972}
1973
1974static void
1975rsvg_defs_handler_end (RsvgSaxHandler *self, const xmlChar *name)
1976{
1977  RsvgSaxHandlerDefs *z = (RsvgSaxHandlerDefs *)self;
1978  RsvgHandle *ctx = z->ctx;
1979
1980  if (!strcmp((char *)name, "defs"))
1981    {
1982      if (ctx->handler != NULL)
1983        {
1984          ctx->handler->free (ctx->handler);
1985          ctx->handler = NULL;
1986        }
1987    }
1988
1989  /* pop the state stack */
1990  ctx->n_state--;
1991  rsvg_state_finalize (&ctx->state[ctx->n_state]);
1992}
1993
1994static void
1995rsvg_start_defs (RsvgHandle *ctx, const xmlChar **atts)
1996{
1997  RsvgSaxHandlerDefs *handler = g_new0 (RsvgSaxHandlerDefs, 1);
1998
1999  handler->super.free = rsvg_defs_handler_free;
2000  handler->super.characters = rsvg_defs_handler_characters;
2001  handler->super.start_element = rsvg_defs_handler_start;
2002  handler->super.end_element   = rsvg_defs_handler_end;
2003  handler->ctx = ctx;
2004
2005  ctx->handler = &handler->super;
2006}
2007
2008/* end defs */
2009
2010static GString *
2011rsvg_make_poly_point_list(const char * points)
2012{
2013  guint idx = 0, size = strlen(points);
2014  GString * str = g_string_sized_new (size);
2015       
2016  while (idx < size)
2017    {
2018      /* scan for first point */
2019      while (!g_ascii_isdigit (points[idx]) && (points[idx] != '.')
2020             && (points[idx] != '-') && (idx < size))
2021        idx++;
2022     
2023      /* now build up the point list (everything until next letter!) */
2024      if (idx < size && points[idx] == '-')
2025        g_string_append_c (str, points[idx++]); /* handle leading '-' */
2026      while ((g_ascii_isdigit (points[idx]) || (points[idx] == '.')) && (idx < size))
2027          g_string_append_c (str, points[idx++]);
2028
2029      g_string_append_c (str, ' ');
2030    }
2031
2032  return str;
2033}
2034
2035static void
2036rsvg_start_any_poly(RsvgHandle *ctx, const xmlChar **atts, gboolean is_polyline)
2037{
2038  /* the only difference i'm making between polygon and polyline is
2039     that a polyline closes the path */
2040
2041  int i;
2042  const char * verts = (const char *)NULL;
2043  GString * g = NULL;
2044  gchar ** pointlist = NULL;
2045  const char * klazz = NULL;
2046
2047  if (atts != NULL)
2048    {
2049      for (i = 0; atts[i] != NULL; i += 2)
2050        {
2051          /* support for svg < 1.0 which used verts */
2052          if (!strcmp ((char *)atts[i], "verts") || !strcmp ((char *)atts[i], "points"))
2053            verts = (const char *)atts[i + 1];
2054          else if (!strcmp ((char *)atts[i], "class"))
2055            klazz = (const char *)atts[i + 1];
2056        }
2057    }
2058
2059  if (!verts)
2060    return;
2061
2062  rsvg_parse_style_attrs (ctx, (is_polyline ? "polyline" : "polygon"), klazz, atts);
2063
2064  /* todo: make the following more memory and CPU friendly */
2065  g = rsvg_make_poly_point_list (verts);
2066  pointlist = g_strsplit (g->str, " ", -1);
2067  g_string_free (g, TRUE);
2068
2069  /* represent as a "moveto, lineto*, close" path */ 
2070  if (pointlist)
2071    {
2072      GString * d = g_string_sized_new (strlen(verts));
2073      g_string_append_printf (d, "M %s %s ", pointlist[0], pointlist[1] );
2074
2075      for (i = 2; pointlist[i] != NULL && pointlist[i][0] != '\0'; i += 2)
2076          g_string_append_printf (d, "L %s %s ", pointlist[i], pointlist[i+1]);
2077
2078      if (!is_polyline)
2079        g_string_append (d, "Z");
2080
2081      rsvg_render_path (ctx, d->str);
2082      g_string_free (d, TRUE);
2083      g_strfreev(pointlist);
2084    }
2085}
2086
2087static void
2088rsvg_start_polygon (RsvgHandle *ctx, const xmlChar **atts)
2089{
2090  rsvg_start_any_poly (ctx, atts, FALSE);
2091}
2092
2093static void
2094rsvg_start_polyline (RsvgHandle *ctx, const xmlChar **atts)
2095{
2096  rsvg_start_any_poly (ctx, atts, TRUE);
2097}
2098
2099/* TODO 1: issue with affining alpha images - this is gdkpixbuf's fault...
2100 * TODO 2: issue with rotating images - do we want to rotate the whole
2101 *         canvas 2x to get this right, only to have #1 bite us?
2102 */
2103static void
2104rsvg_start_image (RsvgHandle *ctx, const xmlChar **atts)
2105{
2106  int i;
2107  double x = 0., y = 0., w = -1., h = -1.;
2108  const char * href = NULL;
2109  const char * klazz = NULL;
2110
2111  GdkPixbuf *img;
2112  GError *err = NULL;
2113
2114  gboolean has_alpha;
2115  guchar *rgb = NULL;
2116  int dest_rowstride;
2117  double tmp_affine[6];
2118  RsvgState *state = &ctx->state[ctx->n_state - 1];
2119
2120  if (atts != NULL)
2121    {
2122      for (i = 0; atts[i] != NULL; i += 2)
2123        {
2124          if (!strcmp ((char *)atts[i], "x"))
2125            x = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2126          else if (!strcmp ((char *)atts[i], "y"))
2127            y = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2128          else if (!strcmp ((char *)atts[i], "width"))
2129            w = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2130          else if (!strcmp ((char *)atts[i], "height"))
2131            h = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2132          /* path is used by some older adobe illustrator versions */
2133          else if (!strcmp ((char *)atts[i], "path") || !strcmp((char *)atts[i], "xlink:href"))
2134            href = (const char *)atts[i + 1];
2135          else if (!strcmp ((char *)atts[i], "class"))
2136            klazz = (const char *)atts[i + 1];
2137        }
2138    }
2139
2140  if (!href || x < 0. || y < 0. || w <= 0. || h <= 0.)
2141    return;
2142 
2143  rsvg_parse_style_attrs (ctx, "image", klazz, atts);
2144
2145  img = gdk_pixbuf_new_from_file (href, &err);
2146 
2147  if (!img)
2148    {
2149      if (err)
2150        {
2151          g_warning ("Couldn't load pixbuf (%s): %s\n", href, err->message);
2152          g_error_free (err);
2153        }
2154      return;
2155    }
2156
2157  /* scale/resize the dest image */
2158  art_affine_scale (tmp_affine, (double)w / (double)gdk_pixbuf_get_width (img),
2159                    (double)h / (double)gdk_pixbuf_get_height (img));
2160  art_affine_multiply (state->affine, tmp_affine, state->affine);
2161
2162  has_alpha = gdk_pixbuf_get_has_alpha (img);
2163  dest_rowstride = (int)(w * (has_alpha ? 4 : 3) + 3) & ~3;
2164  rgb = g_new (guchar, h * dest_rowstride);
2165
2166  if(has_alpha)
2167    art_rgb_rgba_affine (rgb, 0, 0, w, h, dest_rowstride,
2168                         gdk_pixbuf_get_pixels (img),
2169                         gdk_pixbuf_get_width (img),
2170                         gdk_pixbuf_get_height (img),
2171                         gdk_pixbuf_get_rowstride (img),
2172                         state->affine,
2173                         ART_FILTER_NEAREST,
2174                         NULL);
2175  else
2176    art_rgb_affine (rgb, 0, 0, w, h, dest_rowstride,
2177                    gdk_pixbuf_get_pixels (img),
2178                    gdk_pixbuf_get_width (img),
2179                    gdk_pixbuf_get_height (img),
2180                    gdk_pixbuf_get_rowstride (img),
2181                    state->affine,
2182                    ART_FILTER_NEAREST,
2183                    NULL);
2184
2185  g_object_unref (G_OBJECT (img));
2186  img = gdk_pixbuf_new_from_data (rgb, GDK_COLORSPACE_RGB, has_alpha, 8, w, h, dest_rowstride, NULL, NULL);
2187
2188  if (!img)
2189    {
2190      g_free (rgb);
2191      return;
2192    }
2193
2194  gdk_pixbuf_copy_area (img, 0, 0,
2195                        gdk_pixbuf_get_width (img) * state->affine[0],
2196                        gdk_pixbuf_get_height (img) * state->affine[3],
2197                        ctx->pixbuf,
2198                        state->affine[4] + x,
2199                        state->affine[5] + y);
2200 
2201  g_object_unref (G_OBJECT (img));
2202  g_free (rgb);
2203}
2204
2205static void
2206rsvg_start_line (RsvgHandle *ctx, const xmlChar **atts)
2207{
2208  int i;
2209  double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
2210  char * d = NULL;
2211  const char * klazz = NULL;
2212  RsvgState *state = &ctx->state[ctx->n_state - 1];
2213
2214  if (atts != NULL)
2215    {
2216      for (i = 0; atts[i] != NULL; i += 2)
2217        {
2218          if (!strcmp ((char *)atts[i], "x1"))
2219            x1 = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2220          else if (!strcmp ((char *)atts[i], "y1"))
2221            y1 = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2222          if (!strcmp ((char *)atts[i], "x2"))
2223            x2 = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2224          else if (!strcmp ((char *)atts[i], "y2"))
2225            y2 = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2226          else if (!strcmp ((char *)atts[i], "class"))
2227            klazz = (const char *)atts[i + 1];
2228        }     
2229    }
2230  rsvg_parse_style_attrs (ctx, "line", klazz, atts);
2231
2232  /* emulate a line using a path */
2233  d = g_strdup_printf ("M %f %f L %f %f", x1, y1, x2, y2);
2234
2235  rsvg_render_path (ctx, d);
2236  g_free (d);
2237}
2238
2239static void
2240rsvg_start_rect (RsvgHandle *ctx, const xmlChar **atts)
2241{
2242  int i;
2243  double x = -1, y = -1, w = -1, h = -1, rx = 0, ry = 0;
2244  char * d = NULL;
2245  const char * klazz = NULL;
2246  RsvgState *state = &ctx->state[ctx->n_state - 1];
2247 
2248  if (atts != NULL)
2249    {
2250      for (i = 0; atts[i] != NULL; i += 2)
2251        {
2252          if (!strcmp ((char *)atts[i], "x"))
2253            x = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2254          else if (!strcmp ((char *)atts[i], "y"))
2255            y = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2256          else if (!strcmp ((char *)atts[i], "width"))
2257            w = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2258          else if (!strcmp ((char *)atts[i], "height"))
2259            h = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2260          else if (!strcmp ((char *)atts[i], "rx"))
2261            rx = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2262          else if (!strcmp ((char *)atts[i], "ry"))
2263            ry = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2264          else if (!strcmp ((char *)atts[i], "class"))
2265            klazz = (const char *)atts[i + 1];
2266        }
2267    }
2268
2269  if (x < 0. || y < 0. || w < 0. || h < 0. || rx < 0. || ry < 0.)
2270    return;
2271
2272  rsvg_parse_style_attrs (ctx, "rect", klazz, atts);
2273
2274  /* incrementing y by 1 properly draws borders. this is a HACK */
2275  y++;
2276
2277  /* emulate a rect using a path */
2278  d = g_strdup_printf ("M %f %f "
2279                       "H %f "
2280                       "A %f,%f %f,%f %f %f,%f "
2281                       "V %f "
2282                       "A %f,%f %f,%f %f %f,%f "
2283                       "H %f "
2284                       "A %f,%f %f,%f %f %f,%f "
2285                       "V %f "
2286                       "A %f,%f %f,%f %f %f,%f",
2287                       x + rx, y,
2288                       x + w - rx,
2289                       rx, ry, 0., 0., 1., x + w, y + ry,
2290                       y + h - ry,
2291                       rx, ry, 0., 0., 1., x + w - rx, y + h,
2292                       x + rx,
2293                       rx, ry, 0., 0., 1., x, y + h - ry,
2294                       y + ry,
2295                       rx, ry, 0., 0., 1., x + rx, y);
2296
2297  rsvg_render_path (ctx, d);
2298  g_free (d);
2299}
2300
2301static void
2302rsvg_start_circle (RsvgHandle *ctx, const xmlChar **atts)
2303{
2304  int i;
2305  double cx = 0, cy = 0, r = 0;
2306  char * d = NULL;
2307  const char * klazz = NULL;
2308  RsvgState *state = &ctx->state[ctx->n_state - 1];
2309 
2310  if (atts != NULL)
2311    {
2312      for (i = 0; atts[i] != NULL; i += 2)
2313        {
2314          if (!strcmp ((char *)atts[i], "cx"))
2315            cx = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2316          else if (!strcmp ((char *)atts[i], "cy"))
2317            cy = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2318          else if (!strcmp ((char *)atts[i], "r"))
2319            r = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi,
2320                                                  rsvg_viewport_percentage((gdouble)ctx->width, (gdouble)ctx->height),
2321                                                  state->font_size, 0.);
2322          else if (!strcmp ((char *)atts[i], "class"))
2323            klazz = (const char *)atts[i + 1];
2324        }
2325    }
2326
2327  if (cx < 0. || cy < 0. || r <= 0.)
2328    return;
2329
2330  rsvg_parse_style_attrs (ctx, "circle", klazz, atts);
2331
2332  /* approximate a circle using 4 bezier curves */
2333  d = g_strdup_printf ("M %f %f "
2334                       "C %f %f %f %f %f %f "
2335                       "C %f %f %f %f %f %f "
2336                       "C %f %f %f %f %f %f "
2337                       "C %f %f %f %f %f %f "
2338                       "Z",
2339                       cx + r, cy,
2340                       cx + r, cy + r * RSVG_ARC_MAGIC, cx + r * RSVG_ARC_MAGIC, cy + r, cx, cy + r,
2341                       cx - r * RSVG_ARC_MAGIC, cy + r, cx - r, cy + r * RSVG_ARC_MAGIC, cx - r, cy,
2342                       cx - r, cy - r * RSVG_ARC_MAGIC, cx - r * RSVG_ARC_MAGIC, cy - r, cx, cy - r,
2343                       cx + r * RSVG_ARC_MAGIC, cy - r, cx + r, cy - r * RSVG_ARC_MAGIC, cx + r, cy
2344                       );
2345
2346  rsvg_render_path (ctx, d);
2347  g_free (d);
2348}
2349
2350static void
2351rsvg_start_ellipse (RsvgHandle *ctx, const xmlChar **atts)
2352{
2353  int i;
2354  double cx = 0, cy = 0, rx = 0, ry = 0;
2355  char * d = NULL;
2356  const char * klazz = NULL;
2357  RsvgState *state = &ctx->state[ctx->n_state - 1];
2358
2359  if (atts != NULL)
2360    {
2361      for (i = 0; atts[i] != NULL; i += 2)
2362        {
2363          if (!strcmp ((char *)atts[i], "cx"))
2364            cx = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2365          else if (!strcmp ((char *)atts[i], "cy"))
2366            cy = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2367          else if (!strcmp ((char *)atts[i], "rx"))
2368            rx = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->width, state->font_size, 0.);
2369          else if (!strcmp ((char *)atts[i], "ry"))
2370            ry = rsvg_css_parse_normalized_length ((char *)atts[i + 1], ctx->dpi, (gdouble)ctx->height, state->font_size, 0.);
2371          else if (!strcmp ((char *)atts[i], "class"))
2372            klazz = (const char *)atts[i + 1];
2373        }
2374    }
2375
2376  if (cx < 0. || cy < 0. || rx <= 0. || ry <= 0.)
2377    return;
2378
2379  rsvg_parse_style_attrs (ctx, "ellipse", klazz, atts);
2380
2381  /* approximate an ellipse using 4 bezier curves */
2382  d = g_strdup_printf ("M %f %f "
2383                       "C %f %f %f %f %f %f "
2384                       "C %f %f %f %f %f %f "
2385                       "C %f %f %f %f %f %f "
2386                       "C %f %f %f %f %f %f "
2387                       "Z",
2388                       cx + rx, cy,
2389                       cx + rx, cy - RSVG_ARC_MAGIC * ry, cx + RSVG_ARC_MAGIC * rx, cy - ry, cx, cy - ry,
2390                       cx - RSVG_ARC_MAGIC * rx, cy - ry, cx - rx, cy - RSVG_ARC_MAGIC * ry, cx - rx, cy,
2391                       cx - rx, cy + RSVG_ARC_MAGIC * ry, cx - RSVG_ARC_MAGIC * rx, cy + ry, cx, cy + ry,
2392                       cx + RSVG_ARC_MAGIC * rx, cy + ry, cx + rx, cy + RSVG_ARC_MAGIC * ry, cx + rx, cy
2393                       );
2394
2395  rsvg_render_path (ctx, d);
2396  g_free (d);
2397
2398  return;
2399}
2400
2401static void
2402rsvg_start_element (void *data, const xmlChar *name, const xmlChar **atts)
2403{
2404  RsvgHandle *ctx = (RsvgHandle *)data;
2405
2406  if (ctx->handler)
2407    {
2408      ctx->handler_nest++;
2409      if (ctx->handler->start_element != NULL)
2410        ctx->handler->start_element (ctx->handler, name, atts);
2411    }
2412  else
2413    {
2414      /* push the state stack */
2415      if (ctx->n_state == ctx->n_state_max)
2416        ctx->state = g_renew (RsvgState, ctx->state, ctx->n_state_max <<= 1);
2417      if (ctx->n_state)
2418        rsvg_state_clone (&ctx->state[ctx->n_state],
2419                          &ctx->state[ctx->n_state - 1]);
2420      else
2421        rsvg_state_init (ctx->state);
2422      ctx->n_state++;
2423
2424      if (!strcmp ((char *)name, "svg"))
2425        rsvg_start_svg (ctx, atts);
2426      else if (!strcmp ((char *)name, "g"))
2427        rsvg_start_g (ctx, atts);
2428      else if (!strcmp ((char *)name, "path"))
2429        rsvg_start_path (ctx, atts);
2430      else if (!strcmp ((char *)name, "text"))
2431        rsvg_start_text (ctx, atts);
2432      else if (!strcmp ((char *)name, "image"))
2433        rsvg_start_image (ctx, atts);
2434      else if (!strcmp ((char *)name, "line"))
2435        rsvg_start_line (ctx, atts);
2436      else if (!strcmp ((char *)name, "rect"))
2437        rsvg_start_rect (ctx, atts);
2438      else if (!strcmp ((char *)name, "circle"))
2439        rsvg_start_circle (ctx, atts);
2440      else if (!strcmp ((char *)name, "ellipse"))
2441        rsvg_start_ellipse (ctx, atts);
2442      else if (!strcmp ((char *)name, "defs"))
2443        rsvg_start_defs (ctx, atts);
2444      else if (!strcmp ((char *)name, "polygon"))
2445        rsvg_start_polygon (ctx, atts);
2446      else if (!strcmp ((char *)name, "polyline"))
2447        rsvg_start_polyline (ctx, atts);
2448
2449      /* */
2450      else if (!strcmp ((char *)name, "linearGradient"))
2451        rsvg_start_linear_gradient (ctx, atts);
2452      else if (!strcmp ((char *)name, "radialGradient"))
2453        rsvg_start_radial_gradient (ctx, atts);
2454    }
2455}
2456
2457static void
2458rsvg_end_element (void *data, const xmlChar *name)
2459{
2460  RsvgHandle *ctx = (RsvgHandle *)data;
2461
2462  if (ctx->handler_nest > 0)
2463    {
2464      if (ctx->handler->end_element != NULL)
2465        ctx->handler->end_element (ctx->handler, name);
2466      ctx->handler_nest--;
2467    }
2468  else
2469    {
2470      if (ctx->handler != NULL)
2471        {
2472          ctx->handler->free (ctx->handler);
2473          ctx->handler = NULL;
2474        }
2475
2476      if (!strcmp ((char *)name, "g"))
2477        rsvg_end_g (ctx);
2478
2479      /* pop the state stack */
2480      ctx->n_state--;
2481      rsvg_state_finalize (&ctx->state[ctx->n_state]);
2482    }
2483}
2484
2485static void
2486rsvg_characters (void *data, const xmlChar *ch, int len)
2487{
2488  RsvgHandle *ctx = (RsvgHandle *)data;
2489
2490  if (ctx->handler && ctx->handler->characters != NULL)
2491    ctx->handler->characters (ctx->handler, ch, len);
2492}
2493
2494static xmlEntityPtr
2495rsvg_get_entity (void *data, const xmlChar *name)
2496{
2497  RsvgHandle *ctx = (RsvgHandle *)data;
2498
2499  return (xmlEntityPtr)g_hash_table_lookup (ctx->entities, name);
2500}
2501
2502static void
2503rsvg_entity_decl (void *data, const xmlChar *name, int type,
2504                  const xmlChar *publicId, const xmlChar *systemId, xmlChar *content)
2505{
2506  RsvgHandle *ctx = (RsvgHandle *)data;
2507  GHashTable *entities = ctx->entities;
2508  xmlEntityPtr entity;
2509  char *dupname;
2510
2511  entity = g_new0 (xmlEntity, 1);
2512  entity->type = type;
2513  entity->length = strlen (name);
2514  dupname = g_strdup (name);
2515  entity->name = dupname;
2516  entity->ExternalID = g_strdup (publicId);
2517  entity->SystemID = g_strdup (systemId);
2518  if (content)
2519    {
2520      entity->content = xmlMemStrdup (content);
2521      entity->length = strlen (content);
2522    }
2523  g_hash_table_insert (entities, dupname, entity);
2524}
2525
2526static void
2527rsvg_error_cb (void *data, const char *msg, ...)
2528{
2529        va_list args;
2530       
2531        va_start (args, msg);
2532        vfprintf (stderr, msg, args);
2533        va_end (args);
2534}
2535
2536static xmlSAXHandler rsvgSAXHandlerStruct = {
2537    NULL, /* internalSubset */
2538    NULL, /* isStandalone */
2539    NULL, /* hasInternalSubset */
2540    NULL, /* hasExternalSubset */
2541    NULL, /* resolveEntity */
2542    rsvg_get_entity, /* getEntity */
2543    rsvg_entity_decl, /* entityDecl */
2544    NULL, /* notationDecl */
2545    NULL, /* attributeDecl */
2546    NULL, /* elementDecl */
2547    NULL, /* unparsedEntityDecl */
2548    NULL, /* setDocumentLocator */
2549    NULL, /* startDocument */
2550    NULL, /* endDocument */
2551    rsvg_start_element, /* startElement */
2552    rsvg_end_element, /* endElement */
2553    NULL, /* reference */
2554    rsvg_characters, /* characters */
2555    NULL, /* ignorableWhitespace */
2556    NULL, /* processingInstruction */
2557    NULL, /* comment */
2558    NULL, /* xmlParserWarning */
2559    rsvg_error_cb, /* xmlParserError */
2560    rsvg_error_cb, /* xmlParserFatalError */
2561    NULL, /* getParameterEntity */
2562    rsvg_characters, /* cdataCallback */
2563    NULL /* */
2564};
2565
2566GQuark
2567rsvg_error_quark (void)
2568{
2569  static GQuark q = 0;
2570  if (q == 0)
2571    q = g_quark_from_static_string ("rsvg-error-quark");
2572
2573  return q;
2574}
2575
2576/**
2577 * rsvg_handle_new:
2578 * @void:
2579 *
2580 * Returns a new rsvg handle.  Must be freed with @rsvg_handle_free.  This
2581 * handle can be used for dynamically loading an image.  You need to feed it
2582 * data using @rsvg_handle_write, then call @rsvg_handle_close when done.  No
2583 * more than one image can be loaded with one handle.
2584 *
2585 * Return value: A new #RsvgHandle
2586 **/
2587RsvgHandle *
2588rsvg_handle_new (void)
2589{
2590  RsvgHandle *handle;
2591
2592  handle = g_new0 (RsvgHandle, 1);
2593  handle->n_state = 0;
2594  handle->n_state_max = 16;
2595  handle->state = g_new (RsvgState, handle->n_state_max);
2596  handle->defs = rsvg_defs_new ();
2597  handle->handler_nest = 0;
2598  handle->entities = g_hash_table_new (g_str_hash, g_str_equal);
2599  handle->dpi = internal_dpi;
2600
2601  handle->css_props = g_hash_table_new_full (g_str_hash, g_str_equal,
2602                                             g_free, g_free);
2603
2604  handle->ctxt = NULL;
2605
2606  return handle;
2607}
2608
2609/**
2610 * rsvg_set_default_dpi
2611 * @dpi: Dots Per Inch (aka Pixels Per Inch)
2612 *
2613 * Sets the DPI for the all future outgoing pixbufs. Common values are
2614 * 72, 90, and 300 DPI. Passing a number <= 0 to #dpi will
2615 * reset the DPI to whatever the default value happens to be.
2616 */
2617void
2618rsvg_set_default_dpi (double dpi)
2619{
2620  if (dpi <= 0.)
2621    internal_dpi = RSVG_DEFAULT_DPI;
2622  else
2623    internal_dpi = dpi;
2624}
2625
2626/**
2627 * rsvg_handle_set_dpi
2628 * @handle: An #RsvgHandle
2629 * @dpi: Dots Per Inch (aka Pixels Per Inch)
2630 *
2631 * Sets the DPI for the outgoing pixbuf. Common values are
2632 * 72, 90, and 300 DPI. Passing a number <= 0 to #dpi will
2633 * reset the DPI to whatever the default value happens to be.
2634 */
2635void
2636rsvg_handle_set_dpi (RsvgHandle * handle, double dpi)
2637{
2638  g_return_if_fail (handle != NULL);
2639
2640    if (dpi <= 0.)
2641        handle->dpi = internal_dpi;
2642    else
2643        handle->dpi = dpi;
2644}
2645
2646/**
2647 * rsvg_handle_set_size_callback:
2648 * @handle: An #RsvgHandle
2649 * @size_func: A sizing function, or %NULL
2650 * @user_data: User data to pass to @size_func, or %NULL
2651 * @user_data_destroy: Destroy function for @user_data, or %NULL
2652 *
2653 * Sets the sizing function for the @handle.  This function is called right
2654 * after the size of the image has been loaded.  The size of the image is passed
2655 * in to the function, which may then modify these values to set the real size
2656 * of the generated pixbuf.  If the image has no associated size, then the size
2657 * arguments are set to -1.
2658 **/
2659void
2660rsvg_handle_set_size_callback (RsvgHandle     *handle,
2661                               RsvgSizeFunc    size_func,
2662                               gpointer        user_data,
2663                               GDestroyNotify  user_data_destroy)
2664{
2665  g_return_if_fail (handle != NULL);
2666
2667  if (handle->user_data_destroy)
2668    (* handle->user_data_destroy) (handle->user_data);
2669
2670  handle->size_func = size_func;
2671  handle->user_data = user_data;
2672  handle->user_data_destroy = user_data_destroy;
2673}
2674
2675/**
2676 * rsvg_handle_write:
2677 * @handle: An #RsvgHandle
2678 * @buf: Pointer to svg data
2679 * @count: length of the @buf buffer in bytes
2680 * @error: return location for errors
2681 *
2682 * Loads the next @count bytes of the image.  This will return #TRUE if the data
2683 * was loaded successful, and #FALSE if an error occurred.  In the latter case,
2684 * the loader will be closed, and will not accept further writes. If FALSE is
2685 * returned, @error will be set to an error from the #RSVG_ERROR domain.
2686 *
2687 * Return value: #TRUE if the write was successful, or #FALSE if there was an
2688 * error.
2689 **/
2690gboolean
2691rsvg_handle_write (RsvgHandle    *handle,
2692                   const guchar  *buf,
2693                   gsize          count,
2694                   GError       **error)
2695{
2696  GError *real_error;
2697  g_return_val_if_fail (handle != NULL, FALSE);
2698
2699  handle->error = &real_error;
2700  if (handle->ctxt == NULL)
2701    {
2702      handle->ctxt = xmlCreatePushParserCtxt (
2703              &rsvgSAXHandlerStruct, handle, NULL, 0, NULL);
2704      handle->ctxt->replaceEntities = TRUE;
2705    }
2706
2707  xmlParseChunk (handle->ctxt, buf, count, 0);
2708
2709  handle->error = NULL;
2710  /* FIXME: Error handling not implemented. */
2711  /*  if (*real_error != NULL)
2712    {
2713      g_propagate_error (error, real_error);
2714      return FALSE;
2715      }*/
2716  return TRUE;
2717}
2718
2719/**
2720 * rsvg_handle_close:
2721 * @handle: An #RsvgHandle
2722 *
2723 * Closes @handle, to indicate that loading the image is complete.  This will
2724 * return #TRUE if the loader closed successfully.  Note that @handle isn't
2725 * freed until @rsvg_handle_free is called.
2726 *
2727 * Return value: #TRUE if the loader closed successfully, or #FALSE if there was
2728 * an error.
2729 **/
2730gboolean
2731rsvg_handle_close (RsvgHandle  *handle,
2732                   GError     **error)
2733{
2734  gchar chars[1] = { '\0' };
2735  GError *real_error;
2736
2737  handle->error = &real_error;
2738
2739  if (handle->ctxt != NULL)
2740  {
2741    xmlParseChunk (handle->ctxt, chars, 1, TRUE);
2742    xmlFreeParserCtxt (handle->ctxt);
2743  }
2744 
2745  /* FIXME: Error handling not implemented. */
2746  /*
2747  if (real_error != NULL)
2748    {
2749      g_propagate_error (error, real_error);
2750      return FALSE;
2751      }*/
2752  return TRUE;
2753}
2754
2755/**
2756 * rsvg_handle_get_pixbuf:
2757 * @handle: An #RsvgHandle
2758 *
2759 * Returns the pixbuf loaded by #handle.  The pixbuf returned will be reffed, so
2760 * the caller of this function must assume that ref.  If insufficient data has
2761 * been read to create the pixbuf, or an error occurred in loading, then %NULL
2762 * will be returned.  Note that the pixbuf may not be complete until
2763 * @rsvg_handle_close has been called.
2764 *
2765 * Return value: the pixbuf loaded by #handle, or %NULL.
2766 **/
2767GdkPixbuf *
2768rsvg_handle_get_pixbuf (RsvgHandle *handle)
2769{
2770  g_return_val_if_fail (handle != NULL, NULL);
2771
2772  if (handle->pixbuf)
2773    return g_object_ref (handle->pixbuf);
2774
2775  return NULL;
2776}
2777
2778/**
2779 * rsvg_handle_free:
2780 * @handle: An #RsvgHandle
2781 *
2782 * Frees #handle.
2783 **/
2784void
2785rsvg_handle_free (RsvgHandle *handle)
2786{
2787  int i;
2788
2789  if (handle->pango_context != NULL)
2790    g_object_unref (handle->pango_context);
2791  rsvg_defs_free (handle->defs);
2792
2793  for (i = 0; i < handle->n_state; i++)
2794    rsvg_state_finalize (&handle->state[i]);
2795  g_free (handle->state);
2796
2797  g_hash_table_foreach (handle->entities, rsvg_ctx_free_helper, NULL);
2798  g_hash_table_destroy (handle->entities);
2799
2800  g_hash_table_destroy (handle->css_props);
2801
2802  if (handle->user_data_destroy)
2803    (* handle->user_data_destroy) (handle->user_data);
2804  if (handle->pixbuf)
2805    g_object_unref (handle->pixbuf);
2806  g_free (handle);
2807}
2808
2809typedef enum {
2810  RSVG_SIZE_ZOOM,
2811  RSVG_SIZE_WH,
2812  RSVG_SIZE_WH_MAX,
2813  RSVG_SIZE_ZOOM_MAX
2814} RsvgSizeType;
2815
2816struct RsvgSizeCallbackData
2817{
2818  RsvgSizeType type;
2819  double x_zoom;
2820  double y_zoom;
2821  gint width;
2822  gint height;
2823};
2824
2825static void
2826rsvg_size_callback (int *width,
2827                    int *height,
2828                    gpointer  data)
2829{
2830  struct RsvgSizeCallbackData *real_data = (struct RsvgSizeCallbackData *) data;
2831  double zoomx, zoomy, zoom;
2832
2833  switch (real_data->type) {
2834  case RSVG_SIZE_ZOOM:
2835    if (*width < 0 || *height < 0)
2836      return;
2837
2838    *width = floor (real_data->x_zoom * *width + 0.5);
2839    *height = floor (real_data->y_zoom * *height + 0.5);
2840    return;
2841
2842  case RSVG_SIZE_ZOOM_MAX:
2843    if (*width < 0 || *height < 0)
2844      return;
2845
2846    *width = floor (real_data->x_zoom * *width + 0.5);
2847    *height = floor (real_data->y_zoom * *height + 0.5);
2848   
2849    if (*width > real_data->width || *height > real_data->height)
2850      {
2851        zoomx = (double) real_data->width / *width;
2852        zoomy = (double) real_data->height / *height;
2853        zoom = MIN (zoomx, zoomy);
2854       
2855        *width = floor (zoom * *width + 0.5);
2856        *height = floor (zoom * *height + 0.5);
2857      }
2858    return;
2859
2860  case RSVG_SIZE_WH_MAX:
2861    if (*width < 0 || *height < 0)
2862      return;
2863
2864    zoomx = (double) real_data->width / *width;
2865    zoomy = (double) real_data->height / *height;
2866    zoom = MIN (zoomx, zoomy);
2867   
2868    *width = floor (zoom * *width + 0.5);
2869    *height = floor (zoom * *height + 0.5);
2870    return;
2871
2872  case RSVG_SIZE_WH:
2873
2874    if (real_data->width != -1)
2875      *width = real_data->width;
2876    if (real_data->height != -1)
2877      *height = real_data->height;
2878    return;
2879  }
2880
2881  g_assert_not_reached ();
2882}
2883
2884static GdkPixbuf *
2885rsvg_pixbuf_from_file_with_size_data (const gchar * file_name,
2886                                      struct RsvgSizeCallbackData * data,
2887                                      GError ** error)
2888{
2889  char chars[SVG_BUFFER_SIZE];
2890  gint result;
2891  GdkPixbuf *retval;
2892  RsvgHandle *handle;
2893
2894#if ENABLE_GNOME_VFS
2895  GnomeVFSHandle * f = NULL;
2896  if (GNOME_VFS_OK != gnome_vfs_open (&handle, file_name, GNOME_VFS_OPEN_READ))
2897    {
2898      /* FIXME: Set up error. */
2899      return NULL;
2900    }
2901#else
2902  FILE *f = fopen (file_name, "r");
2903  if (!f)
2904    {
2905      /* FIXME: Set up error. */
2906      return NULL;
2907    }
2908#endif
2909
2910  handle = rsvg_handle_new ();
2911
2912  rsvg_handle_set_size_callback (handle, rsvg_size_callback, data, NULL);
2913
2914#if ENABLE_GNOME_VFS
2915  while (GNOME_VFS_OK == gnome_vfs_read (f,chars, SVG_BUFFER_SIZE, &result))
2916    rsvg_handle_write (handle, chars, result, error);
2917#else
2918  while ((result = fread (chars, 1, SVG_BUFFER_SIZE, f)) > 0)
2919    rsvg_handle_write (handle, chars, result, error);
2920#endif
2921
2922  rsvg_handle_close (handle, error);
2923  retval = rsvg_handle_get_pixbuf (handle);
2924
2925#if ENABLE_GNOME_VFS
2926  gnome_vfs_close (f);
2927#else
2928  fclose (f);
2929#endif
2930
2931  rsvg_handle_free (handle);
2932
2933  return retval;
2934}
2935
2936/**
2937 * rsvg_pixbuf_from_file:
2938 * @file_name: A file name
2939 * @error: return location for errors
2940 *
2941 * Loads a new #GdkPixbuf from @file_name and returns it.  The caller must
2942 * assume the reference to the reurned pixbuf. If an error occurred, @error is
2943 * set and %NULL is returned.
2944 *
2945 * Return value: A newly allocated #GdkPixbuf, or %NULL
2946 **/
2947GdkPixbuf *
2948rsvg_pixbuf_from_file (const gchar *file_name,
2949                       GError     **error)
2950{
2951  return rsvg_pixbuf_from_file_at_size (file_name, -1, -1, error);
2952}
2953
2954/**
2955 * rsvg_pixbuf_from_file_at_zoom:
2956 * @file_name: A file name
2957 * @x_zoom: The horizontal zoom factor
2958 * @y_zoom: The vertical zoom factor
2959 * @error: return location for errors
2960 *
2961 * Loads a new #GdkPixbuf from @file_name and returns it.  This pixbuf is scaled
2962 * from the size indicated by the file by a factor of @x_zoom and @y_zoom.  The
2963 * caller must assume the reference to the returned pixbuf. If an error
2964 * occurred, @error is set and %NULL is returned.
2965 *
2966 * Return value: A newly allocated #GdkPixbuf, or %NULL
2967 **/
2968GdkPixbuf *
2969rsvg_pixbuf_from_file_at_zoom (const gchar *file_name,
2970                               double       x_zoom,
2971                               double       y_zoom,
2972                               GError     **error)
2973{
2974  struct RsvgSizeCallbackData data;
2975
2976  g_return_val_if_fail (file_name != NULL, NULL);
2977  g_return_val_if_fail (x_zoom > 0.0 && y_zoom > 0.0, NULL);
2978
2979  data.type = RSVG_SIZE_ZOOM;
2980  data.x_zoom = x_zoom;
2981  data.y_zoom = y_zoom;
2982
2983  return rsvg_pixbuf_from_file_with_size_data (file_name, &data, error);
2984}
2985
2986/**
2987 * rsvg_pixbuf_from_file_at_zoom_with_max:
2988 * @file_name: A file name
2989 * @x_zoom: The horizontal zoom factor
2990 * @y_zoom: The vertical zoom factor
2991 * @max_width: The requested max width
2992 * @max_height: The requested max heigh
2993 * @error: return location for errors
2994 *
2995 * Loads a new #GdkPixbuf from @file_name and returns it.  This pixbuf is scaled
2996 * from the size indicated by the file by a factor of @x_zoom and @y_zoom. If the
2997 * resulting pixbuf would be larger than max_width/max_heigh it is uniformly scaled
2998 * down to fit in that rectangle. The caller must assume the reference to the
2999 * returned pixbuf. If an error occurred, @error is set and %NULL is returned.
3000 *
3001 * Return value: A newly allocated #GdkPixbuf, or %NULL
3002 **/
3003GdkPixbuf  *
3004rsvg_pixbuf_from_file_at_zoom_with_max (const gchar  *file_name,
3005                                        double        x_zoom,
3006                                        double        y_zoom,
3007                                        gint          max_width,
3008                                        gint          max_height,
3009                                        GError      **error)
3010{
3011  struct RsvgSizeCallbackData data;
3012
3013  g_return_val_if_fail (file_name != NULL, NULL);
3014  g_return_val_if_fail (x_zoom > 0.0 && y_zoom > 0.0, NULL);
3015
3016  data.type = RSVG_SIZE_ZOOM_MAX;
3017  data.x_zoom = x_zoom;
3018  data.y_zoom = y_zoom;
3019  data.width = max_width;
3020  data.height = max_height;
3021
3022  return rsvg_pixbuf_from_file_with_size_data (file_name, &data, error);
3023}
3024
3025/**
3026 * rsvg_pixbuf_from_file_at_size:
3027 * @file_name: A file name
3028 * @width: The new width, or -1
3029 * @height: The new height, or -1
3030 * @error: return location for errors
3031 *
3032 * Loads a new #GdkPixbuf from @file_name and returns it.  This pixbuf is scaled
3033 * from the size indicated to the new size indicated by @width and @height.  If
3034 * either of these are -1, then the default size of the image being loaded is
3035 * used.  The caller must assume the reference to the returned pixbuf. If an
3036 * error occurred, @error is set and %NULL is returned.
3037 *
3038 * Return value: A newly allocated #GdkPixbuf, or %NULL
3039 **/
3040GdkPixbuf *
3041rsvg_pixbuf_from_file_at_size (const gchar *file_name,
3042                               gint         width,
3043                               gint         height,
3044                               GError     **error)
3045{
3046  struct RsvgSizeCallbackData data;
3047
3048  data.type = RSVG_SIZE_WH;
3049  data.width = width;
3050  data.height = height;
3051
3052  return rsvg_pixbuf_from_file_with_size_data (file_name, &data, error);
3053}
3054
3055/**
3056 * rsvg_pixbuf_from_file_at_max_size:
3057 * @file_name: A file name
3058 * @max_width: The requested max width
3059 * @max_height: The requested max heigh
3060 * @error: return location for errors
3061 *
3062 * Loads a new #GdkPixbuf from @file_name and returns it.  This pixbuf is uniformly
3063 * scaled so that the it fits into a rectangle of size max_width * max_height. The
3064 * caller must assume the reference to the returned pixbuf. If an error occurred,
3065 * @error is set and %NULL is returned.
3066 *
3067 * Return value: A newly allocated #GdkPixbuf, or %NULL
3068 **/
3069GdkPixbuf  *
3070rsvg_pixbuf_from_file_at_max_size (const gchar     *file_name,
3071                                   gint             max_width,
3072                                   gint             max_height,
3073                                   GError         **error)
3074{
3075  struct RsvgSizeCallbackData data;
3076
3077  data.type = RSVG_SIZE_WH_MAX;
3078  data.width = max_width;
3079  data.height = max_height;
3080
3081  return rsvg_pixbuf_from_file_with_size_data (file_name, &data, error);
3082}
Note: See TracBrowser for help on using the repository browser.