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