/******************************************************************************
 * program:     rasimg library 0.18                                           *
 * function:    Object set for handling vector images.                        *
 * modul:       vec_image.cc                                                  *
 * licency:     GPL or LGPL                                                   *
 * Copyright: (c) 2018-2021 Jaroslav Fojtik                                   *
 ******************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include "vecimage.h"


static void FixPsAccent(string & s, const char *TexAccent, const char *PsAccent);
static void FixPsAccent2(string & s, const char *TexAccent, const char *PsAccent);


/** Normalise angle after difference calculatiun to be in a range <-pi;+pi>
 * @param[in]	num1	Angle to normalise.
 * @return		Normalised angle. */
float NormAngleDEG(float num1)
{  
  if(num1>180)
  {
    num1 += 180;
    num1 = num1 -  (360) * ((int)(num1/(360)));
    num1 -= 180;
  }
  else if(num1<-180)
  {
    num1 -= 180;
    num1 = num1 -  (360) * ((int)(num1/(360)));
    num1 += 180;
  }
return num1;
}



/// Emit Postscript command for font selection.
/// @param[in,out]	Text	String builter that holds whole PS image.
/// @param[in]		FontSize  Size of font in [points]
/// @param[in]		FontName  Optional naome of font.
void Font2PS(string & Text, float FontSize, const char *FontName)
{
  Text.cat_printf("/%s findfont %2.2f scalefont setfont", FontName, FontSize);
}


/** Store color in PS notation. */
void Color2PS(string & Text, const RGB_Record & TextColor)
{
  Text.cat_printf("%0.2g %0.2g %0.2g setrgbcolor",
	         (float)TextColor.Red/256.0,
		 (float)TextColor.Green/256.0,
	         (float)TextColor.Blue/256.0);
}



////////////////////////////////////

vecPen::vecPen(void)
{
  LineStyle = 1;
  PenWidth = -1;
}


void vecPen::prepExport(PS_State *PSS) const
{
  if(PSS)
  {
    if(memcmp(&LineColor,&PSS->LineColor,sizeof(RGB_Record)) != 0) 
    {
      memcpy(&PSS->LineColor,&LineColor,sizeof(RGB_Record));
      PSS->dirty |= PSS_LineColor;      
    }
    if(PSS->LineStyle != LineStyle)
    {
      PSS->LineStyle = LineStyle;
      PSS->dirty |= PSS_LineStyle;
    }
    if(PenWidth>=0 && PSS->LineWidth!=PenWidth)
    {      
      PSS->LineWidth = PenWidth;
      PSS->dirty |= PSS_LineWidth;
    }
  }
}


void vecPen::AttribFromPSS(const PS_State &PSS)
{
  memcpy(&LineColor, &PSS.LineColor, sizeof(PSS.LineColor));
  LineStyle = PSS.LineStyle;
  PenWidth = PSS.LineWidth;
}


vecBrush::vecBrush(const PS_State &PSS)
{
  BrushStyle = PSS.FillPattern;
  memcpy(&FillColor, &PSS.FillColor, sizeof(PSS.FillColor));
}


void vecBrush::prepExport(PS_State *PSS) const
{
  if(PSS)
  {
    if(memcmp(&FillColor,&PSS->FillColor,sizeof(RGB_Record)) != 0) 
    {
      memcpy(&PSS->FillColor,&FillColor,sizeof(RGB_Record));
      PSS->dirty |= PSS_FillColor;
    }
    if(BrushStyle != PSS->FillPattern)
        PSS->FillPattern = BrushStyle;
  }
}


void vecBrush::AttribFromPSS(const PS_State &PSS)
{
  memcpy(&FillColor, &PSS.FillColor, sizeof(FillColor));
  BrushStyle = PSS.FillPattern;  
}


vecFont::vecFont()
{
  ConvertCpg = NULL;
  Weight = 0;
  Italic = 0;
  FontOrientation10 = 0;
  memset(&TextColor,0,sizeof(TextColor));
}


vecFont::vecFont(const PS_State &PSS)
{
  ConvertCpg = PSS.ConvertCpg;
  FontSize = PSS.FontSize;
  FontSizeW = PSS.FontSizeW;
  Weight = PSS.FontWeight;
  Italic = PSS.FontItallic;
  FontOrientation10 = PSS.FontOrientation10;
  memcpy(&TextColor, &PSS.TextColor, sizeof(TextColor));
}


void vecFont::prepExport(PS_State *PSS) const
{
  if(PSS)
  {
    if(ConvertCpg!=PSS->ConvertCpg)
    {
      PSS->ConvertCpg = ConvertCpg;
    }
    if(FontSize!=PSS->FontSize)
    {
      PSS->FontSize = FontSize;
    }
    if(Weight != PSS->FontWeight)
    {
      PSS->FontWeight = Weight;
    }
    if(Italic != PSS->FontItallic)
    {
      PSS->FontItallic = Italic;
    }
    if(FontOrientation10 != PSS->FontOrientation10)
    {
      PSS->FontOrientation10 = FontOrientation10;
    }
  }
}

////////////////////////////////////

vecTransform::vecTransform(void)
{
  CenterX = CenterY = TranslateX = TranslateY = 0;
  RotAngle = 0;
  ScaleX = ScaleY = 1;
}


bool vecTransform::ApplyTransform(string &s)
{
bool ContextSaved = false;

  if(fabs(RotAngle)>1e-10)
  {
    s.cat_printf("\ngsave\n%2.2f %2.2f translate\n%2.2f rotate\n%2.2f %2.2f translate",
		 CenterX,CenterY, RotAngle, -CenterX,-CenterY);
    ContextSaved = true;
  }

  if(fabs(TranslateX)>1e-10 || fabs(TranslateY)>1e-10)
  {
    if(!ContextSaved)
    {
      s += "\ngsave";
      ContextSaved = true;
    }
    s.cat_printf("\n%2.2f %2.2f translate", TranslateX, TranslateY);
  }

  if(fabs(1-ScaleX)>1e-3 || fabs(1-ScaleY)>1e-3)
  {
    if(!ContextSaved)
    {
      s += "\ngsave";
      ContextSaved = true;
    }
    s.cat_printf("\n%2.2f %2.2f scale", ScaleX, ScaleY);
  }

  return ContextSaved;
}


////////////////////////////////////

