/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
#define SS_MAKKA true
#include "swindow/SGEngine.h"
#include "swindow/SImage.h"
#include "stoolkit/SBinHashtable.h"
#include "stoolkit/SProperties.h"
#include "math.h"


static bool cacheOn=true;

typedef SBinHashtable<SImage*> SEImageCache;
static SEImageCache imageCache;

static unsigned int cacheSize = 100;
static unsigned int cacheCount = 0;

#define SS_MITER_ALLOW 2
#define FILL_REPLAY_LINEWIDTH  0.5 
#define STROKE_REPLAY_LINEWIDTH 0.25

static void clearImageCache();

/**
 * After this size things won't be cached.
 */
void
SGEngine::setCacheSize(unsigned int size)
{
  cacheSize = size;
}

/**
 * turn on/off the cache and clear it
 */
void
SGEngine::setCacheOn (bool on)
{
  cacheOn=on;
  clearImageCache();
}
/**
 * @author: Gaspar Sinai <gaspar@yudit.org>
 * @version: 2000-04-23
 * This is the abstract widget toolkit package
 */

/**
 * This class is meant to be the base class of canvases
 * @param windingrule is true if nonzero winding areas should
 * be filled instead of nonzero intersects.
 */
SGEngine::SGEngine(bool windingrule)
{
  SS_Matrix2D m;
  matrix.append (m);
  setup (1.0);
  winding = windingrule;
}

SGEngine::~SGEngine ()
{
}

/** 
 * start a new path.
 * @param _pen is the pen to draw with. it controls oversampling.
 * by experience the best and most efficient pen is size 1.0
 * that give you a 2x2 oversampling.
 * @param _lw is the linewidth
 */
void
SGEngine::_newpath (double _lw)
{
  pathNow.clear();
  pathVector.clear();
  setup (_lw);
  unfinished =false;
}

/**
 * set up scanning parameters
 * By experience the best pen is the 0.5-pen
 */
void
SGEngine::setup(double linewidth)
{
  minx = 0.0;
  miny = 0.0;
  maxx = 0.0;
  maxy = 0.0;
  unfinished = false;
  undelta = 0.0;
  unx = 0.0;
  uny = 0.0;
  if (linewidth > 2.0)
  {
    scancount = 1;
    oversample = 1;
    epsylon = 1;
  }
  else
  {
    scancount = 2;
    if (linewidth <= 0.125)
    {
      oversample = 5;
      epsylon = 0.125;
    }
    else if (linewidth <= 0.25)
    {
      oversample = 4;
      epsylon = 0.25;
    }
    else if (linewidth <= 0.5)
    {
      oversample = 3;
      epsylon = 0.5;
    }
    else
    {
      oversample = 2;
      epsylon = 0.5;
    }
  }
  //colors = scancount * (oversample * oversample) +1;
  //colors = (oversample * oversample) +1;
  colors = (oversample * oversample) +1;
}

void
SGEngine::_moveto (double _x, double _y)
{
  if (unfinished)
  {
    linetoInternal (unx, uny);
    unfinished = false;
  }

  SS_Matrix2D m = matrix[matrix.size()-1];
  double x = m.x0 * _x + m.y0 * _y + m.t0;
  double y = m.x1 * _x + m.y1 * _y + m.t1;

  if (pathVector.size()==0 && pathNow.size()==0)
  {
    minx=x ; miny=y;
    maxx=x ; maxy=y;
  }
  if (pathNow.size())
  {
    pathVector.append (pathNow);
  }
  pathNow.clear();
  pathNow.append(x);
  pathNow.append(y);
  if (x < minx) minx=x;
  if (x > maxx) maxx=x;
  if (y < miny) miny=y;
  if (y > maxy) maxy=y;
}

void
SGEngine::_lineto (double _x, double _y)
{
  SS_Matrix2D m = matrix[matrix.size()-1];
  double x = m.x0 * _x + m.y0 * _y + m.t0;
  double y = m.x1 * _x + m.y1 * _y + m.t1;

  double px0 = pathNow[pathNow.size()-2];
  double py0 = pathNow[pathNow.size()-1];
  double d2 = (x - px0) * (x - px0) + (y - py0) * (y - py0);

  if (d2<epsylon)
  {
    if (!unfinished || ((unfinished && undelta < d2) || SS_MAKKA))
    {
      undelta = d2;
      unx = x;
      uny = y;
      unfinished = true;
    }
  }
  else
  {
    linetoInternal (x, y);
    unfinished=false;
  }
}

/**
 * Cubic bezier curve. first two points are the control points 
 * last point is the endpoint. Statring point is on the stack.
 */
