MagickExport MagickBooleanType DrawGradientImage(Image *image,
  const DrawInfo *draw_info)
{
  CacheView
    *image_view;

  const GradientInfo
    *gradient;

  const SegmentInfo
    *gradient_vector;

  double
    length;

  ExceptionInfo
    *exception;

  MagickBooleanType
    status;

  MagickPixelPacket
    zero;

  PointInfo
    point;

  RectangleInfo
    bounding_box;

  ssize_t
    y;

  /*
    Draw linear or radial gradient on image.
  */
  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  assert(draw_info != (const DrawInfo *) NULL);
  gradient=(&draw_info->gradient);
  gradient_vector=(&gradient->gradient_vector);
  point.x=gradient_vector->x2-gradient_vector->x1;
  point.y=gradient_vector->y2-gradient_vector->y1;
  length=sqrt(point.x*point.x+point.y*point.y);
  bounding_box=gradient->bounding_box;
  status=MagickTrue;
  exception=(&image->exception);
  GetMagickPixelPacket(image,&zero);
  image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static) shared(status) \
    magick_number_threads(image,image,bounding_box.height-bounding_box.y,1)
#endif
  for (y=bounding_box.y; y < (ssize_t) bounding_box.height; y++)
  {
    double
      alpha,
      offset;

    MagickPixelPacket
      composite,
      pixel;

    IndexPacket
      *magick_restrict indexes;

    ssize_t
      i,
      x;

    PixelPacket
      *magick_restrict q;

    ssize_t
      j;

    if (status == MagickFalse)
      continue;
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (q == (PixelPacket *) NULL)
      {
        status=MagickFalse;
        continue;
      }
    indexes=GetCacheViewAuthenticIndexQueue(image_view);
    pixel=zero;
    composite=zero;
    offset=GetStopColorOffset(gradient,0,y);
    if (gradient->type != RadialGradient)
      offset*=PerceptibleReciprocal(length);
    for (x=bounding_box.x; x < (ssize_t) bounding_box.width; x++)
    {
      SetMagickPixelPacket(image,q,indexes+x,&pixel);
      switch (gradient->spread)
      {
        case UndefinedSpread:
        case PadSpread:
        {
          if ((x != CastDoubleToLong(ceil(gradient_vector->x1-0.5))) ||
              (y != CastDoubleToLong(ceil(gradient_vector->y1-0.5))))
            {
              offset=GetStopColorOffset(gradient,x,y);
              if (gradient->type != RadialGradient)
                offset*=PerceptibleReciprocal(length);
            }
          for (i=0; i < (ssize_t) gradient->number_stops; i++)
            if (offset < gradient->stops[i].offset)
              break;
          if ((offset < 0.0) || (i == 0))
            composite=gradient->stops[0].color;
          else
            if ((offset > 1.0) || (i == (ssize_t) gradient->number_stops))
              composite=gradient->stops[gradient->number_stops-1].color;
            else
              {
                j=i;
                i--;
                alpha=(offset-gradient->stops[i].offset)/
                  (gradient->stops[j].offset-gradient->stops[i].offset);
                MagickPixelCompositeBlend(&gradient->stops[i].color,1.0-alpha,
                  &gradient->stops[j].color,alpha,&composite);
              }
          break;
        }
        case ReflectSpread:
        {
          if ((x != CastDoubleToLong(ceil(gradient_vector->x1-0.5))) ||
              (y != CastDoubleToLong(ceil(gradient_vector->y1-0.5))))
            {
              offset=GetStopColorOffset(gradient,x,y);
              if (gradient->type != RadialGradient)
                offset*=PerceptibleReciprocal(length);
            }
          if (offset < 0.0)
            offset=(-offset);
          if ((ssize_t) fmod(offset,2.0) == 0)
            offset=fmod(offset,1.0);
          else
            offset=1.0-fmod(offset,1.0);
          for (i=0; i < (ssize_t) gradient->number_stops; i++)
            if (offset < gradient->stops[i].offset)
              break;
          if (i == 0)
            composite=gradient->stops[0].color;
          else
            if (i == (ssize_t) gradient->number_stops)
              composite=gradient->stops[gradient->number_stops-1].color;
            else
              {
                j=i;
                i--;
                alpha=(offset-gradient->stops[i].offset)/
                  (gradient->stops[j].offset-gradient->stops[i].offset);
                MagickPixelCompositeBlend(&gradient->stops[i].color,1.0-alpha,
                  &gradient->stops[j].color,alpha,&composite);
              }
          break;
        }
        case RepeatSpread:
        {
          double
            repeat;

          MagickBooleanType
            antialias;

          antialias=MagickFalse;
          repeat=0.0;
          if ((x != CastDoubleToLong(ceil(gradient_vector->x1-0.5))) ||
              (y != CastDoubleToLong(ceil(gradient_vector->y1-0.5))))
            {
              offset=GetStopColorOffset(gradient,x,y);
              if (gradient->type == LinearGradient)
                {
                  repeat=fmod(offset,length);
                  if (repeat < 0.0)
                    repeat=length-fmod(-repeat,length);
                  else
                    repeat=fmod(offset,length);
                  antialias=(repeat < length) && ((repeat+1.0) > length) ?
                    MagickTrue : MagickFalse;
                  offset=PerceptibleReciprocal(length)*repeat;
                }
              else
                {
                  repeat=fmod(offset,(double) gradient->radius);
                  if (repeat < 0.0)
                    repeat=gradient->radius-fmod(-repeat,
                      (double) gradient->radius);
                  else
                    repeat=fmod(offset,(double) gradient->radius);
                  antialias=repeat+1.0 > gradient->radius ? MagickTrue :
                    MagickFalse;
                  offset=repeat*PerceptibleReciprocal(gradient->radius);
                }
            }
          for (i=0; i < (ssize_t) gradient->number_stops; i++)
            if (offset < gradient->stops[i].offset)
              break;
          if (i == 0)
            composite=gradient->stops[0].color;
          else
            if (i == (ssize_t) gradient->number_stops)
              composite=gradient->stops[gradient->number_stops-1].color;
            else
              {
                j=i;
                i--;
                alpha=(offset-gradient->stops[i].offset)/
                  (gradient->stops[j].offset-gradient->stops[i].offset);
                if (antialias != MagickFalse)
                  {
                    if (gradient->type == LinearGradient)
                      alpha=length-repeat;
                    else
                      alpha=gradient->radius-repeat;
                    i=0;
                    j=(ssize_t) gradient->number_stops-1L;
                  }
                MagickPixelCompositeBlend(&gradient->stops[i].color,1.0-alpha,
                  &gradient->stops[j].color,alpha,&composite);
              }
          break;
        }
      }
      MagickPixelCompositeOver(&composite,composite.opacity,&pixel,
        pixel.opacity,&pixel);
      SetPixelPacket(image,&pixel,q,indexes+x);
      q++;
    }
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
  }
  image_view=DestroyCacheView(image_view);
  return(status);
}


static MagickBooleanType TraceArcPath(MVGInfo *mvg_info,const PointInfo start,
  const PointInfo end,const PointInfo arc,const double angle,
  const MagickBooleanType large_arc,const MagickBooleanType sweep)
{
  double
    alpha,
    beta,
    delta,
    factor,
    gamma,
    theta;

  MagickStatusType
    status;

  PointInfo
    center,
    points[3],
    radii;

  double
    cosine,
    sine;

  PrimitiveInfo
    *primitive_info;

  PrimitiveInfo
    *p;

  ssize_t
    i;

  size_t
    arc_segments;

  ssize_t
    offset;

  offset=mvg_info->offset;
  primitive_info=(*mvg_info->primitive_info)+mvg_info->offset;
  primitive_info->coordinates=0;
  if ((fabs(start.x-end.x) < MagickEpsilon) &&
      (fabs(start.y-end.y) < MagickEpsilon))
    return(TracePoint(primitive_info,end));
  radii.x=fabs(arc.x);
  radii.y=fabs(arc.y);
  if ((radii.x < MagickEpsilon) || (radii.y < MagickEpsilon))
    return(TraceLine(primitive_info,start,end));
  cosine=cos(DegreesToRadians(fmod((double) angle,360.0)));
  sine=sin(DegreesToRadians(fmod((double) angle,360.0)));
  center.x=(double) (cosine*(end.x-start.x)/2+sine*(end.y-start.y)/2);
  center.y=(double) (cosine*(end.y-start.y)/2-sine*(end.x-start.x)/2);
  delta=(center.x*center.x)/(radii.x*radii.x)+(center.y*center.y)/
    (radii.y*radii.y);
  if (delta < MagickEpsilon)
    return(TraceLine(primitive_info,start,end));
  if (delta > 1.0)
    {
      radii.x*=sqrt((double) delta);
      radii.y*=sqrt((double) delta);
    }
  points[0].x=(double) (cosine*start.x/radii.x+sine*start.y/radii.x);
  points[0].y=(double) (cosine*start.y/radii.y-sine*start.x/radii.y);
  points[1].x=(double) (cosine*end.x/radii.x+sine*end.y/radii.x);
  points[1].y=(double) (cosine*end.y/radii.y-sine*end.x/radii.y);
  alpha=points[1].x-points[0].x;
  beta=points[1].y-points[0].y;
  if (fabs(alpha*alpha+beta*beta) < MagickEpsilon)
    return(TraceLine(primitive_info,start,end));
  factor=PerceptibleReciprocal(alpha*alpha+beta*beta)-0.25;
  if (factor <= 0.0)
    factor=0.0;
  else
    {
      factor=sqrt((double) factor);
      if (sweep == large_arc)
        factor=(-factor);
    }
  center.x=(double) ((points[0].x+points[1].x)/2-factor*beta);
  center.y=(double) ((points[0].y+points[1].y)/2+factor*alpha);
  alpha=atan2(points[0].y-center.y,points[0].x-center.x);
  theta=atan2(points[1].y-center.y,points[1].x-center.x)-alpha;
  if ((theta < 0.0) && (sweep != MagickFalse))
    theta+=2.0*MagickPI;
  else
    if ((theta > 0.0) && (sweep == MagickFalse))
      theta-=2.0*MagickPI;
  arc_segments=(size_t) CastDoubleToLong(ceil(fabs((double) (theta/(0.5*
    MagickPI+MagickEpsilon)))));
  p=primitive_info;
  status=MagickTrue;
  for (i=0; i < (ssize_t) arc_segments; i++)
  {
    beta=0.5*((alpha+(i+1)*theta/arc_segments)-(alpha+i*theta/arc_segments));
    gamma=(8.0/3.0)*sin(fmod((double) (0.5*beta),DegreesToRadians(360.0)))*
      sin(fmod((double) (0.5*beta),DegreesToRadians(360.0)))/
      sin(fmod((double) beta,DegreesToRadians(360.0)));
    points[0].x=(double) (center.x+cos(fmod((double) (alpha+(double) i*theta/
      arc_segments),DegreesToRadians(360.0)))-gamma*sin(fmod((double) (alpha+
      (double) i*theta/arc_segments),DegreesToRadians(360.0))));
    points[0].y=(double) (center.y+sin(fmod((double) (alpha+(double) i*theta/
      arc_segments),DegreesToRadians(360.0)))+gamma*cos(fmod((double) (alpha+
      (double) i*theta/arc_segments),DegreesToRadians(360.0))));
    points[2].x=(double) (center.x+cos(fmod((double) (alpha+(double) (i+1)*
      theta/arc_segments),DegreesToRadians(360.0))));
    points[2].y=(double) (center.y+sin(fmod((double) (alpha+(double) (i+1)*
      theta/arc_segments),DegreesToRadians(360.0))));
    points[1].x=(double) (points[2].x+gamma*sin(fmod((double) (alpha+(double)
      (i+1)*theta/arc_segments),DegreesToRadians(360.0))));
    points[1].y=(double) (points[2].y-gamma*cos(fmod((double) (alpha+(double)
      (i+1)*theta/arc_segments),DegreesToRadians(360.0))));
    p->point.x=(p == primitive_info) ? start.x : (p-1)->point.x;
    p->point.y=(p == primitive_info) ? start.y : (p-1)->point.y;
    (p+1)->point.x=(double) (cosine*radii.x*points[0].x-sine*radii.y*
      points[0].y);
    (p+1)->point.y=(double) (sine*radii.x*points[0].x+cosine*radii.y*
      points[0].y);
    (p+2)->point.x=(double) (cosine*radii.x*points[1].x-sine*radii.y*
      points[1].y);
    (p+2)->point.y=(double) (sine*radii.x*points[1].x+cosine*radii.y*
      points[1].y);
    (p+3)->point.x=(double) (cosine*radii.x*points[2].x-sine*radii.y*
      points[2].y);
    (p+3)->point.y=(double) (sine*radii.x*points[2].x+cosine*radii.y*
      points[2].y);
    if (i == (ssize_t) (arc_segments-1))
      (p+3)->point=end;
    status&=TraceBezier(mvg_info,4);
    if (status == 0)
      break;
    p=(*mvg_info->primitive_info)+mvg_info->offset;
    mvg_info->offset+=p->coordinates;
    p+=p->coordinates;
  }
  if (status == 0)
    return(MagickFalse);
  mvg_info->offset=offset;
  primitive_info=(*mvg_info->primitive_info)+mvg_info->offset;
  primitive_info->coordinates=(size_t) (p-primitive_info);
  primitive_info->closed_subpath=MagickFalse;
  for (i=0; i < (ssize_t) primitive_info->coordinates; i++)
  {
    p->primitive=primitive_info->primitive;
    p--;
  }
  return(MagickTrue);
}


