/* $Id: svg.c 4532 2015-12-22 13:18:53Z mskala $ */
/* Copyright (C) 2003-2012  George Williams
 * Copyright (C) 2015  Matthew Skala
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * The name of the author may not be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "fontanvilvw.h"
#include <unistd.h>
#include <math.h>
#include <time.h>
#include <utype.h>
#include <chardata.h>
#include <ustring.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "sd.h"
#include "unicodelib.h"

/* ************************************************************************** */
/* ****************************    SVG Output    **************************** */
/* ************************************************************************** */

static void latin1ToUtf8Out(AFILE *file,char *str) {
   /* beware of characters above 0x80, also &, <, > (things that are magic for xml) */
   while (*str) {
      if (*str=='&' || *str=='<' || *str=='>' || (*str&0x80))
	 afprintf(file, "&#%d;", (uint8_t)*str);
      else
	 aputc(*str, file);
      ++str;
   }
}

static int svg_outfontheader(AFILE *file,SplineFont *sf,int layer) {
   int defwid=SFFigureDefWidth(sf, NULL);
   struct pfminfo info;
   static const char *condexp[] =
      { "squinchy", "ultra-condensed", "extra-condensed",
      "condensed", "semi-condensed", "normal", "semi-expanded", "expanded",
      "extra-expanded", "ultra-expanded", "broad"
   };
   DBounds bb;
   BlueData bd;
   char *hash, *hasv, ch;
   int minu, maxu, i;
   time_t now;

   memset(&info, 0, sizeof(info));
   SFDefaultOS2Info(&info, sf, sf->fontname);
   SplineFontLayerFindBounds(sf, layer, &bb);
   QuickBlues(sf, layer, &bd);

   afprintf(file, "<?xml version=\"1.0\" standalone=\"no\"?>\n");
   afprintf(file,
	   "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\" >\n");
   if (sf->comments != NULL) {
      afprintf(file, "<!--\n");
      latin1ToUtf8Out(file, sf->comments);
      afprintf(file, "\n-->\n");
   }
   afprintf(file, "<svg>\n");
   time(&now);
   afprintf(file, "<metadata>\nCreated by FontAnvil %d at %s\n",
	   FONTANVIL_VERSIONDATE_RAW, ctime(&now));
   if (sf->copyright != NULL) {
      latin1ToUtf8Out(file, sf->copyright);
      aputc('\n', file);
   }
   afprintf(file, "</metadata>\n");
   afprintf(file, "<defs>\n");
   afprintf(file, "<font id=\"%s\" horiz-adv-x=\"%d\" ", sf->fontname, defwid);
   if (sf->hasvmetrics)
      afprintf(file, "vert-adv-y=\"%d\" ", sf->ascent+sf->descent);
   aputc('>', file);
   aputc('\n', file);
   afprintf(file, "  <font-face \n");
   afprintf(file, "    font-family=\"%s\"\n",
	   sf->familyname_with_timestamp?sf->
	   familyname_with_timestamp:sf->familyname);
   afprintf(file, "    font-weight=\"%d\"\n", info.weight);
   if (strstrmatch(sf->fontname, "obli")
       || strstrmatch(sf->fontname, "slanted"))
      afprintf(file, "    font-style=\"oblique\"\n");
   else if (MacStyleCode(sf, NULL)&sf_italic)
      afprintf(file, "    font-style=\"italic\"\n");
   if (strstrmatch(sf->fontname, "small") || strstrmatch(sf->fontname, "cap"))
      afprintf(file, "    font-variant=\"small-caps\"\n");
   afprintf(file, "    font-stretch=\"%s\"\n", condexp[info.width]);
   afprintf(file, "    units-per-em=\"%d\"\n", sf->ascent+sf->descent);
   afprintf(file, "    panose-1=\"%d %d %d %d %d %d %d %d %d %d\"\n",
	   info.panose[0], info.panose[1], info.panose[2], info.panose[3],
	   info.panose[4], info.panose[5], info.panose[6], info.panose[7],
	   info.panose[8], info.panose[9]);
   afprintf(file, "    ascent=\"%d\"\n", sf->ascent);
   afprintf(file, "    descent=\"%d\"\n", -sf->descent);
   if (bd.xheight>0)
      afprintf(file, "    x-height=\"%g\"\n", (double) bd.xheight);
   if (bd.caph>0)
      afprintf(file, "    cap-height=\"%g\"\n", (double) bd.caph);
   afprintf(file, "    bbox=\"%g %g %g %g\"\n", (double) bb.minx,
	   (double) bb.miny, (double) bb.maxx, (double) bb.maxy);
   afprintf(file, "    underline-thickness=\"%g\"\n", (double) sf->uwidth);
   afprintf(file, "    underline-position=\"%g\"\n", (double) sf->upos);
   if (sf->italicangle != 0)
      afprintf(file, "    slope=\"%g\"\n", (double) sf->italicangle);
   hash=PSDictHasEntry(sf->private, "StdHW");
   hasv=PSDictHasEntry(sf->private, "StdVW");
   if (hash != NULL) {
      if (*hash=='[')
	 ++hash;
      ch=hash[strlen(hash)-1];
      if (ch==']')
	 hash[strlen(hash)-1]='\0';
      afprintf(file, "    stemh=\"%s\"\n", hash);
      if (ch==']')
	 hash[strlen(hash)]=ch;
   }
   if (hasv != NULL) {
      if (*hasv=='[')
	 ++hasv;
      ch=hasv[strlen(hasv)-1];
      if (ch==']')
	 hasv[strlen(hasv)-1]='\0';
      afprintf(file, "    stemv=\"%s\"\n", hasv);
      if (ch==']')
	 hasv[strlen(hasv)]=ch;
   }
   minu=0x7fffff;
   maxu=0;
   for (i=0; i<sf->glyphcnt; ++i)
      if (sf->glyphs[i] != NULL && sf->glyphs[i]->unicodeenc>0) {
	 if (sf->glyphs[i]->unicodeenc<minu)
	    minu=sf->glyphs[i]->unicodeenc;
	 if (sf->glyphs[i]->unicodeenc>maxu)
	    maxu=sf->glyphs[i]->unicodeenc;
      }
   if (maxu != 0)
      afprintf(file, "    unicode-range=\"U+%04X-%04X\"\n", minu, maxu);
   afprintf(file, "  />\n");
   return (defwid);
}

static int svg_pathdump(AFILE *file,SplineSet *spl,int lineout,
			int forceclosed, int do_clips) {
   BasePoint last;
   char buffer[85];
   int closed=false;
   Spline *sp, *first;

   /* as I see it there is nothing to be gained by optimizing out the */
   /* command characters, since they just have to be replaced by spaces */
   /* so I don't bother to */

   last.x=last.y=0;
   while (spl != NULL) {
      if ((do_clips && spl->is_clip_path)
	  || (!do_clips && !spl->is_clip_path)) {
	 sprintf(buffer, "M%g %g", (double) spl->first->me.x,
		 (double) spl->first->me.y);
	 if (lineout+strlen(buffer) >= 255) {
	    aputc('\n', file);
	    lineout=0;
	 }
	 afputs(buffer, file);
	 lineout += strlen(buffer);
	 last=spl->first->me;
	 closed=false;

	 first=NULL;
	 for (sp=spl->first->next; sp != NULL && sp != first;
	      sp=sp->to->next) {
	    if (first==NULL)
	       first=sp;
	    if (sp->knownlinear) {
	       if (sp->to->me.x==sp->from->me.x)
		  sprintf(buffer, "v%g", (double) (sp->to->me.y-last.y));
	       else if (sp->to->me.y==sp->from->me.y)
		  sprintf(buffer, "h%g", (double) (sp->to->me.x-last.x));
	       else if (sp->to->next==first) {
		  strcpy(buffer, "z");
		  closed=true;
	       } else
		  sprintf(buffer, "l%g %g", (double) (sp->to->me.x-last.x),
			  (double) (sp->to->me.y-last.y));
	    } else if (sp->order2) {
	       if (sp->from->prev != NULL && sp->from != spl->first &&
		   sp->from->me.x-sp->from->prevcp.x ==
		   sp->from->nextcp.x-sp->from->me.x
		   && sp->from->me.y-sp->from->prevcp.y ==
		   sp->from->nextcp.y-sp->from->me.y)
		  sprintf(buffer, "t%g %g", (double) (sp->to->me.x-last.x),
			  (double) (sp->to->me.y-last.y));
	       else
		  sprintf(buffer, "q%g %g %g %g",
			  (double) (sp->to->prevcp.x-last.x),
			  (double) (sp->to->prevcp.y-last.y),
			  (double) (sp->to->me.x-last.x),
			  (double) (sp->to->me.y-last.y));
	    } else {
	       if (sp->from->prev != NULL && sp->from != spl->first &&
		   sp->from->me.x-sp->from->prevcp.x ==
		   sp->from->nextcp.x-sp->from->me.x
		   && sp->from->me.y-sp->from->prevcp.y ==
		   sp->from->nextcp.y-sp->from->me.y)
		  sprintf(buffer, "s%g %g %g %g",
			  (double) (sp->to->prevcp.x-last.x),
			  (double) (sp->to->prevcp.y-last.y),
			  (double) (sp->to->me.x-last.x),
			  (double) (sp->to->me.y-last.y));
	       else
		  sprintf(buffer, "c%g %g %g %g %g %g",
			  (double) (sp->from->nextcp.x-last.x),
			  (double) (sp->from->nextcp.y-last.y),
			  (double) (sp->to->prevcp.x-last.x),
			  (double) (sp->to->prevcp.y-last.y),
			  (double) (sp->to->me.x-last.x),
			  (double) (sp->to->me.y-last.y));
	    }
	    if (lineout+strlen(buffer) >= 255) {
	       aputc('\n', file);
	       lineout=0;
	    }
	    afputs(buffer, file);
	    lineout += strlen(buffer);
	    last=sp->to->me;
	 }
	 if (!closed && (forceclosed || spl->first->prev != NULL)) {
	    if (lineout >= 254) {
	       aputc('\n', file);
	       lineout=0;
	    }
	    aputc('z', file);
	    ++lineout;
	 }
      }
      spl=spl->next;
   }
   return (lineout);
}

static void svg_dumpstroke(AFILE *file,struct pen *cpen,
			   struct pen *fallback, char *scname,
			   SplineChar * nested, int layer, int istop) {
   static char *joins[]={ "miter","round","bevel","inherit",NULL };
   static char *caps[]={ "butt","round","square","inherit",NULL };
   struct pen pen;

   pen=*cpen;
   if (fallback != NULL) {
      if (pen.brush.col==COLOR_INHERITED)
	 pen.brush.col=fallback->brush.col;
      if (pen.brush.opacity<0)
	 pen.brush.opacity=fallback->brush.opacity;
      if (pen.width==WIDTH_INHERITED)
	 pen.width=fallback->width;
      if (pen.linecap==lc_inherited)
	 pen.linecap=fallback->linecap;
      if (pen.linejoin==lj_inherited)
	 pen.linejoin=fallback->linejoin;
      if (pen.dashes[0]==0 && pen.dashes[1]==DASH_INHERITED)
	 memcpy(pen.dashes, fallback->dashes, sizeof(pen.dashes));
   }

   if (pen.brush.gradient != NULL) {
      afprintf(file, "stroke=\"url(#%s", scname);
      if (nested != NULL)
	 afprintf(file, "-%s", nested->name);
      afprintf(file, "-ly%d-stroke-grad)\" ", layer);
   } else if (pen.brush.pattern != NULL && istop) {
      afprintf(file, "stroke=\"url(#%s", scname);
      if (nested != NULL)
	 afprintf(file, "-%s", nested->name);
      afprintf(file, "-ly%d-stroke-pattern)\" ", layer);
   } else {
      if (pen.brush.col != COLOR_INHERITED)
	 afprintf(file, "stroke=\"#%02x%02x%02x\" ",
		 COLOR_RED(pen.brush.col), COLOR_GREEN(pen.brush.col),
		 COLOR_BLUE(pen.brush.col));
      else
	 afprintf(file, "stroke=\"currentColor\" ");
      if (pen.brush.opacity >= 0)
	 afprintf(file, "stroke-opacity=\"%g\" ", pen.brush.opacity);
   }
   if (pen.width != WIDTH_INHERITED)
      afprintf(file, "stroke-width=\"%g\" ", pen.width);
   if (pen.linecap != lc_inherited)
      afprintf(file, "stroke-linecap=\"%s\" ", caps[pen.linecap]);
   if (pen.linejoin != lc_inherited)
      afprintf(file, "stroke-linejoin=\"%s\" ", joins[pen.linejoin]);
/* the current transformation matrix will not affect the fill, but it will */
/*  affect the way stroke looks. So we must include it here. BUT the spline */
/*  set has already been transformed, so we must apply the inverse transform */
/*  to the splineset before outputting it, so that applying the transform */
/*  will give us the splines we desire. */
   if (pen.trans[0] != 1.0 || pen.trans[3] != 1.0 || pen.trans[1] != 0
       || pen.trans[2] != 0)
      afprintf(file, "transform=\"matrix(%g, %g, %g, %g, 0, 0)\" ",
	      (double) pen.trans[0], (double) pen.trans[1],
	      (double) pen.trans[2], (double) pen.trans[3]);
   if (pen.dashes[0]==0 && pen.dashes[1]==DASH_INHERITED) {
      afprintf(file, "stroke-dasharray=\"inherit\" ");
   } else if (pen.dashes[0] != 0) {
      int i;

      afprintf(file, "stroke-dasharray=\"");
      for (i=0; i<DASH_MAX && pen.dashes[i] != 0; ++i)
	 afprintf(file, "%d ", pen.dashes[i]);
      afprintf(file, "\" ");
   } else {
      /* That's the default, don't need to say it */
      /* afprintf(file, "stroke-dasharray=\"none\" " ) */
      ;
   }
}

static void svg_dumpfill(AFILE *file,struct brush *cbrush,
			 struct brush *fallback, int dofill, char *scname,
			 SplineChar * nested, int layer, int istop) {
   struct brush brush;

   if (!dofill) {
      afprintf(file, "fill=\"none\" ");
      return;
   }

   brush=*cbrush;
   if (fallback != NULL) {
      if (brush.col==COLOR_INHERITED)
	 brush.col=fallback->col;
      if (brush.opacity<0)
	 brush.opacity=fallback->opacity;
   }

   if (brush.gradient != NULL) {
      afprintf(file, "fill=\"url(#%s", scname);
      if (nested != NULL)
	 afprintf(file, "-%s", nested->name);
      afprintf(file, "-ly%d-fill-grad)\" ", layer);
   } else if (brush.pattern != NULL && istop) {
      afprintf(file, "fill=\"url(#%s", scname);
      if (nested != NULL)
	 afprintf(file, "-%s", nested->name);
      afprintf(file, "-ly%d-fill-pattern)\" ", layer);
   } else {
      if (brush.col != COLOR_INHERITED)
	 afprintf(file, "fill=\"#%02x%02x%02x\" ",
		 COLOR_RED(brush.col), COLOR_GREEN(brush.col),
		 COLOR_BLUE(brush.col));
      else
	 afprintf(file, "fill=\"currentColor\" ");
      if (brush.opacity >= 0)
	 afprintf(file, "fill-opacity=\"%g\" ", brush.opacity);
   }
}

static SplineSet *TransBy(SplineSet *ss,double trans[4]) {
   double inversetrans[6], transform[6];

   if (trans[0]==1.0 && trans[3]==1.0 && trans[1]==0 && trans[2]==0)
      return (ss);
   memcpy(transform, trans, 4*sizeof(double));
   transform[4]=transform[5]=0;
   MatInverse(inversetrans, transform);
   return (SplinePointListTransform
	   (SplinePointListCopy(ss), inversetrans, tpt_AllPoints));
}

static int svg_sc_any(SplineChar *sc,int layer) {
   int i, j;
   int any;
   RefChar *ref;
   int first, last;

   first=last=layer;
   if (sc->parent->multilayer)
      last=sc->layer_cnt-1;
   any=false;
   for (i=first; i <= last && !any; ++i) {
      any=sc->layers[i].splines != NULL || sc->layers[i].images != NULL;
      for (ref=sc->layers[i].refs; ref != NULL && !any; ref=ref->next)
	 for (j=0; j<ref->layer_cnt && !any; ++j)
	    any=ref->layers[j].splines != NULL;
   }
   return (any);
}