void
SGEngine::_curveto (double _x0, double _y0, double _x1, double _y1, 
  double _x2, double _y2)
{
  SS_Matrix2D m = matrix[matrix.size()-1];
  double x0 = m.x0 * _x0 + m.y0 * _y0 + m.t0;
  double y0 = m.x1 * _x0 + m.y1 * _y0 + m.t1;

  double x1 = m.x0 * _x1 + m.y0 * _y1 + m.t0;
  double y1 = m.x1 * _x1 + m.y1 * _y1 + m.t1;

  double x2 = m.x0 * _x2 + m.y0 * _y2 + m.t0;
  double y2 = m.x1 * _x2 + m.y1 * _y2 + m.t1;

  /* If the area is less than one we draw the lines */
  double px0 = pathNow[pathNow.size()-2];
  double py0 = pathNow[pathNow.size()-1];
  curvetoInternal (px0, py0, x0, y0, x1, y1, x2, y2, 0);
}
void
SGEngine::_closepath()
{
  SS_Matrix2D m = matrix[matrix.size()-1];
  if (unfinished)
  {
    linetoInternal (unx, uny);
    unfinished = false;
  }
  if (pathNow.size () < 2) return;
  double x0 = pathNow[0];
  double y0 = pathNow[1];
  pathNow.append(x0);
  pathNow.append(y0);
  if (pathNow.size()) pathVector.append (pathNow);
  pathNow.clear();
}

/**
 * TODO: This routine is not yet implemented
 */
SImage*
SGEngine::_stroke(int _x, int _y, unsigned int  _width, 
unsigned int  _height, const SPen& pen)
{
/*
  if (unfinished)
  {
    linetoInternal (unx, uny);
    unfinished = false;
  }
*/
  if (_height == 0 || _width == 0) {
    return 0;
  }
//fprintf (stderr, "HEIGHT=%d\n", _height);
  bool offScreen = false;
#if 0
  if (unfinished)
  {
    linetoInternal (unx, uny);
    unfinished = false;
  }
#endif
  unsigned int lines = 0;
  for (unsigned int a=0; a<pathVector.size(); a++)
    for (unsigned int b=0; b+3<pathVector[a].size(); b=b+2) lines++;
  if (lines==0) return 0;
    

  // Mitter can be bad.
  double cminx =  minx - pen.getLineWidth() * SS_MITER_ALLOW ;
  if (int (cminx) < _x) 
  {
    cminx = double (_x);
    offScreen = true;
  }
  double cminy =  miny - pen.getLineWidth() * SS_MITER_ALLOW ;
  if (int (cminy) < _y) 
  {
    cminy = double (_y);
    offScreen = true;
  }

  int origoX=(int) cminx;
  int origoY=(int) cminy;

  // FIXME : this should be same for fill (x)
  double cmaxx = maxx+1.0 + pen.getLineWidth() * SS_MITER_ALLOW ;
  if (cmaxx > double (int(cminx) + int(_width)))
  {
    cmaxx = double (int(cminx) + int(_width));
    offScreen = true;
  }

  double cmaxy = maxy+1.0 + pen.getLineWidth() * SS_MITER_ALLOW ;
  if (cmaxy > double (int(cminy) + int (_height)))
  {
    cmaxy = double (int(cminy)+ int(_height));
    offScreen = true;
  }

  int cwidth = (int) cmaxx - (int) origoX;
  int cheight = (int) cmaxy - (int) origoY;
  unsigned int width = (cwidth < 0) ? 1 : cwidth;
  unsigned int height = (cheight < 0) ? 1 : cheight;

  if (height == 0 || width == 0) return 0;
  if (cwidth < 0 || cheight < 0)
  {
    offScreen = true;
  }

/*
  fprintf (stderr, "XXXXX width=%u height=%u origo=%d,%d\n",
    width, height, origoX, origoY);
*/

  unsigned int i;
  unsigned int imageSize = width *height;

  SS_WORD32 *image = new SS_WORD32[imageSize];
  CHECK_NEW (image);
  memset (image, 0, imageSize * sizeof (SS_WORD32));

  for (unsigned int i=0; i<pathVector.size(); i++)
  {
    for (unsigned int j=0; j+3<pathVector[i].size(); j=j+2)
    {
      const double* v0 = &pathVector[i].array()[j];
      
      strokeScan (image,  width, height, v0, 
        j+5 < pathVector[i].size(), origoX, origoY, pen, true);
/*
      double x0 = pathVector[i][j];
      double y0 = pathVector[i][j+1];
      double x1 = pathVector[i][j+2];
      double y1 = pathVector[i][j+3];
*/
    }
 }
  for (i=0; i<imageSize; i++)
  {
    SS_WORD32 bits = image[i];
    SS_WORD32 cnt = 0;
    while (bits)
    {
      if (bits&1) cnt++;
      bits = bits >> 1;
    }
    /* count how many bits we have */
    image[i] = cnt;
  }
 
  /**
   * copy the resulting image
   */
  SImage* im = new SImage (image, colors, origoX, 
        origoY, width, height);
  CHECK_NEW (im);
  im->px = (int)newpathX;
  im->py = (int)newpathY;
  im->offScreen = offScreen;
  return im;
}