static MagickBooleanType DrawRoundLinecap(Image *image,
  const DrawInfo *draw_info,const PrimitiveInfo *primitive_info)
{
  PrimitiveInfo
    linecap[5];

  ssize_t
    i;

  for (i=0; i < 4; i++)
    linecap[i]=(*primitive_info);
  linecap[0].coordinates=4;
  linecap[1].point.x+=2.0*MagickEpsilon;
  linecap[2].point.x+=2.0*MagickEpsilon;
  linecap[2].point.y+=2.0*MagickEpsilon;
  linecap[3].point.y+=2.0*MagickEpsilon;
  linecap[4].primitive=UndefinedPrimitive;
  return(DrawPolygonPrimitive(image,draw_info,linecap));
}


static MagickBooleanType DrawBoundingRectangles(Image *image,
  const DrawInfo *draw_info,const PolygonInfo *polygon_info)
{
  double
    mid;

  DrawInfo
    *clone_info;

  MagickStatusType
    status;

  PointInfo
    end,
    resolution,
    start;

  PrimitiveInfo
    primitive_info[6];

  ssize_t
    i;

  SegmentInfo
    bounds;

  ssize_t
    coordinates;

  (void) memset(primitive_info,0,sizeof(primitive_info));
  clone_info=CloneDrawInfo((ImageInfo *) NULL,draw_info);
  status=QueryColorDatabase("#0000",&clone_info->fill,&image->exception);
  if (status == MagickFalse)
    {
      clone_info=DestroyDrawInfo(clone_info);
      return(MagickFalse);
    }
  resolution.x=96.0;
  resolution.y=96.0;
  if (clone_info->density != (char *) NULL)
    {
      GeometryInfo
        geometry_info;

      MagickStatusType
        flags;

      flags=ParseGeometry(clone_info->density,&geometry_info);
      resolution.x=geometry_info.rho;
      resolution.y=geometry_info.sigma;
      if ((flags & SigmaValue) == MagickFalse)
        resolution.y=resolution.x;
    }
  mid=(resolution.x/96.0)*ExpandAffine(&clone_info->affine)*
    clone_info->stroke_width/2.0;
  bounds.x1=0.0;
  bounds.y1=0.0;
  bounds.x2=0.0;
  bounds.y2=0.0;
  if (polygon_info != (PolygonInfo *) NULL)
    {
      bounds=polygon_info->edges[0].bounds;
      for (i=1; i < (ssize_t) polygon_info->number_edges; i++)
      {
        if (polygon_info->edges[i].bounds.x1 < (double) bounds.x1)
          bounds.x1=polygon_info->edges[i].bounds.x1;
        if (polygon_info->edges[i].bounds.y1 < (double) bounds.y1)
          bounds.y1=polygon_info->edges[i].bounds.y1;
        if (polygon_info->edges[i].bounds.x2 > (double) bounds.x2)
          bounds.x2=polygon_info->edges[i].bounds.x2;
        if (polygon_info->edges[i].bounds.y2 > (double) bounds.y2)
          bounds.y2=polygon_info->edges[i].bounds.y2;
      }
      bounds.x1-=mid;
      bounds.x1=bounds.x1 < 0.0 ? 0.0 : bounds.x1 >= (double)
        image->columns ? (double) image->columns-1 : bounds.x1;
      bounds.y1-=mid;
      bounds.y1=bounds.y1 < 0.0 ? 0.0 : bounds.y1 >= (double)
        image->rows ? (double) image->rows-1 : bounds.y1;
      bounds.x2+=mid;
      bounds.x2=bounds.x2 < 0.0 ? 0.0 : bounds.x2 >= (double)
        image->columns ? (double) image->columns-1 : bounds.x2;
      bounds.y2+=mid;
      bounds.y2=bounds.y2 < 0.0 ? 0.0 : bounds.y2 >= (double)
        image->rows ? (double) image->rows-1 : bounds.y2;
      for (i=0; i < (ssize_t) polygon_info->number_edges; i++)
      {
        if (polygon_info->edges[i].direction != 0)
          status=QueryColorDatabase("#f00",&clone_info->stroke,
            &image->exception);
        else
          status=QueryColorDatabase("#0f0",&clone_info->stroke,
            &image->exception);
        if (status == MagickFalse)
          break;
        start.x=(double) (polygon_info->edges[i].bounds.x1-mid);
        start.y=(double) (polygon_info->edges[i].bounds.y1-mid);
        end.x=(double) (polygon_info->edges[i].bounds.x2+mid);
        end.y=(double) (polygon_info->edges[i].bounds.y2+mid);
        primitive_info[0].primitive=RectanglePrimitive;
        status&=TraceRectangle(primitive_info,start,end);
        primitive_info[0].method=ReplaceMethod;
        coordinates=(ssize_t) primitive_info[0].coordinates;
        primitive_info[coordinates].primitive=UndefinedPrimitive;
        status=DrawPrimitive(image,clone_info,primitive_info);
        if (status == MagickFalse)
          break;
      }
      if (i < (ssize_t) polygon_info->number_edges)
        {
          clone_info=DestroyDrawInfo(clone_info);
          return(status == 0 ? MagickFalse : MagickTrue);
        }
    }
  status=QueryColorDatabase("#00f",&clone_info->stroke,&image->exception);
  if (status == MagickFalse)
    {
      clone_info=DestroyDrawInfo(clone_info);
      return(MagickFalse);
    }
  start.x=(double) (bounds.x1-mid);
  start.y=(double) (bounds.y1-mid);
  end.x=(double) (bounds.x2+mid);
  end.y=(double) (bounds.y2+mid);
  primitive_info[0].primitive=RectanglePrimitive;
  status&=TraceRectangle(primitive_info,start,end);
  primitive_info[0].method=ReplaceMethod;
  coordinates=(ssize_t) primitive_info[0].coordinates;
  primitive_info[coordinates].primitive=UndefinedPrimitive;
  status=DrawPrimitive(image,clone_info,primitive_info);
  clone_info=DestroyDrawInfo(clone_info);
  return(status == 0 ? MagickFalse : MagickTrue);
}


