svgparser.c

Go to the documentation of this file.

#include "svgparser.h"
#define MID(lo, hi) (lo + ((hi - lo) >> 1))
// XAML's path minilanguage also allows -Infinity, Infinity, and NaN; this is not done here.

#define CHAR_EOF  -1
#define CHAR_BAD  -2

#define SvgIsSpaceChar(x) \
  ((x)==0x20 || (x)==0x0A || (x)==0x0D || (x)==0x09)

typedef struct{
 char *str;
 LONG pos;
 LONG length;
} STRINGFILE;

typedef struct{
 int type;
 union {
  double real;
  LONG integer;
 } v;
} SVGNUMBER;

LONG PEEKCHAR(STRINGFILE* sf) {
 LONG c;
 DWORD oldpos=sf->pos;
 if(sf->pos>=sf->length)
  return CHAR_EOF;
 c=sf->str[sf->pos]; 
// DebugOut("%d: %c",sf->pos,c);
 sf->pos=oldpos;
 return c;
}
LONG NEXTCHAR(STRINGFILE*sf){
 LONG c;
 if(sf->pos>=sf->length)
  return CHAR_EOF;
 c=sf->str[sf->pos]; 
 sf->pos++;
 return c;
}
// Separator    ::= S+|(S* ',' S*)
int SvgParseSep(STRINGFILE*sbuf){
 LONG c;
 DWORD beginpos=sbuf->pos;
 c=PEEKCHAR(sbuf);
 if(c==CHAR_EOF)
  return 0;
 else if(SvgIsSpaceChar(c)){
  do {
   NEXTCHAR(sbuf);
   c=PEEKCHAR(sbuf);
  } while(SvgIsSpaceChar(c));
 }
 if(c==','){
  do {
   NEXTCHAR(sbuf);
   c=PEEKCHAR(sbuf);
  } while(SvgIsSpaceChar(c));
 }
 return (sbuf->pos==beginpos)?0:1;
}
// SpaceOrParen ::= Separator|S* ')'
int SvgParseSepOrParen(STRINGFILE*sbuf){
 LONG c;
 DWORD beginpos=sbuf->pos;
 c=PEEKCHAR(sbuf);
 if(c==CHAR_EOF)
  return 0;
 else if(SvgIsSpaceChar(c)){
  do {
   NEXTCHAR(sbuf);
   c=PEEKCHAR(sbuf);
  } while(SvgIsSpaceChar(c));
 }
 if(c==')'){
  NEXTCHAR(sbuf);
  return 2;
 }
 if(c==','){
  do {
   NEXTCHAR(sbuf);
   c=PEEKCHAR(sbuf);
  } while(SvgIsSpaceChar(c));
 }
 return (sbuf->pos==beginpos)?0:1;
}
BOOL SvgParseSpace(STRINGFILE*sbuf){
 BOOL ret=FALSE;
 LONG c;
 c=PEEKCHAR(sbuf);
 while(SvgIsSpaceChar(c)){
  ret=TRUE;
  NEXTCHAR(sbuf);
  c=PEEKCHAR(sbuf);
 }
 return ret;
}
BOOL SvgParseString(STRINGFILE*sbuf,char *string){
 LONG c;
 DWORD oldpos=sbuf->pos;
 while(*string){
  int c2=*string++;
  c=NEXTCHAR(sbuf);
  if(c!=c2){
   sbuf->pos=oldpos;
   return FALSE;
  }
 }
 return TRUE;
}
BOOL SvgParseInteger(STRINGFILE*sbuf,LONG*pint){
 int sign=1;
 LONG dec=0;
 int deccount=0;
 LONG c=PEEKCHAR(sbuf);
 if(c=='+')
  sign=1;
 else if(c=='-')
  sign=-1;
 else if(c>='0'&&c<='9'){
  dec=dec*10+(c-'0');
  deccount++;
 }
 else {
  return FALSE;
 }
 NEXTCHAR(sbuf);
 while(1){
  c=PEEKCHAR(sbuf);
  if(c>='0'&&c<='9'){
   dec=dec*10+(c-'0');
   deccount++;
  }
  else {
   if(deccount==0)
    return FALSE;
   *pint=dec*sign;
   return TRUE;
  }
  NEXTCHAR(sbuf);
 }
 return FALSE;
}
BOOL SvgParseNumberEx(STRINGFILE*sbuf,SVGNUMBER*num, BOOL lax){
 LONG c;
 int sign=1;
 LONG dec=0;
 double decReal=0.0;
 double decFrac=0.0;
 double decInc=10;
 int deccount=0;
 LONG exp=0;
 int mode=0;
 LONG exppos=0;
 c=PEEKCHAR(sbuf);
 if(c=='+')
  sign=1;
 else if(c=='-')
  sign=-1;
 else if(c>='0'&&c<='9'){
  dec=dec*10+(c-'0');
  decReal=decReal*10+(c-'0');
  deccount++;
 }
 else if(c=='.'){
  dec=0;
  decReal=0;
  mode=1;
 }
 else {
  return FALSE;
 }
 NEXTCHAR(sbuf);
 while(mode==0){
  c=PEEKCHAR(sbuf);
  if(c>='0'&&c<='9'){
   dec=dec*10+(c-'0');
   decReal=decReal*10+(c-'0');
   deccount++;
  }
  else if(c=='E'||c=='e'){
   if(deccount==0)
    return FALSE;
   exppos=sbuf->pos;
   mode=2;
  }
  else if(c=='.'){
   mode=1;
   deccount=0;
  }
  else {
   if(deccount==0)
    return FALSE;
   num->type=1;
   num->v.integer=dec*sign;
   return TRUE;
  }
  NEXTCHAR(sbuf);
 }
 while(mode==1){
  c=PEEKCHAR(sbuf);
  if(c>='0'&&c<='9'){
   decFrac+=((double)(c-'0'))/decInc;
   decInc*=10;
   deccount++;
  }
  else if(c=='E'||c=='e'){
   if(deccount==0&&!lax)
    return FALSE;
   exppos=sbuf->pos;
   mode=2;
  }
  else {
   if(deccount==0&&!lax)
    return FALSE;
   num->type=2;
   num->v.real=(decReal+decFrac)*sign;
   return TRUE;
  }
  NEXTCHAR(sbuf);
 }
 ASSERT(mode==2);
 decReal=(decReal+decFrac)*sign;
 deccount=0;
 sign=1;
 c=PEEKCHAR(sbuf);
 if(c=='+')
  sign=1;
 else if(c=='-')
  sign=-1;
 else if(c>='0'&&c<='9'){
  exp=c-'0';
  deccount++;
 }
 else {
  sbuf->pos=exppos;
  num->type=2;
  num->v.real=(decReal+decFrac)*sign;
  return TRUE;
 }
 NEXTCHAR(sbuf);
 while(1){
  c=PEEKCHAR(sbuf);
  if(c>='0'&&c<='9'){
   exp=exp*10+(c-'0');
   deccount++;
  }
  else {
   if(deccount==0)
    return FALSE;
   exp*=sign;
   num->type=2;
   num->v.real=decReal*pow(10.0,exp);
   return TRUE;
  }
  NEXTCHAR(sbuf);
 }
 return FALSE;
}
BOOL SvgParseDouble(STRINGFILE*sbuf,double *v){
 SVGNUMBER num;
 if(SvgParseNumberEx(sbuf,&num,FALSE)){
  if(num.type==2)
   *v=num.v.real;
  else {
   *v=(double)num.v.integer;
  }
  return TRUE;
 }
 return FALSE;
}
BOOL SvgParseDoubleSpace(STRINGFILE*sbuf,double *v){
 BOOL ret=SvgParseDouble(sbuf,v);
 SvgParseSpace(sbuf);
 return ret;
}
BOOL SvgParseDoubleSep(STRINGFILE*sbuf,double *v){
 BOOL ret=SvgParseDouble(sbuf,v);
 SvgParseSep(sbuf);
 return ret;
}
BOOL SvgParseDoubleLax(STRINGFILE*sbuf,double *v){
 SVGNUMBER num;
 if(SvgParseNumberEx(sbuf,&num,TRUE)){
  if(num.type==2)
   *v=num.v.real;
  else {
   *v=(double)num.v.integer;
  }
  return TRUE;
 }
 return FALSE;
}
BOOL SvgParseDoubleSepLax(STRINGFILE*sbuf,double *v){
 BOOL ret=SvgParseDoubleLax(sbuf,v);
 SvgParseSep(sbuf);
 return ret;
}
BOOL SvgParseFlag(STRINGFILE*sbuf,BOOL*v){
 LONG c=PEEKCHAR(sbuf);
 *v=0;
 if(c=='1')
  *v=1;
 else if(c!='0'){
  return FALSE;
 }
 NEXTCHAR(sbuf);
 return SvgParseSep(sbuf);
}