void
SGEngine::strokeScan (SS_WORD32 *image, int width, int height, 
   const double* vectors, bool join, double origoX, double origoY,
  const SPen& pen, bool horizontal) 
  {
    double dirX = vectors[2] - vectors[0];
    double dirY = vectors[3] - vectors[1];
    // 90 degrees
    double length = sqrt (dirX*dirX + dirY*dirY);
    if (length < epsylon/100.0) {
        return;
    } 

    double penw = pen.getLineWidth() ;
 //   length, oversample, dirX, dirY);
    // Unit vector
/*
    double dir90UnitX = -dirY / length;
    double dir90UnitY = dirX / length;
*/

    // Other direction

    double unitX = dirX / length;
    double unitY = dirY / length;
    double inc = 0.5 / oversample;
    double penr2 = (penw/2) * (penw/2);

    for (double t0 = 0.0; t0<=length; t0=t0+inc) {
        double x0 = vectors[0] + t0  * unitX; 
        double y0 = vectors[1] + t0  * unitY; 
        for (double tx =-penw/2.0; tx<=penw/2.0; tx=tx+inc) {
            for (double ty =-penw/2.0; ty<=penw/2.0; ty=ty+inc) {
                double x = x0 + tx;
                double y = y0 + ty;
                int indXOver = (int) ((x-(double)origoX) * (double)oversample);
                int indYOver = (int) ((y-(double)origoY) * (double)oversample);
                if (tx*tx + ty*ty > penr2) continue;
                int indX = indXOver/oversample;
                int indY = indYOver/oversample;

                int bit = ((indXOver % oversample) * oversample) 
                    + (indYOver % oversample);

                if (indX < 0 || indY < 0) continue;              
                if (indX >= width || indY >= height) continue;              
                image[indX + indY * width] |= (1 << bit);
            }
        }
    }
}


SImage*
SGEngine::_fill(int _x, int _y, unsigned int _width, unsigned int _height)
{
  if (_height == 0 || _width == 0) return 0;
//fprintf (stderr, "HEIGHT=%d\n", _height);
  bool offScreen = false;
  if (unfinished)
  {
    linetoInternal (unx, uny);
    unfinished = false;
  }
  unsigned int lines = 0;
  for (unsigned int a=0; a<pathVector.size(); a++)
    for (unsigned b=0; b+3<pathVector[a].size(); b=b+2) lines++;
  if (lines==0) return 0;

  /* Set clipping here */
  double cminx =  minx;
  if (int (minx) < _x) 
  {
    cminx = double (_x);
    offScreen = true;
  }
  double cminy =  miny;
  if (int (miny) < _y) 
  {
    cminy = double (_y);
    offScreen = true;
  }

  int origoX=oversample * (int) cminx;
  int origoY=oversample * (int) cminy;

  double cmaxx = maxx+1.0;
  if (maxx > double (int(cminx) + int(_width)))
  {
    cmaxx = double (int(cminx) + int(_width));
    offScreen = true;
  }

  double cmaxy = maxy+1.0;
  if (maxy > double (int(cminy) + int (_height)))
  {
    cmaxy = double (int(cminy)+ int(_height));
    offScreen = true;
  }

  int cwidth = oversample * (int)  cmaxx - (int) origoX;
  int cheight = oversample * (int) cmaxy - (int) origoY;

  unsigned int width = (cwidth < 0) ? oversample : cwidth;
  unsigned int height = (cheight < 0) ? oversample : cheight;

//  fprintf (stderr, "miny=%g maxy=%g origoY=%d cmaxy=%g cminy=%g cheight=%d height=%d _height=%d\n",
//      miny, maxy, origoY, cmaxy, cminy, cheight, height, _height);

  if (height == 0 || width == 0) return 0;

  if (cwidth < 0 || cheight < 0)
  {
    offScreen = true;
  }

  unsigned int i;
  unsigned int imageSize = width/oversample *height/oversample;

  SS_WORD32 *image = new SS_WORD32[imageSize];
  CHECK_NEW (image);
  memset (image, 0, imageSize * sizeof (SS_WORD32));
 
  /**
   * scan the lines horizontally 
   */ 
  SS_InterSection** scanHoriz = new SS_InterSection* [height];
  CHECK_NEW (scanHoriz);

  for (i=0; i<height; i++)
  {
    scanHoriz[i] = new SS_InterSection();
    CHECK_NEW (scanHoriz[i]);
  }
  scan (scanHoriz, origoX, origoY, (int) height, false);
  render (image, scanHoriz, width, height, false); 
  for (i=0; i<height; i++)
  {
    delete scanHoriz[i];
    scanHoriz[i] = 0;
  }
  delete[] scanHoriz;

  /**
   * scan the lines vertically 
   */ 
  if (scancount > 0)
  {
    SS_InterSection** scanVert = new SS_InterSection* [width];
    CHECK_NEW (scanVert);
    for (i=0; i<width; i++)
    {
      scanVert[i] = new SS_InterSection();
      CHECK_NEW (scanVert[i]);
    }
    scan (scanVert, origoY, origoX, (int) width, true);
    render (image, scanVert, height, width, true); 

    for (i=0; i<width; i++)
    {
      delete scanVert[i];
      scanVert[i] = 0;
    }
    delete[] scanVert;
  }
  for (i=0; i<imageSize; i++)
  {
    SS_WORD32 bits = image[i];
    SS_WORD32 cnt = 0;
    while (bits)
    {
      if (bits&1) cnt++;
      bits = bits >> 1;
    }
    /* count how many bits we have */
    image[i] = cnt;
  }
  /**
   * copy the resulting image
   */
  SImage* im = new SImage (image, colors, origoX/oversample, 
        origoY/oversample, width/oversample, height/oversample);
  CHECK_NEW (im);
  im->offScreen = offScreen;
  return im;
}

