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

Revision 18352, 49.3 KB checked in by ghudson, 21 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18351, 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
6   This program is free software; you can redistribute it and/or
7   modify it under the terms of the GNU Library General Public License as
8   published by the Free Software Foundation; either version 2 of the
9   License, or (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   Library General Public License for more details.
15
16   You should have received a copy of the GNU Library General Public
17   License along with this program; if not, write to the
18   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19   Boston, MA 02111-1307, USA.
20
21   Author: Raph Levien <raph@artofcode.com>
22*/
23
24#include "config.h"
25#include "rsvg.h"
26
27#include <math.h>
28#include <string.h>
29#include <stdarg.h>
30
31#include <libart_lgpl/art_affine.h>
32#include <libart_lgpl/art_vpath_bpath.h>
33#include <libart_lgpl/art_svp_vpath_stroke.h>
34#include <libart_lgpl/art_svp_vpath.h>
35#include <libart_lgpl/art_svp_intersect.h>
36#include <libart_lgpl/art_render_mask.h>
37#include <libart_lgpl/art_render_svp.h>
38#include <libart_lgpl/art_rgba.h>
39
40#include <libxml/SAX.h>
41#include <libxml/xmlmemory.h>
42
43#include <pango/pangoft2.h>
44
45#include "rsvg-bpath-util.h"
46#include "rsvg-path.h"
47#include "rsvg-css.h"
48#include "rsvg-paint-server.h"
49
50#define noVERBOSE
51
52#define SVG_BUFFER_SIZE (1024 * 8)
53
54typedef struct {
55  double affine[6];
56
57  gint opacity; /* 0..255 */
58
59  RsvgPaintServer *fill;
60  gint fill_opacity; /* 0..255 */
61
62  RsvgPaintServer *stroke;
63  gint stroke_opacity; /* 0..255 */
64  double stroke_width;
65
66  ArtPathStrokeCapType cap;
67  ArtPathStrokeJoinType join;
68
69  double font_size;
70  char *font_family;
71
72  guint32 stop_color; /* rgb */
73  gint stop_opacity; /* 0..255 */
74
75  gboolean in_defs;
76
77  GdkPixbuf *save_pixbuf;
78} RsvgState;
79
80typedef struct RsvgSaxHandler RsvgSaxHandler;
81
82struct RsvgSaxHandler {
83  void (*free) (RsvgSaxHandler *self);
84  void (*start_element) (RsvgSaxHandler *self, const xmlChar *name, const xmlChar **atts);
85  void (*end_element) (RsvgSaxHandler *self, const xmlChar *name);
86  void (*characters) (RsvgSaxHandler *self, const xmlChar *ch, int len);
87};
88
89struct RsvgHandle {
90  RsvgSizeFunc size_func;
91  gpointer user_data;
92  GDestroyNotify user_data_destroy;
93  GdkPixbuf *pixbuf;
94
95  /* stack; there is a state for each element */
96  RsvgState *state;
97  int n_state;
98  int n_state_max;
99
100  RsvgDefs *defs;
101
102  RsvgSaxHandler *handler; /* should this be a handler stack? */
103  int handler_nest;
104
105  GHashTable *entities; /* g_malloc'd string -> xmlEntityPtr */
106
107  PangoContext *pango_context;
108  xmlParserCtxtPtr ctxt;
109  GError **error;
110};
111
112static void
113rsvg_state_init (RsvgState *state)
114{
115  memset (state, 0, sizeof (*state));
116
117  art_affine_identity (state->affine);
118
119  state->opacity = 0xff;
120  state->fill = rsvg_paint_server_parse (NULL, "#000");
121  state->fill_opacity = 0xff;
122  state->stroke_opacity = 0xff;
123  state->stroke_width = 1;
124  state->cap = ART_PATH_STROKE_CAP_BUTT;
125  state->join = ART_PATH_STROKE_JOIN_MITER;
126  state->stop_opacity = 0xff;
127}
128
129static void
130rsvg_state_clone (RsvgState *dst, const RsvgState *src)
131{
132  *dst = *src;
133  dst->font_family = g_strdup (src->font_family);
134  rsvg_paint_server_ref (dst->fill);
135  rsvg_paint_server_ref (dst->stroke);
136  dst->save_pixbuf = NULL;
137}
138
139static void
140rsvg_state_finalize (RsvgState *state)
141{
142  g_free (state->font_family);
143  rsvg_paint_server_unref (state->fill);
144  rsvg_paint_server_unref (state->stroke);
145}
146
147static void
148rsvg_ctx_free_helper (gpointer key, gpointer value, gpointer user_data)
149{
150  xmlEntityPtr entval = (xmlEntityPtr)value;
151
152  /* key == entval->name, so it's implicitly freed below */
153
154  g_free ((char *) entval->name);
155  g_free ((char *) entval->ExternalID);
156  g_free ((char *) entval->SystemID);
157  xmlFree (entval->content);
158  xmlFree (entval->orig);
159  g_free (entval);
160}
161
162static void
163rsvg_pixmap_destroy (guchar *pixels, gpointer data)
164{
165  g_free (pixels);
166}
167
168static void
169rsvg_start_svg (RsvgHandle *ctx, const xmlChar **atts)
170{
171  int i;
172  int width = -1, height = -1;
173  int rowstride;
174  art_u8 *pixels;
175  gint fixed;
176  RsvgState *state;
177  gboolean has_alpha = 1;
178  gint new_width, new_height;
179  double x_zoom;
180  double y_zoom;
181
182  if (atts != NULL)
183    {
184      for (i = 0; atts[i] != NULL; i += 2)
185        {
186          if (!strcmp ((char *)atts[i], "width"))
187            width = rsvg_css_parse_length ((char *)atts[i + 1], &fixed);
188          else if (!strcmp ((char *)atts[i], "height"))
189            height = rsvg_css_parse_length ((char *)atts[i + 1], &fixed);
190        }
191
192#ifdef VERBOSE
193      fprintf (stdout, "rsvg_start_svg: width = %d, height = %d\n",
194               width, height);
195#endif
196
197      if (width == 0 || height == 0)
198        {
199          /* FIXME: GError here? */
200          g_warning ("rsvg_start_svg: can't render 0-sized SVG");
201          return;
202        }
203
204      new_width = width;
205      new_height = height;
206      if (ctx->size_func)
207        (* ctx->size_func) (&new_width, &new_height, ctx->user_data);
208
209      if (new_width == 0 || new_height == 0)
210        {
211          /* FIXME: GError here? */
212          g_warning ("rsvg_start_svg: can't render 0-sized SVG");
213          return;
214        }
215
216      x_zoom = (width < 0 || new_width < 0) ? 1 : (double) new_width / width;
217      y_zoom = (height < 0 || new_height < 0) ? 1 : (double) new_height / height;
218
219      /* Scale size of target pixbuf */
220      state = &ctx->state[ctx->n_state - 1];
221      art_affine_scale (state->affine, x_zoom, y_zoom);
222
223      if (new_width < 0 || new_height < 0)
224        {
225          g_warning ("rsvg_start_svg: width and height not specified in the SVG, nor supplied by the size callback");
226          if (new_width < 0) new_width = 500;
227          if (new_height < 0) new_height = 500;
228        }
229
230      if (new_width >= INT_MAX / 4)
231        {
232          /* FIXME: GError here? */
233          g_warning ("rsvg_start_svg: width too large");
234          return;
235        }
236      rowstride = (new_width * (has_alpha ? 4 : 3) + 3) & ~3;
237      if (rowstride > INT_MAX / new_height)
238        {
239          /* FIXME: GError here? */
240          g_warning ("rsvg_start_svg: width too large");
241          return;
242        }
243
244      /* FIXME: Add GError here if size is too big. */
245
246      pixels = g_try_malloc (rowstride * new_height);
247      if (pixels == NULL)
248        {
249          /* FIXME: GError here? */
250          g_warning ("rsvg_start_svg: dimensions too large");
251          return;
252        }
253      memset (pixels, has_alpha ? 0 : 255, rowstride * new_height);
254      ctx->pixbuf = gdk_pixbuf_new_from_data (pixels,
255                                              GDK_COLORSPACE_RGB,
256                                              has_alpha, 8,
257                                              new_width, new_height,
258                                              rowstride,
259                                              rsvg_pixmap_destroy,
260                                              NULL);
261    }
262}
263
264/* Parse a CSS2 style argument, setting the SVG context attributes. */
265static void
266rsvg_parse_style_arg (RsvgHandle *ctx, RsvgState *state, const char *str)
267{
268  int arg_off;
269
270  arg_off = rsvg_css_param_arg_offset (str);
271  if (rsvg_css_param_match (str, "opacity"))
272    {
273      state->opacity = rsvg_css_parse_opacity (str + arg_off);
274    }
275  else if (rsvg_css_param_match (str, "fill"))
276    {
277      rsvg_paint_server_unref (state->fill);
278      state->fill = rsvg_paint_server_parse (ctx->defs, str + arg_off);
279    }
280  else if (rsvg_css_param_match (str, "fill-opacity"))
281    {
282      state->fill_opacity = rsvg_css_parse_opacity (str + arg_off);
283    }
284  else if (rsvg_css_param_match (str, "stroke"))
285    {
286      rsvg_paint_server_unref (state->stroke);
287      state->stroke = rsvg_paint_server_parse (ctx->defs, str + arg_off);
288    }
289  else if (rsvg_css_param_match (str, "stroke-width"))
290    {
291      int fixed;
292      state->stroke_width = rsvg_css_parse_length (str + arg_off, &fixed);
293    }
294  else if (rsvg_css_param_match (str, "stroke-linecap"))
295    {
296      if (!strcmp (str + arg_off, "butt"))
297        state->cap = ART_PATH_STROKE_CAP_BUTT;
298      else if (!strcmp (str + arg_off, "round"))
299        state->cap = ART_PATH_STROKE_CAP_ROUND;
300      else if (!strcmp (str + arg_off, "square"))
301        state->cap = ART_PATH_STROKE_CAP_SQUARE;
302      else
303        g_warning ("unknown line cap style %s", str + arg_off);
304    }
305  else if (rsvg_css_param_match (str, "stroke-opacity"))
306    {
307      state->stroke_opacity = rsvg_css_parse_opacity (str + arg_off);
308    }
309  else if (rsvg_css_param_match (str, "stroke-linejoin"))
310    {
311      if (!strcmp (str + arg_off, "miter"))
312        state->join = ART_PATH_STROKE_JOIN_MITER;
313      else if (!strcmp (str + arg_off, "round"))
314        state->join = ART_PATH_STROKE_JOIN_ROUND;
315      else if (!strcmp (str + arg_off, "bevel"))
316        state->join = ART_PATH_STROKE_JOIN_BEVEL;
317      else
318        g_warning ("unknown line join style %s", str + arg_off);
319    }
320  else if (rsvg_css_param_match (str, "font-size"))
321    {
322      state->font_size = rsvg_css_parse_fontsize (str + arg_off);
323    }
324  else if (rsvg_css_param_match (str, "font-family"))
325    {
326      g_free (state->font_family);
327      state->font_family = g_strdup (str + arg_off);
328    }
329  else if (rsvg_css_param_match (str, "stop-color"))
330    {
331      state->stop_color = rsvg_css_parse_color (str + arg_off);
332    }
333  else if (rsvg_css_param_match (str, "stop-opacity"))
334    {
335      state->stop_opacity = rsvg_css_parse_opacity (str + arg_off);
336    }
337}
338
339/* Split a CSS2 style into individual style arguments, setting attributes
340   in the SVG context.
341
342   It's known that this is _way_ out of spec. A more complete CSS2
343   implementation will happen later.
344*/
345static void
346rsvg_parse_style (RsvgHandle *ctx, RsvgState *state, const char *str)
347{
348  int start, end;
349  char *arg;
350
351  start = 0;
352  while (str[start] != '\0')
353    {
354      for (end = start; str[end] != '\0' && str[end] != ';'; end++);
355      arg = g_new (char, 1 + end - start);
356      memcpy (arg, str + start, end - start);
357      arg[end - start] = '\0';
358      rsvg_parse_style_arg (ctx, state, arg);
359      g_free (arg);
360      start = end;
361      if (str[start] == ';') start++;
362      while (str[start] == ' ') start++;
363    }
364}
365
366/* Parse an SVG transform string into an affine matrix. Reference: SVG
367   working draft dated 1999-07-06, section 8.5. Return TRUE on
368   success. */
369static gboolean
370rsvg_parse_transform (double dst[6], const char *src)
371{
372  int idx;
373  char keyword[32];
374  double args[6];
375  int n_args;
376  guint key_len;
377  double tmp_affine[6];
378
379  art_affine_identity (dst);
380
381  idx = 0;
382  while (src[idx])
383    {
384      /* skip initial whitespace */
385      while (g_ascii_isspace (src[idx]))
386        idx++;
387
388      /* parse keyword */
389      for (key_len = 0; key_len < sizeof (keyword); key_len++)
390        {
391          char c;
392
393          c = src[idx];
394          if (g_ascii_isalpha (c) || c == '-')
395            keyword[key_len] = src[idx++];
396          else
397            break;
398        }
399      if (key_len >= sizeof (keyword))
400        return FALSE;
401      keyword[key_len] = '\0';
402
403      /* skip whitespace */
404      while (g_ascii_isspace (src[idx]))
405        idx++;
406
407      if (src[idx] != '(')
408        return FALSE;
409      idx++;
410
411      for (n_args = 0; ; n_args++)
412        {
413          char c;
414          char *end_ptr;
415
416          /* skip whitespace */
417          while (g_ascii_isspace (src[idx]))
418            idx++;
419          c = src[idx];
420          if (g_ascii_isdigit (c) || c == '+' || c == '-' || c == '.')
421            {
422              if (n_args == sizeof(args) / sizeof(args[0]))
423                return FALSE; /* too many args */
424              args[n_args] = strtod (src + idx, &end_ptr);
425              idx = end_ptr - src;
426
427              while (g_ascii_isspace (src[idx]))
428                idx++;
429
430              /* skip optional comma */
431              if (src[idx] == ',')
432                idx++;
433            }
434          else if (c == ')')
435            break;
436          else
437            return FALSE;
438        }
439      idx++;
440
441      /* ok, have parsed keyword and args, now modify the transform */
442      if (!strcmp (keyword, "matrix"))
443        {
444          if (n_args != 6)
445            return FALSE;
446          art_affine_multiply (dst, args, dst);
447        }
448      else if (!strcmp (keyword, "translate"))
449        {
450          if (n_args == 1)
451            args[1] = 0;
452          else if (n_args != 2)
453            return FALSE;
454          art_affine_translate (tmp_affine, args[0], args[1]);
455          art_affine_multiply (dst, tmp_affine, dst);
456        }
457      else if (!strcmp (keyword, "scale"))
458        {
459          if (n_args == 1)
460            args[1] = args[0];
461          else if (n_args != 2)
462            return FALSE;
463          art_affine_scale (tmp_affine, args[0], args[1]);
464          art_affine_multiply (dst, tmp_affine, dst);
465        }
466      else if (!strcmp (keyword, "rotate"))
467        {
468          if (n_args != 1)
469            return FALSE;
470          art_affine_rotate (tmp_affine, args[0]);
471          art_affine_multiply (dst, tmp_affine, dst);
472        }
473      else if (!strcmp (keyword, "skewX"))
474        {
475          if (n_args != 1)
476            return FALSE;
477          art_affine_shear (tmp_affine, args[0]);
478          art_affine_multiply (dst, tmp_affine, dst);
479        }
480      else if (!strcmp (keyword, "skewY"))
481        {
482          if (n_args != 1)
483            return FALSE;
484          art_affine_shear (tmp_affine, args[0]);
485          /* transpose the affine, given that we know [1] is zero */
486          tmp_affine[1] = tmp_affine[2];
487          tmp_affine[2] = 0;
488          art_affine_multiply (dst, tmp_affine, dst);
489        }
490      else
491        return FALSE; /* unknown keyword */
492    }
493  return TRUE;
494}
495
496/**
497 * rsvg_parse_transform_attr: Parse transform attribute and apply to state.
498 * @ctx: Rsvg context.
499 * @state: State in which to apply the transform.
500 * @str: String containing transform.
501 *
502 * Parses the transform attribute in @str and applies it to @state.
503 **/
504static void
505rsvg_parse_transform_attr (RsvgHandle *ctx, RsvgState *state, const char *str)
506{
507  double affine[6];
508
509  if (rsvg_parse_transform (affine, str))
510    {
511      art_affine_multiply (state->affine, affine, state->affine);
512    }
513  else
514    {
515      /* parse error for transform attribute. todo: report */
516    }
517}
518
519/**
520 * rsvg_parse_style_attrs: Parse style attribute.
521 * @ctx: Rsvg context.
522 * @atts: Attributes in SAX style.
523 *
524 * Parses style and transform attributes and modifies state at top of
525 * stack.
526 **/
527static void
528rsvg_parse_style_attrs (RsvgHandle *ctx, const xmlChar **atts)
529{
530  int i;
531
532  if (atts != NULL)
533    {
534      for (i = 0; atts[i] != NULL; i += 2)
535        {
536          if (!strcmp ((char *)atts[i], "style"))
537            rsvg_parse_style (ctx, &ctx->state[ctx->n_state - 1],
538                              (char *)atts[i + 1]);
539          else if (!strcmp ((char *)atts[i], "transform"))
540            rsvg_parse_transform_attr (ctx, &ctx->state[ctx->n_state - 1],
541                                       (char *)atts[i + 1]);
542        }
543    }
544}
545
546/**
547 * rsvg_push_opacity_group: Begin a new transparency group.
548 * @ctx: Context in which to push.
549 *
550 * Pushes a new transparency group onto the stack. The top of the stack
551 * is stored in the context, while the "saved" value is in the state
552 * stack.
553 **/
554static void
555rsvg_push_opacity_group (RsvgHandle *ctx)
556{
557  RsvgState *state;
558  GdkPixbuf *pixbuf;
559  art_u8 *pixels;
560  int width, height, rowstride;
561
562  state = &ctx->state[ctx->n_state - 1];
563  pixbuf = ctx->pixbuf;
564
565  state->save_pixbuf = pixbuf;
566
567  if (pixbuf == NULL)
568    {
569      /* FIXME: What warning/GError here? */
570      return;
571    }
572
573  if (!gdk_pixbuf_get_has_alpha (pixbuf))
574    {
575      g_warning ("push/pop transparency group on non-alpha buffer nyi");
576      return;
577    }
578
579  width = gdk_pixbuf_get_width (pixbuf);
580  height = gdk_pixbuf_get_height (pixbuf);
581  rowstride = gdk_pixbuf_get_rowstride (pixbuf);
582  pixels = g_new (art_u8, rowstride * height);
583  memset (pixels, 0, rowstride * height);
584
585  pixbuf = gdk_pixbuf_new_from_data (pixels,
586                                     GDK_COLORSPACE_RGB,
587                                     TRUE,
588                                     gdk_pixbuf_get_bits_per_sample (pixbuf),
589                                     width,
590                                     height,
591                                     rowstride,
592                                     rsvg_pixmap_destroy,
593                                     NULL);
594  ctx->pixbuf = pixbuf;
595}
596
597/**
598 * rsvg_pop_opacity_group: End a transparency group.
599 * @ctx: Context in which to push.
600 * @opacity: Opacity for blending (0..255).
601 *
602 * Pops a new transparency group from the stack, recompositing with the
603 * next on stack.
604 **/
605static void
606rsvg_pop_opacity_group (RsvgHandle *ctx, int opacity)
607{
608  RsvgState *state = &ctx->state[ctx->n_state - 1];
609  GdkPixbuf *tos, *nos;
610  art_u8 *tos_pixels, *nos_pixels;
611  int width;
612  int height;
613  int rowstride;
614  int x, y;
615  int tmp;
616
617  tos = ctx->pixbuf;
618  nos = state->save_pixbuf;
619
620  if (tos == NULL || nos == NULL)
621    {
622      /* FIXME: What warning/GError here? */
623      return;
624    }
625
626  if (!gdk_pixbuf_get_has_alpha (nos))
627    {
628      g_warning ("push/pop transparency group on non-alpha buffer nyi");
629      return;
630    }
631
632  width = gdk_pixbuf_get_width (tos);
633  height = gdk_pixbuf_get_height (tos);
634  rowstride = gdk_pixbuf_get_rowstride (tos);
635
636  tos_pixels = gdk_pixbuf_get_pixels (tos);
637  nos_pixels = gdk_pixbuf_get_pixels (nos);
638
639  for (y = 0; y < height; y++)
640    {
641      for (x = 0; x < width; x++)
642        {
643          art_u8 r, g, b, a;
644          a = tos_pixels[4 * x + 3];
645          if (a)
646            {
647              r = tos_pixels[4 * x];
648              g = tos_pixels[4 * x + 1];
649              b = tos_pixels[4 * x + 2];
650              tmp = a * opacity + 0x80;
651              a = (tmp + (tmp >> 8)) >> 8;
652              art_rgba_run_alpha (nos_pixels + 4 * x, r, g, b, a, 1);
653            }
654        }
655      tos_pixels += rowstride;
656      nos_pixels += rowstride;
657    }
658
659  g_object_unref (tos);
660  ctx->pixbuf = nos;
661}
662
663static void
664rsvg_start_g (RsvgHandle *ctx, const xmlChar **atts)
665{
666  RsvgState *state = &ctx->state[ctx->n_state - 1];
667
668  rsvg_parse_style_attrs (ctx, atts);
669
670  if (state->opacity != 0xff)
671    rsvg_push_opacity_group (ctx);
672}
673
674static void
675rsvg_end_g (RsvgHandle *ctx)
676{
677  RsvgState *state = &ctx->state[ctx->n_state - 1];
678
679  if (state->opacity != 0xff)
680    rsvg_pop_opacity_group (ctx, state->opacity);
681}
682
683/**
684 * rsvg_close_vpath: Close a vector path.
685 * @src: Source vector path.
686 *
687 * Closes any open subpaths in the vector path.
688 *
689 * Return value: Closed vector path, allocated with g_new.
690 **/
691static ArtVpath *
692rsvg_close_vpath (const ArtVpath *src)
693{
694  ArtVpath *result;
695  int n_result, n_result_max;
696  int src_ix;
697  double beg_x, beg_y;
698  gboolean open;
699
700  n_result = 0;
701  n_result_max = 16;
702  result = g_new (ArtVpath, n_result_max);
703
704  beg_x = 0;
705  beg_y = 0;
706  open = FALSE;
707
708  for (src_ix = 0; src[src_ix].code != ART_END; src_ix++)
709    {
710      if (n_result == n_result_max)
711        result = g_renew (ArtVpath, result, n_result_max <<= 1);
712      result[n_result].code = src[src_ix].code == ART_MOVETO_OPEN ?
713        ART_MOVETO : src[src_ix].code;
714      result[n_result].x = src[src_ix].x;
715      result[n_result].y = src[src_ix].y;
716      n_result++;
717      if (src[src_ix].code == ART_MOVETO_OPEN)
718        {
719          beg_x = src[src_ix].x;
720          beg_y = src[src_ix].y;
721          open = TRUE;
722        }
723      else if (src[src_ix + 1].code != ART_LINETO)
724        {
725          if (open && (beg_x != src[src_ix].x || beg_y != src[src_ix].y))
726            {
727              if (n_result == n_result_max)
728                result = g_renew (ArtVpath, result, n_result_max <<= 1);
729              result[n_result].code = ART_LINETO;
730              result[n_result].x = beg_x;
731              result[n_result].y = beg_y;
732              n_result++;
733            }
734          open = FALSE;
735        }
736    }
737  if (n_result == n_result_max)
738    result = g_renew (ArtVpath, result, n_result_max <<= 1);
739  result[n_result].code = ART_END;
740  result[n_result].x = 0.0;
741  result[n_result].y = 0.0;
742  return result;
743}
744
745/**
746 * rsvg_render_svp: Render an SVP.
747 * @ctx: Context in which to render.
748 * @svp: SVP to render.
749 * @ps: Paint server for rendering.
750 * @opacity: Opacity as 0..0xff.
751 *
752 * Renders the SVP over the pixbuf in @ctx.
753 **/
754static void
755rsvg_render_svp (RsvgHandle *ctx, const ArtSVP *svp,
756                 RsvgPaintServer *ps, int opacity)
757{
758  GdkPixbuf *pixbuf;
759  ArtRender *render;
760  gboolean has_alpha;
761
762  pixbuf = ctx->pixbuf;
763  if (pixbuf == NULL)
764    {
765      /* FIXME: What warning/GError here? */
766      return;
767    }
768
769  has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
770
771  render = art_render_new (0, 0,
772                           gdk_pixbuf_get_width (pixbuf),
773                           gdk_pixbuf_get_height (pixbuf),
774                           gdk_pixbuf_get_pixels (pixbuf),
775                           gdk_pixbuf_get_rowstride (pixbuf),
776                           gdk_pixbuf_get_n_channels (pixbuf) -
777                           (has_alpha ? 1 : 0),
778                           gdk_pixbuf_get_bits_per_sample (pixbuf),
779                           has_alpha ? ART_ALPHA_SEPARATE : ART_ALPHA_NONE,
780                           NULL);
781
782  art_render_svp (render, svp);
783  art_render_mask_solid (render, (opacity << 8) + opacity + (opacity >> 7));
784  rsvg_render_paint_server (render, ps, NULL); /* todo: paint server ctx */
785  art_render_invoke (render);
786}
787
788static void
789rsvg_render_bpath (RsvgHandle *ctx, const ArtBpath *bpath)
790{
791  RsvgState *state;
792  ArtBpath *affine_bpath;
793  ArtVpath *vpath;
794  ArtSVP *svp;
795  GdkPixbuf *pixbuf;
796  gboolean need_tmpbuf;
797  int opacity;
798  int tmp;
799
800  pixbuf = ctx->pixbuf;
801  if (pixbuf == NULL)
802    {
803      /* FIXME: What warning/GError here? */
804      return;
805    }
806
807  state = &ctx->state[ctx->n_state - 1];
808  affine_bpath = art_bpath_affine_transform (bpath,
809                                             state->affine);
810
811  vpath = art_bez_path_to_vec (affine_bpath, 0.25);
812  art_free (affine_bpath);
813
814  need_tmpbuf = (state->fill != NULL) && (state->stroke != NULL) &&
815    state->opacity != 0xff;
816
817  if (need_tmpbuf)
818    rsvg_push_opacity_group (ctx);
819
820  if (state->fill != NULL)
821    {
822      ArtVpath *closed_vpath;
823      ArtSVP *svp2;
824      ArtSvpWriter *swr;
825
826      closed_vpath = rsvg_close_vpath (vpath);
827      svp = art_svp_from_vpath (closed_vpath);
828      g_free (closed_vpath);
829     
830      swr = art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO);
831      art_svp_intersector (svp, swr);
832
833      svp2 = art_svp_writer_rewind_reap (swr);
834      art_svp_free (svp);
835
836      opacity = state->fill_opacity;
837      if (!need_tmpbuf && state->opacity != 0xff)
838        {
839          tmp = opacity * state->opacity + 0x80;
840          opacity = (tmp + (tmp >> 8)) >> 8;
841        }
842      rsvg_render_svp (ctx, svp2, state->fill, opacity);
843      art_svp_free (svp2);
844    }
845
846  if (state->stroke != NULL)
847    {
848      /* todo: libart doesn't yet implement anamorphic scaling of strokes */
849      double stroke_width = state->stroke_width *
850        art_affine_expansion (state->affine);
851
852      if (stroke_width < 0.25)
853        stroke_width = 0.25;
854
855      svp = art_svp_vpath_stroke (vpath, state->join, state->cap,
856                                  stroke_width, 4, 0.25);
857      opacity = state->stroke_opacity;
858      if (!need_tmpbuf && state->opacity != 0xff)
859        {
860          tmp = opacity * state->opacity + 0x80;
861          opacity = (tmp + (tmp >> 8)) >> 8;
862        }
863      rsvg_render_svp (ctx, svp, state->stroke, opacity);
864      art_svp_free (svp);
865    }
866
867  if (need_tmpbuf)
868    rsvg_pop_opacity_group (ctx, state->opacity);
869
870  art_free (vpath);
871}
872
873static void
874rsvg_start_path (RsvgHandle *ctx, const xmlChar **atts)
875{
876  int i;
877  char *d = NULL;
878
879  rsvg_parse_style_attrs (ctx, atts);
880  if (atts != NULL)
881    {
882      for (i = 0; atts[i] != NULL; i += 2)
883        {
884          if (!strcmp ((char *)atts[i], "d"))
885            d = (char *)atts[i + 1];
886        }
887    }
888  if (d != NULL)
889    {
890      RsvgBpathDef *bpath_def;
891
892      bpath_def = rsvg_parse_path (d);
893      rsvg_bpath_def_art_finish (bpath_def);
894
895      rsvg_render_bpath (ctx, bpath_def->bpath);
896
897      rsvg_bpath_def_free (bpath_def);
898    }
899}
900
901/* begin text - this should likely get split into its own .c file */
902
903typedef struct _RsvgSaxHandlerText RsvgSaxHandlerText;
904
905struct _RsvgSaxHandlerText {
906  RsvgSaxHandler super;
907  RsvgHandle *ctx;
908  double xpos;
909  double ypos;
910};
911
912static void
913rsvg_text_handler_free (RsvgSaxHandler *self)
914{
915  g_free (self);
916}
917
918static void
919rsvg_text_handler_characters (RsvgSaxHandler *self, const xmlChar *ch, int len)
920{
921  RsvgSaxHandlerText *z = (RsvgSaxHandlerText *)self;
922  RsvgHandle *ctx = z->ctx;
923  char *string;
924  int beg, end;
925  RsvgState *state;
926  ArtRender *render;
927  GdkPixbuf *pixbuf;
928  gboolean has_alpha;
929  int opacity;
930  PangoLayout *layout;
931  PangoFontDescription *font;
932  PangoLayoutLine *line;
933  PangoRectangle ink_rect, line_ink_rect;
934  FT_Bitmap bitmap;
935
936  state = &ctx->state[ctx->n_state - 1];
937  if (state->fill == NULL && state->font_size <= 0)
938    {
939      return;
940    }
941
942  pixbuf = ctx->pixbuf;
943  if (pixbuf == NULL)
944    {
945      /* FIXME: What warning/GError here? */
946      return;
947    }
948
949  /* Copy ch into string, chopping off leading and trailing whitespace */
950  for (beg = 0; beg < len; beg++)
951    if (!g_ascii_isspace (ch[beg]))
952      break;
953
954  for (end = len; end > beg; end--)
955    if (!g_ascii_isspace (ch[end - 1]))
956      break;
957
958  string = g_malloc (end - beg + 1);
959  memcpy (string, ch + beg, end - beg);
960  string[end - beg] = 0;
961
962#ifdef VERBOSE
963  fprintf (stderr, "text characters(%s, %d)\n", string, len);
964#endif
965
966  if (ctx->pango_context == NULL)
967    ctx->pango_context = pango_ft2_get_context (72, 72); /* FIXME: dpi? */
968
969  has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
970 
971  render = art_render_new (0, 0,
972                           gdk_pixbuf_get_width (pixbuf),
973                           gdk_pixbuf_get_height (pixbuf),
974                           gdk_pixbuf_get_pixels (pixbuf),
975                           gdk_pixbuf_get_rowstride (pixbuf),
976                           gdk_pixbuf_get_n_channels (pixbuf) -
977                           (has_alpha ? 1 : 0),
978                           gdk_pixbuf_get_bits_per_sample (pixbuf),
979                           has_alpha ? ART_ALPHA_SEPARATE : ART_ALPHA_NONE,
980                           NULL);
981 
982  layout = pango_layout_new (ctx->pango_context);
983  pango_layout_set_text (layout, string, -1);
984  font = pango_font_description_copy (pango_context_get_font_description (ctx->pango_context));
985  if (state->font_family)
986    pango_font_description_set_family_static (font, state->font_family);
987  pango_font_description_set_size (font, state->font_size * PANGO_SCALE);
988  pango_layout_set_font_description (layout, font);
989  pango_font_description_free (font);
990 
991  pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
992 
993  line = pango_layout_get_line (layout, 0);
994  if (line == NULL)
995    line_ink_rect = ink_rect; /* nothing to draw anyway */
996  else
997    pango_layout_line_get_pixel_extents (line, &line_ink_rect, NULL);
998 
999  bitmap.rows = ink_rect.height;
1000  bitmap.width = ink_rect.width;
1001  bitmap.pitch = (bitmap.width + 3) & ~3;
1002  bitmap.buffer = g_malloc0 (bitmap.rows * bitmap.pitch);
1003  bitmap.num_grays = 0x100;
1004  bitmap.pixel_mode = ft_pixel_mode_grays;
1005 
1006  pango_ft2_render_layout (&bitmap, layout, -ink_rect.x, -ink_rect.y);
1007 
1008  g_object_unref (layout);
1009 
1010  rsvg_render_paint_server (render, state->fill, NULL); /* todo: paint server ctx */
1011  opacity = state->fill_opacity * state->opacity;
1012  opacity = opacity + (opacity >> 7) + (opacity >> 14);
1013#ifdef VERBOSE
1014  fprintf (stderr, "opacity = %d\n", opacity);
1015#endif
1016  art_render_mask_solid (render, opacity);
1017  art_render_mask (render,
1018                   state->affine[4] + line_ink_rect.x,
1019                   state->affine[5] + line_ink_rect.y,
1020                   state->affine[4] + line_ink_rect.x + bitmap.width,
1021                   state->affine[5] + line_ink_rect.y + bitmap.rows,
1022                   bitmap.buffer, bitmap.pitch);
1023  art_render_invoke (render);
1024  g_free (bitmap.buffer);
1025
1026  g_free (string);
1027}
1028
1029static void
1030rsvg_start_text (RsvgHandle *ctx, const xmlChar **atts)
1031{
1032  RsvgSaxHandlerText *handler = g_new0 (RsvgSaxHandlerText, 1);
1033
1034  handler->super.free = rsvg_text_handler_free;
1035  handler->super.characters = rsvg_text_handler_characters;
1036  handler->ctx = ctx;
1037
1038  /* todo: parse "x" and "y" attributes */
1039  handler->xpos = 0;
1040  handler->ypos = 0;
1041
1042  rsvg_parse_style_attrs (ctx, atts);
1043  ctx->handler = &handler->super;
1044#ifdef VERBOSE
1045  fprintf (stderr, "begin text!\n");
1046#endif
1047}
1048
1049/* end text */
1050
1051static void
1052rsvg_start_defs (RsvgHandle *ctx, const xmlChar **atts)
1053{
1054  RsvgState *state = &ctx->state[ctx->n_state - 1];
1055
1056  state->in_defs = TRUE;
1057}
1058
1059typedef struct _RsvgSaxHandlerGstops RsvgSaxHandlerGstops;
1060
1061struct _RsvgSaxHandlerGstops {
1062  RsvgSaxHandler super;
1063  RsvgHandle *ctx;
1064  RsvgGradientStops *stops;
1065};
1066
1067static void
1068rsvg_gradient_stop_handler_free (RsvgSaxHandler *self)
1069{
1070  g_free (self);
1071}
1072
1073static void
1074rsvg_gradient_stop_handler_start (RsvgSaxHandler *self, const xmlChar *name,
1075                                  const xmlChar **atts)
1076{
1077  RsvgSaxHandlerGstops *z = (RsvgSaxHandlerGstops *)self;
1078  RsvgGradientStops *stops = z->stops;
1079  int i;
1080  double offset = 0;
1081  gboolean got_offset = FALSE;
1082  gint fixed;
1083  RsvgState state;
1084  int n_stop;
1085
1086  if (strcmp ((char *)name, "stop"))
1087    {
1088      g_warning ("unexpected <%s> element in gradient\n", name);
1089      return;
1090    }
1091
1092  rsvg_state_init (&state);
1093
1094  if (atts != NULL)
1095    {
1096      for (i = 0; atts[i] != NULL; i += 2)
1097        {
1098          if (!strcmp ((char *)atts[i], "offset"))
1099            {
1100              offset = rsvg_css_parse_length ((char *)atts[i + 1], &fixed);
1101              got_offset = TRUE;
1102            }
1103          else if (!strcmp ((char *)atts[i], "style"))
1104            rsvg_parse_style (z->ctx, &state, (char *)atts[i + 1]);
1105        }
1106    }
1107
1108  rsvg_state_finalize (&state);
1109
1110  if (!got_offset)
1111    {
1112      g_warning ("gradient stop must specify offset\n");
1113      return;
1114    }
1115
1116  n_stop = stops->n_stop++;
1117  if (n_stop == 0)
1118    stops->stop = g_new (RsvgGradientStop, 1);
1119  else if (!(n_stop & (n_stop - 1)))
1120    /* double the allocation if size is a power of two */
1121    stops->stop = g_renew (RsvgGradientStop, stops->stop, n_stop << 1);
1122  stops->stop[n_stop].offset = offset;
1123  stops->stop[n_stop].rgba = (state.stop_color << 8) | state.stop_opacity;
1124}
1125
1126static void
1127rsvg_gradient_stop_handler_end (RsvgSaxHandler *self, const xmlChar *name)
1128{
1129}
1130
1131static RsvgSaxHandler *
1132rsvg_gradient_stop_handler_new (RsvgHandle *ctx, RsvgGradientStops **p_stops)
1133{
1134  RsvgSaxHandlerGstops *gstops = g_new0 (RsvgSaxHandlerGstops, 1);
1135  RsvgGradientStops *stops = g_new (RsvgGradientStops, 1);
1136
1137  gstops->super.free = rsvg_gradient_stop_handler_free;
1138  gstops->super.start_element = rsvg_gradient_stop_handler_start;
1139  gstops->super.end_element = rsvg_gradient_stop_handler_end;
1140  gstops->ctx = ctx;
1141  gstops->stops = stops;
1142
1143  stops->n_stop = 0;
1144  stops->stop = NULL;
1145
1146  *p_stops = stops;
1147  return &gstops->super;
1148}
1149
1150static void
1151rsvg_linear_gradient_free (RsvgDefVal *self)
1152{
1153  RsvgLinearGradient *z = (RsvgLinearGradient *)self;
1154
1155  g_free (z->stops->stop);
1156  g_free (z->stops);
1157  g_free (self);
1158}
1159
1160static void
1161rsvg_start_linear_gradient (RsvgHandle *ctx, const xmlChar **atts)
1162{
1163  RsvgState *state = &ctx->state[ctx->n_state - 1];
1164  RsvgLinearGradient *grad;
1165  int i;
1166  char *id = NULL;
1167  double x1 = 0, y1 = 0, x2 = 100, y2 = 0;
1168  ArtGradientSpread spread = ART_GRADIENT_PAD;
1169
1170  /* todo: only handles numeric coordinates in gradientUnits = userSpace */
1171  if (atts != NULL)
1172    {
1173      for (i = 0; atts[i] != NULL; i += 2)
1174        {
1175          if (!strcmp ((char *)atts[i], "id"))
1176            id = (char *)atts[i + 1];
1177          else if (!strcmp ((char *)atts[i], "x1"))
1178            x1 = atof ((char *)atts[i + 1]);
1179          else if (!strcmp ((char *)atts[i], "y1"))
1180            y1 = atof ((char *)atts[i + 1]);
1181          else if (!strcmp ((char *)atts[i], "x2"))
1182            x2 = atof ((char *)atts[i + 1]);
1183          else if (!strcmp ((char *)atts[i], "y2"))
1184            y2 = atof ((char *)atts[i + 1]);
1185          else if (!strcmp ((char *)atts[i], "spreadMethod"))
1186            {
1187              if (!strcmp ((char *)atts[i + 1], "pad"))
1188                spread = ART_GRADIENT_PAD;
1189              else if (!strcmp ((char *)atts[i + 1], "reflect"))
1190                spread = ART_GRADIENT_REFLECT;
1191              else if (!strcmp ((char *)atts[i + 1], "repeat"))
1192                spread = ART_GRADIENT_REPEAT;
1193            }
1194        }
1195    }
1196
1197  grad = g_new (RsvgLinearGradient, 1);
1198  grad->super.type = RSVG_DEF_LINGRAD;
1199  grad->super.free = rsvg_linear_gradient_free;
1200
1201  ctx->handler = rsvg_gradient_stop_handler_new (ctx, &grad->stops);
1202
1203  rsvg_defs_set (ctx->defs, id, &grad->super);
1204
1205  for (i = 0; i < 6; i++)
1206    grad->affine[i] = state->affine[i];
1207  grad->x1 = x1;
1208  grad->y1 = y1;
1209  grad->x2 = x2;
1210  grad->y2 = y2;
1211  grad->spread = spread;
1212}
1213
1214static void
1215rsvg_radial_gradient_free (RsvgDefVal *self)
1216{
1217  RsvgRadialGradient *z = (RsvgRadialGradient *)self;
1218
1219  g_free (z->stops->stop);
1220  g_free (z->stops);
1221  g_free (self);
1222}
1223
1224static void
1225rsvg_start_radial_gradient (RsvgHandle *ctx, const xmlChar **atts)
1226{
1227  RsvgState *state = &ctx->state[ctx->n_state - 1];
1228  RsvgRadialGradient *grad;
1229  int i;
1230  char *id = NULL;
1231  double cx = 50, cy = 50, r = 50, fx = 50, fy = 50;
1232
1233  /* todo: only handles numeric coordinates in gradientUnits = userSpace */
1234  if (atts != NULL)
1235    {
1236      for (i = 0; atts[i] != NULL; i += 2)
1237        {
1238          if (!strcmp ((char *)atts[i], "id"))
1239            id = (char *)atts[i + 1];
1240          else if (!strcmp ((char *)atts[i], "cx"))
1241            cx = atof ((char *)atts[i + 1]);
1242          else if (!strcmp ((char *)atts[i], "cy"))
1243            cy = atof ((char *)atts[i + 1]);
1244          else if (!strcmp ((char *)atts[i], "r"))
1245            r = atof ((char *)atts[i + 1]);
1246          else if (!strcmp ((char *)atts[i], "fx"))
1247            fx = atof ((char *)atts[i + 1]);
1248          else if (!strcmp ((char *)atts[i], "fy"))
1249            fy = atof ((char *)atts[i + 1]);
1250        }
1251    }
1252
1253  grad = g_new (RsvgRadialGradient, 1);
1254  grad->super.type = RSVG_DEF_RADGRAD;
1255  grad->super.free = rsvg_radial_gradient_free;
1256
1257  ctx->handler = rsvg_gradient_stop_handler_new (ctx, &grad->stops);
1258
1259  rsvg_defs_set (ctx->defs, id, &grad->super);
1260
1261  for (i = 0; i < 6; i++)
1262    grad->affine[i] = state->affine[i];
1263  grad->cx = cx;
1264  grad->cy = cy;
1265  grad->r = r;
1266  grad->fx = fx;
1267  grad->fy = fy;
1268}
1269
1270static void
1271rsvg_start_element (void *data, const xmlChar *name, const xmlChar **atts)
1272{
1273  RsvgHandle *ctx = (RsvgHandle *)data;
1274#ifdef VERBOSE
1275  int i;
1276#endif
1277
1278#ifdef VERBOSE
1279  fprintf (stdout, "SAX.startElement(%s", (char *) name);
1280  if (atts != NULL) {
1281    for (i = 0;(atts[i] != NULL);i++) {
1282      fprintf (stdout, ", %s='", atts[i++]);
1283      fprintf (stdout, "%s'", atts[i]);
1284    }
1285  }
1286  fprintf (stdout, ")\n");
1287#endif
1288
1289  if (ctx->handler)
1290    {
1291      ctx->handler_nest++;
1292      if (ctx->handler->start_element != NULL)
1293        ctx->handler->start_element (ctx->handler, name, atts);
1294    }
1295  else
1296    {
1297      /* push the state stack */
1298      if (ctx->n_state == ctx->n_state_max)
1299        ctx->state = g_renew (RsvgState, ctx->state, ctx->n_state_max <<= 1);
1300      if (ctx->n_state)
1301        rsvg_state_clone (&ctx->state[ctx->n_state],
1302                          &ctx->state[ctx->n_state - 1]);
1303      else
1304        rsvg_state_init (ctx->state);
1305      ctx->n_state++;
1306
1307      if (!strcmp ((char *)name, "svg"))
1308        rsvg_start_svg (ctx, atts);
1309      else if (!strcmp ((char *)name, "g"))
1310        rsvg_start_g (ctx, atts);
1311      else if (!strcmp ((char *)name, "path"))
1312        rsvg_start_path (ctx, atts);
1313      else if (!strcmp ((char *)name, "text"))
1314        rsvg_start_text (ctx, atts);
1315      else if (!strcmp ((char *)name, "defs"))
1316        rsvg_start_defs (ctx, atts);
1317      else if (!strcmp ((char *)name, "linearGradient"))
1318        rsvg_start_linear_gradient (ctx, atts);
1319      else if (!strcmp ((char *)name, "radialGradient"))
1320        rsvg_start_radial_gradient (ctx, atts);
1321    }
1322}
1323
1324static void
1325rsvg_end_element (void *data, const xmlChar *name)
1326{
1327  RsvgHandle *ctx = (RsvgHandle *)data;
1328
1329  if (ctx->handler_nest > 0)
1330    {
1331      if (ctx->handler->end_element != NULL)
1332        ctx->handler->end_element (ctx->handler, name);
1333      ctx->handler_nest--;
1334    }
1335  else
1336    {
1337      if (ctx->handler != NULL)
1338        {
1339          ctx->handler->free (ctx->handler);
1340          ctx->handler = NULL;
1341        }
1342
1343      if (!strcmp ((char *)name, "g"))
1344        rsvg_end_g (ctx);
1345
1346      /* pop the state stack */
1347      ctx->n_state--;
1348      rsvg_state_finalize (&ctx->state[ctx->n_state]);
1349
1350#ifdef VERBOSE
1351      fprintf (stdout, "SAX.endElement(%s)\n", (char *) name);
1352#endif
1353    }
1354}
1355
1356static void
1357rsvg_characters (void *data, const xmlChar *ch, int len)
1358{
1359  RsvgHandle *ctx = (RsvgHandle *)data;
1360
1361  if (ctx->handler && ctx->handler->characters != NULL)
1362    ctx->handler->characters (ctx->handler, ch, len);
1363}
1364
1365static xmlEntityPtr
1366rsvg_get_entity (void *data, const xmlChar *name)
1367{
1368  RsvgHandle *ctx = (RsvgHandle *)data;
1369
1370  return (xmlEntityPtr)g_hash_table_lookup (ctx->entities, name);
1371}
1372
1373static void
1374rsvg_entity_decl (void *data, const xmlChar *name, int type,
1375                  const xmlChar *publicId, const xmlChar *systemId, xmlChar *content)
1376{
1377  RsvgHandle *ctx = (RsvgHandle *)data;
1378  GHashTable *entities = ctx->entities;
1379  xmlEntityPtr entity;
1380  char *dupname;
1381
1382  entity = g_new0 (xmlEntity, 1);
1383  entity->type = type;
1384  entity->length = strlen (name);
1385  dupname = g_strdup (name);
1386  entity->name = dupname;
1387  entity->ExternalID = g_strdup (publicId);
1388  entity->SystemID = g_strdup (systemId);
1389  if (content)
1390    {
1391      entity->content = xmlMemStrdup (content);
1392      entity->length = strlen (content);
1393    }
1394  g_hash_table_insert (entities, dupname, entity);
1395}
1396
1397static void
1398rsvg_error_cb (void *data, const char *msg, ...)
1399{
1400        va_list args;
1401       
1402        va_start (args, msg);
1403#ifdef VERBOSE
1404        fprintf (stderr, "fatal svg parse error: ");
1405#endif
1406        vfprintf (stderr, msg, args);
1407        va_end (args);
1408}
1409
1410static xmlSAXHandler rsvgSAXHandlerStruct = {
1411    NULL, /* internalSubset */
1412    NULL, /* isStandalone */
1413    NULL, /* hasInternalSubset */
1414    NULL, /* hasExternalSubset */
1415    NULL, /* resolveEntity */
1416    rsvg_get_entity, /* getEntity */
1417    rsvg_entity_decl, /* entityDecl */
1418    NULL, /* notationDecl */
1419    NULL, /* attributeDecl */
1420    NULL, /* elementDecl */
1421    NULL, /* unparsedEntityDecl */
1422    NULL, /* setDocumentLocator */
1423    NULL, /* startDocument */
1424    NULL, /* endDocument */
1425    rsvg_start_element, /* startElement */
1426    rsvg_end_element, /* endElement */
1427    NULL, /* reference */
1428    rsvg_characters, /* characters */
1429    NULL, /* ignorableWhitespace */
1430    NULL, /* processingInstruction */
1431    NULL, /* comment */
1432    NULL, /* xmlParserWarning */
1433    rsvg_error_cb, /* xmlParserError */
1434    rsvg_error_cb, /* xmlParserFatalError */
1435    NULL, /* getParameterEntity */
1436};
1437
1438GQuark
1439rsvg_error_quark (void)
1440{
1441  static GQuark q = 0;
1442  if (q == 0)
1443    q = g_quark_from_static_string ("rsvg-error-quark");
1444
1445  return q;
1446}
1447
1448/**
1449 * rsvg_handle_new:
1450 * @void:
1451 *
1452 * Returns a new rsvg handle.  Must be freed with @rsvg_handle_free.  This
1453 * handle can be used for dynamically loading an image.  You need to feed it
1454 * data using @rsvg_handle_write, then call @rsvg_handle_close when done.  No
1455 * more than one image can be loaded with one handle.
1456 *
1457 * Return value: A new #RsvgHandle
1458 **/
1459RsvgHandle *
1460rsvg_handle_new (void)
1461{
1462  RsvgHandle *handle;
1463
1464  handle = g_new0 (RsvgHandle, 1);
1465  handle->n_state = 0;
1466  handle->n_state_max = 16;
1467  handle->state = g_new (RsvgState, handle->n_state_max);
1468  handle->defs = rsvg_defs_new ();
1469  handle->handler_nest = 0;
1470  handle->entities = g_hash_table_new (g_str_hash, g_str_equal);
1471
1472  handle->ctxt = NULL;
1473
1474  return handle;
1475}
1476
1477/**
1478 * rsvg_handle_set_size_callback:
1479 * @handle: An #RsvgHandle
1480 * @size_func: A sizing function, or %NULL
1481 * @user_data: User data to pass to @size_func, or %NULL
1482 * @user_data_destroy: Destroy function for @user_data, or %NULL
1483 *
1484 * Sets the sizing function for the @handle.  This function is called right
1485 * after the size of the image has been loaded.  The size of the image is passed
1486 * in to the function, which may then modify these values to set the real size
1487 * of the generated pixbuf.  If the image has no associated size, then the size
1488 * arguments are set to -1.
1489 **/
1490void
1491rsvg_handle_set_size_callback (RsvgHandle     *handle,
1492                               RsvgSizeFunc    size_func,
1493                               gpointer        user_data,
1494                               GDestroyNotify  user_data_destroy)
1495{
1496  g_return_if_fail (handle != NULL);
1497
1498  if (handle->user_data_destroy)
1499    (* handle->user_data_destroy) (handle->user_data);
1500
1501  handle->size_func = size_func;
1502  handle->user_data = user_data;
1503  handle->user_data_destroy = user_data_destroy;
1504}
1505
1506/**
1507 * rsvg_handle_write:
1508 * @handle: An #RsvgHandle
1509 * @buf: Pointer to svg data
1510 * @count: length of the @buf buffer in bytes
1511 * @error: return location for errors
1512 *
1513 * Loads the next @count bytes of the image.  This will return #TRUE if the data
1514 * was loaded successful, and #FALSE if an error occurred.  In the latter case,
1515 * the loader will be closed, and will not accept further writes. If FALSE is
1516 * returned, @error will be set to an error from the #RSVG_ERROR domain.
1517 *
1518 * Return value: #TRUE if the write was successful, or #FALSE if there was an
1519 * error.
1520 **/
1521gboolean
1522rsvg_handle_write (RsvgHandle    *handle,
1523                   const guchar  *buf,
1524                   gsize          count,
1525                   GError       **error)
1526{
1527  GError *real_error;
1528  g_return_val_if_fail (handle != NULL, FALSE);
1529
1530  handle->error = &real_error;
1531  if (handle->ctxt == NULL)
1532    {
1533      handle->ctxt = xmlCreatePushParserCtxt (
1534              &rsvgSAXHandlerStruct, handle, NULL, 0, NULL);
1535      handle->ctxt->replaceEntities = TRUE;
1536    }
1537
1538  xmlParseChunk (handle->ctxt, buf, count, 0);
1539
1540  handle->error = NULL;
1541  /* FIXME: Error handling not implemented. */
1542  /*  if (*real_error != NULL)
1543    {
1544      g_propagate_error (error, real_error);
1545      return FALSE;
1546      }*/
1547  return TRUE;
1548}
1549
1550/**
1551 * rsvg_handle_close:
1552 * @handle: An #RsvgHandle
1553 *
1554 * Closes @handle, to indicate that loading the image is complete.  This will
1555 * return #TRUE if the loader closed successfully.  Note that @handle isn't
1556 * freed until @rsvg_handle_free is called.
1557 *
1558 * Return value: #TRUE if the loader closed successfully, or #FALSE if there was
1559 * an error.
1560 **/
1561gboolean
1562rsvg_handle_close (RsvgHandle  *handle,
1563                   GError     **error)
1564{
1565  gchar chars[1] = { '\0' };
1566  GError *real_error;
1567
1568  handle->error = &real_error;
1569
1570  if (handle->ctxt != NULL)
1571  {
1572    xmlParseChunk (handle->ctxt, chars, 1, TRUE);
1573    xmlFreeParserCtxt (handle->ctxt);
1574  }
1575 
1576  /* FIXME: Error handling not implemented. */
1577  /*
1578  if (real_error != NULL)
1579    {
1580      g_propagate_error (error, real_error);
1581      return FALSE;
1582      }*/
1583  return TRUE;
1584}
1585
1586/**
1587 * rsvg_handle_get_pixbuf:
1588 * @handle: An #RsvgHandle
1589 *
1590 * Returns the pixbuf loaded by #handle.  The pixbuf returned will be reffed, so
1591 * the caller of this function must assume that ref.  If insufficient data has
1592 * been read to create the pixbuf, or an error occurred in loading, then %NULL
1593 * will be returned.  Note that the pixbuf may not be complete until
1594 * @rsvg_handle_close has been called.
1595 *
1596 * Return value: the pixbuf loaded by #handle, or %NULL.
1597 **/
1598GdkPixbuf *
1599rsvg_handle_get_pixbuf (RsvgHandle *handle)
1600{
1601  g_return_val_if_fail (handle != NULL, NULL);
1602
1603  if (handle->pixbuf)
1604    return g_object_ref (handle->pixbuf);
1605
1606  return NULL;
1607}
1608
1609/**
1610 * rsvg_handle_free:
1611 * @handle: An #RsvgHandle
1612 *
1613 * Frees #handle.
1614 **/
1615void
1616rsvg_handle_free (RsvgHandle *handle)
1617{
1618  int i;
1619
1620  if (handle->pango_context != NULL)
1621    g_object_unref (handle->pango_context);
1622  rsvg_defs_free (handle->defs);
1623
1624  for (i = 0; i < handle->n_state; i++)
1625    rsvg_state_finalize (&handle->state[i]);
1626  g_free (handle->state);
1627
1628  g_hash_table_foreach (handle->entities, rsvg_ctx_free_helper, NULL);
1629  g_hash_table_destroy (handle->entities);
1630
1631  if (handle->user_data_destroy)
1632    (* handle->user_data_destroy) (handle->user_data);
1633  if (handle->pixbuf)
1634    g_object_unref (handle->pixbuf);
1635  g_free (handle);
1636}
1637
1638typedef enum {
1639  RSVG_SIZE_ZOOM,
1640  RSVG_SIZE_WH,
1641  RSVG_SIZE_WH_MAX,
1642  RSVG_SIZE_ZOOM_MAX
1643} RsvgSizeType;
1644
1645struct RsvgSizeCallbackData
1646{
1647  double x_zoom;
1648  double y_zoom;
1649  gint width;
1650  gint height;
1651  RsvgSizeType type;
1652  gboolean zoom_set;
1653  gboolean max_size_set;
1654};
1655
1656static void
1657rsvg_size_callback (int *width,
1658                    int *height,
1659                    gpointer  data)
1660{
1661  struct RsvgSizeCallbackData *real_data = (struct RsvgSizeCallbackData *) data;
1662  double zoomx, zoomy, zoom;
1663
1664  switch (real_data->type) {
1665  case RSVG_SIZE_ZOOM:
1666    if (*width < 0 || *height < 0)
1667      return;
1668
1669    *width = floor (real_data->x_zoom * *width + 0.5);
1670    *height = floor (real_data->y_zoom * *height + 0.5);
1671    return;
1672
1673  case RSVG_SIZE_ZOOM_MAX:
1674    if (*width < 0 || *height < 0)
1675      return;
1676
1677    *width = floor (real_data->x_zoom * *width + 0.5);
1678    *height = floor (real_data->y_zoom * *height + 0.5);
1679   
1680    if (*width > real_data->width || *height > real_data->height)
1681      {
1682        zoomx = (double) real_data->width / *width;
1683        zoomy = (double) real_data->height / *height;
1684        zoom = MIN (zoomx, zoomy);
1685       
1686        *width = floor (zoom * *width + 0.5);
1687        *height = floor (zoom * *height + 0.5);
1688      }
1689    return;
1690
1691  case RSVG_SIZE_WH_MAX:
1692    if (*width < 0 || *height < 0)
1693      return;
1694
1695    zoomx = (double) real_data->width / *width;
1696    zoomy = (double) real_data->height / *height;
1697    zoom = MIN (zoomx, zoomy);
1698   
1699    *width = floor (zoom * *width + 0.5);
1700    *height = floor (zoom * *height + 0.5);
1701    return;
1702
1703  case RSVG_SIZE_WH:
1704    if (*width < 0 || *height < 0)
1705      return;
1706
1707    if (real_data->width != -1)
1708      *width = real_data->width;
1709    if (real_data->height != -1)
1710      *height = real_data->height;
1711    return;
1712  }
1713
1714  g_assert_not_reached ();
1715}
1716
1717/**
1718 * rsvg_pixbuf_from_file:
1719 * @file_name: A file name
1720 * @error: return location for errors
1721 *
1722 * Loads a new #GdkPixbuf from @file_name and returns it.  The caller must
1723 * assume the reference to the reurned pixbuf. If an error occurred, @error is
1724 * set and %NULL is returned.
1725 *
1726 * Return value: A newly allocated #GdkPixbuf, or %NULL
1727 **/
1728GdkPixbuf *
1729rsvg_pixbuf_from_file (const gchar *file_name,
1730                       GError     **error)
1731{
1732  return rsvg_pixbuf_from_file_at_size (file_name, -1, -1, error);
1733}
1734
1735/**
1736 * rsvg_pixbuf_from_file_at_zoom:
1737 * @file_name: A file name
1738 * @x_zoom: The horizontal zoom factor
1739 * @y_zoom: The vertical zoom factor
1740 * @error: return location for errors
1741 *
1742 * Loads a new #GdkPixbuf from @file_name and returns it.  This pixbuf is scaled
1743 * from the size indicated by the file by a factor of @x_zoom and @y_zoom.  The
1744 * caller must assume the reference to the returned pixbuf. If an error
1745 * occurred, @error is set and %NULL is returned.
1746 *
1747 * Return value: A newly allocated #GdkPixbuf, or %NULL
1748 **/
1749GdkPixbuf *
1750rsvg_pixbuf_from_file_at_zoom (const gchar *file_name,
1751                               double       x_zoom,
1752                               double       y_zoom,
1753                               GError     **error)
1754{
1755  FILE *f;
1756  char chars[SVG_BUFFER_SIZE];
1757  gint result;
1758  GdkPixbuf *retval;
1759  RsvgHandle *handle;
1760  struct RsvgSizeCallbackData data;
1761
1762  g_return_val_if_fail (file_name != NULL, NULL);
1763  g_return_val_if_fail (x_zoom > 0.0 && y_zoom > 0.0, NULL);
1764
1765  f = fopen (file_name, "r");
1766  if (!f)
1767    {
1768      /* FIXME: Set up error. */
1769      return NULL;
1770    }
1771
1772  handle = rsvg_handle_new ();
1773  data.type = RSVG_SIZE_ZOOM;
1774  data.x_zoom = x_zoom;
1775  data.y_zoom = y_zoom;
1776
1777  rsvg_handle_set_size_callback (handle, rsvg_size_callback, &data, NULL);
1778  while ((result = fread (chars, 1, SVG_BUFFER_SIZE, f)) > 0)
1779    rsvg_handle_write (handle, chars, result, error);
1780  rsvg_handle_close (handle, error);
1781 
1782  retval = rsvg_handle_get_pixbuf (handle);
1783
1784  fclose (f);
1785  rsvg_handle_free (handle);
1786  return retval;
1787}
1788
1789/**
1790 * rsvg_pixbuf_from_file_at_zoom_with_max:
1791 * @file_name: A file name
1792 * @x_zoom: The horizontal zoom factor
1793 * @y_zoom: The vertical zoom factor
1794 * @max_width: The requested max width
1795 * @max_height: The requested max heigh
1796 * @error: return location for errors
1797 *
1798 * Loads a new #GdkPixbuf from @file_name and returns it.  This pixbuf is scaled
1799 * from the size indicated by the file by a factor of @x_zoom and @y_zoom. If the
1800 * resulting pixbuf would be larger than max_width/max_heigh it is uniformly scaled
1801 * down to fit in that rectangle. The caller must assume the reference to the
1802 * returned pixbuf. If an error occurred, @error is set and %NULL is returned.
1803 *
1804 * Return value: A newly allocated #GdkPixbuf, or %NULL
1805 **/
1806GdkPixbuf  *
1807rsvg_pixbuf_from_file_at_zoom_with_max (const gchar  *file_name,
1808                                        double        x_zoom,
1809                                        double        y_zoom,
1810                                        gint          max_width,
1811                                        gint          max_height,
1812                                        GError      **error)
1813{
1814  FILE *f;
1815  char chars[SVG_BUFFER_SIZE];
1816  gint result;
1817  GdkPixbuf *retval;
1818  RsvgHandle *handle;
1819  struct RsvgSizeCallbackData data;
1820
1821  g_return_val_if_fail (file_name != NULL, NULL);
1822  g_return_val_if_fail (x_zoom > 0.0 && y_zoom > 0.0, NULL);
1823
1824  f = fopen (file_name, "r");
1825  if (!f)
1826    {
1827      /* FIXME: Set up error. */
1828      return NULL;
1829    }
1830
1831  handle = rsvg_handle_new ();
1832  data.type = RSVG_SIZE_ZOOM_MAX;
1833  data.x_zoom = x_zoom;
1834  data.y_zoom = y_zoom;
1835  data.width = max_width;
1836  data.height = max_height;
1837
1838  rsvg_handle_set_size_callback (handle, rsvg_size_callback, &data, NULL);
1839  while ((result = fread (chars, 1, SVG_BUFFER_SIZE, f)) > 0)
1840    rsvg_handle_write (handle, chars, result, error);
1841  rsvg_handle_close (handle, error);
1842 
1843  retval = rsvg_handle_get_pixbuf (handle);
1844
1845  fclose (f);
1846  rsvg_handle_free (handle);
1847  return retval;
1848}
1849
1850/**
1851 * rsvg_pixbuf_from_file_at_size:
1852 * @file_name: A file name
1853 * @width: The new width, or -1
1854 * @height: The new height, or -1
1855 * @error: return location for errors
1856 *
1857 * Loads a new #GdkPixbuf from @file_name and returns it.  This pixbuf is scaled
1858 * from the size indicated to the new size indicated by @width and @height.  If
1859 * either of these are -1, then the default size of the image being loaded is
1860 * used.  The caller must assume the reference to the returned pixbuf. If an
1861 * error occurred, @error is set and %NULL is returned.
1862 *
1863 * Return value: A newly allocated #GdkPixbuf, or %NULL
1864 **/
1865GdkPixbuf *
1866rsvg_pixbuf_from_file_at_size (const gchar *file_name,
1867                               gint         width,
1868                               gint         height,
1869                               GError     **error)
1870{
1871  FILE *f;
1872  char chars[SVG_BUFFER_SIZE];
1873  gint result;
1874  GdkPixbuf *retval;
1875  RsvgHandle *handle;
1876  struct RsvgSizeCallbackData data;
1877
1878  f = fopen (file_name, "r");
1879  if (!f)
1880    {
1881      /* FIXME: Set up error. */
1882      return NULL;
1883    }
1884  handle = rsvg_handle_new ();
1885  data.type = RSVG_SIZE_WH;
1886  data.width = width;
1887  data.height = height;
1888
1889  rsvg_handle_set_size_callback (handle, rsvg_size_callback, &data, NULL);
1890  while ((result = fread (chars, 1, SVG_BUFFER_SIZE, f)) > 0)
1891    rsvg_handle_write (handle, chars, result, error);
1892
1893  rsvg_handle_close (handle, error);
1894 
1895  retval = rsvg_handle_get_pixbuf (handle);
1896
1897  fclose (f);
1898  rsvg_handle_free (handle);
1899  return retval;
1900}
1901
1902/**
1903 * rsvg_pixbuf_from_file_at_max_size:
1904 * @file_name: A file name
1905 * @max_width: The requested max width
1906 * @max_height: The requested max heigh
1907 * @error: return location for errors
1908 *
1909 * Loads a new #GdkPixbuf from @file_name and returns it.  This pixbuf is uniformly
1910 * scaled so that the it fits into a rectangle of size max_width * max_height. The
1911 * caller must assume the reference to the returned pixbuf. If an error occurred,
1912 * @error is set and %NULL is returned.
1913 *
1914 * Return value: A newly allocated #GdkPixbuf, or %NULL
1915 **/
1916GdkPixbuf  *
1917rsvg_pixbuf_from_file_at_max_size (const gchar     *file_name,
1918                                   gint             max_width,
1919                                   gint             max_height,
1920                                   GError         **error)
1921{
1922  FILE *f;
1923  char chars[SVG_BUFFER_SIZE];
1924  gint result;
1925  GdkPixbuf *retval;
1926  RsvgHandle *handle;
1927  struct RsvgSizeCallbackData data;
1928
1929  f = fopen (file_name, "r");
1930  if (!f)
1931    {
1932      /* FIXME: Set up error. */
1933      return NULL;
1934    }
1935  handle = rsvg_handle_new ();
1936  data.type = RSVG_SIZE_WH_MAX;
1937  data.width = max_width;
1938  data.height = max_height;
1939
1940  rsvg_handle_set_size_callback (handle, rsvg_size_callback, &data, NULL);
1941  while ((result = fread (chars, 1, SVG_BUFFER_SIZE, f)) > 0)
1942    rsvg_handle_write (handle, chars, result, error);
1943
1944  rsvg_handle_close (handle, error);
1945 
1946  retval = rsvg_handle_get_pixbuf (handle);
1947
1948  fclose (f);
1949  rsvg_handle_free (handle);
1950  return retval;
1951}
1952
1953
Note: See TracBrowser for help on using the repository browser.