diff -Naur xpdf.orig/splash/Splash.cc xpdf/splash/Splash.cc
--- xpdf.orig/splash/Splash.cc	2010-03-22 23:55:22.000000000 +0900
+++ xpdf/splash/Splash.cc	2010-03-23 00:14:18.000000000 +0900
@@ -12,6 +12,7 @@
 
 #include <stdlib.h>
 #include <string.h>
+#include <limits.h>
 #include "gmem.h"
 #include "SplashErrorCodes.h"
 #include "SplashMath.h"
@@ -1937,7 +1938,10 @@
   xq = w % scaledWidth;
 
   // allocate pixel buffer
-  pixBuf = (SplashColorPtr)gmalloc((yp + 1) * w);
+  if (yp < 0 || yp > INT_MAX - 1) {
+    return splashErrBadArg;
+  }
+  pixBuf = (SplashColorPtr)gmallocn(yp + 1, w);
 
   // initialize the pixel pipe
   pipeInit(&pipe, 0, 0, state->fillPattern, NULL, state->fillAlpha,
@@ -2233,9 +2237,12 @@
   xq = w % scaledWidth;
 
   // allocate pixel buffers
-  colorBuf = (SplashColorPtr)gmalloc((yp + 1) * w * nComps);
+  if (yp < 0 || yp > INT_MAX - 1 || w > INT_MAX / nComps) {
+    return splashErrBadArg;
+  }
+  colorBuf = (SplashColorPtr)gmallocn(yp + 1, w * nComps);
   if (srcAlpha) {
-    alphaBuf = (Guchar *)gmalloc((yp + 1) * w);
+    alphaBuf = (Guchar *)gmallocn(yp + 1, w);
   } else {
     alphaBuf = NULL;
   }
diff -Naur xpdf.orig/splash/Splash.cc.orig xpdf/splash/Splash.cc.orig
--- xpdf.orig/splash/Splash.cc.orig	1970-01-01 09:00:00.000000000 +0900
+++ xpdf/splash/Splash.cc.orig	2010-03-23 00:14:09.000000000 +0900
@@ -0,0 +1,3335 @@
+//========================================================================
+//
+// Splash.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gmem.h"
+#include "SplashErrorCodes.h"
+#include "SplashMath.h"
+#include "SplashBitmap.h"
+#include "SplashState.h"
+#include "SplashPath.h"
+#include "SplashXPath.h"
+#include "SplashXPathScanner.h"
+#include "SplashPattern.h"
+#include "SplashScreen.h"
+#include "SplashFont.h"
+#include "SplashGlyphBitmap.h"
+#include "Splash.h"
+
+//------------------------------------------------------------------------
+
+// distance of Bezier control point from center for circle approximation
+// = (4 * (sqrt(2) - 1) / 3) * r
+#define bezierCircle ((SplashCoord)0.55228475)
+#define bezierCircle2 ((SplashCoord)(0.5 * 0.55228475))
+
+// Divide a 16-bit value (in [0, 255*255]) by 255, returning an 8-bit result.
+static inline Guchar div255(int x) {
+  return (Guchar)((x + (x >> 8) + 0x80) >> 8);
+}
+
+//------------------------------------------------------------------------
+// SplashPipe
+//------------------------------------------------------------------------
+
+#define splashPipeMaxStages 9
+
+struct SplashPipe {
+  // pixel coordinates
+  int x, y;
+
+  // source pattern
+  SplashPattern *pattern;
+
+  // source alpha and color
+  SplashCoord aInput;
+  GBool usesShape;
+  Guchar aSrc;
+  SplashColorPtr cSrc;
+  SplashColor cSrcVal;
+
+  // non-isolated group alpha0
+  Guchar *alpha0Ptr;
+
+  // soft mask
+  SplashColorPtr softMaskPtr;
+
+  // destination alpha and color
+  SplashColorPtr destColorPtr;
+  int destColorMask;
+  Guchar *destAlphaPtr;
+
+  // shape
+  SplashCoord shape;
+
+  // result alpha and color
+  GBool noTransparency;
+  SplashPipeResultColorCtrl resultColorCtrl;
+
+  // non-isolated group correction
+  int nonIsolatedGroup;
+};
+
+SplashPipeResultColorCtrl Splash::pipeResultColorNoAlphaBlend[] = {
+  splashPipeResultColorNoAlphaBlendMono,
+  splashPipeResultColorNoAlphaBlendMono,
+  splashPipeResultColorNoAlphaBlendRGB,
+  splashPipeResultColorNoAlphaBlendRGB
+#if SPLASH_CMYK
+  ,
+  splashPipeResultColorNoAlphaBlendCMYK
+#endif
+};
+
+SplashPipeResultColorCtrl Splash::pipeResultColorAlphaNoBlend[] = {
+  splashPipeResultColorAlphaNoBlendMono,
+  splashPipeResultColorAlphaNoBlendMono,
+  splashPipeResultColorAlphaNoBlendRGB,
+  splashPipeResultColorAlphaNoBlendRGB
+#if SPLASH_CMYK
+  ,
+  splashPipeResultColorAlphaNoBlendCMYK
+#endif
+};
+
+SplashPipeResultColorCtrl Splash::pipeResultColorAlphaBlend[] = {
+  splashPipeResultColorAlphaBlendMono,
+  splashPipeResultColorAlphaBlendMono,
+  splashPipeResultColorAlphaBlendRGB,
+  splashPipeResultColorAlphaBlendRGB
+#if SPLASH_CMYK
+  ,
+  splashPipeResultColorAlphaBlendCMYK
+#endif
+};
+
+//------------------------------------------------------------------------
+
+static void blendXor(SplashColorPtr src, SplashColorPtr dest,
+		     SplashColorPtr blend, SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = src[i] ^ dest[i];
+  }
+}
+
+//------------------------------------------------------------------------
+// modified region
+//------------------------------------------------------------------------
+
+void Splash::clearModRegion() {
+  modXMin = bitmap->getWidth();
+  modYMin = bitmap->getHeight();
+  modXMax = -1;
+  modYMax = -1;
+}
+
+inline void Splash::updateModX(int x) {
+  if (x < modXMin) {
+    modXMin = x;
+  }
+  if (x > modXMax) {
+    modXMax = x;
+  }
+}
+
+inline void Splash::updateModY(int y) {
+  if (y < modYMin) {
+    modYMin = y;
+  }
+  if (y > modYMax) {
+    modYMax = y;
+  }
+}
+
+//------------------------------------------------------------------------
+// pipeline
+//------------------------------------------------------------------------
+
+inline void Splash::pipeInit(SplashPipe *pipe, int x, int y,
+			     SplashPattern *pattern, SplashColorPtr cSrc,
+			     SplashCoord aInput, GBool usesShape,
+			     GBool nonIsolatedGroup) {
+  pipeSetXY(pipe, x, y);
+  pipe->pattern = NULL;
+
+  // source color
+  if (pattern) {
+    if (pattern->isStatic()) {
+      pattern->getColor(x, y, pipe->cSrcVal);
+    } else {
+      pipe->pattern = pattern;
+    }
+    pipe->cSrc = pipe->cSrcVal;
+  } else {
+    pipe->cSrc = cSrc;
+  }
+
+  // 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) {
+    pipe->noTransparency = gTrue;
+  } else {
+    pipe->noTransparency = gFalse;
+  }
+
+  // result color
+  if (pipe->noTransparency) {
+    // the !state->blendFunc case is handled separately in pipeRun
+    pipe->resultColorCtrl = pipeResultColorNoAlphaBlend[bitmap->mode];
+  } else if (!state->blendFunc) {
+    pipe->resultColorCtrl = pipeResultColorAlphaNoBlend[bitmap->mode];
+  } else {
+    pipe->resultColorCtrl = pipeResultColorAlphaBlend[bitmap->mode];
+  }
+
+  // non-isolated group correction
+  if (nonIsolatedGroup) {
+    pipe->nonIsolatedGroup = splashColorModeNComps[bitmap->mode];
+  } else {
+    pipe->nonIsolatedGroup = 0;
+  }
+}
+
+inline void Splash::pipeRun(SplashPipe *pipe) {
+  Guchar aSrc, aDest, alpha2, alpha0, aResult;
+  SplashColor cDest, cBlend;
+  Guchar cResult0, cResult1, cResult2, cResult3;
+
+  //----- source color
+
+  // static pattern: handled in pipeInit
+  // fixed color: handled in pipeInit
+
+  // dynamic pattern
+  if (pipe->pattern) {
+    pipe->pattern->getColor(pipe->x, pipe->y, pipe->cSrcVal);
+  }
+
+  if (pipe->noTransparency && !state->blendFunc) {
+
+    //----- write destination pixel
+
+    switch (bitmap->mode) {
+    case splashModeMono1:
+      cResult0 = 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;
+      }
+      break;
+    case splashModeMono8:
+      *pipe->destColorPtr++ = pipe->cSrc[0];
+      break;
+    case splashModeRGB8:
+      *pipe->destColorPtr++ = pipe->cSrc[0];
+      *pipe->destColorPtr++ = pipe->cSrc[1];
+      *pipe->destColorPtr++ = pipe->cSrc[2];
+      break;
+    case splashModeBGR8:
+      *pipe->destColorPtr++ = pipe->cSrc[2];
+      *pipe->destColorPtr++ = pipe->cSrc[1];
+      *pipe->destColorPtr++ = pipe->cSrc[0];
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      *pipe->destColorPtr++ = pipe->cSrc[0];
+      *pipe->destColorPtr++ = pipe->cSrc[1];
+      *pipe->destColorPtr++ = pipe->cSrc[2];
+      *pipe->destColorPtr++ = pipe->cSrc[3];
+      break;
+#endif
+    }
+    if (pipe->destAlphaPtr) {
+      *pipe->destAlphaPtr++ = 255;
+    }
+
+  } else {
+
+    //----- read destination pixel
+
+    switch (bitmap->mode) {
+    case splashModeMono1:
+      cDest[0] = (*pipe->destColorPtr & pipe->destColorMask) ? 0xff : 0x00;
+      break;
+    case splashModeMono8:
+      cDest[0] = *pipe->destColorPtr;
+      break;
+    case splashModeRGB8:
+      cDest[0] = pipe->destColorPtr[0];
+      cDest[1] = pipe->destColorPtr[1];
+      cDest[2] = pipe->destColorPtr[2];
+      break;
+    case splashModeBGR8:
+      cDest[0] = pipe->destColorPtr[2];
+      cDest[1] = pipe->destColorPtr[1];
+      cDest[2] = pipe->destColorPtr[0];
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      cDest[0] = pipe->destColorPtr[0];
+      cDest[1] = pipe->destColorPtr[1];
+      cDest[2] = pipe->destColorPtr[2];
+      cDest[3] = pipe->destColorPtr[3];
+      break;
+#endif
+    }
+    if (pipe->destAlphaPtr) {
+      aDest = *pipe->destAlphaPtr;
+    } else {
+      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);
+      } else {
+	aSrc = (Guchar)splashRound(pipe->aInput * *pipe->softMaskPtr++);
+      }
+    } else if (pipe->usesShape) {
+      // pipe->aInput is premultiplied by 255 in pipeInit
+      aSrc = (Guchar)splashRound(pipe->aInput * pipe->shape);
+    } else {
+      // precomputed in pipeInit
+      aSrc = pipe->aSrc;
+    }
+
+    //----- result alpha and non-isolated group element correction
+
+    if (pipe->noTransparency) {
+      alpha2 = aResult = 255;
+    } else {
+      aResult = aSrc + aDest - div255(aSrc * aDest);
+
+      if (pipe->alpha0Ptr) {
+	alpha0 = *pipe->alpha0Ptr++;
+	alpha2 = aResult + alpha0 - div255(aResult * alpha0);
+      } else {
+	alpha2 = aResult;
+      }
+    }
+
+    //----- result color
+
+    cResult0 = cResult1 = cResult2 = cResult3 = 0; // make gcc happy
+
+    switch (pipe->resultColorCtrl) {
+
+#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]);
+      break;
+
+    case splashPipeResultColorAlphaNoBlendMono:
+      if (alpha2 == 0) {
+	cResult0 = 0;
+      } else {
+	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
+			     aSrc * pipe->cSrc[0]) / alpha2);
+      }
+      break;
+    case splashPipeResultColorAlphaNoBlendRGB:
+      if (alpha2 == 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);
+      }
+      break;
+#if SPLASH_CMYK
+    case splashPipeResultColorAlphaNoBlendCMYK:
+      if (alpha2 == 0) {
+	cResult0 = 0;
+	cResult1 = 0;
+	cResult2 = 0;
+	cResult3 = 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);
+	cResult3 = (Guchar)(((alpha2 - aSrc) * cDest[3] +
+			     aSrc * pipe->cSrc[3]) / alpha2);
+      }
+      break;
+#endif
+
+    case splashPipeResultColorAlphaBlendMono:
+      if (alpha2 == 0) {
+	cResult0 = 0;
+      } else {
+	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
+			     aSrc * ((255 - aDest) * pipe->cSrc[0] +
+				     aDest * cBlend[0]) / 255) /
+			    alpha2);
+      }
+      break;
+    case splashPipeResultColorAlphaBlendRGB:
+      if (alpha2 == 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);
+      }
+      break;
+#if SPLASH_CMYK
+    case splashPipeResultColorAlphaBlendCMYK:
+      if (alpha2 == 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);
+      }
+      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) {
+    case splashModeMono1:
+      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;
+      }
+      break;
+    case splashModeMono8:
+      *pipe->destColorPtr++ = cResult0;
+      break;
+    case splashModeRGB8:
+      *pipe->destColorPtr++ = cResult0;
+      *pipe->destColorPtr++ = cResult1;
+      *pipe->destColorPtr++ = cResult2;
+      break;
+    case splashModeBGR8:
+      *pipe->destColorPtr++ = cResult2;
+      *pipe->destColorPtr++ = cResult1;
+      *pipe->destColorPtr++ = cResult0;
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      *pipe->destColorPtr++ = cResult0;
+      *pipe->destColorPtr++ = cResult1;
+      *pipe->destColorPtr++ = cResult2;
+      *pipe->destColorPtr++ = cResult3;
+      break;
+#endif
+    }
+    if (pipe->destAlphaPtr) {
+      *pipe->destAlphaPtr++ = aResult;
+    }
+
+  }
+
+  ++pipe->x;
+}
+
+inline void Splash::pipeSetXY(SplashPipe *pipe, int x, int y) {
+  pipe->x = x;
+  pipe->y = y;
+  if (state->softMask) {
+    pipe->softMaskPtr =
+        &state->softMask->data[y * state->softMask->rowSize + x];
+  }
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + (x >> 3)];
+    pipe->destColorMask = 0x80 >> (x & 7);
+    break;
+  case splashModeMono8:
+    pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + x];
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x];
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x];
+    break;
+#endif
+  }
+  if (bitmap->alpha) {
+    pipe->destAlphaPtr = &bitmap->alpha[y * bitmap->width + x];
+  } else {
+    pipe->destAlphaPtr = NULL;
+  }
+  if (state->inNonIsolatedGroup && alpha0Bitmap->alpha) {
+    pipe->alpha0Ptr =
+        &alpha0Bitmap->alpha[(alpha0Y + y) * alpha0Bitmap->width +
+			     (alpha0X + x)];
+  } else {
+    pipe->alpha0Ptr = NULL;
+  }
+}
+
+inline void Splash::pipeIncX(SplashPipe *pipe) {
+  ++pipe->x;
+  if (state->softMask) {
+    ++pipe->softMaskPtr;
+  }
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    if (!(pipe->destColorMask >>= 1)) {
+      pipe->destColorMask = 0x80;
+      ++pipe->destColorPtr;
+    }
+    break;
+  case splashModeMono8:
+    ++pipe->destColorPtr;
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    pipe->destColorPtr += 3;
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    pipe->destColorPtr += 4;
+    break;
+#endif
+  }
+  if (pipe->destAlphaPtr) {
+    ++pipe->destAlphaPtr;
+  }
+  if (pipe->alpha0Ptr) {
+    ++pipe->alpha0Ptr;
+  }
+}
+
+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);
+    updateModX(x);
+    updateModY(y);
+  }
+}
+
+inline void Splash::drawAAPixelInit() {
+  aaBufY = -1;
+}
+
+inline void Splash::drawAAPixel(SplashPipe *pipe, int x, int y) {
+#if splashAASize == 4
+  static int bitCount4[16] = { 0, 1, 1, 2, 1, 2, 2, 3,
+			       1, 2, 2, 3, 2, 3, 3, 4 };
+  int w;
+#else
+  int xx, yy;
+#endif
+  SplashColorPtr p;
+  int x0, x1, t;
+
+  if (x < 0 || x >= bitmap->width ||
+      y < state->clip->getYMinI() || y > state->clip->getYMaxI()) {
+    return;
+  }
+
+  // update aaBuf
+  if (y != aaBufY) {
+    memset(aaBuf->getDataPtr(), 0xff,
+	   aaBuf->getRowSize() * aaBuf->getHeight());
+    x0 = 0;
+    x1 = bitmap->width - 1;
+    state->clip->clipAALine(aaBuf, &x0, &x1, y);
+    aaBufY = y;
+  }
+
+  // compute the shape value
+#if splashAASize == 4
+  p = aaBuf->getDataPtr() + (x >> 1);
+  w = aaBuf->getRowSize();
+  if (x & 1) {
+    t = bitCount4[*p & 0x0f] + bitCount4[p[w] & 0x0f] +
+        bitCount4[p[2*w] & 0x0f] + bitCount4[p[3*w] & 0x0f];
+  } else {
+    t = bitCount4[*p >> 4] + bitCount4[p[w] >> 4] +
+        bitCount4[p[2*w] >> 4] + bitCount4[p[3*w] >> 4];
+  }
+#else
+  t = 0;
+  for (yy = 0; yy < splashAASize; ++yy) {
+    for (xx = 0; xx < splashAASize; ++xx) {
+      p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() +
+	  ((x * splashAASize + xx) >> 3);
+      t += (*p >> (7 - ((x * splashAASize + xx) & 7))) & 1;
+    }
+  }
+#endif
+
+  // draw the pixel
+  if (t != 0) {
+    pipeSetXY(pipe, x, y);
+    pipe->shape *= aaGamma[t];
+    pipeRun(pipe);
+    updateModX(x);
+    updateModY(y);
+  }
+}
+
+inline void Splash::drawSpan(SplashPipe *pipe, int x0, int x1, int y,
+			     GBool noClip) {
+  int x;
+
+  pipeSetXY(pipe, x0, y);
+  if (noClip) {
+    for (x = x0; x <= x1; ++x) {
+      pipeRun(pipe);
+    }
+    updateModX(x0);
+    updateModX(x1);
+    updateModY(y);
+  } else {
+    for (x = x0; x <= x1; ++x) {
+      if (state->clip->test(x, y)) {
+	pipeRun(pipe);
+	updateModX(x);
+	updateModY(y);
+      } else {
+	pipeIncX(pipe);
+      }
+    }
+  }
+}
+
+inline void Splash::drawAALine(SplashPipe *pipe, int x0, int x1, int y) {
+#if splashAASize == 4
+  static int bitCount4[16] = { 0, 1, 1, 2, 1, 2, 2, 3,
+			       1, 2, 2, 3, 2, 3, 3, 4 };
+  SplashColorPtr p0, p1, p2, p3;
+  int t;
+#else
+  SplashColorPtr p;
+  int xx, yy, t;
+#endif
+  int x;
+
+#if splashAASize == 4
+  p0 = aaBuf->getDataPtr() + (x0 >> 1);
+  p1 = p0 + aaBuf->getRowSize();
+  p2 = p1 + aaBuf->getRowSize();
+  p3 = p2 + aaBuf->getRowSize();
+#endif
+  pipeSetXY(pipe, x0, y);
+  for (x = x0; x <= x1; ++x) {
+
+    // compute the shape value
+#if splashAASize == 4
+    if (x & 1) {
+      t = bitCount4[*p0 & 0x0f] + bitCount4[*p1 & 0x0f] +
+	  bitCount4[*p2 & 0x0f] + bitCount4[*p3 & 0x0f];
+      ++p0; ++p1; ++p2; ++p3;
+    } else {
+      t = bitCount4[*p0 >> 4] + bitCount4[*p1 >> 4] +
+	  bitCount4[*p2 >> 4] + bitCount4[*p3 >> 4];
+    }
+#else
+    t = 0;
+    for (yy = 0; yy < splashAASize; ++yy) {
+      for (xx = 0; xx < splashAASize; ++xx) {
+	p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() +
+	    ((x * splashAASize + xx) >> 3);
+	t += (*p >> (7 - ((x * splashAASize + xx) & 7))) & 1;
+      }
+    }
+#endif
+
+    if (t != 0) {
+      pipe->shape = aaGamma[t];
+      pipeRun(pipe);
+      updateModX(x);
+      updateModY(y);
+    } else {
+      pipeIncX(pipe);
+    }
+  }
+}
+
+//------------------------------------------------------------------------
+
+// Transform a point from user space to device space.
+inline void Splash::transform(SplashCoord *matrix,
+			      SplashCoord xi, SplashCoord yi,
+			      SplashCoord *xo, SplashCoord *yo) {
+  //                          [ m[0] m[1] 0 ]
+  // [xo yo 1] = [xi yi 1] *  [ m[2] m[3] 0 ]
+  //                          [ m[4] m[5] 1 ]
+  *xo = xi * matrix[0] + yi * matrix[2] + matrix[4];
+  *yo = xi * matrix[1] + yi * matrix[3] + matrix[5];
+}
+
+//------------------------------------------------------------------------
+// Splash
+//------------------------------------------------------------------------
+
+Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
+	       SplashScreenParams *screenParams) {
+  int i;
+
+  bitmap = bitmapA;
+  vectorAntialias = vectorAntialiasA;
+  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);
+    }
+  } else {
+    aaBuf = NULL;
+  }
+  clearModRegion();
+  debugMode = gFalse;
+}
+
+Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
+	       SplashScreen *screenA) {
+  int i;
+
+  bitmap = bitmapA;
+  vectorAntialias = vectorAntialiasA;
+  state = new SplashState(bitmap->width, bitmap->height, vectorAntialias,
+			  screenA);
+  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);
+    }
+  } else {
+    aaBuf = NULL;
+  }
+  clearModRegion();
+  debugMode = gFalse;
+}
+
+Splash::~Splash() {
+  while (state->next) {
+    restoreState();
+  }
+  delete state;
+  if (vectorAntialias) {
+    delete aaBuf;
+  }
+}
+
+//------------------------------------------------------------------------
+// state read
+//------------------------------------------------------------------------
+
+SplashCoord *Splash::getMatrix() {
+  return state->matrix;
+}
+
+SplashPattern *Splash::getStrokePattern() {
+  return state->strokePattern;
+}
+
+SplashPattern *Splash::getFillPattern() {
+  return state->fillPattern;
+}
+
+SplashScreen *Splash::getScreen() {
+  return state->screen;
+}
+
+SplashBlendFunc Splash::getBlendFunc() {
+  return state->blendFunc;
+}
+
+SplashCoord Splash::getStrokeAlpha() {
+  return state->strokeAlpha;
+}
+
+SplashCoord Splash::getFillAlpha() {
+  return state->fillAlpha;
+}
+
+SplashCoord Splash::getLineWidth() {
+  return state->lineWidth;
+}
+
+int Splash::getLineCap() {
+  return state->lineCap;
+}
+
+int Splash::getLineJoin() {
+  return state->lineJoin;
+}
+
+SplashCoord Splash::getMiterLimit() {
+  return state->miterLimit;
+}
+
+SplashCoord Splash::getFlatness() {
+  return state->flatness;
+}
+
+SplashCoord *Splash::getLineDash() {
+  return state->lineDash;
+}
+
+int Splash::getLineDashLength() {
+  return state->lineDashLength;
+}
+
+SplashCoord Splash::getLineDashPhase() {
+  return state->lineDashPhase;
+}
+
+SplashClip *Splash::getClip() {
+  return state->clip;
+}
+
+SplashBitmap *Splash::getSoftMask() {
+  return state->softMask;
+}
+
+GBool Splash::getInNonIsolatedGroup() {
+  return state->inNonIsolatedGroup;
+}
+
+//------------------------------------------------------------------------
+// state write
+//------------------------------------------------------------------------
+
+void Splash::setMatrix(SplashCoord *matrix) {
+  memcpy(state->matrix, matrix, 6 * sizeof(SplashCoord));
+}
+
+void Splash::setStrokePattern(SplashPattern *strokePattern) {
+  state->setStrokePattern(strokePattern);
+}
+
+void Splash::setFillPattern(SplashPattern *fillPattern) {
+  state->setFillPattern(fillPattern);
+}
+
+void Splash::setScreen(SplashScreen *screen) {
+  state->setScreen(screen);
+}
+
+void Splash::setBlendFunc(SplashBlendFunc func) {
+  state->blendFunc = func;
+}
+
+void Splash::setStrokeAlpha(SplashCoord alpha) {
+  state->strokeAlpha = alpha;
+}
+
+void Splash::setFillAlpha(SplashCoord alpha) {
+  state->fillAlpha = alpha;
+}
+
+void Splash::setLineWidth(SplashCoord lineWidth) {
+  state->lineWidth = lineWidth;
+}
+
+void Splash::setLineCap(int lineCap) {
+  state->lineCap = lineCap;
+}
+
+void Splash::setLineJoin(int lineJoin) {
+  state->lineJoin = lineJoin;
+}
+
+void Splash::setMiterLimit(SplashCoord miterLimit) {
+  state->miterLimit = miterLimit;
+}
+
+void Splash::setFlatness(SplashCoord flatness) {
+  if (flatness < 1) {
+    state->flatness = 1;
+  } else {
+    state->flatness = flatness;
+  }
+}
+
+void Splash::setLineDash(SplashCoord *lineDash, int lineDashLength,
+			 SplashCoord lineDashPhase) {
+  state->setLineDash(lineDash, lineDashLength, lineDashPhase);
+}
+
+void Splash::setStrokeAdjust(GBool strokeAdjust) {
+  state->strokeAdjust = strokeAdjust;
+}
+
+void Splash::clipResetToRect(SplashCoord x0, SplashCoord y0,
+			     SplashCoord x1, SplashCoord y1) {
+  state->clip->resetToRect(x0, y0, x1, y1);
+}
+
+SplashError Splash::clipToRect(SplashCoord x0, SplashCoord y0,
+			       SplashCoord x1, SplashCoord y1) {
+  return state->clip->clipToRect(x0, y0, x1, y1);
+}
+
+SplashError Splash::clipToPath(SplashPath *path, GBool eo) {
+  return state->clip->clipToPath(path, state->matrix, state->flatness, eo);
+}
+
+void Splash::setSoftMask(SplashBitmap *softMask) {
+  state->setSoftMask(softMask);
+}
+
+void Splash::setInNonIsolatedGroup(SplashBitmap *alpha0BitmapA,
+				   int alpha0XA, int alpha0YA) {
+  alpha0Bitmap = alpha0BitmapA;
+  alpha0X = alpha0XA;
+  alpha0Y = alpha0YA;
+  state->inNonIsolatedGroup = gTrue;
+}
+
+//------------------------------------------------------------------------
+// state save/restore
+//------------------------------------------------------------------------
+
+void Splash::saveState() {
+  SplashState *newState;
+
+  newState = state->copy();
+  newState->next = state;
+  state = newState;
+}
+
+SplashError Splash::restoreState() {
+  SplashState *oldState;
+
+  if (!state->next) {
+    return splashErrNoSave;
+  }
+  oldState = state;
+  state = state->next;
+  delete oldState;
+  return splashOk;
+}
+
+//------------------------------------------------------------------------
+// drawing operations
+//------------------------------------------------------------------------
+
+void Splash::clear(SplashColorPtr color, Guchar alpha) {
+  SplashColorPtr row, p;
+  Guchar mono;
+  int x, y;
+
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    mono = (color[0] & 0x80) ? 0xff : 0x00;
+    if (bitmap->rowSize < 0) {
+      memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	     mono, -bitmap->rowSize * bitmap->height);
+    } else {
+      memset(bitmap->data, mono, bitmap->rowSize * bitmap->height);
+    }
+    break;
+  case splashModeMono8:
+    if (bitmap->rowSize < 0) {
+      memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	     color[0], -bitmap->rowSize * bitmap->height);
+    } else {
+      memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height);
+    }
+    break;
+  case splashModeRGB8:
+    if (color[0] == color[1] && color[1] == color[2]) {
+      if (bitmap->rowSize < 0) {
+	memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	       color[0], -bitmap->rowSize * bitmap->height);
+      } else {
+	memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height);
+      }
+    } else {
+      row = bitmap->data;
+      for (y = 0; y < bitmap->height; ++y) {
+	p = row;
+	for (x = 0; x < bitmap->width; ++x) {
+	  *p++ = color[2];
+	  *p++ = color[1];
+	  *p++ = color[0];
+	}
+	row += bitmap->rowSize;
+      }
+    }
+    break;
+  case splashModeBGR8:
+    if (color[0] == color[1] && color[1] == color[2]) {
+      if (bitmap->rowSize < 0) {
+	memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	       color[0], -bitmap->rowSize * bitmap->height);
+      } else {
+	memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height);
+      }
+    } else {
+      row = bitmap->data;
+      for (y = 0; y < bitmap->height; ++y) {
+	p = row;
+	for (x = 0; x < bitmap->width; ++x) {
+	  *p++ = color[0];
+	  *p++ = color[1];
+	  *p++ = color[2];
+	}
+	row += bitmap->rowSize;
+      }
+    }
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    if (color[0] == color[1] && color[1] == color[2] && color[2] == color[3]) {
+      if (bitmap->rowSize < 0) {
+	memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	       color[0], -bitmap->rowSize * bitmap->height);
+      } else {
+	memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height);
+      }
+    } else {
+      row = bitmap->data;
+      for (y = 0; y < bitmap->height; ++y) {
+	p = row;
+	for (x = 0; x < bitmap->width; ++x) {
+	  *p++ = color[0];
+	  *p++ = color[1];
+	  *p++ = color[2];
+	  *p++ = color[3];
+	}
+	row += bitmap->rowSize;
+      }
+    }
+    break;
+#endif
+  }
+
+  if (bitmap->alpha) {
+    memset(bitmap->alpha, alpha, bitmap->width * bitmap->height);
+  }
+
+  updateModX(0);
+  updateModY(0);
+  updateModX(bitmap->width - 1);
+  updateModY(bitmap->height - 1);
+}
+
+SplashError Splash::stroke(SplashPath *path) {
+  SplashPath *path2, *dPath;
+
+  if (debugMode) {
+    printf("stroke [dash:%d] [width:%.2f]:\n",
+	   state->lineDashLength, (double)state->lineWidth);
+    dumpPath(path);
+  }
+  opClipRes = splashClipAllOutside;
+  if (path->length == 0) {
+    return splashErrEmptyPath;
+  }
+  path2 = flattenPath(path, state->matrix, state->flatness);
+  if (state->lineDashLength > 0) {
+    dPath = makeDashedPath(path2);
+    delete path2;
+    path2 = dPath;
+  }
+  if (state->lineWidth == 0) {
+    strokeNarrow(path2);
+  } else {
+    strokeWide(path2);
+  }
+  delete path2;
+  return splashOk;
+}
+
+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;
+  SplashClipResult clipRes;
+  int nClipRes[3];
+  int i;
+
+  nClipRes[0] = nClipRes[1] = nClipRes[2] = 0;
+
+  xPath = new SplashXPath(path, state->matrix, state->flatness, gFalse);
+
+  pipeInit(&pipe, 0, 0, state->strokePattern, NULL, state->strokeAlpha,
+	   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;
+	  }
+	  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;
+	  }
+	  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];
+  }
+  if (nClipRes[splashClipPartial] ||
+      (nClipRes[splashClipAllInside] && nClipRes[splashClipAllOutside])) {
+    opClipRes = splashClipPartial;
+  } else if (nClipRes[splashClipAllInside]) {
+    opClipRes = splashClipAllInside;
+  } else {
+    opClipRes = splashClipAllOutside;
+  }
+
+  delete xPath;
+}
+
+void Splash::strokeWide(SplashPath *path) {
+  SplashPath *path2;
+
+  path2 = makeStrokePath(path, gFalse);
+  fillWithPattern(path2, gFalse, state->strokePattern, state->strokeAlpha);
+  delete path2;
+}
+
+SplashPath *Splash::flattenPath(SplashPath *path, SplashCoord *matrix,
+				SplashCoord flatness) {
+  SplashPath *fPath;
+  SplashCoord flatness2;
+  Guchar flag;
+  int i;
+
+  fPath = new SplashPath();
+  flatness2 = flatness * flatness;
+  i = 0;
+  while (i < path->length) {
+    flag = path->flags[i];
+    if (flag & splashPathFirst) {
+      fPath->moveTo(path->pts[i].x, path->pts[i].y);
+      ++i;
+    } else {
+      if (flag & splashPathCurve) {
+	flattenCurve(path->pts[i-1].x, path->pts[i-1].y,
+		     path->pts[i  ].x, path->pts[i  ].y,
+		     path->pts[i+1].x, path->pts[i+1].y,
+		     path->pts[i+2].x, path->pts[i+2].y,
+		     matrix, flatness2, fPath);
+	i += 3;
+      } else {
+	fPath->lineTo(path->pts[i].x, path->pts[i].y);
+	++i;
+      }
+      if (path->flags[i-1] & splashPathClosed) {
+	fPath->close();
+      }
+    }
+  }
+  return fPath;
+}
+
+void Splash::flattenCurve(SplashCoord x0, SplashCoord y0,
+			  SplashCoord x1, SplashCoord y1,
+			  SplashCoord x2, SplashCoord y2,
+			  SplashCoord x3, SplashCoord y3,
+			  SplashCoord *matrix, SplashCoord flatness2,
+			  SplashPath *fPath) {
+  SplashCoord cx[splashMaxCurveSplits + 1][3];
+  SplashCoord cy[splashMaxCurveSplits + 1][3];
+  int cNext[splashMaxCurveSplits + 1];
+  SplashCoord xl0, xl1, xl2, xr0, xr1, xr2, xr3, xx1, xx2, xh;
+  SplashCoord yl0, yl1, yl2, yr0, yr1, yr2, yr3, yy1, yy2, yh;
+  SplashCoord dx, dy, mx, my, tx, ty, d1, d2;
+  int p1, p2, p3;
+
+  // initial segment
+  p1 = 0;
+  p2 = splashMaxCurveSplits;
+  cx[p1][0] = x0;  cy[p1][0] = y0;
+  cx[p1][1] = x1;  cy[p1][1] = y1;
+  cx[p1][2] = x2;  cy[p1][2] = y2;
+  cx[p2][0] = x3;  cy[p2][0] = y3;
+  cNext[p1] = p2;
+
+  while (p1 < splashMaxCurveSplits) {
+
+    // get the next segment
+    xl0 = cx[p1][0];  yl0 = cy[p1][0];
+    xx1 = cx[p1][1];  yy1 = cy[p1][1];
+    xx2 = cx[p1][2];  yy2 = cy[p1][2];
+    p2 = cNext[p1];
+    xr3 = cx[p2][0];  yr3 = cy[p2][0];
+
+    // compute the distances (in device space) from the control points
+    // to the midpoint of the straight line (this is a bit of a hack,
+    // but it's much faster than computing the actual distances to the
+    // line)
+    transform(matrix, (xl0 + xr3) * 0.5, (yl0 + yr3) * 0.5, &mx, &my);
+    transform(matrix, xx1, yy1, &tx, &ty);
+    dx = tx - mx;
+    dy = ty - my;
+    d1 = dx*dx + dy*dy;
+    transform(matrix, xx2, yy2, &tx, &ty);
+    dx = tx - mx;
+    dy = ty - my;
+    d2 = dx*dx + dy*dy;
+
+    // if the curve is flat enough, or no more subdivisions are
+    // allowed, add the straight line segment
+    if (p2 - p1 == 1 || (d1 <= flatness2 && d2 <= flatness2)) {
+      fPath->lineTo(xr3, yr3);
+      p1 = p2;
+
+    // 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;
+      // add the new subdivision points
+      p3 = (p1 + p2) / 2;
+      cx[p1][1] = xl1;  cy[p1][1] = yl1;
+      cx[p1][2] = xl2;  cy[p1][2] = yl2;
+      cNext[p1] = p3;
+      cx[p3][0] = xr0;  cy[p3][0] = yr0;
+      cx[p3][1] = xr1;  cy[p3][1] = yr1;
+      cx[p3][2] = xr2;  cy[p3][2] = yr2;
+      cNext[p3] = p2;
+    }
+  }
+}
+
+SplashPath *Splash::makeDashedPath(SplashPath *path) {
+  SplashPath *dPath;
+  SplashCoord lineDashTotal;
+  SplashCoord lineDashStartPhase, lineDashDist, segLen;
+  SplashCoord x0, y0, x1, y1, xa, ya;
+  GBool lineDashStartOn, lineDashOn, newPath;
+  int lineDashStartIdx, lineDashIdx;
+  int i, j, k;
+
+  lineDashTotal = 0;
+  for (i = 0; i < state->lineDashLength; ++i) {
+    lineDashTotal += state->lineDash[i];
+  }
+  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;
+  }
+
+  dPath = new SplashPath();
+
+  // process each subpath
+  i = 0;
+  while (i < path->length) {
+
+    // find the end of the subpath
+    for (j = i;
+	 j < path->length - 1 && !(path->flags[j] & splashPathLast);
+	 ++j) ;
+
+    // initialize the dash parameters
+    lineDashOn = lineDashStartOn;
+    lineDashIdx = lineDashStartIdx;
+    lineDashDist = state->lineDash[lineDashIdx] - lineDashStartPhase;
+
+    // process each segment of the subpath
+    newPath = gTrue;
+    for (k = i; k < j; ++k) {
+
+      // grab the segment
+      x0 = path->pts[k].x;
+      y0 = path->pts[k].y;
+      x1 = path->pts[k+1].x;
+      y1 = path->pts[k+1].y;
+      segLen = splashDist(x0, y0, x1, y1);
+
+      // process the segment
+      while (segLen > 0) {
+
+	if (lineDashDist >= segLen) {
+	  if (lineDashOn) {
+	    if (newPath) {
+	      dPath->moveTo(x0, y0);
+	      newPath = gFalse;
+	    }
+	    dPath->lineTo(x1, y1);
+	  }
+	  lineDashDist -= segLen;
+	  segLen = 0;
+
+	} else {
+	  xa = x0 + (lineDashDist / segLen) * (x1 - x0);
+	  ya = y0 + (lineDashDist / segLen) * (y1 - y0);
+	  if (lineDashOn) {
+	    if (newPath) {
+	      dPath->moveTo(x0, y0);
+	      newPath = gFalse;
+	    }
+	    dPath->lineTo(xa, ya);
+	  }
+	  x0 = xa;
+	  y0 = ya;
+	  segLen -= lineDashDist;
+	  lineDashDist = 0;
+	}
+
+	// get the next entry in the dash array
+	if (lineDashDist <= 0) {
+	  lineDashOn = !lineDashOn;
+	  if (++lineDashIdx == state->lineDashLength) {
+	    lineDashIdx = 0;
+	  }
+	  lineDashDist = state->lineDash[lineDashIdx];
+	  newPath = gTrue;
+	}
+      }
+    }
+    i = j + 1;
+  }
+
+  return dPath;
+}
+
+SplashError Splash::fill(SplashPath *path, GBool eo) {
+  if (debugMode) {
+    printf("fill [eo:%d]:\n", eo);
+    dumpPath(path);
+  }
+  return fillWithPattern(path, eo, state->fillPattern, state->fillAlpha);
+}
+
+SplashError Splash::fillWithPattern(SplashPath *path, GBool eo,
+				    SplashPattern *pattern,
+				    SplashCoord alpha) {
+  SplashPipe pipe;
+  SplashXPath *xPath;
+  SplashXPathScanner *scanner;
+  int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y;
+  SplashClipResult clipRes, clipRes2;
+
+  if (path->length == 0) {
+    return splashErrEmptyPath;
+  }
+  xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue);
+  if (vectorAntialias) {
+    xPath->aaScale();
+  }
+  xPath->sort();
+  scanner = new SplashXPathScanner(xPath, eo);
+
+  // get the min and max x and y values
+  if (vectorAntialias) {
+    scanner->getBBoxAA(&xMinI, &yMinI, &xMaxI, &yMaxI);
+  } else {
+    scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI);
+  }
+
+  // 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();
+    }
+
+    pipeInit(&pipe, 0, yMinI, pattern, NULL, alpha, vectorAntialias, gFalse);
+
+    // draw the spans
+    if (vectorAntialias) {
+      for (y = yMinI; y <= yMaxI; ++y) {
+	scanner->renderAALine(aaBuf, &x0, &x1, y);
+	if (clipRes != splashClipAllInside) {
+	  state->clip->clipAALine(aaBuf, &x0, &x1, y);
+	}
+	drawAALine(&pipe, x0, x1, y);
+      }
+    } else {
+      for (y = yMinI; y <= yMaxI; ++y) {
+	while (scanner->getNextSpan(y, &x0, &x1)) {
+	  if (clipRes == splashClipAllInside) {
+	    drawSpan(&pipe, x0, x1, y, gTrue);
+	  } else {
+	    // limit the x range
+	    if (x0 < state->clip->getXMinI()) {
+	      x0 = state->clip->getXMinI();
+	    }
+	    if (x1 > state->clip->getXMaxI()) {
+	      x1 = state->clip->getXMaxI();
+	    }
+	    clipRes2 = state->clip->testSpan(x0, x1, y);
+	    drawSpan(&pipe, x0, x1, y, clipRes2 == splashClipAllInside);
+	  }
+	}
+      }
+    }
+  }
+  opClipRes = clipRes;
+
+  delete scanner;
+  delete xPath;
+  return splashOk;
+}
+
+SplashError Splash::xorFill(SplashPath *path, GBool eo) {
+  SplashPipe pipe;
+  SplashXPath *xPath;
+  SplashXPathScanner *scanner;
+  int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y;
+  SplashClipResult clipRes, clipRes2;
+  SplashBlendFunc origBlendFunc;
+
+  if (path->length == 0) {
+    return splashErrEmptyPath;
+  }
+  xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue);
+  xPath->sort();
+  scanner = new SplashXPathScanner(xPath, eo);
+
+  // get the min and max x and y values
+  scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI);
+
+  // 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();
+    }
+
+    origBlendFunc = state->blendFunc;
+    state->blendFunc = &blendXor;
+    pipeInit(&pipe, 0, yMinI, state->fillPattern, NULL, 1, gFalse, gFalse);
+
+    // draw the spans
+    for (y = yMinI; y <= yMaxI; ++y) {
+      while (scanner->getNextSpan(y, &x0, &x1)) {
+	if (clipRes == splashClipAllInside) {
+	  drawSpan(&pipe, x0, x1, y, gTrue);
+	} else {
+	  // limit the x range
+	  if (x0 < state->clip->getXMinI()) {
+	    x0 = state->clip->getXMinI();
+	  }
+	  if (x1 > state->clip->getXMaxI()) {
+	    x1 = state->clip->getXMaxI();
+	  }
+	  clipRes2 = state->clip->testSpan(x0, x1, y);
+	  drawSpan(&pipe, x0, x1, y, clipRes2 == splashClipAllInside);
+	}
+      }
+    }
+    state->blendFunc = origBlendFunc;
+  }
+  opClipRes = clipRes;
+
+  delete scanner;
+  delete xPath;
+  return splashOk;
+}
+
+SplashError Splash::fillChar(SplashCoord x, SplashCoord y,
+			     int c, SplashFont *font) {
+  SplashGlyphBitmap glyph;
+  SplashCoord xt, yt;
+  int x0, y0, xFrac, yFrac;
+  SplashClipResult clipRes;
+
+  if (debugMode) {
+    printf("fillChar: x=%.2f y=%.2f c=%3d=0x%02x='%c'\n",
+	   (double)x, (double)y, c, c, c);
+  }
+  transform(state->matrix, x, y, &xt, &yt);
+  x0 = splashFloor(xt);
+  xFrac = splashFloor((xt - x0) * splashFontFraction);
+  y0 = splashFloor(yt);
+  yFrac = splashFloor((yt - y0) * splashFontFraction);
+  if (!font->getGlyph(c, xFrac, yFrac, &glyph, x0, y0, state->clip, &clipRes)) {
+    return splashErrNoGlyph;
+  }
+  if (clipRes != splashClipAllOutside) {
+    fillGlyph2(x0, y0, &glyph, clipRes == splashClipAllInside);
+  }
+  opClipRes = clipRes;
+  if (glyph.freeData) {
+    gfree(glyph.data);
+  }
+  return splashOk;
+}
+
+void Splash::fillGlyph(SplashCoord x, SplashCoord y,
+			      SplashGlyphBitmap *glyph) {
+  SplashCoord xt, yt;
+  int x0, y0;
+
+  transform(state->matrix, x, y, &xt, &yt);
+  x0 = splashFloor(xt);
+  y0 = splashFloor(yt);
+  SplashClipResult clipRes = state->clip->testRect(x0 - glyph->x,
+                             y0 - glyph->y,
+                             x0 - glyph->x + glyph->w - 1,
+                             y0 - glyph->y + glyph->h - 1);
+  if (clipRes != splashClipAllOutside) {
+    fillGlyph2(x0, y0, glyph, clipRes == splashClipAllInside);
+  }
+  opClipRes = clipRes;
+}
+
+void Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph, GBool noClip) {
+  SplashPipe pipe;
+  int alpha0, alpha;
+  Guchar *p;
+  int x1, y1, xx, xx1, yy;
+
+  p = glyph->data;
+  int xStart = x0 - glyph->x;
+  int yStart = y0 - glyph->y;
+  int xxLimit = glyph->w;
+  int yyLimit = glyph->h;
+
+  if (yStart < 0)
+  {
+    p += glyph->w * -yStart; // move p to the beginning of the first painted row
+    yyLimit += yStart;
+    yStart = 0;
+  }
+
+  if (xStart < 0)
+  {
+    p += -xStart; // move p to the first painted pixel
+    xxLimit += xStart;
+    xStart = 0;
+  }
+
+  if (xxLimit + xStart >= bitmap->width) xxLimit = bitmap->width - xStart;
+  if (yyLimit + yStart >= bitmap->height) yyLimit = bitmap->height - yStart;
+
+    if (noClip) {
+      if (glyph->aa) {
+	pipeInit(&pipe, xStart, yStart,
+		 state->fillPattern, NULL, state->fillAlpha, 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);
+	      updateModX(x1);
+	      updateModY(y1);
+	    } else {
+	      pipeIncX(&pipe);
+	    }
+	  }
+	  p += glyph->w;
+	}
+      } else {
+	const int widthEight = (int)ceil(glyph->w / 8.0);
+
+	pipeInit(&pipe, xStart, yStart,
+		 state->fillPattern, NULL, state->fillAlpha, 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 = p[xx / 8];
+	    for (xx1 = 0; xx1 < 8 && xx + xx1 < xxLimit; ++xx1, ++x1) {
+	      if (alpha0 & 0x80) {
+		pipeRun(&pipe);
+		updateModX(x1);
+		updateModY(y1);
+	      } else {
+		pipeIncX(&pipe);
+	      }
+	      alpha0 <<= 1;
+	    }
+	  }
+	  p += widthEight;
+	}
+      }
+    } else {
+      if (glyph->aa) {
+	pipeInit(&pipe, xStart, yStart,
+		 state->fillPattern, NULL, state->fillAlpha, 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);
+		updateModX(x1);
+		updateModY(y1);
+	      } else {
+		pipeIncX(&pipe);
+	      }
+	    } else {
+	      pipeIncX(&pipe);
+	    }
+	  }
+	  p += glyph->w;
+	}
+      } else {
+	const int widthEight = (int)ceil(glyph->w / 8.0);
+
+	pipeInit(&pipe, xStart, yStart,
+		 state->fillPattern, NULL, state->fillAlpha, 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 = p[xx / 8];
+	    for (xx1 = 0; xx1 < 8 && xx + xx1 < xxLimit; ++xx1, ++x1) {
+	      if (state->clip->test(x1, y1)) {
+		if (alpha0 & 0x80) {
+		  pipeRun(&pipe);
+		  updateModX(x1);
+		  updateModY(y1);
+		} else {
+		  pipeIncX(&pipe);
+		}
+	      } else {
+		pipeIncX(&pipe);
+	      }
+	      alpha0 <<= 1;
+	    }
+	  }
+	  p += widthEight;
+	}
+      }
+    }
+}
+
+SplashError Splash::fillImageMask(SplashImageMaskSource src, void *srcData,
+				  int w, int h, SplashCoord *mat,
+				  GBool glyphMode) {
+  SplashPipe pipe;
+  GBool rot;
+  SplashCoord xScale, yScale, xShear, yShear, yShear1;
+  int tx, tx2, ty, ty2, scaledWidth, scaledHeight, xSign, ySign;
+  int ulx, uly, llx, lly, urx, ury, lrx, lry;
+  int ulx1, uly1, llx1, lly1, urx1, ury1, lrx1, lry1;
+  int xMin, xMax, yMin, yMax;
+  SplashClipResult clipRes, clipRes2;
+  int yp, yq, yt, yStep, lastYStep;
+  int xp, xq, xt, xStep, xSrc;
+  int k1, spanXMin, spanXMax, spanY;
+  SplashColorPtr pixBuf, p;
+  int pixAcc;
+  int x, y, x1, x2, y2;
+  SplashCoord y1;
+  int n, m, i, j;
+
+  if (debugMode) {
+    printf("fillImageMask: w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n",
+	   w, h, (double)mat[0], (double)mat[1], (double)mat[2],
+	   (double)mat[3], (double)mat[4], (double)mat[5]);
+  }
+
+  if (w == 0 && h == 0) return splashErrZeroImage;
+
+  // check for singular matrix
+  if (splashAbs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.000001) {
+    return splashErrSingularMatrix;
+  }
+
+  // compute scale, shear, rotation, translation parameters
+  rot = splashAbs(mat[1]) > splashAbs(mat[0]);
+  if (rot) {
+    xScale = -mat[1];
+    yScale = mat[2] - (mat[0] * mat[3]) / mat[1];
+    xShear = -mat[3] / yScale;
+    yShear = -mat[0] / mat[1];
+  } else {
+    xScale = mat[0];
+    yScale = mat[3] - (mat[1] * mat[2]) / mat[0];
+    xShear = mat[2] / yScale;
+    yShear = mat[1] / mat[0];
+  }
+  // Note 1: The PDF spec says that all pixels whose *centers* lie
+  // within the region get painted -- but that doesn't seem to match
+  // up with what Acrobat actually does: it ends up leaving gaps
+  // between image stripes.  So we use the same rule here as for
+  // fills: any pixel that overlaps the region gets painted.
+  // Note 2: The "glyphMode" flag is a kludge: it switches back to
+  // "correct" behavior (matching the spec), for use in rendering Type
+  // 3 fonts.
+  // Note 3: The +/-0.01 in these computations is to avoid floating
+  // point precision problems which can lead to gaps between image
+  // stripes (it can cause image stripes to overlap, but that's a much
+  // less visible problem).
+  if (glyphMode) {
+    if (xScale >= 0) {
+      tx = splashRound(mat[4]);
+      tx2 = splashRound(mat[4] + xScale) - 1;
+    } else {
+      tx = splashRound(mat[4]) - 1;
+      tx2 = splashRound(mat[4] + xScale);
+    }
+  } else {
+    if (xScale >= 0) {
+      tx = splashFloor(mat[4] - 0.01);
+      tx2 = splashFloor(mat[4] + xScale + 0.01);
+    } else {
+      tx = splashFloor(mat[4] + 0.01);
+      tx2 = splashFloor(mat[4] + xScale - 0.01);
+    }
+  }
+  scaledWidth = abs(tx2 - tx) + 1;
+  if (glyphMode) {
+    if (yScale >= 0) {
+      ty = splashRound(mat[5]);
+      ty2 = splashRound(mat[5] + yScale) - 1;
+    } else {
+      ty = splashRound(mat[5]) - 1;
+      ty2 = splashRound(mat[5] + yScale);
+    }
+  } else {
+    if (yScale >= 0) {
+      ty = splashFloor(mat[5] - 0.01);
+      ty2 = splashFloor(mat[5] + yScale + 0.01);
+    } else {
+      ty = splashFloor(mat[5] + 0.01);
+      ty2 = splashFloor(mat[5] + yScale - 0.01);
+    }
+  }
+  scaledHeight = abs(ty2 - ty) + 1;
+  xSign = (xScale < 0) ? -1 : 1;
+  ySign = (yScale < 0) ? -1 : 1;
+  yShear1 = (SplashCoord)xSign * yShear;
+
+  // clipping
+  ulx1 = 0;
+  uly1 = 0;
+  urx1 = xSign * (scaledWidth - 1);
+  ury1 = (int)(yShear * urx1);
+  llx1 = splashRound(xShear * ySign * (scaledHeight - 1));
+  lly1 = ySign * (scaledHeight - 1) + (int)(yShear * llx1);
+  lrx1 = xSign * (scaledWidth - 1) +
+           splashRound(xShear * ySign * (scaledHeight - 1));
+  lry1 = ySign * (scaledHeight - 1) + (int)(yShear * lrx1);
+  if (rot) {
+    ulx = tx + uly1;    uly = ty - ulx1;
+    urx = tx + ury1;    ury = ty - urx1;
+    llx = tx + lly1;    lly = ty - llx1;
+    lrx = tx + lry1;    lry = ty - lrx1;
+  } else {
+    ulx = tx + ulx1;    uly = ty + uly1;
+    urx = tx + urx1;    ury = ty + ury1;
+    llx = tx + llx1;    lly = ty + lly1;
+    lrx = tx + lrx1;    lry = ty + lry1;
+  }
+  xMin = (ulx < urx) ? (ulx < llx) ? (ulx < lrx) ? ulx : lrx
+                                   : (llx < lrx) ? llx : lrx
+		     : (urx < llx) ? (urx < lrx) ? urx : lrx
+                                   : (llx < lrx) ? llx : lrx;
+  xMax = (ulx > urx) ? (ulx > llx) ? (ulx > lrx) ? ulx : lrx
+                                   : (llx > lrx) ? llx : lrx
+		     : (urx > llx) ? (urx > lrx) ? urx : lrx
+                                   : (llx > lrx) ? llx : lrx;
+  yMin = (uly < ury) ? (uly < lly) ? (uly < lry) ? uly : lry
+                                   : (lly < lry) ? lly : lry
+		     : (ury < lly) ? (ury < lry) ? ury : lry
+                                   : (lly < lry) ? lly : lry;
+  yMax = (uly > ury) ? (uly > lly) ? (uly > lry) ? uly : lry
+                                   : (lly > lry) ? lly : lry
+		     : (ury > lly) ? (ury > lry) ? ury : lry
+                                   : (lly > lry) ? lly : lry;
+  clipRes = state->clip->testRect(xMin, yMin, xMax, yMax);
+  opClipRes = clipRes;
+
+  // compute Bresenham parameters for x and y scaling
+  yp = h / scaledHeight;
+  yq = h % scaledHeight;
+  xp = w / scaledWidth;
+  xq = w % scaledWidth;
+
+  // allocate pixel buffer
+  pixBuf = (SplashColorPtr)gmalloc((yp + 1) * w);
+
+  // initialize the pixel pipe
+  pipeInit(&pipe, 0, 0, state->fillPattern, NULL, state->fillAlpha,
+	   gTrue, gFalse);
+  if (vectorAntialias) {
+    drawAAPixelInit();
+  }
+
+  // init y scale Bresenham
+  yt = 0;
+  lastYStep = 1;
+
+  for (y = 0; y < scaledHeight; ++y) {
+
+    // y scale Bresenham
+    yStep = yp;
+    yt += yq;
+    if (yt >= scaledHeight) {
+      yt -= scaledHeight;
+      ++yStep;
+    }
+
+    // read row(s) from image
+    n = (yp > 0) ? yStep : lastYStep;
+    if (n > 0) {
+      p = pixBuf;
+      for (i = 0; i < n; ++i) {
+	(*src)(srcData, p);
+	p += w;
+      }
+    }
+    lastYStep = yStep;
+
+    // loop-invariant constants
+    k1 = splashRound(xShear * ySign * y);
+
+    // clipping test
+    if (clipRes != splashClipAllInside &&
+	!rot &&
+	(int)(yShear * k1) ==
+	  (int)(yShear * (xSign * (scaledWidth - 1) + k1))) {
+      if (xSign > 0) {
+	spanXMin = tx + k1;
+	spanXMax = spanXMin + (scaledWidth - 1);
+      } else {
+	spanXMax = tx + k1;
+	spanXMin = spanXMax - (scaledWidth - 1);
+      }
+      spanY = ty + ySign * y + (int)(yShear * k1);
+      clipRes2 = state->clip->testSpan(spanXMin, spanXMax, spanY);
+      if (clipRes2 == splashClipAllOutside) {
+	continue;
+      }
+    } else {
+      clipRes2 = clipRes;
+    }
+
+    // init x scale Bresenham
+    xt = 0;
+    xSrc = 0;
+
+    // x shear
+    x1 = k1;
+
+    // y shear
+    y1 = (SplashCoord)ySign * y + yShear * x1;
+    // this is a kludge: if yShear1 is negative, then (int)y1 would
+    // change immediately after the first pixel, which is not what we
+    // want
+    if (yShear1 < 0) {
+      y1 += 0.999;
+    }
+
+    // loop-invariant constants
+    n = yStep > 0 ? yStep : 1;
+
+    for (x = 0; x < scaledWidth; ++x) {
+
+      // x scale Bresenham
+      xStep = xp;
+      xt += xq;
+      if (xt >= scaledWidth) {
+	xt -= scaledWidth;
+	++xStep;
+      }
+
+      // rotation
+      if (rot) {
+	x2 = (int)y1;
+	y2 = -x1;
+      } else {
+	x2 = x1;
+	y2 = (int)y1;
+      }
+
+      // compute the alpha value for (x,y) after the x and y scaling
+      // operations
+      m = xStep > 0 ? xStep : 1;
+      p = pixBuf + xSrc;
+      pixAcc = 0;
+      for (i = 0; i < n; ++i) {
+	for (j = 0; j < m; ++j) {
+	  pixAcc += *p++;
+	}
+	p += w - m;
+      }
+
+      // blend fill color with background
+      if (pixAcc != 0) {
+	pipe.shape = (pixAcc == n * m)
+	                 ? (SplashCoord)1
+	                 : (SplashCoord)pixAcc / (SplashCoord)(n * m);
+	if (vectorAntialias && clipRes2 != splashClipAllInside) {
+	  drawAAPixel(&pipe, tx + x2, ty + y2);
+	} else {
+	  drawPixel(&pipe, tx + x2, ty + y2, clipRes2 == splashClipAllInside);
+	}
+      }
+
+      // x scale Bresenham
+      xSrc += xStep;
+
+      // x shear
+      x1 += xSign;
+
+      // y shear
+      y1 += yShear1;
+    }
+  }
+
+  // free memory
+  gfree(pixBuf);
+
+  return splashOk;
+}
+
+SplashError Splash::drawImage(SplashImageSource src, void *srcData,
+			      SplashColorMode srcMode, GBool srcAlpha,
+			      int w, int h, SplashCoord *mat) {
+  SplashPipe pipe;
+  GBool ok, rot;
+  SplashCoord xScale, yScale, xShear, yShear, yShear1;
+  int tx, tx2, ty, ty2, scaledWidth, scaledHeight, xSign, ySign;
+  int ulx, uly, llx, lly, urx, ury, lrx, lry;
+  int ulx1, uly1, llx1, lly1, urx1, ury1, lrx1, lry1;
+  int xMin, xMax, yMin, yMax;
+  SplashClipResult clipRes, clipRes2;
+  int yp, yq, yt, yStep, lastYStep;
+  int xp, xq, xt, xStep, xSrc;
+  int k1, spanXMin, spanXMax, spanY;
+  SplashColorPtr colorBuf, p;
+  SplashColor pix;
+  Guchar *alphaBuf, *q;
+#if SPLASH_CMYK
+  int pixAcc0, pixAcc1, pixAcc2, pixAcc3;
+#else
+  int pixAcc0, pixAcc1, pixAcc2;
+#endif
+  int alphaAcc;
+  SplashCoord pixMul, alphaMul, alpha;
+  int x, y, x1, x2, y2;
+  SplashCoord y1;
+  int nComps, n, m, i, j;
+
+  if (debugMode) {
+    printf("drawImage: srcMode=%d srcAlpha=%d w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n",
+	   srcMode, srcAlpha, w, h, (double)mat[0], (double)mat[1], (double)mat[2],
+	   (double)mat[3], (double)mat[4], (double)mat[5]);
+  }
+
+  // check color modes
+  ok = gFalse; // make gcc happy
+  nComps = 0; // make gcc happy
+  switch (bitmap->mode) {
+  case splashModeMono1:
+  case splashModeMono8:
+    ok = srcMode == splashModeMono8;
+    nComps = 1;
+    break;
+  case splashModeRGB8:
+    ok = srcMode == splashModeRGB8;
+    nComps = 3;
+    break;
+  case splashModeBGR8:
+    ok = srcMode == splashModeBGR8;
+    nComps = 3;
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    ok = srcMode == splashModeCMYK8;
+    nComps = 4;
+    break;
+#endif
+  }
+  if (!ok) {
+    return splashErrModeMismatch;
+  }
+
+  // check for singular matrix
+  if (splashAbs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.000001) {
+    return splashErrSingularMatrix;
+  }
+
+  // compute scale, shear, rotation, translation parameters
+  rot = splashAbs(mat[1]) > splashAbs(mat[0]);
+  if (rot) {
+    xScale = -mat[1];
+    yScale = mat[2] - (mat[0] * mat[3]) / mat[1];
+    xShear = -mat[3] / yScale;
+    yShear = -mat[0] / mat[1];
+  } else {
+    xScale = mat[0];
+    yScale = mat[3] - (mat[1] * mat[2]) / mat[0];
+    xShear = mat[2] / yScale;
+    yShear = mat[1] / mat[0];
+  }
+  // Note 1: The PDF spec says that all pixels whose *centers* lie
+  // within the region get painted -- but that doesn't seem to match
+  // up with what Acrobat actually does: it ends up leaving gaps
+  // between image stripes.  So we use the same rule here as for
+  // fills: any pixel that overlaps the region gets painted.
+  // Note 2: The +/-0.01 in these computations is to avoid floating
+  // point precision problems which can lead to gaps between image
+  // stripes (it can cause image stripes to overlap, but that's a much
+  // less visible problem).
+  if (xScale >= 0) {
+    tx = splashFloor(mat[4] - 0.01);
+    tx2 = splashFloor(mat[4] + xScale + 0.01);
+  } else {
+    tx = splashFloor(mat[4] + 0.01);
+    tx2 = splashFloor(mat[4] + xScale - 0.01);
+  }
+  scaledWidth = abs(tx2 - tx) + 1;
+  if (yScale >= 0) {
+    ty = splashFloor(mat[5] - 0.01);
+    ty2 = splashFloor(mat[5] + yScale + 0.01);
+  } else {
+    ty = splashFloor(mat[5] + 0.01);
+    ty2 = splashFloor(mat[5] + yScale - 0.01);
+  }
+  scaledHeight = abs(ty2 - ty) + 1;
+  xSign = (xScale < 0) ? -1 : 1;
+  ySign = (yScale < 0) ? -1 : 1;
+  yShear1 = (SplashCoord)xSign * yShear;
+
+  // clipping
+  ulx1 = 0;
+  uly1 = 0;
+  urx1 = xSign * (scaledWidth - 1);
+  ury1 = (int)(yShear * urx1);
+  llx1 = splashRound(xShear * ySign * (scaledHeight - 1));
+  lly1 = ySign * (scaledHeight - 1) + (int)(yShear * llx1);
+  lrx1 = xSign * (scaledWidth - 1) +
+           splashRound(xShear * ySign * (scaledHeight - 1));
+  lry1 = ySign * (scaledHeight - 1) + (int)(yShear * lrx1);
+  if (rot) {
+    ulx = tx + uly1;    uly = ty - ulx1;
+    urx = tx + ury1;    ury = ty - urx1;
+    llx = tx + lly1;    lly = ty - llx1;
+    lrx = tx + lry1;    lry = ty - lrx1;
+  } else {
+    ulx = tx + ulx1;    uly = ty + uly1;
+    urx = tx + urx1;    ury = ty + ury1;
+    llx = tx + llx1;    lly = ty + lly1;
+    lrx = tx + lrx1;    lry = ty + lry1;
+  }
+  xMin = (ulx < urx) ? (ulx < llx) ? (ulx < lrx) ? ulx : lrx
+                                   : (llx < lrx) ? llx : lrx
+		     : (urx < llx) ? (urx < lrx) ? urx : lrx
+                                   : (llx < lrx) ? llx : lrx;
+  xMax = (ulx > urx) ? (ulx > llx) ? (ulx > lrx) ? ulx : lrx
+                                   : (llx > lrx) ? llx : lrx
+		     : (urx > llx) ? (urx > lrx) ? urx : lrx
+                                   : (llx > lrx) ? llx : lrx;
+  yMin = (uly < ury) ? (uly < lly) ? (uly < lry) ? uly : lry
+                                   : (lly < lry) ? lly : lry
+		     : (ury < lly) ? (ury < lry) ? ury : lry
+                                   : (lly < lry) ? lly : lry;
+  yMax = (uly > ury) ? (uly > lly) ? (uly > lry) ? uly : lry
+                                   : (lly > lry) ? lly : lry
+		     : (ury > lly) ? (ury > lry) ? ury : lry
+                                   : (lly > lry) ? lly : lry;
+  clipRes = state->clip->testRect(xMin, yMin, xMax, yMax);
+  opClipRes = clipRes;
+  if (clipRes == splashClipAllOutside) {
+    return splashOk;
+  }
+
+  // compute Bresenham parameters for x and y scaling
+  yp = h / scaledHeight;
+  yq = h % scaledHeight;
+  xp = w / scaledWidth;
+  xq = w % scaledWidth;
+
+  // allocate pixel buffers
+  colorBuf = (SplashColorPtr)gmalloc((yp + 1) * w * nComps);
+  if (srcAlpha) {
+    alphaBuf = (Guchar *)gmalloc((yp + 1) * w);
+  } else {
+    alphaBuf = NULL;
+  }
+
+  pixAcc0 = pixAcc1 = pixAcc2 = 0; // make gcc happy
+#if SPLASH_CMYK
+  pixAcc3 = 0; // make gcc happy
+#endif
+
+  // initialize the pixel pipe
+  pipeInit(&pipe, 0, 0, NULL, pix, state->fillAlpha,
+	   srcAlpha || (vectorAntialias && clipRes != splashClipAllInside),
+	   gFalse);
+  if (vectorAntialias) {
+    drawAAPixelInit();
+  }
+
+  if (srcAlpha) {
+
+    // init y scale Bresenham
+    yt = 0;
+    lastYStep = 1;
+
+    for (y = 0; y < scaledHeight; ++y) {
+
+      // y scale Bresenham
+      yStep = yp;
+      yt += yq;
+      if (yt >= scaledHeight) {
+	yt -= scaledHeight;
+	++yStep;
+      }
+
+      // read row(s) from image
+      n = (yp > 0) ? yStep : lastYStep;
+      if (n > 0) {
+	p = colorBuf;
+	q = alphaBuf;
+	for (i = 0; i < n; ++i) {
+	  (*src)(srcData, p, q);
+	  p += w * nComps;
+	  q += w;
+	}
+      }
+      lastYStep = yStep;
+
+      // loop-invariant constants
+      k1 = splashRound(xShear * ySign * y);
+
+      // clipping test
+      if (clipRes != splashClipAllInside &&
+	  !rot &&
+	  (int)(yShear * k1) ==
+	    (int)(yShear * (xSign * (scaledWidth - 1) + k1))) {
+	if (xSign > 0) {
+	  spanXMin = tx + k1;
+	  spanXMax = spanXMin + (scaledWidth - 1);
+	} else {
+	  spanXMax = tx + k1;
+	  spanXMin = spanXMax - (scaledWidth - 1);
+	}
+	spanY = ty + ySign * y + (int)(yShear * k1);
+	clipRes2 = state->clip->testSpan(spanXMin, spanXMax, spanY);
+	if (clipRes2 == splashClipAllOutside) {
+	  continue;
+	}
+      } else {
+	clipRes2 = clipRes;
+      }
+
+      // init x scale Bresenham
+      xt = 0;
+      xSrc = 0;
+
+      // x shear
+      x1 = k1;
+
+      // y shear
+      y1 = (SplashCoord)ySign * y + yShear * x1;
+      // this is a kludge: if yShear1 is negative, then (int)y1 would
+      // change immediately after the first pixel, which is not what
+      // we want
+      if (yShear1 < 0) {
+	y1 += 0.999;
+      }
+
+      // loop-invariant constants
+      n = yStep > 0 ? yStep : 1;
+
+      switch (srcMode) {
+
+      case splashModeMono1:
+      case splashModeMono8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  alphaAcc = 0;
+	  p = colorBuf + xSrc;
+	  q = alphaBuf + xSrc;
+	  pixAcc0 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      alphaAcc += *q++;
+	    }
+	    p += w - m;
+	    q += w - m;
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+	  alphaMul = pixMul * (1.0 / 255.0);
+	  alpha = (SplashCoord)alphaAcc * alphaMul;
+
+	  if (alpha > 0) {
+	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+
+	    // set pixel
+	    pipe.shape = alpha;
+	    if (vectorAntialias && clipRes != splashClipAllInside) {
+	      drawAAPixel(&pipe, tx + x2, ty + y2);
+	    } else {
+	      drawPixel(&pipe, tx + x2, ty + y2,
+			clipRes2 == splashClipAllInside);
+	    }
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+
+      case splashModeRGB8:
+      case splashModeBGR8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  alphaAcc = 0;
+	  p = colorBuf + xSrc * 3;
+	  q = alphaBuf + xSrc;
+	  pixAcc0 = pixAcc1 = pixAcc2 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      pixAcc1 += *p++;
+	      pixAcc2 += *p++;
+	      alphaAcc += *q++;
+	    }
+	    p += 3 * (w - m);
+	    q += w - m;
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+	  alphaMul = pixMul * (1.0 / 255.0);
+	  alpha = (SplashCoord)alphaAcc * alphaMul;
+
+	  if (alpha > 0) {
+	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+	    pix[1] = (int)((SplashCoord)pixAcc1 * pixMul);
+	    pix[2] = (int)((SplashCoord)pixAcc2 * pixMul);
+
+	    // set pixel
+	    pipe.shape = alpha;
+	    if (vectorAntialias && clipRes != splashClipAllInside) {
+	      drawAAPixel(&pipe, tx + x2, ty + y2);
+	    } else {
+	      drawPixel(&pipe, tx + x2, ty + y2,
+			clipRes2 == splashClipAllInside);
+	    }
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  alphaAcc = 0;
+	  p = colorBuf + xSrc * 4;
+	  q = alphaBuf + xSrc;
+	  pixAcc0 = pixAcc1 = pixAcc2 = pixAcc3 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      pixAcc1 += *p++;
+	      pixAcc2 += *p++;
+	      pixAcc3 += *p++;
+	      alphaAcc += *q++;
+	    }
+	    p += 4 * (w - m);
+	    q += w - m;
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+	  alphaMul = pixMul * (1.0 / 255.0);
+	  alpha = (SplashCoord)alphaAcc * alphaMul;
+
+	  if (alpha > 0) {
+	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+	    pix[1] = (int)((SplashCoord)pixAcc1 * pixMul);
+	    pix[2] = (int)((SplashCoord)pixAcc2 * pixMul);
+	    pix[3] = (int)((SplashCoord)pixAcc3 * pixMul);
+
+	    // set pixel
+	    pipe.shape = alpha;
+	    if (vectorAntialias && clipRes != splashClipAllInside) {
+	      drawAAPixel(&pipe, tx + x2, ty + y2);
+	    } else {
+	      drawPixel(&pipe, tx + x2, ty + y2,
+			clipRes2 == splashClipAllInside);
+	    }
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+#endif // SPLASH_CMYK
+      }
+    }
+
+  } else {
+
+    // init y scale Bresenham
+    yt = 0;
+    lastYStep = 1;
+
+    for (y = 0; y < scaledHeight; ++y) {
+
+      // y scale Bresenham
+      yStep = yp;
+      yt += yq;
+      if (yt >= scaledHeight) {
+	yt -= scaledHeight;
+	++yStep;
+      }
+
+      // read row(s) from image
+      n = (yp > 0) ? yStep : lastYStep;
+      if (n > 0) {
+	p = colorBuf;
+	for (i = 0; i < n; ++i) {
+	  (*src)(srcData, p, NULL);
+	  p += w * nComps;
+	}
+      }
+      lastYStep = yStep;
+
+      // loop-invariant constants
+      k1 = splashRound(xShear * ySign * y);
+
+      // clipping test
+      if (clipRes != splashClipAllInside &&
+	  !rot &&
+	  (int)(yShear * k1) ==
+	    (int)(yShear * (xSign * (scaledWidth - 1) + k1))) {
+	if (xSign > 0) {
+	  spanXMin = tx + k1;
+	  spanXMax = spanXMin + (scaledWidth - 1);
+	} else {
+	  spanXMax = tx + k1;
+	  spanXMin = spanXMax - (scaledWidth - 1);
+	}
+	spanY = ty + ySign * y + (int)(yShear * k1);
+	clipRes2 = state->clip->testSpan(spanXMin, spanXMax, spanY);
+	if (clipRes2 == splashClipAllOutside) {
+	  continue;
+	}
+      } else {
+	clipRes2 = clipRes;
+      }
+
+      // init x scale Bresenham
+      xt = 0;
+      xSrc = 0;
+
+      // x shear
+      x1 = k1;
+
+      // y shear
+      y1 = (SplashCoord)ySign * y + yShear * x1;
+      // this is a kludge: if yShear1 is negative, then (int)y1 would
+      // change immediately after the first pixel, which is not what
+      // we want
+      if (yShear1 < 0) {
+	y1 += 0.999;
+      }
+
+      // loop-invariant constants
+      n = yStep > 0 ? yStep : 1;
+
+      switch (srcMode) {
+
+      case splashModeMono1:
+      case splashModeMono8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  p = colorBuf + xSrc;
+	  pixAcc0 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	    }
+	    p += w - m;
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+
+	  pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+
+	  // set pixel
+	  if (vectorAntialias && clipRes != splashClipAllInside) {
+	    pipe.shape = (SplashCoord)1;
+	    drawAAPixel(&pipe, tx + x2, ty + y2);
+	  } else {
+	    drawPixel(&pipe, tx + x2, ty + y2,
+		      clipRes2 == splashClipAllInside);
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+
+      case splashModeRGB8:
+      case splashModeBGR8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  p = colorBuf + xSrc * 3;
+	  pixAcc0 = pixAcc1 = pixAcc2 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      pixAcc1 += *p++;
+	      pixAcc2 += *p++;
+	    }
+	    p += 3 * (w - m);
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+
+	  pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+	  pix[1] = (int)((SplashCoord)pixAcc1 * pixMul);
+	  pix[2] = (int)((SplashCoord)pixAcc2 * pixMul);
+
+	  // set pixel
+	  if (vectorAntialias && clipRes != splashClipAllInside) {
+	    pipe.shape = (SplashCoord)1;
+	    drawAAPixel(&pipe, tx + x2, ty + y2);
+	  } else {
+	    drawPixel(&pipe, tx + x2, ty + y2,
+		      clipRes2 == splashClipAllInside);
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  p = colorBuf + xSrc * 4;
+	  pixAcc0 = pixAcc1 = pixAcc2 = pixAcc3 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      pixAcc1 += *p++;
+	      pixAcc2 += *p++;
+	      pixAcc3 += *p++;
+	    }
+	    p += 4 * (w - m);
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+
+	  pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+	  pix[1] = (int)((SplashCoord)pixAcc1 * pixMul);
+	  pix[2] = (int)((SplashCoord)pixAcc2 * pixMul);
+	  pix[3] = (int)((SplashCoord)pixAcc3 * pixMul);
+
+	  // set pixel
+	  if (vectorAntialias && clipRes != splashClipAllInside) {
+	    pipe.shape = (SplashCoord)1;
+	    drawAAPixel(&pipe, tx + x2, ty + y2);
+	  } else {
+	    drawPixel(&pipe, tx + x2, ty + y2,
+		      clipRes2 == splashClipAllInside);
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+#endif // SPLASH_CMYK
+      }
+    }
+
+  }
+
+  gfree(colorBuf);
+  gfree(alphaBuf);
+
+  return splashOk;
+}
+
+SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
+			      int xDest, int yDest, int w, int h,
+			      GBool noClip, GBool nonIsolated) {
+  SplashPipe pipe;
+  SplashColor pixel;
+  Guchar alpha;
+  Guchar *ap;
+  int x, y;
+
+  if (src->mode != bitmap->mode) {
+    return splashErrModeMismatch;
+  }
+
+  if (src->alpha) {
+    pipeInit(&pipe, xDest, yDest, NULL, pixel, state->fillAlpha,
+	     gTrue, nonIsolated);
+    for (y = 0; y < h; ++y) {
+      pipeSetXY(&pipe, xDest, yDest + y);
+      ap = src->getAlphaPtr() + (ySrc + y) * src->getWidth() + xSrc;
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	alpha = *ap++;
+	if (noClip || state->clip->test(xDest + x, yDest + y)) {
+	  // this uses shape instead of alpha, which isn't technically
+	  // correct, but works out the same
+	  pipe.shape = (SplashCoord)(alpha / 255.0);
+	  pipeRun(&pipe);
+	  updateModX(xDest + x);
+	  updateModY(yDest + y);
+	} else {
+	  pipeIncX(&pipe);
+	}
+      }
+    }
+  } else {
+    pipeInit(&pipe, xDest, yDest, NULL, pixel, state->fillAlpha,
+	     gFalse, nonIsolated);
+    for (y = 0; y < h; ++y) {
+      pipeSetXY(&pipe, xDest, yDest + y);
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	if (noClip || state->clip->test(xDest + x, yDest + y)) {
+	  pipeRun(&pipe);
+	  updateModX(xDest + x);
+	  updateModY(yDest + y);
+	} else {
+	  pipeIncX(&pipe);
+	}
+      }
+    }
+  }
+
+  return splashOk;
+}
+
+void Splash::compositeBackground(SplashColorPtr color) {
+  SplashColorPtr p;
+  Guchar *q;
+  Guchar alpha, alpha1, c, color0, color1, color2, color3;
+  int x, y, mask;
+
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    color0 = color[0];
+    for (y = 0; y < bitmap->height; ++y) {
+      p = &bitmap->data[y * bitmap->rowSize];
+      q = &bitmap->alpha[y * bitmap->width];
+      mask = 0x80;
+      for (x = 0; x < bitmap->width; ++x) {
+	alpha = *q++;
+	alpha1 = 255 - alpha;
+	c = (*p & mask) ? 0xff : 0x00;
+	c = div255(alpha1 * color0 + alpha * c);
+	if (c & 0x80) {
+	  *p |= mask;
+	} else {
+	  *p &= ~mask;
+	}
+	if (!(mask >>= 1)) {
+	  mask = 0x80;
+	  ++p;
+	}
+      }
+    }
+    break;
+  case splashModeMono8:
+    color0 = color[0];
+    for (y = 0; y < bitmap->height; ++y) {
+      p = &bitmap->data[y * bitmap->rowSize];
+      q = &bitmap->alpha[y * bitmap->width];
+      for (x = 0; x < bitmap->width; ++x) {
+	alpha = *q++;
+	alpha1 = 255 - alpha;
+	p[0] = div255(alpha1 * color0 + alpha * p[0]);
+	++p;
+      }
+    }
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    color0 = color[0];
+    color1 = color[1];
+    color2 = color[2];
+    for (y = 0; y < bitmap->height; ++y) {
+      p = &bitmap->data[y * bitmap->rowSize];
+      q = &bitmap->alpha[y * bitmap->width];
+      for (x = 0; x < bitmap->width; ++x) {
+	alpha = *q++;
+	alpha1 = 255 - alpha;
+	p[0] = div255(alpha1 * color0 + alpha * p[0]);
+	p[1] = div255(alpha1 * color1 + alpha * p[1]);
+	p[2] = div255(alpha1 * color2 + alpha * p[2]);
+	p += 3;
+      }
+    }
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    color0 = color[0];
+    color1 = color[1];
+    color2 = color[2];
+    color3 = color[3];
+    for (y = 0; y < bitmap->height; ++y) {
+      p = &bitmap->data[y * bitmap->rowSize];
+      q = &bitmap->alpha[y * bitmap->width];
+      for (x = 0; x < bitmap->width; ++x) {
+	alpha = *q++;
+	alpha1 = 255 - alpha;
+	p[0] = div255(alpha1 * color0 + alpha * p[0]);
+	p[1] = div255(alpha1 * color1 + alpha * p[1]);
+	p[2] = div255(alpha1 * color2 + alpha * p[2]);
+	p[3] = div255(alpha1 * color3 + alpha * p[3]);
+	p += 4;
+      }
+    }
+    break;
+#endif
+  }
+  memset(bitmap->alpha, 255, bitmap->width * bitmap->height);
+}
+
+SplashError Splash::blitTransparent(SplashBitmap *src, int xSrc, int ySrc,
+				    int xDest, int yDest, int w, int h) {
+  SplashColor pixel;
+  SplashColorPtr p;
+  Guchar *q;
+  int x, y, mask;
+
+  if (src->mode != bitmap->mode) {
+    return splashErrModeMismatch;
+  }
+
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    for (y = 0; y < h; ++y) {
+      p = &bitmap->data[(yDest + y) * bitmap->rowSize + (xDest >> 3)];
+      mask = 0x80 >> (xDest & 7);
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	if (pixel[0]) {
+	  *p |= mask;
+	} else {
+	  *p &= ~mask;
+	}
+	if (!(mask >>= 1)) {
+	  mask = 0x80;
+	  ++p;
+	}
+      }
+    }
+    break;
+  case splashModeMono8:
+    for (y = 0; y < h; ++y) {
+      p = &bitmap->data[(yDest + y) * bitmap->rowSize + xDest];
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	*p++ = pixel[0];
+      }
+    }
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    for (y = 0; y < h; ++y) {
+      p = &bitmap->data[(yDest + y) * bitmap->rowSize + 3 * xDest];
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	*p++ = pixel[0];
+	*p++ = pixel[1];
+	*p++ = pixel[2];
+      }
+    }
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    for (y = 0; y < h; ++y) {
+      p = &bitmap->data[(yDest + y) * bitmap->rowSize + 4 * xDest];
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	*p++ = pixel[0];
+	*p++ = pixel[1];
+	*p++ = pixel[2];
+	*p++ = pixel[3];
+      }
+    }
+    break;
+#endif
+  }
+
+  if (bitmap->alpha) {
+    for (y = 0; y < h; ++y) {
+      q = &bitmap->alpha[(yDest + y) * bitmap->width + xDest];
+      for (x = 0; x < w; ++x) {
+	*q++ = 0x00;
+      }
+    }
+  }
+
+  return splashOk;
+}
+
+SplashPath *Splash::makeStrokePath(SplashPath *path, GBool flatten) {
+  SplashPath *pathIn, *pathOut;
+  SplashCoord w, d, dx, dy, wdx, wdy, dxNext, dyNext, wdxNext, wdyNext;
+  SplashCoord crossprod, dotprod, miter, m;
+  GBool first, last, closed;
+  int subpathStart, next, i;
+  int left0, left1, left2, right0, right1, right2, join0, join1, join2;
+  int leftFirst, rightFirst, firstPt;
+
+  if (flatten) {
+    pathIn = flattenPath(path, state->matrix, state->flatness);
+    if (state->lineDashLength > 0) {
+      pathOut = makeDashedPath(pathIn);
+      delete pathIn;
+      pathIn = pathOut;
+    }
+  } else {
+    pathIn = path;
+  }
+
+  subpathStart = 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;
+    }
+    if ((first = pathIn->flags[i] & splashPathFirst)) {
+      subpathStart = i;
+      closed = pathIn->flags[i] & splashPathClosed;
+    }
+    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;
+    } 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);
+    }
+    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) {
+      firstPt = pathOut->length - 1;
+    }
+    if (first && !closed) {
+      switch (state->lineCap) {
+      case splashLineCapButt:
+	pathOut->lineTo(pathIn->pts[i].x + wdy, pathIn->pts[i].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);
+	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);
+	break;
+      }
+    } else {
+      pathOut->lineTo(pathIn->pts[i].x + wdy, pathIn->pts[i].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);
+
+    // 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);
+	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);
+	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);
+	break;
+      }
+    } else {
+      pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx);
+    }
+
+    // draw the right side of the segment rectangle
+    right2 = pathOut->length - 1;
+    pathOut->close();
+
+    // draw the join
+    join2 = pathOut->length;
+    if (!last || closed) {
+      crossprod = dx * dyNext - dy * dxNext;
+      dotprod = -(dx * dxNext + dy * dyNext);
+      if (dotprod > 0.99999) {
+	// 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)
+	miter = (state->miterLimit + 1) * (state->miterLimit + 1);
+	m = 0;
+      } else {
+	miter = (SplashCoord)2 / ((SplashCoord)1 - dotprod);
+	if (miter < 1) {
+	  // this can happen because of floating point inaccuracies
+	  miter = 1;
+	}
+	m = splashSqrt(miter - 1);
+      }
+
+      // 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);
+
+      } else {
+	pathOut->moveTo(pathIn->pts[i+1].x, pathIn->pts[i+1].y);
+
+	// angle < 180
+	if (crossprod < 0) {
+	  pathOut->lineTo(pathIn->pts[i+1].x - wdyNext,
+			  pathIn->pts[i+1].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);
+	  // bevel join or miter join outside limit
+	  } else {
+	    pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx);
+	  }
+
+	// angle >= 180
+	} else {
+	  pathOut->lineTo(pathIn->pts[i+1].x + wdy,
+			  pathIn->pts[i+1].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);
+	  // bevel join or miter join outside limit
+	  } else {
+	    pathOut->lineTo(pathIn->pts[i+1].x + wdyNext,
+			    pathIn->pts[i+1].y - wdxNext);
+	  }
+	}
+      }
+
+      pathOut->close();
+    }
+
+    // add stroke adjustment hints
+    if (state->strokeAdjust) {
+      if (i >= subpathStart + 1) {
+	if (i >= subpathStart + 2) {
+	  pathOut->addStrokeAdjustHint(left1, right1, left0 + 1, right0);
+	  pathOut->addStrokeAdjustHint(left1, right1, join0, left2);
+	} else {
+	  pathOut->addStrokeAdjustHint(left1, right1, firstPt, left2);
+	}
+	pathOut->addStrokeAdjustHint(left1, right1, right2 + 1, right2 + 1);
+      }
+      left0 = left1;
+      left1 = left2;
+      right0 = right1;
+      right1 = right2;
+      join0 = join1;
+      join1 = join2;
+      if (i == subpathStart) {
+	leftFirst = left2;
+	rightFirst = right2;
+      }
+      if (last) {
+	if (i >= subpathStart + 2) {
+	  pathOut->addStrokeAdjustHint(left1, right1, left0 + 1, right0);
+	  pathOut->addStrokeAdjustHint(left1, right1,
+				       join0, pathOut->length - 1);
+	} else {
+	  pathOut->addStrokeAdjustHint(left1, right1,
+				       firstPt, pathOut->length - 1);
+	}
+	if (closed) {
+	  pathOut->addStrokeAdjustHint(left1, right1, firstPt, leftFirst);
+	  pathOut->addStrokeAdjustHint(left1, right1,
+				       rightFirst + 1, rightFirst + 1);
+	  pathOut->addStrokeAdjustHint(leftFirst, rightFirst,
+				       left1 + 1, right1);
+	  pathOut->addStrokeAdjustHint(leftFirst, rightFirst,
+				       join1, pathOut->length - 1);
+	}
+      }
+    }
+  }
+
+  if (pathIn != path) {
+    delete pathIn;
+  }
+
+  return pathOut;
+}
+
+void Splash::dumpPath(SplashPath *path) {
+  int i;
+
+  for (i = 0; i < path->length; ++i) {
+    printf("  %3d: x=%8.2f y=%8.2f%s%s%s%s\n",
+	   i, (double)path->pts[i].x, (double)path->pts[i].y,
+	   (path->flags[i] & splashPathFirst) ? " first" : "",
+	   (path->flags[i] & splashPathLast) ? " last" : "",
+	   (path->flags[i] & splashPathClosed) ? " closed" : "",
+	   (path->flags[i] & splashPathCurve) ? " curve" : "");
+  }
+}
+
+void Splash::dumpXPath(SplashXPath *path) {
+  int i;
+
+  for (i = 0; i < path->length; ++i) {
+    printf("  %4d: x0=%8.2f y0=%8.2f x1=%8.2f y1=%8.2f %s%s%s%s%s%s%s\n",
+	   i, (double)path->segs[i].x0, (double)path->segs[i].y0,
+	   (double)path->segs[i].x1, (double)path->segs[i].y1,
+	   (path->segs[i].flags	& splashXPathFirst) ? "F" : " ",
+	   (path->segs[i].flags	& splashXPathLast) ? "L" : " ",
+	   (path->segs[i].flags	& splashXPathEnd0) ? "0" : " ",
+	   (path->segs[i].flags	& splashXPathEnd1) ? "1" : " ",
+	   (path->segs[i].flags	& splashXPathHoriz) ? "H" : " ",
+	   (path->segs[i].flags	& splashXPathVert) ? "V" : " ",
+	   (path->segs[i].flags	& splashXPathFlip) ? "P" : " ");
+  }
+}
diff -Naur xpdf.orig/splash/SplashBitmap.cc xpdf/splash/SplashBitmap.cc
--- xpdf.orig/splash/SplashBitmap.cc	2010-03-22 23:55:22.000000000 +0900
+++ xpdf/splash/SplashBitmap.cc	2010-03-23 00:14:18.000000000 +0900
@@ -11,6 +11,7 @@
 #endif
 
 #include <stdio.h>