void
SGEngine::_pushmatrix()
{
  if (matrix.size() > 10) {
    fprintf (stderr, "pushpmatrix size-%u\n", matrix.size());
    return;
  }
  SS_Matrix2D m = matrix[matrix.size()-1];
  matrix.append (m);
}

void
SGEngine::_popmatrix()
{
//  fprintf (stderr, "Popmatrix size-%u\n", matrix.size());
  if (matrix.size())
  {
    matrix.truncate(matrix.size()-1);
  }
}

/**
 * TODO: not implemented 
 */
void
SGEngine::_rotate (double angle)
{
  SS_Matrix2D m = matrix[matrix.size()-1];
  m.rotate (angle);
  popmatrix ();
  matrix.append (m);
}

/**
 * immaediate action.
 */
void
SGEngine::_scale (double x, double y)
{
  SS_Matrix2D m = matrix[matrix.size()-1];
  m.scale (x, y);
  popmatrix ();
  matrix.append (m);
}

void
SGEngine::_translate (double x, double y)
{
  SS_Matrix2D m = matrix[matrix.size()-1];
  m.translate (x, y);
  popmatrix ();
  matrix.append (m);
}


/**
 * The following section contains the guts of the engine: the rendering
 */

/**
 * This routine is called when no coordnate transform is needed
 */
void
SGEngine::curvetoInternal (double x0, double y0, double x1, 
  double y1, double x2, double y2, double x3, double y3, int rec)
{
  /* http://www.cs.wpi.edu/~matt/courses/cs563/talks/curves.html */
  double dist2 = (x3 - x0) * (x3 - x0) + (y3 - y0) * (y3 - y0);

  // Tune this. 1.0 should be fine.
  if (dist2 < 1.0)
  {
    if (unfinished)
    {
      double px0 = pathNow[pathNow.size()-2];
      double py0 = pathNow[pathNow.size()-1];
      double d2 = (x3 - px0) * (x3 - px0) + (y3 - py0) * (y3 - py0);
      if (d2 > epsylon)
      {
        linetoInternal (x3, y3);
        unfinished = false;
      }
      else
      {
        if (undelta < d2 || SS_MAKKA)
        {
          undelta = d2;
          unx = x3;
          uny = y3;
        }
      }
    }
    else
    {
      unfinished = true;
      unx = x3;
      uny = y3;
    }
    return;
  }

  /* divide it up into two sub-sections */
  double qx0 = x0; double qy0 = y0;

  double qx1 = (x0 + x1)/2.0;
  double qy1 = (y0 + y1)/2.0;

  double qx2 = qx1/2.0 + (x1 + x2) / 4.0;
  double qy2 = qy1/2.0 + (y1 + y2) / 4.0;

  double rx3 = x3;
  double ry3 = y3;

  double rx2 = (x2 + x3) / 2.0;
  double ry2 = (y2 + y3) / 2.0; 

  double rx1 = (x1 + x2) / 4.0 + rx2 / 2.0;
  double ry1 = (y1 + y2) / 4.0 + ry2 / 2.0;

  double qx3 = (qx2 + rx1) / 2.0;
  double qy3 = (qy2 + ry1) / 2.0;

  double rx0 = qx3;
  double ry0 = qy3; 
 
  curvetoInternal (qx0, qy0, qx1, qy1, qx2, qy2, qx3, qy3, rec);
  curvetoInternal (rx0, ry0, rx1, ry1, rx2, ry2, rx3, ry3, rec);
}


