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