static int base64tab[]={
   'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
      'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
   'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
      'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
   '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};

static void DataURI_ImageDump(AFILE *file,struct gimage *img) {
   char *mimetype=NULL;
   AFILE *imgf;
   int done=false;
   int threechars[3], fourchars[4], i, ch, ch_on_line;

#if !defined(_NO_LIBJPEG)
   struct _GImage *base =
      img->list_len==0?img->u.image:img->u.images[0];
#endif

   /* Technically we can only put a file into an URI if the whole thing is */
   /*  less than 1024 bytes long. But I shall ignore that issue */
   imgf=atmpfile();
#if !defined(_NO_LIBJPEG)
   if (base->image_type==it_true) {
      done=GImageWrite_Jpeg(img,imgf,78,false);
      mimetype="image/jpeg";
   }
#endif
#ifndef _NO_LIBPNG
   if (!done) {
      done=GImageWrite_Png(img,imgf,false);
      mimetype="image/png";
   }
#endif
   if (!done) {
      GImageWrite_Bmp(img,imgf);
      mimetype="image/bmp";
   }

   afprintf(file,"%s;base64,",mimetype);
   afseek(imgf,0,SEEK_SET);

   /* Now do base64 output conversion */

   afseek(imgf,0,SEEK_SET);
   ch=agetc(imgf);
   ch_on_line=0;
   while (ch != EOF) {
      threechars[0]=threechars[1]=threechars[2]=0;
      for (i=0; i<3 && ch != EOF; ++i) {
	 threechars[i]=ch;
	 ch=agetc(imgf);
      }
      if (i>0) {
	 fourchars[0]=base64tab[threechars[0] >> 2];
	 fourchars[1] =
	    base64tab[((threechars[0]&0x3) << 4)|(threechars[1] >> 4)];
	 fourchars[2] =
	    base64tab[((threechars[1]&0xf) << 2)|(threechars[2] >> 6)];
	 fourchars[3]=base64tab[threechars[2]&0x3f];
	 if (i<3)
	    fourchars[3]='=';
	 if (i<2)
	    fourchars[2]='=';
	 aputc(fourchars[0], file);
	 aputc(fourchars[1], file);
	 aputc(fourchars[2], file);
	 aputc(fourchars[3], file);
	 ch_on_line += 4;
	 if (ch_on_line >= 72) {
	    aputc('\n', file);
	    ch_on_line=0;
	 }
      }
   }
   afclose(imgf);
}

static void svg_dumpgradient(AFILE *file,struct gradient *gradient,
			     char *scname, SplineChar * nested, int layer,
			     int is_fill) {
   int i;
   Color csame;
   float osame;

   afprintf(file, "    <%s ",
	   gradient->radius==0?"linearGradient":"radialGradient");
   if (nested==NULL)
      afprintf(file, " id=\"%s-ly%d-%s-grad\"", scname, layer,
	      is_fill?"fill":"stroke");
   else
      afprintf(file, " id=\"%s-%s-ly%d-%s-grad\"", scname, nested->name, layer,
	      is_fill?"fill":"stroke");
   afprintf(file, "\n\tgradientUnits=\"userSpaceOnUse\"");
   if (gradient->radius==0) {
      afprintf(file, "\n\tx1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"",
	      (double) gradient->start.x, (double) gradient->start.y,
	      (double) gradient->stop.x, (double) gradient->stop.y);
   } else {
      if (gradient->start.x==gradient->stop.x
	  && gradient->start.y==gradient->stop.y)
	 afprintf(file, "\n\tcx=\"%g\" cy=\"%g\" r=\"%g\"",
		 (double) gradient->stop.x, (double) gradient->stop.y,
		 (double) gradient->radius);
      else
	 afprintf(file, "\n\tfx=\"%g\" fy=\"%g\" cx=\"%g\" cy=\"%g\" r=\"%g\"",
		 (double) gradient->start.x, (double) gradient->start.y,
		 (double) gradient->stop.x, (double) gradient->stop.y,
		 (double) gradient->radius);
   }
   afprintf(file, "\n\tspreadMethod=\"%s\">\n",
	   gradient->sm==sm_pad?"pad" :
	   gradient->sm==sm_reflect?"reflect":"repeat");

   csame=-1;
   osame=-1;
   for (i=0; i<gradient->stop_cnt; ++i) {
      if (csame==-1)
	 csame=gradient->grad_stops[i].col;
      else if (csame != gradient->grad_stops[i].col)
	 csame=-2;
      if (osame==-1)
	 osame=gradient->grad_stops[i].opacity;
      else if (osame != gradient->grad_stops[i].opacity)
	 osame=-2;
   }
   for (i=0; i<gradient->stop_cnt; ++i) {
      afprintf(file, "      <stop offset=\"%g\"",
	      (double) gradient->grad_stops[i].offset);
      if (csame==-2) {
	 if (gradient->grad_stops[i].col==COLOR_INHERITED)
	    afprintf(file, " stop-color=\"inherit\"");
	 else
	    afprintf(file, " stop-color=\"#%06x\"",
		    gradient->grad_stops[i].col);
      }
      if (osame<0) {
	 if (gradient->grad_stops[i].opacity==COLOR_INHERITED)
	    afprintf(file, " stop-opacity=\"inherit\"");
	 else
	    afprintf(file, " stop-opacity=\"%g\"",
		    (double) gradient->grad_stops[i].opacity);
      }
      afprintf(file, "/>\n");
   }
   afprintf(file, "    </%s>\n",
	   gradient->radius==0?"linearGradient":"radialGradient");
}

static void svg_dumpscdefs(AFILE *file,SplineChar *sc,char *name,
			   int istop);
static void svg_dumptype3(AFILE *file,SplineChar *sc,char *name,
			  int istop);

static void svg_dumppattern(AFILE *file,struct pattern *pattern,
			    char *scname, SplineChar * base,
			    SplineChar * nested, int layer, int is_fill) {
   SplineChar *pattern_sc=SFGetChar(base->parent, -1, pattern->pattern);
   char *patsubname=NULL;

   if (pattern_sc != NULL) {
      patsubname=strconcat3(scname, "-", pattern->pattern);
      svg_dumpscdefs(file, pattern_sc, patsubname, false);
   } else
      ErrorMsg(2,"No glyph named %s, used as a pattern in %s\n",
	       pattern->pattern, scname);

   afprintf(file, "    <pattern ");
   if (nested==NULL)
      afprintf(file, " id=\"%s-ly%d-%s-pattern\"", scname, layer,
	      is_fill?"fill":"stroke");
   else
      afprintf(file, " id=\"%s-%s-ly%d-%s-pattern\"", scname, nested->name,
	      layer, is_fill?"fill":"stroke");
   afprintf(file, "\n\tpatternUnits=\"userSpaceOnUse\"");
   if (pattern_sc != NULL) {
      DBounds b;

      PatternSCBounds(pattern_sc, &b);
      afprintf(file, "\n\tviewBox=\"%g %g %g %g\"",
	      (double) b.minx, (double) b.miny,
	      (double) (b.maxx-b.minx), (double) (b.maxy-b.miny));
   }
   afprintf(file, "\n\twidth=\"%g\" height=\"%g\"",
	   (double) pattern->width, (double) pattern->height);
   if (pattern->transform[0] != 1 || pattern->transform[1] != 0 ||
       pattern->transform[2] != 0 || pattern->transform[3] != 1 ||
       pattern->transform[4] != 0 || pattern->transform[5] != 0) {
      afprintf(file, "\n\tpatternTransform=\"matrix(%g %g %g %g %g %g)\"",
	      (double) pattern->transform[0], (double) pattern->transform[1],
	      (double) pattern->transform[2], (double) pattern->transform[3],
	      (double) pattern->transform[4], (double) pattern->transform[5]);
   }
   if (pattern_sc != NULL)
      svg_dumpscdefs(file, pattern_sc, patsubname, false);
   afprintf(file, "    </pattern>\n");
   free(patsubname);
}

static void svg_layer_defs(AFILE *file,SplineSet *splines,
			   struct brush *fill_brush, struct pen *stroke_pen,
			   SplineChar * sc, char *scname, SplineChar * nested,
			   int layer, int istop) {
   if (SSHasClip(splines)) {
      if (nested==NULL)
	 afprintf(file, "    <clipPath id=\"%s-ly%d-clip\">\n", scname, layer);
      else
	 afprintf(file, "    <clipPath id=\"%s-%s-ly%d-clip\">\n", scname,
		 nested->name, layer);
      afprintf(file, "      <path d=\"\n");
      svg_pathdump(file, sc->layers[layer].splines, 16, true, true);
      afprintf(file, "\"/>\n");
      afprintf(file, "    </clipPath>\n");
   }
   if (fill_brush->gradient != NULL)
      svg_dumpgradient(file, fill_brush->gradient, scname, nested, layer,
		       true);
   else if (fill_brush->pattern != NULL && istop)
      svg_dumppattern(file, fill_brush->pattern, scname, sc, nested, layer,
		      true);
   if (stroke_pen->brush.gradient != NULL)
      svg_dumpgradient(file, stroke_pen->brush.gradient, scname, nested,
		       layer, false);
   else if (stroke_pen->brush.pattern != NULL && istop)
      svg_dumppattern(file, stroke_pen->brush.pattern, scname, sc, nested,
		      layer, false);
}

static void svg_dumpscdefs(AFILE *file,SplineChar *sc,char *name,
			   int istop) {
   int i, j;
   RefChar *ref;

   for (i=ly_fore; i<sc->layer_cnt; ++i) {
      svg_layer_defs(file, sc->layers[i].splines, &sc->layers[i].fill_brush,
		     &sc->layers[i].stroke_pen, sc, name, NULL, i, istop);
      for (ref=sc->layers[i].refs; ref != NULL; ref=ref->next) {
	 for (j=0; j<ref->layer_cnt; ++j)
	    if (ref->layers[j].splines != NULL) {
	       svg_layer_defs(file, ref->layers[j].splines,
			      &ref->layers[j].fill_brush,
			      &ref->layers[j].stroke_pen, sc, name, ref->sc,
			      j, istop);
	    }
      }
   }
}

static void svg_dumptype3(AFILE *file,SplineChar *sc,char *name,int istop) {
   int i, j;
   RefChar *ref;
   ImageList *images;
   SplineSet *transed;

   for (i=ly_fore; i<sc->layer_cnt; ++i) {
      if (SSHasDrawn(sc->layers[i].splines)) {
	 afprintf(file, "  <g ");
	 if (SSHasClip(sc->layers[i].splines))
	    afprintf(file, "clip-path=\"url(#%s-ly%d-clip)\" ", name, i);
	 transed=sc->layers[i].splines;
	 if (sc->layers[i].dostroke) {
	    svg_dumpstroke(file, &sc->layers[i].stroke_pen, NULL, name, NULL,
			   i, istop);
	    transed=TransBy(transed, sc->layers[i].stroke_pen.trans);
	 }
	 svg_dumpfill(file, &sc->layers[i].fill_brush, NULL,
		      sc->layers[i].dofill, name, NULL, i, istop);
	 afprintf(file, ">\n");
	 afprintf(file, "    <path d=\"\n");
	 svg_pathdump(file, transed, 12, !sc->layers[i].dostroke, false);
	 afprintf(file, "\"/>\n");
	 if (transed != sc->layers[i].splines)
	    SplinePointListsFree(transed);
	 afprintf(file, "  </g>\n");
      }
      for (ref=sc->layers[i].refs; ref != NULL; ref=ref->next) {
	 for (j=0; j<ref->layer_cnt; ++j)
	    if (ref->layers[j].splines != NULL) {
	       afprintf(file, "   <g ");
	       transed=ref->layers[j].splines;
	       if (SSHasClip(transed))
		  afprintf(file, "clip-path=\"url(#%s-%s-ly%d-clip)\" ", name,
			  ref->sc->name, j);
	       if (ref->layers[j].dostroke) {
		  svg_dumpstroke(file, &ref->layers[j].stroke_pen,
				 &sc->layers[i].stroke_pen, sc->name, ref->sc,
				 j, istop);
		  transed=TransBy(transed, ref->layers[j].stroke_pen.trans);
	       }
	       svg_dumpfill(file, &ref->layers[j].fill_brush,
			    &sc->layers[i].fill_brush, ref->layers[j].dofill,
			    sc->name, ref->sc, j, istop);
	       afprintf(file, ">\n");
	       afprintf(file, "  <path d=\"\n");
	       svg_pathdump(file, transed, 12, !ref->layers[j].dostroke,
			    false);
	       afprintf(file, "\"/>\n");
	       if (transed != ref->layers[j].splines)
		  SplinePointListsFree(transed);
	       afprintf(file, "   </g>\n");
	    }
      }
      for (images=sc->layers[i].images; images != NULL;
	   images=images->next) {
	 struct _GImage *base;

	 afprintf(file, "      <image\n");
	 base=images->image->list_len==0?images->image->u.image :
	    images->image->u.images[0];
	 afprintf(file, "\twidth=\"%g\"\n\theight=\"%g\"\n",
		 (double) (base->width * images->xscale),
		 (double) (base->height * images->yscale));
	 afprintf(file, "\tx=\"%g\"\n\ty=\"%g\"\n", (double) images->xoff,
		 (double) images->yoff);
	 afprintf(file, "\txlink:href=\"data:");
	 DataURI_ImageDump(file, images->image);
	 afprintf(file, "\" />\n");
      }
   }
}

static void svg_scpathdump(AFILE *file,SplineChar *sc,char *endpath,
			   int layer) {
   RefChar *ref;
   int lineout;
   int i, j;
   int needs_defs=0;

   if (!svg_sc_any(sc, layer)) {
      /* I think a space is represented by leaving out the d (path) entirely */
      /*  rather than having d="" */
      afputs(" />\n", file);
   } else if (sc->parent->strokedfont) {
      /* Can't be done with a path, requires nested elements (I think) */
      afprintf(file,
	      ">\n  <g stroke=\"currentColor\" stroke-width=\"%g\" fill=\"none\">\n",
	      (double) sc->parent->strokewidth);
      afprintf(file, "    <path d=\"");
      lineout =
	 svg_pathdump(file, sc->layers[layer].splines, 3, false, false);
      for (ref=sc->layers[layer].refs; ref != NULL; ref=ref->next)
	 lineout =
	    svg_pathdump(file, ref->layers[0].splines, lineout, false, false);
      if (lineout >= 255-4)
	 aputc('\n', file);
      aputc('"', file);
      afputs(" />\n  </g>\n", file);
      afputs(endpath, file);
   } else if (!sc->parent->multilayer) {
      afprintf(file, "d=\"");
      lineout=svg_pathdump(file, sc->layers[layer].splines, 3, true, false);
      for (ref=sc->layers[layer].refs; ref != NULL; ref=ref->next)
	 lineout =
	    svg_pathdump(file, ref->layers[0].splines, lineout, true, false);
      if (lineout >= 255-4)
	 aputc('\n', file);
      aputc('"', file);
      afputs(" />\n", file);
   } else {
      afputs(">\n", file);
      for (i=ly_fore; i<sc->layer_cnt && !needs_defs; ++i) {
	 if (SSHasClip(sc->layers[i].splines))
	    needs_defs=true;
	 else if (sc->layers[i].fill_brush.pattern != NULL ||
		  sc->layers[i].fill_brush.gradient != NULL ||
		  sc->layers[i].stroke_pen.brush.pattern != NULL ||
		  sc->layers[i].stroke_pen.brush.gradient != NULL)
	    needs_defs=true;
	 for (ref=sc->layers[i].refs; ref != NULL; ref=ref->next) {
	    for (j=0; j<ref->layer_cnt; ++j)
	       if (ref->layers[j].splines != NULL) {
		  if (SSHasClip(ref->layers[j].splines))
		     needs_defs=true;
		  else if (ref->layers[j].fill_brush.pattern != NULL ||
			   ref->layers[j].fill_brush.gradient != NULL ||
			   ref->layers[j].stroke_pen.brush.pattern != NULL ||
			   ref->layers[j].stroke_pen.brush.gradient != NULL)
		     needs_defs=true;
	       }
	 }
      }
      if (needs_defs) {
	 afprintf(file, "  <defs>\n");
	 svg_dumpscdefs(file, sc, sc->name, true);
	 afprintf(file, "  </defs>\n");
      }
      svg_dumptype3(file, sc, sc->name, true);
      afputs(endpath, file);
   }
}

static int LigCnt(SplineFont *sf,PST *lig,int32_t *univals,int max) {
   char *pt, *end;
   int c=0;
   SplineChar *sc;

   if (lig->type != pst_ligature)
      return (0);
   else if (!lig->subtable->lookup->store_in_afm)
      return (0);
   pt=lig->u.lig.components;
   while (1) {
      end=strchr(pt, ' ');
      if (end != NULL)
	 *end='\0';
      sc=SFGetChar(sf, -1, pt);
      if (end != NULL)
	 *end=' ';
      if (sc==NULL || sc->unicodeenc==-1)
	 return (0);
      if (c >= max)
	 return (0);
      univals[c++]=sc->unicodeenc;
      if (end==NULL)
	 return (c);
      pt=end+1;
      while (*pt==' ')
	 ++pt;
   }
}

static PST *HasLigature(SplineChar *sc) {
   PST *pst, *best=NULL;
   int bestc=0, c;
   int32_t univals[50];

   for (pst=sc->possub; pst != NULL; pst=pst->next) {
      if (pst->type==pst_ligature) {
	 c=LigCnt(sc->parent, pst, univals,
		    sizeof(univals)/sizeof(univals[0]));
	 if (c>1 && c>bestc) {
	    c=bestc;
	    best=pst;
	 }
      }
   }
   return (best);
}

static SplineChar *SCHasSubs(SplineChar *sc,uint32_t tag) {
   PST *pst;

   for (pst=sc->possub; pst != NULL; pst=pst->next) {
      if (pst->type==pst_substitution &&
	  FeatureTagInFeatureScriptList(tag, pst->subtable->lookup->features))
	 return (SFGetChar(sc->parent, -1, pst->u.subs.variant));
   }
   return (NULL);
}

static void svg_scdump(AFILE *file,SplineChar *sc,int defwid,int encuni,
		       int vs, int layer) {
   PST *best=NULL;
   unichar_t alt;
   int32_t univals[50];
   int i, c;

   best=HasLigature(sc);
   if (sc->comment != NULL) {
      afprintf(file, "\n<!--\n%s\n-->\n", sc->comment);
   }
   afprintf(file, "    <glyph glyph-name=\"%s\" ", sc->name);
   if (best != NULL) {
      c=LigCnt(sc->parent, best, univals,
		 sizeof(univals)/sizeof(univals[0]));
      afputs("unicode=\"", file);
      for (i=0; i<c; ++i)
	 if (univals[i] >= 'A' && univals[i]<'z')
	    aputc(univals[i], file);
	 else
	    afprintf(file, "&#x%x;", (unsigned int) univals[i]);
      afputs("\" ", file);
   } else if (encuni != -1 && encuni<0x110000) {
      if (encuni != 0x9 &&
	  encuni != 0xa &&
	  encuni != 0xd &&
	  !(encuni >= 0x20 && encuni <= 0xd7ff) &&
	  !(encuni >= 0xe000 && encuni <= 0xfffd) &&
	  !(encuni >= 0x10000 && encuni <= 0x10ffff))
	 /* Not allowed in XML */ ;
      else if ((encuni >= 0x7f && encuni <= 0x84) ||
	       (encuni >= 0x86 && encuni <= 0x9f) ||
	       (encuni >= 0xfdd0 && encuni <= 0xfddf) ||
	       (encuni&0xffff)==0xfffe || (encuni&0xffff)==0xffff)
	 /* Not recommended in XML */ ;
      else if (encuni >= 32 && encuni<127 &&
	       encuni != '"' && encuni != '&' &&
	       encuni != '<' && encuni != '>')
	 afprintf(file, "unicode=\"%c\" ", encuni);
      else if ((alt=(unichar_t)arabic_base_forms_lookup(encuni)))
	 /* For arabic forms use the base representation in the 0600 block */
	 afprintf(file, "unicode=\"&#x%x;\" ", alt);
      else
	 afprintf(file, "unicode=\"&#x%x;\" ", encuni);
      if (vs != -1)
	 afprintf(file, "unicode=\"&#x%x;\" ", vs);
   }
   if (sc->width != defwid)
      afprintf(file, "horiz-adv-x=\"%d\" ", sc->width);
   if (sc->parent->hasvmetrics
       && sc->vwidth != sc->parent->ascent+sc->parent->descent)
      afprintf(file, "vert-adv-y=\"%d\" ", sc->vwidth);
   if (strstr(sc->name, ".vert") != NULL || strstr(sc->name, ".vrt2") != NULL)
      afprintf(file, "orientation=\"v\" ");
   if (encuni != -1 && encuni<0x10000) {
      if (isarabinitial(encuni))
	 afprintf(file, "arabic-form=\"initial\" ");
      else if (isarabmedial(encuni))
	 afprintf(file, "arabic-form=\"medial\" ");
      else if (isarabfinal(encuni))
	 afprintf(file, "arabic-form=\"final\" ");
      else if (isarabisolated(encuni))
	 afprintf(file, "arabic-form=\"isolated\" ");
   }
   aputc('\n', file);
   svg_scpathdump(file, sc, " </glyph>\n", layer);
   sc->ticked=true;
}

static void svg_notdefdump(AFILE *file,SplineFont *sf,int defwid,
			   int layer) {
   int notdefpos;

   notdefpos=SFFindNotdef(sf, -2);

   if (notdefpos != -1) {
      SplineChar *sc=sf->glyphs[notdefpos];

      afprintf(file, "<missing-glyph ");
      if (sc->width != defwid)
	 afprintf(file, "horiz-adv-x=\"%d\" ", sc->width);
      if (sc->parent->hasvmetrics
	  && sc->vwidth != sc->parent->ascent+sc->parent->descent)
	 afprintf(file, "vert-adv-y=\"%d\" ", sc->vwidth);
      aputc('\n', file);
      svg_scpathdump(file, sc, " </glyph>\n", layer);
   } else {
      /* We'll let both the horiz and vert advances default to the values */
      /*  specified by the font, and I think a space is done by omitting */
      /*  d (the path) altogether */
      afprintf(file, "    <missing-glyph />\n");	/* Is this a blank space? */
   }
}

static void fputkerns(AFILE *file,char *names) {
   while (*names) {
      if (*names==' ') {
	 aputc(',', file);
	 while (names[1]==' ')
	    ++names;
      } else
	 aputc(*names, file);
      ++names;
   }
}

static void svg_dumpkerns(AFILE *file,SplineFont *sf,int isv) {
   int i, j;
   KernPair *kp;
   KernClass *kc;

   for (i=0; i<sf->glyphcnt; ++i)
      if (SCWorthOutputting(sf->glyphs[i])) {
	 for (kp=isv?sf->glyphs[i]->vkerns:sf->glyphs[i]->kerns;
	      kp != NULL; kp=kp->next)
	    if (kp->off != 0 && SCWorthOutputting(kp->sc)) {
	       afprintf(file, isv?"    <vkern ":"    <hkern ");
	       if (sf->glyphs[i]->unicodeenc==-1
		   || HasLigature(sf->glyphs[i]))
		  afprintf(file, "g1=\"%s\" ", sf->glyphs[i]->name);
	       else if (sf->glyphs[i]->unicodeenc >= 'A'
			&& sf->glyphs[i]->unicodeenc <= 'z')
		  afprintf(file, "u1=\"%c\" ", sf->glyphs[i]->unicodeenc);
	       else
		  afprintf(file, "u1=\"&#x%x;\" ", sf->glyphs[i]->unicodeenc);
	       if (kp->sc->unicodeenc==-1 || HasLigature(kp->sc))
		  afprintf(file, "g2=\"%s\" ", kp->sc->name);
	       else if (kp->sc->unicodeenc >= 'A'
			&& kp->sc->unicodeenc <= 'z')
		  afprintf(file, "u2=\"%c\" ", kp->sc->unicodeenc);
	       else
		  afprintf(file, "u2=\"&#x%x;\" ", kp->sc->unicodeenc);
	       afprintf(file, "k=\"%d\" />\n", -kp->off);
	    }
      }

   for (kc=isv?sf->vkerns:sf->kerns; kc != NULL; kc=kc->next) {
      for (i=1; i<kc->first_cnt; ++i)
	 for (j=1; j<kc->second_cnt; ++j) {
	    if (kc->offsets[i * kc->second_cnt+j] != 0 &&
		*kc->firsts[i] != '\0' && *kc->seconds[j] != '\0') {
	       afprintf(file, isv?"    <vkern g1=\"":"    <hkern g1=\"");
	       fputkerns(file, kc->firsts[i]);
	       afprintf(file, "\"\n\tg2=\"");
	       fputkerns(file, kc->seconds[j]);
	       afprintf(file, "\"\n\tk=\"%d\" />\n",
		       -kc->offsets[i * kc->second_cnt+j]);
	    }
	 }
   }
}

static void svg_outfonttrailer(AFILE *file,SplineFont *sf) {
   afprintf(file, "  </font>\n");
   afprintf(file, "</defs></svg>\n");
}

static int AnyArabicForm(SplineChar *sc) {
   struct altuni *altuni;

   if (sc->unicodeenc != -1 && sc->unicodeenc<0x10000 &&
       (isarabinitial(sc->unicodeenc) ||
	isarabmedial(sc->unicodeenc) ||
	isarabfinal(sc->unicodeenc) || isarabisolated(sc->unicodeenc)))
      return (sc->unicodeenc);
   for (altuni=sc->altuni; altuni != NULL; altuni=altuni->next)
      if (altuni->unienc != -1 && altuni->unienc<0x10000 &&
	  altuni->vs==-1 && altuni->fid==0 &&
	  (isarabinitial(altuni->unienc) ||
	   isarabmedial(altuni->unienc) ||
	   isarabfinal(altuni->unienc) || isarabisolated(altuni->unienc)))
	 return (altuni->unienc);

   return (-1);
}

static int UnformedUni(int uni) {
   return (uni==-1 || uni >= 0x10000 ||
	   !(isarabinitial(uni) ||
	     isarabmedial(uni) || isarabfinal(uni) || isarabisolated(uni)));
}

static void svg_sfdump(AFILE *file,SplineFont *sf,int layer) {
   int defwid, i, formeduni;
   struct altuni *altuni;

   for (i=0; i<sf->glyphcnt; ++i)
      if (sf->glyphs[i] != NULL)
	 sf->glyphs[i]->ticked=false;

   defwid=svg_outfontheader(file, sf, layer);
   svg_notdefdump(file, sf, defwid, layer);

   /* Ligatures must be output before their initial components */
   for (i=0; i<sf->glyphcnt; ++i) {
      if (SCWorthOutputting(sf->glyphs[i])) {
	 if (HasLigature(sf->glyphs[i]))
	    svg_scdump(file, sf->glyphs[i], defwid, sf->glyphs[i]->unicodeenc,
		       -1, layer);
	 /* Variation selectors should probably be treated as ligatures */
	 for (altuni=sf->glyphs[i]->altuni; altuni != NULL;
	      altuni=altuni->next)
	    if (altuni->vs != -1 && altuni->fid==0)
	       svg_scdump(file, sf->glyphs[i], defwid, altuni->unienc,
			  altuni->vs, layer);
      }
   }
   /* And formed arabic before unformed */
   for (i=0; i<sf->glyphcnt; ++i) {
      SplineChar *sc=sf->glyphs[i];

      if (SCWorthOutputting(sc) && !sc->ticked) {
	 if ((formeduni=AnyArabicForm(sc)) != -1)
	    svg_scdump(file, sc, defwid, formeduni, -1, layer);
	 else if (sc->unicodeenc >= 0x0600 && sc->unicodeenc <= 0x06ff) {
	    /* The conventions now (as I understand them) suggest that */
	    /*  fonts not use the unicode encodings for formed arabic */
	    /*  but should use simple substitutions instead */
	    SplineChar *formed;

	    formed=SCHasSubs(sc, CHR('i', 'n', 'i', 't'));
	    if (SCWorthOutputting(formed) && formed->unicodeenc==-1 &&
		!formed->ticked &&
		arabic_forms_lookup(sc->unicodeenc).initial != 0)
	       svg_scdump(file, formed, defwid,
	                  arabic_forms_lookup(sc->unicodeenc).initial,
			  -1, layer);
	    formed=SCHasSubs(sc, CHR('m', 'e', 'd', 'i'));
	    if (SCWorthOutputting(formed) && formed->unicodeenc==-1 &&
		!formed->ticked &&
		arabic_forms_lookup(sc->unicodeenc).medial != 0)
	       svg_scdump(file, formed, defwid,
	                  arabic_forms_lookup(sc->unicodeenc).medial,
			  -1, layer);
	    formed=SCHasSubs(sc, CHR('f', 'i', 'n', 'a'));
	    if (SCWorthOutputting(formed) && formed->unicodeenc==-1 &&
		!formed->ticked &&
		arabic_forms_lookup(sc->unicodeenc).final != 0)
	       svg_scdump(file, formed, defwid,
	                  arabic_forms_lookup(sc->unicodeenc).final,
			  -1, layer);
	    formed=SCHasSubs(sc, CHR('i', 's', 'o', 'l'));
	    if (SCWorthOutputting(formed) && formed->unicodeenc==-1 &&
		!formed->ticked &&
		arabic_forms_lookup(sc->unicodeenc).isolated != 0)
	       svg_scdump(file, formed, defwid,
			  arabic_forms_lookup(sc->unicodeenc).isolated,
			  -1, layer);
	 }
      }
   }
   for (i=0; i<sf->glyphcnt; ++i) {
      if (SCWorthOutputting(sf->glyphs[i]) && !sf->glyphs[i]->ticked) {
	 if (UnformedUni(sf->glyphs[i]->unicodeenc))
	    svg_scdump(file, sf->glyphs[i], defwid, sf->glyphs[i]->unicodeenc,
		       -1, layer);
	 for (altuni=sf->glyphs[i]->altuni; altuni != NULL;
	      altuni=altuni->next)
	    if (altuni->vs==-1 && altuni->fid==0)
	       svg_scdump(file, sf->glyphs[i], defwid, altuni->unienc,
			  altuni->vs, layer);
      }
   }
   svg_dumpkerns(file, sf, false);
   svg_dumpkerns(file, sf, true);
   svg_outfonttrailer(file, sf);
}

int WriteSVGFont(char *fontname, SplineFont *sf, enum fontformat format,
		 int flags, EncMap * map, int layer) {
   AFILE *file;
   int ret;

   if ((file=afopen(fontname, "w+"))==NULL)
      return (0);
   svg_sfdump(file, sf, layer);
   ret=true;
   if (aferror(file))
      ret=false;
   if (afclose(file)==-1)
      return (0);
   return (ret);
}

int _ExportSVG(AFILE *svg, SplineChar * sc, int layer) {
   char *end;
   int em_size;
   DBounds b;

   SplineCharLayerFindBounds(sc, layer, &b);
   em_size=sc->parent->ascent+sc->parent->descent;
   if (b.minx>0)
      b.minx=0;
   if (b.maxx<em_size)
      b.maxx=em_size;
   if (b.miny>-sc->parent->descent)
      b.miny=-sc->parent->descent;
   if (b.maxy<em_size)
      b.maxy=em_size;

   afprintf(svg, "<?xml version=\"1.0\" standalone=\"no\"?>\n");
   afprintf(svg,
	   "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\" >\n");
   afprintf(svg, "<svg viewBox=\"%d %d %d %d\">\n", (int) floor(b.minx),
	   (int) floor(b.miny), (int) ceil(b.maxx), (int) ceil(b.maxy));
   afprintf(svg, "  <g transform=\"matrix(1 0 0 -1 0 %d)\">\n",
	   sc->parent->ascent);
   if (sc->parent->multilayer || sc->parent->strokedfont
       || !svg_sc_any(sc, layer)) {
      afprintf(svg, "   <g ");
      end="   </g>\n";
   } else {
      afprintf(svg, "   <path fill=\"currentColor\"\n");
      end="   </path>\n";
   }
   svg_scpathdump(svg, sc, end, layer);
   afprintf(svg, "  </g>\n\n");
   afprintf(svg, "</svg>\n");

   return (!aferror(svg));
}

/* ************************************************************************** */
/* *****************************    SVG Input    **************************** */
/* ************************************************************************** */

#ifdef _NO_LIBXML
static int HasSVG(void) {
   return (false);
}

SplineFont *SFReadSVG(char *filename, int flags) {
   return (NULL);
}

char **NamesReadSVG(char *filename) {
   return (NULL);
}

SplineSet *SplinePointListInterpretSVG(char *filename,char *memory,
				       int memlen, int em_size, int ascent,
				       int is_stroked) {
   return (NULL);
}
#else

#   ifndef HAVE_ICONV_H
#      undef iconv
#      undef iconv_t
#      undef iconv_open
#      undef iconv_close
#   endif

#   include <libxml/parser.h>

#   ifdef __CygWin
/*
 * FIXME: Check whether this kludge is still (a) necessary, (b)
 * functional. At least (a) seems unlikely to have remained true over
 * time.
 */
/* Nasty kludge, but xmlFree doesn't work on cygwin (or I can't get it to) */
#      define xmlFree free
#   endif

static int libxml_init_base() {
   return (true);
}

/* Find a node with the given id */
static xmlNodePtr XmlFindID(xmlNodePtr xml,char *name) {
   xmlChar *id;
   xmlNodePtr child, ret;

   id=xmlGetProp(xml, (xmlChar *) "id");
   if (id != NULL && xmlStrcmp(id, (xmlChar *) name)==0) {
      xmlFree(id);
      return (xml);
   }
   if (id != NULL)
      xmlFree(id);

   for (child=xml->children; child != NULL; child=child->next) {
      ret=XmlFindID(child, name);
      if (ret != NULL)
	 return (ret);
   }
   return (NULL);
}

static xmlNodePtr XmlFindURI(xmlNodePtr xml,char *name) {
   xmlNodePtr ret;
   char *pt, ch;

   if (strncmp(name, "url(#", 5) != 0)
      return (NULL);
   name += 5;
   for (pt=name; *pt != ')' && *pt != '\0'; ++pt);
   ch=*pt;
   *pt='\0';
   ret=XmlFindID(xml, name);
   *pt=ch;
   return (ret);
}

/* We want to look for "font" nodes within "svg" nodes. Since "svg" nodes may */
/*  be embedded within another xml/html document there may be several of them */
/*  and there may be several fonts within each */
static int _FindSVGFontNodes(xmlNodePtr node,xmlNodePtr *fonts,int cnt,
			     int max, char *nodename) {
   if (xmlStrcmp(node->name, (const xmlChar *) nodename)==0) {
      if (strcmp(nodename, "svg")==0)
	 nodename="font";
      else {
	 fonts[cnt++]=node;
	 if (cnt >= max)
	    return (cnt);
      }
   }

   for (node=node->children; node != NULL; node=node->next) {
      cnt=_FindSVGFontNodes(node, fonts, cnt, max, nodename);
      if (cnt >= max)
	 return (cnt);
   }
   return (cnt);
}

static xmlNodePtr *FindSVGFontNodes(xmlDocPtr doc) {
   xmlNodePtr *fonts=NULL;
   int cnt;

   fonts=calloc(100, sizeof(xmlNodePtr));	/* If the file has more than 100 fonts in it then it's foolish to expect the user to pick out one, so let's limit ourselves to 100 */
   cnt=_FindSVGFontNodes(xmlDocGetRootElement(doc), fonts, 0, 100, "svg");
   if (cnt==0) {
      free(fonts);
      return (NULL);
   }
   return (fonts);
}

static xmlNodePtr SVGPickFont(xmlNodePtr *fonts,char *filename) {
   int cnt;
   char **names;
   xmlChar *name;
   char *pt, *lparen;
   int choice;

   for (cnt=0; fonts[cnt] != NULL; ++cnt);
   names=malloc((cnt+1)*sizeof(char *));
   for (cnt=0; fonts[cnt] != NULL; ++cnt) {
      name=xmlGetProp(fonts[cnt], (xmlChar *) "id");
      if (name==NULL) {
	 names[cnt]=fastrdup("nameless-font");
      } else {
	 names[cnt]=fastrdup((char *) name);
	 xmlFree(name);
      }
   }
   names[cnt]=NULL;

   choice=-1;
   pt=NULL;
   if (filename != NULL)
      pt=strrchr(filename, '/');
   if (pt==NULL)
      pt=filename;
   if (pt != NULL && (lparen=strchr(pt, '(')) != NULL
       && strchr(lparen, ')') != NULL) {
      char *find=fastrdup(lparen+1);

      pt=strchr(find, ')');
      if (pt != NULL)
	 *pt='\0';
      for (choice=cnt-1; choice >= 0; --choice)
	 if (strcmp(names[choice], find)==0)
	    break;
      if (choice==-1) {
	 char *fn=fastrdup(filename);

	 fn[lparen-filename]='\0';
	 ErrorMsg(2,"Not in collection:  %s is not in %.100s\n",find,fn);
	 free(fn);
      }
      free(find);
   } else
     choice=0;

   for (cnt=0;names[cnt]!=NULL;cnt++)
     free(names[cnt]);
   free(names);
   if (choice!=-1)
      return (fonts[choice]);

   return (NULL);
}

/* I don't see where the spec says that the seperator between numbers is */
/*  comma or whitespace (both is ok too) */
/* But the style sheet spec says it, so I probably just missed it */
static char *skipcomma(char *pt) {
   while (isspace(*pt))
      ++pt;
   if (*pt==',')
      ++pt;
   return (pt);
}

static void SVGTraceArc(SplineSet *cur,BasePoint *current,
			double x, double y, double rx, double ry,
			double axisrot, int large_arc, int sweep) {
   double cosr, sinr;
   double x1p, y1p;
   double lambda, factor;
   double cxp, cyp, cx, cy;
   double tmpx, tmpy, t2x, t2y;
   double startangle, delta, a;
   SplinePoint *final, *sp;
   BasePoint arcp[4], prevcp[4], nextcp[4], firstcp[2];
   int i, j, ia, firstia;
   static double sines[]={ 0,1,0,-1,0,1,0,-1,0,1,0,-1 };
   static double cosines[]={ 1,0,-1,0,1,0,-1,0,1,0,-1,0 };

   final=SplinePointCreate(x, y);
   if (rx<0)
      rx=-rx;
   if (ry<0)
      ry=-ry;
   if (rx != 0 && ry != 0) {
      /* Page 647 in the SVG 1.1 spec describes how to do this */
      /* This is Appendix F (Implementation notes) section 6.5 */
      cosr=cos(axisrot);
      sinr=sin(axisrot);
      x1p=cosr * (current->x-x)/2+sinr * (current->y-y)/2;
      y1p=-sinr * (current->x-x)/2+cosr * (current->y-y)/2;
      /* Correct for bad radii */
      lambda=x1p * x1p/(rx * rx)+y1p * y1p/(ry * ry);
      if (lambda>1) {
	 lambda=sqrt(lambda);
	 rx *= lambda;
	 ry *= lambda;
      }
      factor=rx * rx * ry * ry-rx * rx * y1p * y1p-ry * ry * x1p * x1p;
      if (RealNear(factor, 0))
	 factor=0;		/* Avoid rounding errors that lead to small negative values */
      else
	 factor=sqrt(factor/(rx * rx * y1p * y1p+ry * ry * x1p * x1p));
      if (large_arc==sweep)
	 factor=-factor;
      cxp=factor * (rx * y1p)/ry;
      cyp=-factor * (ry * x1p)/rx;
      cx=cosr * cxp-sinr * cyp+(current->x+x)/2;
      cy=sinr * cxp+cosr * cyp+(current->y+y)/2;

      tmpx=(x1p-cxp)/rx;
      tmpy=(y1p-cyp)/ry;
      startangle=acos(tmpx/sqrt(tmpx * tmpx+tmpy * tmpy));
      if (tmpy<0)
	 startangle=-startangle;
      t2x=(-x1p-cxp)/rx;
      t2y=(-y1p-cyp)/ry;
      delta=(tmpx * t2x+tmpy * t2y) /
	 sqrt((tmpx * tmpx+tmpy * tmpy)*(t2x * t2x+t2y * t2y));
      /* We occasionally got rounding errors near -1 */
      if (delta <= -1)
	 delta=M_PI;
      else if (delta >= 1)
	 delta=0;
      else
	 delta=acos(delta);
      if (tmpx * t2y-tmpy * t2x<0)
	 delta=-delta;
      if (sweep==0 && delta>0)
	 delta -= 2*M_PI;
      if (sweep && delta<0)
	 delta += 2*M_PI;

      if (delta>0) {
	 i=0;
	 ia=firstia=floor(startangle/(M_PI/2))+1;
	 for (a=ia * (M_PI/2), ia += 4;
	      a<startangle+delta && !RealNear(a, startangle+delta);
	      a += M_PI/2, ++i, ++ia) {
	    t2x=rx * cosines[ia];
	    t2y=ry * sines[ia];
	    arcp[i].x=cosr * t2x-sinr * t2y+cx;
	    arcp[i].y=sinr * t2x+cosr * t2y+cy;
	    if (t2x==0) {
	       t2x=rx * cosines[ia+1];
	       t2y=0;
	    } else {
	       t2x=0;
	       t2y=ry * sines[ia+1];
	    }
	    prevcp[i].x=arcp[i].x-.552*(cosr * t2x-sinr * t2y);
	    prevcp[i].y=arcp[i].y-.552*(sinr * t2x+cosr * t2y);
	    nextcp[i].x=arcp[i].x+.552*(cosr * t2x-sinr * t2y);
	    nextcp[i].y=arcp[i].y+.552*(sinr * t2x+cosr * t2y);
	 }
      } else {
	 i=0;
	 ia=firstia=ceil(startangle/(M_PI/2))-1;
	 for (a=ia * (M_PI/2), ia += 8;
	      a>startangle+delta && !RealNear(a, startangle+delta);
	      a -= M_PI/2, ++i, --ia) {
	    t2x=rx * cosines[ia];
	    t2y=ry * sines[ia];
	    arcp[i].x=cosr * t2x-sinr * t2y+cx;
	    arcp[i].y=sinr * t2x+cosr * t2y+cy;
	    if (t2x==0) {
	       t2x=rx * cosines[ia+1];
	       t2y=0;
	    } else {
	       t2x=0;
	       t2y=ry * sines[ia+1];
	    }
	    prevcp[i].x=arcp[i].x+.552*(cosr * t2x-sinr * t2y);
	    prevcp[i].y=arcp[i].y+.552*(sinr * t2x+cosr * t2y);
	    nextcp[i].x=arcp[i].x-.552*(cosr * t2x-sinr * t2y);
	    nextcp[i].y=arcp[i].y-.552*(sinr * t2x+cosr * t2y);
	 }
      }
      if (i != 0) {
	 double firsta=firstia * M_PI/2;
	 double d=(firsta-startangle)/2;
	 double th=startangle+d;
	 double hypot=1/cos(d);
	 BasePoint temp;

	 t2x=rx * cos(th)*hypot;
	 t2y=ry * sin(th)*hypot;
	 temp.x=cosr * t2x-sinr * t2y+cx;
	 temp.y=sinr * t2x+cosr * t2y+cy;
	 firstcp[0].x=cur->last->me.x+.552*(temp.x-cur->last->me.x);
	 firstcp[0].y=cur->last->me.y+.552*(temp.y-cur->last->me.y);
	 firstcp[1].x=arcp[0].x+.552*(temp.x-arcp[0].x);
	 firstcp[1].y=arcp[0].y+.552*(temp.y-arcp[0].y);
      }
      for (j=0; j<i; ++j) {
	 sp=SplinePointCreate(arcp[j].x, arcp[j].y);
	 if (j != 0) {
	    sp->prevcp=prevcp[j];
	    cur->last->nextcp=nextcp[j-1];
	 } else {
	    sp->prevcp=firstcp[1];
	    cur->last->nextcp=firstcp[0];
	 }
	 sp->noprevcp=cur->last->nonextcp=false;
	 SplineMake(cur->last, sp, false);
	 cur->last=sp;
      }
      {
	 double hypot, c, s;
	 BasePoint temp;

	 if (i==0) {
	    double th=startangle+delta/2;

	    hypot=1.0/cos(delta/2);
	    c=cos(th);
	    s=sin(th);
	 } else {
	    double lasta=delta<0?a+M_PI/2:a-M_PI/2;
	    double d=(startangle+delta-lasta);
	    double th=lasta+d/2;

	    hypot=1.0/cos(d/2);
	    c=cos(th);
	    s=sin(th);
	 }
	 t2x=rx * c * hypot;
	 t2y=ry * s * hypot;
	 temp.x=cosr * t2x-sinr * t2y+cx;
	 temp.y=sinr * t2x+cosr * t2y+cy;
	 cur->last->nextcp.x =
	    cur->last->me.x+.552*(temp.x-cur->last->me.x);
	 cur->last->nextcp.y =
	    cur->last->me.y+.552*(temp.y-cur->last->me.y);
	 final->prevcp.x=final->me.x+.552*(temp.x-final->me.x);
	 final->prevcp.y=final->me.y+.552*(temp.y-final->me.y);
	 cur->last->nonextcp=final->noprevcp=false;
      }
   }
   *current=final->me;
   SplineMake(cur->last, final, false);
   cur->last=final;
}

static SplineSet *SVGParsePath(xmlChar *path) {
   BasePoint current;
   SplineSet *head=NULL, *last=NULL, *cur=NULL;
   SplinePoint *sp;
   int type='M';
   double x1, x2, x, y1, y2, y, rx, ry, axisrot;
   int large_arc, sweep;
   int order2=0;
   char *end;

   current.x=current.y=0;

   while (*path) {
      while (*path==' ')
	 ++path;
      while (isalpha(*path))
	 type=*path++;
      if (*path=='\0' && type != 'z' && type != 'Z')
	 break;
      if (type=='m' || type=='M') {
	 if (cur != NULL && cur->last != cur->first) {
	    if (RealNear(cur->last->me.x, cur->first->me.x) &&
		RealNear(cur->last->me.y, cur->first->me.y)) {
	       cur->first->prevcp=cur->last->prevcp;
	       cur->first->noprevcp=cur->last->noprevcp;
	       cur->first->prev=cur->last->prev;
	       cur->first->prev->to=cur->first;
	       SplinePointFree(cur->last);
	    } else
	       SplineMake(cur->last, cur->first, order2);
	    cur->last=cur->first;
	 }
	 x=strtod((char *) path, &end);
	 end=skipcomma(end);
	 y=strtod(end, &end);
	 if (type=='m') {
	    x += current.x;
	    y += current.y;
	 }
	 sp=SplinePointCreate(x, y);
	 current=sp->me;
	 cur=chunkalloc(sizeof(SplineSet));
	 if (head==NULL)
	    head=cur;
	 else
	    last->next=cur;
	 last=cur;
	 cur->first=cur->last=sp;
	 /* If you omit a command after a moveto then it defaults to lineto */
	 if (type=='m')
	    type='l';
	 else
	    type='L';
      } else if (type=='z' || type=='Z') {
	 if (cur != NULL && cur->last != cur->first) {
	    if (RealNear(cur->last->me.x, cur->first->me.x) &&
		RealNear(cur->last->me.y, cur->first->me.y)) {
	       cur->first->prevcp=cur->last->prevcp;
	       cur->first->noprevcp=cur->last->noprevcp;
	       cur->first->prev=cur->last->prev;
	       cur->first->prev->to=cur->first;
	       SplinePointFree(cur->last);
	    } else
	       SplineMake(cur->last, cur->first, order2);
	    cur->last=cur->first;
	    current=cur->first->me;
	 }
	 type=' ';
	 end=(char *) path;
      } else {
	 if (cur==NULL) {
	    sp=SplinePointCreate(current.x, current.y);
	    cur=chunkalloc(sizeof(SplineSet));
	    if (head==NULL)
	       head=cur;
	    else
	       last->next=cur;
	    last=cur;
	    cur->first=cur->last=sp;
	 }
	 switch (type) {
	   case 'l':
	   case 'L':
	      x=strtod((char *) path, &end);
	      end=skipcomma(end);
	      y=strtod(end, &end);
	      if (type=='l') {
		 x += current.x;
		 y += current.y;
	      }
	      sp=SplinePointCreate(x, y);
	      current=sp->me;
	      SplineMake(cur->last, sp, order2);
	      cur->last=sp;
	      break;
	   case 'h':
	   case 'H':
	      x=strtod((char *) path, &end);
	      y=current.y;
	      if (type=='h') {
		 x += current.x;
	      }
	      sp=SplinePointCreate(x, y);
	      current=sp->me;
	      SplineMake(cur->last, sp, order2);
	      cur->last=sp;
	      break;
	   case 'v':
	   case 'V':
	      x=current.x;
	      y=strtod((char *) path, &end);
	      if (type=='v') {
		 y += current.y;
	      }
	      sp=SplinePointCreate(x, y);
	      current=sp->me;
	      SplineMake(cur->last, sp, order2);
	      cur->last=sp;
	      break;
	   case 'c':
	   case 'C':
	      x1=strtod((char *) path, &end);
	      end=skipcomma(end);
	      y1=strtod(end, &end);
	      end=skipcomma(end);
	      x2=strtod(end, &end);
	      end=skipcomma(end);
	      y2=strtod(end, &end);
	      end=skipcomma(end);
	      x=strtod(end, &end);
	      end=skipcomma(end);
	      y=strtod(end, &end);
	      if (type=='c') {
		 x1 += current.x;
		 y1 += current.y;
		 x2 += current.x;
		 y2 += current.y;
		 x += current.x;
		 y += current.y;
	      }
	      sp=SplinePointCreate(x, y);
	      sp->prevcp.x=x2;
	      sp->prevcp.y=y2;
	      sp->noprevcp=false;
	      cur->last->nextcp.x=x1;
	      cur->last->nextcp.y=y1;
	      cur->last->nonextcp=false;
	      current=sp->me;
	      SplineMake(cur->last, sp, false);
	      cur->last=sp;
	      break;
	   case 's':
	   case 'S':
	      x1=2*cur->last->me.x-cur->last->prevcp.x;
	      y1=2*cur->last->me.y-cur->last->prevcp.y;
	      x2=strtod((char *) path, &end);
	      end=skipcomma(end);
	      y2=strtod(end, &end);
	      end=skipcomma(end);
	      x=strtod(end, &end);
	      end=skipcomma(end);
	      y=strtod(end, &end);
	      if (type=='s') {
		 x2 += current.x;
		 y2 += current.y;
		 x += current.x;
		 y += current.y;
	      }
	      sp=SplinePointCreate(x, y);
	      sp->prevcp.x=x2;
	      sp->prevcp.y=y2;
	      sp->noprevcp=false;
	      cur->last->nextcp.x=x1;
	      cur->last->nextcp.y=y1;
	      cur->last->nonextcp=false;
	      current=sp->me;
	      SplineMake(cur->last, sp, false);
	      cur->last=sp;
	      break;
	   case 'Q':
	   case 'q':
	      x1=strtod((char *) path, &end);
	      end=skipcomma(end);
	      y1=strtod(end, &end);
	      end=skipcomma(end);
	      x=strtod(end, &end);
	      end=skipcomma(end);
	      y=strtod(end, &end);
	      if (type=='q') {
		 x1 += current.x;
		 y1 += current.y;
		 x += current.x;
		 y += current.y;
	      }
	      sp=SplinePointCreate(x, y);
	      sp->prevcp.x=x1;
	      sp->prevcp.y=y1;
	      sp->noprevcp=false;
	      cur->last->nextcp.x=x1;
	      cur->last->nextcp.y=y1;
	      cur->last->nonextcp=false;
	      current=sp->me;
	      SplineMake(cur->last, sp, true);
	      cur->last=sp;
	      order2=true;
	      break;
	   case 'T':
	   case 't':
	      x=strtod((char *) path, &end);
	      end=skipcomma(end);
	      y=strtod(end, &end);
	      if (type=='t') {
		 x += current.x;
		 y += current.y;
	      }
	      x1=2*cur->last->me.x-cur->last->prevcp.x;
	      y1=2*cur->last->me.y-cur->last->prevcp.y;
	      sp=SplinePointCreate(x, y);
	      sp->prevcp.x=x1;
	      sp->prevcp.y=y1;
	      sp->noprevcp=false;
	      cur->last->nextcp.x=x1;
	      cur->last->nextcp.y=y1;
	      cur->last->nonextcp=false;
	      current=sp->me;
	      SplineMake(cur->last, sp, true);
	      cur->last=sp;
	      order2=true;
	      break;
	   case 'A':
	   case 'a':
	      rx=strtod((char *) path, &end);
	      end=skipcomma(end);
	      ry=strtod(end, &end);
	      end=skipcomma(end);
	      axisrot=strtod(end, &end)*M_PI/180;
	      end=skipcomma(end);
	      large_arc=strtol(end, &end, 10);
	      end=skipcomma(end);
	      sweep=strtol(end, &end, 10);
	      end=skipcomma(end);
	      x=strtod(end, &end);
	      end=skipcomma(end);
	      y=strtod(end, &end);
	      if (type=='a') {
		 x += current.x;
		 y += current.y;
	      }
	      if (x != current.x || y != current.y)
		 SVGTraceArc(cur, &current, x, y, rx, ry, axisrot, large_arc,
			     sweep);
	      break;
	   default:
	      ErrorMsg(2,"Unknown type '%c' found in path specification\n",
		       type);
	      break;
	 }
      }
      path=(xmlChar *) skipcomma(end);
   }
   return (head);
}

static SplineSet *SVGParseExtendedPath(xmlNodePtr svg,xmlNodePtr top) {
   /* Inkscape exends paths by allowing a sprio representation */
   /* But their representation looks nothing like spiros and I can't guess at it */
   xmlChar *outline /*, *effect, *spirooutline */ ;
   SplineSet *head=NULL;

   outline=xmlGetProp(svg, (xmlChar *) "d");
   if (outline != NULL) {
      head=SVGParsePath(outline);
      xmlFree(outline);
   }
   return (head);
}

static SplineSet *SVGParseRect(xmlNodePtr rect) {
   /* x,y,width,height,rx,ry */
   double x, y, width, height, rx, ry;
   char *num;
   SplinePoint *sp;
   SplineSet *cur;

   num=(char *) xmlGetProp(rect, (xmlChar *) "x");
   if (num != NULL) {
      x=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      x=0;
   num=(char *) xmlGetProp(rect, (xmlChar *) "width");
   if (num != NULL) {
      width=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      return (NULL);
   num=(char *) xmlGetProp(rect, (xmlChar *) "y");
   if (num != NULL) {
      y=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      y=0;
   num=(char *) xmlGetProp(rect, (xmlChar *) "height");
   if (num != NULL) {
      height=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      return (NULL);

   rx=ry=0;
   num=(char *) xmlGetProp(rect, (xmlChar *) "rx");
   if (num != NULL) {
      ry=rx=strtod((char *) num, NULL);
      xmlFree(num);
   }
   num=(char *) xmlGetProp(rect, (xmlChar *) "ry");
   if (num != NULL) {
      ry=strtod((char *) num, NULL);
      if (rx==0)
	 ry=rx;
      xmlFree(num);
   }

   if (2*rx>width)
      rx=width/2;
   if (2*ry>height)
      ry=height/2;

   cur=chunkalloc(sizeof(SplineSet));
   if (rx==0) {
      cur->first=SplinePointCreate(x, y+height);
      cur->last=SplinePointCreate(x+width, y+height);
      SplineMake(cur->first, cur->last, true);
      sp=SplinePointCreate(x+width, y);
      SplineMake(cur->last, sp, true);
      cur->last=sp;
      sp=SplinePointCreate(x, y);
      SplineMake(cur->last, sp, true);
      SplineMake(sp, cur->first, true);
      cur->last=cur->first;
      return (cur);
   } else {
      cur->first=SplinePointCreate(x, y+height-ry);
      cur->last=SplinePointCreate(x+rx, y+height);
      cur->first->nextcp.x=x;
      cur->first->nextcp.y=y+height;
      cur->last->prevcp=cur->first->nextcp;
      cur->first->nonextcp=cur->last->noprevcp=false;
      SplineMake(cur->first, cur->last, false);
      if (rx<2*width) {
	 sp=SplinePointCreate(x+width-rx, y+height);
	 SplineMake(cur->last, sp, true);
	 cur->last=sp;
      }
      sp=SplinePointCreate(x+width, y+height-ry);
      sp->prevcp.x=x+width;
      sp->prevcp.y=y+height;
      cur->last->nextcp=sp->prevcp;
      cur->last->nonextcp=sp->noprevcp=false;
      SplineMake(cur->last, sp, false);
      cur->last=sp;
      if (ry<2*height) {
	 sp=SplinePointCreate(x+width, y+ry);
	 SplineMake(cur->last, sp, false);
	 cur->last=sp;
      }
      sp=SplinePointCreate(x+width-rx, y);
      sp->prevcp.x=x+width;
      sp->prevcp.y=y;
      cur->last->nextcp=sp->prevcp;
      cur->last->nonextcp=sp->noprevcp=false;
      SplineMake(cur->last, sp, false);
      cur->last=sp;
      if (rx<2*width) {
	 sp=SplinePointCreate(x+rx, y);
	 SplineMake(cur->last, sp, false);
	 cur->last=sp;
      }
      cur->last->nextcp.x=x;
      cur->last->nextcp.y=y;
      cur->last->nonextcp=false;
      if (ry >= 2*height) {
	 cur->first->prevcp=cur->last->nextcp;
	 cur->first->noprevcp=false;
      } else {
	 sp=SplinePointCreate(x, y+ry);
	 sp->prevcp=cur->last->nextcp;
	 sp->noprevcp=false;
	 SplineMake(cur->last, sp, false);
	 cur->last=sp;
      }
      SplineMake(cur->last, cur->first, false);
      cur->first=cur->last;
      return (cur);
   }
}

static SplineSet *SVGParseLine(xmlNodePtr line) {
   /* x1,y1, x2,y2 */
   double x, y, x2, y2;
   char *num;
   SplinePoint *sp1, *sp2;
   SplineSet *cur;

   num=(char *) xmlGetProp(line, (xmlChar *) "x1");
   if (num != NULL) {
      x=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      x=0;
   num=(char *) xmlGetProp(line, (xmlChar *) "x2");
   if (num != NULL) {
      x2=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      x2=0;
   num=(char *) xmlGetProp(line, (xmlChar *) "y1");
   if (num != NULL) {
      y=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      y=0;
   num=(char *) xmlGetProp(line, (xmlChar *) "y2");
   if (num != NULL) {
      y2=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      y2=0;

   sp1=SplinePointCreate(x, y);
   sp2=SplinePointCreate(x2, y2);
   SplineMake(sp1, sp2, false);
   cur=chunkalloc(sizeof(SplineSet));
   cur->first=sp1;
   cur->last=sp2;
   return (cur);
}

static SplineSet *SVGParseEllipse(xmlNodePtr ellipse,int iscircle) {
   /* cx,cy,rx,ry */
   /* cx,cy,r */
   double cx, cy, rx, ry;
   char *num;
   SplinePoint *sp;
   SplineSet *cur;

   num=(char *) xmlGetProp(ellipse, (xmlChar *) "cx");
   if (num != NULL) {
      cx=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      cx=0;
   num=(char *) xmlGetProp(ellipse, (xmlChar *) "cy");
   if (num != NULL) {
      cy=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      cy=0;
   if (iscircle) {
      num=(char *) xmlGetProp(ellipse, (xmlChar *) "r");
      if (num != NULL) {
	 rx=ry=strtod((char *) num, NULL);
	 xmlFree(num);
      } else
	 return (NULL);
   } else {
      num=(char *) xmlGetProp(ellipse, (xmlChar *) "rx");
      if (num != NULL) {
	 rx=strtod((char *) num, NULL);
	 xmlFree(num);
      } else
	 return (NULL);
      num=(char *) xmlGetProp(ellipse, (xmlChar *) "ry");
      if (num != NULL) {
	 ry=strtod((char *) num, NULL);
	 xmlFree(num);
      } else
	 return (NULL);
   }
   if (rx<0)
      rx=-rx;
   if (ry<0)
      ry=-ry;

   cur=chunkalloc(sizeof(SplineSet));
   cur->first=SplinePointCreate(cx-rx, cy);
   cur->last=SplinePointCreate(cx, cy+ry);
   cur->first->nextcp.x=cx-rx;
   cur->first->nextcp.y=cy+ry;
   cur->last->prevcp=cur->first->nextcp;
   cur->first->noprevcp=cur->first->nonextcp=false;
   cur->last->noprevcp=cur->last->nonextcp=false;
   SplineMake(cur->first, cur->last, true);
   sp=SplinePointCreate(cx+rx, cy);
   sp->prevcp.x=cx+rx;
   sp->prevcp.y=cy+ry;
   sp->nextcp.x=cx+rx;
   sp->nextcp.y=cy-ry;
   sp->nonextcp=sp->noprevcp=false;
   cur->last->nextcp=sp->prevcp;
   SplineMake(cur->last, sp, true);
   cur->last=sp;
   sp=SplinePointCreate(cx, cy-ry);
   sp->prevcp=cur->last->nextcp;
   sp->nextcp.x=cx-rx;
   sp->nextcp.y=cy-ry;
   sp->nonextcp=sp->noprevcp=false;
   cur->first->prevcp=sp->nextcp;
   SplineMake(cur->last, sp, true);
   SplineMake(sp, cur->first, true);
   cur->last=cur->first;
   return (cur);
}

static SplineSet *SVGParsePoly(xmlNodePtr poly,int isgon) {
   /* points */
   double x, y;
   char *pts, *end;
   SplinePoint *sp;
   SplineSet *cur;

   pts=(char *) xmlGetProp(poly, (xmlChar *) "points");
   if (pts==NULL)
      return (NULL);

   x=strtod(pts, &end);
   while (isspace(*end) || *end==',')
      ++end;
   y=strtod(end, &end);
   while (isspace(*end))
      ++end;

   cur=chunkalloc(sizeof(SplineSet));
   cur->first=cur->last=SplinePointCreate(x, y);
   while (*end) {
      x=strtod(end, &end);
      while (isspace(*end) || *end==',')
	 ++end;
      y=strtod(end, &end);
      while (isspace(*end))
	 ++end;
      sp=SplinePointCreate(x, y);
      SplineMake(cur->last, sp, false);
      cur->last=sp;
   }
   if (isgon) {
      if (RealNear(cur->last->me.x, cur->first->me.x) &&
	  RealNear(cur->last->me.y, cur->first->me.y)) {
	 cur->first->prev=cur->last->prev;
	 cur->first->prev->to=cur->first;
	 SplinePointFree(cur->last);
      } else
	 SplineMake(cur->last, cur->first, false);
      cur->last=cur->first;
   }
   return (cur);
}

struct svg_state {
   double linewidth;
   int dofill, dostroke;
   uint32_t fillcol, strokecol;
   float fillopacity, strokeopacity;
   int isvisible;
   enum linecap lc;
   enum linejoin lj;
   double transform[6];
   DashType dashes[DASH_MAX];
   SplineSet *clippath;
   uint8_t free_clip;
   uint32_t currentColor;
   uint32_t stopColor;
   float stopOpacity;
};

static void SVGFigureTransform(struct svg_state *st,char *name) {
   double trans[6], res[6];
   double a, cx, cy;
   char *pt, *paren, *end;

   /* matrix(a,b,c,d,e,f)
      rotate(theta[,cx,cy])
      scale(sx[,sy])
      translate(x,y)
      skewX(theta)
      skewY(theta)
    */

   for (pt=(char *) name; isspace(*pt); ++pt);
   while (*pt) {
      paren=strchr(pt, '(');
      if (paren==NULL)
	 break;
      if (strncmp(pt, "matrix", paren-pt)==0) {
	 trans[0]=strtod(paren+1, &end);
	 trans[1]=strtod(skipcomma(end), &end);
	 trans[2]=strtod(skipcomma(end), &end);
	 trans[3]=strtod(skipcomma(end), &end);
	 trans[4]=strtod(skipcomma(end), &end);
	 trans[5]=strtod(skipcomma(end), &end);
      } else if (strncmp(pt, "rotate", paren-pt)==0) {
	 trans[4]=trans[5]=0;
	 a=strtod(paren+1, &end)*M_PI/180;
	 trans[0]=trans[3]=cos(a);
	 trans[1]=sin(a);
	 trans[2]=-trans[1];
	 while (isspace(*end))
	    ++end;
	 if (*end != ')') {
	    cx=strtod(skipcomma(end), &end);
	    cy=strtod(skipcomma(end), &end);
	    res[0]=res[3]=1;
	    res[1]=res[2]=0;
	    res[4]=cx;
	    res[5]=cy;
	    MatMultiply(res, trans, res);
	    trans[0]=trans[3]=1;
	    trans[1]=trans[2]=0;
	    trans[4]=-cx;
	    trans[5]=-cy;
	    MatMultiply(res, trans, res);
	    memcpy(trans, res, sizeof(res));
	 }
      } else if (strncmp(pt, "scale", paren-pt)==0) {
	 trans[1]=trans[2]=trans[4]=trans[5]=0;
	 trans[0]=trans[3]=strtod(paren+1, &end);
	 while (isspace(*end))
	    ++end;
	 if (*end != ')')
	    trans[3]=strtod(skipcomma(end), &end);
      } else if (strncmp(pt, "translate", paren-pt)==0) {
	 trans[0]=trans[3]=1;
	 trans[1]=trans[2]=trans[5]=0;
	 trans[4]=strtod(paren+1, &end);
	 while (isspace(*end))
	    ++end;
	 if (*end != ')')
	    trans[5]=strtod(skipcomma(end), &end);
      } else if (strncmp(pt, "skewX", paren-pt)==0) {
	 trans[0]=trans[3]=1;
	 trans[1]=trans[2]=trans[4]=trans[5]=0;
	 trans[2]=tan(strtod(paren+1, &end)*M_PI/180);
      } else if (strncmp(pt, "skewY", paren-pt)==0) {
	 trans[0]=trans[3]=1;
	 trans[1]=trans[2]=trans[4]=trans[5]=0;
	 trans[1]=tan(strtod(paren+1, &end)*M_PI/180);
      } else
	 break;
      while (isspace(*end))
	 ++end;
      if (*end != ')')
	 break;
      MatMultiply(trans, st->transform, st->transform);
      pt=end+1;
      while (isspace(*pt))
	 ++pt;
   }
}

static void SVGuseTransform(struct svg_state *st,xmlNodePtr use,
			    xmlNodePtr symbol) {
   double x, y, uwid, uheight, swid, sheight;
   char *num, *end;
   double trans[6];

   num=(char *) xmlGetProp(use, (xmlChar *) "x");
   if (num != NULL) {
      x=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      x=0;
   num=(char *) xmlGetProp(use, (xmlChar *) "y");
   if (num != NULL) {
      y=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      y=0;
   if (x != 0 || y != 0) {
      trans[0]=trans[3]=1;
      trans[1]=trans[2]=0;
      trans[4]=x;
      trans[5]=y;
      MatMultiply(trans, st->transform, st->transform);
   }
   num=(char *) xmlGetProp(use, (xmlChar *) "width");
   if (num != NULL) {
      uwid=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      uwid=0;
   num=(char *) xmlGetProp(use, (xmlChar *) "height");
   if (num != NULL) {
      uheight=strtod((char *) num, NULL);
      xmlFree(num);
   } else
      uheight=0;
   num=(char *) xmlGetProp(symbol, (xmlChar *) "viewBox");
   if (num != NULL) {
      x=strtod((char *) num, &end);
      y=strtod((char *) end+1, &end);
      swid=strtod((char *) end+1, &end);
      sheight=strtod((char *) end+1, &end);
      xmlFree(num);
   } else
      return;
   if (uwid != 0 || uheight != 0) {
      trans[0]=trans[3]=1;
      trans[1]=trans[2]=trans[4]=trans[5]=0;
      if (uwid != 0 && swid != 0)
	 trans[0]=uwid/swid;
      if (uheight != 0 && sheight != 0)
	 trans[3]=uheight/sheight;
      MatMultiply(trans, st->transform, st->transform);
   }
}

static double parseGCoord(xmlChar *prop,int bb_units,double bb_low,
			double bb_high) {
   char *end;
   double val=strtod((char *) prop, &end);

   if (*end=='%')
      val /= 100.0;

   if (bb_units)
      val=bb_low+val * (bb_high-bb_low);
   return (val);
}

static int xmlParseColor(xmlChar *name,uint32_t *color,char **url,
			 struct svg_state *st);

static void xmlParseColorSource(xmlNodePtr top,char *name,DBounds *bbox,
				struct svg_state *st, struct gradient **_grad,
				struct epattern **_epat) {
   xmlNodePtr colour_source=XmlFindURI(top, name);
   int islinear;
   xmlChar *prop;
   xmlNodePtr kid;
   int scnt;

   *_grad=NULL;
   *_epat=NULL;
   if (colour_source==NULL)
      ErrorMsg(2,"Could not find Color Source with id %s.\n", name);
   else
      if ((islinear =
	   xmlStrcmp(colour_source->name, (xmlChar *) "linearGradient")==0)
	  || xmlStrcmp(colour_source->name,
		       (xmlChar *) "radialGradient")==0) {
      struct gradient *grad=chunkalloc(sizeof(struct gradient));
      int bbox_units;

      *_grad=grad;

      prop=xmlGetProp(colour_source, (xmlChar *) "gradientUnits");
      if (prop != NULL) {
	 bbox_units=xmlStrcmp(prop, (xmlChar *) "userSpaceOnUse") != 0;
	 xmlFree(prop);
      } else
	 bbox_units=true;

      prop=xmlGetProp(colour_source, (xmlChar *) "gradientTransform");
      /* I don't support this currently */
      if (prop != NULL)
	 xmlFree(prop);

      grad->sm=sm_pad;
      prop=xmlGetProp(colour_source, (xmlChar *) "spreadMethod");
      if (prop != NULL) {
	 if (xmlStrcmp(prop, (xmlChar *) "reflect")==0)
	    grad->sm=sm_reflect;
	 else if (xmlStrcmp(prop, (xmlChar *) "repeat")==0)
	    grad->sm=sm_repeat;
	 xmlFree(prop);
      }

      if (islinear) {
	 grad->start.x=bbox->minx;
	 grad->start.y=bbox->miny;
	 grad->stop.x=bbox->maxx;
	 grad->stop.y=bbox->maxy;

	 prop=xmlGetProp(colour_source, (xmlChar *) "x1");
	 if (prop != NULL) {
	    grad->start.x =
	       parseGCoord(prop, bbox_units, bbox->minx, bbox->maxx);
	    xmlFree(prop);
	 }

	 prop=xmlGetProp(colour_source, (xmlChar *) "x2");
	 if (prop != NULL) {
	    grad->stop.x =
	       parseGCoord(prop, bbox_units, bbox->minx, bbox->maxx);
	    xmlFree(prop);
	 }

	 prop=xmlGetProp(colour_source, (xmlChar *) "y1");
	 if (prop != NULL) {
	    grad->start.y =
	       parseGCoord(prop, bbox_units, bbox->miny, bbox->maxy);
	    xmlFree(prop);
	 }

	 prop=xmlGetProp(colour_source, (xmlChar *) "y2");
	 if (prop != NULL) {
	    grad->stop.y =
	       parseGCoord(prop, bbox_units, bbox->miny, bbox->maxy);
	    xmlFree(prop);
	 }

	 grad->radius=0;
      } else {
	 double offx=(bbox->maxx-bbox->minx)/2;
	 double offy=(bbox->maxy-bbox->miny)/2;

	 grad->stop.x=(bbox->minx+bbox->maxx)/2;
	 grad->stop.y=(bbox->minx+bbox->maxy)/2;
	 grad->radius=sqrt(offx * offx+offy * offy);

	 prop=xmlGetProp(colour_source, (xmlChar *) "cx");
	 if (prop != NULL) {
	    grad->stop.x =
	       parseGCoord(prop, bbox_units, bbox->minx, bbox->maxx);
	    xmlFree(prop);
	 }

	 prop=xmlGetProp(colour_source, (xmlChar *) "cy");
	 if (prop != NULL) {
	    grad->stop.y =
	       parseGCoord(prop, bbox_units, bbox->miny, bbox->maxy);
	    xmlFree(prop);
	 }

	 prop=xmlGetProp(colour_source, (xmlChar *) "radius");
	 if (prop != NULL) {
	    grad->radius =
	       parseGCoord(prop, bbox_units, 0,
			   sqrt(4*(offx * offx+offy * offy)));
	    xmlFree(prop);
	 }

	 grad->start=grad->stop;
	 prop=xmlGetProp(colour_source, (xmlChar *) "fx");
	 if (prop != NULL) {
	    grad->start.x =
	       parseGCoord(prop, bbox_units, bbox->minx, bbox->maxx);
	    xmlFree(prop);
	 }

	 prop=xmlGetProp(colour_source, (xmlChar *) "fy");
	 if (prop != NULL) {
	    grad->start.y =
	       parseGCoord(prop, bbox_units, bbox->miny, bbox->maxy);
	    xmlFree(prop);
	 }
      }

      scnt=0;
      for (kid=colour_source->children; kid != NULL; kid=kid->next)
	 if (xmlStrcmp(kid->name, (xmlChar *) "stop")==0)
	    ++scnt;

      if (scnt==0) {
	 /* I'm not sure how to use the style stop-color, but I'm guessing */
	 /*  this might be it */
	 grad->stop_cnt=1;
	 grad->grad_stops=calloc(1, sizeof(struct grad_stops));
	 grad->grad_stops[scnt].offset=1;
	 grad->grad_stops[scnt].col=st->stopColor;
	 grad->grad_stops[scnt].opacity=st->stopOpacity;
	 ++scnt;
      } else {
	 grad->stop_cnt=scnt;
	 grad->grad_stops=calloc(scnt, sizeof(struct grad_stops));
	 scnt=0;
	 for (kid=colour_source->children; kid != NULL; kid=kid->next)
	    if (xmlStrcmp(kid->name, (xmlChar *) "stop")==0) {
	       grad->grad_stops[scnt].col=st->stopColor;
	       grad->grad_stops[scnt].opacity=st->stopOpacity;

	       prop=xmlGetProp(kid, (xmlChar *) "offset");
	       if (prop != NULL) {
		  grad->grad_stops[scnt].offset =
		     parseGCoord(prop, false, 0, 1.0);
		  xmlFree(prop);
	       }

	       prop=xmlGetProp(kid, (xmlChar *) "stop-color");
	       if (prop != NULL) {
		  xmlParseColor(prop, &grad->grad_stops[scnt].col, NULL, st);
		  xmlFree(prop);
	       }

	       prop=xmlGetProp(kid, (xmlChar *) "stop-opacity");
	       if (prop != NULL) {
		  grad->grad_stops[scnt].opacity =
		     strtod((char *) prop, NULL);
		  xmlFree(prop);
	       } else
		  grad->grad_stops[scnt].opacity=1.0;

	       ++scnt;
	    }
      }
   } else if (xmlStrcmp(colour_source->name, (xmlChar *) "pattern")==0) {
      ErrorMsg(2,"FontAnvil does not currently parse pattern Color Sources (%s).\n",
	       name);
   } else {
      ErrorMsg(2,"Color Source with id %s had an unexpected type %s.\n",
	       name, (char *) colour_source->name);
   }
}

/* Annoyingly we can't parse the colour source until we have parsed the contents */
/*  because the colour source needs to know the bounding box of the total item */
/*  This means that for something like <g> we must now go back and figure out */
/*  which components get this gradient/pattern, and which have their own source*/
/*  that overrides the inherited one */
static void xmlApplyColourSources(xmlNodePtr top,Entity *head,
				  struct svg_state *st,
				  char *fill_colour_source,
				  char *stroke_colour_source) {
   DBounds b, ssb;
   Entity *e;
   struct gradient *grad;
   struct epattern *epat;

   memset(&b, 0, sizeof(b));
   for (e=head; e != NULL; e=e->next)
      if (e->type==et_splines) {
	 SplineSetFindBounds(e->u.splines.splines, &ssb);
	 if (b.minx==0 && b.miny==0 && b.maxx==0 && b.maxy==0)
	    b=ssb;
	 else {
	    if (b.minx>ssb.minx)
	       b.minx=ssb.minx;
	    if (b.maxx>ssb.maxx)
	       b.maxx=ssb.maxx;
	    if (b.miny>ssb.miny)
	       b.miny=ssb.miny;
	    if (b.maxy>ssb.maxy)
	       b.maxy=ssb.maxy;
	 }
      }
   if (b.minx==b.maxx)
      b.maxx=b.minx+1;
   if (b.miny==b.maxy)
      b.maxy=b.maxy+1;

   if (fill_colour_source != NULL) {
      xmlParseColorSource(top, fill_colour_source, &b, st, &grad, &epat);
      free(fill_colour_source);
      for (e=head; e != NULL; e=e->next)
	 if (e->type==et_splines) {
	    if (e->u.splines.fill.grad==NULL
		&& e->u.splines.fill.tile==NULL
		&& e->u.splines.fill.col==COLOR_INHERITED) {
	       e->u.splines.fill.grad=GradientCopy(grad, NULL);
	       /*e->u.splines.fill.tile=EPatternCopy(epat,NULL); */
	    }
	 }
      GradientFree(grad);
      /*EPatternFree(epat); */
   }

   if (stroke_colour_source != NULL) {
      xmlParseColorSource(top, stroke_colour_source, &b, st, &grad, &epat);
      free(stroke_colour_source);
      for (e=head; e != NULL; e=e->next)
	 if (e->type==et_splines) {
	    if (e->u.splines.stroke.grad==NULL
		&& e->u.splines.stroke.tile==NULL
		&& e->u.splines.stroke.col==COLOR_INHERITED) {
	       e->u.splines.stroke.grad=GradientCopy(grad, NULL);
	       /*e->u.splines.stroke.tile=EPatternCopy(epat,NULL); */
	    }
	 }
      GradientFree(grad);
      /*EPatternFree(epat); */
   }
}

static int xmlParseColor(xmlChar *name,uint32_t *color,char **url,
			 struct svg_state *st) {
   int doit, i;
   static struct {
      char *name;
      uint32_t col;
   } stdcols[]={
      {
      "red", 0xff0000}, {
      "green", 0x008000}, {
      "blue", 0x0000ff}, {
      "cyan", 0x00ffff}, {
      "magenta", 0xff00ff}, {
      "yellow", 0xffff00}, {
      "black", 0x000000}, {
      "darkgray", 0x404040}, {
      "darkgrey", 0x404040}, {
      "gray", 0x808080}, {
      "grey", 0x808080}, {
      "lightgray", 0xc0c0c0}, {
      "lightgrey", 0xc0c0c0}, {
      "white", 0xffffff}, {
      "maroon", 0x800000}, {
      "olive", 0x808000}, {
      "navy", 0x000080}, {
      "purple", 0x800080}, {
      "lime", 0x00ff00}, {
      "aqua", 0x00ffff}, {
      "teal", 0x008080}, {
      "fuchsia", 0xff0080}, {
      "silver", 0xc0c0c0}, {
      NULL, 0}
   };

   doit=xmlStrcmp(name, (xmlChar *) "none") != 0;
   if (doit) {
      for (i=0; stdcols[i].name != NULL; ++i)
	 if (xmlStrcmp(name, (xmlChar *) stdcols[i].name)==0)
	    break;
      if (stdcols[i].name != NULL)
	 *color=stdcols[i].col;
      else if (xmlStrcmp(name, (xmlChar *) "currentColor")==0)
	 *color=st->currentColor;
      else if (name[0]=='#') {
	 unsigned int temp=0;

	 if (sscanf((char *) name, "#%x", &temp) != 1)
	    ErrorMsg(2,"Bad hex color spec: %s\n", (char *) name);
	 if (strlen((char *) name)==4) {
	    *color=(((temp&0xf00)*0x11) << 8) |
	       (((temp&0x0f0)*0x11) << 4)|(((temp&0x00f)*0x11));
	 } else if (strlen((char *) name)==7) {
	    *color=temp;
	 } else
	    *color=COLOR_INHERITED;
      } else if (strncmp((char *) name, "rgb(", 4)==0) {
	 float r=0, g=0, b=0;

	 if (sscanf((char *) name+4, "%g,%g,%g", &r, &g, &b) != 3)
	    ErrorMsg(2,"Bad RGB color spec: %s\n", (char *) name);
	 if (strchr((char *) name, '.') != NULL) {
	    if (r >= 1)
	       r=1;
	    else if (r<0)
	       r=0;
	    if (g >= 1)
	       g=1;
	    else if (g<0)
	       g=0;
	    if (b >= 1)
	       b=1;
	    else if (b<0)
	       b=0;
	    *color=(((int) rint(r*255)) << 16) |
	       (((int) rint(g*255)) << 8)|(((int) rint(b*255)));
	 } else {
	    if (r >= 255)
	       r=255;
	    else if (r<0)
	       r=0;
	    if (g >= 255)
	       g=255;
	    else if (g<0)
	       g=0;
	    if (b >= 255)
	       b=255;
	    else if (b<0)
	       b=0;
	    *color=(((int) r) << 16)|(((int) g) << 8)|(((int) b));
	 }
      } else if (url != NULL && strncmp((char *) name, "url(#", 5)==0) {
	 *url=fastrdup((char *) name);
	 *color=COLOR_INHERITED;
      } else {
	 ErrorMsg(2,"Failed to parse color %s\n", (char *) name);
	 *color=COLOR_INHERITED;
      }
   }
   return (doit);
}

static int base64ch(int ch) {
   if (ch >= 'A' && ch <= 'Z')
      return (ch-'A');
   if (ch >= 'a' && ch <= 'z')
      return (ch-'a'+26);
   if (ch >= '0' && ch <= '9')
      return (ch-'0'+52);
   if (ch=='+')
      return (62);
   if (ch=='/')
      return (63);
   if (ch=='=')
      return (64);

   return (-1);
}

static void DecodeBase64ToFile(AFILE *tmp,char *str) {
   char fourchars[4];
   int i;

   while (*str) {
      fourchars[0]=fourchars[1]=fourchars[2]=fourchars[3]=64;
      for (i=0; i<4; ++i) {
	 while (isspace(*str) || base64ch(*str)==-1)
	    ++str;
	 if (*str=='\0')
	    break;
	 fourchars[i]=base64ch(*str++);
      }
      if (fourchars[0]<64 && fourchars[1]<64) {
	 aputc((fourchars[0] << 2)|(fourchars[1] >> 4), tmp);
	 if (fourchars[2]<64) {
	    aputc((fourchars[1] << 4)|(fourchars[2] >> 2), tmp);
	    if (fourchars[3]<64)
	       aputc((fourchars[2] << 6)|fourchars[3], tmp);
	 }
      }
   }
}

static GImage *GImageFromDataURI(char *uri) {
   char *mimetype;
   int is_base64=false, ch;
   AFILE *tmp;
   GImage *img;

   if (uri==NULL)
      return (NULL);
   if (strncmp(uri, "data:", 5) != 0)
      return (NULL);
   uri += 5;

   mimetype=uri;
   while (*uri != ',' && *uri != ';' && *uri != '\0')
      ++uri;
   if (*uri=='\0')
      return (NULL);
   ch=*uri;
   *uri='\0';
   if (ch==';' && strncmp(uri+1, "base64,", 7)==0) {
      is_base64=true;
      uri += 6;
      ch=',';
   } else if (ch==';')	/* Can't deal with other encoding methods */
      return (NULL);

   ++uri;
   if (strcmp(mimetype, "image/png")==0 ||
       strcmp(mimetype, "image/jpeg")==0 ||
       strcmp(mimetype, "image/bmp")==0)
      /* These we support (if we've got the libraries) */ ;
   else {
      ErrorMsg(2,"Unsupported mime type in data URI: %s\n", mimetype);
      return (NULL);
   }
   tmp=atmpfile();
   if (is_base64)
      DecodeBase64ToFile(tmp,uri);
   else {
      while (*uri) {
	 aputc(*uri, tmp);
	 ++uri;
      }
   }
   afseek(tmp,0,SEEK_SET);
#   ifndef _NO_LIBPNG
   if (strcmp(mimetype, "image/png")==0)
      img=GImageRead_Png(tmp);
   else
#   endif
#   ifndef _NO_LIBJPEG
   if (strcmp(mimetype, "image/jpeg")==0)
      img=GImageRead_Jpeg(tmp);
   else
#   endif
   if (strcmp(mimetype, "image/bmp")==0)
      img=GImageRead_Bmp(tmp);
   else
      img=NULL;
   afclose(tmp);
   return (img);
}

static Entity *SVGParseImage(xmlNodePtr svg) {
   double x=0,y=0,width=1,height=1;
   GImage *img;
   struct _GImage *base;
   Entity *ent;
   xmlChar *val;

   val=xmlGetProp(svg, (xmlChar *) "x");
   if (val != NULL) {
      x=strtod((char *) val, NULL);
      free(val);
   }
   val=xmlGetProp(svg, (xmlChar *) "y");
   if (val != NULL) {
      y=strtod((char *) val, NULL);
      free(val);
   }

   val=xmlGetProp(svg, (xmlChar *) "width");
   if (val != NULL) {
      width=strtod((char *) val, NULL);
      free(val);
   }
   val=xmlGetProp(svg, (xmlChar *) "height");
   if (val != NULL) {
      height=strtod((char *) val, NULL);
      free(val);
   }

   val=xmlGetProp(svg, (xmlChar *) /*"xlink:href" */ "href");
   if (val==NULL)
      return (NULL);
   if (strncmp((char *) val, "data:", 5) != 0) {
      ErrorMsg(2,"FontAnvil only supports embedded images in data: URIs\n");
      free(val);
      return (NULL);		/* I can only handle data URIs */
   }
   img=GImageFromDataURI((char *)val);
   free(val);
   if (img==NULL)
      return (NULL);
   base=img->list_len==0?img->u.image:img->u.images[0];

   ent=chunkalloc(sizeof(Entity));
   ent->type=et_image;
   ent->u.image.image=img;
   ent->u.image.transform[1]=ent->u.image.transform[2]=0;
   ent->u.image.transform[0]=width/base->width;
   ent->u.image.transform[3]=height/base->height;
   ent->u.image.transform[4]=x;
   ent->u.image.transform[5]=y;
   ent->u.image.col=0xffffffff;
   return (ent);
}

static Entity *EntityCreate(SplinePointList *head,struct svg_state *state) {
   Entity *ent=calloc(1, sizeof(Entity));

   ent->type=et_splines;
   ent->u.splines.splines=head;
   ent->u.splines.cap=state->lc;
   ent->u.splines.join=state->lj;
   ent->u.splines.stroke_width=state->linewidth;
   ent->u.splines.fill.col =
      state->dofill?state->fillcol:state->
      dostroke?0xffffffff:COLOR_INHERITED;
   ent->u.splines.stroke.col =
      state->dostroke?state->strokecol:0xffffffff;
   ent->u.splines.fill.opacity=state->fillopacity;
   ent->u.splines.stroke.opacity=state->strokeopacity;
   memcpy(ent->u.splines.transform, state->transform, 6*sizeof(double));
   ent->clippath=SplinePointListCopy(state->clippath);
   return (ent);
}

static void SvgStateFree(struct svg_state *st) {
   if (st->free_clip)
      SplinePointListFree(st->clippath);
}

static void SVGFigureStyle(struct svg_state *st,char *name,
			   char **fill_colour_source,
			   char **stroke_colour_source) {
   char *pt;
   char namebuf[200], propbuf[400];

   while (1) {
      while (isspace(*name))
	 ++name;
      if (*name==':') {
	 /* Missing prop name, skip the value */
	 while (*name != ';' && *name != '\0')
	    ++name;
	 if (*name==';')
	    ++name;
      } else if (*name != '\0' && *name != ';') {
	 pt=namebuf;
	 while (*name != '\0' && *name != ':' && *name != ';'
		&& !isspace(*name)) {
	    if (pt<namebuf+sizeof(namebuf)-1)
	       *pt++=*name;
	    ++name;
	 }
	 *pt='\0';
	 while (*name != ':' && *name != ';' && *name != '\0')
	    ++name;
	 if (*name==':')
	    ++name;

	 /* fetch prop value */
	 while (isspace(*name))
	    ++name;
	 propbuf[0]='\0';
	 if (*name != '\0' && *name != ';') {
	    pt=propbuf;
	    while (*name != '\0' && *name != ';' && !isspace(*name)) {
	       if (pt<propbuf+sizeof(propbuf)-1)
		  *pt++=*name;
	       ++name;
	    }
	    *pt='\0';
	 }
	 while (*name != ';' && *name != '\0')
	    ++name;
	 if (*name==';')
	    ++name;

	 if (strcmp(namebuf, "color")==0)
	    xmlParseColor(propbuf, &st->currentColor, NULL, st);
	 else if (strcmp(namebuf, "fill")==0)
	    st->dofill =
	       xmlParseColor(propbuf, &st->fillcol, fill_colour_source, st);
	 else if (strcmp(namebuf, "visibility")==0)
	    st->isvisible=strcmp(propbuf, "hidden") != 0 &&
	       strcmp(propbuf, "colapse") != 0;
	 else if (strcmp(namebuf, "fill-opacity")==0)
	    st->fillopacity=strtod(propbuf, NULL);
	 else if (strcmp(namebuf, "stroke")==0)
	    st->dostroke =
	       xmlParseColor(propbuf, &st->strokecol, stroke_colour_source,
			     st);
	 else if (strcmp(namebuf, "stroke-opacity")==0)
	    st->strokeopacity=strtod((char *) propbuf, NULL);
	 else if (strcmp(namebuf, "stroke-width")==0)
	    st->linewidth=strtod((char *) propbuf, NULL);
	 else if (strcmp(namebuf, "stroke-linecap")==0)
	    st->lc=strcmp(propbuf, "butt")?lc_butt :
	       strcmp(propbuf, "round")?lc_round:lc_square;
	 else if (strcmp(namebuf, "stroke-linejoin")==0)
	    st->lj=strcmp(propbuf, "miter")?lj_miter :
	       strcmp(propbuf, "round")?lj_round:lj_bevel;
	 else if (strcmp(namebuf, "stroke-dasharray")==0) {
	    if (strcmp(propbuf, "inherit")) {
	       st->dashes[0]=0;
	       st->dashes[1]=DASH_INHERITED;
	    } else if (strcmp(propbuf, "none")) {
	       st->dashes[0]=0;
	       st->dashes[1]=0;
	    } else {
	       int i;
	       char *pt, *end;

	       pt=propbuf;
	       for (i=0; i<DASH_MAX && *pt != '\0'; ++i) {
		  st->dashes[i]=strtol(pt, &end, 10);
		  pt=end;
	       }
	       if (i<DASH_MAX)
		  st->dashes[i]=0;
	    }
	 } else if (strcmp(namebuf, "stop-color")==0)
	    xmlParseColor(propbuf, &st->stopColor, NULL, st);
	 else if (strcmp(namebuf, "stop-opacity")==0)
	    st->stopOpacity=strtod(propbuf, NULL);
	 else {
	    /* Lots of props we ignore */
	    ;
	 }
      } else if (*name==';')
	 ++name;
      if (*name=='\0')
	 break;
   }
}

static Entity *_SVGParseSVG(xmlNodePtr svg,xmlNodePtr top,
			    struct svg_state *inherit) {
   struct svg_state st;
   xmlChar *name;
   xmlNodePtr kid;
   Entity *ehead, *elast, *eret;
   SplineSet *head;
   int treat_symbol_as_g=false;
   char *fill_colour_source=NULL, *stroke_colour_source=NULL;

   if (svg==NULL)
      return (NULL);

   st=*inherit;
   st.free_clip=false;
 tail_recurse:
   name=xmlGetProp(svg, (xmlChar *) "display");
   if (name != NULL) {
      int hide=xmlStrcmp(name, (xmlChar *) "none")==0;

      xmlFree(name);
      if (hide)
	 return (NULL);
   }
   name=xmlGetProp(svg, (xmlChar *) "visibility");
   if (name != NULL) {
      st.isvisible=xmlStrcmp(name, (xmlChar *) "hidden") != 0 &&
	 xmlStrcmp(name, (xmlChar *) "colapse") != 0;
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "fill");
   if (name != NULL) {
      st.dofill=xmlParseColor(name, &st.fillcol, &fill_colour_source, &st);
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "fill-opacity");
   if (name != NULL) {
      st.fillopacity=strtod((char *) name, NULL);
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "stroke");
   if (name != NULL) {
      st.dostroke =
	 xmlParseColor(name, &st.strokecol, &stroke_colour_source, &st);
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "stroke-opacity");
   if (name != NULL) {
      st.strokeopacity=strtod((char *) name, NULL);
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "stroke-width");
   if (name != NULL) {
      st.linewidth=strtod((char *) name, NULL);
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "stroke-linecap");
   if (name != NULL) {
      st.lc=xmlStrcmp(name, (xmlChar *) "butt")?lc_butt :
	 xmlStrcmp(name, (xmlChar *) "round")?lc_round:lc_square;
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "stroke-linejoin");
   if (name != NULL) {
      st.lj=xmlStrcmp(name, (xmlChar *) "miter")?lj_miter :
	 xmlStrcmp(name, (xmlChar *) "round")?lj_round:lj_bevel;
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "stroke-dasharray");
   if (name != NULL) {
      if (xmlStrcmp(name, (xmlChar *) "inherit")) {
	 st.dashes[0]=0;
	 st.dashes[1]=DASH_INHERITED;
      } else if (xmlStrcmp(name, (xmlChar *) "none")) {
	 st.dashes[0]=0;
	 st.dashes[1]=0;
      } else {
	 int i;
	 xmlChar *pt, *end;

	 pt=name;
	 for (i=0; i<DASH_MAX && *pt != '\0'; ++i) {
	    st.dashes[i]=strtol((char *) pt, (char **) &end, 10);
	    pt=end;
	 }
	 if (i<DASH_MAX)
	    st.dashes[i]=0;
      }
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "style");
   if (name != NULL) {
      SVGFigureStyle(&st, (char *) name, &fill_colour_source,
		     &stroke_colour_source);
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "transform");
   if (name != NULL) {
      SVGFigureTransform(&st, (char *) name);
      xmlFree(name);
   }
   name=xmlGetProp(svg, (xmlChar *) "clip-path");
   if (name != NULL) {
      xmlNodePtr clip=XmlFindURI(top, (char *) name);

      if (clip != NULL && xmlStrcmp(clip->name, (xmlChar *) "clipPath")==0) {
	 const xmlChar *temp=clip->name;
	 struct svg_state null_state;

	 memset(&null_state, 0, sizeof(null_state));
	 null_state.isvisible=true;
	 null_state.transform[0]=null_state.transform[3]=1;
	 clip->name=(xmlChar *) "g";
	 eret=_SVGParseSVG(clip, top, &null_state);
	 clip->name=temp;
	 if (eret != NULL && eret->type==et_splines) {
	    st.clippath=eret->u.splines.splines;
	    eret->u.splines.splines=NULL;
	 }
	 free(eret);
      } else
	 ErrorMsg(2,"Could not find clippath named %s.\n", name);
      xmlFree(name);
   }

   if ((treat_symbol_as_g && xmlStrcmp(svg->name, (xmlChar *) "symbol")==0)
       || xmlStrcmp(svg->name, (xmlChar *) "svg")==0
       || xmlStrcmp(svg->name, (xmlChar *) "glyph")==0
       || xmlStrcmp(svg->name, (xmlChar *) "pattern")==0
       || xmlStrcmp(svg->name, (xmlChar *) "g")==0) {
      ehead=elast=NULL;
      for (kid=svg->children; kid != NULL; kid=kid->next) {
	 eret=_SVGParseSVG(kid, top, &st);
	 if (eret != NULL) {
	    if (elast==NULL)
	       ehead=eret;
	    else
	       elast->next=eret;
	    while (eret->next != NULL)
	       eret=eret->next;
	    elast=eret;
	 }
      }
      SvgStateFree(&st);
      if (fill_colour_source != NULL || stroke_colour_source != NULL)
	 xmlApplyColourSources(top, ehead, &st, fill_colour_source,
			       stroke_colour_source);
      return (ehead);
   } else if (xmlStrcmp(svg->name, (xmlChar *) "use")==0) {
      name=xmlGetProp(svg, (xmlChar *) "href");
      kid=NULL;
      if (name != NULL && *name=='#') {	/* Within this file */
	 kid=XmlFindID(top, (char *) name+1);
	 treat_symbol_as_g=true;
      }
      SVGuseTransform(&st, svg, kid);
      svg=kid;
      if (name != NULL)
	 xmlFree(name);
      if (svg != NULL)
	 goto tail_recurse;
      SvgStateFree(&st);
      return (NULL);
   }

   if (!st.isvisible) {
      SvgStateFree(&st);
      return (NULL);
   }

   /* basic shapes */
   head=NULL;
   if (xmlStrcmp(svg->name, (xmlChar *) "path")==0) {
      head=SVGParseExtendedPath(svg, top);
   } else if (xmlStrcmp(svg->name, (xmlChar *) "rect")==0) {
      head=SVGParseRect(svg);	/* x,y,width,height,rx,ry */
   } else if (xmlStrcmp(svg->name, (xmlChar *) "circle")==0) {
      head=SVGParseEllipse(svg, true);	/* cx,cy, r */
   } else if (xmlStrcmp(svg->name, (xmlChar *) "ellipse")==0) {
      head=SVGParseEllipse(svg, false);	/* cx,cy, rx,ry */
   } else if (xmlStrcmp(svg->name, (xmlChar *) "line")==0) {
      head=SVGParseLine(svg);	/* x1,y1, x2,y2 */
   } else if (xmlStrcmp(svg->name, (xmlChar *) "polyline")==0) {
      head=SVGParsePoly(svg, 0);	/* points */
   } else if (xmlStrcmp(svg->name, (xmlChar *) "polygon")==0) {
      head=SVGParsePoly(svg, 1);	/* points */
   } else if (xmlStrcmp(svg->name, (xmlChar *) "image")==0) {
      eret=SVGParseImage(svg);
      if (eret != NULL)
	 eret->clippath=st.clippath;
      return (eret);
   } else
      return (NULL);
   if (head==NULL)
      return (NULL);

   SPLCategorizePoints(head);

   eret =
      EntityCreate(SplinePointListTransform
		   (head, st.transform, tpt_AllPoints), &st);
   if (fill_colour_source != NULL || stroke_colour_source != NULL)
      xmlApplyColourSources(top, eret, &st, fill_colour_source,
			    stroke_colour_source);
   SvgStateFree(&st);
   return (eret);
}

static Entity *SVGParseSVG(xmlNodePtr svg,int em_size,int ascent) {
   struct svg_state st;
   char *num, *end;
   double x, y, swidth, sheight, width=1, height=1;

   memset(&st, 0, sizeof(st));
   st.lc=lc_inherited;
   st.lj=lj_inherited;
   st.linewidth=WIDTH_INHERITED;
   st.fillcol=COLOR_INHERITED;
   st.strokecol=COLOR_INHERITED;
   st.currentColor=COLOR_INHERITED;
   st.stopColor=COLOR_INHERITED;
   st.isvisible=true;
   st.transform[0]=1;
   st.transform[3]=-1;	/* The SVG coord system has y increasing down */
   /*  Font coords have y increasing up */
   st.transform[5]=ascent;
   st.strokeopacity=st.fillopacity=1.0;

   num=(char *) xmlGetProp(svg, (xmlChar *) "width");
   if (num != NULL) {
      width=strtod(num, NULL);
      xmlFree(num);
   }
   num=(char *) xmlGetProp(svg, (xmlChar *) "height");
   if (num != NULL) {
      height=strtod(num, NULL);
      xmlFree(num);
   }
   if (height <= 0)
      height=1;
   if (width <= 0)
      width=1;
   num=(char *) xmlGetProp(svg, (xmlChar *) "viewBox");
   if (num != NULL) {
      x=strtod((char *) num, &end);
      y=strtod((char *) end+1, &end);
      swidth=strtod((char *) end+1, &end);
      sheight=strtod((char *) end+1, &end);
      xmlFree(num);
      if (width>height) {
	 if (swidth != 0) {
	    st.transform[0] *= em_size/swidth;
	    st.transform[3] *= em_size/swidth;
	 }
      } else {
	 if (sheight != 0) {
	    st.transform[0] *= em_size/sheight;
	    st.transform[3] *= em_size/sheight;
	 }
      }
   }
   return (_SVGParseSVG(svg, svg, &st));
}

static void SVGParseGlyphBody(SplineChar *sc,xmlNodePtr glyph,int *flags) {
   xmlChar *path;

   path=xmlGetProp(glyph, (xmlChar *) "d");
   if (path != NULL) {
      sc->layers[ly_fore].splines=SVGParseExtendedPath(glyph, glyph);
      xmlFree(path);
   } else {
      Entity *ent =
	 SVGParseSVG(glyph, sc->parent->ascent+sc->parent->descent,
		     sc->parent->ascent);

      sc->layer_cnt=1;
      SCAppendEntityLayers(sc, ent);
      if (sc->layer_cnt==1)
	 ++sc->layer_cnt;
      else
	 sc->parent->multilayer=true;
   }

   SCCategorizePoints(sc);
}

static SplineChar *SVGParseGlyphArgs(xmlNodePtr glyph,int defh,int defv,
				     SplineFont *sf) {
   SplineChar *sc=SFSplineCharCreate(sf);
   xmlChar *name, *form, *glyphname, *unicode, *orientation;
   uint32_t *u;
   char buffer[100];

   name=xmlGetProp(glyph, (xmlChar *) "horiz-adv-x");
   if (name != NULL) {
      sc->width=strtod((char *) name, NULL);
      xmlFree(name);
   } else
      sc->width=defh;
   name=xmlGetProp(glyph, (xmlChar *) "vert-adv-y");
   if (name != NULL) {
      sc->vwidth=strtod((char *) name, NULL);
      xmlFree(name);
   } else
      sc->vwidth=defv;
   name=xmlGetProp(glyph, (xmlChar *) "vert-adv-y");
   if (name != NULL) {
      sc->vwidth=strtod((char *) name, NULL);
      xmlFree(name);
   } else
      sc->vwidth=defv;

   form=xmlGetProp(glyph, (xmlChar *) "arabic-form");
   unicode=xmlGetProp(glyph, (xmlChar *) "unicode");
   glyphname=xmlGetProp(glyph, (xmlChar *) "glyph-name");
   orientation=xmlGetProp(glyph, (xmlChar *) "orientation");
   if (unicode != NULL) {

      u=utf82u_copy((char *) unicode);
      xmlFree(unicode);
      if (u[1]=='\0') {
	 sc->unicodeenc=u[0];
	 if (form != NULL && u[0] >= 0x600 && u[0] <= 0x6ff) {
	    if (xmlStrcmp(form, (xmlChar *) "initial")==0)
	       sc->unicodeenc=arabic_forms_lookup(u[0]).initial;
	    else if (xmlStrcmp(form, (xmlChar *) "medial")==0)
	       sc->unicodeenc=arabic_forms_lookup(u[0]).medial;
	    else if (xmlStrcmp(form, (xmlChar *) "final")==0)
	       sc->unicodeenc=arabic_forms_lookup(u[0]).final;
	    else if (xmlStrcmp(form, (xmlChar *) "isolated")==0)
	       sc->unicodeenc=arabic_forms_lookup(u[0]).isolated;
	 }
      }
      free(u);
   }
   if (glyphname != NULL) {
      if (sc->unicodeenc==-1)
	 sc->unicodeenc=UniFromName((char *) glyphname, ui_none, &custom);
      sc->name=fastrdup((char *) glyphname);
      xmlFree(glyphname);
   } else if (orientation != NULL && *orientation=='v'
	      && sc->unicodeenc != -1) {
      if (sc->unicodeenc<0x10000)
	 sprintf(buffer, "uni%04X.vert", sc->unicodeenc);
      else
	 sprintf(buffer, "u%04X.vert", sc->unicodeenc);
      sc->name=fastrdup(buffer);
   }
   /* we finish off defaulting the glyph name in the parseglyph routine */
   if (form != NULL)
      xmlFree(form);
   if (orientation != NULL)
      xmlFree(orientation);
   return (sc);
}

static SplineChar *SVGParseMissing(SplineFont *sf,xmlNodePtr notdef,
				   int defh, int defv, int enc, int *flags) {
   SplineChar *sc=SVGParseGlyphArgs(notdef, defh, defv, sf);

   sc->parent=sf;
   sc->name=fastrdup(".notdef");
   sc->unicodeenc=0;
   SVGParseGlyphBody(sc, notdef, flags);
   return (sc);
}

static SplineChar *SVGParseGlyph(SplineFont *sf,xmlNodePtr glyph,int defh,
				 int defv, int enc, int *flags) {
   char buffer[400];
   SplineChar *sc=SVGParseGlyphArgs(glyph, defh, defv, sf);

   sc->parent=sf;
   if (sc->name==NULL) {
      if (sc->unicodeenc==-1) {
	 sprintf(buffer, "glyph%d", enc);
	 sc->name=fastrdup(buffer);
      } else
	 sc->name=fastrdup(StdGlyphName(buffer, sc->unicodeenc, ui_none, NULL));
   }
   SVGParseGlyphBody(sc, glyph, flags);
   return (sc);
}

static void SVGLigatureFixupCheck(SplineChar *sc,xmlNodePtr glyph) {
   xmlChar *unicode;
   uint32_t *u;
   int len, len2;
   SplineChar **chars, *any=NULL;
   char *comp, *pt;

   unicode=xmlGetProp(glyph, (xmlChar *) "unicode");
   if (unicode != NULL) {
      u=utf82u_copy((char *) unicode);
      xmlFree(unicode);
      if (u[1] != '\0' && u[2]=='\0' && ((u[1] >= 0x180B && u[1] <= 0x180D) ||	/* Mongolian VS */
					   (u[1] >= 0xfe00 && u[1] <= 0xfe0f) ||	/* First VS block */
					   (u[1] >= 0xE0100 && u[1] <= 0xE01EF))) {	/* Second VS block */
	 /* Problably a variant glyph marked with a variation selector */
	 /* ... not a true ligature at all */
	 /* http://babelstone.blogspot.com/2007/06/secret-life-of-variation-selectors.html */
	 struct altuni *altuni=chunkalloc(sizeof(struct altuni));

	 altuni->unienc=u[0];
	 altuni->vs=u[1];
	 altuni->fid=0;
	 altuni->next=sc->altuni;
	 sc->altuni=altuni;
      } else if (u[1] != '\0') {
	 /* Normal ligature */
	 for (len=0; u[len] != 0; ++len);
	 chars=malloc(len * sizeof(SplineChar *));
	 for (len=len2=0; u[len] != 0; ++len) {
	    chars[len]=SFGetChar(sc->parent, u[len], NULL);
	    if (chars[len]==NULL)
	       len2 += 9;
	    else {
	       len2 += strlen(chars[len]->name)+1;
	       if (any==NULL)
		  any=chars[len];
	    }
	 }
	 if (any==NULL)
	    any=sc;
	 comp=pt=malloc(len2+1);
	 *pt='\0';
	 for (len=0; u[len] != 0; ++len) {
	    if (chars[len] != NULL)
	       strcpy(pt, chars[len]->name);
	    else if (u[len]<0x10000)
	       sprintf(pt, "uni%04X", (unsigned int) u[len]);
	    else
	       sprintf(pt, "u%04X", (unsigned int) u[len]);
	    pt += strlen(pt);
	    if (u[len+1] != '\0')
	       *pt++=' ';
	 }
	 SubsNew(sc, pst_ligature, CHR('l', 'i', 'g', 'a'), comp, any);
	 /* Understand the unicode latin ligatures. There are too many */
	 /*  arabic ones */
	 if (u[0]=='f') {
	    if (u[1]=='f' && u[2]==0)
	       sc->unicodeenc=0xfb00;
	    else if (u[1]=='i' && u[2]==0)
	       sc->unicodeenc=0xfb01;
	    else if (u[1]=='l' && u[2]==0)
	       sc->unicodeenc=0xfb02;
	    else if (u[1]=='f' && u[2]=='i' && u[3]==0)
	       sc->unicodeenc=0xfb03;
	    else if (u[1]=='f' && u[2]=='l' && u[3]==0)
	       sc->unicodeenc=0xfb04;
	    else if (u[1]=='t' && u[2]==0)
	       sc->unicodeenc=0xfb05;
	 } else if (u[0]=='s' && u[1]=='t' && u[2]==0)
	    sc->unicodeenc=0xfb06;
	 if (strncmp(sc->name, "glyph", 5)==0 && isdigit(sc->name[5])) {
	    /* It's a default name, we can do better */
	    free(sc->name);
	    sc->name=fastrdup(comp);
	    for (pt=sc->name; *pt; ++pt)
	       if (*pt==' ')
		  *pt='_';
	 }
      }
      free(u);
   }
}

static char *SVGGetNames(SplineFont *sf,xmlChar *g,xmlChar *utf8,
			 SplineChar ** sc) {
   uint32_t *u=NULL;
   char *names;
   int len, i, ch;
   SplineChar *temp;
   char *pt, *gpt;

   *sc=NULL;
   len=0;
   if (utf8 != NULL) {
      u=utf82u_copy((char *) utf8);
      for (i=0; u[i] != 0; ++i) {
	 temp=SFGetChar(sf, u[i], NULL);
	 if (temp != NULL) {
	    if (*sc==NULL)
	       *sc=temp;
	    len=strlen(temp->name)+1;
	 }
      }
   }
   names=pt=malloc(len+(g != NULL?strlen((char *) g):0)+1);
   if (utf8 != NULL) {
      for (i=0; u[i] != 0; ++i) {
	 temp=SFGetChar(sf, u[i], NULL);
	 if (temp != NULL) {
	    strcpy(pt, temp->name);
	    pt += strlen(pt);
	    *pt++=' ';
	 }
      }
      free(u);
   }
   if (g != NULL) {
      for (gpt=(char *) g; *gpt;) {
	 if (*gpt==',' || isspace(*gpt)) {
	    while (*gpt==',' || isspace(*gpt))
	       ++gpt;
	    *pt++=' ';
	 } else {
	    *pt++=*gpt++;
	 }
      }
      if (*sc==NULL) {
	 for (gpt=(char *) g; *gpt != '\0' && *gpt != ',' && !isspace(*gpt);
	      ++gpt);
	 ch=*gpt;
	 *gpt='\0';
	 *sc=SFGetChar(sf, -1, (char *) g);
	 *gpt=ch;
      }
   }
   if (pt>names && pt[-1]==' ')
      --pt;
   *pt='\0';
   return (names);
}

static void SVGParseKern(SplineFont *sf,xmlNodePtr kern,int isv) {
   xmlChar *k, *g1, *u1, *g2, *u2;
   double off;
   char *c1, *c2;
   char *pt1, *pt2, *end1, *end2;
   SplineChar *sc1, *sc2;
   uint32_t script;
   struct lookup_subtable *subtable;

   k=xmlGetProp(kern, (xmlChar *) "k");
   if (k==NULL)
      return;
   off=-strtod((char *) k, NULL);
   xmlFree(k);
   if (off==0)
      return;

   g1=xmlGetProp(kern, (xmlChar *) "g1");
   u1=xmlGetProp(kern, (xmlChar *) "u1");
   if (g1==NULL && u1==NULL)
      return;
   c1=SVGGetNames(sf, g1, u1, &sc1);
   if (g1 != NULL)
      xmlFree(g1);
   if (u1 != NULL)
      xmlFree(u1);

   g2=xmlGetProp(kern, (xmlChar *) "g2");
   u2=xmlGetProp(kern, (xmlChar *) "u2");
   if (g2==NULL && u2==NULL) {
      free(c1);
      return;
   }
   c2=SVGGetNames(sf, g2, u2, &sc2);
   if (g2 != NULL)
      xmlFree(g2);
   if (u2 != NULL)
      xmlFree(u2);

   script=DEFAULT_SCRIPT;
   if (sc1 != NULL)
      script=SCScriptFromUnicode(sc1);
   if (script==DEFAULT_SCRIPT && sc2 != NULL)
      script=SCScriptFromUnicode(sc2);
/* It is tempting to use a kern class... but it doesn't work. No guarantees */
/*  that either of our two "classes" will ever show again. We could do the */
/*  complex shinanigans we do in parsing feature files, but it's a lot easier */
/*  just to make kernpairs. */
   subtable=SFSubTableFindOrMake(sf,
				   isv?CHR('v', 'k', 'r', 'n'):CHR('k',
								       'e',
								       'r',
								       'n'),
				   script, gpos_pair);
   subtable->lookup->lookup_flags=((sc1 != NULL && SCRightToLeft(sc1))
				     || (sc1==NULL && sc2 != NULL
					 && SCRightToLeft(sc2)))?pst_r2l :
      0;
   for (pt1=c1;;) {
      while (*pt1==' ')
	 ++pt1;
      if (*pt1=='\0')
	 break;
      end1=strchr(pt1, ' ');
      if (end1 != NULL)
	 *end1='\0';
      sc1=SFGetChar(sf, -1, pt1);
      if (sc1 != NULL)
	 for (pt2=c2;;) {
	    while (*pt2==' ')
	       ++pt2;
	    if (*pt2=='\0')
	       break;
	    end2=strchr(pt2, ' ');
	    if (end2 != NULL)
	       *end2='\0';
	    sc2=SFGetChar(sf, -1, pt2);
	    if (sc2 != NULL) {
	       KernPair *kp=chunkalloc(sizeof(KernPair));

	       kp->sc=sc2;
	       kp->off=off;
	       if (isv) {
		  kp->next=sc1->vkerns;
		  sc1->vkerns=kp;
	       } else {
		  kp->next=sc1->kerns;
		  sc1->kerns=kp;
	       }
	       kp->subtable=subtable;
	    }
	    if (end2==NULL)
	       break;
	    *end2=' ';
	    pt2=end2+1;
	 }
      if (end1==NULL)
	 break;
      *end1=' ';
      pt1=end1+1;
   }
   free(c1);
   free(c2);
}

static SplineFont *SVGParseFont(xmlNodePtr font) {
   int cnt, flags=-1;
   xmlNodePtr kids;
   int defh=0, defv=0;
   xmlChar *name;
   SplineFont *sf;
   EncMap *map;
   int i;

   sf=SplineFontEmpty();
   name=xmlGetProp(font, (xmlChar *) "horiz-adv-x");
   if (name != NULL) {
      defh=strtod((char *) name, NULL);
      xmlFree(name);
   }
   name=xmlGetProp(font, (xmlChar *) "vert-adv-y");
   if (name != NULL) {
      defv=strtod((char *) name, NULL);
      xmlFree(name);
      sf->hasvmetrics=true;
   }
   name=xmlGetProp(font, (xmlChar *) "id");
   if (name != NULL) {
      sf->fontname=fastrdup((char *) name);
      xmlFree(name);
   }

   cnt=0;
   for (kids=font->children; kids != NULL; kids=kids->next) {
      int ascent=0, descent=0;

      if (xmlStrcmp(kids->name, (const xmlChar *) "font-face")==0) {
	 name=xmlGetProp(kids, (xmlChar *) "units-per-em");
	 if (name != NULL) {
	    int val=rint(strtod((char *) name, NULL));

	    xmlFree(name);
	    if (val<0)
	       val=0;
	    sf->ascent=val*800/1000;
	    sf->descent=val-sf->ascent;
	    if (defv==0)
	       defv=val;
	    if (defh==0)
	       defh=val;
	    SFDefaultOS2Simple(&sf->pfminfo, sf);
	 } else {
	    ErrorMsg(2,"This font does not specify units-per-em\n");
	    SplineFontFree(sf);
	    return (NULL);
	 }
	 name=xmlGetProp(kids, (xmlChar *) "font-family");
	 if (name != NULL) {
	    if (strchr((char *) name, ',') != NULL)
	       *strchr((char *) name, ',')='\0';
	    sf->familyname=fastrdup((char *) name);
	    xmlFree(name);
	 }
	 name=xmlGetProp(kids, (xmlChar *) "font-weight");
	 if (name != NULL) {
	    if (strnmatch((char *) name, "normal", 6)==0) {
	       sf->pfminfo.weight=400;
	       sf->weight=fastrdup("Regular");
	       sf->pfminfo.panose[2]=5;
	    } else if (strnmatch((char *) name, "bold", 4)==0) {
	       sf->pfminfo.weight=700;
	       sf->weight=fastrdup("Bold");
	       sf->pfminfo.panose[2]=8;
	    } else {
	       sf->pfminfo.weight=strtod((char *) name, NULL);
	       if (sf->pfminfo.weight <= 100) {
		  sf->weight=fastrdup("Thin");
		  sf->pfminfo.panose[2]=2;
	       } else if (sf->pfminfo.weight <= 200) {
		  sf->weight=fastrdup("Extra-Light");
		  sf->pfminfo.panose[2]=3;
	       } else if (sf->pfminfo.weight <= 300) {
		  sf->weight=fastrdup("Light");
		  sf->pfminfo.panose[2]=4;
	       } else if (sf->pfminfo.weight <= 400) {
		  sf->weight=fastrdup("Regular");
		  sf->pfminfo.panose[2]=5;
	       } else if (sf->pfminfo.weight <= 500) {
		  sf->weight=fastrdup("Medium");
		  sf->pfminfo.panose[2]=6;
	       } else if (sf->pfminfo.weight <= 600) {
		  sf->weight=fastrdup("DemiBold");
		  sf->pfminfo.panose[2]=7;
	       } else if (sf->pfminfo.weight <= 700) {
		  sf->weight=fastrdup("Bold");
		  sf->pfminfo.panose[2]=8;
	       } else if (sf->pfminfo.weight <= 800) {
		  sf->weight=fastrdup("Heavy");
		  sf->pfminfo.panose[2]=9;
	       } else {
		  sf->weight=fastrdup("Black");
		  sf->pfminfo.panose[2]=10;
	       }
	    }
	    sf->pfminfo.panose_set=true;
	    sf->pfminfo.pfmset=true;
	    xmlFree(name);
	 }
	 name=xmlGetProp(kids, (xmlChar *) "font-stretch");
	 if (name != NULL) {
	    if (strnmatch((char *) name, "normal", 6)==0) {
	       sf->pfminfo.panose[3]=3;
	       sf->pfminfo.width=5;
	    } else if (strmatch((char *) name, "ultra-condensed")==0) {
	       sf->pfminfo.panose[3]=8;
	       sf->pfminfo.width=1;
	    } else if (strmatch((char *) name, "extra-condensed")==0) {
	       sf->pfminfo.panose[3]=8;
	       sf->pfminfo.width=2;
	    } else if (strmatch((char *) name, "condensed")==0) {
	       sf->pfminfo.panose[3]=6;
	       sf->pfminfo.width=3;
	    } else if (strmatch((char *) name, "semi-condensed")==0) {
	       sf->pfminfo.panose[3]=6;
	       sf->pfminfo.width=4;
	    } else if (strmatch((char *) name, "ultra-expanded")==0) {
	       sf->pfminfo.panose[3]=7;
	       sf->pfminfo.width=9;
	    } else if (strmatch((char *) name, "extra-expanded")==0) {
	       sf->pfminfo.panose[3]=7;
	       sf->pfminfo.width=8;
	    } else if (strmatch((char *) name, "expanded")==0) {
	       sf->pfminfo.panose[3]=5;
	       sf->pfminfo.width=7;
	    } else if (strmatch((char *) name, "semi-expanded")==0) {
	       sf->pfminfo.panose[3]=5;
	       sf->pfminfo.width=6;
	    }
	    sf->pfminfo.panose_set=true;
	    sf->pfminfo.pfmset=true;
	    xmlFree(name);
	 }
	 name=xmlGetProp(kids, (xmlChar *) "panose-1");
	 if (name != NULL) {
	    char *pt, *end;
	    int i;

	    for (i=0, pt=(char *) name; i<10 && *pt; pt=end, ++i) {
	       sf->pfminfo.panose[i]=strtol(pt, &end, 10);
	    }
	    sf->pfminfo.panose_set=true;
	    xmlFree(name);
	 }
	 name=xmlGetProp(kids, (xmlChar *) "slope");
	 if (name != NULL) {
	    sf->italicangle=strtod((char *) name, NULL);
	    xmlFree(name);
	 }
	 name=xmlGetProp(kids, (xmlChar *) "underline-position");
	 if (name != NULL) {
	    sf->upos=strtod((char *) name, NULL);
	    xmlFree(name);
	 }
	 name=xmlGetProp(kids, (xmlChar *) "underline-thickness");
	 if (name != NULL) {
	    sf->uwidth=strtod((char *) name, NULL);
	    xmlFree(name);
	 }
	 name=xmlGetProp(kids, (xmlChar *) "ascent");
	 if (name != NULL) {
	    ascent=strtod((char *) name, NULL);
	    xmlFree(name);
	 }
	 name=xmlGetProp(kids, (xmlChar *) "descent");
	 if (name != NULL) {
	    descent=strtod((char *) name, NULL);
	    xmlFree(name);
	 }
	 if (ascent-descent==sf->ascent+sf->descent) {
	    sf->ascent=ascent;
	    sf->descent=-descent;
	 }
	 sf->pfminfo.pfmset=true;
      } else if (xmlStrcmp(kids->name, (const xmlChar *) "glyph")==0 ||
		 xmlStrcmp(kids->name,
			   (const xmlChar *) "missing-glyph")==0)
	 ++cnt;
   }
   if (sf->descent==0) {
      ErrorMsg(2,"This font does not specify font-face\n");
      SplineFontFree(sf);
      return (NULL);
   }
   if (sf->weight==NULL)
      sf->weight=fastrdup("Regular");
   if (sf->fontname==NULL && sf->familyname==NULL)
      sf->fontname=GetNextUntitledName();
   if (sf->familyname==NULL)
      sf->familyname=fastrdup(sf->fontname);
   if (sf->fontname==NULL)
      sf->fontname=EnforcePostScriptName(sf->familyname);
   sf->fullname=fastrdup(sf->fontname);

   /* Give ourselves an xuid, just in case they want to convert to PostScript */
   if (xuid != NULL) {
      sf->xuid=malloc(strlen(xuid)+20);
      sprintf(sf->xuid, "[%s %d]", xuid, (rand()&0xffffff));
   }

   sf->glyphcnt=sf->glyphmax=cnt;
   sf->glyphs=malloc(cnt * sizeof(SplineChar *));

   cnt=0;
   for (kids=font->children; kids != NULL; kids=kids->next) {
      if (xmlStrcmp(kids->name, (const xmlChar *) "missing-glyph")==0) {
	 sf->glyphs[cnt]=SVGParseMissing(sf, kids, defh, defv, cnt, &flags);
	 if (sf->glyphs[cnt] != NULL) {
	    sf->glyphs[cnt]->orig_pos=cnt;
	    cnt++;
	 }
      } else if (xmlStrcmp(kids->name, (const xmlChar *) "glyph")==0) {
	 sf->glyphs[cnt]=SVGParseGlyph(sf, kids, defh, defv, cnt, &flags);
	 if (sf->glyphs[cnt] != NULL) {
	    sf->glyphs[cnt]->orig_pos=cnt;
	    cnt++;
	 }
      }
   }
   cnt=0;
   for (kids=font->children; kids != NULL; kids=kids->next) {
      if (xmlStrcmp(kids->name, (const xmlChar *) "hkern")==0) {
	 SVGParseKern(sf, kids, false);
      } else if (xmlStrcmp(kids->name, (const xmlChar *) "vkern")==0) {
	 SVGParseKern(sf, kids, true);
      } else if (xmlStrcmp(kids->name, (const xmlChar *) "glyph")==0) {
	 SVGLigatureFixupCheck(sf->glyphs[cnt++], kids);
      } else if (xmlStrcmp(kids->name, (const xmlChar *) "missing-glyph") ==
		 0) {
	 ++cnt;
      }
   }

   map=chunkalloc(sizeof(EncMap));
   map->enccount=map->encmax=map->backmax=sf->glyphcnt;
   map->enc=FindOrMakeEncoding("Original");
   map->map=malloc(sf->glyphcnt * sizeof(int));
   map->backmap=malloc(sf->glyphcnt * sizeof(int));
   for (i=0; i<sf->glyphcnt; ++i)
      map->map[i]=map->backmap[i]=i;
   sf->map=map;

   return (sf);
}

static int SPLFindOrder(SplineSet *ss) {
   Spline *s, *first;

   while (ss != NULL) {
      first=NULL;
      for (s=ss->first->next; s != NULL && s != first; s=s->to->next) {
	 if (first==NULL)
	    first=s;
	 if (!s->knownlinear)
	    return (s->order2);
      }
      ss=ss->next;
   }
   return (-1);
}

static int EntFindOrder(Entity *ent) {
   int ret;

   while (ent != NULL) {
      if (ent->type==et_splines) {
	 ret=SPLFindOrder(ent->u.splines.splines);
	 if (ret != -1)
	    return (ret);
      }
      ent=ent->next;
   }
   return (-1);
}

int SFFindOrder(SplineFont *sf) {
   int i, ret;

   for (i=0; i<sf->glyphcnt; ++i)
      if (sf->glyphs[i] != NULL) {
	 ret=SPLFindOrder(sf->glyphs[i]->layers[ly_fore].splines);
	 if (ret != -1)
	    return (ret);
      }
   return (0);
}

int SFLFindOrder(SplineFont *sf, int layerdest) {
   int i, ret;

   for (i=0; i<sf->glyphcnt; ++i)
      if (sf->glyphs[i] != NULL) {
	 ret=SPLFindOrder(sf->glyphs[i]->layers[layerdest].splines);
	 if (ret != -1)
	    return (ret);
      }
   return (0);
}

static void SPLSetOrder(SplineSet *ss,int order2) {
   Spline *s, *first;
   SplinePoint *from, *to;

   while (ss != NULL) {
      first=NULL;
      for (s=ss->first->next; s != NULL && s != first; s=s->to->next) {
	 if (first==NULL)
	    first=s;
	 if (s->order2 != order2) {
	    if (s->knownlinear) {
	       s->from->nextcp=s->from->me;
	       s->to->prevcp=s->to->me;
	       s->from->nonextcp=s->to->noprevcp=true;
	       s->order2=order2;
	    } else if (order2) {
	       from=SplineTtfApprox(s);
	       s->from->nextcp=from->nextcp;
	       s->from->nonextcp=from->nonextcp;
	       s->from->next=from->next;
	       from->next->from=s->from;
	       SplinePointFree(from);
	       for (to=s->from->next->to; to->next != NULL;
		    to=to->next->to);
	       s->to->prevcp=to->prevcp;
	       s->to->noprevcp=to->noprevcp;
	       s->to->prev=to->prev;
	       to->prev->to=s->to;
	       SplinePointFree(to);
	       to=s->to;
	       from=s->from;
	       SplineFree(s);
	       if (first==s)
		  first=from->next;
	       s=to->prev;
	    } else {
	       s->from->nextcp.x=s->splines[0].c/3+s->from->me.x;
	       s->from->nextcp.y=s->splines[1].c/3+s->from->me.y;
	       s->to->prevcp.x =
		  s->from->nextcp.x+(s->splines[0].b+s->splines[0].c)/3;
	       s->to->prevcp.y =
		  s->from->nextcp.y+(s->splines[1].b+s->splines[1].c)/3;
	       s->order2=false;
	       SplineRefigure(s);
	    }
	 }
      }
      ss=ss->next;
   }
}

static void EntSetOrder(Entity *ent,int order2) {
   while (ent != NULL) {
      if (ent->type==et_splines)
	 SPLSetOrder(ent->u.splines.splines, order2);
      SPLSetOrder(ent->clippath, order2);
      ent=ent->next;
   }
}

void SFSetOrder(SplineFont *sf, int order2) {
   int i, j;

   for (i=0; i<sf->glyphcnt; ++i)
      if (sf->glyphs[i] != NULL) {
	 for (j=ly_fore; j<sf->glyphs[i]->layer_cnt; ++j) {
	    SPLSetOrder(sf->glyphs[i]->layers[j].splines, order2);
	    sf->glyphs[i]->layers[j].order2=order2;
	 }
      }
}

void SFLSetOrder(SplineFont *sf, int layerdest, int order2) {
   int i;

   for (i=0; i<sf->glyphcnt; ++i)
      if (sf->glyphs[i] != NULL) {
	 SPLSetOrder(sf->glyphs[i]->layers[layerdest].splines, order2);
	 sf->glyphs[i]->layers[layerdest].order2=order2;
      }
}

static SplineFont *_SFReadSVG(xmlDocPtr doc,char *filename) {
   xmlNodePtr *fonts, font;
   SplineFont *sf;
   char *chosenname=NULL;

   fonts=FindSVGFontNodes(doc);
   if (fonts==NULL || fonts[0]==NULL) {
      ErrorMsg(2,"This file contains no SVG fonts.\n");
      xmlFreeDoc(doc);
      return (NULL);
   }
   font=fonts[0];
   if (fonts[1] != NULL) {
      xmlChar *name;

      font=SVGPickFont(fonts, filename);
      name=xmlGetProp(font, (xmlChar *) "id");
      if (name != NULL) {
	 chosenname=cu_copy(utf82u_copy((char *) name));
	 xmlFree(name);
      }
   }
   free(fonts);
   sf=SVGParseFont(font);
   xmlFreeDoc(doc);

   if (sf != NULL) {
      struct stat b;

      sf->layers[ly_fore].order2=sf->layers[ly_back].order2 =
	 sf->grid.order2=SFFindOrder(sf);
      SFSetOrder(sf, sf->layers[ly_fore].order2);
      sf->chosenname=chosenname;
      if (stat(filename, &b) != -1) {
	 sf->modificationtime=b.st_mtime;
	 sf->creationtime=b.st_mtime;
      }
   }
   return (sf);
}

SplineFont *SFReadSVG(char *filename, int flags) {
   xmlDocPtr doc;
   char *temp=filename, *pt, *lparen;

   if (!libxml_init_base()) {
      ErrorMsg(2,"Can't find libxml2.\n");
      return (NULL);
   }

   pt=strrchr(filename, '/');
   if (pt==NULL)
      pt=filename;
   if ((lparen=strchr(pt, '(')) != NULL && strchr(lparen, ')') != NULL) {
      temp=fastrdup(filename);
      pt=temp+(lparen-filename);
      *pt='\0';
   }

   doc=xmlParseFile(temp);
   if (temp != filename)
      free(temp);
   if (doc==NULL) {
      /* Can I get an error message from libxml? */
      return (NULL);
   }
   return (_SFReadSVG(doc, filename));
}

char **NamesReadSVG(char *filename) {
   xmlNodePtr *fonts;
   xmlDocPtr doc;
   char **ret=NULL;
   int cnt;
   xmlChar *name;

   if (!libxml_init_base()) {
      ErrorMsg(2,"Can't find libxml2.\n");
      return (NULL);
   }

   doc=xmlParseFile(filename);
   if (doc==NULL) {
      /* Can I get an error message from libxml? */
      return (NULL);
   }

   fonts=FindSVGFontNodes(doc);
   if (fonts==NULL || fonts[0]==NULL) {
      xmlFreeDoc(doc);
      return (NULL);
   }

   for (cnt=0; fonts[cnt] != NULL; ++cnt);
   ret=malloc((cnt+1)*sizeof(char *));
   for (cnt=0; fonts[cnt] != NULL; ++cnt) {
      name=xmlGetProp(fonts[cnt], (xmlChar *) "id");
      if (name==NULL) {
	 ret[cnt]=fastrdup("nameless-font");
      } else {
	 ret[cnt]=fastrdup((char *) name);
	 xmlFree(name);
      }
   }
   ret[cnt]=NULL;

   free(fonts);
   xmlFreeDoc(doc);

   return (ret);
}

Entity *EntityInterpretSVG(char *filename, char *memory, int memlen,
			   int em_size, int ascent) {
   xmlDocPtr doc;
   xmlNodePtr top;
   Entity *ret;
   int order2;

   if (!libxml_init_base()) {
      ErrorMsg(2,"Can't find libxml2.\n");
      return (NULL);
   }
   if (filename != NULL)
      doc=xmlParseFile(filename);
   else
      doc=xmlParseMemory(memory, memlen);
   if (doc==NULL) {
      /* Can I get an error message from libxml???? */
      return (NULL);
   }

   top=xmlDocGetRootElement(doc);
   if (xmlStrcmp(top->name, (xmlChar *) "svg") != 0) {
      ErrorMsg(2,"%s does not contain an <svg> element at the top\n",
	       filename);
      xmlFreeDoc(doc);
      return (NULL);
   }

   ret=SVGParseSVG(top, em_size, ascent);
   xmlFreeDoc(doc);

   if (loaded_fonts_same_as_new)
      order2=new_fonts_are_order2;
   else
      order2=EntFindOrder(ret);
   if (order2==-1)
      order2=0;
   EntSetOrder(ret, order2);

   return (ret);
}

SplineSet *SplinePointListInterpretSVG(char *filename,char *memory,
				       int memlen, int em_size, int ascent,
				       int is_stroked) {
   Entity *ret =
      EntityInterpretSVG(filename, memory, memlen, em_size, ascent);
   int flags=-1;

   return (SplinesFromEntities(ret, &flags, is_stroked));
}

static int HasSVG(void) {
   return (libxml_init_base());
}
#endif