/**
 * This routine is called when no coordnate transform is needed
 */
void
SGEngine::linetoInternal (double x, double y)
{
  pathNow.append(x);
  pathNow.append(y);
  if (x < minx) minx=x;
  if (x > maxx) maxx=x;
  if (y < miny) miny=y;
  if (y > maxy) maxy=y;
}

/**
 * can the curves into the buffer
 * If winding is true use the non-zero winding rule rather that
 * intersect rule.
 * @param inter in the allocated  and initialisez intersection array
 * @param offsetx is the offset in internal buffer
 * @param offsety is the offset in scanline.
 * @parm height is the height of the internal buffer 
 * @param xy is 0 if x is x y is y 1 vice versa.
 */
void
SGEngine::scan (SS_InterSection** intersBuff, 
  int ox, int oy, int height, bool swap)
{
  if (winding)
  {
    scanWinding (intersBuff, ox, oy, height, swap);
  }
  else
  {
    scanCrosses (intersBuff, ox, oy, height, swap);
  }
}

/**
 * Scan the curves into the buffer using nonzero winding rule.
 * @param inter in the allocated  and initialisez intersection array
 * @param offsetx is the offset in internal buffer
 * @param offsety is the offset in scanline.
 * @parm height is the height of the internal buffer 
 * @param xy is 0 if x is x y is y 1 vice versa.
 */
void
SGEngine::scanWinding (SS_InterSection** intersBuff, 
  int ox, int oy, int height, bool swap)
{
  int* crossBuff = new int[height];
  CHECK_NEW (crossBuff);

  double is;
  double diff;

  unsigned int swapindx = (swap) ? 1 : 0;
  unsigned int swapindy = (swap) ? 0 : 1;
  /* all paths */
  unsigned int i;
  unsigned int j;
  SS_InterSection** clockwise = new SS_InterSection*[(unsigned int)height];
  CHECK_NEW (clockwise);
  for (i=0; i<(unsigned int)height; i++)
  {
    clockwise[i] = new SS_InterSection();
    CHECK_NEW (clockwise[i]);
  }
  for (i=0; i<pathVector.size(); i++)
  {
    memset (crossBuff, 0, height * sizeof (int));
    /* one path */
    for (j=0; j+3<pathVector[i].size(); j=j+2)
    {
      bool lastone = (j+3+2>=pathVector[i].size());
      /* Blur the image by half a grid to get a better contour */
      double x0 = pathVector[i][j+swapindx] * (double) oversample  
           + 0.5 - (double) ox ;
      double y0 = pathVector[i][j+swapindy] * (double) oversample 
           + 0.5 - (double) oy ;
      double x1 = pathVector[i][j+swapindx+2] * (double) oversample
           + 0.5 - (double) ox ;
      double y1 = pathVector[i][j+swapindy+2] * (double) oversample 
           + 0.5 - (double) oy ;

      int from = (int) y0;
      int to = (int) y1;
      int increment = (y0 > y1) ? -1 : 1;

      if (increment > 0)
      {
        if (from >= height || to < 0)
        {
          continue;
        }
        if (from < 0) from = 0;
        if (to >= height) to  = height-1;
        diff = y1 - y0;
      }
      else
      {
        if (to >= height || from < 0)
        {
          continue;
        }
        if (to < 0) to = 0;
        if (from >= height) from  = height-1;
        diff = y0 - y1;
      }
      /* scan betbeen  y0..y1 */
      for (int k=from; ; k+=increment)
      {
         double cline = double (k);
         bool crosses  = (increment > 0) 
               ? (cline >= y0 && cline < y1) 
               : (cline >= y1 && cline < y0);

         int lastCross = crossBuff[k];
         if (lastCross == 0)
         {
           lastCross =  -increment;
         }

         if (increment * lastCross > 0 || !crosses)
         {
            if (k==to) break;
            continue;
         }
         crossBuff[k] = increment;

         if (diff < 0.1)
         {
           is = x0;
         }
         else
         {
           is = x0 + (x1 - x0) * (cline - y0) / (y1-y0);
         }
         unsigned int pos = intersBuff[k]->appendSorted ((int)is);
         clockwise[k]->insert (pos, increment);
         /*
          * This happened if we got inside/out it all wrong
          * Mainly happens when we get a straight line first and we
          * think is is positive.
          */
         if (lastone && (intersBuff[k]->size() & 1)!=0)
         {
           intersBuff[k]->remove (0);
           clockwise[k]->remove (0);
         }
         if (k==to) break;
      }
    }
  }
  
  for (i=0; i<(unsigned int)height; i++)
  {
    int swinding = 0;
    for (j=0; j<intersBuff[i]->size(); )
    {
      int oldwin = swinding;
      swinding += clockwise[i]->peek(j);
      if (oldwin==0 || swinding==0)
      {
         j++; continue;
      }
      /* remove the ones that don't go from/to zero */
      intersBuff[i]->remove (j);
      clockwise[i]->remove (j);
    }
    delete clockwise[i];
    clockwise[i] = 0;
  }
  delete[] crossBuff;
  delete[] clockwise;
}

