//========================================================================
//
// SplashClip.cc
//
//========================================================================

#include <config.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <stdlib.h>
#include <string.h>
#include "goo/gmem.h"
#include "SplashErrorCodes.h"
#include "SplashPath.h"
#include "SplashXPath.h"
#include "SplashXPathScanner.h"
#include "SplashBitmap.h"
#include "SplashClip.h"

//------------------------------------------------------------------------
// SplashClip.flags
//------------------------------------------------------------------------

#define splashClipEO       0x01	// use even-odd rule

//------------------------------------------------------------------------
// SplashClip
//------------------------------------------------------------------------

SplashClip::SplashClip(SplashCoord x0, SplashCoord y0,
		       SplashCoord x1, SplashCoord y1,
		       GBool antialiasA) {
  antialias = antialiasA;
  if (x0 < x1) {
    xMin = x0;
    xMax = x1;
  } else {
    xMin = x1;
    xMax = x0;
  }
  if (y0 < y1) {
    yMin = y0;
    yMax = y1;
  } else {
    yMin = y1;
    yMax = y0;
  }
  xMinI = splashFloor(xMin);
  yMinI = splashFloor(yMin);
  xMaxI = splashFloor(xMax);
  yMaxI = splashFloor(yMax);
  paths = NULL;
  flags = NULL;
  scanners = NULL;
  length = size = 0;
}

SplashClip::SplashClip(SplashClip *clip) {
  int i;

  antialias = clip->antialias;
  xMin = clip->xMin;
  yMin = clip->yMin;
  xMax = clip->xMax;
  yMax = clip->yMax;
  xMinI = clip->xMinI;
  yMinI = clip->yMinI;
  xMaxI = clip->xMaxI;
  yMaxI = clip->yMaxI;
  length = clip->length;
  size = clip->size;
  paths = (SplashXPath **)gmallocn(size, sizeof(SplashXPath *));
  flags = (Guchar *)gmallocn(size, sizeof(Guchar));
  scanners = (SplashXPathScanner **)
                 gmallocn(size, sizeof(SplashXPathScanner *));
  for (i = 0; i < length; ++i) {
    paths[i] = clip->paths[i]->copy();
    flags[i] = clip->flags[i];
    scanners[i] = new SplashXPathScanner(paths[i], flags[i] & splashClipEO);
  }
}

SplashClip::~SplashClip() {
  int i;

  for (i = 0; i < length; ++i) {
    delete paths[i];
    delete scanners[i];
  }
  gfree(paths);
  gfree(flags);
  gfree(scanners);
}

void SplashClip::grow(int nPaths) {
  if (length + nPaths > size) {
    if (size == 0) {
      size = 32;
    }
    while (size < length + nPaths) {
      size *= 2;
    }
    paths = (SplashXPath **)greallocn(paths, size, sizeof(SplashXPath *));
    flags = (Guchar *)greallocn(flags, size, sizeof(Guchar));
    scanners = (SplashXPathScanner **)
                   greallocn(scanners, size, sizeof(SplashXPathScanner *));
  }
}

void SplashClip::resetToRect(SplashCoord x0, SplashCoord y0,
			     SplashCoord x1, SplashCoord y1) {
  int i;

  for (i = 0; i < length; ++i) {
    delete paths[i];
    delete scanners[i];
  }
  gfree(paths);
  gfree(flags);
  gfree(scanners);
  paths = NULL;
  flags = NULL;
  scanners = NULL;
  length = size = 0;

  if (x0 < x1) {
    xMin = x0;
    xMax = x1;
  } else {
    xMin = x1;
    xMax = x0;
  }
  if (y0 < y1) {
    yMin = y0;
    yMax = y1;
  } else {
    yMin = y1;
    yMax = y0;
  }
  xMinI = splashFloor(xMin);
  yMinI = splashFloor(yMin);
  xMaxI = splashFloor(xMax);
  yMaxI = splashFloor(yMax);
}

