1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3%                                                                             %
4%                                                                             %
5%                                                                             %
6%                            SSSSS  V   V   GGGG                              %
7%                            SS     V   V  G                                  %
8%                             SSS   V   V  G GG                               %
9%                               SS   V V   G   G                              %
10%                            SSSSS    V     GGG                               %
11%                                                                             %
12%                                                                             %
13%                  Read/Write Scalable Vector Graphics Format                 %
14%                                                                             %
15%                              Software Design                                %
16%                                   Cristy                                    %
17%                             William Radcliffe                               %
18%                                March 2000                                   %
19%                                                                             %
20%                                                                             %
21%  Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization      %
22%  dedicated to making software imaging solutions freely available.           %
23%                                                                             %
24%  You may not use this file except in compliance with the License.  You may  %
25%  obtain a copy of the License at                                            %
26%                                                                             %
27%    http://www.imagemagick.org/script/license.php                            %
28%                                                                             %
29%  Unless required by applicable law or agreed to in writing, software        %
30%  distributed under the License is distributed on an "AS IS" BASIS,          %
31%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
32%  See the License for the specific language governing permissions and        %
33%  limitations under the License.                                             %
34%                                                                             %
35%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36%
37%
38*/
39
40/*
41  Include declarations.
42*/
43#include "MagickCore/studio.h"
44#include "MagickCore/annotate.h"
45#include "MagickCore/artifact.h"
46#include "MagickCore/attribute.h"
47#include "MagickCore/blob.h"
48#include "MagickCore/blob-private.h"
49#include "MagickCore/cache.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/composite-private.h"
52#include "MagickCore/delegate.h"
53#include "MagickCore/delegate-private.h"
54#include "MagickCore/draw.h"
55#include "MagickCore/exception.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/gem.h"
58#include "MagickCore/image.h"
59#include "MagickCore/image-private.h"
60#include "MagickCore/list.h"
61#include "MagickCore/log.h"
62#include "MagickCore/magick.h"
63#include "MagickCore/memory_.h"
64#include "MagickCore/module.h"
65#include "MagickCore/monitor.h"
66#include "MagickCore/monitor-private.h"
67#include "MagickCore/quantum-private.h"
68#include "MagickCore/pixel-accessor.h"
69#include "MagickCore/property.h"
70#include "MagickCore/resource_.h"
71#include "MagickCore/static.h"
72#include "MagickCore/string_.h"
73#include "MagickCore/string-private.h"
74#include "MagickCore/token.h"
75#include "MagickCore/utility.h"
76#if defined(MAGICKCORE_XML_DELEGATE)
77#  if defined(MAGICKCORE_WINDOWS_SUPPORT)
78#    if !defined(__MINGW32__) && !defined(__MINGW64__)
79#      include <win32config.h>
80#    endif
81#  endif
82#  include <libxml/parser.h>
83#  include <libxml/xmlmemory.h>
84#  include <libxml/parserInternals.h>
85#  include <libxml/xmlerror.h>
86#endif
87
88#if defined(MAGICKCORE_AUTOTRACE_DELEGATE)
89#include "autotrace/autotrace.h"
90#endif
91
92#if defined(MAGICKCORE_RSVG_DELEGATE)
93#include "librsvg/rsvg.h"
94#if !defined(LIBRSVG_CHECK_VERSION)
95#include "librsvg/rsvg-cairo.h"
96#include "librsvg/librsvg-features.h"
97#elif !LIBRSVG_CHECK_VERSION(2,36,2)
98#include "librsvg/rsvg-cairo.h"
99#include "librsvg/librsvg-features.h"
100#endif
101#endif
102
103/*
104  Typedef declarations.
105*/
106typedef struct _BoundingBox
107{
108  double
109    x,
110    y,
111    width,
112    height;
113} BoundingBox;
114
115typedef struct _ElementInfo
116{
117  double
118    cx,
119    cy,
120    major,
121    minor,
122    angle;
123} ElementInfo;
124
125typedef struct _SVGInfo
126{
127  FILE
128    *file;
129
130  ExceptionInfo
131    *exception;
132
133  Image
134    *image;
135
136  const ImageInfo
137    *image_info;
138
139  AffineMatrix
140    affine;
141
142  size_t
143    width,
144    height;
145
146  char
147    *size,
148    *title,
149    *comment;
150
151  int
152    n;
153
154  double
155    *scale,
156    pointsize;
157
158  ElementInfo
159    element;
160
161  SegmentInfo
162    segment;
163
164  BoundingBox
165    bounds,
166    center,
167    view_box;
168
169  PointInfo
170    radius;
171
172  char
173    *stop_color,
174    *offset,
175    *text,
176    *vertices,
177    *url;
178
179#if defined(MAGICKCORE_XML_DELEGATE)
180  xmlParserCtxtPtr
181    parser;
182
183  xmlDocPtr
184    document;
185#endif
186} SVGInfo;
187
188/*
189  Forward declarations.
190*/
191static MagickBooleanType
192  WriteSVGImage(const ImageInfo *,Image *,ExceptionInfo *);
193
194/*
195%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
196%                                                                             %
197%                                                                             %
198%                                                                             %
199%   I s S V G                                                                 %
200%                                                                             %
201%                                                                             %
202%                                                                             %
203%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
204%
205%  IsSVG()() returns MagickTrue if the image format type, identified by the
206%  magick string, is SVG.
207%
208%  The format of the IsSVG method is:
209%
210%      MagickBooleanType IsSVG(const unsigned char *magick,const size_t length)
211%
212%  A description of each parameter follows:
213%
214%    o magick: compare image format pattern against these bytes.
215%
216%    o length: Specifies the length of the magick string.
217%
218*/
219static MagickBooleanType IsSVG(const unsigned char *magick,const size_t length)
220{
221  if (length < 4)
222    return(MagickFalse);
223  if (LocaleNCompare((const char *) magick,"?xml",4) == 0)
224    return(MagickTrue);
225  return(MagickFalse);
226}
227
228#if defined(MAGICKCORE_XML_DELEGATE)
229/*
230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
231%                                                                             %
232%                                                                             %
233%                                                                             %
234%   R e a d S V G I m a g e                                                   %
235%                                                                             %
236%                                                                             %
237%                                                                             %
238%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
239%
240%  ReadSVGImage() reads a Scalable Vector Gaphics file and returns it.  It
241%  allocates the memory necessary for the new Image structure and returns a
242%  pointer to the new image.
243%
244%  The format of the ReadSVGImage method is:
245%
246%      Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
247%
248%  A description of each parameter follows:
249%
250%    o image_info: the image info.
251%
252%    o exception: return any errors or warnings in this structure.
253%
254*/
255
256static SVGInfo *AcquireSVGInfo(void)
257{
258  SVGInfo
259    *svg_info;
260
261  svg_info=(SVGInfo *) AcquireMagickMemory(sizeof(*svg_info));
262  if (svg_info == (SVGInfo *) NULL)
263    return((SVGInfo *) NULL);
264  (void) ResetMagickMemory(svg_info,0,sizeof(*svg_info));
265  svg_info->text=AcquireString("");
266  svg_info->scale=(double *) AcquireMagickMemory(sizeof(*svg_info->scale));
267  if (svg_info->scale == (double *) NULL)
268    ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
269  GetAffineMatrix(&svg_info->affine);
270  svg_info->scale[0]=ExpandAffine(&svg_info->affine);
271  return(svg_info);
272}
273
274static SVGInfo *DestroySVGInfo(SVGInfo *svg_info)
275{
276  if (svg_info->text != (char *) NULL)
277    svg_info->text=DestroyString(svg_info->text);
278  if (svg_info->scale != (double *) NULL)
279    svg_info->scale=(double *) RelinquishMagickMemory(svg_info->scale);
280  if (svg_info->title != (char *) NULL)
281    svg_info->title=DestroyString(svg_info->title);
282  if (svg_info->comment != (char *) NULL)
283    svg_info->comment=DestroyString(svg_info->comment);
284  return((SVGInfo *) RelinquishMagickMemory(svg_info));
285}
286
287static double GetUserSpaceCoordinateValue(const SVGInfo *svg_info,int type,
288  const char *string)
289{
290  char
291    *next_token,
292    token[MagickPathExtent];
293
294  const char
295    *p;
296
297  double
298    value;
299
300  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",string);
301  assert(string != (const char *) NULL);
302  p=(const char *) string;
303  GetNextToken(p,&p,MagickPathExtent,token);
304  value=StringToDouble(token,&next_token);
305  if (strchr(token,'%') != (char *) NULL)
306    {
307      double
308        alpha,
309        beta;
310
311      if (type > 0)
312        {
313          if (svg_info->view_box.width == 0.0)
314            return(0.0);
315          return(svg_info->view_box.width*value/100.0);
316        }
317      if (type < 0)
318        {
319          if (svg_info->view_box.height == 0.0)
320            return(0.0);
321          return(svg_info->view_box.height*value/100.0);
322        }
323      alpha=value-svg_info->view_box.width;
324      beta=value-svg_info->view_box.height;
325      return(hypot(alpha,beta)/sqrt(2.0)/100.0);
326    }
327  GetNextToken(p,&p,MagickPathExtent,token);
328  if (LocaleNCompare(token,"cm",2) == 0)
329    return(DefaultResolution*svg_info->scale[0]/2.54*value);
330  if (LocaleNCompare(token,"em",2) == 0)
331    return(svg_info->pointsize*value);
332  if (LocaleNCompare(token,"ex",2) == 0)
333    return(svg_info->pointsize*value/2.0);
334  if (LocaleNCompare(token,"in",2) == 0)
335    return(DefaultResolution*svg_info->scale[0]*value);
336  if (LocaleNCompare(token,"mm",2) == 0)
337    return(DefaultResolution*svg_info->scale[0]/25.4*value);
338  if (LocaleNCompare(token,"pc",2) == 0)
339    return(DefaultResolution*svg_info->scale[0]/6.0*value);
340  if (LocaleNCompare(token,"pt",2) == 0)
341    return(1.25*svg_info->scale[0]*value);
342  if (LocaleNCompare(token,"px",2) == 0)
343    return(value);
344  return(value);
345}
346
347static void StripStyleTokens(char *message)
348{
349  register char
350    *p,
351    *q;
352
353  size_t
354    length;
355
356  assert(message != (char *) NULL);
357  if (*message == '\0')
358    return;
359  length=strlen(message);
360  p=message;
361  while (isspace((int) ((unsigned char) *p)) != 0)
362    p++;
363  q=message+length-1;
364  while ((isspace((int) ((unsigned char) *q)) != 0) && (q > p))
365    q--;
366  (void) CopyMagickMemory(message,p,(size_t) (q-p+1));
367  message[q-p+1]='\0';
368  StripString(message);
369}
370
371static char **GetStyleTokens(void *context,const char *style,int *number_tokens)
372{
373  char
374    *text,
375    **tokens;
376
377  register ssize_t
378    i;
379
380  SVGInfo
381    *svg_info;
382
383  svg_info=(SVGInfo *) context;
384  (void) svg_info;
385  *number_tokens=0;
386  if (style == (const char *) NULL)
387    return((char **) NULL);
388  text=AcquireString(style);
389  (void) SubstituteString(&text,":","\n");
390  (void) SubstituteString(&text,";","\n");
391  tokens=StringToList(text);
392  text=DestroyString(text);
393  for (i=0; tokens[i] != (char *) NULL; i++)
394    StripStyleTokens(tokens[i]);
395  *number_tokens=(int) i;
396  return(tokens);
397}
398
399static char **GetTransformTokens(void *context,const char *text,
400  int *number_tokens)
401{
402  char
403    **tokens;
404
405  register const char
406    *p,
407    *q;
408
409  register size_t
410    i;
411
412  size_t
413    extent;
414
415  SVGInfo
416    *svg_info;
417
418  svg_info=(SVGInfo *) context;
419  *number_tokens=0;
420  if (text == (const char *) NULL)
421    return((char **) NULL);
422  extent=8;
423  tokens=(char **) AcquireQuantumMemory(extent+2UL,sizeof(*tokens));
424  if (tokens == (char **) NULL)
425    {
426      (void) ThrowMagickException(svg_info->exception,GetMagickModule(),
427        ResourceLimitError,"MemoryAllocationFailed","`%s'",text);
428      return((char **) NULL);
429    }
430  /*
431    Convert string to an ASCII list.
432  */
433  i=0;
434  p=text;
435  for (q=p; *q != '\0'; q++)
436  {
437    if ((*q != '(') && (*q != ')') && (*q != '\0'))
438      continue;
439    if (i == extent)
440      {
441        extent<<=1;
442        tokens=(char **) ResizeQuantumMemory(tokens,extent+2,sizeof(*tokens));
443        if (tokens == (char **) NULL)
444          {
445            (void) ThrowMagickException(svg_info->exception,GetMagickModule(),
446              ResourceLimitError,"MemoryAllocationFailed","`%s'",text);
447            return((char **) NULL);
448          }
449      }
450    tokens[i]=AcquireString(p);
451    (void) CopyMagickString(tokens[i],p,(size_t) (q-p+1));
452    StripString(tokens[i]);
453    i++;
454    p=q+1;
455  }
456  tokens[i]=AcquireString(p);
457  (void) CopyMagickString(tokens[i],p,(size_t) (q-p+1));
458  StripString(tokens[i++]);
459  tokens[i]=(char *) NULL;
460  *number_tokens=i;
461  return(tokens);
462}
463
464#if defined(__cplusplus) || defined(c_plusplus)
465extern "C" {
466#endif
467
468static int SVGIsStandalone(void *context)
469{
470  SVGInfo
471    *svg_info;
472
473  /*
474    Is this document tagged standalone?
475  */
476  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.SVGIsStandalone()");
477  svg_info=(SVGInfo *) context;
478  return(svg_info->document->standalone == 1);
479}
480
481static int SVGHasInternalSubset(void *context)
482{
483  SVGInfo
484    *svg_info;
485
486  /*
487    Does this document has an internal subset?
488  */
489  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
490    "  SAX.SVGHasInternalSubset()");
491  svg_info=(SVGInfo *) context;
492  return(svg_info->document->intSubset != NULL);
493}
494
495static int SVGHasExternalSubset(void *context)
496{
497  SVGInfo
498    *svg_info;
499
500  /*
501    Does this document has an external subset?
502  */
503  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
504    "  SAX.SVGHasExternalSubset()");
505  svg_info=(SVGInfo *) context;
506  return(svg_info->document->extSubset != NULL);
507}
508
509static void SVGInternalSubset(void *context,const xmlChar *name,
510  const xmlChar *external_id,const xmlChar *system_id)
511{
512  SVGInfo
513    *svg_info;
514
515  /*
516    Does this document has an internal subset?
517  */
518  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
519    "  SAX.internalSubset(%s, %s, %s)",(const char *) name,
520    (external_id != (const xmlChar *) NULL ? (const char *) external_id : "none"),
521    (system_id != (const xmlChar *) NULL ? (const char *) system_id : "none"));
522  svg_info=(SVGInfo *) context;
523  (void) xmlCreateIntSubset(svg_info->document,name,external_id,system_id);
524}
525
526static xmlParserInputPtr SVGResolveEntity(void *context,
527  const xmlChar *public_id,const xmlChar *system_id)
528{
529  SVGInfo
530    *svg_info;
531
532  xmlParserInputPtr
533    stream;
534
535  /*
536    Special entity resolver, better left to the parser, it has more
537    context than the application layer.  The default behaviour is to
538    not resolve the entities, in that case the ENTITY_REF nodes are
539    built in the structure (and the parameter values).
540  */
541  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
542    "  SAX.resolveEntity(%s, %s)",
543    (public_id != (const xmlChar *) NULL ? (const char *) public_id : "none"),
544    (system_id != (const xmlChar *) NULL ? (const char *) system_id : "none"));
545  svg_info=(SVGInfo *) context;
546  stream=xmlLoadExternalEntity((const char *) system_id,(const char *)
547    public_id,svg_info->parser);
548  return(stream);
549}
550
551static xmlEntityPtr SVGGetEntity(void *context,const xmlChar *name)
552{
553  SVGInfo
554    *svg_info;
555
556  /*
557    Get an entity by name.
558  */
559  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.SVGGetEntity(%s)",
560    name);
561  svg_info=(SVGInfo *) context;
562  return(xmlGetDocEntity(svg_info->document,name));
563}
564
565static xmlEntityPtr SVGGetParameterEntity(void *context,const xmlChar *name)
566{
567  SVGInfo
568    *svg_info;
569
570  /*
571    Get a parameter entity by name.
572  */
573  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
574    "  SAX.getParameterEntity(%s)",name);
575  svg_info=(SVGInfo *) context;
576  return(xmlGetParameterEntity(svg_info->document,name));
577}
578
579static void SVGEntityDeclaration(void *context,const xmlChar *name,int type,
580  const xmlChar *public_id,const xmlChar *system_id,xmlChar *content)
581{
582  SVGInfo
583    *svg_info;
584
585  /*
586    An entity definition has been parsed.
587  */
588  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
589    "  SAX.entityDecl(%s, %d, %s, %s, %s)",name,type,
590    public_id != (xmlChar *) NULL ? (const char *) public_id : "none",
591    system_id != (xmlChar *) NULL ? (const char *) system_id : "none",content);
592  svg_info=(SVGInfo *) context;
593  if (svg_info->parser->inSubset == 1)
594    (void) xmlAddDocEntity(svg_info->document,name,type,public_id,system_id,
595      content);
596  else
597    if (svg_info->parser->inSubset == 2)
598      (void) xmlAddDtdEntity(svg_info->document,name,type,public_id,system_id,
599        content);
600}
601
602static void SVGAttributeDeclaration(void *context,const xmlChar *element,
603  const xmlChar *name,int type,int value,const xmlChar *default_value,
604  xmlEnumerationPtr tree)
605{
606  SVGInfo
607    *svg_info;
608
609  xmlChar
610    *fullname,
611    *prefix;
612
613  xmlParserCtxtPtr
614    parser;
615
616  /*
617    An attribute definition has been parsed.
618  */
619  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
620    "  SAX.attributeDecl(%s, %s, %d, %d, %s, ...)",element,name,type,value,
621    default_value);
622  svg_info=(SVGInfo *) context;
623  fullname=(xmlChar *) NULL;
624  prefix=(xmlChar *) NULL;
625  parser=svg_info->parser;
626  fullname=(xmlChar *) xmlSplitQName(parser,name,&prefix);
627  if (parser->inSubset == 1)
628    (void) xmlAddAttributeDecl(&parser->vctxt,svg_info->document->intSubset,
629      element,fullname,prefix,(xmlAttributeType) type,
630      (xmlAttributeDefault) value,default_value,tree);
631  else
632    if (parser->inSubset == 2)
633      (void) xmlAddAttributeDecl(&parser->vctxt,svg_info->document->extSubset,
634        element,fullname,prefix,(xmlAttributeType) type,
635        (xmlAttributeDefault) value,default_value,tree);
636  if (prefix != (xmlChar *) NULL)
637    xmlFree(prefix);
638  if (fullname != (xmlChar *) NULL)
639    xmlFree(fullname);
640}
641
642static void SVGElementDeclaration(void *context,const xmlChar *name,int type,
643  xmlElementContentPtr content)
644{
645  SVGInfo
646    *svg_info;
647
648  xmlParserCtxtPtr
649    parser;
650
651  /*
652    An element definition has been parsed.
653  */
654  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
655    "  SAX.elementDecl(%s, %d, ...)",name,type);
656  svg_info=(SVGInfo *) context;
657  parser=svg_info->parser;
658  if (parser->inSubset == 1)
659    (void) xmlAddElementDecl(&parser->vctxt,svg_info->document->intSubset,
660      name,(xmlElementTypeVal) type,content);
661  else
662    if (parser->inSubset == 2)
663      (void) xmlAddElementDecl(&parser->vctxt,svg_info->document->extSubset,
664        name,(xmlElementTypeVal) type,content);
665}
666
667static void SVGNotationDeclaration(void *context,const xmlChar *name,
668  const xmlChar *public_id,const xmlChar *system_id)
669{
670  SVGInfo
671    *svg_info;
672
673  xmlParserCtxtPtr
674    parser;
675
676  /*
677    What to do when a notation declaration has been parsed.
678  */
679  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
680    "  SAX.notationDecl(%s, %s, %s)",name,
681    public_id != (const xmlChar *) NULL ? (const char *) public_id : "none",
682    system_id != (const xmlChar *) NULL ? (const char *) system_id : "none");
683  svg_info=(SVGInfo *) context;
684  parser=svg_info->parser;
685  if (parser->inSubset == 1)
686    (void) xmlAddNotationDecl(&parser->vctxt,svg_info->document->intSubset,
687      name,public_id,system_id);
688  else
689    if (parser->inSubset == 2)
690      (void) xmlAddNotationDecl(&parser->vctxt,svg_info->document->intSubset,
691        name,public_id,system_id);
692}
693
694static void SVGUnparsedEntityDeclaration(void *context,const xmlChar *name,
695  const xmlChar *public_id,const xmlChar *system_id,const xmlChar *notation)
696{
697  SVGInfo
698    *svg_info;
699
700  /*
701    What to do when an unparsed entity declaration is parsed.
702  */
703  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
704    "  SAX.unparsedEntityDecl(%s, %s, %s, %s)",name,
705    public_id != (xmlChar *) NULL ? (const char *) public_id : "none",
706    system_id != (xmlChar *) NULL ? (const char *) system_id : "none",notation);
707  svg_info=(SVGInfo *) context;
708  (void) xmlAddDocEntity(svg_info->document,name,
709    XML_EXTERNAL_GENERAL_UNPARSED_ENTITY,public_id,system_id,notation);
710
711}
712
713static void SVGSetDocumentLocator(void *context,xmlSAXLocatorPtr location)
714{
715  SVGInfo
716    *svg_info;
717
718  /*
719    Receive the document locator at startup, actually xmlDefaultSAXLocator.
720  */
721  (void) location;
722  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
723    "  SAX.setDocumentLocator()");
724  svg_info=(SVGInfo *) context;
725  (void) svg_info;
726}
727
728static void SVGStartDocument(void *context)
729{
730  SVGInfo
731    *svg_info;
732
733  xmlParserCtxtPtr
734    parser;
735
736  /*
737    Called when the document start being processed.
738  */
739  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.startDocument()");
740  svg_info=(SVGInfo *) context;
741  parser=svg_info->parser;
742  svg_info->document=xmlNewDoc(parser->version);
743  if (svg_info->document == (xmlDocPtr) NULL)
744    return;
745  if (parser->encoding == NULL)
746    svg_info->document->encoding=(const xmlChar *) NULL;
747  else
748    svg_info->document->encoding=xmlStrdup(parser->encoding);
749  svg_info->document->standalone=parser->standalone;
750}
751
752static void SVGEndDocument(void *context)
753{
754  SVGInfo
755    *svg_info;
756
757  /*
758    Called when the document end has been detected.
759  */
760  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.endDocument()");
761  svg_info=(SVGInfo *) context;
762  if (svg_info->offset != (char *) NULL)
763    svg_info->offset=DestroyString(svg_info->offset);
764  if (svg_info->stop_color != (char *) NULL)
765    svg_info->stop_color=DestroyString(svg_info->stop_color);
766  if (svg_info->scale != (double *) NULL)
767    svg_info->scale=(double *) RelinquishMagickMemory(svg_info->scale);
768  if (svg_info->text != (char *) NULL)
769    svg_info->text=DestroyString(svg_info->text);
770  if (svg_info->vertices != (char *) NULL)
771    svg_info->vertices=DestroyString(svg_info->vertices);
772  if (svg_info->url != (char *) NULL)
773    svg_info->url=DestroyString(svg_info->url);
774#if defined(MAGICKCORE_XML_DELEGATE)
775  if (svg_info->document != (xmlDocPtr) NULL)
776    {
777      xmlFreeDoc(svg_info->document);
778      svg_info->document=(xmlDocPtr) NULL;
779    }
780#endif
781}
782
783static void SVGStartElement(void *context,const xmlChar *name,
784  const xmlChar **attributes)
785{
786  char
787    *color,
788    id[MagickPathExtent],
789    *next_token,
790    token[MagickPathExtent],
791    **tokens,
792    *units;
793
794  const char
795    *keyword,
796    *p,
797    *value;
798
799  int
800    number_tokens;
801
802  SVGInfo
803    *svg_info;
804
805  register ssize_t
806    i,
807    j;
808
809  /*
810    Called when an opening tag has been processed.
811  */
812  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.startElement(%s",
813    name);
814  svg_info=(SVGInfo *) context;
815  svg_info->n++;
816  svg_info->scale=(double *) ResizeQuantumMemory(svg_info->scale,
817    svg_info->n+1UL,sizeof(*svg_info->scale));
818  if (svg_info->scale == (double *) NULL)
819    {
820      (void) ThrowMagickException(svg_info->exception,GetMagickModule(),
821        ResourceLimitError,"MemoryAllocationFailed","`%s'",name);
822      return;
823    }
824  svg_info->scale[svg_info->n]=svg_info->scale[svg_info->n-1];
825  color=AcquireString("none");
826  units=AcquireString("userSpaceOnUse");
827  *id='\0';
828  *token='\0';
829  value=(const char *) NULL;
830  if (attributes != (const xmlChar **) NULL)
831    for (i=0; (attributes[i] != (const xmlChar *) NULL); i+=2)
832    {
833      keyword=(const char *) attributes[i];
834      value=(const char *) attributes[i+1];
835      switch (*keyword)
836      {
837        case 'C':
838        case 'c':
839        {
840          if (LocaleCompare(keyword,"cx") == 0)
841            {
842              svg_info->element.cx=
843                GetUserSpaceCoordinateValue(svg_info,1,value);
844              break;
845            }
846          if (LocaleCompare(keyword,"cy") == 0)
847            {
848              svg_info->element.cy=
849                GetUserSpaceCoordinateValue(svg_info,-1,value);
850              break;
851            }
852          break;
853        }
854        case 'F':
855        case 'f':
856        {
857          if (LocaleCompare(keyword,"fx") == 0)
858            {
859              svg_info->element.major=
860                GetUserSpaceCoordinateValue(svg_info,1,value);
861              break;
862            }
863          if (LocaleCompare(keyword,"fy") == 0)
864            {
865              svg_info->element.minor=
866                GetUserSpaceCoordinateValue(svg_info,-1,value);
867              break;
868            }
869          break;
870        }
871        case 'H':
872        case 'h':
873        {
874          if (LocaleCompare(keyword,"height") == 0)
875            {
876              svg_info->bounds.height=
877                GetUserSpaceCoordinateValue(svg_info,-1,value);
878              break;
879            }
880          break;
881        }
882        case 'I':
883        case 'i':
884        {
885          if (LocaleCompare(keyword,"id") == 0)
886            {
887              (void) CopyMagickString(id,value,MagickPathExtent);
888              break;
889            }
890          break;
891        }
892        case 'R':
893        case 'r':
894        {
895          if (LocaleCompare(keyword,"r") == 0)
896            {
897              svg_info->element.angle=
898                GetUserSpaceCoordinateValue(svg_info,0,value);
899              break;
900            }
901          break;
902        }
903        case 'W':
904        case 'w':
905        {
906          if (LocaleCompare(keyword,"width") == 0)
907            {
908              svg_info->bounds.width=
909                GetUserSpaceCoordinateValue(svg_info,1,value);
910              break;
911            }
912          break;
913        }
914        case 'X':
915        case 'x':
916        {
917          if (LocaleCompare(keyword,"x") == 0)
918            {
919              svg_info->bounds.x=GetUserSpaceCoordinateValue(svg_info,1,value)-
920                svg_info->center.x;
921              break;
922            }
923          if (LocaleCompare(keyword,"x1") == 0)
924            {
925              svg_info->segment.x1=GetUserSpaceCoordinateValue(svg_info,1,
926                value);
927              break;
928            }
929          if (LocaleCompare(keyword,"x2") == 0)
930            {
931              svg_info->segment.x2=GetUserSpaceCoordinateValue(svg_info,1,
932                value);
933              break;
934            }
935          break;
936        }
937        case 'Y':
938        case 'y':
939        {
940          if (LocaleCompare(keyword,"y") == 0)
941            {
942              svg_info->bounds.y=GetUserSpaceCoordinateValue(svg_info,-1,value)-
943                svg_info->center.y;
944              break;
945            }
946          if (LocaleCompare(keyword,"y1") == 0)
947            {
948              svg_info->segment.y1=
949                GetUserSpaceCoordinateValue(svg_info,-1,value);
950              break;
951            }
952          if (LocaleCompare(keyword,"y2") == 0)
953            {
954              svg_info->segment.y2=
955                GetUserSpaceCoordinateValue(svg_info,-1,value);
956              break;
957            }
958          break;
959        }
960        default:
961          break;
962      }
963    }
964  if (strchr((char *) name,':') != (char *) NULL)
965    {
966      /*
967        Skip over namespace.
968      */
969      for ( ; *name != ':'; name++) ;
970      name++;
971    }
972  switch (*name)
973  {
974    case 'C':
975    case 'c':
976    {
977      if (LocaleCompare((const char *) name,"circle") == 0)
978        {
979          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
980          break;
981        }
982      if (LocaleCompare((const char *) name,"clipPath") == 0)
983        {
984          (void) FormatLocaleFile(svg_info->file,"push clip-path '%s'\n",id);
985          break;
986        }
987      break;
988    }
989    case 'D':
990    case 'd':
991    {
992      if (LocaleCompare((const char *) name,"defs") == 0)
993        {
994          (void) FormatLocaleFile(svg_info->file,"push defs\n");
995          break;
996        }
997      break;
998    }
999    case 'E':
1000    case 'e':
1001    {
1002      if (LocaleCompare((const char *) name,"ellipse") == 0)
1003        {
1004          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1005          break;
1006        }
1007      break;
1008    }
1009    case 'G':
1010    case 'g':
1011    {
1012      if (LocaleCompare((const char *) name,"g") == 0)
1013        {
1014          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1015          break;
1016        }
1017      break;
1018    }
1019    case 'I':
1020    case 'i':
1021    {
1022      if (LocaleCompare((const char *) name,"image") == 0)
1023        {
1024          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1025          break;
1026        }
1027      break;
1028    }
1029    case 'L':
1030    case 'l':
1031    {
1032      if (LocaleCompare((const char *) name,"line") == 0)
1033        {
1034          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1035          break;
1036        }
1037      if (LocaleCompare((const char *) name,"linearGradient") == 0)
1038        {
1039          (void) FormatLocaleFile(svg_info->file,
1040            "push gradient '%s' linear %g,%g %g,%g\n",id,
1041            svg_info->segment.x1,svg_info->segment.y1,svg_info->segment.x2,
1042            svg_info->segment.y2);
1043          break;
1044        }
1045      break;
1046    }
1047    case 'P':
1048    case 'p':
1049    {
1050      if (LocaleCompare((const char *) name,"path") == 0)
1051        {
1052          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1053          break;
1054        }
1055      if (LocaleCompare((const char *) name,"pattern") == 0)
1056        {
1057          (void) FormatLocaleFile(svg_info->file,
1058            "push pattern '%s' %g,%g %g,%g\n",id,
1059            svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.width,
1060            svg_info->bounds.height);
1061          break;
1062        }
1063      if (LocaleCompare((const char *) name,"polygon") == 0)
1064        {
1065          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1066          break;
1067        }
1068      if (LocaleCompare((const char *) name,"polyline") == 0)
1069        {
1070          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1071          break;
1072        }
1073      break;
1074    }
1075    case 'R':
1076    case 'r':
1077    {
1078      if (LocaleCompare((const char *) name,"radialGradient") == 0)
1079        {
1080          (void) FormatLocaleFile(svg_info->file,
1081            "push gradient '%s' radial %g,%g %g,%g %g\n",
1082            id,svg_info->element.cx,svg_info->element.cy,
1083            svg_info->element.major,svg_info->element.minor,
1084            svg_info->element.angle);
1085          break;
1086        }
1087      if (LocaleCompare((const char *) name,"rect") == 0)
1088        {
1089          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1090          break;
1091        }
1092      break;
1093    }
1094    case 'S':
1095    case 's':
1096    {
1097      if (LocaleCompare((const char *) name,"svg") == 0)
1098        {
1099          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1100          break;
1101        }
1102      break;
1103    }
1104    case 'T':
1105    case 't':
1106    {
1107      if (LocaleCompare((const char *) name,"text") == 0)
1108        {
1109          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1110          svg_info->bounds.x=0.0;
1111          svg_info->bounds.y=0.0;
1112          svg_info->bounds.width=0.0;
1113          svg_info->bounds.height=0.0;
1114          break;
1115        }
1116      if (LocaleCompare((const char *) name,"tspan") == 0)
1117        {
1118          if (*svg_info->text != '\0')
1119            {
1120              DrawInfo
1121                *draw_info;
1122
1123              TypeMetric
1124                metrics;
1125
1126              char
1127                *text;
1128
1129              text=EscapeString(svg_info->text,'\'');
1130              (void) FormatLocaleFile(svg_info->file,"text %g,%g '%s'\n",
1131                svg_info->bounds.x-svg_info->center.x,svg_info->bounds.y-
1132                svg_info->center.y,text);
1133              text=DestroyString(text);
1134              draw_info=CloneDrawInfo(svg_info->image_info,(DrawInfo *) NULL);
1135              draw_info->pointsize=svg_info->pointsize;
1136              draw_info->text=AcquireString(svg_info->text);
1137              (void) ConcatenateString(&draw_info->text," ");
1138              (void) GetTypeMetrics(svg_info->image,draw_info,
1139                &metrics,svg_info->exception);
1140              svg_info->bounds.x+=metrics.width;
1141              draw_info=DestroyDrawInfo(draw_info);
1142              *svg_info->text='\0';
1143            }
1144          (void) FormatLocaleFile(svg_info->file,"push graphic-context\n");
1145          break;
1146        }
1147      break;
1148    }
1149    default:
1150      break;
1151  }
1152  if (attributes != (const xmlChar **) NULL)
1153    for (i=0; (attributes[i] != (const xmlChar *) NULL); i+=2)
1154    {
1155      keyword=(const char *) attributes[i];
1156      value=(const char *) attributes[i+1];
1157      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1158        "    %s = %s",keyword,value);
1159      switch (*keyword)
1160      {
1161        case 'A':
1162        case 'a':
1163        {
1164          if (LocaleCompare(keyword,"angle") == 0)
1165            {
1166              (void) FormatLocaleFile(svg_info->file,"angle %g\n",
1167                GetUserSpaceCoordinateValue(svg_info,0,value));
1168              break;
1169            }
1170          break;
1171        }
1172        case 'C':
1173        case 'c':
1174        {
1175          if (LocaleCompare(keyword,"clip-path") == 0)
1176            {
1177              (void) FormatLocaleFile(svg_info->file,"clip-path '%s'\n",value);
1178              break;
1179            }
1180          if (LocaleCompare(keyword,"clip-rule") == 0)
1181            {
1182              (void) FormatLocaleFile(svg_info->file,"clip-rule '%s'\n",value);
1183              break;
1184            }
1185          if (LocaleCompare(keyword,"clipPathUnits") == 0)
1186            {
1187              (void) CloneString(&units,value);
1188              (void) FormatLocaleFile(svg_info->file,"clip-units '%s'\n",value);
1189              break;
1190            }
1191          if (LocaleCompare(keyword,"color") == 0)
1192            {
1193              (void) CloneString(&color,value);
1194              break;
1195            }
1196          if (LocaleCompare(keyword,"cx") == 0)
1197            {
1198              svg_info->element.cx=
1199                GetUserSpaceCoordinateValue(svg_info,1,value);
1200              break;
1201            }
1202          if (LocaleCompare(keyword,"cy") == 0)
1203            {
1204              svg_info->element.cy=
1205                GetUserSpaceCoordinateValue(svg_info,-1,value);
1206              break;
1207            }
1208          break;
1209        }
1210        case 'D':
1211        case 'd':
1212        {
1213          if (LocaleCompare(keyword,"d") == 0)
1214            {
1215              (void) CloneString(&svg_info->vertices,value);
1216              break;
1217            }
1218          if (LocaleCompare(keyword,"dx") == 0)
1219            {
1220              svg_info->bounds.x+=GetUserSpaceCoordinateValue(svg_info,1,value);
1221              break;
1222            }
1223          if (LocaleCompare(keyword,"dy") == 0)
1224            {
1225              svg_info->bounds.y+=
1226                GetUserSpaceCoordinateValue(svg_info,-1,value);
1227              break;
1228            }
1229          break;
1230        }
1231        case 'F':
1232        case 'f':
1233        {
1234          if (LocaleCompare(keyword,"fill") == 0)
1235            {
1236              if (LocaleCompare(value,"currentColor") == 0)
1237                {
1238                  (void) FormatLocaleFile(svg_info->file,"fill '%s'\n",color);
1239                  break;
1240                }
1241              (void) FormatLocaleFile(svg_info->file,"fill '%s'\n",value);
1242              break;
1243            }
1244          if (LocaleCompare(keyword,"fillcolor") == 0)
1245            {
1246              (void) FormatLocaleFile(svg_info->file,"fill '%s'\n",value);
1247              break;
1248            }
1249          if (LocaleCompare(keyword,"fill-rule") == 0)
1250            {
1251              (void) FormatLocaleFile(svg_info->file,"fill-rule '%s'\n",value);
1252              break;
1253            }
1254          if (LocaleCompare(keyword,"fill-alpha") == 0)
1255            {
1256              (void) FormatLocaleFile(svg_info->file,"fill-alpha '%s'\n",
1257                value);
1258              break;
1259            }
1260          if (LocaleCompare(keyword,"font-family") == 0)
1261            {
1262              (void) FormatLocaleFile(svg_info->file,"font-family '%s'\n",
1263                value);
1264              break;
1265            }
1266          if (LocaleCompare(keyword,"font-stretch") == 0)
1267            {
1268              (void) FormatLocaleFile(svg_info->file,"font-stretch '%s'\n",
1269                value);
1270              break;
1271            }
1272          if (LocaleCompare(keyword,"font-style") == 0)
1273            {
1274              (void) FormatLocaleFile(svg_info->file,"font-style '%s'\n",value);
1275              break;
1276            }
1277          if (LocaleCompare(keyword,"font-size") == 0)
1278            {
1279              if (LocaleCompare(value,"xx-small") == 0)
1280                svg_info->pointsize=6.144;
1281              else if (LocaleCompare(value,"x-small") == 0)
1282                svg_info->pointsize=7.68;
1283              else if (LocaleCompare(value,"small") == 0)
1284                svg_info->pointsize=9.6;
1285              else if (LocaleCompare(value,"medium") == 0)
1286                svg_info->pointsize=12.0;
1287              else if (LocaleCompare(value,"large") == 0)
1288                svg_info->pointsize=14.4;
1289              else if (LocaleCompare(value,"x-large") == 0)
1290                svg_info->pointsize=17.28;
1291              else if (LocaleCompare(value,"xx-large") == 0)
1292                svg_info->pointsize=20.736;
1293              else
1294                svg_info->pointsize=GetUserSpaceCoordinateValue(svg_info,0,
1295                  value);
1296              (void) FormatLocaleFile(svg_info->file,"font-size %g\n",
1297                svg_info->pointsize);
1298              break;
1299            }
1300          if (LocaleCompare(keyword,"font-weight") == 0)
1301            {
1302              (void) FormatLocaleFile(svg_info->file,"font-weight '%s'\n",
1303                value);
1304              break;
1305            }
1306          break;
1307        }
1308        case 'G':
1309        case 'g':
1310        {
1311          if (LocaleCompare(keyword,"gradientTransform") == 0)
1312            {
1313              AffineMatrix
1314                affine,
1315                current,
1316                transform;
1317
1318              GetAffineMatrix(&transform);
1319              (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  ");
1320              tokens=GetTransformTokens(context,value,&number_tokens);
1321              if (tokens == (char **) NULL)
1322                break;
1323              for (j=0; j < (number_tokens-1); j+=2)
1324              {
1325                keyword=(char *) tokens[j];
1326                if (keyword == (char *) NULL)
1327                  continue;
1328                value=(char *) tokens[j+1];
1329                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1330                  "    %s: %s",keyword,value);
1331                current=transform;
1332                GetAffineMatrix(&affine);
1333                switch (*keyword)
1334                {
1335                  case 'M':
1336                  case 'm':
1337                  {
1338                    if (LocaleCompare(keyword,"matrix") == 0)
1339                      {
1340                        p=(const char *) value;
1341                        GetNextToken(p,&p,MagickPathExtent,token);
1342                        affine.sx=StringToDouble(value,(char **) NULL);
1343                        GetNextToken(p,&p,MagickPathExtent,token);
1344                        if (*token == ',')
1345                          GetNextToken(p,&p,MagickPathExtent,token);
1346                        affine.rx=StringToDouble(token,&next_token);
1347                        GetNextToken(p,&p,MagickPathExtent,token);
1348                        if (*token == ',')
1349                          GetNextToken(p,&p,MagickPathExtent,token);
1350                        affine.ry=StringToDouble(token,&next_token);
1351                        GetNextToken(p,&p,MagickPathExtent,token);
1352                        if (*token == ',')
1353                          GetNextToken(p,&p,MagickPathExtent,token);
1354                        affine.sy=StringToDouble(token,&next_token);
1355                        GetNextToken(p,&p,MagickPathExtent,token);
1356                        if (*token == ',')
1357                          GetNextToken(p,&p,MagickPathExtent,token);
1358                        affine.tx=StringToDouble(token,&next_token);
1359                        GetNextToken(p,&p,MagickPathExtent,token);
1360                        if (*token == ',')
1361                          GetNextToken(p,&p,MagickPathExtent,token);
1362                        affine.ty=StringToDouble(token,&next_token);
1363                        break;
1364                      }
1365                    break;
1366                  }
1367                  case 'R':
1368                  case 'r':
1369                  {
1370                    if (LocaleCompare(keyword,"rotate") == 0)
1371                      {
1372                        double
1373                          angle;
1374
1375                        angle=GetUserSpaceCoordinateValue(svg_info,0,value);
1376                        affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
1377                        affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
1378                        affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
1379                        affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
1380                        break;
1381                      }
1382                    break;
1383                  }
1384                  case 'S':
1385                  case 's':
1386                  {
1387                    if (LocaleCompare(keyword,"scale") == 0)
1388                      {
1389                        for (p=(const char *) value; *p != '\0'; p++)
1390                          if ((isspace((int) ((unsigned char) *p)) != 0) ||
1391                              (*p == ','))
1392                            break;
1393                        affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value);
1394                        affine.sy=affine.sx;
1395                        if (*p != '\0')
1396                          affine.sy=
1397                            GetUserSpaceCoordinateValue(svg_info,-1,p+1);
1398                        svg_info->scale[svg_info->n]=ExpandAffine(&affine);
1399                        break;
1400                      }
1401                    if (LocaleCompare(keyword,"skewX") == 0)
1402                      {
1403                        affine.sx=svg_info->affine.sx;
1404                        affine.ry=tan(DegreesToRadians(fmod(
1405                          GetUserSpaceCoordinateValue(svg_info,1,value),
1406                          360.0)));
1407                        affine.sy=svg_info->affine.sy;
1408                        break;
1409                      }
1410                    if (LocaleCompare(keyword,"skewY") == 0)
1411                      {
1412                        affine.sx=svg_info->affine.sx;
1413                        affine.rx=tan(DegreesToRadians(fmod(
1414                          GetUserSpaceCoordinateValue(svg_info,-1,value),
1415                          360.0)));
1416                        affine.sy=svg_info->affine.sy;
1417                        break;
1418                      }
1419                    break;
1420                  }
1421                  case 'T':
1422                  case 't':
1423                  {
1424                    if (LocaleCompare(keyword,"translate") == 0)
1425                      {
1426                        for (p=(const char *) value; *p != '\0'; p++)
1427                          if ((isspace((int) ((unsigned char) *p)) != 0) ||
1428                              (*p == ','))
1429                            break;
1430                        affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value);
1431                        affine.ty=affine.tx;
1432                        if (*p != '\0')
1433                          affine.ty=
1434                            GetUserSpaceCoordinateValue(svg_info,-1,p+1);
1435                        break;
1436                      }
1437                    break;
1438                  }
1439                  default:
1440                    break;
1441                }
1442                transform.sx=affine.sx*current.sx+affine.ry*current.rx;
1443                transform.rx=affine.rx*current.sx+affine.sy*current.rx;
1444                transform.ry=affine.sx*current.ry+affine.ry*current.sy;
1445                transform.sy=affine.rx*current.ry+affine.sy*current.sy;
1446                transform.tx=affine.tx*current.sx+affine.ty*current.ry+
1447                  current.tx;
1448                transform.ty=affine.tx*current.rx+affine.ty*current.sy+
1449                  current.ty;
1450              }
1451              (void) FormatLocaleFile(svg_info->file,
1452                "affine %g %g %g %g %g %g\n",transform.sx,
1453                transform.rx,transform.ry,transform.sy,transform.tx,
1454                transform.ty);
1455              for (j=0; tokens[j] != (char *) NULL; j++)
1456                tokens[j]=DestroyString(tokens[j]);
1457              tokens=(char **) RelinquishMagickMemory(tokens);
1458              break;
1459            }
1460          if (LocaleCompare(keyword,"gradientUnits") == 0)
1461            {
1462              (void) CloneString(&units,value);
1463              (void) FormatLocaleFile(svg_info->file,"gradient-units '%s'\n",
1464                value);
1465              break;
1466            }
1467          break;
1468        }
1469        case 'H':
1470        case 'h':
1471        {
1472          if (LocaleCompare(keyword,"height") == 0)
1473            {
1474              svg_info->bounds.height=
1475                GetUserSpaceCoordinateValue(svg_info,-1,value);
1476              break;
1477            }
1478          if (LocaleCompare(keyword,"href") == 0)
1479            {
1480              (void) CloneString(&svg_info->url,value);
1481              break;
1482            }
1483          break;
1484        }
1485        case 'M':
1486        case 'm':
1487        {
1488          if (LocaleCompare(keyword,"major") == 0)
1489            {
1490              svg_info->element.major=
1491                GetUserSpaceCoordinateValue(svg_info,1,value);
1492              break;
1493            }
1494          if (LocaleCompare(keyword,"minor") == 0)
1495            {
1496              svg_info->element.minor=
1497                GetUserSpaceCoordinateValue(svg_info,-1,value);
1498              break;
1499            }
1500          break;
1501        }
1502        case 'O':
1503        case 'o':
1504        {
1505          if (LocaleCompare(keyword,"offset") == 0)
1506            {
1507              (void) CloneString(&svg_info->offset,value);
1508              break;
1509            }
1510          if (LocaleCompare(keyword,"opacity") == 0)
1511            {
1512              (void) FormatLocaleFile(svg_info->file,"opacity '%s'\n",value);
1513              break;
1514            }
1515          break;
1516        }
1517        case 'P':
1518        case 'p':
1519        {
1520          if (LocaleCompare(keyword,"path") == 0)
1521            {
1522              (void) CloneString(&svg_info->url,value);
1523              break;
1524            }
1525          if (LocaleCompare(keyword,"points") == 0)
1526            {
1527              (void) CloneString(&svg_info->vertices,value);
1528              break;
1529            }
1530          break;
1531        }
1532        case 'R':
1533        case 'r':
1534        {
1535          if (LocaleCompare(keyword,"r") == 0)
1536            {
1537              svg_info->element.major=
1538                GetUserSpaceCoordinateValue(svg_info,1,value);
1539              svg_info->element.minor=
1540                GetUserSpaceCoordinateValue(svg_info,-1,value);
1541              break;
1542            }
1543          if (LocaleCompare(keyword,"rotate") == 0)
1544            {
1545              double
1546                angle;
1547
1548              angle=GetUserSpaceCoordinateValue(svg_info,0,value);
1549              (void) FormatLocaleFile(svg_info->file,"translate %g,%g\n",
1550                svg_info->bounds.x,svg_info->bounds.y);
1551              svg_info->bounds.x=0;
1552              svg_info->bounds.y=0;
1553              (void) FormatLocaleFile(svg_info->file,"rotate %g\n",angle);
1554              break;
1555            }
1556          if (LocaleCompare(keyword,"rx") == 0)
1557            {
1558              if (LocaleCompare((const char *) name,"ellipse") == 0)
1559                svg_info->element.major=
1560                  GetUserSpaceCoordinateValue(svg_info,1,value);
1561              else
1562                svg_info->radius.x=
1563                  GetUserSpaceCoordinateValue(svg_info,1,value);
1564              break;
1565            }
1566          if (LocaleCompare(keyword,"ry") == 0)
1567            {
1568              if (LocaleCompare((const char *) name,"ellipse") == 0)
1569                svg_info->element.minor=
1570                  GetUserSpaceCoordinateValue(svg_info,-1,value);
1571              else
1572                svg_info->radius.y=
1573                  GetUserSpaceCoordinateValue(svg_info,-1,value);
1574              break;
1575            }
1576          break;
1577        }
1578        case 'S':
1579        case 's':
1580        {
1581          if (LocaleCompare(keyword,"stop-color") == 0)
1582            {
1583              (void) CloneString(&svg_info->stop_color,value);
1584              break;
1585            }
1586          if (LocaleCompare(keyword,"stroke") == 0)
1587            {
1588              if (LocaleCompare(value,"currentColor") == 0)
1589                {
1590                  (void) FormatLocaleFile(svg_info->file,"stroke '%s'\n",color);
1591                  break;
1592                }
1593              (void) FormatLocaleFile(svg_info->file,"stroke '%s'\n",value);
1594              break;
1595            }
1596          if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
1597            {
1598              (void) FormatLocaleFile(svg_info->file,"stroke-antialias %d\n",
1599                LocaleCompare(value,"true") == 0);
1600              break;
1601            }
1602          if (LocaleCompare(keyword,"stroke-dasharray") == 0)
1603            {
1604              (void) FormatLocaleFile(svg_info->file,"stroke-dasharray %s\n",
1605                value);
1606              break;
1607            }
1608          if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
1609            {
1610              (void) FormatLocaleFile(svg_info->file,"stroke-dashoffset %s\n",
1611                value);
1612              break;
1613            }
1614          if (LocaleCompare(keyword,"stroke-linecap") == 0)
1615            {
1616              (void) FormatLocaleFile(svg_info->file,"stroke-linecap '%s'\n",
1617                value);
1618              break;
1619            }
1620          if (LocaleCompare(keyword,"stroke-linejoin") == 0)
1621            {
1622              (void) FormatLocaleFile(svg_info->file,"stroke-linejoin '%s'\n",
1623                value);
1624              break;
1625            }
1626          if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
1627            {
1628              (void) FormatLocaleFile(svg_info->file,"stroke-miterlimit '%s'\n",
1629                value);
1630              break;
1631            }
1632          if (LocaleCompare(keyword,"stroke-opacity") == 0)
1633            {
1634              (void) FormatLocaleFile(svg_info->file,"stroke-opacity '%s'\n",
1635                value);
1636              break;
1637            }
1638          if (LocaleCompare(keyword,"stroke-width") == 0)
1639            {
1640              (void) FormatLocaleFile(svg_info->file,"stroke-width %g\n",
1641                GetUserSpaceCoordinateValue(svg_info,1,value));
1642              break;
1643            }
1644          if (LocaleCompare(keyword,"style") == 0)
1645            {
1646              (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  ");
1647              tokens=GetStyleTokens(context,value,&number_tokens);
1648              for (j=0; j < (number_tokens-1); j+=2)
1649              {
1650                keyword=(char *) tokens[j];
1651                value=(char *) tokens[j+1];
1652                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1653                  "    %s: %s",keyword,value);
1654                switch (*keyword)
1655                {
1656                  case 'C':
1657                  case 'c':
1658                  {
1659                     if (LocaleCompare(keyword,"clip-path") == 0)
1660                       {
1661                         (void) FormatLocaleFile(svg_info->file,
1662                           "clip-path '%s'\n",value);
1663                         break;
1664                       }
1665                    if (LocaleCompare(keyword,"clip-rule") == 0)
1666                      {
1667                        (void) FormatLocaleFile(svg_info->file,
1668                          "clip-rule '%s'\n",value);
1669                        break;
1670                      }
1671                     if (LocaleCompare(keyword,"clipPathUnits") == 0)
1672                       {
1673                         (void) CloneString(&units,value);
1674                         (void) FormatLocaleFile(svg_info->file,
1675                          "clip-units '%s'\n",value);
1676                         break;
1677                       }
1678                    if (LocaleCompare(keyword,"color") == 0)
1679                      {
1680                        (void) CloneString(&color,value);
1681                        break;
1682                      }
1683                    break;
1684                  }
1685                  case 'F':
1686                  case 'f':
1687                  {
1688                    if (LocaleCompare(keyword,"fill") == 0)
1689                      {
1690                         if (LocaleCompare(value,"currentColor") == 0)
1691                           {
1692                             (void) FormatLocaleFile(svg_info->file,
1693                          "fill '%s'\n",color);
1694                             break;
1695                           }
1696                        if (LocaleCompare(value,"#000000ff") == 0)
1697                          (void) FormatLocaleFile(svg_info->file,
1698                            "fill '#000000'\n");
1699                        else
1700                          (void) FormatLocaleFile(svg_info->file,"fill '%s'\n",
1701                            value);
1702                        break;
1703                      }
1704                    if (LocaleCompare(keyword,"fillcolor") == 0)
1705                      {
1706                        (void) FormatLocaleFile(svg_info->file,"fill '%s'\n",
1707                          value);
1708                        break;
1709                      }
1710                    if (LocaleCompare(keyword,"fill-rule") == 0)
1711                      {
1712                        (void) FormatLocaleFile(svg_info->file,
1713                          "fill-rule '%s'\n",value);
1714                        break;
1715                      }
1716                    if (LocaleCompare(keyword,"fill-alpha") == 0)
1717                      {
1718                        (void) FormatLocaleFile(svg_info->file,
1719                          "fill-alpha '%s'\n",value);
1720                        break;
1721                      }
1722                    if (LocaleCompare(keyword,"font-family") == 0)
1723                      {
1724                        (void) FormatLocaleFile(svg_info->file,
1725                          "font-family '%s'\n",value);
1726                        break;
1727                      }
1728                    if (LocaleCompare(keyword,"font-stretch") == 0)
1729                      {
1730                        (void) FormatLocaleFile(svg_info->file,
1731                          "font-stretch '%s'\n",value);
1732                        break;
1733                      }
1734                    if (LocaleCompare(keyword,"font-style") == 0)
1735                      {
1736                        (void) FormatLocaleFile(svg_info->file,
1737                          "font-style '%s'\n",value);
1738                        break;
1739                      }
1740                    if (LocaleCompare(keyword,"font-size") == 0)
1741                      {
1742                        svg_info->pointsize=GetUserSpaceCoordinateValue(
1743                          svg_info,0,value);
1744                        (void) FormatLocaleFile(svg_info->file,"font-size %g\n",
1745                          svg_info->pointsize);
1746                        break;
1747                      }
1748                    if (LocaleCompare(keyword,"font-weight") == 0)
1749                      {
1750                        (void) FormatLocaleFile(svg_info->file,
1751                          "font-weight '%s'\n",value);
1752                        break;
1753                      }
1754                    break;
1755                  }
1756                  case 'O':
1757                  case 'o':
1758                  {
1759                    if (LocaleCompare(keyword,"offset") == 0)
1760                      {
1761                        (void) FormatLocaleFile(svg_info->file,"offset %g\n",
1762                          GetUserSpaceCoordinateValue(svg_info,1,value));
1763                        break;
1764                      }
1765                    if (LocaleCompare(keyword,"opacity") == 0)
1766                      {
1767                        (void) FormatLocaleFile(svg_info->file,
1768                          "opacity '%s'\n",value);
1769                        break;
1770                      }
1771                    break;
1772                  }
1773                  case 'S':
1774                  case 's':
1775                  {
1776                    if (LocaleCompare(keyword,"stop-color") == 0)
1777                      {
1778                        (void) CloneString(&svg_info->stop_color,value);
1779                        break;
1780                      }
1781                    if (LocaleCompare(keyword,"stroke") == 0)
1782                      {
1783                         if (LocaleCompare(value,"currentColor") == 0)
1784                           {
1785                             (void) FormatLocaleFile(svg_info->file,
1786                          "stroke '%s'\n",color);
1787                             break;
1788                           }
1789                        if (LocaleCompare(value,"#000000ff") == 0)
1790                          (void) FormatLocaleFile(svg_info->file,
1791                            "fill '#000000'\n");
1792                        else
1793                          (void) FormatLocaleFile(svg_info->file,
1794                            "stroke '%s'\n",value);
1795                        break;
1796                      }
1797                    if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
1798                      {
1799                        (void) FormatLocaleFile(svg_info->file,
1800                          "stroke-antialias %d\n",
1801                          LocaleCompare(value,"true") == 0);
1802                        break;
1803                      }
1804                    if (LocaleCompare(keyword,"stroke-dasharray") == 0)
1805                      {
1806                        (void) FormatLocaleFile(svg_info->file,
1807                          "stroke-dasharray %s\n",value);
1808                        break;
1809                      }
1810                    if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
1811                      {
1812                        (void) FormatLocaleFile(svg_info->file,
1813                          "stroke-dashoffset %s\n",value);
1814                        break;
1815                      }
1816                    if (LocaleCompare(keyword,"stroke-linecap") == 0)
1817                      {
1818                        (void) FormatLocaleFile(svg_info->file,
1819                          "stroke-linecap '%s'\n",value);
1820                        break;
1821                      }
1822                    if (LocaleCompare(keyword,"stroke-linejoin") == 0)
1823                      {
1824                        (void) FormatLocaleFile(svg_info->file,
1825                          "stroke-linejoin '%s'\n",value);
1826                        break;
1827                      }
1828                    if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
1829                      {
1830                        (void) FormatLocaleFile(svg_info->file,
1831                          "stroke-miterlimit '%s'\n",value);
1832                        break;
1833                      }
1834                    if (LocaleCompare(keyword,"stroke-opacity") == 0)
1835                      {
1836                        (void) FormatLocaleFile(svg_info->file,
1837                          "stroke-opacity '%s'\n",value);
1838                        break;
1839                      }
1840                    if (LocaleCompare(keyword,"stroke-width") == 0)
1841                      {
1842                        (void) FormatLocaleFile(svg_info->file,
1843                          "stroke-width %g\n",
1844                          GetUserSpaceCoordinateValue(svg_info,1,value));
1845                        break;
1846                      }
1847                    break;
1848                  }
1849                  case 't':
1850                  case 'T':
1851                  {
1852                    if (LocaleCompare(keyword,"text-align") == 0)
1853                      {
1854                        (void) FormatLocaleFile(svg_info->file,
1855                          "text-align '%s'\n",value);
1856                        break;
1857                      }
1858                    if (LocaleCompare(keyword,"text-anchor") == 0)
1859                      {
1860                        (void) FormatLocaleFile(svg_info->file,
1861                          "text-anchor '%s'\n",value);
1862                        break;
1863                      }
1864                    if (LocaleCompare(keyword,"text-decoration") == 0)
1865                      {
1866                        if (LocaleCompare(value,"underline") == 0)
1867                          (void) FormatLocaleFile(svg_info->file,
1868                          "decorate underline\n");
1869                        if (LocaleCompare(value,"line-through") == 0)
1870                          (void) FormatLocaleFile(svg_info->file,
1871                          "decorate line-through\n");
1872                        if (LocaleCompare(value,"overline") == 0)
1873                          (void) FormatLocaleFile(svg_info->file,
1874                          "decorate overline\n");
1875                        break;
1876                      }
1877                    if (LocaleCompare(keyword,"text-antialiasing") == 0)
1878                      {
1879                        (void) FormatLocaleFile(svg_info->file,
1880                          "text-antialias %d\n",
1881                          LocaleCompare(value,"true") == 0);
1882                        break;
1883                      }
1884                    break;
1885                  }
1886                  default:
1887                    break;
1888                }
1889              }
1890              for (j=0; tokens[j] != (char *) NULL; j++)
1891                tokens[j]=DestroyString(tokens[j]);
1892              tokens=(char **) RelinquishMagickMemory(tokens);
1893              break;
1894            }
1895          break;
1896        }
1897        case 'T':
1898        case 't':
1899        {
1900          if (LocaleCompare(keyword,"text-align") == 0)
1901            {
1902              (void) FormatLocaleFile(svg_info->file,"text-align '%s'\n",
1903                value);
1904              break;
1905            }
1906          if (LocaleCompare(keyword,"text-anchor") == 0)
1907            {
1908              (void) FormatLocaleFile(svg_info->file,"text-anchor '%s'\n",
1909                value);
1910              break;
1911            }
1912          if (LocaleCompare(keyword,"text-decoration") == 0)
1913            {
1914              if (LocaleCompare(value,"underline") == 0)
1915                (void) FormatLocaleFile(svg_info->file,"decorate underline\n");
1916              if (LocaleCompare(value,"line-through") == 0)
1917                (void) FormatLocaleFile(svg_info->file,
1918                  "decorate line-through\n");
1919              if (LocaleCompare(value,"overline") == 0)
1920                (void) FormatLocaleFile(svg_info->file,"decorate overline\n");
1921              break;
1922            }
1923          if (LocaleCompare(keyword,"text-antialiasing") == 0)
1924            {
1925              (void) FormatLocaleFile(svg_info->file,"text-antialias %d\n",
1926                LocaleCompare(value,"true") == 0);
1927              break;
1928            }
1929          if (LocaleCompare(keyword,"transform") == 0)
1930            {
1931              AffineMatrix
1932                affine,
1933                current,
1934                transform;
1935
1936              GetAffineMatrix(&transform);
1937              (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  ");
1938              tokens=GetTransformTokens(context,value,&number_tokens);
1939              if (tokens == (char **) NULL)
1940                break;
1941              for (j=0; j < (number_tokens-1); j+=2)
1942              {
1943                keyword=(char *) tokens[j];
1944                value=(char *) tokens[j+1];
1945                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1946                  "    %s: %s",keyword,value);
1947                current=transform;
1948                GetAffineMatrix(&affine);
1949                switch (*keyword)
1950                {
1951                  case 'M':
1952                  case 'm':
1953                  {
1954                    if (LocaleCompare(keyword,"matrix") == 0)
1955                      {
1956                        p=(const char *) value;
1957                        GetNextToken(p,&p,MagickPathExtent,token);
1958                        affine.sx=StringToDouble(value,(char **) NULL);
1959                        GetNextToken(p,&p,MagickPathExtent,token);
1960                        if (*token == ',')
1961                          GetNextToken(p,&p,MagickPathExtent,token);
1962                        affine.rx=StringToDouble(token,&next_token);
1963                        GetNextToken(p,&p,MagickPathExtent,token);
1964                        if (*token == ',')
1965                          GetNextToken(p,&p,MagickPathExtent,token);
1966                        affine.ry=StringToDouble(token,&next_token);
1967                        GetNextToken(p,&p,MagickPathExtent,token);
1968                        if (*token == ',')
1969                          GetNextToken(p,&p,MagickPathExtent,token);
1970                        affine.sy=StringToDouble(token,&next_token);
1971                        GetNextToken(p,&p,MagickPathExtent,token);
1972                        if (*token == ',')
1973                          GetNextToken(p,&p,MagickPathExtent,token);
1974                        affine.tx=StringToDouble(token,&next_token);
1975                        GetNextToken(p,&p,MagickPathExtent,token);
1976                        if (*token == ',')
1977                          GetNextToken(p,&p,MagickPathExtent,token);
1978                        affine.ty=StringToDouble(token,&next_token);
1979                        break;
1980                      }
1981                    break;
1982                  }
1983                  case 'R':
1984                  case 'r':
1985                  {
1986                    if (LocaleCompare(keyword,"rotate") == 0)
1987                      {
1988                        double
1989                          angle,
1990                          x,
1991                          y;
1992
1993                        p=(const char *) value;
1994                        GetNextToken(p,&p,MagickPathExtent,token);
1995                        angle=StringToDouble(value,(char **) NULL);
1996                        GetNextToken(p,&p,MagickPathExtent,token);
1997                        if (*token == ',')
1998                          GetNextToken(p,&p,MagickPathExtent,token);
1999                        x=StringToDouble(token,&next_token);
2000                        GetNextToken(p,&p,MagickPathExtent,token);
2001                        if (*token == ',')
2002                          GetNextToken(p,&p,MagickPathExtent,token);
2003                        y=StringToDouble(token,&next_token);
2004                        affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
2005                        affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
2006                        affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
2007                        affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
2008                        affine.tx=x;
2009                        affine.ty=y;
2010                        svg_info->center.x=x;
2011                        svg_info->center.y=y;
2012                        break;
2013                      }
2014                    break;
2015                  }
2016                  case 'S':
2017                  case 's':
2018                  {
2019                    if (LocaleCompare(keyword,"scale") == 0)
2020                      {
2021                        for (p=(const char *) value; *p != '\0'; p++)
2022                          if ((isspace((int) ((unsigned char) *p)) != 0) ||
2023                              (*p == ','))
2024                            break;
2025                        affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value);
2026                        affine.sy=affine.sx;
2027                        if (*p != '\0')
2028                          affine.sy=GetUserSpaceCoordinateValue(svg_info,-1,
2029                            p+1);
2030                        svg_info->scale[svg_info->n]=ExpandAffine(&affine);
2031                        break;
2032                      }
2033                    if (LocaleCompare(keyword,"skewX") == 0)
2034                      {
2035                        affine.sx=svg_info->affine.sx;
2036                        affine.ry=tan(DegreesToRadians(fmod(
2037                          GetUserSpaceCoordinateValue(svg_info,1,value),
2038                          360.0)));
2039                        affine.sy=svg_info->affine.sy;
2040                        break;
2041                      }
2042                    if (LocaleCompare(keyword,"skewY") == 0)
2043                      {
2044                        affine.sx=svg_info->affine.sx;
2045                        affine.rx=tan(DegreesToRadians(fmod(
2046                          GetUserSpaceCoordinateValue(svg_info,-1,value),
2047                          360.0)));
2048                        affine.sy=svg_info->affine.sy;
2049                        break;
2050                      }
2051                    break;
2052                  }
2053                  case 'T':
2054                  case 't':
2055                  {
2056                    if (LocaleCompare(keyword,"translate") == 0)
2057                      {
2058                        for (p=(const char *) value; *p != '\0'; p++)
2059                          if ((isspace((int) ((unsigned char) *p)) != 0) ||
2060                              (*p == ','))
2061                            break;
2062                        affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value);
2063                        affine.ty=affine.tx;
2064                        if (*p != '\0')
2065                          affine.ty=GetUserSpaceCoordinateValue(svg_info,-1,
2066                            p+1);
2067                        break;
2068                      }
2069                    break;
2070                  }
2071                  default:
2072                    break;
2073                }
2074                transform.sx=affine.sx*current.sx+affine.ry*current.rx;
2075                transform.rx=affine.rx*current.sx+affine.sy*current.rx;
2076                transform.ry=affine.sx*current.ry+affine.ry*current.sy;
2077                transform.sy=affine.rx*current.ry+affine.sy*current.sy;
2078                transform.tx=affine.tx*current.sx+affine.ty*current.ry+
2079                  current.tx;
2080                transform.ty=affine.tx*current.rx+affine.ty*current.sy+
2081                  current.ty;
2082              }
2083              (void) FormatLocaleFile(svg_info->file,
2084                "affine %g %g %g %g %g %g\n",transform.sx,transform.rx,
2085                transform.ry,transform.sy,transform.tx,transform.ty);
2086              for (j=0; tokens[j] != (char *) NULL; j++)
2087                tokens[j]=DestroyString(tokens[j]);
2088              tokens=(char **) RelinquishMagickMemory(tokens);
2089              break;
2090            }
2091          break;
2092        }
2093        case 'V':
2094        case 'v':
2095        {
2096          if (LocaleCompare(keyword,"verts") == 0)
2097            {
2098              (void) CloneString(&svg_info->vertices,value);
2099              break;
2100            }
2101          if (LocaleCompare(keyword,"viewBox") == 0)
2102            {
2103              p=(const char *) value;
2104              GetNextToken(p,&p,MagickPathExtent,token);
2105              svg_info->view_box.x=StringToDouble(token,&next_token);
2106              GetNextToken(p,&p,MagickPathExtent,token);
2107              if (*token == ',')
2108                GetNextToken(p,&p,MagickPathExtent,token);
2109              svg_info->view_box.y=StringToDouble(token,&next_token);
2110              GetNextToken(p,&p,MagickPathExtent,token);
2111              if (*token == ',')
2112                GetNextToken(p,&p,MagickPathExtent,token);
2113              svg_info->view_box.width=StringToDouble(token,
2114                (char **) NULL);
2115              if (svg_info->bounds.width == 0)
2116                svg_info->bounds.width=svg_info->view_box.width;
2117              GetNextToken(p,&p,MagickPathExtent,token);
2118              if (*token == ',')
2119                GetNextToken(p,&p,MagickPathExtent,token);
2120              svg_info->view_box.height=StringToDouble(token,
2121                (char **) NULL);
2122              if (svg_info->bounds.height == 0)
2123                svg_info->bounds.height=svg_info->view_box.height;
2124              break;
2125            }
2126          break;
2127        }
2128        case 'W':
2129        case 'w':
2130        {
2131          if (LocaleCompare(keyword,"width") == 0)
2132            {
2133              svg_info->bounds.width=
2134                GetUserSpaceCoordinateValue(svg_info,1,value);
2135              break;
2136            }
2137          break;
2138        }
2139        case 'X':
2140        case 'x':
2141        {
2142          if (LocaleCompare(keyword,"x") == 0)
2143            {
2144              svg_info->bounds.x=GetUserSpaceCoordinateValue(svg_info,1,value);
2145              break;
2146            }
2147          if (LocaleCompare(keyword,"xlink:href") == 0)
2148            {
2149              (void) CloneString(&svg_info->url,value);
2150              break;
2151            }
2152          if (LocaleCompare(keyword,"x1") == 0)
2153            {
2154              svg_info->segment.x1=
2155                GetUserSpaceCoordinateValue(svg_info,1,value);
2156              break;
2157            }
2158          if (LocaleCompare(keyword,"x2") == 0)
2159            {
2160              svg_info->segment.x2=
2161                GetUserSpaceCoordinateValue(svg_info,1,value);
2162              break;
2163            }
2164          break;
2165        }
2166        case 'Y':
2167        case 'y':
2168        {
2169          if (LocaleCompare(keyword,"y") == 0)
2170            {
2171              svg_info->bounds.y=GetUserSpaceCoordinateValue(svg_info,-1,value);
2172              break;
2173            }
2174          if (LocaleCompare(keyword,"y1") == 0)
2175            {
2176              svg_info->segment.y1=
2177                GetUserSpaceCoordinateValue(svg_info,-1,value);
2178              break;
2179            }
2180          if (LocaleCompare(keyword,"y2") == 0)
2181            {
2182              svg_info->segment.y2=
2183                GetUserSpaceCoordinateValue(svg_info,-1,value);
2184              break;
2185            }
2186          break;
2187        }
2188        default:
2189          break;
2190      }
2191    }
2192  if (LocaleCompare((const char *) name,"svg") == 0)
2193    {
2194      if (svg_info->document->encoding != (const xmlChar *) NULL)
2195        (void) FormatLocaleFile(svg_info->file,"encoding \"%s\"\n",
2196          (const char *) svg_info->document->encoding);
2197      if (attributes != (const xmlChar **) NULL)
2198        {
2199          double
2200            sx,
2201            sy,
2202            tx,
2203            ty;
2204
2205          if ((svg_info->view_box.width == 0.0) ||
2206              (svg_info->view_box.height == 0.0))
2207            svg_info->view_box=svg_info->bounds;
2208          svg_info->width=0;
2209          if (svg_info->bounds.width > 0.0)
2210            svg_info->width=(size_t) floor(svg_info->bounds.width+0.5);
2211          svg_info->height=0;
2212          if (svg_info->bounds.height > 0.0)
2213            svg_info->height=(size_t) floor(svg_info->bounds.height+0.5);
2214          (void) FormatLocaleFile(svg_info->file,"viewbox 0 0 %.20g %.20g\n",
2215            (double) svg_info->width,(double) svg_info->height);
2216          sx=(double) svg_info->width/svg_info->view_box.width;
2217          sy=(double) svg_info->height/svg_info->view_box.height;
2218          tx=svg_info->view_box.x != 0.0 ? (double) -sx*svg_info->view_box.x :
2219            0.0;
2220          ty=svg_info->view_box.y != 0.0 ? (double) -sy*svg_info->view_box.y :
2221            0.0;
2222          (void) FormatLocaleFile(svg_info->file,"affine %g 0 0 %g %g %g\n",
2223            sx,sy,tx,ty);
2224        }
2225    }
2226  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  )");
2227  units=DestroyString(units);
2228  if (color != (char *) NULL)
2229    color=DestroyString(color);
2230}
2231
2232static void SVGEndElement(void *context,const xmlChar *name)
2233{
2234  SVGInfo
2235    *svg_info;
2236
2237  /*
2238    Called when the end of an element has been detected.
2239  */
2240  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2241    "  SAX.endElement(%s)",name);
2242  svg_info=(SVGInfo *) context;
2243  if (strchr((char *) name,':') != (char *) NULL)
2244    {
2245      /*
2246        Skip over namespace.
2247      */
2248      for ( ; *name != ':'; name++) ;
2249      name++;
2250    }
2251  switch (*name)
2252  {
2253    case 'C':
2254    case 'c':
2255    {
2256      if (LocaleCompare((const char *) name,"circle") == 0)
2257        {
2258          (void) FormatLocaleFile(svg_info->file,"circle %g,%g %g,%g\n",
2259            svg_info->element.cx,svg_info->element.cy,svg_info->element.cx,
2260            svg_info->element.cy+svg_info->element.minor);
2261          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2262          break;
2263        }
2264      if (LocaleCompare((const char *) name,"clipPath") == 0)
2265        {
2266          (void) FormatLocaleFile(svg_info->file,"pop clip-path\n");
2267          break;
2268        }
2269      break;
2270    }
2271    case 'D':
2272    case 'd':
2273    {
2274      if (LocaleCompare((const char *) name,"defs") == 0)
2275        {
2276          (void) FormatLocaleFile(svg_info->file,"pop defs\n");
2277          break;
2278        }
2279      if (LocaleCompare((const char *) name,"desc") == 0)
2280        {
2281          register char
2282            *p;
2283
2284          if (*svg_info->text == '\0')
2285            break;
2286          (void) fputc('#',svg_info->file);
2287          for (p=svg_info->text; *p != '\0'; p++)
2288          {
2289            (void) fputc(*p,svg_info->file);
2290            if (*p == '\n')
2291              (void) fputc('#',svg_info->file);
2292          }
2293          (void) fputc('\n',svg_info->file);
2294          *svg_info->text='\0';
2295          break;
2296        }
2297      break;
2298    }
2299    case 'E':
2300    case 'e':
2301    {
2302      if (LocaleCompare((const char *) name,"ellipse") == 0)
2303        {
2304          double
2305            angle;
2306
2307          angle=svg_info->element.angle;
2308          (void) FormatLocaleFile(svg_info->file,"ellipse %g,%g %g,%g 0,360\n",
2309            svg_info->element.cx,svg_info->element.cy,
2310            angle == 0.0 ? svg_info->element.major : svg_info->element.minor,
2311            angle == 0.0 ? svg_info->element.minor : svg_info->element.major);
2312          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2313          break;
2314        }
2315      break;
2316    }
2317    case 'G':
2318    case 'g':
2319    {
2320      if (LocaleCompare((const char *) name,"g") == 0)
2321        {
2322          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2323          break;
2324        }
2325      break;
2326    }
2327    case 'I':
2328    case 'i':
2329    {
2330      if (LocaleCompare((const char *) name,"image") == 0)
2331        {
2332          (void) FormatLocaleFile(svg_info->file,
2333            "image Over %g,%g %g,%g '%s'\n",svg_info->bounds.x,
2334            svg_info->bounds.y,svg_info->bounds.width,svg_info->bounds.height,
2335            svg_info->url);
2336          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2337          break;
2338        }
2339      break;
2340    }
2341    case 'L':
2342    case 'l':
2343    {
2344      if (LocaleCompare((const char *) name,"line") == 0)
2345        {
2346          (void) FormatLocaleFile(svg_info->file,"line %g,%g %g,%g\n",
2347            svg_info->segment.x1,svg_info->segment.y1,svg_info->segment.x2,
2348            svg_info->segment.y2);
2349          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2350          break;
2351        }
2352      if (LocaleCompare((const char *) name,"linearGradient") == 0)
2353        {
2354          (void) FormatLocaleFile(svg_info->file,"pop gradient\n");
2355          break;
2356        }
2357      break;
2358    }
2359    case 'P':
2360    case 'p':
2361    {
2362      if (LocaleCompare((const char *) name,"pattern") == 0)
2363        {
2364          (void) FormatLocaleFile(svg_info->file,"pop pattern\n");
2365          break;
2366        }
2367      if (LocaleCompare((const char *) name,"path") == 0)
2368        {
2369          (void) FormatLocaleFile(svg_info->file,"path '%s'\n",
2370            svg_info->vertices);
2371          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2372          break;
2373        }
2374      if (LocaleCompare((const char *) name,"polygon") == 0)
2375        {
2376          (void) FormatLocaleFile(svg_info->file,"polygon %s\n",
2377            svg_info->vertices);
2378          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2379          break;
2380        }
2381      if (LocaleCompare((const char *) name,"polyline") == 0)
2382        {
2383          (void) FormatLocaleFile(svg_info->file,"polyline %s\n",
2384            svg_info->vertices);
2385          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2386          break;
2387        }
2388      break;
2389    }
2390    case 'R':
2391    case 'r':
2392    {
2393      if (LocaleCompare((const char *) name,"radialGradient") == 0)
2394        {
2395          (void) FormatLocaleFile(svg_info->file,"pop gradient\n");
2396          break;
2397        }
2398      if (LocaleCompare((const char *) name,"rect") == 0)
2399        {
2400          if ((svg_info->radius.x == 0.0) && (svg_info->radius.y == 0.0))
2401            {
2402              (void) FormatLocaleFile(svg_info->file,"rectangle %g,%g %g,%g\n",
2403                svg_info->bounds.x,svg_info->bounds.y,
2404                svg_info->bounds.x+svg_info->bounds.width,
2405                svg_info->bounds.y+svg_info->bounds.height);
2406              (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2407              break;
2408            }
2409          if (svg_info->radius.x == 0.0)
2410            svg_info->radius.x=svg_info->radius.y;
2411          if (svg_info->radius.y == 0.0)
2412            svg_info->radius.y=svg_info->radius.x;
2413          (void) FormatLocaleFile(svg_info->file,
2414            "roundRectangle %g,%g %g,%g %g,%g\n",
2415            svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.x+
2416            svg_info->bounds.width,svg_info->bounds.y+svg_info->bounds.height,
2417            svg_info->radius.x,svg_info->radius.y);
2418          svg_info->radius.x=0.0;
2419          svg_info->radius.y=0.0;
2420          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2421          break;
2422        }
2423      break;
2424    }
2425    case 'S':
2426    case 's':
2427    {
2428      if (LocaleCompare((const char *) name,"stop") == 0)
2429        {
2430          (void) FormatLocaleFile(svg_info->file,"stop-color '%s' %s\n",
2431            svg_info->stop_color,svg_info->offset);
2432          break;
2433        }
2434      if (LocaleCompare((const char *) name,"svg") == 0)
2435        {
2436          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2437          break;
2438        }
2439      break;
2440    }
2441    case 'T':
2442    case 't':
2443    {
2444      if (LocaleCompare((const char *) name,"text") == 0)
2445        {
2446          if (*svg_info->text != '\0')
2447            {
2448              char
2449                *text;
2450
2451              text=EscapeString(svg_info->text,'\'');
2452              (void) FormatLocaleFile(svg_info->file,"text %g,%g '%s'\n",
2453                svg_info->bounds.x,svg_info->bounds.y,text);
2454              text=DestroyString(text);
2455              *svg_info->text='\0';
2456            }
2457          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2458          break;
2459        }
2460      if (LocaleCompare((const char *) name,"tspan") == 0)
2461        {
2462          if (*svg_info->text != '\0')
2463            {
2464              DrawInfo
2465                *draw_info;
2466
2467              TypeMetric
2468                metrics;
2469
2470              char
2471                *text;
2472
2473              text=EscapeString(svg_info->text,'\'');
2474              (void) FormatLocaleFile(svg_info->file,"text %g,%g '%s'\n",
2475                svg_info->bounds.x,svg_info->bounds.y,text);
2476              text=DestroyString(text);
2477              draw_info=CloneDrawInfo(svg_info->image_info,(DrawInfo *) NULL);
2478              draw_info->pointsize=svg_info->pointsize;
2479              draw_info->text=AcquireString(svg_info->text);
2480              (void) ConcatenateString(&draw_info->text," ");
2481              (void) GetTypeMetrics(svg_info->image,draw_info,&metrics,
2482                svg_info->exception);
2483              svg_info->bounds.x+=metrics.width;
2484              draw_info=DestroyDrawInfo(draw_info);
2485              *svg_info->text='\0';
2486            }
2487          (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2488          break;
2489        }
2490      if (LocaleCompare((const char *) name,"title") == 0)
2491        {
2492          if (*svg_info->text == '\0')
2493            break;
2494          (void) CloneString(&svg_info->title,svg_info->text);
2495          *svg_info->text='\0';
2496          break;
2497        }
2498      break;
2499    }
2500    default:
2501      break;
2502  }
2503  *svg_info->text='\0';
2504  (void) ResetMagickMemory(&svg_info->element,0,sizeof(svg_info->element));
2505  (void) ResetMagickMemory(&svg_info->segment,0,sizeof(svg_info->segment));
2506  svg_info->n--;
2507}
2508
2509static void SVGCharacters(void *context,const xmlChar *c,int length)
2510{
2511  char
2512    *text;
2513
2514  register char
2515    *p;
2516
2517  register ssize_t
2518    i;
2519
2520  SVGInfo
2521    *svg_info;
2522
2523  /*
2524    Receiving some characters from the parser.
2525  */
2526  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2527    "  SAX.characters(%s,%.20g)",c,(double) length);
2528  svg_info=(SVGInfo *) context;
2529  text=(char *) AcquireQuantumMemory(length+1,sizeof(*text));
2530  if (text == (char *) NULL)
2531    return;
2532  p=text;
2533  for (i=0; i < (ssize_t) length; i++)
2534    *p++=c[i];
2535  *p='\0';
2536  StripString(text);
2537  if (svg_info->text == (char *) NULL)
2538    svg_info->text=text;
2539  else
2540    {
2541      (void) ConcatenateString(&svg_info->text,text);
2542      text=DestroyString(text);
2543    }
2544}
2545
2546static void SVGReference(void *context,const xmlChar *name)
2547{
2548  SVGInfo
2549    *svg_info;
2550
2551  xmlParserCtxtPtr
2552    parser;
2553
2554  /*
2555    Called when an entity reference is detected.
2556  */
2557  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.reference(%s)",
2558    name);
2559  svg_info=(SVGInfo *) context;
2560  parser=svg_info->parser;
2561  if (parser == (xmlParserCtxtPtr) NULL)
2562    return;
2563  if (parser->node == (xmlNodePtr) NULL)
2564    return;
2565  if (*name == '#')
2566    (void) xmlAddChild(parser->node,xmlNewCharRef(svg_info->document,name));
2567  else
2568    (void) xmlAddChild(parser->node,xmlNewReference(svg_info->document,name));
2569}
2570
2571static void SVGIgnorableWhitespace(void *context,const xmlChar *c,int length)
2572{
2573  SVGInfo
2574    *svg_info;
2575
2576  /*
2577    Receiving some ignorable whitespaces from the parser.
2578  */
2579  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2580    "  SAX.ignorableWhitespace(%.30s, %d)",c,length);
2581  svg_info=(SVGInfo *) context;
2582  (void) svg_info;
2583}
2584
2585static void SVGProcessingInstructions(void *context,const xmlChar *target,
2586  const xmlChar *data)
2587{
2588  SVGInfo
2589    *svg_info;
2590
2591  /*
2592    A processing instruction has been parsed.
2593  */
2594  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2595    "  SAX.processingInstruction(%s, %s)",target,data);
2596  svg_info=(SVGInfo *) context;
2597  (void) svg_info;
2598}
2599
2600static void SVGComment(void *context,const xmlChar *value)
2601{
2602  SVGInfo
2603    *svg_info;
2604
2605  /*
2606    A comment has been parsed.
2607  */
2608  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.comment(%s)",
2609    value);
2610  svg_info=(SVGInfo *) context;
2611  if (svg_info->comment != (char *) NULL)
2612    (void) ConcatenateString(&svg_info->comment,"\n");
2613  (void) ConcatenateString(&svg_info->comment,(const char *) value);
2614}
2615
2616static void SVGWarning(void *context,const char *format,...)
2617{
2618  char
2619    *message,
2620    reason[MagickPathExtent];
2621
2622  SVGInfo
2623    *svg_info;
2624
2625  va_list
2626    operands;
2627
2628  /**
2629    Display and format a warning messages, gives file, line, position and
2630    extra parameters.
2631  */
2632  va_start(operands,format);
2633  svg_info=(SVGInfo *) context;
2634  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.warning: ");
2635  (void) LogMagickEvent(CoderEvent,GetMagickModule(),format,operands);
2636#if !defined(MAGICKCORE_HAVE_VSNPRINTF)
2637  (void) vsprintf(reason,format,operands);
2638#else
2639  (void) vsnprintf(reason,MagickPathExtent,format,operands);
2640#endif
2641  message=GetExceptionMessage(errno);
2642  (void) ThrowMagickException(svg_info->exception,GetMagickModule(),
2643    DelegateWarning,reason,"`%s`",message);
2644  message=DestroyString(message);
2645  va_end(operands);
2646}
2647
2648static void SVGError(void *context,const char *format,...)
2649{
2650  char
2651    *message,
2652    reason[MagickPathExtent];
2653
2654  SVGInfo
2655    *svg_info;
2656
2657  va_list
2658    operands;
2659
2660  /*
2661    Display and format a error formats, gives file, line, position and
2662    extra parameters.
2663  */
2664  va_start(operands,format);
2665  svg_info=(SVGInfo *) context;
2666  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.error: ");
2667  (void) LogMagickEvent(CoderEvent,GetMagickModule(),format,operands);
2668#if !defined(MAGICKCORE_HAVE_VSNPRINTF)
2669  (void) vsprintf(reason,format,operands);
2670#else
2671  (void) vsnprintf(reason,MagickPathExtent,format,operands);
2672#endif
2673  message=GetExceptionMessage(errno);
2674  (void) ThrowMagickException(svg_info->exception,GetMagickModule(),CoderError,
2675    reason,"`%s`",message);
2676  message=DestroyString(message);
2677  va_end(operands);
2678}
2679
2680static void SVGCDataBlock(void *context,const xmlChar *value,int length)
2681{
2682  SVGInfo
2683    *svg_info;
2684
2685   xmlNodePtr
2686     child;
2687
2688  xmlParserCtxtPtr
2689    parser;
2690
2691  /*
2692    Called when a pcdata block has been parsed.
2693  */
2694  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.pcdata(%s, %d)",
2695    value,length);
2696  svg_info=(SVGInfo *) context;
2697  parser=svg_info->parser;
2698  child=xmlGetLastChild(parser->node);
2699  if ((child != (xmlNodePtr) NULL) && (child->type == XML_CDATA_SECTION_NODE))
2700    {
2701      xmlTextConcat(child,value,length);
2702      return;
2703    }
2704  (void) xmlAddChild(parser->node,xmlNewCDataBlock(parser->myDoc,value,length));
2705}
2706
2707static void SVGExternalSubset(void *context,const xmlChar *name,
2708  const xmlChar *external_id,const xmlChar *system_id)
2709{
2710  SVGInfo
2711    *svg_info;
2712
2713  xmlParserCtxt
2714    parser_context;
2715
2716  xmlParserCtxtPtr
2717    parser;
2718
2719  xmlParserInputPtr
2720    input;
2721
2722  /*
2723    Does this document has an external subset?
2724  */
2725  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2726    "  SAX.externalSubset(%s, %s, %s)",name,
2727    (external_id != (const xmlChar *) NULL ? (const char *) external_id : "none"),
2728    (system_id != (const xmlChar *) NULL ? (const char *) system_id : "none"));
2729  svg_info=(SVGInfo *) context;
2730  parser=svg_info->parser;
2731  if (((external_id == NULL) && (system_id == NULL)) ||
2732      ((parser->validate == 0) || (parser->wellFormed == 0) ||
2733      (svg_info->document == 0)))
2734    return;
2735  input=SVGResolveEntity(context,external_id,system_id);
2736  if (input == NULL)
2737    return;
2738  (void) xmlNewDtd(svg_info->document,name,external_id,system_id);
2739  parser_context=(*parser);
2740  parser->inputTab=(xmlParserInputPtr *) xmlMalloc(5*sizeof(*parser->inputTab));
2741  if (parser->inputTab == (xmlParserInputPtr *) NULL)
2742    {
2743      parser->errNo=XML_ERR_NO_MEMORY;
2744      parser->input=parser_context.input;
2745      parser->inputNr=parser_context.inputNr;
2746      parser->inputMax=parser_context.inputMax;
2747      parser->inputTab=parser_context.inputTab;
2748      return;
2749  }
2750  parser->inputNr=0;
2751  parser->inputMax=5;
2752  parser->input=NULL;
2753  xmlPushInput(parser,input);
2754  (void) xmlSwitchEncoding(parser,xmlDetectCharEncoding(parser->input->cur,4));
2755  if (input->filename == (char *) NULL)
2756    input->filename=(char *) xmlStrdup(system_id);
2757  input->line=1;
2758  input->col=1;
2759  input->base=parser->input->cur;
2760  input->cur=parser->input->cur;
2761  input->free=NULL;
2762  xmlParseExternalSubset(parser,external_id,system_id);
2763  while (parser->inputNr > 1)
2764    (void) xmlPopInput(parser);
2765  xmlFreeInputStream(parser->input);
2766  xmlFree(parser->inputTab);
2767  parser->input=parser_context.input;
2768  parser->inputNr=parser_context.inputNr;
2769  parser->inputMax=parser_context.inputMax;
2770  parser->inputTab=parser_context.inputTab;
2771}
2772
2773#if defined(__cplusplus) || defined(c_plusplus)
2774}
2775#endif
2776
2777/*
2778  Static declarations.
2779*/
2780static char
2781  SVGDensityGeometry[] = "90.0x90.0";
2782
2783
2784static Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
2785{
2786  char
2787    filename[MagickPathExtent];
2788
2789  FILE
2790    *file;
2791
2792  Image
2793    *image;
2794
2795  int
2796    status,
2797    unique_file;
2798
2799  ssize_t
2800    n;
2801
2802  SVGInfo
2803    *svg_info;
2804
2805  unsigned char
2806    message[MagickPathExtent];
2807
2808  xmlSAXHandler
2809    sax_modules;
2810
2811  xmlSAXHandlerPtr
2812    sax_handler;
2813
2814  /*
2815    Open image file.
2816  */
2817  assert(image_info != (const ImageInfo *) NULL);
2818  assert(image_info->signature == MagickCoreSignature);
2819  assert(exception != (ExceptionInfo *) NULL);
2820  if (image_info->debug != MagickFalse)
2821    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
2822      image_info->filename);
2823  assert(exception->signature == MagickCoreSignature);
2824  image=AcquireImage(image_info,exception);
2825  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
2826  if (status == MagickFalse)
2827    {
2828      image=DestroyImageList(image);
2829      return((Image *) NULL);
2830    }
2831  if ((image->resolution.x == 0.0) || (image->resolution.y == 0.0))
2832    {
2833      GeometryInfo
2834        geometry_info;
2835
2836      int
2837        flags;
2838
2839      flags=ParseGeometry(SVGDensityGeometry,&geometry_info);
2840      image->resolution.x=geometry_info.rho;
2841      image->resolution.y=geometry_info.sigma;
2842      if ((flags & SigmaValue) == 0)
2843        image->resolution.y=image->resolution.x;
2844    }
2845  if (LocaleCompare(image_info->magick,"MSVG") != 0)
2846    {
2847      const DelegateInfo
2848        *delegate_info;
2849
2850      delegate_info=GetDelegateInfo("svg:decode",(char *) NULL,exception);
2851      if (delegate_info != (const DelegateInfo *) NULL)
2852        {
2853          char
2854            background[MagickPathExtent],
2855            command[MagickPathExtent],
2856            *density,
2857            input_filename[MagickPathExtent],
2858            opacity[MagickPathExtent],
2859            output_filename[MagickPathExtent],
2860            unique[MagickPathExtent];
2861
2862          int
2863            status;
2864
2865          struct stat
2866            attributes;
2867
2868          /*
2869            Our best hope of compliance with the SVG standard.
2870          */
2871          status=AcquireUniqueSymbolicLink(image->filename,input_filename);
2872          (void) AcquireUniqueFilename(output_filename);
2873          (void) AcquireUniqueFilename(unique);
2874          density=AcquireString("");
2875          (void) FormatLocaleString(density,MagickPathExtent,"%.20g,%.20g",
2876            image->resolution.x,image->resolution.y);
2877          (void) FormatLocaleString(background,MagickPathExtent,
2878            "rgb(%.20g%%,%.20g%%,%.20g%%)",
2879            100.0*QuantumScale*image->background_color.red,
2880            100.0*QuantumScale*image->background_color.green,
2881            100.0*QuantumScale*image->background_color.blue);
2882          (void) FormatLocaleString(opacity,MagickPathExtent,"%.20g",
2883            QuantumScale*image->background_color.alpha);
2884          (void) FormatLocaleString(command,MagickPathExtent,GetDelegateCommands(
2885            delegate_info),input_filename,output_filename,density,background,
2886            opacity,unique);
2887          density=DestroyString(density);
2888          status=ExternalDelegateCommand(MagickFalse,image_info->verbose,
2889            command,(char *) NULL,exception);
2890          (void) RelinquishUniqueFileResource(unique);
2891          (void) RelinquishUniqueFileResource(input_filename);
2892          if ((status == 0) && (stat(output_filename,&attributes) == 0) &&
2893              (attributes.st_size > 0))
2894            {
2895              Image
2896                *svg_image;
2897
2898              ImageInfo
2899                *read_info;
2900
2901              read_info=CloneImageInfo(image_info);
2902              (void) CopyMagickString(read_info->filename,output_filename,
2903                MagickPathExtent);
2904              svg_image=ReadImage(read_info,exception);
2905              read_info=DestroyImageInfo(read_info);
2906              (void) RelinquishUniqueFileResource(output_filename);
2907              if (svg_image != (Image *) NULL)
2908                {
2909                  image=DestroyImage(image);
2910                  return(svg_image);
2911                }
2912            }
2913          (void) RelinquishUniqueFileResource(output_filename);
2914        }
2915      {
2916#if defined(MAGICKCORE_RSVG_DELEGATE)
2917#if defined(MAGICKCORE_CAIRO_DELEGATE)
2918        cairo_surface_t
2919          *cairo_surface;
2920
2921        cairo_t
2922          *cairo_image;
2923
2924        MemoryInfo
2925          *pixel_info;
2926
2927        register unsigned char
2928          *p;
2929
2930        RsvgDimensionData
2931          dimension_info;
2932
2933        unsigned char
2934          *pixels;
2935
2936#else
2937        GdkPixbuf
2938          *pixel_buffer;
2939
2940        register const guchar
2941          *p;
2942#endif
2943
2944        GError
2945          *error;
2946
2947        PixelInfo
2948          fill_color;
2949
2950        register ssize_t
2951          x;
2952
2953        register Quantum
2954          *q;
2955
2956        RsvgHandle
2957          *svg_handle;
2958
2959        ssize_t
2960          y;
2961
2962        svg_handle=rsvg_handle_new();
2963        if (svg_handle == (RsvgHandle *) NULL)
2964          ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
2965        rsvg_handle_set_base_uri(svg_handle,image_info->filename);
2966        if ((image->resolution.x != 90.0) && (image->resolution.y != 90.0))
2967          rsvg_handle_set_dpi_x_y(svg_handle,image->resolution.x,
2968            image->resolution.y);
2969        while ((n=ReadBlob(image,MagickPathExtent-1,message)) != 0)
2970        {
2971          message[n]='\0';
2972          error=(GError *) NULL;
2973          (void) rsvg_handle_write(svg_handle,message,n,&error);
2974          if (error != (GError *) NULL)
2975            g_error_free(error);
2976        }
2977        error=(GError *) NULL;
2978        rsvg_handle_close(svg_handle,&error);
2979        if (error != (GError *) NULL)
2980          g_error_free(error);
2981#if defined(MAGICKCORE_CAIRO_DELEGATE)
2982        rsvg_handle_get_dimensions(svg_handle,&dimension_info);
2983        if (image_info->size != (char *) NULL)
2984          {
2985            (void) GetGeometry(image_info->size,(ssize_t *) NULL,
2986              (ssize_t *) NULL,&image->columns,&image->rows);
2987            if ((image->columns != 0) || (image->rows != 0))
2988              {
2989                image->resolution.x=90.0*image->columns/dimension_info.width;
2990                image->resolution.y=90.0*image->rows/dimension_info.height;
2991                if (image->resolution.x == 0)
2992                  image->resolution.x=image->resolution.y;
2993                else if (image->resolution.y == 0)
2994                  image->resolution.y=image->resolution.x;
2995                else
2996                  image->resolution.x=image->resolution.y=MagickMin(
2997                    image->resolution.x,image->resolution.y);
2998              }
2999          }
3000        image->columns=image->resolution.x*dimension_info.width/90.0;
3001        image->rows=image->resolution.y*dimension_info.height/90.0;
3002        pixel_info=(MemoryInfo *) NULL;
3003#else
3004        pixel_buffer=rsvg_handle_get_pixbuf(svg_handle);
3005        rsvg_handle_free(svg_handle);
3006        image->columns=gdk_pixbuf_get_width(pixel_buffer);
3007        image->rows=gdk_pixbuf_get_height(pixel_buffer);
3008#endif
3009        image->alpha_trait=BlendPixelTrait;
3010        SetImageProperty(image,"svg:base-uri",
3011          rsvg_handle_get_base_uri(svg_handle),exception);
3012        status=SetImageExtent(image,image->columns,image->rows,exception);
3013        if (status == MagickFalse)
3014          {
3015#if !defined(MAGICKCORE_CAIRO_DELEGATE)
3016            g_object_unref(G_OBJECT(pixel_buffer));
3017#endif
3018            g_object_unref(svg_handle);
3019            ThrowReaderException(MissingDelegateError,
3020              "NoDecodeDelegateForThisImageFormat");
3021          }
3022        if (image_info->ping == MagickFalse)
3023          {
3024#if defined(MAGICKCORE_CAIRO_DELEGATE)
3025            size_t
3026              stride;
3027
3028            stride=4*image->columns;
3029#if defined(MAGICKCORE_PANGOCAIRO_DELEGATE)
3030            stride=(size_t) cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32,
3031              (int) image->columns);
3032#endif
3033            pixel_info=AcquireVirtualMemory(stride,image->rows*sizeof(*pixels));
3034            if (pixel_info == (MemoryInfo *) NULL)
3035              {
3036                g_object_unref(svg_handle);
3037                ThrowReaderException(ResourceLimitError,
3038                  "MemoryAllocationFailed");
3039              }
3040            pixels=(unsigned char *) GetVirtualMemoryBlob(pixel_info);
3041#endif
3042            (void) SetImageBackgroundColor(image,exception);
3043#if defined(MAGICKCORE_CAIRO_DELEGATE)
3044            cairo_surface=cairo_image_surface_create_for_data(pixels,
3045              CAIRO_FORMAT_ARGB32,(int) image->columns,(int) image->rows,(int)
3046              stride);
3047            if (cairo_surface == (cairo_surface_t *) NULL)
3048              {
3049                pixel_info=RelinquishVirtualMemory(pixel_info);
3050                g_object_unref(svg_handle);
3051                ThrowReaderException(ResourceLimitError,
3052                  "MemoryAllocationFailed");
3053              }
3054            cairo_image=cairo_create(cairo_surface);
3055            cairo_set_operator(cairo_image,CAIRO_OPERATOR_CLEAR);
3056            cairo_paint(cairo_image);
3057            cairo_set_operator(cairo_image,CAIRO_OPERATOR_OVER);
3058            cairo_scale(cairo_image,image->resolution.x/90.0,
3059              image->resolution.y/90.0);
3060            rsvg_handle_render_cairo(svg_handle,cairo_image);
3061            cairo_destroy(cairo_image);
3062            cairo_surface_destroy(cairo_surface);
3063            g_object_unref(svg_handle);
3064            p=pixels;
3065#else
3066            p=gdk_pixbuf_get_pixels(pixel_buffer);
3067#endif
3068            GetPixelInfo(image,&fill_color);
3069            for (y=0; y < (ssize_t) image->rows; y++)
3070            {
3071              q=GetAuthenticPixels(image,0,y,image->columns,1,exception);
3072              if (q == (Quantum *) NULL)
3073                break;
3074              for (x=0; x < (ssize_t) image->columns; x++)
3075              {
3076#if defined(MAGICKCORE_CAIRO_DELEGATE)
3077                fill_color.blue=ScaleCharToQuantum(*p++);
3078                fill_color.green=ScaleCharToQuantum(*p++);
3079                fill_color.red=ScaleCharToQuantum(*p++);
3080#else
3081                fill_color.red=ScaleCharToQuantum(*p++);
3082                fill_color.green=ScaleCharToQuantum(*p++);
3083                fill_color.blue=ScaleCharToQuantum(*p++);
3084#endif
3085                fill_color.alpha=ScaleCharToQuantum(*p++);
3086#if defined(MAGICKCORE_CAIRO_DELEGATE)
3087                {
3088                  double
3089                    gamma;
3090
3091                  gamma=QuantumScale*fill_color.alpha;
3092                  gamma=PerceptibleReciprocal(gamma);
3093                  fill_color.blue*=gamma;
3094                  fill_color.green*=gamma;
3095                  fill_color.red*=gamma;
3096                }
3097#endif
3098                CompositePixelOver(image,&fill_color,fill_color.alpha,q,(double)
3099                  GetPixelAlpha(image,q),q);
3100                q+=GetPixelChannels(image);
3101              }
3102              if (SyncAuthenticPixels(image,exception) == MagickFalse)
3103                break;
3104              if (image->previous == (Image *) NULL)
3105                {
3106                  status=SetImageProgress(image,LoadImageTag,(MagickOffsetType)
3107                    y,image->rows);
3108                  if (status == MagickFalse)
3109                    break;
3110                }
3111            }
3112          }
3113#if defined(MAGICKCORE_CAIRO_DELEGATE)
3114        if (pixel_info != (MemoryInfo *) NULL)
3115          pixel_info=RelinquishVirtualMemory(pixel_info);
3116#else
3117        g_object_unref(G_OBJECT(pixel_buffer));
3118#endif
3119        (void) CloseBlob(image);
3120        return(GetFirstImageInList(image));
3121#endif
3122      }
3123    }
3124  /*
3125    Open draw file.
3126  */
3127  file=(FILE *) NULL;
3128  unique_file=AcquireUniqueFileResource(filename);
3129  if (unique_file != -1)
3130    file=fdopen(unique_file,"w");
3131  if ((unique_file == -1) || (file == (FILE *) NULL))
3132    {
3133      (void) CopyMagickString(image->filename,filename,MagickPathExtent);
3134      ThrowFileException(exception,FileOpenError,"UnableToCreateTemporaryFile",
3135        image->filename);
3136      image=DestroyImageList(image);
3137      return((Image *) NULL);
3138    }
3139  /*
3140    Parse SVG file.
3141  */
3142  svg_info=AcquireSVGInfo();
3143  if (svg_info == (SVGInfo *) NULL)
3144    {
3145      (void) fclose(file);
3146      ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
3147    }
3148  svg_info->file=file;
3149  svg_info->exception=exception;
3150  svg_info->image=image;
3151  svg_info->image_info=image_info;
3152  svg_info->bounds.width=image->columns;
3153  svg_info->bounds.height=image->rows;
3154  if (image_info->size != (char *) NULL)
3155    (void) CloneString(&svg_info->size,image_info->size);
3156  if (image->debug != MagickFalse)
3157    (void) LogMagickEvent(CoderEvent,GetMagickModule(),"begin SAX");
3158  (void) xmlSubstituteEntitiesDefault(1);
3159  (void) ResetMagickMemory(&sax_modules,0,sizeof(sax_modules));
3160  sax_modules.internalSubset=SVGInternalSubset;
3161  sax_modules.isStandalone=SVGIsStandalone;
3162  sax_modules.hasInternalSubset=SVGHasInternalSubset;
3163  sax_modules.hasExternalSubset=SVGHasExternalSubset;
3164  sax_modules.resolveEntity=SVGResolveEntity;
3165  sax_modules.getEntity=SVGGetEntity;
3166  sax_modules.entityDecl=SVGEntityDeclaration;
3167  sax_modules.notationDecl=SVGNotationDeclaration;
3168  sax_modules.attributeDecl=SVGAttributeDeclaration;
3169  sax_modules.elementDecl=SVGElementDeclaration;
3170  sax_modules.unparsedEntityDecl=SVGUnparsedEntityDeclaration;
3171  sax_modules.setDocumentLocator=SVGSetDocumentLocator;
3172  sax_modules.startDocument=SVGStartDocument;
3173  sax_modules.endDocument=SVGEndDocument;
3174  sax_modules.startElement=SVGStartElement;
3175  sax_modules.endElement=SVGEndElement;
3176  sax_modules.reference=SVGReference;
3177  sax_modules.characters=SVGCharacters;
3178  sax_modules.ignorableWhitespace=SVGIgnorableWhitespace;
3179  sax_modules.processingInstruction=SVGProcessingInstructions;
3180  sax_modules.comment=SVGComment;
3181  sax_modules.warning=SVGWarning;
3182  sax_modules.error=SVGError;
3183  sax_modules.fatalError=SVGError;
3184  sax_modules.getParameterEntity=SVGGetParameterEntity;
3185  sax_modules.cdataBlock=SVGCDataBlock;
3186  sax_modules.externalSubset=SVGExternalSubset;
3187  sax_handler=(&sax_modules);
3188  n=ReadBlob(image,MagickPathExtent-1,message);
3189  message[n]='\0';
3190  if (n > 0)
3191    {
3192      svg_info->parser=xmlCreatePushParserCtxt(sax_handler,svg_info,(char *)
3193        message,n,image->filename);
3194      while ((n=ReadBlob(image,MagickPathExtent-1,message)) != 0)
3195      {
3196        message[n]='\0';
3197        status=xmlParseChunk(svg_info->parser,(char *) message,(int) n,0);
3198        if (status != 0)
3199          break;
3200      }
3201    }
3202  (void) xmlParseChunk(svg_info->parser,(char *) message,0,1);
3203  SVGEndDocument(svg_info);
3204  xmlFreeParserCtxt(svg_info->parser);
3205  if (image->debug != MagickFalse)
3206    (void) LogMagickEvent(CoderEvent,GetMagickModule(),"end SAX");
3207  (void) fclose(file);
3208  (void) CloseBlob(image);
3209  image->columns=svg_info->width;
3210  image->rows=svg_info->height;
3211  if (exception->severity >= ErrorException)
3212    {
3213      image=DestroyImage(image);
3214      return((Image *) NULL);
3215    }
3216  if (image_info->ping == MagickFalse)
3217    {
3218      ImageInfo
3219        *read_info;
3220
3221      /*
3222        Draw image.
3223      */
3224      image=DestroyImage(image);
3225      image=(Image *) NULL;
3226      read_info=CloneImageInfo(image_info);
3227      SetImageInfoBlob(read_info,(void *) NULL,0);
3228      if (read_info->density != (char *) NULL)
3229        read_info->density=DestroyString(read_info->density);
3230      (void) FormatLocaleString(read_info->filename,MagickPathExtent,"mvg:%s",
3231        filename);
3232      image=ReadImage(read_info,exception);
3233      read_info=DestroyImageInfo(read_info);
3234      if (image != (Image *) NULL)
3235        (void) CopyMagickString(image->filename,image_info->filename,
3236          MagickPathExtent);
3237    }
3238  /*
3239    Relinquish resources.
3240  */
3241  if (image != (Image *) NULL)
3242    {
3243      if (svg_info->title != (char *) NULL)
3244        (void) SetImageProperty(image,"svg:title",svg_info->title,exception);
3245      if (svg_info->comment != (char *) NULL)
3246        (void) SetImageProperty(image,"svg:comment",svg_info->comment,
3247          exception);
3248    }
3249  svg_info=DestroySVGInfo(svg_info);
3250  (void) RelinquishUniqueFileResource(filename);
3251  return(GetFirstImageInList(image));
3252}
3253#endif
3254
3255/*
3256%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3257%                                                                             %
3258%                                                                             %
3259%                                                                             %
3260%   R e g i s t e r S V G I m a g e                                           %
3261%                                                                             %
3262%                                                                             %
3263%                                                                             %
3264%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3265%
3266%  RegisterSVGImage() adds attributes for the SVG image format to
3267%  the list of supported formats.  The attributes include the image format
3268%  tag, a method to read and/or write the format, whether the format
3269%  supports the saving of more than one frame to the same file or blob,
3270%  whether the format supports native in-memory I/O, and a brief
3271%  description of the format.
3272%
3273%  The format of the RegisterSVGImage method is:
3274%
3275%      size_t RegisterSVGImage(void)
3276%
3277*/
3278ModuleExport size_t RegisterSVGImage(void)
3279{
3280  char
3281    version[MagickPathExtent];
3282
3283  MagickInfo
3284    *entry;
3285
3286  *version='\0';
3287#if defined(LIBXML_DOTTED_VERSION)
3288  (void) CopyMagickString(version,"XML " LIBXML_DOTTED_VERSION,
3289    MagickPathExtent);
3290#endif
3291#if defined(MAGICKCORE_RSVG_DELEGATE)
3292#if !GLIB_CHECK_VERSION(2,35,0)
3293  g_type_init();
3294#endif
3295#if defined(MAGICKCORE_XML_DELEGATE)
3296  xmlInitParser();
3297#endif
3298  (void) FormatLocaleString(version,MagickPathExtent,"RSVG %d.%d.%d",
3299    LIBRSVG_MAJOR_VERSION,LIBRSVG_MINOR_VERSION,LIBRSVG_MICRO_VERSION);
3300#endif
3301  entry=AcquireMagickInfo("SVG","SVG","Scalable Vector Graphics");
3302#if defined(MAGICKCORE_XML_DELEGATE)
3303  entry->decoder=(DecodeImageHandler *) ReadSVGImage;
3304#endif
3305  entry->encoder=(EncodeImageHandler *) WriteSVGImage;
3306  entry->flags^=CoderBlobSupportFlag;
3307  entry->mime_type=ConstantString("image/svg+xml");
3308  if (*version != '\0')
3309    entry->version=ConstantString(version);
3310  entry->magick=(IsImageFormatHandler *) IsSVG;
3311  (void) RegisterMagickInfo(entry);
3312  entry=AcquireMagickInfo("SVG","SVGZ","Compressed Scalable Vector Graphics");
3313#if defined(MAGICKCORE_XML_DELEGATE)
3314  entry->decoder=(DecodeImageHandler *) ReadSVGImage;
3315#endif
3316  entry->encoder=(EncodeImageHandler *) WriteSVGImage;
3317  entry->flags^=CoderBlobSupportFlag;
3318  entry->mime_type=ConstantString("image/svg+xml");
3319  if (*version != '\0')
3320    entry->version=ConstantString(version);
3321  entry->magick=(IsImageFormatHandler *) IsSVG;
3322  (void) RegisterMagickInfo(entry);
3323  entry=AcquireMagickInfo("SVG","MSVG",
3324    "ImageMagick's own SVG internal renderer");
3325#if defined(MAGICKCORE_XML_DELEGATE)
3326  entry->decoder=(DecodeImageHandler *) ReadSVGImage;
3327#endif
3328  entry->encoder=(EncodeImageHandler *) WriteSVGImage;
3329  entry->flags^=CoderBlobSupportFlag;
3330  entry->magick=(IsImageFormatHandler *) IsSVG;
3331  (void) RegisterMagickInfo(entry);
3332  return(MagickImageCoderSignature);
3333}
3334
3335/*
3336%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3337%                                                                             %
3338%                                                                             %
3339%                                                                             %
3340%   U n r e g i s t e r S V G I m a g e                                       %
3341%                                                                             %
3342%                                                                             %
3343%                                                                             %
3344%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3345%
3346%  UnregisterSVGImage() removes format registrations made by the
3347%  SVG module from the list of supported formats.
3348%
3349%  The format of the UnregisterSVGImage method is:
3350%
3351%      UnregisterSVGImage(void)
3352%
3353*/
3354ModuleExport void UnregisterSVGImage(void)
3355{
3356  (void) UnregisterMagickInfo("SVGZ");
3357  (void) UnregisterMagickInfo("SVG");
3358  (void) UnregisterMagickInfo("MSVG");
3359#if defined(MAGICKCORE_XML_DELEGATE)
3360  xmlCleanupParser();
3361#endif
3362}
3363
3364/*
3365%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3366%                                                                             %
3367%                                                                             %
3368%                                                                             %
3369%   W r i t e S V G I m a g e                                                 %
3370%                                                                             %
3371%                                                                             %
3372%                                                                             %
3373%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3374%
3375%  WriteSVGImage() writes a image in the SVG - XML based W3C standard
3376%  format.
3377%
3378%  The format of the WriteSVGImage method is:
3379%
3380%      MagickBooleanType WriteSVGImage(const ImageInfo *image_info,
3381%        Image *image,ExceptionInfo *exception)
3382%
3383%  A description of each parameter follows.
3384%
3385%    o image_info: the image info.
3386%
3387%    o image:  The image.
3388%
3389%    o exception: return any errors or warnings in this structure.
3390%
3391*/
3392
3393static void AffineToTransform(Image *image,AffineMatrix *affine)
3394{
3395  char
3396    transform[MagickPathExtent];
3397
3398  if ((fabs(affine->tx) < MagickEpsilon) && (fabs(affine->ty) < MagickEpsilon))
3399    {
3400      if ((fabs(affine->rx) < MagickEpsilon) &&
3401          (fabs(affine->ry) < MagickEpsilon))
3402        {
3403          if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
3404              (fabs(affine->sy-1.0) < MagickEpsilon))
3405            {
3406              (void) WriteBlobString(image,"\">\n");
3407              return;
3408            }
3409          (void) FormatLocaleString(transform,MagickPathExtent,
3410            "\" transform=\"scale(%g,%g)\">\n",affine->sx,affine->sy);
3411          (void) WriteBlobString(image,transform);
3412          return;
3413        }
3414      else
3415        {
3416          if ((fabs(affine->sx-affine->sy) < MagickEpsilon) &&
3417              (fabs(affine->rx+affine->ry) < MagickEpsilon) &&
3418              (fabs(affine->sx*affine->sx+affine->rx*affine->rx-1.0) <
3419               2*MagickEpsilon))
3420            {
3421              double
3422                theta;
3423
3424              theta=(180.0/MagickPI)*atan2(affine->rx,affine->sx);
3425              (void) FormatLocaleString(transform,MagickPathExtent,
3426                "\" transform=\"rotate(%g)\">\n",theta);
3427              (void) WriteBlobString(image,transform);
3428              return;
3429            }
3430        }
3431    }
3432  else
3433    {
3434      if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
3435          (fabs(affine->rx) < MagickEpsilon) &&
3436          (fabs(affine->ry) < MagickEpsilon) &&
3437          (fabs(affine->sy-1.0) < MagickEpsilon))
3438        {
3439          (void) FormatLocaleString(transform,MagickPathExtent,
3440            "\" transform=\"translate(%g,%g)\">\n",affine->tx,affine->ty);
3441          (void) WriteBlobString(image,transform);
3442          return;
3443        }
3444    }
3445  (void) FormatLocaleString(transform,MagickPathExtent,
3446    "\" transform=\"matrix(%g %g %g %g %g %g)\">\n",
3447    affine->sx,affine->rx,affine->ry,affine->sy,affine->tx,affine->ty);
3448  (void) WriteBlobString(image,transform);
3449}
3450
3451static MagickBooleanType IsPoint(const char *point)
3452{
3453  char
3454    *p;
3455
3456  ssize_t
3457    value;
3458
3459  value=strtol(point,&p,10);
3460  (void) value;
3461  return(p != point ? MagickTrue : MagickFalse);
3462}
3463
3464static MagickBooleanType TraceSVGImage(Image *image,ExceptionInfo *exception)
3465{
3466#if defined(MAGICKCORE_AUTOTRACE_DELEGATE)
3467  {
3468    at_bitmap_type
3469      *trace;
3470
3471    at_fitting_opts_type
3472      *fitting_options;
3473
3474    at_output_opts_type
3475      *output_options;
3476
3477    at_splines_type
3478      *splines;
3479
3480    ImageType
3481      type;
3482
3483    register const PixelPacket
3484      *p;
3485
3486    register ssize_t
3487      i,
3488      x;
3489
3490    size_t
3491      number_planes;
3492
3493    ssize_t
3494      y;
3495
3496    /*
3497      Trace image and write as SVG.
3498    */
3499    fitting_options=at_fitting_opts_new();
3500    output_options=at_output_opts_new();
3501    (void) SetImageGray(image,exception);
3502    type=GetImageType(image);
3503    number_planes=3;
3504    if ((type == BilevelType) || (type == GrayscaleType))
3505      number_planes=1;
3506    trace=at_bitmap_new(image->columns,image->rows,number_planes);
3507    i=0;
3508    for (y=0; y < (ssize_t) image->rows; y++)
3509    {
3510      p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3511      if (p == (const PixelPacket *) NULL)
3512        break;
3513      for (x=0; x < (ssize_t) image->columns; x++)
3514      {
3515        trace->bitmap[i++]=GetPixelRed(image,p);
3516        if (number_planes == 3)
3517          {
3518            trace->bitmap[i++]=GetPixelGreen(image,p);
3519            trace->bitmap[i++]=GetPixelBlue(image,p);
3520          }
3521        p+=GetPixelChannels(image);
3522      }
3523    }
3524    splines=at_splines_new_full(trace,fitting_options,NULL,NULL,NULL,NULL,NULL,
3525      NULL);
3526    at_splines_write(at_output_get_handler_by_suffix((char *) "svg"),
3527      GetBlobFileHandle(image),image->filename,output_options,splines,NULL,
3528      NULL);
3529    /*
3530      Free resources.
3531    */
3532    at_splines_free(splines);
3533    at_bitmap_free(trace);
3534    at_output_opts_free(output_options);
3535    at_fitting_opts_free(fitting_options);
3536  }
3537#else
3538  {
3539    char
3540      *base64,
3541      message[MagickPathExtent];
3542
3543    Image
3544      *clone_image;
3545
3546    ImageInfo
3547      *image_info;
3548
3549    register char
3550      *p;
3551
3552    size_t
3553      blob_length,
3554      encode_length;
3555
3556    ssize_t
3557      i;
3558
3559    unsigned char
3560      *blob;
3561
3562    (void) WriteBlobString(image,
3563      "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
3564    (void) WriteBlobString(image,
3565      "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"");
3566    (void) WriteBlobString(image,
3567      " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
3568    (void) FormatLocaleString(message,MagickPathExtent,
3569      "<svg version=\"1.1\" id=\"Layer_1\" "
3570      "xmlns=\"http://www.w3.org/2000/svg\" "
3571      "xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" "
3572      "width=\"%.20gpx\" height=\"%.20gpx\" viewBox=\"0 0 %.20g %.20g\" "
3573      "enable-background=\"new 0 0 %.20g %.20g\" xml:space=\"preserve\">",
3574      (double) image->columns,(double) image->rows,
3575      (double) image->columns,(double) image->rows,
3576      (double) image->columns,(double) image->rows);
3577    (void) WriteBlobString(image,message);
3578    clone_image=CloneImage(image,0,0,MagickTrue,exception);
3579    if (clone_image == (Image *) NULL)
3580      return(MagickFalse);
3581    image_info=AcquireImageInfo();
3582    (void) CopyMagickString(image_info->magick,"PNG",MagickPathExtent);
3583    blob_length=2048;
3584    blob=(unsigned char *) ImageToBlob(image_info,clone_image,&blob_length,
3585      exception);
3586    clone_image=DestroyImage(clone_image);
3587    image_info=DestroyImageInfo(image_info);
3588    if (blob == (unsigned char *) NULL)
3589      return(MagickFalse);
3590    encode_length=0;
3591    base64=Base64Encode(blob,blob_length,&encode_length);
3592    blob=(unsigned char *) RelinquishMagickMemory(blob);
3593    (void) FormatLocaleString(message,MagickPathExtent,
3594      "  <image id=\"image%.20g\" width=\"%.20g\" height=\"%.20g\" "
3595      "x=\"%.20g\" y=\"%.20g\"\n    xlink:href=\"data:image/png;base64,",
3596      (double) image->scene,(double) image->columns,(double) image->rows,
3597      (double) image->page.x,(double) image->page.y);
3598    (void) WriteBlobString(image,message);
3599    p=base64;
3600    for (i=(ssize_t) encode_length; i > 0; i-=76)
3601    {
3602      (void) FormatLocaleString(message,MagickPathExtent,"%.76s",p);
3603      (void) WriteBlobString(image,message);
3604      p+=76;
3605      if (i > 76)
3606        (void) WriteBlobString(image,"\n");
3607    }
3608    base64=DestroyString(base64);
3609    (void) WriteBlobString(image,"\" />\n");
3610    (void) WriteBlobString(image,"</svg>\n");
3611  }
3612#endif
3613  CloseBlob(image);
3614  return(MagickTrue);
3615}
3616
3617static MagickBooleanType WriteSVGImage(const ImageInfo *image_info,Image *image,
3618  ExceptionInfo *exception)
3619{
3620#define BezierQuantum  200
3621
3622  AffineMatrix
3623    affine;
3624
3625  char
3626    keyword[MagickPathExtent],
3627    message[MagickPathExtent],
3628    name[MagickPathExtent],
3629    *next_token,
3630    *token,
3631    type[MagickPathExtent];
3632
3633  const char
3634    *p,
3635    *q,
3636    *value;
3637
3638  int
3639    n;
3640
3641  ssize_t
3642    j;
3643
3644  MagickBooleanType
3645    active,
3646    status;
3647
3648  PointInfo
3649    point;
3650
3651  PrimitiveInfo
3652    *primitive_info;
3653
3654  PrimitiveType
3655    primitive_type;
3656
3657  register ssize_t
3658    x;
3659
3660  register ssize_t
3661    i;
3662
3663  size_t
3664    extent,
3665    length,
3666    number_points;
3667
3668  SVGInfo
3669    svg_info;
3670
3671  /*
3672    Open output image file.
3673  */
3674  assert(image_info != (const ImageInfo *) NULL);
3675  assert(image_info->signature == MagickCoreSignature);
3676  assert(image != (Image *) NULL);
3677  assert(image->signature == MagickCoreSignature);
3678  if (image->debug != MagickFalse)
3679    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3680  assert(exception != (ExceptionInfo *) NULL);
3681  assert(exception->signature == MagickCoreSignature);
3682  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
3683  if (status == MagickFalse)
3684    return(status);
3685  value=GetImageArtifact(image,"SVG");
3686  if (value != (char *) NULL)
3687    {
3688      (void) WriteBlobString(image,value);
3689      (void) CloseBlob(image);
3690      return(MagickTrue);
3691    }
3692  value=GetImageArtifact(image,"MVG");
3693  if (value == (char *) NULL)
3694    return(TraceSVGImage(image,exception));
3695  /*
3696    Write SVG header.
3697  */
3698  (void) WriteBlobString(image,"<?xml version=\"1.0\" standalone=\"no\"?>\n");
3699  (void) WriteBlobString(image,
3700    "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n");
3701  (void) WriteBlobString(image,
3702    "  \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n");
3703  (void) FormatLocaleString(message,MagickPathExtent,
3704    "<svg width=\"%.20g\" height=\"%.20g\">\n",(double) image->columns,(double)
3705    image->rows);
3706  (void) WriteBlobString(image,message);
3707  /*
3708    Allocate primitive info memory.
3709  */
3710  number_points=2047;
3711  primitive_info=(PrimitiveInfo *) AcquireQuantumMemory(number_points,
3712    sizeof(*primitive_info));
3713  if (primitive_info == (PrimitiveInfo *) NULL)
3714    ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
3715  GetAffineMatrix(&affine);
3716  token=AcquireString(value);
3717  extent=strlen(token)+MagickPathExtent;
3718  active=MagickFalse;
3719  n=0;
3720  status=MagickTrue;
3721  for (q=(const char *) value; *q != '\0'; )
3722  {
3723    /*
3724      Interpret graphic primitive.
3725    */
3726    GetNextToken(q,&q,MagickPathExtent,keyword);
3727    if (*keyword == '\0')
3728      break;
3729    if (*keyword == '#')
3730      {
3731        /*
3732          Comment.
3733        */
3734        if (active != MagickFalse)
3735          {
3736            AffineToTransform(image,&affine);
3737            active=MagickFalse;
3738          }
3739        (void) WriteBlobString(image,"<desc>");
3740        (void) WriteBlobString(image,keyword+1);
3741        for ( ; (*q != '\n') && (*q != '\0'); q++)
3742          switch (*q)
3743          {
3744            case '<': (void) WriteBlobString(image,"&lt;"); break;
3745            case '>': (void) WriteBlobString(image,"&gt;"); break;
3746            case '&': (void) WriteBlobString(image,"&amp;"); break;
3747            default: (void) WriteBlobByte(image,*q); break;
3748          }
3749        (void) WriteBlobString(image,"</desc>\n");
3750        continue;
3751      }
3752    primitive_type=UndefinedPrimitive;
3753    switch (*keyword)
3754    {
3755      case ';':
3756        break;
3757      case 'a':
3758      case 'A':
3759      {
3760        if (LocaleCompare("affine",keyword) == 0)
3761          {
3762            GetNextToken(q,&q,extent,token);
3763            affine.sx=StringToDouble(token,&next_token);
3764            GetNextToken(q,&q,extent,token);
3765            if (*token == ',')
3766              GetNextToken(q,&q,extent,token);
3767            affine.rx=StringToDouble(token,&next_token);
3768            GetNextToken(q,&q,extent,token);
3769            if (*token == ',')
3770              GetNextToken(q,&q,extent,token);
3771            affine.ry=StringToDouble(token,&next_token);
3772            GetNextToken(q,&q,extent,token);
3773            if (*token == ',')
3774              GetNextToken(q,&q,extent,token);
3775            affine.sy=StringToDouble(token,&next_token);
3776            GetNextToken(q,&q,extent,token);
3777            if (*token == ',')
3778              GetNextToken(q,&q,extent,token);
3779            affine.tx=StringToDouble(token,&next_token);
3780            GetNextToken(q,&q,extent,token);
3781            if (*token == ',')
3782              GetNextToken(q,&q,extent,token);
3783            affine.ty=StringToDouble(token,&next_token);
3784            break;
3785          }
3786        if (LocaleCompare("alpha",keyword) == 0)
3787          {
3788            primitive_type=AlphaPrimitive;
3789            break;
3790          }
3791        if (LocaleCompare("angle",keyword) == 0)
3792          {
3793            GetNextToken(q,&q,extent,token);
3794            affine.rx=StringToDouble(token,&next_token);
3795            affine.ry=StringToDouble(token,&next_token);
3796            break;
3797          }
3798        if (LocaleCompare("arc",keyword) == 0)
3799          {
3800            primitive_type=ArcPrimitive;
3801            break;
3802          }
3803        status=MagickFalse;
3804        break;
3805      }
3806      case 'b':
3807      case 'B':
3808      {
3809        if (LocaleCompare("bezier",keyword) == 0)
3810          {
3811            primitive_type=BezierPrimitive;
3812            break;
3813          }
3814        status=MagickFalse;
3815        break;
3816      }
3817      case 'c':
3818      case 'C':
3819      {
3820        if (LocaleCompare("clip-path",keyword) == 0)
3821          {
3822            GetNextToken(q,&q,extent,token);
3823            (void) FormatLocaleString(message,MagickPathExtent,
3824              "clip-path:url(#%s);",token);
3825            (void) WriteBlobString(image,message);
3826            break;
3827          }
3828        if (LocaleCompare("clip-rule",keyword) == 0)
3829          {
3830            GetNextToken(q,&q,extent,token);
3831            (void) FormatLocaleString(message,MagickPathExtent,
3832              "clip-rule:%s;",token);
3833            (void) WriteBlobString(image,message);
3834            break;
3835          }
3836        if (LocaleCompare("clip-units",keyword) == 0)
3837          {
3838            GetNextToken(q,&q,extent,token);
3839            (void) FormatLocaleString(message,MagickPathExtent,
3840              "clipPathUnits=%s;",token);
3841            (void) WriteBlobString(image,message);
3842            break;
3843          }
3844        if (LocaleCompare("circle",keyword) == 0)
3845          {
3846            primitive_type=CirclePrimitive;
3847            break;
3848          }
3849        if (LocaleCompare("color",keyword) == 0)
3850          {
3851            primitive_type=ColorPrimitive;
3852            break;
3853          }
3854        status=MagickFalse;
3855        break;
3856      }
3857      case 'd':
3858      case 'D':
3859      {
3860        if (LocaleCompare("decorate",keyword) == 0)
3861          {
3862            GetNextToken(q,&q,extent,token);
3863            (void) FormatLocaleString(message,MagickPathExtent,
3864              "text-decoration:%s;",token);
3865            (void) WriteBlobString(image,message);
3866            break;
3867          }
3868        status=MagickFalse;
3869        break;
3870      }
3871      case 'e':
3872      case 'E':
3873      {
3874        if (LocaleCompare("ellipse",keyword) == 0)
3875          {
3876            primitive_type=EllipsePrimitive;
3877            break;
3878          }
3879        status=MagickFalse;
3880        break;
3881      }
3882      case 'f':
3883      case 'F':
3884      {
3885        if (LocaleCompare("fill",keyword) == 0)
3886          {
3887            GetNextToken(q,&q,extent,token);
3888            (void) FormatLocaleString(message,MagickPathExtent,"fill:%s;",
3889              token);
3890            (void) WriteBlobString(image,message);
3891            break;
3892          }
3893        if (LocaleCompare("fill-rule",keyword) == 0)
3894          {
3895            GetNextToken(q,&q,extent,token);
3896            (void) FormatLocaleString(message,MagickPathExtent,
3897              "fill-rule:%s;",token);
3898            (void) WriteBlobString(image,message);
3899            break;
3900          }
3901        if (LocaleCompare("fill-alpha",keyword) == 0)
3902          {
3903            GetNextToken(q,&q,extent,token);
3904            (void) FormatLocaleString(message,MagickPathExtent,
3905              "fill-alpha:%s;",token);
3906            (void) WriteBlobString(image,message);
3907            break;
3908          }
3909        if (LocaleCompare("font-family",keyword) == 0)
3910          {
3911            GetNextToken(q,&q,extent,token);
3912            (void) FormatLocaleString(message,MagickPathExtent,
3913              "font-family:%s;",token);
3914            (void) WriteBlobString(image,message);
3915            break;
3916          }
3917        if (LocaleCompare("font-stretch",keyword) == 0)
3918          {
3919            GetNextToken(q,&q,extent,token);
3920            (void) FormatLocaleString(message,MagickPathExtent,
3921              "font-stretch:%s;",token);
3922            (void) WriteBlobString(image,message);
3923            break;
3924          }
3925        if (LocaleCompare("font-style",keyword) == 0)
3926          {
3927            GetNextToken(q,&q,extent,token);
3928            (void) FormatLocaleString(message,MagickPathExtent,
3929              "font-style:%s;",token);
3930            (void) WriteBlobString(image,message);
3931            break;
3932          }
3933        if (LocaleCompare("font-size",keyword) == 0)
3934          {
3935            GetNextToken(q,&q,extent,token);
3936            (void) FormatLocaleString(message,MagickPathExtent,
3937              "font-size:%s;",token);
3938            (void) WriteBlobString(image,message);
3939            break;
3940          }
3941        if (LocaleCompare("font-weight",keyword) == 0)
3942          {
3943            GetNextToken(q,&q,extent,token);
3944            (void) FormatLocaleString(message,MagickPathExtent,
3945              "font-weight:%s;",token);
3946            (void) WriteBlobString(image,message);
3947            break;
3948          }
3949        status=MagickFalse;
3950        break;
3951      }
3952      case 'g':
3953      case 'G':
3954      {
3955        if (LocaleCompare("gradient-units",keyword) == 0)
3956          {
3957            GetNextToken(q,&q,extent,token);
3958            break;
3959          }
3960        if (LocaleCompare("text-align",keyword) == 0)
3961          {
3962            GetNextToken(q,&q,extent,token);
3963            (void) FormatLocaleString(message,MagickPathExtent,
3964              "text-align %s ",token);
3965            (void) WriteBlobString(image,message);
3966            break;
3967          }
3968        if (LocaleCompare("text-anchor",keyword) == 0)
3969          {
3970            GetNextToken(q,&q,extent,token);
3971            (void) FormatLocaleString(message,MagickPathExtent,
3972              "text-anchor %s ",token);
3973            (void) WriteBlobString(image,message);
3974            break;
3975          }
3976        status=MagickFalse;
3977        break;
3978      }
3979      case 'i':
3980      case 'I':
3981      {
3982        if (LocaleCompare("image",keyword) == 0)
3983          {
3984            GetNextToken(q,&q,extent,token);
3985            primitive_type=ImagePrimitive;
3986            break;
3987          }
3988        status=MagickFalse;
3989        break;
3990      }
3991      case 'l':
3992      case 'L':
3993      {
3994        if (LocaleCompare("line",keyword) == 0)
3995          {
3996            primitive_type=LinePrimitive;
3997            break;
3998          }
3999        status=MagickFalse;
4000        break;
4001      }
4002      case 'o':
4003      case 'O':
4004      {
4005        if (LocaleCompare("opacity",keyword) == 0)
4006          {
4007            GetNextToken(q,&q,extent,token);
4008            (void) FormatLocaleString(message,MagickPathExtent,"opacity %s ",
4009              token);
4010            (void) WriteBlobString(image,message);
4011            break;
4012          }
4013        status=MagickFalse;
4014        break;
4015      }
4016      case 'p':
4017      case 'P':
4018      {
4019        if (LocaleCompare("path",keyword) == 0)
4020          {
4021            primitive_type=PathPrimitive;
4022            break;
4023          }
4024        if (LocaleCompare("point",keyword) == 0)
4025          {
4026            primitive_type=PointPrimitive;
4027            break;
4028          }
4029        if (LocaleCompare("polyline",keyword) == 0)
4030          {
4031            primitive_type=PolylinePrimitive;
4032            break;
4033          }
4034        if (LocaleCompare("polygon",keyword) == 0)
4035          {
4036            primitive_type=PolygonPrimitive;
4037            break;
4038          }
4039        if (LocaleCompare("pop",keyword) == 0)
4040          {
4041            GetNextToken(q,&q,extent,token);
4042            if (LocaleCompare("clip-path",token) == 0)
4043              {
4044                (void) WriteBlobString(image,"</clipPath>\n");
4045                break;
4046              }
4047            if (LocaleCompare("defs",token) == 0)
4048              {
4049                (void) WriteBlobString(image,"</defs>\n");
4050                break;
4051              }
4052            if (LocaleCompare("gradient",token) == 0)
4053              {
4054                (void) FormatLocaleString(message,MagickPathExtent,
4055                  "</%sGradient>\n",type);
4056                (void) WriteBlobString(image,message);
4057                break;
4058              }
4059            if (LocaleCompare("graphic-context",token) == 0)
4060              {
4061                n--;
4062                if (n < 0)
4063                  ThrowWriterException(DrawError,
4064                    "UnbalancedGraphicContextPushPop");
4065                (void) WriteBlobString(image,"</g>\n");
4066              }
4067            if (LocaleCompare("pattern",token) == 0)
4068              {
4069                (void) WriteBlobString(image,"</pattern>\n");
4070                break;
4071              }
4072            if (LocaleCompare("defs",token) == 0)
4073            (void) WriteBlobString(image,"</g>\n");
4074            break;
4075          }
4076        if (LocaleCompare("push",keyword) == 0)
4077          {
4078            GetNextToken(q,&q,extent,token);
4079            if (LocaleCompare("clip-path",token) == 0)
4080              {
4081                GetNextToken(q,&q,extent,token);
4082                (void) FormatLocaleString(message,MagickPathExtent,
4083                  "<clipPath id=\"%s\">\n",token);
4084                (void) WriteBlobString(image,message);
4085                break;
4086              }
4087            if (LocaleCompare("defs",token) == 0)
4088              {
4089                (void) WriteBlobString(image,"<defs>\n");
4090                break;
4091              }
4092            if (LocaleCompare("gradient",token) == 0)
4093              {
4094                GetNextToken(q,&q,extent,token);
4095                (void) CopyMagickString(name,token,MagickPathExtent);
4096                GetNextToken(q,&q,extent,token);
4097                (void) CopyMagickString(type,token,MagickPathExtent);
4098                GetNextToken(q,&q,extent,token);
4099                svg_info.segment.x1=StringToDouble(token,&next_token);
4100                svg_info.element.cx=StringToDouble(token,&next_token);
4101                GetNextToken(q,&q,extent,token);
4102                if (*token == ',')
4103                  GetNextToken(q,&q,extent,token);
4104                svg_info.segment.y1=StringToDouble(token,&next_token);
4105                svg_info.element.cy=StringToDouble(token,&next_token);
4106                GetNextToken(q,&q,extent,token);
4107                if (*token == ',')
4108                  GetNextToken(q,&q,extent,token);
4109                svg_info.segment.x2=StringToDouble(token,&next_token);
4110                svg_info.element.major=StringToDouble(token,
4111                  (char **) NULL);
4112                GetNextToken(q,&q,extent,token);
4113                if (*token == ',')
4114                  GetNextToken(q,&q,extent,token);
4115                svg_info.segment.y2=StringToDouble(token,&next_token);
4116                svg_info.element.minor=StringToDouble(token,
4117                  (char **) NULL);
4118                (void) FormatLocaleString(message,MagickPathExtent,
4119                  "<%sGradient id=\"%s\" x1=\"%g\" y1=\"%g\" x2=\"%g\" "
4120                  "y2=\"%g\">\n",type,name,svg_info.segment.x1,
4121                  svg_info.segment.y1,svg_info.segment.x2,svg_info.segment.y2);
4122                if (LocaleCompare(type,"radial") == 0)
4123                  {
4124                    GetNextToken(q,&q,extent,token);
4125                    if (*token == ',')
4126                      GetNextToken(q,&q,extent,token);
4127                    svg_info.element.angle=StringToDouble(token,
4128                      (char **) NULL);
4129                    (void) FormatLocaleString(message,MagickPathExtent,
4130                      "<%sGradient id=\"%s\" cx=\"%g\" cy=\"%g\" r=\"%g\" "
4131                      "fx=\"%g\" fy=\"%g\">\n",type,name,
4132                      svg_info.element.cx,svg_info.element.cy,
4133                      svg_info.element.angle,svg_info.element.major,
4134                      svg_info.element.minor);
4135                  }
4136                (void) WriteBlobString(image,message);
4137                break;
4138              }
4139            if (LocaleCompare("graphic-context",token) == 0)
4140              {
4141                n++;
4142                if (active)
4143                  {
4144                    AffineToTransform(image,&affine);
4145                    active=MagickFalse;
4146                  }
4147                (void) WriteBlobString(image,"<g style=\"");
4148                active=MagickTrue;
4149              }
4150            if (LocaleCompare("pattern",token) == 0)
4151              {
4152                GetNextToken(q,&q,extent,token);
4153                (void) CopyMagickString(name,token,MagickPathExtent);
4154                GetNextToken(q,&q,extent,token);
4155                svg_info.bounds.x=StringToDouble(token,&next_token);
4156                GetNextToken(q,&q,extent,token);
4157                if (*token == ',')
4158                  GetNextToken(q,&q,extent,token);
4159                svg_info.bounds.y=StringToDouble(token,&next_token);
4160                GetNextToken(q,&q,extent,token);
4161                if (*token == ',')
4162                  GetNextToken(q,&q,extent,token);
4163                svg_info.bounds.width=StringToDouble(token,
4164                  (char **) NULL);
4165                GetNextToken(q,&q,extent,token);
4166                if (*token == ',')
4167                  GetNextToken(q,&q,extent,token);
4168                svg_info.bounds.height=StringToDouble(token,(char **) NULL);
4169                (void) FormatLocaleString(message,MagickPathExtent,
4170                  "<pattern id=\"%s\" x=\"%g\" y=\"%g\" width=\"%g\" "
4171                  "height=\"%g\">\n",name,svg_info.bounds.x,svg_info.bounds.y,
4172                  svg_info.bounds.width,svg_info.bounds.height);
4173                (void) WriteBlobString(image,message);
4174                break;
4175              }
4176            break;
4177          }
4178        status=MagickFalse;
4179        break;
4180      }
4181      case 'r':
4182      case 'R':
4183      {
4184        if (LocaleCompare("rectangle",keyword) == 0)
4185          {
4186            primitive_type=RectanglePrimitive;
4187            break;
4188          }
4189        if (LocaleCompare("roundRectangle",keyword) == 0)
4190          {
4191            primitive_type=RoundRectanglePrimitive;
4192            break;
4193          }
4194        if (LocaleCompare("rotate",keyword) == 0)
4195          {
4196            GetNextToken(q,&q,extent,token);
4197            (void) FormatLocaleString(message,MagickPathExtent,"rotate(%s) ",
4198              token);
4199            (void) WriteBlobString(image,message);
4200            break;
4201          }
4202        status=MagickFalse;
4203        break;
4204      }
4205      case 's':
4206      case 'S':
4207      {
4208        if (LocaleCompare("scale",keyword) == 0)
4209          {
4210            GetNextToken(q,&q,extent,token);
4211            affine.sx=StringToDouble(token,&next_token);
4212            GetNextToken(q,&q,extent,token);
4213            if (*token == ',')
4214              GetNextToken(q,&q,extent,token);
4215            affine.sy=StringToDouble(token,&next_token);
4216            break;
4217          }
4218        if (LocaleCompare("skewX",keyword) == 0)
4219          {
4220            GetNextToken(q,&q,extent,token);
4221            (void) FormatLocaleString(message,MagickPathExtent,"skewX(%s) ",
4222              token);
4223            (void) WriteBlobString(image,message);
4224            break;
4225          }
4226        if (LocaleCompare("skewY",keyword) == 0)
4227          {
4228            GetNextToken(q,&q,extent,token);
4229            (void) FormatLocaleString(message,MagickPathExtent,"skewY(%s) ",
4230              token);
4231            (void) WriteBlobString(image,message);
4232            break;
4233          }
4234        if (LocaleCompare("stop-color",keyword) == 0)
4235          {
4236            char
4237              color[MagickPathExtent];
4238
4239            GetNextToken(q,&q,extent,token);
4240            (void) CopyMagickString(color,token,MagickPathExtent);
4241            GetNextToken(q,&q,extent,token);
4242            (void) FormatLocaleString(message,MagickPathExtent,
4243              "  <stop offset=\"%s\" stop-color=\"%s\" />\n",token,color);
4244            (void) WriteBlobString(image,message);
4245            break;
4246          }
4247        if (LocaleCompare("stroke",keyword) == 0)
4248          {
4249            GetNextToken(q,&q,extent,token);
4250            (void) FormatLocaleString(message,MagickPathExtent,"stroke:%s;",
4251              token);
4252            (void) WriteBlobString(image,message);
4253            break;
4254          }
4255        if (LocaleCompare("stroke-antialias",keyword) == 0)
4256          {
4257            GetNextToken(q,&q,extent,token);
4258            (void) FormatLocaleString(message,MagickPathExtent,
4259              "stroke-antialias:%s;",token);
4260            (void) WriteBlobString(image,message);
4261            break;
4262          }
4263        if (LocaleCompare("stroke-dasharray",keyword) == 0)
4264          {
4265            if (IsPoint(q))
4266              {
4267                ssize_t
4268                  k;
4269
4270                p=q;
4271                GetNextToken(p,&p,extent,token);
4272                for (k=0; IsPoint(token); k++)
4273                  GetNextToken(p,&p,extent,token);
4274                (void) WriteBlobString(image,"stroke-dasharray:");
4275                for (j=0; j < k; j++)
4276                {
4277                  GetNextToken(q,&q,extent,token);
4278                  (void) FormatLocaleString(message,MagickPathExtent,"%s ",
4279                    token);
4280                  (void) WriteBlobString(image,message);
4281                }
4282                (void) WriteBlobString(image,";");
4283                break;
4284              }
4285            GetNextToken(q,&q,extent,token);
4286            (void) FormatLocaleString(message,MagickPathExtent,
4287              "stroke-dasharray:%s;",token);
4288            (void) WriteBlobString(image,message);
4289            break;
4290          }
4291        if (LocaleCompare("stroke-dashoffset",keyword) == 0)
4292          {
4293            GetNextToken(q,&q,extent,token);
4294            (void) FormatLocaleString(message,MagickPathExtent,
4295              "stroke-dashoffset:%s;",token);
4296            (void) WriteBlobString(image,message);
4297            break;
4298          }
4299        if (LocaleCompare("stroke-linecap",keyword) == 0)
4300          {
4301            GetNextToken(q,&q,extent,token);
4302            (void) FormatLocaleString(message,MagickPathExtent,
4303              "stroke-linecap:%s;",token);
4304            (void) WriteBlobString(image,message);
4305            break;
4306          }
4307        if (LocaleCompare("stroke-linejoin",keyword) == 0)
4308          {
4309            GetNextToken(q,&q,extent,token);
4310            (void) FormatLocaleString(message,MagickPathExtent,
4311              "stroke-linejoin:%s;",token);
4312            (void) WriteBlobString(image,message);
4313            break;
4314          }
4315        if (LocaleCompare("stroke-miterlimit",keyword) == 0)
4316          {
4317            GetNextToken(q,&q,extent,token);
4318            (void) FormatLocaleString(message,MagickPathExtent,
4319              "stroke-miterlimit:%s;",token);
4320            (void) WriteBlobString(image,message);
4321            break;
4322          }
4323        if (LocaleCompare("stroke-opacity",keyword) == 0)
4324          {
4325            GetNextToken(q,&q,extent,token);
4326            (void) FormatLocaleString(message,MagickPathExtent,
4327              "stroke-opacity:%s;",token);
4328            (void) WriteBlobString(image,message);
4329            break;
4330          }
4331        if (LocaleCompare("stroke-width",keyword) == 0)
4332          {
4333            GetNextToken(q,&q,extent,token);
4334            (void) FormatLocaleString(message,MagickPathExtent,
4335              "stroke-width:%s;",token);
4336            (void) WriteBlobString(image,message);
4337            continue;
4338          }
4339        status=MagickFalse;
4340        break;
4341      }
4342      case 't':
4343      case 'T':
4344      {
4345        if (LocaleCompare("text",keyword) == 0)
4346          {
4347            primitive_type=TextPrimitive;
4348            break;
4349          }
4350        if (LocaleCompare("text-antialias",keyword) == 0)
4351          {
4352            GetNextToken(q,&q,extent,token);
4353            (void) FormatLocaleString(message,MagickPathExtent,
4354              "text-antialias:%s;",token);
4355            (void) WriteBlobString(image,message);
4356            break;
4357          }
4358        if (LocaleCompare("tspan",keyword) == 0)
4359          {
4360            primitive_type=TextPrimitive;
4361            break;
4362          }
4363        if (LocaleCompare("translate",keyword) == 0)
4364          {
4365            GetNextToken(q,&q,extent,token);
4366            affine.tx=StringToDouble(token,&next_token);
4367            GetNextToken(q,&q,extent,token);
4368            if (*token == ',')
4369              GetNextToken(q,&q,extent,token);
4370            affine.ty=StringToDouble(token,&next_token);
4371            break;
4372          }
4373        status=MagickFalse;
4374        break;
4375      }
4376      case 'v':
4377      case 'V':
4378      {
4379        if (LocaleCompare("viewbox",keyword) == 0)
4380          {
4381            GetNextToken(q,&q,extent,token);
4382            if (*token == ',')
4383              GetNextToken(q,&q,extent,token);
4384            GetNextToken(q,&q,extent,token);
4385            if (*token == ',')
4386              GetNextToken(q,&q,extent,token);
4387            GetNextToken(q,&q,extent,token);
4388            if (*token == ',')
4389              GetNextToken(q,&q,extent,token);
4390            GetNextToken(q,&q,extent,token);
4391            break;
4392          }
4393        status=MagickFalse;
4394        break;
4395      }
4396      default:
4397      {
4398        status=MagickFalse;
4399        break;
4400      }
4401    }
4402    if (status == MagickFalse)
4403      break;
4404    if (primitive_type == UndefinedPrimitive)
4405      continue;
4406    /*
4407      Parse the primitive attributes.
4408    */
4409    i=0;
4410    j=0;
4411    for (x=0; *q != '\0'; x++)
4412    {
4413      /*
4414        Define points.
4415      */
4416      if (IsPoint(q) == MagickFalse)
4417        break;
4418      GetNextToken(q,&q,extent,token);
4419      point.x=StringToDouble(token,&next_token);
4420      GetNextToken(q,&q,extent,token);
4421      if (*token == ',')
4422        GetNextToken(q,&q,extent,token);
4423      point.y=StringToDouble(token,&next_token);
4424      GetNextToken(q,(const char **) NULL,extent,token);
4425      if (*token == ',')
4426        GetNextToken(q,&q,extent,token);
4427      primitive_info[i].primitive=primitive_type;
4428      primitive_info[i].point=point;
4429      primitive_info[i].coordinates=0;
4430      primitive_info[i].method=FloodfillMethod;
4431      i++;
4432      if (i < (ssize_t) (number_points-6*BezierQuantum-360))
4433        continue;
4434      number_points+=6*BezierQuantum+360;
4435      primitive_info=(PrimitiveInfo *) ResizeQuantumMemory(primitive_info,
4436        number_points,sizeof(*primitive_info));
4437      if (primitive_info == (PrimitiveInfo *) NULL)
4438        {
4439          (void) ThrowMagickException(exception,GetMagickModule(),
4440            ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
4441          break;
4442        }
4443    }
4444    primitive_info[j].primitive=primitive_type;
4445    primitive_info[j].coordinates=x;
4446    primitive_info[j].method=FloodfillMethod;
4447    primitive_info[j].text=(char *) NULL;
4448    if (active)
4449      {
4450        AffineToTransform(image,&affine);
4451        active=MagickFalse;
4452      }
4453    active=MagickFalse;
4454    switch (primitive_type)
4455    {
4456      case PointPrimitive:
4457      default:
4458      {
4459        if (primitive_info[j].coordinates != 1)
4460          {
4461            status=MagickFalse;
4462            break;
4463          }
4464        break;
4465      }
4466      case LinePrimitive:
4467      {
4468        if (primitive_info[j].coordinates != 2)
4469          {
4470            status=MagickFalse;
4471            break;
4472          }
4473          (void) FormatLocaleString(message,MagickPathExtent,
4474          "  <line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"/>\n",
4475          primitive_info[j].point.x,primitive_info[j].point.y,
4476          primitive_info[j+1].point.x,primitive_info[j+1].point.y);
4477        (void) WriteBlobString(image,message);
4478        break;
4479      }
4480      case RectanglePrimitive:
4481      {
4482        if (primitive_info[j].coordinates != 2)
4483          {
4484            status=MagickFalse;
4485            break;
4486          }
4487          (void) FormatLocaleString(message,MagickPathExtent,
4488          "  <rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"/>\n",
4489          primitive_info[j].point.x,primitive_info[j].point.y,
4490          primitive_info[j+1].point.x-primitive_info[j].point.x,
4491          primitive_info[j+1].point.y-primitive_info[j].point.y);
4492        (void) WriteBlobString(image,message);
4493        break;
4494      }
4495      case RoundRectanglePrimitive:
4496      {
4497        if (primitive_info[j].coordinates != 3)
4498          {
4499            status=MagickFalse;
4500            break;
4501          }
4502        (void) FormatLocaleString(message,MagickPathExtent,
4503          "  <rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" rx=\"%g\" "
4504          "ry=\"%g\"/>\n",primitive_info[j].point.x,
4505          primitive_info[j].point.y,primitive_info[j+1].point.x-
4506          primitive_info[j].point.x,primitive_info[j+1].point.y-
4507          primitive_info[j].point.y,primitive_info[j+2].point.x,
4508          primitive_info[j+2].point.y);
4509        (void) WriteBlobString(image,message);
4510        break;
4511      }
4512      case ArcPrimitive:
4513      {
4514        if (primitive_info[j].coordinates != 3)
4515          {
4516            status=MagickFalse;
4517            break;
4518          }
4519        break;
4520      }
4521      case EllipsePrimitive:
4522      {
4523        if (primitive_info[j].coordinates != 3)
4524          {
4525            status=MagickFalse;
4526            break;
4527          }
4528          (void) FormatLocaleString(message,MagickPathExtent,
4529          "  <ellipse cx=\"%g\" cy=\"%g\" rx=\"%g\" ry=\"%g\"/>\n",
4530          primitive_info[j].point.x,primitive_info[j].point.y,
4531          primitive_info[j+1].point.x,primitive_info[j+1].point.y);
4532        (void) WriteBlobString(image,message);
4533        break;
4534      }
4535      case CirclePrimitive:
4536      {
4537        double
4538          alpha,
4539          beta;
4540
4541        if (primitive_info[j].coordinates != 2)
4542          {
4543            status=MagickFalse;
4544            break;
4545          }
4546        alpha=primitive_info[j+1].point.x-primitive_info[j].point.x;
4547        beta=primitive_info[j+1].point.y-primitive_info[j].point.y;
4548        (void) FormatLocaleString(message,MagickPathExtent,
4549          "  <circle cx=\"%g\" cy=\"%g\" r=\"%g\"/>\n",
4550          primitive_info[j].point.x,primitive_info[j].point.y,
4551          hypot(alpha,beta));
4552        (void) WriteBlobString(image,message);
4553        break;
4554      }
4555      case PolylinePrimitive:
4556      {
4557        if (primitive_info[j].coordinates < 2)
4558          {
4559            status=MagickFalse;
4560            break;
4561          }
4562        (void) CopyMagickString(message,"  <polyline points=\"",
4563           MagickPathExtent);
4564        (void) WriteBlobString(image,message);
4565        length=strlen(message);
4566        for ( ; j < i; j++)
4567        {
4568          (void) FormatLocaleString(message,MagickPathExtent,"%g,%g ",
4569            primitive_info[j].point.x,primitive_info[j].point.y);
4570          length+=strlen(message);
4571          if (length >= 80)
4572            {
4573              (void) WriteBlobString(image,"\n    ");
4574              length=strlen(message)+5;
4575            }
4576          (void) WriteBlobString(image,message);
4577        }
4578        (void) WriteBlobString(image,"\"/>\n");
4579        break;
4580      }
4581      case PolygonPrimitive:
4582      {
4583        if (primitive_info[j].coordinates < 3)
4584          {
4585            status=MagickFalse;
4586            break;
4587          }
4588        primitive_info[i]=primitive_info[j];
4589        primitive_info[i].coordinates=0;
4590        primitive_info[j].coordinates++;
4591        i++;
4592        (void) CopyMagickString(message,"  <polygon points=\"",MagickPathExtent);
4593        (void) WriteBlobString(image,message);
4594        length=strlen(message);
4595        for ( ; j < i; j++)
4596        {
4597          (void) FormatLocaleString(message,MagickPathExtent,"%g,%g ",
4598            primitive_info[j].point.x,primitive_info[j].point.y);
4599          length+=strlen(message);
4600          if (length >= 80)
4601            {
4602              (void) WriteBlobString(image,"\n    ");
4603              length=strlen(message)+5;
4604            }
4605          (void) WriteBlobString(image,message);
4606        }
4607        (void) WriteBlobString(image,"\"/>\n");
4608        break;
4609      }
4610      case BezierPrimitive:
4611      {
4612        if (primitive_info[j].coordinates < 3)
4613          {
4614            status=MagickFalse;
4615            break;
4616          }
4617        break;
4618      }
4619      case PathPrimitive:
4620      {
4621        int
4622          number_attributes;
4623
4624        GetNextToken(q,&q,extent,token);
4625        number_attributes=1;
4626        for (p=token; *p != '\0'; p++)
4627          if (isalpha((int) *p))
4628            number_attributes++;
4629        if (i > (ssize_t) (number_points-6*BezierQuantum*number_attributes-1))
4630          {
4631            number_points+=6*BezierQuantum*number_attributes;
4632            primitive_info=(PrimitiveInfo *) ResizeQuantumMemory(primitive_info,
4633              number_points,sizeof(*primitive_info));
4634            if (primitive_info == (PrimitiveInfo *) NULL)
4635              {
4636                (void) ThrowMagickException(exception,GetMagickModule(),
4637                  ResourceLimitError,"MemoryAllocationFailed","`%s'",
4638                  image->filename);
4639                break;
4640              }
4641          }
4642        (void) WriteBlobString(image,"  <path d=\"");
4643        (void) WriteBlobString(image,token);
4644        (void) WriteBlobString(image,"\"/>\n");
4645        break;
4646      }
4647      case AlphaPrimitive:
4648      case ColorPrimitive:
4649      {
4650        if (primitive_info[j].coordinates != 1)
4651          {
4652            status=MagickFalse;
4653            break;
4654          }
4655        GetNextToken(q,&q,extent,token);
4656        if (LocaleCompare("point",token) == 0)
4657          primitive_info[j].method=PointMethod;
4658        if (LocaleCompare("replace",token) == 0)
4659          primitive_info[j].method=ReplaceMethod;
4660        if (LocaleCompare("floodfill",token) == 0)
4661          primitive_info[j].method=FloodfillMethod;
4662        if (LocaleCompare("filltoborder",token) == 0)
4663          primitive_info[j].method=FillToBorderMethod;
4664        if (LocaleCompare("reset",token) == 0)
4665          primitive_info[j].method=ResetMethod;
4666        break;
4667      }
4668      case TextPrimitive:
4669      {
4670        register char
4671          *p;
4672
4673        if (primitive_info[j].coordinates != 1)
4674          {
4675            status=MagickFalse;
4676            break;
4677          }
4678        GetNextToken(q,&q,extent,token);
4679        (void) FormatLocaleString(message,MagickPathExtent,
4680          "  <text x=\"%g\" y=\"%g\">",primitive_info[j].point.x,
4681          primitive_info[j].point.y);
4682        (void) WriteBlobString(image,message);
4683        for (p=token; *p != '\0'; p++)
4684          switch (*p)
4685          {
4686            case '<': (void) WriteBlobString(image,"&lt;"); break;
4687            case '>': (void) WriteBlobString(image,"&gt;"); break;
4688            case '&': (void) WriteBlobString(image,"&amp;"); break;
4689            default: (void) WriteBlobByte(image,*p); break;
4690          }
4691        (void) WriteBlobString(image,"</text>\n");
4692        break;
4693      }
4694      case ImagePrimitive:
4695      {
4696        if (primitive_info[j].coordinates != 2)
4697          {
4698            status=MagickFalse;
4699            break;
4700          }
4701        GetNextToken(q,&q,extent,token);
4702        (void) FormatLocaleString(message,MagickPathExtent,
4703          "  <image x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" "
4704          "xlink:href=\"%s\"/>\n",primitive_info[j].point.x,
4705          primitive_info[j].point.y,primitive_info[j+1].point.x,
4706          primitive_info[j+1].point.y,token);
4707        (void) WriteBlobString(image,message);
4708        break;
4709      }
4710    }
4711    if (primitive_info == (PrimitiveInfo *) NULL)
4712      break;
4713    primitive_info[i].primitive=UndefinedPrimitive;
4714    if (status == MagickFalse)
4715      break;
4716  }
4717  (void) WriteBlobString(image,"</svg>\n");
4718  /*
4719    Relinquish resources.
4720  */
4721  token=DestroyString(token);
4722  if (primitive_info != (PrimitiveInfo *) NULL)
4723    primitive_info=(PrimitiveInfo *) RelinquishMagickMemory(primitive_info);
4724  (void) CloseBlob(image);
4725  return(status);
4726}
4727