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