#help_index "Graphics/Sprite;Sprites"

U0 SpriteElem2Code(CDoc *doc,CSprite *tmpg)
{
  U8 buf1[STR_LEN],buf2[STR_LEN];
  I32 *ptr;
  I64 i,j,k,col,width_internal;
  CD3I32 *p;
  CMeshTri *tri;
  if (!doc) doc=DocPut;
  DocPrint(doc,"%Z",tmpg->type&SPG_TYPE_MASK,"ST_SPRITE_ELEM_CODES");
  switch (tmpg->type&SPG_TYPE_MASK) {
    case SPT_COLOR:
      DocPrint(doc,"{%s}",
            Color2Str(buf1,tmpg->c.color));
      break;
    case SPT_DITHER_COLOR:
      DocPrint(doc,"{%s}",
            Color2Str(buf2,ROPF_DITHER|tmpg->d.dither_color.u8[0]|
            tmpg->d.dither_color.u8[1]<<COLORROP_BITS));
      break;
    case SPT_TRANSFORM_ON:
      DocPrint(doc,"{ON}");
      break;
    case SPT_TRANSFORM_OFF:
      DocPrint(doc,"{OFF}");
      break;
    case SPT_LINE:
    case SPT_ARROW:
    case SPT_PLANAR_SYMMETRY:
      DocPrint(doc,"{(%d,%d),(%d,%d)}",tmpg->pp.x1,tmpg->pp.y1,
            tmpg->pp.x2,tmpg->pp.y2);
      break;
    case SPT_RECT:
      DocPrint(doc,"{(%d,%d):(%d,%d)}",tmpg->pp.x1,tmpg->pp.y1,
            tmpg->pp.x2-tmpg->pp.x1,tmpg->pp.y2-tmpg->pp.y1);
      break;
    case SPT_ROTATED_RECT:
      DocPrint(doc,"{(%d,%d):(%d,%d),%0.4f}",tmpg->ppa.x1,tmpg->ppa.y1,
            tmpg->ppa.x2-tmpg->ppa.x1,tmpg->ppa.y2-tmpg->ppa.y1,
            180/pi*Wrap(tmpg->ppa.angle));
      break;
    case SPT_PT:
    case SPT_FLOOD_FILL:
    case SPT_SHIFT:
      DocPrint(doc,"{(%d,%d)}",tmpg->p.x1,tmpg->p.y1);
      break;
    case SPT_FLOOD_FILL_NOT:
      DocPrint(doc,"{(%d,%d),TRUE}",tmpg->p.x1,tmpg->p.y1);
      break;
    case SPT_CIRCLE:
      DocPrint(doc,"{(%d,%d):%d}",tmpg->pr.x1,tmpg->pr.y1,tmpg->pr.radius);
      break;
    case SPT_THICK:
      DocPrint(doc,"{%d}",tmpg->t.thick);
      break;
    case SPT_ELLIPSE:
      DocPrint(doc,"{(%d,%d):(%d,%d),%0.4f}",tmpg->pwha.x1,tmpg->pwha.y1,
            tmpg->pwha.width,tmpg->pwha.height,180/pi*Wrap(tmpg->pwha.angle));
      break;
    case SPT_POLYGON:
      DocPrint(doc,"{%d,(%d,%d):(%d,%d),%0.4f}",tmpg->pwhas.sides,
            tmpg->pwhas.x1,tmpg->pwhas.y1,
            tmpg->pwhas.width,tmpg->pwhas.height,
            180/pi*Wrap(tmpg->pwhas.angle));
      break;
    case SPT_TEXT:
    case SPT_TEXT_BOX:
    case SPT_TEXT_DIAMOND:
      DocPrint(doc,"{(%d,%d),\"%Q\"}",tmpg->ps.x1,tmpg->ps.y1,tmpg->ps.st);
      break;
    case SPT_POLYLINE:
      ptr=&tmpg->nu.u;
      DocPrint(doc,"{");
      for (i=0;i<tmpg->nu.num;i++,ptr+=2) {
        DocPrint(doc,"(%d,%d)",ptr[0],ptr[1]);
        if (i+1<tmpg->nu.num) DocPrint(doc,",");
        if (i&3==3 && i+1<tmpg->nu.num)
          DocPrint(doc,"\n");
      }
      DocPrint(doc,"}");
      break;
    case SPT_BSPLINE2:
    case SPT_BSPLINE3:
    case SPT_BSPLINE2_CLOSED:
    case SPT_BSPLINE3_CLOSED:
      ptr=&tmpg->nu.u;
      DocPrint(doc,"{");
      for (i=0;i<tmpg->nu.num;i++,ptr+=3) {
        DocPrint(doc,"(%d,%d,%d)",ptr[0],ptr[1],ptr[2]);
        if (i+1<tmpg->nu.num) DocPrint(doc,",");
        if (i&3==3 && i+1<tmpg->nu.num)
          DocPrint(doc,"\n");
      }
      if (tmpg->type&SPG_TYPE_MASK==SPT_BSPLINE2||
            tmpg->type&SPG_TYPE_MASK==SPT_BSPLINE3)
        DocPrint(doc,",FALSE}");
      else
        DocPrint(doc,",TRUE}");
      break;
    case SPT_POLYPT:
      DocPrint(doc,"{(%d,%d),",tmpg->npu.x,tmpg->npu.y);
      ptr=&tmpg->npu.u;
      col=16;
      for (i=0;i<tmpg->npu.num;i++) {
        DocPrint(doc,"%d",BFieldExtU32(ptr,i*3,3));
        if (++col>=64 && i+1<tmpg->npu.num) {
          DocPrint(doc,"\n");
          col=0;
        }
      }
      DocPrint(doc,"}");
      break;
    case SPT_BITMAP:
      DocPrint(doc,"{(%d,%d):(%d,%d),\n",tmpg->pwhu.x1,tmpg->pwhu.y1,
            tmpg->pwhu.width,tmpg->pwhu.height);
      width_internal=(tmpg->pwhu.width+7)&~7;
      if (width_internal<80)
        k=width_internal;
      else
        k=64;
      ptr=&tmpg->pwhu.u;
      col=0;
      for (j=0;j<tmpg->pwhu.height;j++)
        for (i=0;i<width_internal;i++,ptr(U8 *)++) {
          if (i>=tmpg->pwhu.width)
            DocPrint(doc,"_");
          else if (*ptr(U8 *)<16)
            DocPrint(doc,"%X",*ptr(U8 *));
          else
            DocPrint(doc,"%c",CH_SHIFT_SPACE);
          if (++col>=k && (i+1<width_internal||j+1<tmpg->pwhu.height)) {
            DocPrint(doc,"\n");
            col=0;
          }
        }
      DocPrint(doc,"}");
      break;
    case SPT_MESH:
      DocPrint(doc,"{FALSE,");
      p=&tmpg->mu.u;
      col=0;
      for (i=0;i<tmpg->mu.vertex_cnt;i++,p++) {
        DocPrint(doc,"(%d,%d,%d)",p->x,p->y,p->z);
        if (i+1<tmpg->mu.vertex_cnt) DocPrint(doc,",");
        if (++col==4) {
          DocPrint(doc,"\t//%d\n",i);
          col=0;
        }
      }
      DocPrint(doc,":");
      tri=p;
      for (i=0;i<tmpg->mu.tri_cnt;i++,tri++) {
        DocPrint(doc,"(%s,%d,%d,%d)",Color2Str(buf1,tri->color),
              tri->nums[0],tri->nums[1],tri->nums[2]);
        if (i+1<tmpg->mu.tri_cnt) DocPrint(doc,",");
        if (++col>=3 && i+1<tmpg->mu.tri_cnt) {
          DocPrint(doc,"\n");
          col=0;
        }
      }
      DocPrint(doc,"}");
      break;
    case SPT_SHIFTABLE_MESH:
      DocPrint(doc,"{TRUE,(%d,%d,%d):",tmpg->pmu.x,tmpg->pmu.y,tmpg->pmu.z);
      p=&tmpg->pmu.u;
      col=1;
      for (i=0;i<tmpg->pmu.vertex_cnt;i++,p++) {
        DocPrint(doc,"(%d,%d,%d)",p->x,p->y,p->z);
        if (i+1<tmpg->pmu.vertex_cnt) DocPrint(doc,",");
        if (++col==4) {
          DocPrint(doc,"\t//%d\n",i);
          col=0;
        }
      }
      DocPrint(doc,":");
      tri=p;
      for (i=0;i<tmpg->pmu.tri_cnt;i++,tri++) {
        DocPrint(doc,"(%s,%d,%d,%d)",Color2Str(buf1,tri->color),
              tri->nums[0],tri->nums[1],tri->nums[2]);
        if (i+1<tmpg->pmu.tri_cnt) DocPrint(doc,",");
        if (++col>=3 && i+1<tmpg->pmu.tri_cnt) {
          DocPrint(doc,"\n");
          col=0;
        }
      }
      DocPrint(doc,"}");
      break;
  }
    DocPrint(doc,";\n");
}