+#include <limits.h>
 #include "gmem.h"
 #include "SplashErrorCodes.h"
 #include "SplashBitmap.h"
@@ -27,30 +28,48 @@
   mode = modeA;
   switch (mode) {
   case splashModeMono1:
-    rowSize = (width + 7) >> 3;
+    if (width > 0) {
+      rowSize = (width + 7) >> 3;
+    } else {
+      rowSize = -1;
+    }
     break;
   case splashModeMono8:
-    rowSize = width;
+    if (width > 0) {
+      rowSize = width;
+    } else {
+      rowSize = -1;
+    }
     break;
   case splashModeRGB8:
   case splashModeBGR8:
-    rowSize = width * 3;
+    if (width > 0 && width <= INT_MAX / 3) {
+      rowSize = width * 3;
+    } else {
+      rowSize = -1;
+    }
     break;
 #if SPLASH_CMYK
   case splashModeCMYK8:
-    rowSize = width * 4;
+    if (width > 0 && width <= INT_MAX / 4) {
+      rowSize = width * 4;
+    } else {
+      rowSize = -1;
+    }
     break;
 #endif
   }
-  rowSize += rowPad - 1;
-  rowSize -= rowSize % rowPad;
-  data = (SplashColorPtr)gmalloc(rowSize * height);
+  if (rowSize > 0) {
+    rowSize += rowPad - 1;
+    rowSize -= rowSize % rowPad;
+  }
+  data = (SplashColorPtr)gmallocn(height, rowSize);
   if (!topDown) {
     data += (height - 1) * rowSize;
     rowSize = -rowSize;
   }
   if (alphaA) {
-    alpha = (Guchar *)gmalloc(width * height);
+    alpha = (Guchar *)gmallocn(width, height);
   } else {
     alpha = NULL;
   }
