]> granicus.if.org Git - imagemagick/blob - coders/svg.c
Be safe, call libraw_strerr() before we close the library
[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__) && !defined(__MINGW64__)
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   entry->mime_type=ConstantString("image/svg+xml");
3350   if (*version != '\0')
3351     entry->version=ConstantString(version);
3352   entry->magick=(IsImageFormatHandler *) IsSVG;
3353   (void) RegisterMagickInfo(entry);
3354   entry=AcquireMagickInfo("SVG","SVGZ","Compressed Scalable Vector Graphics");
3355 #if defined(MAGICKCORE_XML_DELEGATE)
3356   entry->decoder=(DecodeImageHandler *) ReadSVGImage;
3357 #endif
3358   entry->encoder=(EncodeImageHandler *) WriteSVGImage;
3359   entry->flags^=CoderBlobSupportFlag;
3360   entry->mime_type=ConstantString("image/svg+xml");
3361   if (*version != '\0')
3362     entry->version=ConstantString(version);
3363   entry->magick=(IsImageFormatHandler *) IsSVG;
3364   (void) RegisterMagickInfo(entry);
3365   entry=AcquireMagickInfo("SVG","MSVG",
3366     "ImageMagick's own SVG internal renderer");
3367 #if defined(MAGICKCORE_XML_DELEGATE)
3368   entry->decoder=(DecodeImageHandler *) ReadSVGImage;
3369 #endif
3370   entry->encoder=(EncodeImageHandler *) WriteSVGImage;
3371   entry->flags^=CoderBlobSupportFlag;
3372   entry->magick=(IsImageFormatHandler *) IsSVG;
3373   (void) RegisterMagickInfo(entry);
3374   return(MagickImageCoderSignature);
3375 }
3376
3377
3378 /*
3379 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3380 %                                                                             %
3381 %                                                                             %
3382 %                                                                             %
3383 %   U n r e g i s t e r S V G I m a g e                                       %
3384 %                                                                             %
3385 %                                                                             %
3386 %                                                                             %
3387 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3388 %
3389 %  UnregisterSVGImage() removes format registrations made by the
3390 %  SVG module from the list of supported formats.
3391 %
3392 %  The format of the UnregisterSVGImage method is:
3393 %
3394 %      UnregisterSVGImage(void)
3395 %
3396 */
3397 ModuleExport void UnregisterSVGImage(void)
3398 {
3399   (void) UnregisterMagickInfo("SVGZ");
3400   (void) UnregisterMagickInfo("SVG");
3401   (void) UnregisterMagickInfo("MSVG");
3402 #if defined(MAGICKCORE_XML_DELEGATE)
3403   xmlCleanupParser();
3404 #endif
3405 }
3406
3407
3408 /*
3409 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3410 %                                                                             %
3411 %                                                                             %
3412 %                                                                             %
3413 %   W r i t e S V G I m a g e                                                 %
3414 %                                                                             %
3415 %                                                                             %
3416 %                                                                             %
3417 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3418 %
3419 %  WriteSVGImage() writes a image in the SVG - XML based W3C standard
3420 %  format.
3421 %
3422 %  The format of the WriteSVGImage method is:
3423 %
3424 %      MagickBooleanType WriteSVGImage(const ImageInfo *image_info,
3425 %        Image *image,ExceptionInfo *exception)
3426 %
3427 %  A description of each parameter follows.
3428 %
3429 %    o image_info: the image info.
3430 %
3431 %    o image:  The image.
3432 %
3433 %    o exception: return any errors or warnings in this structure.
3434 %
3435 */
3436
3437 static void AffineToTransform(Image *image,AffineMatrix *affine)
3438 {
3439   char
3440     transform[MagickPathExtent];
3441
3442   if ((fabs(affine->tx) < MagickEpsilon) && (fabs(affine->ty) < MagickEpsilon))
3443     {
3444       if ((fabs(affine->rx) < MagickEpsilon) &&
3445           (fabs(affine->ry) < MagickEpsilon))
3446         {
3447           if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
3448               (fabs(affine->sy-1.0) < MagickEpsilon))
3449             {
3450               (void) WriteBlobString(image,"\">\n");
3451               return;
3452             }
3453           (void) FormatLocaleString(transform,MagickPathExtent,
3454             "\" transform=\"scale(%g,%g)\">\n",affine->sx,affine->sy);
3455           (void) WriteBlobString(image,transform);
3456           return;
3457         }
3458       else
3459         {
3460           if ((fabs(affine->sx-affine->sy) < MagickEpsilon) &&
3461               (fabs(affine->rx+affine->ry) < MagickEpsilon) &&
3462               (fabs(affine->sx*affine->sx+affine->rx*affine->rx-1.0) <
3463                2*MagickEpsilon))
3464             {
3465               double
3466                 theta;
3467
3468               theta=(180.0/MagickPI)*atan2(affine->rx,affine->sx);
3469               (void) FormatLocaleString(transform,MagickPathExtent,
3470                 "\" transform=\"rotate(%g)\">\n",theta);
3471               (void) WriteBlobString(image,transform);
3472               return;
3473             }
3474         }
3475     }
3476   else
3477     {
3478       if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
3479           (fabs(affine->rx) < MagickEpsilon) &&
3480           (fabs(affine->ry) < MagickEpsilon) &&
3481           (fabs(affine->sy-1.0) < MagickEpsilon))
3482         {
3483           (void) FormatLocaleString(transform,MagickPathExtent,
3484             "\" transform=\"translate(%g,%g)\">\n",affine->tx,affine->ty);
3485           (void) WriteBlobString(image,transform);
3486           return;
3487         }
3488     }
3489   (void) FormatLocaleString(transform,MagickPathExtent,
3490     "\" transform=\"matrix(%g %g %g %g %g %g)\">\n",
3491     affine->sx,affine->rx,affine->ry,affine->sy,affine->tx,affine->ty);
3492   (void) WriteBlobString(image,transform);
3493 }
3494
3495 static MagickBooleanType IsPoint(const char *point)
3496 {
3497   char
3498     *p;
3499
3500   ssize_t
3501     value;
3502
3503   value=strtol(point,&p,10);
3504   (void) value;
3505   return(p != point ? MagickTrue : MagickFalse);
3506 }
3507
3508 static MagickBooleanType TraceSVGImage(Image *image,ExceptionInfo *exception)
3509 {
3510 #if defined(MAGICKCORE_AUTOTRACE_DELEGATE)
3511   {
3512     at_bitmap_type
3513       *trace;
3514
3515     at_fitting_opts_type
3516       *fitting_options;
3517
3518     at_output_opts_type
3519       *output_options;
3520
3521     at_splines_type
3522       *splines;
3523
3524     ImageType
3525       type;
3526
3527     register const Quantum
3528       *p;
3529
3530     register ssize_t
3531       i,
3532       x;
3533
3534     size_t
3535       number_planes;
3536
3537     ssize_t
3538       y;
3539
3540     /*
3541       Trace image and write as SVG.
3542     */
3543     fitting_options=at_fitting_opts_new();
3544     output_options=at_output_opts_new();
3545     (void) SetImageGray(image,exception);
3546     type=GetImageType(image);
3547     number_planes=3;
3548     if ((type == BilevelType) || (type == GrayscaleType))
3549       number_planes=1;
3550     trace=at_bitmap_new(image->columns,image->rows,number_planes);
3551     i=0;
3552     for (y=0; y < (ssize_t) image->rows; y++)
3553     {
3554       p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3555       if (p == (const Quantum *) NULL)
3556         break;
3557       for (x=0; x < (ssize_t) image->columns; x++)
3558       {
3559         trace->bitmap[i++]=GetPixelRed(image,p);
3560         if (number_planes == 3)
3561           {
3562             trace->bitmap[i++]=GetPixelGreen(image,p);
3563             trace->bitmap[i++]=GetPixelBlue(image,p);
3564           }
3565         p+=GetPixelChannels(image);
3566       }
3567     }
3568     splines=at_splines_new_full(trace,fitting_options,NULL,NULL,NULL,NULL,NULL,
3569       NULL);
3570     at_splines_write(at_output_get_handler_by_suffix((char *) "svg"),
3571       GetBlobFileHandle(image),image->filename,output_options,splines,NULL,
3572       NULL);
3573     /*
3574       Free resources.
3575     */
3576     at_splines_free(splines);
3577     at_bitmap_free(trace);
3578     at_output_opts_free(output_options);
3579     at_fitting_opts_free(fitting_options);
3580   }
3581 #else
3582   {
3583     char
3584       *base64,
3585       message[MagickPathExtent];
3586
3587     Image
3588       *clone_image;
3589
3590     ImageInfo
3591       *image_info;
3592
3593     register char
3594       *p;
3595
3596     size_t
3597       blob_length,
3598       encode_length;
3599
3600     ssize_t
3601       i;
3602
3603     unsigned char
3604       *blob;
3605
3606     (void) WriteBlobString(image,
3607       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
3608     (void) WriteBlobString(image,
3609       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"");
3610     (void) WriteBlobString(image,
3611       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
3612     (void) FormatLocaleString(message,MagickPathExtent,
3613       "<svg version=\"1.1\" id=\"Layer_1\" "
3614       "xmlns=\"http://www.w3.org/2000/svg\" "
3615       "xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" "
3616       "width=\"%.20gpx\" height=\"%.20gpx\" viewBox=\"0 0 %.20g %.20g\" "
3617       "enable-background=\"new 0 0 %.20g %.20g\" xml:space=\"preserve\">",
3618       (double) image->columns,(double) image->rows,
3619       (double) image->columns,(double) image->rows,
3620       (double) image->columns,(double) image->rows);
3621     (void) WriteBlobString(image,message);
3622     clone_image=CloneImage(image,0,0,MagickTrue,exception);
3623     if (clone_image == (Image *) NULL)
3624       return(MagickFalse);
3625     image_info=AcquireImageInfo();
3626     (void) CopyMagickString(image_info->magick,"PNG",MagickPathExtent);
3627     blob_length=2048;
3628     blob=(unsigned char *) ImageToBlob(image_info,clone_image,&blob_length,
3629       exception);
3630     clone_image=DestroyImage(clone_image);
3631     image_info=DestroyImageInfo(image_info);
3632     if (blob == (unsigned char *) NULL)
3633       return(MagickFalse);
3634     encode_length=0;
3635     base64=Base64Encode(blob,blob_length,&encode_length);
3636     blob=(unsigned char *) RelinquishMagickMemory(blob);
3637     (void) FormatLocaleString(message,MagickPathExtent,
3638       "  <image id=\"image%.20g\" width=\"%.20g\" height=\"%.20g\" "
3639       "x=\"%.20g\" y=\"%.20g\"\n    xlink:href=\"data:image/png;base64,",
3640       (double) image->scene,(double) image->columns,(double) image->rows,
3641       (double) image->page.x,(double) image->page.y);
3642     (void) WriteBlobString(image,message);
3643     p=base64;
3644     for (i=(ssize_t) encode_length; i > 0; i-=76)
3645     {
3646       (void) FormatLocaleString(message,MagickPathExtent,"%.76s",p);
3647       (void) WriteBlobString(image,message);
3648       p+=76;
3649       if (i > 76)
3650         (void) WriteBlobString(image,"\n");
3651     }
3652     base64=DestroyString(base64);
3653     (void) WriteBlobString(image,"\" />\n");
3654     (void) WriteBlobString(image,"</svg>\n");
3655   }
3656 #endif
3657   CloseBlob(image);
3658   return(MagickTrue);
3659 }
3660
3661 static MagickBooleanType WriteSVGImage(const ImageInfo *image_info,Image *image,
3662   ExceptionInfo *exception)
3663 {
3664 #define BezierQuantum  200
3665
3666   AffineMatrix
3667     affine;
3668
3669   char
3670     keyword[MagickPathExtent],
3671     message[MagickPathExtent],
3672     name[MagickPathExtent],
3673     *next_token,
3674     *token,
3675     type[MagickPathExtent];
3676
3677   const char
3678     *p,
3679     *q,
3680     *value;
3681
3682   int
3683     n;
3684
3685   ssize_t
3686     j;
3687
3688   MagickBooleanType
3689     active,
3690     status;
3691
3692   PointInfo
3693     point;
3694
3695   PrimitiveInfo
3696     *primitive_info;
3697
3698   PrimitiveType
3699     primitive_type;
3700
3701   register ssize_t
3702     x;
3703
3704   register ssize_t
3705     i;
3706
3707   size_t
3708     extent,
3709     length,
3710     number_points;
3711
3712   SVGInfo
3713     svg_info;
3714
3715   /*
3716     Open output image file.
3717   */
3718   assert(image_info != (const ImageInfo *) NULL);
3719   assert(image_info->signature == MagickCoreSignature);
3720   assert(image != (Image *) NULL);
3721   assert(image->signature == MagickCoreSignature);
3722   if (image->debug != MagickFalse)
3723     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3724   assert(exception != (ExceptionInfo *) NULL);
3725   assert(exception->signature == MagickCoreSignature);
3726   status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
3727   if (status == MagickFalse)
3728     return(status);
3729   value=GetImageArtifact(image,"SVG");
3730   if (value != (char *) NULL)
3731     {
3732       (void) WriteBlobString(image,value);
3733       (void) CloseBlob(image);
3734       return(MagickTrue);
3735     }
3736   value=GetImageArtifact(image,"MVG");
3737   if (value == (char *) NULL)
3738     return(TraceSVGImage(image,exception));
3739   /*
3740     Write SVG header.
3741   */
3742   (void) WriteBlobString(image,"<?xml version=\"1.0\" standalone=\"no\"?>\n");
3743   (void) WriteBlobString(image,
3744     "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n");
3745   (void) WriteBlobString(image,
3746     "  \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n");
3747   (void) FormatLocaleString(message,MagickPathExtent,
3748     "<svg width=\"%.20g\" height=\"%.20g\">\n",(double) image->columns,(double)
3749     image->rows);
3750   (void) WriteBlobString(image,message);
3751   /*
3752     Allocate primitive info memory.
3753   */
3754   number_points=2047;
3755   primitive_info=(PrimitiveInfo *) AcquireQuantumMemory(number_points,
3756     sizeof(*primitive_info));
3757   if (primitive_info == (PrimitiveInfo *) NULL)
3758     ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
3759   GetAffineMatrix(&affine);
3760   token=AcquireString(value);
3761   extent=strlen(token)+MagickPathExtent;
3762   active=MagickFalse;
3763   n=0;
3764   status=MagickTrue;
3765   for (q=(const char *) value; *q != '\0'; )
3766   {
3767     /*
3768       Interpret graphic primitive.
3769     */
3770     GetNextToken(q,&q,MagickPathExtent,keyword);
3771     if (*keyword == '\0')
3772       break;
3773     if (*keyword == '#')
3774       {
3775         /*
3776           Comment.
3777         */
3778         if (active != MagickFalse)
3779           {
3780             AffineToTransform(image,&affine);
3781             active=MagickFalse;
3782           }
3783         (void) WriteBlobString(image,"<desc>");
3784         (void) WriteBlobString(image,keyword+1);
3785         for ( ; (*q != '\n') && (*q != '\0'); q++)
3786           switch (*q)
3787           {
3788             case '<': (void) WriteBlobString(image,"&lt;"); break;
3789             case '>': (void) WriteBlobString(image,"&gt;"); break;
3790             case '&': (void) WriteBlobString(image,"&amp;"); break;
3791             default: (void) WriteBlobByte(image,*q); break;
3792           }
3793         (void) WriteBlobString(image,"</desc>\n");
3794         continue;
3795       }
3796     primitive_type=UndefinedPrimitive;
3797     switch (*keyword)
3798     {
3799       case ';':
3800         break;
3801       case 'a':
3802       case 'A':
3803       {
3804         if (LocaleCompare("affine",keyword) == 0)
3805           {
3806             GetNextToken(q,&q,extent,token);
3807             affine.sx=StringToDouble(token,&next_token);
3808             GetNextToken(q,&q,extent,token);
3809             if (*token == ',')
3810               GetNextToken(q,&q,extent,token);
3811             affine.rx=StringToDouble(token,&next_token);
3812             GetNextToken(q,&q,extent,token);
3813             if (*token == ',')
3814               GetNextToken(q,&q,extent,token);
3815             affine.ry=StringToDouble(token,&next_token);
3816             GetNextToken(q,&q,extent,token);
3817             if (*token == ',')
3818               GetNextToken(q,&q,extent,token);
3819             affine.sy=StringToDouble(token,&next_token);
3820             GetNextToken(q,&q,extent,token);
3821             if (*token == ',')
3822               GetNextToken(q,&q,extent,token);
3823             affine.tx=StringToDouble(token,&next_token);
3824             GetNextToken(q,&q,extent,token);
3825             if (*token == ',')
3826               GetNextToken(q,&q,extent,token);
3827             affine.ty=StringToDouble(token,&next_token);
3828             break;
3829           }
3830         if (LocaleCompare("alpha",keyword) == 0)
3831           {
3832             primitive_type=AlphaPrimitive;
3833             break;
3834           }
3835         if (LocaleCompare("angle",keyword) == 0)
3836           {
3837             GetNextToken(q,&q,extent,token);
3838             affine.rx=StringToDouble(token,&next_token);
3839             affine.ry=StringToDouble(token,&next_token);
3840             break;
3841           }
3842         if (LocaleCompare("arc",keyword) == 0)
3843           {
3844             primitive_type=ArcPrimitive;
3845             break;
3846           }
3847         status=MagickFalse;
3848         break;
3849       }
3850       case 'b':
3851       case 'B':
3852       {
3853         if (LocaleCompare("bezier",keyword) == 0)
3854           {
3855             primitive_type=BezierPrimitive;
3856             break;
3857           }
3858         status=MagickFalse;
3859         break;
3860       }
3861       case 'c':
3862       case 'C':
3863       {
3864         if (LocaleCompare("clip-path",keyword) == 0)
3865           {
3866             GetNextToken(q,&q,extent,token);
3867             (void) FormatLocaleString(message,MagickPathExtent,
3868               "clip-path:url(#%s);",token);
3869             (void) WriteBlobString(image,message);
3870             break;
3871           }
3872         if (LocaleCompare("clip-rule",keyword) == 0)
3873           {
3874             GetNextToken(q,&q,extent,token);
3875             (void) FormatLocaleString(message,MagickPathExtent,
3876               "clip-rule:%s;",token);
3877             (void) WriteBlobString(image,message);
3878             break;
3879           }
3880         if (LocaleCompare("clip-units",keyword) == 0)
3881           {
3882             GetNextToken(q,&q,extent,token);
3883             (void) FormatLocaleString(message,MagickPathExtent,
3884               "clipPathUnits=%s;",token);
3885             (void) WriteBlobString(image,message);
3886             break;
3887           }
3888         if (LocaleCompare("circle",keyword) == 0)
3889           {
3890             primitive_type=CirclePrimitive;
3891             break;
3892           }
3893         if (LocaleCompare("color",keyword) == 0)
3894           {
3895             primitive_type=ColorPrimitive;
3896             break;
3897           }
3898         status=MagickFalse;
3899         break;
3900       }
3901       case 'd':
3902       case 'D':
3903       {
3904         if (LocaleCompare("decorate",keyword) == 0)
3905           {
3906             GetNextToken(q,&q,extent,token);
3907             (void) FormatLocaleString(message,MagickPathExtent,
3908               "text-decoration:%s;",token);
3909             (void) WriteBlobString(image,message);
3910             break;
3911           }
3912         status=MagickFalse;
3913         break;
3914       }
3915       case 'e':
3916       case 'E':
3917       {
3918         if (LocaleCompare("ellipse",keyword) == 0)
3919           {
3920             primitive_type=EllipsePrimitive;
3921             break;
3922           }
3923         status=MagickFalse;
3924         break;
3925       }
3926       case 'f':
3927       case 'F':
3928       {
3929         if (LocaleCompare("fill",keyword) == 0)
3930           {
3931             GetNextToken(q,&q,extent,token);
3932             (void) FormatLocaleString(message,MagickPathExtent,"fill:%s;",
3933               token);
3934             (void) WriteBlobString(image,message);
3935             break;
3936           }
3937         if (LocaleCompare("fill-rule",keyword) == 0)
3938           {
3939             GetNextToken(q,&q,extent,token);
3940             (void) FormatLocaleString(message,MagickPathExtent,
3941               "fill-rule:%s;",token);
3942             (void) WriteBlobString(image,message);
3943             break;
3944           }
3945         if (LocaleCompare("fill-opacity",keyword) == 0)
3946           {
3947             GetNextToken(q,&q,extent,token);
3948             (void) FormatLocaleString(message,MagickPathExtent,
3949               "fill-opacity:%s;",token);
3950             (void) WriteBlobString(image,message);
3951             break;
3952           }
3953         if (LocaleCompare("font-family",keyword) == 0)
3954           {
3955             GetNextToken(q,&q,extent,token);
3956             (void) FormatLocaleString(message,MagickPathExtent,
3957               "font-family:%s;",token);
3958             (void) WriteBlobString(image,message);
3959             break;
3960           }
3961         if (LocaleCompare("font-stretch",keyword) == 0)
3962           {
3963             GetNextToken(q,&q,extent,token);
3964             (void) FormatLocaleString(message,MagickPathExtent,
3965               "font-stretch:%s;",token);
3966             (void) WriteBlobString(image,message);
3967             break;
3968           }
3969         if (LocaleCompare("font-style",keyword) == 0)
3970           {
3971             GetNextToken(q,&q,extent,token);
3972             (void) FormatLocaleString(message,MagickPathExtent,
3973               "font-style:%s;",token);
3974             (void) WriteBlobString(image,message);
3975             break;
3976           }
3977         if (LocaleCompare("font-size",keyword) == 0)
3978           {
3979             GetNextToken(q,&q,extent,token);
3980             (void) FormatLocaleString(message,MagickPathExtent,
3981               "font-size:%s;",token);
3982             (void) WriteBlobString(image,message);
3983             break;
3984           }
3985         if (LocaleCompare("font-weight",keyword) == 0)
3986           {
3987             GetNextToken(q,&q,extent,token);
3988             (void) FormatLocaleString(message,MagickPathExtent,
3989               "font-weight:%s;",token);
3990             (void) WriteBlobString(image,message);
3991             break;
3992           }
3993         status=MagickFalse;
3994         break;
3995       }
3996       case 'g':
3997       case 'G':
3998       {
3999         if (LocaleCompare("gradient-units",keyword) == 0)
4000           {
4001             GetNextToken(q,&q,extent,token);
4002             break;
4003           }
4004         if (LocaleCompare("text-align",keyword) == 0)
4005           {
4006             GetNextToken(q,&q,extent,token);
4007             (void) FormatLocaleString(message,MagickPathExtent,
4008               "text-align %s ",token);
4009             (void) WriteBlobString(image,message);
4010             break;
4011           }
4012         if (LocaleCompare("text-anchor",keyword) == 0)
4013           {
4014             GetNextToken(q,&q,extent,token);
4015             (void) FormatLocaleString(message,MagickPathExtent,
4016               "text-anchor %s ",token);
4017             (void) WriteBlobString(image,message);
4018             break;
4019           }
4020         status=MagickFalse;
4021         break;
4022       }
4023       case 'i':
4024       case 'I':
4025       {
4026         if (LocaleCompare("image",keyword) == 0)
4027           {
4028             GetNextToken(q,&q,extent,token);
4029             primitive_type=ImagePrimitive;
4030             break;
4031           }
4032         status=MagickFalse;
4033         break;
4034       }
4035       case 'l':
4036       case 'L':
4037       {
4038         if (LocaleCompare("line",keyword) == 0)
4039           {
4040             primitive_type=LinePrimitive;
4041             break;
4042           }
4043         status=MagickFalse;
4044         break;
4045       }
4046       case 'o':
4047       case 'O':
4048       {
4049         if (LocaleCompare("opacity",keyword) == 0)
4050           {
4051             GetNextToken(q,&q,extent,token);
4052             (void) FormatLocaleString(message,MagickPathExtent,"opacity %s ",
4053               token);
4054             (void) WriteBlobString(image,message);
4055             break;
4056           }
4057         status=MagickFalse;
4058         break;
4059       }
4060       case 'p':
4061       case 'P':
4062       {
4063         if (LocaleCompare("path",keyword) == 0)
4064           {
4065             primitive_type=PathPrimitive;
4066             break;
4067           }
4068         if (LocaleCompare("point",keyword) == 0)
4069           {
4070             primitive_type=PointPrimitive;
4071             break;
4072           }
4073         if (LocaleCompare("polyline",keyword) == 0)
4074           {
4075             primitive_type=PolylinePrimitive;
4076             break;
4077           }
4078         if (LocaleCompare("polygon",keyword) == 0)
4079           {
4080             primitive_type=PolygonPrimitive;
4081             break;
4082           }
4083         if (LocaleCompare("pop",keyword) == 0)
4084           {
4085             GetNextToken(q,&q,extent,token);
4086             if (LocaleCompare("clip-path",token) == 0)
4087               {
4088                 (void) WriteBlobString(image,"</clipPath>\n");
4089                 break;
4090               }
4091             if (LocaleCompare("defs",token) == 0)
4092               {
4093                 (void) WriteBlobString(image,"</defs>\n");
4094                 break;
4095               }
4096             if (LocaleCompare("gradient",token) == 0)
4097               {
4098                 (void) FormatLocaleString(message,MagickPathExtent,
4099                   "</%sGradient>\n",type);
4100                 (void) WriteBlobString(image,message);
4101                 break;
4102               }
4103             if (LocaleCompare("graphic-context",token) == 0)
4104               {
4105                 n--;
4106                 if (n < 0)
4107                   ThrowWriterException(DrawError,
4108                     "UnbalancedGraphicContextPushPop");
4109                 (void) WriteBlobString(image,"</g>\n");
4110               }
4111             if (LocaleCompare("pattern",token) == 0)
4112               {
4113                 (void) WriteBlobString(image,"</pattern>\n");
4114                 break;
4115               }
4116             if (LocaleCompare("defs",token) == 0)
4117             (void) WriteBlobString(image,"</g>\n");
4118             break;
4119           }
4120         if (LocaleCompare("push",keyword) == 0)
4121           {
4122             GetNextToken(q,&q,extent,token);
4123             if (LocaleCompare("clip-path",token) == 0)
4124               {
4125                 GetNextToken(q,&q,extent,token);
4126                 (void) FormatLocaleString(message,MagickPathExtent,
4127                   "<clipPath id=\"%s\">\n",token);
4128                 (void) WriteBlobString(image,message);
4129                 break;
4130               }
4131             if (LocaleCompare("defs",token) == 0)
4132               {
4133                 (void) WriteBlobString(image,"<defs>\n");
4134                 break;
4135               }
4136             if (LocaleCompare("gradient",token) == 0)
4137               {
4138                 GetNextToken(q,&q,extent,token);
4139                 (void) CopyMagickString(name,token,MagickPathExtent);
4140                 GetNextToken(q,&q,extent,token);
4141                 (void) CopyMagickString(type,token,MagickPathExtent);
4142                 GetNextToken(q,&q,extent,token);
4143                 svg_info.segment.x1=StringToDouble(token,&next_token);
4144                 svg_info.element.cx=StringToDouble(token,&next_token);
4145                 GetNextToken(q,&q,extent,token);
4146                 if (*token == ',')
4147                   GetNextToken(q,&q,extent,token);
4148                 svg_info.segment.y1=StringToDouble(token,&next_token);
4149                 svg_info.element.cy=StringToDouble(token,&next_token);
4150                 GetNextToken(q,&q,extent,token);
4151                 if (*token == ',')
4152                   GetNextToken(q,&q,extent,token);
4153                 svg_info.segment.x2=StringToDouble(token,&next_token);
4154                 svg_info.element.major=StringToDouble(token,
4155                   (char **) NULL);
4156                 GetNextToken(q,&q,extent,token);
4157                 if (*token == ',')
4158                   GetNextToken(q,&q,extent,token);
4159                 svg_info.segment.y2=StringToDouble(token,&next_token);
4160                 svg_info.element.minor=StringToDouble(token,
4161                   (char **) NULL);
4162                 (void) FormatLocaleString(message,MagickPathExtent,
4163                   "<%sGradient id=\"%s\" x1=\"%g\" y1=\"%g\" x2=\"%g\" "
4164                   "y2=\"%g\">\n",type,name,svg_info.segment.x1,
4165                   svg_info.segment.y1,svg_info.segment.x2,svg_info.segment.y2);
4166                 if (LocaleCompare(type,"radial") == 0)
4167                   {
4168                     GetNextToken(q,&q,extent,token);
4169                     if (*token == ',')
4170                       GetNextToken(q,&q,extent,token);
4171                     svg_info.element.angle=StringToDouble(token,
4172                       (char **) NULL);
4173                     (void) FormatLocaleString(message,MagickPathExtent,
4174                       "<%sGradient id=\"%s\" cx=\"%g\" cy=\"%g\" r=\"%g\" "
4175                       "fx=\"%g\" fy=\"%g\">\n",type,name,
4176                       svg_info.element.cx,svg_info.element.cy,
4177                       svg_info.element.angle,svg_info.element.major,
4178                       svg_info.element.minor);
4179                   }
4180                 (void) WriteBlobString(image,message);
4181                 break;
4182               }
4183             if (LocaleCompare("graphic-context",token) == 0)
4184               {
4185                 n++;
4186                 if (active)
4187                   {
4188                     AffineToTransform(image,&affine);
4189                     active=MagickFalse;
4190                   }
4191                 (void) WriteBlobString(image,"<g style=\"");
4192                 active=MagickTrue;
4193               }
4194             if (LocaleCompare("pattern",token) == 0)
4195               {
4196                 GetNextToken(q,&q,extent,token);
4197                 (void) CopyMagickString(name,token,MagickPathExtent);
4198                 GetNextToken(q,&q,extent,token);
4199                 svg_info.bounds.x=StringToDouble(token,&next_token);
4200                 GetNextToken(q,&q,extent,token);
4201                 if (*token == ',')
4202                   GetNextToken(q,&q,extent,token);
4203                 svg_info.bounds.y=StringToDouble(token,&next_token);
4204                 GetNextToken(q,&q,extent,token);
4205                 if (*token == ',')
4206                   GetNextToken(q,&q,extent,token);
4207                 svg_info.bounds.width=StringToDouble(token,
4208                   (char **) NULL);
4209                 GetNextToken(q,&q,extent,token);
4210                 if (*token == ',')
4211                   GetNextToken(q,&q,extent,token);
4212                 svg_info.bounds.height=StringToDouble(token,(char **) NULL);
4213                 (void) FormatLocaleString(message,MagickPathExtent,
4214                   "<pattern id=\"%s\" x=\"%g\" y=\"%g\" width=\"%g\" "
4215                   "height=\"%g\">\n",name,svg_info.bounds.x,svg_info.bounds.y,
4216                   svg_info.bounds.width,svg_info.bounds.height);
4217                 (void) WriteBlobString(image,message);
4218                 break;
4219               }
4220             break;
4221           }
4222         status=MagickFalse;
4223         break;
4224       }
4225       case 'r':
4226       case 'R':
4227       {
4228         if (LocaleCompare("rectangle",keyword) == 0)
4229           {
4230             primitive_type=RectanglePrimitive;
4231             break;
4232           }
4233         if (LocaleCompare("roundRectangle",keyword) == 0)
4234           {
4235             primitive_type=RoundRectanglePrimitive;
4236             break;
4237           }
4238         if (LocaleCompare("rotate",keyword) == 0)
4239           {
4240             GetNextToken(q,&q,extent,token);
4241             (void) FormatLocaleString(message,MagickPathExtent,"rotate(%s) ",
4242               token);
4243             (void) WriteBlobString(image,message);
4244             break;
4245           }
4246         status=MagickFalse;
4247         break;
4248       }
4249       case 's':
4250       case 'S':
4251       {
4252         if (LocaleCompare("scale",keyword) == 0)
4253           {
4254             GetNextToken(q,&q,extent,token);
4255             affine.sx=StringToDouble(token,&next_token);
4256             GetNextToken(q,&q,extent,token);
4257             if (*token == ',')
4258               GetNextToken(q,&q,extent,token);
4259             affine.sy=StringToDouble(token,&next_token);
4260             break;
4261           }
4262         if (LocaleCompare("skewX",keyword) == 0)
4263           {
4264             GetNextToken(q,&q,extent,token);
4265             (void) FormatLocaleString(message,MagickPathExtent,"skewX(%s) ",
4266               token);
4267             (void) WriteBlobString(image,message);
4268             break;
4269           }
4270         if (LocaleCompare("skewY",keyword) == 0)
4271           {
4272             GetNextToken(q,&q,extent,token);
4273             (void) FormatLocaleString(message,MagickPathExtent,"skewY(%s) ",
4274               token);
4275             (void) WriteBlobString(image,message);
4276             break;
4277           }
4278         if (LocaleCompare("stop-color",keyword) == 0)
4279           {
4280             char
4281               color[MagickPathExtent];
4282
4283             GetNextToken(q,&q,extent,token);
4284             (void) CopyMagickString(color,token,MagickPathExtent);
4285             GetNextToken(q,&q,extent,token);
4286             (void) FormatLocaleString(message,MagickPathExtent,
4287               "  <stop offset=\"%s\" stop-color=\"%s\" />\n",token,color);
4288             (void) WriteBlobString(image,message);
4289             break;
4290           }
4291         if (LocaleCompare("stroke",keyword) == 0)
4292           {
4293             GetNextToken(q,&q,extent,token);
4294             (void) FormatLocaleString(message,MagickPathExtent,"stroke:%s;",
4295               token);
4296             (void) WriteBlobString(image,message);
4297             break;
4298           }
4299         if (LocaleCompare("stroke-antialias",keyword) == 0)
4300           {
4301             GetNextToken(q,&q,extent,token);
4302             (void) FormatLocaleString(message,MagickPathExtent,
4303               "stroke-antialias:%s;",token);
4304             (void) WriteBlobString(image,message);
4305             break;
4306           }
4307         if (LocaleCompare("stroke-dasharray",keyword) == 0)
4308           {
4309             if (IsPoint(q))
4310               {
4311                 ssize_t
4312                   k;
4313
4314                 p=q;
4315                 GetNextToken(p,&p,extent,token);
4316                 for (k=0; IsPoint(token); k++)
4317                   GetNextToken(p,&p,extent,token);
4318                 (void) WriteBlobString(image,"stroke-dasharray:");
4319                 for (j=0; j < k; j++)
4320                 {
4321                   GetNextToken(q,&q,extent,token);
4322                   (void) FormatLocaleString(message,MagickPathExtent,"%s ",
4323                     token);
4324                   (void) WriteBlobString(image,message);
4325                 }
4326                 (void) WriteBlobString(image,";");
4327                 break;
4328               }
4329             GetNextToken(q,&q,extent,token);
4330             (void) FormatLocaleString(message,MagickPathExtent,
4331               "stroke-dasharray:%s;",token);
4332             (void) WriteBlobString(image,message);
4333             break;
4334           }
4335         if (LocaleCompare("stroke-dashoffset",keyword) == 0)
4336           {
4337             GetNextToken(q,&q,extent,token);
4338             (void) FormatLocaleString(message,MagickPathExtent,
4339               "stroke-dashoffset:%s;",token);
4340             (void) WriteBlobString(image,message);
4341             break;
4342           }
4343         if (LocaleCompare("stroke-linecap",keyword) == 0)
4344           {
4345             GetNextToken(q,&q,extent,token);
4346             (void) FormatLocaleString(message,MagickPathExtent,
4347               "stroke-linecap:%s;",token);
4348             (void) WriteBlobString(image,message);
4349             break;
4350           }
4351         if (LocaleCompare("stroke-linejoin",keyword) == 0)
4352           {
4353             GetNextToken(q,&q,extent,token);
4354             (void) FormatLocaleString(message,MagickPathExtent,
4355               "stroke-linejoin:%s;",token);
4356             (void) WriteBlobString(image,message);
4357             break;
4358           }
4359         if (LocaleCompare("stroke-miterlimit",keyword) == 0)
4360           {
4361             GetNextToken(q,&q,extent,token);
4362             (void) FormatLocaleString(message,MagickPathExtent,
4363               "stroke-miterlimit:%s;",token);
4364             (void) WriteBlobString(image,message);
4365             break;
4366           }
4367         if (LocaleCompare("stroke-opacity",keyword) == 0)
4368           {
4369             GetNextToken(q,&q,extent,token);
4370             (void) FormatLocaleString(message,MagickPathExtent,
4371               "stroke-opacity:%s;",token);
4372             (void) WriteBlobString(image,message);
4373             break;
4374           }
4375         if (LocaleCompare("stroke-width",keyword) == 0)
4376           {
4377             GetNextToken(q,&q,extent,token);
4378             (void) FormatLocaleString(message,MagickPathExtent,
4379               "stroke-width:%s;",token);
4380             (void) WriteBlobString(image,message);
4381             continue;
4382           }
4383         status=MagickFalse;
4384         break;
4385       }
4386       case 't':
4387       case 'T':
4388       {
4389         if (LocaleCompare("text",keyword) == 0)
4390           {
4391             primitive_type=TextPrimitive;
4392             break;
4393           }
4394         if (LocaleCompare("text-antialias",keyword) == 0)
4395           {
4396             GetNextToken(q,&q,extent,token);
4397             (void) FormatLocaleString(message,MagickPathExtent,
4398               "text-antialias:%s;",token);
4399             (void) WriteBlobString(image,message);
4400             break;
4401           }
4402         if (LocaleCompare("tspan",keyword) == 0)
4403           {
4404             primitive_type=TextPrimitive;
4405             break;
4406           }
4407         if (LocaleCompare("translate",keyword) == 0)
4408           {
4409             GetNextToken(q,&q,extent,token);
4410             affine.tx=StringToDouble(token,&next_token);
4411             GetNextToken(q,&q,extent,token);
4412             if (*token == ',')
4413               GetNextToken(q,&q,extent,token);
4414             affine.ty=StringToDouble(token,&next_token);
4415             break;
4416           }
4417         status=MagickFalse;
4418         break;
4419       }
4420       case 'v':
4421       case 'V':
4422       {
4423         if (LocaleCompare("viewbox",keyword) == 0)
4424           {
4425             GetNextToken(q,&q,extent,token);
4426             if (*token == ',')
4427               GetNextToken(q,&q,extent,token);
4428             GetNextToken(q,&q,extent,token);
4429             if (*token == ',')
4430               GetNextToken(q,&q,extent,token);
4431             GetNextToken(q,&q,extent,token);
4432             if (*token == ',')
4433               GetNextToken(q,&q,extent,token);
4434             GetNextToken(q,&q,extent,token);
4435             break;
4436           }
4437         status=MagickFalse;
4438         break;
4439       }
4440       default:
4441       {
4442         status=MagickFalse;
4443         break;
4444       }
4445     }
4446     if (status == MagickFalse)
4447       break;
4448     if (primitive_type == UndefinedPrimitive)
4449       continue;
4450     /*
4451       Parse the primitive attributes.
4452     */
4453     i=0;
4454     j=0;
4455     for (x=0; *q != '\0'; x++)
4456     {
4457       /*
4458         Define points.
4459       */
4460       if (IsPoint(q) == MagickFalse)
4461         break;
4462       GetNextToken(q,&q,extent,token);
4463       point.x=StringToDouble(token,&next_token);
4464       GetNextToken(q,&q,extent,token);
4465       if (*token == ',')
4466         GetNextToken(q,&q,extent,token);
4467       point.y=StringToDouble(token,&next_token);
4468       GetNextToken(q,(const char **) NULL,extent,token);
4469       if (*token == ',')
4470         GetNextToken(q,&q,extent,token);
4471       primitive_info[i].primitive=primitive_type;
4472       primitive_info[i].point=point;
4473       primitive_info[i].coordinates=0;
4474       primitive_info[i].method=FloodfillMethod;
4475       i++;
4476       if (i < (ssize_t) (number_points-6*BezierQuantum-360))
4477         continue;
4478       number_points+=6*BezierQuantum+360;
4479       primitive_info=(PrimitiveInfo *) ResizeQuantumMemory(primitive_info,
4480         number_points,sizeof(*primitive_info));
4481       if (primitive_info == (PrimitiveInfo *) NULL)
4482         {
4483           (void) ThrowMagickException(exception,GetMagickModule(),
4484             ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
4485           break;
4486         }
4487     }
4488     primitive_info[j].primitive=primitive_type;
4489     primitive_info[j].coordinates=x;
4490     primitive_info[j].method=FloodfillMethod;
4491     primitive_info[j].text=(char *) NULL;
4492     if (active)
4493       {
4494         AffineToTransform(image,&affine);
4495         active=MagickFalse;
4496       }
4497     active=MagickFalse;
4498     switch (primitive_type)
4499     {
4500       case PointPrimitive:
4501       default:
4502       {
4503         if (primitive_info[j].coordinates != 1)
4504           {
4505             status=MagickFalse;
4506             break;
4507           }
4508         break;
4509       }
4510       case LinePrimitive:
4511       {
4512         if (primitive_info[j].coordinates != 2)
4513           {
4514             status=MagickFalse;
4515             break;
4516           }
4517           (void) FormatLocaleString(message,MagickPathExtent,
4518           "  <line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"/>\n",
4519           primitive_info[j].point.x,primitive_info[j].point.y,
4520           primitive_info[j+1].point.x,primitive_info[j+1].point.y);
4521         (void) WriteBlobString(image,message);
4522         break;
4523       }
4524       case RectanglePrimitive:
4525       {
4526         if (primitive_info[j].coordinates != 2)
4527           {
4528             status=MagickFalse;
4529             break;
4530           }
4531           (void) FormatLocaleString(message,MagickPathExtent,
4532           "  <rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"/>\n",
4533           primitive_info[j].point.x,primitive_info[j].point.y,
4534           primitive_info[j+1].point.x-primitive_info[j].point.x,
4535           primitive_info[j+1].point.y-primitive_info[j].point.y);
4536         (void) WriteBlobString(image,message);
4537         break;
4538       }
4539       case RoundRectanglePrimitive:
4540       {
4541         if (primitive_info[j].coordinates != 3)
4542           {
4543             status=MagickFalse;
4544             break;
4545           }
4546         (void) FormatLocaleString(message,MagickPathExtent,
4547           "  <rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" rx=\"%g\" "
4548           "ry=\"%g\"/>\n",primitive_info[j].point.x,
4549           primitive_info[j].point.y,primitive_info[j+1].point.x-
4550           primitive_info[j].point.x,primitive_info[j+1].point.y-
4551           primitive_info[j].point.y,primitive_info[j+2].point.x,
4552           primitive_info[j+2].point.y);
4553         (void) WriteBlobString(image,message);
4554         break;
4555       }
4556       case ArcPrimitive:
4557       {
4558         if (primitive_info[j].coordinates != 3)
4559           {
4560             status=MagickFalse;
4561             break;
4562           }
4563         break;
4564       }
4565       case EllipsePrimitive:
4566       {
4567         if (primitive_info[j].coordinates != 3)
4568           {
4569             status=MagickFalse;
4570             break;
4571           }
4572           (void) FormatLocaleString(message,MagickPathExtent,
4573           "  <ellipse cx=\"%g\" cy=\"%g\" rx=\"%g\" ry=\"%g\"/>\n",
4574           primitive_info[j].point.x,primitive_info[j].point.y,
4575           primitive_info[j+1].point.x,primitive_info[j+1].point.y);
4576         (void) WriteBlobString(image,message);
4577         break;
4578       }
4579       case CirclePrimitive:
4580       {
4581         double
4582           alpha,
4583           beta;
4584
4585         if (primitive_info[j].coordinates != 2)
4586           {
4587             status=MagickFalse;
4588             break;
4589           }
4590         alpha=primitive_info[j+1].point.x-primitive_info[j].point.x;
4591         beta=primitive_info[j+1].point.y-primitive_info[j].point.y;
4592         (void) FormatLocaleString(message,MagickPathExtent,
4593           "  <circle cx=\"%g\" cy=\"%g\" r=\"%g\"/>\n",
4594           primitive_info[j].point.x,primitive_info[j].point.y,
4595           hypot(alpha,beta));
4596         (void) WriteBlobString(image,message);
4597         break;
4598       }
4599       case PolylinePrimitive:
4600       {
4601         if (primitive_info[j].coordinates < 2)
4602           {
4603             status=MagickFalse;
4604             break;
4605           }
4606         (void) CopyMagickString(message,"  <polyline points=\"",
4607            MagickPathExtent);
4608         (void) WriteBlobString(image,message);
4609         length=strlen(message);
4610         for ( ; j < i; j++)
4611         {
4612           (void) FormatLocaleString(message,MagickPathExtent,"%g,%g ",
4613             primitive_info[j].point.x,primitive_info[j].point.y);
4614           length+=strlen(message);
4615           if (length >= 80)
4616             {
4617               (void) WriteBlobString(image,"\n    ");
4618               length=strlen(message)+5;
4619             }
4620           (void) WriteBlobString(image,message);
4621         }
4622         (void) WriteBlobString(image,"\"/>\n");
4623         break;
4624       }
4625       case PolygonPrimitive:
4626       {
4627         if (primitive_info[j].coordinates < 3)
4628           {
4629             status=MagickFalse;
4630             break;
4631           }
4632         primitive_info[i]=primitive_info[j];
4633         primitive_info[i].coordinates=0;
4634         primitive_info[j].coordinates++;
4635         i++;
4636         (void) CopyMagickString(message,"  <polygon points=\"",MagickPathExtent);
4637         (void) WriteBlobString(image,message);
4638         length=strlen(message);
4639         for ( ; j < i; j++)
4640         {
4641           (void) FormatLocaleString(message,MagickPathExtent,"%g,%g ",
4642             primitive_info[j].point.x,primitive_info[j].point.y);
4643           length+=strlen(message);
4644           if (length >= 80)
4645             {
4646               (void) WriteBlobString(image,"\n    ");
4647               length=strlen(message)+5;
4648             }
4649           (void) WriteBlobString(image,message);
4650         }
4651         (void) WriteBlobString(image,"\"/>\n");
4652         break;
4653       }
4654       case BezierPrimitive:
4655       {
4656         if (primitive_info[j].coordinates < 3)
4657           {
4658             status=MagickFalse;
4659             break;
4660           }
4661         break;
4662       }
4663       case PathPrimitive:
4664       {
4665         int
4666           number_attributes;
4667
4668         GetNextToken(q,&q,extent,token);
4669         number_attributes=1;
4670         for (p=token; *p != '\0'; p++)
4671           if (isalpha((int) *p))
4672             number_attributes++;
4673         if (i > (ssize_t) (number_points-6*BezierQuantum*number_attributes-1))
4674           {
4675             number_points+=6*BezierQuantum*number_attributes;
4676             primitive_info=(PrimitiveInfo *) ResizeQuantumMemory(primitive_info,
4677               number_points,sizeof(*primitive_info));
4678             if (primitive_info == (PrimitiveInfo *) NULL)
4679               {
4680                 (void) ThrowMagickException(exception,GetMagickModule(),
4681                   ResourceLimitError,"MemoryAllocationFailed","`%s'",
4682                   image->filename);
4683                 break;
4684               }
4685           }
4686         (void) WriteBlobString(image,"  <path d=\"");
4687         (void) WriteBlobString(image,token);
4688         (void) WriteBlobString(image,"\"/>\n");
4689         break;
4690       }
4691       case AlphaPrimitive:
4692       case ColorPrimitive:
4693       {
4694         if (primitive_info[j].coordinates != 1)
4695           {
4696             status=MagickFalse;
4697             break;
4698           }
4699         GetNextToken(q,&q,extent,token);
4700         if (LocaleCompare("point",token) == 0)
4701           primitive_info[j].method=PointMethod;
4702         if (LocaleCompare("replace",token) == 0)
4703           primitive_info[j].method=ReplaceMethod;
4704         if (LocaleCompare("floodfill",token) == 0)
4705           primitive_info[j].method=FloodfillMethod;
4706         if (LocaleCompare("filltoborder",token) == 0)
4707           primitive_info[j].method=FillToBorderMethod;
4708         if (LocaleCompare("reset",token) == 0)
4709           primitive_info[j].method=ResetMethod;
4710         break;
4711       }
4712       case TextPrimitive:
4713       {
4714         register char
4715           *p;
4716
4717         if (primitive_info[j].coordinates != 1)
4718           {
4719             status=MagickFalse;
4720             break;
4721           }
4722         GetNextToken(q,&q,extent,token);
4723         (void) FormatLocaleString(message,MagickPathExtent,
4724           "  <text x=\"%g\" y=\"%g\">",primitive_info[j].point.x,
4725           primitive_info[j].point.y);
4726         (void) WriteBlobString(image,message);
4727         for (p=token; *p != '\0'; p++)
4728           switch (*p)
4729           {
4730             case '<': (void) WriteBlobString(image,"&lt;"); break;
4731             case '>': (void) WriteBlobString(image,"&gt;"); break;
4732             case '&': (void) WriteBlobString(image,"&amp;"); break;
4733             default: (void) WriteBlobByte(image,*p); break;
4734           }
4735         (void) WriteBlobString(image,"</text>\n");
4736         break;
4737       }
4738       case ImagePrimitive:
4739       {
4740         if (primitive_info[j].coordinates != 2)
4741           {
4742             status=MagickFalse;
4743             break;
4744           }
4745         GetNextToken(q,&q,extent,token);
4746         (void) FormatLocaleString(message,MagickPathExtent,
4747           "  <image x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" "
4748           "xlink:href=\"%s\"/>\n",primitive_info[j].point.x,
4749           primitive_info[j].point.y,primitive_info[j+1].point.x,
4750           primitive_info[j+1].point.y,token);
4751         (void) WriteBlobString(image,message);
4752         break;
4753       }
4754     }
4755     if (primitive_info == (PrimitiveInfo *) NULL)
4756       break;
4757     primitive_info[i].primitive=UndefinedPrimitive;
4758     if (status == MagickFalse)
4759       break;
4760   }
4761   (void) WriteBlobString(image,"</svg>\n");
4762   /*
4763     Relinquish resources.
4764   */
4765   token=DestroyString(token);
4766   if (primitive_info != (PrimitiveInfo *) NULL)
4767     primitive_info=(PrimitiveInfo *) RelinquishMagickMemory(primitive_info);
4768   (void) CloseBlob(image);
4769   return(status);
4770 }