/**
 * Scan the curves into the buffer using crosses rule
 * @param inter in the allocated  and initialisez intersection array
 * @param offsetx is the offset in internal buffer
 * @param offsety is the offset in scanline.
 * @parm height is the height of the internal buffer 
 * @param xy is 0 if x is x y is y 1 vice versa.
 */
void
SGEngine::scanCrosses (SS_InterSection** intersBuff, 
  int ox, int oy, int height, bool swap)
{
  int* crossBuff = new int[height];
  CHECK_NEW (crossBuff);

  double is;
  double diff;

  unsigned int swapindx = (swap) ? 1 : 0;
  unsigned int swapindy = (swap) ? 0 : 1;
  /* all paths */
  for (unsigned int i=0; i<pathVector.size(); i++)
  {
    memset (crossBuff, 0, height * sizeof (int));
    /* one path */
    for (unsigned j=0; j+3<pathVector[i].size(); j=j+2)
    {
      bool lastone = (j+3+2>=pathVector[i].size());
      /* Blur the image by half a grid to get a better contour */
      double x0 = pathVector[i][j+swapindx] * (double) oversample  
           + 0.5 - (double) ox ;
      double y0 = pathVector[i][j+swapindy] * (double) oversample 
           + 0.5 - (double) oy ;
      double x1 = pathVector[i][j+swapindx+2] * (double) oversample
           + 0.5 - (double) ox ;
      double y1 = pathVector[i][j+swapindy+2] * (double) oversample 
           + 0.5 - (double) oy ;

      int from = (int) y0;
      int to = (int) y1;
      int increment = (y0 > y1) ? -1 : 1;

      if (increment > 0)
      {
        if (from >= height || to < 0)
        {
          continue;
        }
        if (from < 0) from = 0;
        if (to >= height) to  = height-1;
        diff = y1 - y0;
      }
      else
      {
        if (to >= height || from < 0)
        {
          continue;
        }
        if (to < 0) to = 0;
        if (from >= height) from  = height-1;
        diff = y0 - y1;
      }
      /* scan betbeen  y0..y1 */
      for (int k=from; ; k+=increment)
      {
         double cline = double (k);
         bool crosses  = (increment > 0) 
               ? (cline >= y0 && cline < y1) 
               : (cline >= y1 && cline < y0);

         int lastCross = crossBuff[k];
         if (lastCross == 0)
         {
           lastCross =  -increment;
         }

         if (increment * lastCross > 0 || !crosses)
         {
            if (k==to) break;
            continue;
         }
         crossBuff[k] = increment;

         if (diff < 0.1)
         {
           is = x0;
         }
         else
         {
           is = x0 + (x1 - x0) * (cline - y0) / (y1-y0);
         }
         intersBuff[k]->appendSorted ((int)is);
         /*
          * This happened if we got inside/out it all wrong
          * Mainly happens when we get a straight line first and we
          * think is is positive.
          */
         if (lastone && (intersBuff[k]->size() & 1)!=0)
         {
           intersBuff[k]->remove (0);
         }
         if (k==to) break;
      }
    }
  }
  delete[] crossBuff;
}