VectorEllipse::VectorEllipse(float iniBottomRect, float iniTopRect, float iniRightRect, float iniLeftRect)
{
 Tx = NULL;
 BottomRect = iniBottomRect;
 TopRect = iniTopRect;
 RightRect = iniRightRect;
 LeftRect = iniLeftRect; 
 bAngle = 0;
 eAngle = 360;
}


VectorEllipse::~VectorEllipse()
{
  if(Tx)
      {delete(Tx); Tx=NULL;}
}


temp_string VectorEllipse::Export2EPS(PS_State *PSS) const
{
temp_string str;

  if(PSS != NULL)
  {
    vecPen::prepExport(PSS);    
    vecBrush::prepExport(PSS);
    PS_Attr(str,PSS);
  }

  bool ContextSaved = false;
  if(Tx) 
    ContextSaved = Tx->ApplyTransform(str);

  str.cat_printf("\nnewpath"
                 "\n%2.2f %2.2f %2.2f %2.2f %d %d DrawEllipse",
			 (RightRect+LeftRect)/2, (TopRect+BottomRect)/2,
			 fabs(RightRect-LeftRect)/2, fabs(TopRect-BottomRect)/2,
			 bAngle, eAngle);

  if(BrushStyle!=0)
  {
    FillObjectPS(LeftRect,RightRect,BottomRect,TopRect,str,PSS);
  }
  else
  {    
    str += "\nstroke";
  }

  if(ContextSaved) str += "\ngrestore";

return str;
}


void VectorEllipse::Transform(const AbstractTransformXY &Tx)
{
  Tx.ApplyTransform(LeftRect, BottomRect);
  Tx.ApplyTransform(RightRect, TopRect);

  if(TopRect < BottomRect)
  {
    const float f = TopRect;
    TopRect = BottomRect;
    BottomRect = f;
  }
  if(RightRect < LeftRect)
  {
    const float f = RightRect;
    RightRect = LeftRect;
    LeftRect = f;
  }

  if(this->Tx!=NULL)
    Tx.ApplyTransform(this->Tx->CenterX, this->Tx->CenterY);
}



/////////////////////////////////////////////////////////////


VectorRectangle::VectorRectangle(float iniBottomRect, float iniTopRect, float iniLeftRect, float iniRightRect)
{
 Tx = NULL;
 BottomRect = iniBottomRect;
 TopRect = iniTopRect;
 RightRect = iniRightRect;
 LeftRect = iniLeftRect;
}


VectorRectangle::~VectorRectangle()
{
  if(Tx)
      {delete(Tx); Tx=NULL;}
}


unsigned VectorRectangle::isInside(float xp, float yp) const
{
  if(BrushStyle==0) return 0;
  if(xp>=LeftRect && xp<=RightRect && yp>=BottomRect && xp<=TopRect)
  {
    if(xp>LeftRect && xp<RightRect && yp>BottomRect && xp<TopRect) return 2;
    return 1;
  }
  return 0;  
}


temp_string VectorRectangle::Export2EPS(PS_State *PSS) const
{
temp_string str;

  if(PSS != NULL)
  {
    vecBrush::prepExport(PSS);    
    vecPen::prepExport(PSS);
    PS_Attr(str,PSS);
  }

  bool ContextSaved = false;
  if(Tx) 
    ContextSaved = Tx->ApplyTransform(str);
  
  str.cat_printf("\nnewpath\n%2.2f %2.2f moveto", LeftRect, BottomRect);
  str.cat_printf("\n%2.2f %2.2f lineto", LeftRect, TopRect);
  str.cat_printf("\n%2.2f %2.2f lineto", RightRect, TopRect);
  str.cat_printf("\n%2.2f %2.2f lineto", RightRect, BottomRect);
  str.cat_printf("\n%2.2f %2.2f lineto", LeftRect, BottomRect);

  if(BrushStyle!=0)
  {
    FillObjectPS(LeftRect,RightRect,BottomRect,TopRect,str,PSS);    
  }
  else
  {    
    str += "\nstroke";
  }  

  if(ContextSaved) str += "\ngrestore";

return str;
}


void VectorRectangle::Transform(const AbstractTransformXY &Tx)
{
  Tx.ApplyTransform(LeftRect, BottomRect);
  Tx.ApplyTransform(RightRect, TopRect);

  if(TopRect < BottomRect)
  {
    const float f = TopRect;
    TopRect = BottomRect;
    BottomRect = f;
  }
  if(RightRect < LeftRect)
  {
    const float f = RightRect;
    RightRect = LeftRect;
    LeftRect = f;
  }
}


/////////////////////////////////////////////////////////////

VectorRectangleArc::VectorRectangleArc(float iniBottomRect, float iniTopRect, 
                    float iniRightRect, float iniLeftRect, float iniHradius, float iniVradius)
                   : VectorRectangle(iniBottomRect,iniTopRect,iniRightRect,iniLeftRect)
{
  Hradius = iniHradius;
  Vradius = iniVradius;
}


void VectorRectangleArc::Transform(const AbstractTransformXY &Tx)
{
  VectorRectangle::Transform(Tx);
  Tx.ApplyTransform(Hradius, Vradius);
  if(Hradius<0) Hradius=-Hradius;
  if(Vradius<0) Vradius=-Vradius;
}


temp_string VectorRectangleArc::Export2EPS(PS_State *PSS) const
{
float Ty = 1;
bool ContextSaved = false;

  if(fabs(Hradius)<1e-5 || fabs(Vradius)<1e-5)
      return VectorRectangle::Export2EPS(PSS);

  temp_string PSData;

  if(PSS != NULL)
  {
    vecBrush::prepExport(PSS);    
    vecPen::prepExport(PSS);
    PS_Attr(PSData, PSS);
  }

  if(Tx) 
    ContextSaved = Tx->ApplyTransform(PSData);
  
  float Y_ll = BottomRect;
  float Y_ur = TopRect;
  if(fabs(Hradius-Vradius) > 1e-5)
  {	//Horizontal and Vertical radiuses are different - scale canvas
    Ty = Vradius/Hradius;
    if(!ContextSaved) PSData+="\ngsave";
    PSData.cat_printf("\n0 %2.2f translate\n1 %2.3f scale", Y_ll, 1.0/Ty);
    Y_ur -= Y_ll;
    Y_ll = 0;
    ContextSaved = true;
 }

 PSData += "\nnewpath";
 PSData.cat_printf("\n%2.2f %2.2f moveto", LeftRect+Hradius, Ty*Y_ll);
 PSData.cat_printf("\n%2.2f %2.2f %2.2f 270 0 arc", LeftRect-Hradius, Ty*Y_ll+Hradius , Hradius);
 PSData.cat_printf("\n%2.2f %2.2f %2.2f 0 90 arc", LeftRect-Hradius, Ty*Y_ur-Hradius, Hradius);
 PSData.cat_printf("\n%2.2f %2.2f %2.2f 90 180 arc", RightRect+Hradius, Ty*Y_ur-Hradius, Hradius);
 PSData.cat_printf("\n%2.2f %2.2f %2.2f 180 270 arc", RightRect+Hradius, Ty*Y_ll+Hradius, Hradius);

 PSData+="\nclosepath";
 FillObjectPS(RightRect, LeftRect, Ty*Y_ll, Ty*Y_ur,PSData,PSS);
 if(ContextSaved) PSData+="\ngrestore";
 return PSData;
}