SplashError SplashClip::clipToRect(SplashCoord x0, SplashCoord y0,
				   SplashCoord x1, SplashCoord y1) {
  if (x0 < x1) {
    if (x0 > xMin) {
      xMin = x0;
      xMinI = splashFloor(xMin);
    }
    if (x1 < xMax) {
      xMax = x1;
      xMaxI = splashFloor(xMax);
    }
  } else {
    if (x1 > xMin) {
      xMin = x1;
      xMinI = splashFloor(xMin);
    }
    if (x0 < xMax) {
      xMax = x0;
      xMaxI = splashFloor(xMax);
    }
  }
  if (y0 < y1) {
    if (y0 > yMin) {
      yMin = y0;
      yMinI = splashFloor(yMin);
    }
    if (y1 < yMax) {
      yMax = y1;
      yMaxI = splashFloor(yMax);
    }
  } else {
    if (y1 > yMin) {
      yMin = y1;
      yMinI = splashFloor(yMin);
    }
    if (y0 < yMax) {
      yMax = y0;
      yMaxI = splashFloor(yMax);
    }
  }
  return splashOk;
}

SplashError SplashClip::clipToPath(SplashPath *path, SplashCoord *matrix,
				   SplashCoord flatness, GBool eo) {
  SplashXPath *xPath;

  xPath = new SplashXPath(path, matrix, flatness, gTrue);

  // check for an empty path
  if (xPath->length == 0) {
    xMax = xMin - 1;
    yMax = yMin - 1;
    xMaxI = splashFloor(xMax);
    yMaxI = splashFloor(yMax);
    delete xPath;

  // check for a rectangle
  } else if (xPath->length == 4 &&
	     ((xPath->segs[0].x0 == xPath->segs[0].x1 &&
	       xPath->segs[0].x0 == xPath->segs[1].x0 &&
	       xPath->segs[0].x0 == xPath->segs[3].x1 &&
	       xPath->segs[2].x0 == xPath->segs[2].x1 &&
	       xPath->segs[2].x0 == xPath->segs[1].x1 &&
	       xPath->segs[2].x0 == xPath->segs[3].x0 &&
	       xPath->segs[1].y0 == xPath->segs[1].y1 &&
	       xPath->segs[1].y0 == xPath->segs[0].y1 &&
	       xPath->segs[1].y0 == xPath->segs[2].y0 &&
	       xPath->segs[3].y0 == xPath->segs[3].y1 &&
	       xPath->segs[3].y0 == xPath->segs[0].y0 &&
	       xPath->segs[3].y0 == xPath->segs[2].y1) ||
	      (xPath->segs[0].y0 == xPath->segs[0].y1 &&
	       xPath->segs[0].y0 == xPath->segs[1].y0 &&
	       xPath->segs[0].y0 == xPath->segs[3].y1 &&
	       xPath->segs[2].y0 == xPath->segs[2].y1 &&
	       xPath->segs[2].y0 == xPath->segs[1].y1 &&
	       xPath->segs[2].y0 == xPath->segs[3].y0 &&
	       xPath->segs[1].x0 == xPath->segs[1].x1 &&
	       xPath->segs[1].x0 == xPath->segs[0].x1 &&
	       xPath->segs[1].x0 == xPath->segs[2].x0 &&
	       xPath->segs[3].x0 == xPath->segs[3].x1 &&
	       xPath->segs[3].x0 == xPath->segs[0].x0 &&
	       xPath->segs[3].x0 == xPath->segs[2].x1))) {
    clipToRect(xPath->segs[0].x0, xPath->segs[0].y0,
	       xPath->segs[2].x0, xPath->segs[2].y0);
    delete xPath;

  } else {
    grow(1);
    if (antialias) {
      xPath->aaScale();
    }
    xPath->sort();
    paths[length] = xPath;
    flags[length] = eo ? splashClipEO : 0;
    scanners[length] = new SplashXPathScanner(xPath, eo);
    ++length;
  }

  return splashOk;
}

GBool SplashClip::test(int x, int y) {
  int i;

  // check the rectangle
  if (x < xMinI || x > xMaxI || y < yMinI || y > yMaxI) {
    return gFalse;
  }

  // check the paths
  if (antialias) {
    for (i = 0; i < length; ++i) {
      if (!scanners[i]->test(x * splashAASize, y * splashAASize)) {
	return gFalse;
      }
    }
  } else {
    for (i = 0; i < length; ++i) {
      if (!scanners[i]->test(x, y)) {
	return gFalse;
      }
    }
  }

  return gTrue;
}