void
SGEngine::render (SS_WORD32* image, SS_InterSection** intersBuff, 
 unsigned int width, unsigned int height, bool swap)
{
  int first;
  int next;

  /* These are the array increments x, y */
  int muxx = (swap) ?  1 : width/oversample;
  int muxy = (swap) ?  height/oversample : 1;

  //for (unsigned i=0; i<height; i=i+SD_OVERSAMPLE)
  SS_WORD32 ovs2 = oversample * oversample;
  for (unsigned i=0; i<height; i++)
  {
    //for (unsigned int j=0; j<SD_OVERSAMPLE; j++)
    {
      int lastx = -1;
      if (intersBuff[i]->size() == 0) continue;

      for (unsigned int k=0; k+1<intersBuff[i]->size(); k=k+2)
      {
        first = intersBuff[i]->peek (k);
        next =  intersBuff[i]->peek (k+1);

        if (first < 0) first = 0;
        if (lastx < first)  lastx=first; 
        if (next+1 >= (int) width) next = ((int) width)-1;

        while (lastx<=next)
        {
           SS_WORD32 ind = muxy*(lastx/oversample) + muxx*((i)/oversample);
           SS_WORD32 mask = (swap) 
                   ? 1 << (((i) + oversample * lastx) % ovs2)
                   : 1 << (((i) * oversample + lastx) % ovs2);
           SS_WORD32 vle = image[ind];
           vle = vle | mask;
           image[ind] = vle;
           lastx++;
        }
      }
    }
  }
}

/**
 * create a new path.
 * if an image exists that has the same id, it will be
 * moved to x, y and returned.  You shoulld delete the image
 * aftwerwards. 
 *
 * IMPORTANT: it is the creator of the id that actually
 * is responsible of distinguishing different penWidth values.
 *
 * If the image with id is not in cache, it will return 0.
 * @param id is the unique id of the image.
 * @param x is the x offset
 * @param y is the y offset
 */
bool
SGEngine::beginImage (int x, int y, const SString& id)
{
  primitive.clear();
  pathVector.clear();
  images.clear();
  newpathID = id;
  newpathX = x;
  newpathY = y;
  /* with a bit of a luck we have it in the cache */
  if (id.size() && imageCache.get (id) != 0)
  {
    return true;
  }
  return false;
}

SImage*
SGEngine::endImage () {
    primitive.clear();
    pathVector.clear();
    SImage* ret = (SImage*) imageCache.get (newpathID);
    if (ret)
    {
        SImage * im = new SImage(*ret);
        im->setOrigoX (im->getOrigoX() + (int)newpathX - im->px);
        im->setOrigoY (im->getOrigoY() + (int)newpathY - im->py);
        return im;
    }
    if (images.size() == 0) return 0;
    // FIXME: merge all images.
    SImage currentLayer (*images[0]);
    delete images[0];
    for (unsigned int i=1; i<images.size(); i++) {
        currentLayer = currentLayer.addLayer ((*images[i]));
        delete images[i];
    } 
    images.clear();
    ret = new SImage (currentLayer);
    if (ret->offScreen)
    {
        //fprintf (stderr, "refuse to put off screen image into the cache\n");
    }

    ret->compress ();
    if (cacheOn && !ret->offScreen && newpathID.size() > 0) 
    {
        cacheCount++;
        if (cacheCount > cacheSize)
        {
            fprintf (stderr, "SGEngine:: clearing cache (%u elements)\n",cacheCount);
            clearImageCache();
            cacheCount = 1;
        }
// makka
        imageCache.put (newpathID, new SImage (*ret));
    //fprintf (stderr, "putting screen image %*.*s into the cache\n",
     //      SSARGS(newpathID));
    }
    return ret;
}

void
SGEngine::newpath ()
{
  pathVector.clear();
  /* take a first hand look. */
  SGPrimitive p;
  p.newpath();
  primitive.append (p);
}

/**
 * Stroke and fill resets the machinesry and returns the rendered image
 * @param x is the x corner
 * @param x is the y corner
 * @param width is the desired width
 * @param height is the desired height
 * @param lw is the lineWidth (if value is less than zero - subpixel *) 
 */
void 
SGEngine::stroke(int x, int y, unsigned int width, unsigned int height, 
  const SPen& pen)
{
  //double lw = pen.getLineWidth();
  SGPrimitive p; 
  p.stroke(x, y, width, height);
  primitive.append (p);
  _replay(STROKE_REPLAY_LINEWIDTH);
  if (unfinished)
  {
    linetoInternal (unx, uny);
    unfinished = false;
  }
  if (pathNow.size()) pathVector.append (pathNow);
  pathNow.clear();

  SImage* ret = _stroke (x, y, width, height, pen);
  if (ret) {
    SImage colsi = ret->colorize(pen.getForeground());
    delete ret;
    ret = new SImage(colsi);
    images.append (ret);
  }
}

void
SGEngine::fill (int x, int y, unsigned int width, unsigned int height, 
  const SPen& pen)
{
  double lw = pen.getLineWidth();
  SImage* si = fillInternal (x, y, width, height, lw);
  //fprintf  (stderr, "Image Layer appended %lx\n", (unsigned long) ret);
  if (si) {
    SImage colsi = si->colorize(pen.getForeground());
    delete si;
    si = new SImage(colsi);
    images.append (si);
  }
}