/////////////////////////////////////////////////////////////


VectorPolygon::VectorPolygon(float *iniPoints, int iniPointCount)
{
  Points = iniPoints;
  CountPoints = iniPointCount;
  Close = false;
  Outline = true;  
}


VectorPolygon::~VectorPolygon()
{
  if(Points!=NULL)
  {
    free(Points);
    Points = NULL;
  }  
  CountPoints = 0;  
}


temp_string VectorPolygon::Export2EPS(PS_State *PSS) const
{
temp_string str;
  if(Points==NULL || CountPoints<=0) return str;

  if(PSS != NULL)
  {
    vecBrush::prepExport(PSS);
    vecPen::prepExport(PSS);    
    PS_Attr(str,PSS);
    if(PSS->PolyFillMode==1 && UpperCont!=NULL && CountPoints>=3)
    {
      unsigned IntersectCount=0;
      const VectorObject *vec = UpperCont->FirstObject;
      while(vec!=NULL)
      {
        if(vec==this) break;
        IntersectCount += vec->isInside(Points[0],Points[1]);
        if(vec==UpperCont->LastObject) break;
        vec = vec->NextObject;
      }
      if((IntersectCount & 3) == 2)
      {
        PSS->FirstTimeFix = true;	// Flip foreground to background.
      }
    }
  }
  
  const float *pEnd = Points + 2*CountPoints;
  float MinPolyX=65537,MaxPolyX=-32767,MinPolyY=65537,MaxPolyY=-32767;

  for(float *p=Points; p<pEnd; p+=2)
  {
    const float x = *p;
    const float y = p[1];
    if(x<MinPolyX) MinPolyX=x;
    if(x>MaxPolyX) MaxPolyX=x;
    if(y<MinPolyY) MinPolyY=y;
    if(y>MaxPolyY) MaxPolyY=y;

    str.cat_printf(p==Points?"\nnewpath\n%2.2f %2.2f moveto":"\n%2.2f %2.2f lineto",
		    x, y);
  }
  if(Close) str += "\nclosepath";
  
  FillObjectPS(MinPolyX,MaxPolyX,MinPolyY,MaxPolyY,str,PSS);
  
  return str;
}


void VectorPolygon::Transform(const AbstractTransformXY &Tx)
{
  if(Points==NULL || CountPoints<=0) return;

  const float *pEnd = Points + 2*CountPoints;
  for(float *p=Points; p<pEnd; p+=2)
  {  
    Tx.ApplyTransform(p[0], p[1]);
  }
}


/*unsigned VectorPolygon::isInside(float xp, float yp) const
{
  unsigned isX = isInsideX(xp,yp);
  unsigned isY = isInsideY(xp,yp);

  if(isX == isY) return isX; 
  
FILE *F;
F=fopen("R:\\dump.csv","wb");
const float *pEnd = Points + 2*(CountPoints);
for(float *p=Points; p<pEnd; p+=2)
{
  fprintf(F,"%f,%f\n", p[0], p[1]);
}
fclose(F);

  if(isX < isY) return isX;	// Trouble detected here.
  return isY;
}*/


unsigned VectorPolygon::isInside(float xp, float yp) const
{
unsigned Intersects = 0;
bool TouchLine = false;
int OriUpDn = 0;

  if(CountPoints<=2 || Points==NULL || !Close || BrushStyle==0) return 0;

  const float *pEnd = Points + 2*(CountPoints-1);
  for(float *p=Points; p<pEnd; p+=2)
  {
    if(fabs(p[1]-p[3]) < 1e-5)			// x axis segment; same y
    {
      if(fabs(p[1]-yp) < 1e-5)			// crossing the half-line
      {
        if(p[0]>xp || p[2]>xp) continue;
        if(OriUpDn==0)
        {
          if(fabs(Points[1]-pEnd[1]) >= 1e-5)
            OriUpDn = (Points[1]>pEnd[1]) ? 1 : -1;
          else
          {
            for(int idx=2*(CountPoints-2)+1; idx>2; idx-=2)
            {
              if(fabs(Points[idx]-Points[idx+2]) >= 1e-5)
              {
                OriUpDn = (Points[idx+2]>Points[idx]) ? 1 : -1;
                break;
              }
            }
          }
          TouchLine = true;
        }        
      }      
      continue;
    }    

    const float t = (yp-p[1]) / (p[3]-p[1]);
    const float xl = p[0] + (p[2]-p[0])*t;
    if(xl > xp) {TouchLine=false;continue;}	// No intersection with half-line

    if(fabs(yp-p[1])<1e-5 || fabs(yp-p[3])<1e-5) // Node intersection.
    {
      Intersects++;
      if(TouchLine)
      {
        int NewOri = (p[3]>p[1]) ? 1 : -1;
        if(NewOri!=OriUpDn)			// Up & Down touch
          Intersects -= 2;			// This is NOT exit from an area.
        TouchLine = false;
      }
      else
        TouchLine = true;

      OriUpDn = (p[3]>p[1]) ? 1 : -1;            
      continue;
    }

    TouchLine = false;
    if(t>1 || t<0) continue;			// No intersection with line segment.
    Intersects += 2;
  }

  if(fabs(pEnd[1]-Points[1]) >= 1e-5)	 	// No x axis segment
  {
    const float t = (yp-pEnd[1]) / (pEnd[1]-Points[1]);
    const float xl = pEnd[0] + t*(Points[0]-pEnd[0]);
    if(xl < xp)
    {
      if(fabs(yp-pEnd[1])<1e-5 || fabs(yp-Points[1])<1e-5) // Node intersection.
      {
        Intersects++;        
      }
      else
      {
        if(t<=1 && t>=0) 
		Intersects += 2;
      }
    }
  }

  return Intersects;
}