MagickExport DrawInfo *CloneDrawInfo(const ImageInfo *image_info,
  const DrawInfo *draw_info)
{
  DrawInfo
    *clone_info;

  clone_info=(DrawInfo *) AcquireCriticalMemory(sizeof(*clone_info));
  GetDrawInfo(image_info,clone_info);
  if (draw_info == (DrawInfo *) NULL)
    return(clone_info);
  if (draw_info->id != (char *) NULL)
    (void) CloneString(&clone_info->id,draw_info->id);
  if (draw_info->primitive != (char *) NULL)
    (void) CloneString(&clone_info->primitive,draw_info->primitive);
  if (draw_info->geometry != (char *) NULL)
    (void) CloneString(&clone_info->geometry,draw_info->geometry);
  clone_info->compliance=draw_info->compliance;
  clone_info->viewbox=draw_info->viewbox;
  clone_info->affine=draw_info->affine;
  clone_info->gravity=draw_info->gravity;
  clone_info->fill=draw_info->fill;
  clone_info->stroke=draw_info->stroke;
  clone_info->stroke_width=draw_info->stroke_width;
  if (draw_info->fill_pattern != (Image *) NULL)
    clone_info->fill_pattern=CloneImage(draw_info->fill_pattern,0,0,MagickTrue,
      &draw_info->fill_pattern->exception);
  else
    if (draw_info->tile != (Image *) NULL)
      clone_info->fill_pattern=CloneImage(draw_info->tile,0,0,MagickTrue,
        &draw_info->tile->exception);
  clone_info->tile=NewImageList();  /* tile is deprecated */
  if (draw_info->stroke_pattern != (Image *) NULL)
    clone_info->stroke_pattern=CloneImage(draw_info->stroke_pattern,0,0,
      MagickTrue,&draw_info->stroke_pattern->exception);
  clone_info->stroke_antialias=draw_info->stroke_antialias;
  clone_info->text_antialias=draw_info->text_antialias;
  clone_info->fill_rule=draw_info->fill_rule;
  clone_info->linecap=draw_info->linecap;
  clone_info->linejoin=draw_info->linejoin;
  clone_info->miterlimit=draw_info->miterlimit;
  clone_info->dash_offset=draw_info->dash_offset;
  clone_info->decorate=draw_info->decorate;
  clone_info->compose=draw_info->compose;
  if (draw_info->text != (char *) NULL)
    (void) CloneString(&clone_info->text,draw_info->text);
  if (draw_info->font != (char *) NULL)
    (void) CloneString(&clone_info->font,draw_info->font);
  if (draw_info->metrics != (char *) NULL)
    (void) CloneString(&clone_info->metrics,draw_info->metrics);
  if (draw_info->family != (char *) NULL)
    (void) CloneString(&clone_info->family,draw_info->family);
  clone_info->style=draw_info->style;
  clone_info->stretch=draw_info->stretch;
  clone_info->weight=draw_info->weight;
  if (draw_info->encoding != (char *) NULL)
    (void) CloneString(&clone_info->encoding,draw_info->encoding);
  clone_info->pointsize=draw_info->pointsize;
  clone_info->kerning=draw_info->kerning;
  clone_info->interline_spacing=draw_info->interline_spacing;
  clone_info->interword_spacing=draw_info->interword_spacing;
  clone_info->direction=draw_info->direction;
  if (draw_info->density != (char *) NULL)
    (void) CloneString(&clone_info->density,draw_info->density);
  clone_info->align=draw_info->align;
  clone_info->undercolor=draw_info->undercolor;
  clone_info->border_color=draw_info->border_color;
  if (draw_info->server_name != (char *) NULL)
    (void) CloneString(&clone_info->server_name,draw_info->server_name);
  if (draw_info->dash_pattern != (double *) NULL)
    {
      ssize_t
        x;

      for (x=0; fabs(draw_info->dash_pattern[x]) >= MagickEpsilon; x++) ;
      clone_info->dash_pattern=(double *) AcquireQuantumMemory((size_t) (2*x+2),
        sizeof(*clone_info->dash_pattern));
      if (clone_info->dash_pattern == (double *) NULL)
        ThrowFatalException(ResourceLimitFatalError,
          "UnableToAllocateDashPattern");
      (void) memset(clone_info->dash_pattern,0,(size_t) (2*x+2)*
        sizeof(*clone_info->dash_pattern));
      (void) memcpy(clone_info->dash_pattern,draw_info->dash_pattern,(size_t)
        (x+1)*sizeof(*clone_info->dash_pattern));
    }
  clone_info->gradient=draw_info->gradient;
  if (draw_info->gradient.stops != (StopInfo *) NULL)
    {
      size_t
        number_stops;

      number_stops=clone_info->gradient.number_stops;
      clone_info->gradient.stops=(StopInfo *) AcquireQuantumMemory((size_t)
        number_stops,sizeof(*clone_info->gradient.stops));
      if (clone_info->gradient.stops == (StopInfo *) NULL)
        ThrowFatalException(ResourceLimitFatalError,
          "UnableToAllocateDashPattern");
      (void) memcpy(clone_info->gradient.stops,draw_info->gradient.stops,
        (size_t) number_stops*sizeof(*clone_info->gradient.stops));
    }
  clone_info->bounds=draw_info->bounds;
  clone_info->fill_opacity=draw_info->fill_opacity;
  clone_info->stroke_opacity=draw_info->stroke_opacity;
  clone_info->element_reference=draw_info->element_reference;
  clone_info->clip_path=draw_info->clip_path;
  clone_info->clip_units=draw_info->clip_units;
  if (draw_info->clip_mask != (char *) NULL)
    (void) CloneString(&clone_info->clip_mask,draw_info->clip_mask);
  if (draw_info->clipping_mask != (Image *) NULL)
    clone_info->clipping_mask=CloneImage(draw_info->clipping_mask,0,0,
      MagickTrue,&draw_info->clipping_mask->exception);
  if (draw_info->composite_mask != (Image *) NULL)
    clone_info->composite_mask=CloneImage(draw_info->composite_mask,0,0,
      MagickTrue,&draw_info->composite_mask->exception);
  clone_info->render=draw_info->render;
  clone_info->debug=IsEventLogging();
  return(clone_info);
}


MagickExport MagickBooleanType DrawClipPath(Image *image,
  const DrawInfo *draw_info,const char *id)
{
  const char
    *clip_path;

  Image
    *clipping_mask;

  MagickBooleanType
    status;

  clip_path=GetImageArtifact(image,id);
  if (clip_path == (const char *) NULL)
    return(MagickFalse);
  clipping_mask=DrawClippingMask(image,draw_info,draw_info->clip_mask,clip_path,
    &image->exception);
  if (clipping_mask == (Image *) NULL)
    return(MagickFalse);
  status=SetImageClipMask(image,clipping_mask);
  clipping_mask=DestroyImage(clipping_mask);
  return(status);
}