/**
 * Stroke and fill resets the machinesry and returns the rendered image
 * @param x is the x corner
 * @param x is the y corner
 * @param width is the desired width
 * @param height is the desired height
 * @return the resulting image. Put it in the cache with 'newpathID' if
 * newpathID is not "" and the image is not off the screen.
 */
SImage* 
SGEngine::fillInternal (int x, int y, unsigned int width, unsigned int height, 
  double lw)
{
  SGPrimitive p; p.fill (x, y, width, height); primitive.append (p);

  if (newpathID.size()==0)
  {
    //fprintf (stderr, "screen image %*.*s cacheOn=%d\n",
     //      SSARGS(newpathID), (int)cacheOn);
    _replay(FILL_REPLAY_LINEWIDTH);
    SImage* ret = _fill (x, y, width, height);
    pathVector.clear();
    return ret;
  }

  /*--------- no luck with cached image. -------------*/    
  _replay(FILL_REPLAY_LINEWIDTH);
  SImage *ii = _fill (x, y, width, height);
  if (ii==0) return 0;

  ii->px = (int)newpathX;
  ii->py = (int)newpathY;

//fprintf (stderr, "created one with newpathX=%d, newpathY=%d\n",
 //  (int)newpathX, (int)newpathY);
  pathVector.clear();
  return ii;
}

/**
 * Go through the primitives and replay them
 */
void 
SGEngine::_replay (double lineWidth)
{
  for (unsigned int i=0; i<primitive.size(); i++)
  {
    SGPrimitive p (primitive[i]);
    switch (p.type)
    {
    case SGPrimitive::CURVETO:
      _curveto (p.params[0], p.params[1],
             p.params[2], p.params[3], 
             p.params[4], p.params[5]);
      break;
    case SGPrimitive::NEWPATH:
      _newpath (lineWidth);
      unfinished =false;
      break;
    case SGPrimitive::MOVETO:
      _moveto (p.params[0], p.params[1]);
      break;
    case SGPrimitive::LINETO:
      _lineto (p.params[0], p.params[1]);
      break;
    case SGPrimitive::TRANSLATE:
      _translate (p.params[0], p.params[1]);
      break;
    case SGPrimitive::SCALE:
      _scale (p.params[0], p.params[1]);
      break;
    case SGPrimitive::CLOSEPATH:
      _closepath ();
      break;
    case SGPrimitive::PUSHMATRIX:
      _pushmatrix ();
      break;
    case SGPrimitive::POPMATRIX:
      _popmatrix ();
      break;
    case SGPrimitive::ROTATE:
      _rotate (p.params[0]);
      break;
    default:
      break;
    }
  }
}


void
SGEngine::moveto (double x, double y)
{
  SGPrimitive p; p.moveto(x,y);
  primitive.append (p);
}

void
SGEngine::lineto (double x, double y)
{
  SGPrimitive p; p.lineto(x,y);
  primitive.append (p);
}

void
SGEngine::curveto (double x0, double y0, double x1, double y1, double x2, double y2)
{
  SGPrimitive p; p.curveto(x0,y0,x1,y1,x2,y2);
  primitive.append (p);
}

void
SGEngine::closepath()
{
  SGPrimitive p; p.closepath();
  primitive.append (p);
}


void
SGEngine::pushmatrix()
{
  // need this because scale is not a primitive...
  _pushmatrix();
  SGPrimitive p; p.pushmatrix();
  primitive.append (p);
}

void
SGEngine::popmatrix()
{
  // need this because scale is not a primitive...
  _popmatrix();
  SGPrimitive p; p.popmatrix();
  primitive.append (p);
}

void
SGEngine::scale (double x, double y)
{
  _scale (x,y);
  //SGPrimitive p; p.scale(x,y);
  //primitive.append (p);
}

void
SGEngine::translate (double x, double y)
{
   _translate (x, y);

  //SGPrimitive p; p.translate(x,y);
  //primitive.append (p);
}

void
SGEngine::rotate (double angle)
{
   _rotate (angle);
  //SGPrimitive p; p.rotate(angle);
  //primitive.append (p);
}

/**
 * This routine is not supposed to be used extensively. This is
 * to check the current matrix.
 */
SS_Matrix2D
SGEngine::getCurrentMatrix() const
{
  return SS_Matrix2D(matrix[matrix.size()-1]);
}

static void clearImageCache() {
    for (unsigned int i=0; i<imageCache.size(); i++)
    {
        for (unsigned int j=0; j<imageCache.size(i); j++)
        {
            SImage* e = imageCache.get (i, j);
            if (e != 0) delete e;
        }
    }
    imageCache.clear ();
    cacheCount = 0;
}