typedef struct{
 int coord;
 double curx;
 double cury;
 double movx;
 double movy;
 double cx,cy;
 LONG iaIndex;
 LONG lastcmd;
 LONG prevcmd;
 STRINGFILE sf;
 ITEMARRAY ia;
} SVGPATHPARSER;

int SvgPathParserNext(LPVOID pspp, PATHSEGMENT *ps);
LPVOID SvgPathParserCreate(char *path);
void SvgPathParserFree(LPVOID pspp);

int SvgPathParserGetWinding(LPVOID p){
 return WIND_NONZERO;
}

LPVOID SvgPathParserCreate(char *path){
 SVGPATHPARSER *spp;
 if(!path)
  return NULL;
 spp=malloc(sizeof(SVGPATHPARSER));
 if(!spp)return NULL;
 ItemArrayInitEx(&spp->ia,sizeof(PATHSEGMENT),5,10);
 spp->coord=0;
 spp->curx=0.0;
 spp->cury=0.0;
 spp->movx=0.0;
 spp->movy=0.0;
 spp->lastcmd=0;
 spp->prevcmd=0;
 spp->coord=0;
 spp->sf.str=path;
 spp->sf.pos=0;
 spp->sf.length=strlen(path);
 return spp;
}

void SvgPathParserFree(LPVOID pspp){
 SVGPATHPARSER *spp=(SVGPATHPARSER *)pspp;
 if(!spp)return;
 ItemArrayFree(&spp->ia);
 free(spp);
}