SplashClipResult SplashClip::testRect(int rectXMin, int rectYMin,
				      int rectXMax, int rectYMax) {
  // This tests the rectangle:
  //     x = [rectXMin, rectXMax + 1)    (note: rect coords are ints)
  //     y = [rectYMin, rectYMax + 1)
  // against the clipping region:
  //     x = [xMin, xMax]                (note: clipping coords are fp)
  //     y = [yMin, yMax]
  if ((SplashCoord)(rectXMax + 1) <= xMin || (SplashCoord)rectXMin > xMax ||
      (SplashCoord)(rectYMax + 1) <= yMin || (SplashCoord)rectYMin > yMax) {
    return splashClipAllOutside;
  }
  if ((SplashCoord)rectXMin >= xMin && (SplashCoord)(rectXMax + 1) <= xMax &&
      (SplashCoord)rectYMin >= yMin && (SplashCoord)(rectYMax + 1) <= yMax &&
      length == 0) {
    return splashClipAllInside;
  }
  return splashClipPartial;
}

SplashClipResult SplashClip::testSpan(int spanXMin, int spanXMax, int spanY) {
  int i;

  // This tests the rectangle:
  //     x = [spanXMin, spanXMax + 1)    (note: span coords are ints)
  //     y = [spanY, spanY + 1)
  // against the clipping region:
  //     x = [xMin, xMax]                (note: clipping coords are fp)
  //     y = [yMin, yMax]
  if ((SplashCoord)(spanXMax + 1) <= xMin || (SplashCoord)spanXMin > xMax ||
      (SplashCoord)(spanY + 1) <= yMin || (SplashCoord)spanY > yMax) {
    return splashClipAllOutside;
  }
  if (!((SplashCoord)spanXMin >= xMin && (SplashCoord)(spanXMax + 1) <= xMax &&
	(SplashCoord)spanY >= yMin && (SplashCoord)(spanY + 1) <= yMax)) {
    return splashClipPartial;
  }
  if (antialias) {
    for (i = 0; i < length; ++i) {
      if (!scanners[i]->testSpan(spanXMin * splashAASize,
				 spanXMax * splashAASize + (splashAASize - 1),
				 spanY * splashAASize)) {
	return splashClipPartial;
      }
    }
  } else {
    for (i = 0; i < length; ++i) {
      if (!scanners[i]->testSpan(spanXMin, spanXMax, spanY)) {
	return splashClipPartial;
      }
    }
  }
  return splashClipAllInside;
}

void SplashClip::clipAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y) {
  int xx0, xx1, xx, yy, i;
  SplashColorPtr p;

  // zero out pixels with x < xMin
  xx0 = *x0 * splashAASize;
  xx1 = splashFloor(xMin * splashAASize);
  if (xx1 > aaBuf->getWidth()) {
    xx1 = aaBuf->getWidth();
  }
  if (xx0 < xx1) {
    xx0 &= ~7;
    for (yy = 0; yy < splashAASize; ++yy) {
      p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3);
      for (xx = xx0; xx + 7 < xx1; xx += 8) {
	*p++ = 0;
      }
      if (xx < xx1) {
	*p &= 0xff >> (xx1 & 7);
      }
    }
    *x0 = splashFloor(xMin);
  }

  // zero out pixels with x > xMax
  xx0 = splashFloor(xMax * splashAASize) + 1;
  if (xx0 < 0) {
    xx0 = 0;
  }
  xx1 = (*x1 + 1) * splashAASize;
  if (xx0 < xx1) {
    for (yy = 0; yy < splashAASize; ++yy) {
      p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3);
      xx = xx0;
      if (xx & 7) {
	*p &= 0xff00 >> (xx & 7);
	xx = (xx & ~7) + 8;
	++p;
      }
      for (; xx < xx1; xx += 8) {
	*p++ = 0;
      }
    }
    *x1 = splashFloor(xMax);
  }

  // check the paths
  for (i = 0; i < length; ++i) {
    scanners[i]->clipAALine(aaBuf, x0, x1, y);
  }
}
