blob: 1072365839d99db512d14bc35a3e1b275d63242b [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% SSSSS V V GGGG %
7% SS V V G %
8% SSS V V G GG %
9% SS V V G G %
10% SSSSS V GGG %
11% %
12% %
13% Read/Write Scalable Vector Graphics Format %
14% %
15% Software Design %
cristyde984cd2013-12-01 14:49:27 +000016% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000017% William Radcliffe %
18% March 2000 %
19% %
20% %
Cristyd8420112021-01-01 14:52:00 -050021% Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000022% dedicated to making software imaging solutions freely available. %
23% %
24% You may not use this file except in compliance with the License. You may %
25% obtain a copy of the License at %
26% %
Cristy83d74de2018-10-13 10:17:25 -040027% https://imagemagick.org/script/license.php %
cristy3ed852e2009-09-05 21:47:34 +000028% %
29% Unless required by applicable law or agreed to in writing, software %
30% distributed under the License is distributed on an "AS IS" BASIS, %
31% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
32% See the License for the specific language governing permissions and %
33% limitations under the License. %
34% %
35%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
36%
37%
38*/
Cristyd51e6172018-04-28 09:01:01 -040039
cristy3ed852e2009-09-05 21:47:34 +000040/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/annotate.h"
45#include "MagickCore/artifact.h"
46#include "MagickCore/attribute.h"
47#include "MagickCore/blob.h"
48#include "MagickCore/blob-private.h"
49#include "MagickCore/cache.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/composite-private.h"
cristy1164d5f2012-08-15 00:58:25 +000052#include "MagickCore/delegate.h"
53#include "MagickCore/delegate-private.h"
cristy4c08aed2011-07-01 19:47:50 +000054#include "MagickCore/draw.h"
55#include "MagickCore/exception.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/gem.h"
58#include "MagickCore/image.h"
59#include "MagickCore/image-private.h"
60#include "MagickCore/list.h"
61#include "MagickCore/log.h"
62#include "MagickCore/magick.h"
63#include "MagickCore/memory_.h"
Dirk Lemstra06344a02017-10-15 10:10:01 +020064#include "MagickCore/memory-private.h"
cristy4c08aed2011-07-01 19:47:50 +000065#include "MagickCore/module.h"
66#include "MagickCore/monitor.h"
67#include "MagickCore/monitor-private.h"
Cristyec9c8942019-10-23 06:01:08 -040068#include "MagickCore/option.h"
cristy4c08aed2011-07-01 19:47:50 +000069#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/property.h"
Cristyec9c8942019-10-23 06:01:08 -040071#include "MagickCore/quantum-private.h"
cristy4c08aed2011-07-01 19:47:50 +000072#include "MagickCore/resource_.h"
73#include "MagickCore/static.h"
74#include "MagickCore/string_.h"
75#include "MagickCore/string-private.h"
76#include "MagickCore/token.h"
77#include "MagickCore/utility.h"
Cristyfedaa752019-02-03 07:23:20 -050078
cristy3ed852e2009-09-05 21:47:34 +000079#if defined(MAGICKCORE_XML_DELEGATE)
cristy0157aea2010-04-24 21:12:18 +000080# if defined(MAGICKCORE_WINDOWS_SUPPORT)
Dirk Lemstrada5b62d2017-10-01 22:28:46 +020081# if !defined(__MINGW32__)
cristy3ed852e2009-09-05 21:47:34 +000082# include <win32config.h>
83# endif
84# endif
cristy3ed852e2009-09-05 21:47:34 +000085# include <libxml/xmlmemory.h>
86# include <libxml/parserInternals.h>
87# include <libxml/xmlerror.h>
88#endif
89
90#if defined(MAGICKCORE_AUTOTRACE_DELEGATE)
91#include "autotrace/autotrace.h"
92#endif
93
94#if defined(MAGICKCORE_RSVG_DELEGATE)
95#include "librsvg/rsvg.h"
anthonyb17d1642013-03-18 00:53:33 +000096#if !defined(LIBRSVG_CHECK_VERSION)
97#include "librsvg/rsvg-cairo.h"
98#include "librsvg/librsvg-features.h"
99#elif !LIBRSVG_CHECK_VERSION(2,36,2)
cristy3ed852e2009-09-05 21:47:34 +0000100#include "librsvg/rsvg-cairo.h"
cristy3ed852e2009-09-05 21:47:34 +0000101#include "librsvg/librsvg-features.h"
102#endif
cristyd1b3b292013-03-09 17:29:47 +0000103#endif
Cristyd51e6172018-04-28 09:01:01 -0400104
cristy3ed852e2009-09-05 21:47:34 +0000105/*
Cristyb297b0f2018-12-01 11:53:59 -0500106 Define declarations.
107*/
108#define DefaultSVGDensity 96.0
109
110/*
cristy3ed852e2009-09-05 21:47:34 +0000111 Typedef declarations.
112*/
113typedef struct _BoundingBox
114{
115 double
116 x,
117 y,
118 width,
119 height;
120} BoundingBox;
121
122typedef struct _ElementInfo
123{
124 double
125 cx,
126 cy,
127 major,
128 minor,
129 angle;
130} ElementInfo;
131
132typedef struct _SVGInfo
133{
134 FILE
135 *file;
136
137 ExceptionInfo
138 *exception;
139
140 Image
141 *image;
142
143 const ImageInfo
144 *image_info;
145
146 AffineMatrix
147 affine;
148
cristybb503372010-05-27 20:51:26 +0000149 size_t
cristy3ed852e2009-09-05 21:47:34 +0000150 width,
151 height;
152
153 char
154 *size,
155 *title,
156 *comment;
157
158 int
159 n;
160
161 double
162 *scale,
163 pointsize;
164
165 ElementInfo
166 element;
167
168 SegmentInfo
169 segment;
170
171 BoundingBox
172 bounds,
Cristyc13321f2019-05-26 19:05:02 -0400173 text_offset,
cristy3ed852e2009-09-05 21:47:34 +0000174 view_box;
175
176 PointInfo
177 radius;
178
179 char
180 *stop_color,
181 *offset,
182 *text,
183 *vertices,
184 *url;
185
186#if defined(MAGICKCORE_XML_DELEGATE)
187 xmlParserCtxtPtr
188 parser;
189
190 xmlDocPtr
191 document;
192#endif
Cristy80c52892018-05-03 20:06:56 -0400193
194 ssize_t
195 svgDepth;
cristy3ed852e2009-09-05 21:47:34 +0000196} SVGInfo;
Cristyd51e6172018-04-28 09:01:01 -0400197
cristy3ed852e2009-09-05 21:47:34 +0000198/*
Cristyfedaa752019-02-03 07:23:20 -0500199 Static declarations.
200*/
201static char
202 SVGDensityGeometry[] = "96.0x96.0";
203
204/*
cristy3ed852e2009-09-05 21:47:34 +0000205 Forward declarations.
206*/
207static MagickBooleanType
cristy3a37efd2011-08-28 20:31:03 +0000208 WriteSVGImage(const ImageInfo *,Image *,ExceptionInfo *);
Cristy868ede62018-02-26 19:04:51 -0500209
cristy3ed852e2009-09-05 21:47:34 +0000210/*
211%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
212% %
213% %
214% %
215% I s S V G %
216% %
217% %
218% %
219%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
220%
221% IsSVG()() returns MagickTrue if the image format type, identified by the
222% magick string, is SVG.
223%
224% The format of the IsSVG method is:
225%
226% MagickBooleanType IsSVG(const unsigned char *magick,const size_t length)
227%
228% A description of each parameter follows:
229%
230% o magick: compare image format pattern against these bytes.
231%
232% o length: Specifies the length of the magick string.
233%
234*/
235static MagickBooleanType IsSVG(const unsigned char *magick,const size_t length)
236{
237 if (length < 4)
238 return(MagickFalse);
Dirk Lemstrafde65722018-07-08 22:16:50 +0200239 if (LocaleNCompare((const char *) magick+1,"svg",3) == 0)
240 return(MagickTrue);
241 if (length < 5)
242 return(MagickFalse);
243 if (LocaleNCompare((const char *) magick+1,"?xml",4) == 0)
cristy3ed852e2009-09-05 21:47:34 +0000244 return(MagickTrue);
245 return(MagickFalse);
246}
Dirk Lemstrafde65722018-07-08 22:16:50 +0200247
cristy3ed852e2009-09-05 21:47:34 +0000248/*
249%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
250% %
251% %
252% %
253% R e a d S V G I m a g e %
254% %
255% %
256% %
257%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
258%
259% ReadSVGImage() reads a Scalable Vector Gaphics file and returns it. It
260% allocates the memory necessary for the new Image structure and returns a
261% pointer to the new image.
262%
263% The format of the ReadSVGImage method is:
264%
265% Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
266%
267% A description of each parameter follows:
268%
269% o image_info: the image info.
270%
271% o exception: return any errors or warnings in this structure.
272%
273*/
274
Cristyfedaa752019-02-03 07:23:20 -0500275static Image *RenderSVGImage(const ImageInfo *image_info,Image *image,
276 ExceptionInfo *exception)
277{
278 char
279 background[MagickPathExtent],
280 command[MagickPathExtent],
281 *density,
282 input_filename[MagickPathExtent],
283 opacity[MagickPathExtent],
284 output_filename[MagickPathExtent],
285 unique[MagickPathExtent];
286
287 const DelegateInfo
288 *delegate_info;
289
290 Image
291 *next;
292
293 int
294 status;
295
296 struct stat
297 attributes;
298
299 /*
300 Our best hope for compliance with the SVG standard.
301 */
302 delegate_info=GetDelegateInfo("svg:decode",(char *) NULL,exception);
303 if (delegate_info == (const DelegateInfo *) NULL)
304 return((Image *) NULL);
305 status=AcquireUniqueSymbolicLink(image->filename,input_filename);
Cristy0c322462020-02-18 12:37:02 -0500306 (void) AcquireUniqueFilename(unique);
307 (void) FormatLocaleString(output_filename,MagickPathExtent,"%s.png",unique);
Cristyfedaa752019-02-03 07:23:20 -0500308 (void) AcquireUniqueFilename(unique);
309 density=AcquireString("");
Cristy0c322462020-02-18 12:37:02 -0500310 (void) FormatLocaleString(density,MagickPathExtent,"%.20g",
311 ceil(sqrt(image->resolution.x*image->resolution.y)-0.5));
Cristyfedaa752019-02-03 07:23:20 -0500312 (void) FormatLocaleString(background,MagickPathExtent,
313 "rgb(%.20g%%,%.20g%%,%.20g%%)",
314 100.0*QuantumScale*image->background_color.red,
315 100.0*QuantumScale*image->background_color.green,
316 100.0*QuantumScale*image->background_color.blue);
317 (void) FormatLocaleString(opacity,MagickPathExtent,"%.20g",QuantumScale*
Cristy4e28e4a2020-11-13 22:28:33 +0000318 image->background_color.alpha-MagickEpsilon);
Cristyfedaa752019-02-03 07:23:20 -0500319 (void) FormatLocaleString(command,MagickPathExtent,
320 GetDelegateCommands(delegate_info),input_filename,output_filename,density,
321 background,opacity,unique);
322 density=DestroyString(density);
323 status=ExternalDelegateCommand(MagickFalse,image_info->verbose,command,
324 (char *) NULL,exception);
325 (void) RelinquishUniqueFileResource(unique);
326 (void) RelinquishUniqueFileResource(input_filename);
327 if ((status == 0) && (stat(output_filename,&attributes) == 0) &&
328 (attributes.st_size > 0))
329 {
330 Image
331 *svg_image;
332
333 ImageInfo
334 *read_info;
335
336 read_info=CloneImageInfo(image_info);
337 (void) CopyMagickString(read_info->filename,output_filename,
338 MagickPathExtent);
339 svg_image=ReadImage(read_info,exception);
340 read_info=DestroyImageInfo(read_info);
341 if (svg_image != (Image *) NULL)
342 {
343 (void) RelinquishUniqueFileResource(output_filename);
344 for (next=GetFirstImageInList(svg_image); next != (Image *) NULL; )
345 {
346 (void) CopyMagickString(next->filename,image->filename,
Elliott Hughes5d41fba2021-04-12 16:36:42 -0700347 MagickPathExtent);
348 (void) CopyMagickString(next->magick,image->magick,
349 MagickPathExtent);
Cristyfedaa752019-02-03 07:23:20 -0500350 next=GetNextImageInList(next);
351 }
352 return(svg_image);
353 }
354 }
355 (void) RelinquishUniqueFileResource(output_filename);
356 return((Image *) NULL);
357}
358
Elliott Hughes5d41fba2021-04-12 16:36:42 -0700359#if defined(MAGICKCORE_RSVG_DELEGATE)
360static Image *RenderRSVGImage(const ImageInfo *image_info,Image *image,
361 ExceptionInfo *exception)
362{
363#if defined(MAGICKCORE_CAIRO_DELEGATE)
364 cairo_surface_t
365 *cairo_surface;
366
367 cairo_t
368 *cairo_image;
369
370 MagickBooleanType
371 apply_density;
372
373 MemoryInfo
374 *pixel_info;
375
376 unsigned char
377 *p;
378
379 RsvgDimensionData
380 dimension_info;
381
382 unsigned char
383 *pixels;
384
385#else
386 GdkPixbuf
387 *pixel_buffer;
388
389 const guchar
390 *p;
391#endif
392
393 const char
394 *option;
395
396 GError
397 *error;
398
399 Image
400 *next;
401
402 MagickBooleanType
403 status;
404
405 PixelInfo
406 fill_color;
407
408 ssize_t
409 x,
410 n;
411
412 Quantum
413 *q;
414
415 RsvgHandle
416 *svg_handle;
417
418 ssize_t
419 y;
420
421 unsigned char
422 *buffer;
423
424 buffer=(unsigned char *) AcquireQuantumMemory(MagickMaxBufferExtent,
425 sizeof(*buffer));
426 if (buffer == (unsigned char *) NULL)
427 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
428#if LIBRSVG_CHECK_VERSION(2,40,3)
429 option=GetImageOption(image_info,"svg:xml-parse-huge");
430 if ((option != (char *) NULL) && (IsStringTrue(option) != MagickFalse))
431 svg_handle=rsvg_handle_new_with_flags(RSVG_HANDLE_FLAG_UNLIMITED);
432 else
433#endif
434 svg_handle=rsvg_handle_new();
435 if (svg_handle == (RsvgHandle *) NULL)
436 {
437 buffer=(unsigned char *) RelinquishMagickMemory(buffer);
438 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
439 }
440 rsvg_handle_set_base_uri(svg_handle,image_info->filename);
441 if ((fabs(image->resolution.x) > MagickEpsilon) &&
442 (fabs(image->resolution.y) > MagickEpsilon))
443 rsvg_handle_set_dpi_x_y(svg_handle,image->resolution.x,
444 image->resolution.y);
445 while ((n=ReadBlob(image,MagickMaxBufferExtent-1,buffer)) != 0)
446 {
447 buffer[n]='\0';
448 error=(GError *) NULL;
449 (void) rsvg_handle_write(svg_handle,buffer,n,&error);
450 if (error != (GError *) NULL)
451 g_error_free(error);
452 }
453 buffer=(unsigned char *) RelinquishMagickMemory(buffer);
454 error=(GError *) NULL;
455 rsvg_handle_close(svg_handle,&error);
456 if (error != (GError *) NULL)
457 g_error_free(error);
458#if defined(MAGICKCORE_CAIRO_DELEGATE)
459 apply_density=MagickTrue;
460 rsvg_handle_get_dimensions(svg_handle,&dimension_info);
461 if ((image->resolution.x > 0.0) && (image->resolution.y > 0.0))
462 {
463 RsvgDimensionData
464 dpi_dimension_info;
465
466 /*
467 We should not apply the density when the internal 'factor' is 'i'.
468 This can be checked by using the trick below.
469 */
470 rsvg_handle_set_dpi_x_y(svg_handle,image->resolution.x*256,
471 image->resolution.y*256);
472 rsvg_handle_get_dimensions(svg_handle,&dpi_dimension_info);
473 if ((fabs(dpi_dimension_info.width-dimension_info.width) >= MagickEpsilon) ||
474 (fabs(dpi_dimension_info.height-dimension_info.height) >= MagickEpsilon))
475 apply_density=MagickFalse;
476 rsvg_handle_set_dpi_x_y(svg_handle,image->resolution.x,
477 image->resolution.y);
478 }
479 if (image_info->size != (char *) NULL)
480 {
481 (void) GetGeometry(image_info->size,(ssize_t *) NULL,
482 (ssize_t *) NULL,&image->columns,&image->rows);
483 if ((image->columns != 0) || (image->rows != 0))
484 {
485 image->resolution.x=DefaultSVGDensity*image->columns/
486 dimension_info.width;
487 image->resolution.y=DefaultSVGDensity*image->rows/
488 dimension_info.height;
489 if (fabs(image->resolution.x) < MagickEpsilon)
490 image->resolution.x=image->resolution.y;
491 else
492 if (fabs(image->resolution.y) < MagickEpsilon)
493 image->resolution.y=image->resolution.x;
494 else
495 image->resolution.x=image->resolution.y=MagickMin(
496 image->resolution.x,image->resolution.y);
497 apply_density=MagickTrue;
498 }
499 }
500 if (apply_density != MagickFalse)
501 {
502 image->columns=image->resolution.x*dimension_info.width/
503 DefaultSVGDensity;
504 image->rows=image->resolution.y*dimension_info.height/
505 DefaultSVGDensity;
506 }
507 else
508 {
509 image->columns=dimension_info.width;
510 image->rows=dimension_info.height;
511 }
512 pixel_info=(MemoryInfo *) NULL;
513#else
514 pixel_buffer=rsvg_handle_get_pixbuf(svg_handle);
515 rsvg_handle_free(svg_handle);
516 image->columns=gdk_pixbuf_get_width(pixel_buffer);
517 image->rows=gdk_pixbuf_get_height(pixel_buffer);
518#endif
519 image->alpha_trait=BlendPixelTrait;
520 if (image_info->ping == MagickFalse)
521 {
522#if defined(MAGICKCORE_CAIRO_DELEGATE)
523 size_t
524 stride;
525#endif
526
527 status=SetImageExtent(image,image->columns,image->rows,exception);
528 if (status == MagickFalse)
529 {
530#if !defined(MAGICKCORE_CAIRO_DELEGATE)
531 g_object_unref(G_OBJECT(pixel_buffer));
532#endif
533 g_object_unref(svg_handle);
534 ThrowReaderException(MissingDelegateError,
535 "NoDecodeDelegateForThisImageFormat");
536 }
537#if defined(MAGICKCORE_CAIRO_DELEGATE)
538 stride=4*image->columns;
539#if defined(MAGICKCORE_PANGOCAIRO_DELEGATE)
540 stride=(size_t) cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32,
541 (int) image->columns);
542#endif
543 pixel_info=AcquireVirtualMemory(stride,image->rows*sizeof(*pixels));
544 if (pixel_info == (MemoryInfo *) NULL)
545 {
546 g_object_unref(svg_handle);
547 ThrowReaderException(ResourceLimitError,
548 "MemoryAllocationFailed");
549 }
550 pixels=(unsigned char *) GetVirtualMemoryBlob(pixel_info);
551#endif
552 (void) SetImageBackgroundColor(image,exception);
553#if defined(MAGICKCORE_CAIRO_DELEGATE)
554 cairo_surface=cairo_image_surface_create_for_data(pixels,
555 CAIRO_FORMAT_ARGB32,(int) image->columns,(int) image->rows,(int)
556 stride);
557 if ((cairo_surface == (cairo_surface_t *) NULL) ||
558 (cairo_surface_status(cairo_surface) != CAIRO_STATUS_SUCCESS))
559 {
560 if (cairo_surface != (cairo_surface_t *) NULL)
561 cairo_surface_destroy(cairo_surface);
562 pixel_info=RelinquishVirtualMemory(pixel_info);
563 g_object_unref(svg_handle);
564 ThrowReaderException(ResourceLimitError,
565 "MemoryAllocationFailed");
566 }
567 cairo_image=cairo_create(cairo_surface);
568 cairo_set_operator(cairo_image,CAIRO_OPERATOR_CLEAR);
569 cairo_paint(cairo_image);
570 cairo_set_operator(cairo_image,CAIRO_OPERATOR_OVER);
571 if (apply_density != MagickFalse)
572 cairo_scale(cairo_image,image->resolution.x/DefaultSVGDensity,
573 image->resolution.y/DefaultSVGDensity);
574 rsvg_handle_render_cairo(svg_handle,cairo_image);
575 cairo_destroy(cairo_image);
576 cairo_surface_destroy(cairo_surface);
577 g_object_unref(svg_handle);
578 p=pixels;
579#else
580 p=gdk_pixbuf_get_pixels(pixel_buffer);
581#endif
582 GetPixelInfo(image,&fill_color);
583 for (y=0; y < (ssize_t) image->rows; y++)
584 {
585 q=GetAuthenticPixels(image,0,y,image->columns,1,exception);
586 if (q == (Quantum *) NULL)
587 break;
588 for (x=0; x < (ssize_t) image->columns; x++)
589 {
590#if defined(MAGICKCORE_CAIRO_DELEGATE)
591 fill_color.blue=ScaleCharToQuantum(*p++);
592 fill_color.green=ScaleCharToQuantum(*p++);
593 fill_color.red=ScaleCharToQuantum(*p++);
594#else
595 fill_color.red=ScaleCharToQuantum(*p++);
596 fill_color.green=ScaleCharToQuantum(*p++);
597 fill_color.blue=ScaleCharToQuantum(*p++);
598#endif
599 fill_color.alpha=ScaleCharToQuantum(*p++);
600#if defined(MAGICKCORE_CAIRO_DELEGATE)
601 {
602 double
603 gamma;
604
605 gamma=QuantumScale*fill_color.alpha;
606 gamma=PerceptibleReciprocal(gamma);
607 fill_color.blue*=gamma;
608 fill_color.green*=gamma;
609 fill_color.red*=gamma;
610 }
611#endif
612 CompositePixelOver(image,&fill_color,fill_color.alpha,q,(double)
613 GetPixelAlpha(image,q),q);
614 q+=GetPixelChannels(image);
615 }
616 if (SyncAuthenticPixels(image,exception) == MagickFalse)
617 break;
618 if (image->previous == (Image *) NULL)
619 {
620 status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
621 image->rows);
622 if (status == MagickFalse)
623 break;
624 }
625 }
626 }
627#if defined(MAGICKCORE_CAIRO_DELEGATE)
628 else
629 g_object_unref(svg_handle);
630 if (pixel_info != (MemoryInfo *) NULL)
631 pixel_info=RelinquishVirtualMemory(pixel_info);
632#else
633 g_object_unref(G_OBJECT(pixel_buffer));
634#endif
635 (void) CloseBlob(image);
636 for (next=GetFirstImageInList(image); next != (Image *) NULL; )
637 {
638 (void) CopyMagickString(next->filename,image->filename,MagickPathExtent);
639 (void) CopyMagickString(next->magick,image->magick,MagickPathExtent);
640 next=GetNextImageInList(next);
641 }
642 return(GetFirstImageInList(image));
643}
644#endif
645
Cristyfedaa752019-02-03 07:23:20 -0500646#if defined(MAGICKCORE_XML_DELEGATE)
cristy3ed852e2009-09-05 21:47:34 +0000647static SVGInfo *AcquireSVGInfo(void)
648{
649 SVGInfo
650 *svg_info;
651
Cristy8357b5d2020-11-22 12:39:10 +0000652 svg_info=(SVGInfo *) AcquireMagickMemory(sizeof(*svg_info));
cristy3ed852e2009-09-05 21:47:34 +0000653 if (svg_info == (SVGInfo *) NULL)
654 return((SVGInfo *) NULL);
Cristy81bfff22018-03-10 07:58:31 -0500655 (void) memset(svg_info,0,sizeof(*svg_info));
cristy3ed852e2009-09-05 21:47:34 +0000656 svg_info->text=AcquireString("");
Dirk Lemstra06344a02017-10-15 10:10:01 +0200657 svg_info->scale=(double *) AcquireCriticalMemory(sizeof(*svg_info->scale));
cristy3ed852e2009-09-05 21:47:34 +0000658 GetAffineMatrix(&svg_info->affine);
659 svg_info->scale[0]=ExpandAffine(&svg_info->affine);
660 return(svg_info);
661}
662
663static SVGInfo *DestroySVGInfo(SVGInfo *svg_info)
664{
Cristya3c3c392020-08-06 11:41:57 -0400665 if (svg_info->size != (char *) NULL)
666 svg_info->size=DestroyString(svg_info->size);
cristy3ed852e2009-09-05 21:47:34 +0000667 if (svg_info->text != (char *) NULL)
668 svg_info->text=DestroyString(svg_info->text);
669 if (svg_info->scale != (double *) NULL)
dirkd5bc5a12014-04-04 17:09:07 +0000670 svg_info->scale=(double *) RelinquishMagickMemory(svg_info->scale);
cristy3ed852e2009-09-05 21:47:34 +0000671 if (svg_info->title != (char *) NULL)
672 svg_info->title=DestroyString(svg_info->title);
673 if (svg_info->comment != (char *) NULL)
674 svg_info->comment=DestroyString(svg_info->comment);
675 return((SVGInfo *) RelinquishMagickMemory(svg_info));
676}
677
678static double GetUserSpaceCoordinateValue(const SVGInfo *svg_info,int type,
679 const char *string)
680{
681 char
Cristy3ed6c692016-05-23 19:24:23 -0400682 *next_token,
cristy151b66d2015-04-15 10:50:31 +0000683 token[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +0000684
685 const char
686 *p;
687
688 double
689 value;
690
691 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",string);
692 assert(string != (const char *) NULL);
693 p=(const char *) string;
Cristy448fd182019-07-27 16:26:38 -0400694 (void) GetNextToken(p,&p,MagickPathExtent,token);
Cristy3ed6c692016-05-23 19:24:23 -0400695 value=StringToDouble(token,&next_token);
cristy3ed852e2009-09-05 21:47:34 +0000696 if (strchr(token,'%') != (char *) NULL)
697 {
698 double
699 alpha,
700 beta;
701
702 if (type > 0)
703 {
Elliott Hughes5d41fba2021-04-12 16:36:42 -0700704 if (svg_info->view_box.width < MagickEpsilon)
cristyacff16a2012-08-16 00:21:35 +0000705 return(0.0);
cristy3ed852e2009-09-05 21:47:34 +0000706 return(svg_info->view_box.width*value/100.0);
707 }
708 if (type < 0)
709 {
Elliott Hughes5d41fba2021-04-12 16:36:42 -0700710 if (svg_info->view_box.height < MagickEpsilon)
cristyacff16a2012-08-16 00:21:35 +0000711 return(0.0);
cristy3ed852e2009-09-05 21:47:34 +0000712 return(svg_info->view_box.height*value/100.0);
713 }
714 alpha=value-svg_info->view_box.width;
715 beta=value-svg_info->view_box.height;
716 return(hypot(alpha,beta)/sqrt(2.0)/100.0);
717 }
Cristy448fd182019-07-27 16:26:38 -0400718 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristy3ed852e2009-09-05 21:47:34 +0000719 if (LocaleNCompare(token,"cm",2) == 0)
Cristyb297b0f2018-12-01 11:53:59 -0500720 return(DefaultSVGDensity*svg_info->scale[0]/2.54*value);
cristy3ed852e2009-09-05 21:47:34 +0000721 if (LocaleNCompare(token,"em",2) == 0)
722 return(svg_info->pointsize*value);
723 if (LocaleNCompare(token,"ex",2) == 0)
724 return(svg_info->pointsize*value/2.0);
725 if (LocaleNCompare(token,"in",2) == 0)
Cristyb297b0f2018-12-01 11:53:59 -0500726 return(DefaultSVGDensity*svg_info->scale[0]*value);
cristy3ed852e2009-09-05 21:47:34 +0000727 if (LocaleNCompare(token,"mm",2) == 0)
Cristyb297b0f2018-12-01 11:53:59 -0500728 return(DefaultSVGDensity*svg_info->scale[0]/25.4*value);
cristy3ed852e2009-09-05 21:47:34 +0000729 if (LocaleNCompare(token,"pc",2) == 0)
Cristyb297b0f2018-12-01 11:53:59 -0500730 return(DefaultSVGDensity*svg_info->scale[0]/6.0*value);
cristy3ed852e2009-09-05 21:47:34 +0000731 if (LocaleNCompare(token,"pt",2) == 0)
Cristy06486f72018-07-28 09:22:11 -0400732 return(svg_info->scale[0]*value);
cristy3ed852e2009-09-05 21:47:34 +0000733 if (LocaleNCompare(token,"px",2) == 0)
734 return(value);
735 return(value);
736}
737
cristy3ed852e2009-09-05 21:47:34 +0000738#if defined(__cplusplus) || defined(c_plusplus)
739extern "C" {
740#endif
741
742static int SVGIsStandalone(void *context)
743{
744 SVGInfo
745 *svg_info;
746
747 /*
748 Is this document tagged standalone?
749 */
750 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.SVGIsStandalone()");
751 svg_info=(SVGInfo *) context;
752 return(svg_info->document->standalone == 1);
753}
754
755static int SVGHasInternalSubset(void *context)
756{
757 SVGInfo
758 *svg_info;
759
760 /*
761 Does this document has an internal subset?
762 */
763 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
764 " SAX.SVGHasInternalSubset()");
765 svg_info=(SVGInfo *) context;
766 return(svg_info->document->intSubset != NULL);
767}
768
769static int SVGHasExternalSubset(void *context)
770{
771 SVGInfo
772 *svg_info;
773
774 /*
775 Does this document has an external subset?
776 */
777 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
778 " SAX.SVGHasExternalSubset()");
779 svg_info=(SVGInfo *) context;
780 return(svg_info->document->extSubset != NULL);
781}
782
783static void SVGInternalSubset(void *context,const xmlChar *name,
784 const xmlChar *external_id,const xmlChar *system_id)
785{
786 SVGInfo
787 *svg_info;
788
789 /*
790 Does this document has an internal subset?
791 */
792 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
793 " SAX.internalSubset(%s, %s, %s)",(const char *) name,
794 (external_id != (const xmlChar *) NULL ? (const char *) external_id : "none"),
795 (system_id != (const xmlChar *) NULL ? (const char *) system_id : "none"));
796 svg_info=(SVGInfo *) context;
797 (void) xmlCreateIntSubset(svg_info->document,name,external_id,system_id);
798}
799
800static xmlParserInputPtr SVGResolveEntity(void *context,
801 const xmlChar *public_id,const xmlChar *system_id)
802{
803 SVGInfo
804 *svg_info;
805
806 xmlParserInputPtr
807 stream;
808
809 /*
810 Special entity resolver, better left to the parser, it has more
811 context than the application layer. The default behaviour is to
812 not resolve the entities, in that case the ENTITY_REF nodes are
813 built in the structure (and the parameter values).
814 */
815 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
816 " SAX.resolveEntity(%s, %s)",
817 (public_id != (const xmlChar *) NULL ? (const char *) public_id : "none"),
818 (system_id != (const xmlChar *) NULL ? (const char *) system_id : "none"));
819 svg_info=(SVGInfo *) context;
820 stream=xmlLoadExternalEntity((const char *) system_id,(const char *)
821 public_id,svg_info->parser);
822 return(stream);
823}
824
825static xmlEntityPtr SVGGetEntity(void *context,const xmlChar *name)
826{
827 SVGInfo
828 *svg_info;
829
830 /*
831 Get an entity by name.
832 */
833 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.SVGGetEntity(%s)",
834 name);
835 svg_info=(SVGInfo *) context;
836 return(xmlGetDocEntity(svg_info->document,name));
837}
838
839static xmlEntityPtr SVGGetParameterEntity(void *context,const xmlChar *name)
840{
841 SVGInfo
842 *svg_info;
843
844 /*
845 Get a parameter entity by name.
846 */
847 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
848 " SAX.getParameterEntity(%s)",name);
849 svg_info=(SVGInfo *) context;
850 return(xmlGetParameterEntity(svg_info->document,name));
851}
852
853static void SVGEntityDeclaration(void *context,const xmlChar *name,int type,
854 const xmlChar *public_id,const xmlChar *system_id,xmlChar *content)
855{
856 SVGInfo
857 *svg_info;
858
859 /*
860 An entity definition has been parsed.
861 */
862 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
863 " SAX.entityDecl(%s, %d, %s, %s, %s)",name,type,
864 public_id != (xmlChar *) NULL ? (const char *) public_id : "none",
865 system_id != (xmlChar *) NULL ? (const char *) system_id : "none",content);
866 svg_info=(SVGInfo *) context;
867 if (svg_info->parser->inSubset == 1)
868 (void) xmlAddDocEntity(svg_info->document,name,type,public_id,system_id,
869 content);
870 else
871 if (svg_info->parser->inSubset == 2)
872 (void) xmlAddDtdEntity(svg_info->document,name,type,public_id,system_id,
873 content);
874}
875
876static void SVGAttributeDeclaration(void *context,const xmlChar *element,
877 const xmlChar *name,int type,int value,const xmlChar *default_value,
878 xmlEnumerationPtr tree)
879{
880 SVGInfo
881 *svg_info;
882
883 xmlChar
884 *fullname,
885 *prefix;
886
887 xmlParserCtxtPtr
888 parser;
889
890 /*
891 An attribute definition has been parsed.
892 */
893 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
894 " SAX.attributeDecl(%s, %s, %d, %d, %s, ...)",element,name,type,value,
895 default_value);
896 svg_info=(SVGInfo *) context;
897 fullname=(xmlChar *) NULL;
898 prefix=(xmlChar *) NULL;
899 parser=svg_info->parser;
900 fullname=(xmlChar *) xmlSplitQName(parser,name,&prefix);
901 if (parser->inSubset == 1)
902 (void) xmlAddAttributeDecl(&parser->vctxt,svg_info->document->intSubset,
903 element,fullname,prefix,(xmlAttributeType) type,
904 (xmlAttributeDefault) value,default_value,tree);
905 else
906 if (parser->inSubset == 2)
907 (void) xmlAddAttributeDecl(&parser->vctxt,svg_info->document->extSubset,
908 element,fullname,prefix,(xmlAttributeType) type,
909 (xmlAttributeDefault) value,default_value,tree);
910 if (prefix != (xmlChar *) NULL)
911 xmlFree(prefix);
912 if (fullname != (xmlChar *) NULL)
913 xmlFree(fullname);
914}
915
916static void SVGElementDeclaration(void *context,const xmlChar *name,int type,
917 xmlElementContentPtr content)
918{
919 SVGInfo
920 *svg_info;
921
922 xmlParserCtxtPtr
923 parser;
924
925 /*
926 An element definition has been parsed.
927 */
928 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
929 " SAX.elementDecl(%s, %d, ...)",name,type);
930 svg_info=(SVGInfo *) context;
931 parser=svg_info->parser;
932 if (parser->inSubset == 1)
933 (void) xmlAddElementDecl(&parser->vctxt,svg_info->document->intSubset,
934 name,(xmlElementTypeVal) type,content);
935 else
936 if (parser->inSubset == 2)
937 (void) xmlAddElementDecl(&parser->vctxt,svg_info->document->extSubset,
938 name,(xmlElementTypeVal) type,content);
939}
940
Cristya64a7bc2018-08-03 07:20:50 -0400941static void SVGStripString(const MagickBooleanType trim,char *message)
Cristy578a60b2018-06-17 11:30:29 -0400942{
Cristyf2dc1dd2020-12-28 13:59:26 -0500943 char
Cristy578a60b2018-06-17 11:30:29 -0400944 *p,
945 *q;
946
947 size_t
948 length;
949
950 assert(message != (char *) NULL);
951 if (*message == '\0')
952 return;
953 /*
954 Remove comment.
955 */
Cristyf85c2312018-07-16 18:00:25 -0400956 q=message;
Cristy578a60b2018-06-17 11:30:29 -0400957 for (p=message; *p != '\0'; p++)
958 {
959 if ((*p == '/') && (*(p+1) == '*'))
960 {
Cristyf85c2312018-07-16 18:00:25 -0400961 for ( ; *p != '\0'; p++)
962 if ((*p == '*') && (*(p+1) == '/'))
Cristy5d71e232018-10-06 09:14:15 -0400963 {
964 p+=2;
965 break;
966 }
Cristyf85c2312018-07-16 18:00:25 -0400967 if (*p == '\0')
968 break;
Cristy578a60b2018-06-17 11:30:29 -0400969 }
Cristyf85c2312018-07-16 18:00:25 -0400970 *q++=(*p);
Cristy578a60b2018-06-17 11:30:29 -0400971 }
Cristyf85c2312018-07-16 18:00:25 -0400972 *q='\0';
Cristya4642512019-02-11 19:58:52 -0500973 length=strlen(message);
974 if ((trim != MagickFalse) && (length != 0))
Cristya64a7bc2018-08-03 07:20:50 -0400975 {
976 /*
977 Remove whitespace.
978 */
Cristya64a7bc2018-08-03 07:20:50 -0400979 p=message;
980 while (isspace((int) ((unsigned char) *p)) != 0)
981 p++;
982 if ((*p == '\'') || (*p == '"'))
983 p++;
984 q=message+length-1;
985 while ((isspace((int) ((unsigned char) *q)) != 0) && (q > p))
986 q--;
987 if (q > p)
988 if ((*q == '\'') || (*q == '"'))
989 q--;
990 (void) memmove(message,p,(size_t) (q-p+1));
991 message[q-p+1]='\0';
992 }
Cristy578a60b2018-06-17 11:30:29 -0400993 /*
994 Convert newlines to a space.
995 */
996 for (p=message; *p != '\0'; p++)
997 if (*p == '\n')
998 *p=' ';
999}
1000
Cristyaefcc452018-05-23 20:08:48 -04001001static char **SVGKeyValuePairs(void *context,const int key_sentinel,
1002 const int value_sentinel,const char *text,size_t *number_tokens)
1003{
1004 char
1005 **tokens;
1006
Cristyf2dc1dd2020-12-28 13:59:26 -05001007 const char
Cristyaefcc452018-05-23 20:08:48 -04001008 *p,
1009 *q;
1010
Cristyf2dc1dd2020-12-28 13:59:26 -05001011 ssize_t
Cristyaefcc452018-05-23 20:08:48 -04001012 i;
1013
1014 size_t
1015 extent;
1016
1017 SVGInfo
1018 *svg_info;
1019
1020 svg_info=(SVGInfo *) context;
1021 *number_tokens=0;
1022 if (text == (const char *) NULL)
1023 return((char **) NULL);
1024 extent=8;
1025 tokens=(char **) AcquireQuantumMemory(extent+2UL,sizeof(*tokens));
1026 if (tokens == (char **) NULL)
1027 {
1028 (void) ThrowMagickException(svg_info->exception,GetMagickModule(),
1029 ResourceLimitError,"MemoryAllocationFailed","`%s'",text);
1030 return((char **) NULL);
1031 }
1032 /*
1033 Convert string to an ASCII list.
1034 */
1035 i=0;
1036 p=text;
1037 for (q=p; *q != '\0'; q++)
1038 {
1039 if ((*q != key_sentinel) && (*q != value_sentinel) && (*q != '\0'))
1040 continue;
1041 if (i == (ssize_t) extent)
1042 {
1043 extent<<=1;
1044 tokens=(char **) ResizeQuantumMemory(tokens,extent+2,sizeof(*tokens));
1045 if (tokens == (char **) NULL)
1046 {
1047 (void) ThrowMagickException(svg_info->exception,GetMagickModule(),
1048 ResourceLimitError,"MemoryAllocationFailed","`%s'",text);
1049 return((char **) NULL);
1050 }
1051 }
1052 tokens[i]=AcquireString(p);
1053 (void) CopyMagickString(tokens[i],p,(size_t) (q-p+1));
Cristya64a7bc2018-08-03 07:20:50 -04001054 SVGStripString(MagickTrue,tokens[i]);
Cristyaefcc452018-05-23 20:08:48 -04001055 i++;
1056 p=q+1;
1057 }
1058 tokens[i]=AcquireString(p);
1059 (void) CopyMagickString(tokens[i],p,(size_t) (q-p+1));
Cristya64a7bc2018-08-03 07:20:50 -04001060 SVGStripString(MagickTrue,tokens[i++]);
Cristyaefcc452018-05-23 20:08:48 -04001061 tokens[i]=(char *) NULL;
1062 *number_tokens=(size_t) i;
1063 return(tokens);
1064}
1065
cristy3ed852e2009-09-05 21:47:34 +00001066static void SVGNotationDeclaration(void *context,const xmlChar *name,
1067 const xmlChar *public_id,const xmlChar *system_id)
1068{
1069 SVGInfo
1070 *svg_info;
1071
1072 xmlParserCtxtPtr
1073 parser;
1074
1075 /*
1076 What to do when a notation declaration has been parsed.
1077 */
1078 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1079 " SAX.notationDecl(%s, %s, %s)",name,
1080 public_id != (const xmlChar *) NULL ? (const char *) public_id : "none",
1081 system_id != (const xmlChar *) NULL ? (const char *) system_id : "none");
1082 svg_info=(SVGInfo *) context;
1083 parser=svg_info->parser;
1084 if (parser->inSubset == 1)
1085 (void) xmlAddNotationDecl(&parser->vctxt,svg_info->document->intSubset,
1086 name,public_id,system_id);
1087 else
1088 if (parser->inSubset == 2)
1089 (void) xmlAddNotationDecl(&parser->vctxt,svg_info->document->intSubset,
1090 name,public_id,system_id);
1091}
1092
Cristyaefcc452018-05-23 20:08:48 -04001093static void SVGProcessStyleElement(void *context,const xmlChar *name,
1094 const char *style)
1095{
1096 char
1097 background[MagickPathExtent],
1098 *color,
1099 *keyword,
1100 *units,
1101 *value;
1102
1103 char
1104 **tokens;
1105
Cristyf2dc1dd2020-12-28 13:59:26 -05001106 ssize_t
Cristyaefcc452018-05-23 20:08:48 -04001107 i;
1108
1109 size_t
1110 number_tokens;
1111
1112 SVGInfo
1113 *svg_info;
1114
1115 (void) LogMagickEvent(CoderEvent,GetMagickModule()," ");
1116 svg_info=(SVGInfo *) context;
1117 tokens=SVGKeyValuePairs(context,':',';',style,&number_tokens);
1118 if (tokens == (char **) NULL)
1119 return;
1120 for (i=0; i < (ssize_t) (number_tokens-1); i+=2)
1121 {
1122 keyword=(char *) tokens[i];
1123 value=(char *) tokens[i+1];
1124 if (LocaleCompare(keyword,"font-size") != 0)
1125 continue;
1126 svg_info->pointsize=GetUserSpaceCoordinateValue(svg_info,0,value);
1127 (void) FormatLocaleFile(svg_info->file,"font-size %g\n",
1128 svg_info->pointsize);
1129 }
Cristy61d7e8b2018-05-29 10:57:27 -04001130 color=AcquireString("none");
1131 units=AcquireString("userSpaceOnUse");
Cristyaefcc452018-05-23 20:08:48 -04001132 for (i=0; i < (ssize_t) (number_tokens-1); i+=2)
1133 {
1134 keyword=(char *) tokens[i];
1135 value=(char *) tokens[i+1];
1136 (void) LogMagickEvent(CoderEvent,GetMagickModule()," %s: %s",keyword,
1137 value);
1138 switch (*keyword)
1139 {
1140 case 'B':
1141 case 'b':
1142 {
1143 if (LocaleCompare((const char *) name,"background") == 0)
1144 {
1145 if (LocaleCompare((const char *) name,"svg") == 0)
1146 (void) CopyMagickString(background,value,MagickPathExtent);
1147 break;
1148 }
1149 break;
1150 }
1151 case 'C':
1152 case 'c':
1153 {
1154 if (LocaleCompare(keyword,"clip-path") == 0)
1155 {
1156 (void) FormatLocaleFile(svg_info->file,"clip-path \"%s\"\n",value);
1157 break;
1158 }
1159 if (LocaleCompare(keyword,"clip-rule") == 0)
1160 {
1161 (void) FormatLocaleFile(svg_info->file,"clip-rule \"%s\"\n",value);
1162 break;
1163 }
1164 if (LocaleCompare(keyword,"clipPathUnits") == 0)
1165 {
1166 (void) CloneString(&units,value);
1167 (void) FormatLocaleFile(svg_info->file,"clip-units \"%s\"\n",
1168 value);
1169 break;
1170 }
1171 if (LocaleCompare(keyword,"color") == 0)
1172 {
1173 (void) CloneString(&color,value);
Cristyfc52fa02020-09-05 08:48:01 -04001174 (void) FormatLocaleFile(svg_info->file,"currentColor \"%s\"\n",
1175 color);
Cristyaefcc452018-05-23 20:08:48 -04001176 break;
1177 }
1178 break;
1179 }
1180 case 'F':
1181 case 'f':
1182 {
1183 if (LocaleCompare(keyword,"fill") == 0)
1184 {
1185 if (LocaleCompare(value,"currentColor") == 0)
1186 {
1187 (void) FormatLocaleFile(svg_info->file,"fill \"%s\"\n",color);
1188 break;
1189 }
1190 if (LocaleCompare(value,"#000000ff") == 0)
1191 (void) FormatLocaleFile(svg_info->file,"fill '#000000'\n");
1192 else
1193 (void) FormatLocaleFile(svg_info->file,"fill \"%s\"\n",value);
1194 break;
1195 }
1196 if (LocaleCompare(keyword,"fillcolor") == 0)
1197 {
1198 (void) FormatLocaleFile(svg_info->file,"fill \"%s\"\n",value);
1199 break;
1200 }
1201 if (LocaleCompare(keyword,"fill-rule") == 0)
1202 {
1203 (void) FormatLocaleFile(svg_info->file,"fill-rule \"%s\"\n",value);
1204 break;
1205 }
1206 if (LocaleCompare(keyword,"fill-opacity") == 0)
1207 {
1208 (void) FormatLocaleFile(svg_info->file,"fill-opacity \"%s\"\n",
1209 value);
1210 break;
1211 }
Cristyf65abb52018-06-17 15:39:19 -04001212 if (LocaleCompare(keyword,"font") == 0)
1213 {
1214 char
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02001215 font_family[MagickPathExtent],
1216 font_size[MagickPathExtent],
1217 font_style[MagickPathExtent];
Cristyf65abb52018-06-17 15:39:19 -04001218
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02001219 if (sscanf(value,"%2048s %2048s %2048s",font_style,font_size,
1220 font_family) != 3)
Cristyf65abb52018-06-17 15:39:19 -04001221 break;
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02001222 if (GetUserSpaceCoordinateValue(svg_info,0,font_style) == 0)
Cristy2667e842018-07-07 09:00:08 -04001223 (void) FormatLocaleFile(svg_info->file,"font-style \"%s\"\n",
1224 style);
1225 else
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02001226 if (sscanf(value,"%2048s %2048s",font_size,font_family) != 2)
Cristy2667e842018-07-07 09:00:08 -04001227 break;
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02001228 (void) FormatLocaleFile(svg_info->file,"font-size \"%s\"\n",
1229 font_size);
Cristyf65abb52018-06-17 15:39:19 -04001230 (void) FormatLocaleFile(svg_info->file,"font-family \"%s\"\n",
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02001231 font_family);
Cristyf65abb52018-06-17 15:39:19 -04001232 break;
1233 }
Cristyaefcc452018-05-23 20:08:48 -04001234 if (LocaleCompare(keyword,"font-family") == 0)
1235 {
1236 (void) FormatLocaleFile(svg_info->file,"font-family \"%s\"\n",
1237 value);
1238 break;
1239 }
1240 if (LocaleCompare(keyword,"font-stretch") == 0)
1241 {
1242 (void) FormatLocaleFile(svg_info->file,"font-stretch \"%s\"\n",
1243 value);
1244 break;
1245 }
1246 if (LocaleCompare(keyword,"font-style") == 0)
1247 {
1248 (void) FormatLocaleFile(svg_info->file,"font-style \"%s\"\n",value);
1249 break;
1250 }
1251 if (LocaleCompare(keyword,"font-size") == 0)
1252 {
1253 svg_info->pointsize=GetUserSpaceCoordinateValue(svg_info,0,value);
1254 (void) FormatLocaleFile(svg_info->file,"font-size %g\n",
1255 svg_info->pointsize);
1256 break;
1257 }
1258 if (LocaleCompare(keyword,"font-weight") == 0)
1259 {
1260 (void) FormatLocaleFile(svg_info->file,"font-weight \"%s\"\n",
1261 value);
1262 break;
1263 }
1264 break;
1265 }
Cristy86d77652018-08-04 18:43:58 -04001266 case 'K':
1267 case 'k':
1268 {
1269 if (LocaleCompare(keyword,"kerning") == 0)
1270 {
1271 (void) FormatLocaleFile(svg_info->file,"kerning \"%s\"\n",value);
1272 break;
1273 }
1274 break;
1275 }
1276 case 'L':
1277 case 'l':
1278 {
1279 if (LocaleCompare(keyword,"letter-spacing") == 0)
1280 {
1281 (void) FormatLocaleFile(svg_info->file,"letter-spacing \"%s\"\n",
1282 value);
1283 break;
1284 }
1285 break;
1286 }
Cristy00c2ed72018-07-29 18:48:44 -04001287 case 'M':
1288 case 'm':
1289 {
1290 if (LocaleCompare(keyword,"mask") == 0)
1291 {
1292 (void) FormatLocaleFile(svg_info->file,"mask \"%s\"\n",value);
1293 break;
1294 }
1295 break;
1296 }
Cristyaefcc452018-05-23 20:08:48 -04001297 case 'O':
1298 case 'o':
1299 {
1300 if (LocaleCompare(keyword,"offset") == 0)
1301 {
1302 (void) FormatLocaleFile(svg_info->file,"offset %g\n",
1303 GetUserSpaceCoordinateValue(svg_info,1,value));
1304 break;
1305 }
1306 if (LocaleCompare(keyword,"opacity") == 0)
1307 {
1308 (void) FormatLocaleFile(svg_info->file,"opacity \"%s\"\n",value);
1309 break;
1310 }
1311 break;
1312 }
1313 case 'S':
1314 case 's':
1315 {
1316 if (LocaleCompare(keyword,"stop-color") == 0)
1317 {
1318 (void) CloneString(&svg_info->stop_color,value);
1319 break;
1320 }
1321 if (LocaleCompare(keyword,"stroke") == 0)
1322 {
1323 if (LocaleCompare(value,"currentColor") == 0)
1324 {
1325 (void) FormatLocaleFile(svg_info->file,"stroke \"%s\"\n",color);
1326 break;
1327 }
1328 if (LocaleCompare(value,"#000000ff") == 0)
1329 (void) FormatLocaleFile(svg_info->file,"fill '#000000'\n");
1330 else
1331 (void) FormatLocaleFile(svg_info->file,
1332 "stroke \"%s\"\n",value);
1333 break;
1334 }
1335 if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
1336 {
1337 (void) FormatLocaleFile(svg_info->file,"stroke-antialias %d\n",
1338 LocaleCompare(value,"true") == 0);
1339 break;
1340 }
1341 if (LocaleCompare(keyword,"stroke-dasharray") == 0)
1342 {
1343 (void) FormatLocaleFile(svg_info->file,"stroke-dasharray %s\n",
1344 value);
1345 break;
1346 }
1347 if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
1348 {
1349 (void) FormatLocaleFile(svg_info->file,"stroke-dashoffset %g\n",
1350 GetUserSpaceCoordinateValue(svg_info,1,value));
1351 break;
1352 }
1353 if (LocaleCompare(keyword,"stroke-linecap") == 0)
1354 {
1355 (void) FormatLocaleFile(svg_info->file,"stroke-linecap \"%s\"\n",
1356 value);
1357 break;
1358 }
1359 if (LocaleCompare(keyword,"stroke-linejoin") == 0)
1360 {
1361 (void) FormatLocaleFile(svg_info->file,"stroke-linejoin \"%s\"\n",
1362 value);
1363 break;
1364 }
1365 if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
1366 {
1367 (void) FormatLocaleFile(svg_info->file,"stroke-miterlimit \"%s\"\n",
1368 value);
1369 break;
1370 }
1371 if (LocaleCompare(keyword,"stroke-opacity") == 0)
1372 {
1373 (void) FormatLocaleFile(svg_info->file,"stroke-opacity \"%s\"\n",
1374 value);
1375 break;
1376 }
1377 if (LocaleCompare(keyword,"stroke-width") == 0)
1378 {
1379 (void) FormatLocaleFile(svg_info->file,"stroke-width %g\n",
1380 GetUserSpaceCoordinateValue(svg_info,1,value));
1381 break;
1382 }
1383 break;
1384 }
1385 case 't':
1386 case 'T':
1387 {
1388 if (LocaleCompare(keyword,"text-align") == 0)
1389 {
1390 (void) FormatLocaleFile(svg_info->file,"text-align \"%s\"\n",value);
1391 break;
1392 }
1393 if (LocaleCompare(keyword,"text-anchor") == 0)
1394 {
1395 (void) FormatLocaleFile(svg_info->file,"text-anchor \"%s\"\n",
1396 value);
1397 break;
1398 }
1399 if (LocaleCompare(keyword,"text-decoration") == 0)
1400 {
1401 if (LocaleCompare(value,"underline") == 0)
1402 (void) FormatLocaleFile(svg_info->file,"decorate underline\n");
1403 if (LocaleCompare(value,"line-through") == 0)
1404 (void) FormatLocaleFile(svg_info->file,"decorate line-through\n");
1405 if (LocaleCompare(value,"overline") == 0)
1406 (void) FormatLocaleFile(svg_info->file,"decorate overline\n");
1407 break;
1408 }
1409 if (LocaleCompare(keyword,"text-antialiasing") == 0)
1410 {
1411 (void) FormatLocaleFile(svg_info->file,"text-antialias %d\n",
1412 LocaleCompare(value,"true") == 0);
1413 break;
1414 }
1415 break;
1416 }
1417 default:
1418 break;
1419 }
1420 }
Cristy61d7e8b2018-05-29 10:57:27 -04001421 if (units != (char *) NULL)
1422 units=DestroyString(units);
1423 if (color != (char *) NULL)
1424 color=DestroyString(color);
Cristyaefcc452018-05-23 20:08:48 -04001425 for (i=0; tokens[i] != (char *) NULL; i++)
1426 tokens[i]=DestroyString(tokens[i]);
1427 tokens=(char **) RelinquishMagickMemory(tokens);
1428}
1429
cristy3ed852e2009-09-05 21:47:34 +00001430static void SVGUnparsedEntityDeclaration(void *context,const xmlChar *name,
1431 const xmlChar *public_id,const xmlChar *system_id,const xmlChar *notation)
1432{
1433 SVGInfo
1434 *svg_info;
1435
1436 /*
1437 What to do when an unparsed entity declaration is parsed.
1438 */
1439 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1440 " SAX.unparsedEntityDecl(%s, %s, %s, %s)",name,
1441 public_id != (xmlChar *) NULL ? (const char *) public_id : "none",
1442 system_id != (xmlChar *) NULL ? (const char *) system_id : "none",notation);
1443 svg_info=(SVGInfo *) context;
1444 (void) xmlAddDocEntity(svg_info->document,name,
1445 XML_EXTERNAL_GENERAL_UNPARSED_ENTITY,public_id,system_id,notation);
1446
1447}
1448
1449static void SVGSetDocumentLocator(void *context,xmlSAXLocatorPtr location)
1450{
1451 SVGInfo
1452 *svg_info;
1453
1454 /*
1455 Receive the document locator at startup, actually xmlDefaultSAXLocator.
1456 */
1457 (void) location;
1458 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1459 " SAX.setDocumentLocator()");
1460 svg_info=(SVGInfo *) context;
cristyda16f162011-02-19 23:52:17 +00001461 (void) svg_info;
cristy3ed852e2009-09-05 21:47:34 +00001462}
1463
1464static void SVGStartDocument(void *context)
1465{
1466 SVGInfo
1467 *svg_info;
1468
1469 xmlParserCtxtPtr
1470 parser;
1471
1472 /*
1473 Called when the document start being processed.
1474 */
1475 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.startDocument()");
1476 svg_info=(SVGInfo *) context;
cristy3ed852e2009-09-05 21:47:34 +00001477 parser=svg_info->parser;
1478 svg_info->document=xmlNewDoc(parser->version);
1479 if (svg_info->document == (xmlDocPtr) NULL)
1480 return;
1481 if (parser->encoding == NULL)
1482 svg_info->document->encoding=(const xmlChar *) NULL;
1483 else
1484 svg_info->document->encoding=xmlStrdup(parser->encoding);
1485 svg_info->document->standalone=parser->standalone;
1486}
1487
1488static void SVGEndDocument(void *context)
1489{
1490 SVGInfo
1491 *svg_info;
1492
1493 /*
1494 Called when the document end has been detected.
1495 */
1496 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.endDocument()");
1497 svg_info=(SVGInfo *) context;
1498 if (svg_info->offset != (char *) NULL)
1499 svg_info->offset=DestroyString(svg_info->offset);
1500 if (svg_info->stop_color != (char *) NULL)
1501 svg_info->stop_color=DestroyString(svg_info->stop_color);
1502 if (svg_info->scale != (double *) NULL)
1503 svg_info->scale=(double *) RelinquishMagickMemory(svg_info->scale);
1504 if (svg_info->text != (char *) NULL)
1505 svg_info->text=DestroyString(svg_info->text);
1506 if (svg_info->vertices != (char *) NULL)
1507 svg_info->vertices=DestroyString(svg_info->vertices);
1508 if (svg_info->url != (char *) NULL)
1509 svg_info->url=DestroyString(svg_info->url);
1510#if defined(MAGICKCORE_XML_DELEGATE)
1511 if (svg_info->document != (xmlDocPtr) NULL)
1512 {
1513 xmlFreeDoc(svg_info->document);
1514 svg_info->document=(xmlDocPtr) NULL;
1515 }
1516#endif
1517}
1518
1519static void SVGStartElement(void *context,const xmlChar *name,
1520 const xmlChar **attributes)
1521{
Cristyd7e36592018-04-21 14:45:52 -04001522#define PushGraphicContext(id) \
1523{ \
1524 if (*id == '\0') \
1525 (void) FormatLocaleFile(svg_info->file,"push graphic-context\n"); \
1526 else \
1527 (void) FormatLocaleFile(svg_info->file,"push graphic-context \"%s\"\n", \
1528 id); \
1529}
1530
cristy3ed852e2009-09-05 21:47:34 +00001531 char
1532 *color,
Cristyb38b8e62018-03-31 10:46:32 -04001533 background[MagickPathExtent],
cristy151b66d2015-04-15 10:50:31 +00001534 id[MagickPathExtent],
Cristy3ed6c692016-05-23 19:24:23 -04001535 *next_token,
cristy151b66d2015-04-15 10:50:31 +00001536 token[MagickPathExtent],
cristy3ed852e2009-09-05 21:47:34 +00001537 **tokens,
1538 *units;
1539
1540 const char
1541 *keyword,
1542 *p,
1543 *value;
1544
Cristyf2dc1dd2020-12-28 13:59:26 -05001545 ssize_t
Cristyfc887f22016-08-22 07:43:54 -04001546 i,
1547 j;
1548
1549 size_t
cristy3ed852e2009-09-05 21:47:34 +00001550 number_tokens;
1551
1552 SVGInfo
1553 *svg_info;
1554
cristy3ed852e2009-09-05 21:47:34 +00001555 /*
1556 Called when an opening tag has been processed.
1557 */
1558 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.startElement(%s",
1559 name);
1560 svg_info=(SVGInfo *) context;
1561 svg_info->n++;
1562 svg_info->scale=(double *) ResizeQuantumMemory(svg_info->scale,
1563 svg_info->n+1UL,sizeof(*svg_info->scale));
1564 if (svg_info->scale == (double *) NULL)
1565 {
1566 (void) ThrowMagickException(svg_info->exception,GetMagickModule(),
1567 ResourceLimitError,"MemoryAllocationFailed","`%s'",name);
1568 return;
1569 }
1570 svg_info->scale[svg_info->n]=svg_info->scale[svg_info->n-1];
1571 color=AcquireString("none");
1572 units=AcquireString("userSpaceOnUse");
Cristy5f478ca2016-05-14 18:43:20 -04001573 *id='\0';
1574 *token='\0';
Cristyb38b8e62018-03-31 10:46:32 -04001575 *background='\0';
cristy3ed852e2009-09-05 21:47:34 +00001576 value=(const char *) NULL;
Cristya4408152018-02-19 20:31:01 -05001577 if ((LocaleCompare((char *) name,"image") == 0) ||
1578 (LocaleCompare((char *) name,"pattern") == 0) ||
1579 (LocaleCompare((char *) name,"rect") == 0) ||
1580 (LocaleCompare((char *) name,"text") == 0) ||
1581 (LocaleCompare((char *) name,"use") == 0))
1582 {
1583 svg_info->bounds.x=0.0;
1584 svg_info->bounds.y=0.0;
1585 }
cristy3ed852e2009-09-05 21:47:34 +00001586 if (attributes != (const xmlChar **) NULL)
1587 for (i=0; (attributes[i] != (const xmlChar *) NULL); i+=2)
1588 {
1589 keyword=(const char *) attributes[i];
1590 value=(const char *) attributes[i+1];
1591 switch (*keyword)
1592 {
1593 case 'C':
1594 case 'c':
1595 {
1596 if (LocaleCompare(keyword,"cx") == 0)
1597 {
1598 svg_info->element.cx=
1599 GetUserSpaceCoordinateValue(svg_info,1,value);
1600 break;
1601 }
1602 if (LocaleCompare(keyword,"cy") == 0)
1603 {
1604 svg_info->element.cy=
1605 GetUserSpaceCoordinateValue(svg_info,-1,value);
1606 break;
1607 }
1608 break;
1609 }
1610 case 'F':
1611 case 'f':
1612 {
1613 if (LocaleCompare(keyword,"fx") == 0)
1614 {
1615 svg_info->element.major=
1616 GetUserSpaceCoordinateValue(svg_info,1,value);
1617 break;
1618 }
1619 if (LocaleCompare(keyword,"fy") == 0)
1620 {
1621 svg_info->element.minor=
1622 GetUserSpaceCoordinateValue(svg_info,-1,value);
1623 break;
1624 }
1625 break;
1626 }
1627 case 'H':
1628 case 'h':
1629 {
1630 if (LocaleCompare(keyword,"height") == 0)
1631 {
1632 svg_info->bounds.height=
1633 GetUserSpaceCoordinateValue(svg_info,-1,value);
1634 break;
1635 }
1636 break;
1637 }
1638 case 'I':
1639 case 'i':
1640 {
1641 if (LocaleCompare(keyword,"id") == 0)
1642 {
cristy151b66d2015-04-15 10:50:31 +00001643 (void) CopyMagickString(id,value,MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00001644 break;
1645 }
1646 break;
1647 }
1648 case 'R':
1649 case 'r':
1650 {
1651 if (LocaleCompare(keyword,"r") == 0)
1652 {
Cristya5623962020-03-08 10:13:40 -04001653 svg_info->element.angle=GetUserSpaceCoordinateValue(svg_info,0,
1654 value);
1655 break;
1656 }
1657 break;
1658 }
cristy3ed852e2009-09-05 21:47:34 +00001659 case 'W':
1660 case 'w':
1661 {
1662 if (LocaleCompare(keyword,"width") == 0)
1663 {
1664 svg_info->bounds.width=
1665 GetUserSpaceCoordinateValue(svg_info,1,value);
1666 break;
1667 }
1668 break;
1669 }
1670 case 'X':
1671 case 'x':
1672 {
1673 if (LocaleCompare(keyword,"x") == 0)
1674 {
Cristyc13321f2019-05-26 19:05:02 -04001675 svg_info->bounds.x=GetUserSpaceCoordinateValue(svg_info,1,value);
cristy3ed852e2009-09-05 21:47:34 +00001676 break;
1677 }
1678 if (LocaleCompare(keyword,"x1") == 0)
1679 {
1680 svg_info->segment.x1=GetUserSpaceCoordinateValue(svg_info,1,
1681 value);
1682 break;
1683 }
1684 if (LocaleCompare(keyword,"x2") == 0)
1685 {
1686 svg_info->segment.x2=GetUserSpaceCoordinateValue(svg_info,1,
1687 value);
1688 break;
1689 }
1690 break;
1691 }
1692 case 'Y':
1693 case 'y':
1694 {
1695 if (LocaleCompare(keyword,"y") == 0)
1696 {
Cristyc13321f2019-05-26 19:05:02 -04001697 svg_info->bounds.y=GetUserSpaceCoordinateValue(svg_info,-1,value);
cristy3ed852e2009-09-05 21:47:34 +00001698 break;
1699 }
1700 if (LocaleCompare(keyword,"y1") == 0)
1701 {
Cristya4408152018-02-19 20:31:01 -05001702 svg_info->segment.y1=GetUserSpaceCoordinateValue(svg_info,-1,
1703 value);
cristy3ed852e2009-09-05 21:47:34 +00001704 break;
1705 }
1706 if (LocaleCompare(keyword,"y2") == 0)
1707 {
Cristya4408152018-02-19 20:31:01 -05001708 svg_info->segment.y2=GetUserSpaceCoordinateValue(svg_info,-1,
1709 value);
cristy3ed852e2009-09-05 21:47:34 +00001710 break;
1711 }
1712 break;
1713 }
1714 default:
1715 break;
1716 }
1717 }
cristy13d07042010-11-21 20:56:18 +00001718 if (strchr((char *) name,':') != (char *) NULL)
cristy768b9302010-11-20 00:05:47 +00001719 {
1720 /*
1721 Skip over namespace.
1722 */
1723 for ( ; *name != ':'; name++) ;
1724 name++;
1725 }
cristy3ed852e2009-09-05 21:47:34 +00001726 switch (*name)
1727 {
1728 case 'C':
1729 case 'c':
1730 {
1731 if (LocaleCompare((const char *) name,"circle") == 0)
1732 {
Cristyd7e36592018-04-21 14:45:52 -04001733 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001734 break;
1735 }
1736 if (LocaleCompare((const char *) name,"clipPath") == 0)
1737 {
Cristybd8bfcd2018-03-25 10:15:04 -04001738 (void) FormatLocaleFile(svg_info->file,"push clip-path \"%s\"\n",id);
cristy3ed852e2009-09-05 21:47:34 +00001739 break;
1740 }
1741 break;
1742 }
1743 case 'D':
1744 case 'd':
1745 {
1746 if (LocaleCompare((const char *) name,"defs") == 0)
1747 {
cristyb51dff52011-05-19 16:55:47 +00001748 (void) FormatLocaleFile(svg_info->file,"push defs\n");
cristy3ed852e2009-09-05 21:47:34 +00001749 break;
1750 }
1751 break;
1752 }
1753 case 'E':
1754 case 'e':
1755 {
1756 if (LocaleCompare((const char *) name,"ellipse") == 0)
1757 {
Cristyd7e36592018-04-21 14:45:52 -04001758 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001759 break;
1760 }
1761 break;
1762 }
Cristy6abd75a2018-03-01 10:19:41 -05001763 case 'F':
Cristy4a38f5d2018-03-01 10:22:17 -05001764 case 'f':
Cristy6abd75a2018-03-01 10:19:41 -05001765 {
1766 if (LocaleCompare((const char *) name,"foreignObject") == 0)
1767 {
Cristyd7e36592018-04-21 14:45:52 -04001768 PushGraphicContext(id);
Cristy6abd75a2018-03-01 10:19:41 -05001769 break;
1770 }
1771 break;
1772 }
cristy3ed852e2009-09-05 21:47:34 +00001773 case 'G':
1774 case 'g':
1775 {
1776 if (LocaleCompare((const char *) name,"g") == 0)
1777 {
Cristyd7e36592018-04-21 14:45:52 -04001778 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001779 break;
1780 }
1781 break;
1782 }
1783 case 'I':
1784 case 'i':
1785 {
1786 if (LocaleCompare((const char *) name,"image") == 0)
1787 {
Cristyd7e36592018-04-21 14:45:52 -04001788 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001789 break;
1790 }
1791 break;
1792 }
1793 case 'L':
1794 case 'l':
1795 {
1796 if (LocaleCompare((const char *) name,"line") == 0)
1797 {
Cristyd7e36592018-04-21 14:45:52 -04001798 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001799 break;
1800 }
1801 if (LocaleCompare((const char *) name,"linearGradient") == 0)
1802 {
cristyb51dff52011-05-19 16:55:47 +00001803 (void) FormatLocaleFile(svg_info->file,
Cristybd8bfcd2018-03-25 10:15:04 -04001804 "push gradient \"%s\" linear %g,%g %g,%g\n",id,
cristy3ed852e2009-09-05 21:47:34 +00001805 svg_info->segment.x1,svg_info->segment.y1,svg_info->segment.x2,
1806 svg_info->segment.y2);
1807 break;
1808 }
1809 break;
1810 }
Cristy189a2a22018-04-28 12:00:18 -04001811 case 'M':
1812 case 'm':
1813 {
1814 if (LocaleCompare((const char *) name,"mask") == 0)
1815 {
1816 (void) FormatLocaleFile(svg_info->file,"push mask \"%s\"\n",id);
1817 break;
1818 }
1819 break;
1820 }
cristy3ed852e2009-09-05 21:47:34 +00001821 case 'P':
1822 case 'p':
1823 {
1824 if (LocaleCompare((const char *) name,"path") == 0)
1825 {
Cristyd7e36592018-04-21 14:45:52 -04001826 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001827 break;
1828 }
1829 if (LocaleCompare((const char *) name,"pattern") == 0)
1830 {
cristyb51dff52011-05-19 16:55:47 +00001831 (void) FormatLocaleFile(svg_info->file,
Cristybd8bfcd2018-03-25 10:15:04 -04001832 "push pattern \"%s\" %g,%g %g,%g\n",id,
cristy3ed852e2009-09-05 21:47:34 +00001833 svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.width,
1834 svg_info->bounds.height);
1835 break;
1836 }
1837 if (LocaleCompare((const char *) name,"polygon") == 0)
1838 {
Cristyd7e36592018-04-21 14:45:52 -04001839 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001840 break;
1841 }
1842 if (LocaleCompare((const char *) name,"polyline") == 0)
1843 {
Cristyd7e36592018-04-21 14:45:52 -04001844 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001845 break;
1846 }
1847 break;
1848 }
1849 case 'R':
1850 case 'r':
1851 {
1852 if (LocaleCompare((const char *) name,"radialGradient") == 0)
1853 {
cristyb51dff52011-05-19 16:55:47 +00001854 (void) FormatLocaleFile(svg_info->file,
Cristybd8bfcd2018-03-25 10:15:04 -04001855 "push gradient \"%s\" radial %g,%g %g,%g %g\n",
cristy3ed852e2009-09-05 21:47:34 +00001856 id,svg_info->element.cx,svg_info->element.cy,
1857 svg_info->element.major,svg_info->element.minor,
1858 svg_info->element.angle);
1859 break;
1860 }
1861 if (LocaleCompare((const char *) name,"rect") == 0)
1862 {
Cristyd7e36592018-04-21 14:45:52 -04001863 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001864 break;
1865 }
1866 break;
1867 }
1868 case 'S':
1869 case 's':
1870 {
Cristy865c6512018-05-20 19:32:04 -04001871 if (LocaleCompare((char *) name,"style") == 0)
1872 break;
cristy3ed852e2009-09-05 21:47:34 +00001873 if (LocaleCompare((const char *) name,"svg") == 0)
1874 {
Cristy80c52892018-05-03 20:06:56 -04001875 svg_info->svgDepth++;
Cristyd7e36592018-04-21 14:45:52 -04001876 PushGraphicContext(id);
Cristyfb080242018-04-29 15:27:26 -04001877 (void) FormatLocaleFile(svg_info->file,"compliance \"SVG\"\n");
Cristybd8bfcd2018-03-25 10:15:04 -04001878 (void) FormatLocaleFile(svg_info->file,"fill \"black\"\n");
Cristya4408152018-02-19 20:31:01 -05001879 (void) FormatLocaleFile(svg_info->file,"fill-opacity 1\n");
Cristybd8bfcd2018-03-25 10:15:04 -04001880 (void) FormatLocaleFile(svg_info->file,"stroke \"none\"\n");
Cristya4408152018-02-19 20:31:01 -05001881 (void) FormatLocaleFile(svg_info->file,"stroke-width 1\n");
1882 (void) FormatLocaleFile(svg_info->file,"stroke-opacity 1\n");
Cristyb36fbbd2018-04-23 07:18:44 -04001883 (void) FormatLocaleFile(svg_info->file,"fill-rule nonzero\n");
cristy3ed852e2009-09-05 21:47:34 +00001884 break;
1885 }
Cristy865c6512018-05-20 19:32:04 -04001886 if (LocaleCompare((const char *) name,"symbol") == 0)
1887 {
1888 (void) FormatLocaleFile(svg_info->file,"push symbol\n");
1889 break;
1890 }
cristy3ed852e2009-09-05 21:47:34 +00001891 break;
1892 }
1893 case 'T':
1894 case 't':
1895 {
1896 if (LocaleCompare((const char *) name,"text") == 0)
1897 {
Cristyd7e36592018-04-21 14:45:52 -04001898 PushGraphicContext(id);
Cristyc13321f2019-05-26 19:05:02 -04001899 svg_info->text_offset.x=svg_info->bounds.x;
1900 svg_info->text_offset.y=svg_info->bounds.y;
cristye4c6a422011-11-24 18:57:53 +00001901 svg_info->bounds.x=0.0;
1902 svg_info->bounds.y=0.0;
1903 svg_info->bounds.width=0.0;
1904 svg_info->bounds.height=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001905 break;
1906 }
1907 if (LocaleCompare((const char *) name,"tspan") == 0)
1908 {
1909 if (*svg_info->text != '\0')
1910 {
cristy3ed852e2009-09-05 21:47:34 +00001911 char
1912 *text;
1913
Cristy860af932019-03-13 09:58:42 -04001914 text=EscapeString(svg_info->text,'\"');
Cristybd8bfcd2018-03-25 10:15:04 -04001915 (void) FormatLocaleFile(svg_info->file,"text %g,%g \"%s\"\n",
Cristyc13321f2019-05-26 19:05:02 -04001916 svg_info->text_offset.x,svg_info->text_offset.y,text);
cristy3ed852e2009-09-05 21:47:34 +00001917 text=DestroyString(text);
cristy3ed852e2009-09-05 21:47:34 +00001918 *svg_info->text='\0';
1919 }
Cristyd7e36592018-04-21 14:45:52 -04001920 PushGraphicContext(id);
1921 break;
1922 }
1923 break;
1924 }
1925 case 'U':
1926 case 'u':
1927 {
1928 if (LocaleCompare((char *) name,"use") == 0)
1929 {
1930 PushGraphicContext(id);
cristy3ed852e2009-09-05 21:47:34 +00001931 break;
1932 }
1933 break;
1934 }
1935 default:
1936 break;
1937 }
1938 if (attributes != (const xmlChar **) NULL)
1939 for (i=0; (attributes[i] != (const xmlChar *) NULL); i+=2)
1940 {
1941 keyword=(const char *) attributes[i];
1942 value=(const char *) attributes[i+1];
1943 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1944 " %s = %s",keyword,value);
1945 switch (*keyword)
1946 {
1947 case 'A':
1948 case 'a':
1949 {
1950 if (LocaleCompare(keyword,"angle") == 0)
1951 {
cristyb51dff52011-05-19 16:55:47 +00001952 (void) FormatLocaleFile(svg_info->file,"angle %g\n",
cristy3ed852e2009-09-05 21:47:34 +00001953 GetUserSpaceCoordinateValue(svg_info,0,value));
1954 break;
1955 }
1956 break;
1957 }
1958 case 'C':
1959 case 'c':
1960 {
Cristyaefcc452018-05-23 20:08:48 -04001961 if (LocaleCompare(keyword,"class") == 0)
1962 {
Cristy760fe752019-11-11 19:16:06 -05001963 p=value;
1964 (void) GetNextToken(p,&p,MagickPathExtent,token);
1965 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04001966 (void) GetNextToken(p,&p,MagickPathExtent,token);
Cristy760fe752019-11-11 19:16:06 -05001967 if (*token != '\0')
1968 (void) FormatLocaleFile(svg_info->file,"class \"%s\"\n",value);
1969 else
1970 (void) FormatLocaleFile(svg_info->file,"class \"none\"\n");
Cristyaefcc452018-05-23 20:08:48 -04001971 break;
1972 }
cristy3ed852e2009-09-05 21:47:34 +00001973 if (LocaleCompare(keyword,"clip-path") == 0)
1974 {
Cristyb38b8e62018-03-31 10:46:32 -04001975 (void) FormatLocaleFile(svg_info->file,"clip-path \"%s\"\n",
1976 value);
cristy3ed852e2009-09-05 21:47:34 +00001977 break;
1978 }
1979 if (LocaleCompare(keyword,"clip-rule") == 0)
1980 {
Cristyb38b8e62018-03-31 10:46:32 -04001981 (void) FormatLocaleFile(svg_info->file,"clip-rule \"%s\"\n",
1982 value);
cristy3ed852e2009-09-05 21:47:34 +00001983 break;
1984 }
1985 if (LocaleCompare(keyword,"clipPathUnits") == 0)
1986 {
1987 (void) CloneString(&units,value);
Cristyb38b8e62018-03-31 10:46:32 -04001988 (void) FormatLocaleFile(svg_info->file,"clip-units \"%s\"\n",
1989 value);
cristy3ed852e2009-09-05 21:47:34 +00001990 break;
1991 }
1992 if (LocaleCompare(keyword,"color") == 0)
1993 {
1994 (void) CloneString(&color,value);
1995 break;
1996 }
1997 if (LocaleCompare(keyword,"cx") == 0)
1998 {
1999 svg_info->element.cx=
2000 GetUserSpaceCoordinateValue(svg_info,1,value);
2001 break;
2002 }
2003 if (LocaleCompare(keyword,"cy") == 0)
2004 {
2005 svg_info->element.cy=
2006 GetUserSpaceCoordinateValue(svg_info,-1,value);
2007 break;
2008 }
2009 break;
2010 }
2011 case 'D':
2012 case 'd':
2013 {
2014 if (LocaleCompare(keyword,"d") == 0)
2015 {
2016 (void) CloneString(&svg_info->vertices,value);
2017 break;
2018 }
2019 if (LocaleCompare(keyword,"dx") == 0)
2020 {
Cristyc63c5042018-06-24 11:34:07 -04002021 double
2022 dx;
2023
2024 dx=GetUserSpaceCoordinateValue(svg_info,1,value);
2025 svg_info->bounds.x+=dx;
Cristyc13321f2019-05-26 19:05:02 -04002026 svg_info->text_offset.x+=dx;
Cristyc63c5042018-06-24 11:34:07 -04002027 if (LocaleCompare((char *) name,"text") == 0)
2028 (void) FormatLocaleFile(svg_info->file,"translate %g,0.0\n",dx);
cristy3ed852e2009-09-05 21:47:34 +00002029 break;
2030 }
2031 if (LocaleCompare(keyword,"dy") == 0)
2032 {
Cristyc63c5042018-06-24 11:34:07 -04002033 double
2034 dy;
2035
2036 dy=GetUserSpaceCoordinateValue(svg_info,-1,value);
2037 svg_info->bounds.y+=dy;
Cristyc13321f2019-05-26 19:05:02 -04002038 svg_info->text_offset.y+=dy;
Cristyc63c5042018-06-24 11:34:07 -04002039 if (LocaleCompare((char *) name,"text") == 0)
2040 (void) FormatLocaleFile(svg_info->file,"translate 0.0,%g\n",dy);
cristy3ed852e2009-09-05 21:47:34 +00002041 break;
2042 }
2043 break;
2044 }
2045 case 'F':
2046 case 'f':
2047 {
2048 if (LocaleCompare(keyword,"fill") == 0)
2049 {
2050 if (LocaleCompare(value,"currentColor") == 0)
2051 {
Cristybd8bfcd2018-03-25 10:15:04 -04002052 (void) FormatLocaleFile(svg_info->file,"fill \"%s\"\n",color);
cristy3ed852e2009-09-05 21:47:34 +00002053 break;
2054 }
Cristybd8bfcd2018-03-25 10:15:04 -04002055 (void) FormatLocaleFile(svg_info->file,"fill \"%s\"\n",value);
cristy3ed852e2009-09-05 21:47:34 +00002056 break;
2057 }
2058 if (LocaleCompare(keyword,"fillcolor") == 0)
2059 {
Cristybd8bfcd2018-03-25 10:15:04 -04002060 (void) FormatLocaleFile(svg_info->file,"fill \"%s\"\n",value);
cristy3ed852e2009-09-05 21:47:34 +00002061 break;
2062 }
2063 if (LocaleCompare(keyword,"fill-rule") == 0)
2064 {
Cristyb38b8e62018-03-31 10:46:32 -04002065 (void) FormatLocaleFile(svg_info->file,"fill-rule \"%s\"\n",
2066 value);
cristy3ed852e2009-09-05 21:47:34 +00002067 break;
2068 }
Cristyb5298432016-09-19 20:07:29 -04002069 if (LocaleCompare(keyword,"fill-opacity") == 0)
cristy3ed852e2009-09-05 21:47:34 +00002070 {
Cristybd8bfcd2018-03-25 10:15:04 -04002071 (void) FormatLocaleFile(svg_info->file,"fill-opacity \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002072 value);
cristy3ed852e2009-09-05 21:47:34 +00002073 break;
2074 }
2075 if (LocaleCompare(keyword,"font-family") == 0)
2076 {
Cristybd8bfcd2018-03-25 10:15:04 -04002077 (void) FormatLocaleFile(svg_info->file,"font-family \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002078 value);
cristy3ed852e2009-09-05 21:47:34 +00002079 break;
2080 }
2081 if (LocaleCompare(keyword,"font-stretch") == 0)
2082 {
Cristybd8bfcd2018-03-25 10:15:04 -04002083 (void) FormatLocaleFile(svg_info->file,"font-stretch \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002084 value);
cristy3ed852e2009-09-05 21:47:34 +00002085 break;
2086 }
2087 if (LocaleCompare(keyword,"font-style") == 0)
2088 {
Cristyb38b8e62018-03-31 10:46:32 -04002089 (void) FormatLocaleFile(svg_info->file,"font-style \"%s\"\n",
2090 value);
cristy3ed852e2009-09-05 21:47:34 +00002091 break;
2092 }
2093 if (LocaleCompare(keyword,"font-size") == 0)
2094 {
Cristy3ed6c692016-05-23 19:24:23 -04002095 if (LocaleCompare(value,"xx-small") == 0)
2096 svg_info->pointsize=6.144;
2097 else if (LocaleCompare(value,"x-small") == 0)
2098 svg_info->pointsize=7.68;
2099 else if (LocaleCompare(value,"small") == 0)
2100 svg_info->pointsize=9.6;
2101 else if (LocaleCompare(value,"medium") == 0)
2102 svg_info->pointsize=12.0;
2103 else if (LocaleCompare(value,"large") == 0)
2104 svg_info->pointsize=14.4;
2105 else if (LocaleCompare(value,"x-large") == 0)
2106 svg_info->pointsize=17.28;
2107 else if (LocaleCompare(value,"xx-large") == 0)
2108 svg_info->pointsize=20.736;
2109 else
2110 svg_info->pointsize=GetUserSpaceCoordinateValue(svg_info,0,
2111 value);
cristyb51dff52011-05-19 16:55:47 +00002112 (void) FormatLocaleFile(svg_info->file,"font-size %g\n",
2113 svg_info->pointsize);
cristy3ed852e2009-09-05 21:47:34 +00002114 break;
2115 }
2116 if (LocaleCompare(keyword,"font-weight") == 0)
2117 {
Cristybd8bfcd2018-03-25 10:15:04 -04002118 (void) FormatLocaleFile(svg_info->file,"font-weight \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002119 value);
cristy3ed852e2009-09-05 21:47:34 +00002120 break;
2121 }
2122 break;
2123 }
2124 case 'G':
2125 case 'g':
2126 {
2127 if (LocaleCompare(keyword,"gradientTransform") == 0)
2128 {
2129 AffineMatrix
2130 affine,
2131 current,
2132 transform;
2133
2134 GetAffineMatrix(&transform);
2135 (void) LogMagickEvent(CoderEvent,GetMagickModule()," ");
Cristyaefcc452018-05-23 20:08:48 -04002136 tokens=SVGKeyValuePairs(context,'(',')',value,&number_tokens);
Cristy25a7cc42016-03-25 11:17:57 -04002137 if (tokens == (char **) NULL)
2138 break;
Cristyfc887f22016-08-22 07:43:54 -04002139 for (j=0; j < (ssize_t) (number_tokens-1); j+=2)
cristy3ed852e2009-09-05 21:47:34 +00002140 {
2141 keyword=(char *) tokens[j];
2142 if (keyword == (char *) NULL)
2143 continue;
2144 value=(char *) tokens[j+1];
2145 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2146 " %s: %s",keyword,value);
2147 current=transform;
2148 GetAffineMatrix(&affine);
2149 switch (*keyword)
2150 {
2151 case 'M':
2152 case 'm':
2153 {
2154 if (LocaleCompare(keyword,"matrix") == 0)
2155 {
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02002156 p=value;
Cristy448fd182019-07-27 16:26:38 -04002157 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristydbdd0e32011-11-04 23:29:40 +00002158 affine.sx=StringToDouble(value,(char **) NULL);
Cristy448fd182019-07-27 16:26:38 -04002159 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristy3ed852e2009-09-05 21:47:34 +00002160 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04002161 (void) GetNextToken(p,&p,MagickPathExtent,token);
Cristy3ed6c692016-05-23 19:24:23 -04002162 affine.rx=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04002163 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristy3ed852e2009-09-05 21:47:34 +00002164 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04002165 (void) GetNextToken(p,&p,MagickPathExtent,token);
Cristy3ed6c692016-05-23 19:24:23 -04002166 affine.ry=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04002167 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristy3ed852e2009-09-05 21:47:34 +00002168 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04002169 (void) GetNextToken(p,&p,MagickPathExtent,token);
Cristy3ed6c692016-05-23 19:24:23 -04002170 affine.sy=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04002171 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristy3ed852e2009-09-05 21:47:34 +00002172 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04002173 (void) GetNextToken(p,&p,MagickPathExtent,token);
Cristy3ed6c692016-05-23 19:24:23 -04002174 affine.tx=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04002175 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristy3ed852e2009-09-05 21:47:34 +00002176 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04002177 (void) GetNextToken(p,&p,MagickPathExtent,token);
Cristy3ed6c692016-05-23 19:24:23 -04002178 affine.ty=StringToDouble(token,&next_token);
cristy3ed852e2009-09-05 21:47:34 +00002179 break;
2180 }
2181 break;
2182 }
2183 case 'R':
2184 case 'r':
2185 {
2186 if (LocaleCompare(keyword,"rotate") == 0)
2187 {
2188 double
2189 angle;
2190
2191 angle=GetUserSpaceCoordinateValue(svg_info,0,value);
2192 affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
2193 affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
2194 affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
2195 affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
2196 break;
2197 }
2198 break;
2199 }
2200 case 'S':
2201 case 's':
2202 {
2203 if (LocaleCompare(keyword,"scale") == 0)
2204 {
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02002205 for (p=value; *p != '\0'; p++)
cristy3ed852e2009-09-05 21:47:34 +00002206 if ((isspace((int) ((unsigned char) *p)) != 0) ||
2207 (*p == ','))
2208 break;
2209 affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value);
2210 affine.sy=affine.sx;
2211 if (*p != '\0')
2212 affine.sy=
2213 GetUserSpaceCoordinateValue(svg_info,-1,p+1);
2214 svg_info->scale[svg_info->n]=ExpandAffine(&affine);
2215 break;
2216 }
2217 if (LocaleCompare(keyword,"skewX") == 0)
2218 {
2219 affine.sx=svg_info->affine.sx;
2220 affine.ry=tan(DegreesToRadians(fmod(
2221 GetUserSpaceCoordinateValue(svg_info,1,value),
2222 360.0)));
2223 affine.sy=svg_info->affine.sy;
2224 break;
2225 }
2226 if (LocaleCompare(keyword,"skewY") == 0)
2227 {
2228 affine.sx=svg_info->affine.sx;
2229 affine.rx=tan(DegreesToRadians(fmod(
2230 GetUserSpaceCoordinateValue(svg_info,-1,value),
2231 360.0)));
2232 affine.sy=svg_info->affine.sy;
2233 break;
2234 }
2235 break;
2236 }
2237 case 'T':
2238 case 't':
2239 {
2240 if (LocaleCompare(keyword,"translate") == 0)
2241 {
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02002242 for (p=value; *p != '\0'; p++)
cristy3ed852e2009-09-05 21:47:34 +00002243 if ((isspace((int) ((unsigned char) *p)) != 0) ||
2244 (*p == ','))
2245 break;
2246 affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value);
2247 affine.ty=affine.tx;
2248 if (*p != '\0')
2249 affine.ty=
2250 GetUserSpaceCoordinateValue(svg_info,-1,p+1);
2251 break;
2252 }
2253 break;
2254 }
2255 default:
2256 break;
2257 }
cristyef7c8a52010-10-10 13:46:51 +00002258 transform.sx=affine.sx*current.sx+affine.ry*current.rx;
2259 transform.rx=affine.rx*current.sx+affine.sy*current.rx;
2260 transform.ry=affine.sx*current.ry+affine.ry*current.sy;
2261 transform.sy=affine.rx*current.ry+affine.sy*current.sy;
cristy2ac30482011-11-08 20:24:57 +00002262 transform.tx=affine.tx*current.sx+affine.ty*current.ry+
2263 current.tx;
2264 transform.ty=affine.tx*current.rx+affine.ty*current.sy+
2265 current.ty;
cristy3ed852e2009-09-05 21:47:34 +00002266 }
cristyb51dff52011-05-19 16:55:47 +00002267 (void) FormatLocaleFile(svg_info->file,
cristye7f51092010-01-17 00:39:37 +00002268 "affine %g %g %g %g %g %g\n",transform.sx,
cristy8cd5b312010-01-07 01:10:24 +00002269 transform.rx,transform.ry,transform.sy,transform.tx,
2270 transform.ty);
cristy3ed852e2009-09-05 21:47:34 +00002271 for (j=0; tokens[j] != (char *) NULL; j++)
2272 tokens[j]=DestroyString(tokens[j]);
2273 tokens=(char **) RelinquishMagickMemory(tokens);
2274 break;
2275 }
2276 if (LocaleCompare(keyword,"gradientUnits") == 0)
2277 {
2278 (void) CloneString(&units,value);
Cristybd8bfcd2018-03-25 10:15:04 -04002279 (void) FormatLocaleFile(svg_info->file,"gradient-units \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002280 value);
cristy3ed852e2009-09-05 21:47:34 +00002281 break;
2282 }
2283 break;
2284 }
2285 case 'H':
2286 case 'h':
2287 {
2288 if (LocaleCompare(keyword,"height") == 0)
2289 {
2290 svg_info->bounds.height=
2291 GetUserSpaceCoordinateValue(svg_info,-1,value);
2292 break;
2293 }
2294 if (LocaleCompare(keyword,"href") == 0)
2295 {
Cristy2a0763f2018-04-22 08:16:54 -04002296 (void) CloneString(&svg_info->url,value);
cristy3ed852e2009-09-05 21:47:34 +00002297 break;
2298 }
2299 break;
2300 }
Cristy86d77652018-08-04 18:43:58 -04002301 case 'K':
2302 case 'k':
2303 {
2304 if (LocaleCompare(keyword,"kerning") == 0)
2305 {
2306 (void) FormatLocaleFile(svg_info->file,"kerning \"%s\"\n",
2307 value);
2308 break;
2309 }
2310 break;
2311 }
2312 case 'L':
2313 case 'l':
2314 {
2315 if (LocaleCompare(keyword,"letter-spacing") == 0)
2316 {
2317 (void) FormatLocaleFile(svg_info->file,"letter-spacing \"%s\"\n",
2318 value);
2319 break;
2320 }
2321 break;
2322 }
cristy3ed852e2009-09-05 21:47:34 +00002323 case 'M':
2324 case 'm':
2325 {
2326 if (LocaleCompare(keyword,"major") == 0)
2327 {
2328 svg_info->element.major=
2329 GetUserSpaceCoordinateValue(svg_info,1,value);
2330 break;
2331 }
Cristy189a2a22018-04-28 12:00:18 -04002332 if (LocaleCompare(keyword,"mask") == 0)
2333 {
2334 (void) FormatLocaleFile(svg_info->file,"mask \"%s\"\n",value);
2335 break;
2336 }
cristy3ed852e2009-09-05 21:47:34 +00002337 if (LocaleCompare(keyword,"minor") == 0)
2338 {
2339 svg_info->element.minor=
2340 GetUserSpaceCoordinateValue(svg_info,-1,value);
2341 break;
2342 }
2343 break;
2344 }
2345 case 'O':
2346 case 'o':
2347 {
2348 if (LocaleCompare(keyword,"offset") == 0)
2349 {
2350 (void) CloneString(&svg_info->offset,value);
2351 break;
2352 }
2353 if (LocaleCompare(keyword,"opacity") == 0)
2354 {
Cristybd8bfcd2018-03-25 10:15:04 -04002355 (void) FormatLocaleFile(svg_info->file,"opacity \"%s\"\n",value);
cristy3ed852e2009-09-05 21:47:34 +00002356 break;
2357 }
2358 break;
2359 }
2360 case 'P':
2361 case 'p':
2362 {
2363 if (LocaleCompare(keyword,"path") == 0)
2364 {
2365 (void) CloneString(&svg_info->url,value);
2366 break;
2367 }
2368 if (LocaleCompare(keyword,"points") == 0)
2369 {
2370 (void) CloneString(&svg_info->vertices,value);
2371 break;
2372 }
2373 break;
2374 }
2375 case 'R':
2376 case 'r':
2377 {
2378 if (LocaleCompare(keyword,"r") == 0)
2379 {
2380 svg_info->element.major=
2381 GetUserSpaceCoordinateValue(svg_info,1,value);
2382 svg_info->element.minor=
2383 GetUserSpaceCoordinateValue(svg_info,-1,value);
2384 break;
2385 }
2386 if (LocaleCompare(keyword,"rotate") == 0)
2387 {
2388 double
2389 angle;
2390
2391 angle=GetUserSpaceCoordinateValue(svg_info,0,value);
cristyb51dff52011-05-19 16:55:47 +00002392 (void) FormatLocaleFile(svg_info->file,"translate %g,%g\n",
cristy8cd5b312010-01-07 01:10:24 +00002393 svg_info->bounds.x,svg_info->bounds.y);
cristy3ed852e2009-09-05 21:47:34 +00002394 svg_info->bounds.x=0;
2395 svg_info->bounds.y=0;
cristyb51dff52011-05-19 16:55:47 +00002396 (void) FormatLocaleFile(svg_info->file,"rotate %g\n",angle);
cristy3ed852e2009-09-05 21:47:34 +00002397 break;
2398 }
2399 if (LocaleCompare(keyword,"rx") == 0)
2400 {
2401 if (LocaleCompare((const char *) name,"ellipse") == 0)
2402 svg_info->element.major=
2403 GetUserSpaceCoordinateValue(svg_info,1,value);
2404 else
2405 svg_info->radius.x=
2406 GetUserSpaceCoordinateValue(svg_info,1,value);
2407 break;
2408 }
2409 if (LocaleCompare(keyword,"ry") == 0)
2410 {
2411 if (LocaleCompare((const char *) name,"ellipse") == 0)
2412 svg_info->element.minor=
2413 GetUserSpaceCoordinateValue(svg_info,-1,value);
2414 else
2415 svg_info->radius.y=
2416 GetUserSpaceCoordinateValue(svg_info,-1,value);
2417 break;
2418 }
2419 break;
2420 }
2421 case 'S':
2422 case 's':
2423 {
2424 if (LocaleCompare(keyword,"stop-color") == 0)
2425 {
2426 (void) CloneString(&svg_info->stop_color,value);
2427 break;
2428 }
2429 if (LocaleCompare(keyword,"stroke") == 0)
2430 {
2431 if (LocaleCompare(value,"currentColor") == 0)
2432 {
Cristyb38b8e62018-03-31 10:46:32 -04002433 (void) FormatLocaleFile(svg_info->file,"stroke \"%s\"\n",
2434 color);
cristy3ed852e2009-09-05 21:47:34 +00002435 break;
2436 }
Cristybd8bfcd2018-03-25 10:15:04 -04002437 (void) FormatLocaleFile(svg_info->file,"stroke \"%s\"\n",value);
cristy3ed852e2009-09-05 21:47:34 +00002438 break;
2439 }
2440 if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
2441 {
cristyb51dff52011-05-19 16:55:47 +00002442 (void) FormatLocaleFile(svg_info->file,"stroke-antialias %d\n",
cristy3ed852e2009-09-05 21:47:34 +00002443 LocaleCompare(value,"true") == 0);
2444 break;
2445 }
2446 if (LocaleCompare(keyword,"stroke-dasharray") == 0)
2447 {
cristyb51dff52011-05-19 16:55:47 +00002448 (void) FormatLocaleFile(svg_info->file,"stroke-dasharray %s\n",
2449 value);
cristy3ed852e2009-09-05 21:47:34 +00002450 break;
2451 }
2452 if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
2453 {
Cristycc855a02016-07-23 20:45:24 -04002454 (void) FormatLocaleFile(svg_info->file,"stroke-dashoffset %g\n",
2455 GetUserSpaceCoordinateValue(svg_info,1,value));
cristy3ed852e2009-09-05 21:47:34 +00002456 break;
2457 }
2458 if (LocaleCompare(keyword,"stroke-linecap") == 0)
2459 {
Cristybd8bfcd2018-03-25 10:15:04 -04002460 (void) FormatLocaleFile(svg_info->file,"stroke-linecap \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002461 value);
cristy3ed852e2009-09-05 21:47:34 +00002462 break;
2463 }
2464 if (LocaleCompare(keyword,"stroke-linejoin") == 0)
2465 {
Cristybd8bfcd2018-03-25 10:15:04 -04002466 (void) FormatLocaleFile(svg_info->file,"stroke-linejoin \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002467 value);
cristy3ed852e2009-09-05 21:47:34 +00002468 break;
2469 }
2470 if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
2471 {
Cristyb38b8e62018-03-31 10:46:32 -04002472 (void) FormatLocaleFile(svg_info->file,
2473 "stroke-miterlimit \"%s\"\n",value);
cristy3ed852e2009-09-05 21:47:34 +00002474 break;
2475 }
2476 if (LocaleCompare(keyword,"stroke-opacity") == 0)
2477 {
Cristybd8bfcd2018-03-25 10:15:04 -04002478 (void) FormatLocaleFile(svg_info->file,"stroke-opacity \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002479 value);
cristy3ed852e2009-09-05 21:47:34 +00002480 break;
2481 }
2482 if (LocaleCompare(keyword,"stroke-width") == 0)
2483 {
cristyb51dff52011-05-19 16:55:47 +00002484 (void) FormatLocaleFile(svg_info->file,"stroke-width %g\n",
cristy3ed852e2009-09-05 21:47:34 +00002485 GetUserSpaceCoordinateValue(svg_info,1,value));
2486 break;
2487 }
2488 if (LocaleCompare(keyword,"style") == 0)
2489 {
Cristyaefcc452018-05-23 20:08:48 -04002490 SVGProcessStyleElement(context,name,value);
cristy3ed852e2009-09-05 21:47:34 +00002491 break;
2492 }
2493 break;
2494 }
2495 case 'T':
2496 case 't':
2497 {
2498 if (LocaleCompare(keyword,"text-align") == 0)
2499 {
Cristybd8bfcd2018-03-25 10:15:04 -04002500 (void) FormatLocaleFile(svg_info->file,"text-align \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002501 value);
cristy3ed852e2009-09-05 21:47:34 +00002502 break;
2503 }
2504 if (LocaleCompare(keyword,"text-anchor") == 0)
2505 {
Cristybd8bfcd2018-03-25 10:15:04 -04002506 (void) FormatLocaleFile(svg_info->file,"text-anchor \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00002507 value);
cristy3ed852e2009-09-05 21:47:34 +00002508 break;
2509 }
2510 if (LocaleCompare(keyword,"text-decoration") == 0)
2511 {
2512 if (LocaleCompare(value,"underline") == 0)
cristyb51dff52011-05-19 16:55:47 +00002513 (void) FormatLocaleFile(svg_info->file,"decorate underline\n");
cristy3ed852e2009-09-05 21:47:34 +00002514 if (LocaleCompare(value,"line-through") == 0)
cristyb51dff52011-05-19 16:55:47 +00002515 (void) FormatLocaleFile(svg_info->file,
2516 "decorate line-through\n");
cristy3ed852e2009-09-05 21:47:34 +00002517 if (LocaleCompare(value,"overline") == 0)
cristyb51dff52011-05-19 16:55:47 +00002518 (void) FormatLocaleFile(svg_info->file,"decorate overline\n");
cristy3ed852e2009-09-05 21:47:34 +00002519 break;
2520 }
2521 if (LocaleCompare(keyword,"text-antialiasing") == 0)
2522 {
cristyb51dff52011-05-19 16:55:47 +00002523 (void) FormatLocaleFile(svg_info->file,"text-antialias %d\n",
cristy3ed852e2009-09-05 21:47:34 +00002524 LocaleCompare(value,"true") == 0);
2525 break;
2526 }
Cristycfeb5792020-05-16 13:42:30 -04002527 if (LocaleCompare(keyword,"transform") == 0)
2528 {
2529 AffineMatrix
2530 affine,
2531 current,
2532 transform;
2533
2534 GetAffineMatrix(&transform);
2535 (void) LogMagickEvent(CoderEvent,GetMagickModule()," ");
2536 tokens=SVGKeyValuePairs(context,'(',')',value,&number_tokens);
2537 if (tokens == (char **) NULL)
2538 break;
2539 for (j=0; j < (ssize_t) (number_tokens-1); j+=2)
2540 {
2541 keyword=(char *) tokens[j];
2542 value=(char *) tokens[j+1];
2543 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2544 " %s: %s",keyword,value);
2545 current=transform;
2546 GetAffineMatrix(&affine);
2547 switch (*keyword)
2548 {
2549 case 'M':
2550 case 'm':
2551 {
2552 if (LocaleCompare(keyword,"matrix") == 0)
2553 {
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02002554 p=value;
Cristycfeb5792020-05-16 13:42:30 -04002555 (void) GetNextToken(p,&p,MagickPathExtent,token);
2556 affine.sx=StringToDouble(value,(char **) NULL);
2557 (void) GetNextToken(p,&p,MagickPathExtent,token);
2558 if (*token == ',')
2559 (void) GetNextToken(p,&p,MagickPathExtent,token);
2560 affine.rx=StringToDouble(token,&next_token);
2561 (void) GetNextToken(p,&p,MagickPathExtent,token);
2562 if (*token == ',')
2563 (void) GetNextToken(p,&p,MagickPathExtent,token);
2564 affine.ry=StringToDouble(token,&next_token);
2565 (void) GetNextToken(p,&p,MagickPathExtent,token);
2566 if (*token == ',')
2567 (void) GetNextToken(p,&p,MagickPathExtent,token);
2568 affine.sy=StringToDouble(token,&next_token);
2569 (void) GetNextToken(p,&p,MagickPathExtent,token);
2570 if (*token == ',')
2571 (void) GetNextToken(p,&p,MagickPathExtent,token);
2572 affine.tx=StringToDouble(token,&next_token);
2573 (void) GetNextToken(p,&p,MagickPathExtent,token);
2574 if (*token == ',')
2575 (void) GetNextToken(p,&p,MagickPathExtent,token);
2576 affine.ty=StringToDouble(token,&next_token);
2577 break;
2578 }
2579 break;
2580 }
2581 case 'R':
2582 case 'r':
2583 {
2584 if (LocaleCompare(keyword,"rotate") == 0)
2585 {
2586 double
2587 angle,
2588 x,
2589 y;
2590
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02002591 p=value;
Cristycfeb5792020-05-16 13:42:30 -04002592 (void) GetNextToken(p,&p,MagickPathExtent,token);
2593 angle=StringToDouble(value,(char **) NULL);
2594 affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
2595 affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
2596 affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
2597 affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
2598 (void) GetNextToken(p,&p,MagickPathExtent,token);
2599 if (*token == ',')
2600 (void) GetNextToken(p,&p,MagickPathExtent,token);
2601 x=StringToDouble(token,&next_token);
2602 (void) GetNextToken(p,&p,MagickPathExtent,token);
2603 if (*token == ',')
2604 (void) GetNextToken(p,&p,MagickPathExtent,token);
2605 y=StringToDouble(token,&next_token);
Cristy731c9ef2020-11-24 21:56:54 +00002606 y=StringToDouble(token,&next_token);
2607 affine.tx=(-1.0*(svg_info->bounds.x+x*
2608 cos(DegreesToRadians(fmod(angle,360.0)))-y*
2609 sin(DegreesToRadians(fmod(angle,360.0)))))+x;
2610 affine.ty=(-1.0*(svg_info->bounds.y+x*
Cristycfeb5792020-05-16 13:42:30 -04002611 sin(DegreesToRadians(fmod(angle,360.0)))+y*
Cristy731c9ef2020-11-24 21:56:54 +00002612 cos(DegreesToRadians(fmod(angle,360.0)))))+y;
Cristycfeb5792020-05-16 13:42:30 -04002613 break;
2614 }
2615 break;
2616 }
2617 case 'S':
2618 case 's':
2619 {
2620 if (LocaleCompare(keyword,"scale") == 0)
2621 {
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02002622 for (p=value; *p != '\0'; p++)
Cristycfeb5792020-05-16 13:42:30 -04002623 if ((isspace((int) ((unsigned char) *p)) != 0) ||
2624 (*p == ','))
2625 break;
2626 affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value);
2627 affine.sy=affine.sx;
2628 if (*p != '\0')
2629 affine.sy=GetUserSpaceCoordinateValue(svg_info,-1,
2630 p+1);
2631 svg_info->scale[svg_info->n]=ExpandAffine(&affine);
2632 break;
2633 }
2634 if (LocaleCompare(keyword,"skewX") == 0)
2635 {
2636 affine.sx=svg_info->affine.sx;
2637 affine.ry=tan(DegreesToRadians(fmod(
2638 GetUserSpaceCoordinateValue(svg_info,1,value),
2639 360.0)));
2640 affine.sy=svg_info->affine.sy;
2641 break;
2642 }
2643 if (LocaleCompare(keyword,"skewY") == 0)
2644 {
2645 affine.sx=svg_info->affine.sx;
2646 affine.rx=tan(DegreesToRadians(fmod(
2647 GetUserSpaceCoordinateValue(svg_info,-1,value),
2648 360.0)));
2649 affine.sy=svg_info->affine.sy;
2650 break;
2651 }
2652 break;
2653 }
2654 case 'T':
2655 case 't':
2656 {
2657 if (LocaleCompare(keyword,"translate") == 0)
2658 {
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02002659 for (p=value; *p != '\0'; p++)
Cristycfeb5792020-05-16 13:42:30 -04002660 if ((isspace((int) ((unsigned char) *p)) != 0) ||
2661 (*p == ','))
2662 break;
2663 affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value);
2664 affine.ty=0;
2665 if (*p != '\0')
2666 affine.ty=GetUserSpaceCoordinateValue(svg_info,-1,
2667 p+1);
2668 break;
2669 }
2670 break;
2671 }
2672 default:
2673 break;
2674 }
2675 transform.sx=affine.sx*current.sx+affine.ry*current.rx;
2676 transform.rx=affine.rx*current.sx+affine.sy*current.rx;
2677 transform.ry=affine.sx*current.ry+affine.ry*current.sy;
2678 transform.sy=affine.rx*current.ry+affine.sy*current.sy;
2679 transform.tx=affine.tx*current.sx+affine.ty*current.ry+
2680 current.tx;
2681 transform.ty=affine.tx*current.rx+affine.ty*current.sy+
2682 current.ty;
2683 }
2684 (void) FormatLocaleFile(svg_info->file,
2685 "affine %g %g %g %g %g %g\n",transform.sx,transform.rx,
2686 transform.ry,transform.sy,transform.tx,transform.ty);
2687 for (j=0; tokens[j] != (char *) NULL; j++)
2688 tokens[j]=DestroyString(tokens[j]);
2689 tokens=(char **) RelinquishMagickMemory(tokens);
2690 break;
2691 }
cristy3ed852e2009-09-05 21:47:34 +00002692 break;
2693 }
2694 case 'V':
2695 case 'v':
2696 {
2697 if (LocaleCompare(keyword,"verts") == 0)
2698 {
2699 (void) CloneString(&svg_info->vertices,value);
2700 break;
2701 }
2702 if (LocaleCompare(keyword,"viewBox") == 0)
2703 {
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02002704 p=value;
Cristy448fd182019-07-27 16:26:38 -04002705 (void) GetNextToken(p,&p,MagickPathExtent,token);
Cristy3ed6c692016-05-23 19:24:23 -04002706 svg_info->view_box.x=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04002707 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristy3ed852e2009-09-05 21:47:34 +00002708 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04002709 (void) GetNextToken(p,&p,MagickPathExtent,token);
Cristy3ed6c692016-05-23 19:24:23 -04002710 svg_info->view_box.y=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04002711 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristy3ed852e2009-09-05 21:47:34 +00002712 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04002713 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristydbdd0e32011-11-04 23:29:40 +00002714 svg_info->view_box.width=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +00002715 (char **) NULL);
Elliott Hughes5d41fba2021-04-12 16:36:42 -07002716 if (svg_info->bounds.width < MagickEpsilon)
cristy3ed852e2009-09-05 21:47:34 +00002717 svg_info->bounds.width=svg_info->view_box.width;
Cristy448fd182019-07-27 16:26:38 -04002718 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristy3ed852e2009-09-05 21:47:34 +00002719 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04002720 (void) GetNextToken(p,&p,MagickPathExtent,token);
cristydbdd0e32011-11-04 23:29:40 +00002721 svg_info->view_box.height=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +00002722 (char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002723 if (svg_info->bounds.height == 0)
2724 svg_info->bounds.height=svg_info->view_box.height;
2725 break;
2726 }
2727 break;
2728 }
2729 case 'W':
2730 case 'w':
2731 {
2732 if (LocaleCompare(keyword,"width") == 0)
2733 {
2734 svg_info->bounds.width=
2735 GetUserSpaceCoordinateValue(svg_info,1,value);
2736 break;
2737 }
2738 break;
2739 }
2740 case 'X':
2741 case 'x':
2742 {
2743 if (LocaleCompare(keyword,"x") == 0)
2744 {
2745 svg_info->bounds.x=GetUserSpaceCoordinateValue(svg_info,1,value);
2746 break;
2747 }
2748 if (LocaleCompare(keyword,"xlink:href") == 0)
2749 {
Cristy2a0763f2018-04-22 08:16:54 -04002750 (void) CloneString(&svg_info->url,value);
cristy3ed852e2009-09-05 21:47:34 +00002751 break;
2752 }
2753 if (LocaleCompare(keyword,"x1") == 0)
2754 {
2755 svg_info->segment.x1=
2756 GetUserSpaceCoordinateValue(svg_info,1,value);
2757 break;
2758 }
2759 if (LocaleCompare(keyword,"x2") == 0)
2760 {
2761 svg_info->segment.x2=
2762 GetUserSpaceCoordinateValue(svg_info,1,value);
2763 break;
2764 }
2765 break;
2766 }
2767 case 'Y':
2768 case 'y':
2769 {
2770 if (LocaleCompare(keyword,"y") == 0)
2771 {
2772 svg_info->bounds.y=GetUserSpaceCoordinateValue(svg_info,-1,value);
2773 break;
2774 }
2775 if (LocaleCompare(keyword,"y1") == 0)
2776 {
2777 svg_info->segment.y1=
2778 GetUserSpaceCoordinateValue(svg_info,-1,value);
2779 break;
2780 }
2781 if (LocaleCompare(keyword,"y2") == 0)
2782 {
2783 svg_info->segment.y2=
2784 GetUserSpaceCoordinateValue(svg_info,-1,value);
2785 break;
2786 }
2787 break;
2788 }
2789 default:
2790 break;
2791 }
2792 }
2793 if (LocaleCompare((const char *) name,"svg") == 0)
2794 {
2795 if (svg_info->document->encoding != (const xmlChar *) NULL)
cristyb51dff52011-05-19 16:55:47 +00002796 (void) FormatLocaleFile(svg_info->file,"encoding \"%s\"\n",
cristy3ed852e2009-09-05 21:47:34 +00002797 (const char *) svg_info->document->encoding);
2798 if (attributes != (const xmlChar **) NULL)
2799 {
2800 double
2801 sx,
cristy59ee19d2011-12-01 15:56:42 +00002802 sy,
2803 tx,
2804 ty;
cristy3ed852e2009-09-05 21:47:34 +00002805
Elliott Hughes5d41fba2021-04-12 16:36:42 -07002806 if ((svg_info->view_box.width < MagickEpsilon) ||
2807 (svg_info->view_box.height < MagickEpsilon))
cristy3ed852e2009-09-05 21:47:34 +00002808 svg_info->view_box=svg_info->bounds;
Cristy5f478ca2016-05-14 18:43:20 -04002809 svg_info->width=0;
Elliott Hughes5d41fba2021-04-12 16:36:42 -07002810 if (svg_info->bounds.width >= MagickEpsilon)
Cristy5f478ca2016-05-14 18:43:20 -04002811 svg_info->width=(size_t) floor(svg_info->bounds.width+0.5);
2812 svg_info->height=0;
Elliott Hughes5d41fba2021-04-12 16:36:42 -07002813 if (svg_info->bounds.height >= MagickEpsilon)
Cristy5f478ca2016-05-14 18:43:20 -04002814 svg_info->height=(size_t) floor(svg_info->bounds.height+0.5);
cristyb51dff52011-05-19 16:55:47 +00002815 (void) FormatLocaleFile(svg_info->file,"viewbox 0 0 %.20g %.20g\n",
2816 (double) svg_info->width,(double) svg_info->height);
Cristyc68d49b2019-10-22 06:26:44 -04002817 sx=PerceptibleReciprocal(svg_info->view_box.width)*svg_info->width;
2818 sy=PerceptibleReciprocal(svg_info->view_box.height)*svg_info->height;
cristy59ee19d2011-12-01 15:56:42 +00002819 tx=svg_info->view_box.x != 0.0 ? (double) -sx*svg_info->view_box.x :
2820 0.0;
2821 ty=svg_info->view_box.y != 0.0 ? (double) -sy*svg_info->view_box.y :
2822 0.0;
2823 (void) FormatLocaleFile(svg_info->file,"affine %g 0 0 %g %g %g\n",
2824 sx,sy,tx,ty);
Cristy80c52892018-05-03 20:06:56 -04002825 if ((svg_info->svgDepth == 1) && (*background != '\0'))
Cristyb38b8e62018-03-31 10:46:32 -04002826 {
Cristyd7e36592018-04-21 14:45:52 -04002827 PushGraphicContext(id);
Cristyb38b8e62018-03-31 10:46:32 -04002828 (void) FormatLocaleFile(svg_info->file,"fill %s\n",background);
2829 (void) FormatLocaleFile(svg_info->file,
2830 "rectangle 0,0 %g,%g\n",svg_info->view_box.width,
2831 svg_info->view_box.height);
2832 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2833 }
cristy3ed852e2009-09-05 21:47:34 +00002834 }
2835 }
2836 (void) LogMagickEvent(CoderEvent,GetMagickModule()," )");
Cristy61d7e8b2018-05-29 10:57:27 -04002837 if (units != (char *) NULL)
2838 units=DestroyString(units);
cristy3ed852e2009-09-05 21:47:34 +00002839 if (color != (char *) NULL)
2840 color=DestroyString(color);
2841}
2842
2843static void SVGEndElement(void *context,const xmlChar *name)
2844{
2845 SVGInfo
2846 *svg_info;
2847
2848 /*
2849 Called when the end of an element has been detected.
2850 */
2851 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2852 " SAX.endElement(%s)",name);
2853 svg_info=(SVGInfo *) context;
cristy13d07042010-11-21 20:56:18 +00002854 if (strchr((char *) name,':') != (char *) NULL)
cristy768b9302010-11-20 00:05:47 +00002855 {
2856 /*
2857 Skip over namespace.
2858 */
2859 for ( ; *name != ':'; name++) ;
2860 name++;
2861 }
cristy3ed852e2009-09-05 21:47:34 +00002862 switch (*name)
2863 {
2864 case 'C':
2865 case 'c':
2866 {
2867 if (LocaleCompare((const char *) name,"circle") == 0)
2868 {
cristyb51dff52011-05-19 16:55:47 +00002869 (void) FormatLocaleFile(svg_info->file,"circle %g,%g %g,%g\n",
cristy8cd5b312010-01-07 01:10:24 +00002870 svg_info->element.cx,svg_info->element.cy,svg_info->element.cx,
2871 svg_info->element.cy+svg_info->element.minor);
cristyb51dff52011-05-19 16:55:47 +00002872 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00002873 break;
2874 }
2875 if (LocaleCompare((const char *) name,"clipPath") == 0)
2876 {
cristyb51dff52011-05-19 16:55:47 +00002877 (void) FormatLocaleFile(svg_info->file,"pop clip-path\n");
cristy3ed852e2009-09-05 21:47:34 +00002878 break;
2879 }
2880 break;
2881 }
2882 case 'D':
2883 case 'd':
2884 {
2885 if (LocaleCompare((const char *) name,"defs") == 0)
2886 {
cristyb51dff52011-05-19 16:55:47 +00002887 (void) FormatLocaleFile(svg_info->file,"pop defs\n");
cristy3ed852e2009-09-05 21:47:34 +00002888 break;
2889 }
2890 if (LocaleCompare((const char *) name,"desc") == 0)
2891 {
Cristyf2dc1dd2020-12-28 13:59:26 -05002892 char
cristy3ed852e2009-09-05 21:47:34 +00002893 *p;
2894
2895 if (*svg_info->text == '\0')
2896 break;
2897 (void) fputc('#',svg_info->file);
2898 for (p=svg_info->text; *p != '\0'; p++)
2899 {
2900 (void) fputc(*p,svg_info->file);
2901 if (*p == '\n')
2902 (void) fputc('#',svg_info->file);
2903 }
2904 (void) fputc('\n',svg_info->file);
2905 *svg_info->text='\0';
2906 break;
2907 }
2908 break;
2909 }
2910 case 'E':
2911 case 'e':
2912 {
2913 if (LocaleCompare((const char *) name,"ellipse") == 0)
2914 {
2915 double
2916 angle;
2917
2918 angle=svg_info->element.angle;
cristyb51dff52011-05-19 16:55:47 +00002919 (void) FormatLocaleFile(svg_info->file,"ellipse %g,%g %g,%g 0,360\n",
cristy3ed852e2009-09-05 21:47:34 +00002920 svg_info->element.cx,svg_info->element.cy,
2921 angle == 0.0 ? svg_info->element.major : svg_info->element.minor,
2922 angle == 0.0 ? svg_info->element.minor : svg_info->element.major);
cristyb51dff52011-05-19 16:55:47 +00002923 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00002924 break;
2925 }
2926 break;
2927 }
Cristy6abd75a2018-03-01 10:19:41 -05002928 case 'F':
Cristy4a38f5d2018-03-01 10:22:17 -05002929 case 'f':
Cristy6abd75a2018-03-01 10:19:41 -05002930 {
2931 if (LocaleCompare((const char *) name,"foreignObject") == 0)
2932 {
2933 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
2934 break;
2935 }
2936 break;
2937 }
cristy3ed852e2009-09-05 21:47:34 +00002938 case 'G':
2939 case 'g':
2940 {
2941 if (LocaleCompare((const char *) name,"g") == 0)
2942 {
cristyb51dff52011-05-19 16:55:47 +00002943 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00002944 break;
2945 }
2946 break;
2947 }
2948 case 'I':
2949 case 'i':
2950 {
2951 if (LocaleCompare((const char *) name,"image") == 0)
2952 {
cristyb51dff52011-05-19 16:55:47 +00002953 (void) FormatLocaleFile(svg_info->file,
Cristybd8bfcd2018-03-25 10:15:04 -04002954 "image Over %g,%g %g,%g \"%s\"\n",svg_info->bounds.x,
cristyb51dff52011-05-19 16:55:47 +00002955 svg_info->bounds.y,svg_info->bounds.width,svg_info->bounds.height,
2956 svg_info->url);
2957 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00002958 break;
2959 }
2960 break;
2961 }
2962 case 'L':
2963 case 'l':
2964 {
2965 if (LocaleCompare((const char *) name,"line") == 0)
2966 {
cristyb51dff52011-05-19 16:55:47 +00002967 (void) FormatLocaleFile(svg_info->file,"line %g,%g %g,%g\n",
cristy8cd5b312010-01-07 01:10:24 +00002968 svg_info->segment.x1,svg_info->segment.y1,svg_info->segment.x2,
2969 svg_info->segment.y2);
cristyb51dff52011-05-19 16:55:47 +00002970 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00002971 break;
2972 }
2973 if (LocaleCompare((const char *) name,"linearGradient") == 0)
2974 {
cristyb51dff52011-05-19 16:55:47 +00002975 (void) FormatLocaleFile(svg_info->file,"pop gradient\n");
cristy3ed852e2009-09-05 21:47:34 +00002976 break;
2977 }
2978 break;
2979 }
Cristy189a2a22018-04-28 12:00:18 -04002980 case 'M':
2981 case 'm':
2982 {
2983 if (LocaleCompare((const char *) name,"mask") == 0)
2984 {
2985 (void) FormatLocaleFile(svg_info->file,"pop mask\n");
2986 break;
2987 }
2988 break;
2989 }
cristy3ed852e2009-09-05 21:47:34 +00002990 case 'P':
2991 case 'p':
2992 {
2993 if (LocaleCompare((const char *) name,"pattern") == 0)
2994 {
cristyb51dff52011-05-19 16:55:47 +00002995 (void) FormatLocaleFile(svg_info->file,"pop pattern\n");
cristy3ed852e2009-09-05 21:47:34 +00002996 break;
2997 }
2998 if (LocaleCompare((const char *) name,"path") == 0)
2999 {
Cristybd8bfcd2018-03-25 10:15:04 -04003000 (void) FormatLocaleFile(svg_info->file,"path \"%s\"\n",
cristyb51dff52011-05-19 16:55:47 +00003001 svg_info->vertices);
3002 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00003003 break;
3004 }
3005 if (LocaleCompare((const char *) name,"polygon") == 0)
3006 {
cristyb51dff52011-05-19 16:55:47 +00003007 (void) FormatLocaleFile(svg_info->file,"polygon %s\n",
3008 svg_info->vertices);
3009 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00003010 break;
3011 }
3012 if (LocaleCompare((const char *) name,"polyline") == 0)
3013 {
cristyb51dff52011-05-19 16:55:47 +00003014 (void) FormatLocaleFile(svg_info->file,"polyline %s\n",
3015 svg_info->vertices);
3016 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00003017 break;
3018 }
3019 break;
3020 }
3021 case 'R':
3022 case 'r':
3023 {
3024 if (LocaleCompare((const char *) name,"radialGradient") == 0)
3025 {
cristyb51dff52011-05-19 16:55:47 +00003026 (void) FormatLocaleFile(svg_info->file,"pop gradient\n");
cristy3ed852e2009-09-05 21:47:34 +00003027 break;
3028 }
3029 if (LocaleCompare((const char *) name,"rect") == 0)
3030 {
3031 if ((svg_info->radius.x == 0.0) && (svg_info->radius.y == 0.0))
3032 {
Cristy1520a8e2018-05-28 17:47:58 -04003033 if ((fabs(svg_info->bounds.width-1.0) < MagickEpsilon) &&
3034 (fabs(svg_info->bounds.height-1.0) < MagickEpsilon))
3035 (void) FormatLocaleFile(svg_info->file,"point %g,%g\n",
3036 svg_info->bounds.x,svg_info->bounds.y);
3037 else
3038 (void) FormatLocaleFile(svg_info->file,
3039 "rectangle %g,%g %g,%g\n",svg_info->bounds.x,
3040 svg_info->bounds.y,svg_info->bounds.x+svg_info->bounds.width,
3041 svg_info->bounds.y+svg_info->bounds.height);
cristyb51dff52011-05-19 16:55:47 +00003042 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00003043 break;
3044 }
3045 if (svg_info->radius.x == 0.0)
3046 svg_info->radius.x=svg_info->radius.y;
3047 if (svg_info->radius.y == 0.0)
3048 svg_info->radius.y=svg_info->radius.x;
cristyb51dff52011-05-19 16:55:47 +00003049 (void) FormatLocaleFile(svg_info->file,
cristye7f51092010-01-17 00:39:37 +00003050 "roundRectangle %g,%g %g,%g %g,%g\n",
cristy3ed852e2009-09-05 21:47:34 +00003051 svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.x+
3052 svg_info->bounds.width,svg_info->bounds.y+svg_info->bounds.height,
3053 svg_info->radius.x,svg_info->radius.y);
3054 svg_info->radius.x=0.0;
3055 svg_info->radius.y=0.0;
cristyb51dff52011-05-19 16:55:47 +00003056 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00003057 break;
3058 }
3059 break;
3060 }
3061 case 'S':
3062 case 's':
3063 {
3064 if (LocaleCompare((const char *) name,"stop") == 0)
3065 {
Cristybd8bfcd2018-03-25 10:15:04 -04003066 (void) FormatLocaleFile(svg_info->file,"stop-color \"%s\" %s\n",
Cristy4e28e4a2020-11-13 22:28:33 +00003067 svg_info->stop_color,svg_info->offset == (char *) NULL ? "100%" :
Cristy64790e82020-02-18 19:16:03 -05003068 svg_info->offset);
cristy3ed852e2009-09-05 21:47:34 +00003069 break;
3070 }
Cristy865c6512018-05-20 19:32:04 -04003071 if (LocaleCompare((char *) name,"style") == 0)
Cristy2a0763f2018-04-22 08:16:54 -04003072 {
Cristyaefcc452018-05-23 20:08:48 -04003073 char
3074 *keyword,
3075 **tokens,
3076 *value;
3077
Cristyf2dc1dd2020-12-28 13:59:26 -05003078 ssize_t
Cristyaefcc452018-05-23 20:08:48 -04003079 j;
3080
3081 size_t
3082 number_tokens;
3083
Cristy865c6512018-05-20 19:32:04 -04003084 /*
3085 Find style definitions in svg_info->text.
3086 */
Cristyaefcc452018-05-23 20:08:48 -04003087 tokens=SVGKeyValuePairs(context,'{','}',svg_info->text,
3088 &number_tokens);
3089 if (tokens == (char **) NULL)
3090 break;
3091 for (j=0; j < (ssize_t) (number_tokens-1); j+=2)
3092 {
3093 keyword=(char *) tokens[j];
3094 value=(char *) tokens[j+1];
3095 (void) FormatLocaleFile(svg_info->file,"push class \"%s\"\n",
Cristy6c355322018-05-28 16:48:50 -04003096 *keyword == '.' ? keyword+1 : keyword);
Cristyaefcc452018-05-23 20:08:48 -04003097 SVGProcessStyleElement(context,name,value);
3098 (void) FormatLocaleFile(svg_info->file,"pop class\n");
3099 }
Cristyd3ae9c12019-03-28 20:22:13 -04003100 for (j=0; tokens[j] != (char *) NULL; j++)
3101 tokens[j]=DestroyString(tokens[j]);
3102 tokens=(char **) RelinquishMagickMemory(tokens);
Cristy2a0763f2018-04-22 08:16:54 -04003103 break;
3104 }
cristy3ed852e2009-09-05 21:47:34 +00003105 if (LocaleCompare((const char *) name,"svg") == 0)
3106 {
cristyb51dff52011-05-19 16:55:47 +00003107 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
Cristy80c52892018-05-03 20:06:56 -04003108 svg_info->svgDepth--;
cristy3ed852e2009-09-05 21:47:34 +00003109 break;
3110 }
Cristy865c6512018-05-20 19:32:04 -04003111 if (LocaleCompare((const char *) name,"symbol") == 0)
3112 {
3113 (void) FormatLocaleFile(svg_info->file,"pop symbol\n");
3114 break;
3115 }
cristy3ed852e2009-09-05 21:47:34 +00003116 break;
3117 }
3118 case 'T':
3119 case 't':
3120 {
3121 if (LocaleCompare((const char *) name,"text") == 0)
3122 {
3123 if (*svg_info->text != '\0')
3124 {
3125 char
3126 *text;
3127
Cristyd23af3f2018-08-04 16:02:20 -04003128 SVGStripString(MagickTrue,svg_info->text);
Cristy860af932019-03-13 09:58:42 -04003129 text=EscapeString(svg_info->text,'\"');
Cristyc13321f2019-05-26 19:05:02 -04003130 (void) FormatLocaleFile(svg_info->file,"text %g,%g \"%s\"\n",
3131 svg_info->text_offset.x,svg_info->text_offset.y,text);
cristy3ed852e2009-09-05 21:47:34 +00003132 text=DestroyString(text);
3133 *svg_info->text='\0';
3134 }
cristyb51dff52011-05-19 16:55:47 +00003135 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00003136 break;
3137 }
3138 if (LocaleCompare((const char *) name,"tspan") == 0)
3139 {
3140 if (*svg_info->text != '\0')
3141 {
cristy3ed852e2009-09-05 21:47:34 +00003142 char
3143 *text;
3144
Cristy860af932019-03-13 09:58:42 -04003145 text=EscapeString(svg_info->text,'\"');
Cristybd8bfcd2018-03-25 10:15:04 -04003146 (void) FormatLocaleFile(svg_info->file,"text %g,%g \"%s\"\n",
Cristyc13321f2019-05-26 19:05:02 -04003147 svg_info->bounds.x,svg_info->bounds.y,text);
cristy3ed852e2009-09-05 21:47:34 +00003148 text=DestroyString(text);
cristy3ed852e2009-09-05 21:47:34 +00003149 *svg_info->text='\0';
3150 }
cristyb51dff52011-05-19 16:55:47 +00003151 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
cristy3ed852e2009-09-05 21:47:34 +00003152 break;
3153 }
3154 if (LocaleCompare((const char *) name,"title") == 0)
3155 {
3156 if (*svg_info->text == '\0')
3157 break;
3158 (void) CloneString(&svg_info->title,svg_info->text);
3159 *svg_info->text='\0';
3160 break;
3161 }
3162 break;
3163 }
Cristyd7e36592018-04-21 14:45:52 -04003164 case 'U':
3165 case 'u':
3166 {
3167 if (LocaleCompare((char *) name,"use") == 0)
3168 {
3169 if ((svg_info->bounds.x != 0.0) || (svg_info->bounds.y != 0.0))
3170 (void) FormatLocaleFile(svg_info->file,"translate %g,%g\n",
3171 svg_info->bounds.x,svg_info->bounds.y);
Cristy2a0763f2018-04-22 08:16:54 -04003172 (void) FormatLocaleFile(svg_info->file,"use \"url(%s)\"\n",
3173 svg_info->url);
Cristyd7e36592018-04-21 14:45:52 -04003174 (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
3175 break;
3176 }
3177 break;
3178 }
cristy3ed852e2009-09-05 21:47:34 +00003179 default:
3180 break;
3181 }
3182 *svg_info->text='\0';
Cristy81bfff22018-03-10 07:58:31 -05003183 (void) memset(&svg_info->element,0,sizeof(svg_info->element));
3184 (void) memset(&svg_info->segment,0,sizeof(svg_info->segment));
cristy3ed852e2009-09-05 21:47:34 +00003185 svg_info->n--;
3186}
3187
3188static void SVGCharacters(void *context,const xmlChar *c,int length)
3189{
cristy6dc3b782011-11-08 19:24:00 +00003190 char
3191 *text;
3192
Cristyf2dc1dd2020-12-28 13:59:26 -05003193 char
cristy3ed852e2009-09-05 21:47:34 +00003194 *p;
3195
Cristyf2dc1dd2020-12-28 13:59:26 -05003196 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003197 i;
3198
3199 SVGInfo
3200 *svg_info;
3201
3202 /*
3203 Receiving some characters from the parser.
3204 */
3205 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003206 " SAX.characters(%s,%.20g)",c,(double) length);
cristy3ed852e2009-09-05 21:47:34 +00003207 svg_info=(SVGInfo *) context;
cristy6dc3b782011-11-08 19:24:00 +00003208 text=(char *) AcquireQuantumMemory(length+1,sizeof(*text));
3209 if (text == (char *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003210 return;
cristy6dc3b782011-11-08 19:24:00 +00003211 p=text;
cristybb503372010-05-27 20:51:26 +00003212 for (i=0; i < (ssize_t) length; i++)
cristy3ed852e2009-09-05 21:47:34 +00003213 *p++=c[i];
3214 *p='\0';
Cristya64a7bc2018-08-03 07:20:50 -04003215 SVGStripString(MagickFalse,text);
cristy6dc3b782011-11-08 19:24:00 +00003216 if (svg_info->text == (char *) NULL)
3217 svg_info->text=text;
3218 else
3219 {
3220 (void) ConcatenateString(&svg_info->text,text);
3221 text=DestroyString(text);
3222 }
cristy3ed852e2009-09-05 21:47:34 +00003223}
3224
3225static void SVGReference(void *context,const xmlChar *name)
3226{
3227 SVGInfo
3228 *svg_info;
3229
3230 xmlParserCtxtPtr
3231 parser;
3232
3233 /*
3234 Called when an entity reference is detected.
3235 */
3236 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.reference(%s)",
3237 name);
3238 svg_info=(SVGInfo *) context;
3239 parser=svg_info->parser;
3240 if (parser == (xmlParserCtxtPtr) NULL)
3241 return;
3242 if (parser->node == (xmlNodePtr) NULL)
3243 return;
3244 if (*name == '#')
3245 (void) xmlAddChild(parser->node,xmlNewCharRef(svg_info->document,name));
3246 else
3247 (void) xmlAddChild(parser->node,xmlNewReference(svg_info->document,name));
3248}
3249
3250static void SVGIgnorableWhitespace(void *context,const xmlChar *c,int length)
3251{
3252 SVGInfo
3253 *svg_info;
3254
3255 /*
3256 Receiving some ignorable whitespaces from the parser.
3257 */
3258 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3259 " SAX.ignorableWhitespace(%.30s, %d)",c,length);
3260 svg_info=(SVGInfo *) context;
cristyda16f162011-02-19 23:52:17 +00003261 (void) svg_info;
cristy3ed852e2009-09-05 21:47:34 +00003262}
3263
3264static void SVGProcessingInstructions(void *context,const xmlChar *target,
3265 const xmlChar *data)
3266{
3267 SVGInfo
3268 *svg_info;
3269
3270 /*
3271 A processing instruction has been parsed.
3272 */
3273 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3274 " SAX.processingInstruction(%s, %s)",target,data);
3275 svg_info=(SVGInfo *) context;
cristyda16f162011-02-19 23:52:17 +00003276 (void) svg_info;
cristy3ed852e2009-09-05 21:47:34 +00003277}
3278
3279static void SVGComment(void *context,const xmlChar *value)
3280{
3281 SVGInfo
3282 *svg_info;
3283
3284 /*
3285 A comment has been parsed.
3286 */
3287 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.comment(%s)",
3288 value);
3289 svg_info=(SVGInfo *) context;
3290 if (svg_info->comment != (char *) NULL)
3291 (void) ConcatenateString(&svg_info->comment,"\n");
3292 (void) ConcatenateString(&svg_info->comment,(const char *) value);
3293}
3294
Cristy94eb2ec2018-11-11 10:46:30 -05003295static void SVGWarning(void *,const char *,...)
3296 magick_attribute((__format__ (__printf__,2,3)));
3297
cristy3ed852e2009-09-05 21:47:34 +00003298static void SVGWarning(void *context,const char *format,...)
3299{
3300 char
3301 *message,
cristy151b66d2015-04-15 10:50:31 +00003302 reason[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00003303
3304 SVGInfo
3305 *svg_info;
3306
3307 va_list
3308 operands;
3309
3310 /**
3311 Display and format a warning messages, gives file, line, position and
3312 extra parameters.
3313 */
3314 va_start(operands,format);
3315 svg_info=(SVGInfo *) context;
3316 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.warning: ");
3317 (void) LogMagickEvent(CoderEvent,GetMagickModule(),format,operands);
3318#if !defined(MAGICKCORE_HAVE_VSNPRINTF)
3319 (void) vsprintf(reason,format,operands);
3320#else
cristy151b66d2015-04-15 10:50:31 +00003321 (void) vsnprintf(reason,MagickPathExtent,format,operands);
cristy3ed852e2009-09-05 21:47:34 +00003322#endif
3323 message=GetExceptionMessage(errno);
3324 (void) ThrowMagickException(svg_info->exception,GetMagickModule(),
3325 DelegateWarning,reason,"`%s`",message);
3326 message=DestroyString(message);
3327 va_end(operands);
Elliott Hughes5d41fba2021-04-12 16:36:42 -07003328 svg_info->parser->instate=XML_PARSER_EOF;
cristy3ed852e2009-09-05 21:47:34 +00003329}
3330
Cristy94eb2ec2018-11-11 10:46:30 -05003331static void SVGError(void *,const char *,...)
3332 magick_attribute((__format__ (__printf__,2,3)));
3333
cristy3ed852e2009-09-05 21:47:34 +00003334static void SVGError(void *context,const char *format,...)
3335{
3336 char
3337 *message,
cristy151b66d2015-04-15 10:50:31 +00003338 reason[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00003339
3340 SVGInfo
3341 *svg_info;
3342
3343 va_list
3344 operands;
3345
3346 /*
3347 Display and format a error formats, gives file, line, position and
3348 extra parameters.
3349 */
3350 va_start(operands,format);
3351 svg_info=(SVGInfo *) context;
3352 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.error: ");
3353 (void) LogMagickEvent(CoderEvent,GetMagickModule(),format,operands);
3354#if !defined(MAGICKCORE_HAVE_VSNPRINTF)
3355 (void) vsprintf(reason,format,operands);
3356#else
cristy151b66d2015-04-15 10:50:31 +00003357 (void) vsnprintf(reason,MagickPathExtent,format,operands);
cristy3ed852e2009-09-05 21:47:34 +00003358#endif
3359 message=GetExceptionMessage(errno);
3360 (void) ThrowMagickException(svg_info->exception,GetMagickModule(),CoderError,
3361 reason,"`%s`",message);
3362 message=DestroyString(message);
3363 va_end(operands);
3364}
3365
3366static void SVGCDataBlock(void *context,const xmlChar *value,int length)
3367{
3368 SVGInfo
3369 *svg_info;
3370
Dirk Lemstra72e247f2019-10-27 07:22:12 +01003371 xmlNodePtr
3372 child;
cristy3ed852e2009-09-05 21:47:34 +00003373
3374 xmlParserCtxtPtr
3375 parser;
3376
3377 /*
3378 Called when a pcdata block has been parsed.
3379 */
3380 (void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.pcdata(%s, %d)",
3381 value,length);
3382 svg_info=(SVGInfo *) context;
3383 parser=svg_info->parser;
3384 child=xmlGetLastChild(parser->node);
3385 if ((child != (xmlNodePtr) NULL) && (child->type == XML_CDATA_SECTION_NODE))
3386 {
3387 xmlTextConcat(child,value,length);
3388 return;
3389 }
Dirk Lemstra1e2387c2019-10-27 07:41:56 +01003390 child=xmlNewCDataBlock(parser->myDoc,value,length);
3391 if (xmlAddChild(parser->node,child) == (xmlNodePtr) NULL)
3392 xmlFreeNode(child);
cristy3ed852e2009-09-05 21:47:34 +00003393}
3394
3395static void SVGExternalSubset(void *context,const xmlChar *name,
3396 const xmlChar *external_id,const xmlChar *system_id)
3397{
3398 SVGInfo
3399 *svg_info;
3400
3401 xmlParserCtxt
3402 parser_context;
3403
3404 xmlParserCtxtPtr
3405 parser;
3406
3407 xmlParserInputPtr
3408 input;
3409
3410 /*
3411 Does this document has an external subset?
3412 */
3413 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
3414 " SAX.externalSubset(%s, %s, %s)",name,
3415 (external_id != (const xmlChar *) NULL ? (const char *) external_id : "none"),
3416 (system_id != (const xmlChar *) NULL ? (const char *) system_id : "none"));
3417 svg_info=(SVGInfo *) context;
3418 parser=svg_info->parser;
3419 if (((external_id == NULL) && (system_id == NULL)) ||
3420 ((parser->validate == 0) || (parser->wellFormed == 0) ||
3421 (svg_info->document == 0)))
3422 return;
3423 input=SVGResolveEntity(context,external_id,system_id);
3424 if (input == NULL)
3425 return;
3426 (void) xmlNewDtd(svg_info->document,name,external_id,system_id);
3427 parser_context=(*parser);
3428 parser->inputTab=(xmlParserInputPtr *) xmlMalloc(5*sizeof(*parser->inputTab));
3429 if (parser->inputTab == (xmlParserInputPtr *) NULL)
3430 {
3431 parser->errNo=XML_ERR_NO_MEMORY;
3432 parser->input=parser_context.input;
3433 parser->inputNr=parser_context.inputNr;
3434 parser->inputMax=parser_context.inputMax;
3435 parser->inputTab=parser_context.inputTab;
3436 return;
3437 }
3438 parser->inputNr=0;
3439 parser->inputMax=5;
3440 parser->input=NULL;
3441 xmlPushInput(parser,input);
3442 (void) xmlSwitchEncoding(parser,xmlDetectCharEncoding(parser->input->cur,4));
3443 if (input->filename == (char *) NULL)
3444 input->filename=(char *) xmlStrdup(system_id);
3445 input->line=1;
3446 input->col=1;
3447 input->base=parser->input->cur;
3448 input->cur=parser->input->cur;
3449 input->free=NULL;
3450 xmlParseExternalSubset(parser,external_id,system_id);
3451 while (parser->inputNr > 1)
3452 (void) xmlPopInput(parser);
3453 xmlFreeInputStream(parser->input);
3454 xmlFree(parser->inputTab);
3455 parser->input=parser_context.input;
3456 parser->inputNr=parser_context.inputNr;
3457 parser->inputMax=parser_context.inputMax;
3458 parser->inputTab=parser_context.inputTab;
3459}
3460
cristy3ed852e2009-09-05 21:47:34 +00003461#if defined(__cplusplus) || defined(c_plusplus)
3462}
3463#endif
3464
3465static Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
3466{
cristy3ed852e2009-09-05 21:47:34 +00003467 char
cristy151b66d2015-04-15 10:50:31 +00003468 filename[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00003469
Dirk Lemstra76923ae2020-01-11 16:16:10 +01003470 const char
3471 *option;
3472
cristy3ed852e2009-09-05 21:47:34 +00003473 FILE
3474 *file;
3475
3476 Image
Cristyd51e6172018-04-28 09:01:01 -04003477 *image,
3478 *next;
cristy3ed852e2009-09-05 21:47:34 +00003479
3480 int
3481 status,
3482 unique_file;
3483
cristybb503372010-05-27 20:51:26 +00003484 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003485 n;
3486
3487 SVGInfo
3488 *svg_info;
3489
3490 unsigned char
cristy151b66d2015-04-15 10:50:31 +00003491 message[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00003492
cristy1f9e1ed2009-11-18 04:09:38 +00003493 xmlSAXHandler
cristy5f6f01c2009-11-19 19:36:42 +00003494 sax_modules;
cristy1f9e1ed2009-11-18 04:09:38 +00003495
cristy3ed852e2009-09-05 21:47:34 +00003496 xmlSAXHandlerPtr
3497 sax_handler;
3498
3499 /*
3500 Open image file.
3501 */
3502 assert(image_info != (const ImageInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003503 assert(image_info->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003504 assert(exception != (ExceptionInfo *) NULL);
3505 if (image_info->debug != MagickFalse)
3506 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3507 image_info->filename);
cristye1c94d92015-06-28 12:16:33 +00003508 assert(exception->signature == MagickCoreSignature);
cristy9950d572011-10-01 18:22:35 +00003509 image=AcquireImage(image_info,exception);
cristy3ed852e2009-09-05 21:47:34 +00003510 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
3511 if (status == MagickFalse)
3512 {
3513 image=DestroyImageList(image);
3514 return((Image *) NULL);
3515 }
Cristyfa3b1432017-04-13 21:08:52 -04003516 if ((fabs(image->resolution.x) < MagickEpsilon) ||
3517 (fabs(image->resolution.y) < MagickEpsilon))
cristy7da596e2012-08-15 11:36:05 +00003518 {
3519 GeometryInfo
3520 geometry_info;
3521
3522 int
3523 flags;
3524
3525 flags=ParseGeometry(SVGDensityGeometry,&geometry_info);
3526 image->resolution.x=geometry_info.rho;
3527 image->resolution.y=geometry_info.sigma;
3528 if ((flags & SigmaValue) == 0)
3529 image->resolution.y=image->resolution.x;
3530 }
cristy3ed852e2009-09-05 21:47:34 +00003531 if (LocaleCompare(image_info->magick,"MSVG") != 0)
3532 {
Cristyfedaa752019-02-03 07:23:20 -05003533 Image
3534 *svg_image;
cristy1164d5f2012-08-15 00:58:25 +00003535
Elliott Hughes5d41fba2021-04-12 16:36:42 -07003536#if defined(MAGICKCORE_RSVG_DELEGATE)
3537 if (LocaleCompare(image_info->magick,"RSVG") == 0)
3538 {
3539 svg_image=RenderRSVGImage(image_info,image,exception);
3540 return(svg_image);
3541 }
3542#endif
Cristyfedaa752019-02-03 07:23:20 -05003543 svg_image=RenderSVGImage(image_info,image,exception);
3544 if (svg_image != (Image *) NULL)
cristy1164d5f2012-08-15 00:58:25 +00003545 {
Cristyfedaa752019-02-03 07:23:20 -05003546 image=DestroyImageList(image);
3547 return(svg_image);
cristy1164d5f2012-08-15 00:58:25 +00003548 }
cristy3ed852e2009-09-05 21:47:34 +00003549#if defined(MAGICKCORE_RSVG_DELEGATE)
Elliott Hughes5d41fba2021-04-12 16:36:42 -07003550 svg_image=RenderRSVGImage(image_info,image,exception);
3551 return(svg_image);
cristy3ed852e2009-09-05 21:47:34 +00003552#endif
cristy3ed852e2009-09-05 21:47:34 +00003553 }
3554 /*
3555 Open draw file.
3556 */
3557 file=(FILE *) NULL;
3558 unique_file=AcquireUniqueFileResource(filename);
3559 if (unique_file != -1)
3560 file=fdopen(unique_file,"w");
3561 if ((unique_file == -1) || (file == (FILE *) NULL))
3562 {
cristy151b66d2015-04-15 10:50:31 +00003563 (void) CopyMagickString(image->filename,filename,MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00003564 ThrowFileException(exception,FileOpenError,"UnableToCreateTemporaryFile",
3565 image->filename);
3566 image=DestroyImageList(image);
3567 return((Image *) NULL);
3568 }
3569 /*
3570 Parse SVG file.
3571 */
3572 svg_info=AcquireSVGInfo();
3573 if (svg_info == (SVGInfo *) NULL)
cristy100a0562014-04-18 01:27:37 +00003574 {
3575 (void) fclose(file);
3576 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
3577 }
cristy3ed852e2009-09-05 21:47:34 +00003578 svg_info->file=file;
3579 svg_info->exception=exception;
3580 svg_info->image=image;
3581 svg_info->image_info=image_info;
3582 svg_info->bounds.width=image->columns;
3583 svg_info->bounds.height=image->rows;
Cristy80c52892018-05-03 20:06:56 -04003584 svg_info->svgDepth=0;
cristy3ed852e2009-09-05 21:47:34 +00003585 if (image_info->size != (char *) NULL)
3586 (void) CloneString(&svg_info->size,image_info->size);
3587 if (image->debug != MagickFalse)
3588 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"begin SAX");
Cristy88d320a2019-03-31 12:04:45 -04003589 xmlInitParser();
cristy3ed852e2009-09-05 21:47:34 +00003590 (void) xmlSubstituteEntitiesDefault(1);
Cristy81bfff22018-03-10 07:58:31 -05003591 (void) memset(&sax_modules,0,sizeof(sax_modules));
cristy5f6f01c2009-11-19 19:36:42 +00003592 sax_modules.internalSubset=SVGInternalSubset;
3593 sax_modules.isStandalone=SVGIsStandalone;
3594 sax_modules.hasInternalSubset=SVGHasInternalSubset;
3595 sax_modules.hasExternalSubset=SVGHasExternalSubset;
3596 sax_modules.resolveEntity=SVGResolveEntity;
3597 sax_modules.getEntity=SVGGetEntity;
3598 sax_modules.entityDecl=SVGEntityDeclaration;
3599 sax_modules.notationDecl=SVGNotationDeclaration;
3600 sax_modules.attributeDecl=SVGAttributeDeclaration;
3601 sax_modules.elementDecl=SVGElementDeclaration;
3602 sax_modules.unparsedEntityDecl=SVGUnparsedEntityDeclaration;
3603 sax_modules.setDocumentLocator=SVGSetDocumentLocator;
3604 sax_modules.startDocument=SVGStartDocument;
3605 sax_modules.endDocument=SVGEndDocument;
3606 sax_modules.startElement=SVGStartElement;
3607 sax_modules.endElement=SVGEndElement;
3608 sax_modules.reference=SVGReference;
3609 sax_modules.characters=SVGCharacters;
3610 sax_modules.ignorableWhitespace=SVGIgnorableWhitespace;
3611 sax_modules.processingInstruction=SVGProcessingInstructions;
3612 sax_modules.comment=SVGComment;
3613 sax_modules.warning=SVGWarning;
3614 sax_modules.error=SVGError;
3615 sax_modules.fatalError=SVGError;
3616 sax_modules.getParameterEntity=SVGGetParameterEntity;
3617 sax_modules.cdataBlock=SVGCDataBlock;
3618 sax_modules.externalSubset=SVGExternalSubset;
3619 sax_handler=(&sax_modules);
Cristy4dbaf0e2016-06-01 09:48:38 -04003620 n=ReadBlob(image,MagickPathExtent-1,message);
3621 message[n]='\0';
cristy3ed852e2009-09-05 21:47:34 +00003622 if (n > 0)
3623 {
3624 svg_info->parser=xmlCreatePushParserCtxt(sax_handler,svg_info,(char *)
3625 message,n,image->filename);
Dirk Lemstra43dfb182020-09-25 20:07:04 +02003626 if (svg_info->parser != (xmlParserCtxtPtr) NULL)
3627 {
3628 option=GetImageOption(image_info,"svg:xml-parse-huge");
3629 if ((option != (char *) NULL) && (IsStringTrue(option) != MagickFalse))
3630 (void) xmlCtxtUseOptions(svg_info->parser,XML_PARSE_HUGE);
3631 while ((n=ReadBlob(image,MagickPathExtent-1,message)) != 0)
3632 {
3633 message[n]='\0';
3634 status=xmlParseChunk(svg_info->parser,(char *) message,(int) n,0);
3635 if (status != 0)
3636 break;
3637 }
3638 }
3639 }
3640 if (svg_info->parser == (xmlParserCtxtPtr) NULL)
3641 {
3642 svg_info=DestroySVGInfo(svg_info);
3643 (void) RelinquishUniqueFileResource(filename);
3644 image=DestroyImage(image);
3645 return((Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00003646 }
3647 (void) xmlParseChunk(svg_info->parser,(char *) message,0,1);
Cristy5777d242016-06-16 16:58:13 -04003648 SVGEndDocument(svg_info);
Dirk Lemstra913a10b2019-10-27 21:25:25 +01003649 if (svg_info->parser->myDoc != (xmlDocPtr) NULL)
3650 xmlFreeDoc(svg_info->parser->myDoc);
cristy3ed852e2009-09-05 21:47:34 +00003651 xmlFreeParserCtxt(svg_info->parser);
3652 if (image->debug != MagickFalse)
3653 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"end SAX");
cristy3ed852e2009-09-05 21:47:34 +00003654 (void) fclose(file);
3655 (void) CloseBlob(image);
3656 image->columns=svg_info->width;
3657 image->rows=svg_info->height;
3658 if (exception->severity >= ErrorException)
3659 {
Dirk Lemstrab0e61972017-04-17 18:07:48 +02003660 svg_info=DestroySVGInfo(svg_info);
3661 (void) RelinquishUniqueFileResource(filename);
cristy3ed852e2009-09-05 21:47:34 +00003662 image=DestroyImage(image);
3663 return((Image *) NULL);
3664 }
3665 if (image_info->ping == MagickFalse)
3666 {
3667 ImageInfo
3668 *read_info;
3669
3670 /*
3671 Draw image.
3672 */
3673 image=DestroyImage(image);
3674 image=(Image *) NULL;
3675 read_info=CloneImageInfo(image_info);
3676 SetImageInfoBlob(read_info,(void *) NULL,0);
cristy151b66d2015-04-15 10:50:31 +00003677 (void) FormatLocaleString(read_info->filename,MagickPathExtent,"mvg:%s",
cristy3ed852e2009-09-05 21:47:34 +00003678 filename);
3679 image=ReadImage(read_info,exception);
3680 read_info=DestroyImageInfo(read_info);
3681 if (image != (Image *) NULL)
3682 (void) CopyMagickString(image->filename,image_info->filename,
cristy151b66d2015-04-15 10:50:31 +00003683 MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00003684 }
3685 /*
3686 Relinquish resources.
3687 */
3688 if (image != (Image *) NULL)
3689 {
3690 if (svg_info->title != (char *) NULL)
cristyd15e6592011-10-15 00:13:06 +00003691 (void) SetImageProperty(image,"svg:title",svg_info->title,exception);
cristy3ed852e2009-09-05 21:47:34 +00003692 if (svg_info->comment != (char *) NULL)
cristyd15e6592011-10-15 00:13:06 +00003693 (void) SetImageProperty(image,"svg:comment",svg_info->comment,
3694 exception);
cristy3ed852e2009-09-05 21:47:34 +00003695 }
Cristyd51e6172018-04-28 09:01:01 -04003696 for (next=GetFirstImageInList(image); next != (Image *) NULL; )
3697 {
Elliott Hughes5d41fba2021-04-12 16:36:42 -07003698 (void) CopyMagickString(next->filename,image->filename,MagickPathExtent);
3699 (void) CopyMagickString(next->magick,"SVG",MagickPathExtent);
Cristyd51e6172018-04-28 09:01:01 -04003700 next=GetNextImageInList(next);
3701 }
cristy3ed852e2009-09-05 21:47:34 +00003702 svg_info=DestroySVGInfo(svg_info);
3703 (void) RelinquishUniqueFileResource(filename);
3704 return(GetFirstImageInList(image));
3705}
Cristyfedaa752019-02-03 07:23:20 -05003706#else
3707static Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
3708{
3709 Image
3710 *image,
3711 *svg_image;
3712
3713 MagickBooleanType
3714 status;
3715
3716 assert(image_info != (const ImageInfo *) NULL);
3717 assert(image_info->signature == MagickCoreSignature);
3718 assert(exception != (ExceptionInfo *) NULL);
3719 if (image_info->debug != MagickFalse)
3720 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
3721 image_info->filename);
3722 assert(exception->signature == MagickCoreSignature);
3723 image=AcquireImage(image_info,exception);
3724 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
3725 if (status == MagickFalse)
3726 {
3727 image=DestroyImageList(image);
3728 return((Image *) NULL);
3729 }
3730 if ((fabs(image->resolution.x) < MagickEpsilon) ||
3731 (fabs(image->resolution.y) < MagickEpsilon))
3732 {
3733 GeometryInfo
3734 geometry_info;
3735
3736 MagickStatusType
3737 flags;
3738
3739 flags=ParseGeometry(SVGDensityGeometry,&geometry_info);
3740 image->resolution.x=geometry_info.rho;
3741 image->resolution.y=geometry_info.sigma;
3742 if ((flags & SigmaValue) == 0)
3743 image->resolution.y=image->resolution.x;
3744 }
3745 svg_image=RenderSVGImage(image_info,image,exception);
3746 image=DestroyImage(image);
3747 return(svg_image);
3748}
cristy3ed852e2009-09-05 21:47:34 +00003749#endif
Cristy868ede62018-02-26 19:04:51 -05003750
cristy3ed852e2009-09-05 21:47:34 +00003751/*
3752%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3753% %
3754% %
3755% %
3756% R e g i s t e r S V G I m a g e %
3757% %
3758% %
3759% %
3760%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3761%
3762% RegisterSVGImage() adds attributes for the SVG image format to
3763% the list of supported formats. The attributes include the image format
3764% tag, a method to read and/or write the format, whether the format
3765% supports the saving of more than one frame to the same file or blob,
3766% whether the format supports native in-memory I/O, and a brief
3767% description of the format.
3768%
3769% The format of the RegisterSVGImage method is:
3770%
cristybb503372010-05-27 20:51:26 +00003771% size_t RegisterSVGImage(void)
cristy3ed852e2009-09-05 21:47:34 +00003772%
3773*/
cristybb503372010-05-27 20:51:26 +00003774ModuleExport size_t RegisterSVGImage(void)
cristy3ed852e2009-09-05 21:47:34 +00003775{
3776 char
cristy151b66d2015-04-15 10:50:31 +00003777 version[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00003778
3779 MagickInfo
3780 *entry;
3781
3782 *version='\0';
3783#if defined(LIBXML_DOTTED_VERSION)
Cristy5777d242016-06-16 16:58:13 -04003784 (void) CopyMagickString(version,"XML " LIBXML_DOTTED_VERSION,
3785 MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00003786#endif
3787#if defined(MAGICKCORE_RSVG_DELEGATE)
cristya3afd2c2013-08-31 20:26:57 +00003788#if !GLIB_CHECK_VERSION(2,35,0)
cristy7febf992013-03-25 12:01:32 +00003789 g_type_init();
cristyc3eda392013-04-03 00:22:13 +00003790#endif
cristy151b66d2015-04-15 10:50:31 +00003791 (void) FormatLocaleString(version,MagickPathExtent,"RSVG %d.%d.%d",
cristy3ed852e2009-09-05 21:47:34 +00003792 LIBRSVG_MAJOR_VERSION,LIBRSVG_MINOR_VERSION,LIBRSVG_MICRO_VERSION);
3793#endif
dirk06b627a2015-04-06 18:59:17 +00003794 entry=AcquireMagickInfo("SVG","SVG","Scalable Vector Graphics");
cristy3ed852e2009-09-05 21:47:34 +00003795 entry->decoder=(DecodeImageHandler *) ReadSVGImage;
cristy3ed852e2009-09-05 21:47:34 +00003796 entry->encoder=(EncodeImageHandler *) WriteSVGImage;
Dirk Lemstra046709a2017-08-26 22:53:00 +02003797#if defined(MAGICKCORE_RSVG_DELEGATE)
3798 entry->flags^=CoderDecoderThreadSupportFlag;
3799#endif
cristy99872d02014-02-05 12:26:14 +00003800 entry->mime_type=ConstantString("image/svg+xml");
cristy3ed852e2009-09-05 21:47:34 +00003801 if (*version != '\0')
3802 entry->version=ConstantString(version);
3803 entry->magick=(IsImageFormatHandler *) IsSVG;
cristy3ed852e2009-09-05 21:47:34 +00003804 (void) RegisterMagickInfo(entry);
dirk06b627a2015-04-06 18:59:17 +00003805 entry=AcquireMagickInfo("SVG","SVGZ","Compressed Scalable Vector Graphics");
cristy3ed852e2009-09-05 21:47:34 +00003806#if defined(MAGICKCORE_XML_DELEGATE)
3807 entry->decoder=(DecodeImageHandler *) ReadSVGImage;
3808#endif
3809 entry->encoder=(EncodeImageHandler *) WriteSVGImage;
Dirk Lemstra046709a2017-08-26 22:53:00 +02003810#if defined(MAGICKCORE_RSVG_DELEGATE)
3811 entry->flags^=CoderDecoderThreadSupportFlag;
3812#endif
cristy99872d02014-02-05 12:26:14 +00003813 entry->mime_type=ConstantString("image/svg+xml");
cristy3ed852e2009-09-05 21:47:34 +00003814 if (*version != '\0')
3815 entry->version=ConstantString(version);
3816 entry->magick=(IsImageFormatHandler *) IsSVG;
cristy3ed852e2009-09-05 21:47:34 +00003817 (void) RegisterMagickInfo(entry);
Elliott Hughes5d41fba2021-04-12 16:36:42 -07003818#if defined(MAGICKCORE_RSVG_DELEGATE)
3819 entry=AcquireMagickInfo("SVG","RSVG","Librsvg SVG renderer");
3820 entry->decoder=(DecodeImageHandler *) ReadSVGImage;
3821 entry->encoder=(EncodeImageHandler *) WriteSVGImage;
3822 entry->flags^=CoderDecoderThreadSupportFlag;
3823 entry->mime_type=ConstantString("image/svg+xml");
3824 if (*version != '\0')
3825 entry->version=ConstantString(version);
3826 entry->magick=(IsImageFormatHandler *) IsSVG;
3827 (void) RegisterMagickInfo(entry);
3828#endif
dirk06b627a2015-04-06 18:59:17 +00003829 entry=AcquireMagickInfo("SVG","MSVG",
3830 "ImageMagick's own SVG internal renderer");
cristy3ed852e2009-09-05 21:47:34 +00003831#if defined(MAGICKCORE_XML_DELEGATE)
3832 entry->decoder=(DecodeImageHandler *) ReadSVGImage;
3833#endif
3834 entry->encoder=(EncodeImageHandler *) WriteSVGImage;
Dirk Lemstra046709a2017-08-26 22:53:00 +02003835#if defined(MAGICKCORE_RSVG_DELEGATE)
3836 entry->flags^=CoderDecoderThreadSupportFlag;
3837#endif
cristy3ed852e2009-09-05 21:47:34 +00003838 entry->magick=(IsImageFormatHandler *) IsSVG;
cristy3ed852e2009-09-05 21:47:34 +00003839 (void) RegisterMagickInfo(entry);
3840 return(MagickImageCoderSignature);
3841}
Cristy868ede62018-02-26 19:04:51 -05003842
cristy3ed852e2009-09-05 21:47:34 +00003843/*
3844%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3845% %
3846% %
3847% %
3848% U n r e g i s t e r S V G I m a g e %
3849% %
3850% %
3851% %
3852%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3853%
3854% UnregisterSVGImage() removes format registrations made by the
3855% SVG module from the list of supported formats.
3856%
3857% The format of the UnregisterSVGImage method is:
3858%
3859% UnregisterSVGImage(void)
3860%
3861*/
3862ModuleExport void UnregisterSVGImage(void)
3863{
3864 (void) UnregisterMagickInfo("SVGZ");
3865 (void) UnregisterMagickInfo("SVG");
Elliott Hughes5d41fba2021-04-12 16:36:42 -07003866#if defined(MAGICKCORE_RSVG_DELEGATE)
3867 (void) UnregisterMagickInfo("RSVG");
3868#endif
cristy3ed852e2009-09-05 21:47:34 +00003869 (void) UnregisterMagickInfo("MSVG");
cristy3ed852e2009-09-05 21:47:34 +00003870}
Cristy868ede62018-02-26 19:04:51 -05003871
cristy3ed852e2009-09-05 21:47:34 +00003872/*
3873%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3874% %
3875% %
3876% %
3877% W r i t e S V G I m a g e %
3878% %
3879% %
3880% %
3881%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3882%
3883% WriteSVGImage() writes a image in the SVG - XML based W3C standard
3884% format.
3885%
3886% The format of the WriteSVGImage method is:
3887%
cristy3a37efd2011-08-28 20:31:03 +00003888% MagickBooleanType WriteSVGImage(const ImageInfo *image_info,
3889% Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003890%
3891% A description of each parameter follows.
3892%
3893% o image_info: the image info.
3894%
3895% o image: The image.
3896%
cristy3a37efd2011-08-28 20:31:03 +00003897% o exception: return any errors or warnings in this structure.
3898%
cristy3ed852e2009-09-05 21:47:34 +00003899*/
3900
3901static void AffineToTransform(Image *image,AffineMatrix *affine)
3902{
3903 char
cristy151b66d2015-04-15 10:50:31 +00003904 transform[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00003905
3906 if ((fabs(affine->tx) < MagickEpsilon) && (fabs(affine->ty) < MagickEpsilon))
3907 {
3908 if ((fabs(affine->rx) < MagickEpsilon) &&
3909 (fabs(affine->ry) < MagickEpsilon))
3910 {
3911 if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
3912 (fabs(affine->sy-1.0) < MagickEpsilon))
3913 {
3914 (void) WriteBlobString(image,"\">\n");
3915 return;
3916 }
cristy151b66d2015-04-15 10:50:31 +00003917 (void) FormatLocaleString(transform,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00003918 "\" transform=\"scale(%g,%g)\">\n",affine->sx,affine->sy);
cristy3ed852e2009-09-05 21:47:34 +00003919 (void) WriteBlobString(image,transform);
3920 return;
3921 }
3922 else
3923 {
3924 if ((fabs(affine->sx-affine->sy) < MagickEpsilon) &&
3925 (fabs(affine->rx+affine->ry) < MagickEpsilon) &&
3926 (fabs(affine->sx*affine->sx+affine->rx*affine->rx-1.0) <
3927 2*MagickEpsilon))
3928 {
3929 double
3930 theta;
3931
3932 theta=(180.0/MagickPI)*atan2(affine->rx,affine->sx);
cristy151b66d2015-04-15 10:50:31 +00003933 (void) FormatLocaleString(transform,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00003934 "\" transform=\"rotate(%g)\">\n",theta);
cristy3ed852e2009-09-05 21:47:34 +00003935 (void) WriteBlobString(image,transform);
3936 return;
3937 }
3938 }
3939 }
3940 else
3941 {
3942 if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
3943 (fabs(affine->rx) < MagickEpsilon) &&
3944 (fabs(affine->ry) < MagickEpsilon) &&
3945 (fabs(affine->sy-1.0) < MagickEpsilon))
3946 {
cristy151b66d2015-04-15 10:50:31 +00003947 (void) FormatLocaleString(transform,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00003948 "\" transform=\"translate(%g,%g)\">\n",affine->tx,affine->ty);
cristy3ed852e2009-09-05 21:47:34 +00003949 (void) WriteBlobString(image,transform);
3950 return;
3951 }
3952 }
cristy151b66d2015-04-15 10:50:31 +00003953 (void) FormatLocaleString(transform,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00003954 "\" transform=\"matrix(%g %g %g %g %g %g)\">\n",
cristy8cd5b312010-01-07 01:10:24 +00003955 affine->sx,affine->rx,affine->ry,affine->sy,affine->tx,affine->ty);
cristy3ed852e2009-09-05 21:47:34 +00003956 (void) WriteBlobString(image,transform);
3957}
3958
3959static MagickBooleanType IsPoint(const char *point)
3960{
3961 char
3962 *p;
3963
cristybb503372010-05-27 20:51:26 +00003964 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003965 value;
3966
Cristyfedaa752019-02-03 07:23:20 -05003967 value=(ssize_t) strtol(point,&p,10);
cristyda16f162011-02-19 23:52:17 +00003968 (void) value;
cristy3ed852e2009-09-05 21:47:34 +00003969 return(p != point ? MagickTrue : MagickFalse);
3970}
3971
cristyc82a27b2011-10-21 01:07:16 +00003972static MagickBooleanType TraceSVGImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003973{
cristy3ed852e2009-09-05 21:47:34 +00003974#if defined(MAGICKCORE_AUTOTRACE_DELEGATE)
3975 {
Cristy51968542020-02-22 09:54:09 -05003976 at_bitmap
cristy3ed852e2009-09-05 21:47:34 +00003977 *trace;
3978
3979 at_fitting_opts_type
3980 *fitting_options;
3981
3982 at_output_opts_type
3983 *output_options;
3984
3985 at_splines_type
3986 *splines;
3987
3988 ImageType
3989 type;
3990
Cristyf2dc1dd2020-12-28 13:59:26 -05003991 const Quantum
cristya9b9e252014-06-10 00:18:20 +00003992 *p;
3993
Cristyf2dc1dd2020-12-28 13:59:26 -05003994 ssize_t
cristya9b9e252014-06-10 00:18:20 +00003995 i,
3996 x;
cristy3ed852e2009-09-05 21:47:34 +00003997
cristybb503372010-05-27 20:51:26 +00003998 size_t
cristy3ed852e2009-09-05 21:47:34 +00003999 number_planes;
4000
cristya9b9e252014-06-10 00:18:20 +00004001 ssize_t
4002 y;
4003
cristy3ed852e2009-09-05 21:47:34 +00004004 /*
4005 Trace image and write as SVG.
4006 */
4007 fitting_options=at_fitting_opts_new();
4008 output_options=at_output_opts_new();
dirkab4f0bb2015-07-25 11:46:32 +00004009 (void) SetImageGray(image,exception);
4010 type=GetImageType(image);
cristy3ed852e2009-09-05 21:47:34 +00004011 number_planes=3;
4012 if ((type == BilevelType) || (type == GrayscaleType))
4013 number_planes=1;
4014 trace=at_bitmap_new(image->columns,image->rows,number_planes);
4015 i=0;
cristybb503372010-05-27 20:51:26 +00004016 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004017 {
cristyad3b66f2011-10-31 02:07:10 +00004018 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
Cristy7ae396f2017-01-21 09:04:33 -05004019 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004020 break;
cristybb503372010-05-27 20:51:26 +00004021 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004022 {
cristy4c08aed2011-07-01 19:47:50 +00004023 trace->bitmap[i++]=GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004024 if (number_planes == 3)
4025 {
cristy4c08aed2011-07-01 19:47:50 +00004026 trace->bitmap[i++]=GetPixelGreen(image,p);
4027 trace->bitmap[i++]=GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004028 }
cristyed231572011-07-14 02:18:59 +00004029 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00004030 }
4031 }
4032 splines=at_splines_new_full(trace,fitting_options,NULL,NULL,NULL,NULL,NULL,
4033 NULL);
4034 at_splines_write(at_output_get_handler_by_suffix((char *) "svg"),
4035 GetBlobFileHandle(image),image->filename,output_options,splines,NULL,
4036 NULL);
4037 /*
4038 Free resources.
4039 */
4040 at_splines_free(splines);
4041 at_bitmap_free(trace);
4042 at_output_opts_free(output_options);
4043 at_fitting_opts_free(fitting_options);
4044 }
4045#else
4046 {
4047 char
cristya9b9e252014-06-10 00:18:20 +00004048 *base64,
Cristy9e113482019-08-21 09:19:28 -04004049 filename[MagickPathExtent],
cristy151b66d2015-04-15 10:50:31 +00004050 message[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00004051
Cristy55abbb42019-09-09 20:05:44 -04004052 const DelegateInfo
4053 *delegate_info;
4054
cristya9b9e252014-06-10 00:18:20 +00004055 Image
4056 *clone_image;
cristy3ed852e2009-09-05 21:47:34 +00004057
cristya9b9e252014-06-10 00:18:20 +00004058 ImageInfo
4059 *image_info;
4060
Cristy9e113482019-08-21 09:19:28 -04004061 MagickBooleanType
4062 status;
4063
Cristyf2dc1dd2020-12-28 13:59:26 -05004064 char
cristya9b9e252014-06-10 00:18:20 +00004065 *p;
4066
4067 size_t
4068 blob_length,
4069 encode_length;
4070
4071 ssize_t
4072 i;
4073
4074 unsigned char
4075 *blob;
4076
Cristy55abbb42019-09-09 20:05:44 -04004077 delegate_info=GetDelegateInfo((char *) NULL,"TRACE",exception);
4078 if (delegate_info != (DelegateInfo *) NULL)
4079 {
4080 /*
4081 Trace SVG with tracing delegate.
4082 */
4083 image_info=AcquireImageInfo();
4084 (void) CopyMagickString(image_info->magick,"TRACE",MagickPathExtent);
4085 (void) FormatLocaleString(filename,MagickPathExtent,"trace:%s",
4086 image_info->filename);
4087 (void) CopyMagickString(image_info->filename,filename,MagickPathExtent);
4088 status=WriteImage(image_info,image,exception);
4089 image_info=DestroyImageInfo(image_info);
4090 return(status);
4091 }
cristya9b9e252014-06-10 00:18:20 +00004092 (void) WriteBlobString(image,
4093 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
cristy3ed852e2009-09-05 21:47:34 +00004094 (void) WriteBlobString(image,
cristy374f65e2015-02-10 16:59:21 +00004095 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"");
cristy3ed852e2009-09-05 21:47:34 +00004096 (void) WriteBlobString(image,
cristy374f65e2015-02-10 16:59:21 +00004097 " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
cristy151b66d2015-04-15 10:50:31 +00004098 (void) FormatLocaleString(message,MagickPathExtent,
cristy374f65e2015-02-10 16:59:21 +00004099 "<svg version=\"1.1\" id=\"Layer_1\" "
4100 "xmlns=\"http://www.w3.org/2000/svg\" "
4101 "xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" "
4102 "width=\"%.20gpx\" height=\"%.20gpx\" viewBox=\"0 0 %.20g %.20g\" "
4103 "enable-background=\"new 0 0 %.20g %.20g\" xml:space=\"preserve\">",
4104 (double) image->columns,(double) image->rows,
4105 (double) image->columns,(double) image->rows,
4106 (double) image->columns,(double) image->rows);
cristy3ed852e2009-09-05 21:47:34 +00004107 (void) WriteBlobString(image,message);
cristya9b9e252014-06-10 00:18:20 +00004108 clone_image=CloneImage(image,0,0,MagickTrue,exception);
4109 if (clone_image == (Image *) NULL)
4110 return(MagickFalse);
4111 image_info=AcquireImageInfo();
cristy151b66d2015-04-15 10:50:31 +00004112 (void) CopyMagickString(image_info->magick,"PNG",MagickPathExtent);
cristya9b9e252014-06-10 00:18:20 +00004113 blob_length=2048;
4114 blob=(unsigned char *) ImageToBlob(image_info,clone_image,&blob_length,
4115 exception);
dirk1c5ca762015-01-24 13:10:18 +00004116 clone_image=DestroyImage(clone_image);
4117 image_info=DestroyImageInfo(image_info);
4118 if (blob == (unsigned char *) NULL)
4119 return(MagickFalse);
cristya9b9e252014-06-10 00:18:20 +00004120 encode_length=0;
4121 base64=Base64Encode(blob,blob_length,&encode_length);
4122 blob=(unsigned char *) RelinquishMagickMemory(blob);
cristy151b66d2015-04-15 10:50:31 +00004123 (void) FormatLocaleString(message,MagickPathExtent,
cristya9b9e252014-06-10 00:18:20 +00004124 " <image id=\"image%.20g\" width=\"%.20g\" height=\"%.20g\" "
Cristyd7e36592018-04-21 14:45:52 -04004125 "x=\"%.20g\" y=\"%.20g\"\n href=\"data:image/png;base64,",
cristya9b9e252014-06-10 00:18:20 +00004126 (double) image->scene,(double) image->columns,(double) image->rows,
4127 (double) image->page.x,(double) image->page.y);
4128 (void) WriteBlobString(image,message);
4129 p=base64;
4130 for (i=(ssize_t) encode_length; i > 0; i-=76)
cristy3ed852e2009-09-05 21:47:34 +00004131 {
cristy151b66d2015-04-15 10:50:31 +00004132 (void) FormatLocaleString(message,MagickPathExtent,"%.76s",p);
cristya9b9e252014-06-10 00:18:20 +00004133 (void) WriteBlobString(image,message);
4134 p+=76;
4135 if (i > 76)
4136 (void) WriteBlobString(image,"\n");
cristy3ed852e2009-09-05 21:47:34 +00004137 }
dirk1c5ca762015-01-24 13:10:18 +00004138 base64=DestroyString(base64);
cristya9b9e252014-06-10 00:18:20 +00004139 (void) WriteBlobString(image,"\" />\n");
cristy3ed852e2009-09-05 21:47:34 +00004140 (void) WriteBlobString(image,"</svg>\n");
4141 }
4142#endif
Cristyfedaa752019-02-03 07:23:20 -05004143 (void) CloseBlob(image);
cristy3ed852e2009-09-05 21:47:34 +00004144 return(MagickTrue);
4145}
4146
cristy3a37efd2011-08-28 20:31:03 +00004147static MagickBooleanType WriteSVGImage(const ImageInfo *image_info,Image *image,
4148 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004149{
4150#define BezierQuantum 200
4151
4152 AffineMatrix
4153 affine;
4154
4155 char
cristy151b66d2015-04-15 10:50:31 +00004156 keyword[MagickPathExtent],
4157 message[MagickPathExtent],
4158 name[MagickPathExtent],
Cristy3ed6c692016-05-23 19:24:23 -04004159 *next_token,
cristy3ed852e2009-09-05 21:47:34 +00004160 *token,
cristy151b66d2015-04-15 10:50:31 +00004161 type[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00004162
4163 const char
4164 *p,
4165 *q,
4166 *value;
4167
4168 int
4169 n;
4170
cristybb503372010-05-27 20:51:26 +00004171 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004172 j;
4173
4174 MagickBooleanType
4175 active,
4176 status;
4177
4178 PointInfo
4179 point;
4180
4181 PrimitiveInfo
4182 *primitive_info;
4183
4184 PrimitiveType
4185 primitive_type;
4186
Cristyf2dc1dd2020-12-28 13:59:26 -05004187 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004188 x;
4189
Cristyf2dc1dd2020-12-28 13:59:26 -05004190 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004191 i;
4192
4193 size_t
dirkfc0f1242016-03-26 00:36:39 +01004194 extent,
4195 length,
4196 number_points;
cristy3ed852e2009-09-05 21:47:34 +00004197
4198 SVGInfo
4199 svg_info;
4200
cristy3ed852e2009-09-05 21:47:34 +00004201 /*
4202 Open output image file.
4203 */
4204 assert(image_info != (const ImageInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00004205 assert(image_info->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00004206 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00004207 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00004208 if (image->debug != MagickFalse)
4209 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy3a37efd2011-08-28 20:31:03 +00004210 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00004211 assert(exception->signature == MagickCoreSignature);
cristy3a37efd2011-08-28 20:31:03 +00004212 status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
cristy3ed852e2009-09-05 21:47:34 +00004213 if (status == MagickFalse)
4214 return(status);
4215 value=GetImageArtifact(image,"SVG");
4216 if (value != (char *) NULL)
4217 {
4218 (void) WriteBlobString(image,value);
4219 (void) CloseBlob(image);
4220 return(MagickTrue);
4221 }
Cristyfdb31cf2019-02-12 21:26:35 -05004222 value=GetImageArtifact(image,"mvg:vector-graphics");
cristy3ed852e2009-09-05 21:47:34 +00004223 if (value == (char *) NULL)
cristyc82a27b2011-10-21 01:07:16 +00004224 return(TraceSVGImage(image,exception));
cristy3ed852e2009-09-05 21:47:34 +00004225 /*
4226 Write SVG header.
4227 */
4228 (void) WriteBlobString(image,"<?xml version=\"1.0\" standalone=\"no\"?>\n");
4229 (void) WriteBlobString(image,
4230 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n");
4231 (void) WriteBlobString(image,
4232 " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n");
cristy151b66d2015-04-15 10:50:31 +00004233 (void) FormatLocaleString(message,MagickPathExtent,
cristye8c25f92010-06-03 00:53:06 +00004234 "<svg width=\"%.20g\" height=\"%.20g\">\n",(double) image->columns,(double)
4235 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00004236 (void) WriteBlobString(image,message);
4237 /*
4238 Allocate primitive info memory.
4239 */
4240 number_points=2047;
4241 primitive_info=(PrimitiveInfo *) AcquireQuantumMemory(number_points,
4242 sizeof(*primitive_info));
4243 if (primitive_info == (PrimitiveInfo *) NULL)
4244 ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
4245 GetAffineMatrix(&affine);
4246 token=AcquireString(value);
dirkfc0f1242016-03-26 00:36:39 +01004247 extent=strlen(token)+MagickPathExtent;
cristy3ed852e2009-09-05 21:47:34 +00004248 active=MagickFalse;
4249 n=0;
4250 status=MagickTrue;
4251 for (q=(const char *) value; *q != '\0'; )
4252 {
4253 /*
4254 Interpret graphic primitive.
4255 */
Cristy448fd182019-07-27 16:26:38 -04004256 (void) GetNextToken(q,&q,MagickPathExtent,keyword);
cristy3ed852e2009-09-05 21:47:34 +00004257 if (*keyword == '\0')
4258 break;
4259 if (*keyword == '#')
4260 {
4261 /*
4262 Comment.
4263 */
4264 if (active != MagickFalse)
4265 {
4266 AffineToTransform(image,&affine);
4267 active=MagickFalse;
4268 }
4269 (void) WriteBlobString(image,"<desc>");
4270 (void) WriteBlobString(image,keyword+1);
4271 for ( ; (*q != '\n') && (*q != '\0'); q++)
4272 switch (*q)
4273 {
4274 case '<': (void) WriteBlobString(image,"&lt;"); break;
4275 case '>': (void) WriteBlobString(image,"&gt;"); break;
4276 case '&': (void) WriteBlobString(image,"&amp;"); break;
Cristyfedaa752019-02-03 07:23:20 -05004277 default: (void) WriteBlobByte(image,(unsigned char) *q); break;
cristy3ed852e2009-09-05 21:47:34 +00004278 }
4279 (void) WriteBlobString(image,"</desc>\n");
4280 continue;
4281 }
4282 primitive_type=UndefinedPrimitive;
4283 switch (*keyword)
4284 {
4285 case ';':
4286 break;
4287 case 'a':
4288 case 'A':
4289 {
4290 if (LocaleCompare("affine",keyword) == 0)
4291 {
Cristy448fd182019-07-27 16:26:38 -04004292 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004293 affine.sx=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004294 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004295 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004296 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004297 affine.rx=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004298 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004299 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004300 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004301 affine.ry=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004302 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004303 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004304 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004305 affine.sy=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004306 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004307 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004308 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004309 affine.tx=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004310 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004311 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004312 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004313 affine.ty=StringToDouble(token,&next_token);
cristy3ed852e2009-09-05 21:47:34 +00004314 break;
4315 }
dirk34e1a212015-03-22 16:45:12 +00004316 if (LocaleCompare("alpha",keyword) == 0)
4317 {
4318 primitive_type=AlphaPrimitive;
4319 break;
4320 }
cristy3ed852e2009-09-05 21:47:34 +00004321 if (LocaleCompare("angle",keyword) == 0)
4322 {
Cristy448fd182019-07-27 16:26:38 -04004323 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004324 affine.rx=StringToDouble(token,&next_token);
4325 affine.ry=StringToDouble(token,&next_token);
cristy3ed852e2009-09-05 21:47:34 +00004326 break;
4327 }
4328 if (LocaleCompare("arc",keyword) == 0)
4329 {
4330 primitive_type=ArcPrimitive;
4331 break;
4332 }
4333 status=MagickFalse;
4334 break;
4335 }
4336 case 'b':
4337 case 'B':
4338 {
4339 if (LocaleCompare("bezier",keyword) == 0)
4340 {
4341 primitive_type=BezierPrimitive;
4342 break;
4343 }
4344 status=MagickFalse;
4345 break;
4346 }
4347 case 'c':
4348 case 'C':
4349 {
4350 if (LocaleCompare("clip-path",keyword) == 0)
4351 {
Cristy448fd182019-07-27 16:26:38 -04004352 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004353 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004354 "clip-path:url(#%s);",token);
4355 (void) WriteBlobString(image,message);
4356 break;
4357 }
4358 if (LocaleCompare("clip-rule",keyword) == 0)
4359 {
Cristy448fd182019-07-27 16:26:38 -04004360 (void) GetNextToken(q,&q,extent,token);
Cristyd51e6172018-04-28 09:01:01 -04004361 (void) FormatLocaleString(message,MagickPathExtent,"clip-rule:%s;",
4362 token);
cristy3ed852e2009-09-05 21:47:34 +00004363 (void) WriteBlobString(image,message);
4364 break;
4365 }
4366 if (LocaleCompare("clip-units",keyword) == 0)
4367 {
Cristy448fd182019-07-27 16:26:38 -04004368 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004369 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004370 "clipPathUnits=%s;",token);
4371 (void) WriteBlobString(image,message);
4372 break;
4373 }
4374 if (LocaleCompare("circle",keyword) == 0)
4375 {
4376 primitive_type=CirclePrimitive;
4377 break;
4378 }
4379 if (LocaleCompare("color",keyword) == 0)
4380 {
4381 primitive_type=ColorPrimitive;
4382 break;
4383 }
Cristyd002f5a2020-03-12 09:33:20 -04004384 if (LocaleCompare("compliance",keyword) == 0)
4385 {
4386 (void) GetNextToken(q,&q,extent,token);
4387 break;
4388 }
cristy3ed852e2009-09-05 21:47:34 +00004389 status=MagickFalse;
4390 break;
4391 }
4392 case 'd':
4393 case 'D':
4394 {
4395 if (LocaleCompare("decorate",keyword) == 0)
4396 {
Cristy448fd182019-07-27 16:26:38 -04004397 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004398 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004399 "text-decoration:%s;",token);
4400 (void) WriteBlobString(image,message);
4401 break;
4402 }
4403 status=MagickFalse;
4404 break;
4405 }
4406 case 'e':
4407 case 'E':
4408 {
4409 if (LocaleCompare("ellipse",keyword) == 0)
4410 {
4411 primitive_type=EllipsePrimitive;
4412 break;
4413 }
4414 status=MagickFalse;
4415 break;
4416 }
4417 case 'f':
4418 case 'F':
4419 {
4420 if (LocaleCompare("fill",keyword) == 0)
4421 {
Cristy448fd182019-07-27 16:26:38 -04004422 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004423 (void) FormatLocaleString(message,MagickPathExtent,"fill:%s;",
cristy3ed852e2009-09-05 21:47:34 +00004424 token);
4425 (void) WriteBlobString(image,message);
4426 break;
4427 }
4428 if (LocaleCompare("fill-rule",keyword) == 0)
4429 {
Cristy448fd182019-07-27 16:26:38 -04004430 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004431 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004432 "fill-rule:%s;",token);
4433 (void) WriteBlobString(image,message);
4434 break;
4435 }
Cristyb5298432016-09-19 20:07:29 -04004436 if (LocaleCompare("fill-opacity",keyword) == 0)
cristy3ed852e2009-09-05 21:47:34 +00004437 {
Cristy448fd182019-07-27 16:26:38 -04004438 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004439 (void) FormatLocaleString(message,MagickPathExtent,
Cristyb5298432016-09-19 20:07:29 -04004440 "fill-opacity:%s;",token);
cristy3ed852e2009-09-05 21:47:34 +00004441 (void) WriteBlobString(image,message);
4442 break;
4443 }
4444 if (LocaleCompare("font-family",keyword) == 0)
4445 {
Cristy448fd182019-07-27 16:26:38 -04004446 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004447 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004448 "font-family:%s;",token);
4449 (void) WriteBlobString(image,message);
4450 break;
4451 }
4452 if (LocaleCompare("font-stretch",keyword) == 0)
4453 {
Cristy448fd182019-07-27 16:26:38 -04004454 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004455 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004456 "font-stretch:%s;",token);
4457 (void) WriteBlobString(image,message);
4458 break;
4459 }
4460 if (LocaleCompare("font-style",keyword) == 0)
4461 {
Cristy448fd182019-07-27 16:26:38 -04004462 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004463 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004464 "font-style:%s;",token);
4465 (void) WriteBlobString(image,message);
4466 break;
4467 }
4468 if (LocaleCompare("font-size",keyword) == 0)
4469 {
Cristy448fd182019-07-27 16:26:38 -04004470 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004471 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004472 "font-size:%s;",token);
4473 (void) WriteBlobString(image,message);
4474 break;
4475 }
4476 if (LocaleCompare("font-weight",keyword) == 0)
4477 {
Cristy448fd182019-07-27 16:26:38 -04004478 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004479 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004480 "font-weight:%s;",token);
4481 (void) WriteBlobString(image,message);
4482 break;
4483 }
4484 status=MagickFalse;
4485 break;
4486 }
4487 case 'g':
4488 case 'G':
4489 {
4490 if (LocaleCompare("gradient-units",keyword) == 0)
4491 {
Cristy448fd182019-07-27 16:26:38 -04004492 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004493 break;
4494 }
4495 if (LocaleCompare("text-align",keyword) == 0)
4496 {
Cristy448fd182019-07-27 16:26:38 -04004497 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004498 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004499 "text-align %s ",token);
4500 (void) WriteBlobString(image,message);
4501 break;
4502 }
4503 if (LocaleCompare("text-anchor",keyword) == 0)
4504 {
Cristy448fd182019-07-27 16:26:38 -04004505 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004506 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004507 "text-anchor %s ",token);
4508 (void) WriteBlobString(image,message);
4509 break;
4510 }
4511 status=MagickFalse;
4512 break;
4513 }
4514 case 'i':
4515 case 'I':
4516 {
4517 if (LocaleCompare("image",keyword) == 0)
4518 {
Cristy448fd182019-07-27 16:26:38 -04004519 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004520 primitive_type=ImagePrimitive;
4521 break;
4522 }
4523 status=MagickFalse;
4524 break;
4525 }
Cristy86d77652018-08-04 18:43:58 -04004526 case 'k':
4527 case 'K':
4528 {
4529 if (LocaleCompare("kerning",keyword) == 0)
4530 {
Cristy448fd182019-07-27 16:26:38 -04004531 (void) GetNextToken(q,&q,extent,token);
Cristy86d77652018-08-04 18:43:58 -04004532 (void) FormatLocaleString(message,MagickPathExtent,"kerning:%s;",
4533 token);
4534 (void) WriteBlobString(image,message);
4535 }
4536 break;
4537 }
cristy3ed852e2009-09-05 21:47:34 +00004538 case 'l':
4539 case 'L':
4540 {
Cristy86d77652018-08-04 18:43:58 -04004541 if (LocaleCompare("letter-spacing",keyword) == 0)
4542 {
Cristy448fd182019-07-27 16:26:38 -04004543 (void) GetNextToken(q,&q,extent,token);
Cristy86d77652018-08-04 18:43:58 -04004544 (void) FormatLocaleString(message,MagickPathExtent,
4545 "letter-spacing:%s;",token);
4546 (void) WriteBlobString(image,message);
4547 break;
4548 }
cristy3ed852e2009-09-05 21:47:34 +00004549 if (LocaleCompare("line",keyword) == 0)
4550 {
4551 primitive_type=LinePrimitive;
4552 break;
4553 }
4554 status=MagickFalse;
4555 break;
4556 }
cristy3ed852e2009-09-05 21:47:34 +00004557 case 'o':
4558 case 'O':
4559 {
4560 if (LocaleCompare("opacity",keyword) == 0)
4561 {
Cristy448fd182019-07-27 16:26:38 -04004562 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004563 (void) FormatLocaleString(message,MagickPathExtent,"opacity %s ",
cristy3ed852e2009-09-05 21:47:34 +00004564 token);
4565 (void) WriteBlobString(image,message);
4566 break;
4567 }
4568 status=MagickFalse;
4569 break;
4570 }
4571 case 'p':
4572 case 'P':
4573 {
4574 if (LocaleCompare("path",keyword) == 0)
4575 {
4576 primitive_type=PathPrimitive;
4577 break;
4578 }
4579 if (LocaleCompare("point",keyword) == 0)
4580 {
4581 primitive_type=PointPrimitive;
4582 break;
4583 }
4584 if (LocaleCompare("polyline",keyword) == 0)
4585 {
4586 primitive_type=PolylinePrimitive;
4587 break;
4588 }
4589 if (LocaleCompare("polygon",keyword) == 0)
4590 {
4591 primitive_type=PolygonPrimitive;
4592 break;
4593 }
4594 if (LocaleCompare("pop",keyword) == 0)
4595 {
Cristy448fd182019-07-27 16:26:38 -04004596 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004597 if (LocaleCompare("clip-path",token) == 0)
4598 {
4599 (void) WriteBlobString(image,"</clipPath>\n");
4600 break;
4601 }
4602 if (LocaleCompare("defs",token) == 0)
4603 {
4604 (void) WriteBlobString(image,"</defs>\n");
4605 break;
4606 }
4607 if (LocaleCompare("gradient",token) == 0)
4608 {
cristy151b66d2015-04-15 10:50:31 +00004609 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004610 "</%sGradient>\n",type);
4611 (void) WriteBlobString(image,message);
4612 break;
4613 }
4614 if (LocaleCompare("graphic-context",token) == 0)
4615 {
4616 n--;
4617 if (n < 0)
4618 ThrowWriterException(DrawError,
4619 "UnbalancedGraphicContextPushPop");
4620 (void) WriteBlobString(image,"</g>\n");
4621 }
4622 if (LocaleCompare("pattern",token) == 0)
4623 {
4624 (void) WriteBlobString(image,"</pattern>\n");
4625 break;
4626 }
Cristy2a0763f2018-04-22 08:16:54 -04004627 if (LocaleCompare("symbol",token) == 0)
4628 {
4629 (void) WriteBlobString(image,"</symbol>\n");
4630 break;
4631 }
4632 if ((LocaleCompare("defs",token) == 0) ||
4633 (LocaleCompare("symbol",token) == 0))
4634 (void) WriteBlobString(image,"</g>\n");
cristy3ed852e2009-09-05 21:47:34 +00004635 break;
4636 }
4637 if (LocaleCompare("push",keyword) == 0)
4638 {
Cristyd002f5a2020-03-12 09:33:20 -04004639 *name='\0';
Cristy448fd182019-07-27 16:26:38 -04004640 (void) GetNextToken(q,&q,extent,token);
Cristyd002f5a2020-03-12 09:33:20 -04004641 if (*q == '"')
4642 (void) GetNextToken(q,&q,extent,name);
cristy3ed852e2009-09-05 21:47:34 +00004643 if (LocaleCompare("clip-path",token) == 0)
4644 {
Cristy448fd182019-07-27 16:26:38 -04004645 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004646 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004647 "<clipPath id=\"%s\">\n",token);
4648 (void) WriteBlobString(image,message);
4649 break;
4650 }
4651 if (LocaleCompare("defs",token) == 0)
4652 {
4653 (void) WriteBlobString(image,"<defs>\n");
4654 break;
4655 }
4656 if (LocaleCompare("gradient",token) == 0)
4657 {
Cristy448fd182019-07-27 16:26:38 -04004658 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004659 (void) CopyMagickString(name,token,MagickPathExtent);
Cristy448fd182019-07-27 16:26:38 -04004660 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004661 (void) CopyMagickString(type,token,MagickPathExtent);
Cristy448fd182019-07-27 16:26:38 -04004662 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004663 svg_info.segment.x1=StringToDouble(token,&next_token);
4664 svg_info.element.cx=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004665 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004666 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004667 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004668 svg_info.segment.y1=StringToDouble(token,&next_token);
4669 svg_info.element.cy=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004670 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004671 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004672 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004673 svg_info.segment.x2=StringToDouble(token,&next_token);
cristydbdd0e32011-11-04 23:29:40 +00004674 svg_info.element.major=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +00004675 (char **) NULL);
Cristy448fd182019-07-27 16:26:38 -04004676 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004677 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004678 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004679 svg_info.segment.y2=StringToDouble(token,&next_token);
cristydbdd0e32011-11-04 23:29:40 +00004680 svg_info.element.minor=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +00004681 (char **) NULL);
cristy151b66d2015-04-15 10:50:31 +00004682 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00004683 "<%sGradient id=\"%s\" x1=\"%g\" y1=\"%g\" x2=\"%g\" "
4684 "y2=\"%g\">\n",type,name,svg_info.segment.x1,
cristy3ed852e2009-09-05 21:47:34 +00004685 svg_info.segment.y1,svg_info.segment.x2,svg_info.segment.y2);
4686 if (LocaleCompare(type,"radial") == 0)
4687 {
Cristy448fd182019-07-27 16:26:38 -04004688 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004689 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004690 (void) GetNextToken(q,&q,extent,token);
cristydbdd0e32011-11-04 23:29:40 +00004691 svg_info.element.angle=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +00004692 (char **) NULL);
cristy151b66d2015-04-15 10:50:31 +00004693 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00004694 "<%sGradient id=\"%s\" cx=\"%g\" cy=\"%g\" r=\"%g\" "
4695 "fx=\"%g\" fy=\"%g\">\n",type,name,
cristy8cd5b312010-01-07 01:10:24 +00004696 svg_info.element.cx,svg_info.element.cy,
4697 svg_info.element.angle,svg_info.element.major,
4698 svg_info.element.minor);
cristy3ed852e2009-09-05 21:47:34 +00004699 }
4700 (void) WriteBlobString(image,message);
4701 break;
4702 }
4703 if (LocaleCompare("graphic-context",token) == 0)
4704 {
4705 n++;
4706 if (active)
4707 {
4708 AffineToTransform(image,&affine);
4709 active=MagickFalse;
4710 }
4711 (void) WriteBlobString(image,"<g style=\"");
4712 active=MagickTrue;
4713 }
4714 if (LocaleCompare("pattern",token) == 0)
4715 {
Cristy448fd182019-07-27 16:26:38 -04004716 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004717 (void) CopyMagickString(name,token,MagickPathExtent);
Cristy448fd182019-07-27 16:26:38 -04004718 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004719 svg_info.bounds.x=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004720 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004721 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004722 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004723 svg_info.bounds.y=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004724 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004725 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004726 (void) GetNextToken(q,&q,extent,token);
cristydbdd0e32011-11-04 23:29:40 +00004727 svg_info.bounds.width=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +00004728 (char **) NULL);
Cristy448fd182019-07-27 16:26:38 -04004729 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004730 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004731 (void) GetNextToken(q,&q,extent,token);
Cristyd00bc462016-05-26 19:22:59 -04004732 svg_info.bounds.height=StringToDouble(token,(char **) NULL);
cristy151b66d2015-04-15 10:50:31 +00004733 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00004734 "<pattern id=\"%s\" x=\"%g\" y=\"%g\" width=\"%g\" "
Cristyd00bc462016-05-26 19:22:59 -04004735 "height=\"%g\">\n",name,svg_info.bounds.x,svg_info.bounds.y,
4736 svg_info.bounds.width,svg_info.bounds.height);
cristy3ed852e2009-09-05 21:47:34 +00004737 (void) WriteBlobString(image,message);
4738 break;
4739 }
Cristy2a0763f2018-04-22 08:16:54 -04004740 if (LocaleCompare("symbol",token) == 0)
4741 {
4742 (void) WriteBlobString(image,"<symbol>\n");
4743 break;
4744 }
cristy3ed852e2009-09-05 21:47:34 +00004745 break;
4746 }
4747 status=MagickFalse;
4748 break;
4749 }
4750 case 'r':
4751 case 'R':
4752 {
4753 if (LocaleCompare("rectangle",keyword) == 0)
4754 {
4755 primitive_type=RectanglePrimitive;
4756 break;
4757 }
4758 if (LocaleCompare("roundRectangle",keyword) == 0)
4759 {
4760 primitive_type=RoundRectanglePrimitive;
4761 break;
4762 }
4763 if (LocaleCompare("rotate",keyword) == 0)
4764 {
Cristy448fd182019-07-27 16:26:38 -04004765 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004766 (void) FormatLocaleString(message,MagickPathExtent,"rotate(%s) ",
cristy3ed852e2009-09-05 21:47:34 +00004767 token);
4768 (void) WriteBlobString(image,message);
4769 break;
4770 }
4771 status=MagickFalse;
4772 break;
4773 }
4774 case 's':
4775 case 'S':
4776 {
4777 if (LocaleCompare("scale",keyword) == 0)
4778 {
Cristy448fd182019-07-27 16:26:38 -04004779 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004780 affine.sx=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004781 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004782 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004783 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004784 affine.sy=StringToDouble(token,&next_token);
cristy3ed852e2009-09-05 21:47:34 +00004785 break;
4786 }
4787 if (LocaleCompare("skewX",keyword) == 0)
4788 {
Cristy448fd182019-07-27 16:26:38 -04004789 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004790 (void) FormatLocaleString(message,MagickPathExtent,"skewX(%s) ",
cristy3ed852e2009-09-05 21:47:34 +00004791 token);
4792 (void) WriteBlobString(image,message);
4793 break;
4794 }
4795 if (LocaleCompare("skewY",keyword) == 0)
4796 {
Cristy448fd182019-07-27 16:26:38 -04004797 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004798 (void) FormatLocaleString(message,MagickPathExtent,"skewY(%s) ",
cristy3ed852e2009-09-05 21:47:34 +00004799 token);
4800 (void) WriteBlobString(image,message);
4801 break;
4802 }
4803 if (LocaleCompare("stop-color",keyword) == 0)
4804 {
4805 char
cristy151b66d2015-04-15 10:50:31 +00004806 color[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00004807
Cristy448fd182019-07-27 16:26:38 -04004808 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004809 (void) CopyMagickString(color,token,MagickPathExtent);
Cristy448fd182019-07-27 16:26:38 -04004810 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004811 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004812 " <stop offset=\"%s\" stop-color=\"%s\" />\n",token,color);
4813 (void) WriteBlobString(image,message);
4814 break;
4815 }
4816 if (LocaleCompare("stroke",keyword) == 0)
4817 {
Cristy448fd182019-07-27 16:26:38 -04004818 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004819 (void) FormatLocaleString(message,MagickPathExtent,"stroke:%s;",
cristy3ed852e2009-09-05 21:47:34 +00004820 token);
4821 (void) WriteBlobString(image,message);
4822 break;
4823 }
4824 if (LocaleCompare("stroke-antialias",keyword) == 0)
4825 {
Cristy448fd182019-07-27 16:26:38 -04004826 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004827 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004828 "stroke-antialias:%s;",token);
4829 (void) WriteBlobString(image,message);
4830 break;
4831 }
4832 if (LocaleCompare("stroke-dasharray",keyword) == 0)
4833 {
4834 if (IsPoint(q))
4835 {
cristybb503372010-05-27 20:51:26 +00004836 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00004837 k;
4838
4839 p=q;
Cristy448fd182019-07-27 16:26:38 -04004840 (void) GetNextToken(p,&p,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004841 for (k=0; IsPoint(token); k++)
Cristy448fd182019-07-27 16:26:38 -04004842 (void) GetNextToken(p,&p,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004843 (void) WriteBlobString(image,"stroke-dasharray:");
4844 for (j=0; j < k; j++)
4845 {
Cristy448fd182019-07-27 16:26:38 -04004846 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004847 (void) FormatLocaleString(message,MagickPathExtent,"%s ",
cristy3ed852e2009-09-05 21:47:34 +00004848 token);
4849 (void) WriteBlobString(image,message);
4850 }
4851 (void) WriteBlobString(image,";");
4852 break;
4853 }
Cristy448fd182019-07-27 16:26:38 -04004854 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004855 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004856 "stroke-dasharray:%s;",token);
4857 (void) WriteBlobString(image,message);
4858 break;
4859 }
4860 if (LocaleCompare("stroke-dashoffset",keyword) == 0)
4861 {
Cristy448fd182019-07-27 16:26:38 -04004862 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004863 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004864 "stroke-dashoffset:%s;",token);
4865 (void) WriteBlobString(image,message);
4866 break;
4867 }
4868 if (LocaleCompare("stroke-linecap",keyword) == 0)
4869 {
Cristy448fd182019-07-27 16:26:38 -04004870 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004871 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004872 "stroke-linecap:%s;",token);
4873 (void) WriteBlobString(image,message);
4874 break;
4875 }
4876 if (LocaleCompare("stroke-linejoin",keyword) == 0)
4877 {
Cristy448fd182019-07-27 16:26:38 -04004878 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004879 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004880 "stroke-linejoin:%s;",token);
4881 (void) WriteBlobString(image,message);
4882 break;
4883 }
4884 if (LocaleCompare("stroke-miterlimit",keyword) == 0)
4885 {
Cristy448fd182019-07-27 16:26:38 -04004886 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004887 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004888 "stroke-miterlimit:%s;",token);
4889 (void) WriteBlobString(image,message);
4890 break;
4891 }
4892 if (LocaleCompare("stroke-opacity",keyword) == 0)
4893 {
Cristy448fd182019-07-27 16:26:38 -04004894 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004895 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004896 "stroke-opacity:%s;",token);
4897 (void) WriteBlobString(image,message);
4898 break;
4899 }
4900 if (LocaleCompare("stroke-width",keyword) == 0)
4901 {
Cristy448fd182019-07-27 16:26:38 -04004902 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004903 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004904 "stroke-width:%s;",token);
4905 (void) WriteBlobString(image,message);
4906 continue;
4907 }
4908 status=MagickFalse;
4909 break;
4910 }
4911 case 't':
4912 case 'T':
4913 {
4914 if (LocaleCompare("text",keyword) == 0)
4915 {
4916 primitive_type=TextPrimitive;
4917 break;
4918 }
4919 if (LocaleCompare("text-antialias",keyword) == 0)
4920 {
Cristy448fd182019-07-27 16:26:38 -04004921 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00004922 (void) FormatLocaleString(message,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00004923 "text-antialias:%s;",token);
4924 (void) WriteBlobString(image,message);
4925 break;
4926 }
4927 if (LocaleCompare("tspan",keyword) == 0)
4928 {
4929 primitive_type=TextPrimitive;
4930 break;
4931 }
4932 if (LocaleCompare("translate",keyword) == 0)
4933 {
Cristy448fd182019-07-27 16:26:38 -04004934 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004935 affine.tx=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004936 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004937 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004938 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004939 affine.ty=StringToDouble(token,&next_token);
cristy3ed852e2009-09-05 21:47:34 +00004940 break;
4941 }
4942 status=MagickFalse;
4943 break;
4944 }
4945 case 'v':
4946 case 'V':
4947 {
4948 if (LocaleCompare("viewbox",keyword) == 0)
4949 {
Cristy448fd182019-07-27 16:26:38 -04004950 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004951 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004952 (void) GetNextToken(q,&q,extent,token);
4953 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004954 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004955 (void) GetNextToken(q,&q,extent,token);
4956 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004957 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004958 (void) GetNextToken(q,&q,extent,token);
4959 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004960 break;
4961 }
4962 status=MagickFalse;
4963 break;
4964 }
4965 default:
4966 {
4967 status=MagickFalse;
4968 break;
4969 }
4970 }
4971 if (status == MagickFalse)
4972 break;
4973 if (primitive_type == UndefinedPrimitive)
4974 continue;
4975 /*
4976 Parse the primitive attributes.
4977 */
4978 i=0;
4979 j=0;
4980 for (x=0; *q != '\0'; x++)
4981 {
4982 /*
4983 Define points.
4984 */
4985 if (IsPoint(q) == MagickFalse)
4986 break;
Cristy448fd182019-07-27 16:26:38 -04004987 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004988 point.x=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004989 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004990 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004991 (void) GetNextToken(q,&q,extent,token);
Cristy3ed6c692016-05-23 19:24:23 -04004992 point.y=StringToDouble(token,&next_token);
Cristy448fd182019-07-27 16:26:38 -04004993 (void) GetNextToken(q,(const char **) NULL,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004994 if (*token == ',')
Cristy448fd182019-07-27 16:26:38 -04004995 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00004996 primitive_info[i].primitive=primitive_type;
4997 primitive_info[i].point=point;
4998 primitive_info[i].coordinates=0;
4999 primitive_info[i].method=FloodfillMethod;
5000 i++;
cristybb503372010-05-27 20:51:26 +00005001 if (i < (ssize_t) (number_points-6*BezierQuantum-360))
cristy3ed852e2009-09-05 21:47:34 +00005002 continue;
5003 number_points+=6*BezierQuantum+360;
5004 primitive_info=(PrimitiveInfo *) ResizeQuantumMemory(primitive_info,
5005 number_points,sizeof(*primitive_info));
5006 if (primitive_info == (PrimitiveInfo *) NULL)
5007 {
cristy3a37efd2011-08-28 20:31:03 +00005008 (void) ThrowMagickException(exception,GetMagickModule(),
cristy3ed852e2009-09-05 21:47:34 +00005009 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
5010 break;
5011 }
5012 }
5013 primitive_info[j].primitive=primitive_type;
Cristyfedaa752019-02-03 07:23:20 -05005014 primitive_info[j].coordinates=(size_t) x;
cristy3ed852e2009-09-05 21:47:34 +00005015 primitive_info[j].method=FloodfillMethod;
5016 primitive_info[j].text=(char *) NULL;
5017 if (active)
5018 {
5019 AffineToTransform(image,&affine);
5020 active=MagickFalse;
5021 }
5022 active=MagickFalse;
5023 switch (primitive_type)
5024 {
5025 case PointPrimitive:
5026 default:
5027 {
5028 if (primitive_info[j].coordinates != 1)
5029 {
5030 status=MagickFalse;
5031 break;
5032 }
5033 break;
5034 }
5035 case LinePrimitive:
5036 {
5037 if (primitive_info[j].coordinates != 2)
5038 {
5039 status=MagickFalse;
5040 break;
5041 }
cristy151b66d2015-04-15 10:50:31 +00005042 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00005043 " <line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"/>\n",
cristy3ed852e2009-09-05 21:47:34 +00005044 primitive_info[j].point.x,primitive_info[j].point.y,
5045 primitive_info[j+1].point.x,primitive_info[j+1].point.y);
5046 (void) WriteBlobString(image,message);
5047 break;
5048 }
5049 case RectanglePrimitive:
5050 {
5051 if (primitive_info[j].coordinates != 2)
5052 {
5053 status=MagickFalse;
5054 break;
5055 }
cristy151b66d2015-04-15 10:50:31 +00005056 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00005057 " <rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"/>\n",
cristy3ed852e2009-09-05 21:47:34 +00005058 primitive_info[j].point.x,primitive_info[j].point.y,
5059 primitive_info[j+1].point.x-primitive_info[j].point.x,
5060 primitive_info[j+1].point.y-primitive_info[j].point.y);
5061 (void) WriteBlobString(image,message);
5062 break;
5063 }
5064 case RoundRectanglePrimitive:
5065 {
5066 if (primitive_info[j].coordinates != 3)
5067 {
5068 status=MagickFalse;
5069 break;
5070 }
cristy151b66d2015-04-15 10:50:31 +00005071 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00005072 " <rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" rx=\"%g\" "
5073 "ry=\"%g\"/>\n",primitive_info[j].point.x,
cristy8cd5b312010-01-07 01:10:24 +00005074 primitive_info[j].point.y,primitive_info[j+1].point.x-
5075 primitive_info[j].point.x,primitive_info[j+1].point.y-
5076 primitive_info[j].point.y,primitive_info[j+2].point.x,
5077 primitive_info[j+2].point.y);
cristy3ed852e2009-09-05 21:47:34 +00005078 (void) WriteBlobString(image,message);
5079 break;
5080 }
5081 case ArcPrimitive:
5082 {
5083 if (primitive_info[j].coordinates != 3)
5084 {
5085 status=MagickFalse;
5086 break;
5087 }
5088 break;
5089 }
5090 case EllipsePrimitive:
5091 {
5092 if (primitive_info[j].coordinates != 3)
5093 {
5094 status=MagickFalse;
5095 break;
5096 }
cristy151b66d2015-04-15 10:50:31 +00005097 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00005098 " <ellipse cx=\"%g\" cy=\"%g\" rx=\"%g\" ry=\"%g\"/>\n",
cristy3ed852e2009-09-05 21:47:34 +00005099 primitive_info[j].point.x,primitive_info[j].point.y,
5100 primitive_info[j+1].point.x,primitive_info[j+1].point.y);
5101 (void) WriteBlobString(image,message);
5102 break;
5103 }
5104 case CirclePrimitive:
5105 {
5106 double
5107 alpha,
5108 beta;
5109
5110 if (primitive_info[j].coordinates != 2)
5111 {
5112 status=MagickFalse;
5113 break;
5114 }
5115 alpha=primitive_info[j+1].point.x-primitive_info[j].point.x;
5116 beta=primitive_info[j+1].point.y-primitive_info[j].point.y;
cristy151b66d2015-04-15 10:50:31 +00005117 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00005118 " <circle cx=\"%g\" cy=\"%g\" r=\"%g\"/>\n",
cristy3ed852e2009-09-05 21:47:34 +00005119 primitive_info[j].point.x,primitive_info[j].point.y,
5120 hypot(alpha,beta));
5121 (void) WriteBlobString(image,message);
5122 break;
5123 }
5124 case PolylinePrimitive:
5125 {
5126 if (primitive_info[j].coordinates < 2)
5127 {
5128 status=MagickFalse;
5129 break;
5130 }
Cristyd00bc462016-05-26 19:22:59 -04005131 (void) CopyMagickString(message," <polyline points=\"",
5132 MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00005133 (void) WriteBlobString(image,message);
5134 length=strlen(message);
5135 for ( ; j < i; j++)
5136 {
cristy151b66d2015-04-15 10:50:31 +00005137 (void) FormatLocaleString(message,MagickPathExtent,"%g,%g ",
cristy3ed852e2009-09-05 21:47:34 +00005138 primitive_info[j].point.x,primitive_info[j].point.y);
5139 length+=strlen(message);
5140 if (length >= 80)
5141 {
5142 (void) WriteBlobString(image,"\n ");
5143 length=strlen(message)+5;
5144 }
5145 (void) WriteBlobString(image,message);
5146 }
5147 (void) WriteBlobString(image,"\"/>\n");
5148 break;
5149 }
5150 case PolygonPrimitive:
5151 {
5152 if (primitive_info[j].coordinates < 3)
5153 {
5154 status=MagickFalse;
5155 break;
5156 }
5157 primitive_info[i]=primitive_info[j];
5158 primitive_info[i].coordinates=0;
5159 primitive_info[j].coordinates++;
5160 i++;
Cristyc13321f2019-05-26 19:05:02 -04005161 (void) CopyMagickString(message," <polygon points=\"",
5162 MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00005163 (void) WriteBlobString(image,message);
5164 length=strlen(message);
5165 for ( ; j < i; j++)
5166 {
cristy151b66d2015-04-15 10:50:31 +00005167 (void) FormatLocaleString(message,MagickPathExtent,"%g,%g ",
cristy3ed852e2009-09-05 21:47:34 +00005168 primitive_info[j].point.x,primitive_info[j].point.y);
5169 length+=strlen(message);
5170 if (length >= 80)
5171 {
5172 (void) WriteBlobString(image,"\n ");
5173 length=strlen(message)+5;
5174 }
5175 (void) WriteBlobString(image,message);
5176 }
5177 (void) WriteBlobString(image,"\"/>\n");
5178 break;
5179 }
5180 case BezierPrimitive:
5181 {
5182 if (primitive_info[j].coordinates < 3)
5183 {
5184 status=MagickFalse;
5185 break;
5186 }
5187 break;
5188 }
5189 case PathPrimitive:
5190 {
5191 int
5192 number_attributes;
5193
Cristy448fd182019-07-27 16:26:38 -04005194 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00005195 number_attributes=1;
5196 for (p=token; *p != '\0'; p++)
Cristyb7769d82019-07-27 21:55:06 -04005197 if (isalpha((int) ((unsigned char) *p)) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005198 number_attributes++;
cristybb503372010-05-27 20:51:26 +00005199 if (i > (ssize_t) (number_points-6*BezierQuantum*number_attributes-1))
cristy3ed852e2009-09-05 21:47:34 +00005200 {
5201 number_points+=6*BezierQuantum*number_attributes;
5202 primitive_info=(PrimitiveInfo *) ResizeQuantumMemory(primitive_info,
5203 number_points,sizeof(*primitive_info));
5204 if (primitive_info == (PrimitiveInfo *) NULL)
5205 {
cristy3a37efd2011-08-28 20:31:03 +00005206 (void) ThrowMagickException(exception,GetMagickModule(),
cristy3ed852e2009-09-05 21:47:34 +00005207 ResourceLimitError,"MemoryAllocationFailed","`%s'",
5208 image->filename);
5209 break;
5210 }
5211 }
5212 (void) WriteBlobString(image," <path d=\"");
5213 (void) WriteBlobString(image,token);
5214 (void) WriteBlobString(image,"\"/>\n");
5215 break;
5216 }
dirk34e1a212015-03-22 16:45:12 +00005217 case AlphaPrimitive:
cristy3ed852e2009-09-05 21:47:34 +00005218 case ColorPrimitive:
cristy3ed852e2009-09-05 21:47:34 +00005219 {
5220 if (primitive_info[j].coordinates != 1)
5221 {
5222 status=MagickFalse;
5223 break;
5224 }
Cristy448fd182019-07-27 16:26:38 -04005225 (void) GetNextToken(q,&q,extent,token);
cristy3ed852e2009-09-05 21:47:34 +00005226 if (LocaleCompare("point",token) == 0)
5227 primitive_info[j].method=PointMethod;
5228 if (LocaleCompare("replace",token) == 0)
5229 primitive_info[j].method=ReplaceMethod;
5230 if (LocaleCompare("floodfill",token) == 0)
5231 primitive_info[j].method=FloodfillMethod;
5232 if (LocaleCompare("filltoborder",token) == 0)
5233 primitive_info[j].method=FillToBorderMethod;
5234 if (LocaleCompare("reset",token) == 0)
5235 primitive_info[j].method=ResetMethod;
5236 break;
5237 }
5238 case TextPrimitive:
5239 {
cristy3ed852e2009-09-05 21:47:34 +00005240 if (primitive_info[j].coordinates != 1)
5241 {
5242 status=MagickFalse;
5243 break;
5244 }
Cristy448fd182019-07-27 16:26:38 -04005245 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00005246 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00005247 " <text x=\"%g\" y=\"%g\">",primitive_info[j].point.x,
cristy3ed852e2009-09-05 21:47:34 +00005248 primitive_info[j].point.y);
5249 (void) WriteBlobString(image,message);
Dirk Lemstraa017a0f2020-09-21 21:28:15 +02005250 for (p=(const char *) token; *p != '\0'; p++)
cristy3ed852e2009-09-05 21:47:34 +00005251 switch (*p)
5252 {
5253 case '<': (void) WriteBlobString(image,"&lt;"); break;
5254 case '>': (void) WriteBlobString(image,"&gt;"); break;
5255 case '&': (void) WriteBlobString(image,"&amp;"); break;
Cristyfedaa752019-02-03 07:23:20 -05005256 default: (void) WriteBlobByte(image,(unsigned char) *p); break;
cristy3ed852e2009-09-05 21:47:34 +00005257 }
5258 (void) WriteBlobString(image,"</text>\n");
5259 break;
5260 }
5261 case ImagePrimitive:
5262 {
5263 if (primitive_info[j].coordinates != 2)
5264 {
5265 status=MagickFalse;
5266 break;
5267 }
Cristy448fd182019-07-27 16:26:38 -04005268 (void) GetNextToken(q,&q,extent,token);
cristy151b66d2015-04-15 10:50:31 +00005269 (void) FormatLocaleString(message,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00005270 " <image x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\" "
Cristyd7e36592018-04-21 14:45:52 -04005271 "href=\"%s\"/>\n",primitive_info[j].point.x,
cristy3ed852e2009-09-05 21:47:34 +00005272 primitive_info[j].point.y,primitive_info[j+1].point.x,
5273 primitive_info[j+1].point.y,token);
5274 (void) WriteBlobString(image,message);
5275 break;
5276 }
5277 }
5278 if (primitive_info == (PrimitiveInfo *) NULL)
5279 break;
5280 primitive_info[i].primitive=UndefinedPrimitive;
5281 if (status == MagickFalse)
5282 break;
5283 }
5284 (void) WriteBlobString(image,"</svg>\n");
5285 /*
5286 Relinquish resources.
5287 */
5288 token=DestroyString(token);
5289 if (primitive_info != (PrimitiveInfo *) NULL)
5290 primitive_info=(PrimitiveInfo *) RelinquishMagickMemory(primitive_info);
5291 (void) CloseBlob(image);
5292 return(status);
5293}