public U0 Sprite2Code(CDoc *doc=NULL,U8 *elems)
{//Sprite to text.
  CSprite *tmpg=elems-offset(CSprite.start);
  while (tmpg->type&SPG_TYPE_MASK) {
    SpriteElem2Code(doc,tmpg);
    tmpg(U8 *)+=SpriteElemSize(tmpg);
  }
}

CSprite *Code2SpriteElem(CCmpCtrl *cc,I64 type)
{
  I64 i,num1,num2,size;
  CSprite *res,g;
  CColorROPU32 color;
  U8 *st,*ptr;
  CQueD3I32 headp,*tmpp,*tmpa1;
  CQueMeshTri headt,*tmpt,*tmpt1;
  CQueVectU8 *tmpv;
  MemSet(&g,0,sizeof(CSprite));
  switch (type) {
    start:
      case SPT_COLOR:
      case SPT_DITHER_COLOR:
        st=LexFirstRem(cc,"}");
        color=Str2ColorU32(st);
        Free(st);
        Lex(cc); //Skip color
        g.c.color=color.c0.color;
        if (color&ROPF_DITHER) {
          g.d.dither_color.u8[1]=color.c1.color;
          g.type=SPT_DITHER_COLOR;
        } else
          g.type=SPT_COLOR;
        break;
      case SPT_TRANSFORM_ON:
      case SPT_TRANSFORM_OFF:
        Lex(cc); //Skip {
        if (LexExpressionI64(cc))
          g.type=SPT_TRANSFORM_ON;
        else
          g.type=SPT_TRANSFORM_OFF;
        break;
      case SPT_LINE:
      case SPT_ARROW:
      case SPT_PLANAR_SYMMETRY:
        Lex(cc); //Skip {
        g.type=type;
        LexD2I32(cc,&g.pp.x1);
        if (cc->token!=',')
          LexExcept(cc,"Expecting ',' at ");
        Lex(cc); //Skip ,
        LexD2I32(cc,&g.pp.x2);
        break;
      case SPT_RECT:
      case SPT_ROTATED_RECT:
        Lex(cc); //Skip {
        LexD2I32(cc,&g.pp.x1);
        if (cc->token!=':')
          LexExcept(cc,"Expecting ':' at ");
        Lex(cc); //Skip :
        LexD2I32(cc,&g.pp.x2);
        g.ppa.x2+=g.pp.x1;
        g.ppa.y2+=g.pp.y1;
        if (cc->token==',') {
          Lex(cc); //Skip ,
          g.ppa.angle=pi/180*LexExpressionF64(cc);
          g.type=SPT_ROTATED_RECT;
        } else
          g.type=SPT_RECT;
        break;
      case SPT_PT:
      case SPT_SHIFT:
        Lex(cc); //Skip {
        g.type=type;
        LexD2I32(cc,&g.p.x1);
        break;
      case SPT_FLOOD_FILL:
      case SPT_FLOOD_FILL_NOT:
        Lex(cc); //Skip {
        LexD2I32(cc,&g.p.x1);
        if (cc->token==',') {
          Lex(cc); //Skip ,
          i=LexExpressionI64(cc);
        } else
          i=0;
        if (i)
          g.type=SPT_FLOOD_FILL_NOT;
        else
          g.type=SPT_FLOOD_FILL;
        break;
      case SPT_THICK:
        Lex(cc); //Skip {
        g.t.thick=LexExpressionI64(cc);
        g.type=SPT_THICK;
        break;
      case SPT_CIRCLE:
        Lex(cc); //Skip {
        g.type=SPT_CIRCLE;
        LexD2I32(cc,&g.pr.x1);
        if (cc->token!=':')
          LexExcept(cc,"Expecting ':' at ");
        Lex(cc); //Skip :
        g.pr.radius=LexExpressionI64(cc);
        break;
      case SPT_POLYGON:
        Lex(cc); //Skip {
        g.pwhas.sides=LexExpressionI64(cc);
        if (cc->token!=',')
          LexExcept(cc,"Expecting ',' at ");
      case SPT_ELLIPSE:
        Lex(cc); //Skip {
        g.type=type;
        LexD2I32(cc,&g.pwha.x1);
        if (cc->token!=':')
          LexExcept(cc,"Expecting ':' at ");
        Lex(cc); //Skip :
        LexD2I32(cc,&g.pwha.width);
        if (cc->token!=',')
          LexExcept(cc,"Expecting ',' at ");
        Lex(cc); //Skip ,
        g.pwha.angle=pi/180*LexExpressionF64(cc);
        break;
    end:
      size=SpriteElemSize(&g)+offset(CSprite.start);
      res=MAlloc(size);
      MemCpy(res,&g,size);
      break;
    case SPT_TEXT:
    case SPT_TEXT_BOX:
    case SPT_TEXT_DIAMOND:
      Lex(cc); //Skip {
      g.type=type;
      LexD2I32(cc,&g.ps.x1);
      if (cc->token!=',')
        LexExcept(cc,"Expecting ',' at ");
      if (Lex(cc)==TK_STR)  //Skip ,
        st=LexExtStr(cc);
      else
        LexExcept(cc,"Expecting string at ");
      size=SpriteElemQuedBaseSize(type);
      i=StrLen(st)+1;
      res=MAlloc(size+i);
      MemCpy(res,&g,size);
      MemCpy(res(U8 *)+size,st,i);
      Free(st);
      break;
    case SPT_POLYLINE:
      Lex(cc); //Skip {
      g.type=SPT_POLYLINE;
      QueInit(&headp);
      while (cc->token=='(') {
        tmpp=CAlloc(sizeof(CQueD3I32));
        LexD2I32(cc,&tmpp->p);
        QueIns(tmpp,headp.last);
        g.nu.num++;
        if (cc->token==',')
          Lex(cc); //Skip ,
      }
      if (g.nu.num<2)
        LexExcept(cc,"Expecting point at ");
      size=SpriteElemQuedBaseSize(SPT_POLYLINE);
      res=MAlloc(size+g.nu.num*sizeof(CD2I32));
      MemCpy(res,&g,size);
      ptr=&res->nu.u;
      tmpp=headp.next;
      while (tmpp!=&headp) {
        tmpa1=tmpp->next;
        MemCpy(ptr,&tmpp->p,sizeof(CD2I32));
        ptr+=sizeof(CD2I32);
        Free(tmpp);
        tmpp=tmpa1;
      }
      break;
    case SPT_BSPLINE2:
    case SPT_BSPLINE3:
    case SPT_BSPLINE2_CLOSED:
    case SPT_BSPLINE3_CLOSED:
      Lex(cc); //Skip {
      QueInit(&headp);
      while (cc->token=='(') {
        tmpp=CAlloc(sizeof(CQueD3I32));
        LexD3I32(cc,&tmpp->p);
        QueIns(tmpp,headp.last);
        g.nu.num++;
        if (cc->token==',')
          Lex(cc); //Skip ,
      }
      if (g.nu.num<2)
        LexExcept(cc,"Expecting point at ");
      size=SpriteElemQuedBaseSize(type);
      res=MAlloc(size+g.nu.num*sizeof(CD3I32));
      if (LexExpressionI64(cc)) {
        if (type==SPT_BSPLINE2||type==SPT_BSPLINE2_CLOSED)
          g.type=SPT_BSPLINE2_CLOSED;
        else
          g.type=SPT_BSPLINE3_CLOSED;
      } else {
        if (type==SPT_BSPLINE2||type==SPT_BSPLINE2_CLOSED)
          g.type=SPT_BSPLINE2;
        else
          g.type=SPT_BSPLINE3;
      }
      MemCpy(res,&g,size);
      ptr=&res->nu.u;
      tmpp=headp.next;
      while (tmpp!=&headp) {
        tmpa1=tmpp->next;
        MemCpy(ptr,&tmpp->p,sizeof(CD3I32));
        ptr+=sizeof(CD3I32);
        Free(tmpp);
        tmpp=tmpa1;
      }
      break;
    case SPT_POLYPT:
      Lex(cc); //Skip {
      LexD2I32(cc,&g.npu.x);
      if (cc->token!=',')
        LexExcept(cc,"Expecting ',' at ");
      tmpv=QueVectU8New;
      while (TRUE) {
        if (!(i=LexGetChar(cc)))
          LexExcept(cc,"Expecting '}' at ");
        if (i=='}')
          break;
        if ('0'<=i<='7')
          QueVectU8Put(tmpv,g.npu.num++,i-'0');
      }
      Bts(&cc->flags,CCf_USE_LAST_U16);
      Lex(cc); //Load '}'
      g.type=SPT_POLYPT;
      size=SpriteElemQuedBaseSize(SPT_POLYPT);
      res=CAlloc(size+(g.npu.num*3+7)>>3);
      MemCpy(res,&g,size);
      ptr=&res->npu.u;
      for (i=0;i<g.npu.num;i++)
        BFieldOrU32(ptr,i*3,QueVectU8Get(tmpv,i));
      QueVectU8Del(tmpv);
      break;
    case SPT_BITMAP:
      Lex(cc); //Skip {
      LexD2I32(cc,&g.pwhu.x1);
      if (cc->token!=':')
        LexExcept(cc,"Expecting ':' at ");
      Lex(cc); //Skip :
      LexD2I32(cc,&g.pwhu.width);
      if (cc->token!=',')
        LexExcept(cc,"Expecting ',' at ");
      tmpv=QueVectU8New;
      num1=0;
      while (TRUE) {
        if (!(i=ToUpper(LexGetChar(cc))))
          LexExcept(cc,"Expecting '}' at ");
        if (i=='}')
          break;
        if ('0'<=i<='9')
          QueVectU8Put(tmpv,num1++,i-'0');
        else if ('A'<=i<='F')
          QueVectU8Put(tmpv,num1++,i-'A'+10);
        else if (i==CH_SPACE||i==CH_SHIFT_SPACE)
          QueVectU8Put(tmpv,num1++,TRANSPARENT);
        else if (i=='_')
          QueVectU8Put(tmpv,num1++,0);
      }
      Bts(&cc->flags,CCf_USE_LAST_U16);
      Lex(cc); //Load '}'
      g.type=SPT_BITMAP;
      size=SpriteElemQuedBaseSize(SPT_BITMAP);
      res=CAlloc(size+num1);
      MemCpy(res,&g,size);
      ptr=&res->pwhu.u;
      for (i=0;i<num1;i++)
        *ptr++=QueVectU8Get(tmpv,i);
      QueVectU8Del(tmpv);
      break;
    case SPT_MESH:
    case SPT_SHIFTABLE_MESH:
      Lex(cc); //Skip {
      if (LexExpressionI64(cc)) {
        g.type=SPT_SHIFTABLE_MESH;
        if (cc->token!=',')
          LexExcept(cc,"Expecting ',' at ");
        Lex(cc); //Skip ,
        LexD3I32(cc,&g.pmu.x);
        if (cc->token!=':')
          LexExcept(cc,"Expecting ':' at ");
        Lex(cc); //Skip :
      } else {
        g.type=SPT_MESH;
        if (cc->token!=',')
          LexExcept(cc,"Expecting ',' at ");
        Lex(cc); //Skip ,
      }
      num1=0;
      QueInit(&headp);
      while (cc->token=='(') {
        tmpp=CAlloc(sizeof(CQueD3I32));
        LexD3I32(cc,&tmpp->p);
        QueIns(tmpp,headp.last);
        num1++;
        if (cc->token==',')
          Lex(cc); //Skip ,
      }
      if (cc->token!=':')
        LexExcept(cc,"Expecting ':' at ");
      Lex(cc); //Skip :
      num2=0;
      QueInit(&headt);
      while (cc->token=='(') {
        tmpt=CAlloc(sizeof(CQueMeshTri));
        st=LexFirstRem(cc,",");
        tmpt->color=Str2ColorU32(st);
        Free(st);
        Lex(cc); //Skip color
        if (cc->token!=',')
          LexExcept(cc,"Expecting ',' at ");
        Lex(cc); //Skip ,
        tmpt->nums[0]=LexExpressionI64(cc);
        if (cc->token!=',')
          LexExcept(cc,"Expecting ',' at ");
        Lex(cc); //Skip ,
        tmpt->nums[1]=LexExpressionI64(cc);
        if (cc->token!=',')
          LexExcept(cc,"Expecting ',' at ");
        Lex(cc); //Skip ,
        tmpt->nums[2]=LexExpressionI64(cc);
        if (cc->token!=')')
          LexExcept(cc,"Expecting ')' at ");
        Lex(cc); //Skip )
        QueIns(tmpt,headt.last);
        num2++;
        if (cc->token==',')
          Lex(cc); //Skip ,
      }
      if (g.type==SPT_MESH) {
        g.mu.vertex_cnt=num1;
        g.mu.tri_cnt=num2;
        size=SpriteElemQuedBaseSize(SPT_MESH);
      } else {
        g.pmu.vertex_cnt=num1;
        g.pmu.tri_cnt=num2;
      }
      size=SpriteElemQuedBaseSize(g.type);
      res=MAlloc(size+num1*sizeof(CD3I32)+num2*sizeof(CMeshTri));
      MemCpy(res,&g,size);
      ptr=res(U8 *)+size;
      tmpp=headp.next;
      while (tmpp!=&headp) {
        tmpa1=tmpp->next;
        MemCpy(ptr,&tmpp->p,sizeof(CD3I32));
        ptr+=sizeof(CD3I32);
        Free(tmpp);
        tmpp=tmpa1;
      }
      tmpt=headt.next;
      while (tmpt!=&headt) {
        tmpt1=tmpt->next;
        MemCpy(ptr,&tmpt->start,sizeof(CMeshTri));
        ptr+=sizeof(CMeshTri);
        Free(tmpt);
        tmpt=tmpt1;
      }
      break;
  }
  if (cc->token!='}')
    LexExcept(cc,"Expecting '}' at ");
  if (Lex(cc)!=';')
    LexExcept(cc,"Expecting ';' at ");
  return res;
}