/*unsigned VectorPolygon::isInsideY(float xp, float yp) const
{
unsigned Intersects = 0;
bool TouchLine = false;
int OriUpDn = 0;

  if(CountPoints<=2 || Points==NULL || !Close || BrushStyle==0) return 0;

  const float *pEnd = Points + 2*(CountPoints-1);
  for(float *p=Points; p<pEnd; p+=2)
  {
    if(fabs(p[0]-p[2]) < 1e-5)			// y axis segment; same x
    {
      if(fabs(p[0]-xp) < 1e-5)			// crossing the half-line
      {
        if(p[0]>xp || p[2]>xp) continue;
        if(OriUpDn==0)
        {
          if(fabs(Points[0]-pEnd[0]) >= 1e-5)
            OriUpDn = (Points[0]>pEnd[0]) ? 1 : -1;
          else
          {
            for(int idx=2*(CountPoints-2); idx>2; idx-=2)
            {
              if(fabs(Points[idx]-Points[idx+2]) >= 1e-5)
              {
                OriUpDn = (Points[idx+2]>Points[idx]) ? 1 : -1;
                break;
              }
            }
          }
          TouchLine = true;
        }        
      }      
      continue;
    }    

    const float t = (xp-p[0]) / (p[2]-p[0]);
    const float yl = p[1] + (p[3]-p[1])*t;
    if(yl > yp) {TouchLine=false;continue;}	// No intersection with half-line

    if(fabs(xp-p[0])<1e-5 || fabs(xp-p[2])<1e-5) // Node intersection.
    {
      Intersects++;
      if(TouchLine)
      {
        int NewOri = (p[2]>p[0]) ? 1 : -1;
        if(NewOri!=OriUpDn)			// Up & Down touch
          Intersects -= 2;			// This is NOT exit from an area.
        TouchLine = false;
      }
      else
        TouchLine = true;

      OriUpDn = (p[2]>p[0]) ? 1 : -1;            
      continue;
    }

    TouchLine = false;
    if(t>1 || t<0) continue;			// No intersection with line segment.
    Intersects += 2;
  }

  if(fabs(pEnd[0]-Points[0]) >= 1e-5)	 	// No x axis segment
  {
    const float t = (xp-pEnd[0]) / (pEnd[0]-Points[0]);
    const float yl = pEnd[1] + t*(Points[1]-pEnd[1]);
    if(yl < yp)
    {
      if(fabs(xp-pEnd[0])<1e-5 || fabs(xp-Points[0])<1e-5) // Node intersection.
      {
        Intersects++;        
      }
      else
      {
        if(t<=1 && t>=0) Intersects += 2;
      }
    }
  }

  return Intersects;
}
*/



/////////////////////////////////////////////////////////////


temp_string VectorCurve::Export2EPS(PS_State *PSS) const
{
temp_string str;
float MinPolyX,MaxPolyX,MinPolyY,MaxPolyY;
bool ExtraStartup;
float *p = Points;

	// There must be at least two points for the line.
  if(Points==NULL || CountPoints<=1) return str;

  if((CountPoints-1)%3 == 0) 
  {
    ExtraStartup = true;    
  }
  else
  {
    if((CountPoints)%3!=0 || CountPoints<6) return VectorLine::Export2EPS(PSS);
    ExtraStartup = false;
    p += 2;		// point to anchor points.
  }

  if(PSS != NULL)
  {    
    vecPen::prepExport(PSS);    
    PS_Attr(str,PSS);
  }
  
  str.cat_printf("\nnewpath\n%2.2f %2.2f moveto", *p, p[1]);
  MinPolyX = MaxPolyX = *p;	// Control point
  MinPolyY = MaxPolyY = p[1];

  const float *pEnd = Points + 2*CountPoints;
  if(ExtraStartup)
  {
    for(p=Points+2; p<pEnd; p+=6)
    {
      str.cat_printf("\n%2.2f %2.2f %2.2f %2.2f %2.2f %2.2f curveto", *p, p[1], p[2], p[3], p[4], p[5]);

      if(*p<MinPolyX) MinPolyX=*p;	if(*p>MaxPolyX) MaxPolyX=*p;
      if(p[1]<MinPolyY) MinPolyY=p[1];	if(p[1]>MaxPolyY) MaxPolyY=p[1];

      if(p[2]<MinPolyX) MinPolyX=p[2];	if(p[2]>MaxPolyX) MaxPolyX=p[2];
      if(p[3]<MinPolyY) MinPolyY=p[3];	if(p[3]>MaxPolyY) MaxPolyY=p[3];

      if(p[4]<MinPolyX) MinPolyX=p[4];	if(p[4]>MaxPolyX) MaxPolyX=p[4];
      if(p[5]<MinPolyY) MinPolyY=p[5];	if(p[5]>MaxPolyY) MaxPolyY=p[5];
    }
  }
  else
  {
    float xt_old = Points[4];		// Next control point.
    float yt_old = Points[5];    
    for(p=Points+6; p<pEnd; p+=6)
    {
      str.cat_printf("\n%2.2f %2.2f %2.2f %2.2f %2.2f %2.2f curveto", xt_old,yt_old, *p, p[1], p[2], p[3]);

      if(*p<MinPolyX) MinPolyX=*p;	if(*p>MaxPolyX) MaxPolyX=*p;
      if(p[1]<MinPolyY) MinPolyY=p[1];	if(p[1]>MaxPolyY) MaxPolyY=p[1];

      if(p[2]<MinPolyX) MinPolyX=p[2];	if(p[2]>MaxPolyX) MaxPolyX=p[2];
      if(p[3]<MinPolyY) MinPolyY=p[3];	if(p[3]>MaxPolyY) MaxPolyY=p[3];

      xt_old = p[4];
      yt_old = p[5];  
      if(xt_old<MinPolyX) MinPolyX=xt_old;	if(xt_old>MaxPolyX) MaxPolyX=xt_old;
      if(yt_old<MinPolyY) MinPolyY=yt_old;	if(yt_old>MaxPolyY) MaxPolyY=yt_old;
    }
  }

  if(Filled && BrushStyle!=0)
  {
    str += "\nclosepath";
    FillObjectPS(MinPolyX,MaxPolyX,MinPolyY,MaxPolyY,str,PSS);
  }
  else
  {
    if(Close) str += "\nclosepath";
    str += "\nstroke"; 
  }
  return str;
}


