1 /*
2 % Copyright (C) 2003-2020 GraphicsMagick Group
3 % Copyright (C) 2002 ImageMagick Studio
4 %
5 % This program is covered by multiple licenses, which are described in
6 % Copyright.txt. You should have received a copy of Copyright.txt with this
7 % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
8 %
9 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
10 %                                                                             %
11 %                                                                             %
12 %                                                                             %
13 %                            SSSSS  V   V   GGGG                              %
14 %                            SS     V   V  G                                  %
15 %                             SSS   V   V  G GG                               %
16 %                               SS   V V   G   G                              %
17 %                            SSSSS    V     GGG                               %
18 %                                                                             %
19 %                                                                             %
20 %                 Read/Write Scalable Vector Graphics Format.                 %
21 %                                                                             %
22 %                                                                             %
23 %                              Software Design                                %
24 %                                John Cristy                                  %
25 %                             William Radcliffe                               %
26 %                                March 2000                                   %
27 %                                                                             %
28 %                                                                             %
29 %                                                                             %
30 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
31 %
32 %
33 */
34 
35 /*
36   Include declarations.
37 */
38 #include "magick/studio.h"
39 #include "magick/attribute.h"
40 #include "magick/blob.h"
41 #include "magick/color.h"
42 #include "magick/constitute.h"
43 #include "magick/gem.h"
44 #include "magick/log.h"
45 #include "magick/magick.h"
46 #include "magick/render.h"
47 #include "magick/tempfile.h"
48 #include "magick/utility.h"
49 #if defined(HasXML)
50 #  if defined(MSWINDOWS)
51 #    if defined(__MINGW32__)
52 #      define _MSC_VER
53 #    else
54 #      include <win32config.h>
55 #    endif
56 #  endif
57 #  include <libxml/parser.h>
58 #  include <libxml/xmlmemory.h>
59 #  include <libxml/parserInternals.h>
60 #  include <libxml/xmlerror.h>
61 #endif
62 
63 /*
64   Disable SVG writer by default since it rarely works correctly.
65 */
66 #if !defined(ENABLE_SVG_WRITER)
67 #  define ENABLE_SVG_WRITER 0
68 #endif /* if !defined(ENABLE_SVG_WRITER) */
69 
70 /*
71   Avoid shadowing library globals and functions.
72 */
73 #define attribute attribute_magick
74 
75 #if defined(HasAUTOTRACE)
76 #include "types.h"
77 #include "image-header.h"
78 #include "fit.h"
79 #include "output.h"
80 #include "pxl-outline.h"
81 #include "atquantize.h"
82 #include "thin-image.h"
83 
84 char
85   *version_string = "AutoTrace version 0.24a";
86 #endif
87 
88 /*
89   Define declarations.
90 */
91 #define MVGPrintf  (void) fprintf
92 
93 /*
94   Typedef declarations.
95 */
96 typedef struct _BoundingBox
97 {
98   double
99     x,
100     y,
101     width,
102     height;
103 } BoundingBox;
104 
105 typedef struct _SVGInfo
106 {
107   FILE
108     *file;
109 
110   ExceptionInfo
111     *exception;
112 
113   Image
114     *image;
115 
116   const ImageInfo
117     *image_info;
118 
119   AffineMatrix
120     affine;
121 
122   unsigned long
123     width,
124     height;
125 
126   char
127     *size,
128     *title,
129     *comment;
130 
131   int
132     n;
133 
134   double
135     *scale,
136     pointsize;
137 
138   ElementInfo
139     element;
140 
141   SegmentInfo
142     segment;
143 
144   BoundingBox
145     bounds,
146     view_box;
147 
148   PointInfo
149     radius;
150 
151   char
152     *stop_color,
153     *offset,
154     *text,
155     *vertices,
156     *url;
157 
158   /*
159     Even though it's unlikely to happen, keep track of nested <defs> and
160     elements tagged with an id.
161   */
162   int
163     defsPushCount,      /* for tracking nested <defs> */
164     idLevelInsideDefs,  /* when an "id" is seen, remember svg->n (SVG element level) */
165     svgPushCount;       /* for tracking nested <svg> elements */
166 
167 #if defined(HasXML)
168   xmlParserCtxtPtr
169     parser;
170 
171   xmlDocPtr
172     document;
173 #endif
174 } SVGInfo;
175 
176 /*
177   Forward declarations.
178 */
179 #if ENABLE_SVG_WRITER
180 static unsigned int
181   WriteSVGImage(const ImageInfo *,Image *);
182 #endif /* if ENABLE_SVG_WRITER */
183 
184 #if defined(HasXML)
185 /*
186 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
187 %                                                                             %
188 %                                                                             %
189 %                                                                             %
190 %   R e a d S V G I m a g e                                                   %
191 %                                                                             %
192 %                                                                             %
193 %                                                                             %
194 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
195 %
196 %  Method ReadSVGImage reads a Scalable Vector Gaphics file and returns it.  It
197 %  allocates the memory necessary for the new Image structure and returns a
198 %  pointer to the new image.
199 %
200 %  The format of the ReadSVGImage method is:
201 %
202 %      Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
203 %
204 %  A description of each parameter follows:
205 %
206 %    o image:  Method ReadSVGImage returns a pointer to the image after
207 %      reading. A null image is returned if there is a memory shortage or if
208 %      the image cannot be read.
209 %
210 %    o image_info: Specifies a pointer to a ImageInfo structure.
211 %
212 %    o exception: return any errors or warnings in this structure.
213 %
214 %
215 */
216 
217 static double
GetUserSpaceCoordinateValue(const SVGInfo * svg_info,int type,const char * string,MagickBool positive)218 GetUserSpaceCoordinateValue(const SVGInfo *svg_info,
219                             int type,
220                             const char *string,
221                             MagickBool positive)
222 {
223   char
224     *p,
225     token[MaxTextExtent];
226 
227   double
228     value;
229 
230   assert(string != (const char *) NULL);
231   p=(char *) string;
232   (void) MagickGetToken(p,&p,token,MaxTextExtent);
233   if ((MagickAtoFChk(token,&value) == MagickFail) ||
234       (((positive) && value < 0.0)))
235     {
236       errno=0;
237       ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,
238                      string);
239     }
240   if (strchr(token,'%') != (char *) NULL)
241     {
242       double
243         alpha,
244         beta;
245 
246       if (type > 0)
247         return(svg_info->view_box.width*value/100.0);
248       if (type < 0)
249         return(svg_info->view_box.height*value/100.0);
250       alpha=value-svg_info->view_box.width;
251       beta=value-svg_info->view_box.height;
252       return(sqrt(alpha*alpha+beta*beta)/sqrt(2.0)/100.0);
253     }
254   (void) MagickGetToken(p,&p,token,MaxTextExtent);
255   if (LocaleNCompare(token,"cm",2) == 0)
256     return(72.0*svg_info->scale[0]/2.54*value);
257   if (LocaleNCompare(token,"em",2) == 0)
258     return(svg_info->pointsize*value);
259   if (LocaleNCompare(token,"ex",2) == 0)
260     return(svg_info->pointsize*value/2.0);
261   if (LocaleNCompare(token,"in",2) == 0)
262     return(72.0*svg_info->scale[0]*value);
263   if (LocaleNCompare(token,"mm",2) == 0)
264     return(72.0*svg_info->scale[0]/25.4*value);
265   if (LocaleNCompare(token,"pc",2) == 0)
266     return(72.0*svg_info->scale[0]/6.0*value);
267   if (LocaleNCompare(token,"pt",2) == 0)
268     return(svg_info->scale[0]*value);
269   if (LocaleNCompare(token,"px",2) == 0)
270     return(value);
271   return(value);
272 }
273 
GetStyleTokens(void * context,const char * text,size_t * number_tokens)274 static char **GetStyleTokens(void *context,const char *text,size_t *number_tokens)
275 {
276   char
277     **tokens;
278 
279   const char
280     *p,
281     *q;
282 
283   size_t
284     alloc_tokens,
285     i,
286     iListFront;
287 
288   SVGInfo
289     *svg_info;
290 
291   MagickBool
292     IsFontSize = MagickFalse;
293 
294   svg_info=(SVGInfo *) context;
295   *number_tokens=0;
296   alloc_tokens=0;
297   if (text == (const char *) NULL)
298     return((char **) NULL);
299   /*
300     Determine the number of arguments.
301 
302     style="fill: red; stroke: blue; stroke-width: 3"
303   */
304   for (p=text; *p != '\0'; p++)
305     if (*p == ':')
306       alloc_tokens+=2;
307   if (alloc_tokens == 0)
308     return((char **) NULL);
309   tokens=MagickAllocateMemory(char **,(alloc_tokens+2)*sizeof(*tokens));
310   if (tokens == (char **) NULL)
311     {
312       ThrowException3(svg_info->exception,ResourceLimitError,
313                       MemoryAllocationFailed,UnableToConvertStringToTokens);
314       return((char **) NULL);
315     }
316   (void) memset(tokens,0,(alloc_tokens+2)*sizeof(*tokens));
317   /*
318     Convert string to an ASCII list.
319   */
320   i=0;
321   p=text;
322   iListFront = 0;
323   for (q=p; *q != '\0'; q++)
324     {
325       /*
326         ':' terminates the style element (e.g., fill:)
327         ';' terminates the style element value (e.g., red)
328       */
329       if ((*q != ':') && (*q != ';') && (*q != '\0'))
330         continue;
331       tokens[i]=AllocateString(p);
332       if (tokens[i] == NULL)
333         {
334           ThrowException3(svg_info->exception,ResourceLimitError,
335                           MemoryAllocationFailed,UnableToConvertStringToTokens);
336           break;
337         }
338       (void) strlcpy(tokens[i],p,q-p+1);
339       Strip(tokens[i]);
340       /*
341         Check for "font-size", which we will move to the first position in
342         the list.  This will ensure that any following numerical conversions
343         that depend on the font size will use the new value.
344       */
345       if  ( (i & 1) == 0 )  /*element name*/
346         IsFontSize = (LocaleCompare("font-size",tokens[i]) == 0) ? MagickTrue : MagickFalse;
347       else if  ( IsFontSize )
348         {/*found font-size/value pair*/
349           if  ( (i-1) == iListFront )
350             iListFront += 2;  /* already at front of list */
351           else
352             {
353               /* move "font-size" and value to top of list */
354               char * pToken = tokens[iListFront];
355               tokens[iListFront] = tokens[i-1];
356               tokens[i-1] = pToken;
357               iListFront++;
358               pToken = tokens[iListFront];
359               tokens[iListFront] = tokens[i];
360               tokens[i] = pToken;
361               iListFront++;
362             }
363         }/*found font-size/value pair*/
364       i++;
365       if (i >= alloc_tokens)
366         break;
367       p=q+1;
368     }
369   if (i < alloc_tokens)
370     {
371       tokens[i]=AllocateString(p);
372       if (tokens[i] == NULL)
373         {
374           ThrowException3(svg_info->exception,ResourceLimitError,
375                           MemoryAllocationFailed,UnableToConvertStringToTokens);
376         }
377       else
378         {
379           (void) strlcpy(tokens[i],p,q-p+1);
380           Strip(tokens[i]);
381           i++;
382         }
383     }
384   tokens[i]=(char *) NULL;
385   *number_tokens=i;
386   return(tokens);
387 }
388 
GetTransformTokens(void * context,const char * text,size_t * number_tokens)389 static char **GetTransformTokens(void *context,const char *text,
390                                  size_t *number_tokens)
391 {
392   char
393     **tokens;
394 
395   register const char
396     *p,
397     *q;
398 
399   register size_t
400     i;
401 
402   SVGInfo
403     *svg_info;
404 
405   size_t
406     alloc_tokens;
407 
408   svg_info=(SVGInfo *) context;
409   *number_tokens=0;
410   if (text == (const char *) NULL)
411     return((char **) NULL);
412 
413   alloc_tokens=8;
414   tokens=MagickAllocateMemory(char **,(alloc_tokens+2)*sizeof(*tokens));
415   if (tokens == (char **) NULL)
416     {
417       ThrowException3(svg_info->exception,ResourceLimitError,
418                       MemoryAllocationFailed,UnableToConvertStringToTokens);
419       return((char **) NULL);
420     }
421   /*
422     Convert string to an ASCII list.
423   */
424   i=0;
425   p=text;
426   for (q=p; *q != '\0'; q++)
427     {
428       if ((*q != '(') && (*q != ')') && (*q != '\0'))
429         continue;
430       if (i == alloc_tokens)
431         {
432           alloc_tokens <<= 1;
433           MagickReallocMemory(char **,tokens,(alloc_tokens+2)*sizeof(*tokens));
434           if (tokens == (char **) NULL)
435             {
436               ThrowException3(svg_info->exception,ResourceLimitError,
437                               MemoryAllocationFailed,UnableToConvertStringToTokens);
438               return((char **) NULL);
439             }
440         }
441       tokens[i]=AllocateString(p);
442       (void) strlcpy(tokens[i],p,q-p+1);
443       Strip(tokens[i]);
444       i++;
445       p=q+1;
446     }
447   tokens[i]=AllocateString(p);
448   (void) strlcpy(tokens[i],p,q-p+1);
449   Strip(tokens[i++]);
450   tokens[i]=(char *) NULL;
451   *number_tokens=i;
452   return(tokens);
453 }
454 
455 #if defined(__cplusplus) || defined(c_plusplus)
456 extern "C" {
457 #endif
458 
459   static int SVGIsStandalone(void *context);
460 
461   static int SVGIsStandalone(void *context);
462 
463   static int SVGHasInternalSubset(void *context);
464 
465   static int SVGHasExternalSubset(void *context);
466 
467   static void SVGInternalSubset(void *context,const xmlChar *name,
468                                 const xmlChar *external_id,
469                                 const xmlChar *system_id);
470 
471   static xmlParserInputPtr SVGResolveEntity(void *context,
472                                             const xmlChar *public_id,
473                                             const xmlChar *system_id);
474 
475   static xmlEntityPtr SVGGetEntity(void *context,const xmlChar *name);
476 
477   static xmlEntityPtr SVGGetParameterEntity(void *context,const xmlChar *name);
478 
479   static void SVGEntityDeclaration(void *context,const xmlChar *name,int type,
480                                    const xmlChar *public_id,
481                                    const xmlChar *system_id,xmlChar *content);
482 
483   static void SVGAttributeDeclaration(void *context,const xmlChar *element,
484                                       const xmlChar *name,int type,int value,
485                                       const xmlChar *default_value,
486                                       xmlEnumerationPtr tree);
487 
488   static void SVGElementDeclaration(void *context,const xmlChar *name,int type,
489                                     xmlElementContentPtr content);
490 
491   static void SVGNotationDeclaration(void *context,const xmlChar *name,
492                                      const xmlChar *public_id,
493                                      const xmlChar *system_id);
494 
495   static void SVGUnparsedEntityDeclaration(void *context,const xmlChar *name,
496                                            const xmlChar *public_id,
497                                            const xmlChar *system_id,
498                                            const xmlChar *notation);
499 
500   static void SVGSetDocumentLocator(void *context,
501                                     xmlSAXLocatorPtr location);
502 
503   static void SVGStartDocument(void *context);
504 
505   static void SVGEndDocument(void *context);
506 
507   static void SVGStartElement(void *context,const xmlChar *name,
508                               const xmlChar **attributes);
509 
510   static void SVGEndElement(void *context,const xmlChar *name);
511 
512   static void SVGCharacters(void *context,const xmlChar *c,int length);
513 
514   static void SVGReference(void *context,const xmlChar *name);
515 
516   static void SVGIgnorableWhitespace(void *context,const xmlChar *c,int length);
517 
518   static void SVGProcessingInstructions(void *context,const xmlChar *target,
519                                         const xmlChar *data);
520 
521   static void SVGComment(void *context,const xmlChar *value);
522 
523   static void SVGWarning(void *context,const char *format,...);
524 
525   static void SVGError(void *context,const char *format,...);
526 
527   static void SVGCDataBlock(void *context,const xmlChar *value,int length);
528 
529   static void SVGExternalSubset(void *context,const xmlChar *name,
530                                 const xmlChar *external_id,
531                                 const xmlChar *system_id);
532 
533   ModuleExport void RegisterSVGImage(void);
534 
535   ModuleExport void UnregisterSVGImage(void);
536 
537 #if defined(__cplusplus) || defined(c_plusplus)
538 }
539 #endif
540 
541 
542 static int
SVGIsStandalone(void * context)543 SVGIsStandalone(void *context)
544 {
545   SVGInfo
546     *svg_info;
547 
548   /*
549     Is this document tagged standalone?
550   */
551   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.SVGIsStandalone()");
552   svg_info=(SVGInfo *) context;
553   return(svg_info->document->standalone == 1);
554 }
555 
556 static int
SVGHasInternalSubset(void * context)557 SVGHasInternalSubset(void *context)
558 {
559   SVGInfo
560     *svg_info;
561 
562   /*
563     Does this document has an internal subset?
564   */
565   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
566                         "  SAX.SVGHasInternalSubset()");
567   svg_info=(SVGInfo *) context;
568   return(svg_info->document->intSubset != NULL);
569 }
570 
571 static int
SVGHasExternalSubset(void * context)572 SVGHasExternalSubset(void *context)
573 {
574   SVGInfo
575     *svg_info;
576 
577   /*
578     Does this document has an external subset?
579   */
580   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
581                         "  SAX.SVGHasExternalSubset()");
582   svg_info=(SVGInfo *) context;
583   return(svg_info->document->extSubset != NULL);
584 }
585 
586 static void
SVGInternalSubset(void * context,const xmlChar * name,const xmlChar * external_id,const xmlChar * system_id)587 SVGInternalSubset(void *context,const xmlChar *name,
588                   const xmlChar *external_id,const xmlChar *system_id)
589 {
590   SVGInfo
591     *svg_info;
592 
593   /*
594     Does this document has an internal subset?
595   */
596   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
597                         "  SAX.internalSubset(%.1024s, %.1024s, %.1024s)",(char *) name,
598                         (external_id != (const xmlChar *) NULL ? (char *) external_id : "none"),
599                         (system_id != (const xmlChar *) NULL ? (char *) system_id : "none"));
600   svg_info=(SVGInfo *) context;
601   (void) xmlCreateIntSubset(svg_info->document,name,external_id,system_id);
602 }
603 
604 static xmlParserInputPtr
SVGResolveEntity(void * context,const xmlChar * public_id,const xmlChar * system_id)605 SVGResolveEntity(void *context,
606                  const xmlChar *public_id,const xmlChar *system_id)
607 {
608   SVGInfo
609     *svg_info;
610 
611   xmlParserInputPtr
612     stream;
613 
614   /*
615     Special entity resolver, better left to the parser, it has more
616     context than the application layer.  The default behaviour is to
617     not resolve the entities, in that case the ENTITY_REF nodes are
618     built in the structure (and the parameter values).
619   */
620   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
621                         "  SAX.resolveEntity(%.1024s, %.1024s)",
622                         (public_id != (const xmlChar *) NULL ? (char *) public_id : "none"),
623                         (system_id != (const xmlChar *) NULL ? (char *) system_id : "none"));
624   svg_info=(SVGInfo *) context;
625   stream=xmlLoadExternalEntity((const char *) system_id,(const char *)
626                                public_id,svg_info->parser);
627   return(stream);
628 }
629 
630 static xmlEntityPtr
SVGGetEntity(void * context,const xmlChar * name)631 SVGGetEntity(void *context,const xmlChar *name)
632 {
633   SVGInfo
634     *svg_info;
635 
636   /*
637     Get an entity by name.
638   */
639   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
640                         "  SAX.SVGGetEntity(%.1024s)",name);
641   svg_info=(SVGInfo *) context;
642   return(xmlGetDocEntity(svg_info->document,name));
643 }
644 
645 static xmlEntityPtr
SVGGetParameterEntity(void * context,const xmlChar * name)646 SVGGetParameterEntity(void *context,const xmlChar *name)
647 {
648   SVGInfo
649     *svg_info;
650 
651   /*
652     Get a parameter entity by name.
653   */
654   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
655                         "  SAX.getParameterEntity(%.1024s)",name);
656   svg_info=(SVGInfo *) context;
657   return(xmlGetParameterEntity(svg_info->document,name));
658 }
659 
660 static void
SVGEntityDeclaration(void * context,const xmlChar * name,int type,const xmlChar * public_id,const xmlChar * system_id,xmlChar * content)661 SVGEntityDeclaration(void *context,const xmlChar *name,int type,
662                      const xmlChar *public_id,const xmlChar *system_id,xmlChar *content)
663 {
664   SVGInfo
665     *svg_info;
666 
667   /*
668     An entity definition has been parsed.
669   */
670   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
671                         "  SAX.entityDecl(%.1024s, %d, %.1024s, %.1024s, %.1024s)",name,type,
672                         public_id != (xmlChar *) NULL ? (char *) public_id : "none",
673                         system_id != (xmlChar *) NULL ? (char *) system_id : "none",content);
674   svg_info=(SVGInfo *) context;
675   if (svg_info->parser->inSubset == 1)
676     (void) xmlAddDocEntity(svg_info->document,name,type,public_id,system_id,
677                            content);
678   else
679     if (svg_info->parser->inSubset == 2)
680       (void) xmlAddDtdEntity(svg_info->document,name,type,public_id,system_id,
681                              content);
682 }
683 
684 static void
SVGAttributeDeclaration(void * context,const xmlChar * element,const xmlChar * name,int type,int value,const xmlChar * default_value,xmlEnumerationPtr tree)685 SVGAttributeDeclaration(void *context,const xmlChar *element,
686                         const xmlChar *name,int type,int value,const xmlChar *default_value,
687                         xmlEnumerationPtr tree)
688 {
689   SVGInfo
690     *svg_info;
691 
692   xmlChar
693     *fullname,
694     *prefix;
695 
696   xmlParserCtxtPtr
697     parser;
698 
699   /*
700     An attribute definition has been parsed.
701   */
702   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
703                         "  SAX.attributeDecl(%.1024s, %.1024s, %d, %d, %.1024s, ...)",element,
704                         name,type,value,default_value);
705   svg_info=(SVGInfo *) context;
706   fullname=(xmlChar *) NULL;
707   prefix=(xmlChar *) NULL;
708   parser=svg_info->parser;
709   fullname=(xmlChar *) xmlSplitQName(parser,name,&prefix);
710   if (parser->inSubset == 1)
711     (void) xmlAddAttributeDecl(&parser->vctxt,svg_info->document->intSubset,
712                                element,fullname,prefix,(xmlAttributeType) type,
713                                (xmlAttributeDefault) value,default_value,tree);
714   else
715     if (parser->inSubset == 2)
716       (void) xmlAddAttributeDecl(&parser->vctxt,svg_info->document->extSubset,
717                                  element,fullname,prefix,(xmlAttributeType) type,
718                                  (xmlAttributeDefault) value,default_value,tree);
719   if (prefix != (xmlChar *) NULL)
720     xmlFree(prefix);
721   if (fullname != (xmlChar *) NULL)
722     xmlFree(fullname);
723 }
724 
725 static void
SVGElementDeclaration(void * context,const xmlChar * name,int type,xmlElementContentPtr content)726 SVGElementDeclaration(void *context,const xmlChar *name,int type,
727                       xmlElementContentPtr content)
728 {
729   SVGInfo
730     *svg_info;
731 
732   xmlParserCtxtPtr
733     parser;
734 
735   /*
736     An element definition has been parsed.
737   */
738   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
739                         "  SAX.elementDecl(%.1024s, %d, ...)",name,type);
740   svg_info=(SVGInfo *) context;
741   parser=svg_info->parser;
742   if (parser->inSubset == 1)
743     (void) xmlAddElementDecl(&parser->vctxt,svg_info->document->intSubset,
744                              name,(xmlElementTypeVal) type,content);
745   else
746     if (parser->inSubset == 2)
747       (void) xmlAddElementDecl(&parser->vctxt,svg_info->document->extSubset,
748                                name,(xmlElementTypeVal) type,content);
749 }
750 
751 static void
SVGNotationDeclaration(void * context,const xmlChar * name,const xmlChar * public_id,const xmlChar * system_id)752 SVGNotationDeclaration(void *context,const xmlChar *name,
753                        const xmlChar *public_id,const xmlChar *system_id)
754 {
755   SVGInfo
756     *svg_info;
757 
758   xmlParserCtxtPtr
759     parser;
760 
761   /*
762     What to do when a notation declaration has been parsed.
763   */
764   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
765                         "  SAX.notationDecl(%.1024s, %.1024s, %.1024s)",name,
766                         public_id != (const xmlChar *) NULL ? (char *) public_id : "none",
767                         system_id != (const xmlChar *) NULL ? (char *) system_id : "none");
768   svg_info=(SVGInfo *) context;
769   parser=svg_info->parser;
770   if (parser->inSubset == 1)
771     (void) xmlAddNotationDecl(&parser->vctxt,svg_info->document->intSubset,
772                               name,public_id,system_id);
773   else
774     if (parser->inSubset == 2)
775       (void) xmlAddNotationDecl(&parser->vctxt,svg_info->document->intSubset,
776                                 name,public_id,system_id);
777 }
778 
779 static void
SVGUnparsedEntityDeclaration(void * context,const xmlChar * name,const xmlChar * public_id,const xmlChar * system_id,const xmlChar * notation)780 SVGUnparsedEntityDeclaration(void *context,const xmlChar *name,
781                              const xmlChar *public_id,const xmlChar *system_id,
782                              const xmlChar *notation)
783 {
784   SVGInfo
785     *svg_info;
786 
787   /*
788     What to do when an unparsed entity declaration is parsed.
789   */
790   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
791                         "  SAX.unparsedEntityDecl(%.1024s, %.1024s, %.1024s, %.1024s)",name,
792                         public_id != (xmlChar *) NULL ? (char *) public_id : "none",
793                         system_id != (xmlChar *) NULL ? (char *) system_id : "none",notation);
794   svg_info=(SVGInfo *) context;
795   (void) xmlAddDocEntity(svg_info->document,name,
796                          XML_EXTERNAL_GENERAL_UNPARSED_ENTITY,public_id,system_id,notation);
797 
798 }
799 
800 static void
SVGSetDocumentLocator(void * context,xmlSAXLocatorPtr location)801 SVGSetDocumentLocator(void *context,
802                       xmlSAXLocatorPtr location)
803 {
804   /*   SVGInfo */
805   /*     *svg_info; */
806 
807   ARG_NOT_USED(context);
808   ARG_NOT_USED(location);
809   /*
810     Receive the document locator at startup, actually xmlDefaultSAXLocator.
811   */
812   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.setDocumentLocator()");
813   /*   svg_info=(SVGInfo *) context; */
814 }
815 
816 static void
SVGStartDocument(void * context)817 SVGStartDocument(void *context)
818 {
819   SVGInfo
820     *svg_info;
821 
822   xmlParserCtxtPtr
823     parser;
824 
825   /*
826     Called when the document start being processed.
827   */
828   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.startDocument()");
829   svg_info=(SVGInfo *) context;
830   DestroyExceptionInfo(svg_info->exception);
831   GetExceptionInfo(svg_info->exception);
832   parser=svg_info->parser;
833   svg_info->document=xmlNewDoc(parser->version);
834   if (svg_info->document == (xmlDocPtr) NULL)
835     return;
836   if (parser->encoding == NULL)
837     svg_info->document->encoding=(const xmlChar *) NULL;
838   else
839     svg_info->document->encoding=xmlStrdup(parser->encoding);
840   svg_info->document->standalone=parser->standalone;
841 }
842 
843 static void
SVGEndDocument(void * context)844 SVGEndDocument(void *context)
845 {
846   SVGInfo
847     *svg_info;
848 
849   /*
850     Called when the document end has been detected.
851   */
852   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.endDocument()");
853   svg_info=(SVGInfo *) context;
854   MagickFreeMemory(svg_info->size);
855   MagickFreeMemory(svg_info->title);
856   MagickFreeMemory(svg_info->comment);
857   MagickFreeMemory(svg_info->scale);
858   MagickFreeMemory(svg_info->stop_color);
859   MagickFreeMemory(svg_info->offset);
860   MagickFreeMemory(svg_info->text);
861   MagickFreeMemory(svg_info->vertices);
862   MagickFreeMemory(svg_info->url);
863 
864   /* Don't free xmlParserCtxtPtr parser which is used later */
865 
866   if (svg_info->document != (xmlDocPtr) NULL)
867     {
868       xmlFreeDoc(svg_info->document);
869       svg_info->document=(xmlDocPtr) NULL;
870     }
871 }
872 
873 
874 /*
875   Code from SVGStartElement() that processed transform="..." has been refactored
876   into new function SVGProcessTransformString().
877 */
878 static void
SVGProcessTransformString(void * context,char const * TransformString)879 SVGProcessTransformString  (
880         void *context,
881         char const *TransformString
882         )
883 {/*SVGProcessTransformString*/
884 
885   char
886     **tokens;
887 
888   AffineMatrix
889     affine,
890     current,
891     transform;
892 
893   char
894     *p = NULL,
895     token[MaxTextExtent];
896 
897   SVGInfo
898     *svg_info=(SVGInfo *) context;
899 
900   size_t
901     j,
902     number_tokens = 0;
903 
904   IdentityAffine(&transform);
905   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  ");
906   tokens=GetTransformTokens(context,TransformString,&number_tokens);
907   if ((tokens != (char **) NULL) && (number_tokens > 0))
908     {/*if ((tokens != (char **) NULL) && (number_tokens > 0))*/
909 
910                 const char
911         *keyword = NULL,
912         *value = NULL;
913 
914       for (j=0; j < (number_tokens-1); j+=2)
915         {/*j token loop*/
916 
917           keyword=(char *) tokens[j];   /* matrix, rotate, etc. */
918           value=(char *) tokens[j+1];   /* associated numerical values */
919           (void) LogMagickEvent(CoderEvent,GetMagickModule(),
920                                 "    %.1024s: %.1024s",keyword,value);
921           current=transform;
922           IdentityAffine(&affine);
923           switch (*keyword)
924             {/*keyword switch*/
925 
926             case 'M':
927             case 'm':
928               {/*Mm*/
929                 if (LocaleCompare(keyword,"matrix") == 0)
930                   {
931                     p=(char *) value;
932                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
933                     affine.sx=MagickAtoF(token);
934                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
935                     if (*token == ',')
936                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
937                     affine.rx=MagickAtoF(token);
938                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
939                     if (*token == ',')
940                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
941                     affine.ry=MagickAtoF(token);
942                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
943                     if (*token == ',')
944                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
945                     affine.sy=MagickAtoF(token);
946                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
947                     if (*token == ',')
948                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
949                     affine.tx=MagickAtoF(token);
950                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
951                     if (*token == ',')
952                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
953                     affine.ty=MagickAtoF(token);
954                     break;
955                   }
956                 break;
957               }/*Mm*/
958 
959             case 'R':
960             case 'r':
961               {/*Rr*/
962                 if (LocaleCompare(keyword,"rotate") == 0)
963                   {
964                     double
965                       angle;
966 
967                     angle=GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse);
968                     affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
969                     affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
970                     affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
971                     affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
972                     break;
973                   }
974                 break;
975               }/*Rr*/
976 
977             case 'S':
978             case 's':
979               {/*Ss*/
980                 if (LocaleCompare(keyword,"scale") == 0)
981                   {
982                     for (p=(char *) value; *p != '\0'; p++)
983                       if (isspace((int) (*p)) || (*p == ','))
984                         break;
985                     affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
986                     affine.sy=affine.sx;
987                     if (*p != '\0')
988                       affine.sy=
989                         GetUserSpaceCoordinateValue(svg_info,-1,p+1,MagickFalse);
990                     svg_info->scale[svg_info->n]=ExpandAffine(&affine);
991                     break;
992                   }
993                 if (LocaleCompare(keyword,"skewX") == 0)
994                   {
995                     affine.sx=svg_info->affine.sx;
996                     affine.ry=tan(DegreesToRadians(fmod(
997                       GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse),
998                       360.0)));
999                     affine.sy=svg_info->affine.sy;
1000                     break;
1001                   }
1002                 if (LocaleCompare(keyword,"skewY") == 0)
1003                   {
1004                     affine.sx=svg_info->affine.sx;
1005                     affine.rx=tan(DegreesToRadians(fmod(
1006                       GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse),
1007                       360.0)));
1008                     affine.sy=svg_info->affine.sy;
1009                     break;
1010                   }
1011                 break;
1012               }/*Ss*/
1013 
1014             case 'T':
1015             case 't':
1016               {/*Tt*/
1017                 if (LocaleCompare(keyword,"translate") == 0)
1018                   {
1019                     for (p=(char *) value; *p != '\0'; p++)
1020                       if (isspace((int) (*p)) || (*p == ','))
1021                         break;
1022                     affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1023                     affine.ty=affine.tx;
1024                     if (*p != '\0')
1025                       affine.ty=
1026                         GetUserSpaceCoordinateValue(svg_info,-1,p+1,MagickFalse);
1027                     break;
1028                   }
1029                 break;
1030               }/*Tt*/
1031 
1032             default:
1033               break;
1034 
1035             }/*keyword switch*/
1036 
1037           transform.sx=current.sx*affine.sx+current.ry*affine.rx;
1038           transform.rx=current.rx*affine.sx+current.sy*affine.rx;
1039           transform.ry=current.sx*affine.ry+current.ry*affine.sy;
1040           transform.sy=current.rx*affine.ry+current.sy*affine.sy;
1041           transform.tx=current.sx*affine.tx+current.ry*affine.ty+
1042             current.tx;
1043           transform.ty=current.rx*affine.tx+current.sy*affine.ty+
1044             current.ty;
1045 
1046         }/*j token loop*/
1047 
1048       MVGPrintf(svg_info->file,"affine %g %g %g %g %g %g\n",
1049                 transform.sx,transform.rx,transform.ry,transform.sy,
1050                 transform.tx,transform.ty);
1051 
1052     }/*if ((tokens != (char **) NULL) && (number_tokens > 0))*/
1053 
1054   /* clean up memory used for tokens */
1055   if (tokens != (char **) NULL)
1056     {
1057       for (j=0; tokens[j] != (char *) NULL; j++)
1058         MagickFreeMemory(tokens[j]);
1059       MagickFreeMemory(tokens);
1060     }
1061 
1062 }/*SVGProcessTransformString*/
1063 
1064 
1065 static void
SVGStartElement(void * context,const xmlChar * name,const xmlChar ** attributes)1066 SVGStartElement(void *context,const xmlChar *name,
1067                 const xmlChar **attributes)
1068 {
1069   char
1070     *color = NULL,
1071     id[MaxTextExtent],
1072     *p = NULL,
1073     token[MaxTextExtent],
1074     *units = NULL;
1075 
1076   const char
1077     *keyword = NULL,
1078     *value = NULL;
1079 
1080   size_t
1081     number_tokens = 0;
1082 
1083   SVGInfo
1084     *svg_info;
1085 
1086   size_t
1087     i,
1088     j;
1089 
1090   char
1091     svg_element_background_color[MaxTextExtent];  /* to support style="background:color" */
1092 
1093   MagickBool
1094     IsTSpan = MagickFalse,
1095     IsTextOrTSpan = MagickFalse;
1096 
1097   /*
1098     Called when an opening tag has been processed.
1099   */
1100   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1101                         "  SAX.startElement(%.1024s",name);
1102   id[0]='\0';
1103   token[0]='\0';
1104   svg_info=(SVGInfo *) context;
1105   svg_info->n++;
1106   MagickReallocMemory(double *,svg_info->scale,MagickArraySize((size_t)svg_info->n+1,sizeof(double)));
1107   if (svg_info->scale == (double *) NULL)
1108     {
1109       ThrowException(svg_info->exception,ResourceLimitError,
1110                      MemoryAllocationFailed,"unable to convert SVG image");
1111       return;
1112     }
1113   svg_info->scale[svg_info->n]=svg_info->scale[svg_info->n-1];
1114   color=AllocateString("none");
1115   units=AllocateString("userSpaceOnUse");
1116   value=(const char *) NULL;
1117   svg_element_background_color[0]='\0';
1118   IsTextOrTSpan = IsTSpan = LocaleCompare((char *) name,"tspan") == 0;  /* need to know this early */
1119   /*
1120     According to the SVG spec, for the following SVG elements, if the x or y
1121     attribute is not specified, the effect is as if a value of "0" were specified.
1122   */
1123   if (
1124          (LocaleCompare((char *) name,"image") == 0)
1125       || (LocaleCompare((char *) name,"pattern") == 0)
1126       || (LocaleCompare((char *) name,"rect") == 0)
1127       || (LocaleCompare((char *) name,"text") == 0)
1128       || (LocaleCompare((char *) name,"use") == 0)
1129       )
1130     {
1131       svg_info->bounds.x = svg_info->bounds.y = 0;
1132     }
1133   /*
1134     NOTE: SVG spec makes similar statements for cx,cy of circle and ellipse, and
1135     x1,y1,x2,y2 of line, but these are zeroed out initially, AND at the end of
1136     SVGEndElement() after they have been used.
1137   */
1138 
1139   /*
1140     When "font-size" is (or is contained in) one of the attributes for this SVG
1141     element, we want it to be processed first so that any numerical conversions
1142     that depend on the font size will use the new value.  So we will first scan
1143     the attribute list and move any "font-size", "class" (which may contain a
1144     "font-size"), or "style" (which may contain a "font-size") attributes to the
1145     front of the attribute list.
1146 
1147     For now we will ignore the possibility that "font-size" may be specified
1148     more than once among "font-size", "class", and "style".  However, the
1149     relative order among these three will be preserved.
1150   */
1151   if (attributes != (const xmlChar **) NULL)
1152   {/*have some attributes*/
1153 
1154     size_t iListFront = 0;
1155     for  ( i = 0; (attributes[i] != (const xmlChar *) NULL); i += 2 )
1156       {/*attribute[i]*/
1157 
1158         keyword = (const char *) attributes[i];
1159         if  (  (LocaleCompare(keyword,"font-size") == 0)
1160             || (LocaleCompare(keyword,"class") == 0)
1161             || (LocaleCompare(keyword,"style") == 0)
1162          )
1163          {/*(possible) font-size*/
1164 
1165             if  ( i == iListFront )
1166               iListFront += 2;  /* already at front of list */
1167             else
1168               {
1169                 /* move to front of list */
1170                 const xmlChar * pAttr = attributes[iListFront];
1171                 attributes[iListFront] = attributes[i];
1172                 attributes[i] = pAttr;
1173                 iListFront++;
1174                 pAttr = attributes[iListFront];
1175                 attributes[iListFront] = attributes[i+1];
1176                 attributes[i+1] = pAttr;
1177                 iListFront++;
1178               }
1179 
1180          }/*(possible) font-size*/
1181 
1182       }/*attribute[i]*/
1183 
1184   }/*have some attributes*/
1185 
1186   if (attributes != (const xmlChar **) NULL)
1187     for (i=0; (svg_info->exception->severity < ErrorException) &&
1188            (attributes[i] != (const xmlChar *) NULL); i+=2)
1189       {
1190         keyword=(const char *) attributes[i];
1191         value=(const char *) attributes[i+1];
1192         switch (*keyword)
1193           {
1194           case 'C':
1195           case 'c':
1196             {
1197               if (LocaleCompare(keyword,"cx") == 0)
1198                 {
1199                   svg_info->element.cx=
1200                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1201                   break;
1202                 }
1203               if (LocaleCompare(keyword,"cy") == 0)
1204                 {
1205                   svg_info->element.cy=
1206                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
1207                   break;
1208                 }
1209               break;
1210             }
1211           case 'F':
1212           case 'f':
1213             {
1214               if (LocaleCompare(keyword,"fx") == 0)
1215                 {
1216                   svg_info->element.major=
1217                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1218                   break;
1219                 }
1220               if (LocaleCompare(keyword,"fy") == 0)
1221                 {
1222                   svg_info->element.minor=
1223                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
1224                   break;
1225                 }
1226               break;
1227             }
1228           case 'H':
1229           case 'h':
1230             {
1231               if (LocaleCompare(keyword,"height") == 0)
1232                 {
1233                   svg_info->bounds.height=
1234                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickTrue);
1235                   break;
1236                 }
1237               break;
1238             }
1239           case 'I':
1240           case 'i':
1241             {
1242               if (LocaleCompare(keyword,"id") == 0)
1243                 {
1244                   (void) strlcpy(id,value,MaxTextExtent);
1245                   /* track elements inside <defs> that have an "id" */
1246                   if  ( (svg_info->defsPushCount > 0)
1247                     && (svg_info->idLevelInsideDefs == 0)   /* do not allow nested "id" elements for now */
1248                     && (LocaleCompare((const char *)name,"clipPath") != 0)  /* handled separately */
1249                     && (LocaleCompare((const char *)name,"mask") != 0)      /* handled separately */
1250                     )
1251                       svg_info->idLevelInsideDefs = svg_info->n;
1252                   break;
1253                 }
1254               break;
1255             }
1256           case 'R':
1257           case 'r':
1258             {
1259               if (LocaleCompare(keyword,"r") == 0)
1260                 {
1261                   svg_info->element.angle=
1262                     GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse);
1263                   break;
1264                 }
1265               break;
1266             }
1267           case 'W':
1268           case 'w':
1269             {
1270               if (LocaleCompare(keyword,"width") == 0)
1271                 {
1272                   svg_info->bounds.width=
1273                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue);
1274                   break;
1275                 }
1276               break;
1277             }
1278           case 'X':
1279           case 'x':
1280             {
1281               if (LocaleCompare(keyword,"x") == 0)
1282                 {
1283                   /* if processing a tspan, preserve the current bounds.x, which belongs to the
1284                      previously processed text or tspan; the bounds.x for the current tspan will
1285                      be set later */
1286                   if (!IsTSpan)
1287                     svg_info->bounds.x=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1288                   break;
1289                 }
1290               if (LocaleCompare(keyword,"x1") == 0)
1291                 {
1292                   svg_info->segment.x1=
1293                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1294                   break;
1295                 }
1296               if (LocaleCompare(keyword,"x2") == 0)
1297                 {
1298                   svg_info->segment.x2=
1299                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1300                   break;
1301                 }
1302               break;
1303             }
1304           case 'Y':
1305           case 'y':
1306             {
1307               if (LocaleCompare(keyword,"y") == 0)
1308                 {
1309                   /* if processing a tspan, preserve the current bounds.y, which belongs to the
1310                      previously processed text or tspan; the bounds.y for the current tspan will
1311                      be set later */
1312                   if (!IsTSpan)
1313                     svg_info->bounds.y=GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
1314                   break;
1315                 }
1316               if (LocaleCompare(keyword,"y1") == 0)
1317                 {
1318                   svg_info->segment.y1=
1319                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
1320                   break;
1321                 }
1322               if (LocaleCompare(keyword,"y2") == 0)
1323                 {
1324                   svg_info->segment.y2=
1325                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
1326                   break;
1327                 }
1328               break;
1329             }
1330           default:
1331             break;
1332           }
1333       }
1334   if (svg_info->exception->severity >= ErrorException)
1335     goto svg_start_element_error;
1336   if (strchr((char *) name,':') != (char *) NULL)
1337     {
1338       /*
1339         Skip over namespace.
1340       */
1341       for ( ; *name != ':'; name++) ;
1342       name++;
1343     }
1344   switch (*name)
1345     {
1346     case 'C':
1347     case 'c':
1348       {
1349         if (LocaleCompare((char *) name,"circle") == 0)
1350           {
1351             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "push id" if warranted */
1352               MVGPrintf(svg_info->file,"push id '%s'\n",id);
1353             MVGPrintf(svg_info->file,"push graphic-context\n");
1354             break;
1355           }
1356         if (LocaleCompare((char *) name,"clipPath") == 0)
1357           {
1358             MVGPrintf(svg_info->file,"push clip-path '%s'\n",id);
1359             break;
1360           }
1361         break;
1362       }
1363     case 'D':
1364     case 'd':
1365       {
1366         if (LocaleCompare((char *) name,"defs") == 0)
1367           {
1368             svg_info->defsPushCount++;
1369             MVGPrintf(svg_info->file,"push defs\n");
1370             break;
1371           }
1372         break;
1373       }
1374     case 'E':
1375     case 'e':
1376       {
1377         if (LocaleCompare((char *) name,"ellipse") == 0)
1378           {
1379             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "push id" if warranted */
1380               MVGPrintf(svg_info->file,"push id '%s'\n",id);
1381             MVGPrintf(svg_info->file,"push graphic-context\n");
1382             break;
1383           }
1384         break;
1385       }
1386     case 'F':
1387     case 'f':
1388       {
1389         /*
1390           For now we are ignoring "foreignObject".  However, we do a push/pop
1391           graphic-context so that any settings (e.g., fill) are consumed and
1392           discarded.  Otherwise they might persist and "leak" into the graphic
1393           elements that follow.
1394         */
1395         if (LocaleCompare((char *) name,"foreignObject") == 0)
1396           {
1397             MVGPrintf(svg_info->file,"push graphic-context\n");
1398             break;
1399           }
1400         break;
1401       }
1402     case 'G':
1403     case 'g':
1404       {
1405         if (LocaleCompare((char *) name,"g") == 0)
1406           {
1407             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "push id" if warranted */
1408               MVGPrintf(svg_info->file,"push id '%s'\n",id);
1409             MVGPrintf(svg_info->file,"push graphic-context\n");
1410             break;
1411           }
1412         break;
1413       }
1414     case 'I':
1415     case 'i':
1416       {
1417         if (LocaleCompare((char *) name,"image") == 0)
1418           {
1419             MVGPrintf(svg_info->file,"push graphic-context\n");
1420             break;
1421           }
1422         break;
1423       }
1424     case 'L':
1425     case 'l':
1426       {
1427         if (LocaleCompare((char *) name,"line") == 0)
1428           {
1429             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "push id" if warranted */
1430               MVGPrintf(svg_info->file,"push id '%s'\n",id);
1431             MVGPrintf(svg_info->file,"push graphic-context\n");
1432             break;
1433           }
1434         if (LocaleCompare((char *) name,"linearGradient") == 0)
1435           {
1436             MVGPrintf(svg_info->file,"push gradient '%s' linear %g,%g %g,%g\n",id,
1437                       svg_info->segment.x1,svg_info->segment.y1,svg_info->segment.x2,
1438                       svg_info->segment.y2);
1439             break;
1440           }
1441         break;
1442       }
1443     case 'M':
1444     case 'm':
1445       {
1446         if (LocaleCompare((char *) name,"mask") == 0)   /* added mask */
1447         {
1448           MVGPrintf(svg_info->file,"push mask '%s'\n",id);
1449           break;
1450         }
1451       break;
1452       }
1453     case 'P':
1454     case 'p':
1455       {
1456         if (LocaleCompare((char *) name,"path") == 0)
1457           {
1458             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "push id" if warranted */
1459               MVGPrintf(svg_info->file,"push id '%s'\n",id);
1460             MVGPrintf(svg_info->file,"push graphic-context\n");
1461             break;
1462           }
1463         if (LocaleCompare((char *) name,"pattern") == 0)
1464           {
1465             MVGPrintf(svg_info->file,"push pattern '%s' %g,%g %g,%g\n",id,
1466                       svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.width,
1467                       svg_info->bounds.height);
1468             break;
1469           }
1470         if (LocaleCompare((char *) name,"polygon") == 0)
1471           {
1472             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "push id" if warranted */
1473               MVGPrintf(svg_info->file,"push id '%s'\n",id);
1474             MVGPrintf(svg_info->file,"push graphic-context\n");
1475             break;
1476           }
1477         if (LocaleCompare((char *) name,"polyline") == 0)
1478           {
1479             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "push id" if warranted */
1480               MVGPrintf(svg_info->file,"push id '%s'\n",id);
1481             MVGPrintf(svg_info->file,"push graphic-context\n");
1482             break;
1483           }
1484         break;
1485       }
1486     case 'R':
1487     case 'r':
1488       {
1489         if (LocaleCompare((char *) name,"radialGradient") == 0)
1490           {
1491             if (svg_info->element.angle < 0.0)
1492               {
1493                 errno=0;
1494                 ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,
1495                                value);
1496                 break;
1497               }
1498             MVGPrintf(svg_info->file,"push gradient '%s' radial %g,%g %g,%g %g\n",
1499                       id,svg_info->element.cx,svg_info->element.cy,
1500                       svg_info->element.major,svg_info->element.minor,
1501                       svg_info->element.angle);
1502             break;
1503           }
1504         if (LocaleCompare((char *) name,"rect") == 0)
1505           {
1506             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "push id" if warranted */
1507               MVGPrintf(svg_info->file,"push id '%s'\n",id);
1508             MVGPrintf(svg_info->file,"push graphic-context\n");
1509             break;
1510           }
1511         break;
1512       }
1513     case 'S':
1514     case 's':
1515       {
1516         /* element "style" inside <defs> */
1517         if (LocaleCompare((char *) name,"style") == 0)
1518           {
1519             /*
1520               This is here more or less as a documentation aid.  The real work is done when
1521               we encounter </style>.
1522             */
1523             break;
1524           }
1525         if (LocaleCompare((char *) name,"svg") == 0)
1526           {
1527             svg_info->svgPushCount++;
1528             MVGPrintf(svg_info->file,"push graphic-context\n");
1529             /*
1530               Per the SVG spec, initialize the MVG coder with the following
1531               SVG defaults:
1532                 - svg-compliant: "1" (note: internal to GM, not an SVG element)
1533                 - fill color: "black"
1534                 - fill-opacity value: "1"
1535                 - stroke color: "none"
1536                 - stroke-width value: "1"
1537                 - stroke-opacity value: "1"
1538                 - fill-rule value: "nonzero"
1539             */
1540             MVGPrintf(svg_info->file,"svg-compliant 1\n");
1541             MVGPrintf(svg_info->file,"fill 'black'\n");
1542             MVGPrintf(svg_info->file,"fill-opacity 1\n");
1543             MVGPrintf(svg_info->file,"stroke 'none'\n");
1544             MVGPrintf(svg_info->file,"stroke-width 1\n");
1545             MVGPrintf(svg_info->file,"stroke-opacity 1\n");
1546             MVGPrintf(svg_info->file,"fill-rule 'nonzero'\n");
1547             break;
1548           }
1549         break;
1550       }
1551     case 'T':
1552     case 't':
1553       {
1554         if (LocaleCompare((char *) name,"text") == 0)
1555           {
1556             IsTextOrTSpan = MagickTrue;
1557             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "push id" if warranted */
1558               MVGPrintf(svg_info->file,"push id '%s'\n",id);
1559             MVGPrintf(svg_info->file,"push graphic-context\n");
1560             /* update text current position */
1561             MVGPrintf(svg_info->file,"textx %g\n",svg_info->bounds.x);
1562             MVGPrintf(svg_info->file,"texty %g\n",svg_info->bounds.y);
1563             break;
1564           }
1565         if (LocaleCompare((char *) name,"tspan") == 0)
1566           {
1567             IsTextOrTSpan = MagickTrue;
1568             Strip(svg_info->text);
1569             if (*svg_info->text != '\0')
1570               {
1571                 char
1572                   *text;
1573 
1574                 text=EscapeString(svg_info->text,'\'');
1575                 MVGPrintf(svg_info->file,"textc '%s'\n",text);
1576                 MagickFreeMemory(text);
1577                 /*
1578                   The code that used to be here to compute the next text position has been eliminated.
1579                   The reason is that at this point in the code we may not know the font or font size
1580                   (they may be hidden in a "class" definition), so we can't really do that computation.
1581                   This functionality is now handled by DrawImage() in render.c.
1582                 */
1583                 *svg_info->text='\0';
1584               }
1585             MVGPrintf(svg_info->file,"push graphic-context\n");
1586             break;
1587           }
1588         break;
1589       }
1590     case 'U':
1591     case 'u':
1592       {
1593         if (LocaleCompare((char *) name,"use") == 0)
1594           {
1595             /* "use" behaves like "g" */
1596             MVGPrintf(svg_info->file,"push graphic-context\n");
1597             break;
1598           }
1599         break;
1600        }
1601     default:
1602       break;
1603     }
1604   if (attributes != (const xmlChar **) NULL)
1605     for (i=0; (svg_info->exception->severity < ErrorException) &&
1606            (attributes[i] != (const xmlChar *) NULL); i+=2)
1607       {
1608         keyword=(const char *) attributes[i];
1609         value=(const char *) attributes[i+1];
1610         (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1611                               "    %.1024s = %.1024s",keyword,value);
1612         switch (*keyword)
1613           {
1614           case 'A':
1615           case 'a':
1616             {
1617               if (LocaleCompare(keyword,"angle") == 0)
1618                 {
1619                   MVGPrintf(svg_info->file,"angle %g\n",
1620                             GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse));
1621                   break;
1622                 }
1623               break;
1624             }
1625           case 'C':
1626           case 'c':
1627             {
1628               if (LocaleCompare(keyword,"class") == 0)
1629                 {/*"class=classname"*/
1630                   char * pClassNames = (char * ) value;
1631                   do
1632                     {
1633                       (void) MagickGetToken(pClassNames,&pClassNames,token,MaxTextExtent);
1634                       if  ( *token == ',' )
1635                         (void) MagickGetToken(pClassNames,&pClassNames,token,MaxTextExtent);
1636                       if  ( *token != '\0' )
1637                         MVGPrintf(svg_info->file,"class '%s'\n",token);
1638                   }
1639                   while  ( *token != '\0' );
1640                   break;
1641                 }/*"class=classname"*/
1642               if (LocaleCompare(keyword,"clip-path") == 0)
1643                 {
1644                   MVGPrintf(svg_info->file,"clip-path '%s'\n",value);
1645                   break;
1646                 }
1647               if (LocaleCompare(keyword,"clip-rule") == 0)
1648                 {
1649                   MVGPrintf(svg_info->file,"clip-rule '%s'\n",value);
1650                   break;
1651                 }
1652               if (LocaleCompare(keyword,"clipPathUnits") == 0)
1653                 {
1654                   (void) CloneString(&units,value);
1655                   MVGPrintf(svg_info->file,"clip-units '%s'\n",value);
1656                   break;
1657                 }
1658               if (LocaleCompare(keyword,"color") == 0)
1659                 {
1660                   (void) CloneString(&color,value);
1661                   break;
1662                 }
1663               if (LocaleCompare(keyword,"cx") == 0)
1664                 {
1665                   svg_info->element.cx=
1666                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1667                   break;
1668                 }
1669               if (LocaleCompare(keyword,"cy") == 0)
1670                 {
1671                   svg_info->element.cy=
1672                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
1673                   break;
1674                 }
1675               break;
1676             }
1677           case 'D':
1678           case 'd':
1679             {
1680               if (LocaleCompare(keyword,"d") == 0)
1681                 {
1682                   (void) CloneString(&svg_info->vertices,value);
1683                   break;
1684                 }
1685               if (LocaleCompare(keyword,"dx") == 0)
1686                 {
1687                   double dx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1688                   svg_info->bounds.x+=dx;   /* preserve previous behavior */
1689                   /* update text current position for text or tspan */
1690                   if  ( IsTextOrTSpan )
1691                     {
1692                       char * pUnit;
1693                       (void) MagickGetToken(value,&pUnit,token,MaxTextExtent);
1694                       if  ( *pUnit && ((LocaleNCompare(pUnit,"em",2) == 0) || (LocaleNCompare(pUnit,"ex",2) == 0)) )
1695                         MVGPrintf(svg_info->file,"textdx %s\n",value);  /* postpone interpretation of "em" or "ex" until we know point size */
1696                       else
1697                         MVGPrintf(svg_info->file,"textdx %g\n",dx);
1698                     }
1699                   break;
1700                 }
1701               if (LocaleCompare(keyword,"dy") == 0)
1702                 {
1703                   double dy=GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
1704                   svg_info->bounds.y+=dy;   /* preserve previous behavior */
1705                   /* update text current position for text or tspan */
1706                   if  ( IsTextOrTSpan )
1707                     {
1708                       char * pUnit;
1709                       (void) MagickGetToken(value,&pUnit,token,MaxTextExtent);
1710                       if  ( *pUnit && ((LocaleNCompare(pUnit,"em",2) == 0) || (LocaleNCompare(pUnit,"ex",2) == 0)) )
1711                         MVGPrintf(svg_info->file,"textdy %s\n",value);  /* postpone interpretation of "em" or "ex" until we know point size */
1712                       else
1713                         MVGPrintf(svg_info->file,"textdy %g\n",dy);
1714                     }
1715                   break;
1716                 }
1717               break;
1718             }
1719           case 'F':
1720           case 'f':
1721             {
1722               if (LocaleCompare(keyword,"fill") == 0)
1723                 {
1724                   if (LocaleCompare(value,"currentColor") == 0)
1725                     {
1726                       MVGPrintf(svg_info->file,"fill '%s'\n",color);
1727                       break;
1728                     }
1729                   MVGPrintf(svg_info->file,"fill '%s'\n",value);
1730                   break;
1731                 }
1732               if (LocaleCompare(keyword,"fillcolor") == 0)
1733                 {
1734                   MVGPrintf(svg_info->file,"fill '%s'\n",value);
1735                   break;
1736                 }
1737               if (LocaleCompare(keyword,"fill-rule") == 0)
1738                 {
1739                   MVGPrintf(svg_info->file,"fill-rule '%s'\n",value);
1740                   break;
1741                 }
1742               if (LocaleCompare(keyword,"fill-opacity") == 0)
1743                 {
1744                   MVGPrintf(svg_info->file,"fill-opacity '%s'\n",value);
1745                   break;
1746                 }
1747               if (LocaleCompare(keyword,"font-family") == 0)
1748                 {
1749                   /*
1750                     Deal with Adobe Illustrator 10.0 which double-quotes
1751                     font-family.  Maybe we need a generalized solution for
1752                     this.
1753                   */
1754                   int value_length;
1755                   if ((value[0] == '\'') && ((value_length=(int) strlen(value)) > 2)
1756                       && (value[value_length-1] == '\''))
1757                     {
1758                       MVGPrintf(svg_info->file,"font-family '%.*s'\n",
1759                                 (int)(value_length-2),value+1);
1760                     }
1761                   else
1762                     {
1763                       MVGPrintf(svg_info->file,"font-family '%s'\n",value);
1764                     }
1765                   break;
1766                 }
1767               if (LocaleCompare(keyword,"font-stretch") == 0)
1768                 {
1769                   MVGPrintf(svg_info->file,"font-stretch '%s'\n",value);
1770                   break;
1771                 }
1772               if (LocaleCompare(keyword,"font-style") == 0)
1773                 {
1774                   MVGPrintf(svg_info->file,"font-style '%s'\n",value);
1775                   break;
1776                 }
1777               if (LocaleCompare(keyword,"font-size") == 0)
1778                 {
1779                   svg_info->pointsize=
1780                     GetUserSpaceCoordinateValue(svg_info,0,value,MagickTrue);
1781                   MVGPrintf(svg_info->file,"font-size %g\n",svg_info->pointsize);
1782                   break;
1783                 }
1784               if (LocaleCompare(keyword,"font-weight") == 0)
1785                 {
1786                   MVGPrintf(svg_info->file,"font-weight '%s'\n",value);
1787                   break;
1788                 }
1789               break;
1790             }
1791           case 'G':
1792           case 'g':
1793             {
1794               if (LocaleCompare(keyword,"gradientTransform") == 0)
1795                 {
1796                   char
1797                     **tokens;
1798 
1799                   AffineMatrix
1800                     affine,
1801                     current,
1802                     transform;
1803 
1804                   IdentityAffine(&transform);
1805                   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  ");
1806                   tokens=GetTransformTokens(context,value,&number_tokens);
1807                   if ((tokens != (char **) NULL) && (number_tokens > 0))
1808                     {
1809                       for (j=0; j < (number_tokens-1); j+=2)
1810                         {
1811                           keyword=(char *) tokens[j];
1812                           if (keyword == (char *) NULL)
1813                             continue;
1814                           value=(char *) tokens[j+1];
1815                           (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1816                                                 "    %.1024s: %.1024s",keyword,value);
1817                           current=transform;
1818                           IdentityAffine(&affine);
1819                           switch (*keyword)
1820                             {
1821                             case 'M':
1822                             case 'm':
1823                               {
1824                                 if (LocaleCompare(keyword,"matrix") == 0)
1825                                   {
1826                                     p=(char *) value;
1827                                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
1828                                     affine.sx=MagickAtoF(value);
1829                                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
1830                                     if (*token == ',')
1831                                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
1832                                     affine.rx=MagickAtoF(token);
1833                                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
1834                                     if (*token == ',')
1835                                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
1836                                     affine.ry=MagickAtoF(token);
1837                                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
1838                                     if (*token == ',')
1839                                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
1840                                     affine.sy=MagickAtoF(token);
1841                                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
1842                                     if (*token == ',')
1843                                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
1844                                     affine.tx=MagickAtoF(token);
1845                                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
1846                                     if (*token == ',')
1847                                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
1848                                     affine.ty=MagickAtoF(token);
1849                                     break;
1850                                   }
1851                                 break;
1852                               }
1853                             case 'R':
1854                             case 'r':
1855                               {
1856                                 if (LocaleCompare(keyword,"rotate") == 0)
1857                                   {
1858                                     double
1859                                       angle;
1860 
1861                                     angle=GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse);
1862                                     affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
1863                                     affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
1864                                     affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
1865                                     affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
1866                                     break;
1867                                   }
1868                                 break;
1869                               }
1870                             case 'S':
1871                             case 's':
1872                               {
1873                                 if (LocaleCompare(keyword,"scale") == 0)
1874                                   {
1875                                     for (p=(char *) value; *p != '\0'; p++)
1876                                       if (isspace((int) (*p)) || (*p == ','))
1877                                         break;
1878                                     affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1879                                     affine.sy=affine.sx;
1880                                     if (*p != '\0')
1881                                       affine.sy=
1882                                         GetUserSpaceCoordinateValue(svg_info,-1,p+1,MagickFalse);
1883                                     svg_info->scale[svg_info->n]=ExpandAffine(&affine);
1884                                     break;
1885                                   }
1886                                 if (LocaleCompare(keyword,"skewX") == 0)
1887                                   {
1888                                     affine.sx=svg_info->affine.sx;
1889                                     affine.ry=tan(DegreesToRadians(fmod(
1890                                                                         GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse),
1891                                                                         360.0)));
1892                                     affine.sy=svg_info->affine.sy;
1893                                     break;
1894                                   }
1895                                 if (LocaleCompare(keyword,"skewY") == 0)
1896                                   {
1897                                     affine.sx=svg_info->affine.sx;
1898                                     affine.rx=tan(DegreesToRadians(fmod(
1899                                                                         GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse),
1900                                                                         360.0)));
1901                                     affine.sy=svg_info->affine.sy;
1902                                     break;
1903                                   }
1904                                 break;
1905                               }
1906                             case 'T':
1907                             case 't':
1908                               {
1909                                 if (LocaleCompare(keyword,"translate") == 0)
1910                                   {
1911                                     for (p=(char *) value; *p != '\0'; p++)
1912                                       if (isspace((int) (*p)) || (*p == ','))
1913                                         break;
1914                                     affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1915                                     affine.ty=affine.tx;
1916                                     if (*p != '\0')
1917                                       affine.ty=
1918                                         GetUserSpaceCoordinateValue(svg_info,-1,p+1,MagickFalse);
1919                                     break;
1920                                   }
1921                                 break;
1922                               }
1923                             default:
1924                               break;
1925                             } /* end switch */
1926                           transform.sx=current.sx*affine.sx+current.ry*affine.rx;
1927                           transform.rx=current.rx*affine.sx+current.sy*affine.rx;
1928                           transform.ry=current.sx*affine.ry+current.ry*affine.sy;
1929                           transform.sy=current.rx*affine.ry+current.sy*affine.sy;
1930                           transform.tx=current.sx*affine.tx+current.ry*affine.ty+
1931                             current.tx;
1932                           transform.ty=current.rx*affine.tx+current.sy*affine.ty+
1933                             current.ty;
1934                         } /* end for */
1935                       MVGPrintf(svg_info->file,"affine %g %g %g %g %g %g\n",
1936                                 transform.sx,transform.rx,transform.ry,transform.sy,
1937                                 transform.tx,transform.ty);
1938                     } /* end if */
1939                   if (tokens != (char **) NULL)
1940                     {
1941                       for (j=0; tokens[j] != (char *) NULL; j++)
1942                         MagickFreeMemory(tokens[j]);
1943                       MagickFreeMemory(tokens);
1944                     }
1945                   break;
1946                 }
1947               if (LocaleCompare(keyword,"gradientUnits") == 0)
1948                 {
1949                   (void) CloneString(&units,value);
1950                   MVGPrintf(svg_info->file,"gradient-units '%s'\n",value);
1951                   break;
1952                 }
1953               break;
1954             }
1955           case 'H':
1956           case 'h':
1957             {
1958               if (LocaleCompare(keyword,"height") == 0)
1959                 {
1960                   svg_info->bounds.height=
1961                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickTrue);
1962                   break;
1963                 }
1964               if (LocaleCompare(keyword,"href") == 0)
1965                 {
1966                   /* process "#identifier" as if it were "url(#identifier)" */
1967                   if  ( value[0] == '#' )
1968                     {
1969                       /* reallocate the needed memory once */
1970                       size_t NewSize = strlen(value) + 6;   /* 6 == url()<null> */
1971                       MagickReallocMemory(char *,svg_info->url,NewSize);
1972                       memcpy(svg_info->url,"url(",4);
1973                       strcpy(svg_info->url+4,value);
1974                       svg_info->url[NewSize-2] = ')';
1975                       svg_info->url[NewSize-1] = '\0';
1976                     }
1977                   else
1978                     (void) CloneString(&svg_info->url,value);
1979                   break;
1980                 }
1981               break;
1982             }
1983           case 'M':
1984           case 'm':
1985             {
1986               if (LocaleCompare(keyword,"major") == 0)
1987                 {
1988                   svg_info->element.major=
1989                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
1990                   break;
1991                 }
1992               if (LocaleCompare(keyword,"mask") == 0)   /* added mask */
1993                 {
1994                   MVGPrintf(svg_info->file,"mask '%s'\n",value);
1995                   break;
1996                 }
1997               if (LocaleCompare(keyword,"minor") == 0)
1998                 {
1999                   svg_info->element.minor=
2000                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
2001                   break;
2002                 }
2003               break;
2004             }
2005           case 'O':
2006           case 'o':
2007             {
2008               if (LocaleCompare(keyword,"offset") == 0)
2009                 {
2010                   (void) CloneString(&svg_info->offset,value);
2011                   break;
2012                 }
2013               if (LocaleCompare(keyword,"opacity") == 0)
2014                 {
2015                   MVGPrintf(svg_info->file,"opacity '%s'\n",value);
2016                   break;
2017                 }
2018               break;
2019             }
2020           case 'P':
2021           case 'p':
2022             {
2023               if (LocaleCompare(keyword,"path") == 0)
2024                 {
2025                   (void) CloneString(&svg_info->url,value);
2026                   break;
2027                 }
2028               if (LocaleCompare(keyword,"points") == 0)
2029                 {
2030                   (void) CloneString(&svg_info->vertices,value);
2031                   break;
2032                 }
2033               break;
2034             }
2035           case 'R':
2036           case 'r':
2037             {
2038               if (LocaleCompare(keyword,"r") == 0)
2039                 {
2040                   svg_info->element.major=
2041                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
2042                   svg_info->element.minor=
2043                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
2044                   break;
2045                 }
2046               if (LocaleCompare(keyword,"rotate") == 0)
2047                 {
2048                   double
2049                     angle;
2050 
2051                   angle=GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse);
2052                   /*
2053                     When the current text position was managed in this code, and a "rotate" was encountered
2054                     (indicating that the text character was to be rotated), the code would emit to the MVG file:
2055 
2056                       translate x y (where x, y indicate the current text position)
2057                       rotate angle (where angle indicates the rotation angle)
2058 
2059                     Now that the current text position is being managed by DrawImage() in render.c, this code
2060                     cannot issue the "translate" because it can't know the current text position.  To handle
2061                     this, "textr" (text rotation) has been implemented in DrawImage() to perform the appropriate
2062                     translation/rotation sequence.
2063                   */
2064                   if ( IsTextOrTSpan )
2065                     MVGPrintf(svg_info->file,"textr %g\n",angle);  /* pre-translation will be handled in DrawImage() */
2066                   else
2067                   {
2068                     MVGPrintf(svg_info->file,"translate %g,%g\n",svg_info->bounds.x,svg_info->bounds.y);
2069                     svg_info->bounds.x=0;
2070                     svg_info->bounds.y=0;
2071                     MVGPrintf(svg_info->file,"rotate %g\n",angle);
2072                   }
2073                   break;
2074                 }
2075               if (LocaleCompare(keyword,"rx") == 0)
2076                 {
2077                   if (LocaleCompare((char *) name,"ellipse") == 0)
2078                     svg_info->element.major=
2079                       GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
2080                   else
2081                     svg_info->radius.x=
2082                       GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
2083                   break;
2084                 }
2085               if (LocaleCompare(keyword,"ry") == 0)
2086                 {
2087                   if (LocaleCompare((char *) name,"ellipse") == 0)
2088                     svg_info->element.minor=
2089                       GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
2090                   else
2091                     svg_info->radius.y=
2092                       GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
2093                   break;
2094                 }
2095               break;
2096             }
2097           case 'S':
2098           case 's':
2099             {
2100               if (LocaleCompare(keyword,"stop-color") == 0)
2101                 {
2102                   (void) CloneString(&svg_info->stop_color,value);
2103                   break;
2104                 }
2105               if (LocaleCompare(keyword,"stroke") == 0)
2106                 {
2107                   if (LocaleCompare(value,"currentColor") == 0)
2108                     {
2109                       MVGPrintf(svg_info->file,"stroke '%s'\n",color);
2110                       break;
2111                     }
2112                   MVGPrintf(svg_info->file,"stroke '%s'\n",value);
2113                   break;
2114                 }
2115               if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
2116                 {
2117                   MVGPrintf(svg_info->file,"stroke-antialias %d\n",
2118                             LocaleCompare(value,"true") == 0);
2119                   break;
2120                 }
2121               if (LocaleCompare(keyword,"stroke-dasharray") == 0)
2122                 {
2123                   MVGPrintf(svg_info->file,"stroke-dasharray %s\n",value);
2124                   break;
2125                 }
2126               if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
2127                 {
2128                   double dashoffset=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
2129                   MVGPrintf(svg_info->file,"stroke-dashoffset %g\n",dashoffset);
2130                   break;
2131                 }
2132               if (LocaleCompare(keyword,"stroke-linecap") == 0)
2133                 {
2134                   MVGPrintf(svg_info->file,"stroke-linecap '%s'\n",value);
2135                   break;
2136                 }
2137               if (LocaleCompare(keyword,"stroke-linejoin") == 0)
2138                 {
2139                   MVGPrintf(svg_info->file,"stroke-linejoin '%s'\n",value);
2140                   break;
2141                 }
2142               if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
2143                 {
2144                   double stroke_miterlimit;
2145                   if ((MagickAtoFChk(value,&stroke_miterlimit) != MagickPass) ||
2146                       stroke_miterlimit < 1.0)
2147                     {
2148                       errno=0;
2149                       ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,
2150                                      value);
2151                       break;
2152                     }
2153                   MVGPrintf(svg_info->file,"stroke-miterlimit '%ld'\n",
2154                             (long) stroke_miterlimit);
2155                   break;
2156                 }
2157               if (LocaleCompare(keyword,"stroke-opacity") == 0)
2158                 {
2159                   MVGPrintf(svg_info->file,"stroke-opacity '%s'\n",value);
2160                   break;
2161                 }
2162               if (LocaleCompare(keyword,"stroke-width") == 0)
2163                 {
2164                   MVGPrintf(svg_info->file,"stroke-width %g\n",
2165                             GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue));
2166                   break;
2167                 }
2168               if (LocaleCompare(keyword,"style") == 0)
2169                 {
2170                   char
2171                     **tokens;
2172 
2173                   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  ");
2174                   tokens=GetStyleTokens(context,value,&number_tokens);
2175                   if ((tokens != (char **) NULL) && (number_tokens > 0))
2176                     {
2177                       for (j=0; j < (number_tokens-1); j+=2)
2178                         {
2179                           keyword=(char *) tokens[j];
2180                           value=(char *) tokens[j+1];
2181                           (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2182                                                 "    %.1024s: %.1024s",keyword,value);
2183                           switch (*keyword)
2184                             {
2185                             case 'B':
2186                             case 'b':
2187                               {
2188                                 if (LocaleCompare(keyword,"background") == 0)
2189                                 {
2190                                   /* only do this if background was specified inside <svg ... */
2191                                   if  ( LocaleCompare((const char *)name,"svg") == 0 )
2192                                     strlcpy(svg_element_background_color,value,MaxTextExtent);
2193                                   break;
2194                                 }
2195                                 break;
2196                               }
2197                             case 'C':
2198                             case 'c':
2199                               {
2200                                 if (LocaleCompare(keyword,"clip-path") == 0)
2201                                   {
2202                                     MVGPrintf(svg_info->file,"clip-path '%s'\n",value);
2203                                     break;
2204                                   }
2205                                 if (LocaleCompare(keyword,"clip-rule") == 0)
2206                                   {
2207                                     MVGPrintf(svg_info->file,"clip-rule '%s'\n",value);
2208                                     break;
2209                                   }
2210                                 if (LocaleCompare(keyword,"clipPathUnits") == 0)
2211                                   {
2212                                     (void) CloneString(&units,value);
2213                                     MVGPrintf(svg_info->file,"clip-units '%s'\n",value);
2214                                     break;
2215                                   }
2216                                 if (LocaleCompare(keyword,"color") == 0)
2217                                   {
2218                                     (void) CloneString(&color,value);
2219                                     break;
2220                                   }
2221                                 break;
2222                               }
2223                             case 'F':
2224                             case 'f':
2225                               {
2226                                 if (LocaleCompare(keyword,"fill") == 0)
2227                                   {
2228                                     if (LocaleCompare(value,"currentColor") == 0)
2229                                       {
2230                                         MVGPrintf(svg_info->file,"fill '%s'\n",color);
2231                                         break;
2232                                       }
2233                                     MVGPrintf(svg_info->file,"fill '%s'\n",value);
2234                                     break;
2235                                   }
2236                                 if (LocaleCompare(keyword,"fillcolor") == 0)
2237                                   {
2238                                     MVGPrintf(svg_info->file,"fill '%s'\n",value);
2239                                     break;
2240                                   }
2241                                 if (LocaleCompare(keyword,"fill-rule") == 0)
2242                                   {
2243                                     MVGPrintf(svg_info->file,"fill-rule '%s'\n",value);
2244                                     break;
2245                                   }
2246                                 if (LocaleCompare(keyword,"fill-opacity") == 0)
2247                                   {
2248                                     MVGPrintf(svg_info->file,"fill-opacity '%s'\n",value);
2249                                     break;
2250                                   }
2251                                 if (LocaleCompare(keyword,"font-family") == 0)
2252                                   {
2253                                     MVGPrintf(svg_info->file,"font-family '%s'\n",value);
2254                                     break;
2255                                   }
2256                                 if (LocaleCompare(keyword,"font-stretch") == 0)
2257                                   {
2258                                     MVGPrintf(svg_info->file,"font-stretch '%s'\n",value);
2259                                     break;
2260                                   }
2261                                 if (LocaleCompare(keyword,"font-style") == 0)
2262                                   {
2263                                     MVGPrintf(svg_info->file,"font-style '%s'\n",value);
2264                                     break;
2265                                   }
2266                                 if (LocaleCompare(keyword,"font-size") == 0)
2267                                   {
2268                                     if (LocaleCompare(value,"medium") == 0)
2269                                       svg_info->pointsize=12;
2270                                     else
2271                                       svg_info->pointsize=
2272                                         GetUserSpaceCoordinateValue(svg_info,0,value,MagickTrue);
2273                                     MVGPrintf(svg_info->file,"font-size %g\n",
2274                                               svg_info->pointsize);
2275                                     break;
2276                                   }
2277                                 if (LocaleCompare(keyword,"font-weight") == 0)
2278                                   {
2279                                     MVGPrintf(svg_info->file,"font-weight '%s'\n",value);
2280                                     break;
2281                                   }
2282                                 break;
2283                               }
2284                             case 'O':
2285                             case 'o':
2286                               {
2287                                 if (LocaleCompare(keyword,"offset") == 0)
2288                                   {
2289                                     MVGPrintf(svg_info->file,"offset %g\n",
2290                                               GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse));
2291                                     break;
2292                                   }
2293                                 if (LocaleCompare(keyword,"opacity") == 0)
2294                                   {
2295                                     MVGPrintf(svg_info->file,"opacity '%s'\n",value);
2296                                     break;
2297                                   }
2298                                 break;
2299                               }
2300                             case 'S':
2301                             case 's':
2302                               {
2303                                 if (LocaleCompare(keyword,"stop-color") == 0)
2304                                   {
2305                                     (void) CloneString(&svg_info->stop_color,value);
2306                                     break;
2307                                   }
2308                                 if (LocaleCompare(keyword,"stroke") == 0)
2309                                   {
2310                                     if (LocaleCompare(value,"currentColor") == 0)
2311                                       {
2312                                         MVGPrintf(svg_info->file,"stroke '%s'\n",color);
2313                                         break;
2314                                       }
2315                                     MVGPrintf(svg_info->file,"stroke '%s'\n",value);
2316                                     break;
2317                                   }
2318                                 if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
2319                                   {
2320                                     MVGPrintf(svg_info->file,"stroke-antialias %d\n",
2321                                               LocaleCompare(value,"true") == 0);
2322                                     break;
2323                                   }
2324                                 if (LocaleCompare(keyword,"stroke-dasharray") == 0)
2325                                   {
2326                                     MVGPrintf(svg_info->file,"stroke-dasharray %s\n",value);
2327                                     break;
2328                                   }
2329                                 if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
2330                                   {
2331                                     double dashoffset=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
2332                                     MVGPrintf(svg_info->file,"stroke-dashoffset %g\n",dashoffset);
2333                                     break;
2334                                   }
2335                                 if (LocaleCompare(keyword,"stroke-linecap") == 0)
2336                                   {
2337                                     MVGPrintf(svg_info->file,"stroke-linecap '%s'\n",value);
2338                                     break;
2339                                   }
2340                                 if (LocaleCompare(keyword,"stroke-linejoin") == 0)
2341                                   {
2342                                     MVGPrintf(svg_info->file,"stroke-linejoin '%s'\n",
2343                                               value);
2344                                     break;
2345                                   }
2346                                 if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
2347                                   {
2348                                     double stroke_miterlimit;
2349                                     if ((MagickAtoFChk(value,&stroke_miterlimit) != MagickPass) ||
2350                                         stroke_miterlimit < 1.0)
2351                                       {
2352                                         errno=0;
2353                                         ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,
2354                                                        value);
2355                                         break;
2356                                       }
2357                                     MVGPrintf(svg_info->file,"stroke-miterlimit '%ld'\n",
2358                                               (long) stroke_miterlimit);
2359                                     break;
2360                                   }
2361                                 if (LocaleCompare(keyword,"stroke-opacity") == 0)
2362                                   {
2363                                     MVGPrintf(svg_info->file,"stroke-opacity '%s'\n",value);
2364                                     break;
2365                                   }
2366                                 if (LocaleCompare(keyword,"stroke-width") == 0)
2367                                   {
2368                                     MVGPrintf(svg_info->file,"stroke-width %g\n",
2369                                               GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue));
2370                                     break;
2371                                   }
2372                                 break;
2373                               }
2374                             case 't':
2375                             case 'T':
2376                               {
2377                                 if (LocaleCompare(keyword,"text-align") == 0)
2378                                   {
2379                                     MVGPrintf(svg_info->file,"text-align '%s'\n",value);
2380                                     break;
2381                                   }
2382                                 if (LocaleCompare(keyword,"text-anchor") == 0)
2383                                   {
2384                                     MVGPrintf(svg_info->file,"text-anchor '%s'\n",value);
2385                                     break;
2386                                   }
2387                                 if (LocaleCompare(keyword,"text-decoration") == 0)
2388                                   {
2389                                     if (LocaleCompare(value,"underline") == 0)
2390                                       MVGPrintf(svg_info->file,"decorate underline\n");
2391                                     if (LocaleCompare(value,"line-through") == 0)
2392                                       MVGPrintf(svg_info->file,"decorate line-through\n");
2393                                     if (LocaleCompare(value,"overline") == 0)
2394                                       MVGPrintf(svg_info->file,"decorate overline\n");
2395                                     break;
2396                                   }
2397                                 if (LocaleCompare(keyword,"text-antialiasing") == 0)
2398                                   {
2399                                     MVGPrintf(svg_info->file,"text-antialias %d\n",
2400                                               LocaleCompare(value,"true") == 0);
2401                                     break;
2402                                   }
2403                                 if (LocaleCompare(keyword,"transform") == 0)
2404                                   {
2405                                     /* implement style="transform: translate(..." */
2406                                     SVGProcessTransformString(context,value);
2407                                     break;
2408                                   }
2409                                 break;
2410                               }
2411                             default:
2412                               break;
2413                             }
2414                         }
2415                     }
2416                   if (tokens != (char **) NULL)
2417                     {
2418                       for (j=0; tokens[j] != (char *) NULL; j++)
2419                         MagickFreeMemory(tokens[j]);
2420                       MagickFreeMemory(tokens);
2421                     }
2422                   break;
2423                 }
2424               break;
2425             }
2426           case 'T':
2427           case 't':
2428             {
2429               if (LocaleCompare(keyword,"text-align") == 0)
2430                 {
2431                   MVGPrintf(svg_info->file,"text-align '%s'\n",value);
2432                   break;
2433                 }
2434               if (LocaleCompare(keyword,"text-anchor") == 0)
2435                 {
2436                   MVGPrintf(svg_info->file,"text-anchor '%s'\n",value);
2437                   break;
2438                 }
2439               if (LocaleCompare(keyword,"text-decoration") == 0)
2440                 {
2441                   if (LocaleCompare(value,"underline") == 0)
2442                     MVGPrintf(svg_info->file,"decorate underline\n");
2443                   if (LocaleCompare(value,"line-through") == 0)
2444                     MVGPrintf(svg_info->file,"decorate line-through\n");
2445                   if (LocaleCompare(value,"overline") == 0)
2446                     MVGPrintf(svg_info->file,"decorate overline\n");
2447                   break;
2448                 }
2449               if (LocaleCompare(keyword,"text-antialiasing") == 0)
2450                 {
2451                   MVGPrintf(svg_info->file,"text-antialias %d\n",
2452                             LocaleCompare(value,"true") == 0);
2453                   break;
2454                 }
2455               if (LocaleCompare(keyword,"transform") == 0)
2456                 {
2457                   /*
2458                     The code that was here has been refactored into
2459                     function SVGProcessTransformString()
2460                   */
2461                   SVGProcessTransformString(context,value);
2462                   break;
2463                 }
2464               break;
2465             }
2466           case 'V':
2467           case 'v':
2468             {
2469               if (LocaleCompare(keyword,"verts") == 0)
2470                 {
2471                   (void) CloneString(&svg_info->vertices,value);
2472                   break;
2473                 }
2474               if (LocaleCompare(keyword,"viewBox") == 0)
2475                 {
2476                   p=(char *) value;
2477                   (void) MagickGetToken(p,&p,token,MaxTextExtent);
2478                   svg_info->view_box.x=MagickAtoF(token);
2479                   (void) MagickGetToken(p,&p,token,MaxTextExtent);
2480                   if (*token == ',')
2481                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
2482                   svg_info->view_box.y=MagickAtoF(token);
2483                   (void) MagickGetToken(p,&p,token,MaxTextExtent);
2484                   if (*token == ',')
2485                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
2486                   svg_info->view_box.width=MagickAtoF(token);
2487                   (void) MagickGetToken(p,&p,token,MaxTextExtent);
2488                   if (*token == ',')
2489                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
2490                   svg_info->view_box.height=MagickAtoF(token);
2491                   if (svg_info->view_box.width < 0.0 ||
2492                       svg_info->view_box.height < 0.0)
2493                     {
2494                       ThrowException(svg_info->exception,CorruptImageError,
2495                                      NegativeOrZeroImageSize,
2496                                      svg_info->image->filename);
2497                       goto svg_start_element_error;
2498                     }
2499                   if (svg_info->bounds.width == 0)
2500                     svg_info->bounds.width=svg_info->view_box.width;
2501                   if (svg_info->bounds.height == 0)
2502                     svg_info->bounds.height=svg_info->view_box.height;
2503                   break;
2504                 }
2505               break;
2506             }
2507           case 'W':
2508           case 'w':
2509             {
2510               if (LocaleCompare(keyword,"width") == 0)
2511                 {
2512                   svg_info->bounds.width=
2513                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue);
2514                   break;
2515                 }
2516               break;
2517             }
2518           case 'X':
2519           case 'x':
2520             {
2521               if (LocaleCompare(keyword,"x") == 0)
2522                 {
2523                   svg_info->bounds.x=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
2524                   /* update text current position for tspan (already did this if text) */
2525                   if  ( IsTSpan )
2526                     MVGPrintf(svg_info->file,"textx %g\n",svg_info->bounds.x);
2527                   break;
2528                 }
2529               if (LocaleCompare(keyword,"xlink:href") == 0)
2530                 {
2531                   /* process "#identifier" as if it were "url(#identifier)" */
2532                   if  ( value[0] == '#' )
2533                     {
2534                       /* reallocate the needed memory once */
2535                       size_t NewSize = strlen(value) + 6;   /* 6 == url()<null> */
2536                       MagickReallocMemory(char *,svg_info->url,NewSize);
2537                       memcpy(svg_info->url,"url(",4);
2538                       strcpy(svg_info->url+4,value);
2539                       svg_info->url[NewSize-2] = ')';
2540                       svg_info->url[NewSize-1] = '\0';
2541                     }
2542                   else
2543                     (void) CloneString(&svg_info->url,value);
2544                   break;
2545                 }
2546               if (LocaleCompare(keyword,"x1") == 0)
2547                 {
2548                   svg_info->segment.x1=
2549                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
2550                   break;
2551                 }
2552               if (LocaleCompare(keyword,"x2") == 0)
2553                 {
2554                   svg_info->segment.x2=
2555                     GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
2556                   break;
2557                 }
2558               break;
2559             }
2560           case 'Y':
2561           case 'y':
2562             {
2563               if (LocaleCompare(keyword,"y") == 0)
2564                 {
2565                   svg_info->bounds.y=GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
2566                   /* update text current position for tspan (already did this if text) */
2567                   if  ( IsTSpan )
2568                     MVGPrintf(svg_info->file,"texty %g\n",svg_info->bounds.y);
2569                   break;
2570                 }
2571               if (LocaleCompare(keyword,"y1") == 0)
2572                 {
2573                   svg_info->segment.y1=
2574                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
2575                   break;
2576                 }
2577               if (LocaleCompare(keyword,"y2") == 0)
2578                 {
2579                   svg_info->segment.y2=
2580                     GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
2581                   break;
2582                 }
2583               break;
2584             }
2585           default:
2586             break;
2587           }
2588       }
2589   if (svg_info->exception->severity >= ErrorException)
2590     goto svg_start_element_error;
2591 #ifdef BROKEN_SIZE_OPTION
2592   if (LocaleCompare((char *) name,"svg") == 0)
2593     {
2594       if (svg_info->document->encoding != (const xmlChar *) NULL)
2595         MVGPrintf(svg_info->file,"encoding %.1024s\n",
2596                   (char *) svg_info->document->encoding);
2597       if (attributes != (const xmlChar **) NULL)
2598         {
2599           double
2600             sx,
2601             sy;
2602 
2603           if ((svg_info->view_box.width == 0.0) ||
2604               (svg_info->view_box.height == 0.0))
2605             svg_info->view_box=svg_info->bounds;
2606           svg_info->width=(unsigned long) svg_info->bounds.width;
2607           svg_info->height=(unsigned long) svg_info->bounds.height;
2608           MVGPrintf(svg_info->file,"viewbox 0 0 %lu %lu\n",svg_info->width,
2609                     svg_info->height);
2610           /*
2611             Set initial canvas background color to user specified value
2612           */
2613           {
2614             char
2615               tuple[MaxTextExtent];
2616 
2617             GetColorTuple(&svg_info->image_info->background_color,8,True,True,
2618                           tuple);
2619 
2620             MVGPrintf(svg_info->file,"push graphic-context\n");
2621             MVGPrintf(svg_info->file,"fill %s\n", tuple);
2622             MVGPrintf(svg_info->file,"rectangle 0,0 %g,%g\n",
2623                       svg_info->view_box.width,svg_info->view_box.height);
2624             MVGPrintf(svg_info->file,"pop graphic-context\n");
2625           }
2626           sx=(double) svg_info->width/svg_info->view_box.width;
2627           sy=(double) svg_info->height/svg_info->view_box.height;
2628           MVGPrintf(svg_info->file,"affine %g 0 0 %g %g %g\n",sx,sy,
2629                     -sx*svg_info->view_box.x,-sy*svg_info->view_box.y);
2630         }
2631     }
2632 #else
2633   if (LocaleCompare((char *) name,"svg") == 0)
2634     {
2635       if (svg_info->document->encoding != (const xmlChar *) NULL)
2636         (void) fprintf(svg_info->file,"encoding %.1024s\n",
2637                        (char *) svg_info->document->encoding);
2638       if (attributes != (const xmlChar **) NULL)
2639         {
2640           char
2641             *geometry;
2642 
2643           RectangleInfo
2644             page;
2645 
2646           double
2647             sx,
2648             sy;
2649 
2650           if (svg_info->bounds.width < 0.0 || svg_info->bounds.height < 0.0)
2651             {
2652               ThrowException(svg_info->exception,CorruptImageError,
2653                              NegativeOrZeroImageSize,(char *) NULL);
2654               goto svg_start_element_error;
2655             }
2656           if ((svg_info->view_box.width == 0.0) ||
2657               (svg_info->view_box.height == 0.0))
2658             svg_info->view_box=svg_info->bounds;
2659           if (svg_info->view_box.width < 0.0 || svg_info->view_box.height < 0.0)
2660             {
2661               ThrowException(svg_info->exception,CorruptImageError,
2662                              NegativeOrZeroImageSize,(char *) NULL);
2663               goto svg_start_element_error;
2664             }
2665           SetGeometry(svg_info->image,&page);
2666           page.width=(unsigned long) svg_info->bounds.width;
2667           page.height=(unsigned long) svg_info->bounds.height;
2668           geometry=(char *) NULL;
2669           /* at one point we use to try to use either page geometry
2670              or size to set the dimensions of the output page, but
2671              now we only look for size
2672           */
2673 #ifdef PARSE_PAGE_FIRST
2674           if (svg_info->page != (char *) NULL)
2675             geometry=GetPageGeometry(svg_info->page);
2676           else
2677 #endif
2678             if (svg_info->size != (char *) NULL)
2679               geometry=GetPageGeometry(svg_info->size);
2680           if (geometry != (char *) NULL)
2681             {
2682               /*
2683                 A terminating '>' in a geometry string is interpreted to mean that
2684                 the dimensions of an image should only be changed if its width or
2685                 height exceeds the geometry specification.  For an unapparent and
2686                 undocumented reason, a terminating '>', if present, was being nulled
2687                 out, making this feature unusable for SVG files (now fixed).
2688               */
2689               (void) GetMagickGeometry(geometry,&page.x,&page.y,
2690                                        &page.width,&page.height);
2691               MagickFreeMemory(geometry);
2692             }
2693           /* NOTE: the scale factor returned by ExpandAffine() has already been applied
2694              to page.width and page.height
2695           */
2696           (void) MVGPrintf(svg_info->file,"viewbox 0 0 %g %g\n",
2697                            svg_info->view_box.width,svg_info->view_box.height);
2698           sx=(double) page.width/svg_info->view_box.width;
2699           sy=(double) page.height/svg_info->view_box.height;
2700           MVGPrintf(svg_info->file,"affine %g 0 0 %g %g %g\n",sx,sy,
2701                     -sx*svg_info->view_box.x,-sy*svg_info->view_box.y);
2702           /* only set the output width and height if this is the outermost <svg> */
2703           if  ( svg_info->svgPushCount == 1 )
2704             {/*outermost <svg>*/
2705               svg_info->width=page.width;
2706               svg_info->height=page.height;
2707               /* check if background color was specified using <svg ... style="background:color" */
2708               if  ( svg_element_background_color[0] != '\0' )
2709                 {
2710                   MVGPrintf(svg_info->file,"push graphic-context\n");
2711                   MVGPrintf(svg_info->file,"fill %s\n",svg_element_background_color);
2712                   MVGPrintf(svg_info->file,"rectangle 0,0 %g,%g\n",svg_info->view_box.width,svg_info->view_box.height);
2713                   MVGPrintf(svg_info->file,"pop graphic-context\n");
2714                 }
2715             }/*outermost <svg>*/
2716         }
2717     }
2718 #endif
2719   /* Error dispatch point */
2720  svg_start_element_error:;
2721 
2722   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  )");
2723   MagickFreeMemory(units);
2724   MagickFreeMemory(color);
2725 }
2726 
2727 /* Process the "class" definitions inside <style> ... </style> */
2728 static
ProcessStyleClassDefs(SVGInfo * svg_info)2729 void    ProcessStyleClassDefs (
2730   SVGInfo * svg_info
2731   )
2732 {/*ProcessStyleClassDefs*/
2733 
2734   /*
2735     Style defs look like:
2736 
2737       .class1,.class2,.class3{kwd:val;kwd:val;}
2738 
2739     Class name (e.g., ".class1"} can show up multiple times
2740   */
2741 
2742   /* keyword/value pair for an element */
2743   typedef struct ElementValue
2744     {/*ElementValue*/
2745         struct ElementValue     * pNext;  /* next in list */
2746         char *                pKeyword;
2747         char *                pValue;
2748     }/*ElementValue*/
2749   ElementValue;
2750 
2751   /* the keyword/value pair list associated with a class */
2752   typedef struct ClassDef
2753     {/*ClassDef*/
2754       struct ClassDef * pNext;            /* next in list */
2755       struct ClassDef * pActiveNext;      /* next in active list */
2756       char *            pName;            /* class name */
2757       ElementValue      ElementValueHead; /* list head for style element/value pairs */
2758       ElementValue *    pElementValueLast;
2759     }/*ClassDef*/
2760   ClassDef;
2761 
2762   ClassDef ClassDefHead;  /* list head for classes */
2763   ClassDef * pClassDefLast = &ClassDefHead; /* tail ptr for class def list */
2764   ClassDef ClassDefActiveHead;  /* list head for "active" class defs */
2765   ClassDef * pClassDefActiveLast = &ClassDefActiveHead; /* tail ptr for "active" class def list */
2766   ClassDef * pClassDef;
2767   char * pCopyOfText;
2768   char * pString;
2769   char * cp,*cp2;
2770   int c;
2771 
2772   memset(&ClassDefHead,0,sizeof(ClassDefHead));
2773   memset(&ClassDefActiveHead,0,sizeof(ClassDefActiveHead));
2774 
2775   /* a macro to allocate an zero out a new struct instance,
2776       and add it to the end of a linked list */
2777   #define       ADD_NEW_STRUCT(pNew,pLast,TheTypeDef) \
2778   pNew = MagickAllocateMemory(TheTypeDef *,sizeof(TheTypeDef)); \
2779   memset(pNew,0,sizeof(TheTypeDef)); \
2780   pLast = pLast->pNext = pNew
2781 
2782   /* we will get a modifiable value of the string, and delimit
2783       substrings by storing null characters */
2784   pCopyOfText = AcquireString(svg_info->text);
2785   for  ( pString = pCopyOfText; *pString; )
2786     {/*pString loop*/
2787       char * pClassNameList;
2788       char * pStyleElementList;
2789 
2790       while  ( (c = *pString) && isspace(c) )  pString++;   /* skip white space */
2791       if  ( !*pString )  break;   /* found end of string; done */
2792       pClassNameList = pString;
2793 
2794       /* find the end of the comma-separated list of class names */
2795       while  ( (c = *pString) && (c != '{') )  pString++;
2796       if  ( !*pString )
2797         {
2798           /* malformed input: class name list not followed by '{' */
2799           MagickFreeMemory(pCopyOfText);
2800           return;
2801         }
2802       *pString++ = '\0';
2803       pStyleElementList = pString;
2804 
2805       /* extract the class names */
2806       ClassDefActiveHead.pActiveNext = 0;
2807       pClassDefActiveLast = &ClassDefActiveHead;  /* initially, no active class defs */
2808       for  ( cp = pClassNameList; *cp; )
2809         {/*extract class name loop*/
2810 
2811           while  ( (c = *cp) && (isspace(c) || (c ==',')) )  cp++;  /* skip white space/commas */
2812           if  ( *cp == '.' )  cp++;     /* .classname, skip leading period */
2813           if  ( *cp )
2814           {/*found class name*/
2815 
2816               char * pClassName = cp;
2817               while  ( (c = *cp) && !(isspace(c) || (c == ',')) )  cp++;  /* find white space/comma/null */
2818               if  ( *cp )
2819                 *cp++ = '\0';   /* terminate identifier string and increment */
2820               /* add uniquely to list */
2821               for  (  pClassDef = ClassDefHead.pNext;
2822                       pClassDef && (strcmp(pClassName,pClassDef->pName) != 0);
2823                       pClassDef = pClassDef->pNext );
2824               if  ( pClassDef == 0 )
2825                 {/*new class name*/
2826                   ADD_NEW_STRUCT(pClassDef,pClassDefLast,ClassDef);
2827                   pClassDef->pElementValueLast = &pClassDef->ElementValueHead;
2828                   pClassDef->pName = pClassName;
2829                 }/*new class name*/
2830               pClassDefActiveLast = pClassDefActiveLast->pActiveNext = pClassDef;   /* add to active list */
2831 
2832               }/*found class name*/
2833 
2834         }/*extract class name loop*/
2835 
2836       /* find the end of the style elements */
2837       while  ( (c = *pString) && (c != '}') )  pString++;
2838       if  ( !*pString )
2839         {
2840           /* malformed input: style elements not terminated by '{' */
2841           MagickFreeMemory(pCopyOfText);
2842           return;
2843         }
2844       *pString++ = '\0';  /* advance past '}' for next loop pass */
2845 
2846       /* get the style elements */
2847       for  ( cp = pStyleElementList; *cp; )
2848         {/*extract style elements loop*/
2849 
2850           /* looking for <space><classname><space>: */
2851           while  ( (c = *cp) && isspace(c) )  cp++;   /* skip white space */
2852           if  ( *cp )
2853             {/*found style element*/
2854 
2855               char * pStyleElement = cp;
2856               while  ( (c = *cp) && (c != ':') )  cp++;   /* find colon/null */
2857               for  ( cp2 = cp-1; isspace(*cp2); *cp2-- = '\0');   /* trim white space */
2858               if  ( *cp )
2859                 *cp++ = '\0';   /* terminate style element string and increment */
2860 
2861               /* looking for <space><style-value>; */
2862               while  ( (c = *cp) && isspace(c) )  cp++;   /* skip white space */
2863               if  ( *cp )
2864                 {/*found style element value*/
2865 
2866                   char * pStyleValue = cp;
2867                   while  ( (c = *cp) && (c != ';') )  cp++;   /* find semi-colon/null */
2868                   for  ( cp2 = cp-1; isspace(*cp2); *cp2-- = '\0');   /* trim white space */
2869                   if  ( *cp )
2870                     *cp++ = '\0';   /* terminate style value string and increment */
2871 
2872                   /* add style element/value pair to each active class def */
2873                   for  ( pClassDef = ClassDefActiveHead.pActiveNext; pClassDef; pClassDef = pClassDef->pActiveNext )
2874                     {
2875                       ElementValue * pEV;
2876                       ADD_NEW_STRUCT(pEV,pClassDef->pElementValueLast,ElementValue);
2877                       pEV->pKeyword = pStyleElement;
2878                       pEV->pValue = pStyleValue;
2879                     }
2880 
2881                 }/*found style element value*/
2882 
2883             }/*found style element*/
2884 
2885         }/*extract style elements loop*/
2886 
2887     }/*pString loop*/
2888 
2889   /* emit class definitions */
2890   for  ( pClassDef = ClassDefHead.pNext; pClassDef; pClassDef = pClassDef->pNext )
2891     {/*pClassDef loop*/
2892 
2893       ElementValue * pEV;
2894       MVGPrintf(svg_info->file,"push class '%s'\n",pClassDef->pName);
2895       /* NOTE: we allow class definitions that are empty */
2896       for  ( pEV = pClassDef->ElementValueHead.pNext; pEV; pEV = pEV->pNext )
2897         {/*pEV loop*/
2898 
2899           char * keyword = pEV->pKeyword;
2900           char * value = pEV->pValue;
2901           /* switch below was copied/pasted from elsewhere and modified */
2902           switch (*keyword)
2903             {/*keyword*/
2904 
2905             case 'C':
2906             case 'c':
2907               {/*Cc*/
2908                 if (LocaleCompare(keyword,"clip-path") == 0)
2909                   {
2910                     MVGPrintf(svg_info->file,"clip-path '%s'\n",value);
2911                     break;
2912                   }
2913                 if (LocaleCompare(keyword,"clip-rule") == 0)
2914                   {
2915                     MVGPrintf(svg_info->file,"clip-rule '%s'\n",value);
2916                     break;
2917                   }
2918                 if (LocaleCompare(keyword,"clipPathUnits") == 0)
2919                   {
2920                     MVGPrintf(svg_info->file,"clip-units '%s'\n",value);
2921                     break;
2922                   }
2923                 break;
2924               }/*Cc*/
2925 
2926             case 'F':
2927             case 'f':
2928                {/*Ff*/
2929                 if  ( (LocaleCompare(keyword,"fill") == 0) || (LocaleCompare(keyword,"fillcolor") == 0) )
2930                   {
2931                     MVGPrintf(svg_info->file,"fill '%s'\n",value);
2932                     break;
2933                   }
2934                 if (LocaleCompare(keyword,"fill-rule") == 0)
2935                   {
2936                     MVGPrintf(svg_info->file,"fill-rule '%s'\n",value);
2937                     break;
2938                   }
2939                 if (LocaleCompare(keyword,"fill-opacity") == 0)
2940                   {
2941                     MVGPrintf(svg_info->file,"fill-opacity '%s'\n",value);
2942                     break;
2943                   }
2944                 if (LocaleCompare(keyword,"font-family") == 0)
2945                   {
2946                     /*
2947                       Deal with Adobe Illustrator 10.0 which double-quotes
2948                       font-family.  Maybe we need a generalized solution for
2949                       this.
2950                     */
2951                     size_t n;
2952                     if  ( (value[0] == '\'') && (value[(n = (strlen(value)-1))] == '\'') )
2953                       {
2954                         size_t i;
2955                         FILE * fp = svg_info->file;
2956                         MVGPrintf(fp,"font-family '");
2957                         for(i = 1; i < n; i++)
2958                           fputc(value[i],fp);
2959                         MVGPrintf(fp,"'\n");
2960                       }
2961                     else
2962                       MVGPrintf(svg_info->file,"font-family '%s'\n",value);
2963                     break;
2964                   }
2965                 if (LocaleCompare(keyword,"font-stretch") == 0)
2966                   {
2967                     MVGPrintf(svg_info->file,"font-stretch '%s'\n",value);
2968                     break;
2969                   }
2970                 if (LocaleCompare(keyword,"font-style") == 0)
2971                   {
2972                     MVGPrintf(svg_info->file,"font-style '%s'\n",value);
2973                     break;
2974                   }
2975                 if (LocaleCompare(keyword,"font-size") == 0)
2976                   {
2977                     if (LocaleCompare(value,"medium") == 0)
2978                       svg_info->pointsize = 12;
2979                     else
2980                       svg_info->pointsize = GetUserSpaceCoordinateValue(svg_info,0,value,MagickTrue);
2981                     MVGPrintf(svg_info->file,"font-size %g\n",svg_info->pointsize);
2982                     break;
2983                   }
2984                 if (LocaleCompare(keyword,"font-weight") == 0)
2985                   {
2986                     MVGPrintf(svg_info->file,"font-weight '%s'\n",value);
2987                     break;
2988                   }
2989                 break;
2990               }/*Ff*/
2991 
2992             case 'O':
2993             case 'o':
2994               {/*Oo*/
2995                 if (LocaleCompare(keyword,"offset") == 0)
2996                   {
2997                    MVGPrintf(svg_info->file,"offset %g\n",GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse));
2998                    break;
2999                   }
3000                 if (LocaleCompare(keyword,"opacity") == 0)
3001                   {
3002                     MVGPrintf(svg_info->file,"opacity '%s'\n",value);
3003                     break;
3004                   }
3005                 break;
3006               }/*Oo*/
3007 
3008             case 'S':
3009             case 's':
3010               {/*Ss*/
3011                 if (LocaleCompare(keyword,"stop-color") == 0)
3012                   {
3013                     (void) CloneString(&svg_info->stop_color,value);
3014                     break;
3015                   }
3016                 if (LocaleCompare(keyword,"stroke") == 0)
3017                   {
3018                     MVGPrintf(svg_info->file,"stroke '%s'\n",value);
3019                     break;
3020                   }
3021                 if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
3022                   {
3023                     MVGPrintf(svg_info->file,"stroke-antialias %d\n",LocaleCompare(value,"true") == 0);
3024                     break;
3025                   }
3026                 if (LocaleCompare(keyword,"stroke-dasharray") == 0)
3027                   {
3028                     MVGPrintf(svg_info->file,"stroke-dasharray %s\n",value);
3029                     break;
3030                   }
3031                 if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
3032                   {
3033                     double dashoffset=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
3034                     MVGPrintf(svg_info->file,"stroke-dashoffset %g\n",dashoffset);
3035                     break;
3036                   }
3037                 if (LocaleCompare(keyword,"stroke-linecap") == 0)
3038                   {
3039                     MVGPrintf(svg_info->file,"stroke-linecap '%s'\n",value);
3040                     break;
3041                   }
3042                 if (LocaleCompare(keyword,"stroke-linejoin") == 0)
3043                   {
3044                     MVGPrintf(svg_info->file,"stroke-linejoin '%s'\n",value);
3045                     break;
3046                   }
3047                 if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
3048                   {
3049                     double stroke_miterlimit;
3050                     if ((MagickAtoFChk(value,&stroke_miterlimit) != MagickPass) || stroke_miterlimit < 1.0)
3051                       {
3052                         errno=0;
3053                         ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,value);
3054                         break;
3055                       }
3056                     MVGPrintf(svg_info->file,"stroke-miterlimit '%ld'\n",(long) stroke_miterlimit);
3057                     break;
3058                   }
3059                 if (LocaleCompare(keyword,"stroke-opacity") == 0)
3060                   {
3061                     MVGPrintf(svg_info->file,"stroke-opacity '%s'\n",value);
3062                     break;
3063                   }
3064                 if (LocaleCompare(keyword,"stroke-width") == 0)
3065                   {
3066                     MVGPrintf(svg_info->file,"stroke-width %g\n",GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue));
3067                     break;
3068                   }
3069                 break;
3070               }/*Ss*/
3071 
3072             case 'T':
3073             case 't':
3074               {/*Tt*/
3075                 if (LocaleCompare(keyword,"text-align") == 0)
3076                   {
3077                     MVGPrintf(svg_info->file,"text-align '%s'\n",value);
3078                     break;
3079                   }
3080                 if (LocaleCompare(keyword,"text-anchor") == 0)
3081                   {
3082                     MVGPrintf(svg_info->file,"text-anchor '%s'\n",value);
3083                     break;
3084                   }
3085                 if (LocaleCompare(keyword,"text-decoration") == 0)
3086                   {
3087                     if (LocaleCompare(value,"underline") == 0)
3088                       MVGPrintf(svg_info->file,"decorate underline\n");
3089                     if (LocaleCompare(value,"line-through") == 0)
3090                       MVGPrintf(svg_info->file,"decorate line-through\n");
3091                     if (LocaleCompare(value,"overline") == 0)
3092                       MVGPrintf(svg_info->file,"decorate overline\n");
3093                     break;
3094                   }
3095                 if (LocaleCompare(keyword,"text-antialiasing") == 0)
3096                   {
3097                     MVGPrintf(svg_info->file,"text-antialias %d\n",LocaleCompare(value,"true") == 0);
3098                     break;
3099                   }
3100                 if (LocaleCompare(keyword,"transform") == 0)
3101                   {/*style="transform: ...*/
3102                     SVGProcessTransformString((void *)svg_info,value);
3103                     break;
3104                   }/*style="transform: ...*/
3105                 break;
3106               }/*Tt*/
3107 
3108             default:
3109             break;
3110 
3111             }/*keyword*/
3112 
3113         }/*pEV loop*/
3114 
3115       MVGPrintf(svg_info->file,"pop class\n");
3116 
3117     }/*pClassDef loop*/
3118 
3119   /* clean up */
3120   {
3121     ClassDef * pClassDef;
3122     for(pClassDef = ClassDefHead.pNext; pClassDef; )
3123       {
3124         ElementValue *pEV;
3125         ClassDef * pClassDefTemp = pClassDef;
3126         for(pEV = pClassDef->ElementValueHead.pNext; pEV; )
3127           {
3128             ElementValue * pEVTemp = pEV;
3129             pEV = pEV->pNext;
3130             MagickFreeMemory(pEVTemp);
3131           }
3132         pClassDef = pClassDef->pNext;
3133         MagickFreeMemory(pClassDefTemp);
3134       }
3135   }
3136   MagickFreeMemory(pCopyOfText);
3137 
3138 #undef  ADD_NEW_STRUCT
3139 }/*ProcessStyleClassDefs*/
3140 
3141 static void
SVGEndElement(void * context,const xmlChar * name)3142 SVGEndElement(void *context,const xmlChar *name)
3143 {
3144   SVGInfo
3145     *svg_info;
3146 
3147   /*
3148     Called when the end of an element has been detected.
3149   */
3150   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3151                         "  SAX.endElement(%.1024s)",name);
3152   svg_info=(SVGInfo *) context;
3153   if (strchr((char *) name,':') != (char *) NULL)
3154     {
3155       /*
3156         Skip over namespace.
3157       */
3158       for ( ; *name != ':'; name++) ;
3159       name++;
3160     }
3161   switch (*name)
3162     {
3163     case 'C':
3164     case 'c':
3165       {
3166         if (LocaleCompare((char *) name,"circle") == 0)
3167           {
3168             MVGPrintf(svg_info->file,"circle %g,%g %g,%g\n",svg_info->element.cx,
3169                       svg_info->element.cy,svg_info->element.cx,svg_info->element.cy+
3170                       svg_info->element.minor);
3171             MVGPrintf(svg_info->file,"pop graphic-context\n");
3172             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "pop id" if warranted */
3173               {
3174                 svg_info->idLevelInsideDefs = 0;
3175                 MVGPrintf(svg_info->file,"pop id\n");
3176               }
3177             break;
3178           }
3179         if (LocaleCompare((char *) name,"clipPath") == 0)
3180           {
3181             MVGPrintf(svg_info->file,"pop clip-path\n");
3182             break;
3183           }
3184         break;
3185       }
3186     case 'D':
3187     case 'd':
3188       {
3189         if (LocaleCompare((char *) name,"defs") == 0)
3190           {
3191             svg_info->defsPushCount--;
3192             MVGPrintf(svg_info->file,"pop defs\n");
3193             break;
3194           }
3195         if (LocaleCompare((char *) name,"desc") == 0)
3196           {
3197             register char
3198               *p;
3199 
3200             Strip(svg_info->text);
3201             if (*svg_info->text == '\0')
3202               break;
3203             (void) fputc('#',svg_info->file);
3204             for (p=svg_info->text; *p != '\0'; p++)
3205               {
3206                 (void) fputc(*p,svg_info->file);
3207                 if (*p == '\n')
3208                   (void) fputc('#',svg_info->file);
3209               }
3210             (void) fputc('\n',svg_info->file);
3211             *svg_info->text='\0';
3212             break;
3213           }
3214         break;
3215       }
3216     case 'E':
3217     case 'e':
3218       {
3219         if (LocaleCompare((char *) name,"ellipse") == 0)
3220           {
3221             double
3222               angle;
3223 
3224             angle=svg_info->element.angle;
3225             MVGPrintf(svg_info->file,"ellipse %g,%g %g,%g 0,360\n",
3226                       svg_info->element.cx,svg_info->element.cy,
3227                       angle == 0.0 ? svg_info->element.major : svg_info->element.minor,
3228                       angle == 0.0 ? svg_info->element.minor : svg_info->element.major);
3229             MVGPrintf(svg_info->file,"pop graphic-context\n");
3230             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "pop id" if warranted */
3231               {
3232                 svg_info->idLevelInsideDefs = 0;
3233                 MVGPrintf(svg_info->file,"pop id\n");
3234               }
3235             break;
3236           }
3237         break;
3238       }
3239     case 'F':
3240     case 'f':
3241       {
3242         /*
3243           For now we are ignoring "foreignObject".  However, we do a push/pop
3244           graphic-context so that any settings (e.g., fill) are consumed and
3245           discarded.  Otherwise they might persist and "leak" into the graphic
3246           elements that follow.
3247         */
3248         if (LocaleCompare((char *) name,"foreignObject") == 0)
3249           {
3250             MVGPrintf(svg_info->file,"pop graphic-context\n");
3251             break;
3252           }
3253         break;
3254       }
3255     case 'G':
3256     case 'g':
3257       {
3258         if (LocaleCompare((char *) name,"g") == 0)
3259           {
3260             MVGPrintf(svg_info->file,"pop graphic-context\n");
3261             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "pop id" if warranted */
3262               {
3263                 svg_info->idLevelInsideDefs = 0;
3264                 MVGPrintf(svg_info->file,"pop id\n");
3265               }
3266             break;
3267           }
3268         break;
3269       }
3270     case 'I':
3271     case 'i':
3272       {
3273         if (LocaleCompare((char *) name,"image") == 0)
3274           {
3275             MVGPrintf(svg_info->file,"image Copy %g,%g %g,%g '%s'\n",
3276                       svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.width,
3277                       svg_info->bounds.height,svg_info->url);
3278             MVGPrintf(svg_info->file,"pop graphic-context\n");
3279             break;
3280           }
3281         break;
3282       }
3283     case 'L':
3284     case 'l':
3285       {
3286         if (LocaleCompare((char *) name,"line") == 0)
3287           {
3288             MVGPrintf(svg_info->file,"line %g,%g %g,%g\n",svg_info->segment.x1,
3289                       svg_info->segment.y1,svg_info->segment.x2,svg_info->segment.y2);
3290             MVGPrintf(svg_info->file,"pop graphic-context\n");
3291             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "pop id" if warranted */
3292               {
3293                 svg_info->idLevelInsideDefs = 0;
3294                 MVGPrintf(svg_info->file,"pop id\n");
3295               }
3296             break;
3297           }
3298         if (LocaleCompare((char *) name,"linearGradient") == 0)
3299           {
3300             MVGPrintf(svg_info->file,"pop gradient\n");
3301             break;
3302           }
3303         break;
3304       }
3305     case 'M':
3306     case 'm':
3307       {
3308         if (LocaleCompare((char *) name,"mask") == 0)   /* added mask */
3309           {
3310             MVGPrintf(svg_info->file,"pop mask\n");
3311             break;
3312           }
3313         break;
3314       }
3315     case 'P':
3316     case 'p':
3317       {
3318         if (LocaleCompare((char *) name,"pattern") == 0)
3319           {
3320             MVGPrintf(svg_info->file,"pop pattern\n");
3321             break;
3322           }
3323         if (LocaleCompare((char *) name,"path") == 0)
3324           {
3325             MVGPrintf(svg_info->file,"path '%s'\n",svg_info->vertices);
3326             MVGPrintf(svg_info->file,"pop graphic-context\n");
3327             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "pop id" if warranted */
3328               {
3329                 svg_info->idLevelInsideDefs = 0;
3330                 MVGPrintf(svg_info->file,"pop id\n");
3331               }
3332             break;
3333           }
3334         if (LocaleCompare((char *) name,"polygon") == 0)
3335           {
3336             MVGPrintf(svg_info->file,"polygon %s\n",svg_info->vertices);
3337             MVGPrintf(svg_info->file,"pop graphic-context\n");
3338             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "pop id" if warranted */
3339               {
3340                 svg_info->idLevelInsideDefs = 0;
3341                 MVGPrintf(svg_info->file,"pop id\n");
3342               }
3343             break;
3344           }
3345         if (LocaleCompare((char *) name,"polyline") == 0)
3346           {
3347             MVGPrintf(svg_info->file,"polyline %s\n",svg_info->vertices);
3348             MVGPrintf(svg_info->file,"pop graphic-context\n");
3349             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "pop id" if warranted */
3350               {
3351                 svg_info->idLevelInsideDefs = 0;
3352                 MVGPrintf(svg_info->file,"pop id\n");
3353               }
3354             break;
3355           }
3356         break;
3357       }
3358     case 'R':
3359     case 'r':
3360       {
3361         if (LocaleCompare((char *) name,"radialGradient") == 0)
3362           {
3363             MVGPrintf(svg_info->file,"pop gradient\n");
3364             break;
3365           }
3366         if (LocaleCompare((char *) name,"rect") == 0)
3367           {
3368             if ((svg_info->radius.x == 0.0) && (svg_info->radius.y == 0.0))
3369               {
3370                 MVGPrintf(svg_info->file,"rectangle %g,%g %g,%g\n",
3371                           svg_info->bounds.x,svg_info->bounds.y,
3372                           svg_info->bounds.x+svg_info->bounds.width,
3373                           svg_info->bounds.y+svg_info->bounds.height);
3374                 MVGPrintf(svg_info->file,"pop graphic-context\n");
3375               if  ( svg_info->idLevelInsideDefs == svg_info->n )        /* emit a "pop id" if warranted */
3376                 {
3377                   svg_info->idLevelInsideDefs = 0;
3378                   MVGPrintf(svg_info->file,"pop id\n");
3379                 }
3380                 break;
3381               }
3382             if (svg_info->radius.x == 0.0)
3383               svg_info->radius.x=svg_info->radius.y;
3384             if (svg_info->radius.y == 0.0)
3385               svg_info->radius.y=svg_info->radius.x;
3386             MVGPrintf(svg_info->file,"roundRectangle %g,%g %g,%g %g,%g\n",
3387                       svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.x+
3388                       svg_info->bounds.width,svg_info->bounds.y+svg_info->bounds.height,
3389                       svg_info->radius.x,svg_info->radius.y);
3390             svg_info->radius.x=0.0;
3391             svg_info->radius.y=0.0;
3392             MVGPrintf(svg_info->file,"pop graphic-context\n");
3393             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "pop id" if warranted */
3394               {
3395                 svg_info->idLevelInsideDefs = 0;
3396                 MVGPrintf(svg_info->file,"pop id\n");
3397               }
3398             break;
3399           }
3400         break;
3401       }
3402     case 'S':
3403     case 's':
3404       {
3405         if (LocaleCompare((char *) name,"stop") == 0)
3406           {
3407             MVGPrintf(svg_info->file,"stop-color '%s' %s\n",svg_info->stop_color,
3408                       svg_info->offset);
3409             break;
3410           }
3411         /* element "style" inside <defs> */
3412         if (LocaleCompare((char *) name,"style") == 0)
3413           {
3414             /* the style definitions are in svg_info->text */
3415             ProcessStyleClassDefs(svg_info);
3416             break;
3417           }
3418         if (LocaleCompare((char *) name,"svg") == 0)
3419           {
3420             svg_info->svgPushCount--;
3421             MVGPrintf(svg_info->file,"pop graphic-context\n");
3422             break;
3423           }
3424         break;
3425       }
3426     case 'T':
3427     case 't':
3428       {
3429         if (LocaleCompare((char *) name,"text") == 0)
3430           {
3431             Strip(svg_info->text);
3432             if (*svg_info->text != '\0')
3433               {
3434                 char
3435                   *text;
3436 
3437                 text=EscapeString(svg_info->text,'\'');
3438                 MVGPrintf(svg_info->file,"textc '%s'\n",text);  /* write text at current position */
3439                 MagickFreeMemory(text);
3440                 *svg_info->text='\0';
3441               }
3442             MVGPrintf(svg_info->file,"pop graphic-context\n");
3443             if  ( svg_info->idLevelInsideDefs == svg_info->n )  /* emit a "pop id" if warranted */
3444               {
3445                 svg_info->idLevelInsideDefs = 0;
3446                 MVGPrintf(svg_info->file,"pop id\n");
3447               }
3448             break;
3449           }
3450         if (LocaleCompare((char *) name,"tspan") == 0)
3451           {
3452             Strip(svg_info->text);
3453             if (*svg_info->text != '\0')
3454               {
3455                 char
3456                   *text;
3457 
3458                 text=EscapeString(svg_info->text,'\'');
3459                 MVGPrintf(svg_info->file,"textc '%s'\n",text);  /* write text at current position */
3460                 MagickFreeMemory(text);
3461                 /*
3462                   The code that used to be here to compute the next text position has been eliminated.
3463                   The reason is that at this point in the code we may not know the font or font size
3464                   (they may be hidden in a "class" definition), so we can't really do that computation.
3465                   This functionality is now handled by DrawImage() in render.c.
3466                 */
3467                 *svg_info->text='\0';
3468               }
3469             MVGPrintf(svg_info->file,"pop graphic-context\n");
3470             break;
3471           }
3472         if (LocaleCompare((char *) name,"title") == 0)
3473           {
3474             Strip(svg_info->text);
3475             if (*svg_info->text == '\0')
3476               break;
3477             (void) CloneString(&svg_info->title,svg_info->text);
3478             *svg_info->text='\0';
3479             break;
3480           }
3481         break;
3482       }
3483     case 'U':
3484     case 'u':
3485       {
3486         if (LocaleCompare((char *) name,"use") == 0)
3487           {
3488             /*
3489               If the "use" had a "transform" attribute it has already been output to the MVG file.
3490 
3491               According to the SVG spec for "use":
3492 
3493               In the generated content, the 'use' will be replaced by 'g', where all attributes
3494               from the 'use' element except for 'x', 'y', 'width', 'height' and 'xlink:href' are
3495               transferred to the generated 'g' element. An additional transformation translate(x,y)
3496               is appended to the end (i.e., right-side) of the 'transform' attribute on the generated
3497               'g', where x and y represent the values of the 'x' and 'y' attributes on the 'use'
3498               element. The referenced object and its contents are deep-cloned into the generated tree.
3499             */
3500             if  ( (svg_info->bounds.x != 0.0) || (svg_info->bounds.y != 0.0) )
3501               MVGPrintf(svg_info->file,"translate %g,%g\n",svg_info->bounds.x,svg_info->bounds.y);
3502 
3503             /* NOTE: not implementing "width" and "height" for now */
3504 
3505             MVGPrintf(svg_info->file,"use '%s'\n",svg_info->url);
3506             MVGPrintf(svg_info->file,"pop graphic-context\n");
3507             break;
3508           }
3509         break;
3510       }
3511     default:
3512       break;
3513     }
3514   (void) memset(&svg_info->segment,0,sizeof(svg_info->segment));
3515   (void) memset(&svg_info->element,0,sizeof(svg_info->element));
3516   *svg_info->text='\0';
3517   svg_info->n--;
3518 }
3519 
3520 static void
SVGCharacters(void * context,const xmlChar * c,int length)3521 SVGCharacters(void *context,const xmlChar *c,int length)
3522 {
3523   register char
3524     *p;
3525 
3526   register size_t
3527     i;
3528 
3529   SVGInfo
3530     *svg_info;
3531 
3532   /*
3533     Receiving some characters from the parser.
3534   */
3535   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3536                         "  SAX.characters(%.1024s,%d)",c,length);
3537   svg_info=(SVGInfo *) context;
3538   if (svg_info->text != (char *) NULL)
3539     {
3540       MagickReallocMemory(char *,svg_info->text,strlen(svg_info->text)+length+1);
3541     }
3542   else
3543     {
3544       svg_info->text=MagickAllocateMemory(char *,(size_t) length+1);
3545       if (svg_info->text != (char *) NULL)
3546         *svg_info->text='\0';
3547     }
3548   if (svg_info->text == (char *) NULL)
3549     return;
3550   p=svg_info->text+strlen(svg_info->text);
3551   for (i=0; i < (size_t) length; i++)
3552     *p++=c[i];
3553   *p='\0';
3554 }
3555 
3556 static void
SVGReference(void * context,const xmlChar * name)3557 SVGReference(void *context,const xmlChar *name)
3558 {
3559   SVGInfo
3560     *svg_info;
3561 
3562   xmlParserCtxtPtr
3563     parser;
3564 
3565   /*
3566     Called when an entity reference is detected.
3567   */
3568   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3569                         "  SAX.reference(%.1024s)",name);
3570   svg_info=(SVGInfo *) context;
3571   parser=svg_info->parser;
3572   if (*name == '#')
3573     (void) xmlAddChild(parser->node,xmlNewCharRef(svg_info->document,name));
3574   else
3575     (void) xmlAddChild(parser->node,xmlNewReference(svg_info->document,name));
3576 }
3577 
3578 static void
SVGIgnorableWhitespace(void * context,const xmlChar * c,int length)3579 SVGIgnorableWhitespace(void *context,const xmlChar *c,int length)
3580 {
3581   /*   SVGInfo */
3582   /*     *svg_info; */
3583 
3584   ARG_NOT_USED(context);
3585   /*
3586     Receiving some ignorable whitespaces from the parser.
3587   */
3588   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3589                         "  SAX.ignorableWhitespace(%.30s, %d)",c,length);
3590   /*   svg_info=(SVGInfo *) context; */
3591 }
3592 
3593 static void
SVGProcessingInstructions(void * context,const xmlChar * target,const xmlChar * data)3594 SVGProcessingInstructions(void *context,const xmlChar *target,
3595                                       const xmlChar *data)
3596 {
3597   /*   SVGInfo */
3598   /*     *svg_info; */
3599 
3600   ARG_NOT_USED(context);
3601   /*
3602     A processing instruction has been parsed.
3603   */
3604   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3605                         "  SAX.processingInstruction(%.1024s, %.1024s)",
3606                         target,data);
3607   /*   svg_info=(SVGInfo *) context; */
3608 }
3609 
3610 static void
SVGComment(void * context,const xmlChar * value)3611 SVGComment(void *context,const xmlChar *value)
3612 {
3613   SVGInfo
3614     *svg_info;
3615 
3616   /*
3617     A comment has been parsed.
3618   */
3619   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3620                         "  SAX.comment(%.1024s)",value);
3621   svg_info=(SVGInfo *) context;
3622   if (svg_info->comment != (char *) NULL)
3623     (void) ConcatenateString(&svg_info->comment,"\n");
3624   (void) ConcatenateString(&svg_info->comment,(char *) value);
3625 }
3626 
3627 static void
3628 SVGWarning(void *context,const char *format,...) MAGICK_ATTRIBUTE((__format__ (__printf__,2,3)));
3629 
3630 static void
SVGWarning(void * context,const char * format,...)3631 SVGWarning(void *context,const char *format,...)
3632 {
3633   char
3634     reason[MaxTextExtent];
3635 
3636   SVGInfo
3637     *svg_info;
3638 
3639   va_list
3640     operands;
3641 
3642   /**
3643      Display and format a warning messages, gives file, line, position and
3644      extra parameters.
3645   */
3646   va_start(operands,format);
3647   svg_info=(SVGInfo *) context;
3648   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.warning: ");
3649   (void) LogMagickEvent(CoderEvent,GetMagickModule(),format,operands);
3650 #if !defined(HAVE_VSNPRINTF)
3651   (void) vsprintf(reason,format,operands);
3652 #else
3653   (void) vsnprintf(reason,MaxTextExtent,format,operands);
3654 #endif
3655   ThrowException2(svg_info->exception,CoderWarning,reason,(char *) NULL);
3656   va_end(operands);
3657 }
3658 
3659 static void
3660 SVGError(void *context,const char *format,...) MAGICK_ATTRIBUTE((__format__ (__printf__,2,3)));
3661 
3662 static void
SVGError(void * context,const char * format,...)3663 SVGError(void *context,const char *format,...)
3664 {
3665   char
3666     reason[MaxTextExtent];
3667 
3668   SVGInfo
3669     *svg_info;
3670 
3671   va_list
3672     operands;
3673 
3674   /*
3675     Display and format a error formats, gives file, line, position and
3676     extra parameters.
3677   */
3678   va_start(operands,format);
3679   svg_info=(SVGInfo *) context;
3680   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  SAX.error: ");
3681   (void) LogMagickEvent(CoderEvent,GetMagickModule(),format,operands);
3682 #if !defined(HAVE_VSNPRINTF)
3683   (void) vsprintf(reason,format,operands);
3684 #else
3685   (void) vsnprintf(reason,MaxTextExtent,format,operands);
3686 #endif
3687   ThrowException2(svg_info->exception,CoderError,reason,(char *) NULL);
3688   va_end(operands);
3689 }
3690 
3691 static void
SVGCDataBlock(void * context,const xmlChar * value,int length)3692 SVGCDataBlock(void *context,const xmlChar *value,int length)
3693 {
3694   SVGInfo
3695     *svg_info;
3696 
3697   xmlNodePtr
3698     child;
3699 
3700   xmlParserCtxtPtr
3701     parser;
3702 
3703   /*
3704     Called when a pcdata block has been parsed.
3705   */
3706   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3707                         "  SAX.pcdata(%.1024s, %d)",value,length);
3708   svg_info=(SVGInfo *) context;
3709   parser=svg_info->parser;
3710   child=xmlGetLastChild(parser->node);
3711   if ((child != (xmlNodePtr) NULL) && (child->type == XML_CDATA_SECTION_NODE))
3712     {
3713       (void) xmlTextConcat(child,value,length);
3714       return;
3715     }
3716   /* Create a new node containing a CDATA block. */
3717   /* FIXME: parser->myDoc is null so add fails.  What do do? */
3718   child=xmlNewCDataBlock(parser->myDoc,value,length);
3719   if (xmlAddChild(parser->node,child) == (xmlNodePtr) NULL)
3720     xmlFreeNode(child);
3721 }
3722 
3723 static void
SVGExternalSubset(void * context,const xmlChar * name,const xmlChar * external_id,const xmlChar * system_id)3724 SVGExternalSubset(void *context,const xmlChar *name,
3725                   const xmlChar *external_id,const xmlChar *system_id)
3726 {
3727   SVGInfo
3728     *svg_info;
3729 
3730   xmlParserCtxt
3731     parser_context;
3732 
3733   xmlParserCtxtPtr
3734     parser;
3735 
3736   xmlParserInputPtr
3737     input;
3738 
3739   /*
3740     Does this document has an external subset?
3741   */
3742   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3743                         "  SAX.externalSubset(%.1024s, %.1024s, %.1024s)",name,
3744                         (external_id != (const xmlChar *) NULL ? (char *) external_id : "none"),
3745                         (system_id != (const xmlChar *) NULL ? (char *) system_id : "none"));
3746   svg_info=(SVGInfo *) context;
3747   parser=svg_info->parser;
3748   if (((external_id == NULL) && (system_id == NULL)) ||
3749       (!parser->validate || !parser->wellFormed || !svg_info->document))
3750     return;
3751   input=SVGResolveEntity(context,external_id,system_id);
3752   if (input == NULL)
3753     return;
3754   (void) xmlNewDtd(svg_info->document,name,external_id,system_id);
3755   parser_context=(*parser);
3756   parser->inputTab=(xmlParserInputPtr *) xmlMalloc(5*sizeof(*parser->inputTab));
3757   if (parser->inputTab == (xmlParserInputPtr *) NULL)
3758     {
3759       parser->errNo=XML_ERR_NO_MEMORY;
3760       parser->input=parser_context.input;
3761       parser->inputNr=parser_context.inputNr;
3762       parser->inputMax=parser_context.inputMax;
3763       parser->inputTab=parser_context.inputTab;
3764       return;
3765     }
3766   parser->inputNr=0;
3767   parser->inputMax=5;
3768   parser->input=NULL;
3769   xmlPushInput(parser,input);
3770   (void) xmlSwitchEncoding(parser,xmlDetectCharEncoding(parser->input->cur,4));
3771   if (input->filename == (char *) NULL)
3772     input->filename=(char *) xmlStrdup(system_id);
3773   input->line=1;
3774   input->col=1;
3775   input->base=parser->input->cur;
3776   input->cur=parser->input->cur;
3777   input->free=NULL;
3778   xmlParseExternalSubset(parser,external_id,system_id);
3779   while (parser->inputNr > 1)
3780     (void) xmlPopInput(parser);
3781   xmlFreeInputStream(parser->input);
3782   xmlFree(parser->inputTab);
3783   parser->input=parser_context.input;
3784   parser->inputNr=parser_context.inputNr;
3785   parser->inputMax=parser_context.inputMax;
3786   parser->inputTab=parser_context.inputTab;
3787 }
3788 
3789 static Image *
ReadSVGImage(const ImageInfo * image_info,ExceptionInfo * exception)3790 ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
3791 {
3792   xmlSAXHandler
3793     SAXModules;
3794 
3795   char
3796     filename[MaxTextExtent],
3797     geometry[MaxTextExtent],
3798     message[MaxTextExtent];
3799 
3800   FILE
3801     *file;
3802 
3803   Image
3804     *image;
3805 
3806   size_t
3807     n;
3808 
3809   SVGInfo
3810     svg_info;
3811 
3812   unsigned int
3813     status;
3814 
3815   xmlSAXHandlerPtr
3816     SAXHandler;
3817 
3818   assert(image_info != (const ImageInfo *) NULL);
3819   assert(image_info->signature == MagickSignature);
3820   assert(exception != (ExceptionInfo *) NULL);
3821   assert(exception->signature == MagickSignature);
3822 
3823   /*
3824     Libxml initialization.  We call it here since initialization can
3825     be expensive and we don't know when/if libxml will be
3826     needed. Should normally be called at program start-up but may be
3827     called several times since libxml uses a flag to know if it has
3828     already been initialized.  When libxml2 is built to support
3829     threads, it tests if it is already initialized under a lock and
3830     holds a lock while it is being initialized so calling this
3831     function from multiple threads is ok.
3832   */
3833   xmlInitParser();
3834 
3835   /*
3836     Open image file.
3837   */
3838   image=AllocateImage(image_info);
3839   /*
3840     If there is a geometry string in image_info->size (e.g., gm convert
3841     -size "50x50%" in.svg out.png), AllocateImage() sets image->columns
3842     and image->rows to the width and height values from the size string.
3843     However, this makes no sense if the size string was something like
3844     "50x50%" (we'll get columns = rows = 50).  So we set columns and
3845     rows to 0 here, which is the same as if no size string was supplied
3846     by the client.  This also results in svg_info.bounds to be set to
3847     0,0 below (i.e., unknown), so that svg_info.bounds will be set using
3848     the image size information from either the svg "canvas" width/height
3849     or from the viewbox.  Later, variable "page" is set from
3850     svg_info->bounds. Then the geometry string in image_info->size gets
3851     applied to the (now known) "page" width and height when
3852     SvgStartElement() calls GetMagickGeometry(), and the intended result
3853     is obtained.
3854   */
3855   image->columns = 0;
3856   image->rows = 0;
3857   status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
3858   if (status == False)
3859     ThrowReaderException(FileOpenError,UnableToOpenFile,image);
3860   /*
3861     Open draw file.
3862   */
3863   file=AcquireTemporaryFileStream(filename,TextFileIOMode);
3864   if (file == (FILE *) NULL)
3865     ThrowReaderTemporaryFileException(filename);
3866   /*
3867     Parse SVG file.
3868   */
3869   (void) memset(&svg_info,0,sizeof(SVGInfo));
3870   svg_info.file=file;
3871   svg_info.exception=exception;
3872   svg_info.image=image;
3873   svg_info.image_info=image_info;
3874   svg_info.text=AllocateString("");
3875   svg_info.scale=MagickAllocateMemory(double *,sizeof(double));
3876   if ((svg_info.text == (char *) NULL) || (svg_info.scale == (double *) NULL))
3877     {
3878       (void) fclose(file);
3879       (void) LiberateTemporaryFile(filename);
3880       MagickFreeMemory(svg_info.text);
3881       MagickFreeMemory(svg_info.scale);
3882       ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
3883     }
3884   IdentityAffine(&svg_info.affine);
3885   svg_info.affine.sx=
3886     image->x_resolution == 0.0 ? 1.0 : image->x_resolution/72.0;
3887   svg_info.affine.sy=
3888     image->y_resolution == 0.0 ? 1.0 : image->y_resolution/72.0;
3889   svg_info.scale[0]=ExpandAffine(&svg_info.affine);
3890   svg_info.bounds.width=image->columns;
3891   svg_info.bounds.height=image->rows;
3892   svg_info.defsPushCount = 0;
3893   svg_info.idLevelInsideDefs = 0;
3894   svg_info.svgPushCount = 0;
3895   if (image_info->size != (char *) NULL)
3896     (void) CloneString(&svg_info.size,image_info->size);
3897   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"begin SAX");
3898   (void) xmlSubstituteEntitiesDefault(1);
3899 
3900   (void) memset(&SAXModules,0,sizeof(SAXModules));
3901   SAXModules.internalSubset=SVGInternalSubset;
3902   SAXModules.isStandalone=SVGIsStandalone;
3903   SAXModules.hasInternalSubset=SVGHasInternalSubset;
3904   SAXModules.hasExternalSubset=SVGHasExternalSubset;
3905   SAXModules.resolveEntity=SVGResolveEntity;
3906   SAXModules.getEntity=SVGGetEntity;
3907   SAXModules.entityDecl=SVGEntityDeclaration;
3908   SAXModules.notationDecl=SVGNotationDeclaration;
3909   SAXModules.attributeDecl=SVGAttributeDeclaration;
3910   SAXModules.elementDecl=SVGElementDeclaration;
3911   SAXModules.unparsedEntityDecl=SVGUnparsedEntityDeclaration;
3912   SAXModules.setDocumentLocator=SVGSetDocumentLocator;
3913   SAXModules.startDocument=SVGStartDocument;
3914   SAXModules.endDocument=SVGEndDocument;
3915   SAXModules.startElement=SVGStartElement;
3916   SAXModules.endElement=SVGEndElement;
3917   SAXModules.reference=SVGReference;
3918   SAXModules.characters=SVGCharacters;
3919   SAXModules.ignorableWhitespace=SVGIgnorableWhitespace;
3920   SAXModules.processingInstruction=SVGProcessingInstructions;
3921   SAXModules.comment=SVGComment;
3922   SAXModules.warning=SVGWarning;
3923   SAXModules.error=SVGError;
3924   SAXModules.fatalError=SVGError;
3925   SAXModules.getParameterEntity=SVGGetParameterEntity;
3926   SAXModules.cdataBlock=SVGCDataBlock;
3927   SAXModules.externalSubset=SVGExternalSubset;
3928 
3929   SAXHandler=(&SAXModules);
3930   svg_info.parser=xmlCreatePushParserCtxt(SAXHandler,&svg_info,(char *) NULL,0,
3931                                           image->filename);
3932   if (svg_info.parser == (xmlParserCtxtPtr) NULL)
3933     {
3934       /* FIXME: Handle failure! */
3935     }
3936   while ((n=ReadBlob(image,MaxTextExtent-1,message)) != 0)
3937     {
3938       message[n]='\0';
3939       status=xmlParseChunk(svg_info.parser,message,(int) n,False);
3940       if (status != 0)
3941         break;
3942     }
3943   (void) xmlParseChunk(svg_info.parser,message,0,True);
3944   /*
3945     Assure that our private context is freed, even if we abort before
3946     seeing the document end.
3947   */
3948   SVGEndDocument(&svg_info);
3949   if (svg_info.parser->myDoc != (xmlDocPtr) NULL)
3950     xmlFreeDoc(svg_info.parser->myDoc);
3951   /*
3952     Free all the memory used by a parser context. However the parsed
3953     document in ctxt->myDoc is not freed (so we just did that).
3954   */
3955   xmlFreeParserCtxt(svg_info.parser);
3956   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"end SAX");
3957   (void) fclose(file);
3958   CloseBlob(image);
3959   image->columns=svg_info.width;
3960   image->rows=svg_info.height;
3961   if (!image_info->ping && (exception->severity == UndefinedException))
3962     {
3963       ImageInfo
3964         *clone_info;
3965 
3966       /*
3967         Draw image.
3968       */
3969       DestroyImage(image);
3970       image=(Image *) NULL;
3971       clone_info=CloneImageInfo(image_info);
3972       clone_info->blob=(_BlobInfoPtr_) NULL;
3973       clone_info->length=0;
3974       FormatString(geometry,"%ldx%ld",svg_info.width,svg_info.height);
3975       (void) CloneString(&clone_info->size,geometry);
3976       FormatString(clone_info->filename,"mvg:%.1024s",filename);
3977       if (clone_info->density != (char *) NULL)
3978         MagickFreeMemory(clone_info->density);
3979       image=ReadImage(clone_info,exception);
3980       DestroyImageInfo(clone_info);
3981       if (image != (Image *) NULL)
3982         (void) strlcpy(image->filename,image_info->filename,MaxTextExtent);
3983     }
3984   /*
3985     Add/update image attributes
3986   */
3987   if (image != (Image *) NULL)
3988     {
3989       /* Title */
3990       if (svg_info.title != (char *) NULL)
3991         (void) SetImageAttribute(image,"title",svg_info.title);
3992 
3993       /* Comment */
3994       if (svg_info.comment != (char *) NULL)
3995         (void) SetImageAttribute(image,"comment",svg_info.comment);
3996     }
3997   /*
3998     Free resources allocated above (also freed by SVGEndDocument()).
3999   */
4000   MagickFreeMemory(svg_info.size);
4001   MagickFreeMemory(svg_info.title);
4002   MagickFreeMemory(svg_info.comment);
4003   MagickFreeMemory(svg_info.scale);
4004   MagickFreeMemory(svg_info.text);
4005 
4006   (void) memset(&svg_info,0xbf,sizeof(SVGInfo));
4007   (void) LiberateTemporaryFile(filename);
4008   if (image != (Image *) NULL)
4009     StopTimer(&image->timer);
4010   return(image);
4011 }
4012 #endif
4013 
4014 /*
4015 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4016 %                                                                             %
4017 %                                                                             %
4018 %                                                                             %
4019 %   R e g i s t e r S V G I m a g e                                           %
4020 %                                                                             %
4021 %                                                                             %
4022 %                                                                             %
4023 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4024 %
4025 %  Method RegisterSVGImage adds attributes for the SVG image format to
4026 %  the list of supported formats.  The attributes include the image format
4027 %  tag, a method to read and/or write the format, whether the format
4028 %  supports the saving of more than one frame to the same file or blob,
4029 %  whether the format supports native in-memory I/O, and a brief
4030 %  description of the format.
4031 %
4032 %  The format of the RegisterSVGImage method is:
4033 %
4034 %      RegisterSVGImage(void)
4035 %
4036 */
4037 ModuleExport void
RegisterSVGImage(void)4038 RegisterSVGImage(void)
4039 {
4040 #if defined(LIBXML_DOTTED_VERSION)
4041   static const char
4042     version[] = "XML " LIBXML_DOTTED_VERSION;
4043 #define HAS_VERSION 1
4044 #endif
4045 
4046   MagickInfo
4047     *entry;
4048 
4049   entry=SetMagickInfo("SVG");
4050 #if defined(HasXML)
4051   entry->decoder=(DecoderHandler) ReadSVGImage;
4052 #endif /* defined(HasXML) */
4053 #if ENABLE_SVG_WRITER
4054   entry->encoder=(EncoderHandler) WriteSVGImage;
4055 #endif /* if ENABLE_SVG_WRITER */
4056   entry->description="Scalable Vector Graphics";
4057 #if defined(HAS_VERSION)
4058     entry->version=version;
4059 #endif
4060   entry->module="SVG";
4061   (void) RegisterMagickInfo(entry);
4062 
4063   entry=SetMagickInfo("SVGZ");
4064 #if defined(HasXML)
4065   entry->decoder=(DecoderHandler) ReadSVGImage;
4066 #endif /* defined(HasXML) */
4067 #if ENABLE_SVG_WRITER
4068   entry->encoder=(EncoderHandler) WriteSVGImage;
4069 #endif /* if ENABLE_SVG_WRITER */
4070   entry->description="Scalable Vector Graphics (ZIP compressed)";
4071 #if defined(HAS_VERSION)
4072     entry->version=version;
4073 #endif
4074   entry->module="SVG";
4075   (void) RegisterMagickInfo(entry);
4076 }
4077 
4078 /*
4079 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4080 %                                                                             %
4081 %                                                                             %
4082 %                                                                             %
4083 %   U n r e g i s t e r S V G I m a g e                                       %
4084 %                                                                             %
4085 %                                                                             %
4086 %                                                                             %
4087 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4088 %
4089 %  Method UnregisterSVGImage removes format registrations made by the
4090 %  SVG module from the list of supported formats.
4091 %
4092 %  The format of the UnregisterSVGImage method is:
4093 %
4094 %      UnregisterSVGImage(void)
4095 %
4096 */
4097 ModuleExport void
UnregisterSVGImage(void)4098 UnregisterSVGImage(void)
4099 {
4100   /*
4101     Libxml clean-up. Should only be called just before exit().
4102   */
4103   /* xmlCleanupParser(); */
4104   (void) UnregisterMagickInfo("SVG");
4105   (void) UnregisterMagickInfo("SVGZ");
4106 }
4107 
4108 #if ENABLE_SVG_WRITER
4109 /*
4110 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4111 %                                                                             %
4112 %                                                                             %
4113 %                                                                             %
4114 %   W r i t e S V G I m a g e                                                 %
4115 %                                                                             %
4116 %                                                                             %
4117 %                                                                             %
4118 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4119 %
4120 %  Method WriteSVGImage writes a image in the SVG - XML based W3C standard
4121 %  format.
4122 %
4123 %  The format of the WriteSVGImage method is:
4124 %
4125 %      unsigned int WriteSVGImage(const ImageInfo *image_info,Image *image)
4126 %
4127 %  A description of each parameter follows.
4128 %
4129 %    o status: Method WriteSVGImage return True if the image is written.
4130 %      False is returned is there is a memory shortage or if the image file
4131 %      fails to write.
4132 %
4133 %    o image_info: Specifies a pointer to a ImageInfo structure.
4134 %
4135 %    o image:  A pointer to an Image structure.
4136 %
4137 %
4138 */
4139 
4140 #if defined(HasAUTOTRACE)
4141 static unsigned int
WriteSVGImage(const ImageInfo * image_info,Image * image)4142 WriteSVGImage(const ImageInfo *image_info,Image *image)
4143 {
4144   FILE
4145     *output_file;
4146 
4147   fitting_opts_type
4148     fit_info;
4149 
4150   image_header_type
4151     image_header;
4152 
4153   bitmap_type
4154     bitmap;
4155 
4156   ImageType
4157     image_type;
4158 
4159   int
4160     j;
4161 
4162   pixel_outline_list_type
4163     pixels;
4164 
4165   PixelPacket
4166     p;
4167 
4168   PixelPacket
4169     *pixel;
4170 
4171   QuantizeObj
4172     *quantize_info;
4173 
4174   spline_list_array_type
4175     splines;
4176 
4177   output_write
4178     output_writer;
4179 
4180   register long
4181     i;
4182 
4183   unsigned long
4184     number_pixels,
4185     number_planes,
4186     point,
4187     thin;
4188 
4189   thin=False;
4190   quantize_info=(QuantizeObj *) NULL;
4191   pixel=(&p);
4192   fit_info=new_fitting_opts();
4193   output_writer=output_get_handler("svg");
4194   if (output_writer == NULL)
4195     ThrowWriterException(DelegateError,UnableToWriteSVGFormat,image);
4196   image_type=GetImageType(image);
4197   number_planes=3;
4198   if ((image_type == BilevelType) || (image_type == GrayscaleType))
4199     number_planes=1;
4200   bitmap.np=number_planes;
4201   bitmap.dimensions.width=image->columns;
4202   bitmap.dimensions.height=image->rows;
4203   number_pixels=image->columns*image->rows;
4204   bitmap.bitmap=MagickAllocateMemory(unsigned char *,number_planes*number_pixels);
4205   if (bitmap.bitmap == (unsigned char *) NULL)
4206     ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
4207   point=0;
4208   for (j=0; j < image->rows; j++)
4209     {
4210       for (i=0; i < image->columns; i++)
4211         {
4212           p=AcquireOnePixel(image,i,j,&image->exception);
4213           bitmap.bitmap[point++]=pixel->red;
4214           if (number_planes == 3)
4215             {
4216               bitmap.bitmap[point++]=pixel->green;
4217               bitmap.bitmap[point++]=pixel->blue;
4218             }
4219         }
4220     }
4221   image_header.width=DIMENSIONS_WIDTH(bitmap.dimensions);
4222   image_header.height=DIMENSIONS_HEIGHT(bitmap.dimensions);
4223   if ((fit_info.color_count > 0) && (BITMAP_PLANES(bitmap) == 3))
4224     quantize(bitmap.bitmap,bitmap.bitmap,DIMENSIONS_WIDTH(bitmap.dimensions),
4225              DIMENSIONS_HEIGHT(bitmap.dimensions),fit_info.color_count,
4226              fit_info.bgColor,&quantize_info);
4227   if (thin)
4228     thin_image(&bitmap);
4229   pixels=find_outline_pixels (bitmap);
4230   MagickFreeMemory((bitmap.bitmap));
4231   splines=fitted_splines(pixels,&fit_info);
4232   output_file=fopen(image->filename,"w");
4233   if (output_file == (FILE *) NULL)
4234     ThrowWriterException(FileOpenError,UnableOpenFile,image);
4235   output_writer(output_file,image->filename,0,0,image_header.width,
4236                 image_header.height,splines);
4237   return(True);
4238 }
4239 #else
4240 static void
AffineToTransform(Image * image,AffineMatrix * affine)4241 AffineToTransform(Image *image,AffineMatrix *affine)
4242 {
4243   char
4244     transform[MaxTextExtent];
4245 
4246   if ((fabs(affine->tx) < MagickEpsilon) && (fabs(affine->ty) < MagickEpsilon))
4247     {
4248       if ((fabs(affine->rx) < MagickEpsilon) &&
4249           (fabs(affine->ry) < MagickEpsilon))
4250         {
4251           if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
4252               (fabs(affine->sy-1.0) < MagickEpsilon))
4253             {
4254               (void) WriteBlobString(image,"\">\n");
4255               return;
4256             }
4257           FormatString(transform,"\" transform=\"scale(%g,%g)\">\n",
4258                        affine->sx,affine->sy);
4259           (void) WriteBlobString(image,transform);
4260           return;
4261         }
4262       else
4263         {
4264           if ((fabs(affine->sx-affine->sy) < MagickEpsilon) &&
4265               (fabs(affine->rx+affine->ry) < MagickEpsilon) &&
4266               (fabs(affine->sx*affine->sx+affine->rx*affine->rx-1.0) <
4267                2*MagickEpsilon))
4268             {
4269               double
4270                 theta;
4271 
4272               theta=(180.0/MagickPI)*atan2(affine->rx,affine->sx);
4273               FormatString(transform,"\" transform=\"rotate(%g)\">\n",theta);
4274               (void) WriteBlobString(image,transform);
4275               return;
4276             }
4277         }
4278     }
4279   else
4280     {
4281       if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
4282           (fabs(affine->rx) < MagickEpsilon) &&
4283           (fabs(affine->ry) < MagickEpsilon) &&
4284           (fabs(affine->sy-1.0) < MagickEpsilon))
4285         {
4286           FormatString(transform,"\" transform=\"translate(%g,%g)\">\n",
4287                        affine->tx,affine->ty);
4288           (void) WriteBlobString(image,transform);
4289           return;
4290         }
4291     }
4292   FormatString(transform,"\" transform=\"matrix(%g %g %g %g %g %g)\">\n",
4293                affine->sx,affine->rx,affine->ry,affine->sy,affine->tx,affine->ty);
4294   (void) WriteBlobString(image,transform);
4295 }
4296 
4297 static inline unsigned int
IsPoint(const char * point)4298 IsPoint(const char *point)
4299 {
4300   char
4301     *p;
4302 
4303   (void) strtol(point,&p,10);
4304   return(p != point);
4305 }
4306 
4307 static unsigned int
WriteSVGImage(const ImageInfo * image_info,Image * image)4308 WriteSVGImage(const ImageInfo *image_info,Image *image)
4309 {
4310 #define BezierQuantum  200
4311 
4312   AffineMatrix
4313     affine;
4314 
4315   char
4316     keyword[MaxTextExtent],
4317     message[MaxTextExtent],
4318     name[MaxTextExtent],
4319     *p,
4320     *q,
4321     *token,
4322     type[MaxTextExtent];
4323 
4324   const ImageAttribute
4325     *attribute;
4326 
4327   int
4328     n;
4329 
4330   long
4331     j;
4332 
4333   PointInfo
4334     point;
4335 
4336   PrimitiveInfo
4337     *primitive_info;
4338 
4339   PrimitiveType
4340     primitive_type;
4341 
4342   register long
4343     x;
4344 
4345   register long
4346     i;
4347 
4348   size_t
4349     length,
4350     token_max_length;
4351 
4352   SVGInfo
4353     svg_info;
4354 
4355   unsigned int
4356     active,
4357     status;
4358 
4359   unsigned long
4360     number_points;
4361 
4362   /*
4363     Open output image file.
4364   */
4365   assert(image_info != (const ImageInfo *) NULL);
4366   assert(image_info->signature == MagickSignature);
4367   assert(image != (Image *) NULL);
4368   assert(image->signature == MagickSignature);
4369   attribute=GetImageAttribute(image,"[MVG]");
4370   if ((attribute == (ImageAttribute *) NULL) ||
4371       (attribute->value == (char *) NULL))
4372     ThrowWriterException(CoderError,NoImageVectorGraphics,image);
4373   status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
4374   if (status == False)
4375     ThrowWriterException(FileOpenError,UnableToOpenFile,image);
4376   /*
4377     Write SVG header.
4378   */
4379   (void) WriteBlobString(image,"<?xml version=\"1.0\" standalone=\"no\"?>\n");
4380   (void) WriteBlobString(image,
4381                          "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n");
4382   (void) WriteBlobString(image,
4383                          "  \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n");
4384   (void) FormatString(message,"<svg width=\"%lu\" height=\"%lu\">\n",
4385                       image->columns,image->rows);
4386   (void) WriteBlobString(image,message);
4387   /*
4388     Allocate primitive info memory.
4389   */
4390   number_points=2047;
4391   primitive_info=MagickAllocateMemory(PrimitiveInfo *,
4392                                       number_points*sizeof(PrimitiveInfo));
4393   if (primitive_info == (PrimitiveInfo *) NULL)
4394     ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
4395   IdentityAffine(&affine);
4396   token=AllocateString(attribute->value);
4397   token_max_length=strlen(token);
4398   active=False;
4399   n=0;
4400   status=True;
4401   for (q=attribute->value; *q != '\0'; )
4402     {
4403       /*
4404         Interpret graphic primitive.
4405       */
4406       MagickGetToken(q,&q,keyword,MaxTextExtent);
4407       if (*keyword == '\0')
4408         break;
4409       if (*keyword == '#')
4410         {
4411           /*
4412             Comment.
4413           */
4414           if (active)
4415             {
4416               AffineToTransform(image,&affine);
4417               active=False;
4418             }
4419           (void) WriteBlobString(image,"<desc>");
4420           (void) WriteBlobString(image,keyword+1);
4421           for ( ; (*q != '\n') && (*q != '\0'); q++)
4422             switch (*q)
4423               {
4424               case '<': (void) WriteBlobString(image,"&lt;"); break;
4425               case '>': (void) WriteBlobString(image,"&gt;"); break;
4426               case '&': (void) WriteBlobString(image,"&amp;"); break;
4427               default: (void) WriteBlobByte(image,*q); break;
4428               }
4429           (void) WriteBlobString(image,"</desc>\n");
4430           continue;
4431         }
4432       primitive_type=UndefinedPrimitive;
4433       switch (*keyword)
4434         {
4435         case ';':
4436           break;
4437         case 'a':
4438         case 'A':
4439           {
4440             if (LocaleCompare("affine",keyword) == 0)
4441               {
4442                 MagickGetToken(q,&q,token,token_max_length);
4443                 affine.sx=MagickAtoF(token);
4444                 MagickGetToken(q,&q,token,token_max_length);
4445                 if (*token == ',')
4446                   MagickGetToken(q,&q,token,token_max_length);
4447                 affine.rx=MagickAtoF(token);
4448                 MagickGetToken(q,&q,token,token_max_length);
4449                 if (*token == ',')
4450                   MagickGetToken(q,&q,token,token_max_length);
4451                 affine.ry=MagickAtoF(token);
4452                 MagickGetToken(q,&q,token,token_max_length);
4453                 if (*token == ',')
4454                   MagickGetToken(q,&q,token,token_max_length);
4455                 affine.sy=MagickAtoF(token);
4456                 MagickGetToken(q,&q,token,token_max_length);
4457                 if (*token == ',')
4458                   MagickGetToken(q,&q,token,token_max_length);
4459                 affine.tx=MagickAtoF(token);
4460                 MagickGetToken(q,&q,token,token_max_length);
4461                 if (*token == ',')
4462                   MagickGetToken(q,&q,token,token_max_length);
4463                 affine.ty=MagickAtoF(token);
4464                 break;
4465               }
4466             if (LocaleCompare("angle",keyword) == 0)
4467               {
4468                 MagickGetToken(q,&q,token,token_max_length);
4469                 affine.rx=MagickAtoF(token);
4470                 affine.ry=MagickAtoF(token);
4471                 break;
4472               }
4473             if (LocaleCompare("arc",keyword) == 0)
4474               {
4475                 primitive_type=ArcPrimitive;
4476                 break;
4477               }
4478             status=False;
4479             break;
4480           }
4481         case 'b':
4482         case 'B':
4483           {
4484             if (LocaleCompare("bezier",keyword) == 0)
4485               {
4486                 primitive_type=BezierPrimitive;
4487                 break;
4488               }
4489             status=False;
4490             break;
4491           }
4492         case 'c':
4493         case 'C':
4494           {
4495             if (LocaleCompare("clip-path",keyword) == 0)
4496               {
4497                 MagickGetToken(q,&q,token,token_max_length);
4498                 FormatString(message,"clip-path:url(#%.1024s);",token);
4499                 (void) WriteBlobString(image,message);
4500                 break;
4501               }
4502             if (LocaleCompare("clip-rule",keyword) == 0)
4503               {
4504                 MagickGetToken(q,&q,token,token_max_length);
4505                 FormatString(message,"clip-rule:%.1024s;",token);
4506                 (void) WriteBlobString(image,message);
4507                 break;
4508               }
4509             if (LocaleCompare("clip-units",keyword) == 0)
4510               {
4511                 MagickGetToken(q,&q,token,token_max_length);
4512                 FormatString(message,"clipPathUnits=%.1024s;",token);
4513                 (void) WriteBlobString(image,message);
4514                 break;
4515               }
4516             if (LocaleCompare("circle",keyword) == 0)
4517               {
4518                 primitive_type=CirclePrimitive;
4519                 break;
4520               }
4521             if (LocaleCompare("color",keyword) == 0)
4522               {
4523                 primitive_type=ColorPrimitive;
4524                 break;
4525               }
4526             status=False;
4527             break;
4528           }
4529         case 'd':
4530         case 'D':
4531           {
4532             if (LocaleCompare("decorate",keyword) == 0)
4533               {
4534                 MagickGetToken(q,&q,token,token_max_length);
4535                 FormatString(message,"text-decoration:%.1024s;",token);
4536                 (void) WriteBlobString(image,message);
4537                 break;
4538               }
4539             status=False;
4540             break;
4541           }
4542         case 'e':
4543         case 'E':
4544           {
4545             if (LocaleCompare("ellipse",keyword) == 0)
4546               {
4547                 primitive_type=EllipsePrimitive;
4548                 break;
4549               }
4550             status=False;
4551             break;
4552           }
4553         case 'f':
4554         case 'F':
4555           {
4556             if (LocaleCompare("fill",keyword) == 0)
4557               {
4558                 MagickGetToken(q,&q,token,token_max_length);
4559                 FormatString(message,"fill:%.1024s;",token);
4560                 (void) WriteBlobString(image,message);
4561                 break;
4562               }
4563             if (LocaleCompare("fill-rule",keyword) == 0)
4564               {
4565                 MagickGetToken(q,&q,token,token_max_length);
4566                 FormatString(message,"fill-rule:%.1024s;",token);
4567                 (void) WriteBlobString(image,message);
4568                 break;
4569               }
4570             if (LocaleCompare("fill-opacity",keyword) == 0)
4571               {
4572                 MagickGetToken(q,&q,token,token_max_length);
4573                 FormatString(message,"fill-opacity:%.1024s;",token);
4574                 (void) WriteBlobString(image,message);
4575                 break;
4576               }
4577             if (LocaleCompare("font-family",keyword) == 0)
4578               {
4579                 MagickGetToken(q,&q,token,token_max_length);
4580                 FormatString(message,"font-family:%.1024s;",token);
4581                 (void) WriteBlobString(image,message);
4582                 break;
4583               }
4584             if (LocaleCompare("font-stretch",keyword) == 0)
4585               {
4586                 MagickGetToken(q,&q,token,token_max_length);
4587                 FormatString(message,"font-stretch:%.1024s;",token);
4588                 (void) WriteBlobString(image,message);
4589                 break;
4590               }
4591             if (LocaleCompare("font-style",keyword) == 0)
4592               {
4593                 MagickGetToken(q,&q,token,token_max_length);
4594                 FormatString(message,"font-style:%.1024s;",token);
4595                 (void) WriteBlobString(image,message);
4596                 break;
4597               }
4598             if (LocaleCompare("font-size",keyword) == 0)
4599               {
4600                 MagickGetToken(q,&q,token,token_max_length);
4601                 FormatString(message,"font-size:%.1024s;",token);
4602                 (void) WriteBlobString(image,message);
4603                 break;
4604               }
4605             if (LocaleCompare("font-weight",keyword) == 0)
4606               {
4607                 MagickGetToken(q,&q,token,token_max_length);
4608                 FormatString(message,"font-weight:%.1024s;",token);
4609                 (void) WriteBlobString(image,message);
4610                 break;
4611               }
4612             status=False;
4613             break;
4614           }
4615         case 'g':
4616         case 'G':
4617           {
4618             if (LocaleCompare("gradient-units",keyword) == 0)
4619               {
4620                 MagickGetToken(q,&q,token,token_max_length);
4621                 break;
4622               }
4623             if (LocaleCompare("text-align",keyword) == 0)
4624               {
4625                 MagickGetToken(q,&q,token,token_max_length);
4626                 FormatString(message,"text-align %.1024s ",token);
4627                 (void) WriteBlobString(image,message);
4628                 break;
4629               }
4630             if (LocaleCompare("text-anchor",keyword) == 0)
4631               {
4632                 MagickGetToken(q,&q,token,token_max_length);
4633                 FormatString(message,"text-anchor %.1024s ",token);
4634                 (void) WriteBlobString(image,message);
4635                 break;
4636               }
4637             status=False;
4638             break;
4639           }
4640         case 'i':
4641         case 'I':
4642           {
4643             if (LocaleCompare("image",keyword) == 0)
4644               {
4645                 MagickGetToken(q,&q,token,token_max_length);
4646                 primitive_type=ImagePrimitive;
4647                 break;
4648               }
4649             status=False;
4650             break;
4651           }
4652         case 'l':
4653         case 'L':
4654           {
4655             if (LocaleCompare("line",keyword) == 0)
4656               {
4657                 primitive_type=LinePrimitive;
4658                 break;
4659               }
4660             status=False;
4661             break;
4662           }
4663         case 'm':
4664         case 'M':
4665           {
4666             if (LocaleCompare("matte",keyword) == 0)
4667               {
4668                 primitive_type=MattePrimitive;
4669                 break;
4670               }
4671             status=False;
4672             break;
4673           }
4674         case 'o':
4675         case 'O':
4676           {
4677             if (LocaleCompare("opacity",keyword) == 0)
4678               {
4679                 MagickGetToken(q,&q,token,token_max_length);
4680                 FormatString(message,"opacity %.1024s ",token);
4681                 (void) WriteBlobString(image,message);
4682                 break;
4683               }
4684             status=False;
4685             break;
4686           }
4687         case 'p':
4688         case 'P':
4689           {
4690             if (LocaleCompare("path",keyword) == 0)
4691               {
4692                 primitive_type=PathPrimitive;
4693                 break;
4694               }
4695             if (LocaleCompare("point",keyword) == 0)
4696               {
4697                 primitive_type=PointPrimitive;
4698                 break;
4699               }
4700             if (LocaleCompare("polyline",keyword) == 0)
4701               {
4702                 primitive_type=PolylinePrimitive;
4703                 break;
4704               }
4705             if (LocaleCompare("polygon",keyword) == 0)
4706               {
4707                 primitive_type=PolygonPrimitive;
4708                 break;
4709               }
4710             if (LocaleCompare("pop",keyword) == 0)
4711               {
4712                 MagickGetToken(q,&q,token,token_max_length);
4713                 if (LocaleCompare("clip-path",token) == 0)
4714                   {
4715                     (void) WriteBlobString(image,"</clipPath>\n");
4716                     break;
4717                   }
4718                 if (LocaleCompare("defs",token) == 0)
4719                   {
4720                     (void) WriteBlobString(image,"</defs>\n");
4721                     break;
4722                   }
4723                 if (LocaleCompare("gradient",token) == 0)
4724                   {
4725                     FormatString(message,"</%sGradient>\n",type);
4726                     (void) WriteBlobString(image,message);
4727                     break;
4728                   }
4729                 if (LocaleCompare("graphic-context",token) == 0)
4730                   {
4731                     n--;
4732                     if (n < 0)
4733                       ThrowWriterException(DrawError,
4734                                            UnbalancedGraphicContextPushPop,image);
4735                     (void) WriteBlobString(image,"</g>\n");
4736                   }
4737                 if (LocaleCompare("pattern",token) == 0)
4738                   {
4739                     (void) WriteBlobString(image,"</pattern>\n");
4740                     break;
4741                   }
4742                 if (LocaleCompare("defs",token) == 0)
4743                   (void) WriteBlobString(image,"</g>\n");
4744                 break;
4745               }
4746             if (LocaleCompare("push",keyword) == 0)
4747               {
4748                 MagickGetToken(q,&q,token,token_max_length);
4749                 if (LocaleCompare("clip-path",token) == 0)
4750                   {
4751                     MagickGetToken(q,&q,token,token_max_length);
4752                     FormatString(message,"<clipPath id=\"%s\">\n",token);
4753                     (void) WriteBlobString(image,message);
4754                     break;
4755                   }
4756                 if (LocaleCompare("defs",token) == 0)
4757                   {
4758                     (void) WriteBlobString(image,"<defs>\n");
4759                     break;
4760                   }
4761                 if (LocaleCompare("gradient",token) == 0)
4762                   {
4763                     MagickGetToken(q,&q,token,token_max_length);
4764                     (void) strlcpy(name,token,MaxTextExtent);
4765                     MagickGetToken(q,&q,token,token_max_length);
4766                     (void) strlcpy(type,token,MaxTextExtent);
4767                     MagickGetToken(q,&q,token,token_max_length);
4768                     svg_info.segment.x1=MagickAtoF(token);
4769                     svg_info.element.cx=MagickAtoF(token);
4770                     MagickGetToken(q,&q,token,token_max_length);
4771                     if (*token == ',')
4772                       MagickGetToken(q,&q,token,token_max_length);
4773                     svg_info.segment.y1=MagickAtoF(token);
4774                     svg_info.element.cy=MagickAtoF(token);
4775                     MagickGetToken(q,&q,token,token_max_length);
4776                     if (*token == ',')
4777                       MagickGetToken(q,&q,token,token_max_length);
4778                     svg_info.segment.x2=MagickAtoF(token);
4779                     svg_info.element.major=MagickAtoF(token);
4780                     MagickGetToken(q,&q,token,token_max_length);
4781                     if (*token == ',')
4782                       MagickGetToken(q,&q,token,token_max_length);
4783                     svg_info.segment.y2=MagickAtoF(token);
4784                     svg_info.element.minor=MagickAtoF(token);
4785                     FormatString(message,"<%sGradient id=\"%s\" x1=\"%g\" "
4786                                  "y1=\"%g\" x2=\"%g\" y2=\"%g\">\n",type,name,
4787                                  svg_info.segment.x1,svg_info.segment.y1,svg_info.segment.x2,
4788                                  svg_info.segment.y2);
4789                     if (LocaleCompare(type,"radial") == 0)
4790                       {
4791                         MagickGetToken(q,&q,token,token_max_length);
4792                         if (*token == ',')
4793                           MagickGetToken(q,&q,token,token_max_length);
4794                         svg_info.element.angle=MagickAtoF(token);
4795                         FormatString(message,"<%sGradient id=\"%s\" cx=\"%g\" "
4796                                      "cy=\"%g\" r=\"%g\" fx=\"%g\" fy=\"%g\">\n",type,name,
4797                                      svg_info.element.cx,svg_info.element.cy,
4798                                      svg_info.element.angle,svg_info.element.major,
4799                                      svg_info.element.minor);
4800                       }
4801                     (void) WriteBlobString(image,message);
4802                     break;
4803                   }
4804                 if (LocaleCompare("graphic-context",token) == 0)
4805                   {
4806                     n++;
4807                     if (active)
4808                       {
4809                         AffineToTransform(image,&affine);
4810                         active=False;
4811                       }
4812                     (void) WriteBlobString(image,"<g style=\"");
4813                     active=True;
4814                   }
4815                 if (LocaleCompare("pattern",token) == 0)
4816                   {
4817                     MagickGetToken(q,&q,token,token_max_length);
4818                     (void) strlcpy(name,token,MaxTextExtent);
4819                     MagickGetToken(q,&q,token,token_max_length);
4820                     svg_info.bounds.x=MagickAtoF(token);
4821                     MagickGetToken(q,&q,token,token_max_length);
4822                     if (*token == ',')
4823                       MagickGetToken(q,&q,token,token_max_length);
4824                     svg_info.bounds.y=MagickAtoF(token);
4825                     MagickGetToken(q,&q,token,token_max_length);
4826                     if (*token == ',')
4827                       MagickGetToken(q,&q,token,token_max_length);
4828                     svg_info.bounds.width=MagickAtoF(token);
4829                     MagickGetToken(q,&q,token,token_max_length);
4830                     if (*token == ',')
4831                       MagickGetToken(q,&q,token,token_max_length);
4832                     svg_info.bounds.height=MagickAtoF(token);
4833                     FormatString(message,"<pattern id=\"%s\" x=\"%g\" y=\"%g\" "
4834                                  "width=\"%g\" height=\"%g\">\n",name,svg_info.bounds.x,
4835                                  svg_info.bounds.y,svg_info.bounds.width,
4836                                  svg_info.bounds.height);
4837                     (void) WriteBlobString(image,message);
4838                     break;
4839                   }
4840                 break;
4841               }
4842             status=False;
4843             break;
4844           }
4845         case 'r':
4846         case 'R':
4847           {
4848             if (LocaleCompare("rectangle",keyword) == 0)
4849               {
4850                 primitive_type=RectanglePrimitive;
4851                 break;
4852               }
4853             if (LocaleCompare("roundRectangle",keyword) == 0)
4854               {
4855                 primitive_type=RoundRectanglePrimitive;
4856                 break;
4857               }
4858             if (LocaleCompare("rotate",keyword) == 0)
4859               {
4860                 MagickGetToken(q,&q,token,token_max_length);
4861                 FormatString(message,"rotate(%.1024s) ",token);
4862                 (void) WriteBlobString(image,message);
4863                 break;
4864               }
4865             status=False;
4866             break;
4867           }
4868         case 's':
4869         case 'S':
4870           {
4871             if (LocaleCompare("scale",keyword) == 0)
4872               {
4873                 MagickGetToken(q,&q,token,token_max_length);
4874                 affine.sx=MagickAtoF(token);
4875                 MagickGetToken(q,&q,token,token_max_length);
4876                 if (*token == ',')
4877                   MagickGetToken(q,&q,token,token_max_length);
4878                 affine.sy=MagickAtoF(token);
4879                 break;
4880               }
4881             if (LocaleCompare("skewX",keyword) == 0)
4882               {
4883                 MagickGetToken(q,&q,token,token_max_length);
4884                 FormatString(message,"skewX(%.1024s) ",token);
4885                 (void) WriteBlobString(image,message);
4886                 break;
4887               }
4888             if (LocaleCompare("skewY",keyword) == 0)
4889               {
4890                 MagickGetToken(q,&q,token,token_max_length);
4891                 FormatString(message,"skewY(%.1024s) ",token);
4892                 (void) WriteBlobString(image,message);
4893                 break;
4894               }
4895             if (LocaleCompare("stop-color",keyword) == 0)
4896               {
4897                 char
4898                   color[MaxTextExtent];
4899 
4900                 MagickGetToken(q,&q,token,token_max_length);
4901                 (void) strlcpy(color,token,MaxTextExtent);
4902                 MagickGetToken(q,&q,token,token_max_length);
4903                 FormatString(message,
4904                              "  <stop offset=\"%s\" stop-color=\"%s\" />\n",token,color);
4905                 (void) WriteBlobString(image,message);
4906                 break;
4907               }
4908             if (LocaleCompare("stroke",keyword) == 0)
4909               {
4910                 MagickGetToken(q,&q,token,token_max_length);
4911                 FormatString(message,"stroke:%.1024s;",token);
4912                 (void) WriteBlobString(image,message);
4913                 break;
4914               }
4915             if (LocaleCompare("stroke-antialias",keyword) == 0)
4916               {
4917                 MagickGetToken(q,&q,token,token_max_length);
4918                 FormatString(message,"stroke-antialias:%.1024s;",token);
4919                 (void) WriteBlobString(image,message);
4920                 break;
4921               }
4922             if (LocaleCompare("stroke-dasharray",keyword) == 0)
4923               {
4924                 if (IsPoint(q))
4925                   {
4926                     long
4927                       k;
4928 
4929                     p=q;
4930                     (void) MagickGetToken(p,&p,token,MaxTextExtent);
4931                     for (k=0; IsPoint(token); k++)
4932                       (void) MagickGetToken(p,&p,token,MaxTextExtent);
4933                     (void) WriteBlobString(image,"stroke-dasharray:");
4934                     for (j=0; j < k; j++)
4935                       {
4936                         MagickGetToken(q,&q,token,token_max_length);
4937                         FormatString(message,"%.1024s ",token);
4938                         (void) WriteBlobString(image,message);
4939                       }
4940                     (void) WriteBlobString(image,";");
4941                     break;
4942                   }
4943                 MagickGetToken(q,&q,token,token_max_length);
4944                 FormatString(message,"stroke-dasharray:%.1024s;",token);
4945                 (void) WriteBlobString(image,message);
4946                 break;
4947               }
4948             if (LocaleCompare("stroke-dashoffset",keyword) == 0)
4949               {
4950                 MagickGetToken(q,&q,token,token_max_length);
4951                 FormatString(message,"stroke-dashoffset:%.1024s;",token);
4952                 (void) WriteBlobString(image,message);
4953                 break;
4954               }
4955             if (LocaleCompare("stroke-linecap",keyword) == 0)
4956               {
4957                 MagickGetToken(q,&q,token,token_max_length);
4958                 FormatString(message,"stroke-linecap:%.1024s;",token);
4959                 (void) WriteBlobString(image,message);
4960                 break;
4961               }
4962             if (LocaleCompare("stroke-linejoin",keyword) == 0)
4963               {
4964                 MagickGetToken(q,&q,token,token_max_length);
4965                 FormatString(message,"stroke-linejoin:%.1024s;",token);
4966                 (void) WriteBlobString(image,message);
4967                 break;
4968               }
4969             if (LocaleCompare("stroke-miterlimit",keyword) == 0)
4970               {
4971                 MagickGetToken(q,&q,token,token_max_length);
4972                 FormatString(message,"stroke-miterlimit:%.1024s;",token);
4973                 (void) WriteBlobString(image,message);
4974                 break;
4975               }
4976             if (LocaleCompare("stroke-opacity",keyword) == 0)
4977               {
4978                 MagickGetToken(q,&q,token,token_max_length);
4979                 FormatString(message,"stroke-opacity:%.1024s;",token);
4980                 (void) WriteBlobString(image,message);
4981                 break;
4982               }
4983             if (LocaleCompare("stroke-width",keyword) == 0)
4984               {
4985                 MagickGetToken(q,&q,token,token_max_length);
4986                 FormatString(message,"stroke-width:%.1024s;",token);
4987                 (void) WriteBlobString(image,message);
4988                 continue;
4989               }
4990             status=False;
4991             break;
4992           }
4993         case 't':
4994         case 'T':
4995           {
4996             if (LocaleCompare("text",keyword) == 0)
4997               {
4998                 primitive_type=TextPrimitive;
4999                 break;
5000               }
5001             if (LocaleCompare("text-antialias",keyword) == 0)
5002               {
5003                 MagickGetToken(q,&q,token,token_max_length);
5004                 FormatString(message,"text-antialias:%.1024s;",token);
5005                 (void) WriteBlobString(image,message);
5006                 break;
5007               }
5008             if (LocaleCompare("tspan",keyword) == 0)
5009               {
5010                 primitive_type=TextPrimitive;
5011                 break;
5012               }
5013             if (LocaleCompare("translate",keyword) == 0)
5014               {
5015                 MagickGetToken(q,&q,token,token_max_length);
5016                 affine.tx=MagickAtoF(token);
5017                 MagickGetToken(q,&q,token,token_max_length);
5018                 if (*token == ',')
5019                   MagickGetToken(q,&q,token,token_max_length);
5020                 affine.ty=MagickAtoF(token);
5021                 break;
5022               }
5023             status=False;
5024             break;
5025           }
5026         case 'v':
5027         case 'V':
5028           {
5029             if (LocaleCompare("viewbox",keyword) == 0)
5030               {
5031                 MagickGetToken(q,&q,token,token_max_length);
5032                 if (*token == ',')
5033                   MagickGetToken(q,&q,token,token_max_length);
5034                 MagickGetToken(q,&q,token,token_max_length);
5035                 if (*token == ',')
5036                   MagickGetToken(q,&q,token,token_max_length);
5037                 MagickGetToken(q,&q,token,token_max_length);
5038                 if (*token == ',')
5039                   MagickGetToken(q,&q,token,token_max_length);
5040                 MagickGetToken(q,&q,token,token_max_length);
5041                 break;
5042               }
5043             status=False;
5044             break;
5045           }
5046         default:
5047           {
5048             status=False;
5049             break;
5050           }
5051         }
5052       if (status == False)
5053         break;
5054       if (primitive_type == UndefinedPrimitive)
5055         continue;
5056       /*
5057         Parse the primitive attributes.
5058       */
5059       i=0;
5060       j=0;
5061       for (x=0; *q != '\0'; x++)
5062         {
5063           /*
5064             Define points.
5065           */
5066           if (!IsPoint(q))
5067             break;
5068           MagickGetToken(q,&q,token,token_max_length);
5069           point.x=MagickAtoF(token);
5070           MagickGetToken(q,&q,token,token_max_length);
5071           if (*token == ',')
5072             MagickGetToken(q,&q,token,token_max_length);
5073           point.y=MagickAtoF(token);
5074           MagickGetToken(q,(char **) NULL,token,token_max_length);
5075           if (*token == ',')
5076             MagickGetToken(q,&q,token,token_max_length);
5077           primitive_info[i].primitive=primitive_type;
5078           primitive_info[i].point=point;
5079           primitive_info[i].coordinates=0;
5080           primitive_info[i].method=FloodfillMethod;
5081           i++;
5082           if (i < (long) (number_points-6*BezierQuantum-360))
5083             continue;
5084           number_points+=6*BezierQuantum+360;
5085           MagickReallocMemory(PrimitiveInfo *,primitive_info,
5086                               number_points*sizeof(PrimitiveInfo));
5087           if (primitive_info == (PrimitiveInfo *) NULL)
5088             {
5089               ThrowException3(&image->exception,ResourceLimitError,
5090                               MemoryAllocationFailed,UnableToDrawOnImage);
5091               break;
5092             }
5093         }
5094       primitive_info[j].primitive=primitive_type;
5095       primitive_info[j].coordinates=x;
5096       primitive_info[j].method=FloodfillMethod;
5097       primitive_info[j].text=(char *) NULL;
5098       if (active)
5099         {
5100           AffineToTransform(image,&affine);
5101           active=False;
5102         }
5103       active=False;
5104       switch (primitive_type)
5105         {
5106         case PointPrimitive:
5107         default:
5108           {
5109             if (primitive_info[j].coordinates != 1)
5110               {
5111                 status=False;
5112                 break;
5113               }
5114             break;
5115           }
5116         case LinePrimitive:
5117           {
5118             if (primitive_info[j].coordinates != 2)
5119               {
5120                 status=False;
5121                 break;
5122               }
5123             (void) FormatString(message,
5124                                 "  <line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"/>\n",
5125                                 primitive_info[j].point.x,primitive_info[j].point.y,
5126                                 primitive_info[j+1].point.x,primitive_info[j+1].point.y);
5127             (void) WriteBlobString(image,message);
5128             break;
5129           }
5130         case RectanglePrimitive:
5131           {
5132             if (primitive_info[j].coordinates != 2)
5133               {
5134                 status=False;
5135                 break;
5136               }
5137             (void) FormatString(message,
5138                                 "  <rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"/>\n",
5139                                 primitive_info[j].point.x,primitive_info[j].point.y,
5140                                 primitive_info[j+1].point.x-primitive_info[j].point.x,
5141                                 primitive_info[j+1].point.y-primitive_info[j].point.y);
5142             (void) WriteBlobString(image,message);
5143             break;
5144           }
5145         case RoundRectanglePrimitive:
5146           {
5147             if (primitive_info[j].coordinates != 3)
5148               {
5149                 status=False;
5150                 break;
5151               }
5152             (void) FormatString(message,"  <rect x=\"%g\" y=\"%g\" "
5153                                 "width=\"%g\" height=\"%g\" rx=\"%g\" ry=\"%g\"/>\n",
5154                                 primitive_info[j].point.x,primitive_info[j].point.y,
5155                                 primitive_info[j+1].point.x-primitive_info[j].point.x,
5156                                 primitive_info[j+1].point.y-primitive_info[j].point.y,
5157                                 primitive_info[j+2].point.x,primitive_info[j+2].point.y);
5158             (void) WriteBlobString(image,message);
5159             break;
5160           }
5161         case ArcPrimitive:
5162           {
5163             if (primitive_info[j].coordinates != 3)
5164               {
5165                 status=False;
5166                 break;
5167               }
5168             break;
5169           }
5170         case EllipsePrimitive:
5171           {
5172             if (primitive_info[j].coordinates != 3)
5173               {
5174                 status=False;
5175                 break;
5176               }
5177             (void) FormatString(message,
5178                                 "  <ellipse cx=\"%g\" cy=\"%g\" rx=\"%g\" ry=\"%g\"/>\n",
5179                                 primitive_info[j].point.x,primitive_info[j].point.y,
5180                                 primitive_info[j+1].point.x,primitive_info[j+1].point.y);
5181             (void) WriteBlobString(image,message);
5182             break;
5183           }
5184         case CirclePrimitive:
5185           {
5186             double
5187               alpha,
5188               beta;
5189 
5190             if (primitive_info[j].coordinates != 2)
5191               {
5192                 status=False;
5193                 break;
5194               }
5195             alpha=primitive_info[j+1].point.x-primitive_info[j].point.x;
5196             beta=primitive_info[j+1].point.y-primitive_info[j].point.y;
5197             (void) FormatString(message,"  <circle cx=\"%g\" cy=\"%g\" r=\"%g\"/>\n",
5198                                 primitive_info[j].point.x,primitive_info[j].point.y,
5199                                 sqrt(alpha*alpha+beta*beta));
5200             (void) WriteBlobString(image,message);
5201             break;
5202           }
5203         case PolylinePrimitive:
5204           {
5205             if (primitive_info[j].coordinates < 2)
5206               {
5207                 status=False;
5208                 break;
5209               }
5210             (void) strcpy(message,"  <polyline points=\"");
5211             (void) WriteBlobString(image,message);
5212             length=strlen(message);
5213             for ( ; j < i; j++)
5214               {
5215                 FormatString(message,"%g,%g ",primitive_info[j].point.x,
5216                              primitive_info[j].point.y);
5217                 length+=strlen(message);
5218                 if (length >= 80)
5219                   {
5220                     (void) WriteBlobString(image,"\n    ");
5221                     length=strlen(message)+5;
5222                   }
5223                 (void) WriteBlobString(image,message);
5224               }
5225             (void) WriteBlobString(image,"\"/>\n");
5226             break;
5227           }
5228         case PolygonPrimitive:
5229           {
5230             if (primitive_info[j].coordinates < 3)
5231               {
5232                 status=False;
5233                 break;
5234               }
5235             primitive_info[i]=primitive_info[j];
5236             primitive_info[i].coordinates=0;
5237             primitive_info[j].coordinates++;
5238             i++;
5239             (void) strcpy(message,"  <polygon points=\"");
5240             (void) WriteBlobString(image,message);
5241             length=strlen(message);
5242             for ( ; j < i; j++)
5243               {
5244                 FormatString(message,"%g,%g ",primitive_info[j].point.x,
5245                              primitive_info[j].point.y);
5246                 length+=strlen(message);
5247                 if (length >= 80)
5248                   {
5249                     (void) WriteBlobString(image,"\n    ");
5250                     length=strlen(message)+5;
5251                   }
5252                 (void) WriteBlobString(image,message);
5253               }
5254             (void) WriteBlobString(image,"\"/>\n");
5255             break;
5256           }
5257         case BezierPrimitive:
5258           {
5259             if (primitive_info[j].coordinates < 3)
5260               {
5261                 status=False;
5262                 break;
5263               }
5264             break;
5265           }
5266         case PathPrimitive:
5267           {
5268             int
5269               number_attributes;
5270 
5271             MagickGetToken(q,&q,token,token_max_length);
5272             number_attributes=1;
5273             for (p=token; *p != '\0'; p++)
5274               if (isalpha((int) *p))
5275                 number_attributes++;
5276             if (i > (long) (number_points-6*BezierQuantum*number_attributes-1))
5277               {
5278                 number_points+=6*BezierQuantum*number_attributes;
5279                 MagickReallocMemory(PrimitiveInfo *,primitive_info,
5280                                     number_points*sizeof(PrimitiveInfo));
5281                 if (primitive_info == (PrimitiveInfo *) NULL)
5282                   {
5283                     ThrowException3(&image->exception,ResourceLimitError,
5284                                     MemoryAllocationFailed,UnableToDrawOnImage);
5285                     break;
5286                   }
5287               }
5288             (void) WriteBlobString(image,"  <path d=\"");
5289             (void) WriteBlobString(image,token);
5290             (void) WriteBlobString(image,"\"/>\n");
5291             break;
5292           }
5293         case ColorPrimitive:
5294         case MattePrimitive:
5295           {
5296             if (primitive_info[j].coordinates != 1)
5297               {
5298                 status=False;
5299                 break;
5300               }
5301             MagickGetToken(q,&q,token,token_max_length);
5302             if (LocaleCompare("point",token) == 0)
5303               primitive_info[j].method=PointMethod;
5304             if (LocaleCompare("replace",token) == 0)
5305               primitive_info[j].method=ReplaceMethod;
5306             if (LocaleCompare("floodfill",token) == 0)
5307               primitive_info[j].method=FloodfillMethod;
5308             if (LocaleCompare("filltoborder",token) == 0)
5309               primitive_info[j].method=FillToBorderMethod;
5310             if (LocaleCompare("reset",token) == 0)
5311               primitive_info[j].method=ResetMethod;
5312             break;
5313           }
5314         case TextPrimitive:
5315           {
5316             register char
5317               *p;
5318 
5319             if (primitive_info[j].coordinates != 1)
5320               {
5321                 status=False;
5322                 break;
5323               }
5324             MagickGetToken(q,&q,token,token_max_length);
5325             (void) FormatString(message,"  <text x=\"%g\" y=\"%g\">",
5326                                 primitive_info[j].point.x,primitive_info[j].point.y);
5327             (void) WriteBlobString(image,message);
5328             for (p=token; *p != '\0'; p++)
5329               switch (*p)
5330                 {
5331                 case '<': (void) WriteBlobString(image,"&lt;"); break;
5332                 case '>': (void) WriteBlobString(image,"&gt;"); break;
5333                 case '&': (void) WriteBlobString(image,"&amp;"); break;
5334                 default: (void) WriteBlobByte(image,*p); break;
5335                 }
5336             (void) WriteBlobString(image,"</text>\n");
5337             break;
5338           }
5339         case ImagePrimitive:
5340           {
5341             if (primitive_info[j].coordinates != 2)
5342               {
5343                 status=False;
5344                 break;
5345               }
5346             MagickGetToken(q,&q,token,token_max_length);
5347             (void) FormatString(message,"  <image x=\"%g\" y=\"%g\" "
5348                                 "width=\"%g\" height=\"%g\" xlink:href=\"%.1024s\"/>\n",
5349                                 primitive_info[j].point.x,primitive_info[j].point.y,
5350                                 primitive_info[j+1].point.x,primitive_info[j+1].point.y,token);
5351             (void) WriteBlobString(image,message);
5352             break;
5353           }
5354         }
5355       if (primitive_info == (PrimitiveInfo *) NULL)
5356         break;
5357       primitive_info[i].primitive=UndefinedPrimitive;
5358       if (status == False)
5359         break;
5360     }
5361   (void) WriteBlobString(image,"</svg>\n");
5362   /*
5363     Free resources.
5364   */
5365   MagickFreeMemory(token);
5366   if (primitive_info != (PrimitiveInfo *) NULL)
5367     MagickFreeMemory(primitive_info);
5368   CloseBlob(image);
5369   return(status);
5370 }
5371 #endif
5372 #endif /* if ENABLE_SVG_WRITER */
5373