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