static MagickBooleanType RenderMVGContent(Image *image,
  const DrawInfo *draw_info,const size_t depth)
{
#define RenderImageTag  "Render/Image"

  AffineMatrix
    affine,
    current;

  char
    key[2*MaxTextExtent],
    keyword[MaxTextExtent],
    geometry[MaxTextExtent],
    name[MaxTextExtent],
    *next_token,
    pattern[MaxTextExtent],
    *primitive,
    *token;

  const char
    *q;

  double
    angle,
    coordinates,
    cursor,
    factor,
    primitive_extent;

  DrawInfo
    *clone_info,
    **graphic_context;

  MagickBooleanType
    proceed;

  MagickStatusType
    status;

  MVGInfo
    mvg_info;

  PointInfo
    point;

  PixelPacket
    start_color;

  PrimitiveInfo
    *primitive_info;

  PrimitiveType
    primitive_type;

  const char
    *p;

  ssize_t
    i,
    x;

  SegmentInfo
    bounds;

  size_t
    extent,
    number_points;

  SplayTreeInfo
    *macros;

  ssize_t
    defsDepth,
    j,
    k,
    n,
    symbolDepth;

  TypeMetric
    metrics;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  assert(draw_info != (DrawInfo *) NULL);
  assert(draw_info->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
  if (depth > MagickMaxRecursionDepth)
    ThrowBinaryImageException(DrawError,"VectorGraphicsNestedTooDeeply",
      image->filename);
  if ((draw_info->primitive == (char *) NULL) ||
      (*draw_info->primitive == '\0'))
    return(MagickFalse);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(DrawEvent,GetMagickModule(),"begin draw-image");
  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
    return(MagickFalse);
  if (image->matte == MagickFalse)
    {
      status=SetImageAlphaChannel(image,OpaqueAlphaChannel);
      if (status == MagickFalse)
        return(MagickFalse);
    }
  primitive=(char *) NULL;
  if ((*draw_info->primitive == '@') && (strlen(draw_info->primitive) > 1) &&
      (*(draw_info->primitive+1) != '-') && (depth == 0))
    primitive=FileToString(draw_info->primitive+1,~0UL,&image->exception);
  else
    primitive=AcquireString(draw_info->primitive);
  if (primitive == (char *) NULL)
    return(MagickFalse);
  primitive_extent=(double) strlen(primitive);
  (void) SetImageArtifact(image,"mvg:vector-graphics",primitive);
  n=0;
  /*
    Allocate primitive info memory.
  */
  graphic_context=(DrawInfo **) AcquireMagickMemory(sizeof(*graphic_context));
  if (graphic_context == (DrawInfo **) NULL)
    {
      primitive=DestroyString(primitive);
      ThrowBinaryImageException(ResourceLimitError,"MemoryAllocationFailed",
        image->filename);
    }
  number_points=(size_t) PrimitiveExtentPad;
  primitive_info=(PrimitiveInfo *) AcquireQuantumMemory((size_t) number_points,
    sizeof(*primitive_info));
  if (primitive_info == (PrimitiveInfo *) NULL)
    {
      primitive=DestroyString(primitive);
      for ( ; n >= 0; n--)
        graphic_context[n]=DestroyDrawInfo(graphic_context[n]);
      graphic_context=(DrawInfo **) RelinquishMagickMemory(graphic_context);
      ThrowBinaryImageException(ResourceLimitError,"MemoryAllocationFailed",
        image->filename);
    }
  (void) memset(primitive_info,0,(size_t) number_points*
    sizeof(*primitive_info));
  (void) memset(&mvg_info,0,sizeof(mvg_info));
  mvg_info.primitive_info=(&primitive_info);
  mvg_info.extent=(&number_points);
  mvg_info.exception=(&image->exception);
  graphic_context[n]=CloneDrawInfo((ImageInfo *) NULL,draw_info);
  graphic_context[n]->viewbox=image->page;
  if ((image->page.width == 0) || (image->page.height == 0))
    {
      graphic_context[n]->viewbox.width=image->columns;
      graphic_context[n]->viewbox.height=image->rows;
    }
  token=AcquireString(primitive);
  extent=strlen(token)+MaxTextExtent;
  cursor=0.0;
  defsDepth=0;
  symbolDepth=0;
  macros=GetMVGMacros(primitive);
  status=QueryColorDatabase("#000000",&start_color,&image->exception);
  for (q=primitive; *q != '\0'; )
  {
    /*
      Interpret graphic primitive.
    */
    if (GetNextToken(q,&q,MaxTextExtent,keyword) < 1)
      break;
    if (*keyword == '\0')
      break;
    if (*keyword == '#')
      {
        /*
          Comment.
        */
        while ((*q != '\n') && (*q != '\0'))
          q++;
        continue;
      }
    p=q-strlen(keyword)-1;
    primitive_type=UndefinedPrimitive;
    current=graphic_context[n]->affine;
    GetAffineMatrix(&affine);
    *token='\0';
    switch (*keyword)
    {
      case ';':
        break;
      case 'a':
      case 'A':
      {
        if (LocaleCompare("affine",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            affine.sx=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            affine.rx=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            affine.ry=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            affine.sy=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            affine.tx=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            affine.ty=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        if (LocaleCompare("arc",keyword) == 0)
          {
            primitive_type=ArcPrimitive;
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'b':
      case 'B':
      {
        if (LocaleCompare("bezier",keyword) == 0)
          {
            primitive_type=BezierPrimitive;
            break;
          }
        if (LocaleCompare("border-color",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            status&=QueryColorDatabase(token,&graphic_context[n]->border_color,
              &image->exception);
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'c':
      case 'C':
      {
        if (LocaleCompare("class",keyword) == 0)
          {
            const char
              *mvg_class;

            (void) GetNextToken(q,&q,extent,token);
            if (*token == '\0')
              {
                status=MagickFalse;
                break;
              }
            if (LocaleCompare(token,graphic_context[n]->id) == 0)
              break;
            mvg_class=(const char *) GetValueFromSplayTree(macros,token);
            if ((mvg_class != (const char *) NULL) && (p > primitive))
              {
                char
                  *elements;

                ssize_t
                  offset;

                /*
                  Inject class elements in stream.
                */
                offset=(ssize_t) (p-primitive);
                elements=AcquireString(primitive);
                elements[offset]='\0';
                (void) ConcatenateString(&elements,mvg_class);
                (void) ConcatenateString(&elements,"\n");
                (void) ConcatenateString(&elements,q);
                primitive=DestroyString(primitive);
                primitive=elements;
                q=primitive+offset;
              }
            break;
          }
        if (LocaleCompare("clip-path",keyword) == 0)
          {
            const char
              *clip_path;

            /*
              Take a node from within the MVG document, and duplicate it here.
            */
            (void) GetNextToken(q,&q,extent,token);
            if (*token == '\0')
              {
                status=MagickFalse;
                break;
              }
            (void) CloneString(&graphic_context[n]->clip_mask,token);
            clip_path=(const char *) GetValueFromSplayTree(macros,token);
            if (clip_path != (const char *) NULL)
              {
                if (graphic_context[n]->clipping_mask != (Image *) NULL)
                  graphic_context[n]->clipping_mask=
                    DestroyImage(graphic_context[n]->clipping_mask);
                graphic_context[n]->clipping_mask=DrawClippingMask(image,
                  graphic_context[n],token,clip_path,&image->exception);
                if (graphic_context[n]->compliance != SVGCompliance)
                  {
                    const char
                      *clip_path;

                    clip_path=(const char *) GetValueFromSplayTree(macros,
                      graphic_context[n]->clip_mask);
                    if (clip_path != (const char *) NULL)
                      (void) SetImageArtifact(image,
                        graphic_context[n]->clip_mask,clip_path);
                    status&=DrawClipPath(image,graphic_context[n],
                      graphic_context[n]->clip_mask);
                  }
              }
            break;
          }
        if (LocaleCompare("clip-rule",keyword) == 0)
          {
            ssize_t
              fill_rule;

            (void) GetNextToken(q,&q,extent,token);
            fill_rule=ParseCommandOption(MagickFillRuleOptions,MagickFalse,
              token);
            if (fill_rule == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->fill_rule=(FillRule) fill_rule;
            break;
          }
        if (LocaleCompare("clip-units",keyword) == 0)
          {
            ssize_t
              clip_units;

            (void) GetNextToken(q,&q,extent,token);
            clip_units=ParseCommandOption(MagickClipPathOptions,MagickFalse,
              token);
            if (clip_units == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->clip_units=(ClipPathUnits) clip_units;
            if (clip_units == ObjectBoundingBox)
              {
                GetAffineMatrix(&current);
                affine.sx=draw_info->bounds.x2;
                affine.sy=draw_info->bounds.y2;
                affine.tx=draw_info->bounds.x1;
                affine.ty=draw_info->bounds.y1;
                break;
              }
            break;
          }
        if (LocaleCompare("circle",keyword) == 0)
          {
            primitive_type=CirclePrimitive;
            break;
          }
        if (LocaleCompare("color",keyword) == 0)
          {
            primitive_type=ColorPrimitive;
            break;
          }
        if (LocaleCompare("compliance",keyword) == 0)
          {
            /*
              MVG compliance associates a clipping mask with an image; SVG
              compliance associates a clipping mask with a graphics context.
            */
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->compliance=(ComplianceType) ParseCommandOption(
              MagickComplianceOptions,MagickFalse,token);
            break;
          }
        if (LocaleCompare("currentColor",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'd':
      case 'D':
      {
        if (LocaleCompare("decorate",keyword) == 0)
          {
            ssize_t
              decorate;

            (void) GetNextToken(q,&q,extent,token);
            decorate=ParseCommandOption(MagickDecorateOptions,MagickFalse,
              token);
            if (decorate == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->decorate=(DecorationType) decorate;
            break;
          }
        if (LocaleCompare("density",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            (void) CloneString(&graphic_context[n]->density,token);
            break;
          }
        if (LocaleCompare("direction",keyword) == 0)
          {
            ssize_t
              direction;

            (void) GetNextToken(q,&q,extent,token);
            direction=ParseCommandOption(MagickDirectionOptions,MagickFalse,
              token);
            if (direction == -1)
              status=MagickFalse;
            else
              graphic_context[n]->direction=(DirectionType) direction;
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'e':
      case 'E':
      {
        if (LocaleCompare("ellipse",keyword) == 0)
          {
            primitive_type=EllipsePrimitive;
            break;
          }
        if (LocaleCompare("encoding",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            (void) CloneString(&graphic_context[n]->encoding,token);
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'f':
      case 'F':
      {
        if (LocaleCompare("fill",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            if (graphic_context[n]->clip_path != MagickFalse)
              break;
            (void) FormatLocaleString(pattern,MaxTextExtent,"%s",token);
            if (GetImageArtifact(image,pattern) != (const char *) NULL)
              (void) DrawPatternPath(image,draw_info,token,
                &graphic_context[n]->fill_pattern);
            else
              {
                status&=QueryColorDatabase(token,&graphic_context[n]->fill,
                  &image->exception);
                if (graphic_context[n]->fill_opacity != OpaqueOpacity)
                  graphic_context[n]->fill.opacity=ClampToQuantum(
                    graphic_context[n]->fill_opacity);
              }
            break;
          }
        if (LocaleCompare("fill-opacity",keyword) == 0)
          {
            double
              opacity;

            (void) GetNextToken(q,&q,extent,token);
            if (graphic_context[n]->clip_path != MagickFalse)
              break;
            factor=strchr(token,'%') != (char *) NULL ? 0.01 : 1.0;
            opacity=MagickMin(MagickMax(factor*
              GetDrawValue(token,&next_token),0.0),1.0);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            if (graphic_context[n]->compliance == SVGCompliance)
              graphic_context[n]->fill_opacity*=(1.0-opacity);
            else
              graphic_context[n]->fill_opacity=(QuantumRange-
                graphic_context[n]->fill_opacity)*(1.0-opacity);
            if (graphic_context[n]->fill.opacity != TransparentOpacity)
              graphic_context[n]->fill.opacity=(Quantum)
                graphic_context[n]->fill_opacity;
            else
              graphic_context[n]->fill.opacity=ClampToQuantum(QuantumRange*
                opacity);
            break;
          }
        if (LocaleCompare("fill-rule",keyword) == 0)
          {
            ssize_t
              fill_rule;

            (void) GetNextToken(q,&q,extent,token);
            fill_rule=ParseCommandOption(MagickFillRuleOptions,MagickFalse,
              token);
            if (fill_rule == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->fill_rule=(FillRule) fill_rule;
            break;
          }
        if (LocaleCompare("font",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            (void) CloneString(&graphic_context[n]->font,token);
            if (LocaleCompare("none",token) == 0)
              graphic_context[n]->font=(char *) RelinquishMagickMemory(
                graphic_context[n]->font);
            break;
          }
        if (LocaleCompare("font-family",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            (void) CloneString(&graphic_context[n]->family,token);
            break;
          }
        if (LocaleCompare("font-size",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->pointsize=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        if (LocaleCompare("font-stretch",keyword) == 0)
          {
            ssize_t
              stretch;

            (void) GetNextToken(q,&q,extent,token);
            stretch=ParseCommandOption(MagickStretchOptions,MagickFalse,token);
            if (stretch == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->stretch=(StretchType) stretch;
            break;
          }
        if (LocaleCompare("font-style",keyword) == 0)
          {
            ssize_t
              style;

            (void) GetNextToken(q,&q,extent,token);
            style=ParseCommandOption(MagickStyleOptions,MagickFalse,token);
            if (style == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->style=(StyleType) style;
            break;
          }
        if (LocaleCompare("font-weight",keyword) == 0)
          {
            ssize_t
              weight;

            (void) GetNextToken(q,&q,extent,token);
            weight=ParseCommandOption(MagickWeightOptions,MagickFalse,token);
            if (weight == -1)
              weight=(ssize_t) StringToUnsignedLong(token);
            graphic_context[n]->weight=(size_t) weight;
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'g':
      case 'G':
      {
        if (LocaleCompare("gradient-units",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            break;
          }
        if (LocaleCompare("gravity",keyword) == 0)
          {
            ssize_t
              gravity;

            (void) GetNextToken(q,&q,extent,token);
            gravity=ParseCommandOption(MagickGravityOptions,MagickFalse,token);
            if (gravity == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->gravity=(GravityType) gravity;
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'i':
      case 'I':
      {
        if (LocaleCompare("image",keyword) == 0)
          {
            ssize_t
              compose;

            primitive_type=ImagePrimitive;
            (void) GetNextToken(q,&q,extent,token);
            compose=ParseCommandOption(MagickComposeOptions,MagickFalse,token);
            if (compose == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->compose=(CompositeOperator) compose;
            break;
          }
        if (LocaleCompare("interline-spacing",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->interline_spacing=GetDrawValue(token,
              &next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        if (LocaleCompare("interword-spacing",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->interword_spacing=GetDrawValue(token,
              &next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'k':
      case 'K':
      {
        if (LocaleCompare("kerning",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->kerning=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'l':
      case 'L':
      {
        if (LocaleCompare("letter-spacing",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            if (IsPoint(token) == MagickFalse)
              break;
            clone_info=CloneDrawInfo((ImageInfo *) NULL,graphic_context[n]);
            clone_info->text=AcquireString(" ");
            status&=GetTypeMetrics(image,clone_info,&metrics);
            graphic_context[n]->kerning=metrics.width*
              GetDrawValue(token,&next_token);
            clone_info=DestroyDrawInfo(clone_info);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        if (LocaleCompare("line",keyword) == 0)
          {
            primitive_type=LinePrimitive;
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'm':
      case 'M':
      {
        if (LocaleCompare("mask",keyword) == 0)
          {
            const char
              *mask_path;

            /*
              Take a node from within the MVG document, and duplicate it here.
            */
            (void) GetNextToken(q,&q,extent,token);
            mask_path=(const char *) GetValueFromSplayTree(macros,token);
            if (mask_path != (const char *) NULL)
              {
                if (graphic_context[n]->composite_mask != (Image *) NULL)
                  graphic_context[n]->composite_mask=
                    DestroyImage(graphic_context[n]->composite_mask);
                graphic_context[n]->composite_mask=DrawCompositeMask(image,
                  graphic_context[n],token,mask_path,&image->exception);
                if (graphic_context[n]->compliance != SVGCompliance)
                  status=SetImageMask(image,graphic_context[n]->composite_mask);
              }
            break;
          }
        if (LocaleCompare("matte",keyword) == 0)
          {
            primitive_type=MattePrimitive;
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'o':
      case 'O':
      {
        if (LocaleCompare("offset",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            break;
          }
        if (LocaleCompare("opacity",keyword) == 0)
          {
            double
              opacity;

            (void) GetNextToken(q,&q,extent,token);
            if (graphic_context[n]->clip_path != MagickFalse)
              break;
            factor=strchr(token,'%') != (char *) NULL ? 0.01 : 1.0;
            opacity=MagickMin(MagickMax(factor*
              GetDrawValue(token,&next_token),0.0),1.0);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            if (graphic_context[n]->compliance == SVGCompliance)
              {
                graphic_context[n]->fill_opacity*=(1.0-opacity);
                graphic_context[n]->stroke_opacity*=(1.0-opacity);
              }
            else
              {
                graphic_context[n]->fill_opacity=(QuantumRange-
                  graphic_context[n]->fill_opacity)*(1.0-opacity);
                graphic_context[n]->stroke_opacity=(QuantumRange-
                  graphic_context[n]->stroke_opacity)*(1.0-opacity);
              }
            break;
          }
        status=MagickFalse;
        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)
          {
            if (GetNextToken(q,&q,extent,token) < 1)
              break;
            if (LocaleCompare("class",token) == 0)
              break;
            if (LocaleCompare("clip-path",token) == 0)
              break;
            if (LocaleCompare("defs",token) == 0)
              {
                defsDepth--;
                graphic_context[n]->render=defsDepth > 0 ? MagickFalse :
                  MagickTrue;
                break;
              }
            if (LocaleCompare("gradient",token) == 0)
              break;
            if (LocaleCompare("graphic-context",token) == 0)
              {
                if (n <= 0)
                  {
                    (void) ThrowMagickException(&image->exception,
                      GetMagickModule(),DrawError,
                      "UnbalancedGraphicContextPushPop","`%s'",token);
                    status=MagickFalse;
                    n=0;
                    break;
                  }
                if ((graphic_context[n]->clip_mask != (char *) NULL) &&
                    (graphic_context[n]->compliance != SVGCompliance))
                  if (LocaleCompare(graphic_context[n]->clip_mask,
                      graphic_context[n-1]->clip_mask) != 0)
                    status=SetImageClipMask(image,(Image *) NULL);
                graphic_context[n]=DestroyDrawInfo(graphic_context[n]);
                n--;
                break;
              }
            if (LocaleCompare("mask",token) == 0)
              break;
            if (LocaleCompare("pattern",token) == 0)
              break;
            if (LocaleCompare("symbol",token) == 0)
              {
                symbolDepth--;
                graphic_context[n]->render=symbolDepth > 0 ? MagickFalse :
                  MagickTrue;
                break;
              }
            status=MagickFalse;
            break;
          }
        if (LocaleCompare("push",keyword) == 0)
          {
            if (GetNextToken(q,&q,extent,token) < 1)
              break;
            if (LocaleCompare("class",token) == 0)
              {
                /*
                  Class context.
                */
                for (p=q; *q != '\0'; )
                {
                  if (GetNextToken(q,&q,extent,token) < 1)
                    break;
                  if (LocaleCompare(token,"pop") != 0)
                    continue;
                  (void) GetNextToken(q,(const char **) NULL,extent,token);
                  if (LocaleCompare(token,"class") != 0)
                    continue;
                  break;
                }
                (void) GetNextToken(q,&q,extent,token);
                break;
              }
            if (LocaleCompare("clip-path",token) == 0)
              {
                (void) GetNextToken(q,&q,extent,token);
                for (p=q; *q != '\0'; )
                {
                  if (GetNextToken(q,&q,extent,token) < 1)
                    break;
                  if (LocaleCompare(token,"pop") != 0)
                    continue;
                  (void) GetNextToken(q,(const char **) NULL,extent,token);
                  if (LocaleCompare(token,"clip-path") != 0)
                    continue;
                  break;
                }
                if ((q == (char *) NULL) || (p == (char *) NULL) || ((q-4) < p))
                  {
                    status=MagickFalse;
                    break;
                  }
                (void) GetNextToken(q,&q,extent,token);
                break;
              }
            if (LocaleCompare("defs",token) == 0)
              {
                defsDepth++;
                graphic_context[n]->render=defsDepth > 0 ? MagickFalse :
                  MagickTrue;
                break;
              }
            if (LocaleCompare("gradient",token) == 0)
              {
                char
                  key[2*MaxTextExtent],
                  name[MaxTextExtent],
                  type[MaxTextExtent];

                SegmentInfo
                  segment;

                (void) GetNextToken(q,&q,extent,token);
                (void) CopyMagickString(name,token,MaxTextExtent);
                (void) GetNextToken(q,&q,extent,token);
                (void) CopyMagickString(type,token,MaxTextExtent);
                (void) GetNextToken(q,&q,extent,token);
                segment.x1=GetDrawValue(token,&next_token);
                if (token == next_token)
                  ThrowPointExpectedException(image,token);
                (void) GetNextToken(q,&q,extent,token);
                if (*token == ',')
                  (void) GetNextToken(q,&q,extent,token);
                segment.y1=GetDrawValue(token,&next_token);
                if (token == next_token)
                  ThrowPointExpectedException(image,token);
                (void) GetNextToken(q,&q,extent,token);
                if (*token == ',')
                  (void) GetNextToken(q,&q,extent,token);
                segment.x2=GetDrawValue(token,&next_token);
                if (token == next_token)
                  ThrowPointExpectedException(image,token);
                (void) GetNextToken(q,&q,extent,token);
                if (*token == ',')
                  (void) GetNextToken(q,&q,extent,token);
                segment.y2=GetDrawValue(token,&next_token);
                if (token == next_token)
                  ThrowPointExpectedException(image,token);
                if (LocaleCompare(type,"radial") == 0)
                  {
                    (void) GetNextToken(q,&q,extent,token);
                    if (*token == ',')
                      (void) GetNextToken(q,&q,extent,token);
                  }
                for (p=q; *q != '\0'; )
                {
                  if (GetNextToken(q,&q,extent,token) < 1)
                    break;
                  if (LocaleCompare(token,"pop") != 0)
                    continue;
                  (void) GetNextToken(q,(const char **) NULL,extent,token);
                  if (LocaleCompare(token,"gradient") != 0)
                    continue;
                  break;
                }
                if ((q == (char *) NULL) || (p == (char *) NULL) || ((q-4) < p))
                  {
                    status=MagickFalse;
                    break;
                  }
                (void) CopyMagickString(token,p,(size_t) (q-p-4+1));
                bounds.x1=graphic_context[n]->affine.sx*segment.x1+
                  graphic_context[n]->affine.ry*segment.y1+
                  graphic_context[n]->affine.tx;
                bounds.y1=graphic_context[n]->affine.rx*segment.x1+
                  graphic_context[n]->affine.sy*segment.y1+
                  graphic_context[n]->affine.ty;
                bounds.x2=graphic_context[n]->affine.sx*segment.x2+
                  graphic_context[n]->affine.ry*segment.y2+
                  graphic_context[n]->affine.tx;
                bounds.y2=graphic_context[n]->affine.rx*segment.x2+
                  graphic_context[n]->affine.sy*segment.y2+
                  graphic_context[n]->affine.ty;
                (void) FormatLocaleString(key,MaxTextExtent,"%s",name);
                (void) SetImageArtifact(image,key,token);
                (void) FormatLocaleString(key,MaxTextExtent,"%s-type",name);
                (void) SetImageArtifact(image,key,type);
                (void) FormatLocaleString(key,MaxTextExtent,"%s-geometry",name);
                (void) FormatLocaleString(geometry,MaxTextExtent,
                  "%gx%g%+.15g%+.15g",
                  MagickMax(fabs(bounds.x2-bounds.x1+1.0),1.0),
                  MagickMax(fabs(bounds.y2-bounds.y1+1.0),1.0),
                  bounds.x1,bounds.y1);
                (void) SetImageArtifact(image,key,geometry);
                (void) GetNextToken(q,&q,extent,token);
                break;
              }
            if (LocaleCompare("graphic-context",token) == 0)
              {
                n++;
                graphic_context=(DrawInfo **) ResizeQuantumMemory(
                  graphic_context,(size_t) (n+1),sizeof(*graphic_context));
                if (graphic_context == (DrawInfo **) NULL)
                  {
                    (void) ThrowMagickException(&image->exception,
                      GetMagickModule(),ResourceLimitError,
                      "MemoryAllocationFailed","`%s'",image->filename);
                    break;
                  }
                graphic_context[n]=CloneDrawInfo((ImageInfo *) NULL,
                  graphic_context[n-1]);
                if (*q == '"')
                  {
                    (void) GetNextToken(q,&q,extent,token);
                    (void) CloneString(&graphic_context[n]->id,token);
                  }
                break;
              }
            if (LocaleCompare("mask",token) == 0)
              {
                (void) GetNextToken(q,&q,extent,token);
                break;
              }
            if (LocaleCompare("pattern",token) == 0)
              {
                RectangleInfo
                  bounds;

                (void) GetNextToken(q,&q,extent,token);
                (void) CopyMagickString(name,token,MaxTextExtent);
                (void) GetNextToken(q,&q,extent,token);
                bounds.x=CastDoubleToLong(ceil(GetDrawValue(token,
                  &next_token)-0.5));
                if (token == next_token)
                  ThrowPointExpectedException(image,token);
                (void) GetNextToken(q,&q,extent,token);
                if (*token == ',')
                  (void) GetNextToken(q,&q,extent,token);
                bounds.y=CastDoubleToLong(ceil(GetDrawValue(token,
                  &next_token)-0.5));
                if (token == next_token)
                  ThrowPointExpectedException(image,token);
                (void) GetNextToken(q,&q,extent,token);
                if (*token == ',')
                  (void) GetNextToken(q,&q,extent,token);
                bounds.width=(size_t) floor(GetDrawValue(token,&next_token)+
                  0.5);
                if (token == next_token)
                  ThrowPointExpectedException(image,token);
                (void) GetNextToken(q,&q,extent,token);
                if (*token == ',')
                  (void) GetNextToken(q,&q,extent,token);
                bounds.height=(size_t) floor(GetDrawValue(token,&next_token)+
                  0.5);
                if (token == next_token)
                  ThrowPointExpectedException(image,token);
                for (p=q; *q != '\0'; )
                {
                  if (GetNextToken(q,&q,extent,token) < 1)
                    break;
                  if (LocaleCompare(token,"pop") != 0)
                    continue;
                  (void) GetNextToken(q,(const char **) NULL,extent,token);
                  if (LocaleCompare(token,"pattern") != 0)
                    continue;
                  break;
                }
                if ((q == (char *) NULL) || (p == (char *) NULL) || ((q-4) < p))
                  {
                    status=MagickFalse;
                    break;
                  }
                (void) CopyMagickString(token,p,(size_t) (q-p-4+1));
                (void) FormatLocaleString(key,MaxTextExtent,"%s",name);
                (void) SetImageArtifact(image,key,token);
                (void) FormatLocaleString(key,MaxTextExtent,"%s-geometry",name);
                (void) FormatLocaleString(geometry,MaxTextExtent,
                  "%.20gx%.20g%+.20g%+.20g",(double) bounds.width,(double)
                  bounds.height,(double) bounds.x,(double) bounds.y);
                (void) SetImageArtifact(image,key,geometry);
                (void) GetNextToken(q,&q,extent,token);
                break;
              }
            if (LocaleCompare("symbol",token) == 0)
              {
                symbolDepth++;
                graphic_context[n]->render=symbolDepth > 0 ? MagickFalse :
                  MagickTrue;
                break;
              }
            status=MagickFalse;
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'r':
      case 'R':
      {
        if (LocaleCompare("rectangle",keyword) == 0)
          {
            primitive_type=RectanglePrimitive;
            break;
          }
        if (LocaleCompare("rotate",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            angle=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            affine.sx=cos(DegreesToRadians(fmod((double) angle,360.0)));
            affine.rx=sin(DegreesToRadians(fmod((double) angle,360.0)));
            affine.ry=(-sin(DegreesToRadians(fmod((double) angle,360.0))));
            affine.sy=cos(DegreesToRadians(fmod((double) angle,360.0)));
            break;
          }
        if (LocaleCompare("roundRectangle",keyword) == 0)
          {
            primitive_type=RoundRectanglePrimitive;
            break;
          }
        status=MagickFalse;
        break;
      }
      case 's':
      case 'S':
      {
        if (LocaleCompare("scale",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            affine.sx=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            affine.sy=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        if (LocaleCompare("skewX",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            angle=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            affine.ry=sin(DegreesToRadians(angle));
            break;
          }
        if (LocaleCompare("skewY",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            angle=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            affine.rx=(-tan(DegreesToRadians(angle)/2.0));
            break;
          }
        if (LocaleCompare("stop-color",keyword) == 0)
          {
            GradientType
              type;

            PixelPacket
              stop_color;

            (void) GetNextToken(q,&q,extent,token);
            status&=QueryColorDatabase(token,&stop_color,&image->exception);
            type=LinearGradient;
            if (draw_info->gradient.type == RadialGradient)
              type=RadialGradient;
            (void) GradientImage(image,type,PadSpread,&start_color,&stop_color);
            start_color=stop_color;
            (void) GetNextToken(q,&q,extent,token);
            break;
          }
        if (LocaleCompare("stroke",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            if (graphic_context[n]->clip_path != MagickFalse)
              break;
            (void) FormatLocaleString(pattern,MaxTextExtent,"%s",token);
            if (GetImageArtifact(image,pattern) != (const char *) NULL)
              (void) DrawPatternPath(image,draw_info,token,
                &graphic_context[n]->stroke_pattern);
            else
              {
                status&=QueryColorDatabase(token,&graphic_context[n]->stroke,
                  &image->exception);
                if (graphic_context[n]->stroke_opacity != OpaqueOpacity)
                  graphic_context[n]->stroke.opacity=ClampToQuantum(
                    graphic_context[n]->stroke_opacity);
              }
            break;
          }
        if (LocaleCompare("stroke-antialias",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->stroke_antialias=StringToLong(token) != 0 ?
              MagickTrue : MagickFalse;
            break;
          }
        if (LocaleCompare("stroke-dasharray",keyword) == 0)
          {
            if (graphic_context[n]->dash_pattern != (double *) NULL)
              graphic_context[n]->dash_pattern=(double *)
                RelinquishMagickMemory(graphic_context[n]->dash_pattern);
            if (IsPoint(q) != MagickFalse)
              {
                const char
                  *p;

                p=q;
                (void) GetNextToken(p,&p,extent,token);
                if (*token == ',')
                  (void) GetNextToken(p,&p,extent,token);
                for (x=0; IsPoint(token) != MagickFalse; x++)
                {
                  (void) GetNextToken(p,&p,extent,token);
                  if (*token == ',')
                    (void) GetNextToken(p,&p,extent,token);
                }
                graphic_context[n]->dash_pattern=(double *)
                  AcquireQuantumMemory((size_t) (2*x+2),
                  sizeof(*graphic_context[n]->dash_pattern));
                if (graphic_context[n]->dash_pattern == (double *) NULL)
                  {
                    (void) ThrowMagickException(&image->exception,
                      GetMagickModule(),ResourceLimitError,
                      "MemoryAllocationFailed","`%s'",image->filename);
                    status=MagickFalse;
                    break;
                  }
                (void) memset(graphic_context[n]->dash_pattern,0,(size_t)
                  (2*x+2)*sizeof(*graphic_context[n]->dash_pattern));
                for (j=0; j < x; j++)
                {
                  (void) GetNextToken(q,&q,extent,token);
                  if (*token == ',')
                    (void) GetNextToken(q,&q,extent,token);
                  graphic_context[n]->dash_pattern[j]=GetDrawValue(token,
                    &next_token);
                  if (token == next_token)
                    ThrowPointExpectedException(image,token);
                  if (graphic_context[n]->dash_pattern[j] < 0.0)
                    status=MagickFalse;
                }
                if ((x & 0x01) != 0)
                  for ( ; j < (2*x); j++)
                    graphic_context[n]->dash_pattern[j]=
                      graphic_context[n]->dash_pattern[j-x];
                graphic_context[n]->dash_pattern[j]=0.0;
                break;
              }
            (void) GetNextToken(q,&q,extent,token);
            break;
          }
        if (LocaleCompare("stroke-dashoffset",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->dash_offset=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        if (LocaleCompare("stroke-linecap",keyword) == 0)
          {
            ssize_t
              linecap;

            (void) GetNextToken(q,&q,extent,token);
            linecap=ParseCommandOption(MagickLineCapOptions,MagickFalse,token);
            if (linecap == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->linecap=(LineCap) linecap;
            break;
          }
        if (LocaleCompare("stroke-linejoin",keyword) == 0)
          {
            ssize_t
              linejoin;

            (void) GetNextToken(q,&q,extent,token);
            linejoin=ParseCommandOption(MagickLineJoinOptions,MagickFalse,
              token);
            if (linejoin == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->linejoin=(LineJoin) linejoin;
            break;
          }
        if (LocaleCompare("stroke-miterlimit",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->miterlimit=StringToUnsignedLong(token);
            break;
          }
        if (LocaleCompare("stroke-opacity",keyword) == 0)
          {
            double
              opacity;

            (void) GetNextToken(q,&q,extent,token);
            if (graphic_context[n]->clip_path != MagickFalse)
              break;
            factor=strchr(token,'%') != (char *) NULL ? 0.01 : 1.0;
            opacity=MagickMin(MagickMax(factor*
              GetDrawValue(token,&next_token),0.0),1.0);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            if (graphic_context[n]->compliance == SVGCompliance)
              graphic_context[n]->stroke_opacity*=(1.0-opacity);
            else
              graphic_context[n]->stroke_opacity=(QuantumRange-
                graphic_context[n]->stroke_opacity)*(1.0-opacity);
            if (graphic_context[n]->stroke.opacity != TransparentOpacity)
              graphic_context[n]->stroke.opacity=(Quantum)
                graphic_context[n]->stroke_opacity;
            else
              graphic_context[n]->stroke.opacity=ClampToQuantum(QuantumRange*
                opacity);
            break;
          }
        if (LocaleCompare("stroke-width",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            if (graphic_context[n]->clip_path != MagickFalse)
              break;
            graphic_context[n]->stroke_width=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        status=MagickFalse;
        break;
      }
      case 't':
      case 'T':
      {
        if (LocaleCompare("text",keyword) == 0)
          {
            primitive_type=TextPrimitive;
            cursor=0.0;
            break;
          }
        if (LocaleCompare("text-align",keyword) == 0)
          {
            ssize_t
              align;

            (void) GetNextToken(q,&q,extent,token);
            align=ParseCommandOption(MagickAlignOptions,MagickFalse,token);
            if (align == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->align=(AlignType) align;
            break;
          }
        if (LocaleCompare("text-anchor",keyword) == 0)
          {
            ssize_t
              align;

            (void) GetNextToken(q,&q,extent,token);
            align=ParseCommandOption(MagickAlignOptions,MagickFalse,token);
            if (align == -1)
              {
                status=MagickFalse;
                break;
              }
            graphic_context[n]->align=(AlignType) align;
            break;
          }
        if (LocaleCompare("text-antialias",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->text_antialias=StringToLong(token) != 0 ?
              MagickTrue : MagickFalse;
            break;
          }
        if (LocaleCompare("text-undercolor",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            status&=QueryColorDatabase(token,&graphic_context[n]->undercolor,
              &image->exception);
            break;
          }
        if (LocaleCompare("translate",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            affine.tx=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            affine.ty=GetDrawValue(token,&next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'u':
      case 'U':
      {
        if (LocaleCompare("use",keyword) == 0)
          {
            const char
              *use;

            /*
              Get a macro from the MVG document, and "use" it here.
            */
            (void) GetNextToken(q,&q,extent,token);
            use=(const char *) GetValueFromSplayTree(macros,token);
            if (use != (const char *) NULL)
              {
                clone_info=CloneDrawInfo((ImageInfo *) NULL,graphic_context[n]);
                (void) CloneString(&clone_info->primitive,use);
                status=RenderMVGContent(image,clone_info,depth+1);
                clone_info=DestroyDrawInfo(clone_info);
              }
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'v':
      case 'V':
      {
        if (LocaleCompare("viewbox",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->viewbox.x=CastDoubleToLong(ceil(
              GetDrawValue(token,&next_token)-0.5));
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->viewbox.y=CastDoubleToLong(ceil(
              GetDrawValue(token,&next_token)-0.5));
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->viewbox.width=(size_t) floor(GetDrawValue(
              token,&next_token)+0.5);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            (void) GetNextToken(q,&q,extent,token);
            if (*token == ',')
              (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->viewbox.height=(size_t) floor(GetDrawValue(
              token,&next_token)+0.5);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        status=MagickFalse;
        break;
      }
      case 'w':
      case 'W':
      {
        if (LocaleCompare("word-spacing",keyword) == 0)
          {
            (void) GetNextToken(q,&q,extent,token);
            graphic_context[n]->interword_spacing=GetDrawValue(token,
              &next_token);
            if (token == next_token)
              ThrowPointExpectedException(image,token);
            break;
          }
        status=MagickFalse;
        break;
      }
      default:
      {
        status=MagickFalse;
        break;
      }
    }
    if (status == MagickFalse)
      break;
    if ((fabs(affine.sx-1.0) >= MagickEpsilon) ||
        (fabs(affine.rx) >= MagickEpsilon) || (fabs(affine.ry) >= MagickEpsilon) ||
        (fabs(affine.sy-1.0) >= MagickEpsilon) ||
        (fabs(affine.tx) >= MagickEpsilon) || (fabs(affine.ty) >= MagickEpsilon))
      {
        graphic_context[n]->affine.sx=current.sx*affine.sx+current.ry*affine.rx;
        graphic_context[n]->affine.rx=current.rx*affine.sx+current.sy*affine.rx;
        graphic_context[n]->affine.ry=current.sx*affine.ry+current.ry*affine.sy;
        graphic_context[n]->affine.sy=current.rx*affine.ry+current.sy*affine.sy;
        graphic_context[n]->affine.tx=current.sx*affine.tx+current.ry*affine.ty+
          current.tx;
        graphic_context[n]->affine.ty=current.rx*affine.tx+current.sy*affine.ty+
          current.ty;
      }
    if (primitive_type == UndefinedPrimitive)
      {
        if ((image->debug != MagickFalse) && (q > p))
          (void) LogMagickEvent(DrawEvent,GetMagickModule(),"  %.*s",(int)
            (q-p-1),p);
        continue;
      }
    /*
      Parse the primitive attributes.
    */
    for (i=0; primitive_info[i].primitive != UndefinedPrimitive; i++)
      if ((primitive_info[i].primitive == TextPrimitive) ||
          (primitive_info[i].primitive == ImagePrimitive))
        if (primitive_info[i].text != (char *) NULL)
          primitive_info[i].text=DestroyString(primitive_info[i].text);
    i=0;
    mvg_info.offset=i;
    j=0;
    primitive_info[0].point.x=0.0;
    primitive_info[0].point.y=0.0;
    primitive_info[0].coordinates=0;
    primitive_info[0].method=FloodfillMethod;
    primitive_info[0].closed_subpath=MagickFalse;
    for (x=0; *q != '\0'; x++)
    {
      /*
        Define points.
      */
      if (IsPoint(q) == MagickFalse)
        break;
      (void) GetNextToken(q,&q,extent,token);
      point.x=GetDrawValue(token,&next_token);
      if (token == next_token)
        ThrowPointExpectedException(image,token);
      (void) GetNextToken(q,&q,extent,token);
      if (*token == ',')
        (void) GetNextToken(q,&q,extent,token);
      point.y=GetDrawValue(token,&next_token);
      if (token == next_token)
        ThrowPointExpectedException(image,token);
      (void) GetNextToken(q,(const char **) NULL,extent,token);
      if (*token == ',')
        (void) GetNextToken(q,&q,extent,token);
      primitive_info[i].primitive=primitive_type;
      primitive_info[i].point=point;
      primitive_info[i].coordinates=0;
      primitive_info[i].method=FloodfillMethod;
      primitive_info[i].closed_subpath=MagickFalse;
      i++;
      mvg_info.offset=i;
      if (i < (ssize_t) number_points)
        continue;
      status&=CheckPrimitiveExtent(&mvg_info,(double) number_points);
    }
    if (status == MagickFalse)
      break;
    primitive_info[j].primitive=primitive_type;
    primitive_info[j].coordinates=(size_t) x;
    primitive_info[j].method=FloodfillMethod;
    primitive_info[j].closed_subpath=MagickFalse;
    /*
      Circumscribe primitive within a circle.
    */
    bounds.x1=primitive_info[j].point.x;
    bounds.y1=primitive_info[j].point.y;
    bounds.x2=primitive_info[j].point.x;
    bounds.y2=primitive_info[j].point.y;
    for (k=1; k < (ssize_t) primitive_info[j].coordinates; k++)
    {
      point=primitive_info[j+k].point;
      if (point.x < bounds.x1)
        bounds.x1=point.x;
      if (point.y < bounds.y1)
        bounds.y1=point.y;
      if (point.x > bounds.x2)
        bounds.x2=point.x;
      if (point.y > bounds.y2)
        bounds.y2=point.y;
    }
    /*
      Speculate how many points our primitive might consume.
    */
    coordinates=(double) primitive_info[j].coordinates;
    switch (primitive_type)
    {
      case RectanglePrimitive:
      {
        coordinates*=5.0;
        break;
      }
      case RoundRectanglePrimitive:
      {
        double
          alpha,
          beta,
          radius;

        alpha=bounds.x2-bounds.x1;
        beta=bounds.y2-bounds.y1;
        radius=hypot(alpha,beta);
        coordinates*=5.0;
        coordinates+=2.0*((size_t) ceil((double) MagickPI*radius))+6.0*
          BezierQuantum+360.0;
        break;
      }
      case BezierPrimitive:
      {
        coordinates=(BezierQuantum*(double) primitive_info[j].coordinates);
        break;
      }
      case PathPrimitive:
      {
        char
          *s,
          *t;

        (void) GetNextToken(q,&q,extent,token);
        coordinates=1.0;
        t=token;
        for (s=token; *s != '\0'; s=t)
        {
          double
            value;

          value=GetDrawValue(s,&t);
          (void) value;
          if (s == t)
            {
              t++;
              continue;
            }
          coordinates++;
        }
        for (s=token; *s != '\0'; s++)
          if (strspn(s,"AaCcQqSsTt") != 0)
            coordinates+=(20.0*BezierQuantum)+360.0;
        break;
      }
      default:
        break;
    }
    if (status == MagickFalse)
      break;
    if (((size_t) (i+coordinates)) >= number_points)
      {
        /*
          Resize based on speculative points required by primitive.
        */
        number_points+=coordinates+1;
        if (number_points < (size_t) coordinates)
          {
            (void) ThrowMagickException(&image->exception,GetMagickModule(),
              ResourceLimitError,"MemoryAllocationFailed","`%s'",
              image->filename);
            break;
          }
        mvg_info.offset=i;
        status&=CheckPrimitiveExtent(&mvg_info,(double) number_points);
      }
    status&=CheckPrimitiveExtent(&mvg_info,PrimitiveExtentPad);
    if (status == MagickFalse)
      break;
    mvg_info.offset=j;
    switch (primitive_type)
    {
      case PointPrimitive:
      default:
      {
        if (primitive_info[j].coordinates != 1)
          {
            status=MagickFalse;
            break;
          }
        status&=TracePoint(primitive_info+j,primitive_info[j].point);
        i=(ssize_t) (j+primitive_info[j].coordinates);
        break;
      }
      case LinePrimitive:
      {
        double
          dx,
          dy,
          maximum_length;

        if (primitive_info[j].coordinates != 2)
          {
            status=MagickFalse;
            break;
          }
        dx=primitive_info[i].point.x-primitive_info[i-1].point.x;
        dy=primitive_info[i].point.y-primitive_info[i-1].point.y;
        maximum_length=hypot(dx,dy);
        if (maximum_length > (MaxBezierCoordinates/100.0))
          ThrowPointExpectedException(image,keyword);
        status&=TraceLine(primitive_info+j,primitive_info[j].point,
          primitive_info[j+1].point);
        i=(ssize_t) (j+primitive_info[j].coordinates);
        break;
      }
      case RectanglePrimitive:
      {
        if (primitive_info[j].coordinates != 2)
          {
            status=MagickFalse;
            break;
          }
        status&=TraceRectangle(primitive_info+j,primitive_info[j].point,
          primitive_info[j+1].point);
        i=(ssize_t) (j+primitive_info[j].coordinates);
        break;
      }
      case RoundRectanglePrimitive:
      {
        if (primitive_info[j].coordinates != 3)
          {
            status=MagickFalse;
            break;
          }
        if ((primitive_info[j+2].point.x < 0.0) ||
            (primitive_info[j+2].point.y < 0.0))
          {
            status=MagickFalse;
            break;
          }
        if ((primitive_info[j+1].point.x-primitive_info[j].point.x) < 0.0)
          {
            status=MagickFalse;
            break;
          }
        if ((primitive_info[j+1].point.y-primitive_info[j].point.y) < 0.0)
          {
            status=MagickFalse;
            break;
          }
        status&=TraceRoundRectangle(&mvg_info,primitive_info[j].point,
          primitive_info[j+1].point,primitive_info[j+2].point);
        i=(ssize_t) (j+primitive_info[j].coordinates);
        break;
      }
      case ArcPrimitive:
      {
        if (primitive_info[j].coordinates != 3)
          {
            status=MagickFalse;
            break;
          }
        status&=TraceArc(&mvg_info,primitive_info[j].point,
          primitive_info[j+1].point,primitive_info[j+2].point);
        i=(ssize_t) (j+primitive_info[j].coordinates);
        break;
      }
      case EllipsePrimitive:
      {
        if (primitive_info[j].coordinates != 3)
          {
            status=MagickFalse;
            break;
          }
        if ((primitive_info[j+1].point.x < 0.0) ||
            (primitive_info[j+1].point.y < 0.0))
          {
            status=MagickFalse;
            break;
          }
        status&=TraceEllipse(&mvg_info,primitive_info[j].point,
          primitive_info[j+1].point,primitive_info[j+2].point);
        i=(ssize_t) (j+primitive_info[j].coordinates);
        break;
      }
      case CirclePrimitive:
      {
        if (primitive_info[j].coordinates != 2)
          {
            status=MagickFalse;
            break;
          }
        status&=TraceCircle(&mvg_info,primitive_info[j].point,
          primitive_info[j+1].point);
        i=(ssize_t) (j+primitive_info[j].coordinates);
        break;
      }
      case PolylinePrimitive:
      {
        if (primitive_info[j].coordinates < 1)
          {
            status=MagickFalse;
            break;
          }
        break;
      }
      case PolygonPrimitive:
      {
        if (primitive_info[j].coordinates < 3)
          {
            status=MagickFalse;
            break;
          }
        primitive_info[i]=primitive_info[j];
        primitive_info[i].coordinates=0;
        primitive_info[j].coordinates++;
        primitive_info[j].closed_subpath=MagickTrue;
        i++;
        break;
      }
      case BezierPrimitive:
      {
        if (primitive_info[j].coordinates < 3)
          {
            status=MagickFalse;
            break;
          }
        status&=TraceBezier(&mvg_info,primitive_info[j].coordinates);
        i=(ssize_t) (j+primitive_info[j].coordinates);
        break;
      }
      case PathPrimitive:
      {
        coordinates=(double) TracePath(image,&mvg_info,token);
        if (coordinates < 0.0)
          {
            status=MagickFalse;
            break;
          }
        i=(ssize_t) (j+coordinates);
        break;
      }
      case ColorPrimitive:
      case MattePrimitive:
      {
        ssize_t
          method;

        if (primitive_info[j].coordinates != 1)
          {
            status=MagickFalse;
            break;
          }
        (void) GetNextToken(q,&q,extent,token);
        method=ParseCommandOption(MagickMethodOptions,MagickFalse,token);
        if (method == -1)
          {
            status=MagickFalse;
            break;
          }
        primitive_info[j].method=(PaintMethod) method;
        break;
      }
      case TextPrimitive:
      {
        char
          geometry[MagickPathExtent];

        if (primitive_info[j].coordinates != 1)
          {
            status=MagickFalse;
            break;
          }
        if (*token != ',')
          (void) GetNextToken(q,&q,extent,token);
        (void) CloneString(&primitive_info[j].text,token);
        /*
          Compute text cursor offset.
        */
        clone_info=CloneDrawInfo((ImageInfo *) NULL,graphic_context[n]);
        if ((fabs(mvg_info.point.x-primitive_info->point.x) < MagickEpsilon) &&
            (fabs(mvg_info.point.y-primitive_info->point.y) < MagickEpsilon))
          {
            mvg_info.point=primitive_info->point;
            primitive_info->point.x+=cursor;
          }
        else
          {
            mvg_info.point=primitive_info->point;
            cursor=0.0;
          }
        (void) FormatLocaleString(geometry,MagickPathExtent,"%+f%+f",
          primitive_info->point.x,primitive_info->point.y);
        clone_info->render=MagickFalse;
        clone_info->text=AcquireString(token);
        status&=GetTypeMetrics(image,clone_info,&metrics);
        clone_info=DestroyDrawInfo(clone_info);
        cursor+=metrics.width;
        if (graphic_context[n]->compliance != SVGCompliance)
          cursor=0.0;
        break;
      }
      case ImagePrimitive:
      {
        if (primitive_info[j].coordinates != 2)
          {
            status=MagickFalse;
            break;
          }
        (void) GetNextToken(q,&q,extent,token);
        (void) CloneString(&primitive_info[j].text,token);
        break;
      }
    }
    mvg_info.offset=i;
    if (status == 0)
      break;
    primitive_info[i].primitive=UndefinedPrimitive;
    if ((image->debug != MagickFalse) && (q > p))
      (void) LogMagickEvent(DrawEvent,GetMagickModule(),"  %.*s",(int) (q-p-1),
        p);
    /*
      Sanity check.
    */
    status&=CheckPrimitiveExtent(&mvg_info,ExpandAffine(
      &graphic_context[n]->affine));
    if (status == 0)
      break;
    status&=CheckPrimitiveExtent(&mvg_info,(double)
      graphic_context[n]->stroke_width);
    if (status == 0)
      break;
    if (i == 0)
      continue;
    /*
      Transform points.
    */
    for (i=0; primitive_info[i].primitive != UndefinedPrimitive; i++)
    {
      point=primitive_info[i].point;
      primitive_info[i].point.x=graphic_context[n]->affine.sx*point.x+
        graphic_context[n]->affine.ry*point.y+graphic_context[n]->affine.tx;
      primitive_info[i].point.y=graphic_context[n]->affine.rx*point.x+
        graphic_context[n]->affine.sy*point.y+graphic_context[n]->affine.ty;
      point=primitive_info[i].point;
      if (point.x < graphic_context[n]->bounds.x1)
        graphic_context[n]->bounds.x1=point.x;
      if (point.y < graphic_context[n]->bounds.y1)
        graphic_context[n]->bounds.y1=point.y;
      if (point.x > graphic_context[n]->bounds.x2)
        graphic_context[n]->bounds.x2=point.x;
      if (point.y > graphic_context[n]->bounds.y2)
        graphic_context[n]->bounds.y2=point.y;
      if (primitive_info[i].primitive == ImagePrimitive)
        break;
      if (i >= (ssize_t) number_points)
        ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
    }
    if (graphic_context[n]->render != MagickFalse)
      {
        if ((n != 0) && (graphic_context[n]->compliance != SVGCompliance) &&
            (graphic_context[n]->clip_mask != (char *) NULL) &&
            (LocaleCompare(graphic_context[n]->clip_mask,
             graphic_context[n-1]->clip_mask) != 0))
          {
            const char
              *clip_path;

            clip_path=(const char *) GetValueFromSplayTree(macros,
              graphic_context[n]->clip_mask);
            if (clip_path != (const char *) NULL)
              (void) SetImageArtifact(image,graphic_context[n]->clip_mask,
                clip_path);
            status&=DrawClipPath(image,graphic_context[n],
              graphic_context[n]->clip_mask);
          }
        status&=DrawPrimitive(image,graphic_context[n],primitive_info);
      }
    proceed=SetImageProgress(image,RenderImageTag,q-primitive,(MagickSizeType)
      primitive_extent);
    if (proceed == MagickFalse)
      break;
    if (status == 0)
      break;
  }
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(DrawEvent,GetMagickModule(),"end draw-image");
  /*
    Relinquish resources.
  */
  macros=DestroySplayTree(macros);
  token=DestroyString(token);
  if (primitive_info != (PrimitiveInfo *) NULL)
    {
      for (i=0; primitive_info[i].primitive != UndefinedPrimitive; i++)
        if ((primitive_info[i].primitive == TextPrimitive) ||
            (primitive_info[i].primitive == ImagePrimitive))
          if (primitive_info[i].text != (char *) NULL)
            primitive_info[i].text=DestroyString(primitive_info[i].text);
      primitive_info=(PrimitiveInfo *) RelinquishMagickMemory(primitive_info);
    }
  primitive=DestroyString(primitive);
  for ( ; n >= 0; n--)
    graphic_context[n]=DestroyDrawInfo(graphic_context[n]);
  graphic_context=(DrawInfo **) RelinquishMagickMemory(graphic_context);
  if (status == MagickFalse)
    ThrowBinaryImageException(DrawError,
      "NonconformingDrawingPrimitiveDefinition",keyword);
  return(status != 0 ? MagickTrue : MagickFalse);
}


MagickExport void GetDrawInfo(const ImageInfo *image_info,DrawInfo *draw_info)
{
  char
    *next_token;

  const char
    *option;

  ExceptionInfo
    *exception;

  ImageInfo
    *clone_info;

  /*
    Initialize draw attributes.
  */
  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
  assert(draw_info != (DrawInfo *) NULL);
  (void) memset(draw_info,0,sizeof(*draw_info));
  clone_info=CloneImageInfo(image_info);
  GetAffineMatrix(&draw_info->affine);
  exception=AcquireExceptionInfo();
  (void) QueryColorDatabase("#000F",&draw_info->fill,exception);
  (void) QueryColorDatabase("#FFF0",&draw_info->stroke,exception);
  draw_info->stroke_antialias=clone_info->antialias;
  draw_info->stroke_width=1.0;
  draw_info->fill_rule=EvenOddRule;
  draw_info->opacity=OpaqueOpacity;
  draw_info->fill_opacity=OpaqueOpacity;
  draw_info->stroke_opacity=OpaqueOpacity;
  draw_info->linecap=ButtCap;
  draw_info->linejoin=MiterJoin;
  draw_info->miterlimit=10;
  draw_info->decorate=NoDecoration;
  if (clone_info->font != (char *) NULL)
    draw_info->font=AcquireString(clone_info->font);
  if (clone_info->density != (char *) NULL)
    draw_info->density=AcquireString(clone_info->density);
  draw_info->text_antialias=clone_info->antialias;
  draw_info->pointsize=12.0;
  if (fabs(clone_info->pointsize) >= MagickEpsilon)
    draw_info->pointsize=clone_info->pointsize;
  draw_info->undercolor.opacity=(Quantum) TransparentOpacity;
  draw_info->border_color=clone_info->border_color;
  draw_info->compose=OverCompositeOp;
  if (clone_info->server_name != (char *) NULL)
    draw_info->server_name=AcquireString(clone_info->server_name);
  draw_info->render=MagickTrue;
  draw_info->clip_path=MagickFalse;
  draw_info->debug=IsEventLogging();
  option=GetImageOption(clone_info,"direction");
  if (option != (const char *) NULL)
    draw_info->direction=(DirectionType) ParseCommandOption(
      MagickDirectionOptions,MagickFalse,option);
  else
    draw_info->direction=UndefinedDirection;
  option=GetImageOption(clone_info,"encoding");
  if (option != (const char *) NULL)
    (void) CloneString(&draw_info->encoding,option);
  option=GetImageOption(clone_info,"family");
  if (option != (const char *) NULL)
    (void) CloneString(&draw_info->family,option);
  option=GetImageOption(clone_info,"fill");
  if (option != (const char *) NULL)
    (void) QueryColorDatabase(option,&draw_info->fill,exception);
  option=GetImageOption(clone_info,"gravity");
  if (option != (const char *) NULL)
    draw_info->gravity=(GravityType) ParseCommandOption(MagickGravityOptions,
      MagickFalse,option);
  option=GetImageOption(clone_info,"interline-spacing");
  if (option != (const char *) NULL)
    draw_info->interline_spacing=GetDrawValue(option,&next_token);
  option=GetImageOption(clone_info,"interword-spacing");
  if (option != (const char *) NULL)
    draw_info->interword_spacing=GetDrawValue(option,&next_token);
  option=GetImageOption(clone_info,"kerning");
  if (option != (const char *) NULL)
    draw_info->kerning=GetDrawValue(option,&next_token);
  option=GetImageOption(clone_info,"stroke");
  if (option != (const char *) NULL)
    (void) QueryColorDatabase(option,&draw_info->stroke,exception);
  option=GetImageOption(clone_info,"strokewidth");
  if (option != (const char *) NULL)
    draw_info->stroke_width=GetDrawValue(option,&next_token);
  option=GetImageOption(clone_info,"style");
  if (option != (const char *) NULL)
    draw_info->style=(StyleType) ParseCommandOption(MagickStyleOptions,
      MagickFalse,option);
  option=GetImageOption(clone_info,"undercolor");
  if (option != (const char *) NULL)
    (void) QueryColorDatabase(option,&draw_info->undercolor,exception);
  option=GetImageOption(clone_info,"weight");
  if (option != (const char *) NULL)
    {
      ssize_t
        weight;

      weight=ParseCommandOption(MagickWeightOptions,MagickFalse,option);
      if (weight == -1)
        weight=(ssize_t) StringToUnsignedLong(option);
      draw_info->weight=(size_t) weight;
    }
  exception=DestroyExceptionInfo(exception);
  draw_info->signature=MagickCoreSignature;
  clone_info=DestroyImageInfo(clone_info);
}


static MagickBooleanType CheckPrimitiveExtent(MVGInfo *mvg_info,
  const double pad)
{
  double
    extent;

  size_t
    quantum;

  /*
    Check if there is enough storage for drawing pimitives.
  */
  quantum=sizeof(**mvg_info->primitive_info);
  extent=(double) mvg_info->offset+pad+PrimitiveExtentPad*quantum+1.0;
  if (extent <= (double) *mvg_info->extent)
    return(MagickTrue);
  if (extent == (double) CastDoubleToLong(extent))
    {
      *mvg_info->primitive_info=(PrimitiveInfo *) ResizeQuantumMemory(
        *mvg_info->primitive_info,(size_t) extent,quantum);
      if (*mvg_info->primitive_info != (PrimitiveInfo *) NULL)
        {
          ssize_t
            i;

          *mvg_info->extent=(size_t) extent;
          for (i=mvg_info->offset+1; i < (ssize_t) extent; i++)
            (*mvg_info->primitive_info)[i].primitive=UndefinedPrimitive;
          return(MagickTrue);
        }
    }
  /*
    Reallocation failed, allocate a primitive to facilitate unwinding.
  */
  if (*mvg_info->primitive_info != (PrimitiveInfo *) NULL)
    *mvg_info->primitive_info=(PrimitiveInfo *) RelinquishMagickMemory(
      *mvg_info->primitive_info);
  (void) ThrowMagickException(mvg_info->exception,GetMagickModule(),
    ResourceLimitError,"MemoryAllocationFailed","`%s'","");
  *mvg_info->primitive_info=(PrimitiveInfo *)  AcquireCriticalMemory(
    (size_t) (PrimitiveExtentPad*quantum));
  (void) memset(*mvg_info->primitive_info,0,(size_t)
    (PrimitiveExtentPad*quantum));
  *mvg_info->extent=1;
  return(MagickFalse);
}


static MagickBooleanType DrawStrokePolygon(Image *image,
  const DrawInfo *draw_info,const PrimitiveInfo *primitive_info)
{
  DrawInfo
    *clone_info;

  MagickBooleanType
    closed_path;

  MagickStatusType
    status;

  PrimitiveInfo
    *stroke_polygon;

  const PrimitiveInfo
    *p,
    *q;

  /*
    Draw stroked polygon.
  */
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(DrawEvent,GetMagickModule(),
      "    begin draw-stroke-polygon");
  clone_info=CloneDrawInfo((ImageInfo *) NULL,draw_info);
  clone_info->fill=draw_info->stroke;
  if (clone_info->fill_pattern != (Image *) NULL)
    clone_info->fill_pattern=DestroyImage(clone_info->fill_pattern);
  if (clone_info->stroke_pattern != (Image *) NULL)
    clone_info->fill_pattern=CloneImage(clone_info->stroke_pattern,0,0,
      MagickTrue,&clone_info->stroke_pattern->exception);
  clone_info->stroke.opacity=(Quantum) TransparentOpacity;
  clone_info->stroke_width=0.0;
  clone_info->fill_rule=NonZeroRule;
  status=MagickTrue;
  for (p=primitive_info; p->primitive != UndefinedPrimitive; p+=p->coordinates)
  {
    if (p->coordinates == 1)
      continue;
    stroke_polygon=TraceStrokePolygon(draw_info,p,&image->exception);
    if (stroke_polygon == (PrimitiveInfo *) NULL)
      {
        status=0;
        break;
      }
    status&=DrawPolygonPrimitive(image,clone_info,stroke_polygon);
    stroke_polygon=(PrimitiveInfo *) RelinquishMagickMemory(stroke_polygon);
    if (status == 0)
      break;
    q=p+p->coordinates-1;
    closed_path=p->closed_subpath;
    if ((draw_info->linecap == RoundCap) && (closed_path == MagickFalse))
      {
        status&=DrawRoundLinecap(image,draw_info,p);
        status&=DrawRoundLinecap(image,draw_info,q);
      }
  }
  clone_info=DestroyDrawInfo(clone_info);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(DrawEvent,GetMagickModule(),
      "    end draw-stroke-polygon");
  return(status != 0 ? MagickTrue : MagickFalse);
}