/////////////////////////////////////////////////////////////

VectorLine::VectorLine(int iniPointCount)
{
  Close = false;
  if(iniPointCount<=0)
  {
    Points = NULL;
    CountPoints = 0;
    return;
  }
  Points = (float*)malloc(sizeof(float)*4);
  CountPoints = iniPointCount;
}


VectorLine::VectorLine(float *iniPoints, int iniPointCount)
{
  Points = iniPoints;
  CountPoints = iniPointCount;
  Close = false;
}


VectorLine::~VectorLine()
{
  if(Points!=NULL)
  {
    free(Points);
    Points = NULL;
  }  
  CountPoints = 0;  
}


temp_string VectorLine::Export2EPS(PS_State *PSS) const
{
temp_string str;
	// There must be at least two points for the line.
  if(Points==NULL || CountPoints<=1) return str;

  if(PSS != NULL)
  {    
    vecPen::prepExport(PSS);    
    PS_Attr(str,PSS);
  }

  str.cat_printf("\nnewpath\n%2.2f %2.2f moveto", *Points, Points[1]);

  float *p;
  const float *pEnd = Points + 2*CountPoints;
  for(p=Points+2; p<pEnd; p+=2)
  {
    str.cat_printf("\n%2.2f %2.2f lineto", *p, p[1]);
  }
  if(Close) str += "\nclosepath";
  str += "\nstroke"; 
  return str;
}


void VectorLine::Transform(const AbstractTransformXY &Tx)
{
  if(Points==NULL || CountPoints<=0) return;

  const float *pEnd = Points + 2*CountPoints;
  for(float *p=Points; p<pEnd; p+=2)
  {  
    Tx.ApplyTransform(p[0], p[1]);
  }
}


/////////////////////////////////////////////////////////////


void PsBlob::FeaturesEPS(unsigned char & Feature) const
{
  if(Blob==NULL) return;
  if(StrStr(Blob,"DrawEllipse")!=NULL) Feature |= EPS_DrawElipse;
  if(StrStr(Blob,"accentshow")!=NULL)  Feature |= EPS_accentshow;
  if(StrStr(Blob,"ACCENTSHOW")!=NULL)  Feature |= EPS_ACCENTSHOW;
}


/////////////////////////////////////////////////////////////


VectorList::VectorList(void)
{
  FirstObject = LastObject = NULL;
  VectorObjects = 0;
}


VectorList::~VectorList()
{
VectorObject *IterObject = FirstObject;

  while(IterObject!=NULL)
  {
    VectorObject *ErasedObject = IterObject;
    IterObject = IterObject->NextObject;
    delete(ErasedObject);
  }
  
  FirstObject = LastObject = NULL;
  VectorObjects = 0;
}


temp_string VectorList::Export2EPS(PS_State *PSS) const
{
temp_string str;
VectorObject *IterObject = FirstObject;

  while(IterObject!=NULL)
  {
    str += IterObject->Export2EPS(PSS);
    IterObject = IterObject->NextObject;
  }
  return str;
}


void VectorList::FeaturesEPS(unsigned char & Feature) const
{
VectorObject *IterObject = FirstObject;

  while(IterObject!=NULL)
  {
    IterObject->FeaturesEPS(Feature);
    IterObject = IterObject->NextObject;
  }

}


void VectorList::Transform(const AbstractTransformXY &Tx)
{
VectorObject *IterObject = FirstObject;
  while(IterObject!=NULL)
  {
    IterObject->Transform(Tx);
    IterObject = IterObject->NextObject;
  }
}


void VectorList::AddObject(VectorObject *NewObj)
{
  if(NewObj==NULL || NewObj==this) return;

  NewObj->UpperCont = this;
  if(FirstObject==NULL)
  {
    FirstObject = LastObject = NewObj;
  }
  else
  {
   LastObject->NextObject = NewObj;
   LastObject = NewObj;
  }
  VectorObjects++;
}


void VectorList::Export2EPS(FILE *F, PS_State *PSS) const
{
VectorObject *IterObject = FirstObject;
  if(F==NULL) return;
  while(IterObject!=NULL)
  {
    temp_string StrTmp(IterObject->Export2EPS(PSS));
    if(StrTmp.length() > 0)
      fputs(StrTmp(), F);
    IterObject = IterObject->NextObject;
  }
}

/////////////////////////////////////////////////////////////


VectorImage::VectorImage(VectorList &VecList, const PS_State & NewPSS)
{
  PSS = NewPSS;
  UsageCount = 0;
  FirstObject = VecList.FirstObject;	VecList.FirstObject = NULL;
  LastObject = VecList.LastObject;	VecList.LastObject = NULL;
  VectorObjects = VecList.VectorObjects; VecList.VectorObjects = 0;

  VectorObject *IterObject = FirstObject;
  while(IterObject!=NULL)
  {
    IterObject->UpperCont = this;
    IterObject = IterObject->NextObject;
  }
}


VectorImage::VectorImage()
{
  UsageCount=0;
  memset(&PSS, 0, sizeof(PSS));
}


/////////////////////////////////////////////////////////////


TextContainer::TextContainer()
{
  Text = NULL;
  TextObjects = 0;
  FontOrientation = 0;
  RotCenterX = RotCenterY = 0;
}


TextContainer::~TextContainer()
{
  if(Text!=NULL)
  {
    for(int i=0; i<TextObjects; i++)
    {
      if(Text[i] != NULL)
      {
        delete(Text[i]);
        Text[i] = NULL;
      }
    }
    free(Text);
    Text = NULL;
  }  
  TextObjects = 0;
}


/// Append show only when needed.
void AppendShow(string &str)
{
  int len = length(str);
  if(len==0) return;
  if(len>=2)
    {
    if(str[len-2]=='(' && str[len-1]==')')
      {
      if(len>=3 && str[len-3]==' ') len--;	//Remove orphaned space.
      str = copy(str,0,len-2);
      return;
      }
    }
  str += "\nshow";
}


