/*
% Copyright (C) 2003-2025 GraphicsMagick Group
% Copyright (C) 2002 ImageMagick Studio
%
% This program is covered by multiple licenses, which are described in
% Copyright.txt. You should have received a copy of Copyright.txt with this
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% SSSSS V V GGGG %
% SS V V G %
% SSS V V G GG %
% SS V V G G %
% SSSSS V GGG %
% %
% %
% Read/Write Scalable Vector Graphics Format. %
% %
% %
% Software Design %
% John Cristy %
% William Radcliffe %
% March 2000 %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/
/*
Include declarations.
*/
#include "magick/studio.h"
#include "magick/attribute.h"
#include "magick/blob.h"
#include "magick/color.h"
#include "magick/constitute.h"
#include "magick/gem.h"
#include "magick/log.h"
#include "magick/magick.h"
#include "magick/render.h"
#include "magick/tempfile.h"
#include "magick/utility.h"
#if defined(HasXML)
# if defined(MSWINDOWS)
# if defined(__MINGW32__)
# if !defined(_MSC_VER)
# define _MSC_VER 1200
# endif
# else
# include <win32config.h>
# endif
# endif
# include <libxml/parser.h>
# include <libxml/xmlmemory.h>
# include <libxml/parserInternals.h>
# include <libxml/xmlerror.h>
#endif
/* Enable support for XML internal subset */
#define ENABLE_XML_INTERNAL_SUBSET 1
/*
Avoid shadowing library globals and functions.
*/
#define attribute attribute_magick
#if defined(HasAUTOTRACE)
#include "types.h"
#include "image-header.h"
#include "fit.h"
#include "output.h"
#include "pxl-outline.h"
#include "atquantize.h"
#include "thin-image.h"
char
*version_string = "AutoTrace version 0.24a";
#endif
/*
Define declarations.
*/
#define MVGPrintf (void) fprintf
/*
Typedef declarations.
*/
typedef struct _BoundingBox
{
double
x,
y,
width,
height;
} BoundingBox;
typedef struct _SVGInfo
{
FILE
*file;
ExceptionInfo
*exception;
Image
*image;
const ImageInfo
*image_info;
AffineMatrix
affine;
unsigned long
width,
height;
char
*size,
*title,
*comment;
int
n;
double
*scale,
pointsize;
ElementInfo
element;
SegmentInfo
segment;
BoundingBox
bounds,
view_box;
PointInfo
radius;
char
*stop_color,
*offset,
*text,
*vertices,
*url;
size_t
comment_len,
text_len;
/*
Even though it's unlikely to happen, keep track of nested <defs> and
elements tagged with an id.
*/
int
defsPushCount, /* for tracking nested <defs> */
idLevelInsideDefs, /* when an "id" is seen, remember svg->n (SVG element level) */
svgPushCount; /* for tracking nested <svg> elements */
unsigned long
signature;
} SVGInfo;
/*
Forward declarations.
*/
#if defined(ENABLE_SVG_WRITER) && ENABLE_SVG_WRITER
static unsigned int
WriteSVGImage(const ImageInfo *,Image *);
#endif /* if defined(ENABLE_SVG_WRITER) && ENABLE_SVG_WRITER */
#if defined(HasXML)
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e a d S V G I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Method ReadSVGImage reads a Scalable Vector Gaphics file and returns it. It
% allocates the memory necessary for the new Image structure and returns a
% pointer to the new image.
%
% The format of the ReadSVGImage method is:
%
% Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
%
% A description of each parameter follows:
%
% o image: Method ReadSVGImage returns a pointer to the image after
% reading. A null image is returned if there is a memory shortage or if
% the image cannot be read.
%
% o image_info: Specifies a pointer to a ImageInfo structure.
%
% o exception: return any errors or warnings in this structure.
%
%
*/
static double
GetUserSpaceCoordinateValue(const SVGInfo *svg_info,
int type,
const char *string,
MagickBool positive)
{
char
*p,
token[MaxTextExtent];
double
value;
assert(string != (const char *) NULL);
p=(char *) string;
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if ((MagickAtoFChk(token,&value) == MagickFail) ||
(((positive) && value < 0.0)))
{
errno=0;
ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,
string);
}
if (strchr(token,'%') != (char *) NULL)
{
double
alpha,
beta;
if (type > 0)
return(svg_info->view_box.width*value/100.0);
if (type < 0)
return(svg_info->view_box.height*value/100.0);
alpha=value-svg_info->view_box.width;
beta=value-svg_info->view_box.height;
return(sqrt(alpha*alpha+beta*beta)/sqrt(2.0)/100.0);
}
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (LocaleNCompare(token,"cm",2) == 0)
return(72.0*svg_info->scale[0]/2.54*value);
if (LocaleNCompare(token,"em",2) == 0)
return(svg_info->pointsize*value);
if (LocaleNCompare(token,"ex",2) == 0)
return(svg_info->pointsize*value/2.0);
if (LocaleNCompare(token,"in",2) == 0)
return(72.0*svg_info->scale[0]*value);
if (LocaleNCompare(token,"mm",2) == 0)
return(72.0*svg_info->scale[0]/25.4*value);
if (LocaleNCompare(token,"pc",2) == 0)
return(72.0*svg_info->scale[0]/6.0*value);
if (LocaleNCompare(token,"pt",2) == 0)
return(svg_info->scale[0]*value);
if (LocaleNCompare(token,"px",2) == 0)
return(value);
return(value);
}
#define MagickCopySubstrToToken(dst, src, size) \
do { \
(void) memcpy(dst,src,(size)); \
dst[(size)]='\0'; \
} while (0);
static char **GetStyleTokens(const SVGInfo *svg_info,const char *text,size_t *number_tokens)
{
#define MaxStyleTokens 256
char
**tokens;
const char
*p,
*q;
size_t
alloc_tokens,
i,
iListFront;
MagickBool
IsFontSize = MagickFalse;
assert(svg_info->signature == MagickSignature);
*number_tokens=0;
alloc_tokens=0;
if (text == (const char *) NULL)
return((char **) NULL);
/*
Determine the number of arguments.
style="fill: red; stroke: blue; stroke-width: 3"
*/
for (p=text; *p != '\0'; p++)
if (*p == ':')
alloc_tokens+=2;
if (alloc_tokens == 0)
return((char **) NULL);
if (alloc_tokens >= MaxStyleTokens)
alloc_tokens=MaxStyleTokens;
tokens=MagickAllocateMemory(char **,(alloc_tokens+2)*sizeof(*tokens));
if (tokens == (char **) NULL)
{
ThrowException3(svg_info->exception,ResourceLimitError,
MemoryAllocationFailed,UnableToConvertStringToTokens);
return((char **) NULL);
}
(void) memset(tokens,0,(alloc_tokens+2)*sizeof(*tokens));
/*
Convert string to an ASCII list.
*/
i=0;
p=text;
iListFront = 0;
for (q=p; *q != '\0'; q++)
{
/*
':' terminates the style element (e.g., fill:)
';' terminates the style element value (e.g., red)
*/
if ((*q != ':') && (*q != ';') && (*q != '\0'))
continue;
tokens[i]=MagickAllocateMemory(char *,(size_t) (q-p+1));
if (tokens[i] == NULL)
{
ThrowException3(svg_info->exception,ResourceLimitError,
MemoryAllocationFailed,UnableToConvertStringToTokens);
break;
}
MagickCopySubstrToToken(tokens[i], p, q-p);
(void) MagickStripString(tokens[i]);
/*
Check for "font-size", which we will move to the first position in
the list. This will ensure that any following numerical conversions
that depend on the font size will use the new value.
*/
if ( (i & 1) == 0 ) /*element name*/
IsFontSize = (LocaleCompare("font-size",tokens[i]) == 0) ? MagickTrue : MagickFalse;
else if ( IsFontSize )
{/*found font-size/value pair*/
if ( (i-1) == iListFront )
iListFront += 2; /* already at front of list */
else
{
/* move "font-size" and value to top of list */
char * pToken = tokens[iListFront];
tokens[iListFront] = tokens[i-1];
tokens[i-1] = pToken;
iListFront++;
pToken = tokens[iListFront];
tokens[iListFront] = tokens[i];
tokens[i] = pToken;
iListFront++;
}
}/*found font-size/value pair*/
i++;
if (i >= alloc_tokens)
break;
p=q+1;
}
if (i < alloc_tokens)
{
tokens[i]=MagickAllocateMemory(char *,(size_t) (q-p+1));
if (tokens[i] == NULL)
{
ThrowException3(svg_info->exception,ResourceLimitError,
MemoryAllocationFailed,UnableToConvertStringToTokens);
}
else
{
MagickCopySubstrToToken(tokens[i], p, q-p);
(void) MagickStripString(tokens[i]);
i++;
}
}
tokens[i]=(char *) NULL;
*number_tokens=i;
return(tokens);
}
#define THROW_GET_TRANSFORM_TOKENS_EXCEPTION() \
do { \
ThrowException3(svg_info->exception,ResourceLimitError, \
MemoryAllocationFailed,UnableToConvertStringToTokens); \
} while (0)
static char **GetTransformTokens(const SVGInfo *svg_info,const char *text,
size_t *number_tokens)
{
#define MaxTransformTokens 256
char
**tokens;
register const char
*p,
*q;
register size_t
i;
size_t
alloc_tokens;
assert(svg_info->signature == MagickSignature);
*number_tokens=0;
if (text == (const char *) NULL)
return((char **) NULL);
alloc_tokens=8;
tokens=MagickAllocateMemory(char **,(alloc_tokens+2)*sizeof(*tokens));
if (tokens == (char **) NULL)
{
ThrowException3(svg_info->exception,ResourceLimitError,
MemoryAllocationFailed,UnableToConvertStringToTokens);
return((char **) NULL);
}
/*
Convert string to an ASCII list.
*/
i=0;
p=text;
for (q=p; *q != '\0'; q++)
{
if ((*q != '(') && (*q != ')') && (*q != '\0'))
continue;
if (i == alloc_tokens)
{
alloc_tokens <<= 1;
MagickReallocMemory(char **,tokens,(alloc_tokens+2)*sizeof(*tokens));
if (tokens == (char **) NULL)
THROW_GET_TRANSFORM_TOKENS_EXCEPTION();
}
/*
Apply an arbitrary limit on number of tokens to avoid DOS
*/
if (i >= MaxTransformTokens)
break;
tokens[i]=MagickAllocateMemory(char *,(size_t) (q-p+1));
if (tokens[i] == NULL)
THROW_GET_TRANSFORM_TOKENS_EXCEPTION();
MagickCopySubstrToToken(tokens[i], p, q-p);
(void) MagickStripString(tokens[i]);
i++;
p=q+1;
}
if (i < MaxTransformTokens)
{
tokens[i]=MagickAllocateMemory(char *,(size_t) (q-p+1));
if (tokens[i] == NULL)
THROW_GET_TRANSFORM_TOKENS_EXCEPTION();
MagickCopySubstrToToken(tokens[i], p, q-p);
(void) MagickStripString(tokens[i]);
i++;
}
tokens[i]=(char *) NULL;
*number_tokens=i;
return(tokens);
}
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
static void SVGStartElement(void *context,const xmlChar *name,
const xmlChar **attributes);
static void SVGEndElement(void *context,const xmlChar *name);
static void SVGCharacters(void *context,const xmlChar *c,int length);
static void SVGWarning(void *context,const char *format,...);
ModuleExport void RegisterSVGImage(void);
ModuleExport void UnregisterSVGImage(void);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
/*
Code from SVGStartElement() that processed transform="..." has been refactored
into new function SVGProcessTransformString().
*/
static void
SVGProcessTransformString(const SVGInfo *svg_info,char const *TransformString)
{/*SVGProcessTransformString*/
char
**tokens;
AffineMatrix
affine,
current,
transform;
char
*p = NULL,
token[MaxTextExtent];
size_t
j,
number_tokens = 0;
assert(svg_info->signature == MagickSignature);
IdentityAffine(&transform);
(void) LogMagickEvent(CoderEvent,GetMagickModule()," ");
tokens=GetTransformTokens(svg_info,TransformString,&number_tokens);
if ((tokens != (char **) NULL) && (number_tokens > 0))
{/*if ((tokens != (char **) NULL) && (number_tokens > 0))*/
const char
*keyword = NULL,
*value = NULL;
for (j=0; j < (number_tokens-1); j+=2)
{/*j token loop*/
keyword=(char *) tokens[j]; /* matrix, rotate, etc. */
value=(char *) tokens[j+1]; /* associated numerical values */
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" %.1024s: %.1024s",keyword,value);
current=transform;
IdentityAffine(&affine);
switch (*keyword)
{/*keyword switch*/
case 'M':
case 'm':
{/*Mm*/
if (LocaleCompare(keyword,"matrix") == 0)
{
p=(char *) value;
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.sx=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.rx=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.ry=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.sy=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.tx=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.ty=MagickAtoF(token);
break;
}
break;
}/*Mm*/
case 'R':
case 'r':
{/*Rr*/
if (LocaleCompare(keyword,"rotate") == 0)
{
double
angle;
angle=GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse);
affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
break;
}
break;
}/*Rr*/
case 'S':
case 's':
{/*Ss*/
if (LocaleCompare(keyword,"scale") == 0)
{
for (p=(char *) value; *p != '\0'; p++)
if (isspace((int) (*p)) || (*p == ','))
break;
affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
affine.sy=affine.sx;
if (*p != '\0')
affine.sy=
GetUserSpaceCoordinateValue(svg_info,-1,p+1,MagickFalse);
svg_info->scale[svg_info->n]=ExpandAffine(&affine);
break;
}
if (LocaleCompare(keyword,"skewX") == 0)
{
affine.sx=svg_info->affine.sx;
affine.ry=tan(DegreesToRadians(fmod(
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse),
360.0)));
affine.sy=svg_info->affine.sy;
break;
}
if (LocaleCompare(keyword,"skewY") == 0)
{
affine.sx=svg_info->affine.sx;
affine.rx=tan(DegreesToRadians(fmod(
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse),
360.0)));
affine.sy=svg_info->affine.sy;
break;
}
break;
}/*Ss*/
case 'T':
case 't':
{/*Tt*/
if (LocaleCompare(keyword,"translate") == 0)
{
for (p=(char *) value; *p != '\0'; p++)
if (isspace((int) (*p)) || (*p == ','))
break;
affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
affine.ty=affine.tx;
if (*p != '\0')
affine.ty=
GetUserSpaceCoordinateValue(svg_info,-1,p+1,MagickFalse);
break;
}
break;
}/*Tt*/
default:
break;
}/*keyword switch*/
transform.sx=current.sx*affine.sx+current.ry*affine.rx;
transform.rx=current.rx*affine.sx+current.sy*affine.rx;
transform.ry=current.sx*affine.ry+current.ry*affine.sy;
transform.sy=current.rx*affine.ry+current.sy*affine.sy;
transform.tx=current.sx*affine.tx+current.ry*affine.ty+
current.tx;
transform.ty=current.rx*affine.tx+current.sy*affine.ty+
current.ty;
}/*j token loop*/
MVGPrintf(svg_info->file,"affine %g %g %g %g %g %g\n",
transform.sx,transform.rx,transform.ry,transform.sy,
transform.tx,transform.ty);
}/*if ((tokens != (char **) NULL) && (number_tokens > 0))*/
/* clean up memory used for tokens */
if (tokens != (char **) NULL)
{
for (j=0; tokens[j] != (char *) NULL; j++)
MagickFreeMemory(tokens[j]);
MagickFreeMemory(tokens);
}
}/*SVGProcessTransformString*/
static void
SVGStartElement(void *context,const xmlChar *name,
const xmlChar **attributes)
{
char
*color = NULL,
id[MaxTextExtent],
*p = NULL,
token[MaxTextExtent],
*units = NULL;
const char
*keyword = NULL,
*value = NULL;
size_t
number_tokens = 0;
SVGInfo
*svg_info;
xmlParserCtxtPtr
parser;
size_t
i,
j;
char
svg_element_background_color[MaxTextExtent]; /* to support style="background:color" */
MagickBool
IsTSpan = MagickFalse,
IsTextOrTSpan = MagickFalse;
/*
Called when an opening tag has been processed.
*/
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" SAX.startElement(%.1024s",name);
id[0]='\0';
token[0]='\0';
parser=(xmlParserCtxtPtr) context;
svg_info=(SVGInfo *) parser->_private;
assert(svg_info->signature == MagickSignature);
svg_info->n++;
MagickReallocMemory(double *,svg_info->scale,MagickArraySize((size_t)svg_info->n+1,sizeof(double)));
if (svg_info->scale == (double *) NULL)
{
ThrowException(svg_info->exception,ResourceLimitError,
MemoryAllocationFailed,"unable to convert SVG image");
return;
}
svg_info->scale[svg_info->n]=svg_info->scale[svg_info->n-1];
color=AcquireString("none");
units=AcquireString("userSpaceOnUse");
value=(const char *) NULL;
svg_element_background_color[0]='\0';
IsTextOrTSpan = IsTSpan = LocaleCompare((char *) name,"tspan") == 0; /* need to know this early */
/*
According to the SVG spec, for the following SVG elements, if the x or y
attribute is not specified, the effect is as if a value of "0" were specified.
*/
if (
(LocaleCompare((char *) name,"image") == 0)
|| (LocaleCompare((char *) name,"pattern") == 0)
|| (LocaleCompare((char *) name,"rect") == 0)
|| (LocaleCompare((char *) name,"text") == 0)
|| (LocaleCompare((char *) name,"use") == 0)
)
{
svg_info->bounds.x = svg_info->bounds.y = 0;
}
/*
NOTE: SVG spec makes similar statements for cx,cy of circle and ellipse, and
x1,y1,x2,y2 of line, but these are zeroed out initially, AND at the end of
SVGEndElement() after they have been used.
*/
/*
When "font-size" is (or is contained in) one of the attributes for this SVG
element, we want it to be processed first so that any numerical conversions
that depend on the font size will use the new value. So we will first scan
the attribute list and move any "font-size", "class" (which may contain a
"font-size"), or "style" (which may contain a "font-size") attributes to the
front of the attribute list.
For now we will ignore the possibility that "font-size" may be specified
more than once among "font-size", "class", and "style". However, the
relative order among these three will be preserved.
*/
if (attributes != (const xmlChar **) NULL)
{/*have some attributes*/
size_t iListFront = 0;
for ( i = 0; (attributes[i] != (const xmlChar *) NULL); i += 2 )
{/*attribute[i]*/
keyword = (const char *) attributes[i];
if ( (LocaleCompare(keyword,"font-size") == 0)
|| (LocaleCompare(keyword,"class") == 0)
|| (LocaleCompare(keyword,"style") == 0)
)
{/*(possible) font-size*/
if ( i == iListFront )
iListFront += 2; /* already at front of list */
else
{
/* move to front of list */
const xmlChar * pAttr = attributes[iListFront];
attributes[iListFront] = attributes[i];
attributes[i] = pAttr;
iListFront++;
pAttr = attributes[iListFront];
attributes[iListFront] = attributes[i+1];
attributes[i+1] = pAttr;
iListFront++;
}
}/*(possible) font-size*/
}/*attribute[i]*/
}/*have some attributes*/
if (attributes != (const xmlChar **) NULL)
for (i=0; (svg_info->exception->severity < ErrorException) &&
(attributes[i] != (const xmlChar *) NULL); i+=2)
{
keyword=(const char *) attributes[i];
value=(const char *) attributes[i+1];
switch (*keyword)
{
case 'C':
case 'c':
{
if (LocaleCompare(keyword,"cx") == 0)
{
svg_info->element.cx=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"cy") == 0)
{
svg_info->element.cy=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
break;
}
case 'F':
case 'f':
{
if (LocaleCompare(keyword,"fx") == 0)
{
svg_info->element.major=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"fy") == 0)
{
svg_info->element.minor=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
break;
}
case 'H':
case 'h':
{
if (LocaleCompare(keyword,"height") == 0)
{
svg_info->bounds.height=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickTrue);
break;
}
break;
}
case 'I':
case 'i':
{
if (LocaleCompare(keyword,"id") == 0)
{
(void) strlcpy(id,value,MaxTextExtent);
/* track elements inside <defs> that have an "id" */
if ( (svg_info->defsPushCount > 0)
&& (svg_info->idLevelInsideDefs == 0) /* do not allow nested "id" elements for now */
&& (LocaleCompare((const char *)name,"clipPath") != 0) /* handled separately */
&& (LocaleCompare((const char *)name,"mask") != 0) /* handled separately */
)
svg_info->idLevelInsideDefs = svg_info->n;
break;
}
break;
}
case 'R':
case 'r':
{
if (LocaleCompare(keyword,"r") == 0)
{
svg_info->element.angle=
GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse);
break;
}
break;
}
case 'W':
case 'w':
{
if (LocaleCompare(keyword,"width") == 0)
{
svg_info->bounds.width=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue);
break;
}
break;
}
case 'X':
case 'x':
{
if (LocaleCompare(keyword,"x") == 0)
{
/* if processing a tspan, preserve the current bounds.x, which belongs to the
previously processed text or tspan; the bounds.x for the current tspan will
be set later */
if (!IsTSpan)
svg_info->bounds.x=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"x1") == 0)
{
svg_info->segment.x1=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"x2") == 0)
{
svg_info->segment.x2=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
break;
}
case 'Y':
case 'y':
{
if (LocaleCompare(keyword,"y") == 0)
{
/* if processing a tspan, preserve the current bounds.y, which belongs to the
previously processed text or tspan; the bounds.y for the current tspan will
be set later */
if (!IsTSpan)
svg_info->bounds.y=GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"y1") == 0)
{
svg_info->segment.y1=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"y2") == 0)
{
svg_info->segment.y2=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
break;
}
default:
break;
}
}
if (svg_info->exception->severity >= ErrorException)
goto svg_start_element_error;
if (strchr((char *) name,':') != (char *) NULL)
{
/*
Skip over namespace.
*/
for ( ; *name != ':'; name++) ;
name++;
}
switch (*name)
{
case 'C':
case 'c':
{
if (LocaleCompare((char *) name,"circle") == 0)
{
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "push id" if warranted */
MVGPrintf(svg_info->file,"push id '%s'\n",id);
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
if (LocaleCompare((char *) name,"clipPath") == 0)
{
MVGPrintf(svg_info->file,"push clip-path '%s'\n",id);
break;
}
break;
}
case 'D':
case 'd':
{
if (LocaleCompare((char *) name,"defs") == 0)
{
svg_info->defsPushCount++;
MVGPrintf(svg_info->file,"push defs\n");
break;
}
break;
}
case 'E':
case 'e':
{
if (LocaleCompare((char *) name,"ellipse") == 0)
{
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "push id" if warranted */
MVGPrintf(svg_info->file,"push id '%s'\n",id);
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
break;
}
case 'F':
case 'f':
{
/*
For now we are ignoring "foreignObject". However, we do a push/pop
graphic-context so that any settings (e.g., fill) are consumed and
discarded. Otherwise they might persist and "leak" into the graphic
elements that follow.
*/
if (LocaleCompare((char *) name,"foreignObject") == 0)
{
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
break;
}
case 'G':
case 'g':
{
if (LocaleCompare((char *) name,"g") == 0)
{
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "push id" if warranted */
MVGPrintf(svg_info->file,"push id '%s'\n",id);
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
break;
}
case 'I':
case 'i':
{
if (LocaleCompare((char *) name,"image") == 0)
{
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
break;
}
case 'L':
case 'l':
{
if (LocaleCompare((char *) name,"line") == 0)
{
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "push id" if warranted */
MVGPrintf(svg_info->file,"push id '%s'\n",id);
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
if (LocaleCompare((char *) name,"linearGradient") == 0)
{
MVGPrintf(svg_info->file,"push gradient '%s' linear %g,%g %g,%g\n",id,
svg_info->segment.x1,svg_info->segment.y1,svg_info->segment.x2,
svg_info->segment.y2);
break;
}
break;
}
case 'M':
case 'm':
{
if (LocaleCompare((char *) name,"mask") == 0) /* added mask */
{
MVGPrintf(svg_info->file,"push mask '%s'\n",id);
break;
}
break;
}
case 'P':
case 'p':
{
if (LocaleCompare((char *) name,"path") == 0)
{
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "push id" if warranted */
MVGPrintf(svg_info->file,"push id '%s'\n",id);
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
if (LocaleCompare((char *) name,"pattern") == 0)
{
MVGPrintf(svg_info->file,"push pattern '%s' %g,%g %g,%g\n",id,
svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.width,
svg_info->bounds.height);
break;
}
if (LocaleCompare((char *) name,"polygon") == 0)
{
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "push id" if warranted */
MVGPrintf(svg_info->file,"push id '%s'\n",id);
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
if (LocaleCompare((char *) name,"polyline") == 0)
{
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "push id" if warranted */
MVGPrintf(svg_info->file,"push id '%s'\n",id);
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
break;
}
case 'R':
case 'r':
{
if (LocaleCompare((char *) name,"radialGradient") == 0)
{
if (svg_info->element.angle < 0.0)
{
errno=0;
ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,
value);
break;
}
MVGPrintf(svg_info->file,"push gradient '%s' radial %g,%g %g,%g %g\n",
id,svg_info->element.cx,svg_info->element.cy,
svg_info->element.major,svg_info->element.minor,
svg_info->element.angle);
break;
}
if (LocaleCompare((char *) name,"rect") == 0)
{
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "push id" if warranted */
MVGPrintf(svg_info->file,"push id '%s'\n",id);
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
break;
}
case 'S':
case 's':
{
/* element "style" inside <defs> */
if (LocaleCompare((char *) name,"style") == 0)
{
/*
This is here more or less as a documentation aid. The real work is done when
we encounter </style>.
*/
break;
}
if (LocaleCompare((char *) name,"svg") == 0)
{
svg_info->svgPushCount++;
MVGPrintf(svg_info->file,"push graphic-context\n");
/*
Per the SVG spec, initialize the MVG coder with the following
SVG defaults:
- svg-compliant: "1" (note: internal to GM, not an SVG element)
- fill color: "black"
- fill-opacity value: "1"
- stroke color: "none"
- stroke-width value: "1"
- stroke-opacity value: "1"
- fill-rule value: "nonzero"
*/
MVGPrintf(svg_info->file,"svg-compliant 1\n");
MVGPrintf(svg_info->file,"fill 'black'\n");
MVGPrintf(svg_info->file,"fill-opacity 1\n");
MVGPrintf(svg_info->file,"stroke 'none'\n");
MVGPrintf(svg_info->file,"stroke-width 1\n");
MVGPrintf(svg_info->file,"stroke-opacity 1\n");
MVGPrintf(svg_info->file,"fill-rule 'nonzero'\n");
break;
}
break;
}
case 'T':
case 't':
{
if (LocaleCompare((char *) name,"text") == 0)
{
IsTextOrTSpan = MagickTrue;
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "push id" if warranted */
MVGPrintf(svg_info->file,"push id '%s'\n",id);
MVGPrintf(svg_info->file,"push graphic-context\n");
/* update text current position */
MVGPrintf(svg_info->file,"textx %g\n",svg_info->bounds.x);
MVGPrintf(svg_info->file,"texty %g\n",svg_info->bounds.y);
break;
}
if (LocaleCompare((char *) name,"tspan") == 0)
{
IsTextOrTSpan = MagickTrue;
svg_info->text_len=MagickStripString(svg_info->text);
if (*svg_info->text != '\0')
{
char
*text;
text=EscapeString(svg_info->text,'\'');
MVGPrintf(svg_info->file,"textc '%s'\n",text);
MagickFreeMemory(text);
/*
The code that used to be here to compute the next text position has been eliminated.
The reason is that at this point in the code we may not know the font or font size
(they may be hidden in a "class" definition), so we can't really do that computation.
This functionality is now handled by DrawImage() in render.c.
*/
*svg_info->text='\0';
svg_info->text_len=strlen(svg_info->text);
}
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
break;
}
case 'U':
case 'u':
{
if (LocaleCompare((char *) name,"use") == 0)
{
/* "use" behaves like "g" */
MVGPrintf(svg_info->file,"push graphic-context\n");
break;
}
break;
}
default:
break;
}
if (attributes != (const xmlChar **) NULL)
for (i=0; (svg_info->exception->severity < ErrorException) &&
(attributes[i] != (const xmlChar *) NULL); i+=2)
{
keyword=(const char *) attributes[i];
value=(const char *) attributes[i+1];
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" %.1024s = %.1024s",keyword,value);
switch (*keyword)
{
case 'A':
case 'a':
{
if (LocaleCompare(keyword,"angle") == 0)
{
MVGPrintf(svg_info->file,"angle %g\n",
GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse));
break;
}
break;
}
case 'C':
case 'c':
{
if (LocaleCompare(keyword,"class") == 0)
{/*"class=classname"*/
char * pClassNames = (char * ) value;
do
{
(void) MagickGetToken(pClassNames,&pClassNames,token,MaxTextExtent);
if ( *token == ',' )
(void) MagickGetToken(pClassNames,&pClassNames,token,MaxTextExtent);
if ( *token != '\0' )
MVGPrintf(svg_info->file,"class '%s'\n",token);
}
while ( *token != '\0' );
break;
}/*"class=classname"*/
if (LocaleCompare(keyword,"clip-path") == 0)
{
MVGPrintf(svg_info->file,"clip-path '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"clip-rule") == 0)
{
MVGPrintf(svg_info->file,"clip-rule '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"clipPathUnits") == 0)
{
(void) CloneString(&units,value);
MVGPrintf(svg_info->file,"clip-units '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"color") == 0)
{
(void) CloneString(&color,value);
break;
}
if (LocaleCompare(keyword,"cx") == 0)
{
svg_info->element.cx=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"cy") == 0)
{
svg_info->element.cy=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
break;
}
case 'D':
case 'd':
{
if (LocaleCompare(keyword,"d") == 0)
{
(void) CloneString(&svg_info->vertices,value);
break;
}
if (LocaleCompare(keyword,"dx") == 0)
{
double dx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
svg_info->bounds.x+=dx; /* preserve previous behavior */
/* update text current position for text or tspan */
if ( IsTextOrTSpan )
{
char * pUnit;
(void) MagickGetToken(value,&pUnit,token,MaxTextExtent);
if ( *pUnit && ((LocaleNCompare(pUnit,"em",2) == 0) || (LocaleNCompare(pUnit,"ex",2) == 0)) )
MVGPrintf(svg_info->file,"textdx %s\n",value); /* postpone interpretation of "em" or "ex" until we know point size */
else
MVGPrintf(svg_info->file,"textdx %g\n",dx);
}
break;
}
if (LocaleCompare(keyword,"dy") == 0)
{
double dy=GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
svg_info->bounds.y+=dy; /* preserve previous behavior */
/* update text current position for text or tspan */
if ( IsTextOrTSpan )
{
char * pUnit;
(void) MagickGetToken(value,&pUnit,token,MaxTextExtent);
if ( *pUnit && ((LocaleNCompare(pUnit,"em",2) == 0) || (LocaleNCompare(pUnit,"ex",2) == 0)) )
MVGPrintf(svg_info->file,"textdy %s\n",value); /* postpone interpretation of "em" or "ex" until we know point size */
else
MVGPrintf(svg_info->file,"textdy %g\n",dy);
}
break;
}
break;
}
case 'F':
case 'f':
{
if (LocaleCompare(keyword,"fill") == 0)
{
if (LocaleCompare(value,"currentColor") == 0)
{
MVGPrintf(svg_info->file,"fill '%s'\n",color);
break;
}
MVGPrintf(svg_info->file,"fill '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"fillcolor") == 0)
{
MVGPrintf(svg_info->file,"fill '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"fill-rule") == 0)
{
MVGPrintf(svg_info->file,"fill-rule '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"fill-opacity") == 0)
{
MVGPrintf(svg_info->file,"fill-opacity '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-family") == 0)
{
/*
Deal with Adobe Illustrator 10.0 which double-quotes
font-family. Maybe we need a generalized solution for
this.
*/
int value_length;
if ((value[0] == '\'') && ((value_length=(int) strlen(value)) > 2)
&& (value[value_length-1] == '\''))
{
MVGPrintf(svg_info->file,"font-family '%.*s'\n",
(int)(value_length-2),value+1);
}
else
{
MVGPrintf(svg_info->file,"font-family '%s'\n",value);
}
break;
}
if (LocaleCompare(keyword,"font-stretch") == 0)
{
MVGPrintf(svg_info->file,"font-stretch '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-style") == 0)
{
MVGPrintf(svg_info->file,"font-style '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-size") == 0)
{
svg_info->pointsize=
GetUserSpaceCoordinateValue(svg_info,0,value,MagickTrue);
MVGPrintf(svg_info->file,"font-size %g\n",svg_info->pointsize);
break;
}
if (LocaleCompare(keyword,"font-weight") == 0)
{
MVGPrintf(svg_info->file,"font-weight '%s'\n",value);
break;
}
break;
}
case 'G':
case 'g':
{
if (LocaleCompare(keyword,"gradientTransform") == 0)
{
char
**tokens;
AffineMatrix
affine,
current,
transform;
IdentityAffine(&transform);
(void) LogMagickEvent(CoderEvent,GetMagickModule()," ");
tokens=GetTransformTokens(svg_info,value,&number_tokens);
if ((tokens != (char **) NULL) && (number_tokens > 0))
{
for (j=0; j < (number_tokens-1); j+=2)
{
keyword=(char *) tokens[j];
if (keyword == (char *) NULL)
continue;
value=(char *) tokens[j+1];
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" %.1024s: %.1024s",keyword,value);
current=transform;
IdentityAffine(&affine);
switch (*keyword)
{
case 'M':
case 'm':
{
if (LocaleCompare(keyword,"matrix") == 0)
{
p=(char *) value;
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.sx=MagickAtoF(value);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.rx=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.ry=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.sy=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.tx=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
affine.ty=MagickAtoF(token);
break;
}
break;
}
case 'R':
case 'r':
{
if (LocaleCompare(keyword,"rotate") == 0)
{
double
angle;
angle=GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse);
affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
break;
}
break;
}
case 'S':
case 's':
{
if (LocaleCompare(keyword,"scale") == 0)
{
for (p=(char *) value; *p != '\0'; p++)
if (isspace((int) (*p)) || (*p == ','))
break;
affine.sx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
affine.sy=affine.sx;
if (*p != '\0')
affine.sy=
GetUserSpaceCoordinateValue(svg_info,-1,p+1,MagickFalse);
svg_info->scale[svg_info->n]=ExpandAffine(&affine);
break;
}
if (LocaleCompare(keyword,"skewX") == 0)
{
affine.sx=svg_info->affine.sx;
affine.ry=tan(DegreesToRadians(fmod(
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse),
360.0)));
affine.sy=svg_info->affine.sy;
break;
}
if (LocaleCompare(keyword,"skewY") == 0)
{
affine.sx=svg_info->affine.sx;
affine.rx=tan(DegreesToRadians(fmod(
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse),
360.0)));
affine.sy=svg_info->affine.sy;
break;
}
break;
}
case 'T':
case 't':
{
if (LocaleCompare(keyword,"translate") == 0)
{
for (p=(char *) value; *p != '\0'; p++)
if (isspace((int) (*p)) || (*p == ','))
break;
affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
affine.ty=affine.tx;
if (*p != '\0')
affine.ty=
GetUserSpaceCoordinateValue(svg_info,-1,p+1,MagickFalse);
break;
}
break;
}
default:
break;
} /* end switch */
transform.sx=current.sx*affine.sx+current.ry*affine.rx;
transform.rx=current.rx*affine.sx+current.sy*affine.rx;
transform.ry=current.sx*affine.ry+current.ry*affine.sy;
transform.sy=current.rx*affine.ry+current.sy*affine.sy;
transform.tx=current.sx*affine.tx+current.ry*affine.ty+
current.tx;
transform.ty=current.rx*affine.tx+current.sy*affine.ty+
current.ty;
} /* end for */
MVGPrintf(svg_info->file,"affine %g %g %g %g %g %g\n",
transform.sx,transform.rx,transform.ry,transform.sy,
transform.tx,transform.ty);
} /* end if */
if (tokens != (char **) NULL)
{
for (j=0; tokens[j] != (char *) NULL; j++)
MagickFreeMemory(tokens[j]);
MagickFreeMemory(tokens);
}
break;
}
if (LocaleCompare(keyword,"gradientUnits") == 0)
{
(void) CloneString(&units,value);
MVGPrintf(svg_info->file,"gradient-units '%s'\n",value);
break;
}
break;
}
case 'H':
case 'h':
{
if (LocaleCompare(keyword,"height") == 0)
{
svg_info->bounds.height=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickTrue);
break;
}
if (LocaleCompare(keyword,"href") == 0)
{
/* process "#identifier" as if it were "url(#identifier)" */
if ( value[0] == '#' )
{
/* reallocate the needed memory once */
size_t NewSize = strlen(value) + 6; /* 6 == url()<null> */
MagickReallocMemory(char *,svg_info->url,NewSize);
strlcpy(svg_info->url,"url(",NewSize);
strlcat(svg_info->url,value,NewSize);
strlcat(svg_info->url,")",NewSize);
}
else
{
(void) CloneString(&svg_info->url,value);
}
break;
}
break;
}
case 'M':
case 'm':
{
if (LocaleCompare(keyword,"major") == 0)
{
svg_info->element.major=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"mask") == 0) /* added mask */
{
MVGPrintf(svg_info->file,"mask '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"minor") == 0)
{
svg_info->element.minor=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
break;
}
case 'O':
case 'o':
{
if (LocaleCompare(keyword,"offset") == 0)
{
(void) CloneString(&svg_info->offset,value);
break;
}
if (LocaleCompare(keyword,"opacity") == 0)
{
MVGPrintf(svg_info->file,"opacity '%s'\n",value);
break;
}
break;
}
case 'P':
case 'p':
{
if (LocaleCompare(keyword,"path") == 0)
{
(void) CloneString(&svg_info->url,value);
break;
}
if (LocaleCompare(keyword,"points") == 0)
{
(void) CloneString(&svg_info->vertices,value);
break;
}
break;
}
case 'R':
case 'r':
{
if (LocaleCompare(keyword,"r") == 0)
{
svg_info->element.major=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
svg_info->element.minor=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"rotate") == 0)
{
double
angle;
angle=GetUserSpaceCoordinateValue(svg_info,0,value,MagickFalse);
/*
When the current text position was managed in this code, and a "rotate" was encountered
(indicating that the text character was to be rotated), the code would emit to the MVG file:
translate x y (where x, y indicate the current text position)
rotate angle (where angle indicates the rotation angle)
Now that the current text position is being managed by DrawImage() in render.c, this code
cannot issue the "translate" because it can't know the current text position. To handle
this, "textr" (text rotation) has been implemented in DrawImage() to perform the appropriate
translation/rotation sequence.
*/
if ( IsTextOrTSpan )
MVGPrintf(svg_info->file,"textr %g\n",angle); /* pre-translation will be handled in DrawImage() */
else
{
MVGPrintf(svg_info->file,"translate %g,%g\n",svg_info->bounds.x,svg_info->bounds.y);
svg_info->bounds.x=0;
svg_info->bounds.y=0;
MVGPrintf(svg_info->file,"rotate %g\n",angle);
}
break;
}
if (LocaleCompare(keyword,"rx") == 0)
{
if (LocaleCompare((char *) name,"ellipse") == 0)
svg_info->element.major=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
else
svg_info->radius.x=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"ry") == 0)
{
if (LocaleCompare((char *) name,"ellipse") == 0)
svg_info->element.minor=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
else
svg_info->radius.y=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
break;
}
case 'S':
case 's':
{
if (LocaleCompare(keyword,"stop-color") == 0)
{
(void) CloneString(&svg_info->stop_color,value);
break;
}
if (LocaleCompare(keyword,"stroke") == 0)
{
if (LocaleCompare(value,"currentColor") == 0)
{
MVGPrintf(svg_info->file,"stroke '%s'\n",color);
break;
}
MVGPrintf(svg_info->file,"stroke '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
{
MVGPrintf(svg_info->file,"stroke-antialias %d\n",
LocaleCompare(value,"true") == 0);
break;
}
if (LocaleCompare(keyword,"stroke-dasharray") == 0)
{
MVGPrintf(svg_info->file,"stroke-dasharray %s\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
{
double dashoffset=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
MVGPrintf(svg_info->file,"stroke-dashoffset %g\n",dashoffset);
break;
}
if (LocaleCompare(keyword,"stroke-linecap") == 0)
{
MVGPrintf(svg_info->file,"stroke-linecap '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-linejoin") == 0)
{
MVGPrintf(svg_info->file,"stroke-linejoin '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
{
double stroke_miterlimit;
if ((MagickAtoFChk(value,&stroke_miterlimit) != MagickPass) ||
stroke_miterlimit < 1.0)
{
errno=0;
ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,
value);
break;
}
MVGPrintf(svg_info->file,"stroke-miterlimit '%ld'\n",
(long) stroke_miterlimit);
break;
}
if (LocaleCompare(keyword,"stroke-opacity") == 0)
{
MVGPrintf(svg_info->file,"stroke-opacity '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-width") == 0)
{
MVGPrintf(svg_info->file,"stroke-width %g\n",
GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue));
break;
}
if (LocaleCompare(keyword,"style") == 0)
{
char
**tokens;
(void) LogMagickEvent(CoderEvent,GetMagickModule()," ");
tokens=GetStyleTokens(svg_info,value,&number_tokens);
if ((tokens != (char **) NULL) && (number_tokens > 0))
{
for (j=0; j < (number_tokens-1); j+=2)
{
keyword=(char *) tokens[j];
value=(char *) tokens[j+1];
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" %.1024s: %.1024s",keyword,value);
switch (*keyword)
{
case 'B':
case 'b':
{
if (LocaleCompare(keyword,"background") == 0)
{
/* only do this if background was specified inside <svg ... */
if ( LocaleCompare((const char *)name,"svg") == 0 )
strlcpy(svg_element_background_color,value,MaxTextExtent);
break;
}
break;
}
case 'C':
case 'c':
{
if (LocaleCompare(keyword,"clip-path") == 0)
{
MVGPrintf(svg_info->file,"clip-path '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"clip-rule") == 0)
{
MVGPrintf(svg_info->file,"clip-rule '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"clipPathUnits") == 0)
{
(void) CloneString(&units,value);
MVGPrintf(svg_info->file,"clip-units '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"color") == 0)
{
(void) CloneString(&color,value);
break;
}
break;
}
case 'F':
case 'f':
{
if (LocaleCompare(keyword,"fill") == 0)
{
if (LocaleCompare(value,"currentColor") == 0)
{
MVGPrintf(svg_info->file,"fill '%s'\n",color);
break;
}
MVGPrintf(svg_info->file,"fill '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"fillcolor") == 0)
{
MVGPrintf(svg_info->file,"fill '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"fill-rule") == 0)
{
MVGPrintf(svg_info->file,"fill-rule '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"fill-opacity") == 0)
{
MVGPrintf(svg_info->file,"fill-opacity '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-family") == 0)
{
MVGPrintf(svg_info->file,"font-family '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-stretch") == 0)
{
MVGPrintf(svg_info->file,"font-stretch '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-style") == 0)
{
MVGPrintf(svg_info->file,"font-style '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-size") == 0)
{
if (LocaleCompare(value,"medium") == 0)
svg_info->pointsize=12;
else
svg_info->pointsize=
GetUserSpaceCoordinateValue(svg_info,0,value,MagickTrue);
MVGPrintf(svg_info->file,"font-size %g\n",
svg_info->pointsize);
break;
}
if (LocaleCompare(keyword,"font-weight") == 0)
{
MVGPrintf(svg_info->file,"font-weight '%s'\n",value);
break;
}
break;
}
case 'O':
case 'o':
{
if (LocaleCompare(keyword,"offset") == 0)
{
MVGPrintf(svg_info->file,"offset %g\n",
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse));
break;
}
if (LocaleCompare(keyword,"opacity") == 0)
{
MVGPrintf(svg_info->file,"opacity '%s'\n",value);
break;
}
break;
}
case 'S':
case 's':
{
if (LocaleCompare(keyword,"stop-color") == 0)
{
(void) CloneString(&svg_info->stop_color,value);
break;
}
if (LocaleCompare(keyword,"stroke") == 0)
{
if (LocaleCompare(value,"currentColor") == 0)
{
MVGPrintf(svg_info->file,"stroke '%s'\n",color);
break;
}
MVGPrintf(svg_info->file,"stroke '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
{
MVGPrintf(svg_info->file,"stroke-antialias %d\n",
LocaleCompare(value,"true") == 0);
break;
}
if (LocaleCompare(keyword,"stroke-dasharray") == 0)
{
MVGPrintf(svg_info->file,"stroke-dasharray %s\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
{
double dashoffset=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
MVGPrintf(svg_info->file,"stroke-dashoffset %g\n",dashoffset);
break;
}
if (LocaleCompare(keyword,"stroke-linecap") == 0)
{
MVGPrintf(svg_info->file,"stroke-linecap '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-linejoin") == 0)
{
MVGPrintf(svg_info->file,"stroke-linejoin '%s'\n",
value);
break;
}
if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
{
double stroke_miterlimit;
if ((MagickAtoFChk(value,&stroke_miterlimit) != MagickPass) ||
stroke_miterlimit < 1.0)
{
errno=0;
ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,
value);
break;
}
MVGPrintf(svg_info->file,"stroke-miterlimit '%ld'\n",
(long) stroke_miterlimit);
break;
}
if (LocaleCompare(keyword,"stroke-opacity") == 0)
{
MVGPrintf(svg_info->file,"stroke-opacity '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-width") == 0)
{
MVGPrintf(svg_info->file,"stroke-width %g\n",
GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue));
break;
}
break;
}
case 't':
case 'T':
{
if (LocaleCompare(keyword,"text-align") == 0)
{
MVGPrintf(svg_info->file,"text-align '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"text-anchor") == 0)
{
MVGPrintf(svg_info->file,"text-anchor '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"text-decoration") == 0)
{
if (LocaleCompare(value,"underline") == 0)
MVGPrintf(svg_info->file,"decorate underline\n");
if (LocaleCompare(value,"line-through") == 0)
MVGPrintf(svg_info->file,"decorate line-through\n");
if (LocaleCompare(value,"overline") == 0)
MVGPrintf(svg_info->file,"decorate overline\n");
break;
}
if (LocaleCompare(keyword,"text-antialiasing") == 0)
{
MVGPrintf(svg_info->file,"text-antialias %d\n",
LocaleCompare(value,"true") == 0);
break;
}
if (LocaleCompare(keyword,"transform") == 0)
{
/* implement style="transform: translate(..." */
SVGProcessTransformString(svg_info,value);
break;
}
break;
}
default:
break;
}
}
}
if (tokens != (char **) NULL)
{
for (j=0; tokens[j] != (char *) NULL; j++)
MagickFreeMemory(tokens[j]);
MagickFreeMemory(tokens);
}
break;
}
break;
}
case 'T':
case 't':
{
if (LocaleCompare(keyword,"text-align") == 0)
{
MVGPrintf(svg_info->file,"text-align '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"text-anchor") == 0)
{
MVGPrintf(svg_info->file,"text-anchor '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"text-decoration") == 0)
{
if (LocaleCompare(value,"underline") == 0)
MVGPrintf(svg_info->file,"decorate underline\n");
if (LocaleCompare(value,"line-through") == 0)
MVGPrintf(svg_info->file,"decorate line-through\n");
if (LocaleCompare(value,"overline") == 0)
MVGPrintf(svg_info->file,"decorate overline\n");
break;
}
if (LocaleCompare(keyword,"text-antialiasing") == 0)
{
MVGPrintf(svg_info->file,"text-antialias %d\n",
LocaleCompare(value,"true") == 0);
break;
}
if (LocaleCompare(keyword,"transform") == 0)
{
/*
The code that was here has been refactored into
function SVGProcessTransformString()
*/
SVGProcessTransformString(svg_info,value);
break;
}
break;
}
case 'V':
case 'v':
{
if (LocaleCompare(keyword,"verts") == 0)
{
(void) CloneString(&svg_info->vertices,value);
break;
}
if (LocaleCompare(keyword,"viewBox") == 0)
{
p=(char *) value;
(void) MagickGetToken(p,&p,token,MaxTextExtent);
svg_info->view_box.x=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
svg_info->view_box.y=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
svg_info->view_box.width=MagickAtoF(token);
(void) MagickGetToken(p,&p,token,MaxTextExtent);
if (*token == ',')
(void) MagickGetToken(p,&p,token,MaxTextExtent);
svg_info->view_box.height=MagickAtoF(token);
if (svg_info->view_box.width < 0.0 ||
svg_info->view_box.height < 0.0)
{
ThrowException(svg_info->exception,CorruptImageError,
NegativeOrZeroImageSize,
svg_info->image->filename);
goto svg_start_element_error;
}
if (svg_info->bounds.width == 0)
svg_info->bounds.width=svg_info->view_box.width;
if (svg_info->bounds.height == 0)
svg_info->bounds.height=svg_info->view_box.height;
break;
}
break;
}
case 'W':
case 'w':
{
if (LocaleCompare(keyword,"width") == 0)
{
svg_info->bounds.width=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue);
break;
}
break;
}
case 'X':
case 'x':
{
if (LocaleCompare(keyword,"x") == 0)
{
svg_info->bounds.x=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
/* update text current position for tspan (already did this if text) */
if ( IsTSpan )
MVGPrintf(svg_info->file,"textx %g\n",svg_info->bounds.x);
break;
}
if (LocaleCompare(keyword,"xlink:href") == 0)
{
/* process "#identifier" as if it were "url(#identifier)" */
if ( value[0] == '#' )
{
/* reallocate the needed memory once */
size_t NewSize = strlen(value) + 6; /* 6 == url()<null> */
MagickReallocMemory(char *,svg_info->url,NewSize);
strlcpy(svg_info->url,"url(",NewSize);
strlcat(svg_info->url,value,NewSize);
strlcat(svg_info->url,")",NewSize);
}
else
(void) CloneString(&svg_info->url,value);
break;
}
if (LocaleCompare(keyword,"x1") == 0)
{
svg_info->segment.x1=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"x2") == 0)
{
svg_info->segment.x2=
GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
break;
}
break;
}
case 'Y':
case 'y':
{
if (LocaleCompare(keyword,"y") == 0)
{
svg_info->bounds.y=GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
/* update text current position for tspan (already did this if text) */
if ( IsTSpan )
MVGPrintf(svg_info->file,"texty %g\n",svg_info->bounds.y);
break;
}
if (LocaleCompare(keyword,"y1") == 0)
{
svg_info->segment.y1=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
if (LocaleCompare(keyword,"y2") == 0)
{
svg_info->segment.y2=
GetUserSpaceCoordinateValue(svg_info,-1,value,MagickFalse);
break;
}
break;
}
default:
break;
}
}
if (svg_info->exception->severity >= ErrorException)
goto svg_start_element_error;
#ifdef BROKEN_SIZE_OPTION
if (LocaleCompare((char *) name,"svg") == 0)
{
if (parser->encoding != (const xmlChar *) NULL)
MVGPrintf(svg_info->file,"encoding %.1024s\n",
(const char *) parser->encoding);
if (attributes != (const xmlChar **) NULL)
{
double
sx,
sy;
if ((svg_info->view_box.width == 0.0) ||
(svg_info->view_box.height == 0.0))
svg_info->view_box=svg_info->bounds;
svg_info->width=(unsigned long) svg_info->bounds.width;
svg_info->height=(unsigned long) svg_info->bounds.height;
MVGPrintf(svg_info->file,"viewbox 0 0 %lu %lu\n",svg_info->width,
svg_info->height);
/*
Set initial canvas background color to user specified value
*/
{
char
tuple[MaxTextExtent];
GetColorTuple(&svg_info->image_info->background_color,8,True,True,
tuple);
MVGPrintf(svg_info->file,"push graphic-context\n");
MVGPrintf(svg_info->file,"fill %s\n", tuple);
MVGPrintf(svg_info->file,"rectangle 0,0 %g,%g\n",
svg_info->view_box.width,svg_info->view_box.height);
MVGPrintf(svg_info->file,"pop graphic-context\n");
}
sx=(double) svg_info->width/svg_info->view_box.width;
sy=(double) svg_info->height/svg_info->view_box.height;
MVGPrintf(svg_info->file,"affine %g 0 0 %g %g %g\n",sx,sy,
-sx*svg_info->view_box.x,-sy*svg_info->view_box.y);
}
}
#else
if (LocaleCompare((char *) name,"svg") == 0)
{
/* if (svg_info->document->encoding != (const xmlChar *) NULL) */
/* (void) fprintf(svg_info->file,"encoding %.1024s\n", */
/* (char *) svg_info->document->encoding); */
if (attributes != (const xmlChar **) NULL)
{
char
*geometry;
RectangleInfo
page;
double
sx,
sy;
if (svg_info->bounds.width < MagickEpsilon ||
svg_info->bounds.height < MagickEpsilon)
{
ThrowException(svg_info->exception,CorruptImageError,
NegativeOrZeroImageSize,(char *) NULL);
goto svg_start_element_error;
}
if ((svg_info->view_box.width == 0.0) ||
(svg_info->view_box.height == 0.0))
svg_info->view_box=svg_info->bounds;
if (svg_info->view_box.width < MagickEpsilon ||
svg_info->view_box.height < MagickEpsilon)
{
ThrowException(svg_info->exception,CorruptImageError,
NegativeOrZeroImageSize,(char *) NULL);
goto svg_start_element_error;
}
SetGeometry(svg_info->image,&page);
page.width=(unsigned long) svg_info->bounds.width;
page.height=(unsigned long) svg_info->bounds.height;
geometry=(char *) NULL;
/* at one point we use to try to use either page geometry
or size to set the dimensions of the output page, but
now we only look for size
*/
#ifdef PARSE_PAGE_FIRST
if (svg_info->page != (char *) NULL)
geometry=GetPageGeometry(svg_info->page);
else
#endif
if (svg_info->size != (char *) NULL)
geometry=GetPageGeometry(svg_info->size);
if (geometry != (char *) NULL)
{
/*
A terminating '>' in a geometry string is interpreted to mean that
the dimensions of an image should only be changed if its width or
height exceeds the geometry specification. For an unapparent and
undocumented reason, a terminating '>', if present, was being nulled
out, making this feature unusable for SVG files (now fixed).
*/
(void) GetMagickGeometry(geometry,&page.x,&page.y,
&page.width,&page.height);
MagickFreeMemory(geometry);
}
/* NOTE: the scale factor returned by ExpandAffine() has already been applied
to page.width and page.height
*/
(void) MVGPrintf(svg_info->file,"viewbox 0 0 %g %g\n",
svg_info->view_box.width,svg_info->view_box.height);
sx=(double) page.width/svg_info->view_box.width;
sy=(double) page.height/svg_info->view_box.height;
MVGPrintf(svg_info->file,"affine %g 0 0 %g %g %g\n",sx,sy,
-sx*svg_info->view_box.x,-sy*svg_info->view_box.y);
/* only set the output width and height if this is the outermost <svg> */
if ( svg_info->svgPushCount == 1 )
{/*outermost <svg>*/
svg_info->width=page.width;
svg_info->height=page.height;
/* check if background color was specified using <svg ... style="background:color" */
if ( svg_element_background_color[0] != '\0' )
{
MVGPrintf(svg_info->file,"push graphic-context\n");
MVGPrintf(svg_info->file,"fill %s\n",svg_element_background_color);
MVGPrintf(svg_info->file,"rectangle 0,0 %g,%g\n",svg_info->view_box.width,svg_info->view_box.height);
MVGPrintf(svg_info->file,"pop graphic-context\n");
}
}/*outermost <svg>*/
}
}
#endif
/* Error dispatch point */
svg_start_element_error:;
(void) LogMagickEvent(CoderEvent,GetMagickModule()," )");
MagickFreeMemory(units);
MagickFreeMemory(color);
}
/* Process the "class" definitions inside <style> ... </style> */
static
void ProcessStyleClassDefs (
SVGInfo * svg_info
)
{/*ProcessStyleClassDefs*/
/*
Style defs look like:
.class1,.class2,.class3{kwd:val;kwd:val;}
Class name (e.g., ".class1"} can show up multiple times
*/
/* keyword/value pair for an element */
typedef struct ElementValue
{/*ElementValue*/
struct ElementValue * pNext; /* next in list */
char * pKeyword;
char * pValue;
}/*ElementValue*/
ElementValue;
/* the keyword/value pair list associated with a class */
typedef struct ClassDef
{/*ClassDef*/
struct ClassDef * pNext; /* next in list */
struct ClassDef * pActiveNext; /* next in active list */
char * pName; /* class name */
ElementValue ElementValueHead; /* list head for style element/value pairs */
ElementValue * pElementValueLast;
}/*ClassDef*/
ClassDef;
ClassDef ClassDefHead; /* list head for classes */
ClassDef * pClassDefLast = &ClassDefHead; /* tail ptr for class def list */
ClassDef ClassDefActiveHead; /* list head for "active" class defs */
ClassDef * pClassDefActiveLast = &ClassDefActiveHead; /* tail ptr for "active" class def list */
ClassDef * pClassDef;
char * pCopyOfText;
char * pString;
char * cp,*cp2;
int c;
memset(&ClassDefHead,0,sizeof(ClassDefHead));
memset(&ClassDefActiveHead,0,sizeof(ClassDefActiveHead));
/* a macro to allocate an zero out a new struct instance,
and add it to the end of a linked list */
/* FIXME: Does not deal with allocation failure! */
#define ADD_NEW_STRUCT(pNew,pLast,TheTypeDef) \
pNew = MagickAllocateClearedMemory(TheTypeDef *,sizeof(TheTypeDef)); \
pLast = pLast->pNext = pNew
/* we will get a modifiable value of the string, and delimit
substrings by storing null characters */
pCopyOfText = AcquireString(svg_info->text);
for ( pString = pCopyOfText; *pString; )
{/*pString loop*/
char * pClassNameList;
char * pStyleElementList;
while ( (c = *pString) && isspace(c) ) pString++; /* skip white space */
if ( !*pString ) break; /* found end of string; done */
pClassNameList = pString;
/* find the end of the comma-separated list of class names */
while ( (c = *pString) && (c != '{') ) pString++;
if ( !*pString )
{
/* malformed input: class name list not followed by '{' */
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Malformed input: class name list not followed by '{");
goto process_style_class_defs_abort;
return;
}
*pString++ = '\0';
pStyleElementList = pString;
/* extract the class names */
ClassDefActiveHead.pActiveNext = 0;
pClassDefActiveLast = &ClassDefActiveHead; /* initially, no active class defs */
for ( cp = pClassNameList; *cp; )
{/*extract class name loop*/
while ( (c = *cp) && (isspace(c) || (c ==',')) ) cp++; /* skip white space/commas */
if ( *cp == '.' ) cp++; /* .classname, skip leading period */
if ( *cp )
{/*found class name*/
char * pClassName = cp;
while ( (c = *cp) && !(isspace(c) || (c == ',')) ) cp++; /* find white space/comma/null */
if ( *cp )
*cp++ = '\0'; /* terminate identifier string and increment */
#if 0
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Found ClassName: \"%s\"", pClassName);
#endif
/* add uniquely to list */
for ( pClassDef = ClassDefHead.pNext;
pClassDef && (strcmp(pClassName,pClassDef->pName) != 0);
pClassDef = pClassDef->pNext );
if ( pClassDef == 0 )
{ /* new class name */
ADD_NEW_STRUCT(pClassDef,pClassDefLast,ClassDef);
pClassDef->pElementValueLast = &pClassDef->ElementValueHead;
pClassDef->pName = pClassName;
pClassDef->pActiveNext = 0;
pClassDefActiveLast = pClassDefActiveLast->pActiveNext = pClassDef; /* add to active list */
} /* new class name */
else
{ /* found on "all" list; if already on active list, ignore */
ClassDef * pClassDefActive;
for ( pClassDefActive = ClassDefActiveHead.pActiveNext;
pClassDefActive && (pClassDefActive != pClassDef);
pClassDefActive = pClassDefActive->pActiveNext );
if ( pClassDefActive == 0 ) /* did not find on active list */
{
pClassDef->pActiveNext = 0;
pClassDefActiveLast = pClassDefActiveLast->pActiveNext = pClassDef; /* add to active list */
#if 0
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Adding Active Class: \"%s\"", pClassDef->pName);
#endif
}
}/* found on "all" list; if already on active list, ignore */
}/*found class name*/
}/*extract class name loop*/
#if 0
/* verify lists */
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"=== Verifying full ClassDef list...");
for ( pClassDef = ClassDefHead.pNext;
pClassDef;
pClassDef = pClassDef->pNext )
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"%p Class %s", pClassDef, pClassDef->pName);
}
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Done Verifying full ClassDef list");
#endif
#if 0
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"=== Verifying active ClassDef list...");
for ( pClassDef = ClassDefActiveHead.pActiveNext;
pClassDef;
pClassDef = pClassDef->pActiveNext ) /* Seems this is supposed to be pActiveNext */
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"%p Class %s", pClassDef, pClassDef->pName);
}
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Done Verifying active ClassDef list");
#endif
/* find the end of the style elements */
while ( (c = *pString) && (c != '}') ) pString++;
if ( !*pString )
{
/* malformed input: style elements not terminated by '{' */
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Malformed input: style elements not terminated by '{'");
goto process_style_class_defs_abort;
}
*pString++ = '\0'; /* advance past '}' for next loop pass */
/* get the style elements */
for ( cp = pStyleElementList; *cp; )
{/*extract style elements loop*/
/* looking for <space><classname><space>: */
while ( (c = *cp) && isspace(c) ) cp++; /* skip white space */
if ( *cp )
{/*found style element*/
char * pStyleElement = cp;
while ( (c = *cp) && (c != ':') ) cp++; /* find colon/null */
for ( cp2 = cp-1; isspace((int) (*cp2)); *cp2-- = '\0'); /* trim white space */
if ( *cp )
*cp++ = '\0'; /* terminate style element string and increment */
/* looking for <space><style-value>; */
while ( (c = *cp) && isspace(c) ) cp++; /* skip white space */
if ( *cp )
{/*found style element value*/
char * pStyleValue = cp;
while ( (c = *cp) && (c != ';') ) cp++; /* find semi-colon/null */
for ( cp2 = cp-1; isspace((int) (*cp2)); *cp2-- = '\0'); /* trim white space */
if ( *cp )
*cp++ = '\0'; /* terminate style value string and increment */
/* add style element/value pair to each active class def */
for ( pClassDef = ClassDefActiveHead.pActiveNext;
pClassDef;
pClassDef = pClassDef->pActiveNext )
{
ElementValue * pEV;
ADD_NEW_STRUCT(pEV,pClassDef->pElementValueLast,ElementValue);
pEV->pKeyword = pStyleElement;
pEV->pValue = pStyleValue;
}
}/*found style element value*/
}/*found style element*/
}/*extract style elements loop*/
}/*pString loop*/
/* emit class definitions */
for ( pClassDef = ClassDefHead.pNext; pClassDef; pClassDef = pClassDef->pNext )
{/*pClassDef loop*/
ElementValue * pEV;
MVGPrintf(svg_info->file,"push class '%s'\n",pClassDef->pName);
/* NOTE: we allow class definitions that are empty */
for ( pEV = pClassDef->ElementValueHead.pNext; pEV; pEV = pEV->pNext )
{/*pEV loop*/
char * keyword = pEV->pKeyword;
char * value = pEV->pValue;
/* switch below was copied/pasted from elsewhere and modified */
switch (*keyword)
{/*keyword*/
case 'C':
case 'c':
{/*Cc*/
if (LocaleCompare(keyword,"clip-path") == 0)
{
MVGPrintf(svg_info->file,"clip-path '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"clip-rule") == 0)
{
MVGPrintf(svg_info->file,"clip-rule '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"clipPathUnits") == 0)
{
MVGPrintf(svg_info->file,"clip-units '%s'\n",value);
break;
}
break;
}/*Cc*/
case 'F':
case 'f':
{/*Ff*/
if ( (LocaleCompare(keyword,"fill") == 0) || (LocaleCompare(keyword,"fillcolor") == 0) )
{
MVGPrintf(svg_info->file,"fill '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"fill-rule") == 0)
{
MVGPrintf(svg_info->file,"fill-rule '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"fill-opacity") == 0)
{
MVGPrintf(svg_info->file,"fill-opacity '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-family") == 0)
{
/*
Deal with Adobe Illustrator 10.0 which double-quotes
font-family. Maybe we need a generalized solution for
this.
*/
size_t n;
if ( (value[0] == '\'') && (value[(n = (strlen(value)-1))] == '\'') )
{
size_t i;
FILE * fp = svg_info->file;
MVGPrintf(fp,"font-family '");
for(i = 1; i < n; i++)
fputc(value[i],fp);
MVGPrintf(fp,"'\n");
}
else
MVGPrintf(svg_info->file,"font-family '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-stretch") == 0)
{
MVGPrintf(svg_info->file,"font-stretch '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-style") == 0)
{
MVGPrintf(svg_info->file,"font-style '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"font-size") == 0)
{
if (LocaleCompare(value,"medium") == 0)
svg_info->pointsize = 12;
else
svg_info->pointsize = GetUserSpaceCoordinateValue(svg_info,0,value,MagickTrue);
MVGPrintf(svg_info->file,"font-size %g\n",svg_info->pointsize);
break;
}
if (LocaleCompare(keyword,"font-weight") == 0)
{
MVGPrintf(svg_info->file,"font-weight '%s'\n",value);
break;
}
break;
}/*Ff*/
case 'O':
case 'o':
{/*Oo*/
if (LocaleCompare(keyword,"offset") == 0)
{
MVGPrintf(svg_info->file,"offset %g\n",GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse));
break;
}
if (LocaleCompare(keyword,"opacity") == 0)
{
MVGPrintf(svg_info->file,"opacity '%s'\n",value);
break;
}
break;
}/*Oo*/
case 'S':
case 's':
{/*Ss*/
if (LocaleCompare(keyword,"stop-color") == 0)
{
(void) CloneString(&svg_info->stop_color,value);
break;
}
if (LocaleCompare(keyword,"stroke") == 0)
{
MVGPrintf(svg_info->file,"stroke '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-antialiasing") == 0)
{
MVGPrintf(svg_info->file,"stroke-antialias %d\n",LocaleCompare(value,"true") == 0);
break;
}
if (LocaleCompare(keyword,"stroke-dasharray") == 0)
{
MVGPrintf(svg_info->file,"stroke-dasharray %s\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-dashoffset") == 0)
{
double dashoffset=GetUserSpaceCoordinateValue(svg_info,1,value,MagickFalse);
MVGPrintf(svg_info->file,"stroke-dashoffset %g\n",dashoffset);
break;
}
if (LocaleCompare(keyword,"stroke-linecap") == 0)
{
MVGPrintf(svg_info->file,"stroke-linecap '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-linejoin") == 0)
{
MVGPrintf(svg_info->file,"stroke-linejoin '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-miterlimit") == 0)
{
double stroke_miterlimit;
if ((MagickAtoFChk(value,&stroke_miterlimit) != MagickPass) || stroke_miterlimit < 1.0)
{
errno=0;
ThrowException(svg_info->exception,DrawError,InvalidPrimitiveArgument,value);
break;
}
MVGPrintf(svg_info->file,"stroke-miterlimit '%ld'\n",(long) stroke_miterlimit);
break;
}
if (LocaleCompare(keyword,"stroke-opacity") == 0)
{
MVGPrintf(svg_info->file,"stroke-opacity '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"stroke-width") == 0)
{
MVGPrintf(svg_info->file,"stroke-width %g\n",GetUserSpaceCoordinateValue(svg_info,1,value,MagickTrue));
break;
}
break;
}/*Ss*/
case 'T':
case 't':
{/*Tt*/
if (LocaleCompare(keyword,"text-align") == 0)
{
MVGPrintf(svg_info->file,"text-align '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"text-anchor") == 0)
{
MVGPrintf(svg_info->file,"text-anchor '%s'\n",value);
break;
}
if (LocaleCompare(keyword,"text-decoration") == 0)
{
if (LocaleCompare(value,"underline") == 0)
MVGPrintf(svg_info->file,"decorate underline\n");
if (LocaleCompare(value,"line-through") == 0)
MVGPrintf(svg_info->file,"decorate line-through\n");
if (LocaleCompare(value,"overline") == 0)
MVGPrintf(svg_info->file,"decorate overline\n");
break;
}
if (LocaleCompare(keyword,"text-antialiasing") == 0)
{
MVGPrintf(svg_info->file,"text-antialias %d\n",LocaleCompare(value,"true") == 0);
break;
}
if (LocaleCompare(keyword,"transform") == 0)
{/*style="transform: ...*/
SVGProcessTransformString(svg_info,value);
break;
}/*style="transform: ...*/
break;
}/*Tt*/
default:
break;
}/*keyword*/
}/*pEV loop*/
MVGPrintf(svg_info->file,"pop class\n");
}/*pClassDef loop*/
/* clean up */
process_style_class_defs_abort:;
{
ClassDef * pClassDef;
for(pClassDef = ClassDefHead.pNext; pClassDef; )
{
ElementValue *pEV;
ClassDef * pClassDefTemp = pClassDef;
for(pEV = pClassDef->ElementValueHead.pNext; pEV; )
{
ElementValue * pEVTemp = pEV;
pEV = pEV->pNext;
MagickFreeMemory(pEVTemp);
}
pClassDef = pClassDef->pNext;
MagickFreeMemory(pClassDefTemp);
}
}
MagickFreeMemory(pCopyOfText);
#undef ADD_NEW_STRUCT
}/*ProcessStyleClassDefs*/
static void
SVGEndElement(void *context,const xmlChar *name)
{
SVGInfo
*svg_info;
xmlParserCtxtPtr
parser;
/*
Called when the end of an element has been detected.
*/
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" SAX.endElement(%.1024s)",name);
parser=(xmlParserCtxtPtr) context;
svg_info=(SVGInfo *) parser->_private;
assert(svg_info->signature == MagickSignature);
if (strchr((char *) name,':') != (char *) NULL)
{
/*
Skip over namespace.
*/
for ( ; *name != ':'; name++) ;
name++;
}
switch (*name)
{
case 'C':
case 'c':
{
if (LocaleCompare((char *) name,"circle") == 0)
{
MVGPrintf(svg_info->file,"circle %g,%g %g,%g\n",svg_info->element.cx,
svg_info->element.cy,svg_info->element.cx,svg_info->element.cy+
svg_info->element.minor);
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
if (LocaleCompare((char *) name,"clipPath") == 0)
{
MVGPrintf(svg_info->file,"pop clip-path\n");
break;
}
break;
}
case 'D':
case 'd':
{
if (LocaleCompare((char *) name,"defs") == 0)
{
svg_info->defsPushCount--;
MVGPrintf(svg_info->file,"pop defs\n");
break;
}
if (LocaleCompare((char *) name,"desc") == 0)
{
register char
*p;
svg_info->text_len=MagickStripString(svg_info->text);
if (*svg_info->text == '\0')
break;
(void) fputc('#',svg_info->file);
for (p=svg_info->text; *p != '\0'; p++)
{
(void) fputc(*p,svg_info->file);
if (*p == '\n')
(void) fputc('#',svg_info->file);
}
(void) fputc('\n',svg_info->file);
*svg_info->text='\0';
svg_info->text_len=strlen(svg_info->text);
break;
}
break;
}
case 'E':
case 'e':
{
if (LocaleCompare((char *) name,"ellipse") == 0)
{
double
angle;
angle=svg_info->element.angle;
MVGPrintf(svg_info->file,"ellipse %g,%g %g,%g 0,360\n",
svg_info->element.cx,svg_info->element.cy,
angle == 0.0 ? svg_info->element.major : svg_info->element.minor,
angle == 0.0 ? svg_info->element.minor : svg_info->element.major);
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
break;
}
case 'F':
case 'f':
{
/*
For now we are ignoring "foreignObject". However, we do a push/pop
graphic-context so that any settings (e.g., fill) are consumed and
discarded. Otherwise they might persist and "leak" into the graphic
elements that follow.
*/
if (LocaleCompare((char *) name,"foreignObject") == 0)
{
MVGPrintf(svg_info->file,"pop graphic-context\n");
break;
}
break;
}
case 'G':
case 'g':
{
if (LocaleCompare((char *) name,"g") == 0)
{
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
break;
}
case 'I':
case 'i':
{
if (LocaleCompare((char *) name,"image") == 0)
{
MVGPrintf(svg_info->file,"image Copy %g,%g %g,%g '%s'\n",
svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.width,
svg_info->bounds.height,svg_info->url);
MVGPrintf(svg_info->file,"pop graphic-context\n");
break;
}
break;
}
case 'L':
case 'l':
{
if (LocaleCompare((char *) name,"line") == 0)
{
MVGPrintf(svg_info->file,"line %g,%g %g,%g\n",svg_info->segment.x1,
svg_info->segment.y1,svg_info->segment.x2,svg_info->segment.y2);
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
if (LocaleCompare((char *) name,"linearGradient") == 0)
{
MVGPrintf(svg_info->file,"pop gradient\n");
break;
}
break;
}
case 'M':
case 'm':
{
if (LocaleCompare((char *) name,"mask") == 0) /* added mask */
{
MVGPrintf(svg_info->file,"pop mask\n");
break;
}
break;
}
case 'P':
case 'p':
{
if (LocaleCompare((char *) name,"pattern") == 0)
{
MVGPrintf(svg_info->file,"pop pattern\n");
break;
}
if (LocaleCompare((char *) name,"path") == 0)
{
MVGPrintf(svg_info->file,"path '%s'\n",svg_info->vertices);
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
if (LocaleCompare((char *) name,"polygon") == 0)
{
MVGPrintf(svg_info->file,"polygon %s\n",svg_info->vertices);
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
if (LocaleCompare((char *) name,"polyline") == 0)
{
MVGPrintf(svg_info->file,"polyline %s\n",svg_info->vertices);
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
break;
}
case 'R':
case 'r':
{
if (LocaleCompare((char *) name,"radialGradient") == 0)
{
MVGPrintf(svg_info->file,"pop gradient\n");
break;
}
if (LocaleCompare((char *) name,"rect") == 0)
{
if ((svg_info->radius.x == 0.0) && (svg_info->radius.y == 0.0))
{
MVGPrintf(svg_info->file,"rectangle %g,%g %g,%g\n",
svg_info->bounds.x,svg_info->bounds.y,
svg_info->bounds.x+svg_info->bounds.width,
svg_info->bounds.y+svg_info->bounds.height);
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
if (svg_info->radius.x == 0.0)
svg_info->radius.x=svg_info->radius.y;
if (svg_info->radius.y == 0.0)
svg_info->radius.y=svg_info->radius.x;
MVGPrintf(svg_info->file,"roundRectangle %g,%g %g,%g %g,%g\n",
svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.x+
svg_info->bounds.width,svg_info->bounds.y+svg_info->bounds.height,
svg_info->radius.x,svg_info->radius.y);
svg_info->radius.x=0.0;
svg_info->radius.y=0.0;
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
break;
}
case 'S':
case 's':
{
if (LocaleCompare((char *) name,"stop") == 0)
{
MVGPrintf(svg_info->file,"stop-color '%s' %s\n",svg_info->stop_color,
svg_info->offset);
break;
}
/* element "style" inside <defs> */
if (LocaleCompare((char *) name,"style") == 0)
{
/* the style definitions are in svg_info->text */
ProcessStyleClassDefs(svg_info);
break;
}
if (LocaleCompare((char *) name,"svg") == 0)
{
svg_info->svgPushCount--;
MVGPrintf(svg_info->file,"pop graphic-context\n");
break;
}
break;
}
case 'T':
case 't':
{
if (LocaleCompare((char *) name,"text") == 0)
{
svg_info->text_len=MagickStripString(svg_info->text);
if (*svg_info->text != '\0')
{
char
*text;
text=EscapeString(svg_info->text,'\'');
MVGPrintf(svg_info->file,"textc '%s'\n",text); /* write text at current position */
MagickFreeMemory(text);
*svg_info->text='\0';
svg_info->text_len=strlen(svg_info->text);
}
MVGPrintf(svg_info->file,"pop graphic-context\n");
if ( svg_info->idLevelInsideDefs == svg_info->n ) /* emit a "pop id" if warranted */
{
svg_info->idLevelInsideDefs = 0;
MVGPrintf(svg_info->file,"pop id\n");
}
break;
}
if (LocaleCompare((char *) name,"tspan") == 0)
{
svg_info->text_len=MagickStripString(svg_info->text);
if (*svg_info->text != '\0')
{
char
*text;
text=EscapeString(svg_info->text,'\'');
MVGPrintf(svg_info->file,"textc '%s'\n",text); /* write text at current position */
MagickFreeMemory(text);
/*
The code that used to be here to compute the next text position has been eliminated.
The reason is that at this point in the code we may not know the font or font size
(they may be hidden in a "class" definition), so we can't really do that computation.
This functionality is now handled by DrawImage() in render.c.
*/
*svg_info->text='\0';
svg_info->text_len=strlen(svg_info->text);
}
MVGPrintf(svg_info->file,"pop graphic-context\n");
break;
}
if (LocaleCompare((char *) name,"title") == 0)
{
svg_info->text_len=MagickStripString(svg_info->text);
if (*svg_info->text == '\0')
break;
(void) CloneString(&svg_info->title,svg_info->text);
*svg_info->text='\0';
svg_info->text_len=strlen(svg_info->text);
break;
}
break;
}
case 'U':
case 'u':
{
if (LocaleCompare((char *) name,"use") == 0)
{
/*
If the "use" had a "transform" attribute it has already been output to the MVG file.
According to the SVG spec for "use":
In the generated content, the 'use' will be replaced by 'g', where all attributes
from the 'use' element except for 'x', 'y', 'width', 'height' and 'xlink:href' are
transferred to the generated 'g' element. An additional transformation translate(x,y)
is appended to the end (i.e., right-side) of the 'transform' attribute on the generated
'g', where x and y represent the values of the 'x' and 'y' attributes on the 'use'
element. The referenced object and its contents are deep-cloned into the generated tree.
*/
if ( (svg_info->bounds.x != 0.0) || (svg_info->bounds.y != 0.0) )
MVGPrintf(svg_info->file,"translate %g,%g\n",svg_info->bounds.x,svg_info->bounds.y);
/* NOTE: not implementing "width" and "height" for now */
MVGPrintf(svg_info->file,"use '%s'\n",svg_info->url);
MVGPrintf(svg_info->file,"pop graphic-context\n");
break;
}
break;
}
default:
break;
}
(void) memset(&svg_info->segment,0,sizeof(svg_info->segment));
(void) memset(&svg_info->element,0,sizeof(svg_info->element));
*svg_info->text='\0';
svg_info->text_len=strlen(svg_info->text);
svg_info->n--;
}
static void
SVGCharacters(void *context,const xmlChar *c,int length)
{
SVGInfo
*svg_info;
xmlParserCtxtPtr
parser;
char
*p;
char
*new_text;
register int
i;
size_t
new_text_len;
/*
Receiving some characters from the parser.
*/
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" SAX.characters(%.1024s,%d)",c,length);
parser=(xmlParserCtxtPtr) context;
svg_info=(SVGInfo *) parser->_private;
assert(svg_info->signature == MagickSignature);
new_text_len = svg_info->text_len+length;
new_text = MagickReallocateResourceLimitedMemory(char *,svg_info->text,new_text_len+1);
if (new_text != NULL)
{
svg_info->text = new_text;
}
else
{
/* There was a problem! Retain existing buffer. */
return;
}
if (svg_info->text == (char *) NULL)
return;
p=svg_info->text+svg_info->text_len;
for (i=0; i < length; i++)
*p++=c[i];
*p='\0';
svg_info->text_len = new_text_len;
}
static void
SVGComment(void *context,const xmlChar *value)
{
SVGInfo
*svg_info;
xmlParserCtxtPtr
parser;
char
*p;
char
*new_comment;
size_t
i;
size_t
new_comment_len,
length;
/*
A comment has been parsed.
*/
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" SAX.comment(%.1024s)",value);
parser=(xmlParserCtxtPtr) context;
svg_info=(SVGInfo *) parser->_private;
assert(svg_info->signature == MagickSignature);
/*
Old way concatenated all comments like this:
if (svg_info->comment != (char *) NULL)
(void) ConcatenateString(&svg_info->comment,"\n");
(void) ConcatenateString(&svg_info->comment,(char *) value);
*/
for (length = 0; value[length] != 0; length++)
;
new_comment_len = svg_info->comment_len+length+(svg_info->comment_len ? 1 : 0);
/* Cap the maximum collected comment size */
if (new_comment_len > 4*MaxTextExtent)
return;
new_comment = MagickReallocateResourceLimitedMemory(char *,svg_info->comment,new_comment_len+1);
if (new_comment != NULL)
{
svg_info->comment = new_comment;
}
else
{
/* There was a problem! Retain existing buffer. */
return;
}
if (svg_info->comment == (char *) NULL)
return;
p=svg_info->comment+svg_info->comment_len;
if (svg_info->comment_len)
{
/* Add a new-line when appending */
*p='\n';
p++;
}
for (i=0; i < length; i++)
*p++=value[i];
*p='\0';
svg_info->comment_len = new_comment_len;
}
static void
SVGWarning(void *context,const char *format,...) MAGICK_FUNC_PRINTF_FORMAT(2,3);
static void
SVGWarning(void *context,const char *format,...)
{
char
reason[MaxTextExtent];
SVGInfo
*svg_info;
xmlParserCtxtPtr
parser;
va_list
operands;
/**
Display and format a warning messages, gives file, line, position and
extra parameters.
*/
va_start(operands,format);
parser=(xmlParserCtxtPtr) context;
svg_info=(SVGInfo *) parser->_private;
assert(svg_info->signature == MagickSignature);
(void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.warning: ");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),format,operands);
#if !defined(HAVE_VSNPRINTF)
(void) vsprintf(reason,format,operands);
#else
(void) vsnprintf(reason,MaxTextExtent,format,operands);
#endif
ThrowException2(svg_info->exception,CoderWarning,reason,(char *) NULL);
va_end(operands);
}
static void
SVGError(void *context,const char *format,...) MAGICK_FUNC_PRINTF_FORMAT(2,3);
static void
SVGError(void *context,const char *format,...)
{
char
reason[MaxTextExtent];
SVGInfo
*svg_info;
xmlParserCtxtPtr
parser;
va_list
operands;
/*
Display and format a error formats, gives file, line, position and
extra parameters.
*/
va_start(operands,format);
parser=(xmlParserCtxtPtr) context;
svg_info=(SVGInfo *) parser->_private;
assert(svg_info->signature == MagickSignature);
(void) LogMagickEvent(CoderEvent,GetMagickModule()," SAX.error: ");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),format,operands);
#if !defined(HAVE_VSNPRINTF)
(void) vsprintf(reason,format,operands);
#else
(void) vsnprintf(reason,MaxTextExtent,format,operands);
#endif
ThrowException2(svg_info->exception,CoderError,reason,(char *) NULL);
va_end(operands);
xmlStopParser(parser);
}
static Image *
ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
{
char
filename[MaxTextExtent],
geometry[MaxTextExtent],
message[MaxTextExtent];
FILE
*file;
Image
*image;
size_t
n = 0;
SVGInfo
svg_info;
unsigned int
status;
xmlSAXHandler
SAXModules;
xmlSAXHandlerPtr
SAXHandler;
xmlParserCtxtPtr
parser;
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickSignature);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
filename[0] = '\0';
geometry[0] = '\0';
message[0] = '\0';
/*
Libxml initialization. We call it here since initialization can
be expensive and we don't know when/if libxml will be
needed. Should normally be called at program start-up but may be
called several times since libxml uses a flag to know if it has
already been initialized. When libxml2 is built to support
threads, it tests if it is already initialized under a lock and
holds a lock while it is being initialized so calling this
function from multiple threads is ok.
*/
xmlInitParser();
/*
Open image file.
*/
image=AllocateImage(image_info);
/*
If there is a geometry string in image_info->size (e.g., gm convert
-size "50x50%" in.svg out.png), AllocateImage() sets image->columns
and image->rows to the width and height values from the size string.
However, this makes no sense if the size string was something like
"50x50%" (we'll get columns = rows = 50). So we set columns and
rows to 0 here, which is the same as if no size string was supplied
by the client. This also results in svg_info.bounds to be set to
0,0 below (i.e., unknown), so that svg_info.bounds will be set using
the image size information from either the svg "canvas" width/height
or from the viewbox. Later, variable "page" is set from
svg_info->bounds. Then the geometry string in image_info->size gets
applied to the (now known) "page" width and height when
SvgStartElement() calls GetMagickGeometry(), and the intended result
is obtained.
*/
image->columns = 0;
image->rows = 0;
status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
if (status == False)
ThrowReaderException(FileOpenError,UnableToOpenFile,image);
/*
Open draw file.
*/
file=AcquireTemporaryFileStream(filename,TextFileIOMode);
if (file == (FILE *) NULL)
ThrowReaderTemporaryFileException(filename);
/*
Parse SVG file.
*/
(void) memset(&svg_info,0,sizeof(SVGInfo));
svg_info.file=file;
svg_info.exception=exception;
svg_info.image=image;
svg_info.image_info=image_info;
svg_info.text=MagickAllocateResourceLimitedMemory(char *, MaxTextExtent);
svg_info.scale=MagickAllocateMemory(double *,sizeof(double));
if ((svg_info.text == (char *) NULL) || (svg_info.scale == (double *) NULL))
{
(void) fclose(file);
(void) LiberateTemporaryFile(filename);
MagickFreeResourceLimitedMemory(svg_info.text);
MagickFreeMemory(svg_info.scale);
ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
}
svg_info.text[0] = '\0';
svg_info.text_len=strlen(svg_info.text);
IdentityAffine(&svg_info.affine);
svg_info.affine.sx=
image->x_resolution == 0.0 ? 1.0 : image->x_resolution/72.0;
svg_info.affine.sy=
image->y_resolution == 0.0 ? 1.0 : image->y_resolution/72.0;
svg_info.scale[0]=ExpandAffine(&svg_info.affine);
svg_info.bounds.width=image->columns;
svg_info.bounds.height=image->rows;
svg_info.defsPushCount = 0;
svg_info.idLevelInsideDefs = 0;
svg_info.svgPushCount = 0;
if (image_info->size != (char *) NULL)
(void) CloneString(&svg_info.size,image_info->size);
svg_info.signature=MagickSignature;
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"begin SAX");
/*
TODO: Upgrade to SAX version 2 (startElementNs/endElementNs)
*/
xmlSAXVersion(&SAXModules,1);
SAXModules.startElement=SVGStartElement;
SAXModules.endElement=SVGEndElement;
SAXModules.reference=(referenceSAXFunc) NULL;
SAXModules.characters=SVGCharacters;
SAXModules.ignorableWhitespace=(ignorableWhitespaceSAXFunc) NULL;
SAXModules.processingInstruction=(processingInstructionSAXFunc) NULL;
SAXModules.comment=SVGComment;
SAXModules.warning=SVGWarning;
SAXModules.error=SVGError;
SAXModules.fatalError=SVGError;
SAXModules.cdataBlock=SVGCharacters;
SAXHandler=(&SAXModules);
parser=(xmlParserCtxtPtr) NULL;
/*
https://gnome.pages.gitlab.gnome.org/libxml2/html/parser_8h.html#xmlCreatePushParserCtxt
*/
parser=xmlCreatePushParserCtxt(SAXHandler, /* a SAX handler (optional) */
(void *) NULL, /* user data for SAX callbacks (optional) */
(void *) NULL, /* initial chunk (optional, deprecated) */
0, /* size of initial chunk in bytes */
image->filename /* file name or URI (optional) */
);
if (parser == (xmlParserCtxtPtr) NULL)
{
ThrowException(exception,DrawError,UnableToDrawOnImage,
"Failed to push XML parser context");
}
if (parser != (xmlParserCtxtPtr) NULL)
{
/*
Enable substituting entity values in the output.
xmlCtxtUseOptions(svg_info.parser,XML_PARSE_NOENT) enables external ENTITY support
(e.g. SVGResolveEntity() which allows XML to be loaded from an
external source (url or local file). This may be a security
hazard if the input is not trustworthy or if connecting to the
correct source is not assured. If the XML is parsed on the
backside of a firewall then it may be able to access unintended
resources.
See "https://www.w3.org/TR/SVG11/svgdtd.html#DTD.1.16".
https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-parser.html#xmlParserOption
https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-parser.html#XML_PARSE_NOENT
(void) xmlCtxtUseOptions(svg_info.parser,XML_PARSE_NOENT);
*/
/*
Disable network access via XML_PARSE_NONET
(void) xmlCtxtUseOptions(svg_info.parser,XML_PARSE_NONET);
*/
parser->_private=(SVGInfo *) &svg_info;
while ((n=ReadBlob(image,MaxTextExtent-1,message)) != 0)
{
message[n]='\0';
/* fprintf(stderr,"SVG: %s\n", message); */
status=xmlParseChunk(parser,message,(int) n,False);
if (status != 0)
break;
/*
The following should not be needed since SVGError invokes
xmlStopParser() which should stop the parser by causing
xmlParseChunk() to return non-zero!
*/
/* Supported since libxml2 2.14.0 */
#if LIBXML_VERSION >= 21400
if (xmlCtxtIsStopped(parser))
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" xmlCtxtIsStopped");
status=1;
break;
}
#endif
if (svg_info.exception->severity >= ErrorException)
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" xmlStopParser failed to stop!");
status=1;
break;
}
}
if ((n > 0) && (status != 0))
(void) xmlParseChunk(parser,message,0,True);
/*
Assure that our private context is freed, even if we abort before
seeing the document end.
*/
/* SVGEndDocument(&svg_info); FIXME */
if (parser->myDoc != (xmlDocPtr) NULL)
{
xmlFreeDoc(parser->myDoc);
parser->myDoc = (xmlDocPtr) NULL;
}
/*
Free all the memory used by a parser context. However the parsed
document in ctxt->myDoc is not freed (so we just did that).
*/
xmlFreeParserCtxt(parser);
parser=(xmlParserCtxtPtr) NULL;
}
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"end SAX");
(void) fclose(file);
CloseBlob(image);
image->columns=svg_info.width;
image->rows=svg_info.height;
if (!image_info->ping && (exception->severity == UndefinedException))
{
ImageInfo
*clone_info;
/*
Draw image.
*/
DestroyImage(image);
image=(Image *) NULL;
clone_info=CloneImageInfo(image_info);
clone_info->blob=(_BlobInfoPtr_) NULL;
clone_info->length=0;
FormatString(geometry,"%ldx%ld",svg_info.width,svg_info.height);
(void) CloneString(&clone_info->size,geometry);
FormatString(clone_info->filename,"mvg:%.1024s",filename);
if (clone_info->density != (char *) NULL)
MagickFreeMemory(clone_info->density);
image=ReadImage(clone_info,exception);
DestroyImageInfo(clone_info);
if (image != (Image *) NULL)
(void) strlcpy(image->filename,image_info->filename,MaxTextExtent);
}
/*
Add/update image attributes
*/
if (image != (Image *) NULL)
{
/* Title */
if (svg_info.title != (char *) NULL)
(void) SetImageAttribute(image,"title",svg_info.title);
/* Comment */
if (svg_info.comment != (char *) NULL)
(void) SetImageAttribute(image,"comment",svg_info.comment);
}
/*
Free allocated resources from struct SVGInfo
*/
MagickFreeMemory(svg_info.size); /* char ptr */
MagickFreeMemory(svg_info.title); /* char ptr */
MagickFreeResourceLimitedMemory(svg_info.comment); /* char ptr */
MagickFreeMemory(svg_info.scale); /* double ptr */
MagickFreeMemory(svg_info.stop_color); /* char ptr */
MagickFreeMemory(svg_info.offset); /* char ptr */
MagickFreeResourceLimitedMemory(svg_info.text); /* char ptr */
MagickFreeMemory(svg_info.vertices); /* char ptr */
MagickFreeMemory(svg_info.url); /* char ptr */
(void) memset(&svg_info,0xbf,sizeof(SVGInfo));
(void) LiberateTemporaryFile(filename);
if (image != (Image *) NULL)
StopTimer(&image->timer);
return(image);
}
#endif
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e g i s t e r S V G I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Method RegisterSVGImage adds attributes for the SVG image format to
% the list of supported formats. The attributes include the image format
% tag, a method to read and/or write the format, whether the format
% supports the saving of more than one frame to the same file or blob,
% whether the format supports native in-memory I/O, and a brief
% description of the format.
%
% The SVG writer is disabled by default since it does not work.
%
% The format of the RegisterSVGImage method is:
%
% RegisterSVGImage(void)
%
*/
ModuleExport void
RegisterSVGImage(void)
{
#if defined(LIBXML_DOTTED_VERSION)
static const char
version[] = "XML " LIBXML_DOTTED_VERSION;
#define HAS_VERSION 1
#endif
MagickInfo
*entry;
entry=SetMagickInfo("SVG");
#if defined(HasXML)
entry->decoder=(DecoderHandler) ReadSVGImage;
#endif /* defined(HasXML) */
#if defined(ENABLE_SVG_WRITER) && ENABLE_SVG_WRITER
entry->encoder=(EncoderHandler) WriteSVGImage;
#endif /* if defined(ENABLE_SVG_WRITER) && ENABLE_SVG_WRITER */
entry->description="Scalable Vector Graphics";
#if defined(HAS_VERSION)
entry->version=version;
#endif
entry->module="SVG";
(void) RegisterMagickInfo(entry);
entry=SetMagickInfo("SVGZ");
#if defined(HasXML)
entry->decoder=(DecoderHandler) ReadSVGImage;
#endif /* defined(HasXML) */
#if defined(ENABLE_SVG_WRITER) && ENABLE_SVG_WRITER
entry->encoder=(EncoderHandler) WriteSVGImage;
#endif /* if defined(ENABLE_SVG_WRITER) && ENABLE_SVG_WRITER */
entry->description="Scalable Vector Graphics (ZIP compressed)";
#if defined(HAS_VERSION)
entry->version=version;
#endif
entry->module="SVG";
(void) RegisterMagickInfo(entry);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% U n r e g i s t e r S V G I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Method UnregisterSVGImage removes format registrations made by the
% SVG module from the list of supported formats.
%
% The format of the UnregisterSVGImage method is:
%
% UnregisterSVGImage(void)
%
*/
ModuleExport void
UnregisterSVGImage(void)
{
/*
Libxml clean-up. Should only be called just before exit().
*/
/* xmlCleanupParser(); */
(void) UnregisterMagickInfo("SVG");
(void) UnregisterMagickInfo("SVGZ");
}
#if defined(ENABLE_SVG_WRITER) && ENABLE_SVG_WRITER
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% W r i t e S V G I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Method WriteSVGImage writes a image in the SVG - XML based W3C standard
% format.
%
% The format of the WriteSVGImage method is:
%
% unsigned int WriteSVGImage(const ImageInfo *image_info,Image *image)
%
% A description of each parameter follows.
%
% o status: Method WriteSVGImage return True if the image is written.
% False is returned is there is a memory shortage or if the image file
% fails to write.
%
% o image_info: Specifies a pointer to a ImageInfo structure.
%
% o image: A pointer to an Image structure.
%
%
*/
#if defined(HasAUTOTRACE)
static unsigned int
WriteSVGImage(const ImageInfo *image_info,Image *image)
{
FILE
*output_file;
fitting_opts_type
fit_info;
image_header_type
image_header;
bitmap_type
bitmap;
ImageType
image_type;
int
j;
pixel_outline_list_type
pixels;
PixelPacket
p;
PixelPacket
*pixel;
QuantizeObj
*quantize_info;
spline_list_array_type
splines;
output_write
output_writer;
register long
i;
unsigned long
number_pixels,
number_planes,
point,
thin;
thin=False;
quantize_info=(QuantizeObj *) NULL;
pixel=(&p);
fit_info=new_fitting_opts();
output_writer=output_get_handler("svg");
if (output_writer == NULL)
ThrowWriterException(DelegateError,UnableToWriteSVGFormat,image);
image_type=GetImageType(image);
number_planes=3;
if ((image_type == BilevelType) || (image_type == GrayscaleType))
number_planes=1;
bitmap.np=number_planes;
bitmap.dimensions.width=image->columns;
bitmap.dimensions.height=image->rows;
number_pixels=image->columns*image->rows;
bitmap.bitmap=MagickAllocateMemory(unsigned char *,number_planes*number_pixels);
if (bitmap.bitmap == (unsigned char *) NULL)
ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
point=0;
for (j=0; j < image->rows; j++)
{
for (i=0; i < image->columns; i++)
{
p=AcquireOnePixel(image,i,j,&image->exception);
bitmap.bitmap[point++]=pixel->red;
if (number_planes == 3)
{
bitmap.bitmap[point++]=pixel->green;
bitmap.bitmap[point++]=pixel->blue;
}
}
}
image_header.width=DIMENSIONS_WIDTH(bitmap.dimensions);
image_header.height=DIMENSIONS_HEIGHT(bitmap.dimensions);
if ((fit_info.color_count > 0) && (BITMAP_PLANES(bitmap) == 3))
quantize(bitmap.bitmap,bitmap.bitmap,DIMENSIONS_WIDTH(bitmap.dimensions),
DIMENSIONS_HEIGHT(bitmap.dimensions),fit_info.color_count,
fit_info.bgColor,&quantize_info);
if (thin)
thin_image(&bitmap);
pixels=find_outline_pixels (bitmap);
MagickFreeMemory((bitmap.bitmap));
splines=fitted_splines(pixels,&fit_info);
output_file=fopen(image->filename,"w");
if (output_file == (FILE *) NULL)
ThrowWriterException(FileOpenError,UnableOpenFile,image);
output_writer(output_file,image->filename,0,0,image_header.width,
image_header.height,splines);
return(True);
}
#else
static void
AffineToTransform(Image *image,AffineMatrix *affine)
{
char
transform[MaxTextExtent];
if ((fabs(affine->tx) < MagickEpsilon) && (fabs(affine->ty) < MagickEpsilon))
{
if ((fabs(affine->rx) < MagickEpsilon) &&
(fabs(affine->ry) < MagickEpsilon))
{
if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
(fabs(affine->sy-1.0) < MagickEpsilon))
{
(void) WriteBlobString(image,"\">\n");
return;
}
FormatString(transform,"\" transform=\"scale(%g,%g)\">\n",
affine->sx,affine->sy);
(void) WriteBlobString(image,transform);
return;
}
else
{
if ((fabs(affine->sx-affine->sy) < MagickEpsilon) &&
(fabs(affine->rx+affine->ry) < MagickEpsilon) &&
(fabs(affine->sx*affine->sx+affine->rx*affine->rx-1.0) <
2*MagickEpsilon))
{
double
theta;
theta=(180.0/MagickPI)*atan2(affine->rx,affine->sx);
FormatString(transform,"\" transform=\"rotate(%g)\">\n",theta);
(void) WriteBlobString(image,transform);
return;
}
}
}
else
{
if ((fabs(affine->sx-1.0) < MagickEpsilon) &&
(fabs(affine->rx) < MagickEpsilon) &&
(fabs(affine->ry) < MagickEpsilon) &&
(fabs(affine->sy-1.0) < MagickEpsilon))
{
FormatString(transform,"\" transform=\"translate(%g,%g)\">\n",
affine->tx,affine->ty);
(void) WriteBlobString(image,transform);
return;
}
}
FormatString(transform,"\" transform=\"matrix(%g %g %g %g %g %g)\">\n",
affine->sx,affine->rx,affine->ry,affine->sy,affine->tx,affine->ty);
(void) WriteBlobString(image,transform);
}
static inline unsigned int
IsPoint(const char *point)
{
char
*p;
(void) strtol(point,&p,10);
return(p != point);
}
static unsigned int
WriteSVGImage(const ImageInfo *image_info,Image *image)
{
#define BezierQuantum 200
AffineMatrix
affine;
char
keyword[MaxTextExtent],
message[MaxTextExtent],
name[MaxTextExtent],
*p,
*q,
*token,
type[MaxTextExtent];
const ImageAttribute
*attribute;
int
n;
long
j;
PointInfo
point;
PrimitiveInfo
*primitive_info;
PrimitiveType
primitive_type;
register long
x;
register long
i;
size_t
length,
token_max_length;
SVGInfo
svg_info;
unsigned int
active,
status;
unsigned long
number_points;
/*
Open output image file.
*/
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickSignature);
assert(image != (Image *) NULL);
assert(image->signature == MagickSignature);
attribute=GetImageAttribute(image,"[MVG]");
if ((attribute == (ImageAttribute *) NULL) ||
(attribute->value == (char *) NULL))
ThrowWriterException(CoderError,NoImageVectorGraphics,image);
status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
if (status == False)
ThrowWriterException(FileOpenError,UnableToOpenFile,image);
/*
Write SVG header.
*/
(void) WriteBlobString(image,"<?xml version=\"1.0\" standalone=\"no\"?>\n");
(void) WriteBlobString(image,
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n");
(void) WriteBlobString(image,
" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n");
(void) FormatString(message,"<svg width=\"%lu\" height=\"%lu\">\n",
image->columns,image->rows);
(void) WriteBlobString(image,message);
/*
Allocate primitive info memory.
*/
number_points=2047;
primitive_info=MagickAllocateMemory(PrimitiveInfo *,
number_points*sizeof(PrimitiveInfo));
if (primitive_info == (PrimitiveInfo *) NULL)
ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
IdentityAffine(&affine);
token=AcquireString(attribute->value);
token_max_length=strlen(token);
active=False;
n=0;
status=True;
for (q=attribute->value; *q != '\0'; )
{
/*
Interpret graphic primitive.
*/
MagickGetToken(q,&q,keyword,MaxTextExtent);
if (*keyword == '\0')
break;
if (*keyword == '#')
{
/*
Comment.
*/
if (active)
{
AffineToTransform(image,&affine);
active=False;
}
(void) WriteBlobString(image,"<desc>");
(void) WriteBlobString(image,keyword+1);
for ( ; (*q != '\n') && (*q != '\0'); q++)
switch (*q)
{
case '<': (void) WriteBlobString(image,"<"); break;
case '>': (void) WriteBlobString(image,">"); break;
case '&': (void) WriteBlobString(image,"&"); break;
default: (void) WriteBlobByte(image,*q); break;
}
(void) WriteBlobString(image,"</desc>\n");
continue;
}
primitive_type=UndefinedPrimitive;
switch (*keyword)
{
case ';':
break;
case 'a':
case 'A':
{
if (LocaleCompare("affine",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
affine.sx=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
affine.rx=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
affine.ry=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
affine.sy=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
affine.tx=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
affine.ty=MagickAtoF(token);
break;
}
if (LocaleCompare("angle",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
affine.rx=MagickAtoF(token);
affine.ry=MagickAtoF(token);
break;
}
if (LocaleCompare("arc",keyword) == 0)
{
primitive_type=ArcPrimitive;
break;
}
status=False;
break;
}
case 'b':
case 'B':
{
if (LocaleCompare("bezier",keyword) == 0)
{
primitive_type=BezierPrimitive;
break;
}
status=False;
break;
}
case 'c':
case 'C':
{
if (LocaleCompare("clip-path",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"clip-path:url(#%.1024s);",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("clip-rule",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"clip-rule:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("clip-units",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"clipPathUnits=%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("circle",keyword) == 0)
{
primitive_type=CirclePrimitive;
break;
}
if (LocaleCompare("color",keyword) == 0)
{
primitive_type=ColorPrimitive;
break;
}
status=False;
break;
}
case 'd':
case 'D':
{
if (LocaleCompare("decorate",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"text-decoration:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
status=False;
break;
}
case 'e':
case 'E':
{
if (LocaleCompare("ellipse",keyword) == 0)
{
primitive_type=EllipsePrimitive;
break;
}
status=False;
break;
}
case 'f':
case 'F':
{
if (LocaleCompare("fill",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"fill:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("fill-rule",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"fill-rule:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("fill-opacity",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"fill-opacity:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("font-family",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"font-family:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("font-stretch",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"font-stretch:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("font-style",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"font-style:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("font-size",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"font-size:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("font-weight",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"font-weight:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
status=False;
break;
}
case 'g':
case 'G':
{
if (LocaleCompare("gradient-units",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
break;
}
if (LocaleCompare("text-align",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"text-align %.1024s ",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("text-anchor",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"text-anchor %.1024s ",token);
(void) WriteBlobString(image,message);
break;
}
status=False;
break;
}
case 'i':
case 'I':
{
if (LocaleCompare("image",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
primitive_type=ImagePrimitive;
break;
}
status=False;
break;
}
case 'l':
case 'L':
{
if (LocaleCompare("line",keyword) == 0)
{
primitive_type=LinePrimitive;
break;
}
status=False;
break;
}
case 'm':
case 'M':
{
if (LocaleCompare("matte",keyword) == 0)
{
primitive_type=MattePrimitive;
break;
}
status=False;
break;
}
case 'o':
case 'O':
{
if (LocaleCompare("opacity",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"opacity %.1024s ",token);
(void) WriteBlobString(image,message);
break;
}
status=False;
break;
}
case 'p':
case 'P':
{
if (LocaleCompare("path",keyword) == 0)
{
primitive_type=PathPrimitive;
break;
}
if (LocaleCompare("point",keyword) == 0)
{
primitive_type=PointPrimitive;
break;
}
if (LocaleCompare("polyline",keyword) == 0)
{
primitive_type=PolylinePrimitive;
break;
}
if (LocaleCompare("polygon",keyword) == 0)
{
primitive_type=PolygonPrimitive;
break;
}
if (LocaleCompare("pop",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
if (LocaleCompare("clip-path",token) == 0)
{
(void) WriteBlobString(image,"</clipPath>\n");
break;
}
if (LocaleCompare("defs",token) == 0)
{
(void) WriteBlobString(image,"</defs>\n");
break;
}
if (LocaleCompare("gradient",token) == 0)
{
FormatString(message,"</%sGradient>\n",type);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("graphic-context",token) == 0)
{
n--;
if (n < 0)
ThrowWriterException(DrawError,
UnbalancedGraphicContextPushPop,image);
(void) WriteBlobString(image,"</g>\n");
}
if (LocaleCompare("pattern",token) == 0)
{
(void) WriteBlobString(image,"</pattern>\n");
break;
}
if (LocaleCompare("defs",token) == 0)
(void) WriteBlobString(image,"</g>\n");
break;
}
if (LocaleCompare("push",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
if (LocaleCompare("clip-path",token) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"<clipPath id=\"%s\">\n",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("defs",token) == 0)
{
(void) WriteBlobString(image,"<defs>\n");
break;
}
if (LocaleCompare("gradient",token) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
(void) strlcpy(name,token,MaxTextExtent);
MagickGetToken(q,&q,token,token_max_length);
(void) strlcpy(type,token,MaxTextExtent);
MagickGetToken(q,&q,token,token_max_length);
svg_info.segment.x1=MagickAtoF(token);
svg_info.element.cx=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
svg_info.segment.y1=MagickAtoF(token);
svg_info.element.cy=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
svg_info.segment.x2=MagickAtoF(token);
svg_info.element.major=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
svg_info.segment.y2=MagickAtoF(token);
svg_info.element.minor=MagickAtoF(token);
FormatString(message,"<%sGradient id=\"%s\" x1=\"%g\" "
"y1=\"%g\" x2=\"%g\" y2=\"%g\">\n",type,name,
svg_info.segment.x1,svg_info.segment.y1,svg_info.segment.x2,
svg_info.segment.y2);
if (LocaleCompare(type,"radial") == 0)
{
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
svg_info.element.angle=MagickAtoF(token);
FormatString(message,"<%sGradient id=\"%s\" cx=\"%g\" "
"cy=\"%g\" r=\"%g\" fx=\"%g\" fy=\"%g\">\n",type,name,
svg_info.element.cx,svg_info.element.cy,
svg_info.element.angle,svg_info.element.major,
svg_info.element.minor);
}
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("graphic-context",token) == 0)
{
n++;
if (active)
{
AffineToTransform(image,&affine);
active=False;
}
(void) WriteBlobString(image,"<g style=\"");
active=True;
}
if (LocaleCompare("pattern",token) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
(void) strlcpy(name,token,MaxTextExtent);
MagickGetToken(q,&q,token,token_max_length);
svg_info.bounds.x=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
svg_info.bounds.y=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
svg_info.bounds.width=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
svg_info.bounds.height=MagickAtoF(token);
FormatString(message,"<pattern id=\"%s\" x=\"%g\" y=\"%g\" "
"width=\"%g\" height=\"%g\">\n",name,svg_info.bounds.x,
svg_info.bounds.y,svg_info.bounds.width,
svg_info.bounds.height);
(void) WriteBlobString(image,message);
break;
}
break;
}
status=False;
break;
}
case 'r':
case 'R':
{
if (LocaleCompare("rectangle",keyword) == 0)
{
primitive_type=RectanglePrimitive;
break;
}
if (LocaleCompare("roundRectangle",keyword) == 0)
{
primitive_type=RoundRectanglePrimitive;
break;
}
if (LocaleCompare("rotate",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"rotate(%.1024s) ",token);
(void) WriteBlobString(image,message);
break;
}
status=False;
break;
}
case 's':
case 'S':
{
if (LocaleCompare("scale",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
affine.sx=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
affine.sy=MagickAtoF(token);
break;
}
if (LocaleCompare("skewX",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"skewX(%.1024s) ",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("skewY",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"skewY(%.1024s) ",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stop-color",keyword) == 0)
{
char
color[MaxTextExtent];
MagickGetToken(q,&q,token,token_max_length);
(void) strlcpy(color,token,MaxTextExtent);
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,
" <stop offset=\"%s\" stop-color=\"%s\" />\n",token,color);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stroke",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"stroke:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stroke-antialias",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"stroke-antialias:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stroke-dasharray",keyword) == 0)
{
if (IsPoint(q))
{
long
k;
p=q;
(void) MagickGetToken(p,&p,token,MaxTextExtent);
for (k=0; IsPoint(token); k++)
(void) MagickGetToken(p,&p,token,MaxTextExtent);
(void) WriteBlobString(image,"stroke-dasharray:");
for (j=0; j < k; j++)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"%.1024s ",token);
(void) WriteBlobString(image,message);
}
(void) WriteBlobString(image,";");
break;
}
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"stroke-dasharray:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stroke-dashoffset",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"stroke-dashoffset:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stroke-linecap",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"stroke-linecap:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stroke-linejoin",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"stroke-linejoin:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stroke-miterlimit",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"stroke-miterlimit:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stroke-opacity",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"stroke-opacity:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("stroke-width",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"stroke-width:%.1024s;",token);
(void) WriteBlobString(image,message);
continue;
}
status=False;
break;
}
case 't':
case 'T':
{
if (LocaleCompare("text",keyword) == 0)
{
primitive_type=TextPrimitive;
break;
}
if (LocaleCompare("text-antialias",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
FormatString(message,"text-antialias:%.1024s;",token);
(void) WriteBlobString(image,message);
break;
}
if (LocaleCompare("tspan",keyword) == 0)
{
primitive_type=TextPrimitive;
break;
}
if (LocaleCompare("translate",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
affine.tx=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
affine.ty=MagickAtoF(token);
break;
}
status=False;
break;
}
case 'v':
case 'V':
{
if (LocaleCompare("viewbox",keyword) == 0)
{
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
MagickGetToken(q,&q,token,token_max_length);
break;
}
status=False;
break;
}
default:
{
status=False;
break;
}
}
if (status == False)
break;
if (primitive_type == UndefinedPrimitive)
continue;
/*
Parse the primitive attributes.
*/
i=0;
j=0;
for (x=0; *q != '\0'; x++)
{
/*
Define points.
*/
if (!IsPoint(q))
break;
MagickGetToken(q,&q,token,token_max_length);
point.x=MagickAtoF(token);
MagickGetToken(q,&q,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
point.y=MagickAtoF(token);
MagickGetToken(q,(char **) NULL,token,token_max_length);
if (*token == ',')
MagickGetToken(q,&q,token,token_max_length);
primitive_info[i].primitive=primitive_type;
primitive_info[i].point=point;
primitive_info[i].coordinates=0;
primitive_info[i].method=FloodfillMethod;
i++;
if (i < (long) (number_points-6*BezierQuantum-360))
continue;
number_points+=6*BezierQuantum+360;
MagickReallocMemory(PrimitiveInfo *,primitive_info,
number_points*sizeof(PrimitiveInfo));
if (primitive_info == (PrimitiveInfo *) NULL)
{
ThrowException3(&image->exception,ResourceLimitError,
MemoryAllocationFailed,UnableToDrawOnImage);
break;
}
}
primitive_info[j].primitive=primitive_type;
primitive_info[j].coordinates=x;
primitive_info[j].method=FloodfillMethod;
primitive_info[j].text=(char *) NULL;
if (active)
{
AffineToTransform(image,&affine);
active=False;
}
active=False;
switch (primitive_type)
{
case PointPrimitive:
default:
{
if (primitive_info[j].coordinates != 1)
{
status=False;
break;
}
break;
}
case LinePrimitive:
{
if (primitive_info[j].coordinates != 2)
{
status=False;
break;
}
(void) FormatString(message,
" <line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"/>\n",
primitive_info[j].point.x,primitive_info[j].point.y,
primitive_info[j+1].point.x,primitive_info[j+1].point.y);
(void) WriteBlobString(image,message);
break;
}
case RectanglePrimitive:
{
if (primitive_info[j].coordinates != 2)
{
status=False;
break;
}
(void) FormatString(message,
" <rect x=\"%g\" y=\"%g\" width=\"%g\" height=\"%g\"/>\n",
primitive_info[j].point.x,primitive_info[j].point.y,
primitive_info[j+1].point.x-primitive_info[j].point.x,
primitive_info[j+1].point.y-primitive_info[j].point.y);
(void) WriteBlobString(image,message);
break;
}
case RoundRectanglePrimitive:
{
if (primitive_info[j].coordinates != 3)
{
status=False;
break;
}
(void) FormatString(message," <rect x=\"%g\" y=\"%g\" "
"width=\"%g\" height=\"%g\" rx=\"%g\" ry=\"%g\"/>\n",
primitive_info[j].point.x,primitive_info[j].point.y,
primitive_info[j+1].point.x-primitive_info[j].point.x,
primitive_info[j+1].point.y-primitive_info[j].point.y,
primitive_info[j+2].point.x,primitive_info[j+2].point.y);
(void) WriteBlobString(image,message);
break;
}
case ArcPrimitive:
{
if (primitive_info[j].coordinates != 3)
{
status=False;
break;
}
break;
}
case EllipsePrimitive:
{
if (primitive_info[j].coordinates != 3)
{
status=False;
break;
}
(void) FormatString(message,
" <ellipse cx=\"%g\" cy=\"%g\" rx=\"%g\" ry=\"%g\"/>\n",
primitive_info[j].point.x,primitive_info[j].point.y,
primitive_info[j+1].point.x,primitive_info[j+1].point.y);
(void) WriteBlobString(image,message);
break;
}
case CirclePrimitive:
{
double
alpha,
beta;
if (primitive_info[j].coordinates != 2)
{
status=False;
break;
}
alpha=primitive_info[j+1].point.x-primitive_info[j].point.x;
beta=primitive_info[j+1].point.y-primitive_info[j].point.y;
(void) FormatString(message," <circle cx=\"%g\" cy=\"%g\" r=\"%g\"/>\n",
primitive_info[j].point.x,primitive_info[j].point.y,
sqrt(alpha*alpha+beta*beta));
(void) WriteBlobString(image,message);
break;
}
case PolylinePrimitive:
{
if (primitive_info[j].coordinates < 2)
{
status=False;
break;
}
(void) strlcpy(message," <polyline points=\"",sizeof(message));
(void) WriteBlobString(image,message);
length=strlen(message);
for ( ; j < i; j++)
{
FormatString(message,"%g,%g ",primitive_info[j].point.x,
primitive_info[j].point.y);
length+=strlen(message);
if (length >= 80)
{
(void) WriteBlobString(image,"\n ");
length=strlen(message)+5;
}
(void) WriteBlobString(image,message);
}
(void) WriteBlobString(image,"\"/>\n");
break;
}
case PolygonPrimitive:
{
if (primitive_info[j].coordinates < 3)
{
status=False;
break;
}
primitive_info[i]=primitive_info[j];
primitive_info[i].coordinates=0;
primitive_info[j].coordinates++;
i++;
(void) strlcpy(message," <polygon points=\"",sizeof(message));
(void) WriteBlobString(image,message);
length=strlen(message);
for ( ; j < i; j++)
{
FormatString(message,"%g,%g ",primitive_info[j].point.x,
primitive_info[j].point.y);
length+=strlen(message);
if (length >= 80)
{
(void) WriteBlobString(image,"\n ");
length=strlen(message)+5;
}
(void) WriteBlobString(image,message);
}
(void) WriteBlobString(image,"\"/>\n");
break;
}
case BezierPrimitive:
{
if (primitive_info[j].coordinates < 3)
{
status=False;
break;
}
break;
}
case PathPrimitive:
{
int
number_attributes;
MagickGetToken(q,&q,token,token_max_length);
number_attributes=1;
for (p=token; *p != '\0'; p++)
if (isalpha((int) *p))
number_attributes++;
if (i > (long) (number_points-6*BezierQuantum*number_attributes-1))
{
number_points+=6*BezierQuantum*number_attributes;
MagickReallocMemory(PrimitiveInfo *,primitive_info,
number_points*sizeof(PrimitiveInfo));
if (primitive_info == (PrimitiveInfo *) NULL)
{
ThrowException3(&image->exception,ResourceLimitError,
MemoryAllocationFailed,UnableToDrawOnImage);
break;
}
}
(void) WriteBlobString(image," <path d=\"");
(void) WriteBlobString(image,token);
(void) WriteBlobString(image,"\"/>\n");
break;
}
case ColorPrimitive:
case MattePrimitive:
{
if (primitive_info[j].coordinates != 1)
{
status=False;
break;
}
MagickGetToken(q,&q,token,token_max_length);
if (LocaleCompare("point",token) == 0)
primitive_info[j].method=PointMethod;
if (LocaleCompare("replace",token) == 0)
primitive_info[j].method=ReplaceMethod;
if (LocaleCompare("floodfill",token) == 0)
primitive_info[j].method=FloodfillMethod;
if (LocaleCompare("filltoborder",token) == 0)
primitive_info[j].method=FillToBorderMethod;
if (LocaleCompare("reset",token) == 0)
primitive_info[j].method=ResetMethod;
break;
}
case TextPrimitive:
{
register char
*p;
if (primitive_info[j].coordinates != 1)
{
status=False;
break;
}
MagickGetToken(q,&q,token,token_max_length);
(void) FormatString(message," <text x=\"%g\" y=\"%g\">",
primitive_info[j].point.x,primitive_info[j].point.y);
(void) WriteBlobString(image,message);
for (p=token; *p != '\0'; p++)
switch (*p)
{
case '<': (void) WriteBlobString(image,"<"); break;
case '>': (void) WriteBlobString(image,">"); break;
case '&': (void) WriteBlobString(image,"&"); break;
default: (void) WriteBlobByte(image,*p); break;
}
(void) WriteBlobString(image,"</text>\n");
break;
}
case ImagePrimitive:
{
if (primitive_info[j].coordinates != 2)
{
status=False;
break;
}
MagickGetToken(q,&q,token,token_max_length);
(void) FormatString(message," <image x=\"%g\" y=\"%g\" "
"width=\"%g\" height=\"%g\" xlink:href=\"%.1024s\"/>\n",
primitive_info[j].point.x,primitive_info[j].point.y,
primitive_info[j+1].point.x,primitive_info[j+1].point.y,token);
(void) WriteBlobString(image,message);
break;
}
}
if (primitive_info == (PrimitiveInfo *) NULL)
break;
primitive_info[i].primitive=UndefinedPrimitive;
if (status == False)
break;
}
(void) WriteBlobString(image,"</svg>\n");
/*
Free resources.
*/
MagickFreeMemory(token);
if (primitive_info != (PrimitiveInfo *) NULL)
MagickFreeMemory(primitive_info);
status &= CloseBlob(image);
return(status);
}
#endif
#endif /* if defined(ENABLE_SVG_WRITER) && ENABLE_SVG_WRITER */