public U8 *Code2Sprite(CDoc *doc,I64 *_size=NULL)
{//Text to sprite.
  CSprite head;
  U8 *res;
  Bool okay=TRUE,unlock_doc=DocLock(doc);
  CCmpCtrl *cc=CmpCtrlNew(,CCF_DONT_FREE_BUF);
  CHashTable *old_hash_table_lst=cc->htc.hash_table_lst;
  CHashGeneric *tmph;
  I64 i,size=0;
  QueInit(&head);
  LexAttachDoc(cc,,doc);
  try {
    do {
      cc->htc.hash_table_lst=NULL;
      if (Lex(cc)==TK_IDENT && //Skip ;
            (tmph=HashFind(cc->cur_str,gr.sprite_hash,SPHT_ELEM_CODE))) {
        i=tmph->user_data0;
        cc->htc.hash_table_lst=old_hash_table_lst;
        if (Lex(cc)=='{') //Skip ident
          QueIns(Code2SpriteElem(cc,i),head.last);
      } else if (cc->token)
        LexExcept(cc,"Expecting sprite element type name at ");
    } while (cc->token);
    okay=TRUE;
  } catch {
    Fs->catch_except=TRUE;
    okay=FALSE;
  }
  if (unlock_doc)
    DocUnlock(doc);
  if (okay) {
    CmpCtrlDel(cc); //TODO: can crash
    res=SpriteQue2Sprite(&head,&size);
  } else {
    res=NULL;
    size=0;
  }
  if (_size) *_size=size;
  QueDel(&head);
  return res;
}