temp_string TextContainer::Export2EPS(PS_State *PSS) const
{
temp_string str;
RGB_Record BkTextColor;
float CurPosX = PosX;
float CurPosY = PosY;
const bool TextRotated = fabs(NormAngleDEG(FontOrientation)) > 0.01;

  if(Text!=NULL && TextObjects>0)
  {
    if(PSS!=NULL)
    {    
      PS_Attr(str,PSS);   
      memcpy(&BkTextColor,&PSS->LineColor,sizeof(RGB_Record));
    }
    if(TextRotated)
    {
      str += "\ngsave";
      str.cat_printf("\n%2.2f %2.2f translate", CurPosX+RotCenterX, CurPosY+RotCenterY);
      str.cat_printf("\n%g rotate", FontOrientation);
      str.cat_printf("\n%2.2f %2.2f translate", -CurPosX-RotCenterX, -CurPosY-RotCenterY);
    }

    for(int i=0; i<TextObjects; i++)
    {
      if(Text[i]!=NULL)
      {
        FixPsAccent(Text[i]->contents,"\\'{","(\\302)");	// acute
        FixPsAccent(Text[i]->contents,"\\v{","(\\317)");	// charon
        FixPsAccent(Text[i]->contents,"\\r{","(\\312)");	// ring
        FixPsAccent(Text[i]->contents,"\\`{","(\\301)");	// grave
        FixPsAccent2(Text[i]->contents,"\\accent39","( \\47)"); //insert space before apostrophe \\47
        
        if(i>0) AppendShow(str);

        if(PSS!=NULL)
        {
          if(memcmp(&Text[i]->TextColor,&PSS->LineColor,sizeof(RGB_Record)) != 0) 
          {			// EPS does not handle separatelly text color and line color.
            PSS->dirty |= PSS_LineColor;
            memcpy(&PSS->LineColor,&Text[i]->TextColor,sizeof(RGB_Record));
            PS_Attr(str,PSS);
          }
        }

        if(i==0 || fabs(Text[i]->size-Text[i-1]->size)>1e-3 || Text[i]->TargetFont!=Text[i-1]->TargetFont)
          str.cat_printf("\n/%s findfont %.2f scalefont setfont",
                         Text[i]->TargetFont(), Text[i]->size*2.66f);

        if(i==0)
        {
          str.cat_printf("\nnewpath %2.2f %2.2f moveto", CurPosX, CurPosY);         
        }

        char *ScanS = Text[i]->contents();
	char *ScanS2;
	while((ScanS2=StrChr(ScanS,'\n')) !=  NULL)
	{
	  *ScanS2 = 0;
          //if(ScanS!=NULL && *ScanS!=0)
	  {
            str += "\n(";	
            str += ScanS;
	    str += ')';
          }
	  *ScanS2 = '\n';
	  ScanS = ScanS2 + 1;

	  CurPosY -= 1.01 * Text[i]->size*2.66f;
	  str.cat_printf("\nshow newpath %2.2f %2.2f moveto", CurPosX, CurPosY);
	}

	//if(ScanS!=NULL && *ScanS!=0)
	{
          str += "\n(";	
          str += ScanS;
	  str += ')';
        }
      }      
    }
    AppendShow(str);
    if(TextRotated)
    {    
      str += "\ngrestore";
      if(PSS)   
        memcpy(&PSS->LineColor,&BkTextColor,sizeof(RGB_Record));
    }
  }

return str;
}


void TextContainer::Transform(const AbstractTransformXY &Tx)
{
  RotCenterX += PosX;
  RotCenterY += PosY;
  Tx.ApplyTransform(RotCenterX, RotCenterY);
  Tx.ApplyTransform(PosX, PosY);
  RotCenterX -= PosX;
  RotCenterY -= PosY;
}


void TextContainer::AddText(temp_string contents, const PS_State &PSS)
{
  const char *PsFontName;  

  //printf("|%s|",contents());
  if(PSS.FontItallic)
    PsFontName = (PSS.FontWeight>=500) ? "Times-BoldItalic" : "Times-Italic";
  else
    PsFontName = (PSS.FontWeight>=500) ? "Times-Bold" : "Times-Roman";
  
  AddText(contents, PsFontName, PSS);
}


void TextContainer::AddText(temp_string contents, const char *font, const PS_State &PSS)
{
TextObject *pTobj;
  if(contents.length()<=0) return;

	// The most signifficant character replacements, ), ( and \ are special characters.
  contents = replacesubstring(contents,"(","\\(");
  contents = replacesubstring(contents,")","\\)");

  if(Text==NULL || TextObjects<=0)
  {
    Text = (TextObject**)malloc(sizeof(TextObject*));
    pTobj = Text[0] = new TextObject;
    pTobj->TargetFont = font;
    pTobj->contents = contents;
    pTobj->size = PSS.FontSize;
    //pTobj->Weight = Weight;
    memcpy(&pTobj->TextColor,&PSS.TextColor,sizeof(RGB_Record));  
    TextObjects = 1;
    return;
  }

  pTobj = Text[TextObjects-1];
  if(pTobj->TargetFont==font && fabs(pTobj->size-PSS.FontSize)<1e-4 && 
     memcmp(&PSS.TextColor,&pTobj->TextColor,sizeof(RGB_Record))==0)	//&& pTobj->Weight==Weight
  {
    pTobj->contents += contents;
    return;
  }

  TextObjects++;
  TextObject **tmp = (TextObject**)realloc(Text, (TextObjects)*sizeof(TextObject*));
  if(tmp==NULL) 
    {	// @TODO - report a memory problem.
    TextObjects--;
    return;
    }
  Text = tmp;
  pTobj = Text[TextObjects-1] = new TextObject;
  pTobj->TargetFont = font;
  pTobj->contents = contents;
  
  pTobj->size = PSS.FontSize;
    //pTobj->Weight = Weight;  
  memcpy(&pTobj->TextColor,&PSS.TextColor,sizeof(RGB_Record));  
}


bool TextContainer::isEmpty(void) const
{
  if(Text==NULL || TextObjects<=0) return true;
  for(int i=0; i<TextObjects; i++)
    {
    if(Text[i]!=NULL && Text[i]->contents.length()>0) 
      return false;
    }
  return true;
}