diff -Naur xpdf.orig/splash/SplashErrorCodes.h xpdf/splash/SplashErrorCodes.h
--- xpdf.orig/splash/SplashErrorCodes.h	2010-03-22 23:55:22.000000000 +0900
+++ xpdf/splash/SplashErrorCodes.h	2010-03-23 00:14:18.000000000 +0900
@@ -31,4 +31,6 @@
 
 #define splashErrZeroImage       9      // image of 0x0
 
+#define splashErrBadArg          9	// bad argument
+
 #endif
diff -Naur xpdf.orig/splash/SplashErrorCodes.h.orig xpdf/splash/SplashErrorCodes.h.orig
--- xpdf.orig/splash/SplashErrorCodes.h.orig	1970-01-01 09:00:00.000000000 +0900
+++ xpdf/splash/SplashErrorCodes.h.orig	2010-03-23 00:14:09.000000000 +0900
@@ -0,0 +1,34 @@
+//========================================================================
+//
+// SplashErrorCodes.h
+//
+//========================================================================
+
+#ifndef SPLASHERRORCODES_H
+#define SPLASHERRORCODES_H
+
+#include <aconf.h>
+
+//------------------------------------------------------------------------
+
+#define splashOk                 0	// no error
+
+#define splashErrNoCurPt         1	// no current point
+
+#define splashErrEmptyPath       2	// zero points in path
+
+#define splashErrBogusPath       3	// only one point in subpath
+
+#define splashErrNoSave	         4	// state stack is empty
+
+#define splashErrOpenFile        5	// couldn't open file
+
+#define splashErrNoGlyph         6	// couldn't get the requested glyph
+
+#define splashErrModeMismatch    7	// invalid combination of color modes
+
+#define splashErrSingularMatrix  8	// matrix is singular
+
+#define splashErrZeroImage       9      // image of 0x0
+
+#endif
diff -Naur xpdf.orig/xpdf/PSOutputDev.cc xpdf/xpdf/PSOutputDev.cc
--- xpdf.orig/xpdf/PSOutputDev.cc	2010-03-22 23:55:22.000000000 +0900
+++ xpdf/xpdf/PSOutputDev.cc	2010-03-23 00:14:18.000000000 +0900
@@ -4386,7 +4386,7 @@
 	     width, -height, height);
 
   // allocate a line buffer
-  lineBuf = (Guchar *)gmalloc(4 * width);
+  lineBuf = (Guchar *)gmallocn(width, 4);
 
   // set up to process the data stream
   imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
