[poppler] Branch 'xpdf303merge' - poppler/GfxState.cc poppler/GfxState.h poppler/GlobalParams.cc poppler/GlobalParams.h poppler/GlobalParamsWin.cc poppler/PDFDoc.cc poppler/SplashOutputDev.cc poppler/SplashOutputDev.h splash/Splash.cc splash/SplashClip.cc splash/Splash.h splash/SplashState.cc splash/SplashState.h splash/SplashXPathScanner.cc splash/SplashXPathScanner.h

Albert Astals Cid aacid at kemper.freedesktop.org
Sat Jan 7 08:14:44 PST 2012


 poppler/GfxState.cc          |   94 +-
 poppler/GfxState.h           |   17 
 poppler/GlobalParams.cc      |    7 
 poppler/GlobalParams.h       |    3 
 poppler/GlobalParamsWin.cc   |    2 
 poppler/PDFDoc.cc            |    2 
 poppler/SplashOutputDev.cc   |  134 +++
 poppler/SplashOutputDev.h    |    8 
 splash/Splash.cc             | 1658 ++++++++++++++++++++++++++++++-------------
 splash/Splash.h              |   42 -
 splash/SplashClip.cc         |   57 -
 splash/SplashState.cc        |   49 +
 splash/SplashState.h         |   12 
 splash/SplashXPathScanner.cc |  474 +++++++-----
 splash/SplashXPathScanner.h  |   24 
 15 files changed, 1845 insertions(+), 738 deletions(-)

New commits:
commit 34ae382915d9d9b2b3c015fee3c24907a6b52b8b
Author: Albert Astals Cid <aacid at kde.org>
Date:   Sat Jan 7 17:14:05 2012 +0100

    xpdf303: Merge some stuff in Splash [Thomas Freitag]
    
    1. merge the complete pipe changes
    a) including the overprint implementation from Derek used by pipe
    b) including the transfer function implementation
    2. Two changes (not really a merge) to get it compiled under windows (in
    GlobalParams.cc & PDFDoc.cc)
    3. merge fill and stroke changes
    a) including merge of SplashClip.cc
    b) including merge of SplashXPathScanner.cc

diff --git a/poppler/GfxState.cc b/poppler/GfxState.cc
index 67c54b5..224b9cf 100644
--- a/poppler/GfxState.cc
+++ b/poppler/GfxState.cc
@@ -213,6 +213,7 @@ cmsHPROFILE GfxColorSpace::getDisplayProfile() {
 //------------------------------------------------------------------------
 
 GfxColorSpace::GfxColorSpace() {
+  overprintMask = 0x0f;
 }
 
 GfxColorSpace::~GfxColorSpace() {
@@ -1857,6 +1858,7 @@ GfxIndexedColorSpace::GfxIndexedColorSpace(GfxColorSpace *baseA,
   indexHigh = indexHighA;
   lookup = (Guchar *)gmallocn((indexHigh + 1) * base->getNComps(),
 			      sizeof(Guchar));
+  overprintMask = base->getOverprintMask();
 }
 
 GfxIndexedColorSpace::~GfxIndexedColorSpace() {
@@ -2049,8 +2051,29 @@ GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA,
   alt = altA;
   func = funcA;
   nonMarking = !name->cmp("None");
-}
-
+  if (!name->cmp("Cyan")) {
+    overprintMask = 0x01;
+  } else if (!name->cmp("Magenta")) {
+    overprintMask = 0x02;
+  } else if (!name->cmp("Yellow")) {
+    overprintMask = 0x04;
+  } else if (!name->cmp("Black")) {
+    overprintMask = 0x08;
+  }
+}
+
+GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA,
+						 GfxColorSpace *altA,
+						 Function *funcA,
+						 GBool nonMarkingA,
+						 Guint overprintMaskA) {
+  name = nameA;
+  alt = altA;
+  func = funcA;
+  nonMarking = nonMarkingA;
+  overprintMask = overprintMaskA;
+}
+
 GfxSeparationColorSpace::~GfxSeparationColorSpace() {
   delete name;
   delete alt;
@@ -2058,7 +2081,8 @@ GfxSeparationColorSpace::~GfxSeparationColorSpace() {
 }
 
 GfxColorSpace *GfxSeparationColorSpace::copy() {
-  return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy());
+  return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(),
+				     nonMarking, overprintMask);
 }
 
 //~ handle the 'All' and 'None' colorants
@@ -2153,15 +2177,54 @@ void GfxSeparationColorSpace::getDefaultColor(GfxColor *color) {
 // GfxDeviceNColorSpace
 //------------------------------------------------------------------------
 
-GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
-					   GfxColorSpace *altA,
-					   Function *funcA) {
+GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
+					   GooString **namesA,
+					   GfxColorSpace *altA,
+					   Function *funcA) {
+  int i;
+
   nComps = nCompsA;
   alt = altA;
   func = funcA;
-  nonMarking = gFalse;
-}
-
+  nonMarking = gTrue;
+  overprintMask = 0;
+  for (i = 0; i < nComps; ++i) {
+    names[i] = namesA[i];
+    if (names[i]->cmp("None")) {
+      nonMarking = gFalse;
+    }
+    if (!names[i]->cmp("Cyan")) {
+      overprintMask |= 0x01;
+    } else if (!names[i]->cmp("Magenta")) {
+      overprintMask |= 0x02;
+    } else if (!names[i]->cmp("Yellow")) {
+      overprintMask |= 0x04;
+    } else if (!names[i]->cmp("Black")) {
+      overprintMask |= 0x08;
+    } else {
+      overprintMask = 0x0f;
+    }
+  }
+}
+
+GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
+					   GooString **namesA,
+					   GfxColorSpace *altA,
+					   Function *funcA,
+					   GBool nonMarkingA,
+					   Guint overprintMaskA) {
+  int i;
+
+  nComps = nCompsA;
+  alt = altA;
+  func = funcA;
+  nonMarking = nonMarkingA;
+  overprintMask = overprintMaskA;
+  for (i = 0; i < nComps; ++i) {
+    names[i] = namesA[i]->copy();
+  }
+}
+
 GfxDeviceNColorSpace::~GfxDeviceNColorSpace() {
   int i;
 
@@ -2173,15 +2236,8 @@ GfxDeviceNColorSpace::~GfxDeviceNColorSpace() {
 }
 
 GfxColorSpace *GfxDeviceNColorSpace::copy() {
-  GfxDeviceNColorSpace *cs;
-  int i;
-
-  cs = new GfxDeviceNColorSpace(nComps, alt->copy(), func->copy());
-  for (i = 0; i < nComps; ++i) {
-    cs->names[i] = names[i]->copy();
-  }
-  cs->nonMarking = nonMarking;
-  return cs;
+  return new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(),
+				  nonMarking, overprintMask);
 }
 
 //~ handle the 'None' colorant
@@ -2229,7 +2285,7 @@ GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, Gfx *gfx, int recursion)
     goto err4;
   }
   obj1.free();
-  cs = new GfxDeviceNColorSpace(nCompsA, altA, funcA);
+  cs = new GfxDeviceNColorSpace(nCompsA, namesA, altA, funcA);
   cs->nonMarking = gTrue;
   for (i = 0; i < nCompsA; ++i) {
     cs->names[i] = namesA[i];
diff --git a/poppler/GfxState.h b/poppler/GfxState.h
index 95aa434..632acc1 100644
--- a/poppler/GfxState.h
+++ b/poppler/GfxState.h
@@ -227,6 +227,9 @@ public:
   // mark the page (e.g., the "None" colorant).
   virtual GBool isNonMarking() { return gFalse; }
 
+  // Return the color space's overprint mask.
+  Guint getOverprintMask() { return overprintMask; }
+
   // Return the number of color space modes
   static int getNumColorSpaceModes();
 
@@ -243,6 +246,9 @@ public:
   // result will be a cmsHPROFILE 
   static void *getDisplayProfile();
 #endif
+protected:
+
+  Guint overprintMask;
 };
 
 //------------------------------------------------------------------------
@@ -580,6 +586,10 @@ public:
 
 private:
 
+  GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA,
+			  Function *funcA, GBool nonMarkingA,
+			  Guint overprintMaskA);
+
   GooString *name;		// colorant name
   GfxColorSpace *alt;		// alternate color space
   Function *func;		// tint transform (into alternate color space)
@@ -593,7 +603,8 @@ private:
 class GfxDeviceNColorSpace: public GfxColorSpace {
 public:
 
-  GfxDeviceNColorSpace(int nCompsA, GfxColorSpace *alt, Function *func);
+  GfxDeviceNColorSpace(int nCompsA, GooString **namesA,
+		       GfxColorSpace *alt, Function *func);
   virtual ~GfxDeviceNColorSpace();
   virtual GfxColorSpace *copy();
   virtual GfxColorSpaceMode getMode() { return csDeviceN; }
@@ -617,6 +628,10 @@ public:
 
 private:
 
+  GfxDeviceNColorSpace(int nCompsA, GooString **namesA,
+		       GfxColorSpace *alt, Function *func,
+		       GBool nonMarkingA, Guint overprintMaskA);
+
   int nComps;			// number of components
   GooString			// colorant names
     *names[gfxColorMaxComps];
diff --git a/poppler/GlobalParams.cc b/poppler/GlobalParams.cc
index dc08ccc..5757acc 100644
--- a/poppler/GlobalParams.cc
+++ b/poppler/GlobalParams.cc
@@ -694,6 +694,7 @@ GlobalParams::GlobalParams(const char *customPopplerDataDir)
   screenGamma = 1.0;
   screenBlackThreshold = 0.0;
   screenWhiteThreshold = 1.0;
+  overprintPreview = gFalse;
   mapNumericCharNames = gTrue;
   mapUnknownCharNames = gFalse;
   printCommands = gFalse;
@@ -1922,6 +1923,12 @@ void GlobalParams::setScreenWhiteThreshold(double whiteThreshold)
   unlockGlobalParams;
 }
 