static void CheckAccent(unsigned char & Feature, const char *PS_text)
{
  if(PS_text==NULL) return;
  if(StrStr(PS_text,"accentshow")!=NULL)  Feature |= EPS_accentshow;
  if(StrStr(PS_text,"ACCENTSHOW")!=NULL)  Feature |= EPS_ACCENTSHOW;
}

void TextContainer::FeaturesEPS(unsigned char & Feature) const
{
  CheckAccent(Feature, Export2EPS());
}



/////////////////////////////////////////////////////////////

#include "typedfs.h"
#include "raster.h"


void Color2PS(string & Text, const RGB_Record & TextColor);


static void FixPsAccent2(string & s, const char *TexAccent, const char *PsAccent)
{
string Text2;
const char *pos;

 while((pos=strstr(s(),TexAccent)) != NULL)
   {
   if(pos>s())
     {
     if(pos[-1]=='(')
       Text2 = copy(s,0,pos-s()-1);  // Discard empty text before
     else
       {
       Text2 = copy(s,0,pos-s());
       Text2 += ") show ";	// Close previous text path
       }
     }

   pos += strlen(TexAccent);
   Text2 += PsAccent;
   Text2 += '(';   
   Text2 += *pos;	pos++;
   Text2 += ") accentshow (";     
   if(*pos!=0)
       Text2 += pos;		// Append a rest of the string.

   s = Text2;   
   }
}


/** Transfer accent to PostSctipt text.
 * @param[in,out] s		String with text to be fixed.
 * @param[in]	TexAccent	Accent prefix in LaTeX.
 * @param[in]   PsAccent	Accent equivalent in PS.*/
static void FixPsAccent(string & s, const char *TexAccent, const char *PsAccent)
{
string Text2;
const char *pos;

 while((pos=strstr(s(),TexAccent)) != NULL)
   {
   bool CharIsUpper = false;
   if(pos>s())
     {
     if(pos[-1]=='(')
       Text2 = copy(s,0,pos-s()-1);  // Discard empty text before
     else
       {
       Text2 = copy(s,0,pos-s());
       Text2 += ") show ";	// Close previous text path
       }
     }

   if(Text2.length()<=0 && PsAccent!=NULL && PsAccent[0]=='(')
     Text2 += PsAccent + 1;
   else
     Text2 += PsAccent;
   Text2 += "(";
   pos += 3;
   while(*pos!='}')
     {
     if(*pos==0) break;
     if(isupper(*pos)) CharIsUpper = true;
     Text2 += *pos++;
   }
   if(CharIsUpper) Text2 += ") ACCENTSHOW";
              else Text2 += ") accentshow";
   Text2 += " (";
   if(*pos!=0)
     Text2 += pos+1;		// Append a rest of the string.

   s = Text2;
   }
}


int LineCountTxt(const char *Txt)
{
int LineCount = 1;

  while((Txt=strchr(Txt,'\n')) != NULL)
  {
    Txt++;
    LineCount++;
  }
  return LineCount;
}


/** Convert accents to PS equivalents. */
static void FixPsSymbol(string & s, const char *TeXSymb, const char PSsymb,
                        const string &CurrentFont, const string &SymbolFont)
{
string Text2;
const char *pos;

 while((pos=strstr(s(),TeXSymb)) != NULL)
   {
   if(pos>s())
     {
     if(pos[-1]=='(')
       Text2 = copy(s,0,pos-s()-1);  // Discard empty text before
     else
       {
       Text2 = copy(s,0,pos-s());
       Text2 += ") show ";	// Close previous text path
       }
     }
   else
     Text2 = ") show ";

   Text2 += SymbolFont;
   Text2 += "(";
   Text2 += PSsymb;
   Text2 += ") show\n";

   Text2 += CurrentFont;
   pos += StrLen(TeXSymb);

   Text2 += " (";   
   if(*pos!=0)
     Text2 += pos;		// Append a rest of the string.

   s = Text2;
   }
}


/// Emit Postscript command for line type change.
void PS_Attr(string & PSData, PS_State *PSS)
{
 if(PSS->dirty & PSS_LineColor)
   {
   PSData.cat_printf("\n%0.2g %0.2g %0.2g setrgbcolor",
	(float)PSS->LineColor.Red/256.0,
	(float)PSS->LineColor.Green/256.0,
	(float)PSS->LineColor.Blue/256.0);
   PSS->dirty &= ~PSS_LineColor;
   }
 if(PSS->dirty & PSS_LineStyle)
   {
   switch(PSS->LineStyle)
	{
	case 2:PSData+="\n[12 4]0"; break;
	case 3:PSData+="\n[2 2]0"; break;
	case 4:PSData+="\n[8 3 2 3]0"; break;
	case 5:PSData+="\n[8 8]0"; break;
	case 6:PSData+="\n[6 2 2 2 2 2]0"; break;
	case 7:PSData+="\n[4 4]0"; break;
	case 8:PSData+="\n[8 2 3 2]0"; break;
	case 9:PSData+="\n[14 2]0"; break;
	case 10:PSData+="\n[1 1]0"; break;
	case 11:PSData+="\n[1 3]0"; break;
	case 12:PSData+="\n[3 5]0"; break;
	case 13:PSData+="\n[8 2 4 2]0"; break;
	case 14:PSData+="\n[2 2 10 2]0"; break;
	case 15:PSData+="\n[10 2 2 2]0"; break;
	case 16:PSData+="\n[10 2 2 2 2 2]0"; break;	//type No13 in wpg2
	case 17:PSData+="\n[12 4 12 4 4 4]0"; break;	//type No14 in wpg2
	case 18:PSData+="\n[12 2 12 2 3 2 3 2]0"; break;//type No15 in wpg2
	case 19:PSData+="\n[1 2 1 2 1 5]0"; break;	//type No7 in wpg2
	case 1:
	//case 0: //no line
	default:PSData+="\n[]0";
	}
   PSData+=" setdash";
   PSS->dirty &= ~PSS_LineStyle;
   }
 if(PSS->dirty & PSS_LineWidth)
  {
  PSData.cat_printf("\n%2.2f setlinewidth",PSS->LineWidth);
  PSS->dirty &= ~PSS_LineWidth;
  }
}