diff -Naur xpdf.orig/xpdf/PSOutputDev.cc.orig xpdf/xpdf/PSOutputDev.cc.orig
--- xpdf.orig/xpdf/PSOutputDev.cc.orig	1970-01-01 09:00:00.000000000 +0900
+++ xpdf/xpdf/PSOutputDev.cc.orig	2010-03-23 00:14:09.000000000 +0900
@@ -0,0 +1,6307 @@
+//========================================================================
+//
+// PSOutputDev.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+#include <locale.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <math.h>
+#include "GString.h"
+#include "GList.h"
+#include "config.h"
+#include "GlobalParams.h"
+#include "Object.h"
+#include "Error.h"
+#include "Function.h"
+#include "Gfx.h"
+#include "GfxState.h"
+#include "GfxFont.h"
+#include "UnicodeMap.h"
+#include "FoFiType1C.h"
+#include "FoFiTrueType.h"
+#include "Catalog.h"
+#include "Page.h"
+#include "Stream.h"
+#include "Annot.h"
+#include "XRef.h"
+#include "PreScanOutputDev.h"
+#if HAVE_SPLASH
+#  include "Splash.h"
+#  include "SplashBitmap.h"
+#  include "SplashOutputDev.h"
+#endif
+#include "PSOutputDev.h"
+
+#ifdef MACOS
+// needed for setting type/creator of MacOS files
+#include "ICSupport.h"
+#endif
+
+// the MSVC math.h doesn't define this
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+//------------------------------------------------------------------------
+
+// Resolution at which pages with transparency will be rasterized.
+#define splashDPI 300
+
+//------------------------------------------------------------------------
+// PostScript prolog and setup
+//------------------------------------------------------------------------
+
+// The '~' escapes mark prolog code that is emitted only in certain
+// levels:
+//
+//   ~[123][sn]
+//      ^   ^----- s=psLevel*Sep, n=psLevel*
+//      +----- 1=psLevel1*, 2=psLevel2*, 3=psLevel3*
+
+static char *prolog[] = {
+  "/xpdf 75 dict def xpdf begin",
+  "% PDF special state",
+  "/pdfDictSize 15 def",
+  "~1sn",
+  "/pdfStates 64 array def",
+  "  0 1 63 {",
+  "    pdfStates exch pdfDictSize dict",
+  "    dup /pdfStateIdx 3 index put",
+  "    put",
+  "  } for",
+  "~123sn",
+  "/pdfSetup {",
+  "  3 1 roll 2 array astore",
+  "  /setpagedevice where {",
+  "    pop 3 dict begin",
+  "      /PageSize exch def",
+  "      /ImagingBBox null def",
+  "      /Policies 1 dict dup begin /PageSize 3 def end def",
+  "      { /Duplex true def } if",
+  "    currentdict end setpagedevice",
+  "  } {",
+  "    pop pop",
+  "  } ifelse",
+  "} def",
+  "~1sn",
+  "/pdfOpNames [",
+  "  /pdfFill /pdfStroke /pdfLastFill /pdfLastStroke",
+  "  /pdfTextMat /pdfFontSize /pdfCharSpacing /pdfTextRender",
+  "  /pdfTextRise /pdfWordSpacing /pdfHorizScaling /pdfTextClipPath",
+  "] def",
+  "~123sn",
+  "/pdfStartPage {",
+  "~1sn",
+  "  pdfStates 0 get begin",
+  "~23sn",
+  "  pdfDictSize dict begin",
+  "~23n",
+  "  /pdfFillCS [] def",
+  "  /pdfFillXform {} def",
+  "  /pdfStrokeCS [] def",
+  "  /pdfStrokeXform {} def",
+  "~1n",
+  "  /pdfFill 0 def",
+  "  /pdfStroke 0 def",
+  "~1s",
+  "  /pdfFill [0 0 0 1] def",
+  "  /pdfStroke [0 0 0 1] def",
+  "~23sn",
+  "  /pdfFill [0] def",
+  "  /pdfStroke [0] def",
+  "  /pdfFillOP false def",
+  "  /pdfStrokeOP false def",
+  "~123sn",
+  "  /pdfLastFill false def",
+  "  /pdfLastStroke false def",
+  "  /pdfTextMat [1 0 0 1 0 0] def",
+  "  /pdfFontSize 0 def",
+  "  /pdfCharSpacing 0 def",
+  "  /pdfTextRender 0 def",
+  "  /pdfTextRise 0 def",
+  "  /pdfWordSpacing 0 def",
+  "  /pdfHorizScaling 1 def",
+  "  /pdfTextClipPath [] def",
+  "} def",
+  "/pdfEndPage { end } def",
+  "~23s",
+  "% separation convention operators",
+  "/findcmykcustomcolor where {",
+  "  pop",
+  "}{",
+  "  /findcmykcustomcolor { 5 array astore } def",
+  "} ifelse",
+  "/setcustomcolor where {",
+  "  pop",
+  "}{",
+  "  /setcustomcolor {",
+  "    exch",
+  "    [ exch /Separation exch dup 4 get exch /DeviceCMYK exch",
+  "      0 4 getinterval cvx",
+  "      [ exch /dup load exch { mul exch dup } /forall load",
+  "        /pop load dup ] cvx",
+  "    ] setcolorspace setcolor",
+  "  } def",
+  "} ifelse",
+  "/customcolorimage where {",
+  "  pop",
+  "}{",
+  "  /customcolorimage {",
+  "    gsave",
+  "    [ exch /Separation exch dup 4 get exch /DeviceCMYK exch",
+  "      0 4 getinterval",
+  "      [ exch /dup load exch { mul exch dup } /forall load",
+  "        /pop load dup ] cvx",
+  "    ] setcolorspace",
+  "    10 dict begin",
+  "      /ImageType 1 def",
+  "      /DataSource exch def",
+  "      /ImageMatrix exch def",
+  "      /BitsPerComponent exch def",
+  "      /Height exch def",
+  "      /Width exch def",
+  "      /Decode [1 0] def",
+  "    currentdict end",
+  "    image",
+  "    grestore",
+  "  } def",
+  "} ifelse",
+  "~123sn",
+  "% PDF color state",
+  "~1n",
+  "/g { dup /pdfFill exch def setgray",
+  "     /pdfLastFill true def /pdfLastStroke false def } def",
+  "/G { dup /pdfStroke exch def setgray",
+  "     /pdfLastStroke true def /pdfLastFill false def } def",
+  "/fCol {",
+  "  pdfLastFill not {",
+  "    pdfFill setgray",
+  "    /pdfLastFill true def /pdfLastStroke false def",
+  "  } if",
+  "} def",
+  "/sCol {",
+  "  pdfLastStroke not {",
+  "    pdfStroke setgray",
+  "    /pdfLastStroke true def /pdfLastFill false def",
+  "  } if",
+  "} def",
+  "~1s",
+  "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor",
+  "     /pdfLastFill true def /pdfLastStroke false def } def",
+  "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor",
+  "     /pdfLastStroke true def /pdfLastFill false def } def",
+  "/fCol {",
+  "  pdfLastFill not {",
+  "    pdfFill aload pop setcmykcolor",
+  "    /pdfLastFill true def /pdfLastStroke false def",
+  "  } if",
+  "} def",
+  "/sCol {",
+  "  pdfLastStroke not {",
+  "    pdfStroke aload pop setcmykcolor",
+  "    /pdfLastStroke true def /pdfLastFill false def",
+  "  } if",
+  "} def",
+  "~23n",
+  "/cs { /pdfFillXform exch def dup /pdfFillCS exch def",
+  "      setcolorspace } def",
+  "/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def",
+  "      setcolorspace } def",
+  "/sc { pdfLastFill not { pdfFillCS setcolorspace } if",
+  "      dup /pdfFill exch def aload pop pdfFillXform setcolor",
+  "     /pdfLastFill true def /pdfLastStroke false def } def",
+  "/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if",
+  "      dup /pdfStroke exch def aload pop pdfStrokeXform setcolor",
+  "     /pdfLastStroke true def /pdfLastFill false def } def",
+  "/op { /pdfFillOP exch def",
+  "      pdfLastFill { pdfFillOP setoverprint } if } def",
+  "/OP { /pdfStrokeOP exch def",
+  "      pdfLastStroke { pdfStrokeOP setoverprint } if } def",
+  "/fCol {",
+  "  pdfLastFill not {",
+  "    pdfFillCS setcolorspace",
+  "    pdfFill aload pop pdfFillXform setcolor",
+  "    pdfFillOP setoverprint",
+  "    /pdfLastFill true def /pdfLastStroke false def",
+  "  } if",
+  "} def",
+  "/sCol {",
+  "  pdfLastStroke not {",
+  "    pdfStrokeCS setcolorspace",
+  "    pdfStroke aload pop pdfStrokeXform setcolor",
+  "    pdfStrokeOP setoverprint",
+  "    /pdfLastStroke true def /pdfLastFill false def",
+  "  } if",
+  "} def",
+  "~23s",
+  "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor",
+  "     /pdfLastFill true def /pdfLastStroke false def } def",
+  "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor",
+  "     /pdfLastStroke true def /pdfLastFill false def } def",
+  "/ck { 6 copy 6 array astore /pdfFill exch def",
+  "      findcmykcustomcolor exch setcustomcolor",
+  "      /pdfLastFill true def /pdfLastStroke false def } def",
+  "/CK { 6 copy 6 array astore /pdfStroke exch def",
+  "      findcmykcustomcolor exch setcustomcolor",
+  "      /pdfLastStroke true def /pdfLastFill false def } def",
+  "/op { /pdfFillOP exch def",
+  "      pdfLastFill { pdfFillOP setoverprint } if } def",
+  "/OP { /pdfStrokeOP exch def",
+  "      pdfLastStroke { pdfStrokeOP setoverprint } if } def",
+  "/fCol {",
+  "  pdfLastFill not {",
+  "    pdfFill aload length 4 eq {",
+  "      setcmykcolor",
+  "    }{",
+  "      findcmykcustomcolor exch setcustomcolor",
+  "    } ifelse",
+  "    pdfFillOP setoverprint",
+  "    /pdfLastFill true def /pdfLastStroke false def",
+  "  } if",
+  "} def",
+  "/sCol {",
+  "  pdfLastStroke not {",
+  "    pdfStroke aload length 4 eq {",
+  "      setcmykcolor",
+  "    }{",
+  "      findcmykcustomcolor exch setcustomcolor",
+  "    } ifelse",
+  "    pdfStrokeOP setoverprint",
+  "    /pdfLastStroke true def /pdfLastFill false def",
+  "  } if",
+  "} def",
+  "~123sn",
+  "% build a font",
+  "/pdfMakeFont {",
+  "  4 3 roll findfont",
+  "  4 2 roll matrix scale makefont",
+  "  dup length dict begin",
+  "    { 1 index /FID ne { def } { pop pop } ifelse } forall",
+  "    /Encoding exch def",
+  "    currentdict",
+  "  end",
+  "  definefont pop",
+  "} def",
+  "/pdfMakeFont16 {",
+  "  exch findfont",
+  "  dup length dict begin",
+  "    { 1 index /FID ne { def } { pop pop } ifelse } forall",
+  "    /WMode exch def",
+  "    currentdict",
+  "  end",
+  "  definefont pop",
+  "} def",
+  "~3sn",
+  "/pdfMakeFont16L3 {",
+  "  1 index /CIDFont resourcestatus {",
+  "    pop pop 1 index /CIDFont findresource /CIDFontType known",
+  "  } {",
+  "    false",
+  "  } ifelse",
+  "  {",
+  "    0 eq { /Identity-H } { /Identity-V } ifelse",
+  "    exch 1 array astore composefont pop",
+  "  } {",
+  "    pdfMakeFont16",
+  "  } ifelse",
+  "} def",
+  "~123sn",
+  "% graphics state operators",
+  "~1sn",
+  "/q {",
+  "  gsave",
+  "  pdfOpNames length 1 sub -1 0 { pdfOpNames exch get load } for",
+  "  pdfStates pdfStateIdx 1 add get begin",
+  "  pdfOpNames { exch def } forall",
+  "} def",
+  "/Q { end grestore } def",
+  "~23sn",
+  "/q { gsave pdfDictSize dict begin } def",
+  "/Q {",
+  "  end grestore",
+  "  /pdfLastFill where {",
+  "    pop",
+  "    pdfLastFill {",
+  "      pdfFillOP setoverprint",
+  "    } {",
+  "      pdfStrokeOP setoverprint",
+  "    } ifelse",
+  "  } if",
+  "} def",
+  "~123sn",
+  "/cm { concat } def",
+  "/d { setdash } def",
+  "/i { setflat } def",
+  "/j { setlinejoin } def",
+  "/J { setlinecap } def",
+  "/M { setmiterlimit } def",
+  "/w { setlinewidth } def",
+  "% path segment operators",
+  "/m { moveto } def",
+  "/l { lineto } def",
+  "/c { curveto } def",
+  "/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto",
+  "      neg 0 rlineto closepath } def",
+  "/h { closepath } def",
+  "% path painting operators",
+  "/S { sCol stroke } def",
+  "/Sf { fCol stroke } def",
+  "/f { fCol fill } def",
+  "/f* { fCol eofill } def",
+  "% clipping operators",
+  "/W { clip newpath } def",
+  "/W* { eoclip newpath } def",
+  "/Ws { strokepath clip newpath } def",
+  "% text state operators",
+  "/Tc { /pdfCharSpacing exch def } def",
+  "/Tf { dup /pdfFontSize exch def",
+  "      dup pdfHorizScaling mul exch matrix scale",
+  "      pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put",
+  "      exch findfont exch makefont setfont } def",
+  "/Tr { /pdfTextRender exch def } def",
+  "/Ts { /pdfTextRise exch def } def",
+  "/Tw { /pdfWordSpacing exch def } def",
+  "/Tz { /pdfHorizScaling exch def } def",
+  "% text positioning operators",
+  "/Td { pdfTextMat transform moveto } def",
+  "/Tm { /pdfTextMat exch def } def",
+  "% text string operators",
+  "/cshow where {",
+  "  pop",
+  "  /cshow2 {",
+  "    dup {",
+  "      pop pop",
+  "      1 string dup 0 3 index put 3 index exec",
+  "    } exch cshow",
+  "    pop pop",
+  "  } def",
+  "}{",
+  "  /cshow2 {",
+  "    currentfont /FontType get 0 eq {",
+  "      0 2 2 index length 1 sub {",
+  "        2 copy get exch 1 add 2 index exch get",
+  "        2 copy exch 256 mul add",
+  "        2 string dup 0 6 5 roll put dup 1 5 4 roll put",
+  "        3 index exec",
+  "      } for",
+  "    } {",
+  "      dup {",
+  "        1 string dup 0 3 index put 3 index exec",
+  "      } forall",
+  "    } ifelse",
+  "    pop pop",
+  "  } def",
+  "} ifelse",
+  "/awcp {", // awidthcharpath
+  "  exch {",
+  "    false charpath",
+  "    5 index 5 index rmoveto",
+  "    6 index eq { 7 index 7 index rmoveto } if",
+  "  } exch cshow2",
+  "  6 {pop} repeat",
+  "} def",
+  "/Tj {",
+  "  fCol",  // because stringwidth has to draw Type 3 chars
+  "  1 index stringwidth pdfTextMat idtransform pop",
+  "  sub 1 index length dup 0 ne { div } { pop pop 0 } ifelse",
+  "  pdfWordSpacing pdfHorizScaling mul 0 pdfTextMat dtransform 32",
+  "  4 3 roll pdfCharSpacing pdfHorizScaling mul add 0",
+  "  pdfTextMat dtransform",
+  "  6 5 roll Tj1",
+  "} def",
+  "/Tj16 {",
+  "  fCol",  // because stringwidth has to draw Type 3 chars
+  "  2 index stringwidth pdfTextMat idtransform pop",
+  "  sub exch div",
+  "  pdfWordSpacing pdfHorizScaling mul 0 pdfTextMat dtransform 32",
+  "  4 3 roll pdfCharSpacing pdfHorizScaling mul add 0",
+  "  pdfTextMat dtransform",
+  "  6 5 roll Tj1",
+  "} def",
+  "/Tj16V {",
+  "  fCol",  // because stringwidth has to draw Type 3 chars
+  "  2 index stringwidth pdfTextMat idtransform exch pop",
+  "  sub exch div",
+  "  0 pdfWordSpacing pdfTextMat dtransform 32",
+  "  4 3 roll pdfCharSpacing add 0 exch",
+  "  pdfTextMat dtransform",
+  "  6 5 roll Tj1",
+  "} def",
+  "/Tj1 {",
+  "  0 pdfTextRise pdfTextMat dtransform rmoveto",
+  "  currentpoint 8 2 roll",
+  "  pdfTextRender 1 and 0 eq {",
+  "    6 copy awidthshow",
+  "  } if",
+  "  pdfTextRender 3 and dup 1 eq exch 2 eq or {",
+  "    7 index 7 index moveto",
+  "    6 copy",
+  "    currentfont /FontType get 3 eq { fCol } { sCol } ifelse",
+  "    false awcp currentpoint stroke moveto",
+  "  } if",
+  "  pdfTextRender 4 and 0 ne {",
+  "    8 6 roll moveto",
+  "    false awcp",
+  "    /pdfTextClipPath [ pdfTextClipPath aload pop",
+  "      {/moveto cvx}",
+  "      {/lineto cvx}",
+  "      {/curveto cvx}",
+  "      {/closepath cvx}",
+  "    pathforall ] def",
+  "    currentpoint newpath moveto",
+  "  } {",
+  "    8 {pop} repeat",
+  "  } ifelse",
+  "  0 pdfTextRise neg pdfTextMat dtransform rmoveto",
+  "} def",
+  "/TJm { pdfFontSize 0.001 mul mul neg 0",
+  "       pdfTextMat dtransform rmoveto } def",
+  "/TJmV { pdfFontSize 0.001 mul mul neg 0 exch",
+  "        pdfTextMat dtransform rmoveto } def",
+  "/Tclip { pdfTextClipPath cvx exec clip newpath",
+  "         /pdfTextClipPath [] def } def",
+  "~1ns",
+  "% Level 1 image operators",
+  "~1n",
+  "/pdfIm1 {",
+  "  /pdfImBuf1 4 index string def",
+  "  { currentfile pdfImBuf1 readhexstring pop } image",
+  "} def",
+  "~1s",
+  "/pdfIm1Sep {",
+  "  /pdfImBuf1 4 index string def",
+  "  /pdfImBuf2 4 index string def",
+  "  /pdfImBuf3 4 index string def",
+  "  /pdfImBuf4 4 index string def",
+  "  { currentfile pdfImBuf1 readhexstring pop }",
+  "  { currentfile pdfImBuf2 readhexstring pop }",
+  "  { currentfile pdfImBuf3 readhexstring pop }",
+  "  { currentfile pdfImBuf4 readhexstring pop }",
+  "  true 4 colorimage",
+  "} def",
+  "~1ns",
+  "/pdfImM1 {",
+  "  fCol /pdfImBuf1 4 index 7 add 8 idiv string def",
+  "  { currentfile pdfImBuf1 readhexstring pop } imagemask",
+  "} def",
+  "/pdfImM1a {",
+  "  { 2 copy get exch 1 add exch } imagemask",
+  "  pop pop",
+  "} def",
+  "~23sn",
+  "% Level 2 image operators",
+  "/pdfImBuf 100 string def",
+  "/pdfIm {",
+  "  image",
+  "  { currentfile pdfImBuf readline",
+  "    not { pop exit } if",
+  "    (%-EOD-) eq { exit } if } loop",
+  "} def",
+  "~23s",
+  "/pdfImSep {",
+  "  findcmykcustomcolor exch",
+  "  dup /Width get /pdfImBuf1 exch string def",
+  "  dup /Decode get aload pop 1 index sub /pdfImDecodeRange exch def",
+  "  /pdfImDecodeLow exch def",
+  "  begin Width Height BitsPerComponent ImageMatrix DataSource end",
+  "  /pdfImData exch def",
+  "  { pdfImData pdfImBuf1 readstring pop",
+  "    0 1 2 index length 1 sub {",
+  "      1 index exch 2 copy get",
+  "      pdfImDecodeRange mul 255 div pdfImDecodeLow add round cvi",
+  "      255 exch sub put",
+  "    } for }",
+  "  6 5 roll customcolorimage",
+  "  { currentfile pdfImBuf readline",
+  "    not { pop exit } if",
+  "    (%-EOD-) eq { exit } if } loop",
+  "} def",
+  "~23sn",
+  "/pdfImM {",
+  "  fCol imagemask",
+  "  { currentfile pdfImBuf readline",
+  "    not { pop exit } if",
+  "    (%-EOD-) eq { exit } if } loop",
+  "} def",
+  "/pr { 2 index 2 index 3 2 roll putinterval 4 add } def",
+  "/pdfImClip {",
+  "  gsave",
+  "  0 2 4 index length 1 sub {",
+  "    dup 4 index exch 2 copy",
+  "    get 5 index div put",
+  "    1 add 3 index exch 2 copy",
+  "    get 3 index div put",
+  "  } for",
+  "  pop pop rectclip",
+  "} def",
+  "/pdfImClipEnd { grestore } def",
+  "~23sn",
+  "% shading operators",
+  "/colordelta {",
+  "  false 0 1 3 index length 1 sub {",
+  "    dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {",
+  "      pop true",
+  "    } if",
+  "  } for",
+  "  exch pop exch pop",
+  "} def",
+  "/funcCol { func n array astore } def",
+  "/funcSH {",
+  "  dup 0 eq {",
+  "    true",
+  "  } {",
+  "    dup 6 eq {",
+  "      false",
+  "    } {",
+  "      4 index 4 index funcCol dup",
+  "      6 index 4 index funcCol dup",
+  "      3 1 roll colordelta 3 1 roll",
+  "      5 index 5 index funcCol dup",
+  "      3 1 roll colordelta 3 1 roll",
+  "      6 index 8 index funcCol dup",
+  "      3 1 roll colordelta 3 1 roll",
+  "      colordelta or or or",
+  "    } ifelse",
+  "  } ifelse",
+  "  {",
+  "    1 add",
+  "    4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch",
+  "    6 index 6 index 4 index 4 index 4 index funcSH",
+  "    2 index 6 index 6 index 4 index 4 index funcSH",
+  "    6 index 2 index 4 index 6 index 4 index funcSH",
+  "    5 3 roll 3 2 roll funcSH pop pop",
+  "  } {",
+  "    pop 3 index 2 index add 0.5 mul 3 index  2 index add 0.5 mul",
+  "~23n",
+  "    funcCol sc",
+  "~23s",
+  "    funcCol aload pop k",
+  "~23sn",
+  "    dup 4 index exch mat transform m",
+  "    3 index 3 index mat transform l",
+  "    1 index 3 index mat transform l",
+  "    mat transform l pop pop h f*",
+  "  } ifelse",
+  "} def",
+  "/axialCol {",
+  "  dup 0 lt {",
+  "    pop t0",
+  "  } {",
+  "    dup 1 gt {",
+  "      pop t1",
+  "    } {",
+  "      dt mul t0 add",
+  "    } ifelse",
+  "  } ifelse",
+  "  func n array astore",
+  "} def",
+  "/axialSH {",
+  "  dup 0 eq {",
+  "    true",
+  "  } {",
+  "    dup 8 eq {",
+  "      false",
+  "    } {",
+  "      2 index axialCol 2 index axialCol colordelta",
+  "    } ifelse",
+  "  } ifelse",
+  "  {",
+  "    1 add 3 1 roll 2 copy add 0.5 mul",
+  "    dup 4 3 roll exch 4 index axialSH",
+  "    exch 3 2 roll axialSH",
+  "  } {",
+  "    pop 2 copy add 0.5 mul",
+  "~23n",
+  "    axialCol sc",
+  "~23s",
+  "    axialCol aload pop k",
+  "~23sn",
+  "    exch dup dx mul x0 add exch dy mul y0 add",
+  "    3 2 roll dup dx mul x0 add exch dy mul y0 add",
+  "    dx abs dy abs ge {",
+  "      2 copy yMin sub dy mul dx div add yMin m",
+  "      yMax sub dy mul dx div add yMax l",
+  "      2 copy yMax sub dy mul dx div add yMax l",
+  "      yMin sub dy mul dx div add yMin l",
+  "      h f*",
+  "    } {",
+  "      exch 2 copy xMin sub dx mul dy div add xMin exch m",
+  "      xMax sub dx mul dy div add xMax exch l",
+  "      exch 2 copy xMax sub dx mul dy div add xMax exch l",
+  "      xMin sub dx mul dy div add xMin exch l",
+  "      h f*",
+  "    } ifelse",
+  "  } ifelse",
+  "} def",
+  "/radialCol {",
+  "  dup t0 lt {",
+  "    pop t0",
+  "  } {",
+  "    dup t1 gt {",
+  "      pop t1",
+  "    } if",
+  "  } ifelse",
+  "  func n array astore",
+  "} def",
+  "/radialSH {",
+  "  dup 0 eq {",
+  "    true",
+  "  } {",
+  "    dup 8 eq {",
+  "      false",
+  "    } {",
+  "      2 index dt mul t0 add radialCol",
+  "      2 index dt mul t0 add radialCol colordelta",
+  "    } ifelse",
+  "  } ifelse",
+  "  {",
+  "    1 add 3 1 roll 2 copy add 0.5 mul",
+  "    dup 4 3 roll exch 4 index radialSH",
+  "    exch 3 2 roll radialSH",
+  "  } {",
+  "    pop 2 copy add 0.5 mul dt mul t0 add",
+  "~23n",
+  "    radialCol sc",
+  "~23s",
+  "    radialCol aload pop k",
+  "~23sn",
+  "    encl {",
+  "      exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      0 360 arc h",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      360 0 arcn h f",
+  "    } {",
+  "      2 copy",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      a1 a2 arcn",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      a2 a1 arcn h",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      a1 a2 arc",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      a2 a1 arc h f",
+  "    } ifelse",
+  "  } ifelse",
+  "} def",
+  "~123sn",
+  "end",
+  NULL
+};
+
+static char *cmapProlog[] = {
+  "/CIDInit /ProcSet findresource begin",
+  "10 dict begin",
+  "  begincmap",
+  "  /CMapType 1 def",
+  "  /CMapName /Identity-H def",
+  "  /CIDSystemInfo 3 dict dup begin",
+  "    /Registry (Adobe) def",
+  "    /Ordering (Identity) def",
+  "    /Supplement 0 def",
+  "  end def",
+  "  1 begincodespacerange",
+  "    <0000> <ffff>",
+  "  endcodespacerange",
+  "  0 usefont",
+  "  1 begincidrange",
+  "    <0000> <ffff> 0",
+  "  endcidrange",
+  "  endcmap",
+  "  currentdict CMapName exch /CMap defineresource pop",
+  "end",
+  "10 dict begin",
+  "  begincmap",
+  "  /CMapType 1 def",
+  "  /CMapName /Identity-V def",
+  "  /CIDSystemInfo 3 dict dup begin",
+  "    /Registry (Adobe) def",
+  "    /Ordering (Identity) def",
+  "    /Supplement 0 def",
+  "  end def",
+  "  /WMode 1 def",
+  "  1 begincodespacerange",
+  "    <0000> <ffff>",
+  "  endcodespacerange",
+  "  0 usefont",
+  "  1 begincidrange",
+  "    <0000> <ffff> 0",
+  "  endcidrange",
+  "  endcmap",
+  "  currentdict CMapName exch /CMap defineresource pop",
+  "end",
+  "end",
+  NULL
+};
+
+//------------------------------------------------------------------------
+// Fonts
+//------------------------------------------------------------------------
+
+struct PSSubstFont {
+  char *psName;			// PostScript name
+  double mWidth;		// width of 'm' character
+};
+
+static char *psFonts[] = {
+  "Courier",
+  "Courier-Bold",
+  "Courier-Oblique",
+  "Courier-BoldOblique",
+  "Helvetica",
+  "Helvetica-Bold",
+  "Helvetica-Oblique",
+  "Helvetica-BoldOblique",
+  "Symbol",
+  "Times-Roman",
+  "Times-Bold",
+  "Times-Italic",
+  "Times-BoldItalic",
+  "ZapfDingbats",
+  NULL
+};
+
+static PSSubstFont psSubstFonts[] = {
+  {"Helvetica",             0.833},
+  {"Helvetica-Oblique",     0.833},
+  {"Helvetica-Bold",        0.889},
+  {"Helvetica-BoldOblique", 0.889},
+  {"Times-Roman",           0.788},
+  {"Times-Italic",          0.722},
+  {"Times-Bold",            0.833},
+  {"Times-BoldItalic",      0.778},
+  {"Courier",               0.600},
+  {"Courier-Oblique",       0.600},
+  {"Courier-Bold",          0.600},
+  {"Courier-BoldOblique",   0.600}
+};
+
+// Info for 8-bit fonts
+struct PSFont8Info {
+  Ref fontID;
+  Gushort *codeToGID;		// code-to-GID mapping for TrueType fonts
+};
+
+// Encoding info for substitute 16-bit font
+struct PSFont16Enc {
+  Ref fontID;
+  GString *enc;
+};
+
+//------------------------------------------------------------------------
+// process colors
+//------------------------------------------------------------------------
+
+#define psProcessCyan     1
+#define psProcessMagenta  2
+#define psProcessYellow   4
+#define psProcessBlack    8
+#define psProcessCMYK    15
+
+//------------------------------------------------------------------------
+// PSOutCustomColor
+//------------------------------------------------------------------------
+
+class PSOutCustomColor {
+public:
+
+  PSOutCustomColor(double cA, double mA,
+		   double yA, double kA, GString *nameA);
+  ~PSOutCustomColor();
+
+  double c, m, y, k;
+  GString *name;
+  PSOutCustomColor *next;
+};
+
+PSOutCustomColor::PSOutCustomColor(double cA, double mA,
+				   double yA, double kA, GString *nameA) {
+  c = cA;
+  m = mA;
+  y = yA;
+  k = kA;
+  name = nameA;
+  next = NULL;
+}
+
+PSOutCustomColor::~PSOutCustomColor() {
+  delete name;
+}
+
+//------------------------------------------------------------------------
+
+struct PSOutImgClipRect {
+  int x0, x1, y0, y1;
+};
+
+//------------------------------------------------------------------------
+// DeviceNRecoder
+//------------------------------------------------------------------------
+
+class DeviceNRecoder: public FilterStream {
+public:
+
+  DeviceNRecoder(Stream *strA, int widthA, int heightA,
+		 GfxImageColorMap *colorMapA);
+  virtual ~DeviceNRecoder();
+  virtual StreamKind getKind() { return strWeird; }
+  virtual void reset();
+  virtual int getChar()
+    { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx++]; }
+  virtual int lookChar()
+    { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx]; }
+  virtual GString *getPSFilter(int /*psLevel*/, char * /*indent*/) { return NULL; }
+  virtual GBool isBinary(GBool /*last*/ = gTrue) { return gTrue; }
+  virtual GBool isEncoder() { return gTrue; }
+
+private:
+
+  GBool fillBuf();
+
+  int width, height;
+  GfxImageColorMap *colorMap;
+  Function *func;
+  ImageStream *imgStr;
+  int buf[gfxColorMaxComps];
+  int pixelIdx;
+  int bufIdx;
+  int bufSize;
+};
+
+DeviceNRecoder::DeviceNRecoder(Stream *strA, int widthA, int heightA,
+			       GfxImageColorMap *colorMapA):
+    FilterStream(strA) {
+  width = widthA;
+  height = heightA;
+  colorMap = colorMapA;
+  imgStr = NULL;
+  pixelIdx = 0;
+  bufIdx = gfxColorMaxComps;
+  bufSize = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
+              getAlt()->getNComps();
+  func = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
+           getTintTransformFunc();
+}
+
+DeviceNRecoder::~DeviceNRecoder() {
+  if (imgStr) {
+    delete imgStr;
+  }
+}
+
+void DeviceNRecoder::reset() {
+  imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
+			   colorMap->getBits());
+  imgStr->reset();
+}
+
+GBool DeviceNRecoder::fillBuf() {
+  Guchar pixBuf[gfxColorMaxComps];
+  GfxColor color;
+  double x[gfxColorMaxComps], y[gfxColorMaxComps];
+  int i;
+
+  if (pixelIdx >= width * height) {
+    return gFalse;
+  }
+  imgStr->getPixel(pixBuf);
+  colorMap->getColor(pixBuf, &color);
+  for (i = 0;
+       i < ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getNComps();
+       ++i) {
+    x[i] = colToDbl(color.c[i]);
+  }
+  func->transform(x, y);
+  for (i = 0; i < bufSize; ++i) {
+    buf[i] = (int)(y[i] * 255 + 0.5);
+  }
+  bufIdx = 0;
+  ++pixelIdx;
+  return gTrue;
+}
+
+//------------------------------------------------------------------------
+// PSOutputDev
+//------------------------------------------------------------------------
+
+extern "C" {
+typedef void (*SignalFunc)(int);
+}
+
+static void outputToFile(void *stream, char *data, int len) {
+  fwrite(data, 1, len, (FILE *)stream);
+}
+
+PSOutputDev::PSOutputDev(char *fileName, char *pstitle, XRef *xrefA, Catalog *catalog,
+			 int firstPage, int lastPage, PSOutMode modeA,
+			 int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
+			 GBool forceRasterizeA,
+			 GBool manualCtrlA) {
+  FILE *f;
+  PSFileType fileTypeA;
+
+  underlayCbk = NULL;
+  underlayCbkData = NULL;
+  overlayCbk = NULL;
+  overlayCbkData = NULL;
+
+  fontIDs = NULL;
+  fontFileIDs = NULL;
+  fontFileNames = NULL;
+  font8Info = NULL;
+  font16Enc = NULL;
+  imgIDs = NULL;
+  formIDs = NULL;
+  xobjStack = NULL;
+  embFontList = NULL;
+  customColors = NULL;
+  haveTextClip = gFalse;
+  t3String = NULL;
+  
+  forceRasterize = forceRasterizeA;
+
+  // open file or pipe
+  if (!strcmp(fileName, "-")) {
+    fileTypeA = psStdout;
+    f = stdout;
+  } else if (fileName[0] == '|') {
+    fileTypeA = psPipe;
+#ifdef HAVE_POPEN
+#ifndef WIN32
+    signal(SIGPIPE, (SignalFunc)SIG_IGN);
+#endif
+    if (!(f = popen(fileName + 1, "w"))) {
+      error(-1, "Couldn't run print command '%s'", fileName);
+      ok = gFalse;
+      return;
+    }
+#else
+    error(-1, "Print commands are not supported ('%s')", fileName);
+    ok = gFalse;
+    return;
+#endif
+  } else {
+    fileTypeA = psFile;
+    if (!(f = fopen(fileName, "w"))) {
+      error(-1, "Couldn't open PostScript file '%s'", fileName);
+      ok = gFalse;
+      return;
+    }
+  }
+
+  init(outputToFile, f, fileTypeA, pstitle,
+       xrefA, catalog, firstPage, lastPage, modeA,
+       imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA);
+}
+
+void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA,
+		       PSFileType fileTypeA, char *pstitle, XRef *xrefA, Catalog *catalog,
+		       int firstPage, int lastPage, PSOutMode modeA,
+		       int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
+		       GBool manualCtrlA) {
+  Page *page;
+  PDFRectangle *box;
+
+  // initialize
+  setlocale(LC_NUMERIC,"POSIX");
+  ok = gTrue;
+  outputFunc = outputFuncA;
+  outputStream = outputStreamA;
+  fileType = fileTypeA;
+  xref = xrefA;
+  level = globalParams->getPSLevel();
+  mode = modeA;
+  paperWidth = globalParams->getPSPaperWidth();
+  paperHeight = globalParams->getPSPaperHeight();
+  imgLLX = imgLLXA;
+  imgLLY = imgLLYA;
+  imgURX = imgURXA;
+  imgURY = imgURYA;
+  if (imgLLX == 0 && imgURX == 0 && imgLLY == 0 && imgURY == 0) {
+    globalParams->getPSImageableArea(&imgLLX, &imgLLY, &imgURX, &imgURY);
+  }
+  if (paperWidth < 0 || paperHeight < 0) {
+    // this check is needed in case the document has zero pages
+    if (firstPage > 0 && firstPage <= catalog->getNumPages()) {
+      page = catalog->getPage(firstPage);
+      paperWidth = (int)ceil(page->getMediaWidth());
+      paperHeight = (int)ceil(page->getMediaHeight());
+    } else {
+      paperWidth = 1;
+      paperHeight = 1;
+    }
+    imgLLX = imgLLY = 0;
+    imgURX = paperWidth;
+    imgURY = paperHeight;
+  }
+  preload = globalParams->getPSPreload();
+  manualCtrl = manualCtrlA;
+  if (mode == psModeForm) {
+    lastPage = firstPage;
+  }
+  processColors = 0;
+  inType3Char = gFalse;
+
+#if OPI_SUPPORT
+  // initialize OPI nesting levels
+  opi13Nest = 0;
+  opi20Nest = 0;
+#endif
+
+  tx0 = ty0 = -1;
+  xScale0 = yScale0 = 0;
+  rotate0 = -1;
+  clipLLX0 = clipLLY0 = 0;
+  clipURX0 = clipURY0 = -1;
+
+  // initialize fontIDs, fontFileIDs, and fontFileNames lists
+  fontIDSize = 64;
+  fontIDLen = 0;
+  fontIDs = (Ref *)gmallocn(fontIDSize, sizeof(Ref));
+  fontFileIDSize = 64;
+  fontFileIDLen = 0;
+  fontFileIDs = (Ref *)gmallocn(fontFileIDSize, sizeof(Ref));
+  fontFileNameSize = 64;
+  fontFileNameLen = 0;
+  fontFileNames = (GString **)gmallocn(fontFileNameSize, sizeof(GString *));
+  psFileNames = (GString **)gmallocn(fontFileNameSize, sizeof(GString *));
+  nextTrueTypeNum = 0;
+  font8InfoLen = 0;
+  font8InfoSize = 0;
+  font16EncLen = 0;
+  font16EncSize = 0;
+  imgIDLen = 0;
+  imgIDSize = 0;
+  formIDLen = 0;
+  formIDSize = 0;
+
+  xobjStack = new GList();
+  numSaves = 0;
+  numTilingPatterns = 0;
+  nextFunc = 0;
+
+  // initialize embedded font resource comment list
+  embFontList = new GString();
+
+  if (!manualCtrl) {
+    // this check is needed in case the document has zero pages
+    if (firstPage > 0 && firstPage <= catalog->getNumPages()) {
+      writeHeader(firstPage, lastPage,
+		  catalog->getPage(firstPage)->getMediaBox(),
+		  catalog->getPage(firstPage)->getCropBox(),
+		  catalog->getPage(firstPage)->getRotate(), pstitle);
+    } else {
+      box = new PDFRectangle(0, 0, 1, 1);
+      writeHeader(firstPage, lastPage, box, box, 0, pstitle);
+      delete box;
+    }
+    if (mode != psModeForm) {
+      writePS("%%BeginProlog\n");
+    }
+    writeXpdfProcset();
+    if (mode != psModeForm) {
+      writePS("%%EndProlog\n");
+      writePS("%%BeginSetup\n");
+    }
+    writeDocSetup(catalog, firstPage, lastPage);
+    if (mode != psModeForm) {
+      writePS("%%EndSetup\n");
+    }
+  }
+
+  // initialize sequential page number
+  seqPage = 1;
+}
+
+PSOutputDev::~PSOutputDev() {
+  PSOutCustomColor *cc;
+  int i;
+
+  if (ok) {
+    if (!manualCtrl) {
+      writePS("%%Trailer\n");
+      writeTrailer();
+      if (mode != psModeForm) {
+	writePS("%%EOF\n");
+      }
+    }
+    if (fileType == psFile) {
+#ifdef MACOS
+      ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle);
+#endif
+      fclose((FILE *)outputStream);
+    }
+#ifdef HAVE_POPEN
+    else if (fileType == psPipe) {
+      pclose((FILE *)outputStream);
+#ifndef WIN32
+      signal(SIGPIPE, (SignalFunc)SIG_DFL);
+#endif
+    }
+#endif
+  }
+  if (embFontList) {
+    delete embFontList;
+  }
+  if (fontIDs) {
+    gfree(fontIDs);
+  }
+  if (fontFileIDs) {
+    gfree(fontFileIDs);
+  }
+  if (fontFileNames) {
+    for (i = 0; i < fontFileNameLen; ++i) {
+      delete fontFileNames[i];
+    }
+    gfree(fontFileNames);
+  }
+  if (font8Info) {
+    for (i = 0; i < font8InfoLen; ++i) {
+      gfree(font8Info[i].codeToGID);
+    }
+    gfree(font8Info);
+  }
+  if (psFileNames) {
+    for (i = 0; i < fontFileNameLen; ++i) {
+      if (psFileNames[i])
+        delete psFileNames[i];
+    }
+    gfree(psFileNames);
+  }
+  if (font16Enc) {
+    for (i = 0; i < font16EncLen; ++i) {
+      delete font16Enc[i].enc;
+    }
+    gfree(font16Enc);
+  }
+  gfree(imgIDs);
+  gfree(formIDs);
+  if (xobjStack) {
+    delete xobjStack;
+  }
+  while (customColors) {
+    cc = customColors;
+    customColors = cc->next;
+    delete cc;
+  }
+}
+
+void PSOutputDev::writeHeader(int firstPage, int lastPage,
+			      PDFRectangle *mediaBox, PDFRectangle *cropBox,
+			      int pageRotate, char *pstitle) {
+  Object info, obj1;
+  double x1, y1, x2, y2;
+
+  switch (mode) {
+  case psModePS:
+    writePS("%!PS-Adobe-3.0\n");
+    break;
+  case psModeEPS:
+    writePS("%!PS-Adobe-3.0 EPSF-3.0\n");
+    break;
+  case psModeForm:
+    writePS("%!PS-Adobe-3.0 Resource-Form\n");
+    break;
+  }
+
+  xref->getDocInfo(&info);
+  if (info.isDict() && info.dictLookup("Creator", &obj1)->isString()) {
+    writePS("%%Creator: ");
+    writePSTextLine(obj1.getString());
+  }
+  obj1.free();
+  info.free();
+  if(pstitle) {
+    writePSFmt("%%Title: {0:s}\n", pstitle);
+  }
+  writePSFmt("%%LanguageLevel: {0:d}\n",
+	     (level == psLevel1 || level == psLevel1Sep) ? 1 :
+	     (level == psLevel2 || level == psLevel2Sep) ? 2 : 3);
+  if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) {
+    writePS("%%DocumentProcessColors: (atend)\n");
+    writePS("%%DocumentCustomColors: (atend)\n");
+  }
+  writePS("%%DocumentSuppliedResources: (atend)\n");
+
+  switch (mode) {
+  case psModePS:
+    writePSFmt("%%DocumentMedia: plain {0:d} {1:d} 0 () ()\n",
+	       paperWidth, paperHeight);
+    writePSFmt("%%BoundingBox: 0 0 {0:d} {1:d}\n", paperWidth, paperHeight);
+    writePSFmt("%%Pages: {0:d}\n", lastPage - firstPage + 1);
+    writePS("%%EndComments\n");
+    writePS("%%BeginDefaults\n");
+    writePS("%%PageMedia: plain\n");
+    writePS("%%EndDefaults\n");
+    break;
+  case psModeEPS:
+    epsX1 = cropBox->x1;
+    epsY1 = cropBox->y1;
+    epsX2 = cropBox->x2;
+    epsY2 = cropBox->y2;
+    if (pageRotate == 0 || pageRotate == 180) {
+      x1 = epsX1;
+      y1 = epsY1;
+      x2 = epsX2;
+      y2 = epsY2;
+    } else { // pageRotate == 90 || pageRotate == 270
+      x1 = 0;
+      y1 = 0;
+      x2 = epsY2 - epsY1;
+      y2 = epsX2 - epsX1;
+    }
+    writePSFmt("%%BoundingBox: {0:d} {1:d} {2:d} {3:d}\n",
+	       (int)floor(x1), (int)floor(y1), (int)ceil(x2), (int)ceil(y2));
+    if (floor(x1) != ceil(x1) || floor(y1) != ceil(y1) ||
+	floor(x2) != ceil(x2) || floor(y2) != ceil(y2)) {
+      writePSFmt("%%HiResBoundingBox: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n",
+		 x1, y1, x2, y2);
+    }
+    writePS("%%EndComments\n");
+    break;
+  case psModeForm:
+    writePS("%%EndComments\n");
+    writePS("32 dict dup begin\n");
+    writePSFmt("/BBox [{0:d} {1:d} {2:d} {3:d}] def\n",
+	       (int)floor(mediaBox->x1), (int)floor(mediaBox->y1),
+	       (int)ceil(mediaBox->x2), (int)ceil(mediaBox->y2));
+    writePS("/FormType 1 def\n");
+    writePS("/Matrix [1 0 0 1 0 0] def\n");
+    break;
+  }
+}
+
+void PSOutputDev::writeXpdfProcset() {
+  GBool lev1, lev2, lev3, sep, nonSep;
+  char **p;
+  char *q;
+
+  writePSFmt("%%BeginResource: procset xpdf {0:s} 0\n", xpdfVersion);
+  writePSFmt("%%Copyright: {0:s}\n", xpdfCopyright);
+  lev1 = lev2 = lev3 = sep = nonSep = gTrue;
+  for (p = prolog; *p; ++p) {
+    if ((*p)[0] == '~') {
+      lev1 = lev2 = lev3 = sep = nonSep = gFalse;
+      for (q = *p + 1; *q; ++q) {
+	switch (*q) {
+	case '1': lev1 = gTrue; break;
+	case '2': lev2 = gTrue; break;
+	case '3': lev3 = gTrue; break;
+	case 's': sep = gTrue; break;
+	case 'n': nonSep = gTrue; break;
+	}
+      }
+    } else if ((level == psLevel1 && lev1 && nonSep) ||
+	       (level == psLevel1Sep && lev1 && sep) ||
+	       (level == psLevel2 && lev2 && nonSep) ||
+	       (level == psLevel2Sep && lev2 && sep) ||
+	       (level == psLevel3 && lev3 && nonSep) ||
+	       (level == psLevel3Sep && lev3 && sep)) {
+      writePSFmt("{0:s}\n", *p);
+    }
+  }
+  writePS("%%EndResource\n");
+
+  if (level >= psLevel3) {
+    for (p = cmapProlog; *p; ++p) {
+      writePSFmt("{0:s}\n", *p);
+    }
+  }
+}
+
+void PSOutputDev::writeDocSetup(Catalog *catalog,
+				int firstPage, int lastPage) {
+  Page *page;
+  Dict *resDict;
+  Annots *annots;
+  Object obj1, obj2;
+  int pg, i;
+
+  if (mode == psModeForm) {
+    // swap the form and xpdf dicts
+    writePS("xpdf end begin dup begin\n");
+  } else {
+    writePS("xpdf begin\n");
+  }
+  for (pg = firstPage; pg <= lastPage; ++pg) {
+    page = catalog->getPage(pg);
+    if ((resDict = page->getResourceDict())) {
+      setupResources(resDict);
+    }
+    annots = new Annots(xref, catalog, page->getAnnots(&obj1));
+    obj1.free();
+    for (i = 0; i < annots->getNumAnnots(); ++i) {
+      if (annots->getAnnot(i)->getAppearance(&obj1)->isStream()) {
+	obj1.streamGetDict()->lookup("Resources", &obj2);
+	if (obj2.isDict()) {
+	  setupResources(obj2.getDict());
+	}
+	obj2.free();
+      }
+      obj1.free();
+    }
+    delete annots;
+  }
+  if (mode != psModeForm) {
+    if (mode != psModeEPS && !manualCtrl) {
+      writePSFmt("{0:d} {1:d} {2:s} pdfSetup\n",
+		 paperWidth, paperHeight,
+		 globalParams->getPSDuplex() ? "true" : "false");
+    }
+#if OPI_SUPPORT
+    if (globalParams->getPSOPI()) {
+      writePS("/opiMatrix matrix currentmatrix def\n");
+    }
+#endif
+  }
+}
+
+void PSOutputDev::writePageTrailer() {
+  if (mode != psModeForm) {
+    writePS("pdfEndPage\n");
+  }
+}
+
+void PSOutputDev::writeTrailer() {
+  PSOutCustomColor *cc;
+
+  if (mode == psModeForm) {
+    writePS("/Foo exch /Form defineresource pop\n");
+  } else {
+    writePS("end\n");
+    writePS("%%DocumentSuppliedResources:\n");
+    writePS(embFontList->getCString());
+    if (level == psLevel1Sep || level == psLevel2Sep ||
+	level == psLevel3Sep) {
+      writePS("%%DocumentProcessColors:");
+      if (processColors & psProcessCyan) {
+	writePS(" Cyan");
+	 }
+      if (processColors & psProcessMagenta) {
+	writePS(" Magenta");
+      }
+      if (processColors & psProcessYellow) {
+	writePS(" Yellow");
+      }
+      if (processColors & psProcessBlack) {
+	writePS(" Black");
+      }
+      writePS("\n");
+      writePS("%%DocumentCustomColors:");
+      for (cc = customColors; cc; cc = cc->next) {
+	writePSFmt(" ({0:s})", cc->name->getCString());
+      }
+      writePS("\n");
+      writePS("%%CMYKCustomColor:\n");
+      for (cc = customColors; cc; cc = cc->next) {
+	writePSFmt("%%+ {0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t})\n",
+		   cc->c, cc->m, cc->y, cc->k, cc->name);
+      }
+    }
+  }
+}
+
+void PSOutputDev::setupResources(Dict *resDict) {
+  Object xObjDict, xObjRef, xObj, patDict, patRef, pat, resObj;
+  Ref ref0, ref1;
+  GBool skip;
+  int i, j;
+
+  setupFonts(resDict);
+  setupImages(resDict);
+  setupForms(resDict);
+
+  //----- recursively scan XObjects
+  resDict->lookup("XObject", &xObjDict);
+  if (xObjDict.isDict()) {
+    for (i = 0; i < xObjDict.dictGetLength(); ++i) {
+
+      // avoid infinite recursion on XObjects
+      skip = gFalse;
+      if ((xObjDict.dictGetValNF(i, &xObjRef)->isRef())) {
+	ref0 = xObjRef.getRef();
+	for (j = 0; j < xobjStack->getLength(); ++j) {
+	  ref1 = *(Ref *)xobjStack->get(j);
+	  if (ref1.num == ref0.num && ref1.gen == ref0.gen) {
+	    skip = gTrue;
+	    break;
+	  }
+	}
+	if (!skip) {
+	  xobjStack->append(&ref0);
+	}
+      }
+      if (!skip) {
+
+	// process the XObject's resource dictionary
+	xObjDict.dictGetVal(i, &xObj);
+	if (xObj.isStream()) {
+	  xObj.streamGetDict()->lookup("Resources", &resObj);
+	  if (resObj.isDict()) {
+	    setupResources(resObj.getDict());
+	  }
+	  resObj.free();
+	}
+	xObj.free();
+      }
+
+      if (xObjRef.isRef() && !skip) {
+	xobjStack->del(xobjStack->getLength() - 1);
+      }
+      xObjRef.free();
+    }
+  }
+  xObjDict.free();
+
+  //----- recursively scan Patterns
+  resDict->lookup("Pattern", &patDict);
+  if (patDict.isDict()) {
+    inType3Char = gTrue;
+    for (i = 0; i < patDict.dictGetLength(); ++i) {
+
+      // avoid infinite recursion on Patterns
+      skip = gFalse;
+      if ((patDict.dictGetValNF(i, &patRef)->isRef())) {
+	ref0 = patRef.getRef();
+	for (j = 0; j < xobjStack->getLength(); ++j) {
+	  ref1 = *(Ref *)xobjStack->get(j);
+	  if (ref1.num == ref0.num && ref1.gen == ref0.gen) {
+	    skip = gTrue;
+	    break;
+	  }
+	}
+	if (!skip) {
+	  xobjStack->append(&ref0);
+	}
+      }
+      if (!skip) {
+
+	// process the Pattern's resource dictionary
+	patDict.dictGetVal(i, &pat);
+	if (pat.isStream()) {
+	  pat.streamGetDict()->lookup("Resources", &resObj);
+	  if (resObj.isDict()) {
+	    setupResources(resObj.getDict());
+	  }
+	  resObj.free();
+	}
+	pat.free();
+      }
+
+      if (patRef.isRef() && !skip) {
+	xobjStack->del(xobjStack->getLength() - 1);
+      }
+      patRef.free();
+    }
+    inType3Char = gFalse;
+  }
+  patDict.free();
+}
+
+void PSOutputDev::setupFonts(Dict *resDict) {
+  Object obj1, obj2;
+  Ref r;
+  GfxFontDict *gfxFontDict;
+  GfxFont *font;
+  int i;
+
+  if (forceRasterize) return;
+
+  gfxFontDict = NULL;
+  resDict->lookupNF("Font", &obj1);
+  if (obj1.isRef()) {
+    obj1.fetch(xref, &obj2);
+    if (obj2.isDict()) {
+      r = obj1.getRef();
+      gfxFontDict = new GfxFontDict(xref, &r, obj2.getDict());
+    }
+    obj2.free();
+  } else if (obj1.isDict()) {
+    gfxFontDict = new GfxFontDict(xref, NULL, obj1.getDict());
+  }
+  if (gfxFontDict) {
+    for (i = 0; i < gfxFontDict->getNumFonts(); ++i) {
+      if ((font = gfxFontDict->getFont(i))) {
+	setupFont(font, resDict);
+      }
+    }
+    delete gfxFontDict;
+  }
+  obj1.free();
+}
+
+void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) {
+  Ref fontFileID;
+  GString *name;
+  PSFontParam *fontParam;
+  GString *psName;
+  char buf[16];
+  GBool subst;
+  UnicodeMap *uMap;
+  char *charName;
+  double xs, ys;
+  int code;
+  double w1, w2;
+  double *fm;
+  int i, j;
+  DisplayFontParam *dfp;
+
+  // check if font is already set up
+  for (i = 0; i < fontIDLen; ++i) {
+    if (fontIDs[i].num == font->getID()->num &&
+	fontIDs[i].gen == font->getID()->gen) {
+      return;
+    }
+  }
+
+  // add entry to fontIDs list
+  if (fontIDLen >= fontIDSize) {
+    fontIDSize += 64;
+    fontIDs = (Ref *)greallocn(fontIDs, fontIDSize, sizeof(Ref));
+  }
+  fontIDs[fontIDLen++] = *font->getID();
+
+  xs = ys = 1;
+  subst = gFalse;
+
+  // check for resident 8-bit font
+  if (font->getName() &&
+      (fontParam = globalParams->getPSFont(font->getName()))) {
+    psName = new GString(fontParam->psFontName->getCString());
+
+  // check for embedded Type 1 font
+  } else if (globalParams->getPSEmbedType1() &&
+	     font->getType() == fontType1 &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    setupEmbeddedType1Font(&fontFileID, psName);
+
+  // check for embedded Type 1C font
+  } else if (globalParams->getPSEmbedType1() &&
+	     font->getType() == fontType1C &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    // use the PDF font name because the embedded font name might
+    // not include the subset prefix
+    psName = filterPSName(font->getOrigName());
+    setupEmbeddedType1CFont(font, &fontFileID, psName);
+
+  // check for embedded OpenType - Type 1C font
+  } else if (globalParams->getPSEmbedType1() &&
+	     font->getType() == fontType1COT &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    // use the PDF font name because the embedded font name might
+    // not include the subset prefix
+    psName = filterPSName(font->getOrigName());
+    setupEmbeddedOpenTypeT1CFont(font, &fontFileID, psName);
+
+  // check for external Type 1 font file
+  } else if (globalParams->getPSEmbedType1() &&
+	     font->getType() == fontType1 &&
+	     font->getExtFontFile()) {
+    // this assumes that the PS font name matches the PDF font name
+    psName = font->getName()->copy();
+    setupExternalType1Font(font->getExtFontFile(), psName);
+
+  // check for embedded TrueType font
+  } else if (globalParams->getPSEmbedTrueType() &&
+	     (font->getType() == fontTrueType ||
+	      font->getType() == fontTrueTypeOT) &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    setupEmbeddedTrueTypeFont(font, &fontFileID, psName);
+
+  // check for external TrueType font file
+  } else if (globalParams->getPSEmbedTrueType() &&
+	     font->getType() == fontTrueType &&
+	     font->getExtFontFile()) {
+    psName = setupExternalTrueTypeFont(font);
+
+  // check for embedded CID PostScript font
+  } else if (globalParams->getPSEmbedCIDPostScript() &&
+	     font->getType() == fontCIDType0C &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    setupEmbeddedCIDType0Font(font, &fontFileID, psName);
+
+  // check for embedded CID TrueType font
+  } else if (globalParams->getPSEmbedCIDTrueType() &&
+	     (font->getType() == fontCIDType2 ||
+	      font->getType() == fontCIDType2OT) &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    //~ should check to see if font actually uses vertical mode
+    setupEmbeddedCIDTrueTypeFont(font, &fontFileID, psName, gTrue);
+
+  // check for embedded OpenType - CID CFF font
+  } else if (globalParams->getPSEmbedCIDPostScript() &&
+	     font->getType() == fontCIDType0COT &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    setupEmbeddedOpenTypeCFFFont(font, &fontFileID, psName);
+
+  // check for Type 3 font
+  } else if (font->getType() == fontType3) {
+    psName = GString::format("T3_{0:d}_{1:d}",
+			     font->getID()->num, font->getID()->gen);
+    setupType3Font(font, psName, parentResDict);
+  // check for external CID TrueType font file
+  } else if (globalParams->getPSEmbedCIDTrueType() &&
+	     font->getType() == fontCIDType2 &&
+	     font->getExtFontFile()) {
+    psName = setupExternalCIDTrueTypeFont(font, font->getExtFontFile());
+  // do 8-bit font substitution
+  } else if (!font->isCIDFont()) {
+    subst = gTrue;
+    name = font->getName();
+    psName = NULL;
+    if (name) {
+      for (i = 0; psFonts[i]; ++i) {
+	if (name->cmp(psFonts[i]) == 0) {
+	  psName = new GString(psFonts[i]);
+	  break;
+	}
+      }
+    }
+    if (!psName) {
+      if (font->isFixedWidth()) {
+	i = 8;
+      } else if (font->isSerif()) {
+	i = 4;
+      } else {
+	i = 0;
+      }
+      if (font->isBold()) {
+	i += 2;
+      }
+      if (font->isItalic()) {
+	i += 1;
+      }
+      psName = new GString(psSubstFonts[i].psName);
+      for (code = 0; code < 256; ++code) {
+	if ((charName = ((Gfx8BitFont *)font)->getCharName(code)) &&
+	    charName[0] == 'm' && charName[1] == '\0') {
+	  break;
+	}
+      }
+      if (code < 256) {
+	w1 = ((Gfx8BitFont *)font)->getWidth(code);
+      } else {
+	w1 = 0;
+      }
+      w2 = psSubstFonts[i].mWidth;
+      xs = w1 / w2;
+      if (xs < 0.1) {
+	xs = 1;
+      }
+      if (font->getType() == fontType3) {
+	// This is a hack which makes it possible to substitute for some
+	// Type 3 fonts.  The problem is that it's impossible to know what
+	// the base coordinate system used in the font is without actually
+	// rendering the font.
+	ys = xs;
+	fm = font->getFontMatrix();
+	if (fm[0] != 0) {
+	  ys *= fm[3] / fm[0];
+	}
+      } else {
+	ys = 1;
+      }
+    }
+
+  // do 16-bit font substitution
+  } else if ((fontParam = globalParams->
+	        getPSFont16(font->getName(),
+			    ((GfxCIDFont *)font)->getCollection(),
+			    font->getWMode()))) {
+    subst = gTrue;
+    psName = fontParam->psFontName->copy();
+    if (font16EncLen >= font16EncSize) {
+      font16EncSize += 16;
+      font16Enc = (PSFont16Enc *)greallocn(font16Enc,
+					   font16EncSize, sizeof(PSFont16Enc));
+    }
+    font16Enc[font16EncLen].fontID = *font->getID();
+    font16Enc[font16EncLen].enc = fontParam->encoding->copy();
+    if ((uMap = globalParams->getUnicodeMap(font16Enc[font16EncLen].enc))) {
+      uMap->decRefCnt();
+      ++font16EncLen;
+    } else {
+      error(-1, "Couldn't find Unicode map for 16-bit font encoding '%s'",
+	    font16Enc[font16EncLen].enc->getCString());
+    }
+
+    // try the display font for embedding
+  } else if (globalParams->getPSEmbedCIDTrueType() &&
+	     ((GfxCIDFont *)font)->getCollection() &&
+	     (dfp = globalParams->
+	      getDisplayCIDFont(font->getName(),
+				((GfxCIDFont *)font)->getCollection())) &&
+	     dfp->kind == displayFontTT) {
+    psName = setupExternalCIDTrueTypeFont(font, dfp->tt.fileName, dfp->tt.faceIndex);
+
+  // give up - can't do anything with this font
+  } else {
+    error(-1, "Couldn't find a font to substitute for '%s' ('%s' character collection)",
+	  font->getName() ? font->getName()->getCString() : "(unnamed)",
+	  ((GfxCIDFont *)font)->getCollection()
+	    ? ((GfxCIDFont *)font)->getCollection()->getCString()
+	    : "(unknown)");
+    return;
+  }
+
+  // generate PostScript code to set up the font
+  if (font->isCIDFont()) {
+    if (level == psLevel3 || level == psLevel3Sep) {
+      writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16L3\n",
+		 font->getID()->num, font->getID()->gen, psName,
+		 font->getWMode());
+    } else {
+      writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16\n",
+		 font->getID()->num, font->getID()->gen, psName,
+		 font->getWMode());
+    }
+  } else {
+    writePSFmt("/F{0:d}_{1:d} /{2:t} {3:.4g} {4:.4g}\n",
+	       font->getID()->num, font->getID()->gen, psName, xs, ys);
+    for (i = 0; i < 256; i += 8) {
+      writePS((char *)((i == 0) ? "[ " : "  "));
+      for (j = 0; j < 8; ++j) {
+	if (font->getType() == fontTrueType &&
+	    !subst &&
+	    !((Gfx8BitFont *)font)->getHasEncoding()) {
+	  sprintf(buf, "c%02x", i+j);
+	  charName = buf;
+	} else {
+	  charName = ((Gfx8BitFont *)font)->getCharName(i+j);
+	  // this is a kludge for broken PDF files that encode char 32
+	  // as .notdef
+	  if (i+j == 32 && charName && !strcmp(charName, ".notdef")) {
+	    charName = "space";
+	  }
+	}
+	writePS("/");
+	writePSName(charName ? charName : (char *)".notdef");
+	// the empty name is legal in PDF and PostScript, but PostScript
+	// uses a double-slash (//...) for "immediately evaluated names",
+	// so we need to add a space character here
+	if (charName && !charName[0]) {
+	  writePS(" ");
+	}
+      }
+      writePS((i == 256-8) ? (char *)"]\n" : (char *)"\n");
+    }
+    writePS("pdfMakeFont\n");
+  }
+
+  delete psName;
+}
+
+void PSOutputDev::setupEmbeddedType1Font(Ref *id, GString *psName) {
+  static char hexChar[17] = "0123456789abcdef";
+  Object refObj, strObj, obj1, obj2, obj3;
+  Dict *dict;
+  int length1, length2, length3;
+  int c;
+  int start[4];
+  GBool binMode;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // get the font stream and info
+  refObj.initRef(id->num, id->gen);
+  refObj.fetch(xref, &strObj);
+  refObj.free();
+  if (!strObj.isStream()) {
+    error(-1, "Embedded font file object is not a stream");
+    goto err1;
+  }
+  if (!(dict = strObj.streamGetDict())) {
+    error(-1, "Embedded font stream is missing its dictionary");
+    goto err1;
+  }
+  dict->lookup("Length1", &obj1);
+  dict->lookup("Length2", &obj2);
+  dict->lookup("Length3", &obj3);
+  if (!obj1.isInt() || !obj2.isInt() || !obj3.isInt()) {
+    error(-1, "Missing length fields in embedded font stream dictionary");
+    obj1.free();
+    obj2.free();
+    obj3.free();
+    goto err1;
+  }
+  length1 = obj1.getInt();
+  length2 = obj2.getInt();
+  length3 = obj3.getInt();
+  obj1.free();
+  obj2.free();
+  obj3.free();
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // copy ASCII portion of font
+  strObj.streamReset();
+  for (i = 0; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i) {
+    writePSChar(c);
+  }
+
+  // figure out if encrypted portion is binary or ASCII
+  binMode = gFalse;
+  for (i = 0; i < 4; ++i) {
+    start[i] = strObj.streamGetChar();
+    if (start[i] == EOF) {
+      error(-1, "Unexpected end of file in embedded font stream");
+      goto err1;
+    }
+    if (!((start[i] >= '0' && start[i] <= '9') ||
+	  (start[i] >= 'A' && start[i] <= 'F') ||
+	  (start[i] >= 'a' && start[i] <= 'f')))
+      binMode = gTrue;
+  }
+
+  // convert binary data to ASCII
+  if (binMode) {
+    for (i = 0; i < 4; ++i) {
+      writePSChar(hexChar[(start[i] >> 4) & 0x0f]);
+      writePSChar(hexChar[start[i] & 0x0f]);
+    }
+#if 0 // this causes trouble for various PostScript printers
+    // if Length2 is incorrect (too small), font data gets chopped, so
+    // we take a few extra characters from the trailer just in case
+    length2 += length3 >= 8 ? 8 : length3;
+#endif
+    while (i < length2) {
+      if ((c = strObj.streamGetChar()) == EOF) {
+	break;
+      }
+      writePSChar(hexChar[(c >> 4) & 0x0f]);
+      writePSChar(hexChar[c & 0x0f]);
+      if (++i % 32 == 0) {
+	writePSChar('\n');
+      }
+    }
+    if (i % 32 > 0) {
+      writePSChar('\n');
+    }
+
+  // already in ASCII format -- just copy it
+  } else {
+    for (i = 0; i < 4; ++i) {
+      writePSChar(start[i]);
+    }
+    for (i = 4; i < length2; ++i) {
+      if ((c = strObj.streamGetChar()) == EOF) {
+	break;
+      }
+      writePSChar(c);
+    }
+  }
+
+  // write padding and "cleartomark"
+  for (i = 0; i < 8; ++i) {
+    writePS("00000000000000000000000000000000"
+	    "00000000000000000000000000000000\n");
+  }
+  writePS("cleartomark\n");
+
+  // ending comment
+  writePS("%%EndResource\n");
+
+ err1:
+  strObj.streamClose();
+  strObj.free();
+}
+
+//~ This doesn't handle .pfb files or binary eexec data (which only
+//~ happens in pfb files?).
+void PSOutputDev::setupExternalType1Font(GString *fileName, GString *psName) {
+  FILE *fontFile;
+  int c;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileNameLen; ++i) {
+    if (!fontFileNames[i]->cmp(fileName)) {
+      return;
+    }
+  }
+
+  // add entry to fontFileNames list
+  if (fontFileNameLen >= fontFileNameSize) {
+    fontFileNameSize += 64;
+    fontFileNames = (GString **)greallocn(fontFileNames,
+					  fontFileNameSize, sizeof(GString *));
+    psFileNames = (GString **)greallocn(psFileNames,
+					  fontFileNameSize, sizeof(GString *));
+  }
+  fontFileNames[fontFileNameLen] = fileName->copy();
+  psFileNames[fontFileNameLen] = psName->copy();
+  fontFileNameLen++;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // copy the font file
+  if (!(fontFile = fopen(fileName->getCString(), "rb"))) {
+    error(-1, "Couldn't open external font file");
+    return;
+  }
+  while ((c = fgetc(fontFile)) != EOF) {
+    writePSChar(c);
+  }
+  fclose(fontFile);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id,
+					  GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiType1C *ffT1C;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 1 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) {
+    ffT1C->convertToType1(psName->getCString(), NULL, gTrue,
+			  outputFunc, outputStream);
+    delete ffT1C;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id,
+					       GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 1 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    if (ffTT->isOpenTypeCFF()) {
+      ffTT->convertToType1(psName->getCString(), NULL, gTrue,
+			   outputFunc, outputStream);
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id,
+					    GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  Gushort *codeToGID;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen) {
+      psName->appendf("_{0:d}", nextTrueTypeNum++);
+      break;
+    }
+  }
+
+  // add entry to fontFileIDs list
+  if (i == fontFileIDLen) {
+    if (fontFileIDLen >= fontFileIDSize) {
+      fontFileIDSize += 64;
+      fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+    }
+    fontFileIDs[fontFileIDLen++] = *id;
+  }
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 42 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT);
+    ffTT->convertToType42(psName->getCString(),
+			  ((Gfx8BitFont *)font)->getHasEncoding()
+			    ? ((Gfx8BitFont *)font)->getEncoding()
+			    : (char **)NULL,
+			  codeToGID, outputFunc, outputStream);
+    if (codeToGID) {
+      if (font8InfoLen >= font8InfoSize) {
+	font8InfoSize += 16;
+	font8Info = (PSFont8Info *)greallocn(font8Info,
+					     font8InfoSize,
+					     sizeof(PSFont8Info));
+      }
+      font8Info[font8InfoLen].fontID = *font->getID();
+      font8Info[font8InfoLen].codeToGID = codeToGID;
+      ++font8InfoLen;
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+GString *PSOutputDev::setupExternalTrueTypeFont(GfxFont *font) {
+  GString *fileName;
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  Gushort *codeToGID;
+  GString *psName;
+  int i;
+
+  // check if font is already embedded
+  fileName = font->getExtFontFile();
+  for (i = 0; i < fontFileNameLen; ++i) {
+    if (!fontFileNames[i]->cmp(fileName)) {
+      return psFileNames[i]->copy();
+    }
+  }
+
+  psName = filterPSName(font->getName());
+  // add entry to fontFileNames list
+  if (i == fontFileNameLen) {
+    if (fontFileNameLen >= fontFileNameSize) {
+      fontFileNameSize += 64;
+      fontFileNames =
+	(GString **)greallocn(fontFileNames,
+			      fontFileNameSize, sizeof(GString *));
+      psFileNames =
+	(GString **)greallocn(psFileNames,
+			      fontFileNameSize, sizeof(GString *));
+    }
+    fontFileNames[fontFileNameLen] = fileName->copy();
+    psFileNames[fontFileNameLen] = psName->copy();
+    fontFileNameLen++;
+  }
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 42 font
+  fontBuf = font->readExtFontFile(&fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT);
+    ffTT->convertToType42(psName->getCString(),
+			  ((Gfx8BitFont *)font)->getHasEncoding()
+			    ? ((Gfx8BitFont *)font)->getEncoding()
+			    : (char **)NULL,
+			  codeToGID, outputFunc, outputStream);
+    if (codeToGID) {
+      if (font8InfoLen >= font8InfoSize) {
+	font8InfoSize += 16;
+	font8Info = (PSFont8Info *)greallocn(font8Info,
+					     font8InfoSize,
+					     sizeof(PSFont8Info));
+      }
+      font8Info[font8InfoLen].fontID = *font->getID();
+      font8Info[font8InfoLen].codeToGID = codeToGID;
+      ++font8InfoLen;
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+  return psName;
+}
+
+GString *PSOutputDev::setupExternalCIDTrueTypeFont(GfxFont *font, GString *fileName, int faceIndex) {
+  FoFiTrueType *ffTT;
+  Gushort *codeToGID;
+  GString *psName;
+  int i;
+  GString *myFileName;
+
+  myFileName = fileName->copy();
+  if (faceIndex > 0) {
+    char tmp[32];
+    sprintf(tmp, ",%d", faceIndex);
+    myFileName->append(tmp);
+  }
+  // check if font is already embedded
+  for (i = 0; i < fontFileNameLen; ++i) {
+    if (!fontFileNames[i]->cmp(myFileName)) {
+      delete myFileName;
+      return psFileNames[i]->copy();
+    }
+  }
+
+  psName = filterPSName(font->getName());
+  // add entry to fontFileNames list
+  if (i == fontFileNameLen) {
+    if (fontFileNameLen >= fontFileNameSize) {
+      fontFileNameSize += 64;
+      fontFileNames =
+	(GString **)grealloc(fontFileNames,
+			     fontFileNameSize * sizeof(GString *));
+      psFileNames =
+	(GString **)grealloc(psFileNames,
+			     fontFileNameSize * sizeof(GString *));
+    }
+  }
+  fontFileNames[fontFileNameLen] = myFileName;
+  psFileNames[fontFileNameLen] = psName->copy();
+  fontFileNameLen++;
+
+  // beginning comment
+  writePSFmt("%%%%BeginResource: font %s\n", psName->getCString());
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a CID type2 font
+  if ((ffTT = FoFiTrueType::load(fileName->getCString(), faceIndex))) {
+      int n = ((GfxCIDFont *)font)->getCIDToGIDLen();
+      if (n) {
+	codeToGID = (Gushort *)gmalloc(n * sizeof(Gushort));
+	memcpy(codeToGID, ((GfxCIDFont *)font)->getCIDToGID(), n * sizeof(Gushort));
+      } else {
+	codeToGID = ((GfxCIDFont *)font)->getCodeToGIDMap(ffTT, &n);
+      }
+      if (globalParams->getPSLevel() >= psLevel3) {
+	// Level 3: use a CID font
+	ffTT->convertToCIDType2(psName->getCString(),
+				codeToGID, n, gTrue,
+				outputFunc, outputStream);
+      } else {
+	// otherwise: use a non-CID composite font
+	ffTT->convertToType0(psName->getCString(),
+			     codeToGID, n, gTrue,
+			     outputFunc, outputStream);
+      }
+      gfree(codeToGID);
+      delete ffTT;
+  }
+
+  // ending comment
+  writePS("%%EndResource\n");
+  return psName;
+}
+
+void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id,
+					    GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiType1C *ffT1C;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 0 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) {
+    if (globalParams->getPSLevel() >= psLevel3) {
+      // Level 3: use a CID font
+      ffT1C->convertToCIDType0(psName->getCString(), outputFunc, outputStream);
+    } else {
+      // otherwise: use a non-CID composite font
+      ffT1C->convertToType0(psName->getCString(), outputFunc, outputStream);
+    }
+    delete ffT1C;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id,
+					       GString *psName,
+					       GBool needVerticalMetrics) {
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen) {
+      psName->appendf("_{0:d}", nextTrueTypeNum++);
+      break;
+    }
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 0 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    if (globalParams->getPSLevel() >= psLevel3) {
+      // Level 3: use a CID font
+      ffTT->convertToCIDType2(psName->getCString(),
+			      ((GfxCIDFont *)font)->getCIDToGID(),
+			      ((GfxCIDFont *)font)->getCIDToGIDLen(),
+			      needVerticalMetrics,
+			      outputFunc, outputStream);
+    } else {
+      // otherwise: use a non-CID composite font
+      ffTT->convertToType0(psName->getCString(),
+			   ((GfxCIDFont *)font)->getCIDToGID(),
+			   ((GfxCIDFont *)font)->getCIDToGIDLen(),
+			   needVerticalMetrics,
+			   outputFunc, outputStream);
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id,
+					       GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 0 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    if (ffTT->isOpenTypeCFF()) {
+      if (globalParams->getPSLevel() >= psLevel3) {
+	// Level 3: use a CID font
+	ffTT->convertToCIDType0(psName->getCString(),
+				outputFunc, outputStream);
+      } else {
+	// otherwise: use a non-CID composite font
+	ffTT->convertToType0(psName->getCString(), outputFunc, outputStream);
+      }
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupType3Font(GfxFont *font, GString *psName,
+				 Dict *parentResDict) {
+  Dict *resDict;
+  Dict *charProcs;
+  Object charProc;
+  Gfx *gfx;
+  PDFRectangle box;
+  double *m;
+  GString *buf;
+  int i;
+
+  // set up resources used by font
+  if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
+    inType3Char = gTrue;
+    setupResources(resDict);
+    inType3Char = gFalse;
+  } else {
+    resDict = parentResDict;
+  }
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // font dictionary
+  writePS("8 dict begin\n");
+  writePS("/FontType 3 def\n");
+  m = font->getFontMatrix();
+  writePSFmt("/FontMatrix [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] def\n",
+	     m[0], m[1], m[2], m[3], m[4], m[5]);
+  m = font->getFontBBox();
+  writePSFmt("/FontBBox [{0:.4g} {1:.4g} {2:.4g} {3:.4g}] def\n",
+	     m[0], m[1], m[2], m[3]);
+  writePS("/Encoding 256 array def\n");
+  writePS("  0 1 255 { Encoding exch /.notdef put } for\n");
+  writePS("/BuildGlyph {\n");
+  writePS("  exch /CharProcs get exch\n");
+  writePS("  2 copy known not { pop /.notdef } if\n");
+  writePS("  get exec\n");
+  writePS("} bind def\n");
+  writePS("/BuildChar {\n");
+  writePS("  1 index /Encoding get exch get\n");
+  writePS("  1 index /BuildGlyph get exec\n");
+  writePS("} bind def\n");
+  if ((charProcs = ((Gfx8BitFont *)font)->getCharProcs())) {
+    writePSFmt("/CharProcs {0:d} dict def\n", charProcs->getLength());
+    writePS("CharProcs begin\n");
+    box.x1 = m[0];
+    box.y1 = m[1];
+    box.x2 = m[2];
+    box.y2 = m[3];
+    gfx = new Gfx(xref, this, resDict, &box, NULL);
+    inType3Char = gTrue;
+    for (i = 0; i < charProcs->getLength(); ++i) {
+      t3Cacheable = gFalse;
+      t3NeedsRestore = gFalse;
+      writePS("/");
+      writePSName(charProcs->getKey(i));
+      writePS(" {\n");
+      gfx->display(charProcs->getVal(i, &charProc));
+      charProc.free();
+      if (t3String) {
+	if (t3Cacheable) {
+	  buf = GString::format("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} setcachedevice\n",
+				t3WX, t3WY, t3LLX, t3LLY, t3URX, t3URY);
+	} else {
+	  buf = GString::format("{0:.4g} {1:.4g} setcharwidth\n", t3WX, t3WY);
+	}
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+	(*outputFunc)(outputStream, t3String->getCString(),
+		      t3String->getLength());
+	delete t3String;
+	t3String = NULL;
+      }
+      if (t3NeedsRestore) {
+	(*outputFunc)(outputStream, "Q\n", 2);
+      }
+      writePS("} def\n");
+    }
+    inType3Char = gFalse;
+    delete gfx;
+    writePS("end\n");
+  }
+  writePS("currentdict end\n");
+  writePSFmt("/{0:t} exch definefont pop\n", psName);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupImages(Dict *resDict) {
+  Object xObjDict, xObj, xObjRef, subtypeObj;
+  int i;
+
+  if (!(mode == psModeForm || inType3Char || preload)) {
+    return;
+  }
+
+  resDict->lookup("XObject", &xObjDict);
+  if (xObjDict.isDict()) {
+    for (i = 0; i < xObjDict.dictGetLength(); ++i) {
+      xObjDict.dictGetValNF(i, &xObjRef);
+      xObjDict.dictGetVal(i, &xObj);
+      if (xObj.isStream()) {
+	xObj.streamGetDict()->lookup("Subtype", &subtypeObj);
+	if (subtypeObj.isName("Image")) {
+	  if (xObjRef.isRef()) {
+	    setupImage(xObjRef.getRef(), xObj.getStream());
+	  } else {
+	    error(-1, "Image in resource dict is not an indirect reference");
+	  }
+	}
+	subtypeObj.free();
+      }
+      xObj.free();
+      xObjRef.free();
+    }
+  }
+  xObjDict.free();
+}
+
+void PSOutputDev::setupImage(Ref id, Stream *str) {
+  GBool useRLE, useCompressed, useASCIIHex;
+  GString *s;
+  int c;
+  int size, line, col, i;
+
+  // check if image is already setup
+  for (i = 0; i < imgIDLen; ++i) {
+    if (imgIDs[i].num == id.num && imgIDs[i].gen == id.gen) {
+      return;
+    }
+  }
+
+  // add entry to imgIDs list
+  if (imgIDLen >= imgIDSize) {
+    if (imgIDSize == 0) {
+      imgIDSize = 64;
+    } else {
+      imgIDSize *= 2;
+    }
+    imgIDs = (Ref *)greallocn(imgIDs, imgIDSize, sizeof(Ref));
+  }
+  imgIDs[imgIDLen++] = id;
+
+  // filters
+  //~ this does not correctly handle the DeviceN color space
+  //~   -- need to use DeviceNRecoder
+  if (level < psLevel2) {
+    useRLE = gFalse;
+    useCompressed = gFalse;
+    useASCIIHex = gTrue;
+  } else {
+    s = str->getPSFilter(level < psLevel3 ? 2 : 3, "");
+    if (s) {
+      useRLE = gFalse;
+      useCompressed = gTrue;
+      delete s;
+    } else {
+      useRLE = gTrue;
+      useCompressed = gFalse;
+    }
+    useASCIIHex = level == psLevel1 || level == psLevel1Sep ||
+                  globalParams->getPSASCIIHex();
+  }
+  if (useCompressed) {
+    str = str->getUndecodedStream();
+  }
+  if (useRLE) {
+    str = new RunLengthEncoder(str);
+  }
+  if (useASCIIHex) {
+    str = new ASCIIHexEncoder(str);
+  } else {
+    str = new ASCII85Encoder(str);
+  }
+
+  // compute image data size
+  str->reset();
+  col = size = 0;
+  do {
+    do {
+      c = str->getChar();
+    } while (c == '\n' || c == '\r');
+    if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+      break;
+    }
+    if (c == 'z') {
+      ++col;
+    } else {
+      ++col;
+      for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
+	do {
+	  c = str->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	  break;
+	}
+	++col;
+      }
+    }
+    if (col > 225) {
+      ++size;
+      col = 0;
+    }
+  } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
+  // add one entry for the final line of data; add another entry
+  // because the RunLengthDecode filter may read past the end
+  ++size;
+  if (useRLE) {
+    ++size;
+  }
+  writePSFmt("{0:d} array dup /ImData_{1:d}_{2:d} exch def\n",
+	     size, id.num, id.gen);
+  str->close();
+
+  // write the data into the array
+  str->reset();
+  line = col = 0;
+  writePS((char *)(useASCIIHex ? "dup 0 <" : "dup 0 <~"));
+  do {
+    do {
+      c = str->getChar();
+    } while (c == '\n' || c == '\r');
+    if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+      break;
+    }
+    if (c == 'z') {
+      writePSChar(c);
+      ++col;
+    } else {
+      writePSChar(c);
+      ++col;
+      for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
+	do {
+	  c = str->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	  break;
+	}
+	writePSChar(c);
+	++col;
+      }
+    }
+    // each line is: "dup nnnnn <~...data...~> put<eol>"
+    // so max data length = 255 - 20 = 235
+    // chunks are 1 or 4 bytes each, so we have to stop at 232
+    // but make it 225 just to be safe
+    if (col > 225) {
+      writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n"));
+      ++line;
+      writePSFmt((char *)(useASCIIHex ? "dup {0:d} <" : "dup {0:d} <~"), line);
+      col = 0;
+    }
+  } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
+  writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n"));
+  if (useRLE) {
+    ++line;
+    writePSFmt("{0:d} <> put\n", line);
+  } else {
+    writePS("pop\n");
+  }
+  str->close();
+
+  delete str;
+}
+
+void PSOutputDev::setupForms(Dict *resDict) {
+  Object xObjDict, xObj, xObjRef, subtypeObj;
+  int i;
+
+  if (!preload) {
+    return;
+  }
+
+  resDict->lookup("XObject", &xObjDict);
+  if (xObjDict.isDict()) {
+    for (i = 0; i < xObjDict.dictGetLength(); ++i) {
+      xObjDict.dictGetValNF(i, &xObjRef);
+      xObjDict.dictGetVal(i, &xObj);
+      if (xObj.isStream()) {
+	xObj.streamGetDict()->lookup("Subtype", &subtypeObj);
+	if (subtypeObj.isName("Form")) {
+	  if (xObjRef.isRef()) {
+	    setupForm(xObjRef.getRef(), &xObj);
+	  } else {
+	    error(-1, "Form in resource dict is not an indirect reference");
+	  }
+	}
+	subtypeObj.free();
+      }
+      xObj.free();
+      xObjRef.free();
+    }
+  }
+  xObjDict.free();
+}
+
+void PSOutputDev::setupForm(Ref id, Object *strObj) {
+  Dict *dict, *resDict;
+  Object matrixObj, bboxObj, resObj, obj1;
+  double m[6], bbox[4];
+  PDFRectangle box;
+  Gfx *gfx;
+  int i;
+
+  // check if form is already defined
+  for (i = 0; i < formIDLen; ++i) {
+    if (formIDs[i].num == id.num && formIDs[i].gen == id.gen) {
+      return;
+    }
+  }
+
+  // add entry to formIDs list
+  if (formIDLen >= formIDSize) {
+    if (formIDSize == 0) {
+      formIDSize = 64;
+    } else {
+      formIDSize *= 2;
+    }
+    formIDs = (Ref *)greallocn(formIDs, formIDSize, sizeof(Ref));
+  }
+  formIDs[formIDLen++] = id;
+
+  dict = strObj->streamGetDict();
+
+  // get bounding box
+  dict->lookup("BBox", &bboxObj);
+  if (!bboxObj.isArray()) {
+    bboxObj.free();
+    error(-1, "Bad form bounding box");
+    return;
+  }
+  for (i = 0; i < 4; ++i) {
+    bboxObj.arrayGet(i, &obj1);
+    bbox[i] = obj1.getNum();
+    obj1.free();
+  }
+  bboxObj.free();
+
+  // get matrix
+  dict->lookup("Matrix", &matrixObj);
+  if (matrixObj.isArray()) {
+    for (i = 0; i < 6; ++i) {
+      matrixObj.arrayGet(i, &obj1);
+      m[i] = obj1.getNum();
+      obj1.free();
+    }
+  } else {
+    m[0] = 1; m[1] = 0;
+    m[2] = 0; m[3] = 1;
+    m[4] = 0; m[5] = 0;
+  }
+  matrixObj.free();
+
+  // get resources
+  dict->lookup("Resources", &resObj);
+  resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
+
+  writePSFmt("/f_{0:d}_{1:d} {{\n", id.num, id.gen);
+  writePS("q\n");
+  writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] cm\n",
+	     m[0], m[1], m[2], m[3], m[4], m[5]);
+
+  box.x1 = bbox[0];
+  box.y1 = bbox[1];
+  box.x2 = bbox[2];
+  box.y2 = bbox[3];
+  gfx = new Gfx(xref, this, resDict, &box, &box);
+  gfx->display(strObj);
+  delete gfx;
+
+  writePS("Q\n");
+  writePS("} def\n");
+
+  resObj.free();
+}
+
+GBool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/,
+				  int rotateA, GBool useMediaBox, GBool crop,
+				  int sliceX, int sliceY,
+				  int sliceW, int sliceH,
+				  GBool printing, Catalog *catalog,
+				  GBool (*abortCheckCbk)(void *data),
+				  void *abortCheckCbkData) {
+#if HAVE_SPLASH
+  PreScanOutputDev *scan;
+  GBool rasterize;
+  SplashOutputDev *splashOut;
+  SplashColor paperColor;
+  PDFRectangle box;
+  GfxState *state;
+  SplashBitmap *bitmap;
+  Stream *str0, *str;
+  Object obj;
+  Guchar *p;
+  Guchar col[4];
+  double m0, m1, m2, m3, m4, m5;
+  int c, w, h, x, y, comp, i;
+
+  if (!forceRasterize) {
+    scan = new PreScanOutputDev();
+    page->displaySlice(scan, 72, 72, rotateA, useMediaBox, crop,
+		     sliceX, sliceY, sliceW, sliceH,
+		     printing, catalog, abortCheckCbk, abortCheckCbkData);
+    rasterize = scan->usesTransparency();
+    delete scan;
+  } else {
+    rasterize = true;
+  }
+  if (!rasterize) {
+    return gTrue;
+  }
+
+  // rasterize the page
+  if (level == psLevel1) {
+    paperColor[0] = 0xff;
+    splashOut = new SplashOutputDev(splashModeMono8, 1, gFalse,
+				    paperColor, gTrue, gFalse);
+#if SPLASH_CMYK
+  } else if (level == psLevel1Sep) {
+    paperColor[0] = paperColor[1] = paperColor[2] = paperColor[3] = 0;
+    splashOut = new SplashOutputDev(splashModeCMYK8, 1, gFalse,
+				    paperColor, gTrue, gFalse);
+#endif
+  } else {
+    paperColor[0] = paperColor[1] = paperColor[2] = 0xff;
+    splashOut = new SplashOutputDev(splashModeRGB8, 1, gFalse,
+				    paperColor, gTrue, gFalse);
+  }
+  splashOut->startDoc(xref);
+  page->displaySlice(splashOut, splashDPI, splashDPI, rotateA,
+		     useMediaBox, crop,
+		     sliceX, sliceY, sliceW, sliceH,
+		     printing, catalog, abortCheckCbk, abortCheckCbkData);
+
+  // start the PS page
+  page->makeBox(splashDPI, splashDPI, rotateA, useMediaBox, gFalse,
+		sliceX, sliceY, sliceW, sliceH, &box, &crop);
+  rotateA += page->getRotate();
+  if (rotateA >= 360) {
+    rotateA -= 360;
+  } else if (rotateA < 0) {
+    rotateA += 360;
+  }
+  state = new GfxState(splashDPI, splashDPI, &box, rotateA, gFalse);
+  startPage(page->getNum(), state);
+  delete state;
+  switch (rotateA) {
+  case 0:
+  default:  // this should never happen
+    m0 = box.x2 - box.x1;
+    m1 = 0;
+    m2 = 0;
+    m3 = box.y2 - box.y1;
+    m4 = box.x1;
+    m5 = box.y1;
+    break;
+  case 90:
+    m0 = 0;
+    m1 = box.y2 - box.y1;
+    m2 = -(box.x2 - box.x1);
+    m3 = 0;
+    m4 = box.x2;
+    m5 = box.y1;
+    break;
+  case 180:
+    m0 = -(box.x2 - box.x1);
+    m1 = 0;
+    m2 = 0;
+    m3 = -(box.y2 - box.y1);
+    m4 = box.x2;
+    m5 = box.y2;
+    break;
+  case 270:
+    m0 = 0;
+    m1 = -(box.y2 - box.y1);
+    m2 = box.x2 - box.x1;
+    m3 = 0;
+    m4 = box.x1;
+    m5 = box.y2;
+    break;
+  }
+
+  //~ need to add the process colors
+
+  // draw the rasterized image
+  bitmap = splashOut->getBitmap();
+  w = bitmap->getWidth();
+  h = bitmap->getHeight();
+  writePS("gsave\n");
+  writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] concat\n",
+	     m0, m1, m2, m3, m4, m5);
+  switch (level) {
+  case psLevel1:
+    writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1\n",
+	       w, h, w, -h, h);
+    p = bitmap->getDataPtr();
+    i = 0;
+    for (y = 0; y < h; ++y) {
+      for (x = 0; x < w; ++x) {
+	writePSFmt("{0:02x}", *p++);
+	if (++i == 32) {
+	  writePSChar('\n');
+	  i = 0;
+	}
+      }
+    }
+    if (i != 0) {
+      writePSChar('\n');
+    }
+    break;
+  case psLevel1Sep:
+    writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1Sep\n",
+	       w, h, w, -h, h);
+    p = bitmap->getDataPtr();
+    i = 0;
+    col[0] = col[1] = col[2] = col[3] = 0;
+    for (y = 0; y < h; ++y) {
+      for (comp = 0; comp < 4; ++comp) {
+	for (x = 0; x < w; ++x) {
+	  writePSFmt("{0:02x}", p[4*x + comp]);
+	  col[comp] |= p[4*x + comp];
+	  if (++i == 32) {
+	    writePSChar('\n');
+	    i = 0;
+	  }
+	}
+      }
+      p += bitmap->getRowSize();
+    }
+    if (i != 0) {
+      writePSChar('\n');
+    }
+    if (col[0]) {
+      processColors |= psProcessCyan;
+    }
+    if (col[1]) {
+      processColors |= psProcessMagenta;
+    }
+    if (col[2]) {
+      processColors |= psProcessYellow;
+    }
+    if (col[3]) {
+      processColors |= psProcessBlack;
+    }
+    break;
+  case psLevel2:
+  case psLevel2Sep:
+  case psLevel3:
+  case psLevel3Sep:
+    writePS("/DeviceRGB setcolorspace\n");
+    writePS("<<\n  /ImageType 1\n");
+    writePSFmt("  /Width {0:d}\n", bitmap->getWidth());
+    writePSFmt("  /Height {0:d}\n", bitmap->getHeight());
+    writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n", w, -h, h);
+    writePS("  /BitsPerComponent 8\n");
+    writePS("  /Decode [0 1 0 1 0 1]\n");
+    writePS("  /DataSource currentfile\n");
+    if (globalParams->getPSASCIIHex()) {
+      writePS("    /ASCIIHexDecode filter\n");
+    } else {
+      writePS("    /ASCII85Decode filter\n");
+    }
+    writePS("    /RunLengthDecode filter\n");
+    writePS(">>\n");
+    writePS("image\n");
+    obj.initNull();
+    str0 = new MemStream((char *)bitmap->getDataPtr(), 0, w * h * 3, &obj);
+    str = new RunLengthEncoder(str0);
+    if (globalParams->getPSASCIIHex()) {
+      str = new ASCIIHexEncoder(str);
+    } else {
+      str = new ASCII85Encoder(str);
+    }
+    str->reset();
+    while ((c = str->getChar()) != EOF) {
+      writePSChar(c);
+    }
+    str->close();
+    delete str;
+    delete str0;
+    processColors |= psProcessCMYK;
+    break;
+  }
+  delete splashOut;
+  writePS("grestore\n");
+
+  // finish the PS page
+  endPage();
+
+  return gFalse;
+#else
+  return gTrue;
+#endif
+}
+
+void PSOutputDev::startPage(int pageNum, GfxState *state) {
+  int x1, y1, x2, y2, width, height;
+  int imgWidth, imgHeight, imgWidth2, imgHeight2;
+  GBool landscape;
+
+
+  if (mode == psModePS) {
+    writePSFmt("%%Page: {0:d} {1:d}\n", pageNum, seqPage);
+    writePS("%%BeginPageSetup\n");
+  }
+
+  // underlays
+  if (underlayCbk) {
+    (*underlayCbk)(this, underlayCbkData);
+  }
+  if (overlayCbk) {
+    saveState(NULL);
+  }
+
+  switch (mode) {
+
+  case psModePS:
+    // rotate, translate, and scale page
+    imgWidth = imgURX - imgLLX;
+    imgHeight = imgURY - imgLLY;
+    x1 = (int)floor(state->getX1());
+    y1 = (int)floor(state->getY1());
+    x2 = (int)ceil(state->getX2());
+    y2 = (int)ceil(state->getY2());
+    width = x2 - x1;
+    height = y2 - y1;
+    tx = ty = 0;
+    // rotation and portrait/landscape mode
+    if (rotate0 >= 0) {
+      rotate = (360 - rotate0) % 360;
+      landscape = gFalse;
+    } else {
+      rotate = (360 - state->getRotate()) % 360;
+      if (rotate == 0 || rotate == 180) {
+	if (width > height && width > imgWidth) {
+	  rotate += 90;
+	  landscape = gTrue;
+	} else {
+	  landscape = gFalse;
+	}
+      } else { // rotate == 90 || rotate == 270
+	if (height > width && height > imgWidth) {
+	  rotate = 270 - rotate;
+	  landscape = gTrue;
+	} else {
+	  landscape = gFalse;
+	}
+      }
+    }
+    writePSFmt("%%PageOrientation: {0:s}\n",
+	       landscape ? "Landscape" : "Portrait");
+    writePS("pdfStartPage\n");
+    if (rotate == 0) {
+      imgWidth2 = imgWidth;
+      imgHeight2 = imgHeight;
+    } else if (rotate == 90) {
+      writePS("90 rotate\n");
+      ty = -imgWidth;
+      imgWidth2 = imgHeight;
+      imgHeight2 = imgWidth;
+    } else if (rotate == 180) {
+      writePS("180 rotate\n");
+      imgWidth2 = imgWidth;
+      imgHeight2 = imgHeight;
+      tx = -imgWidth;
+      ty = -imgHeight;
+    } else { // rotate == 270
+      writePS("270 rotate\n");
+      tx = -imgHeight;
+      imgWidth2 = imgHeight;
+      imgHeight2 = imgWidth;
+    }
+    // shrink or expand
+    if (xScale0 > 0 && yScale0 > 0) {
+      xScale = xScale0;
+      yScale = yScale0;
+    } else if ((globalParams->getPSShrinkLarger() &&
+	 (width > imgWidth2 || height > imgHeight2)) ||
+	(globalParams->getPSExpandSmaller() &&
+	 (width < imgWidth2 && height < imgHeight2))) {
+      xScale = (double)imgWidth2 / (double)width;
+      yScale = (double)imgHeight2 / (double)height;
+      if (yScale < xScale) {
+	xScale = yScale;
+      } else {
+	yScale = xScale;
+      }
+    } else {
+      xScale = yScale = 1;
+    }
+    // deal with odd bounding boxes or clipping
+    if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
+      tx -= xScale * clipLLX0;
+      ty -= yScale * clipLLY0;
+    } else {
+      tx -= xScale * x1;
+      ty -= yScale * y1;
+    }
+    // center
+    if (tx0 >= 0 && ty0 >= 0) {
+      tx += rotate == 0 ? tx0 : ty0;
+      ty += rotate == 0 ? ty0 : -tx0;
+    } else if (globalParams->getPSCenter()) {
+      if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
+	tx += (imgWidth2 - xScale * (clipURX0 - clipLLX0)) / 2;
+	ty += (imgHeight2 - yScale * (clipURY0 - clipLLY0)) / 2;
+      } else {
+	tx += (imgWidth2 - xScale * width) / 2;
+	ty += (imgHeight2 - yScale * height) / 2;
+      }
+    }
+    tx += rotate == 0 ? imgLLX : imgLLY;
+    ty += rotate == 0 ? imgLLY : -imgLLX;
+    if (tx != 0 || ty != 0) {
+      writePSFmt("{0:.4g} {1:.4g} translate\n", tx, ty);
+    }
+    if (xScale != 1 || yScale != 1) {
+      writePSFmt("{0:.4f} {1:.4f} scale\n", xScale, yScale);
+    }
+    if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} re W\n",
+		 clipLLX0, clipLLY0, clipURX0 - clipLLX0, clipURY0 - clipLLY0);
+    } else {
+      writePSFmt("{0:d} {1:d} {2:d} {3:d} re W\n", x1, y1, x2 - x1, y2 - y1);
+    }
+
+    writePS("%%EndPageSetup\n");
+    ++seqPage;
+    break;
+
+  case psModeEPS:
+    writePS("pdfStartPage\n");
+    tx = ty = 0;
+    rotate = (360 - state->getRotate()) % 360;
+    if (rotate == 0) {
+    } else if (rotate == 90) {
+      writePS("90 rotate\n");
+      tx = -epsX1;
+      ty = -epsY2;
+    } else if (rotate == 180) {
+      writePS("180 rotate\n");
+      tx = -(epsX1 + epsX2);
+      ty = -(epsY1 + epsY2);
+    } else { // rotate == 270
+      writePS("270 rotate\n");
+      tx = -epsX2;
+      ty = -epsY1;
+    }
+    if (tx != 0 || ty != 0) {
+      writePSFmt("{0:.4g} {1:.4g} translate\n", tx, ty);
+    }
+    xScale = yScale = 1;
+    break;
+
+  case psModeForm:
+    writePS("/PaintProc {\n");
+    writePS("begin xpdf begin\n");
+    writePS("pdfStartPage\n");
+    tx = ty = 0;
+    xScale = yScale = 1;
+    rotate = 0;
+    break;
+  }
+}
+
+void PSOutputDev::endPage() {
+  if (overlayCbk) {
+    restoreState(NULL);
+    (*overlayCbk)(this, overlayCbkData);
+  }
+
+
+  if (mode == psModeForm) {
+    writePS("pdfEndPage\n");
+    writePS("end end\n");
+    writePS("} def\n");
+    writePS("end end\n");
+  } else {
+    if (!manualCtrl) {
+      writePS("showpage\n");
+    }
+    writePS("%%PageTrailer\n");
+    writePageTrailer();
+  }
+}
+
+void PSOutputDev::saveState(GfxState * /*state*/) {
+  writePS("q\n");
+  ++numSaves;
+}
+
+void PSOutputDev::restoreState(GfxState * /*state*/) {
+  writePS("Q\n");
+  --numSaves;
+}
+
+void PSOutputDev::updateCTM(GfxState * /*state*/, double m11, double m12,
+			    double m21, double m22, double m31, double m32) {
+  writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] cm\n",
+	     m11, m12, m21, m22, m31, m32);
+}
+
+void PSOutputDev::updateLineDash(GfxState *state) {
+  double *dash;
+  double start;
+  int length, i;
+
+  state->getLineDash(&dash, &length, &start);
+  writePS("[");
+  for (i = 0; i < length; ++i) {
+    writePSFmt("{0:.4g}{1:w}",
+	       dash[i] < 0 ? 0 : dash[i],
+	       (i == length-1) ? 0 : 1);
+  }
+  writePSFmt("] {0:.4g} d\n", start);
+}
+
+void PSOutputDev::updateFlatness(GfxState *state) {
+  writePSFmt("{0:d} i\n", state->getFlatness());
+}
+
+void PSOutputDev::updateLineJoin(GfxState *state) {
+  writePSFmt("{0:d} j\n", state->getLineJoin());
+}
+
+void PSOutputDev::updateLineCap(GfxState *state) {
+  writePSFmt("{0:d} J\n", state->getLineCap());
+}
+
+void PSOutputDev::updateMiterLimit(GfxState *state) {
+  writePSFmt("{0:.4g} M\n", state->getMiterLimit());
+}
+
+void PSOutputDev::updateLineWidth(GfxState *state) {
+  writePSFmt("{0:.4g} w\n", state->getLineWidth());
+}
+
+void PSOutputDev::updateFillColorSpace(GfxState *state) {
+  switch (level) {
+  case psLevel1:
+  case psLevel1Sep:
+    break;
+  case psLevel2:
+  case psLevel3:
+    if (state->getFillColorSpace()->getMode() != csPattern) {
+      dumpColorSpaceL2(state->getFillColorSpace(), gTrue, gFalse, gFalse);
+      writePS(" cs\n");
+    }
+    break;
+  case psLevel2Sep:
+  case psLevel3Sep:
+    break;
+  }
+}
+
+void PSOutputDev::updateStrokeColorSpace(GfxState *state) {
+  switch (level) {
+  case psLevel1:
+  case psLevel1Sep:
+    break;
+  case psLevel2:
+  case psLevel3:
+    if (state->getStrokeColorSpace()->getMode() != csPattern) {
+      dumpColorSpaceL2(state->getStrokeColorSpace(), gTrue, gFalse, gFalse);
+      writePS(" CS\n");
+    }
+    break;
+  case psLevel2Sep:
+  case psLevel3Sep:
+    break;
+  }
+}
+
+void PSOutputDev::updateFillColor(GfxState *state) {
+  GfxColor color;
+  GfxColor *colorPtr;
+  GfxGray gray;
+  GfxCMYK cmyk;
+  GfxSeparationColorSpace *sepCS;
+  double c, m, y, k;
+  int i;
+
+  switch (level) {
+  case psLevel1:
+    state->getFillGray(&gray);
+    writePSFmt("{0:.4g} g\n", colToDbl(gray));
+    break;
+  case psLevel1Sep:
+    state->getFillCMYK(&cmyk);
+    c = colToDbl(cmyk.c);
+    m = colToDbl(cmyk.m);
+    y = colToDbl(cmyk.y);
+    k = colToDbl(cmyk.k);
+    writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n", c, m, y, k);
+    addProcessColor(c, m, y, k);
+    break;
+  case psLevel2:
+  case psLevel3:
+    if (state->getFillColorSpace()->getMode() != csPattern) {
+      colorPtr = state->getFillColor();
+      writePS("[");
+      for (i = 0; i < state->getFillColorSpace()->getNComps(); ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i]));
+      }
+      writePS("] sc\n");
+    }
+    break;
+  case psLevel2Sep:
+  case psLevel3Sep:
+    if (state->getFillColorSpace()->getMode() == csSeparation) {
+      sepCS = (GfxSeparationColorSpace *)state->getFillColorSpace();
+      color.c[0] = gfxColorComp1;
+      sepCS->getCMYK(&color, &cmyk);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) ck\n",
+		 colToDbl(state->getFillColor()->c[0]),
+		 colToDbl(cmyk.c), colToDbl(cmyk.m),
+		 colToDbl(cmyk.y), colToDbl(cmyk.k),
+		 sepCS->getName());
+      addCustomColor(sepCS);
+    } else {
+      state->getFillCMYK(&cmyk);
+      c = colToDbl(cmyk.c);
+      m = colToDbl(cmyk.m);
+      y = colToDbl(cmyk.y);
+      k = colToDbl(cmyk.k);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n", c, m, y, k);
+      addProcessColor(c, m, y, k);
+    }
+    break;
+  }
+  t3Cacheable = gFalse;
+}
+
+void PSOutputDev::updateStrokeColor(GfxState *state) {
+  GfxColor color;
+  GfxColor *colorPtr;
+  GfxGray gray;
+  GfxCMYK cmyk;
+  GfxSeparationColorSpace *sepCS;
+  double c, m, y, k;
+  int i;
+
+  switch (level) {
+  case psLevel1:
+    state->getStrokeGray(&gray);
+    writePSFmt("{0:.4g} G\n", colToDbl(gray));
+    break;
+  case psLevel1Sep:
+    state->getStrokeCMYK(&cmyk);
+    c = colToDbl(cmyk.c);
+    m = colToDbl(cmyk.m);
+    y = colToDbl(cmyk.y);
+    k = colToDbl(cmyk.k);
+    writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n", c, m, y, k);
+    addProcessColor(c, m, y, k);
+    break;
+  case psLevel2:
+  case psLevel3:
+    if (state->getStrokeColorSpace()->getMode() != csPattern) {
+      colorPtr = state->getStrokeColor();
+      writePS("[");
+      for (i = 0; i < state->getStrokeColorSpace()->getNComps(); ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i]));
+      }
+      writePS("] SC\n");
+    }
+    break;
+  case psLevel2Sep:
+  case psLevel3Sep:
+    if (state->getStrokeColorSpace()->getMode() == csSeparation) {
+      sepCS = (GfxSeparationColorSpace *)state->getStrokeColorSpace();
+      color.c[0] = gfxColorComp1;
+      sepCS->getCMYK(&color, &cmyk);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) CK\n",
+		 colToDbl(state->getStrokeColor()->c[0]),
+		 colToDbl(cmyk.c), colToDbl(cmyk.m),
+		 colToDbl(cmyk.y), colToDbl(cmyk.k),
+		 sepCS->getName());
+      addCustomColor(sepCS);
+    } else {
+      state->getStrokeCMYK(&cmyk);
+      c = colToDbl(cmyk.c);
+      m = colToDbl(cmyk.m);
+      y = colToDbl(cmyk.y);
+      k = colToDbl(cmyk.k);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n", c, m, y, k);
+      addProcessColor(c, m, y, k);
+    }
+    break;
+  }
+  t3Cacheable = gFalse;
+}
+
+void PSOutputDev::addProcessColor(double c, double m, double y, double k) {
+  if (c > 0) {
+    processColors |= psProcessCyan;
+  }
+  if (m > 0) {
+    processColors |= psProcessMagenta;
+  }
+  if (y > 0) {
+    processColors |= psProcessYellow;
+  }
+  if (k > 0) {
+    processColors |= psProcessBlack;
+  }
+}
+
+void PSOutputDev::addCustomColor(GfxSeparationColorSpace *sepCS) {
+  PSOutCustomColor *cc;
+  GfxColor color;
+  GfxCMYK cmyk;
+
+  for (cc = customColors; cc; cc = cc->next) {
+    if (!cc->name->cmp(sepCS->getName())) {
+      return;
+    }
+  }
+  color.c[0] = gfxColorComp1;
+  sepCS->getCMYK(&color, &cmyk);
+  cc = new PSOutCustomColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
+			    colToDbl(cmyk.y), colToDbl(cmyk.k),
+			    sepCS->getName()->copy());
+  cc->next = customColors;
+  customColors = cc;
+}
+
+void PSOutputDev::updateFillOverprint(GfxState *state) {
+  if (level >= psLevel2) {
+    writePSFmt("{0:s} op\n", state->getFillOverprint() ? "true" : "false");
+  }
+}
+
+void PSOutputDev::updateStrokeOverprint(GfxState *state) {
+  if (level >= psLevel2) {
+    writePSFmt("{0:s} OP\n", state->getStrokeOverprint() ? "true" : "false");
+  }
+}
+
+void PSOutputDev::updateTransfer(GfxState *state) {
+  Function **funcs;
+  int i;
+
+  funcs = state->getTransfer();
+  if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) {
+    if (level >= psLevel2) {
+      for (i = 0; i < 4; ++i) {
+	cvtFunction(funcs[i]);
+      }
+      writePS("setcolortransfer\n");
+    } else {
+      cvtFunction(funcs[3]);
+      writePS("settransfer\n");
+    }
+  } else if (funcs[0]) {
+    cvtFunction(funcs[0]);
+    writePS("settransfer\n");
+  } else {
+    writePS("{} settransfer\n");
+  }
+}
+
+void PSOutputDev::updateFont(GfxState *state) {
+  if (state->getFont()) {
+    writePSFmt("/F{0:d}_{1:d} {2:.4g} Tf\n",
+	       state->getFont()->getID()->num, state->getFont()->getID()->gen,
+	       fabs(state->getFontSize()) < 0.00001 ? 0.00001
+	                                            : state->getFontSize());
+  }
+}
+
+void PSOutputDev::updateTextMat(GfxState *state) {
+  double *mat;
+
+  mat = state->getTextMat();
+  if (fabs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.00001) {
+    // avoid a singular (or close-to-singular) matrix
+    writePSFmt("[0.00001 0 0 0.00001 {0:.4g} {1:.4g}] Tm\n", mat[4], mat[5]);
+  } else {
+    writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] Tm\n",
+	       mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
+  }
+}
+
+void PSOutputDev::updateCharSpace(GfxState *state) {
+  writePSFmt("{0:.4g} Tc\n", state->getCharSpace());
+}
+
+void PSOutputDev::updateRender(GfxState *state) {
+  int rm;
+
+  rm = state->getRender();
+  writePSFmt("{0:d} Tr\n", rm);
+  rm &= 3;
+  if (rm != 0 && rm != 3) {
+    t3Cacheable = gFalse;
+  }
+}
+
+void PSOutputDev::updateRise(GfxState *state) {
+  writePSFmt("{0:.4g} Ts\n", state->getRise());
+}
+
+void PSOutputDev::updateWordSpace(GfxState *state) {
+  writePSFmt("{0:.4g} Tw\n", state->getWordSpace());
+}
+
+void PSOutputDev::updateHorizScaling(GfxState *state) {
+  double h;
+
+  h = state->getHorizScaling();
+  if (fabs(h) < 0.01) {
+    h = 0.01;
+  }
+  writePSFmt("{0:.4g} Tz\n", h);
+}
+
+void PSOutputDev::updateTextPos(GfxState *state) {
+  writePSFmt("{0:.4g} {1:.4g} Td\n", state->getLineX(), state->getLineY());
+}
+
+void PSOutputDev::updateTextShift(GfxState *state, double shift) {
+  if (state->getFont()->getWMode()) {
+    writePSFmt("{0:.4g} TJmV\n", shift);
+  } else {
+    writePSFmt("{0:.4g} TJm\n", shift);
+  }
+}
+
+void PSOutputDev::stroke(GfxState *state) {
+  doPath(state->getPath());
+  if (t3String) {
+    // if we're construct a cacheable Type 3 glyph, we need to do
+    // everything in the fill color
+    writePS("Sf\n");
+  } else {
+    writePS("S\n");
+  }
+}
+
+void PSOutputDev::fill(GfxState *state) {
+  doPath(state->getPath());
+  writePS("f\n");
+}
+
+void PSOutputDev::eoFill(GfxState *state) {
+  doPath(state->getPath());
+  writePS("f*\n");
+}
+
+void PSOutputDev::tilingPatternFill(GfxState * /*state*/, Object *str,
+				    int paintType, Dict *resDict,
+				    double *mat, double *bbox,
+				    int x0, int y0, int x1, int y1,
+				    double xStep, double yStep) {
+  PDFRectangle box;
+  Gfx *gfx;
+
+  // define a Type 3 font
+  writePS("8 dict begin\n");
+  writePS("/FontType 3 def\n");
+  writePS("/FontMatrix [1 0 0 1 0 0] def\n");
+  writePSFmt("/FontBBox [{0:.4g} {1:.4g} {2:.4g} {3:.4g}] def\n",
+	     bbox[0], bbox[1], bbox[2], bbox[3]);
+  writePS("/Encoding 256 array def\n");
+  writePS("  0 1 255 { Encoding exch /.notdef put } for\n");
+  writePS("  Encoding 120 /x put\n");
+  writePS("/BuildGlyph {\n");
+  writePS("  exch /CharProcs get exch\n");
+  writePS("  2 copy known not { pop /.notdef } if\n");
+  writePS("  get exec\n");
+  writePS("} bind def\n");
+  writePS("/BuildChar {\n");
+  writePS("  1 index /Encoding get exch get\n");
+  writePS("  1 index /BuildGlyph get exec\n");
+  writePS("} bind def\n");
+  writePS("/CharProcs 1 dict def\n");
+  writePS("CharProcs begin\n");
+  box.x1 = bbox[0];
+  box.y1 = bbox[1];
+  box.x2 = bbox[2];
+  box.y2 = bbox[3];
+  gfx = new Gfx(xref, this, resDict, &box, NULL);
+  writePS("/x {\n");
+  if (paintType == 2) {
+    writePSFmt("{0:.4g} 0 {1:.4g} {2:.4g} {3:.4g} {4:.4g} setcachedevice\n",
+	       xStep, bbox[0], bbox[1], bbox[2], bbox[3]);
+  } else {
+    if (x1 - 1 <= x0) {
+      writePS("1 0 setcharwidth\n");
+    } else {
+      writePSFmt("{0:.4g} 0 setcharwidth\n", xStep);
+    }
+  }
+  inType3Char = gTrue;
+  ++numTilingPatterns;
+  gfx->display(str);
+  --numTilingPatterns;
+  inType3Char = gFalse;
+  writePS("} def\n");
+  delete gfx;
+  writePS("end\n");
+  writePS("currentdict end\n");
+  writePSFmt("/xpdfTile{0:d} exch definefont pop\n", numTilingPatterns);
+
+  // draw the tiles
+  writePSFmt("/xpdfTile{0:d} findfont setfont\n", numTilingPatterns);
+  writePSFmt("gsave [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] concat\n",
+	     mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
+  writePSFmt("{0:d} 1 {1:d} {{ {2:.4g} exch {3:.4g} mul m {4:d} 1 {5:d} {{ pop (x) show }} for }} for\n",
+	     y0, y1 - 1, x0 * xStep, yStep, x0, x1 - 1);
+  writePS("grestore\n");
+}
+
+GBool PSOutputDev::functionShadedFill(GfxState * /*state*/,
+				      GfxFunctionShading *shading) {
+  double x0, y0, x1, y1;
+  double *mat;
+  int i;
+
+  if (level == psLevel2Sep || level == psLevel3Sep) {
+    if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
+      return gFalse;
+    }
+    processColors |= psProcessCMYK;
+  }
+
+  shading->getDomain(&x0, &y0, &x1, &y1);
+  mat = shading->getMatrix();
+  writePSFmt("/mat [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] def\n",
+	     mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
+  writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
+  if (shading->getNFuncs() == 1) {
+    writePS("/func ");
+    cvtFunction(shading->getFunc(0));
+    writePS("def\n");
+  } else {
+    writePS("/func {\n");
+    for (i = 0; i < shading->getNFuncs(); ++i) {
+      if (i < shading->getNFuncs() - 1) {
+	writePS("2 copy\n");
+      }
+      cvtFunction(shading->getFunc(i));
+      writePS("exec\n");
+      if (i < shading->getNFuncs() - 1) {
+	writePS("3 1 roll\n");
+      }
+    }
+    writePS("} def\n");
+  }
+  writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} 0 funcSH\n", x0, y0, x1, y1);
+
+  return gTrue;
+}
+
+GBool PSOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading) {
+  double xMin, yMin, xMax, yMax;
+  double x0, y0, x1, y1, dx, dy, mul;
+  double tMin, tMax, t, t0, t1;
+  int i;
+
+  if (level == psLevel2Sep || level == psLevel3Sep) {
+    if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
+      return gFalse;
+    }
+    processColors |= psProcessCMYK;
+  }
+
+  // get the clip region bbox
+  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+
+  // compute min and max t values, based on the four corners of the
+  // clip region bbox
+  shading->getCoords(&x0, &y0, &x1, &y1);
+  dx = x1 - x0;
+  dy = y1 - y0;
+  if (fabs(dx) < 0.01 && fabs(dy) < 0.01) {
+    return gTrue;
+  } else {
+    mul = 1 / (dx * dx + dy * dy);
+    tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
+    t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    if (tMin < 0 && !shading->getExtend0()) {
+      tMin = 0;
+    }
+    if (tMax > 1 && !shading->getExtend1()) {
+      tMax = 1;
+    }
+  }
+
+  // get the function domain
+  t0 = shading->getDomain0();
+  t1 = shading->getDomain1();
+
+  // generate the PS code
+  writePSFmt("/t0 {0:.4g} def\n", t0);
+  writePSFmt("/t1 {0:.4g} def\n", t1);
+  writePSFmt("/dt {0:.4g} def\n", t1 - t0);
+  writePSFmt("/x0 {0:.4g} def\n", x0);
+  writePSFmt("/y0 {0:.4g} def\n", y0);
+  writePSFmt("/dx {0:.4g} def\n", x1 - x0);
+  writePSFmt("/x1 {0:.4g} def\n", x1);
+  writePSFmt("/y1 {0:.4g} def\n", y1);
+  writePSFmt("/dy {0:.4g} def\n", y1 - y0);
+  writePSFmt("/xMin {0:.4g} def\n", xMin);
+  writePSFmt("/yMin {0:.4g} def\n", yMin);
+  writePSFmt("/xMax {0:.4g} def\n", xMax);
+  writePSFmt("/yMax {0:.4g} def\n", yMax);
+  writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
+  if (shading->getNFuncs() == 1) {
+    writePS("/func ");
+    cvtFunction(shading->getFunc(0));
+    writePS("def\n");
+  } else {
+    writePS("/func {\n");
+    for (i = 0; i < shading->getNFuncs(); ++i) {
+      if (i < shading->getNFuncs() - 1) {
+	writePS("dup\n");
+      }
+      cvtFunction(shading->getFunc(i));
+      writePS("exec\n");
+      if (i < shading->getNFuncs() - 1) {
+	writePS("exch\n");
+      }
+    }
+    writePS("} def\n");
+  }
+  writePSFmt("{0:.4g} {1:.4g} 0 axialSH\n", tMin, tMax);
+
+  return gTrue;
+}
+
+GBool PSOutputDev::radialShadedFill(GfxState *state,
+				    GfxRadialShading *shading) {
+  double xMin, yMin, xMax, yMax;
+  double x0, y0, r0, x1, y1, r1, t0, t1;
+  double xa, ya, ra;
+  double sz, xz, yz, sMin, sMax, sa, ta;
+  double theta, alpha, a1, a2;
+  GBool enclosed;
+  int i;
+
+  if (level == psLevel2Sep || level == psLevel3Sep) {
+    if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
+      return gFalse;
+    }
+    processColors |= psProcessCMYK;
+  }
+
+  // get the shading info
+  shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
+  t0 = shading->getDomain0();
+  t1 = shading->getDomain1();
+
+  // Compute the point at which r(s) = 0; check for the enclosed
+  // circles case; and compute the angles for the tangent lines.
+  if (r0 == r1) {
+    enclosed = x0 == x1 && y0 == y1;
+    theta = 0;
+    sz = 0; // make gcc happy
+  } else {
+    sz = -r0 / (r1 - r0);
+    xz = x0 + sz * (x1 - x0);
+    yz = y0 + sz * (y1 - y0);
+    enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0;
+    theta = asin(r0 / sqrt((x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz)));
+    if (r0 > r1) {
+      theta = -theta;
+    }
+  }
+  if (enclosed) {
+    a1 = 0;
+    a2 = 360;
+  } else {
+    alpha = atan2(y1 - y0, x1 - x0);
+    a1 = (180 / M_PI) * (alpha + theta) + 90;
+    a2 = (180 / M_PI) * (alpha - theta) - 90;
+    while (a2 < a1) {
+      a2 += 360;
+    }
+  }
+
+  // compute the (possibly extended) s range
+  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+  if (enclosed) {
+    sMin = 0;
+    sMax = 1;
+  } else {
+    sMin = 1;
+    sMax = 0;
+    // solve for x(s) + r(s) = xMin
+    if ((x1 + r1) - (x0 + r0) != 0) {
+      sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // solve for x(s) - r(s) = xMax
+    if ((x1 - r1) - (x0 - r0) != 0) {
+      sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // solve for y(s) + r(s) = yMin
+    if ((y1 + r1) - (y0 + r0) != 0) {
+      sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // solve for y(s) - r(s) = yMax
+    if ((y1 - r1) - (y0 - r0) != 0) {
+      sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // check against sz
+    if (r0 < r1) {
+      if (sMin < sz) {
+	sMin = sz;
+      }
+    } else if (r0 > r1) {
+      if (sMax > sz) {
+	sMax = sz;
+      }
+    }
+    // check the 'extend' flags
+    if (!shading->getExtend0() && sMin < 0) {
+      sMin = 0;
+    }
+    if (!shading->getExtend1() && sMax > 1) {
+      sMax = 1;
+    }
+  }
+
+  // generate the PS code
+  writePSFmt("/x0 {0:.4g} def\n", x0);
+  writePSFmt("/x1 {0:.4g} def\n", x1);
+  writePSFmt("/dx {0:.4g} def\n", x1 - x0);
+  writePSFmt("/y0 {0:.4g} def\n", y0);
+  writePSFmt("/y1 {0:.4g} def\n", y1);
+  writePSFmt("/dy {0:.4g} def\n", y1 - y0);
+  writePSFmt("/r0 {0:.4g} def\n", r0);
+  writePSFmt("/r1 {0:.4g} def\n", r1);
+  writePSFmt("/dr {0:.4g} def\n", r1 - r0);
+  writePSFmt("/t0 {0:.4g} def\n", t0);
+  writePSFmt("/t1 {0:.4g} def\n", t1);
+  writePSFmt("/dt {0:.4g} def\n", t1 - t0);
+  writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
+  writePSFmt("/encl {0:s} def\n", enclosed ? "true" : "false");
+  writePSFmt("/a1 {0:.4g} def\n", a1);
+  writePSFmt("/a2 {0:.4g} def\n", a2);
+  if (shading->getNFuncs() == 1) {
+    writePS("/func ");
+    cvtFunction(shading->getFunc(0));
+    writePS("def\n");
+  } else {
+    writePS("/func {\n");
+    for (i = 0; i < shading->getNFuncs(); ++i) {
+      if (i < shading->getNFuncs() - 1) {
+	writePS("dup\n");
+      }
+      cvtFunction(shading->getFunc(i));
+      writePS("exec\n");
+      if (i < shading->getNFuncs() - 1) {
+	writePS("exch\n");
+      }
+    }
+    writePS("} def\n");
+  }
+  writePSFmt("{0:.4g} {1:.4g} 0 radialSH\n", sMin, sMax);
+
+  // extend the 'enclosed' case
+  if (enclosed) {
+    // extend the smaller circle
+    if ((shading->getExtend0() && r0 <= r1) ||
+	(shading->getExtend1() && r1 < r0)) {
+      if (r0 <= r1) {
+	ta = t0;
+	ra = r0;
+	xa = x0;
+	ya = y0;
+      } else {
+	ta = t1;
+	ra = r1;
+	xa = x1;
+	ya = y1;
+      }
+      if (level == psLevel2Sep || level == psLevel3Sep) {
+	writePSFmt("{0:.4g} radialCol aload pop k\n", ta);
+      } else {
+	writePSFmt("{0:.4g} radialCol sc\n", ta);
+      }
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} 0 360 arc h f*\n", xa, ya, ra);
+    }
+
+    // extend the larger circle
+    if ((shading->getExtend0() && r0 > r1) ||
+	(shading->getExtend1() && r1 >= r0)) {
+      if (r0 > r1) {
+	ta = t0;
+	ra = r0;
+	xa = x0;
+	ya = y0;
+      } else {
+	ta = t1;
+	ra = r1;
+	xa = x1;
+	ya = y1;
+      }
+      if (level == psLevel2Sep || level == psLevel3Sep) {
+	writePSFmt("{0:.4g} radialCol aload pop k\n", ta);
+      } else {
+	writePSFmt("{0:.4g} radialCol sc\n", ta);
+      }
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} 0 360 arc h\n", xa, ya, ra);
+      writePSFmt("{0:.4g} {1:.4g} m {2:.4g} {3:.4g} l {4:.4g} {5:.4g} l {6:.4g} {7:.4g} l h f*\n",
+		 xMin, yMin, xMin, yMax, xMax, yMax, xMax, yMin);
+    }
+  }
+
+  return gTrue;
+}
+
+void PSOutputDev::clip(GfxState *state) {
+  doPath(state->getPath());
+  writePS("W\n");
+}
+
+void PSOutputDev::eoClip(GfxState *state) {
+  doPath(state->getPath());
+  writePS("W*\n");
+}
+
+void PSOutputDev::clipToStrokePath(GfxState *state) {
+  doPath(state->getPath());
+  writePS("Ws\n");
+}
+
+void PSOutputDev::doPath(GfxPath *path) {
+  GfxSubpath *subpath;
+  double x0, y0, x1, y1, x2, y2, x3, y3, x4, y4;
+  int n, m, i, j;
+
+  n = path->getNumSubpaths();
+
+  if (n == 1 && path->getSubpath(0)->getNumPoints() == 5) {
+    subpath = path->getSubpath(0);
+    x0 = subpath->getX(0);
+    y0 = subpath->getY(0);
+    x4 = subpath->getX(4);
+    y4 = subpath->getY(4);
+    if (x4 == x0 && y4 == y0) {
+      x1 = subpath->getX(1);
+      y1 = subpath->getY(1);
+      x2 = subpath->getX(2);
+      y2 = subpath->getY(2);
+      x3 = subpath->getX(3);
+      y3 = subpath->getY(3);
+      if (x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) {
+	writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} re\n",
+		   x0 < x2 ? x0 : x2, y0 < y1 ? y0 : y1,
+		   fabs(x2 - x0), fabs(y1 - y0));
+	return;
+      } else if (x0 == x3 && x1 == x2 && y0 == y1 && y2 == y3) {
+	writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} re\n",
+		   x0 < x1 ? x0 : x1, y0 < y2 ? y0 : y2,
+		   fabs(x1 - x0), fabs(y2 - y0));
+	return;
+      }
+    }
+  }
+
+  for (i = 0; i < n; ++i) {
+    subpath = path->getSubpath(i);
+    m = subpath->getNumPoints();
+    writePSFmt("{0:.4g} {1:.4g} m\n", subpath->getX(0), subpath->getY(0));
+    j = 1;
+    while (j < m) {
+      if (subpath->getCurve(j)) {
+	writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} c\n",
+		   subpath->getX(j), subpath->getY(j),
+		   subpath->getX(j+1), subpath->getY(j+1),
+		   subpath->getX(j+2), subpath->getY(j+2));
+	j += 3;
+      } else {
+	writePSFmt("{0:.4g} {1:.4g} l\n", subpath->getX(j), subpath->getY(j));
+	++j;
+      }
+    }
+    if (subpath->isClosed()) {
+      writePS("h\n");
+    }
+  }
+}
+
+void PSOutputDev::drawString(GfxState *state, GString *s) {
+  GfxFont *font;
+  int wMode;
+  Gushort *codeToGID;
+  GString *s2;
+  double dx, dy, dx2, dy2, originX, originY;
+  char *p;
+  UnicodeMap *uMap;
+  CharCode code;
+  Unicode u[8];
+  char buf[8];
+  int len, nChars, uLen, n, m, i, j;
+
+  // check for invisible text -- this is used by Acrobat Capture
+  if (state->getRender() == 3) {
+    return;
+  }
+
+  // ignore empty strings
+  if (s->getLength() == 0) {
+    return;
+  }
+
+  // get the font
+  if (!(font = state->getFont())) {
+    return;
+  }
+  wMode = font->getWMode();
+
+  // check for a subtitute 16-bit font
+  uMap = NULL;
+  codeToGID = NULL;
+  if (font->isCIDFont()) {
+    for (i = 0; i < font16EncLen; ++i) {
+      if (font->getID()->num == font16Enc[i].fontID.num &&
+	  font->getID()->gen == font16Enc[i].fontID.gen) {
+	uMap = globalParams->getUnicodeMap(font16Enc[i].enc);
+	break;
+      }
+    }
+
+  // check for a code-to-GID map
+  } else {
+    for (i = 0; i < font8InfoLen; ++i) {
+      if (font->getID()->num == font8Info[i].fontID.num &&
+	  font->getID()->gen == font8Info[i].fontID.gen) {
+	codeToGID = font8Info[i].codeToGID;
+	break;
+      }
+    }
+  }
+
+  // compute width of chars in string, ignoring char spacing and word
+  // spacing -- the Tj operator will adjust for the metrics of the
+  // font that's actually used
+  dx = dy = 0;
+  nChars = 0;
+  p = s->getCString();
+  len = s->getLength();
+  s2 = new GString();
+  while (len > 0) {
+    n = font->getNextChar(p, len, &code,
+			  u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
+			  &dx2, &dy2, &originX, &originY);
+    if (font->isCIDFont()) {
+      if (uMap) {
+	for (i = 0; i < uLen; ++i) {
+	  m = uMap->mapUnicode(u[i], buf, (int)sizeof(buf));
+	  for (j = 0; j < m; ++j) {
+	    s2->append(buf[j]);
+	  }
+	}
+	//~ this really needs to get the number of chars in the target
+	//~ encoding - which may be more than the number of Unicode
+	//~ chars
+	nChars += uLen;
+      } else {
+	s2->append((char)((code >> 8) & 0xff));
+	s2->append((char)(code & 0xff));
+	++nChars;
+      }
+    } else {
+      if (!codeToGID || codeToGID[code]) {
+	s2->append((char)code);
+      }
+    }
+    dx += dx2;
+    dy += dy2;
+    p += n;
+    len -= n;
+  }
+  dx *= state->getFontSize() * state->getHorizScaling();
+  dy *= state->getFontSize();
+  if (uMap) {
+    uMap->decRefCnt();
+  }
+
+  if (s2->getLength() > 0) {
+    writePSString(s2);
+    if (font->isCIDFont()) {
+      if (wMode) {
+	writePSFmt(" {0:d} {1:.4g} Tj16V\n", nChars, dy);
+      } else {
+	writePSFmt(" {0:d} {1:.4g} Tj16\n", nChars, dx);
+      }
+    } else {
+      writePSFmt(" {0:.4g} Tj\n", dx);
+    }
+  }
+  delete s2;
+
+  if (state->getRender() & 4) {
+    haveTextClip = gTrue;
+  }
+}
+
+void PSOutputDev::endTextObject(GfxState * /*state*/) {
+  if (haveTextClip) {
+    writePS("Tclip\n");
+    haveTextClip = gFalse;
+  }
+}
+
+void PSOutputDev::drawImageMask(GfxState * /*state*/, Object *ref, Stream *str,
+				int width, int height, GBool invert,
+				GBool inlineImg) {
+  int len;
+
+  len = height * ((width + 7) / 8);
+  switch (level) {
+  case psLevel1:
+  case psLevel1Sep:
+    doImageL1(ref, NULL, invert, inlineImg, str, width, height, len);
+    break;
+  case psLevel2:
+  case psLevel2Sep:
+    doImageL2(ref, NULL, invert, inlineImg, str, width, height, len,
+	      NULL, NULL, 0, 0, gFalse);
+    break;
+  case psLevel3:
+  case psLevel3Sep:
+    doImageL3(ref, NULL, invert, inlineImg, str, width, height, len,
+	      NULL, NULL, 0, 0, gFalse);
+    break;
+  }
+}
+
+void PSOutputDev::drawImage(GfxState * /*state*/, Object *ref, Stream *str,
+			    int width, int height, GfxImageColorMap *colorMap,
+			    int *maskColors, GBool inlineImg) {
+  int len;
+
+  len = height * ((width * colorMap->getNumPixelComps() *
+		   colorMap->getBits() + 7) / 8);
+  switch (level) {
+  case psLevel1:
+    doImageL1(ref, colorMap, gFalse, inlineImg, str, width, height, len);
+    break;
+  case psLevel1Sep:
+    //~ handle indexed, separation, ... color spaces
+    doImageL1Sep(colorMap, gFalse, inlineImg, str, width, height, len);
+    break;
+  case psLevel2:
+  case psLevel2Sep:
+    doImageL2(ref, colorMap, gFalse, inlineImg, str,
+	      width, height, len, maskColors, NULL, 0, 0, gFalse);
+    break;
+  case psLevel3:
+  case psLevel3Sep:
+    doImageL3(ref, colorMap, gFalse, inlineImg, str,
+	      width, height, len, maskColors, NULL, 0, 0, gFalse);
+    break;
+  }
+  t3Cacheable = gFalse;
+}
+
+void PSOutputDev::drawMaskedImage(GfxState * /*state*/, Object *ref, Stream *str,
+				  int width, int height,
+				  GfxImageColorMap *colorMap,
+				  Stream *maskStr,
+				  int maskWidth, int maskHeight,
+				  GBool maskInvert) {
+  int len;
+
+  len = height * ((width * colorMap->getNumPixelComps() *
+		   colorMap->getBits() + 7) / 8);
+  switch (level) {
+  case psLevel1:
+    doImageL1(ref, colorMap, gFalse, gFalse, str, width, height, len);
+    break;
+  case psLevel1Sep:
+    //~ handle indexed, separation, ... color spaces
+    doImageL1Sep(colorMap, gFalse, gFalse, str, width, height, len);
+    break;
+  case psLevel2:
+  case psLevel2Sep:
+    doImageL2(ref, colorMap, gFalse, gFalse, str, width, height, len,
+	      NULL, maskStr, maskWidth, maskHeight, maskInvert);
+    break;
+  case psLevel3:
+  case psLevel3Sep:
+    doImageL3(ref, colorMap, gFalse, gFalse, str, width, height, len,
+	      NULL, maskStr, maskWidth, maskHeight, maskInvert);
+    break;
+  }
+  t3Cacheable = gFalse;
+}
+
+void PSOutputDev::doImageL1(Object *ref, GfxImageColorMap *colorMap,
+			    GBool invert, GBool inlineImg,
+			    Stream *str, int width, int height, int len) {
+  ImageStream *imgStr;
+  Guchar pixBuf[gfxColorMaxComps];
+  GfxGray gray;
+  int col, x, y, c, i;
+
+  if ((inType3Char || preload) && !colorMap) {
+    if (inlineImg) {
+      // create an array
+      str = new FixedLengthEncoder(str, len);
+      str = new ASCIIHexEncoder(str);
+      str->reset();
+      col = 0;
+      writePS("[<");
+      do {
+	do {
+	  c = str->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == '>' || c == EOF) {
+	  break;
+	}
+	writePSChar(c);
+	++col;
+	// each line is: "<...data...><eol>"
+	// so max data length = 255 - 4 = 251
+	// but make it 240 just to be safe
+	// chunks are 2 bytes each, so we need to stop on an even col number
+	if (col == 240) {
+	  writePS(">\n<");
+	  col = 0;
+	}
+      } while (c != '>' && c != EOF);
+      writePS(">]\n");
+      writePS("0\n");
+      str->close();
+      delete str;
+    } else {
+      // set up to use the array already created by setupImages()
+      writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen());
+    }
+  }
+
+  // image/imagemask command
+  if ((inType3Char || preload) && !colorMap) {
+    writePSFmt("{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1a\n",
+	       width, height, invert ? "true" : "false",
+	       width, -height, height);
+  } else if (colorMap) {
+    writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1\n",
+	       width, height,
+	       width, -height, height);
+  } else {
+    writePSFmt("{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1\n",
+	       width, height, invert ? "true" : "false",
+	       width, -height, height);
+  }
+
+  // image data
+  if (!((inType3Char || preload) && !colorMap)) {
+
+    if (colorMap) {
+
+      // set up to process the data stream
+      imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
+			       colorMap->getBits());
+      imgStr->reset();
+
+      // process the data stream
+      i = 0;
+      for (y = 0; y < height; ++y) {
+
+	// write the line
+	for (x = 0; x < width; ++x) {
+	  imgStr->getPixel(pixBuf);
+	  colorMap->getGray(pixBuf, &gray);
+	  writePSFmt("{0:02x}", colToByte(gray));
+	  if (++i == 32) {
+	    writePSChar('\n');
+	    i = 0;
+	  }
+	}
+      }
+      if (i != 0) {
+	writePSChar('\n');
+      }
+      str->close();
+      delete imgStr;
+
+    // imagemask
+    } else {
+      str->reset();
+      i = 0;
+      for (y = 0; y < height; ++y) {
+	for (x = 0; x < width; x += 8) {
+	  writePSFmt("{0:02x}", str->getChar() & 0xff);
+	  if (++i == 32) {
+	    writePSChar('\n');
+	    i = 0;
+	  }
+	}
+      }
+      if (i != 0) {
+	writePSChar('\n');
+      }
+      str->close();
+    }
+  }
+}
+
+void PSOutputDev::doImageL1Sep(GfxImageColorMap *colorMap,
+			       GBool /*invert*/, GBool /*inlineImg*/,
+			       Stream *str, int width, int height, int /*len*/) {
+  ImageStream *imgStr;
+  Guchar *lineBuf;
+  Guchar pixBuf[gfxColorMaxComps];
+  GfxCMYK cmyk;
+  int x, y, i, comp;
+
+  // width, height, matrix, bits per component
+  writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1Sep\n",
+	     width, height,
+	     width, -height, height);
+
+  // allocate a line buffer
+  lineBuf = (Guchar *)gmalloc(4 * width);
+
+  // set up to process the data stream
+  imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
+			   colorMap->getBits());
+  imgStr->reset();
+
+  // process the data stream
+  i = 0;
+  for (y = 0; y < height; ++y) {
+
+    // read the line
+    for (x = 0; x < width; ++x) {
+      imgStr->getPixel(pixBuf);
+      colorMap->getCMYK(pixBuf, &cmyk);
+      lineBuf[4*x+0] = colToByte(cmyk.c);
+      lineBuf[4*x+1] = colToByte(cmyk.m);
+      lineBuf[4*x+2] = colToByte(cmyk.y);
+      lineBuf[4*x+3] = colToByte(cmyk.k);
+      addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
+		      colToDbl(cmyk.y), colToDbl(cmyk.k));
+    }
+
+    // write one line of each color component
+    for (comp = 0; comp < 4; ++comp) {
+      for (x = 0; x < width; ++x) {
+	writePSFmt("{0:02x}", lineBuf[4*x + comp]);
+	if (++i == 32) {
+	  writePSChar('\n');
+	  i = 0;
+	}
+      }
+    }
+  }
+
+  if (i != 0) {
+    writePSChar('\n');
+  }
+
+  str->close();
+  delete imgStr;
+  gfree(lineBuf);
+}
+
+void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap,
+			    GBool invert, GBool inlineImg,
+			    Stream *str, int width, int height, int len,
+			    int *maskColors, Stream *maskStr,
+			    int maskWidth, int maskHeight, GBool maskInvert) {
+  Stream *str2;
+  ImageStream *imgStr;
+  Guchar *line;
+  PSOutImgClipRect *rects0, *rects1, *rectsTmp, *rectsOut;
+  int rects0Len, rects1Len, rectsSize, rectsOutLen, rectsOutSize;
+  GBool emitRect, addRect, extendRect;
+  GString *s;
+  int n, numComps;
+  GBool useRLE, useASCII, useASCIIHex, useCompressed;
+  GfxSeparationColorSpace *sepCS;
+  GfxColor color;
+  GfxCMYK cmyk;
+  int c;
+  int col, i, j, x0, x1, y, maskXor;
+
+  // color key masking
+  if (maskColors && colorMap && !inlineImg) {
+    // can't read the stream twice for inline images -- but masking
+    // isn't allowed with inline images anyway
+    numComps = colorMap->getNumPixelComps();
+    imgStr = new ImageStream(str, width, numComps, colorMap->getBits());
+    imgStr->reset();
+    rects0Len = rects1Len = rectsOutLen = 0;
+    rectsSize = rectsOutSize = 64;
+    rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
+    rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
+    rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize,
+					    sizeof(PSOutImgClipRect));
+    for (y = 0; y < height; ++y) {
+      if (!(line = imgStr->getLine())) {
+	break;
+      }
+      i = 0;
+      rects1Len = 0;
+      for (x0 = 0; x0 < width; ++x0) {
+	for (j = 0; j < numComps; ++j) {
+	  if (line[x0*numComps+j] < maskColors[2*j] ||
+	      line[x0*numComps+j] > maskColors[2*j+1]) {
+	    break;
+	  }
+	}
+	if (j < numComps) {
+	  break;
+	}
+      }
+      for (x1 = x0; x1 < width; ++x1) {
+	for (j = 0; j < numComps; ++j) {
+	  if (line[x1*numComps+j] < maskColors[2*j] ||
+	      line[x1*numComps+j] > maskColors[2*j+1]) {
+	    break;
+	  }
+	}
+	if (j == numComps) {
+	  break;
+	}
+      }
+      while (x0 < width || i < rects0Len) {
+	emitRect = addRect = extendRect = gFalse;
+	if (x0 >= width) {
+	  emitRect = gTrue;
+	} else if (i >= rects0Len) {
+	  addRect = gTrue;
+	} else if (rects0[i].x0 < x0) {
+	  emitRect = gTrue;
+	} else if (x0 < rects0[i].x0) {
+	  addRect = gTrue;
+	} else if (rects0[i].x1 == x1) {
+	  extendRect = gTrue;
+	} else {
+	  emitRect = addRect = gTrue;
+	}
+	if (emitRect) {
+	  if (rectsOutLen == rectsOutSize) {
+	    rectsOutSize *= 2;
+	    rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
+						     sizeof(PSOutImgClipRect));
+	  }
+	  rectsOut[rectsOutLen].x0 = rects0[i].x0;
+	  rectsOut[rectsOutLen].x1 = rects0[i].x1;
+	  rectsOut[rectsOutLen].y0 = height - y - 1;
+	  rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1;
+	  ++rectsOutLen;
+	  ++i;
+	}
+	if (addRect || extendRect) {
+	  if (rects1Len == rectsSize) {
+	    rectsSize *= 2;
+	    rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize,
+						   sizeof(PSOutImgClipRect));
+	    rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize,
+						   sizeof(PSOutImgClipRect));
+	  }
+	  rects1[rects1Len].x0 = x0;
+	  rects1[rects1Len].x1 = x1;
+	  if (addRect) {
+	    rects1[rects1Len].y0 = y;
+	  }
+	  if (extendRect) {
+	    rects1[rects1Len].y0 = rects0[i].y0;
+	    ++i;
+	  }
+	  ++rects1Len;
+	  for (x0 = x1; x0 < width; ++x0) {
+	    for (j = 0; j < numComps; ++j) {
+	      if (line[x0*numComps+j] < maskColors[2*j] ||
+		  line[x0*numComps+j] > maskColors[2*j+1]) {
+		break;
+	      }
+	    }
+	    if (j < numComps) {
+	      break;
+	    }
+	  }
+	  for (x1 = x0; x1 < width; ++x1) {
+	    for (j = 0; j < numComps; ++j) {
+	      if (line[x1*numComps+j] < maskColors[2*j] ||
+		  line[x1*numComps+j] > maskColors[2*j+1]) {
+		break;
+	      }
+	    }
+	    if (j == numComps) {
+	      break;
+	    }
+	  }
+	}
+      }
+      rectsTmp = rects0;
+      rects0 = rects1;
+      rects1 = rectsTmp;
+      i = rects0Len;
+      rects0Len = rects1Len;
+      rects1Len = i;
+    }
+    for (i = 0; i < rects0Len; ++i) {
+      if (rectsOutLen == rectsOutSize) {
+	rectsOutSize *= 2;
+	rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
+						 sizeof(PSOutImgClipRect));
+      }
+      rectsOut[rectsOutLen].x0 = rects0[i].x0;
+      rectsOut[rectsOutLen].x1 = rects0[i].x1;
+      rectsOut[rectsOutLen].y0 = height - y - 1;
+      rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1;
+      ++rectsOutLen;
+    }
+    writePSFmt("{0:d} array 0\n", rectsOutLen * 4);
+    for (i = 0; i < rectsOutLen; ++i) {
+      writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n",
+		 rectsOut[i].x0, rectsOut[i].y0,
+		 rectsOut[i].x1 - rectsOut[i].x0,
+		 rectsOut[i].y1 - rectsOut[i].y0);
+    }
+    writePSFmt("pop {0:d} {1:d} pdfImClip\n", width, height);
+    gfree(rectsOut);
+    gfree(rects0);
+    gfree(rects1);
+    delete imgStr;
+    str->close();
+
+  // explicit masking
+  } else if (maskStr) {
+    imgStr = new ImageStream(maskStr, maskWidth, 1, 1);
+    imgStr->reset();
+    rects0Len = rects1Len = rectsOutLen = 0;
+    rectsSize = rectsOutSize = 64;
+    rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
+    rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
+    rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize,
+					    sizeof(PSOutImgClipRect));
+    maskXor = maskInvert ? 1 : 0;
+    for (y = 0; y < maskHeight; ++y) {
+      if (!(line = imgStr->getLine())) {
+	break;
+      }
+      i = 0;
+      rects1Len = 0;
+      for (x0 = 0; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ;
+      for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ;
+      while (x0 < maskWidth || i < rects0Len) {
+	emitRect = addRect = extendRect = gFalse;
+	if (x0 >= maskWidth) {
+	  emitRect = gTrue;
+	} else if (i >= rects0Len) {
+	  addRect = gTrue;
+	} else if (rects0[i].x0 < x0) {
+	  emitRect = gTrue;
+	} else if (x0 < rects0[i].x0) {
+	  addRect = gTrue;
+	} else if (rects0[i].x1 == x1) {
+	  extendRect = gTrue;
+	} else {
+	  emitRect = addRect = gTrue;
+	}
+	if (emitRect) {
+	  if (rectsOutLen == rectsOutSize) {
+	    rectsOutSize *= 2;
+	    rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
+						     sizeof(PSOutImgClipRect));
+	  }
+	  rectsOut[rectsOutLen].x0 = rects0[i].x0;
+	  rectsOut[rectsOutLen].x1 = rects0[i].x1;
+	  rectsOut[rectsOutLen].y0 = maskHeight - y - 1;
+	  rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1;
+	  ++rectsOutLen;
+	  ++i;
+	}
+	if (addRect || extendRect) {
+	  if (rects1Len == rectsSize) {
+	    rectsSize *= 2;
+	    rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize,
+						   sizeof(PSOutImgClipRect));
+	    rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize,
+						   sizeof(PSOutImgClipRect));
+	  }
+	  rects1[rects1Len].x0 = x0;
+	  rects1[rects1Len].x1 = x1;
+	  if (addRect) {
+	    rects1[rects1Len].y0 = y;
+	  }
+	  if (extendRect) {
+	    rects1[rects1Len].y0 = rects0[i].y0;
+	    ++i;
+	  }
+	  ++rects1Len;
+	  for (x0 = x1; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ;
+	  for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ;
+	}
+      }
+      rectsTmp = rects0;
+      rects0 = rects1;
+      rects1 = rectsTmp;
+      i = rects0Len;
+      rects0Len = rects1Len;
+      rects1Len = i;
+    }
+    for (i = 0; i < rects0Len; ++i) {
+      if (rectsOutLen == rectsOutSize) {
+	rectsOutSize *= 2;
+	rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
+						 sizeof(PSOutImgClipRect));
+      }
+      rectsOut[rectsOutLen].x0 = rects0[i].x0;
+      rectsOut[rectsOutLen].x1 = rects0[i].x1;
+      rectsOut[rectsOutLen].y0 = maskHeight - y - 1;
+      rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1;
+      ++rectsOutLen;
+    }
+    writePSFmt("{0:d} array 0\n", rectsOutLen * 4);
+    for (i = 0; i < rectsOutLen; ++i) {
+      writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n",
+		 rectsOut[i].x0, rectsOut[i].y0,
+		 rectsOut[i].x1 - rectsOut[i].x0,
+		 rectsOut[i].y1 - rectsOut[i].y0);
+    }
+    writePSFmt("pop {0:d} {1:d} pdfImClip\n", maskWidth, maskHeight);
+    gfree(rectsOut);
+    gfree(rects0);
+    gfree(rects1);
+    delete imgStr;
+    maskStr->close();
+  }
+
+  // color space
+  if (colorMap) {
+    dumpColorSpaceL2(colorMap->getColorSpace(), gFalse, gTrue, gFalse);
+    writePS(" setcolorspace\n");
+  }
+
+  useASCIIHex = globalParams->getPSASCIIHex();
+
+  // set up the image data
+  if (mode == psModeForm || inType3Char || preload) {
+    if (inlineImg) {
+      // create an array
+      str2 = new FixedLengthEncoder(str, len);
+      str2 = new RunLengthEncoder(str2);
+      if (useASCIIHex) {
+	str2 = new ASCIIHexEncoder(str2);
+      } else {
+	str2 = new ASCII85Encoder(str2);
+      }
+      str2->reset();
+      col = 0;
+      writePS((char *)(useASCIIHex ? "[<" : "[<~"));
+      do {
+	do {
+	  c = str2->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	  break;
+	}
+	if (c == 'z') {
+	  writePSChar(c);
+	  ++col;
+	} else {
+	  writePSChar(c);
+	  ++col;
+	  for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
+	    do {
+	      c = str2->getChar();
+	    } while (c == '\n' || c == '\r');
+	    if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	      break;
+	    }
+	    writePSChar(c);
+	    ++col;
+	  }
+	}
+	// each line is: "<~...data...~><eol>"
+	// so max data length = 255 - 6 = 249
+	// chunks are 1 or 5 bytes each, so we have to stop at 245
+	// but make it 240 just to be safe
+	if (col > 240) {
+	  writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~"));
+	  col = 0;
+	}
+      } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
+      writePS((char *)(useASCIIHex ? ">\n" : "~>\n"));
+      // add an extra entry because the RunLengthDecode filter may
+      // read past the end
+      writePS("<>]\n");
+      writePS("0\n");
+      str2->close();
+      delete str2;
+    } else {
+      // set up to use the array already created by setupImages()
+      writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen());
+    }
+  }
+
+  // image dictionary
+  writePS("<<\n  /ImageType 1\n");
+
+  // width, height, matrix, bits per component
+  writePSFmt("  /Width {0:d}\n", width);
+  writePSFmt("  /Height {0:d}\n", height);
+  writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
+	     width, -height, height);
+  if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
+    writePS("  /BitsPerComponent 8\n");
+  } else {
+    writePSFmt("  /BitsPerComponent {0:d}\n",
+	       colorMap ? colorMap->getBits() : 1);
+  }
+
+  // decode 
+  if (colorMap) {
+    writePS("  /Decode [");
+    if ((level == psLevel2Sep || level == psLevel3Sep) &&
+	colorMap->getColorSpace()->getMode() == csSeparation) {
+      // this matches up with the code in the pdfImSep operator
+      n = (1 << colorMap->getBits()) - 1;
+      writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n,
+		 colorMap->getDecodeHigh(0) * n);
+    } else if (colorMap->getColorSpace()->getMode() == csDeviceN) {
+      numComps = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
+	           getAlt()->getNComps();
+      for (i = 0; i < numComps; ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePS("0 1");
+      }
+    } else {
+      numComps = colorMap->getNumPixelComps();
+      for (i = 0; i < numComps; ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePSFmt("{0:.4g} {1:.4g}",
+		   colorMap->getDecodeLow(i), colorMap->getDecodeHigh(i));
+      }
+    }
+    writePS("]\n");
+  } else {
+    writePSFmt("  /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1);
+  }
+
+  // data source
+  if (mode == psModeForm || inType3Char || preload) {
+    writePS("  /DataSource { 2 copy get exch 1 add exch }\n");
+  } else {
+    writePS("  /DataSource currentfile\n");
+  }
+
+  // filters
+  s = str->getPSFilter(level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3,
+		       "    ");
+  if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) ||
+      inlineImg || !s) {
+    useRLE = gTrue;
+    useASCII = !(mode == psModeForm || inType3Char || preload);
+    useCompressed = gFalse;
+  } else {
+    useRLE = gFalse;
+    useASCII = str->isBinary() &&
+               !(mode == psModeForm || inType3Char || preload);
+    useCompressed = gTrue;
+  }
+  if (useASCII) {
+    writePSFmt("    /ASCII{0:s}Decode filter\n",
+	       useASCIIHex ? "Hex" : "85");
+  }
+  if (useRLE) {
+    writePS("    /RunLengthDecode filter\n");
+  }
+  if (useCompressed) {
+    writePS(s->getCString());
+  }
+  if (s) {
+    delete s;
+  }
+
+  if (mode == psModeForm || inType3Char || preload) {
+
+    // end of image dictionary
+    writePSFmt(">>\n{0:s}\n", colorMap ? "image" : "imagemask");
+
+    // get rid of the array and index
+    writePS("pop pop\n");
+
+  } else {
+
+    // cut off inline image streams at appropriate length
+    if (inlineImg) {
+      str = new FixedLengthEncoder(str, len);
+    } else if (useCompressed) {
+      str = str->getUndecodedStream();
+    }
+
+    // recode DeviceN data
+    if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
+      str = new DeviceNRecoder(str, width, height, colorMap);
+    }
+
+    // add RunLengthEncode and ASCIIHex/85 encode filters
+    if (useRLE) {
+      str = new RunLengthEncoder(str);
+    }
+    if (useASCII) {
+      if (useASCIIHex) {
+	str = new ASCIIHexEncoder(str);
+      } else {
+	str = new ASCII85Encoder(str);
+      }
+    }
+
+    // end of image dictionary
+    writePS(">>\n");
+#if OPI_SUPPORT
+    if (opi13Nest) {
+      if (inlineImg) {
+	// this can't happen -- OPI dictionaries are in XObjects
+	error(-1, "Internal: OPI in inline image");
+	n = 0;
+      } else {
+	// need to read the stream to count characters -- the length
+	// is data-dependent (because of ASCII and RLE filters)
+	str->reset();
+	n = 0;
+	while ((c = str->getChar()) != EOF) {
+	  ++n;
+	}
+	str->close();
+      }
+      // +6/7 for "pdfIm\n" / "pdfImM\n"
+      // +8 for newline + trailer
+      n += colorMap ? 14 : 15;
+      writePSFmt("%%BeginData: {0:d} Hex Bytes\n", n);
+    }
+#endif
+    if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap &&
+	colorMap->getColorSpace()->getMode() == csSeparation) {
+      color.c[0] = gfxColorComp1;
+      sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace();
+      sepCS->getCMYK(&color, &cmyk);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n",
+		 colToDbl(cmyk.c), colToDbl(cmyk.m),
+		 colToDbl(cmyk.y), colToDbl(cmyk.k),
+		 sepCS->getName());
+    } else {
+      writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM");
+    }
+
+    // copy the stream data
+    str->reset();
+    while ((c = str->getChar()) != EOF) {
+      writePSChar(c);
+    }
+    str->close();
+
+    // add newline and trailer to the end
+    writePSChar('\n');
+    writePS("%-EOD-\n");
+#if OPI_SUPPORT
+    if (opi13Nest) {
+      writePS("%%EndData\n");
+    }
+#endif
+
+    // delete encoders
+    if (useRLE || useASCII || inlineImg) {
+      delete str;
+    }
+  }
+
+  if ((maskColors && colorMap && !inlineImg) || maskStr) {
+    writePS("pdfImClipEnd\n");
+  }
+}
+
+//~ this doesn't currently support OPI
+void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap,
+			    GBool invert, GBool inlineImg,
+			    Stream *str, int width, int height, int len,
+			    int *maskColors, Stream *maskStr,
+			    int maskWidth, int maskHeight, GBool maskInvert) {
+  Stream *str2;
+  GString *s;
+  int n, numComps;
+  GBool useRLE, useASCII, useASCIIHex, useCompressed;
+  GBool maskUseRLE, maskUseASCII, maskUseCompressed;
+  GfxSeparationColorSpace *sepCS;
+  GfxColor color;
+  GfxCMYK cmyk;
+  int c;
+  int col, i;
+
+  useASCIIHex = globalParams->getPSASCIIHex();
+  useRLE = useASCII = useCompressed = gFalse; // make gcc happy
+  maskUseRLE = maskUseASCII = maskUseCompressed = gFalse; // make gcc happy
+
+  // color space
+  if (colorMap) {
+    dumpColorSpaceL2(colorMap->getColorSpace(), gFalse, gTrue, gFalse);
+    writePS(" setcolorspace\n");
+  }
+
+  // set up the image data
+  if (mode == psModeForm || inType3Char || preload) {
+    if (inlineImg) {
+      // create an array
+      str2 = new FixedLengthEncoder(str, len);
+      str2 = new RunLengthEncoder(str2);
+      if (useASCIIHex) {
+	str2 = new ASCIIHexEncoder(str2);
+      } else {
+	str2 = new ASCII85Encoder(str2);
+      }
+      str2->reset();
+      col = 0;
+      writePS((char *)(useASCIIHex ? "[<" : "[<~"));
+      do {
+	do {
+	  c = str2->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	  break;
+	}
+	if (c == 'z') {
+	  writePSChar(c);
+	  ++col;
+	} else {
+	  writePSChar(c);
+	  ++col;
+	  for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
+	    do {
+	      c = str2->getChar();
+	    } while (c == '\n' || c == '\r');
+	    if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	      break;
+	    }
+	    writePSChar(c);
+	    ++col;
+	  }
+	}
+	// each line is: "<~...data...~><eol>"
+	// so max data length = 255 - 6 = 249
+	// chunks are 1 or 5 bytes each, so we have to stop at 245
+	// but make it 240 just to be safe
+	if (col > 240) {
+	  writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~"));
+	  col = 0;
+	}
+      } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
+      writePS((char *)(useASCIIHex ? ">\n" : "~>\n"));
+      // add an extra entry because the RunLengthDecode filter may
+      // read past the end
+      writePS("<>]\n");
+      writePS("0\n");
+      str2->close();
+      delete str2;
+    } else {
+      // set up to use the array already created by setupImages()
+      writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen());
+    }
+  }
+
+  // explicit masking
+  if (maskStr) {
+    writePS("<<\n  /ImageType 3\n");
+    writePS("  /InterleaveType 3\n");
+    writePS("  /DataDict\n");
+  }
+
+  // image (data) dictionary
+  writePSFmt("<<\n  /ImageType {0:d}\n", (maskColors && colorMap) ? 4 : 1);
+
+  // color key masking
+  if (maskColors && colorMap) {
+    writePS("  /MaskColor [\n");
+    numComps = colorMap->getNumPixelComps();
+    for (i = 0; i < 2 * numComps; i += 2) {
+      writePSFmt("    {0:d} {1:d}\n", maskColors[i], maskColors[i+1]);
+    }
+    writePS("  ]\n");
+  }
+
+  // width, height, matrix, bits per component
+  writePSFmt("  /Width {0:d}\n", width);
+  writePSFmt("  /Height {0:d}\n", height);
+  writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
+	     width, -height, height);
+  if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
+    writePS("  /BitsPerComponent 8\n");
+  } else {
+    writePSFmt("  /BitsPerComponent {0:d}\n",
+	       colorMap ? colorMap->getBits() : 1);
+  }
+
+  // decode 
+  if (colorMap) {
+    writePS("  /Decode [");
+    if ((level == psLevel2Sep || level == psLevel3Sep) &&
+	colorMap->getColorSpace()->getMode() == csSeparation) {
+      // this matches up with the code in the pdfImSep operator
+      n = (1 << colorMap->getBits()) - 1;
+      writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n,
+		 colorMap->getDecodeHigh(0) * n);
+    } else if (colorMap->getColorSpace()->getMode() == csDeviceN) {
+      numComps = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
+	           getAlt()->getNComps();
+      for (i = 0; i < numComps; ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePS("0 1");
+      }
+    } else {
+      numComps = colorMap->getNumPixelComps();
+      for (i = 0; i < numComps; ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(i),
+		   colorMap->getDecodeHigh(i));
+      }
+    }
+    writePS("]\n");
+  } else {
+    writePSFmt("  /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1);
+  }
+
+  // data source
+  if (mode == psModeForm || inType3Char || preload) {
+    writePS("  /DataSource { 2 copy get exch 1 add exch }\n");
+  } else {
+    writePS("  /DataSource currentfile\n");
+  }
+
+  // filters
+  s = str->getPSFilter(level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3,
+		       "    ");
+  if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) ||
+      inlineImg || !s) {
+    useRLE = gTrue;
+    useASCII = !(mode == psModeForm || inType3Char || preload);
+    useCompressed = gFalse;
+  } else {
+    useRLE = gFalse;
+    useASCII = str->isBinary() &&
+               !(mode == psModeForm || inType3Char || preload);
+    useCompressed = gTrue;
+  }
+  if (useASCII) {
+    writePSFmt("    /ASCII{0:s}Decode filter\n",
+	       useASCIIHex ? "Hex" : "85");
+  }
+  if (useRLE) {
+    writePS("    /RunLengthDecode filter\n");
+  }
+  if (useCompressed) {
+    writePS(s->getCString());
+  }
+  if (s) {
+    delete s;
+  }
+
+  // end of image (data) dictionary
+  writePS(">>\n");
+
+  // explicit masking
+  if (maskStr) {
+    writePS("  /MaskDict\n");
+    writePS("<<\n");
+    writePS("  /ImageType 1\n");
+    writePSFmt("  /Width {0:d}\n", maskWidth);
+    writePSFmt("  /Height {0:d}\n", maskHeight);
+    writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
+	       maskWidth, -maskHeight, maskHeight);
+    writePS("  /BitsPerComponent 1\n");
+    writePSFmt("  /Decode [{0:d} {1:d}]\n",
+	       maskInvert ? 1 : 0, maskInvert ? 0 : 1);
+
+    // mask data source
+    writePS("  /DataSource currentfile\n");
+    s = maskStr->getPSFilter(3, "    ");
+    if (!s) {
+      maskUseRLE = gTrue;
+      maskUseASCII = gTrue;
+      maskUseCompressed = gFalse;
+    } else {
+      maskUseRLE = gFalse;
+      maskUseASCII = maskStr->isBinary();
+      maskUseCompressed = gTrue;
+    }
+    if (maskUseASCII) {
+      writePSFmt("    /ASCII{0:s}Decode filter\n",
+		 useASCIIHex ? "Hex" : "85");
+    }
+    if (maskUseRLE) {
+      writePS("    /RunLengthDecode filter\n");
+    }
+    if (maskUseCompressed) {
+      writePS(s->getCString());
+    }
+    if (s) {
+      delete s;
+    }
+
+    writePS(">>\n");
+    writePS(">>\n");
+  }
+
+  if (mode == psModeForm || inType3Char || preload) {
+
+    // image command
+    writePSFmt("{0:s}\n", colorMap ? "image" : "imagemask");
+
+  } else {
+
+    if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap &&
+	colorMap->getColorSpace()->getMode() == csSeparation) {
+      color.c[0] = gfxColorComp1;
+      sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace();
+      sepCS->getCMYK(&color, &cmyk);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n",
+		 colToDbl(cmyk.c), colToDbl(cmyk.m),
+		 colToDbl(cmyk.y), colToDbl(cmyk.k),
+		 sepCS->getName());
+    } else {
+      writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM");
+    }
+
+  }
+
+  // explicit masking
+  if (maskStr) {
+
+    if (maskUseCompressed) {
+      maskStr = maskStr->getUndecodedStream();
+    }
+
+    // add RunLengthEncode and ASCIIHex/85 encode filters
+    if (maskUseRLE) {
+      maskStr = new RunLengthEncoder(maskStr);
+    }
+    if (maskUseASCII) {
+      if (useASCIIHex) {
+	maskStr = new ASCIIHexEncoder(maskStr);
+      } else {
+	maskStr = new ASCII85Encoder(maskStr);
+      }
+    }
+
+    // copy the stream data
+    maskStr->reset();
+    while ((c = maskStr->getChar()) != EOF) {
+      writePSChar(c);
+    }
+    maskStr->close();
+    writePSChar('\n');
+
+    // delete encoders
+    if (maskUseRLE || maskUseASCII) {
+      delete maskStr;
+    }
+  }
+
+  // get rid of the array and index
+  if (mode == psModeForm || inType3Char || preload) {
+    writePS("pop pop\n");
+
+  // image data
+  } else {
+
+    // cut off inline image streams at appropriate length
+    if (inlineImg) {
+      str = new FixedLengthEncoder(str, len);
+    } else if (useCompressed) {
+      str = str->getUndecodedStream();
+    }
+
+    // recode DeviceN data
+    if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
+      str = new DeviceNRecoder(str, width, height, colorMap);
+    }
+
+    // add RunLengthEncode and ASCIIHex/85 encode filters
+    if (useRLE) {
+      str = new RunLengthEncoder(str);
+    }
+    if (useASCII) {
+      if (useASCIIHex) {
+	str = new ASCIIHexEncoder(str);
+      } else {
+	str = new ASCII85Encoder(str);
+      }
+    }
+
+    // copy the stream data
+    str->reset();
+    while ((c = str->getChar()) != EOF) {
+      writePSChar(c);
+    }
+    str->close();
+
+    // add newline and trailer to the end
+    writePSChar('\n');
+    writePS("%-EOD-\n");
+
+    // delete encoders
+    if (useRLE || useASCII || inlineImg) {
+      delete str;
+    }
+  }
+}
+
+void PSOutputDev::dumpColorSpaceL2(GfxColorSpace *colorSpace,
+				   GBool genXform, GBool updateColors,
+				   GBool map01) {
+  GfxCalGrayColorSpace *calGrayCS;
+  GfxCalRGBColorSpace *calRGBCS;
+  GfxLabColorSpace *labCS;
+  GfxIndexedColorSpace *indexedCS;
+  GfxSeparationColorSpace *separationCS;
+  GfxDeviceNColorSpace *deviceNCS;
+  GfxColorSpace *baseCS;
+  Guchar *lookup, *p;
+  double x[gfxColorMaxComps], y[gfxColorMaxComps];
+  double low[gfxColorMaxComps], range[gfxColorMaxComps];
+  GfxColor color;
+  GfxCMYK cmyk;
+  Function *func;
+  int n, numComps, numAltComps;
+  int byte;
+  int i, j, k;
+
+  switch (colorSpace->getMode()) {
+
+  case csDeviceGray:
+    writePS("/DeviceGray");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessBlack;
+    }
+    break;
+
+  case csCalGray:
+    calGrayCS = (GfxCalGrayColorSpace *)colorSpace;
+    writePS("[/CIEBasedA <<\n");
+    writePSFmt(" /DecodeA {{{0:.4g} exp}} bind\n", calGrayCS->getGamma());
+    writePSFmt(" /MatrixA [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calGrayCS->getWhiteX(), calGrayCS->getWhiteY(),
+	       calGrayCS->getWhiteZ());
+    writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calGrayCS->getWhiteX(), calGrayCS->getWhiteY(),
+	       calGrayCS->getWhiteZ());
+    writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calGrayCS->getBlackX(), calGrayCS->getBlackY(),
+	       calGrayCS->getBlackZ());
+    writePS(">>]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessBlack;
+    }
+    break;
+
+  case csDeviceRGB:
+    writePS("/DeviceRGB");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessCMYK;
+    }
+    break;
+
+  case csCalRGB:
+    calRGBCS = (GfxCalRGBColorSpace *)colorSpace;
+    writePS("[/CIEBasedABC <<\n");
+    writePSFmt(" /DecodeABC [{{{0:.4g} exp}} bind {{{1:.4g} exp}} bind {{{2:.4g} exp}} bind]\n",
+	       calRGBCS->getGammaR(), calRGBCS->getGammaG(),
+	       calRGBCS->getGammaB());
+    writePSFmt(" /MatrixABC [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} {6:.4g} {7:.4g} {8:.4g}]\n",
+	       calRGBCS->getMatrix()[0], calRGBCS->getMatrix()[1],
+	       calRGBCS->getMatrix()[2], calRGBCS->getMatrix()[3],
+	       calRGBCS->getMatrix()[4], calRGBCS->getMatrix()[5],
+	       calRGBCS->getMatrix()[6], calRGBCS->getMatrix()[7],
+	       calRGBCS->getMatrix()[8]);
+    writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calRGBCS->getWhiteX(), calRGBCS->getWhiteY(),
+	       calRGBCS->getWhiteZ());
+    writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calRGBCS->getBlackX(), calRGBCS->getBlackY(),
+	       calRGBCS->getBlackZ());
+    writePS(">>]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessCMYK;
+    }
+    break;
+
+  case csDeviceCMYK:
+    writePS("/DeviceCMYK");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessCMYK;
+    }
+    break;
+
+  case csLab:
+    labCS = (GfxLabColorSpace *)colorSpace;
+    writePS("[/CIEBasedABC <<\n");
+    if (map01) {
+      writePS(" /RangeABC [0 1 0 1 0 1]\n");
+      writePSFmt(" /DecodeABC [{{100 mul 16 add 116 div}} bind {{{0:.4g} mul {1:.4g} add}} bind {{{2:.4g} mul {3:.4g} add}} bind]\n",
+		 (labCS->getAMax() - labCS->getAMin()) / 500.0,
+		 labCS->getAMin() / 500.0,
+		 (labCS->getBMax() - labCS->getBMin()) / 200.0,
+		 labCS->getBMin() / 200.0);
+    } else {
+      writePSFmt(" /RangeABC [0 100 {0:.4g} {1:.4g} {2:.4g} {3:.4g}]\n",
+		 labCS->getAMin(), labCS->getAMax(),
+		 labCS->getBMin(), labCS->getBMax());
+      writePS(" /DecodeABC [{16 add 116 div} bind {500 div} bind {200 div} bind]\n");
+    }
+    writePS(" /MatrixABC [1 1 1 1 0 0 0 0 -1]\n");
+    writePS(" /DecodeLMN\n");
+    writePS("   [{dup 6 29 div ge {dup dup mul mul}\n");
+    writePSFmt("     {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n",
+	       labCS->getWhiteX());
+    writePS("    {dup 6 29 div ge {dup dup mul mul}\n");
+    writePSFmt("     {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n",
+	       labCS->getWhiteY());
+    writePS("    {dup 6 29 div ge {dup dup mul mul}\n");
+    writePSFmt("     {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind]\n",
+	       labCS->getWhiteZ());
+    writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       labCS->getWhiteX(), labCS->getWhiteY(), labCS->getWhiteZ());
+    writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       labCS->getBlackX(), labCS->getBlackY(), labCS->getBlackZ());
+    writePS(">>]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessCMYK;
+    }
+    break;
+
+  case csICCBased:
+    // there is no transform function to the alternate color space, so
+    // we can use it directly
+    dumpColorSpaceL2(((GfxICCBasedColorSpace *)colorSpace)->getAlt(),
+		     genXform, updateColors, gFalse);
+    break;
+
+  case csIndexed:
+    indexedCS = (GfxIndexedColorSpace *)colorSpace;
+    baseCS = indexedCS->getBase();
+    writePS("[/Indexed ");
+    dumpColorSpaceL2(baseCS, gFalse, gFalse, gTrue);
+    n = indexedCS->getIndexHigh();
+    numComps = baseCS->getNComps();
+    lookup = indexedCS->getLookup();
+    writePSFmt(" {0:d} <\n", n);
+    if (baseCS->getMode() == csDeviceN) {
+      func = ((GfxDeviceNColorSpace *)baseCS)->getTintTransformFunc();
+      baseCS->getDefaultRanges(low, range, indexedCS->getIndexHigh());
+      if (((GfxDeviceNColorSpace *)baseCS)->getAlt()->getMode() == csLab) {
+	labCS = (GfxLabColorSpace *)((GfxDeviceNColorSpace *)baseCS)->getAlt();
+      } else {
+	labCS = NULL;
+      }
+      numAltComps = ((GfxDeviceNColorSpace *)baseCS)->getAlt()->getNComps();
+      p = lookup;
+      for (i = 0; i <= n; i += 8) {
+	writePS("  ");
+	for (j = i; j < i+8 && j <= n; ++j) {
+	  for (k = 0; k < numComps; ++k) {
+	    x[k] = low[k] + (*p++ / 255.0) * range[k];
+	  }
+	  func->transform(x, y);
+	  if (labCS) {
+	    y[0] /= 100.0;
+	    y[1] = (y[1] - labCS->getAMin()) /
+	           (labCS->getAMax() - labCS->getAMin());
+	    y[2] = (y[2] - labCS->getBMin()) /
+	           (labCS->getBMax() - labCS->getBMin());
+	  }
+	  for (k = 0; k < numAltComps; ++k) {
+	    byte = (int)(y[k] * 255 + 0.5);
+	    if (byte < 0) {
+	      byte = 0;
+	    } else if (byte > 255) {
+	      byte = 255;
+	    }
+	    writePSFmt("{0:02x}", byte);
+	  }
+	  if (updateColors) {
+	    color.c[0] = dblToCol(j);
+	    indexedCS->getCMYK(&color, &cmyk);
+	    addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
+			    colToDbl(cmyk.y), colToDbl(cmyk.k));
+	  }
+	}
+	writePS("\n");
+      }
+    } else {
+      for (i = 0; i <= n; i += 8) {
+	writePS("  ");
+	for (j = i; j < i+8 && j <= n; ++j) {
+	  for (k = 0; k < numComps; ++k) {
+	    writePSFmt("{0:02x}", lookup[j * numComps + k]);
+	  }
+	  if (updateColors) {
+	    color.c[0] = dblToCol(j);
+	    indexedCS->getCMYK(&color, &cmyk);
+	    addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
+			    colToDbl(cmyk.y), colToDbl(cmyk.k));
+	  }
+	}
+	writePS("\n");
+      }
+    }
+    writePS(">]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    break;
+
+  case csSeparation:
+    separationCS = (GfxSeparationColorSpace *)colorSpace;
+    writePS("[/Separation ");
+    writePSString(separationCS->getName());
+    writePS(" ");
+    dumpColorSpaceL2(separationCS->getAlt(), gFalse, gFalse, gFalse);
+    writePS("\n");
+    cvtFunction(separationCS->getFunc());
+    writePS("]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      addCustomColor(separationCS);
+    }
+    break;
+
+  case csDeviceN:
+    // DeviceN color spaces are a Level 3 PostScript feature.
+    deviceNCS = (GfxDeviceNColorSpace *)colorSpace;
+    dumpColorSpaceL2(deviceNCS->getAlt(), gFalse, updateColors, map01);
+    if (genXform) {
+      writePS(" ");
+      cvtFunction(deviceNCS->getTintTransformFunc());
+    }
+    break;
+
+  case csPattern:
+    //~ unimplemented
+    break;
+  }
+}
+
+#if OPI_SUPPORT
+void PSOutputDev::opiBegin(GfxState *state, Dict *opiDict) {
+  Object dict;
+
+  if (globalParams->getPSOPI()) {
+    opiDict->lookup("2.0", &dict);
+    if (dict.isDict()) {
+      opiBegin20(state, dict.getDict());
+      dict.free();
+    } else {
+      dict.free();
+      opiDict->lookup("1.3", &dict);
+      if (dict.isDict()) {
+	opiBegin13(state, dict.getDict());
+      }
+      dict.free();
+    }
+  }
+}
+
+void PSOutputDev::opiBegin20(GfxState *state, Dict *dict) {
+  Object obj1, obj2, obj3, obj4;
+  double width, height, left, right, top, bottom;
+  int w, h;
+  int i;
+
+  writePS("%%BeginOPI: 2.0\n");
+  writePS("%%Distilled\n");
+
+  dict->lookup("F", &obj1);
+  if (getFileSpec(&obj1, &obj2)) {
+    writePSFmt("%%ImageFileName: {0:t}\n", obj2.getString());
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("MainImage", &obj1);
+  if (obj1.isString()) {
+    writePSFmt("%%MainImage: {0:t}\n", obj1.getString());
+  }
+  obj1.free();
+
+  //~ ignoring 'Tags' entry
+  //~ need to use writePSString() and deal with >255-char lines
+
+  dict->lookup("Size", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    width = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    height = obj2.getNum();
+    obj2.free();
+    writePSFmt("%%ImageDimensions: {0:.4g} {1:.4g}\n", width, height);
+  }
+  obj1.free();
+
+  dict->lookup("CropRect", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 4) {
+    obj1.arrayGet(0, &obj2);
+    left = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    top = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    right = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    bottom = obj2.getNum();
+    obj2.free();
+    writePSFmt("%%ImageCropRect: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n",
+	       left, top, right, bottom);
+  }
+  obj1.free();
+
+  dict->lookup("Overprint", &obj1);
+  if (obj1.isBool()) {
+    writePSFmt("%%ImageOverprint: {0:s}\n", obj1.getBool() ? "true" : "false");
+  }
+  obj1.free();
+
+  dict->lookup("Inks", &obj1);
+  if (obj1.isName()) {
+    writePSFmt("%%ImageInks: {0:s}\n", obj1.getName());
+  } else if (obj1.isArray() && obj1.arrayGetLength() >= 1) {
+    obj1.arrayGet(0, &obj2);
+    if (obj2.isName()) {
+      writePSFmt("%%ImageInks: {0:s} {1:d}",
+		 obj2.getName(), (obj1.arrayGetLength() - 1) / 2);
+      for (i = 1; i+1 < obj1.arrayGetLength(); i += 2) {
+	obj1.arrayGet(i, &obj3);
+	obj1.arrayGet(i+1, &obj4);
+	if (obj3.isString() && obj4.isNum()) {
+	  writePS(" ");
+	  writePSString(obj3.getString());
+	  writePSFmt(" {0:.4g}", obj4.getNum());
+	}
+	obj3.free();
+	obj4.free();
+      }
+      writePS("\n");
+    }
+    obj2.free();
+  }
+  obj1.free();
+
+  writePS("gsave\n");
+
+  writePS("%%BeginIncludedImage\n");
+
+  dict->lookup("IncludedImageDimensions", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    w = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    h = obj2.getInt();
+    obj2.free();
+    writePSFmt("%%IncludedImageDimensions: {0:d} {1:d}\n", w, h);
+  }
+  obj1.free();
+
+  dict->lookup("IncludedImageQuality", &obj1);
+  if (obj1.isNum()) {
+    writePSFmt("%%IncludedImageQuality: {0:.4g}\n", obj1.getNum());
+  }
+  obj1.free();
+
+  ++opi20Nest;
+}
+
+void PSOutputDev::opiBegin13(GfxState *state, Dict *dict) {
+  Object obj1, obj2;
+  int left, right, top, bottom, samples, bits, width, height;
+  double c, m, y, k;
+  double llx, lly, ulx, uly, urx, ury, lrx, lry;
+  double tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry;
+  double horiz, vert;
+  int i, j;
+
+  writePS("save\n");
+  writePS("/opiMatrix2 matrix currentmatrix def\n");
+  writePS("opiMatrix setmatrix\n");
+
+  dict->lookup("F", &obj1);
+  if (getFileSpec(&obj1, &obj2)) {
+    writePSFmt("%ALDImageFileName: {0:t}\n", obj2.getString());
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("CropRect", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 4) {
+    obj1.arrayGet(0, &obj2);
+    left = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    top = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    right = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    bottom = obj2.getInt();
+    obj2.free();
+    writePSFmt("%ALDImageCropRect: {0:d} {1:d} {2:d} {3:d}\n",
+	       left, top, right, bottom);
+  }
+  obj1.free();
+
+  dict->lookup("Color", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 5) {
+    obj1.arrayGet(0, &obj2);
+    c = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    m = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    y = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    k = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(4, &obj2);
+    if (obj2.isString()) {
+      writePSFmt("%ALDImageColor: {0:.4g} {1:.4g} {2:.4g} {3:.4g} ",
+		 c, m, y, k);
+      writePSString(obj2.getString());
+      writePS("\n");
+    }
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("ColorType", &obj1);
+  if (obj1.isName()) {
+    writePSFmt("%ALDImageColorType: {0:s}\n", obj1.getName());
+  }
+  obj1.free();
+
+  //~ ignores 'Comments' entry
+  //~ need to handle multiple lines
+
+  dict->lookup("CropFixed", &obj1);
+  if (obj1.isArray()) {
+    obj1.arrayGet(0, &obj2);
+    ulx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    uly = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    lrx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    lry = obj2.getNum();
+    obj2.free();
+    writePSFmt("%ALDImageCropFixed: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n",
+	       ulx, uly, lrx, lry);
+  }
+  obj1.free();
+
+  dict->lookup("GrayMap", &obj1);
+  if (obj1.isArray()) {
+    writePS("%ALDImageGrayMap:");
+    for (i = 0; i < obj1.arrayGetLength(); i += 16) {
+      if (i > 0) {
+	writePS("\n%%+");
+      }
+      for (j = 0; j < 16 && i+j < obj1.arrayGetLength(); ++j) {
+	obj1.arrayGet(i+j, &obj2);
+	writePSFmt(" {0:d}", obj2.getInt());
+	obj2.free();
+      }
+    }
+    writePS("\n");
+  }
+  obj1.free();
+
+  dict->lookup("ID", &obj1);
+  if (obj1.isString()) {
+    writePSFmt("%ALDImageID: {0:t}\n", obj1.getString());
+  }
+  obj1.free();
+
+  dict->lookup("ImageType", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    samples = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    bits = obj2.getInt();
+    obj2.free();
+    writePSFmt("%ALDImageType: {0:d} {1:d}\n", samples, bits);
+  }
+  obj1.free();
+
+  dict->lookup("Overprint", &obj1);
+  if (obj1.isBool()) {
+    writePSFmt("%ALDImageOverprint: {0:s}\n",
+	       obj1.getBool() ? "true" : "false");
+  }
+  obj1.free();
+
+  dict->lookup("Position", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 8) {
+    obj1.arrayGet(0, &obj2);
+    llx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    lly = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    ulx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    uly = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(4, &obj2);
+    urx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(5, &obj2);
+    ury = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(6, &obj2);
+    lrx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(7, &obj2);
+    lry = obj2.getNum();
+    obj2.free();
+    opiTransform(state, llx, lly, &tllx, &tlly);
+    opiTransform(state, ulx, uly, &tulx, &tuly);
+    opiTransform(state, urx, ury, &turx, &tury);
+    opiTransform(state, lrx, lry, &tlrx, &tlry);
+    writePSFmt("%ALDImagePosition: {0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} {6:.4g} {7:.4g}\n",
+	       tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry);
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("Resolution", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    horiz = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    vert = obj2.getNum();
+    obj2.free();
+    writePSFmt("%ALDImageResoution: {0:.4g} {1:.4g}\n", horiz, vert);
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("Size", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    width = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    height = obj2.getInt();
+    obj2.free();
+    writePSFmt("%ALDImageDimensions: {0:d} {1:d}\n", width, height);
+  }
+  obj1.free();
+
+  //~ ignoring 'Tags' entry
+  //~ need to use writePSString() and deal with >255-char lines
+
+  dict->lookup("Tint", &obj1);
+  if (obj1.isNum()) {
+    writePSFmt("%ALDImageTint: {0:.4g}\n", obj1.getNum());
+  }
+  obj1.free();
+
+  dict->lookup("Transparency", &obj1);
+  if (obj1.isBool()) {
+    writePSFmt("%ALDImageTransparency: {0:s}\n",
+	       obj1.getBool() ? "true" : "false");
+  }
+  obj1.free();
+
+  writePS("%%BeginObject: image\n");
+  writePS("opiMatrix2 setmatrix\n");
+  ++opi13Nest;
+}
+
+// Convert PDF user space coordinates to PostScript default user space
+// coordinates.  This has to account for both the PDF CTM and the
+// PSOutputDev page-fitting transform.
+void PSOutputDev::opiTransform(GfxState *state, double x0, double y0,
+			       double *x1, double *y1) {
+  double t;
+
+  state->transform(x0, y0, x1, y1);
+  *x1 += tx;
+  *y1 += ty;
+  if (rotate == 90) {
+    t = *x1;
+    *x1 = -*y1;
+    *y1 = t;
+  } else if (rotate == 180) {
+    *x1 = -*x1;
+    *y1 = -*y1;
+  } else if (rotate == 270) {
+    t = *x1;
+    *x1 = *y1;
+    *y1 = -t;
+  }
+  *x1 *= xScale;
+  *y1 *= yScale;
+}
+
+void PSOutputDev::opiEnd(GfxState *state, Dict *opiDict) {
+  Object dict;
+
+  if (globalParams->getPSOPI()) {
+    opiDict->lookup("2.0", &dict);
+    if (dict.isDict()) {
+      writePS("%%EndIncludedImage\n");
+      writePS("%%EndOPI\n");
+      writePS("grestore\n");
+      --opi20Nest;
+      dict.free();
+    } else {
+      dict.free();
+      opiDict->lookup("1.3", &dict);
+      if (dict.isDict()) {
+	writePS("%%EndObject\n");
+	writePS("restore\n");
+	--opi13Nest;
+      }
+      dict.free();
+    }
+  }
+}
+
+GBool PSOutputDev::getFileSpec(Object *fileSpec, Object *fileName) {
+  if (fileSpec->isString()) {
+    fileSpec->copy(fileName);
+    return gTrue;
+  }
+  if (fileSpec->isDict()) {
+    fileSpec->dictLookup("DOS", fileName);
+    if (fileName->isString()) {
+      return gTrue;
+    }
+    fileName->free();
+    fileSpec->dictLookup("Mac", fileName);
+    if (fileName->isString()) {
+      return gTrue;
+    }
+    fileName->free();
+    fileSpec->dictLookup("Unix", fileName);
+    if (fileName->isString()) {
+      return gTrue;
+    }
+    fileName->free();
+    fileSpec->dictLookup("F", fileName);
+    if (fileName->isString()) {
+      return gTrue;
+    }
+    fileName->free();
+  }
+  return gFalse;
+}
+#endif // OPI_SUPPORT
+
+void PSOutputDev::type3D0(GfxState * /*state*/, double wx, double wy) {
+  writePSFmt("{0:.4g} {1:.4g} setcharwidth\n", wx, wy);
+  writePS("q\n");
+  t3NeedsRestore = gTrue;
+}
+
+void PSOutputDev::type3D1(GfxState * /*state*/, double wx, double wy,
+			  double llx, double lly, double urx, double ury) {
+  t3WX = wx;
+  t3WY = wy;
+  t3LLX = llx;
+  t3LLY = lly;
+  t3URX = urx;
+  t3URY = ury;
+  t3String = new GString();
+  writePS("q\n");
+  t3Cacheable = gTrue;
+  t3NeedsRestore = gTrue;
+}
+
+void PSOutputDev::drawForm(Ref id) {
+  writePSFmt("f_{0:d}_{1:d}\n", id.num, id.gen);
+}
+
+void PSOutputDev::psXObject(Stream *psStream, Stream *level1Stream) {
+  Stream *str;
+  int c;
+
+  if ((level == psLevel1 || level == psLevel1Sep) && level1Stream) {
+    str = level1Stream;
+  } else {
+    str = psStream;
+  }
+  str->reset();
+  while ((c = str->getChar()) != EOF) {
+    writePSChar(c);
+  }
+  str->close();
+}
+
+//~ can nextFunc be reset to 0 -- maybe at the start of each page?
+//~   or maybe at the start of each color space / pattern?
+void PSOutputDev::cvtFunction(Function *func) {
+  SampledFunction *func0;
+  ExponentialFunction *func2;
+  StitchingFunction *func3;
+  PostScriptFunction *func4;
+  int thisFunc, m, n, nSamples, i, j, k;
+
+  switch (func->getType()) {
+
+  case -1:			// identity
+    writePS("{}\n");
+    break;
+
+  case 0:			// sampled
+    func0 = (SampledFunction *)func;
+    thisFunc = nextFunc++;
+    m = func0->getInputSize();
+    n = func0->getOutputSize();
+    nSamples = n;
+    for (i = 0; i < m; ++i) {
+      nSamples *= func0->getSampleSize(i);
+    }
+    writePSFmt("/xpdfSamples{0:d} [\n", thisFunc);
+    for (i = 0; i < nSamples; ++i) {
+      writePSFmt("{0:.4g}\n", func0->getSamples()[i]);
+    }
+    writePS("] def\n");
+    writePSFmt("{{ {0:d} array {1:d} array {2:d} 2 roll\n", 2*m, m, m+2);
+    // [e01] [efrac] x0 x1 ... xm-1
+    for (i = m-1; i >= 0; --i) {
+      // [e01] [efrac] x0 x1 ... xi
+      writePSFmt("{0:.4g} sub {1:.4g} mul {2:.4g} add\n",
+	      func0->getDomainMin(i),
+	      (func0->getEncodeMax(i) - func0->getEncodeMin(i)) /
+	        (func0->getDomainMax(i) - func0->getDomainMin(i)),
+	      func0->getEncodeMin(i));
+      // [e01] [efrac] x0 x1 ... xi-1 xi'
+      writePSFmt("dup 0 lt {{ pop 0 }} {{ dup {0:d} gt {{ pop {1:d} }} if }} ifelse\n",
+		 func0->getSampleSize(i) - 1, func0->getSampleSize(i) - 1);
+      // [e01] [efrac] x0 x1 ... xi-1 xi'
+      writePS("dup floor cvi exch dup ceiling cvi exch 2 index sub\n");
+      // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi') xi'-floor(xi')
+      writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, i);
+      // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi')
+      writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, 2*i+1);
+      // [e01] [efrac] x0 x1 ... xi-1 floor(xi')
+      writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+2, 2*i);
+      // [e01] [efrac] x0 x1 ... xi-1
+    }
+    // [e01] [efrac]
+    for (i = 0; i < n; ++i) {
+      // [e01] [efrac] y(0) ... y(i-1)
+      for (j = 0; j < (1<<m); ++j) {
+	// [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(j-1)
+	writePSFmt("xpdfSamples{0:d}\n", thisFunc);
+	k = m - 1;
+	writePSFmt("{0:d} index {1:d} get\n", i+j+2, 2 * k + ((j >> k) & 1));
+	for (k = m - 2; k >= 0; --k) {
+	  writePSFmt("{0:d} mul {1:d} index {2:d} get add\n",
+		     func0->getSampleSize(k),
+		     i + j + 3,
+		     2 * k + ((j >> k) & 1));
+	}
+	if (n > 1) {
+	  writePSFmt("{0:d} mul {1:d} add ", n, i);
+	}
+	writePS("get\n");
+      }
+      // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^m-1)
+      for (j = 0; j < m; ++j) {
+	// [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^(m-j)-1)
+	for (k = 0; k < (1 << (m - j)); k += 2) {
+	  // [e01] [efrac] y(0) ... y(i-1) <k/2 s' values> <2^(m-j)-k s values>
+	  writePSFmt("{0:d} index {1:d} get dup\n",
+		     i + k/2 + (1 << (m-j)) - k, j);
+	  writePS("3 2 roll mul exch 1 exch sub 3 2 roll mul add\n");
+	  writePSFmt("{0:d} 1 roll\n", k/2 + (1 << m-j) - k - 1);
+	}
+	// [e01] [efrac] s'(0) s'(1) ... s(2^(m-j-1)-1)
+      }
+      // [e01] [efrac] y(0) ... y(i-1) s
+      writePSFmt("{0:.4g} mul {1:.4g} add\n",
+		 func0->getDecodeMax(i) - func0->getDecodeMin(i),
+		 func0->getDecodeMin(i));
+      writePSFmt("dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
+		 func0->getRangeMin(i), func0->getRangeMin(i),
+		 func0->getRangeMax(i), func0->getRangeMax(i));
+      // [e01] [efrac] y(0) ... y(i-1) y(i)
+    }
+    // [e01] [efrac] y(0) ... y(n-1)
+    writePSFmt("{0:d} {1:d} roll pop pop }}\n", n+2, n);
+    break;
+
+  case 2:			// exponential
+    func2 = (ExponentialFunction *)func;
+    n = func2->getOutputSize();
+    writePSFmt("{{ dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
+	       func2->getDomainMin(0), func2->getDomainMin(0),
+	       func2->getDomainMax(0), func2->getDomainMax(0));
+    // x
+    for (i = 0; i < n; ++i) {
+      // x y(0) .. y(i-1)
+      writePSFmt("{0:d} index {1:.4g} exp {2:.4g} mul {3:.4g} add\n",
+		 i, func2->getE(), func2->getC1()[i] - func2->getC0()[i],
+		 func2->getC0()[i]);
+      if (func2->getHasRange()) {
+	writePSFmt("dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
+		   func2->getRangeMin(i), func2->getRangeMin(i),
+		   func2->getRangeMax(i), func2->getRangeMax(i));
+      }
+    }
+    // x y(0) .. y(n-1)
+    writePSFmt("{0:d} {1:d} roll pop }}\n", n+1, n);
+    break;
+
+  case 3:			// stitching
+    func3 = (StitchingFunction *)func;
+    thisFunc = nextFunc++;
+    for (i = 0; i < func3->getNumFuncs(); ++i) {
+      cvtFunction(func3->getFunc(i));
+      writePSFmt("/xpdfFunc{0:d}_{1:d} exch def\n", thisFunc, i);
+    }
+    writePSFmt("{{ dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
+	       func3->getDomainMin(0), func3->getDomainMin(0),
+	       func3->getDomainMax(0), func3->getDomainMax(0));
+    for (i = 0; i < func3->getNumFuncs() - 1; ++i) {
+      writePSFmt("dup {0:.4g} lt {{ {1:.4g} sub {2:.4g} mul {3:.4g} add xpdfFunc{4:d}_{5:d} }} {{\n",
+		 func3->getBounds()[i+1],
+		 func3->getBounds()[i],
+		 func3->getScale()[i],
+		 func3->getEncode()[2*i],
+		 thisFunc, i);
+    }
+    writePSFmt("{0:.4g} sub {1:.4g} mul {2:.4g} add xpdfFunc{3:d}_{4:d}\n",
+	       func3->getBounds()[i],
+	       func3->getScale()[i],
+	       func3->getEncode()[2*i],
+	       thisFunc, i);
+    for (i = 0; i < func3->getNumFuncs() - 1; ++i) {
+      writePS("} ifelse\n");
+    }
+    writePS("}\n");
+    break;
+
+  case 4:			// PostScript
+    func4 = (PostScriptFunction *)func;
+    writePS(func4->getCodeString()->getCString());
+    writePS("\n");
+    break;
+  }
+}
+
+void PSOutputDev::writePSChar(char c) {
+  if (t3String) {
+    t3String->append(c);
+  } else {
+    (*outputFunc)(outputStream, &c, 1);
+  }
+}
+
+void PSOutputDev::writePS(char *s) {
+  if (t3String) {
+    t3String->append(s);
+  } else {
+    (*outputFunc)(outputStream, s, strlen(s));
+  }
+}
+
+void PSOutputDev::writePSFmt(const char *fmt, ...) {
+  va_list args;
+  GString *buf;
+
+  va_start(args, fmt);
+  if (t3String) {
+    t3String->appendfv((char *)fmt, args);
+  } else {
+    buf = GString::formatv((char *)fmt, args);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  }
+  va_end(args);
+}
+
+void PSOutputDev::writePSString(GString *s) {
+  Guchar *p;
+  int n, line;
+  char buf[8];
+
+  writePSChar('(');
+  line = 1;
+  for (p = (Guchar *)s->getCString(), n = s->getLength(); n; ++p, --n) {
+    if (line >= 64) {
+      writePSChar('\\');
+      writePSChar('\n');
+      line = 0;
+    }
+    if (*p == '(' || *p == ')' || *p == '\\') {
+      writePSChar('\\');
+      writePSChar((char)*p);
+      line += 2;
+    } else if (*p < 0x20 || *p >= 0x80) {
+      sprintf(buf, "\\%03o", *p);
+      writePS(buf);
+      line += 4;
+    } else {
+      writePSChar((char)*p);
+      ++line;
+    }
+  }
+  writePSChar(')');
+}
+
+void PSOutputDev::writePSName(char *s) {
+  char *p;
+  char c;
+
+  p = s;
+  while ((c = *p++)) {
+    if (c <= (char)0x20 || c >= (char)0x7f ||
+	c == '(' || c == ')' || c == '<' || c == '>' ||
+	c == '[' || c == ']' || c == '{' || c == '}' ||
+	c == '/' || c == '%') {
+      writePSFmt("#{0:02x}", c & 0xff);
+    } else {
+      writePSChar(c);
+    }
+  }
+}
+
+GString *PSOutputDev::filterPSName(GString *name) {
+  GString *name2;
+  char buf[8];
+  int i;
+  char c;
+
+  name2 = new GString();
+
+  // ghostscript chokes on names that begin with out-of-limits
+  // numbers, e.g., 1e4foo is handled correctly (as a name), but
+  // 1e999foo generates a limitcheck error
+  c = name->getChar(0);
+  if (c >= '0' && c <= '9') {
+    name2->append('f');
+  }
+
+  for (i = 0; i < name->getLength(); ++i) {
+    c = name->getChar(i);
+    if (c <= (char)0x20 || c >= (char)0x7f ||
+	c == '(' || c == ')' || c == '<' || c == '>' ||
+	c == '[' || c == ']' || c == '{' || c == '}' ||
+	c == '/' || c == '%') {
+      sprintf(buf, "#%02x", c & 0xff);
+      name2->append(buf);
+    } else {
+      name2->append(c);
+    }
+  }
+  return name2;
+}
+
+// Write a DSC-compliant <textline>.
+void PSOutputDev::writePSTextLine(GString *s) {
+  int i, j, step;
+  int c;
+
+  // - DSC comments must be printable ASCII; control chars and
+  //   backslashes have to be escaped (we do cheap Unicode-to-ASCII
+  //   conversion by simply ignoring the high byte)
+  // - lines are limited to 255 chars (we limit to 200 here to allow
+  //   for the keyword, which was emitted by the caller)
+  // - lines that start with a left paren are treated as <text>
+  //   instead of <textline>, so we escape a leading paren
+  if (s->getLength() >= 2 &&
+      (s->getChar(0) & 0xff) == 0xfe &&
+      (s->getChar(1) & 0xff) == 0xff) {
+    i = 3;
+    step = 2;
+  } else {
+    i = 0;
+    step = 1;
+  }
+  for (j = 0; i < s->getLength() && j < 200; i += step) {
+    c = s->getChar(i) & 0xff;
+    if (c == '\\') {
+      writePS("\\\\");
+      j += 2;
+    } else if (c < 0x20 || c > 0x7e || (j == 0 && c == '(')) {
+      writePSFmt("\\{0:03o}", c);
+      j += 4;
+    } else {
+      writePSChar(c);
+      ++j;
+    }
+  }
+  writePS("\n");
+}
diff -Naur xpdf.orig/xpdf/Stream.cc xpdf/xpdf/Stream.cc
--- xpdf.orig/xpdf/Stream.cc	2010-03-22 23:55:22.000000000 +0900
+++ xpdf/xpdf/Stream.cc	2010-03-23 00:14:18.000000000 +0900
@@ -323,6 +323,10 @@
   } else {
     imgLineSize = nVals;
   }
+  if (width > INT_MAX / nComps) {
+    // force a call to gmallocn(-1,...), which will throw an exception
+    imgLineSize = -1;
+  }
   imgLine = (Guchar *)gmallocn(imgLineSize, sizeof(Guchar));
   imgIdx = nVals;
 }
