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