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