+void GlobalParams::setOverprintPreview(GBool overprintPreviewA) {
+  lockGlobalParams;
+  overprintPreview = overprintPreviewA;
+  unlockGlobalParams;
+}
+
 void GlobalParams::setMapNumericCharNames(GBool map) {
   lockGlobalParams;
   mapNumericCharNames = map;
diff --git a/poppler/GlobalParams.h b/poppler/GlobalParams.h
index 815269b..f4cb6c1 100644
--- a/poppler/GlobalParams.h
+++ b/poppler/GlobalParams.h
@@ -213,6 +213,7 @@ public:
   double getScreenGamma();
   double getScreenBlackThreshold();
   double getScreenWhiteThreshold();
+  GBool getOverprintPreview() { return overprintPreview; }
   GBool getMapNumericCharNames();
   GBool getMapUnknownCharNames();
   GBool getPrintCommands();
@@ -259,6 +260,7 @@ public:
   void setScreenGamma(double gamma);
   void setScreenBlackThreshold(double blackThreshold);
   void setScreenWhiteThreshold(double whiteThreshold);
+  void setOverprintPreview(GBool overprintPreviewA);
   void setMapNumericCharNames(GBool map);
   void setMapUnknownCharNames(GBool map);
   void setPrintCommands(GBool printCommandsA);
@@ -345,6 +347,7 @@ private:
   double screenGamma;		// screen gamma correction
   double screenBlackThreshold;	// screen black clamping threshold
   double screenWhiteThreshold;	// screen white clamping threshold
+  GBool overprintPreview;	// enable overprint preview
   GBool mapNumericCharNames;	// map numeric char names (from font subsets)?
   GBool mapUnknownCharNames;	// map unknown char names?
   GBool printCommands;		// print the drawing commands
diff --git a/poppler/GlobalParamsWin.cc b/poppler/GlobalParamsWin.cc
index 23ea619..5af717a 100644
--- a/poppler/GlobalParamsWin.cc
+++ b/poppler/GlobalParamsWin.cc
@@ -235,7 +235,7 @@ void GlobalParams::setupBaseFonts(char * dir)
     GetWindowsFontDir(winFontDir, sizeof(winFontDir));
 
     for (int i = 0; displayFontTab[i].name; ++i) {
-        char *fontName = displayFontTab[i].name;
+        char *fontName = (char *) displayFontTab[i].name;
         if (displayFonts->lookup(fontName))
             continue;
 
diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index ec0f9be..608b3ab 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -192,7 +192,7 @@ PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GooString *ownerPassword,
   version.dwOSVersionInfoSize = sizeof(version);
   GetVersionEx(&version);
   if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) {
-    if (_wstat(fileName2, &buf) == 0) {
+    if (_wstat(fileNameU, &buf) == 0) {
       size = buf.st_size;
     }
     file = _wfopen(fileNameU, L"rb");
diff --git a/poppler/SplashOutputDev.cc b/poppler/SplashOutputDev.cc
index 11d8036..52e2bef 100644
--- a/poppler/SplashOutputDev.cc
+++ b/poppler/SplashOutputDev.cc
@@ -1701,6 +1701,41 @@ SplashPattern *SplashOutputDev::getColor(GfxGray gray, GfxRGB *rgb) {
   return pattern;
 }
 
+void SplashOutputDev::setOverprintMask(GfxColorSpace *colorSpace,
+				       GBool overprintFlag,
+				       int overprintMode,
+				       GfxColor *singleColor) {
+#if SPLASH_CMYK
+  Guint mask;
+  GfxCMYK cmyk;
+
+  if (overprintFlag && globalParams->getOverprintPreview()) {
+    mask = colorSpace->getOverprintMask();
+    if (singleColor && overprintMode &&
+	(colorSpace->getMode() == csDeviceCMYK ||
+	 (colorSpace->getMode() == csICCBased &&
+	  colorSpace->getNComps() == 4))) {
+      colorSpace->getCMYK(singleColor, &cmyk);
+      if (cmyk.c == 0) {
+	mask &= ~1;
+      }
+      if (cmyk.m == 0) {
+	mask &= ~2;
+      }
+      if (cmyk.y == 0) {
+	mask &= ~4;
+      }
+      if (cmyk.k == 0) {
+	mask &= ~8;
+      }
+    }
+  } else {
+    mask = 0xffffffff;
+  }
+  splash->setOverprintMask(mask);
+#endif
+}
+
 void SplashOutputDev::updateBlendMode(GfxState *state) {
   splash->setBlendFunc(splashOutBlendFuncs[state->getBlendMode()]);
 }
@@ -1725,6 +1760,51 @@ void SplashOutputDev::updateOverprintMode(GfxState *state) {
   splash->setOverprintMode(state->getOverprintMode());
 }
 
+void SplashOutputDev::updateTransfer(GfxState *state) {
+  Function **transfer;
+  Guchar red[256], green[256], blue[256], gray[256];
+  double x, y;
+  int i;
+
+  transfer = state->getTransfer();
+  if (transfer[0] &&
+      transfer[0]->getInputSize() == 1 &&
+      transfer[0]->getOutputSize() == 1) {
+    if (transfer[1] &&
+	transfer[1]->getInputSize() == 1 &&
+	transfer[1]->getOutputSize() == 1 &&
+	transfer[2] &&
+	transfer[2]->getInputSize() == 1 &&
+	transfer[2]->getOutputSize() == 1 &&
+	transfer[3] &&
+	transfer[3]->getInputSize() == 1 &&
+	transfer[3]->getOutputSize() == 1) {
+      for (i = 0; i < 256; ++i) {
+	x = i / 255.0;
+	transfer[0]->transform(&x, &y);
+	red[i] = (Guchar)(y * 255.0 + 0.5);
+	transfer[1]->transform(&x, &y);
+	green[i] = (Guchar)(y * 255.0 + 0.5);
+	transfer[2]->transform(&x, &y);
+	blue[i] = (Guchar)(y * 255.0 + 0.5);
+	transfer[3]->transform(&x, &y);
+	gray[i] = (Guchar)(y * 255.0 + 0.5);
+      }
+    } else {
+      for (i = 0; i < 256; ++i) {
+	x = i / 255.0;
+	transfer[0]->transform(&x, &y);
+	red[i] = green[i] = blue[i] = gray[i] = (Guchar)(y * 255.0 + 0.5);
+      }
+    }
+  } else {
+    for (i = 0; i < 256; ++i) {
+      red[i] = green[i] = blue[i] = gray[i] = (Guchar)i;
+    }
+  }
+  splash->setTransfer(red, green, blue, gray);
+}
+
 void SplashOutputDev::updateFont(GfxState * /*state*/) {
   needFontUpdate = gTrue;
 }
@@ -2011,7 +2091,9 @@ void SplashOutputDev::stroke(GfxState *state) {
   if (state->getStrokeColorSpace()->isNonMarking()) {
     return;
   }
-  path = convertPath(state, state->getPath());
+  setOverprintMask(state->getStrokeColorSpace(), state->getStrokeOverprint(),
+		   state->getOverprintMode(), state->getStrokeColor());
+  path = convertPath(state, state->getPath(), gFalse);
   splash->stroke(path);
   delete path;
 }
@@ -2022,7 +2104,9 @@ void SplashOutputDev::fill(GfxState *state) {
   if (state->getFillColorSpace()->isNonMarking()) {
     return;
   }
-  path = convertPath(state, state->getPath());
+  setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(),
+		   state->getOverprintMode(), state->getFillColor());
+  path = convertPath(state, state->getPath(), gTrue);
   splash->fill(path, gFalse);
   delete path;
 }
@@ -2033,7 +2117,9 @@ void SplashOutputDev::eoFill(GfxState *state) {
   if (state->getFillColorSpace()->isNonMarking()) {
     return;
   }
-  path = convertPath(state, state->getPath());
+  setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(),
+		   state->getOverprintMode(), state->getFillColor());
+  path = convertPath(state, state->getPath(), gTrue);
   splash->fill(path, gTrue);
   delete path;
 }
@@ -2041,7 +2127,7 @@ void SplashOutputDev::eoFill(GfxState *state) {
 void SplashOutputDev::clip(GfxState *state) {
   SplashPath *path;
 
-  path = convertPath(state, state->getPath());
+  path = convertPath(state, state->getPath(), gTrue);
   splash->clipToPath(path, gFalse);
   delete path;
 }
@@ -2049,7 +2135,7 @@ void SplashOutputDev::clip(GfxState *state) {
 void SplashOutputDev::eoClip(GfxState *state) {
   SplashPath *path;
 
-  path = convertPath(state, state->getPath());
+  path = convertPath(state, state->getPath(), gTrue);
   splash->clipToPath(path, gTrue);
   delete path;
 }
@@ -2057,22 +2143,24 @@ void SplashOutputDev::eoClip(GfxState *state) {
 void SplashOutputDev::clipToStrokePath(GfxState *state) {
   SplashPath *path, *path2;
 
-  path = convertPath(state, state->getPath());
-  path2 = splash->makeStrokePath(path);
+  path = convertPath(state, state->getPath(), gFalse);
+  path2 = splash->makeStrokePath(path, state->getLineWidth());
   delete path;
   splash->clipToPath(path2, gFalse);
   delete path2;
 }
 
-SplashPath *SplashOutputDev::convertPath(GfxState * /*state*/, GfxPath *path) {
+SplashPath *SplashOutputDev::convertPath(GfxState *state, GfxPath *path,
+					 GBool dropEmptySubpaths) {
   SplashPath *sPath;
   GfxSubpath *subpath;
-  int i, j;
+  int n, i, j;
 
+  n = dropEmptySubpaths ? 1 : 0;
   sPath = new SplashPath();
   for (i = 0; i < path->getNumSubpaths(); ++i) {
     subpath = path->getSubpath(i);
-    if (subpath->getNumPoints() > 0) {
+    if (subpath->getNumPoints() > n) {
       sPath->moveTo((SplashCoord)subpath->getX(0),
 		    (SplashCoord)subpath->getY(0));
       j = 1;
@@ -2126,6 +2214,8 @@ void SplashOutputDev::drawChar(GfxState *state, double x, double y,
   // fill
   if (!(render & 1)) {
     if (!haveCSPattern && !state->getFillColorSpace()->isNonMarking()) {
+      setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(),
+	       state->getOverprintMode(), state->getFillColor());
       splash->fillChar((SplashCoord)x, (SplashCoord)y, code, font);
     }
   }
@@ -2135,6 +2225,10 @@ void SplashOutputDev::drawChar(GfxState *state, double x, double y,
     if (!state->getStrokeColorSpace()->isNonMarking()) {
       if ((path = font->getGlyphPath(code))) {
 	path->offset((SplashCoord)x, (SplashCoord)y);
+    setOverprintMask(state->getStrokeColorSpace(),
+		       state->getStrokeOverprint(),
+		       state->getOverprintMode(),
+		       state->getStrokeColor());
 	splash->stroke(path);
 	delete path;
       }
@@ -2264,7 +2358,7 @@ GBool SplashOutputDev::beginType3Char(GfxState *state, double x, double y,
     if (t3Font->cacheTags != NULL) {
       if ((t3Font->cacheTags[i+j].mru & 0x8000) &&
 	t3Font->cacheTags[i+j].code == code) {
-        drawType3Glyph(t3Font, &t3Font->cacheTags[i+j],
+        drawType3Glyph(state, t3Font, &t3Font->cacheTags[i+j],
 		     t3Font->cacheData + (i+j) * t3Font->glyphSize);
         return gTrue;
       }
@@ -2298,7 +2392,7 @@ void SplashOutputDev::endType3Char(GfxState *state) {
     state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3],
 		  t3GlyphStack->origCTM4, t3GlyphStack->origCTM5);
     updateCTM(state, 0, 0, 0, 0, 0, 0);
-    drawType3Glyph(t3GlyphStack->cache,
+    drawType3Glyph(state, t3GlyphStack->cache,
 		   t3GlyphStack->cacheTag, t3GlyphStack->cacheData);
   }
   t3gs = t3GlyphStack;
@@ -2431,10 +2525,12 @@ void SplashOutputDev::type3D1(GfxState *state, double wx, double wy,
   updateCTM(state, 0, 0, 0, 0, 0, 0);
 }
 
-void SplashOutputDev::drawType3Glyph(T3FontCache *t3Font,
+void SplashOutputDev::drawType3Glyph(GfxState *state, T3FontCache *t3Font,
 				     T3FontCacheTag * /*tag*/, Guchar *data) {
   SplashGlyphBitmap glyph;
 
+  setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(),
+		   state->getOverprintMode(), state->getFillColor());
   glyph.x = -t3Font->glyphX;
   glyph.y = -t3Font->glyphY;
   glyph.w = t3Font->glyphW;
@@ -2523,6 +2619,8 @@ void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
   if (state->getFillColorSpace()->isNonMarking()) {
     return;
   }
+  setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(),
+		   state->getOverprintMode(), state->getFillColor());
 
   ctm = state->getCTM();
   for (int i = 0; i < 6; ++i) {
@@ -2919,6 +3017,8 @@ void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
   Guchar pix;
   int n, i;
 
+  setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(),
+		   state->getOverprintMode(), NULL);
   ctm = state->getCTM();
   for (i = 0; i < 6; ++i) {
     if (!isfinite(ctm[i])) return;
@@ -3158,6 +3258,8 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref,
   Guchar pix;
   int n, i;
 
+  setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(),
+		   state->getOverprintMode(), NULL);
   // If the mask is higher resolution than the image, use
   // drawSoftMaskedImage() instead.
   if (maskWidth > width || maskHeight > height) {
@@ -3327,6 +3429,8 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref,
   Guchar pix;
   int n, i;
 
+  setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(),
+		   state->getOverprintMode(), NULL);
   ctm = state->getCTM();
   for (i = 0; i < 6; ++i) {
     if (!isfinite(ctm[i])) return;
@@ -3599,6 +3703,7 @@ void SplashOutputDev::paintTransparencyGroup(GfxState * /*state*/, double * /*bb
 
   // paint the transparency group onto the parent bitmap
   // - the clip path was set in the parent's state)
+  splash->setOverprintMask(0xffffffff);
   splash->composite(tBitmap, 0, 0, tx, ty,
 		    tBitmap->getWidth(), tBitmap->getHeight(),
 		    gFalse, !isolated);
@@ -3948,6 +4053,7 @@ GBool SplashOutputDev::tilingPatternFill(GfxState *state, Catalog *catalog, Obje
   matc[1] = ctm[1];
   matc[2] = ctm[2];
   matc[3] = ctm[3];
+  splash->setOverprintMask(0xffffffff);
   splash->drawImage(&tilingBitmapSrc, &imgData, colorMode, gTrue, result_width, result_height, matc);
   delete tBitmap;
   delete gfx;
@@ -4032,7 +4138,7 @@ GBool SplashOutputDev::univariateShadedFill(GfxState *state, SplashUnivariatePat
   state->lineTo(xMax, yMax);
   state->lineTo(xMin, yMax);
   state->closePath();
-  path = convertPath(state, state->getPath());
+  path = convertPath(state, state->getPath(), gTrue);
 
   retVal = (splash->shadedFill(path, pattern->getShading()->getHasBBox(), pattern) == splashOk);
   state->clearPath();
diff --git a/poppler/SplashOutputDev.h b/poppler/SplashOutputDev.h
index 49f2b12..8cfdbb1 100644
--- a/poppler/SplashOutputDev.h
+++ b/poppler/SplashOutputDev.h
@@ -264,6 +264,7 @@ public:
   virtual void updateFillOverprint(GfxState *state);
   virtual void updateStrokeOverprint(GfxState *state);
   virtual void updateOverprintMode(GfxState *state);
+  virtual void updateTransfer(GfxState *state);
 
   //----- update text state
   virtual void updateFont(GfxState *state);
@@ -389,9 +390,12 @@ private:
 #else
   SplashPattern *getColor(GfxGray gray, GfxRGB *rgb);
 #endif
-  SplashPath *convertPath(GfxState *state, GfxPath *path);
+  void setOverprintMask(GfxColorSpace *colorSpace, GBool overprintFlag,
+			int overprintMode, GfxColor *singleColor);
+  SplashPath *convertPath(GfxState *state, GfxPath *path,
+			  GBool dropEmptySubpaths);
   void doUpdateFont(GfxState *state);
-  void drawType3Glyph(T3FontCache *t3Font,
+  void drawType3Glyph(GfxState *state, T3FontCache *t3Font,
 		      T3FontCacheTag *tag, Guchar *data);
   static GBool imageMaskSrc(void *data, SplashColorPtr line);
   static GBool imageSrc(void *data, SplashColorPtr colorLine,
diff --git a/splash/Splash.cc b/splash/Splash.cc
index 2556652..5091edb 100644
--- a/splash/Splash.cc
+++ b/splash/Splash.cc
@@ -51,6 +51,8 @@
 
 //------------------------------------------------------------------------
 
+#define splashAAGamma 1.5
+
 // distance of Bezier control point from center for circle approximation
 // = (4 * (sqrt(2) - 1) / 3) * r
 #define bezierCircle ((SplashCoord)0.55228475)
@@ -61,6 +63,11 @@ static inline Guchar div255(int x) {
   return (Guchar)((x + (x >> 8) + 0x80) >> 8);
 }
 
+// Clip x to lie in [0, 255].
+static inline Guchar clip255(int x) {
+  return x < 0 ? 0 : x > 255 ? 255 : x;
+}
+
 template<typename T>
 inline void Guswap( T&a, T&b ) { T tmp = a; a=b; b=tmp; }
 
@@ -78,9 +85,8 @@ struct SplashPipe {
   SplashPattern *pattern;
 
   // source alpha and color
-  SplashCoord aInput;
+  Guchar aInput;
   GBool usesShape;
-  Guchar aSrc;
   SplashColorPtr cSrc;
   SplashColor cSrcVal;
 
@@ -96,18 +102,17 @@ struct SplashPipe {
   Guchar *destAlphaPtr;
 
   // shape
-  SplashCoord shape;
+  Guchar shape;
 
   // result alpha and color
   GBool noTransparency;
   SplashPipeResultColorCtrl resultColorCtrl;
 
   // non-isolated group correction
-  int nonIsolatedGroup;
+  GBool nonIsolatedGroup;
 
-  // stroke / fill operation and pattern for calculate overprint
-  GBool stroke;
-  SplashPattern *overprintPattern;
+  // the "run" function
+  void (Splash::*run)(SplashPipe *pipe);
 };
 
 SplashPipeResultColorCtrl Splash::pipeResultColorNoAlphaBlend[] = {
@@ -192,8 +197,8 @@ inline void Splash::updateModY(int y) {
 
 inline void Splash::pipeInit(SplashPipe *pipe, int x, int y,
 			     SplashPattern *pattern, SplashColorPtr cSrc,
-			     SplashCoord aInput, GBool usesShape,
-			     GBool nonIsolatedGroup, SplashPattern *opPattern, GBool strokeA) {
+			     Guchar aInput, GBool usesShape,
+			     GBool nonIsolatedGroup) {
   pipeSetXY(pipe, x, y);
   pipe->pattern = NULL;
 
@@ -211,18 +216,11 @@ inline void Splash::pipeInit(SplashPipe *pipe, int x, int y,
 
   // source alpha
   pipe->aInput = aInput;
-  if (!state->softMask) {
-    if (usesShape) {
-      pipe->aInput *= 255;
-    } else {
-      pipe->aSrc = (Guchar)splashRound(pipe->aInput * 255);
-    }
-  }
   pipe->usesShape = usesShape;
 
   // result alpha
-  if (aInput == 1 && !state->softMask && !usesShape &&
-      !state->inNonIsolatedGroup) {
+  if (aInput == 255 && !state->softMask && !usesShape &&
+      !state->inNonIsolatedGroup && !nonIsolatedGroup) {
     pipe->noTransparency = gTrue;
   } else {
     pipe->noTransparency = gFalse;
@@ -239,19 +237,58 @@ inline void Splash::pipeInit(SplashPipe *pipe, int x, int y,
   }
 
   // non-isolated group correction
-  if (nonIsolatedGroup) {
-    pipe->nonIsolatedGroup = splashColorModeNComps[bitmap->mode];
-  } else {
-    pipe->nonIsolatedGroup = 0;
+  pipe->nonIsolatedGroup = nonIsolatedGroup;
+
+  // select the 'run' function
+  pipe->run = &Splash::pipeRun;
+  if (!pipe->pattern && pipe->noTransparency && !state->blendFunc) {
+    if (bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunSimpleMono1;
+    } else if (bitmap->mode == splashModeMono8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunSimpleMono8;
+    } else if (bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunSimpleRGB8;
+    } else if (bitmap->mode == splashModeXBGR8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunSimpleXBGR8;
+    } else if (bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunSimpleBGR8;
+#if SPLASH_CMYK
+    } else if (bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunSimpleCMYK8;
+#endif
+    }
+  } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask &&
+	     pipe->usesShape &&
+	     !(state->inNonIsolatedGroup && alpha0Bitmap->alpha) &&
+	     !state->blendFunc && !pipe->nonIsolatedGroup) {
+    if (bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunAAMono1;
+    } else if (bitmap->mode == splashModeMono8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunAAMono8;
+    } else if (bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunAARGB8;
+    } else if (bitmap->mode == splashModeXBGR8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunAAXBGR8;
+    } else if (bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunAABGR8;
+#if SPLASH_CMYK
+    } else if (bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunAACMYK8;
+#endif
+    }
   }
-  pipe->stroke = strokeA;
-  pipe->overprintPattern = opPattern;
 }
 
-inline void Splash::pipeRun(SplashPipe *pipe) {
-  Guchar aSrc, aDest, alpha2, alpha0, aResult;
-  SplashColor cDest, cBlend;
+// general case
+void Splash::pipeRun(SplashPipe *pipe) {
+  Guchar aSrc, aDest, alphaI, alphaIm1, alpha0, aResult;
+  SplashColor cSrcNonIso, cDest, cBlend;
+  SplashColorPtr cSrc;
   Guchar cResult0, cResult1, cResult2, cResult3;
+  int t;
+#if SPLASH_CMYK
+  SplashColor cSrc2, cDest2;
+#endif
 
   //----- source color
 
@@ -272,7 +309,7 @@ inline void Splash::pipeRun(SplashPipe *pipe) {
 
     switch (bitmap->mode) {
     case splashModeMono1:
-      cResult0 = pipe->cSrc[0];
+      cResult0 = state->grayTransfer[pipe->cSrc[0]];
       if (state->screen->test(pipe->x, pipe->y, cResult0)) {
 	*pipe->destColorPtr |= pipe->destColorMask;
       } else {
@@ -284,45 +321,39 @@ inline void Splash::pipeRun(SplashPipe *pipe) {
       }
       break;
     case splashModeMono8:
-      *pipe->destColorPtr++ = pipe->cSrc[0];
+      *pipe->destColorPtr++ = state->grayTransfer[pipe->cSrc[0]];
       break;
     case splashModeRGB8:
-      *pipe->destColorPtr++ = pipe->cSrc[0];
-      *pipe->destColorPtr++ = pipe->cSrc[1];
-      *pipe->destColorPtr++ = pipe->cSrc[2];
+      *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]];
+      *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]];
+      *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]];
       break;
     case splashModeXBGR8:
-      *pipe->destColorPtr++ = pipe->cSrc[2];
-      *pipe->destColorPtr++ = pipe->cSrc[1];
-      *pipe->destColorPtr++ = pipe->cSrc[0];
+      *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]];
+      *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]];
+      *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]];
       *pipe->destColorPtr++ = 255;
       break;
     case splashModeBGR8:
-      *pipe->destColorPtr++ = pipe->cSrc[2];
-      *pipe->destColorPtr++ = pipe->cSrc[1];
-      *pipe->destColorPtr++ = pipe->cSrc[0];
+      *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]];
+      *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]];
+      *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]];
       break;
 #if SPLASH_CMYK
     case splashModeCMYK8:
-      if (pipe->overprintPattern != NULL && 
-          ((pipe->stroke && state->strokeOverprint) ||
-          (!pipe->stroke && state->fillOverprint))) {
-        SplashColor cResult;
-        cDest[0] = pipe->destColorPtr[0];
-        cDest[1] = pipe->destColorPtr[1];
-        cDest[2] = pipe->destColorPtr[2];
-        cDest[3] = pipe->destColorPtr[3];
-        pipe->overprintPattern->overprint(state->overprintMode == 1, pipe->aSrc, pipe->cSrc, 255, cDest, cResult);
-        *pipe->destColorPtr++ = cResult[0];
-        *pipe->destColorPtr++ = cResult[1];
-        *pipe->destColorPtr++ = cResult[2];
-        *pipe->destColorPtr++ = cResult[3];
-      } else {
-        *pipe->destColorPtr++ = pipe->cSrc[0];
-        *pipe->destColorPtr++ = pipe->cSrc[1];
-        *pipe->destColorPtr++ = pipe->cSrc[2];
-        *pipe->destColorPtr++ = pipe->cSrc[3];
+      if (state->overprintMask & 1) {
+	pipe->destColorPtr[0] = state->cmykTransferC[pipe->cSrc[0]];
+      }
+      if (state->overprintMask & 2) {
+	pipe->destColorPtr[1] = state->cmykTransferM[pipe->cSrc[1]];
+      }
+      if (state->overprintMask & 4) {
+	pipe->destColorPtr[2] = state->cmykTransferY[pipe->cSrc[2]];
       }
+      if (state->overprintMask & 8) {
+	pipe->destColorPtr[3] = state->cmykTransferK[pipe->cSrc[3]];
+      }
+      pipe->destColorPtr += 4;
       break;
 #endif
     }
@@ -372,41 +403,99 @@ inline void Splash::pipeRun(SplashPipe *pipe) {
       aDest = 0xff;
     }
 
-    //----- blend function
-
-    if (state->blendFunc) {
-      (*state->blendFunc)(pipe->cSrc, cDest, cBlend, bitmap->mode);
-    }
-
     //----- source alpha
 
     if (state->softMask) {
       if (pipe->usesShape) {
-	aSrc = (Guchar)splashRound(pipe->aInput * *pipe->softMaskPtr++
-				   * pipe->shape);
+	aSrc = div255(div255(pipe->aInput * *pipe->softMaskPtr++) *
+		      pipe->shape);
       } else {
-	aSrc = (Guchar)splashRound(pipe->aInput * *pipe->softMaskPtr++);
+	aSrc = div255(pipe->aInput * *pipe->softMaskPtr++);
       }
     } else if (pipe->usesShape) {
-      // pipe->aInput is premultiplied by 255 in pipeInit
-      aSrc = (Guchar)splashRound(pipe->aInput * pipe->shape);
+      aSrc = div255(pipe->aInput * pipe->shape);
+    } else {
+      aSrc = pipe->aInput;
+    }
+
+    //----- non-isolated group correction
+
+    if (pipe->nonIsolatedGroup) {
+      // This path is only used when Splash::composite() is called to
+      // composite a non-isolated group onto the backdrop.  In this
+      // case, pipe->shape is the source (group) alpha.
+      if (pipe->shape == 0) {
+	// this value will be multiplied by zero later, so it doesn't
+	// matter what we use
+	cSrc = pipe->cSrc;
+      } else {
+	t = (aDest * 255) / pipe->shape - aDest;
+	switch (bitmap->mode) {
+#if SPLASH_CMYK
+	case splashModeCMYK8:
+	  cSrcNonIso[3] = clip255(pipe->cSrc[3] +
+				  ((pipe->cSrc[3] - cDest[3]) * t) / 255);
+#endif
+	case splashModeRGB8:
+  case splashModeXBGR8:
+	case splashModeBGR8:
+	  cSrcNonIso[2] = clip255(pipe->cSrc[2] +
+				  ((pipe->cSrc[2] - cDest[2]) * t) / 255);
+	  cSrcNonIso[1] = clip255(pipe->cSrc[1] +
+				  ((pipe->cSrc[1] - cDest[1]) * t) / 255);
+	case splashModeMono1:
+	case splashModeMono8:
+	  cSrcNonIso[0] = clip255(pipe->cSrc[0] +
+				  ((pipe->cSrc[0] - cDest[0]) * t) / 255);
+	  break;
+	}
+	cSrc = cSrcNonIso;
+      }
     } else {
-      // precomputed in pipeInit
-      aSrc = pipe->aSrc;
+      cSrc = pipe->cSrc;
+    }
+
+    //----- blend function
+
+    if (state->blendFunc) {
+#if SPLASH_CMYK
+      if (bitmap->mode == splashModeCMYK8) {
+	// convert colors to additive
+	cSrc2[0] = 0xff - cSrc[0];
+	cSrc2[1] = 0xff - cSrc[1];
+	cSrc2[2] = 0xff - cSrc[2];
+	cSrc2[3] = 0xff - cSrc[3];
+	cDest2[0] = 0xff - cDest[0];
+	cDest2[1] = 0xff - cDest[1];
+	cDest2[2] = 0xff - cDest[2];
+	cDest2[3] = 0xff - cDest[3];
+	(*state->blendFunc)(cSrc2, cDest2, cBlend, bitmap->mode);
+	// convert result back to subtractive
+	cBlend[0] = 0xff - cBlend[0];
+	cBlend[1] = 0xff - cBlend[1];
+	cBlend[2] = 0xff - cBlend[2];
+	cBlend[3] = 0xff - cBlend[3];
+      } else
+#endif
+      (*state->blendFunc)(cSrc, cDest, cBlend, bitmap->mode);
     }
 
     //----- result alpha and non-isolated group element correction
 
     if (pipe->noTransparency) {
-      alpha2 = aResult = 255;
+      alphaI = alphaIm1 = aResult = 255;
     } else {
       aResult = aSrc + aDest - div255(aSrc * aDest);
 
+      // alphaI = alpha_i
+      // alphaIm1 = alpha_(i-1)
       if (pipe->alpha0Ptr) {
 	alpha0 = *pipe->alpha0Ptr++;
-	alpha2 = aResult + alpha0 - div255(aResult * alpha0);
+	alphaI = aResult + alpha0 - div255(aResult * alpha0);
+	alphaIm1 = alpha0 + aDest - div255(alpha0 * aDest);
       } else {
-	alpha2 = aResult;
+	alphaI = aResult;
+	alphaIm1 = aDest;
       }
     }
 
@@ -416,151 +505,132 @@ inline void Splash::pipeRun(SplashPipe *pipe) {
 
     switch (pipe->resultColorCtrl) {
 
+    case splashPipeResultColorNoAlphaBlendMono:
+      cResult0 = state->grayTransfer[div255((255 - aDest) * cSrc[0] +
+					    aDest * cBlend[0])];
+      break;
+    case splashPipeResultColorNoAlphaBlendRGB:
+      cResult0 = state->rgbTransferR[div255((255 - aDest) * cSrc[0] +
+					    aDest * cBlend[0])];
+      cResult1 = state->rgbTransferG[div255((255 - aDest) * cSrc[1] +
+					    aDest * cBlend[1])];
+      cResult2 = state->rgbTransferB[div255((255 - aDest) * cSrc[2] +
+					    aDest * cBlend[2])];
+      break;
 #if SPLASH_CMYK
     case splashPipeResultColorNoAlphaBlendCMYK:
-      cResult3 = div255((255 - aDest) * pipe->cSrc[3] + aDest * cBlend[3]);
-#endif
-    case splashPipeResultColorNoAlphaBlendRGB:
-      cResult2 = div255((255 - aDest) * pipe->cSrc[2] + aDest * cBlend[2]);
-      cResult1 = div255((255 - aDest) * pipe->cSrc[1] + aDest * cBlend[1]);
-    case splashPipeResultColorNoAlphaBlendMono:
-      cResult0 = div255((255 - aDest) * pipe->cSrc[0] + aDest * cBlend[0]);
+      cResult0 = state->cmykTransferC[div255((255 - aDest) * cSrc[0] +
+					     aDest * cBlend[0])];
+      cResult1 = state->cmykTransferM[div255((255 - aDest) * cSrc[1] +
+					     aDest * cBlend[1])];
+      cResult2 = state->cmykTransferY[div255((255 - aDest) * cSrc[2] +
+					     aDest * cBlend[2])];
+      cResult3 = state->cmykTransferK[div255((255 - aDest) * cSrc[3] +
+					     aDest * cBlend[3])];
       break;
+#endif
 
     case splashPipeResultColorAlphaNoBlendMono:
-      if (alpha2 == 0) {
+      if (alphaI == 0) {
 	cResult0 = 0;
       } else {
-	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
-			     aSrc * pipe->cSrc[0]) / alpha2);
+	cResult0 = state->grayTransfer[((alphaI - aSrc) * cDest[0] +
+					aSrc * cSrc[0]) / alphaI];
       }
       break;
     case splashPipeResultColorAlphaNoBlendRGB:
-      if (alpha2 == 0) {
+      if (alphaI == 0) {
 	cResult0 = 0;
 	cResult1 = 0;
 	cResult2 = 0;
       } else {
-	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
-			     aSrc * pipe->cSrc[0]) / alpha2);
-	cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] +
-			     aSrc * pipe->cSrc[1]) / alpha2);
-	cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] +
-			     aSrc * pipe->cSrc[2]) / alpha2);
+	cResult0 = state->rgbTransferR[((alphaI - aSrc) * cDest[0] +
+					aSrc * cSrc[0]) / alphaI];
+	cResult1 = state->rgbTransferG[((alphaI - aSrc) * cDest[1] +
+					aSrc * cSrc[1]) / alphaI];
+	cResult2 = state->rgbTransferB[((alphaI - aSrc) * cDest[2] +
+					aSrc * cSrc[2]) / alphaI];
       }
       break;
 #if SPLASH_CMYK
     case splashPipeResultColorAlphaNoBlendCMYK:
-      if (alpha2 == 0) {
+      if (alphaI == 0) {
 	cResult0 = 0;
 	cResult1 = 0;
 	cResult2 = 0;
 	cResult3 = 0;
       } else {
-	if (pipe->overprintPattern != NULL &&
-	   ((pipe->stroke && state->strokeOverprint) ||
-	   (!pipe->stroke && state->fillOverprint))) {
-	  SplashColor cResult;
-	  pipe->overprintPattern->overprint(state->overprintMode == 1, aSrc, pipe->cSrc, alpha2, cDest, cResult);
-	  cResult0 = cResult[0];
-	  cResult1 = cResult[1];
-	  cResult2 = cResult[2];
-	  cResult3 = cResult[3];
-	} else {
-	  cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
-			     aSrc * pipe->cSrc[0]) / alpha2);
-	  cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] +
-			     aSrc * pipe->cSrc[1]) / alpha2);
-	  cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] +
-			     aSrc * pipe->cSrc[2]) / alpha2);
-	  cResult3 = (Guchar)(((alpha2 - aSrc) * cDest[3] +
-			     aSrc * pipe->cSrc[3]) / alpha2);
-	}
+	cResult0 = state->cmykTransferC[((alphaI - aSrc) * cDest[0] +
+					 aSrc * cSrc[0]) / alphaI];
+	cResult1 = state->cmykTransferM[((alphaI - aSrc) * cDest[1] +
+					 aSrc * cSrc[1]) / alphaI];
+	cResult2 = state->cmykTransferY[((alphaI - aSrc) * cDest[2] +
+					 aSrc * cSrc[2]) / alphaI];
+	cResult3 = state->cmykTransferK[((alphaI - aSrc) * cDest[3] +
+					 aSrc * cSrc[3]) / alphaI];
       }
       break;
 #endif
 
     case splashPipeResultColorAlphaBlendMono:
-      if (alpha2 == 0) {
+      if (alphaI == 0) {
 	cResult0 = 0;
       } else {
-	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
-			     aSrc * ((255 - aDest) * pipe->cSrc[0] +
-				     aDest * cBlend[0]) / 255) /
-			    alpha2);
+	cResult0 = state->grayTransfer[((alphaI - aSrc) * cDest[0] +
+					aSrc * ((255 - alphaIm1) * cSrc[0] +
+						alphaIm1 * cBlend[0]) / 255) /
+				       alphaI];
       }
       break;
     case splashPipeResultColorAlphaBlendRGB:
-      if (alpha2 == 0) {
+      if (alphaI == 0) {
 	cResult0 = 0;
 	cResult1 = 0;
 	cResult2 = 0;
       } else {
-	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
-			     aSrc * ((255 - aDest) * pipe->cSrc[0] +
-				     aDest * cBlend[0]) / 255) /
-			    alpha2);
-	cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] +
-			     aSrc * ((255 - aDest) * pipe->cSrc[1] +
-				     aDest * cBlend[1]) / 255) /
-			    alpha2);
-	cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] +
-			     aSrc * ((255 - aDest) * pipe->cSrc[2] +
-				     aDest * cBlend[2]) / 255) /
-			    alpha2);
+	cResult0 = state->rgbTransferR[((alphaI - aSrc) * cDest[0] +
+					aSrc * ((255 - alphaIm1) * cSrc[0] +
+						alphaIm1 * cBlend[0]) / 255) /
+				       alphaI];
+	cResult1 = state->rgbTransferG[((alphaI - aSrc) * cDest[1] +
+					aSrc * ((255 - alphaIm1) * cSrc[1] +
+						alphaIm1 * cBlend[1]) / 255) /
+				       alphaI];
+	cResult2 = state->rgbTransferB[((alphaI - aSrc) * cDest[2] +
+					aSrc * ((255 - alphaIm1) * cSrc[2] +
+						alphaIm1 * cBlend[2]) / 255) /
+				       alphaI];
       }
       break;
 #if SPLASH_CMYK
     case splashPipeResultColorAlphaBlendCMYK:
-      if (alpha2 == 0) {
+      if (alphaI == 0) {
 	cResult0 = 0;
 	cResult1 = 0;
 	cResult2 = 0;
 	cResult3 = 0;
       } else {
-	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
-			     aSrc * ((255 - aDest) * pipe->cSrc[0] +
-				     aDest * cBlend[0]) / 255) /
-			    alpha2);
-	cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] +
-			     aSrc * ((255 - aDest) * pipe->cSrc[1] +
-				     aDest * cBlend[1]) / 255) /
-			    alpha2);
-	cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] +
-			     aSrc * ((255 - aDest) * pipe->cSrc[2] +
-				     aDest * cBlend[2]) / 255) /
-			    alpha2);
-	cResult3 = (Guchar)(((alpha2 - aSrc) * cDest[3] +
-			     aSrc * ((255 - aDest) * pipe->cSrc[3] +
-				     aDest * cBlend[3]) / 255) /
-			    alpha2);
+	cResult0 = state->cmykTransferC[((alphaI - aSrc) * cDest[0] +
+					 aSrc * ((255 - alphaIm1) * cSrc[0] +
+						 alphaIm1 * cBlend[0]) / 255) /
+					alphaI];
+	cResult1 = state->cmykTransferM[((alphaI - aSrc) * cDest[1] +
+					 aSrc * ((255 - alphaIm1) * cSrc[1] +
+						 alphaIm1 * cBlend[1]) / 255) /
+					alphaI];
+	cResult2 = state->cmykTransferY[((alphaI - aSrc) * cDest[2] +
+					 aSrc * ((255 - alphaIm1) * cSrc[2] +
+						 alphaIm1 * cBlend[2]) / 255) /
+					alphaI];
+	cResult3 = state->cmykTransferK[((alphaI - aSrc) * cDest[3] +
+					 aSrc * ((255 - alphaIm1) * cSrc[3] +
+						 alphaIm1 * cBlend[3]) / 255) /
+					alphaI];
       }
       break;
 #endif
     }
 
-    //----- non-isolated group correction
-
-    if (aResult != 0) {
-      switch (pipe->nonIsolatedGroup) {
-#if SPLASH_CMYK
-      case 4:
-	cResult3 += (cResult3 - cDest[3]) * aDest *
-	            (255 - aResult) / (255 * aResult);
-#endif
-      case 3:
-	cResult2 += (cResult2 - cDest[2]) * aDest *
-	            (255 - aResult) / (255 * aResult);
-	cResult1 += (cResult1 - cDest[1]) * aDest *
-	            (255 - aResult) / (255 * aResult);
-      case 1:
-	cResult0 += (cResult0 - cDest[0]) * aDest *
-	            (255 - aResult) / (255 * aResult);
-      case 0:
-	break;
-      }
-    }
-
     //----- write destination pixel
 
     switch (bitmap->mode) {
@@ -596,10 +666,19 @@ inline void Splash::pipeRun(SplashPipe *pipe) {
       break;
 #if SPLASH_CMYK
     case splashModeCMYK8:
-      *pipe->destColorPtr++ = cResult0;
-      *pipe->destColorPtr++ = cResult1;
-      *pipe->destColorPtr++ = cResult2;
-      *pipe->destColorPtr++ = cResult3;
+      if (state->overprintMask & 1) {
+	pipe->destColorPtr[0] = cResult0;
+      }
+      if (state->overprintMask & 2) {
+	pipe->destColorPtr[1] = cResult1;
+      }
+      if (state->overprintMask & 4) {
+	pipe->destColorPtr[2] = cResult2;
+      }
+      if (state->overprintMask & 8) {
+	pipe->destColorPtr[3] = cResult3;
+      }
+      pipe->destColorPtr += 4;
       break;
 #endif
     }
@@ -612,6 +691,376 @@ inline void Splash::pipeRun(SplashPipe *pipe) {
   ++pipe->x;
 }
 
+// special case:
+// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
+// bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr) {
+void Splash::pipeRunSimpleMono1(SplashPipe *pipe) {
+  Guchar cResult0;
+
+  //----- write destination pixel
+  cResult0 = state->grayTransfer[pipe->cSrc[0]];
+  if (state->screen->test(pipe->x, pipe->y, cResult0)) {
+    *pipe->destColorPtr |= pipe->destColorMask;
+  } else {
+    *pipe->destColorPtr &= ~pipe->destColorMask;
+  }
+  if (!(pipe->destColorMask >>= 1)) {
+    pipe->destColorMask = 0x80;
+    ++pipe->destColorPtr;
+  }
+
+  ++pipe->x;
+}
+
+// special case:
+// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
+// bitmap->mode == splashModeMono8 && pipe->destAlphaPtr) {
+void Splash::pipeRunSimpleMono8(SplashPipe *pipe) {
+  //----- write destination pixel
+  *pipe->destColorPtr++ = state->grayTransfer[pipe->cSrc[0]];
+  *pipe->destAlphaPtr++ = 255;
+
+  ++pipe->x;
+}
+
+// special case:
+// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
+// bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr) {
+void Splash::pipeRunSimpleRGB8(SplashPipe *pipe) {
+  //----- write destination pixel
+  *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]];
+  *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]];
+  *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]];
+  *pipe->destAlphaPtr++ = 255;
+
+  ++pipe->x;
+}
+
+// special case:
+// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
+// bitmap->mode == splashModeXBGR8 && pipe->destAlphaPtr) {
+void Splash::pipeRunSimpleXBGR8(SplashPipe *pipe) {
+  //----- write destination pixel
+  *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]];
+  *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]];
+  *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]];
+  *pipe->destColorPtr++ = 255;
+  *pipe->destAlphaPtr++ = 255;
+
+  ++pipe->x;
+}
+
+// special case:
+// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
+// bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr) {
+void Splash::pipeRunSimpleBGR8(SplashPipe *pipe) {
+  //----- write destination pixel
+  *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]];
+  *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]];
+  *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]];
+  *pipe->destAlphaPtr++ = 255;
+
+  ++pipe->x;
+}
+
+#if SPLASH_CMYK
+// special case:
+// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
+// bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) {
+void Splash::pipeRunSimpleCMYK8(SplashPipe *pipe) {
+  //----- write destination pixel
+  if (state->overprintMask & 1) {
+    pipe->destColorPtr[0] = state->cmykTransferC[pipe->cSrc[0]];
+  }
+  if (state->overprintMask & 2) {
+    pipe->destColorPtr[1] = state->cmykTransferM[pipe->cSrc[1]];
+  }
+  if (state->overprintMask & 4) {
+    pipe->destColorPtr[2] = state->cmykTransferY[pipe->cSrc[2]];
+  }
+  if (state->overprintMask & 8) {
+    pipe->destColorPtr[3] = state->cmykTransferK[pipe->cSrc[3]];
+  }
+  pipe->destColorPtr += 4;
+  *pipe->destAlphaPtr++ = 255;
+
+  ++pipe->x;
+}
+#endif
+
+
+// special case:
+// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
+// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
+// !pipe->nonIsolatedGroup &&
+// bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr
+void Splash::pipeRunAAMono1(SplashPipe *pipe) {
+  Guchar aSrc;
+  SplashColor cDest;
+  Guchar cResult0;
+
+  //----- read destination pixel
+  cDest[0] = (*pipe->destColorPtr & pipe->destColorMask) ? 0xff : 0x00;
+
+  //----- source alpha
+  aSrc = div255(pipe->aInput * pipe->shape);
+
+  //----- result color
+  // note: aDest = alpha2 = aResult = 0xff
+  cResult0 = state->grayTransfer[(Guchar)div255((0xff - aSrc) * cDest[0] +
+						aSrc * pipe->cSrc[0])];
+
+  //----- write destination pixel
+  if (state->screen->test(pipe->x, pipe->y, cResult0)) {
+    *pipe->destColorPtr |= pipe->destColorMask;
+  } else {
+    *pipe->destColorPtr &= ~pipe->destColorMask;
+  }
+  if (!(pipe->destColorMask >>= 1)) {
+    pipe->destColorMask = 0x80;
+    ++pipe->destColorPtr;
+  }
+
+  ++pipe->x;
+}
+
+// special case:
+// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
+// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
+// !pipe->nonIsolatedGroup &&
+// bitmap->mode == splashModeMono8 && pipe->destAlphaPtr
+void Splash::pipeRunAAMono8(SplashPipe *pipe) {
+  Guchar aSrc, aDest, alpha2, aResult;
+  SplashColor cDest;
+  Guchar cResult0;
+
+  //----- read destination pixel
+  cDest[0] = *pipe->destColorPtr;
+  aDest = *pipe->destAlphaPtr;
+
+  //----- source alpha
+  aSrc = div255(pipe->aInput * pipe->shape);
+
+  //----- result alpha and non-isolated group element correction
+  aResult = aSrc + aDest - div255(aSrc * aDest);
+  alpha2 = aResult;
+
+  //----- result color
+  if (alpha2 == 0) {
+    cResult0 = 0;
+  } else {
+    cResult0 = state->grayTransfer[(Guchar)(((alpha2 - aSrc) * cDest[0] +
+					     aSrc * pipe->cSrc[0]) / alpha2)];
+  }
+
+  //----- write destination pixel
+  *pipe->destColorPtr++ = cResult0;
+  *pipe->destAlphaPtr++ = aResult;
+
+  ++pipe->x;
+}
+
+// special case:
+// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
+// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
+// !pipe->nonIsolatedGroup &&
+// bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr
+void Splash::pipeRunAARGB8(SplashPipe *pipe) {
+  Guchar aSrc, aDest, alpha2, aResult;
+  SplashColor cDest;
+  Guchar cResult0, cResult1, cResult2;
+
+  //----- read destination pixel
+  cDest[0] = pipe->destColorPtr[0];
+  cDest[1] = pipe->destColorPtr[1];
+  cDest[2] = pipe->destColorPtr[2];
+  aDest = *pipe->destAlphaPtr;
+
+  //----- source alpha
+  aSrc = div255(pipe->aInput * pipe->shape);
+
+  //----- result alpha and non-isolated group element correction
+  aResult = aSrc + aDest - div255(aSrc * aDest);
+  alpha2 = aResult;
+
+  //----- result color
+  if (alpha2 == 0) {
+    cResult0 = 0;
+    cResult1 = 0;
+    cResult2 = 0;
+  } else {
+    cResult0 = state->rgbTransferR[(Guchar)(((alpha2 - aSrc) * cDest[0] +
+					     aSrc * pipe->cSrc[0]) / alpha2)];
+    cResult1 = state->rgbTransferG[(Guchar)(((alpha2 - aSrc) * cDest[1] +
+					     aSrc * pipe->cSrc[1]) / alpha2)];
+    cResult2 = state->rgbTransferB[(Guchar)(((alpha2 - aSrc) * cDest[2] +
+					     aSrc * pipe->cSrc[2]) / alpha2)];
+  }
+
+  //----- write destination pixel
+  *pipe->destColorPtr++ = cResult0;
+  *pipe->destColorPtr++ = cResult1;
+  *pipe->destColorPtr++ = cResult2;
+  *pipe->destAlphaPtr++ = aResult;
+
+  ++pipe->x;
+}
+
+// special case:
+// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
+// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
+// !pipe->nonIsolatedGroup &&
+// bitmap->mode == splashModeXBGR8 && pipe->destAlphaPtr
+void Splash::pipeRunAAXBGR8(SplashPipe *pipe) {
+  Guchar aSrc, aDest, alpha2, aResult;
+  SplashColor cDest;
+  Guchar cResult0, cResult1, cResult2;
+
+  //----- read destination pixel
+  cDest[0] = pipe->destColorPtr[2];
+  cDest[1] = pipe->destColorPtr[1];
+  cDest[2] = pipe->destColorPtr[0];
+  aDest = *pipe->destAlphaPtr;
+
+  //----- source alpha
+  aSrc = div255(pipe->aInput * pipe->shape);
+
+  //----- result alpha and non-isolated group element correction
+  aResult = aSrc + aDest - div255(aSrc * aDest);
+  alpha2 = aResult;
+
+  //----- result color
+  if (alpha2 == 0) {
+    cResult0 = 0;
+    cResult1 = 0;
+    cResult2 = 0;
+  } else {
+    cResult0 = state->rgbTransferR[(Guchar)(((alpha2 - aSrc) * cDest[0] +
+					     aSrc * pipe->cSrc[0]) / alpha2)];
+    cResult1 = state->rgbTransferG[(Guchar)(((alpha2 - aSrc) * cDest[1] +
+					     aSrc * pipe->cSrc[1]) / alpha2)];
+    cResult2 = state->rgbTransferB[(Guchar)(((alpha2 - aSrc) * cDest[2] +
+					     aSrc * pipe->cSrc[2]) / alpha2)];
+  }
+
+  //----- write destination pixel
+  *pipe->destColorPtr++ = cResult2;
+  *pipe->destColorPtr++ = cResult1;
+  *pipe->destColorPtr++ = cResult0;
+  *pipe->destColorPtr++ = 255;
+  *pipe->destAlphaPtr++ = aResult;
+
+  ++pipe->x;
+}
+
+// special case:
+// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
+// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
+// !pipe->nonIsolatedGroup &&
+// bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr
+void Splash::pipeRunAABGR8(SplashPipe *pipe) {
+  Guchar aSrc, aDest, alpha2, aResult;
+  SplashColor cDest;
+  Guchar cResult0, cResult1, cResult2;
+
+  //----- read destination pixel
+  cDest[0] = pipe->destColorPtr[2];
+  cDest[1] = pipe->destColorPtr[1];
+  cDest[2] = pipe->destColorPtr[0];
+  aDest = *pipe->destAlphaPtr;
+
+  //----- source alpha
+  aSrc = div255(pipe->aInput * pipe->shape);
+
+  //----- result alpha and non-isolated group element correction
+  aResult = aSrc + aDest - div255(aSrc * aDest);
+  alpha2 = aResult;
+
+  //----- result color
+  if (alpha2 == 0) {
+    cResult0 = 0;
+    cResult1 = 0;
+    cResult2 = 0;
+  } else {
+    cResult0 = state->rgbTransferR[(Guchar)(((alpha2 - aSrc) * cDest[0] +
+					     aSrc * pipe->cSrc[0]) / alpha2)];
+    cResult1 = state->rgbTransferG[(Guchar)(((alpha2 - aSrc) * cDest[1] +
+					     aSrc * pipe->cSrc[1]) / alpha2)];
+    cResult2 = state->rgbTransferB[(Guchar)(((alpha2 - aSrc) * cDest[2] +
+					     aSrc * pipe->cSrc[2]) / alpha2)];
+  }
+
+  //----- write destination pixel
+  *pipe->destColorPtr++ = cResult2;
+  *pipe->destColorPtr++ = cResult1;
+  *pipe->destColorPtr++ = cResult0;
+  *pipe->destAlphaPtr++ = aResult;
+
+  ++pipe->x;
+}
+
+#if SPLASH_CMYK
+// special case:
+// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
+// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
+// !pipe->nonIsolatedGroup &&
+// bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr
+void Splash::pipeRunAACMYK8(SplashPipe *pipe) {
+  Guchar aSrc, aDest, alpha2, aResult;
+  SplashColor cDest;
+  Guchar cResult0, cResult1, cResult2, cResult3;
+
+  //----- read destination pixel
+  cDest[0] = pipe->destColorPtr[0];
+  cDest[1] = pipe->destColorPtr[1];
+  cDest[2] = pipe->destColorPtr[2];
+  cDest[3] = pipe->destColorPtr[3];
+  aDest = *pipe->destAlphaPtr;
+
+  //----- source alpha
+  aSrc = div255(pipe->aInput * pipe->shape);
+
+  //----- result alpha and non-isolated group element correction
+  aResult = aSrc + aDest - div255(aSrc * aDest);
+  alpha2 = aResult;
+
+  //----- result color
+  if (alpha2 == 0) {
+    cResult0 = 0;
+    cResult1 = 0;
+    cResult2 = 0;
+    cResult3 = 0;
+  } else {
+    cResult0 = state->cmykTransferC[(Guchar)(((alpha2 - aSrc) * cDest[0] +
+					      aSrc * pipe->cSrc[0]) / alpha2)];
+    cResult1 = state->cmykTransferM[(Guchar)(((alpha2 - aSrc) * cDest[1] +
+					      aSrc * pipe->cSrc[1]) / alpha2)];
+    cResult2 = state->cmykTransferY[(Guchar)(((alpha2 - aSrc) * cDest[2] +
+					      aSrc * pipe->cSrc[2]) / alpha2)];
+    cResult3 = state->cmykTransferK[(Guchar)(((alpha2 - aSrc) * cDest[3] +
+					      aSrc * pipe->cSrc[3]) / alpha2)];
+  }
+
+  //----- write destination pixel
+  if (state->overprintMask & 1) {
+    pipe->destColorPtr[0] = cResult0;
+  }
+  if (state->overprintMask & 2) {
+    pipe->destColorPtr[1] = cResult1;
+  }
+  if (state->overprintMask & 4) {
+    pipe->destColorPtr[2] = cResult2;
+  }
+  if (state->overprintMask & 8) {
+    pipe->destColorPtr[3] = cResult3;
+  }
+  pipe->destColorPtr += 4;
+  *pipe->destAlphaPtr++ = aResult;
+
+  ++pipe->x;
+}
+#endif
+
 inline void Splash::pipeSetXY(SplashPipe *pipe, int x, int y) {
   pipe->x = x;
   pipe->y = y;
@@ -696,7 +1145,7 @@ inline void Splash::drawPixel(SplashPipe *pipe, int x, int y, GBool noClip) {
 
   if (noClip || state->clip->test(x, y)) {
     pipeSetXY(pipe, x, y);
-    pipeRun(pipe);
+    (this->*pipe->run)(pipe);
     updateModX(x);
     updateModY(y);
   }
@@ -757,8 +1206,8 @@ inline void Splash::drawAAPixel(SplashPipe *pipe, int x, int y) {
   // draw the pixel
   if (t != 0) {
     pipeSetXY(pipe, x, y);
-    pipe->shape *= aaGamma[t];
-    pipeRun(pipe);
+    pipe->shape = div255(aaGamma[t] * pipe->shape);
+    (this->*pipe->run)(pipe);
     updateModX(x);
     updateModY(y);
   }
@@ -768,18 +1217,25 @@ inline void Splash::drawSpan(SplashPipe *pipe, int x0, int x1, int y,
 			     GBool noClip) {
   int x;
 
-  pipeSetXY(pipe, x0, y);
   if (noClip) {
+    pipeSetXY(pipe, x0, y);
     for (x = x0; x <= x1; ++x) {
-      pipeRun(pipe);
+      (this->*pipe->run)(pipe);
     }
     updateModX(x0);
     updateModX(x1);
     updateModY(y);
   } else {
+    if (x0 < state->clip->getXMinI()) {
+      x0 = state->clip->getXMinI();
+    }
+    if (x1 > state->clip->getXMaxI()) {
+      x1 = state->clip->getXMaxI();
+    }
+    pipeSetXY(pipe, x0, y);
     for (x = x0; x <= x1; ++x) {
       if (state->clip->test(x, y)) {
-	pipeRun(pipe);
+	(this->*pipe->run)(pipe);
 	updateModX(x);
 	updateModY(y);
       } else {
@@ -833,7 +1289,7 @@ inline void Splash::drawAALine(SplashPipe *pipe, int x0, int x1, int y) {
 
     if (t != 0) {
       pipe->shape = aaGamma[t];
-      pipeRun(pipe);
+      (this->*pipe->run)(pipe);
       updateModX(x);
       updateModY(y);
     } else {
@@ -865,19 +1321,22 @@ Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
 
   bitmap = bitmapA;
   vectorAntialias = vectorAntialiasA;
+  inShading = gFalse;
   state = new SplashState(bitmap->width, bitmap->height, vectorAntialias,
 			  screenParams);
   if (vectorAntialias) {
     aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize,
 			     1, splashModeMono1, gFalse);
     for (i = 0; i <= splashAASize * splashAASize; ++i) {
-      aaGamma[i] = splashPow((SplashCoord)i /
-			       (SplashCoord)(splashAASize * splashAASize),
-			     1.5);
+      aaGamma[i] = (Guchar)splashRound(
+		       splashPow((SplashCoord)i /
+				 (SplashCoord)(splashAASize * splashAASize),
+				 splashAAGamma) * 255);
     }
   } else {
     aaBuf = NULL;
   }
+  minLineWidth = 0;
   clearModRegion();
   debugMode = gFalse;
 }
@@ -887,6 +1346,7 @@ Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
   int i;
 
   bitmap = bitmapA;
+  inShading = gFalse;
   vectorAntialias = vectorAntialiasA;
   state = new SplashState(bitmap->width, bitmap->height, vectorAntialias,
 			  screenA);
@@ -894,13 +1354,15 @@ Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
     aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize,
 			     1, splashModeMono1, gFalse);
     for (i = 0; i <= splashAASize * splashAASize; ++i) {
-      aaGamma[i] = splashPow((SplashCoord)i /
-			       (SplashCoord)(splashAASize * splashAASize),
-			     1.5);
+      aaGamma[i] = (Guchar)splashRound(
+		       splashPow((SplashCoord)i /
+				 (SplashCoord)(splashAASize * splashAASize),
+				 splashAAGamma) * 255);
     }
   } else {
     aaBuf = NULL;
   }
+  minLineWidth = 0;
   clearModRegion();
   debugMode = gFalse;
 }
@@ -979,6 +1441,10 @@ SplashCoord Splash::getLineDashPhase() {
   return state->lineDashPhase;
 }
 
+GBool Splash::getStrokeAdjust() {
+  return state->strokeAdjust;
+}
+
 SplashClip *Splash::getClip() {
   return state->clip;
 }
@@ -1094,6 +1560,15 @@ void Splash::setInNonIsolatedGroup(SplashBitmap *alpha0BitmapA,
   state->inNonIsolatedGroup = gTrue;
 }
 
+void Splash::setTransfer(Guchar *red, Guchar *green, Guchar *blue,
+			 Guchar *gray) {
+  state->setTransfer(red, green, blue, gray);
+}
+
+void Splash::setOverprintMask(Guint overprintMask) {
+  state->overprintMask = overprintMask;
+}
+
 //------------------------------------------------------------------------
 // state save/restore
 //------------------------------------------------------------------------
@@ -1247,6 +1722,7 @@ void Splash::clear(SplashColorPtr color, Guchar alpha) {
 
 SplashError Splash::stroke(SplashPath *path) {
   SplashPath *path2, *dPath;
+  SplashCoord d1, d2, t1, t2, w;
 
   if (debugMode) {
     printf("stroke [dash:%d] [width:%.2f]:\n",
@@ -1262,12 +1738,44 @@ SplashError Splash::stroke(SplashPath *path) {
     dPath = makeDashedPath(path2);
     delete path2;
     path2 = dPath;
+    if (path2->length == 0) {
+      delete path2;
+      return splashErrEmptyPath;
+    }
+  }
+
+  // transform a unit square, and take the half the max of the two
+  // diagonals; the product of this number and the line width is the
+  // (approximate) transformed line width
+  t1 = state->matrix[0] + state->matrix[2];
+  t2 = state->matrix[1] + state->matrix[3];
+  d1 = t1 * t1 + t2 * t2;
+  t1 = state->matrix[0] - state->matrix[2];
+  t2 = state->matrix[1] - state->matrix[3];
+  d2 = t1 * t1 + t2 * t2;
+  if (d2 > d1) {
+    d1 = d2;
   }
-  if (state->lineWidth == 0) {
-    strokeNarrow(path2);
+  d1 *= 0.5;
+  if (d1 > 0 &&
+      d1 * state->lineWidth * state->lineWidth < minLineWidth * minLineWidth) {
+    w = minLineWidth / splashSqrt(d1);
+    strokeWide(path2, w);
+  } else if (bitmap->mode == splashModeMono1) {
+    // this gets close to Adobe's behavior in mono mode
+    if (d1 <= 2) {
+      strokeNarrow(path2);
+    } else {
+      strokeWide(path2, state->lineWidth);
+    }
   } else {
-    strokeWide(path2);
+    if (state->lineWidth == 0) {
+      strokeNarrow(path2);
+    } else {
+      strokeWide(path2, state->lineWidth);
+    }
   }
+
   delete path2;
   return splashOk;
 }
@@ -1276,8 +1784,8 @@ void Splash::strokeNarrow(SplashPath *path) {
   SplashPipe pipe;
   SplashXPath *xPath;
   SplashXPathSeg *seg;
-  int x0, x1, x2, x3, y0, y1, x, y, t;
-  SplashCoord dx, dy, dxdy;
+  int x0, x1, y0, y1, xa, xb, y;
+  SplashCoord dxdy;
   SplashClipResult clipRes;
   int nClipRes[3];
   int i;
@@ -1286,86 +1794,75 @@ void Splash::strokeNarrow(SplashPath *path) {
 
   xPath = new SplashXPath(path, state->matrix, state->flatness, gFalse);
 
-  pipeInit(&pipe, 0, 0, state->strokePattern, NULL, state->strokeAlpha,
-	   gFalse, gFalse, state->strokePattern, gTrue);
+  pipeInit(&pipe, 0, 0, state->strokePattern, NULL,
+	   (Guchar)splashRound(state->strokeAlpha * 255),
+	   gFalse, gFalse);
 
   for (i = 0, seg = xPath->segs; i < xPath->length; ++i, ++seg) {
-
-    x0 = splashFloor(seg->x0);
-    x1 = splashFloor(seg->x1);
-    y0 = splashFloor(seg->y0);
-    y1 = splashFloor(seg->y1);
-
-    // horizontal segment
-    if (y0 == y1) {
-      if (x0 > x1) {
-	t = x0; x0 = x1; x1 = t;
-      }
-      if ((clipRes = state->clip->testSpan(x0, x1, y0))
-	  != splashClipAllOutside) {
-	drawSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside);
-      }
-
-    // segment with |dx| > |dy|
-    } else if (splashAbs(seg->dxdy) > 1) {
-      dx = seg->x1 - seg->x0;
-      dy = seg->y1 - seg->y0;
-      dxdy = seg->dxdy;
-      if (y0 > y1) {
-	t = y0; y0 = y1; y1 = t;
-	t = x0; x0 = x1; x1 = t;
-	dx = -dx;
-	dy = -dy;
-      }
-      if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0,
-					   x0 <= x1 ? x1 : x0, y1))
-	  != splashClipAllOutside) {
-	if (dx > 0) {
-	  x2 = x0;
-	  x3 = splashFloor(seg->x0 + ((SplashCoord)y0 + 1 - seg->y0) * dxdy);
-	  drawSpan(&pipe, x2, (x2 <= x3 - 1) ? x3 - 1 : x2, y0,
-		   clipRes == splashClipAllInside);
-	  x2 = x3;
-	  for (y = y0 + 1; y <= y1 - 1; ++y) {
-	    x3 = splashFloor(seg->x0 + ((SplashCoord)y + 1 - seg->y0) * dxdy);
-	    drawSpan(&pipe, x2, x3 - 1, y, clipRes == splashClipAllInside);
-	    x2 = x3;
+    if (seg->y0 <= seg->y1) {
+      y0 = splashFloor(seg->y0);
+      y1 = splashFloor(seg->y1);
+      x0 = splashFloor(seg->x0);
+      x1 = splashFloor(seg->x1);
+    } else {
+      y0 = splashFloor(seg->y1);
+      y1 = splashFloor(seg->y0);
+      x0 = splashFloor(seg->x1);
+      x1 = splashFloor(seg->x0);
+    }
+    if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0,
+					 x0 <= x1 ? x1 : x0, y1))
+	!= splashClipAllOutside) {
+      if (y0 == y1) {
+	if (x0 <= x1) {
+	  drawSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside);
+	} else {
+	  drawSpan(&pipe, x1, x0, y0, clipRes == splashClipAllInside);
+	}
+      } else {
+	dxdy = seg->dxdy;
+	if (y0 < state->clip->getYMinI()) {
+	  y0 = state->clip->getYMinI();
+	  x0 = splashFloor(seg->x0 + ((SplashCoord)y0 - seg->y0) * dxdy);
+	}
+	if (y1 > state->clip->getYMaxI()) {
+	  y1 = state->clip->getYMaxI();
+	  x1 = splashFloor(seg->x0 + ((SplashCoord)y1 - seg->y0) * dxdy);
+	}
+	if (x0 <= x1) {
+	  xa = x0;
+	  for (y = y0; y <= y1; ++y) {
+	    if (y < y1) {
+	      xb = splashFloor(seg->x0 +
+			       ((SplashCoord)y + 1 - seg->y0) * dxdy);
+	    } else {
+	      xb = x1 + 1;
+	    }
+	    if (xa == xb) {
+	      drawPixel(&pipe, xa, y, clipRes == splashClipAllInside);
+	    } else {
+	      drawSpan(&pipe, xa, xb - 1, y, clipRes == splashClipAllInside);
+	    }
+	    xa = xb;
 	  }
-	  drawSpan(&pipe, x2, x2 <= x1 ? x1 : x2, y1,
-		   clipRes == splashClipAllInside);
 	} else {
-	  x2 = x0;
-	  x3 = splashFloor(seg->x0 + ((SplashCoord)y0 + 1 - seg->y0) * dxdy);
-	  drawSpan(&pipe, (x3 + 1 <= x2) ? x3 + 1 : x2, x2, y0,
-		   clipRes == splashClipAllInside);
-	  x2 = x3;
-	  for (y = y0 + 1; y <= y1 - 1; ++y) {
-	    x3 = splashFloor(seg->x0 + ((SplashCoord)y + 1 - seg->y0) * dxdy);
-	    drawSpan(&pipe, x3 + 1, x2, y, clipRes == splashClipAllInside);
-	    x2 = x3;
+	  xa = x0;
+	  for (y = y0; y <= y1; ++y) {
+	    if (y < y1) {
+	      xb = splashFloor(seg->x0 +
+			       ((SplashCoord)y + 1 - seg->y0) * dxdy);
+	    } else {
+	      xb = x1 - 1;
+	    }
+	    if (xa == xb) {
+	      drawPixel(&pipe, xa, y, clipRes == splashClipAllInside);
+	    } else {
+	      drawSpan(&pipe, xb + 1, xa, y, clipRes == splashClipAllInside);
+	    }
+	    xa = xb;
 	  }
-	  drawSpan(&pipe, x1, (x1 <= x2) ? x2 : x1, y1,
-		   clipRes == splashClipAllInside);
 	}
       }
-
-    // segment with |dy| > |dx|
-    } else {
-      dxdy = seg->dxdy;
-      if (y0 > y1) {
-	t = x0; x0 = x1; x1 = t;
-	t = y0; y0 = y1; y1 = t;
-      }
-      if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0,
-					   x0 <= x1 ? x1 : x0, y1))
-	  != splashClipAllOutside) {
-	drawPixel(&pipe, x0, y0, clipRes == splashClipAllInside);
-	for (y = y0 + 1; y <= y1 - 1; ++y) {
-	  x = splashFloor(seg->x0 + ((SplashCoord)y - seg->y0) * dxdy);
-	  drawPixel(&pipe, x, y, clipRes == splashClipAllInside);
-	}
-	drawPixel(&pipe, x1, y1, clipRes == splashClipAllInside);
-    }
     }
     ++nClipRes[clipRes];
   }
@@ -1381,10 +1878,10 @@ void Splash::strokeNarrow(SplashPath *path) {
   delete xPath;
 }
 
-void Splash::strokeWide(SplashPath *path) {
+void Splash::strokeWide(SplashPath *path, SplashCoord w) {
   SplashPath *path2;
 
-  path2 = makeStrokePath(path, gFalse);
+  path2 = makeStrokePath(path, w, gFalse);
   fillWithPattern(path2, gFalse, state->strokePattern, state->strokeAlpha);
   delete path2;
 }
@@ -1490,18 +1987,18 @@ void Splash::flattenCurve(SplashCoord x0, SplashCoord y0,
 
     // otherwise, subdivide the curve
     } else {
-      xl1 = (xl0 + xx1) * 0.5;
-      yl1 = (yl0 + yy1) * 0.5;
-      xh = (xx1 + xx2) * 0.5;
-      yh = (yy1 + yy2) * 0.5;
-      xl2 = (xl1 + xh) * 0.5;
-      yl2 = (yl1 + yh) * 0.5;
-      xr2 = (xx2 + xr3) * 0.5;
-      yr2 = (yy2 + yr3) * 0.5;
-      xr1 = (xh + xr2) * 0.5;
-      yr1 = (yh + yr2) * 0.5;
-      xr0 = (xl2 + xr1) * 0.5;
-      yr0 = (yl2 + yr1) * 0.5;
+      xl1 = splashAvg(xl0, xx1);
+      yl1 = splashAvg(yl0, yy1);
+      xh = splashAvg(xx1, xx2);
+      yh = splashAvg(yy1, yy2);
+      xl2 = splashAvg(xl1, xh);
+      yl2 = splashAvg(yl1, yh);
+      xr2 = splashAvg(xx2, xr3);
+      yr2 = splashAvg(yy2, yr3);
+      xr1 = splashAvg(xh, xr2);
+      yr1 = splashAvg(yh, yr2);
+      xr0 = splashAvg(xl2, xr1);
+      yr0 = splashAvg(yl2, yr1);
       // add the new subdivision points
       p3 = (p1 + p2) / 2;
       cx[p1][1] = xl1;  cy[p1][1] = yl1;
@@ -1528,15 +2025,21 @@ SplashPath *Splash::makeDashedPath(SplashPath *path) {
   for (i = 0; i < state->lineDashLength; ++i) {
     lineDashTotal += state->lineDash[i];
   }
+  // Acrobat simply draws nothing if the dash array is [0]
+  if (lineDashTotal == 0) {
+    return new SplashPath();
+  }
   lineDashStartPhase = state->lineDashPhase;
   i = splashFloor(lineDashStartPhase / lineDashTotal);
   lineDashStartPhase -= (SplashCoord)i * lineDashTotal;
   lineDashStartOn = gTrue;
   lineDashStartIdx = 0;
-  while (lineDashStartPhase >= state->lineDash[lineDashStartIdx]) {
-    lineDashStartOn = !lineDashStartOn;
-    lineDashStartPhase -= state->lineDash[lineDashStartIdx];
-    ++lineDashStartIdx;
+  if (lineDashStartPhase > 0) {
+    while (lineDashStartPhase >= state->lineDash[lineDashStartIdx]) {
+      lineDashStartOn = !lineDashStartOn;
+      lineDashStartPhase -= state->lineDash[lineDashStartIdx];
+      ++lineDashStartIdx;
+    }
   }
 
   dPath = new SplashPath();
@@ -1646,15 +2149,49 @@ SplashError Splash::fillWithPattern(SplashPath *path, GBool eo,
   if (path->length == 0) {
     return splashErrEmptyPath;
   }
+  if (pathAllOutside(path)) {
+    opClipRes = splashClipAllOutside;
+    return splashOk;
+  }
+
+  // add stroke adjustment hints for filled rectangles -- this only
+  // applies to paths that consist of a single subpath
+  // (this appears to match Acrobat's behavior)
+  if (state->strokeAdjust && !path->hints) {
+    int n;
+    n = path->getLength();
+    if (n == 4 &&
+	!(path->flags[0] & splashPathClosed) &&
+	!(path->flags[1] & splashPathLast) &&
+	!(path->flags[2] & splashPathLast)) {
+      path->close(gTrue);
+      path->addStrokeAdjustHint(0, 2, 0, 4);
+      path->addStrokeAdjustHint(1, 3, 0, 4);
+    } else if (n == 5 &&
+	       (path->flags[0] & splashPathClosed) &&
+	       !(path->flags[1] & splashPathLast) &&
+	       !(path->flags[2] & splashPathLast) &&
+	       !(path->flags[3] & splashPathLast)) {
+      path->addStrokeAdjustHint(0, 2, 0, 4);
+      path->addStrokeAdjustHint(1, 3, 0, 4);
+    }
+  }
+
   xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue);
-  if (vectorAntialias) {
+  if (vectorAntialias && !inShading) {
     xPath->aaScale();
   }
   xPath->sort();
-  scanner = new SplashXPathScanner(xPath, eo);
+  yMinI = state->clip->getYMinI();
+  yMaxI = state->clip->getYMaxI();
+  if (vectorAntialias && !inShading) {
+    yMinI = yMinI * splashAASize;
+    yMaxI = (yMaxI + 1) * splashAASize - 1;
+  }
+  scanner = new SplashXPathScanner(xPath, eo, yMinI, yMaxI);
 
   // get the min and max x and y values
-  if (vectorAntialias) {
+  if (vectorAntialias && !inShading) {
     scanner->getBBoxAA(&xMinI, &yMinI, &xMaxI, &yMaxI);
   } else {
     scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI);
@@ -1663,19 +2200,15 @@ SplashError Splash::fillWithPattern(SplashPath *path, GBool eo,
   // check clipping
   if ((clipRes = state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI))
       != splashClipAllOutside) {
-
-    // limit the y range
-    if (yMinI < state->clip->getYMinI()) {
-      yMinI = state->clip->getYMinI();
-    }
-    if (yMaxI > state->clip->getYMaxI()) {
-      yMaxI = state->clip->getYMaxI();
+    if (scanner->hasPartialClip()) {
+      clipRes = splashClipPartial;
     }
 
-    pipeInit(&pipe, 0, yMinI, pattern, NULL, alpha, vectorAntialias, gFalse, pattern);
+    pipeInit(&pipe, 0, yMinI, pattern, NULL, (Guchar)splashRound(alpha * 255),
+	     vectorAntialias && !inShading, gFalse);
 
     // draw the spans
-    if (vectorAntialias) {
+    if (vectorAntialias && !inShading) {
       for (y = yMinI; y <= yMaxI; ++y) {
 	scanner->renderAALine(aaBuf, &x0, &x1, y);
 	if (clipRes != splashClipAllInside) {
@@ -1710,6 +2243,73 @@ SplashError Splash::fillWithPattern(SplashPath *path, GBool eo,
   return splashOk;
 }
 
+GBool Splash::pathAllOutside(SplashPath *path) {
+  SplashCoord xMin1, yMin1, xMax1, yMax1;
+  SplashCoord xMin2, yMin2, xMax2, yMax2;
+  SplashCoord x, y;
+  int xMinI, yMinI, xMaxI, yMaxI;
+  int i;
+
+  xMin1 = xMax1 = path->pts[0].x;
+  yMin1 = yMax1 = path->pts[0].y;
+  for (i = 1; i < path->length; ++i) {
+    if (path->pts[i].x < xMin1) {
+      xMin1 = path->pts[i].x;
+    } else if (path->pts[i].x > xMax1) {
+      xMax1 = path->pts[i].x;
+    }
+    if (path->pts[i].y < yMin1) {
+      yMin1 = path->pts[i].y;
+    } else if (path->pts[i].y > yMax1) {
+      yMax1 = path->pts[i].y;
+    }
+  }
+
+  transform(state->matrix, xMin1, yMin1, &x, &y);
+  xMin2 = xMax2 = x;
+  yMin2 = yMax2 = y;
+  transform(state->matrix, xMin1, yMax1, &x, &y);
+  if (x < xMin2) {
+    xMin2 = x;
+  } else if (x > xMax2) {
+    xMax2 = x;
+  }
+  if (y < yMin2) {
+    yMin2 = y;
+  } else if (y > yMax2) {
+    yMax2 = y;
+  }
+  transform(state->matrix, xMax1, yMin1, &x, &y);
+  if (x < xMin2) {
+    xMin2 = x;
+  } else if (x > xMax2) {
+    xMax2 = x;
+  }
+  if (y < yMin2) {
+    yMin2 = y;
+  } else if (y > yMax2) {
+    yMax2 = y;
+  }
+  transform(state->matrix, xMax1, yMax1, &x, &y);
+  if (x < xMin2) {
+    xMin2 = x;
+  } else if (x > xMax2) {
+    xMax2 = x;
+  }
+  if (y < yMin2) {
+    yMin2 = y;
+  } else if (y > yMax2) {
+    yMax2 = y;
+  }
+  xMinI = splashFloor(xMin2);
+  yMinI = splashFloor(yMin2);
+  xMaxI = splashFloor(xMax2);
+  yMaxI = splashFloor(yMax2);
+
+  return state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI) ==
+         splashClipAllOutside;
+}
+
 SplashError Splash::xorFill(SplashPath *path, GBool eo) {
   SplashPipe pipe;
   SplashXPath *xPath;
@@ -1723,7 +2323,8 @@ SplashError Splash::xorFill(SplashPath *path, GBool eo) {
   }
   xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue);
   xPath->sort();
-  scanner = new SplashXPathScanner(xPath, eo);
+  scanner = new SplashXPathScanner(xPath, eo, state->clip->getYMinI(),
+				   state->clip->getYMaxI());
 
   // get the min and max x and y values
   scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI);
@@ -1731,18 +2332,13 @@ SplashError Splash::xorFill(SplashPath *path, GBool eo) {
   // check clipping
   if ((clipRes = state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI))
       != splashClipAllOutside) {
-
-    // limit the y range
-    if (yMinI < state->clip->getYMinI()) {
-      yMinI = state->clip->getYMinI();
-    }
-    if (yMaxI > state->clip->getYMaxI()) {
-      yMaxI = state->clip->getYMaxI();
+    if (scanner->hasPartialClip()) {
+      clipRes = splashClipPartial;
     }
 
     origBlendFunc = state->blendFunc;
     state->blendFunc = &blendXor;
-    pipeInit(&pipe, 0, yMinI, state->fillPattern, NULL, 1, gFalse, gFalse, state->fillPattern);
+    pipeInit(&pipe, 0, yMinI, state->fillPattern, NULL, 255, gFalse, gFalse);
 
     // draw the spans
     for (y = yMinI; y <= yMaxI; ++y) {
@@ -1856,14 +2452,14 @@ void Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph, GBool noClip)
   if (noClip) {
     if (glyph->aa) {
       pipeInit(&pipe, xStart, yStart,
-               state->fillPattern, NULL, state->fillAlpha, gTrue, gFalse, state->fillPattern);
+               state->fillPattern, NULL, (Guchar)splashRound(state->fillAlpha * 255), gTrue, gFalse);
       for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) {
         pipeSetXY(&pipe, xStart, y1);
         for (xx = 0, x1 = xStart; xx < xxLimit; ++xx, ++x1) {
           alpha = p[xx];
           if (alpha != 0) {
-            pipe.shape = (SplashCoord)(alpha / 255.0);
-            pipeRun(&pipe);
+            pipe.shape = alpha;
+            (this->*pipe.run)(&pipe);
             updateModX(x1);
             updateModY(y1);
           } else {
@@ -1876,14 +2472,14 @@ void Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph, GBool noClip)
       const int widthEight = splashCeil(glyph->w / 8.0);
 
       pipeInit(&pipe, xStart, yStart,
-               state->fillPattern, NULL, state->fillAlpha, gFalse, gFalse, state->fillPattern);
+               state->fillPattern, NULL, (Guchar)splashRound(state->fillAlpha * 255), gFalse, gFalse);
       for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) {
         pipeSetXY(&pipe, xStart, y1);
         for (xx = 0, x1 = xStart; xx < xxLimit; xx += 8) {
           alpha0 = (xShift > 0 ? (p[xx / 8] << xShift) | (p[xx / 8 + 1] >> (8 - xShift)) : p[xx / 8]);
           for (xx1 = 0; xx1 < 8 && xx + xx1 < xxLimit; ++xx1, ++x1) {
             if (alpha0 & 0x80) {
-              pipeRun(&pipe);
+              (this->*pipe.run)(&pipe);
               updateModX(x1);
               updateModY(y1);
             } else {
@@ -1898,15 +2494,15 @@ void Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph, GBool noClip)
   } else {
     if (glyph->aa) {
       pipeInit(&pipe, xStart, yStart,
-               state->fillPattern, NULL, state->fillAlpha, gTrue, gFalse, state->fillPattern);
+               state->fillPattern, NULL, (Guchar)splashRound(state->fillAlpha * 255), gTrue, gFalse);
       for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) {
         pipeSetXY(&pipe, xStart, y1);
         for (xx = 0, x1 = xStart; xx < xxLimit; ++xx, ++x1) {
           if (state->clip->test(x1, y1)) {
             alpha = p[xx];
             if (alpha != 0) {
-              pipe.shape = (SplashCoord)(alpha / 255.0);
-              pipeRun(&pipe);
+              pipe.shape = alpha;
+              (this->*pipe.run)(&pipe);
               updateModX(x1);
               updateModY(y1);
             } else {
@@ -1922,7 +2518,7 @@ void Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph, GBool noClip)
       const int widthEight = splashCeil(glyph->w / 8.0);
 
       pipeInit(&pipe, xStart, yStart,
-               state->fillPattern, NULL, state->fillAlpha, gFalse, gFalse, state->fillPattern);
+               state->fillPattern, NULL, (Guchar)splashRound(state->fillAlpha * 255), gFalse, gFalse);
       for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) {
         pipeSetXY(&pipe, xStart, y1);
         for (xx = 0, x1 = xStart; xx < xxLimit; xx += 8) {
@@ -1930,7 +2526,7 @@ void Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph, GBool noClip)
           for (xx1 = 0; xx1 < 8 && xx + xx1 < xxLimit; ++xx1, ++x1) {
             if (state->clip->test(x1, y1)) {
               if (alpha0 & 0x80) {
-                pipeRun(&pipe);
+                (this->*pipe.run)(&pipe);
                 updateModX(x1);
                 updateModY(y1);
               } else {
@@ -2099,8 +2695,8 @@ SplashError Splash::fillImageMask(SplashImageMaskSource src, void *srcData,
   pixBuf = (SplashColorPtr)gmallocn((yp + 1), w);
 
   // initialize the pixel pipe
-  pipeInit(&pipe, 0, 0, state->fillPattern, NULL, state->fillAlpha,
-	   gTrue, gFalse, state->fillPattern);
+  pipeInit(&pipe, 0, 0, state->fillPattern, NULL, (Guchar)splashRound(state->fillAlpha * 255),
+	   gTrue, gFalse);
   if (vectorAntialias) {
     drawAAPixelInit();
   }
@@ -2206,9 +2802,9 @@ SplashError Splash::fillImageMask(SplashImageMaskSource src, void *srcData,
 
       // blend fill color with background
       if (pixAcc != 0) {
-	pipe.shape = (pixAcc == n * m)
+	pipe.shape = ((pixAcc == n * m)
 	                 ? (SplashCoord)1
-	                 : (SplashCoord)pixAcc / (SplashCoord)(n * m);
+	                 : (SplashCoord)pixAcc / (SplashCoord)(n * m)) * 255;
 	if (vectorAntialias && clipRes2 != splashClipAllInside) {
 	  drawAAPixel(&pipe, tx + x2, ty + y2);
 	} else {
@@ -2412,9 +3008,9 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 #endif
 
   // initialize the pixel pipe
-  pipeInit(&pipe, 0, 0, NULL, pix, state->fillAlpha,
+  pipeInit(&pipe, 0, 0, NULL, pix, (Guchar)splashRound(state->fillAlpha * 255),
 	   srcAlpha || (vectorAntialias && clipRes != splashClipAllInside),
-	   gFalse, opImagePattern);
+	   gFalse);
   if (vectorAntialias) {
     drawAAPixelInit();
   }
@@ -2531,7 +3127,7 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 	  }
 	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
 	  alphaMul = pixMul * (1.0 / 255.0);
-	  alpha = (SplashCoord)alphaAcc * alphaMul;
+	  alpha = (SplashCoord)alphaAcc * pixMul;
 
 	  if (alpha > 0) {
 	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
@@ -2597,7 +3193,7 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 	  }
 	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
 	  alphaMul = pixMul * (1.0 / 255.0);
-	  alpha = (SplashCoord)alphaAcc * alphaMul;
+	  alpha = (SplashCoord)alphaAcc * pixMul;
 
 	  if (alpha > 0) {
 	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
@@ -2664,7 +3260,7 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 	  }
 	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
 	  alphaMul = pixMul * (1.0 / 255.0);
-	  alpha = (SplashCoord)alphaAcc * alphaMul;
+	  alpha = (SplashCoord)alphaAcc * pixMul;
 
 	  if (alpha > 0) {
 	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
@@ -2735,7 +3331,7 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 	  }
 	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
 	  alphaMul = pixMul * (1.0 / 255.0);
-	  alpha = (SplashCoord)alphaAcc * alphaMul;
+	  alpha = (SplashCoord)alphaAcc * pixMul;
 
 	  if (alpha > 0) {
 	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
@@ -2877,7 +3473,7 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 
 	  // set pixel
 	  if (vectorAntialias && clipRes != splashClipAllInside) {
-	    pipe.shape = (SplashCoord)1;
+	    pipe.shape = (SplashCoord)255;
 	    drawAAPixel(&pipe, tx + x2, ty + y2);
 	  } else {
 	    drawPixel(&pipe, tx + x2, ty + y2,
@@ -2937,7 +3533,7 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 
 	  // set pixel
 	  if (vectorAntialias && clipRes != splashClipAllInside) {
-	    pipe.shape = (SplashCoord)1;
+	    pipe.shape = (SplashCoord)255;
 	    drawAAPixel(&pipe, tx + x2, ty + y2);
 	  } else {
 	    drawPixel(&pipe, tx + x2, ty + y2,
@@ -2998,7 +3594,7 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 
 	  // set pixel
 	  if (vectorAntialias && clipRes != splashClipAllInside) {
-	    pipe.shape = (SplashCoord)1;
+	    pipe.shape = (SplashCoord)255;
 	    drawAAPixel(&pipe, tx + x2, ty + y2);
 	  } else {
 	    drawPixel(&pipe, tx + x2, ty + y2,
@@ -3060,7 +3656,7 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 
 	  // set pixel
 	  if (vectorAntialias && clipRes != splashClipAllInside) {
-	    pipe.shape = (SplashCoord)1;
+	    pipe.shape = (SplashCoord)255;
 	    drawAAPixel(&pipe, tx + x2, ty + y2);
 	  } else {
 	    drawPixel(&pipe, tx + x2, ty + y2,
@@ -3103,7 +3699,7 @@ SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
   }
 
   if (src->alpha) {
-    pipeInit(&pipe, xDest, yDest, NULL, pixel, state->fillAlpha,
+    pipeInit(&pipe, xDest, yDest, NULL, pixel, (Guchar)splashRound(state->fillAlpha * 255),
 	     gTrue, nonIsolated);
     for (y = 0; y < h; ++y) {
       pipeSetXY(&pipe, xDest, yDest + y);
@@ -3114,8 +3710,8 @@ SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
 	  // this uses shape instead of alpha, which isn't technically
 	  // correct, but works out the same
 	  src->getPixel(xSrc + x, ySrc + y, pixel);
-	  pipe.shape = (SplashCoord)(alpha / 255.0);
-	  pipeRun(&pipe);
+	  pipe.shape = alpha;
+	  (this->*pipe.run)(&pipe);
 	  updateModX(xDest + x);
 	  updateModY(yDest + y);
 	} else {
@@ -3124,14 +3720,14 @@ SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
       }
     }
   } else {
-    pipeInit(&pipe, xDest, yDest, NULL, pixel, state->fillAlpha,
+    pipeInit(&pipe, xDest, yDest, NULL, pixel, (Guchar)splashRound(state->fillAlpha * 255),
 	     gFalse, nonIsolated);
     for (y = 0; y < h; ++y) {
       pipeSetXY(&pipe, xDest, yDest + y);
       for (x = 0; x < w; ++x) {
 	if (noClip || state->clip->test(xDest + x, yDest + y)) {
 	  src->getPixel(xSrc + x, ySrc + y, pixel);
-	  pipeRun(&pipe);
+	  (this->*pipe.run)(&pipe);
 	  updateModX(xDest + x);
 	  updateModY(yDest + y);
 	} else {
@@ -3334,7 +3930,7 @@ GBool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
   SplashPipe pipe;
   SplashColor cSrcVal;
 
-  pipeInit(&pipe, 0, 0, NULL, cSrcVal, state->strokeAlpha, gFalse, gFalse, NULL, gTrue);
+  pipeInit(&pipe, 0, 0, NULL, cSrcVal, (Guchar)splashRound(state->strokeAlpha * 255), gFalse, gFalse);
 
   if (vectorAntialias) {
     if (aaBuf == NULL)
@@ -3675,166 +4271,246 @@ SplashError Splash::blitTransparent(SplashBitmap *src, int xSrc, int ySrc,
   return splashOk;
 }
 
-SplashPath *Splash::makeStrokePath(SplashPath *path, GBool flatten) {
-  SplashPath *pathIn, *pathOut;
-  SplashCoord w, d, dx, dy, wdx, wdy, dxNext, dyNext, wdxNext, wdyNext;
+SplashPath *Splash::makeStrokePath(SplashPath *path, SplashCoord w,
+				    GBool flatten) {
+  SplashPath *pathIn, *dashPath, *pathOut;
+  SplashCoord d, dx, dy, wdx, wdy, dxNext, dyNext, wdxNext, wdyNext;
   SplashCoord crossprod, dotprod, miter, m;
   GBool first, last, closed;
-  int subpathStart, next, i;
+  int subpathStart0, subpathStart1, seg, i0, i1, j0, j1, k0, k1;
   int left0, left1, left2, right0, right1, right2, join0, join1, join2;
   int leftFirst, rightFirst, firstPt;
 
+  pathOut = new SplashPath();
+
+  if (path->length == 0) {
+    return pathOut;
+  }
+
   if (flatten) {
     pathIn = flattenPath(path, state->matrix, state->flatness);
     if (state->lineDashLength > 0) {
-      pathOut = makeDashedPath(pathIn);
+      dashPath = makeDashedPath(pathIn);
       delete pathIn;
-      pathIn = pathOut;
+      pathIn = dashPath;
+      if (pathIn->length == 0) {
+	delete pathIn;
+	return pathOut;
+      }
     }
   } else {
     pathIn = path;
   }
 
-  subpathStart = 0; // make gcc happy
+  subpathStart0 = subpathStart1 = 0; // make gcc happy
+  seg = 0; // make gcc happy
   closed = gFalse; // make gcc happy
   left0 = left1 = right0 = right1 = join0 = join1 = 0; // make gcc happy
   leftFirst = rightFirst = firstPt = 0; // make gcc happy
 
-  pathOut = new SplashPath();
-  w = state->lineWidth;
-
-  for (i = 0; i < pathIn->length - 1; ++i) {
-    if (pathIn->flags[i] & splashPathLast) {
-      continue;
+  i0 = 0;
+  for (i1 = i0;
+       !(pathIn->flags[i1] & splashPathLast) &&
+	 i1 + 1 < pathIn->length &&
+	 pathIn->pts[i1+1].x == pathIn->pts[i1].x &&
+	 pathIn->pts[i1+1].y == pathIn->pts[i1].y;
+       ++i1) ;
+
+  while (i1 < pathIn->length) {
+    if ((first = pathIn->flags[i0] & splashPathFirst)) {
+      subpathStart0 = i0;
+      subpathStart1 = i1;
+      seg = 0;
+      closed = pathIn->flags[i0] & splashPathClosed;
     }
-    if ((first = pathIn->flags[i] & splashPathFirst)) {
-      subpathStart = i;
-      closed = pathIn->flags[i] & splashPathClosed;
+    j0 = i1 + 1;
+    if (j0 < pathIn->length) {
+      for (j1 = j0;
+	   !(pathIn->flags[j1] & splashPathLast) &&
+	     j1 + 1 < pathIn->length &&
+	     pathIn->pts[j1+1].x == pathIn->pts[j1].x &&
+	     pathIn->pts[j1+1].y == pathIn->pts[j1].y;
+	   ++j1) ;
+    } else {
+      j1 = j0;
     }
-    last = pathIn->flags[i+1] & splashPathLast;
-
-    // compute the deltas for segment (i, i+1)
-    d = splashDist(pathIn->pts[i].x, pathIn->pts[i].y,
-		   pathIn->pts[i+1].x, pathIn->pts[i+1].y);
-    if (d == 0) {
-      // we need to draw end caps on zero-length lines
-      //~ not clear what the behavior should be for splashLineCapButt
-      //~   with d==0
-      dx = 0;
-      dy = 1;
+    if (pathIn->flags[i1] & splashPathLast) {
+      if (first && state->lineCap == splashLineCapRound) {
+	// special case: zero-length subpath with round line caps -->
+	// draw a circle
+	pathOut->moveTo(pathIn->pts[i0].x + (SplashCoord)0.5 * w,
+			pathIn->pts[i0].y);
+	pathOut->curveTo(pathIn->pts[i0].x + (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].y + bezierCircle2 * w,
+			 pathIn->pts[i0].x + bezierCircle2 * w,
+			 pathIn->pts[i0].y + (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].x,
+			 pathIn->pts[i0].y + (SplashCoord)0.5 * w);
+	pathOut->curveTo(pathIn->pts[i0].x - bezierCircle2 * w,
+			 pathIn->pts[i0].y + (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].x - (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].y + bezierCircle2 * w,
+			 pathIn->pts[i0].x - (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].y);
+	pathOut->curveTo(pathIn->pts[i0].x - (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].y - bezierCircle2 * w,
+			 pathIn->pts[i0].x - bezierCircle2 * w,
+			 pathIn->pts[i0].y - (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].x,
+			 pathIn->pts[i0].y - (SplashCoord)0.5 * w);
+	pathOut->curveTo(pathIn->pts[i0].x + bezierCircle2 * w,
+			 pathIn->pts[i0].y - (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].x + (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].y - bezierCircle2 * w,
+			 pathIn->pts[i0].x + (SplashCoord)0.5 * w,
+			 pathIn->pts[i0].y);
+	pathOut->close();
+      }
+      i0 = j0;
+      i1 = j1;
+      continue;
+    }
+    last = pathIn->flags[j1] & splashPathLast;
+    if (last) {
+      k0 = subpathStart1 + 1;
     } else {
-      d = (SplashCoord)1 / d;
-      dx = d * (pathIn->pts[i+1].x - pathIn->pts[i].x);
-      dy = d * (pathIn->pts[i+1].y - pathIn->pts[i].y);
+      k0 = j1 + 1;
     }
+    for (k1 = k0;
+	 !(pathIn->flags[k1] & splashPathLast) &&
+	   k1 + 1 < pathIn->length &&
+	   pathIn->pts[k1+1].x == pathIn->pts[k1].x &&
+	   pathIn->pts[k1+1].y == pathIn->pts[k1].y;
+	 ++k1) ;
+
+    // compute the deltas for segment (i1, j0)
+#if USE_FIXEDPOINT
+    // the 1/d value can be small, which introduces significant
+    // inaccuracies in fixed point mode
+    d = splashDist(pathIn->pts[i1].x, pathIn->pts[i1].y,
+		   pathIn->pts[j0].x, pathIn->pts[j0].y);
+    dx = (pathIn->pts[j0].x - pathIn->pts[i1].x) / d;
+    dy = (pathIn->pts[j0].y - pathIn->pts[i1].y) / d;
+#else
+    d = (SplashCoord)1 / splashDist(pathIn->pts[i1].x, pathIn->pts[i1].y,
+				    pathIn->pts[j0].x, pathIn->pts[j0].y);
+    dx = d * (pathIn->pts[j0].x - pathIn->pts[i1].x);
+    dy = d * (pathIn->pts[j0].y - pathIn->pts[i1].y);
+#endif
     wdx = (SplashCoord)0.5 * w * dx;
     wdy = (SplashCoord)0.5 * w * dy;
 
-    // compute the deltas for segment (i+1, next)
-    next = last ? subpathStart + 1 : i + 2;
-    d = splashDist(pathIn->pts[i+1].x, pathIn->pts[i+1].y,
-		   pathIn->pts[next].x, pathIn->pts[next].y);
-    if (d == 0) {
-      // we need to draw end caps on zero-length lines
-      //~ not clear what the behavior should be for splashLineCapButt
-      //~   with d==0
-      dxNext = 0;
-      dyNext = 1;
-    } else {
-      d = (SplashCoord)1 / d;
-      dxNext = d * (pathIn->pts[next].x - pathIn->pts[i+1].x);
-      dyNext = d * (pathIn->pts[next].y - pathIn->pts[i+1].y);
-    }
-    wdxNext = (SplashCoord)0.5 * w * dxNext;
-    wdyNext = (SplashCoord)0.5 * w * dyNext;
-
     // draw the start cap
-    pathOut->moveTo(pathIn->pts[i].x - wdy, pathIn->pts[i].y + wdx);
-    if (i == subpathStart) {
+    pathOut->moveTo(pathIn->pts[i0].x - wdy, pathIn->pts[i0].y + wdx);
+    if (i0 == subpathStart0) {
       firstPt = pathOut->length - 1;
     }
     if (first && !closed) {
       switch (state->lineCap) {
       case splashLineCapButt:
-	pathOut->lineTo(pathIn->pts[i].x + wdy, pathIn->pts[i].y - wdx);
+	pathOut->lineTo(pathIn->pts[i0].x + wdy, pathIn->pts[i0].y - wdx);
 	break;
       case splashLineCapRound:
-	pathOut->curveTo(pathIn->pts[i].x - wdy - bezierCircle * wdx,
-			 pathIn->pts[i].y + wdx - bezierCircle * wdy,
-			 pathIn->pts[i].x - wdx - bezierCircle * wdy,
-			 pathIn->pts[i].y - wdy + bezierCircle * wdx,
-			 pathIn->pts[i].x - wdx,
-			 pathIn->pts[i].y - wdy);
-	pathOut->curveTo(pathIn->pts[i].x - wdx + bezierCircle * wdy,
-			 pathIn->pts[i].y - wdy - bezierCircle * wdx,
-			 pathIn->pts[i].x + wdy - bezierCircle * wdx,
-			 pathIn->pts[i].y - wdx - bezierCircle * wdy,
-			 pathIn->pts[i].x + wdy,
-			 pathIn->pts[i].y - wdx);
+	pathOut->curveTo(pathIn->pts[i0].x - wdy - bezierCircle * wdx,
+			 pathIn->pts[i0].y + wdx - bezierCircle * wdy,
+			 pathIn->pts[i0].x - wdx - bezierCircle * wdy,
+			 pathIn->pts[i0].y - wdy + bezierCircle * wdx,
+			 pathIn->pts[i0].x - wdx,
+			 pathIn->pts[i0].y - wdy);
+	pathOut->curveTo(pathIn->pts[i0].x - wdx + bezierCircle * wdy,
+			 pathIn->pts[i0].y - wdy - bezierCircle * wdx,
+			 pathIn->pts[i0].x + wdy - bezierCircle * wdx,
+			 pathIn->pts[i0].y - wdx - bezierCircle * wdy,
+			 pathIn->pts[i0].x + wdy,
+			 pathIn->pts[i0].y - wdx);
 	break;
       case splashLineCapProjecting:
-	pathOut->lineTo(pathIn->pts[i].x - wdx - wdy,
-			pathIn->pts[i].y + wdx - wdy);
-	pathOut->lineTo(pathIn->pts[i].x - wdx + wdy,
-			pathIn->pts[i].y - wdx - wdy);
-	pathOut->lineTo(pathIn->pts[i].x + wdy,
-			pathIn->pts[i].y - wdx);
+	pathOut->lineTo(pathIn->pts[i0].x - wdx - wdy,
+			pathIn->pts[i0].y + wdx - wdy);
+	pathOut->lineTo(pathIn->pts[i0].x - wdx + wdy,
+			pathIn->pts[i0].y - wdx - wdy);
+	pathOut->lineTo(pathIn->pts[i0].x + wdy,
+			pathIn->pts[i0].y - wdx);
 	break;
       }
     } else {
-      pathOut->lineTo(pathIn->pts[i].x + wdy, pathIn->pts[i].y - wdx);
+      pathOut->lineTo(pathIn->pts[i0].x + wdy, pathIn->pts[i0].y - wdx);
     }
 
     // draw the left side of the segment rectangle
     left2 = pathOut->length - 1;
-    pathOut->lineTo(pathIn->pts[i+1].x + wdy, pathIn->pts[i+1].y - wdx);
+    pathOut->lineTo(pathIn->pts[j0].x + wdy, pathIn->pts[j0].y - wdx);
 
     // draw the end cap
     if (last && !closed) {
       switch (state->lineCap) {
       case splashLineCapButt:
-	pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx);
+	pathOut->lineTo(pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx);
 	break;
       case splashLineCapRound:
-	pathOut->curveTo(pathIn->pts[i+1].x + wdy + bezierCircle * wdx,
-			 pathIn->pts[i+1].y - wdx + bezierCircle * wdy,
-			 pathIn->pts[i+1].x + wdx + bezierCircle * wdy,
-			 pathIn->pts[i+1].y + wdy - bezierCircle * wdx,
-			 pathIn->pts[i+1].x + wdx,
-			 pathIn->pts[i+1].y + wdy);
-	pathOut->curveTo(pathIn->pts[i+1].x + wdx - bezierCircle * wdy,
-			 pathIn->pts[i+1].y + wdy + bezierCircle * wdx,
-			 pathIn->pts[i+1].x - wdy + bezierCircle * wdx,
-			 pathIn->pts[i+1].y + wdx + bezierCircle * wdy,
-			 pathIn->pts[i+1].x - wdy,
-			 pathIn->pts[i+1].y + wdx);
+	pathOut->curveTo(pathIn->pts[j0].x + wdy + bezierCircle * wdx,
+			 pathIn->pts[j0].y - wdx + bezierCircle * wdy,
+			 pathIn->pts[j0].x + wdx + bezierCircle * wdy,
+			 pathIn->pts[j0].y + wdy - bezierCircle * wdx,
+			 pathIn->pts[j0].x + wdx,
+			 pathIn->pts[j0].y + wdy);
+	pathOut->curveTo(pathIn->pts[j0].x + wdx - bezierCircle * wdy,
+			 pathIn->pts[j0].y + wdy + bezierCircle * wdx,
+			 pathIn->pts[j0].x - wdy + bezierCircle * wdx,
+			 pathIn->pts[j0].y + wdx + bezierCircle * wdy,
+			 pathIn->pts[j0].x - wdy,
+			 pathIn->pts[j0].y + wdx);
 	break;
       case splashLineCapProjecting:
-	pathOut->lineTo(pathIn->pts[i+1].x + wdy + wdx,
-			pathIn->pts[i+1].y - wdx + wdy);
-	pathOut->lineTo(pathIn->pts[i+1].x - wdy + wdx,
-			pathIn->pts[i+1].y + wdx + wdy);
-	pathOut->lineTo(pathIn->pts[i+1].x - wdy,
-			pathIn->pts[i+1].y + wdx);
+	pathOut->lineTo(pathIn->pts[j0].x + wdy + wdx,
+			pathIn->pts[j0].y - wdx + wdy);
+	pathOut->lineTo(pathIn->pts[j0].x - wdy + wdx,
+			pathIn->pts[j0].y + wdx + wdy);
+	pathOut->lineTo(pathIn->pts[j0].x - wdy,
+			pathIn->pts[j0].y + wdx);
 	break;
       }
     } else {
-      pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx);
+      pathOut->lineTo(pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx);
     }
 
     // draw the right side of the segment rectangle
+    // (NB: if stroke adjustment is enabled, the closepath operation MUST
+    // add a segment because this segment is used for a hint)
     right2 = pathOut->length - 1;
-    pathOut->close();
+    pathOut->close(state->strokeAdjust);
 
     // draw the join
     join2 = pathOut->length;
     if (!last || closed) {
+
+      // compute the deltas for segment (j1, k0)
+#if USE_FIXEDPOINT
+      // the 1/d value can be small, which introduces significant
+      // inaccuracies in fixed point mode
+      d = splashDist(pathIn->pts[j1].x, pathIn->pts[j1].y,
+		     pathIn->pts[k0].x, pathIn->pts[k0].y);
+      dxNext = (pathIn->pts[k0].x - pathIn->pts[j1].x) / d;
+      dyNext = (pathIn->pts[k0].y - pathIn->pts[j1].y) / d;
+#else
+      d = (SplashCoord)1 / splashDist(pathIn->pts[j1].x, pathIn->pts[j1].y,
+				      pathIn->pts[k0].x, pathIn->pts[k0].y);
+      dxNext = d * (pathIn->pts[k0].x - pathIn->pts[j1].x);
+      dyNext = d * (pathIn->pts[k0].y - pathIn->pts[j1].y);
+#endif
+      wdxNext = (SplashCoord)0.5 * w * dxNext;
+      wdyNext = (SplashCoord)0.5 * w * dyNext;
+
+      // compute the join parameters
       crossprod = dx * dyNext - dy * dxNext;
       dotprod = -(dx * dxNext + dy * dyNext);
-      if (dotprod > 0.99999) {
+      if (dotprod > 0.9999) {
 	// avoid a divide-by-zero -- set miter to something arbitrary
 	// such that sqrt(miter) will exceed miterLimit (and m is never
 	// used in that situation)
+	// (note: the comparison value (0.9999) has to be less than
+	// 1-epsilon, where epsilon is the smallest value
+	// representable in the fixed point format)
 	miter = (state->miterLimit + 1) * (state->miterLimit + 1);
 	m = 0;
       } else {
@@ -3848,67 +4524,68 @@ SplashPath *Splash::makeStrokePath(SplashPath *path, GBool flatten) {
 
       // round join
       if (state->lineJoin == splashLineJoinRound) {
-	pathOut->moveTo(pathIn->pts[i+1].x + (SplashCoord)0.5 * w,
-			pathIn->pts[i+1].y);
-	pathOut->curveTo(pathIn->pts[i+1].x + (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].y + bezierCircle2 * w,
-			 pathIn->pts[i+1].x + bezierCircle2 * w,
-			 pathIn->pts[i+1].y + (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].x,
-			 pathIn->pts[i+1].y + (SplashCoord)0.5 * w);
-	pathOut->curveTo(pathIn->pts[i+1].x - bezierCircle2 * w,
-			 pathIn->pts[i+1].y + (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].x - (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].y + bezierCircle2 * w,
-			 pathIn->pts[i+1].x - (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].y);
-	pathOut->curveTo(pathIn->pts[i+1].x - (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].y - bezierCircle2 * w,
-			 pathIn->pts[i+1].x - bezierCircle2 * w,
-			 pathIn->pts[i+1].y - (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].x,
-			 pathIn->pts[i+1].y - (SplashCoord)0.5 * w);
-	pathOut->curveTo(pathIn->pts[i+1].x + bezierCircle2 * w,
-			 pathIn->pts[i+1].y - (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].x + (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].y - bezierCircle2 * w,
-			 pathIn->pts[i+1].x + (SplashCoord)0.5 * w,
-			 pathIn->pts[i+1].y);
+	pathOut->moveTo(pathIn->pts[j0].x + (SplashCoord)0.5 * w,
+			pathIn->pts[j0].y);
+	pathOut->curveTo(pathIn->pts[j0].x + (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].y + bezierCircle2 * w,
+			 pathIn->pts[j0].x + bezierCircle2 * w,
+			 pathIn->pts[j0].y + (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].x,
+			 pathIn->pts[j0].y + (SplashCoord)0.5 * w);
+	pathOut->curveTo(pathIn->pts[j0].x - bezierCircle2 * w,
+			 pathIn->pts[j0].y + (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].x - (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].y + bezierCircle2 * w,
+			 pathIn->pts[j0].x - (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].y);
+	pathOut->curveTo(pathIn->pts[j0].x - (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].y - bezierCircle2 * w,
+			 pathIn->pts[j0].x - bezierCircle2 * w,
+			 pathIn->pts[j0].y - (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].x,
+			 pathIn->pts[j0].y - (SplashCoord)0.5 * w);
+	pathOut->curveTo(pathIn->pts[j0].x + bezierCircle2 * w,
+			 pathIn->pts[j0].y - (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].x + (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].y - bezierCircle2 * w,
+			 pathIn->pts[j0].x + (SplashCoord)0.5 * w,
+			 pathIn->pts[j0].y);
 
       } else {
-	pathOut->moveTo(pathIn->pts[i+1].x, pathIn->pts[i+1].y);
+	pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y);
 
 	// angle < 180
 	if (crossprod < 0) {
-	  pathOut->lineTo(pathIn->pts[i+1].x - wdyNext,
-			  pathIn->pts[i+1].y + wdxNext);
+	  pathOut->lineTo(pathIn->pts[j0].x - wdyNext,
+			  pathIn->pts[j0].y + wdxNext);
 	  // miter join inside limit
 	  if (state->lineJoin == splashLineJoinMiter &&
 	      splashSqrt(miter) <= state->miterLimit) {
-	    pathOut->lineTo(pathIn->pts[i+1].x - wdy + wdx * m,
-			    pathIn->pts[i+1].y + wdx + wdy * m);
-	    pathOut->lineTo(pathIn->pts[i+1].x - wdy,
-			    pathIn->pts[i+1].y + wdx);
+	    pathOut->lineTo(pathIn->pts[j0].x - wdy + wdx * m,
+			    pathIn->pts[j0].y + wdx + wdy * m);
+	    pathOut->lineTo(pathIn->pts[j0].x - wdy,
+			    pathIn->pts[j0].y + wdx);
 	  // bevel join or miter join outside limit
 	  } else {
-	    pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx);
+	    pathOut->lineTo(pathIn->pts[j0].x - wdy,
+			    pathIn->pts[j0].y + wdx);
 	  }
 
 	// angle >= 180
 	} else {
-	  pathOut->lineTo(pathIn->pts[i+1].x + wdy,
-			  pathIn->pts[i+1].y - wdx);
+	  pathOut->lineTo(pathIn->pts[j0].x + wdy,
+			  pathIn->pts[j0].y - wdx);
 	  // miter join inside limit
 	  if (state->lineJoin == splashLineJoinMiter &&
 	      splashSqrt(miter) <= state->miterLimit) {
-	    pathOut->lineTo(pathIn->pts[i+1].x + wdy + wdx * m,
-			    pathIn->pts[i+1].y - wdx + wdy * m);
-	    pathOut->lineTo(pathIn->pts[i+1].x + wdyNext,
-			    pathIn->pts[i+1].y - wdxNext);
+	    pathOut->lineTo(pathIn->pts[j0].x + wdy + wdx * m,
+			    pathIn->pts[j0].y - wdx + wdy * m);
+	    pathOut->lineTo(pathIn->pts[j0].x + wdyNext,
+			    pathIn->pts[j0].y - wdxNext);
 	  // bevel join or miter join outside limit
 	  } else {
-	    pathOut->lineTo(pathIn->pts[i+1].x + wdyNext,
-			    pathIn->pts[i+1].y - wdxNext);
+	    pathOut->lineTo(pathIn->pts[j0].x + wdyNext,
+			    pathIn->pts[j0].y - wdxNext);
 	  }
 	}
       }
@@ -3918,8 +4595,28 @@ SplashPath *Splash::makeStrokePath(SplashPath *path, GBool flatten) {
 
     // add stroke adjustment hints
     if (state->strokeAdjust) {
-      if (i >= subpathStart + 1) {
-	if (i >= subpathStart + 2) {
+      if (seg == 0 && !closed) {
+	if (state->lineCap == splashLineCapButt) {
+	  pathOut->addStrokeAdjustHint(firstPt, left2 + 1,
+				       firstPt, firstPt + 1);
+	  if (last) {
+	    pathOut->addStrokeAdjustHint(firstPt, left2 + 1,
+					 left2 + 1, left2 + 2);
+	  }
+	} else if (state->lineCap == splashLineCapProjecting) {
+	  if (last) {
+	    pathOut->addStrokeAdjustHint(firstPt + 1, left2 + 2,
+					 firstPt + 1, firstPt + 2);
+	    pathOut->addStrokeAdjustHint(firstPt + 1, left2 + 2,
+					 left2 + 2, left2 + 3);
+	  } else {
+	    pathOut->addStrokeAdjustHint(firstPt + 1, left2 + 1,
+					 firstPt + 1, firstPt + 2);
+	  }
+	}
+      }
+      if (seg >= 1) {
+	if (seg >= 2) {
 	  pathOut->addStrokeAdjustHint(left1, right1, left0 + 1, right0);
 	  pathOut->addStrokeAdjustHint(left1, right1, join0, left2);
 	} else {
@@ -3933,12 +4630,12 @@ SplashPath *Splash::makeStrokePath(SplashPath *path, GBool flatten) {
       right1 = right2;
       join0 = join1;
       join1 = join2;
-      if (i == subpathStart) {
+      if (seg == 0) {
 	leftFirst = left2;
 	rightFirst = right2;
       }
       if (last) {
-	if (i >= subpathStart + 2) {
+	if (seg >= 2) {
 	  pathOut->addStrokeAdjustHint(left1, right1, left0 + 1, right0);
 	  pathOut->addStrokeAdjustHint(left1, right1,
 				       join0, pathOut->length - 1);
@@ -3955,8 +4652,21 @@ SplashPath *Splash::makeStrokePath(SplashPath *path, GBool flatten) {
 	  pathOut->addStrokeAdjustHint(leftFirst, rightFirst,
 				       join1, pathOut->length - 1);
 	}
+	if (!closed && seg > 0) {
+	  if (state->lineCap == splashLineCapButt) {
+	    pathOut->addStrokeAdjustHint(left1 - 1, left1 + 1,
+					 left1 + 1, left1 + 2);
+	  } else if (state->lineCap == splashLineCapProjecting) {
+	    pathOut->addStrokeAdjustHint(left1 - 1, left1 + 2,
+					 left1 + 2, left1 + 3);
+	  }
+	}
       }
     }
+
+    i0 = j0;
+    i1 = j1;
+    ++seg;
   }
 
   if (pathIn != path) {
@@ -4011,7 +4721,13 @@ SplashError Splash::shadedFill(SplashPath *path, GBool hasBBox,
     xPath->aaScale();
   }
   xPath->sort();
-  scanner = new SplashXPathScanner(xPath, gFalse);
+  yMinI = state->clip->getYMinI();
+  yMaxI = state->clip->getYMaxI();
+  if (vectorAntialias && !inShading) {
+    yMinI = yMinI * splashAASize;
+    yMaxI = (yMaxI + 1) * splashAASize - 1;
+  }
+  scanner = new SplashXPathScanner(xPath, gFalse, yMinI, yMaxI);
 
   // get the min and max x and y values
   if (vectorAntialias) {
@@ -4030,7 +4746,7 @@ SplashError Splash::shadedFill(SplashPath *path, GBool hasBBox,
       yMaxI = state->clip->getYMaxI();
     }
 
-    pipeInit(&pipe, 0, yMinI, pattern, NULL, state->fillAlpha, vectorAntialias && !hasBBox, gFalse, pattern);
+    pipeInit(&pipe, 0, yMinI, pattern, NULL, (Guchar)splashRound(state->fillAlpha * 255), vectorAntialias && !hasBBox, gFalse);
 
     // draw the spans
     if (vectorAntialias) {
diff --git a/splash/Splash.h b/splash/Splash.h
index 85f92ed..e538808 100644
--- a/splash/Splash.h
+++ b/splash/Splash.h
@@ -108,6 +108,7 @@ public:
   SplashCoord *getLineDash();
   int getLineDashLength();
   SplashCoord getLineDashPhase();
+  GBool getStrokeAdjust();
   SplashClip *getClip();
   SplashBitmap *getSoftMask();
   GBool getInNonIsolatedGroup();
@@ -144,6 +145,8 @@ public:
   void setSoftMask(SplashBitmap *softMask);
   void setInNonIsolatedGroup(SplashBitmap *alpha0BitmapA,
 			     int alpha0XA, int alpha0YA);
+  void setTransfer(Guchar *red, Guchar *green, Guchar *blue, Guchar *gray);
+  void setOverprintMask(Guint overprintMask);
 
   //----- state save/restore
 
@@ -226,14 +229,19 @@ public:
 
   //----- misc
 
-  // Construct a path for a stroke, given the path to be stroked, and
-  // using the current line parameters.  If <flatten> is true, this
-  // function will first flatten the path and handle the linedash.
-  SplashPath *makeStrokePath(SplashPath *path, GBool flatten = gTrue);
+  // Construct a path for a stroke, given the path to be stroked and
+  // the line width <w>.  All other stroke parameters are taken from
+  // the current state.  If <flatten> is true, this function will
+  // first flatten the path and handle the linedash.
+  SplashPath *makeStrokePath(SplashPath *path, SplashCoord w,
+			     GBool flatten = gTrue);
 
   // Return the associated bitmap.
   SplashBitmap *getBitmap() { return bitmap; }
 
+  // Set the minimum line width.
+  void setMinLineWidth(SplashCoord w) { minLineWidth = w; }
+
   // Get a bounding box which includes all modifications since the
   // last call to clearModRegion.
   void getModRegion(int *xMin, int *yMin, int *xMax, int *yMax)
@@ -250,6 +258,7 @@ public:
   void setDebugMode(GBool debugModeA) { debugMode = debugModeA; }
 
 #if 1 //~tmp: turn off anti-aliasing temporarily
+  void setInShading(GBool sh) { inShading = sh; }
   GBool getVectorAntialias() { return vectorAntialias; }
   void setVectorAntialias(GBool vaa) { vectorAntialias = vaa; }
 #endif
@@ -264,9 +273,25 @@ private:
 
   void pipeInit(SplashPipe *pipe, int x, int y,
 		SplashPattern *pattern, SplashColorPtr cSrc,
-		SplashCoord aInput, GBool usesShape,
-		GBool nonIsolatedGroup, SplashPattern *overprintPattern = NULL, GBool stroke = gFalse);
+		Guchar aInput, GBool usesShape,
+		GBool nonIsolatedGroup);
   void pipeRun(SplashPipe *pipe);
+  void pipeRunSimpleMono1(SplashPipe *pipe);
+  void pipeRunSimpleMono8(SplashPipe *pipe);
+  void pipeRunSimpleRGB8(SplashPipe *pipe);
+  void pipeRunSimpleXBGR8(SplashPipe *pipe);
+  void pipeRunSimpleBGR8(SplashPipe *pipe);
+#if SPLASH_CMYK
+  void pipeRunSimpleCMYK8(SplashPipe *pipe);
+#endif
+  void pipeRunAAMono1(SplashPipe *pipe);
+  void pipeRunAAMono8(SplashPipe *pipe);
+  void pipeRunAARGB8(SplashPipe *pipe);
+  void pipeRunAAXBGR8(SplashPipe *pipe);
+  void pipeRunAABGR8(SplashPipe *pipe);
+#if SPLASH_CMYK
+  void pipeRunAACMYK8(SplashPipe *pipe);
+#endif
   void pipeSetXY(SplashPipe *pipe, int x, int y);
   void pipeIncX(SplashPipe *pipe);
   void drawPixel(SplashPipe *pipe, int x, int y, GBool noClip);
@@ -279,7 +304,7 @@ private:
   void updateModX(int x);
   void updateModY(int y);
   void strokeNarrow(SplashPath *path);
-  void strokeWide(SplashPath *path);
+  void strokeWide(SplashPath *path, SplashCoord w);
   SplashPath *flattenPath(SplashPath *path, SplashCoord *matrix,
 			  SplashCoord flatness);
   void flattenCurve(SplashCoord x0, SplashCoord y0,
@@ -291,6 +316,7 @@ private:
   SplashPath *makeDashedPath(SplashPath *xPath);
   SplashError fillWithPattern(SplashPath *path, GBool eo,
 			      SplashPattern *pattern, SplashCoord alpha);
+  GBool pathAllOutside(SplashPath *path);
   void fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph, GBool noclip);
   void dumpPath(SplashPath *path);
   void dumpXPath(SplashXPath *path);
@@ -308,9 +334,11 @@ private:
 				//   bitmap containing the alpha0 values
   int alpha0X, alpha0Y;		// offset within alpha0Bitmap
   SplashCoord aaGamma[splashAASize * splashAASize + 1];
+  SplashCoord minLineWidth;
   int modXMin, modYMin, modXMax, modYMax;
   SplashClipResult opClipRes;
   GBool vectorAntialias;
+  GBool inShading;
   GBool debugMode;
 };
 
diff --git a/splash/SplashClip.cc b/splash/SplashClip.cc
index 5add152..41b73c8 100644
--- a/splash/SplashClip.cc
+++ b/splash/SplashClip.cc
@@ -64,8 +64,8 @@ SplashClip::SplashClip(SplashCoord x0, SplashCoord y0,
   }
   xMinI = splashFloor(xMin);
   yMinI = splashFloor(yMin);
-  xMaxI = splashFloor(xMax);
-  yMaxI = splashFloor(yMax);
+  xMaxI = splashCeil(xMax) - 1;
+  yMaxI = splashCeil(yMax) - 1;
   paths = NULL;
   flags = NULL;
   scanners = NULL;
@@ -73,6 +73,7 @@ SplashClip::SplashClip(SplashCoord x0, SplashCoord y0,
 }
 
 SplashClip::SplashClip(SplashClip *clip) {
+  int yMinAA, yMaxAA;
   int i;
 
   antialias = clip->antialias;
@@ -93,7 +94,15 @@ SplashClip::SplashClip(SplashClip *clip) {
   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);
+    if (antialias) {
+      yMinAA = yMinI * splashAASize;
+      yMaxAA = (yMaxI + 1) * splashAASize - 1;
+    } else {
+      yMinAA = yMinI;
+      yMaxAA = yMaxI;
+    }
+    scanners[i] = new SplashXPathScanner(paths[i], flags[i] & splashClipEO,
+					 yMinAA, yMaxAA);
   }
 }
 
@@ -156,8 +165,8 @@ void SplashClip::resetToRect(SplashCoord x0, SplashCoord y0,
   }
   xMinI = splashFloor(xMin);
   yMinI = splashFloor(yMin);
-  xMaxI = splashFloor(xMax);
-  yMaxI = splashFloor(yMax);
+  xMaxI = splashCeil(xMax) - 1;
+  yMaxI = splashCeil(yMax) - 1;
 }
 
 SplashError SplashClip::clipToRect(SplashCoord x0, SplashCoord y0,
@@ -169,7 +178,7 @@ SplashError SplashClip::clipToRect(SplashCoord x0, SplashCoord y0,
     }
     if (x1 < xMax) {
       xMax = x1;
-      xMaxI = splashFloor(xMax);
+      xMaxI = splashCeil(xMax) - 1;
     }
   } else {
     if (x1 > xMin) {
@@ -178,7 +187,7 @@ SplashError SplashClip::clipToRect(SplashCoord x0, SplashCoord y0,
     }
     if (x0 < xMax) {
       xMax = x0;
-      xMaxI = splashFloor(xMax);
+      xMaxI = splashCeil(xMax) - 1;
     }
   }
   if (y0 < y1) {
@@ -188,7 +197,7 @@ SplashError SplashClip::clipToRect(SplashCoord x0, SplashCoord y0,
     }
     if (y1 < yMax) {
       yMax = y1;
-      yMaxI = splashFloor(yMax);
+      yMaxI = splashCeil(yMax) - 1;
     }
   } else {
     if (y1 > yMin) {
@@ -197,7 +206,7 @@ SplashError SplashClip::clipToRect(SplashCoord x0, SplashCoord y0,
     }
     if (y0 < yMax) {
       yMax = y0;
-      yMaxI = splashFloor(yMax);
+      yMaxI = splashCeil(yMax) - 1;
     }
   }
   return splashOk;
@@ -206,6 +215,7 @@ SplashError SplashClip::clipToRect(SplashCoord x0, SplashCoord y0,
 SplashError SplashClip::clipToPath(SplashPath *path, SplashCoord *matrix,
 				   SplashCoord flatness, GBool eo) {
   SplashXPath *xPath;
+  int yMinAA, yMaxAA;
 
   xPath = new SplashXPath(path, matrix, flatness, gTrue);
 
@@ -213,8 +223,8 @@ SplashError SplashClip::clipToPath(SplashPath *path, SplashCoord *matrix,
   if (xPath->length == 0) {
     xMax = xMin - 1;
     yMax = yMin - 1;
-    xMaxI = splashFloor(xMax);
-    yMaxI = splashFloor(yMax);
+    xMaxI = splashCeil(xMax) - 1;
+    yMaxI = splashCeil(yMax) - 1;
     delete xPath;
 
   // check for a rectangle
@@ -255,7 +265,14 @@ SplashError SplashClip::clipToPath(SplashPath *path, SplashCoord *matrix,
     xPath->sort();
     paths[length] = xPath;
     flags[length] = eo ? splashClipEO : 0;
-    scanners[length] = new SplashXPathScanner(xPath, eo);
+    if (antialias) {
+      yMinAA = yMinI * splashAASize;
+      yMaxAA = (yMaxI + 1) * splashAASize - 1;
+    } else {
+      yMinAA = yMinI;
+      yMaxAA = yMaxI;
+    }
+    scanners[length] = new SplashXPathScanner(xPath, eo, yMinAA, yMaxAA);
     ++length;
   }
 
@@ -268,10 +285,10 @@ SplashClipResult SplashClip::testRect(int rectXMin, int rectYMin,
   //     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) {
+  //     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 &&
@@ -289,10 +306,10 @@ SplashClipResult SplashClip::testSpan(int spanXMin, int spanXMax, int spanY) {
   //     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) {
+  //     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 &&
diff --git a/splash/SplashState.cc b/splash/SplashState.cc
index 15b6a72..7b632b8 100644
--- a/splash/SplashState.cc
+++ b/splash/SplashState.cc
@@ -47,6 +47,7 @@ int splashColorModeNComps[] = {
 SplashState::SplashState(int width, int height, GBool vectorAntialias,
 			 SplashScreenParams *screenParams) {
   SplashColor color;
+  int i;
 
   matrix[0] = 1;  matrix[1] = 0;
   matrix[2] = 0;  matrix[3] = 1;
@@ -74,12 +75,24 @@ SplashState::SplashState(int width, int height, GBool vectorAntialias,
   fillOverprint = gFalse;
   strokeOverprint = gFalse;
   overprintMode = 0;	  
+  for (i = 0; i < 256; ++i) {
+    rgbTransferR[i] = (Guchar)i;
+    rgbTransferG[i] = (Guchar)i;
+    rgbTransferB[i] = (Guchar)i;
+    grayTransfer[i] = (Guchar)i;
+    cmykTransferC[i] = (Guchar)i;
+    cmykTransferM[i] = (Guchar)i;
+    cmykTransferY[i] = (Guchar)i;
+    cmykTransferK[i] = (Guchar)i;
+  }
+  overprintMask = 0xffffffff;
   next = NULL;
 }
 
 SplashState::SplashState(int width, int height, GBool vectorAntialias,
 			 SplashScreen *screenA) {
   SplashColor color;
+  int i;
 
   matrix[0] = 1;  matrix[1] = 0;
   matrix[2] = 0;  matrix[3] = 1;
@@ -107,6 +120,17 @@ SplashState::SplashState(int width, int height, GBool vectorAntialias,
   fillOverprint = gFalse;
   strokeOverprint = gFalse;
   overprintMode = 0;	  
+  for (i = 0; i < 256; ++i) {
+    rgbTransferR[i] = (Guchar)i;
+    rgbTransferG[i] = (Guchar)i;
+    rgbTransferB[i] = (Guchar)i;
+    grayTransfer[i] = (Guchar)i;
+    cmykTransferC[i] = (Guchar)i;
+    cmykTransferM[i] = (Guchar)i;
+    cmykTransferY[i] = (Guchar)i;
+    cmykTransferK[i] = (Guchar)i;
+  }
+  overprintMask = 0xffffffff;
   next = NULL;
 }
 
@@ -140,6 +164,15 @@ SplashState::SplashState(SplashState *state) {
   fillOverprint = state->fillOverprint;
   strokeOverprint = state->strokeOverprint;
   overprintMode = state->overprintMode;	  
+  memcpy(rgbTransferR, state->rgbTransferR, 256);
+  memcpy(rgbTransferG, state->rgbTransferG, 256);
+  memcpy(rgbTransferB, state->rgbTransferB, 256);
+  memcpy(grayTransfer, state->grayTransfer, 256);
+  memcpy(cmykTransferC, state->cmykTransferC, 256);
+  memcpy(cmykTransferM, state->cmykTransferM, 256);
+  memcpy(cmykTransferY, state->cmykTransferY, 256);
+  memcpy(cmykTransferK, state->cmykTransferK, 256);
+  overprintMask = state->overprintMask;
   next = NULL;
 }
 
@@ -189,3 +222,19 @@ void SplashState::setSoftMask(SplashBitmap *softMaskA) {
   softMask = softMaskA;
   deleteSoftMask = gTrue;
 }
+
+void SplashState::setTransfer(Guchar *red, Guchar *green, Guchar *blue,
+			      Guchar *gray) {
+  int i;
+
+  memcpy(rgbTransferR, red, 256);
+  memcpy(rgbTransferG, green, 256);
+  memcpy(rgbTransferB, blue, 256);
+  memcpy(grayTransfer, gray, 256);
+  for (i = 0; i < 256; ++i) {
+    cmykTransferC[i] = 255 - rgbTransferR[255 - i];
+    cmykTransferM[i] = 255 - rgbTransferG[255 - i];
+    cmykTransferY[i] = 255 - rgbTransferB[255 - i];
+    cmykTransferK[i] = 255 - grayTransfer[255 - i];
+  }
+}
diff --git a/splash/SplashState.h b/splash/SplashState.h
index 63dadc6..214bffa 100644
--- a/splash/SplashState.h
+++ b/splash/SplashState.h
@@ -87,6 +87,9 @@ public:
   void setStrokeOverprint(GBool strokeOverprintA) { strokeOverprint = strokeOverprintA; }
   void setOverprintMode(int overprintModeA) { overprintMode = overprintModeA; }
 
+  // Set the transfer function.
+  void setTransfer(Guchar *red, Guchar *green, Guchar *blue, Guchar *gray);
+
 private:
 
   SplashState(SplashState *state);
@@ -114,6 +117,15 @@ private:
   GBool fillOverprint;
   GBool strokeOverprint;
   int overprintMode;
+  Guchar rgbTransferR[256],
+         rgbTransferG[256],
+         rgbTransferB[256];
+  Guchar grayTransfer[256];
+  Guchar cmykTransferC[256],
+         cmykTransferM[256],
+         cmykTransferY[256],
+         cmykTransferK[256];
+  Guint overprintMask;
 
   SplashState *next;		// used by Splash class
 
diff --git a/splash/SplashXPathScanner.cc b/splash/SplashXPathScanner.cc
index 33aa392..c9fe5e5 100644
--- a/splash/SplashXPathScanner.cc
+++ b/splash/SplashXPathScanner.cc
@@ -37,25 +37,30 @@
 //------------------------------------------------------------------------
 
 struct SplashIntersect {
+  int y;
   int x0, x1;			// intersection of segment with [y, y+1)
   int count;			// EO/NZWN counter increment
 };
 
-static bool cmpIntersect(const SplashIntersect &p0, const SplashIntersect &p1) {
-  return p0.x0 < p1.x0;
-}
+struct cmpIntersectFunctor {
+  bool operator()(const SplashIntersect &i0, const SplashIntersect &i1) {
+    return (i0.y != i1.y) ? (i0.y < i1.y) : (i0.x0 < i1.x0);
+  }
+};
 
 //------------------------------------------------------------------------
 // SplashXPathScanner
 //------------------------------------------------------------------------
 
-SplashXPathScanner::SplashXPathScanner(SplashXPath *xPathA, GBool eoA) {
+SplashXPathScanner::SplashXPathScanner(SplashXPath *xPathA, GBool eoA,
+				       int clipYMin, int clipYMax) {
   SplashXPathSeg *seg;
   SplashCoord xMinFP, yMinFP, xMaxFP, yMaxFP;
   int i;
 
   xPath = xPathA;
   eo = eoA;
+  partialClip = gFalse;
 
   // compute the bbox
   if (xPath->length == 0) {
@@ -103,16 +108,25 @@ SplashXPathScanner::SplashXPathScanner(SplashXPath *xPathA, GBool eoA) {
     xMax = splashFloor(xMaxFP);
     yMin = splashFloor(yMinFP);
     yMax = splashFloor(yMaxFP);
+    if (clipYMin > yMin) {
+      yMin = clipYMin;
+      partialClip = gTrue;
+    }
+    if (clipYMax < yMax) {
+      yMax = clipYMax;
+      partialClip = gTrue;
+    }
   }
 
-  interY = yMin - 1;
-  xPathIdx = 0;
+  allInter = NULL;
   inter = NULL;
-  interLen = interSize = 0;
+  computeIntersections();
+  interY = yMin - 1;
 }
 
 SplashXPathScanner::~SplashXPathScanner() {
   gfree(inter);
+  gfree(allInter);
 }
 
 void SplashXPathScanner::getBBoxAA(int *xMinA, int *yMinA,
@@ -124,12 +138,23 @@ void SplashXPathScanner::getBBoxAA(int *xMinA, int *yMinA,
 }
 
 void SplashXPathScanner::getSpanBounds(int y, int *spanXMin, int *spanXMax) {
-  if (interY != y) {
-    computeIntersections(y);
+  int interBegin, interEnd, xx, i;
+
+  if (y < yMin || y > yMax) {
+    interBegin = interEnd = 0;
+  } else {
+    interBegin = inter[y - yMin];
+    interEnd = inter[y - yMin + 1];
   }
-  if (interLen > 0) {
-    *spanXMin = inter[0].x0;
-    *spanXMax = inter[interLen - 1].x1;
+  if (interBegin < interEnd) {
+    *spanXMin = allInter[interBegin].x0;
+    xx = allInter[interBegin].x1;
+    for (i = interBegin + 1; i < interEnd; ++i) {
+      if (allInter[i].x1 > xx) {
+	xx = allInter[i].x1;
+      }
+    }
+    *spanXMax = xx;
   } else {
     *spanXMin = xMax + 1;
     *spanXMax = xMax;
@@ -137,47 +162,50 @@ void SplashXPathScanner::getSpanBounds(int y, int *spanXMin, int *spanXMax) {
 }
 
 GBool SplashXPathScanner::test(int x, int y) {
-  int count, i;
+  int interBegin, interEnd, count, i;
 
-  if (interY != y) {
-    computeIntersections(y);
+  if (y < yMin || y > yMax) {
+    return gFalse;
   }
+  interBegin = inter[y - yMin];
+  interEnd = inter[y - yMin + 1];
   count = 0;
-  for (i = 0; i < interLen && inter[i].x0 <= x; ++i) {
-    if (x <= inter[i].x1) {
+  for (i = interBegin; i < interEnd && allInter[i].x0 <= x; ++i) {
+    if (x <= allInter[i].x1) {
       return gTrue;
     }
-    count += inter[i].count;
+    count += allInter[i].count;
   }
   return eo ? (count & 1) : (count != 0);
 }
 
 GBool SplashXPathScanner::testSpan(int x0, int x1, int y) {
-  int count, xx1, i;
+  int interBegin, interEnd, count, xx1, i;
 
-  if (interY != y) {
-    computeIntersections(y);
+  if (y < yMin || y > yMax) {
+    return gFalse;
   }
-
+  interBegin = inter[y - yMin];
+  interEnd = inter[y - yMin + 1];
   count = 0;
-  for (i = 0; i < interLen && inter[i].x1 < x0; ++i) {
-    count += inter[i].count;
+  for (i = interBegin; i < interEnd && allInter[i].x1 < x0; ++i) {
+    count += allInter[i].count;
   }
 
   // invariant: the subspan [x0,xx1] is inside the path
   xx1 = x0 - 1;
   while (xx1 < x1) {
-    if (i >= interLen) {
+    if (i >= interEnd) {
       return gFalse;
     }
-    if (inter[i].x0 > xx1 + 1 &&
+    if (allInter[i].x0 > xx1 + 1 &&
 	!(eo ? (count & 1) : (count != 0))) {
       return gFalse;
     }
-    if (inter[i].x1 > xx1) {
-      xx1 = inter[i].x1;
+    if (allInter[i].x1 > xx1) {
+      xx1 = allInter[i].x1;
     }
-    count += inter[i].count;
+    count += allInter[i].count;
     ++i;
   }
 
@@ -185,25 +213,31 @@ GBool SplashXPathScanner::testSpan(int x0, int x1, int y) {
 }
 
 GBool SplashXPathScanner::getNextSpan(int y, int *x0, int *x1) {
-  int xx0, xx1;
+  int interEnd, xx0, xx1;
 
+  if (y < yMin || y > yMax) {
+    return gFalse;
+  }
   if (interY != y) {
-    computeIntersections(y);
+    interY = y;
+    interIdx = inter[y - yMin];
+    interCount = 0;
   }
-  if (interIdx >= interLen) {
+  interEnd = inter[y - yMin + 1];
+  if (interIdx >= interEnd) {
     return gFalse;
   }
-  xx0 = inter[interIdx].x0;
-  xx1 = inter[interIdx].x1;
-  interCount += inter[interIdx].count;
+  xx0 = allInter[interIdx].x0;
+  xx1 = allInter[interIdx].x1;
+  interCount += allInter[interIdx].count;
   ++interIdx;
-  while (interIdx < interLen &&
-	 (inter[interIdx].x0 <= xx1 ||
+  while (interIdx < interEnd &&
+	 (allInter[interIdx].x0 <= xx1 ||
 	  (eo ? (interCount & 1) : (interCount != 0)))) {
-    if (inter[interIdx].x1 > xx1) {
-      xx1 = inter[interIdx].x1;
+    if (allInter[interIdx].x1 > xx1) {
+      xx1 = allInter[interIdx].x1;
     }
-    interCount += inter[interIdx].count;
+    interCount += allInter[interIdx].count;
     ++interIdx;
   }
   *x0 = xx0;
@@ -211,161 +245,199 @@ GBool SplashXPathScanner::getNextSpan(int y, int *x0, int *x1) {
   return gTrue;
 }
 
-void SplashXPathScanner::computeIntersections(int y) {
-  SplashCoord xSegMin, xSegMax, ySegMin, ySegMax, xx0, xx1;
+void SplashXPathScanner::computeIntersections() {
   SplashXPathSeg *seg;
-  int i, j;
+  SplashCoord segXMin, segXMax, segYMin, segYMax, xx0, xx1;
+  int x, y, y0, y1, i;
 
-  // find the first segment that intersects [y, y+1)
-  i = (y >= interY) ? xPathIdx : 0;
-  while (i < xPath->length &&
-	 xPath->segs[i].y0 < y && xPath->segs[i].y1 < y) {
-    ++i;
+  if (yMin > yMax) {
+    return;
   }
-  xPathIdx = i;
 
-  // find all of the segments that intersect [y, y+1) and create an
-  // Intersect element for each one
-  interLen = 0;
-  for (j = i; j < xPath->length; ++j) {
-    seg = &xPath->segs[j];
+  // build the list of all intersections
+  allInterLen = 0;
+  allInterSize = 16;
+  allInter = (SplashIntersect *)gmallocn(allInterSize,
+					 sizeof(SplashIntersect));
+  for (i = 0; i < xPath->length; ++i) {
+    seg = &xPath->segs[i];
     if (seg->flags & splashXPathFlip) {
-      ySegMin = seg->y1;
-      ySegMax = seg->y0;
+      segYMin = seg->y1;
+      segYMax = seg->y0;
     } else {
-      ySegMin = seg->y0;
-      ySegMax = seg->y1;
-    }
-
-    // ensure that:      ySegMin < y+1
-    //              y <= ySegMax
-    if (ySegMin >= y + 1) {
-      break;
+      segYMin = seg->y0;
+      segYMax = seg->y1;
     }
-    if (ySegMax < y) {
-      continue;
-    }
-
-    if (interLen == interSize) {
-      if (interSize == 0) {
-	interSize = 16;
-      } else {
-	interSize *= 2;
-      }
-      inter = (SplashIntersect *)greallocn(inter, interSize,
-					   sizeof(SplashIntersect));
-    }
-
     if (seg->flags & splashXPathHoriz) {
-      xx0 = seg->x0;
-      xx1 = seg->x1;
+      y = splashFloor(seg->y0);
+      if (y >= yMin && y <= yMax) {
+	addIntersection(segYMin, segYMax, seg->flags,
+			y, splashFloor(seg->x0), splashFloor(seg->x1));
+      }
     } else if (seg->flags & splashXPathVert) {
-      xx0 = xx1 = seg->x0;
+      y0 = splashFloor(segYMin);
+      if (y0 < yMin) {
+	y0 = yMin;
+      }
+      y1 = splashFloor(segYMax);
+      if (y1 > yMax) {
+	y1 = yMax;
+      }
+      x = splashFloor(seg->x0);
+      for (y = y0; y <= y1; ++y) {
+	addIntersection(segYMin, segYMax, seg->flags, y, x, x);
+      }
     } else {
       if (seg->x0 < seg->x1) {
-	xSegMin = seg->x0;
-	xSegMax = seg->x1;
+	segXMin = seg->x0;
+	segXMax = seg->x1;
       } else {
-	xSegMin = seg->x1;
-	xSegMax = seg->x0;
+	segXMin = seg->x1;
+	segXMax = seg->x0;
       }
-      // intersection with top edge
-      xx0 = seg->x0 + ((SplashCoord)y - seg->y0) * seg->dxdy;
-      // intersection with bottom edge
-      xx1 = seg->x0 + ((SplashCoord)y + 1 - seg->y0) * seg->dxdy;
-      // the segment may not actually extend to the top and/or bottom edges
-      if (xx0 < xSegMin) {
-	xx0 = xSegMin;
-      } else if (xx0 > xSegMax) {
-	xx0 = xSegMax;
+      y0 = splashFloor(segYMin);
+      if (y0 < yMin) {
+	y0 = yMin;
       }
-      if (xx1 < xSegMin) {
-	xx1 = xSegMin;
-      } else if (xx1 > xSegMax) {
-	xx1 = xSegMax;
+      y1 = splashFloor(segYMax);
+      if (y1 > yMax) {
+	y1 = yMax;
+      }
+      // this loop could just add seg->dxdy to xx1 on each iteration,
+      // but that introduces numerical accuracy problems
+      xx1 = seg->x0 + ((SplashCoord)y0 - seg->y0) * seg->dxdy;
+      for (y = y0; y <= y1; ++y) {
+	xx0 = xx1;
+	xx1 = seg->x0 + ((SplashCoord)(y + 1) - seg->y0) * seg->dxdy;
+	// the segment may not actually extend to the top and/or bottom edges
+	if (xx0 < segXMin) {
+	  xx0 = segXMin;
+	} else if (xx0 > segXMax) {
+	  xx0 = segXMax;
+	}
+	if (xx1 < segXMin) {
+	  xx1 = segXMin;
+	} else if (xx1 > segXMax) {
+	  xx1 = segXMax;
+	}
+	addIntersection(segYMin, segYMax, seg->flags, y,
+			splashFloor(xx0), splashFloor(xx1));
       }
     }
-    if (xx0 < xx1) {
-      inter[interLen].x0 = splashFloor(xx0);
-      inter[interLen].x1 = splashFloor(xx1);
-    } else {
-      inter[interLen].x0 = splashFloor(xx1);
-      inter[interLen].x1 = splashFloor(xx0);
-    }
-    if (ySegMin <= y &&
-	(SplashCoord)y < ySegMax &&
-	!(seg->flags & splashXPathHoriz)) {
-      inter[interLen].count = eo ? 1
-	                         : (seg->flags & splashXPathFlip) ? 1 : -1;
-    } else {
-      inter[interLen].count = 0;
+  }
+  std::sort(allInter, allInter + allInterLen, cmpIntersectFunctor());
+
+  // build the list of y pointers
+  inter = (int *)gmallocn(yMax - yMin + 2, sizeof(int));
+  i = 0;
+  for (y = yMin; y <= yMax; ++y) {
+    inter[y - yMin] = i;
+    while (i < allInterLen && allInter[i].y <= y) {
+      ++i;
     }
-    ++interLen;
   }
+  inter[yMax - yMin + 1] = i;
+}
 
-  std::sort(inter, inter + interLen, cmpIntersect);
-
-  interY = y;
-  interIdx = 0;
-  interCount = 0;
+void SplashXPathScanner::addIntersection(double segYMin, double segYMax,
+					 Guint segFlags,
+					 int y, int x0, int x1) {
+  if (allInterLen == allInterSize) {
+    allInterSize *= 2;
+    allInter = (SplashIntersect *)greallocn(allInter, allInterSize,
+					    sizeof(SplashIntersect));
+  }
+  allInter[allInterLen].y = y;
+  if (x0 < x1) {
+    allInter[allInterLen].x0 = x0;
+    allInter[allInterLen].x1 = x1;
+  } else {
+    allInter[allInterLen].x0 = x1;
+    allInter[allInterLen].x1 = x0;
+  }
+  if (segYMin <= y &&
+      (SplashCoord)y < segYMax &&
+      !(segFlags & splashXPathHoriz)) {
+    allInter[allInterLen].count = eo ? 1
+                                     : (segFlags & splashXPathFlip) ? 1 : -1;
+  } else {
+    allInter[allInterLen].count = 0;
+  }
+  ++allInterLen;
 }
 
 void SplashXPathScanner::renderAALine(SplashBitmap *aaBuf,
 				      int *x0, int *x1, int y) {
-  int xx0, xx1, xx, xxMin, xxMax, yy;
+  int xx0, xx1, xx, xxMin, xxMax, yy, interEnd;
   Guchar mask;
   SplashColorPtr p;
 
   memset(aaBuf->getDataPtr(), 0, aaBuf->getRowSize() * aaBuf->getHeight());
   xxMin = aaBuf->getWidth();
   xxMax = -1;
-  for (yy = 0; yy < splashAASize; ++yy) {
-    computeIntersections(splashAASize * y + yy);
-    while (interIdx < interLen) {
-      xx0 = inter[interIdx].x0;
-      xx1 = inter[interIdx].x1;
-      interCount += inter[interIdx].count;
-      ++interIdx;
-      while (interIdx < interLen &&
-	     (inter[interIdx].x0 <= xx1 ||
-	      (eo ? (interCount & 1) : (interCount != 0)))) {
-	if (inter[interIdx].x1 > xx1) {
-	  xx1 = inter[interIdx].x1;
-	}
-	interCount += inter[interIdx].count;
-	++interIdx;
-      }
-      if (xx0 < 0) {
-	xx0 = 0;
-      }
-      ++xx1;
-      if (xx1 > aaBuf->getWidth()) {
-	xx1 = aaBuf->getWidth();
+  if (yMin <= yMax) {
+    if (splashAASize * y < yMin) {
+      interIdx = inter[0];
+    } else if (splashAASize * y > yMax) {
+      interIdx = inter[yMax - yMin + 1];
+    } else {
+      interIdx = inter[splashAASize * y - yMin];
+    }
+    for (yy = 0; yy < splashAASize; ++yy) {
+      if (splashAASize * y + yy < yMin) {
+	interEnd = inter[0];
+      } else if (splashAASize * y + yy > yMax) {
+	interEnd = inter[yMax - yMin + 1];
+      } else {
+	interEnd = inter[splashAASize * y + yy - yMin + 1];
       }
-      // set [xx0, xx1) to 1
-      if (xx0 < xx1) {
-	xx = xx0;
-	p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
-	if (xx & 7) {
-	  mask = 0xff >> (xx & 7);
-	  if ((xx & ~7) == (xx1 & ~7)) {
-	    mask &= (Guchar)(0xff00 >> (xx1 & 7));
+      interCount = 0;
+      while (interIdx < interEnd) {
+	xx0 = allInter[interIdx].x0;
+	xx1 = allInter[interIdx].x1;
+	interCount += allInter[interIdx].count;
+	++interIdx;
+	while (interIdx < interEnd &&
+	       (allInter[interIdx].x0 <= xx1 ||
+		(eo ? (interCount & 1) : (interCount != 0)))) {
+	  if (allInter[interIdx].x1 > xx1) {
+	    xx1 = allInter[interIdx].x1;
 	  }
-	  *p++ |= mask;
-	  xx = (xx & ~7) + 8;
+	  interCount += allInter[interIdx].count;
+	  ++interIdx;
 	}
-	for (; xx + 7 < xx1; xx += 8) {
-	  *p++ |= 0xff;
+	if (xx0 < 0) {
+	  xx0 = 0;
 	}
-	if (xx < xx1) {
-	  *p |= (Guchar)(0xff00 >> (xx1 & 7));
+	++xx1;
+	if (xx1 > aaBuf->getWidth()) {
+	  xx1 = aaBuf->getWidth();
+	}
+	// set [xx0, xx1) to 1
+	if (xx0 < xx1) {
+	  xx = xx0;
+	  p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
+	  if (xx & 7) {
+	    mask = 0xff >> (xx & 7);
+	    if ((xx & ~7) == (xx1 & ~7)) {
+	      mask &= (Guchar)(0xff00 >> (xx1 & 7));
+	    }
+	    *p++ |= mask;
+	    xx = (xx & ~7) + 8;
+	  }
+	  for (; xx + 7 < xx1; xx += 8) {
+	    *p++ |= 0xff;
+	  }
+	  if (xx < xx1) {
+	    *p |= (Guchar)(0xff00 >> (xx1 & 7));
+	  }
+	}
+	if (xx0 < xxMin) {
+	  xxMin = xx0;
+	}
+	if (xx1 > xxMax) {
+	  xxMax = xx1;
 	}
-      }
-      if (xx0 < xxMin) {
-	xxMin = xx0;
-      }
-      if (xx1 > xxMax) {
-	xxMax = xx1;
       }
     }
   }
@@ -375,50 +447,64 @@ void SplashXPathScanner::renderAALine(SplashBitmap *aaBuf,
 
 void SplashXPathScanner::clipAALine(SplashBitmap *aaBuf,
 				    int *x0, int *x1, int y) {
-  int xx0, xx1, xx, yy;
+  int xx0, xx1, xx, yy, interEnd;
   Guchar mask;
   SplashColorPtr p;
 
   for (yy = 0; yy < splashAASize; ++yy) {
     xx = *x0 * splashAASize;
-    computeIntersections(splashAASize * y + yy);
-    while (interIdx < interLen && xx < (*x1 + 1) * splashAASize) {
-      xx0 = inter[interIdx].x0;
-      xx1 = inter[interIdx].x1;
-      interCount += inter[interIdx].count;
-      ++interIdx;
-      while (interIdx < interLen &&
-	     (inter[interIdx].x0 <= xx1 ||
-	      (eo ? (interCount & 1) : (interCount != 0)))) {
-	if (inter[interIdx].x1 > xx1) {
-	  xx1 = inter[interIdx].x1;
+    if (yMin <= yMax) {
+      if (splashAASize * y + yy < yMin) {
+	interIdx = interEnd = inter[0];
+      } else if (splashAASize * y + yy > yMax) {
+	interIdx = interEnd = inter[yMax - yMin + 1];
+      } else {
+	interIdx = inter[splashAASize * y + yy - yMin];
+	if (splashAASize * y + yy > yMax) {
+	  interEnd = inter[yMax - yMin + 1];
+	} else {
+	  interEnd = inter[splashAASize * y + yy - yMin + 1];
 	}
-	interCount += inter[interIdx].count;
-	++interIdx;
-      }
-      if (xx0 > aaBuf->getWidth()) {
-	xx0 = aaBuf->getWidth();
       }
-      // set [xx, xx0) to 0
-      if (xx < xx0) {
-	p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
-	if (xx & 7) {
-	  mask = (Guchar)(0xff00 >> (xx & 7));
-	  if ((xx & ~7) == (xx0 & ~7)) {
-	    mask |= 0xff >> (xx0 & 7);
+      interCount = 0;
+      while (interIdx < interEnd && xx < (*x1 + 1) * splashAASize) {
+	xx0 = allInter[interIdx].x0;
+	xx1 = allInter[interIdx].x1;
+	interCount += allInter[interIdx].count;
+	++interIdx;
+	while (interIdx < interEnd &&
+	       (allInter[interIdx].x0 <= xx1 ||
+		(eo ? (interCount & 1) : (interCount != 0)))) {
+	  if (allInter[interIdx].x1 > xx1) {
+	    xx1 = allInter[interIdx].x1;
 	  }
-	  *p++ &= mask;
-	  xx = (xx & ~7) + 8;
+	  interCount += allInter[interIdx].count;
+	  ++interIdx;
 	}
-	for (; xx + 7 <= xx0; xx += 8) {
-	  *p++ = 0x00;
+	if (xx0 > aaBuf->getWidth()) {
+	  xx0 = aaBuf->getWidth();
 	}
+	// set [xx, xx0) to 0
 	if (xx < xx0) {
-	  *p &= 0xff >> (xx0 & 7);
+	  p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
+	  if (xx & 7) {
+	    mask = (Guchar)(0xff00 >> (xx & 7));
+	    if ((xx & ~7) == (xx0 & ~7)) {
+	      mask |= 0xff >> (xx0 & 7);
+	    }
+	    *p++ &= mask;
+	    xx = (xx & ~7) + 8;
+	  }
+	  for (; xx + 7 < xx0; xx += 8) {
+	    *p++ = 0x00;
+	  }
+	  if (xx < xx0) {
+	    *p &= 0xff >> (xx0 & 7);
+	  }
+	}
+	if (xx1 >= xx) {
+	  xx = xx1 + 1;
 	}
-      }
-      if (xx1 >= xx) {
-	xx = xx1 + 1;
       }
     }
     xx0 = (*x1 + 1) * splashAASize;
@@ -434,7 +520,7 @@ void SplashXPathScanner::clipAALine(SplashBitmap *aaBuf,
 	*p++ &= mask;
 	xx = (xx & ~7) + 8;
       }
-      for (; xx + 7 <= xx0; xx += 8) {
+      for (; xx + 7 < xx0; xx += 8) {
 	*p++ = 0x00;
       }
       if (xx < xx0) {
diff --git a/splash/SplashXPathScanner.h b/splash/SplashXPathScanner.h
index 1cc4f3c..719fae4 100644
--- a/splash/SplashXPathScanner.h
+++ b/splash/SplashXPathScanner.h
@@ -25,7 +25,8 @@ class SplashXPathScanner {
 public:
 
   // Create a new SplashXPathScanner object.  <xPathA> must be sorted.
-  SplashXPathScanner(SplashXPath *xPathA, GBool eoA);
+  SplashXPathScanner(SplashXPath *xPathA, GBool eoA,
+		     int clipYMin, int clipYMax);
 
   ~SplashXPathScanner();
 
@@ -36,6 +37,10 @@ public:
   // Return the path's bounding box.
   void getBBoxAA(int *xMinA, int *yMinA, int *xMaxA, int *yMaxA);
 
+  // Returns true if at least part of the path was outside the
+  // clipYMin/clipYMax bounds passed to the constructor.
+  GBool hasPartialClip() { return partialClip; }
+
   // Return the min/max x values for the span at <y>.
   void getSpanBounds(int y, int *spanXMin, int *spanXMax);
 
@@ -64,22 +69,25 @@ public:
 
 private:
 
-  void computeIntersections(int y);
+  void computeIntersections();
+  void addIntersection(double segYMin, double segYMax,
+		       Guint segFlags,
+		       int y, int x0, int x1);
 
   SplashXPath *xPath;
   GBool eo;
   int xMin, yMin, xMax, yMax;
+  GBool partialClip;
 
-  int interY;			// current y value
+  SplashIntersect *allInter;	// array of intersections
+  int allInterLen;		// number of intersections in <allInter>
+  int allInterSize;		// size of the <allInter> array
+  int *inter;			// indexes into <allInter> for each y value
+  int interY;			// current y value - used by getNextSpan
   int interIdx;			// current index into <inter> - used by
 				//   getNextSpan 
   int interCount;		// current EO/NZWN counter - used by
 				//   getNextSpan
-  int xPathIdx;			// current index into <xPath> - used by
-				//   computeIntersections
-  SplashIntersect *inter;	// intersections array for <interY>
-  int interLen;			// number of intersections in <inter>
-  int interSize;		// size of the <inter> array
 };
 
 #endif


More information about the poppler mailing list