static int SvgNextMarker(SVGPATHPARSER *spp){
 LONG c;
 SvgParseSpace(&spp->sf);
 c=PEEKCHAR(&spp->sf);
 spp->prevcmd=spp->lastcmd;
 switch(c){
  case CHAR_EOF:
   spp->coord=0;
   spp->lastcmd=c;
   return c;
  case 'M': case 'm':
  case 'L': case 'l':
  case 'H': case 'h':
  case 'V': case 'v':
  case 'Q': case 'q':
  case 'C': case 'c':
  case 'S': case 's':
  case 'T': case 't':
  case 'A': case 'a':
   spp->coord=0;
   NEXTCHAR(&spp->sf);
   SvgParseSpace(&spp->sf);
   spp->lastcmd=c;
   return c;
  case 'Z': case 'z':
   spp->coord=0;
   NEXTCHAR(&spp->sf);
   spp->lastcmd=c;
   return c;
  default:
   spp->coord=1;
   return spp->lastcmd;
 }
}

int SvgPathParserNext(LPVOID pspp, PATHSEGMENT *ps){
 LONG c;
 SVGPATHPARSER *spp=(SVGPATHPARSER *)pspp;
 if(!spp||!ps)return -1;
 while(1){
  // If items in the array are to be read
  if(spp->ia.length>0){
   if(spp->iaIndex<spp->ia.length){
    *ps=*(PATHSEGMENT*)ItemArrayPtr(&spp->ia,spp->iaIndex);
    spp->iaIndex++;
    // Clear item array if all path segments were read
    if(spp->iaIndex>=spp->ia.length){
     spp->ia.length=0;
    }
    return 1;
   } else {
    spp->ia.length=0;
   }
  }
  // Process current command
//  DebugOut("lastcmd %c",spp->lastcmd);
  switch(spp->lastcmd){
   case CHAR_EOF:
    // Reached end of path
    return 0;
   case 0:
    // Starting path parser
    SvgNextMarker(spp);
    // Must be moveto or end
    if(spp->lastcmd!=CHAR_EOF &&
       spp->lastcmd!='m' &&
       spp->lastcmd!='M'){
     return -1;
    }
    break;
   case 'M':
   case 'm':
   case 'L':
   case 'l':{
    // In moveto or lineto
    double x,y;
    if(spp->coord>0)
     SvgParseSep(&spp->sf);
    // Getting X and Y
    if(!SvgParseDoubleSepLax(&spp->sf,&x)||
       !SvgParseDoubleLax(&spp->sf,&y)){
     return -1;
    }
    if(spp->lastcmd=='m'||spp->lastcmd=='l'){
     x+=spp->curx;
     y+=spp->cury;
    }
    if(spp->coord==0 && (spp->lastcmd=='M'||spp->lastcmd=='m')){
     // First pair after M or m
     ps->type=PATH_MOVETO;
     spp->movx=x;
     spp->movy=y;
    } else {
     // Lineto or subsequent pairs
     ps->type=PATH_LINETO;
    }
    ps->coords[0]=x;
    ps->coords[1]=y;
    spp->curx=x;
    spp->cury=y;
    // Parse the next marker
    spp->lastcmd=SvgNextMarker(spp);
    return 1;
   }
   case 'H':
   case 'h':{
    // In horizontal lineto
    double x;
    if(spp->coord>0)
     SvgParseSep(&spp->sf);
    // Getting coordinate
    if(!SvgParseDoubleLax(&spp->sf,&x)){
     return -1;
    }
    if(spp->lastcmd=='h'){
     x+=spp->curx;
    }
    ps->type=PATH_LINETO;
    ps->coords[0]=x;
    ps->coords[1]=spp->cury;
    spp->curx=x;
    // Parse the next marker
    spp->lastcmd=SvgNextMarker(spp);
    return 1;
   }
   case 'V':
   case 'v':{
    // In vertical lineto
    double y;
    if(spp->coord>0)
     SvgParseSep(&spp->sf);
    // Getting coordinate
    if(!SvgParseDoubleLax(&spp->sf,&y)){
     return -1;
    }
    if(spp->lastcmd=='v'){
     y+=spp->cury;
    }
    ps->type=PATH_LINETO;
    ps->coords[0]=spp->curx;
    ps->coords[1]=y;
    spp->cury=y;
    // Parse the next marker
    spp->lastcmd=SvgNextMarker(spp);
    return 1;
   }
   case 'Q':
   case 'q':{
    // In quadratic
    double x1,y1,x,y;
    if(spp->coord>0)
     SvgParseSep(&spp->sf);
    // Getting coordinates
    if(!SvgParseDoubleSepLax(&spp->sf,&x1)||
       !SvgParseDoubleSepLax(&spp->sf,&y1)||
       !SvgParseDoubleSepLax(&spp->sf,&x)||
       !SvgParseDoubleLax(&spp->sf,&y)){
     return -1;
    }
    if(spp->lastcmd=='q'){
     x1+=spp->curx;
     y1+=spp->cury;
     x+=spp->curx;
     y+=spp->cury;
    }
    ps->type=PATH_QUADTO;
    ps->coords[0]=x1;
    ps->coords[1]=y1;
    ps->coords[2]=x;
    ps->coords[3]=y;
    // Update control point and current position
    spp->curx=x;
    spp->cury=y;
    spp->cx=x1;
    spp->cy=y1;
    // Parse the next marker
    spp->lastcmd=SvgNextMarker(spp);
    return 1;
   }
   case 'T':
   case 't':{
    // In smooth quadratic
    double x,y;
    if(spp->coord>0)
     SvgParseSep(&spp->sf);
    // Getting coordinates
    if(!SvgParseDoubleSepLax(&spp->sf,&x)||
       !SvgParseDoubleLax(&spp->sf,&y)){
     return -1;
    }
    if(spp->lastcmd=='t'){
     x+=spp->curx;
     y+=spp->cury;
    }
    if(spp->prevcmd=='Q'||
       spp->prevcmd=='q'||
       spp->prevcmd=='T'||
       spp->prevcmd=='t'){
     // Control point implicitly defined by previous
     // curve's control point
     ps->coords[0]=spp->curx+spp->curx-spp->cx;
     ps->coords[1]=spp->cury+spp->cury-spp->cy;
    } else {
     // Control point same as end point
     ps->coords[0]=spp->curx;
     ps->coords[1]=spp->cury;
    }
    ps->type=PATH_QUADTO;
    ps->coords[2]=x;
    ps->coords[3]=y;
    spp->curx=x;
    spp->cury=y;
    spp->cx=ps->coords[0];
    spp->cy=ps->coords[1];
    // Parse the next marker
    spp->lastcmd=SvgNextMarker(spp);
    return 1;
   }
   case 'C':
   case 'c':{
    // In cubic
    double x1,y1,x2,y2,x,y;
    if(spp->coord>0)
     SvgParseSep(&spp->sf);
    // Getting coordinates
    if(!SvgParseDoubleSepLax(&spp->sf,&x1)||
       !SvgParseDoubleSepLax(&spp->sf,&y1)||
       !SvgParseDoubleSepLax(&spp->sf,&x2)||
       !SvgParseDoubleSepLax(&spp->sf,&y2)||
       !SvgParseDoubleSepLax(&spp->sf,&x)||
       !SvgParseDoubleLax(&spp->sf,&y)){
     return -1;
    }
    if(spp->lastcmd=='c'){
     x1+=spp->curx;
     y1+=spp->cury;
     x2+=spp->curx;
     y2+=spp->cury;
     x+=spp->curx;
     y+=spp->cury;
    }
    ps->type=PATH_CUBICTO;
    ps->coords[0]=x1;
    ps->coords[1]=y1;
    ps->coords[2]=x2;
    ps->coords[3]=y2;
    ps->coords[4]=x;
    ps->coords[5]=y;
    // Update control point and current position
    spp->curx=x;
    spp->cury=y;
    spp->cx=x2;
    spp->cy=y2;
    // Parse the next marker
    spp->lastcmd=SvgNextMarker(spp);
    return 1;
   }
   case 'S':
   case 's':{
    // In smooth cubic
    double x2,y2,x,y;
    if(spp->coord>0)
     SvgParseSep(&spp->sf);
    // Getting coordinates
    if(!SvgParseDoubleSepLax(&spp->sf,&x2)||
       !SvgParseDoubleSepLax(&spp->sf,&y2)||
       !SvgParseDoubleSepLax(&spp->sf,&x)||
       !SvgParseDoubleLax(&spp->sf,&y)){
     return -1;
    }
    if(spp->lastcmd=='s'){
     x2+=spp->curx;
     y2+=spp->cury;
     x+=spp->curx;
     y+=spp->cury;
    }
    if(spp->prevcmd=='C'||
       spp->prevcmd=='c'||
       spp->prevcmd=='S'||
       spp->prevcmd=='s'){
     // Control point implicitly defined by previous
     // curve's control point
     ps->coords[0]=spp->curx+spp->curx-spp->cx;
     ps->coords[1]=spp->cury+spp->cury-spp->cy;
    } else {
     ps->coords[0]=spp->curx;
     ps->coords[1]=spp->cury;
    }
    ps->type=PATH_CUBICTO;
    ps->coords[2]=x2;
    ps->coords[3]=y2;
    ps->coords[4]=x;
    ps->coords[5]=y;
    spp->curx=x;
    spp->cury=y;
    spp->cx=ps->coords[2];
    spp->cy=ps->coords[3];
    // Parse the next marker
    spp->lastcmd=SvgNextMarker(spp);
    return 1;
   }
   case 'A':
   case 'a':{
    // In arc
    double rx,ry,rot,x,y;
    BOOL arcflag,sweepflag;
    if(spp->coord>0)
     SvgParseSep(&spp->sf);
    // Getting parameters
    if(!SvgParseDoubleSepLax(&spp->sf,&rx)||
       !SvgParseDoubleSepLax(&spp->sf,&ry)||
       !SvgParseDoubleLax(&spp->sf,&rot)||
       !SvgParseSep(&spp->sf)||
       !SvgParseFlag(&spp->sf,&arcflag)||
       !SvgParseFlag(&spp->sf,&sweepflag)||
       !SvgParseDoubleSepLax(&spp->sf,&x)||
       !SvgParseDoubleLax(&spp->sf,&y)){
     return -1;
    }
    if(rx<0 || ry<0){
     return -1;
    }
    if(spp->lastcmd=='a'){
     x+=spp->curx;
     y+=spp->cury;
    }
    spp->iaIndex=1; // Skip moveto segment
    spp->ia.length=0;
//    DebugOut("[%f %f %f %f %f %d %d]",
//      x,y,rx,ry,rot,arcflag,sweepflag);
    PathAddSvgArc(&spp->ia,spp->curx,spp->cury,x,y,
       rx,ry,rot*DEGTORAD,arcflag,sweepflag);
//    DebugOut("Added: %d",spp->ia.length);
    // Update control point and current position
    spp->curx=x;
    spp->cury=y;
    // Parse the next marker
    spp->lastcmd=SvgNextMarker(spp);
    break;
   }
   case 'Z':
   case 'z':
    if(spp->coord>0)
     return -1;
    ps->type=PATH_CLOSE;
    spp->curx=spp->movx;
    spp->cury=spp->movy;
    spp->lastcmd=SvgNextMarker(spp);
    return 1;
   default:
    return -1;
  }
 }
 return -1;
}

PATHITERATOR SvgPathParser={
SvgPathParserCreate,
SvgPathParserNext,
SvgPathParserFree,
SvgPathParserGetWinding
};

Generated on Thu Mar 27 01:46:53 2008 for Item Arrays by  doxygen 1.4.6-NO