diff -Naur xpdf.orig/xpdf/XRef.cc xpdf/xpdf/XRef.cc
--- xpdf.orig/xpdf/XRef.cc	2010-03-22 23:55:22.000000000 +0900
+++ xpdf/xpdf/XRef.cc	2010-03-23 00:14:18.000000000 +0900
@@ -52,6 +52,8 @@
   // generation 0.
   ObjectStream(XRef *xref, int objStrNumA);
 
+  GBool isOk() { return ok; }
+
   ~ObjectStream();
 
   // Return the object number of this object stream.
@@ -67,6 +69,7 @@
   int nObjects;			// number of objects in the stream
   Object *objs;			// the objects (length = nObjects)
   int *objNums;			// the object numbers (length = nObjects)
+  GBool ok;
 };
 
 ObjectStream::ObjectStream(XRef *xref, int objStrNumA) {
@@ -80,6 +83,7 @@
   nObjects = 0;
   objs = NULL;
   objNums = NULL;
+  ok = gFalse;
 
   if (!xref->fetch(objStrNum, 0, &objStr)->isStream()) {
     goto err1;
@@ -105,6 +109,13 @@
     goto err1;
   }
 
+  // this is an arbitrary limit to avoid integer overflow problems
+  // in the 'new Object[nObjects]' call (Acrobat apparently limits
+  // object streams to 100-200 objects)
+  if (nObjects > 1000000) {
+    error(-1, "Too many objects in an object stream");
+    goto err1;
+  }
   objs = new Object[nObjects];
   objNums = (int *)gmallocn(nObjects, sizeof(int));
   offsets = (int *)gmallocn(nObjects, sizeof(int));
@@ -161,10 +172,10 @@
   }
 
   gfree(offsets);
+  ok = gTrue;
 
  err1:
   objStr.free();
-  return;
 }
 
 ObjectStream::~ObjectStream() {
@@ -837,6 +848,11 @@
 	delete objStr;
       }
       objStr = new ObjectStream(this, e->offset);
+      if (!objStr->isOk()) {
+	delete objStr;
+	objStr = NULL;
+	goto err;
+      }
     }
     objStr->getObject(e->gen, num, obj);
     break;