void FillObjectPS(float MinPolyX, float MaxPolyX, float MinPolyY, float MaxPolyY, string & PSData, PS_State *PSS)
{
string tmp, color;
float Distance;

  if(PSS==NULL)
  {
    PSData += "\nstroke";		//draw frame line
    return;
  }

  if(PSS->FirstTimeFix &&
     PSS->FillColor.Red == PSS->FillBackground.Red &&
     PSS->FillColor.Green == PSS->FillBackground.Green &&
     PSS->FillColor.Blue == PSS->FillBackground.Blue)
    {
    PSS->FillColor.Red = ~PSS->FillColor.Red;
    PSS->FillColor.Green = ~PSS->FillColor.Green;
    PSS->FillColor.Blue = ~PSS->FillColor.Blue;
    PSS->FirstTimeFix = false;
    }

  color.printf("\n%0.2g %0.2g %0.2g setrgbcolor",
		(float)PSS->FillColor.Red/256.0,
	 	(float)PSS->FillColor.Green/256.0,
		(float)PSS->FillColor.Blue/256.0);

  switch(PSS->FillPattern)
		{
		case 1:if(PSS->LineStyle!=0)
			 PSData+="\ngsave";
		       PSData+=color;
		       PSData+="\nfill";
		       if(PSS->LineStyle!=0)
			 PSData+=" grestore";
		       else
			 PSS->dirty |= PSS_LineColor;
		       break;
		case 2:Distance = 2.8;
		       goto FINALDiagonal;
		case 3:Distance = 5.7;
		       goto FINALDiagonal;
		case 4:Distance = 11.3;
FINALDiagonal:	       tmp.printf("\n%2.2f %2.1f %2.2f {%2.2f moveto %2.2f %2.2f rlineto}",  // Diagonal up
			       MinPolyX-(MaxPolyY-MinPolyY),Distance,MaxPolyX,MinPolyY,
			       MaxPolyY-MinPolyY, MaxPolyY-MinPolyY  );
		       goto FINALLY;
		case 5:Distance=2.8;
		       goto FINALCross;
		case 6:Distance=5.7;
		       goto FINALCross;
		case 7:Distance=11.3;
FINALCross:	       tmp.printf("\n%2.2f %2.1f %2.2f {%2.2f moveto %2.2f %2.2f rlineto}for",
			       MinPolyX-(MaxPolyY-MinPolyY),Distance,MaxPolyX,MinPolyY,
			       MaxPolyY-MinPolyY, MaxPolyY-MinPolyY );
			PSData+='\n';
			if(PSS->LineStyle!=0) PSData+="gsave";
			PSData+=" clip newpath 0 setlinewidth\n";
			PSData+=color;
			PSData+=tmp;
			tmp.printf("\n%2.2f %2.1f %2.2f {%2.2f moveto %2.2f %2.2f rlineto}",
			       MinPolyX,Distance,(MaxPolyX+(MaxPolyY-MinPolyY)),MinPolyY,
			       -(MaxPolyY-MinPolyY), MaxPolyY-MinPolyY  );
			goto FINALLY2;
		case 8: Distance=2;
			goto FINALVertical;
		case 9: Distance=4;
			goto FINALVertical;
		case 10:Distance=8;
FINALVertical:		tmp.printf("\n%2.2f %2.1f %2.2f {%2.2f moveto 0 %2.2f rlineto}",
			       MinPolyX,Distance,MaxPolyX,MinPolyY,
			       (MaxPolyY-MinPolyY));
FINALLY:		PSData+='\n';
			if(PSS->LineStyle!=0) PSData+="gsave";
			PSData += " clip newpath 0 setlinewidth\n";
			PSData += color;
FINALLY2:		PSData += tmp;
			PSData += "for\nstroke";
			if(PSS->LineStyle!=0) PSData+=" grestore";
			break;
		case 11:Distance=16;
FINALDot:		tmp.printf("\n[0.5 %2.1f] 0 setdash\n %2.2f %2.1f %2.2f {%2.2f moveto 0 %2.2f rlineto %2.2f %2.2f rmoveto 0 %2.2f rlineto}",
			       Distance-0.5,
			       MinPolyX,Distance,MaxPolyX,
			       MinPolyY,
			       MaxPolyY-MinPolyY,
			       Distance/2.0,-Distance/2.0 - (MaxPolyY-MinPolyY),
			       (MaxPolyY-MinPolyY)+Distance/2.0  );
			goto FINALLY;
		case 12:Distance=8;
			goto FINALDot;
		case 13:Distance=4;
			goto FINALDot;
		case 14:Distance=2;
			goto FINALDot;
		case 15:Distance=1;
			goto FINALDot;

		case 100:Distance=2.8;
FinalDiagDown:		tmp.printf("\n%2.2f %2.1f %2.2f {%2.2f moveto %2.2f %2.2f rlineto}",
			       MinPolyX,Distance,(MaxPolyX+(MaxPolyY-MinPolyY)), MinPolyY,
			       -(MaxPolyY-MinPolyY), MaxPolyY-MinPolyY);
			goto FINALLY;
		case 101:Distance = 5.7;
			goto FinalDiagDown;

		case 102:Distance=2;
FINALHorizontal:	 tmp.printf("\n%2.2f %2.1f %2.2f {%2.2f exch moveto %2.2f 0 rlineto}",
			       MinPolyY,Distance,MaxPolyY, MinPolyX,
			       (MaxPolyX-MinPolyX));
			goto FINALLY;
		case 103:Distance=4;
			goto FINALHorizontal;

		case 104:Distance=2;
FINALHrzCross:		 tmp.printf("\n%2.2f %2.1f %2.2f {%2.2f exch moveto %2.2f 0 rlineto}for",
			       MinPolyY,Distance,MaxPolyY, MinPolyX,
			       (MaxPolyX-MinPolyX));
			PSData+='\n';
			if(PSS->LineStyle!=0) PSData+="gsave";
			PSData+=" clip newpath 0 setlinewidth\n";
			PSData+=color;
			PSData+=tmp;
			tmp.printf("\n%2.2f %2.1f %2.2f {%2.2f moveto 0 %2.2f rlineto}",
			       MinPolyX,Distance,MaxPolyX, MinPolyY,
			       (MaxPolyY-MinPolyY));
			goto FINALLY2;
		case 105:Distance=4;
			goto FINALHrzCross;

		}

  if(PSS->LineStyle!=0)
    PSData+="\nstroke";		//draw frame line
}
