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