//========================================================================
//
// Splash.cc
//
// Copyright 2003-2020 Glyph & Cog, LLC
//
//========================================================================

#include <aconf.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#include "gmem.h"
#include "gmempp.h"
#include "GString.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"

// the MSVC math.h doesn't define this
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

//------------------------------------------------------------------------

// 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);
}

// Clip x to lie in [0, 255].
static inline Guchar clip255(int x) {
  return x < 0 ? 0 : x > 255 ? 255 : (Guchar)x;
}

// Used by drawImage and fillImageMask to divide the target
// quadrilateral into sections.
struct ImageSection {
  int y0, y1;				// actual y range
  int ia0, ia1;				// vertex indices for edge A
  int ib0, ib1;				// vertex indices for edge B
  SplashCoord xa0, ya0, xa1, ya1;	// edge A
  SplashCoord dxdya;			// slope of edge A
  SplashCoord xb0, yb0, xb1, yb1;	// edge B
  SplashCoord dxdyb;			// slope of edge B
};

//------------------------------------------------------------------------
// SplashPipe
//------------------------------------------------------------------------

#define splashPipeMaxStages 9

struct SplashPipe {
  // source pattern
  SplashPattern *pattern;

  // source alpha and color
  Guchar aInput;
  SplashColor cSrcVal;

  // source overprint mask
  //~ this is a kludge - this pointer should be passed as an arg to the
  //~   pipeRun function, but that would require passing in a lot of
  //~   null pointers, since it's rarely used
  Guint *srcOverprintMaskPtr;

  // special cases and result color
  GBool noTransparency;
  GBool shapeOnly;
  SplashPipeResultColorCtrl resultColorCtrl;

  // non-isolated group correction
  // (this is only used when Splash::composite() is called to composite
  // a non-isolated group onto the backdrop)
  GBool nonIsolatedGroup;

  // the "run" function
  void (Splash::*run)(SplashPipe *pipe, int x0, int x1, int y,
		      Guchar *shapePtr, SplashColorPtr cSrcPtr);
};

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
};

//------------------------------------------------------------------------
// modified region
//------------------------------------------------------------------------

void Splash::clearModRegion() {
  modXMin = bitmap->width;
  modYMin = bitmap->height;
  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, SplashPattern *pattern,
			     Guchar aInput, GBool usesShape,
			     GBool nonIsolatedGroup, GBool usesSrcOverprint) {
  SplashColorMode mode;

  mode = bitmap->mode;

  pipe->pattern = NULL;

  // source color
  if (pattern && pattern->isStatic()) {
    pattern->getColor(0, 0, pipe->cSrcVal);
    pipe->pattern = NULL;
  } else {
    pipe->pattern = pattern;
  }

  // source alpha
  pipe->aInput = aInput;

  // source overprint mask
  pipe->srcOverprintMaskPtr = NULL;

  // special cases
  pipe->noTransparency = aInput == 255 &&
                         !state->softMask &&
                         !usesShape &&
                         !state->inNonIsolatedGroup &&
			 !state->inKnockoutGroup &&
                         !nonIsolatedGroup &&
                         state->overprintMask == 0xffffffff;
  pipe->shapeOnly = aInput == 255 &&
                    !state->softMask &&
                    usesShape &&
                    !state->inNonIsolatedGroup &&
		    !state->inKnockoutGroup &&
                    !nonIsolatedGroup &&
                    state->overprintMask == 0xffffffff;

  // result color
  if (pipe->noTransparency) {
    // the !state->blendFunc case is handled separately in pipeRun
    pipe->resultColorCtrl = pipeResultColorNoAlphaBlend[mode];
  } else if (!state->blendFunc) {
    pipe->resultColorCtrl = pipeResultColorAlphaNoBlend[mode];
  } else {
    pipe->resultColorCtrl = pipeResultColorAlphaBlend[mode];
  }

  // non-isolated group correction
  pipe->nonIsolatedGroup = nonIsolatedGroup;

  // select the 'run' function
  pipe->run = &Splash::pipeRun;
  if (overprintMaskBitmap || usesSrcOverprint) {
    // use Splash::pipeRun
  } else if (!pipe->pattern && pipe->noTransparency && !state->blendFunc) {
    if (mode == splashModeMono1 && !bitmap->alpha) {
      pipe->run = &Splash::pipeRunSimpleMono1;
    } else if (mode == splashModeMono8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunSimpleMono8;
    } else if (mode == splashModeRGB8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunSimpleRGB8;
    } else if (mode == splashModeBGR8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunSimpleBGR8;
#if SPLASH_CMYK
    } else if (mode == splashModeCMYK8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunSimpleCMYK8;
#endif
    }
  } else if (!pipe->pattern && pipe->shapeOnly && !state->blendFunc) {
    if (mode == splashModeMono1 && !bitmap->alpha) {
      pipe->run = &Splash::pipeRunShapeMono1;
    } else if (mode == splashModeMono8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunShapeMono8;
    } else if (mode == splashModeRGB8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunShapeRGB8;
    } else if (mode == splashModeBGR8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunShapeBGR8;
#if SPLASH_CMYK
    } else if (mode == splashModeCMYK8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunShapeCMYK8;
#endif
    } else if (mode == splashModeMono8 && !bitmap->alpha) {
      // this is used when drawing soft-masked images
      pipe->run = &Splash::pipeRunShapeNoAlphaMono8;
    }
  } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask &&
	     usesShape &&
	     !(state->inNonIsolatedGroup && groupBackBitmap->alpha) &&
	     !state->inKnockoutGroup &&
	     !state->blendFunc && !pipe->nonIsolatedGroup) {
    if (mode == splashModeMono1 && !bitmap->alpha) {
      pipe->run = &Splash::pipeRunAAMono1;
    } else if (mode == splashModeMono8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunAAMono8;
    } else if (mode == splashModeRGB8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunAARGB8;
    } else if (mode == splashModeBGR8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunAABGR8;
#if SPLASH_CMYK
    } else if (mode == splashModeCMYK8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunAACMYK8;
#endif
    }
  } else if (!pipe->pattern &&
             aInput == 255 &&
             state->softMask &&
             usesShape &&
             !state->inNonIsolatedGroup &&
             !state->inKnockoutGroup &&
             !nonIsolatedGroup &&
             state->overprintMask == 0xffffffff &&
             !state->blendFunc) {
    if (mode == splashModeMono8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunSoftMaskMono8;
    } else if (mode == splashModeRGB8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunSoftMaskRGB8;
    } else if (mode == splashModeBGR8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunSoftMaskBGR8;
#if SPLASH_CMYK
    } else if (mode == splashModeCMYK8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunSoftMaskCMYK8;
#endif
    }
  } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask &&
	     usesShape &&
	     state->inNonIsolatedGroup && groupBackBitmap->alpha &&
	     !state->inKnockoutGroup &&
	     !state->blendFunc && !pipe->nonIsolatedGroup) {
    if (mode == splashModeMono8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunNonIsoMono8;
    } else if (mode == splashModeRGB8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunNonIsoRGB8;
    } else if (mode == splashModeBGR8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunNonIsoBGR8;
#if SPLASH_CMYK
    } else if (mode == splashModeCMYK8 && bitmap->alpha) {
      pipe->run = &Splash::pipeRunNonIsoCMYK8;
#endif
    }
  }
}

// general case
void Splash::pipeRun(SplashPipe *pipe, int x0, int x1, int y,
		     Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar *shapePtr2;
  Guchar shape, aSrc, aDest, alphaI, alphaIm1, alpha0, aResult;
  SplashColor cSrc, cDest, cBlend;
  Guchar shapeVal, cResult0, cResult1, cResult2, cResult3;
  int cSrcStride, shapeStride, x, lastX, t;
  SplashColorPtr destColorPtr;
  Guchar destColorMask;
  Guchar *destAlphaPtr;
  SplashColorPtr color0Ptr;
  Guchar color0Mask;
  Guchar *alpha0Ptr;
  SplashColorPtr softMaskPtr;
  Guint overprintMask;
  Guint *overprintMaskPtr;
#if SPLASH_CMYK
  Guchar aPrev;
  SplashColor cSrc2, cDest2;
#endif

  if (cSrcPtr && !pipe->pattern) {
    cSrcStride = bitmapComps;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }

  if (shapePtr) {
    shapePtr2 = shapePtr;
    shapeStride = 1;
    for (; x0 <= x1; ++x0) {
      if (*shapePtr2) {
	break;
      }
      cSrcPtr += cSrcStride;
      ++shapePtr2;
      if (pipe->srcOverprintMaskPtr) {
	++pipe->srcOverprintMaskPtr;
      }
    }
  } else {
    shapeVal = 0xff;
    shapePtr2 = &shapeVal;
    shapeStride = 0;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  if (bitmap->mode == splashModeMono1) {
    destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)];
    destColorMask = (Guchar)(0x80 >> (x0 & 7));
  } else {
    destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * bitmapComps];
    destColorMask = 0; // make gcc happy
  }
  if (bitmap->alpha) {
    destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
  } else {
    destAlphaPtr = NULL;
  }
  if (state->softMask) {
    softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0];
  } else {
    softMaskPtr = NULL;
  }
  if (state->inKnockoutGroup) {
    if (bitmap->mode == splashModeMono1) {
      color0Ptr =
          &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize +
				 ((groupBackX + x0) >> 3)];
      color0Mask = (Guchar)(0x80 >> ((groupBackX + x0) & 7));
    } else {
      color0Ptr =
          &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize +
				 (groupBackX + x0) * bitmapComps];
      color0Mask = 0; // make gcc happy
    }
  } else {
    color0Ptr = NULL;
    color0Mask = 0; // make gcc happy
  }
  if (state->inNonIsolatedGroup && groupBackBitmap->alpha) {
    alpha0Ptr =
        &groupBackBitmap->alpha[(groupBackY + y)
				  * groupBackBitmap->alphaRowSize +
				(groupBackX + x0)];
  } else {
    alpha0Ptr = NULL;
  }
  if (overprintMaskBitmap) {
    overprintMaskPtr = overprintMaskBitmap + y * bitmap->width + x0;
  } else {
    overprintMaskPtr = NULL;
  }

  for (x = x0; x <= x1; ++x) {

    //----- shape

    shape = *shapePtr2;
    if (!shape) {
      if (bitmap->mode == splashModeMono1) {
	destColorPtr += destColorMask & 1;
	destColorMask = (Guchar)((destColorMask << 7) | (destColorMask >> 1));
      } else {
	destColorPtr += bitmapComps;
      }
      if (destAlphaPtr) {
	++destAlphaPtr;
      }
      if (softMaskPtr) {
	++softMaskPtr;
      }
      if (color0Ptr) {
	if (bitmap->mode == splashModeMono1) {
	  color0Ptr += color0Mask & 1;
	  color0Mask = (Guchar)((color0Mask << 7) | (color0Mask >> 1));
	} else {
	  color0Ptr += bitmapComps;
	}
      }
      if (alpha0Ptr) {
	++alpha0Ptr;
      }
      cSrcPtr += cSrcStride;
      shapePtr2 += shapeStride;
      if (pipe->srcOverprintMaskPtr) {
	++pipe->srcOverprintMaskPtr;
      }
      if (overprintMaskPtr) {
	++overprintMaskPtr;
      }
      continue;
    }
    lastX = x;

    //----- source color

    // static pattern: handled in pipeInit
    // fixed color: handled in pipeInit

    // dynamic pattern
    if (pipe->pattern) {
      pipe->pattern->getColor(x, y, pipe->cSrcVal);
    }

    cResult0 = cResult1 = cResult2 = cResult3 = 0; // make gcc happy

    if (pipe->noTransparency && !state->blendFunc) {

      //----- result color

      switch (bitmap->mode) {
      case splashModeMono1:
      case splashModeMono8:
	cResult0 = state->grayTransfer[cSrcPtr[0]];
	break;
      case splashModeRGB8:
      case splashModeBGR8:
	cResult0 = state->rgbTransferR[cSrcPtr[0]];
	cResult1 = state->rgbTransferG[cSrcPtr[1]];
	cResult2 = state->rgbTransferB[cSrcPtr[2]];
	break;
#if SPLASH_CMYK
      case splashModeCMYK8:
	cResult0 = state->cmykTransferC[cSrcPtr[0]];
	cResult1 = state->cmykTransferM[cSrcPtr[1]];
	cResult2 = state->cmykTransferY[cSrcPtr[2]];
	cResult3 = state->cmykTransferK[cSrcPtr[3]];
	break;
#endif
      }
      aResult = 255;

    } else { // if (noTransparency && !blendFunc)

      //----- read destination pixel
      //      (or backdrop color, for knockout groups)

      if (color0Ptr) {

	switch (bitmap->mode) {
	case splashModeMono1:
	  cDest[0] = (*color0Ptr & color0Mask) ? 0xff : 0x00;
	  color0Ptr += color0Mask & 1;
	  color0Mask = (Guchar)((color0Mask << 7) | (color0Mask >> 1));
	  break;
	case splashModeMono8:
	  cDest[0] = *color0Ptr++;
	  break;
	case splashModeRGB8:
	  cDest[0] = color0Ptr[0];
	  cDest[1] = color0Ptr[1];
	  cDest[2] = color0Ptr[2];
	  color0Ptr += 3;
	  break;
	case splashModeBGR8:
	  cDest[2] = color0Ptr[0];
	  cDest[1] = color0Ptr[1];
	  cDest[0] = color0Ptr[2];
	  color0Ptr += 3;
	  break;
#if SPLASH_CMYK
	case splashModeCMYK8:
	  cDest[0] = color0Ptr[0];
	  cDest[1] = color0Ptr[1];
	  cDest[2] = color0Ptr[2];
	  cDest[3] = color0Ptr[3];
	  color0Ptr += 4;
	  break;
#endif
	}

      } else {

	switch (bitmap->mode) {
	case splashModeMono1:
	  cDest[0] = (*destColorPtr & destColorMask) ? 0xff : 0x00;
	  break;
	case splashModeMono8:
	  cDest[0] = *destColorPtr;
	  break;
	case splashModeRGB8:
	  cDest[0] = destColorPtr[0];
	  cDest[1] = destColorPtr[1];
	  cDest[2] = destColorPtr[2];
	  break;
	case splashModeBGR8:
	  cDest[0] = destColorPtr[2];
	  cDest[1] = destColorPtr[1];
	  cDest[2] = destColorPtr[0];
	  break;
#if SPLASH_CMYK
	case splashModeCMYK8:
	  cDest[0] = destColorPtr[0];
	  cDest[1] = destColorPtr[1];
	  cDest[2] = destColorPtr[2];
	  cDest[3] = destColorPtr[3];
	  break;
#endif
	}

      }

      if (destAlphaPtr) {
	aDest = *destAlphaPtr;
      } else {
	aDest = 0xff;
      }

      //----- read source color; handle overprint

      if (pipe->srcOverprintMaskPtr) {
	overprintMask = *pipe->srcOverprintMaskPtr++;
      } else {
	overprintMask = state->overprintMask;
      }
      if (overprintMaskPtr) {
	*overprintMaskPtr++ |= overprintMask;
      }

      switch (bitmap->mode) {
      case splashModeMono1:
      case splashModeMono8:
	cSrc[0] = state->grayTransfer[cSrcPtr[0]];
	break;
      case splashModeRGB8:
      case splashModeBGR8:
	cSrc[0] = state->rgbTransferR[cSrcPtr[0]];
	cSrc[1] = state->rgbTransferG[cSrcPtr[1]];
	cSrc[2] = state->rgbTransferB[cSrcPtr[2]];
	break;
#if SPLASH_CMYK
      case splashModeCMYK8:
	if (alpha0Ptr) {   // non-isolated group
	  if (color0Ptr) { //   non-isolated, knockout group
	    aPrev = *alpha0Ptr;
	  } else {         //   non-isolated, non-knockout group
	    aPrev = (Guchar)(*alpha0Ptr + aDest - div255(*alpha0Ptr * aDest));
	  }
	} else {           // isolated group
	  if (color0Ptr) { //   isolated, knockout group
	    aPrev = 0;
	  } else {         //   isolated, non-knockout group
	    aPrev = aDest;
	  }
	}
	if (overprintMask & 0x01) {
	  cSrc[0] = state->cmykTransferC[cSrcPtr[0]];
	} else {
	  cSrc[0] = div255(aPrev * cDest[0]);
	}
	if (overprintMask & 0x02) {
	  cSrc[1] = state->cmykTransferM[cSrcPtr[1]];
	} else {
	  cSrc[1] = div255(aPrev * cDest[1]);
	}
	if (overprintMask & 0x04) {
	  cSrc[2] = state->cmykTransferY[cSrcPtr[2]];
	} else {
	  cSrc[2] = div255(aPrev * cDest[2]);
	}
	if (overprintMask & 0x08) {
	  cSrc[3] = state->cmykTransferK[cSrcPtr[3]];
	} else {
	  cSrc[3] = div255(aPrev * cDest[3]);
	}
	break;
#endif
      }

      //----- source alpha

      if (softMaskPtr) {
	if (shapePtr) {
	  aSrc = div255(div255(pipe->aInput * *softMaskPtr++) * shape);
	} else {
	  aSrc = div255(pipe->aInput * *softMaskPtr++);
	}
      } else if (shapePtr) {
	aSrc = div255(pipe->aInput * shape);
      } else {
	aSrc = pipe->aInput;
      }

      //----- non-isolated group correction

      if (pipe->nonIsolatedGroup) {
	// This path is only used when Splash::composite() is called to
	// composite a non-isolated group onto the backdrop.  In this
	// case, shape is the source (group) alpha.
	t = (aDest * 255) / shape - aDest;
	switch (bitmap->mode) {
#if SPLASH_CMYK
	case splashModeCMYK8:
	  cSrc[3] = clip255(cSrc[3] + ((cSrc[3] - cDest[3]) * t) / 255);
#endif
	case splashModeRGB8:
	case splashModeBGR8:
	  cSrc[2] = clip255(cSrc[2] + ((cSrc[2] - cDest[2]) * t) / 255);
	  cSrc[1] = clip255(cSrc[1] + ((cSrc[1] - cDest[1]) * t) / 255);
	case splashModeMono1:
	case splashModeMono8:
	  cSrc[0] = clip255(cSrc[0] + ((cSrc[0] - cDest[0]) * t) / 255);
	  break;
	}
      }

      //----- blend function

      if (state->blendFunc) {
#if SPLASH_CMYK
	if (bitmap->mode == splashModeCMYK8) {
	  // convert colors to additive
	  cSrc2[0] = (Guchar)(0xff - cSrc[0]);
	  cSrc2[1] = (Guchar)(0xff - cSrc[1]);
	  cSrc2[2] = (Guchar)(0xff - cSrc[2]);
	  cSrc2[3] = (Guchar)(0xff - cSrc[3]);
	  cDest2[0] = (Guchar)(0xff - cDest[0]);
	  cDest2[1] = (Guchar)(0xff - cDest[1]);
	  cDest2[2] = (Guchar)(0xff - cDest[2]);
	  cDest2[3] = (Guchar)(0xff - cDest[3]);
	  (*state->blendFunc)(cSrc2, cDest2, cBlend, bitmap->mode);
	  // convert result back to subtractive
	  cBlend[0] = (Guchar)(0xff - cBlend[0]);
	  cBlend[1] = (Guchar)(0xff - cBlend[1]);
	  cBlend[2] = (Guchar)(0xff - cBlend[2]);
	  cBlend[3] = (Guchar)(0xff - cBlend[3]);
	} else
#endif
	(*state->blendFunc)(cSrc, cDest, cBlend, bitmap->mode);
      }

      //----- result alpha and non-isolated group element correction

      // alphaI = alpha_i
      // alphaIm1 = alpha_(i-1)

      if (pipe->noTransparency) {
	alphaI = alphaIm1 = aResult = 255;
      } else if (alpha0Ptr) {
	if (color0Ptr) {
	  // non-isolated, knockout
	  aResult = aSrc;
	  alpha0 = *alpha0Ptr++;
	  alphaI = (Guchar)(aSrc + alpha0 - div255(aSrc * alpha0));
	  alphaIm1 = alpha0;
	} else {
	  // non-isolated, non-knockout
	  aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
	  alpha0 = *alpha0Ptr++;
	  alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0));
	  alphaIm1 = (Guchar)(alpha0 + aDest - div255(alpha0 * aDest));
	}
      } else {
	if (color0Ptr) {
	  // isolated, knockout
	  aResult = aSrc;
	  alphaI = aSrc;
	  alphaIm1 = 0;
	} else {
	  // isolated, non-knockout
	  aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
	  alphaI = aResult;
	  alphaIm1 = aDest;
	}
      }

      //----- result color

      switch (pipe->resultColorCtrl) {

      case splashPipeResultColorNoAlphaBlendMono:
	cResult0 = div255((255 - aDest) * cSrc[0] + aDest * cBlend[0]);
	break;
      case splashPipeResultColorNoAlphaBlendRGB:
	cResult0 = div255((255 - aDest) * cSrc[0] + aDest * cBlend[0]);
	cResult1 = div255((255 - aDest) * cSrc[1] + aDest * cBlend[1]);
	cResult2 = div255((255 - aDest) * cSrc[2] + aDest * cBlend[2]);
	break;
#if SPLASH_CMYK
      case splashPipeResultColorNoAlphaBlendCMYK:
	cResult0 = div255((255 - aDest) * cSrc[0] + aDest * cBlend[0]);
	cResult1 = div255((255 - aDest) * cSrc[1] + aDest * cBlend[1]);
	cResult2 = div255((255 - aDest) * cSrc[2] + aDest * cBlend[2]);
	cResult3 = div255((255 - aDest) * cSrc[3] + aDest * cBlend[3]);
	break;
#endif

      case splashPipeResultColorAlphaNoBlendMono:
	if (alphaI == 0) {
	  cResult0 = 0;
	} else {
	  cResult0 = (Guchar)(((alphaI - aSrc) * cDest[0] + aSrc * cSrc[0])
			      / alphaI);
	}
	break;
      case splashPipeResultColorAlphaNoBlendRGB:
	if (alphaI == 0) {
	  cResult0 = 0;
	  cResult1 = 0;
	  cResult2 = 0;
	} else {
	  cResult0 = (Guchar)(((alphaI - aSrc) * cDest[0] + aSrc * cSrc[0])
			      / alphaI);
	  cResult1 = (Guchar)(((alphaI - aSrc) * cDest[1] + aSrc * cSrc[1])
			      / alphaI);
	  cResult2 = (Guchar)(((alphaI - aSrc) * cDest[2] + aSrc * cSrc[2])
			      / alphaI);
	}
	break;
#if SPLASH_CMYK
      case splashPipeResultColorAlphaNoBlendCMYK:
	if (alphaI == 0) {
	  cResult0 = 0;
	  cResult1 = 0;
	  cResult2 = 0;
	  cResult3 = 0;
	} else {
	  cResult0 = (Guchar)(((alphaI - aSrc) * cDest[0] + aSrc * cSrc[0])
			      / alphaI);
	  cResult1 = (Guchar)(((alphaI - aSrc) * cDest[1] + aSrc * cSrc[1])
			      / alphaI);
	  cResult2 = (Guchar)(((alphaI - aSrc) * cDest[2] + aSrc * cSrc[2])
			      / alphaI);
	  cResult3 = (Guchar)(((alphaI - aSrc) * cDest[3] + aSrc * cSrc[3])
			      / alphaI);
	}
	break;
#endif

      case splashPipeResultColorAlphaBlendMono:
	if (alphaI == 0) {
	  cResult0 = 0;
	} else {
	  cResult0 = (Guchar)(((alphaI - aSrc) * cDest[0] +
			       aSrc * ((255 - alphaIm1) * cSrc[0] +
				       alphaIm1 * cBlend[0]) / 255)
			      / alphaI);
	}
	break;
      case splashPipeResultColorAlphaBlendRGB:
	if (alphaI == 0) {
	  cResult0 = 0;
	  cResult1 = 0;
	  cResult2 = 0;
	} else {
	  cResult0 = (Guchar)(((alphaI - aSrc) * cDest[0] +
			       aSrc * ((255 - alphaIm1) * cSrc[0] +
				       alphaIm1 * cBlend[0]) / 255)
			      / alphaI);
	  cResult1 = (Guchar)(((alphaI - aSrc) * cDest[1] +
			       aSrc * ((255 - alphaIm1) * cSrc[1] +
				       alphaIm1 * cBlend[1]) / 255)
			      / alphaI);
	  cResult2 = (Guchar)(((alphaI - aSrc) * cDest[2] +
			       aSrc * ((255 - alphaIm1) * cSrc[2] +
				       alphaIm1 * cBlend[2]) / 255)
			      / alphaI);
	}
	break;
#if SPLASH_CMYK
      case splashPipeResultColorAlphaBlendCMYK:
	if (alphaI == 0) {
	  cResult0 = 0;
	  cResult1 = 0;
	  cResult2 = 0;
	  cResult3 = 0;
	} else {
	  cResult0 = (Guchar)(((alphaI - aSrc) * cDest[0] +
			       aSrc * ((255 - alphaIm1) * cSrc[0] +
				       alphaIm1 * cBlend[0]) / 255)
			      / alphaI);
	  cResult1 = (Guchar)(((alphaI - aSrc) * cDest[1] +
			       aSrc * ((255 - alphaIm1) * cSrc[1] +
				       alphaIm1 * cBlend[1]) / 255)
			      / alphaI);
	  cResult2 = (Guchar)(((alphaI - aSrc) * cDest[2] +
			       aSrc * ((255 - alphaIm1) * cSrc[2] +
				       alphaIm1 * cBlend[2]) / 255)
			      / alphaI);
	  cResult3 = (Guchar)(((alphaI - aSrc) * cDest[3] +
			       aSrc * ((255 - alphaIm1) * cSrc[3] +
				       alphaIm1 * cBlend[3]) / 255)
			      / alphaI);
	}
	break;
#endif
      }

    } // if (noTransparency && !blendFunc)

    //----- write destination pixel

    switch (bitmap->mode) {
    case splashModeMono1:
      if (state->screen->test(x, y, cResult0)) {
	*destColorPtr |= destColorMask;
      } else {
	*destColorPtr &= (Guchar)~destColorMask;
      }
      destColorPtr += destColorMask & 1;
      destColorMask = (Guchar)((destColorMask << 7) | (destColorMask >> 1));
      break;
    case splashModeMono8:
      *destColorPtr++ = cResult0;
      break;
    case splashModeRGB8:
      destColorPtr[0] = cResult0;
      destColorPtr[1] = cResult1;
      destColorPtr[2] = cResult2;
      destColorPtr += 3;
      break;
    case splashModeBGR8:
      destColorPtr[0] = cResult2;
      destColorPtr[1] = cResult1;
      destColorPtr[2] = cResult0;
      destColorPtr += 3;
      break;
#if SPLASH_CMYK
    case splashModeCMYK8:
      destColorPtr[0] = cResult0;
      destColorPtr[1] = cResult1;
      destColorPtr[2] = cResult2;
      destColorPtr[3] = cResult3;
      destColorPtr += 4;
      break;
#endif
    }
    if (destAlphaPtr) {
      *destAlphaPtr++ = aResult;
    }

    cSrcPtr += cSrcStride;
    shapePtr2 += shapeStride;
  } // for (x ...)

  updateModX(lastX);
}

// special case:
// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
// bitmap->mode == splashModeMono1 && !bitmap->alpha) {
void Splash::pipeRunSimpleMono1(SplashPipe *pipe, int x0, int x1, int y,
				Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar cResult0;
  SplashColorPtr destColorPtr;
  Guchar destColorMask;
  SplashScreenCursor screenCursor;
  int cSrcStride, x;

  if (cSrcPtr) {
    cSrcStride = 1;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModX(x1);
  updateModY(y);

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)];
  destColorMask = (Guchar)(0x80 >> (x0 & 7));

  screenCursor = state->screen->getTestCursor(y);

  for (x = x0; x <= x1; ++x) {

    //----- write destination pixel
    cResult0 = state->grayTransfer[cSrcPtr[0]];
    if (state->screen->testWithCursor(screenCursor, x, cResult0)) {
      *destColorPtr |= destColorMask;
    } else {
      *destColorPtr &= (Guchar)~destColorMask;
    }
    destColorPtr += destColorMask & 1;
    destColorMask = (Guchar)((destColorMask << 7) | (destColorMask >> 1));

    cSrcPtr += cSrcStride;
  }
}

// special case:
// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
// bitmap->mode == splashModeMono8 && bitmap->alpha) {
void Splash::pipeRunSimpleMono8(SplashPipe *pipe, int x0, int x1, int y,
				Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x;

  if (cSrcPtr) {
    cSrcStride = 1;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModX(x1);
  updateModY(y);

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- write destination pixel
    *destColorPtr++ = state->grayTransfer[cSrcPtr[0]];
    *destAlphaPtr++ = 255;

    cSrcPtr += cSrcStride;
  }
}

// special case:
// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
// bitmap->mode == splashModeRGB8 && bitmap->alpha) {
void Splash::pipeRunSimpleRGB8(SplashPipe *pipe, int x0, int x1, int y,
			       Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModX(x1);
  updateModY(y);

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- write destination pixel
    destColorPtr[0] = state->rgbTransferR[cSrcPtr[0]];
    destColorPtr[1] = state->rgbTransferG[cSrcPtr[1]];
    destColorPtr[2] = state->rgbTransferB[cSrcPtr[2]];
    destColorPtr += 3;
    *destAlphaPtr++ = 255;

    cSrcPtr += cSrcStride;
  }
}

// special case:
// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
// bitmap->mode == splashModeBGR8 && bitmap->alpha) {
void Splash::pipeRunSimpleBGR8(SplashPipe *pipe, int x0, int x1, int y,
			       Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModX(x1);
  updateModY(y);

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- write destination pixel
    destColorPtr[0] = state->rgbTransferB[cSrcPtr[2]];
    destColorPtr[1] = state->rgbTransferG[cSrcPtr[1]];
    destColorPtr[2] = state->rgbTransferR[cSrcPtr[0]];
    destColorPtr += 3;
    *destAlphaPtr++ = 255;

    cSrcPtr += cSrcStride;
  }
}

#if SPLASH_CMYK
// special case:
// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
// bitmap->mode == splashModeCMYK8 && bitmap->alpha) {
void Splash::pipeRunSimpleCMYK8(SplashPipe *pipe, int x0, int x1, int y,
				Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x;

  if (cSrcPtr) {
    cSrcStride = 4;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModX(x1);
  updateModY(y);

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- write destination pixel
    destColorPtr[0] = state->cmykTransferC[cSrcPtr[0]];
    destColorPtr[1] = state->cmykTransferM[cSrcPtr[1]];
    destColorPtr[2] = state->cmykTransferY[cSrcPtr[2]];
    destColorPtr[3] = state->cmykTransferK[cSrcPtr[3]];
    destColorPtr += 4;
    *destAlphaPtr++ = 255;

    cSrcPtr += cSrcStride;
  }
}
#endif


// special case:
// !pipe->pattern && pipe->shapeOnly && !state->blendFunc &&
// bitmap->mode == splashModeMono1 && !bitmap->alpha
void Splash::pipeRunShapeMono1(SplashPipe *pipe, int x0, int x1, int y,
			       Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, cSrc0, cDest0, cResult0;
  SplashColorPtr destColorPtr;
  Guchar destColorMask;
  SplashScreenCursor screenCursor;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 1;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)];
  destColorMask = (Guchar)(0x80 >> (x0 & 7));

  screenCursor = state->screen->getTestCursor(y);

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += destColorMask & 1;
      destColorMask = (Guchar)((destColorMask << 7) | (destColorMask >> 1));
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- source color
    cSrc0 = state->grayTransfer[cSrcPtr[0]];

    //----- source alpha
    aSrc = shape;

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      cResult0 = cSrc0;
    } else {

      //----- read destination pixel
      cDest0 = (*destColorPtr & destColorMask) ? 0xff : 0x00;

      //----- result color
      // note: aDest = alphaI = aResult = 0xff
      cResult0 = (Guchar)div255((0xff - aSrc) * cDest0 + aSrc * cSrc0);
    }

    //----- write destination pixel
    if (state->screen->testWithCursor(screenCursor, x, cResult0)) {
      *destColorPtr |= destColorMask;
    } else {
      *destColorPtr &= (Guchar)~destColorMask;
    }
    destColorPtr += destColorMask & 1;
    destColorMask = (Guchar)((destColorMask << 7) | (destColorMask >> 1));

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

// special case:
// !pipe->pattern && pipe->shapeOnly && !state->blendFunc &&
// bitmap->mode == splashModeMono8 && bitmap->alpha
void Splash::pipeRunShapeMono8(SplashPipe *pipe, int x0, int x1, int y,
			       Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult, cSrc0, cDest0, cResult0;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 1;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      ++destColorPtr;
      ++destAlphaPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- source color
    cSrc0 = state->grayTransfer[cSrcPtr[0]];

    //----- source alpha
    aSrc = shape;

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      aResult = 255;
      cResult0 = cSrc0;
    } else {

      //----- read destination alpha
      aDest = *destAlphaPtr;

      //----- special case for aDest = 0
      if (aDest == 0) {
	aResult = aSrc;
	cResult0 = cSrc0;
      } else {

	//----- read destination pixel
	cDest0 = *destColorPtr;

	//----- result alpha and non-isolated group element correction
	aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
	alphaI = aResult;

	//----- result color
	cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
      }
    }

    //----- write destination pixel
    *destColorPtr++ = cResult0;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

// special case:
// !pipe->pattern && pipe->shapeOnly && !state->blendFunc &&
// bitmap->mode == splashModeRGB8 && bitmap->alpha
void Splash::pipeRunShapeRGB8(SplashPipe *pipe, int x0, int x1, int y,
			      Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cSrc1, cSrc2;
  Guchar cDest0, cDest1, cDest2;
  Guchar cResult0, cResult1, cResult2;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 3;
      ++destAlphaPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- source color
    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
    cSrc2 = state->rgbTransferB[cSrcPtr[2]];

    //----- source alpha
    aSrc = shape;

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      aResult = 255;
      cResult0 = cSrc0;
      cResult1 = cSrc1;
      cResult2 = cSrc2;
    } else {

      //----- read destination alpha
      aDest = *destAlphaPtr;

      //----- special case for aDest = 0
      if (aDest == 0) {
	aResult = aSrc;
	cResult0 = cSrc0;
	cResult1 = cSrc1;
	cResult2 = cSrc2;
      } else {

	//----- read destination pixel
	cDest0 = destColorPtr[0];
	cDest1 = destColorPtr[1];
	cDest2 = destColorPtr[2];

	//----- result alpha and non-isolated group element correction
	aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
	alphaI = aResult;

	//----- result color
	cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
	cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
	cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
      }
    }

    //----- write destination pixel
    destColorPtr[0] = cResult0;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult2;
    destColorPtr += 3;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

// special case:
// !pipe->pattern && pipe->shapeOnly && !state->blendFunc &&
// bitmap->mode == splashModeBGR8 && bitmap->alpha
void Splash::pipeRunShapeBGR8(SplashPipe *pipe, int x0, int x1, int y,
			      Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cSrc1, cSrc2;
  Guchar cDest0, cDest1, cDest2;
  Guchar cResult0, cResult1, cResult2;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 3;
      ++destAlphaPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- source color
    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
    cSrc2 = state->rgbTransferB[cSrcPtr[2]];

    //----- source alpha
    aSrc = shape;

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      aResult = 255;
      cResult0 = cSrc0;
      cResult1 = cSrc1;
      cResult2 = cSrc2;
    } else {

      //----- read destination alpha
      aDest = *destAlphaPtr;

      //----- special case for aDest = 0
      if (aDest == 0) {
	aResult = aSrc;
	cResult0 = cSrc0;
	cResult1 = cSrc1;
	cResult2 = cSrc2;
      } else {

	//----- read destination pixel
	cDest0 = destColorPtr[2];
	cDest1 = destColorPtr[1];
	cDest2 = destColorPtr[0];

	//----- result alpha and non-isolated group element correction
	aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
	alphaI = aResult;

	//----- result color
	cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
	cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
	cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
      }
    }

    //----- write destination pixel
    destColorPtr[0] = cResult2;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult0;
    destColorPtr += 3;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

#if SPLASH_CMYK
// special case:
// !pipe->pattern && pipe->shapeOnly && !state->blendFunc &&
// bitmap->mode == splashModeCMYK8 && bitmap->alpha
void Splash::pipeRunShapeCMYK8(SplashPipe *pipe, int x0, int x1, int y,
			       Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cSrc1, cSrc2, cSrc3;
  Guchar cDest0, cDest1, cDest2, cDest3;
  Guchar cResult0, cResult1, cResult2, cResult3;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 4;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 4;
      ++destAlphaPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = destColorPtr[0];
    cDest1 = destColorPtr[1];
    cDest2 = destColorPtr[2];
    cDest3 = destColorPtr[3];
    aDest = *destAlphaPtr;

    //----- overprint
    cSrc0 = state->cmykTransferC[cSrcPtr[0]];
    cSrc1 = state->cmykTransferM[cSrcPtr[1]];
    cSrc2 = state->cmykTransferY[cSrcPtr[2]];
    cSrc3 = state->cmykTransferK[cSrcPtr[3]];

    //----- source alpha
    aSrc = shape;

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      aResult = 255;
      cResult0 = cSrc0;
      cResult1 = cSrc1;
      cResult2 = cSrc2;
      cResult3 = cSrc3;
    } else {

      //----- special case for aDest = 0
      if (aDest == 0) {
	aResult = aSrc;
	cResult0 = cSrc0;
	cResult1 = cSrc1;
	cResult2 = cSrc2;
	cResult3 = cSrc3;
      } else {

	//----- result alpha and non-isolated group element correction
	aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
	alphaI = aResult;

	//----- result color
	cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
	cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
	cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
	cResult3 = (Guchar)(((alphaI - aSrc) * cDest3 + aSrc * cSrc3) / alphaI);
      }
    }

    //----- write destination pixel
    destColorPtr[0] = cResult0;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult2;
    destColorPtr[3] = cResult3;
    destColorPtr += 4;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}
#endif


// special case:
// !pipe->pattern && pipe->shapeOnly && !state->blendFunc &&
// bitmap->mode == splashModeMono8 && !bitmap->alpha
void Splash::pipeRunShapeNoAlphaMono8(SplashPipe *pipe, int x0, int x1, int y,
                                      Guchar *shapePtr,
                                      SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, cSrc0, cDest0, cResult0;
  SplashColorPtr destColorPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 1;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      ++destColorPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- source color
    cSrc0 = state->grayTransfer[cSrcPtr[0]];

    //----- source alpha
    aSrc = shape;

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      cResult0 = cSrc0;
    } else {

      //----- read destination pixel
      cDest0 = *destColorPtr;

      //----- result color
      cResult0 = div255((255 - aSrc) * cDest0 + aSrc * cSrc0);
    }

    //----- write destination pixel
    *destColorPtr++ = cResult0;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

// special case:
// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
// !pipe->nonIsolatedGroup &&
// bitmap->mode == splashModeMono1 && !bitmap->alpha
void Splash::pipeRunAAMono1(SplashPipe *pipe, int x0, int x1, int y,
			    Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, cSrc0, cDest0, cResult0;
  SplashColorPtr destColorPtr;
  Guchar destColorMask;
  SplashScreenCursor screenCursor;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 1;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)];
  destColorMask = (Guchar)(0x80 >> (x0 & 7));

  screenCursor = state->screen->getTestCursor(y);

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += destColorMask & 1;
      destColorMask = (Guchar)((destColorMask << 7) | (destColorMask >> 1));
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = (*destColorPtr & destColorMask) ? 0xff : 0x00;

    //----- source color
    cSrc0 = state->grayTransfer[cSrcPtr[0]];

    //----- source alpha
    aSrc = div255(pipe->aInput * shape);

    //----- result color
    // note: aDest = alphaI = aResult = 0xff
    cResult0 = (Guchar)div255((0xff - aSrc) * cDest0 + aSrc * cSrc0);

    //----- write destination pixel
    if (state->screen->testWithCursor(screenCursor, x, cResult0)) {
      *destColorPtr |= destColorMask;
    } else {
      *destColorPtr &= (Guchar)~destColorMask;
    }
    destColorPtr += destColorMask & 1;
    destColorMask = (Guchar)((destColorMask << 7) | (destColorMask >> 1));

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

// special case:
// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
// !pipe->nonIsolatedGroup &&
// bitmap->mode == splashModeMono8 && bitmap->alpha
void Splash::pipeRunAAMono8(SplashPipe *pipe, int x0, int x1, int y,
			    Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult, cSrc0, cDest0, cResult0;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 1;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      ++destColorPtr;
      ++destAlphaPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = *destColorPtr;
    aDest = *destAlphaPtr;

    //----- source color
    cSrc0 = state->grayTransfer[cSrcPtr[0]];

    //----- source alpha
    aSrc = div255(pipe->aInput * shape);

    //----- result alpha and non-isolated group element correction
    aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
    alphaI = aResult;

    //----- result color
    if (alphaI == 0) {
      cResult0 = 0;
    } else {
      cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
    }

    //----- write destination pixel
    *destColorPtr++ = cResult0;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

// special case:
// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
// !pipe->nonIsolatedGroup &&
// bitmap->mode == splashModeRGB8 && bitmap->alpha
void Splash::pipeRunAARGB8(SplashPipe *pipe, int x0, int x1, int y,
			   Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cSrc1, cSrc2;
  Guchar cDest0, cDest1, cDest2;
  Guchar cResult0, cResult1, cResult2;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 3;
      ++destAlphaPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = destColorPtr[0];
    cDest1 = destColorPtr[1];
    cDest2 = destColorPtr[2];
    aDest = *destAlphaPtr;

    //----- source color
    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
    cSrc2 = state->rgbTransferB[cSrcPtr[2]];

    //----- source alpha
    aSrc = div255(pipe->aInput * shape);

    //----- result alpha and non-isolated group element correction
    aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
    alphaI = aResult;

    //----- result color
    if (alphaI == 0) {
      cResult0 = 0;
      cResult1 = 0;
      cResult2 = 0;
    } else {
      cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
      cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
      cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
    }

    //----- write destination pixel
    destColorPtr[0] = cResult0;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult2;
    destColorPtr += 3;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

// special case:
// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
// !pipe->nonIsolatedGroup &&
// bitmap->mode == splashModeBGR8 && bitmap->alpha
void Splash::pipeRunAABGR8(SplashPipe *pipe, int x0, int x1, int y,
			   Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cSrc1, cSrc2;
  Guchar cDest0, cDest1, cDest2;
  Guchar cResult0, cResult1, cResult2;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 3;
      ++destAlphaPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = destColorPtr[2];
    cDest1 = destColorPtr[1];
    cDest2 = destColorPtr[0];
    aDest = *destAlphaPtr;

    //----- source color
    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
    cSrc2 = state->rgbTransferB[cSrcPtr[2]];

    //----- source alpha
    aSrc = div255(pipe->aInput * shape);

    //----- result alpha and non-isolated group element correction
    aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
    alphaI = aResult;

    //----- result color
    if (alphaI == 0) {
      cResult0 = 0;
      cResult1 = 0;
      cResult2 = 0;
    } else {
      cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
      cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
      cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
    }

    //----- write destination pixel
    destColorPtr[0] = cResult2;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult0;
    destColorPtr += 3;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

#if SPLASH_CMYK
// special case:
// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
// !pipe->nonIsolatedGroup &&
// bitmap->mode == splashModeCMYK8 && bitmap->alpha
void Splash::pipeRunAACMYK8(SplashPipe *pipe, int x0, int x1, int y,
			    Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cSrc1, cSrc2, cSrc3;
  Guchar cDest0, cDest1, cDest2, cDest3;
  Guchar cResult0, cResult1, cResult2, cResult3;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 4;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 4;
      ++destAlphaPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = destColorPtr[0];
    cDest1 = destColorPtr[1];
    cDest2 = destColorPtr[2];
    cDest3 = destColorPtr[3];
    aDest = *destAlphaPtr;

    //----- overprint
    if (state->overprintMask & 1) {
      cSrc0 = state->cmykTransferC[cSrcPtr[0]];
    } else {
      cSrc0 = div255(aDest * cDest0);
    }
    if (state->overprintMask & 2) {
      cSrc1 = state->cmykTransferM[cSrcPtr[1]];
    } else {
      cSrc1 = div255(aDest * cDest1);
    }
    if (state->overprintMask & 4) {
      cSrc2 = state->cmykTransferY[cSrcPtr[2]];
    } else {
      cSrc2 = div255(aDest * cDest2);
    }
    if (state->overprintMask & 8) {
      cSrc3 = state->cmykTransferK[cSrcPtr[3]];
    } else {
      cSrc3 = div255(aDest * cDest3);
    }

    //----- source alpha
    aSrc = div255(pipe->aInput * shape);

    //----- result alpha and non-isolated group element correction
    aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
    alphaI = aResult;

    //----- result color
    if (alphaI == 0) {
      cResult0 = 0;
      cResult1 = 0;
      cResult2 = 0;
      cResult3 = 0;
    } else {
      cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
      cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
      cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
      cResult3 = (Guchar)(((alphaI - aSrc) * cDest3 + aSrc * cSrc3) / alphaI);
    }

    //----- write destination pixel
    destColorPtr[0] = cResult0;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult2;
    destColorPtr[3] = cResult3;
    destColorPtr += 4;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}
#endif


// special case:
// !pipe->pattern && aInput == 255 && state->softMask && usesShape &&
// !state->inNonIsolatedGroup && !state->inKnockoutGroup &&
// !nonIsolatedGroup && state->overprintMask == 0xffffffff &&
// !state->blendFunc &&
// bitmap->mode == splashModeMono8 && bitmap->alpha
void Splash::pipeRunSoftMaskMono8(SplashPipe *pipe, int x0, int x1, int y,
				  Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cDest0, cResult0;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  SplashColorPtr softMaskPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 1;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
  softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      ++destColorPtr;
      ++destAlphaPtr;
      ++softMaskPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read source color
    cSrc0 = state->grayTransfer[cSrcPtr[0]];

    //----- source alpha
    aSrc = div255(*softMaskPtr++ * shape);

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      aResult = 255;
      cResult0 = cSrc0;
    } else {

      //----- read destination alpha
      aDest = *destAlphaPtr;

      //----- special case for aDest = 0
      if (aDest == 0) {
        aResult = aSrc;
        cResult0 = cSrc0;
      } else {

        //----- read destination pixel
        cDest0 = destColorPtr[0];

        //----- result alpha and non-isolated group element correction
        aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
        alphaI = aResult;

        //----- result color
        cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
      }
    }

    //----- write destination pixel
    destColorPtr[0] = cResult0;
    ++destColorPtr;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

// special case:
// !pipe->pattern && aInput == 255 && state->softMask && usesShape &&
// !state->inNonIsolatedGroup && !state->inKnockoutGroup &&
// !nonIsolatedGroup && state->overprintMask == 0xffffffff &&
// !state->blendFunc &&
// bitmap->mode == splashModeRGB8 && bitmap->alpha
void Splash::pipeRunSoftMaskRGB8(SplashPipe *pipe, int x0, int x1, int y,
                                 Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cSrc1, cSrc2;
  Guchar cDest0, cDest1, cDest2;
  Guchar cResult0, cResult1, cResult2;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  SplashColorPtr softMaskPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
  softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 3;
      ++destAlphaPtr;
      ++softMaskPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read source color
    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
    cSrc2 = state->rgbTransferB[cSrcPtr[2]];

    //----- source alpha
    aSrc = div255(*softMaskPtr++ * shape);

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      aResult = 255;
      cResult0 = cSrc0;
      cResult1 = cSrc1;
      cResult2 = cSrc2;
    } else {

      //----- read destination alpha
      aDest = *destAlphaPtr;

      //----- special case for aDest = 0
      if (aDest == 0) {
        aResult = aSrc;
        cResult0 = cSrc0;
        cResult1 = cSrc1;
        cResult2 = cSrc2;
      } else {

        //----- read destination pixel
        cDest0 = destColorPtr[0];
        cDest1 = destColorPtr[1];
        cDest2 = destColorPtr[2];

        //----- result alpha and non-isolated group element correction
        aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
        alphaI = aResult;

        //----- result color
        cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
        cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
        cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
      }
    }

    //----- write destination pixel
    destColorPtr[0] = cResult0;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult2;
    destColorPtr += 3;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

// special case:
// !pipe->pattern && aInput == 255 && state->softMask && usesShape &&
// !state->inNonIsolatedGroup && !state->inKnockoutGroup &&
// !nonIsolatedGroup && state->overprintMask == 0xffffffff &&
// !state->blendFunc &&
// bitmap->mode == splashModeBGR8 && bitmap->alpha
void Splash::pipeRunSoftMaskBGR8(SplashPipe *pipe, int x0, int x1, int y,
                                 Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cSrc1, cSrc2;
  Guchar cDest0, cDest1, cDest2;
  Guchar cResult0, cResult1, cResult2;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  SplashColorPtr softMaskPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
  softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 3;
      ++destAlphaPtr;
      ++softMaskPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read source color
    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
    cSrc2 = state->rgbTransferB[cSrcPtr[2]];

    //----- source alpha
    aSrc = div255(*softMaskPtr++ * shape);

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      aResult = 255;
      cResult0 = cSrc0;
      cResult1 = cSrc1;
      cResult2 = cSrc2;
    } else {

      //----- read destination alpha
      aDest = *destAlphaPtr;

      //----- special case for aDest = 0
      if (aDest == 0) {
        aResult = aSrc;
        cResult0 = cSrc0;
        cResult1 = cSrc1;
        cResult2 = cSrc2;
      } else {

        //----- read destination pixel
        cDest0 = destColorPtr[2];
        cDest1 = destColorPtr[1];
        cDest2 = destColorPtr[0];

        //----- result alpha and non-isolated group element correction
        aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
        alphaI = aResult;

        //----- result color
        cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
        cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
        cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
      }
    }

    //----- write destination pixel
    destColorPtr[2] = cResult0;
    destColorPtr[1] = cResult1;
    destColorPtr[0] = cResult2;
    destColorPtr += 3;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}

#if SPLASH_CMYK
// special case:
// !pipe->pattern && aInput == 255 && state->softMask && usesShape &&
// !state->inNonIsolatedGroup && !state->inKnockoutGroup &&
// !nonIsolatedGroup && state->overprintMask == 0xffffffff &&
// !state->blendFunc &&
// bitmap->mode == splashModeCMYK8 && bitmap->alpha
void Splash::pipeRunSoftMaskCMYK8(SplashPipe *pipe, int x0, int x1, int y,
				  Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, aResult;
  Guchar cSrc0, cSrc1, cSrc2, cSrc3;
  Guchar cDest0, cDest1, cDest2, cDest3;
  Guchar cResult0, cResult1, cResult2, cResult3;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  SplashColorPtr softMaskPtr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 4;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 4];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
  softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 4;
      ++destAlphaPtr;
      ++softMaskPtr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = destColorPtr[0];
    cDest1 = destColorPtr[1];
    cDest2 = destColorPtr[2];
    cDest3 = destColorPtr[3];

    //----- read destination alpha
    aDest = *destAlphaPtr;

    //----- overprint
    cSrc0 = state->cmykTransferC[cSrcPtr[0]];
    cSrc1 = state->cmykTransferM[cSrcPtr[1]];
    cSrc2 = state->cmykTransferY[cSrcPtr[2]];
    cSrc3 = state->cmykTransferK[cSrcPtr[3]];

    //----- source alpha
    aSrc = div255(*softMaskPtr++ * shape);

    //----- special case for aSrc = 255
    if (aSrc == 255) {
      aResult = 255;
      cResult0 = cSrc0;
      cResult1 = cSrc1;
      cResult2 = cSrc2;
      cResult3 = cSrc3;
    } else {

      //----- special case for aDest = 0
      if (aDest == 0) {
        aResult = aSrc;
        cResult0 = cSrc0;
        cResult1 = cSrc1;
        cResult2 = cSrc2;
        cResult3 = cSrc3;
      } else {

        //----- result alpha and non-isolated group element correction
        aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
        alphaI = aResult;

        //----- result color
        cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
        cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
        cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
        cResult3 = (Guchar)(((alphaI - aSrc) * cDest3 + aSrc * cSrc3) / alphaI);
      }
    }

    //----- write destination pixel
    destColorPtr[0] = cResult0;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult2;
    destColorPtr[3] = cResult3;
    destColorPtr += 4;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  }

  updateModX(lastX);
}
#endif


void Splash::pipeRunNonIsoMono8(SplashPipe *pipe, int x0, int x1, int y,
				Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, alpha0, aResult;
  Guchar cSrc0, cDest0, cResult0;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  Guchar *alpha0Ptr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 1;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
  alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y)
				        * groupBackBitmap->alphaRowSize +
				      (groupBackX + x0)];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 1;
      ++destAlphaPtr;
      ++alpha0Ptr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = destColorPtr[0];
    aDest = *destAlphaPtr;

    //----- source color
    cSrc0 = state->grayTransfer[cSrcPtr[0]];

    //----- source alpha
    aSrc = div255(pipe->aInput * shape);

    //----- result alpha and non-isolated group element correction
    aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
    alpha0 = *alpha0Ptr++;
    alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0));

    //----- result color
    if (alphaI == 0) {
      cResult0 = 0;
    } else {
      cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
    }

    //----- write destination pixel
    *destColorPtr++ = cResult0;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  } // for (x ...)

  updateModX(lastX);
}

void Splash::pipeRunNonIsoRGB8(SplashPipe *pipe, int x0, int x1, int y,
			       Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, alpha0, aResult;
  Guchar cSrc0, cSrc1, cSrc2;
  Guchar cDest0, cDest1, cDest2;
  Guchar cResult0, cResult1, cResult2;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  Guchar *alpha0Ptr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
  alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y)
				        * groupBackBitmap->alphaRowSize +
				      (groupBackX + x0)];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 3;
      ++destAlphaPtr;
      ++alpha0Ptr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = destColorPtr[0];
    cDest1 = destColorPtr[1];
    cDest2 = destColorPtr[2];
    aDest = *destAlphaPtr;

    //----- source color
    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
    cSrc2 = state->rgbTransferB[cSrcPtr[2]];

    //----- source alpha
    aSrc = div255(pipe->aInput * shape);

    //----- result alpha and non-isolated group element correction
    aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
    alpha0 = *alpha0Ptr++;
    alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0));

    //----- result color
    if (alphaI == 0) {
      cResult0 = 0;
      cResult1 = 0;
      cResult2 = 0;
    } else {
      cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
      cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
      cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
    }

    //----- write destination pixel
    destColorPtr[0] = cResult0;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult2;
    destColorPtr += 3;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  } // for (x ...)

  updateModX(lastX);
}

void Splash::pipeRunNonIsoBGR8(SplashPipe *pipe, int x0, int x1, int y,
			       Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, alpha0, aResult;
  Guchar cSrc0, cSrc1, cSrc2;
  Guchar cDest0, cDest1, cDest2;
  Guchar cResult0, cResult1, cResult2;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  Guchar *alpha0Ptr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 3;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
  alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y)
				        * groupBackBitmap->alphaRowSize +
				      (groupBackX + x0)];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 3;
      ++destAlphaPtr;
      ++alpha0Ptr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = destColorPtr[2];
    cDest1 = destColorPtr[1];
    cDest2 = destColorPtr[0];
    aDest = *destAlphaPtr;

    //----- source color
    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
    cSrc2 = state->rgbTransferB[cSrcPtr[2]];

    //----- source alpha
    aSrc = div255(pipe->aInput * shape);

    //----- result alpha and non-isolated group element correction
    aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
    alpha0 = *alpha0Ptr++;
    alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0));

    //----- result color
    if (alphaI == 0) {
      cResult0 = 0;
      cResult1 = 0;
      cResult2 = 0;
    } else {
      cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
      cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
      cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
    }

    //----- write destination pixel
    destColorPtr[0] = cResult2;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult0;
    destColorPtr += 3;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  } // for (x ...)

  updateModX(lastX);
}

#if SPLASH_CMYK
void Splash::pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y,
				Guchar *shapePtr, SplashColorPtr cSrcPtr) {
  Guchar shape, aSrc, aDest, alphaI, alpha0, aResult, aPrev;
  Guchar cSrc0, cSrc1, cSrc2, cSrc3;
  Guchar cDest0, cDest1, cDest2, cDest3;
  Guchar cResult0, cResult1, cResult2, cResult3;
  SplashColorPtr destColorPtr;
  Guchar *destAlphaPtr;
  Guchar *alpha0Ptr;
  int cSrcStride, x, lastX;

  if (cSrcPtr) {
    cSrcStride = 4;
  } else {
    cSrcPtr = pipe->cSrcVal;
    cSrcStride = 0;
  }
  for (; x0 <= x1; ++x0) {
    if (*shapePtr) {
      break;
    }
    cSrcPtr += cSrcStride;
    ++shapePtr;
  }
  if (x0 > x1) {
    return;
  }
  updateModX(x0);
  updateModY(y);
  lastX = x0;

  useDestRow(y);

  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 4];
  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
  alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y)
				        * groupBackBitmap->alphaRowSize +
				      (groupBackX + x0)];

  for (x = x0; x <= x1; ++x) {

    //----- shape
    shape = *shapePtr;
    if (!shape) {
      destColorPtr += 4;
      ++destAlphaPtr;
      ++alpha0Ptr;
      cSrcPtr += cSrcStride;
      ++shapePtr;
      continue;
    }
    lastX = x;

    //----- read destination pixel
    cDest0 = destColorPtr[0];
    cDest1 = destColorPtr[1];
    cDest2 = destColorPtr[2];
    cDest3 = destColorPtr[3];
    aDest = *destAlphaPtr;

    //----- overprint
    aPrev = (Guchar)(*alpha0Ptr + aDest - div255(*alpha0Ptr * aDest));
    if (state->overprintMask & 0x01) {
      cSrc0 = state->cmykTransferC[cSrcPtr[0]];
    } else {
      cSrc0 = div255(aPrev * cDest0);
    }
    if (state->overprintMask & 0x02) {
      cSrc1 = state->cmykTransferM[cSrcPtr[1]];
    } else {
      cSrc1 = div255(aPrev * cDest1);
    }
    if (state->overprintMask & 0x04) {
      cSrc2 = state->cmykTransferY[cSrcPtr[2]];
    } else {
      cSrc2 = div255(aPrev * cDest2);
    }
    if (state->overprintMask & 0x08) {
      cSrc3 = state->cmykTransferK[cSrcPtr[3]];
    } else {
      cSrc3 = div255(aPrev * cDest3);
    }

    //----- source alpha
    aSrc = div255(pipe->aInput * shape);

    //----- result alpha and non-isolated group element correction
    aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
    alpha0 = *alpha0Ptr++;
    alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0));

    //----- result color
    if (alphaI == 0) {
      cResult0 = 0;
      cResult1 = 0;
      cResult2 = 0;
      cResult3 = 0;
    } else {
      cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
      cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
      cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
      cResult3 = (Guchar)(((alphaI - aSrc) * cDest3 + aSrc * cSrc3) / alphaI);
    }

    //----- write destination pixel
    destColorPtr[0] = cResult0;
    destColorPtr[1] = cResult1;
    destColorPtr[2] = cResult2;
    destColorPtr[3] = cResult3;
    destColorPtr += 4;
    *destAlphaPtr++ = aResult;

    cSrcPtr += cSrcStride;
    ++shapePtr;
  } // for (x ...)

  updateModX(lastX);
}
#endif


void Splash::useDestRow(int y) {
  int y0, y1, yy;

  if (groupDestInitMode == splashGroupDestPreInit) {
    return;
  }
  if (groupDestInitYMin > groupDestInitYMax) {
    y0 = y1 = y;
    groupDestInitYMin = groupDestInitYMax = y;
  } else if (y < groupDestInitYMin) {
    y0 = y;
    y1 = groupDestInitYMin - 1;
    groupDestInitYMin = y;
  } else if (y > groupDestInitYMax) {
    y0 = groupDestInitYMax + 1;
    y1 = y;
    groupDestInitYMax = y;
  } else {
    return;
  }
  for (yy = y0; yy <= y1; ++yy) {
    if (groupDestInitMode == splashGroupDestInitZero) {
      // same as clear(color=0, alpha=0)
      memset(bitmap->data + bitmap->rowSize * yy, 0,
	     bitmap->rowSize < 0 ? -bitmap->rowSize : bitmap->rowSize);
      if (bitmap->alpha) {
	memset(bitmap->alpha + bitmap->alphaRowSize * yy, 0,
	       bitmap->alphaRowSize);
      }
    } else { // (groupDestInitMode == splashGroupDestInitCopy)
      // same as blitTransparent
      copyGroupBackdropRow(yy);
    }
  }
}

void Splash::copyGroupBackdropRow(int y) {
  SplashColorPtr p, q;
  Guchar mask, srcMask;
  int x;

  if (groupBackBitmap->mode != bitmap->mode) {
    return;
  }

  if (bitmap->mode == splashModeMono1) {
    p = &bitmap->data[y * bitmap->rowSize];
    mask = (Guchar)0x80;
    q = &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize
			       + (groupBackX >> 3)];
    srcMask = (Guchar)(0x80 >> (groupBackX & 7));
    for (x = 0; x < bitmap->width; ++x) {
      if (*q & srcMask) {
	*p |= mask;
      } else {
	*p &= (Guchar)~mask;
      }
      if (!(mask = (Guchar)(mask >> 1))) {
	mask = 0x80;
	++p;
      }
      if (!(srcMask = (Guchar)(srcMask >> 1))) {
	srcMask = 0x80;
	++q;
      }
    }
  } else {
    p = &bitmap->data[y * bitmap->rowSize];
    q = &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize
			       + bitmapComps * groupBackX];
    memcpy(p, q, bitmapComps * bitmap->width);
  }

  if (bitmap->alpha) {
    memset(&bitmap->alpha[y * bitmap->alphaRowSize], 0, bitmap->width);
  }
}

//------------------------------------------------------------------------

// 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];
}

//------------------------------------------------------------------------
// SplashImageCache
//------------------------------------------------------------------------

SplashImageCache::SplashImageCache() {
  tag = NULL;
  width = 0;
  height = 0;
  mode = splashModeRGB8;
  alpha = gFalse;
  interpolate = gFalse;
  colorData = NULL;
  alphaData = NULL;
  refCount = 1;
}

SplashImageCache::~SplashImageCache() {
  if (tag) {
    delete tag;
  }
  gfree(colorData);
  gfree(alphaData);
}

GBool SplashImageCache::match(GString *aTag, int aWidth, int aHeight,
			      SplashColorMode aMode, GBool aAlpha,
			      GBool aInterpolate) {
  return aTag && tag && !aTag->cmp(tag) &&
	 aWidth == width && aHeight == height &&
	 aMode == mode && aAlpha == alpha &&
	 aInterpolate == interpolate;
}

void SplashImageCache::reset(GString *aTag, int aWidth, int aHeight,
			     SplashColorMode aMode, GBool aAlpha,
			     GBool aInterpolate) {
  if (tag) {
    delete tag;
  }
  if (aTag) {
    tag = aTag->copy();
  } else {
    tag = NULL;
  }
  width = aWidth;
  height = aHeight;
  mode = aMode;
  alpha = aAlpha;
  interpolate = aInterpolate; 
  gfree(colorData);
  colorData = NULL;
  gfree(alphaData);
  alphaData = NULL;
}

void SplashImageCache::incRefCount() {
  ++refCount;
}

void SplashImageCache::decRefCount() {
  --refCount;
  if (refCount == 0) {
    delete this;
  }
}

//------------------------------------------------------------------------
// ImageScaler
//------------------------------------------------------------------------

// Abstract base class.
class ImageScaler {
public:

  ImageScaler() {}
  virtual ~ImageScaler() {}

  // Compute the next line of the scaled image.  This can be called up
  // to [scaledHeight] times.
  virtual void nextLine() = 0;

  // Retrieve the color and alpha data generated by the most recent
  // call to nextLine().
  virtual Guchar *colorData() = 0;
  virtual Guchar *alphaData() = 0;
};

//------------------------------------------------------------------------
// BasicImageScaler
//------------------------------------------------------------------------

// The actual image scaler.
class BasicImageScaler: public ImageScaler {
public:

  BasicImageScaler(SplashImageSource aSrc, void *aSrcData,
		   int aSrcWidth, int aSrcHeight, int aNComps, GBool aHasAlpha,
		   int aScaledWidth, int aScaledHeight, GBool aInterpolate);
  virtual ~BasicImageScaler();
  virtual void nextLine();
  virtual Guchar *colorData() { return colorLine; }
  virtual Guchar *alphaData() { return alphaLine; }

protected:

  void vertDownscaleHorizDownscale();
  void vertDownscaleHorizUpscaleNoInterp();
  void vertDownscaleHorizUpscaleInterp();
  void vertUpscaleHorizDownscaleNoInterp();
  void vertUpscaleHorizDownscaleInterp();
  void vertUpscaleHorizUpscaleNoInterp();
  void vertUpscaleHorizUpscaleInterp();

  // source image data function
  SplashImageSource src;
  void *srcData;

  // source image size
  int srcWidth;
  int srcHeight;

  // scaled image size
  int scaledWidth;
  int scaledHeight;

  // number of color and alpha components
  int nComps;
  GBool hasAlpha;

  // params/state for vertical scaling
  int yp, yq;
  int yt, yn;
  int ySrcCur, yScaledCur;
  SplashCoord yInvScale;

  // params for horizontal scaling
  int xp, xq;
  SplashCoord xInvScale;

  // scaling function
  void (BasicImageScaler::*scalingFunc)();

  // temporary buffers for vertical scaling
  Guchar *colorTmpBuf0;
  Guchar *colorTmpBuf1;
  Guchar *colorTmpBuf2;
  Guchar *alphaTmpBuf0;
  Guchar *alphaTmpBuf1;
  Guchar *alphaTmpBuf2;
  Guint *colorAccBuf;
  Guint *alphaAccBuf;

  // output of horizontal scaling
  Guchar *colorLine;
  Guchar *alphaLine;
};

BasicImageScaler::BasicImageScaler(SplashImageSource aSrc, void *aSrcData,
				   int aSrcWidth, int aSrcHeight,
				   int aNComps, GBool aHasAlpha,
				   int aScaledWidth, int aScaledHeight,
				   GBool aInterpolate) {
  colorTmpBuf0 = NULL;
  colorTmpBuf1 = NULL;
  colorTmpBuf2 = NULL;
  alphaTmpBuf0 = NULL;
  alphaTmpBuf1 = NULL;
  alphaTmpBuf2 = NULL;
  colorAccBuf = NULL;
  alphaAccBuf = NULL;
  colorLine = NULL;
  alphaLine = NULL;

  src = aSrc;
  srcData = aSrcData;
  srcWidth = aSrcWidth;
  srcHeight = aSrcHeight;
  scaledWidth = aScaledWidth;
  scaledHeight = aScaledHeight;
  nComps = aNComps;
  hasAlpha = aHasAlpha;

  // select scaling function; allocate buffers
  if (scaledHeight <= srcHeight) {
    // vertical downscaling
    yp = srcHeight / scaledHeight;
    yq = srcHeight % scaledHeight;
    yt = 0;
    colorTmpBuf0 = (Guchar *)gmallocn(srcWidth, nComps);
    colorAccBuf = (Guint *)gmallocn(srcWidth, nComps * (int)sizeof(Guint));
    if (hasAlpha) {
      alphaTmpBuf0 = (Guchar *)gmalloc(srcWidth);
      alphaAccBuf = (Guint *)gmallocn(srcWidth, sizeof(Guint));
    }
    if (scaledWidth <= srcWidth) {
      scalingFunc = &BasicImageScaler::vertDownscaleHorizDownscale;
    } else {
      if (aInterpolate) {
	scalingFunc = &BasicImageScaler::vertDownscaleHorizUpscaleInterp;
      } else {
	scalingFunc = &BasicImageScaler::vertDownscaleHorizUpscaleNoInterp;
      }
    }
  } else {
    // vertical upscaling
    yp = scaledHeight / srcHeight;
    yq = scaledHeight % srcHeight;
    yt = 0;
    yn = 0;
    if (aInterpolate) {
      yInvScale = (SplashCoord)srcHeight / (SplashCoord)scaledHeight;
      colorTmpBuf0 = (Guchar *)gmallocn(srcWidth, nComps);
      colorTmpBuf1 = (Guchar *)gmallocn(srcWidth, nComps);
      if (hasAlpha) {
	alphaTmpBuf0 = (Guchar *)gmalloc(srcWidth);
	alphaTmpBuf1 = (Guchar *)gmalloc(srcWidth);
      }
      ySrcCur = 0;
      yScaledCur = 0;
      if (scaledWidth <= srcWidth) {
	scalingFunc = &BasicImageScaler::vertUpscaleHorizDownscaleInterp;
      } else {
	colorTmpBuf2 = (Guchar *)gmallocn(srcWidth, nComps);
	if (hasAlpha) {
	  alphaTmpBuf2 = (Guchar *)gmalloc(srcWidth);
	}
	scalingFunc = &BasicImageScaler::vertUpscaleHorizUpscaleInterp;
      }
    } else {
      colorTmpBuf0 = (Guchar *)gmallocn(srcWidth, nComps);
      if (hasAlpha) {
	alphaTmpBuf0 = (Guchar *)gmalloc(srcWidth);
      }
      if (scaledWidth <= srcWidth) {
	scalingFunc = &BasicImageScaler::vertUpscaleHorizDownscaleNoInterp;
      } else {
	scalingFunc = &BasicImageScaler::vertUpscaleHorizUpscaleNoInterp;
      }
    }
  }
  if (scaledWidth <= srcWidth) {
    xp = srcWidth / scaledWidth;
    xq = srcWidth % scaledWidth;
  } else {
    xp = scaledWidth / srcWidth;
    xq = scaledWidth % srcWidth;
    if (aInterpolate) {
      xInvScale = (SplashCoord)srcWidth / (SplashCoord)scaledWidth;
    }
  }
  colorLine = (Guchar *)gmallocn(scaledWidth, nComps);
  if (hasAlpha) {
    alphaLine = (Guchar *)gmalloc(scaledWidth);
  }
}

BasicImageScaler::~BasicImageScaler() {
  gfree(colorTmpBuf0);
  gfree(colorTmpBuf1);
  gfree(colorTmpBuf2);
  gfree(alphaTmpBuf0);
  gfree(alphaTmpBuf1);
  gfree(alphaTmpBuf2);
  gfree(colorAccBuf);
  gfree(alphaAccBuf);
  gfree(colorLine);
  gfree(alphaLine);
}

void BasicImageScaler::nextLine() {
  (this->*scalingFunc)();
}

void BasicImageScaler::vertDownscaleHorizDownscale() {
  //--- vert downscale
  int yStep = yp;
  yt += yq;
  if (yt >= scaledHeight) {
    yt -= scaledHeight;
    ++yStep;
  }
  memset(colorAccBuf, 0, (srcWidth * nComps) * sizeof(Guint));
  if (hasAlpha) {
    memset(alphaAccBuf, 0, srcWidth * sizeof(Guint));
  }
  int nRowComps = srcWidth * nComps;
  for (int i = 0; i < yStep; ++i) {
    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
    for (int j = 0; j < nRowComps; ++j) {
      colorAccBuf[j] += colorTmpBuf0[j];
    }
    if (hasAlpha) {
      for (int j = 0; j < srcWidth; ++j) {
	alphaAccBuf[j] += alphaTmpBuf0[j];
      }
    }
  }

  //--- horiz downscale
  int colorAcc[splashMaxColorComps];
  int xt = 0;
  int unscaledColorIdx = 0;
  int unscaledAlphaIdx = 0;
  int scaledColorIdx = 0;

  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= scaledWidth) {
      xt -= scaledWidth;
      ++xStep;
    }

    for (int j = 0; j < nComps; ++j) {
      colorAcc[j] = 0;
    }
    for (int i = 0; i < xStep; ++i) {
      for (int j = 0; j < nComps; ++j) {
	colorAcc[j] += colorAccBuf[unscaledColorIdx + j];
      }
      unscaledColorIdx += nComps;
    }
    int nPixels = yStep * xStep;
    for (int j = 0; j < nComps; ++j) {
      colorLine[scaledColorIdx + j] = (Guchar)(colorAcc[j] / nPixels);
    }
    scaledColorIdx += nComps;

    if (hasAlpha) {
      int alphaAcc = 0;
      for (int i = 0; i < xStep; ++i) {
	alphaAcc += alphaAccBuf[unscaledAlphaIdx];
	++unscaledAlphaIdx;
      }
      alphaLine[scaledIdx] = (Guchar)(alphaAcc / nPixels);
    }
  }
}

void BasicImageScaler::vertDownscaleHorizUpscaleNoInterp() {
  //--- vert downscale
  int yStep = yp;
  yt += yq;
  if (yt >= scaledHeight) {
    yt -= scaledHeight;
    ++yStep;
  }
  memset(colorAccBuf, 0, (srcWidth * nComps) * sizeof(Guint));
  if (hasAlpha) {
    memset(alphaAccBuf, 0, srcWidth * sizeof(Guint));
  }
  int nRowComps = srcWidth * nComps;
  for (int i = 0; i < yStep; ++i) {
    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
    for (int j = 0; j < nRowComps; ++j) {
      colorAccBuf[j] += colorTmpBuf0[j];
    }
    if (hasAlpha) {
      for (int j = 0; j < srcWidth; ++j) {
	alphaAccBuf[j] += alphaTmpBuf0[j];
      }
    }
  }

  //--- horiz upscale
  Guchar colorBuf[splashMaxColorComps];
  int xt = 0;
  int scaledColorIdx = 0;
  int srcColorIdx = 0;
  int scaledAlphaIdx = 0;
  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= srcWidth) {
      xt -= srcWidth;
      ++xStep;
    }
    for (int j = 0; j < nComps; ++j) {
      colorBuf[j] = (Guchar)(colorAccBuf[srcColorIdx + j] / yStep);
    }
    srcColorIdx += nComps;
    for (int i = 0; i < xStep; ++i) {
      for (int j = 0; j < nComps; ++j) {
	colorLine[scaledColorIdx + j] = colorBuf[j];
      }
      scaledColorIdx += nComps;
    }
    if (hasAlpha) {
      Guchar alphaBuf = (Guchar)(alphaAccBuf[srcIdx] / yStep);
      for (int i = 0; i < xStep; ++i) {
	alphaLine[scaledAlphaIdx] = alphaBuf;
	++scaledAlphaIdx;
      }
    }
  }
}

void BasicImageScaler::vertDownscaleHorizUpscaleInterp() {
  //--- vert downscale
  int yStep = yp;
  yt += yq;
  if (yt >= scaledHeight) {
    yt -= scaledHeight;
    ++yStep;
  }
  memset(colorAccBuf, 0, (srcWidth * nComps) * sizeof(Guint));
  if (hasAlpha) {
    memset(alphaAccBuf, 0, srcWidth * sizeof(Guint));
  }
  int nRowComps = srcWidth * nComps;
  for (int i = 0; i < yStep; ++i) {
    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
    for (int j = 0; j < nRowComps; ++j) {
      colorAccBuf[j] += colorTmpBuf0[j];
    }
    if (hasAlpha) {
      for (int j = 0; j < srcWidth; ++j) {
	alphaAccBuf[j] += alphaTmpBuf0[j];
      }
    }
  }
  for (int j = 0; j < srcWidth * nComps; ++j) {
    colorAccBuf[j] /= yStep;
  }
  if (hasAlpha) {
    for (int j = 0; j < srcWidth; ++j) {
      alphaAccBuf[j] /= yStep;
    }
  }

  //--- horiz upscale
  int scaledColorIdx = 0;
  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale;
    int x0 = splashFloor(xs - 0.5);
    int x1 = x0 + 1;
    SplashCoord s0 = (SplashCoord)x1 + 0.5 - xs;
    SplashCoord s1 = (SplashCoord)1 - s0;
    if (x0 < 0) {
      x0 = 0;
    }
    if (x1 >= srcWidth) {
      x1 = srcWidth - 1;
    }
    for (int j = 0; j < nComps; ++j) {
      colorLine[scaledColorIdx + j] =
	  (Guchar)(int)(s0 * colorAccBuf[x0 * nComps + j] +
			s1 * colorAccBuf[x1 * nComps + j]);
    }
    scaledColorIdx += nComps;
    if (hasAlpha) {
      alphaLine[scaledIdx] = (Guchar)(int)(s0 * alphaAccBuf[x0] +
					   s1 * alphaAccBuf[x1]);
    }
  }
}

void BasicImageScaler::vertUpscaleHorizDownscaleNoInterp() {
  //--- vert upscale
  if (yn == 0) {
    yn = yp;
    yt += yq;
    if (yt >= srcHeight) {
      yt -= srcHeight;
      ++yn;
    }
    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
  }
  --yn;

  //--- horiz downscale
  int colorAcc[splashMaxColorComps];
  int xt = 0;
  int unscaledColorIdx = 0;
  int unscaledAlphaIdx = 0;
  int scaledColorIdx = 0;
  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= scaledWidth) {
      xt -= scaledWidth;
      ++xStep;
    }

    for (int j = 0; j < nComps; ++j) {
      colorAcc[j] = 0;
    }
    for (int i = 0; i < xStep; ++i) {
      for (int j = 0; j < nComps; ++j) {
	colorAcc[j] += colorTmpBuf0[unscaledColorIdx + j];
      }
      unscaledColorIdx += nComps;
    }
    for (int j = 0; j < nComps; ++j) {
      colorLine[scaledColorIdx + j] = (Guchar)(colorAcc[j] / xStep);
    }
    scaledColorIdx += nComps;

    if (hasAlpha) {
      int alphaAcc = 0;
      for (int i = 0; i < xStep; ++i) {
	alphaAcc += alphaTmpBuf0[unscaledAlphaIdx];
	++unscaledAlphaIdx;
      }
      alphaLine[scaledIdx] = (Guchar)(alphaAcc / xStep);
    }
  }
}

void BasicImageScaler::vertUpscaleHorizDownscaleInterp() {
  //--- vert upscale
  if (ySrcCur == 0) {
    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
    (*src)(srcData, colorTmpBuf1, alphaTmpBuf1);
    ySrcCur = 1;
  }
  SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale;
  int y0 = splashFloor(ys - 0.5);
  int y1 = y0 + 1;
  SplashCoord vs0 = (SplashCoord)y1 + 0.5 - ys;
  SplashCoord vs1 = (SplashCoord)1 - vs0;
  if (y1 > ySrcCur && ySrcCur < srcHeight - 1) {
    Guchar *t = colorTmpBuf0;
    colorTmpBuf0 = colorTmpBuf1;
    colorTmpBuf1 = t;
    if (hasAlpha) {
      t = alphaTmpBuf0;
      alphaTmpBuf0 = alphaTmpBuf1;
      alphaTmpBuf1 = t;
    }
    (*src)(srcData, colorTmpBuf1, alphaTmpBuf1);
    ++ySrcCur;
  }
  Guchar *color0 = colorTmpBuf0;
  Guchar *color1 = colorTmpBuf1;
  Guchar *alpha0 = alphaTmpBuf0;
  Guchar *alpha1 = alphaTmpBuf1;
  if (y0 < 0) {
    y0 = 0;
    color1 = color0;
    alpha1 = alpha0;
  }
  if (y1 >= srcHeight) {
    y1 = srcHeight - 1;
    color0 = color1;
    alpha0 = alpha1;
  }
  ++yScaledCur;

  //--- horiz downscale
  int colorAcc[splashMaxColorComps];
  int xt = 0;
  int unscaledColorIdx = 0;
  int unscaledAlphaIdx = 0;
  int scaledColorIdx = 0;
  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= scaledWidth) {
      xt -= scaledWidth;
      ++xStep;
    }

    for (int j = 0; j < nComps; ++j) {
      colorAcc[j] = 0;
    }
    for (int i = 0; i < xStep; ++i) {
      for (int j = 0; j < nComps; ++j) {
	colorAcc[j] += (int)(vs0 * (int)color0[unscaledColorIdx + j] +
			     vs1 * (int)color1[unscaledColorIdx + j]);
      }
      unscaledColorIdx += nComps;
    }
    for (int j = 0; j < nComps; ++j) {
      colorLine[scaledColorIdx + j] = (Guchar)(colorAcc[j] / xStep);
    }
    scaledColorIdx += nComps;

    if (hasAlpha) {
      int alphaAcc = 0;
      for (int i = 0; i < xStep; ++i) {
	alphaAcc += (int)(vs0 * (int)alpha0[unscaledAlphaIdx] +
			  vs1 * (int)alpha1[unscaledAlphaIdx]);
	++unscaledAlphaIdx;
      }
      alphaLine[scaledIdx] = (Guchar)(alphaAcc / xStep);
    }
  }
}

void BasicImageScaler::vertUpscaleHorizUpscaleNoInterp() {
  //--- vert upscale
  if (yn == 0) {
    yn = yp;
    yt += yq;
    if (yt >= srcHeight) {
      yt -= srcHeight;
      ++yn;
    }
    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
  }
  --yn;

  //--- horiz upscale
  int xt = 0;
  int scaledColorIdx = 0;
  int srcColorIdx = 0;
  int scaledAlphaIdx = 0;
  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= srcWidth) {
      xt -= srcWidth;
      ++xStep;
    }
    for (int i = 0; i < xStep; ++i) {
      for (int j = 0; j < nComps; ++j) {
	colorLine[scaledColorIdx + j] = colorTmpBuf0[srcColorIdx + j];
      }
      scaledColorIdx += nComps;
    }
    srcColorIdx += nComps;
    if (hasAlpha) {
      Guchar alphaBuf = alphaTmpBuf0[srcIdx];
      for (int i = 0; i < xStep; ++i) {
	alphaLine[scaledAlphaIdx] = alphaBuf;
	++scaledAlphaIdx;
      }
    }
  }
}

void BasicImageScaler::vertUpscaleHorizUpscaleInterp() {
  //--- vert upscale
  if (ySrcCur == 0) {
    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
    (*src)(srcData, colorTmpBuf1, alphaTmpBuf1);
    ySrcCur = 1;
  }
  SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale;
  int y0 = splashFloor(ys - 0.5);
  int y1 = y0 + 1;
  SplashCoord vs0 = (SplashCoord)y1 + 0.5 - ys;
  SplashCoord vs1 = (SplashCoord)1 - vs0;
  if (y1 > ySrcCur && ySrcCur < srcHeight - 1) {
    Guchar *t = colorTmpBuf0;
    colorTmpBuf0 = colorTmpBuf1;
    colorTmpBuf1 = t;
    if (hasAlpha) {
      t = alphaTmpBuf0;
      alphaTmpBuf0 = alphaTmpBuf1;
      alphaTmpBuf1 = t;
    }
    (*src)(srcData, colorTmpBuf1, alphaTmpBuf1);
    ++ySrcCur;
  }
  Guchar *color0 = colorTmpBuf0;
  Guchar *color1 = colorTmpBuf1;
  Guchar *alpha0 = alphaTmpBuf0;
  Guchar *alpha1 = alphaTmpBuf1;
  if (y0 < 0) {
    y0 = 0;
    color1 = color0;
    alpha1 = alpha0;
  }
  if (y1 >= srcHeight) {
    y1 = srcHeight - 1;
    color0 = color1;
    alpha0 = alpha1;
  }
  ++yScaledCur;
  for (int srcIdx = 0; srcIdx < srcWidth * nComps; ++srcIdx) {
    colorTmpBuf2[srcIdx] = (Guchar)(int)(vs0 * (int)color0[srcIdx] +
					 vs1 * (int)color1[srcIdx]);
  }
  if (hasAlpha) {
    for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
      alphaTmpBuf2[srcIdx] = (Guchar)(int)(vs0 * (int)alpha0[srcIdx] +
					   vs1 * (int)alpha1[srcIdx]);
    }
  }

  //--- horiz upscale
  int scaledColorIdx = 0;
  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale;
    int x0 = splashFloor(xs - 0.5);
    int x1 = x0 + 1;
    SplashCoord hs0 = (SplashCoord)x1 + 0.5 - xs;
    SplashCoord hs1 = (SplashCoord)1 - hs0;
    if (x0 < 0) {
      x0 = 0;
    }
    if (x1 >= srcWidth) {
      x1 = srcWidth - 1;
    }
    for (int j = 0; j < nComps; ++j) {
      colorLine[scaledColorIdx + j] =
	  (Guchar)(int)(hs0 * (int)colorTmpBuf2[x0 * nComps + j] +
			hs1 * (int)colorTmpBuf2[x1 * nComps + j]);
    }
    scaledColorIdx += nComps;
    if (hasAlpha) {
      alphaLine[scaledIdx] = (Guchar)(int)(hs0 * (int)alphaTmpBuf2[x0] +
					   hs1 * (int)alphaTmpBuf2[x1]);
    }
  }
}

//------------------------------------------------------------------------
// SavingImageScaler
//------------------------------------------------------------------------

// Wrapper around BasicImageScaler that saves the scaled image for use
// by ReplayImageScaler.
class SavingImageScaler: public BasicImageScaler {
public:

  SavingImageScaler(SplashImageSource aSrc, void *aSrcData,
		    int aSrcWidth, int aSrcHeight, int aNComps, GBool aHasAlpha,
		    int aScaledWidth, int aScaledHeight, GBool aInterpolate,
		    Guchar *aColorCache, Guchar *aAlphaCache);
  virtual void nextLine();

private:

  Guchar *colorPtr;
  Guchar *alphaPtr;
};

SavingImageScaler::SavingImageScaler(SplashImageSource aSrc, void *aSrcData,
				     int aSrcWidth, int aSrcHeight,
				     int aNComps, GBool aHasAlpha,
				     int aScaledWidth, int aScaledHeight,
				     GBool aInterpolate,
				     Guchar *aColorCache, Guchar *aAlphaCache):
  BasicImageScaler(aSrc, aSrcData, aSrcWidth, aSrcHeight, aNComps, aHasAlpha,
		   aScaledWidth, aScaledHeight, aInterpolate)
{
  colorPtr = aColorCache;
  alphaPtr = aAlphaCache;
}

void SavingImageScaler::nextLine() {
  BasicImageScaler::nextLine();
  memcpy(colorPtr, colorData(), scaledWidth * nComps);
  colorPtr += scaledWidth * nComps;
  if (hasAlpha) {
    memcpy(alphaPtr, alphaData(), scaledWidth);
    alphaPtr += scaledWidth;
  }
}

//------------------------------------------------------------------------
// ReplayImageScaler
//------------------------------------------------------------------------

// "Replay" a scaled image saved by SavingImageScaler.
class ReplayImageScaler: public ImageScaler {
public:

  ReplayImageScaler(int aNComps, GBool aHasAlpha,
		    int aScaledWidth,
		    Guchar *aColorCache, Guchar *aAlphaCache);
  virtual void nextLine();
  virtual Guchar *colorData() { return colorLine; }
  virtual Guchar *alphaData() { return alphaLine; }

private:

  int nComps;
  GBool hasAlpha;
  int scaledWidth;
  Guchar *colorPtr;
  Guchar *alphaPtr;
  Guchar *colorLine;
  Guchar *alphaLine;
};

ReplayImageScaler::ReplayImageScaler(int aNComps, GBool aHasAlpha,
				     int aScaledWidth,
				     Guchar *aColorCache, Guchar *aAlphaCache) {
  nComps = aNComps;
  hasAlpha = aHasAlpha;
  scaledWidth = aScaledWidth;
  colorPtr = aColorCache;
  alphaPtr = aAlphaCache;
  colorLine = NULL;
  alphaLine = NULL;
}

void ReplayImageScaler::nextLine() {
  colorLine = colorPtr;
  alphaLine = alphaPtr;
  colorPtr += scaledWidth * nComps;
  if (hasAlpha) {
    alphaPtr += scaledWidth;
  }
}

//------------------------------------------------------------------------
// ImageMaskScaler
//------------------------------------------------------------------------

class ImageMaskScaler {
public:

  // Set up a MaskScaler to scale from [srcWidth]x[srcHeight] to
  // [scaledWidth]x[scaledHeight].
  ImageMaskScaler(SplashImageMaskSource aSrc, void *aSrcData,
		  int aSrcWidth, int aSrcHeight,
		  int aScaledWidth, int aScaledHeight, GBool aInterpolate);

  ~ImageMaskScaler();

  // Compute the next line of the scaled image mask.  This can be
  // called up to [scaledHeight] times.
  void nextLine();

  // Retrieve the data generated by the most recent call to
  // nextLine().
  Guchar *data() { return line; }

private:

  void vertDownscaleHorizDownscale();
  void vertDownscaleHorizUpscaleNoInterp();
  void vertDownscaleHorizUpscaleInterp();
  void vertUpscaleHorizDownscaleNoInterp();
  void vertUpscaleHorizDownscaleInterp();
  void vertUpscaleHorizUpscaleNoInterp();
  void vertUpscaleHorizUpscaleInterp();

  // source image data function
  SplashImageMaskSource src;
  void *srcData;

  // source image size
  int srcWidth;
  int srcHeight;

  // scaled image size
  int scaledWidth;
  int scaledHeight;

  // params/state for vertical scaling
  int yp, yq;
  int yt, yn;
  int ySrcCur, yScaledCur;
  SplashCoord yInvScale;

  // params for horizontal scaling
  int xp, xq;
  SplashCoord xInvScale;

  // vertical and horizontal scaling functions
  void (ImageMaskScaler::*scalingFunc)();

  // temporary buffers for vertical scaling
  Guchar *tmpBuf0;
  Guchar *tmpBuf1;
  Guchar *tmpBuf2;
  Guint *accBuf;

  // output of horizontal scaling
  Guchar *line;
};

ImageMaskScaler::ImageMaskScaler(SplashImageMaskSource aSrc, void *aSrcData,
				 int aSrcWidth, int aSrcHeight,
				 int aScaledWidth, int aScaledHeight,
				 GBool aInterpolate) {
  tmpBuf0 = NULL;
  tmpBuf1 = NULL;
  tmpBuf2 = NULL;
  accBuf = NULL;
  line = NULL;

  src = aSrc;
  srcData = aSrcData;
  srcWidth = aSrcWidth;
  srcHeight = aSrcHeight;
  scaledWidth = aScaledWidth;
  scaledHeight = aScaledHeight;

  // select scaling function; allocate buffers
  if (scaledHeight <= srcHeight) {
    // vertical downscaling
    yp = srcHeight / scaledHeight;
    yq = srcHeight % scaledHeight;
    yt = 0;
    tmpBuf0 = (Guchar *)gmalloc(srcWidth);
    accBuf = (Guint *)gmallocn(srcWidth, sizeof(Guint));
    if (scaledWidth <= srcWidth) {
      scalingFunc = &ImageMaskScaler::vertDownscaleHorizDownscale;
    } else {
      if (aInterpolate) {
	scalingFunc = &ImageMaskScaler::vertDownscaleHorizUpscaleInterp;
      } else {
	scalingFunc = &ImageMaskScaler::vertDownscaleHorizUpscaleNoInterp;
      }
    }
  } else {
    // vertical upscaling
    yp = scaledHeight / srcHeight;
    yq = scaledHeight % srcHeight;
    yt = 0;
    yn = 0;
    if (aInterpolate) {
      yInvScale = (SplashCoord)srcHeight / (SplashCoord)scaledHeight;
      tmpBuf0 = (Guchar *)gmalloc(srcWidth);
      tmpBuf1 = (Guchar *)gmalloc(srcWidth);
      ySrcCur = 0;
      yScaledCur = 0;
      if (scaledWidth <= srcWidth) {
	scalingFunc = &ImageMaskScaler::vertUpscaleHorizDownscaleInterp;
      } else {
	tmpBuf2 = (Guchar *)gmalloc(srcWidth);
	scalingFunc = &ImageMaskScaler::vertUpscaleHorizUpscaleInterp;
      }
    } else {
      tmpBuf0 = (Guchar *)gmalloc(srcWidth);
      if (scaledWidth <= srcWidth) {
	scalingFunc = &ImageMaskScaler::vertUpscaleHorizDownscaleNoInterp;
      } else {
	scalingFunc = &ImageMaskScaler::vertUpscaleHorizUpscaleNoInterp;
      }
    }
  }
  if (scaledWidth <= srcWidth) {
    xp = srcWidth / scaledWidth;
    xq = srcWidth % scaledWidth;
  } else {
    xp = scaledWidth / srcWidth;
    xq = scaledWidth % srcWidth;
    if (aInterpolate) {
      xInvScale = (SplashCoord)srcWidth / (SplashCoord)scaledWidth;
    }
  }
  line = (Guchar *)gmalloc(scaledWidth);
}

ImageMaskScaler::~ImageMaskScaler() {
  gfree(tmpBuf0);
  gfree(tmpBuf1);
  gfree(tmpBuf2);
  gfree(accBuf);
  gfree(line);
}

void ImageMaskScaler::nextLine() {
  (this->*scalingFunc)();
}

void ImageMaskScaler::vertDownscaleHorizDownscale() {
  //--- vert downscale
  int yStep = yp;
  yt += yq;
  if (yt >= scaledHeight) {
    yt -= scaledHeight;
    ++yStep;
  }
  memset(accBuf, 0, srcWidth * sizeof(Guint));
  for (int i = 0; i < yStep; ++i) {
    (*src)(srcData, tmpBuf0);
    for (int j = 0; j < srcWidth; ++j) {
      accBuf[j] += tmpBuf0[j];
    }
  }

  //--- horiz downscale
  int acc;
  int xt = 0;
  int unscaledIdx = 0;
  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= scaledWidth) {
      xt -= scaledWidth;
      ++xStep;
    }

    acc = 0;
    for (int i = 0; i < xStep; ++i) {
      acc += accBuf[unscaledIdx];
      ++unscaledIdx;
    }
    line[scaledIdx] = (Guchar)((255 * acc) / (xStep * yStep));
  }
}

void ImageMaskScaler::vertDownscaleHorizUpscaleNoInterp() {
  //--- vert downscale
  int yStep = yp;
  yt += yq;
  if (yt >= scaledHeight) {
    yt -= scaledHeight;
    ++yStep;
  }
  memset(accBuf, 0, srcWidth * sizeof(Guint));
  for (int i = 0; i < yStep; ++i) {
    (*src)(srcData, tmpBuf0);
    for (int j = 0; j < srcWidth; ++j) {
      accBuf[j] += tmpBuf0[j];
    }
  }

  //--- horiz upscale
  int xt = 0;
  int scaledIdx = 0;
  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= srcWidth) {
      xt -= srcWidth;
      ++xStep;
    }
    Guchar buf = (Guchar)((255 * accBuf[srcIdx]) / yStep);
    for (int i = 0; i < xStep; ++i) {
      line[scaledIdx] = buf;
      ++scaledIdx;
    }
  }
}

void ImageMaskScaler::vertDownscaleHorizUpscaleInterp() {
  //--- vert downscale
  int yStep = yp;
  yt += yq;
  if (yt >= scaledHeight) {
    yt -= scaledHeight;
    ++yStep;
  }
  memset(accBuf, 0, srcWidth * sizeof(Guint));
  for (int i = 0; i < yStep; ++i) {
    (*src)(srcData, tmpBuf0);
    for (int j = 0; j < srcWidth; ++j) {
      accBuf[j] += tmpBuf0[j];
    }
  }
  for (int j = 0; j < srcWidth; ++j) {
    accBuf[j] = (255 * accBuf[j]) / yStep;
  }

  //--- horiz upscale
  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale;
    int x0 = splashFloor(xs - 0.5);
    int x1 = x0 + 1;
    SplashCoord s0 = (SplashCoord)x1 + 0.5 - xs;
    SplashCoord s1 = (SplashCoord)1 - s0;
    if (x0 < 0) {
      x0 = 0;
    }
    if (x1 >= srcWidth) {
      x1 = srcWidth - 1;
    }
    line[scaledIdx] = (Guchar)(int)(s0 * accBuf[x0] + s1 * accBuf[x1]);
  }
}

void ImageMaskScaler::vertUpscaleHorizDownscaleNoInterp() {
  //--- vert upscale
  if (yn == 0) {
    yn = yp;
    yt += yq;
    if (yt >= srcHeight) {
      yt -= srcHeight;
      ++yn;
    }
    (*src)(srcData, tmpBuf0);
  }
  --yn;

  //--- horiz downscale
  int acc;
  int xt = 0;
  int unscaledIdx = 0;
  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= scaledWidth) {
      xt -= scaledWidth;
      ++xStep;
    }

    acc = 0;
    for (int i = 0; i < xStep; ++i) {
      acc += tmpBuf0[unscaledIdx];
      ++unscaledIdx;
    }
    line[scaledIdx] = (Guchar)((255 * acc) / xStep);
  }
}

void ImageMaskScaler::vertUpscaleHorizDownscaleInterp() {
  //--- vert upscale
  if (ySrcCur == 0) {
    (*src)(srcData, tmpBuf0);
    (*src)(srcData, tmpBuf1);
    ySrcCur = 1;
  }
  SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale;
  int y0 = splashFloor(ys - 0.5);
  int y1 = y0 + 1;
  SplashCoord vs0 = (SplashCoord)y1 + 0.5 - ys;
  SplashCoord vs1 = (SplashCoord)1 - vs0;
  if (y1 > ySrcCur && ySrcCur < srcHeight - 1) {
    Guchar *t = tmpBuf0;
    tmpBuf0 = tmpBuf1;
    tmpBuf1 = t;
    (*src)(srcData, tmpBuf1);
    ++ySrcCur;
  }
  Guchar *mask0 = tmpBuf0;
  Guchar *mask1 = tmpBuf1;
  if (y0 < 0) {
    y0 = 0;
    mask1 = mask0;
  }
  if (y1 >= srcHeight) {
    y1 = srcHeight - 1;
    mask0 = mask1;
  }
  ++yScaledCur;

  //--- horiz downscale
  int acc;
  int xt = 0;
  int unscaledIdx = 0;
  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= scaledWidth) {
      xt -= scaledWidth;
      ++xStep;
    }

    acc = 0;
    for (int i = 0; i < xStep; ++i) {
      acc += (int)(vs0 * (int)mask0[unscaledIdx] +
		   vs1 * (int)mask1[unscaledIdx]);
      ++unscaledIdx;
    }
    line[scaledIdx] = (Guchar)((255 * acc) / xStep);
  }
}

void ImageMaskScaler::vertUpscaleHorizUpscaleNoInterp() {
  //--- vert upscale
  if (yn == 0) {
    yn = yp;
    yt += yq;
    if (yt >= srcHeight) {
      yt -= srcHeight;
      ++yn;
    }
    (*src)(srcData, tmpBuf0);
  }
  --yn;

  //--- horiz upscale
  int xt = 0;
  int scaledIdx = 0;
  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
    int xStep = xp;
    xt += xq;
    if (xt >= srcWidth) {
      xt -= srcWidth;
      ++xStep;
    }
    Guchar buf = (Guchar)(255 * tmpBuf0[srcIdx]);
    for (int i = 0; i < xStep; ++i) {
      line[scaledIdx] = buf;
      ++scaledIdx;
    }
  }
}

void ImageMaskScaler::vertUpscaleHorizUpscaleInterp() {
  //--- vert upscale
  if (ySrcCur == 0) {
    (*src)(srcData, tmpBuf0);
    (*src)(srcData, tmpBuf1);
    ySrcCur = 1;
  }
  SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale;
  int y0 = splashFloor(ys - 0.5);
  int y1 = y0 + 1;
  SplashCoord vs0 = (SplashCoord)255 * ((SplashCoord)y1 + 0.5 - ys);
  SplashCoord vs1 = (SplashCoord)255 - vs0;
  if (y1 > ySrcCur && ySrcCur < srcHeight - 1) {
    Guchar *t = tmpBuf0;
    tmpBuf0 = tmpBuf1;
    tmpBuf1 = t;
    (*src)(srcData, tmpBuf1);
    ++ySrcCur;
  }
  Guchar *mask0 = tmpBuf0;
  Guchar *mask1 = tmpBuf1;
  if (y0 < 0) {
    y0 = 0;
    mask1 = mask0;
  }
  if (y1 >= srcHeight) {
    y1 = srcHeight - 1;
    mask0 = mask1;
  }
  ++yScaledCur;
  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
    tmpBuf2[srcIdx] = (Guchar)(int)(vs0 * (int)mask0[srcIdx] +
				    vs1 * (int)mask1[srcIdx]);
  }

  //--- horiz upscale
  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
    SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale;
    int x0 = splashFloor(xs - 0.5);
    int x1 = x0 + 1;
    SplashCoord hs0 = (SplashCoord)x1 + 0.5 - xs;
    SplashCoord hs1 = (SplashCoord)1 - hs0;
    if (x0 < 0) {
      x0 = 0;
    }
    if (x1 >= srcWidth) {
      x1 = srcWidth - 1;
    }
    line[scaledIdx] = (Guchar)(int)(hs0 * (int)tmpBuf2[x0] +
				    hs1 * (int)tmpBuf2[x1]);
  }
}

//------------------------------------------------------------------------
// Splash
//------------------------------------------------------------------------

Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
	       SplashImageCache *imageCacheA,
	       SplashScreenParams *screenParams) {
  bitmap = bitmapA;
  bitmapComps = splashColorModeNComps[bitmap->mode];
  vectorAntialias = vectorAntialiasA;
  inShading = gFalse;
  state = new SplashState(bitmap->width, bitmap->height, vectorAntialias,
			  screenParams);
  scanBuf = (Guchar *)gmalloc(bitmap->width);
  if (bitmap->mode == splashModeMono1) {
    scanBuf2 = (Guchar *)gmalloc(bitmap->width);
  } else {
    scanBuf2 = NULL;
  }
  groupBackBitmap = NULL;
  groupDestInitMode = splashGroupDestPreInit;
  overprintMaskBitmap = NULL;
  minLineWidth = 0;
  clearModRegion();
  debugMode = gFalse;

  if (imageCacheA) {
    imageCache = imageCacheA;
    imageCache->incRefCount();
  } else {
    imageCache = new SplashImageCache();
  }
}

Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
	       SplashImageCache *imageCacheA, SplashScreen *screenA) {
  bitmap = bitmapA;
  bitmapComps = splashColorModeNComps[bitmap->mode];
  vectorAntialias = vectorAntialiasA;
  inShading = gFalse;
  state = new SplashState(bitmap->width, bitmap->height, vectorAntialias,
			  screenA);
  scanBuf = (Guchar *)gmalloc(bitmap->width);
  if (bitmap->mode == splashModeMono1) {
    scanBuf2 = (Guchar *)gmalloc(bitmap->width);
  } else {
    scanBuf2 = NULL;
  }
  groupBackBitmap = NULL;
  groupDestInitMode = splashGroupDestPreInit;
  overprintMaskBitmap = NULL;
  minLineWidth = 0;
  clearModRegion();
  debugMode = gFalse;

  if (imageCacheA) {
    imageCache = imageCacheA;
    imageCache->incRefCount();
  } else {
    imageCache = new SplashImageCache();
  }
}

Splash::~Splash() {
  imageCache->decRefCount();

  while (state->next) {
    restoreState();
  }
  delete state;
  gfree(scanBuf);
  gfree(scanBuf2);
}

//------------------------------------------------------------------------
// 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;
}

SplashStrokeAdjustMode Splash::getStrokeAdjust() {
  return state->strokeAdjust;
}

SplashClip *Splash::getClip() {
  return state->clip;
}

SplashBitmap *Splash::getSoftMask() {
  return state->softMask;
}

GBool Splash::getInNonIsolatedGroup() {
  return state->inNonIsolatedGroup;
}

GBool Splash::getInKnockoutGroup() {
  return state->inKnockoutGroup;
}

//------------------------------------------------------------------------
// 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) {
  if (lineCap >= 0 && lineCap <= 2) {
    state->lineCap = lineCap;
  } else {
    state->lineCap = 0;
  }
}

void Splash::setLineJoin(int lineJoin) {
  if (lineJoin >= 0 && lineJoin <= 2) {
    state->lineJoin = lineJoin;
  } else {
    state->lineJoin = 0;
  }
}

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(SplashStrokeAdjustMode strokeAdjust) {
  state->strokeAdjust = strokeAdjust;
}

void Splash::clipResetToRect(SplashCoord x0, SplashCoord y0,
			     SplashCoord x1, SplashCoord y1) {
  state->clipResetToRect(x0, y0, x1, y1);
}

SplashError Splash::clipToRect(SplashCoord x0, SplashCoord y0,
			       SplashCoord x1, SplashCoord y1) {
  return state->clipToRect(x0, y0, x1, y1);
}

SplashError Splash::clipToPath(SplashPath *path, GBool eo) {
  return state->clipToPath(path, eo);
}

void Splash::setSoftMask(SplashBitmap *softMask) {
  state->setSoftMask(softMask);
}

void Splash::setInTransparencyGroup(SplashBitmap *groupBackBitmapA,
				    int groupBackXA, int groupBackYA,
				    SplashGroupDestInitMode groupDestInitModeA,
				    GBool nonIsolated, GBool knockout) {
  groupBackBitmap = groupBackBitmapA;
  groupBackX = groupBackXA;
  groupBackY = groupBackYA;
  groupDestInitMode = groupDestInitModeA;
  groupDestInitYMin = 1;
  groupDestInitYMax = 0;
  state->inNonIsolatedGroup = nonIsolated;
  state->inKnockoutGroup = knockout;
}

void Splash::forceDeferredInit(int y, int h) {
  useDestRow(y);
  useDestRow(y + h - 1);
}

void Splash::setTransfer(Guchar *red, Guchar *green, Guchar *blue,
			 Guchar *gray) {
  state->setTransfer(red, green, blue, gray);
}

void Splash::setOverprintMask(Guint overprintMask) {
  state->overprintMask = overprintMask;
}


void Splash::setEnablePathSimplification(GBool en) {
  state->enablePathSimplification = en;
}

//------------------------------------------------------------------------
// 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[0];
	  *p++ = color[1];
	  *p++ = color[2];
	}
	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[2];
	  *p++ = color[1];
	  *p++ = color[0];
	}
	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->alphaRowSize * bitmap->height);
  }

  updateModX(0);
  updateModY(0);
  updateModX(bitmap->width - 1);
  updateModY(bitmap->height - 1);
}

SplashError Splash::stroke(SplashPath *path) {
  SplashPath *path2, *dPath;
  SplashCoord t0, t1, t2, t3, w, w2, lineDashMax, lineDashTotal;
  int lineCap, lineJoin, i;

  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);

  // Compute an approximation of the transformed line width.
  // Given a CTM of [m0 m1],
  //                [m2 m3]
  // if |m0|*|m3| >= |m1|*|m2| then use min{|m0|,|m3|}, else
  // use min{|m1|,|m2|}.
  // This handles the common cases -- [s 0   ] and [0    s] --
  //                                  [0 +/-s]     [+/-s 0]
  // well, and still does something reasonable for the uncommon
  // case transforms.
  t0 = splashAbs(state->matrix[0]);
  t1 = splashAbs(state->matrix[1]);
  t2 = splashAbs(state->matrix[2]);
  t3 = splashAbs(state->matrix[3]);
  if (t0 * t3 >= t1 * t2) {
    w = (t0 < t3) ? t0 : t3;
  } else {
    w = (t1 < t2) ? t1 : t2;
  }
  w2 = w * state->lineWidth;

  // construct the dashed path
  if (state->lineDashLength > 0) {

    // check the maximum transformed dash element length (using the
    // same approximation as for line width) -- if it's less than 0.1
    // pixel, don't apply the dash pattern; this avoids a huge
    // performance/memory hit with PDF files that use absurd dash
    // patterns like [0.0007 0.0003]
    lineDashTotal = 0;
    lineDashMax = 0;
    for (i = 0; i < state->lineDashLength; ++i) {
      lineDashTotal += state->lineDash[i];
      if (state->lineDash[i] > lineDashMax) {
	lineDashMax = state->lineDash[i];
      }
    }
    // Acrobat simply draws nothing if the dash array is [0]
    if (lineDashTotal == 0) {
      delete path2;
      return splashOk;
    }
    if (w * lineDashMax > 0.1) {

      dPath = makeDashedPath(path2);
      delete path2;
      path2 = dPath;
      if (path2->length == 0) {
	delete path2;
	return splashErrEmptyPath;
      }
    }
  }

  // round caps on narrow lines look bad, and can't be
  // stroke-adjusted, so use projecting caps instead (but we can't do
  // this if there are zero-length dashes or segments, because those
  // turn into round dots)
  lineCap = state->lineCap;
  lineJoin = state->lineJoin;
  if (state->strokeAdjust == splashStrokeAdjustCAD &&
      w2 < 3.5) {
    if (lineCap == splashLineCapRound &&
	!state->lineDashContainsZeroLengthDashes() &&
	!path->containsZeroLengthSubpaths()) {
      lineCap = splashLineCapProjecting;
    }
    if (lineJoin == splashLineJoinRound) {
      lineJoin = splashLineJoinBevel;
    }
  }

  // if there is a min line width set, and the transformed line width
  // is smaller, use the min line width
  if (w > 0 && w2 < minLineWidth) {
    strokeWide(path2, minLineWidth / w, splashLineCapButt, splashLineJoinBevel);
  } else if (bitmap->mode == splashModeMono1 || !vectorAntialias) {
    // in monochrome mode or if antialiasing is disabled, use 0-width
    // lines for any transformed line width <= 1 -- lines less than 1
    // pixel wide look too fat without antialiasing
    if (w2 < 1.001) {
      strokeNarrow(path2);
    } else {
      strokeWide(path2, state->lineWidth, lineCap, lineJoin);
    }
  } else {
    // in gray and color modes, only use 0-width lines if the line
    // width is explicitly set to 0
    if (state->lineWidth == 0) {
      strokeNarrow(path2);
    } else {
      strokeWide(path2, state->lineWidth, lineCap, lineJoin);
    }
  }

  delete path2;
  return splashOk;
}

void Splash::strokeNarrow(SplashPath *path) {
  SplashPipe pipe;
  SplashXPath *xPath;
  SplashXPathSeg *seg;
  int x0, x1, y0, y1, xa, xb, y;
  SplashCoord dxdy;
  SplashClipResult clipRes;
  int nClipRes[3];
  int i;

  nClipRes[0] = nClipRes[1] = nClipRes[2] = 0;

  xPath = new SplashXPath(path, state->matrix, state->flatness, gFalse,
			  state->enablePathSimplification,
			  state->strokeAdjust);

  pipeInit(&pipe, state->strokePattern,
	   (Guchar)splashRound(state->strokeAlpha * 255),
	   gTrue, gFalse);

  for (i = 0, seg = xPath->segs; i < xPath->length; ++i, ++seg) {
    if (seg->y0 <= seg->y1) {
      y0 = splashFloor(seg->y0);
      y1 = splashFloor(seg->y1);
      x0 = splashFloor(seg->x0);
      x1 = splashFloor(seg->x1);
    } else {
      y0 = splashFloor(seg->y1);
      y1 = splashFloor(seg->y0);
      x0 = splashFloor(seg->x1);
      x1 = splashFloor(seg->x0);
    }
    if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0,
					 x0 <= x1 ? x1 : x0, y1,
					 state->strokeAdjust))
	!= splashClipAllOutside) {
      if (y0 == y1) {
	if (x0 <= x1) {
	  drawStrokeSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside);
	} else {
	  drawStrokeSpan(&pipe, x1, x0, y0, clipRes == splashClipAllInside);
	}
      } else {
	dxdy = seg->dxdy;
	y = state->clip->getYMinI(state->strokeAdjust);
	if (y0 < y) {
	  y0 = y;
	  x0 = splashFloor(seg->x0 + ((SplashCoord)y0 - seg->y0) * dxdy);
	}
	y = state->clip->getYMaxI(state->strokeAdjust);
	if (y1 > y) {
	  y1 = y;
	  x1 = splashFloor(seg->x0 + ((SplashCoord)y1 - seg->y0) * dxdy);
	}
	if (x0 <= x1) {
	  xa = x0;
	  for (y = y0; y <= y1; ++y) {
	    if (y < y1) {
	      xb = splashFloor(seg->x0 +
			       ((SplashCoord)y + 1 - seg->y0) * dxdy);
	    } else {
	      xb = x1 + 1;
	    }
	    if (xa == xb) {
	      drawStrokeSpan(&pipe, xa, xa, y, clipRes == splashClipAllInside);
	    } else {
	      drawStrokeSpan(&pipe, xa, xb - 1, y,
			     clipRes == splashClipAllInside);
	    }
	    xa = xb;
	  }
	} else {
	  xa = x0;
	  for (y = y0; y <= y1; ++y) {
	    if (y < y1) {
	      xb = splashFloor(seg->x0 +
			       ((SplashCoord)y + 1 - seg->y0) * dxdy);
	    } else {
	      xb = x1 - 1;
	    }
	    if (xa == xb) {
	      drawStrokeSpan(&pipe, xa, xa, y, clipRes == splashClipAllInside);
	    } else {
	      drawStrokeSpan(&pipe, xb + 1, xa, y,
			     clipRes == splashClipAllInside);
	    }
	    xa = xb;
	  }
	}
      }
    }
    ++nClipRes[clipRes];
  }
  if (nClipRes[splashClipPartial] ||
      (nClipRes[splashClipAllInside] && nClipRes[splashClipAllOutside])) {
    opClipRes = splashClipPartial;
  } else if (nClipRes[splashClipAllInside]) {
    opClipRes = splashClipAllInside;
  } else {
    opClipRes = splashClipAllOutside;
  }

  delete xPath;
}

void Splash::drawStrokeSpan(SplashPipe *pipe, int x0, int x1, int y,
			    GBool noClip) {
  int x;

  x = state->clip->getXMinI(state->strokeAdjust);
  if (x > x0) {
    x0 = x;
  }
  x = state->clip->getXMaxI(state->strokeAdjust);
  if (x < x1) {
    x1 = x;
  }
  if (x0 > x1) {
    return;
  }
  for (x = x0; x <= x1; ++x) {
    scanBuf[x] = 0xff;
  }
  if (!noClip) {
    if (!state->clip->clipSpanBinary(scanBuf, y, x0, x1, state->strokeAdjust)) {
      return;
    }
  }
  (this->*pipe->run)(pipe, x0, x1, y, scanBuf + x0, NULL);
}

void Splash::strokeWide(SplashPath *path, SplashCoord w,
			int lineCap, int lineJoin) {
  SplashPath *path2;

  path2 = makeStrokePath(path, w, lineCap, lineJoin, 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();
#if USE_FIXEDPOINT
  flatness2 = flatness;
#else
  flatness2 = flatness * flatness;
#endif
  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);
#if USE_FIXEDPOINT
    d1 = splashDist(tx, ty, mx, my);
#else
    dx = tx - mx;
    dy = ty - my;
    d1 = dx*dx + dy*dy;
#endif
    transform(matrix, xx2, yy2, &tx, &ty);
#if USE_FIXEDPOINT
    d2 = splashDist(tx, ty, mx, my);
#else
    dx = tx - mx;
    dy = ty - my;
    d2 = dx*dx + dy*dy;
#endif

    // 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 = splashAvg(xl0, xx1);
      yl1 = splashAvg(yl0, yy1);
      xh = splashAvg(xx1, xx2);
      yh = splashAvg(yy1, yy2);
      xl2 = splashAvg(xl1, xh);
      yl2 = splashAvg(yl1, yh);
      xr2 = splashAvg(xx2, xr3);
      yr2 = splashAvg(yy2, yr3);
      xr1 = splashAvg(xh, xr2);
      yr1 = splashAvg(yh, yr2);
      xr0 = splashAvg(xl2, xr1);
      yr0 = splashAvg(yl2, yr1);
      // add the new subdivision points
      p3 = (p1 + p2) / 2;
      cx[p1][1] = xl1;  cy[p1][1] = yl1;
      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, lineDashEndOn, lineDashOn, newPath;
  int lineDashStartIdx, lineDashIdx, subpathStart, nDashes;
  int i, j, k;

  lineDashTotal = 0;
  for (i = 0; i < state->lineDashLength; ++i) {
    lineDashTotal += state->lineDash[i];
  }
  // Acrobat simply draws nothing if the dash array is [0]
  if (lineDashTotal == 0) {
    return new SplashPath();
  }
  lineDashStartPhase = state->lineDashPhase;
  if (lineDashStartPhase > lineDashTotal * 2) {
    i = splashFloor(lineDashStartPhase / (lineDashTotal * 2));
    lineDashStartPhase -= lineDashTotal * i * 2;
  } else if (lineDashStartPhase < 0) {
    i = splashCeil(-lineDashStartPhase / (lineDashTotal * 2));
    lineDashStartPhase += lineDashTotal * i * 2;
  }
  i = splashFloor(lineDashStartPhase / lineDashTotal);
  lineDashStartPhase -= (SplashCoord)i * lineDashTotal;
  lineDashStartOn = gTrue;
  lineDashStartIdx = 0;
  if (lineDashStartPhase > 0) {
    while (lineDashStartPhase >= state->lineDash[lineDashStartIdx]) {
      lineDashStartOn = !lineDashStartOn;
      lineDashStartPhase -= state->lineDash[lineDashStartIdx];
      if (++lineDashStartIdx == state->lineDashLength) {
	lineDashStartIdx = 0;
      }
    }
  }

  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;
    lineDashEndOn = lineDashStartOn;
    lineDashIdx = lineDashStartIdx;
    lineDashDist = state->lineDash[lineDashIdx] - lineDashStartPhase;
    subpathStart = dPath->length;
    nDashes = 0;

    // 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) {

	// Special case for zero-length dash segments: draw a very
	// short -- but not zero-length -- segment.  This ensures that
	// we get the correct behavior with butt and projecting line
	// caps.  The PS/PDF specs imply that zero-length segments are
	// not drawn unless the line cap is round, but Acrobat and
	// Ghostscript both draw very short segments (for butt caps)
	// and squares (for projecting caps).
	if (lineDashDist == 0) {
	  if (lineDashOn) {
	    if (newPath) {
	      dPath->moveTo(x0, y0);
	      newPath = gFalse;
	      ++nDashes;
	    }
	    xa = x0 + ((SplashCoord)0.001 / segLen) * (x1 - x0);
	    ya = y0 + ((SplashCoord)0.001 / segLen) * (y1 - y0);
	    dPath->lineTo(xa, ya);
	  }

	} else if (lineDashDist >= segLen) {
	  if (lineDashOn) {
	    if (newPath) {
	      dPath->moveTo(x0, y0);
	      newPath = gFalse;
	      ++nDashes;
	    }
	    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;
	      ++nDashes;
	    }
	    dPath->lineTo(xa, ya);
	  }
	  x0 = xa;
	  y0 = ya;
	  segLen -= lineDashDist;
	  lineDashDist = 0;
	}

	lineDashEndOn = lineDashOn;

	// 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;
	}
      }
    }

    // in a closed subpath, where the dash pattern is "on" at both the
    // start and end of the subpath, we need to merge the start and
    // end to get a proper line join
    if ((path->flags[j] & splashPathClosed) &&
	lineDashStartOn &&
	lineDashEndOn) {
      if (nDashes == 1) {
	dPath->close();
      } else if (nDashes > 1) {
	k = subpathStart;
	do {
	  ++k;
	  dPath->lineTo(dPath->pts[k].x, dPath->pts[k].y);
	} while (!(dPath->flags[k] & splashPathLast));
	++k;
	memmove(&dPath->pts[subpathStart], &dPath->pts[k],
		(dPath->length - k) * sizeof(SplashPathPoint));
	memmove(&dPath->flags[subpathStart], &dPath->flags[k],
		(dPath->length - k) * sizeof(Guchar));
	dPath->length -= k - subpathStart;
	dPath->curSubpath -= k - subpathStart;
      }
    }

    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;
  SplashPath *path2;
  SplashXPath *xPath;
  SplashXPathScanner *scanner;
  int xMin, yMin, xMax, xMin2, xMax2, yMax, y, t;
  SplashClipResult clipRes;

  if (path->length == 0) {
    return splashErrEmptyPath;
  }
  if (pathAllOutside(path)) {
    opClipRes = splashClipAllOutside;
    return splashOk;
  }

  path2 = tweakFillPath(path);

  xPath = new SplashXPath(path2, state->matrix, state->flatness, gTrue,
			  state->enablePathSimplification,
			  state->strokeAdjust);
  if (path2 != path) {
    delete path2;
  }
  xMin = xPath->getXMin();
  yMin = xPath->getYMin();
  xMax = xPath->getXMax();
  yMax = xPath->getYMax();
  if (xMin > xMax || yMin > yMax) {
    delete xPath;
    return splashOk;
  }
  scanner = new SplashXPathScanner(xPath, eo, yMin, yMax);

  // check clipping
  if ((clipRes = state->clip->testRect(xMin, yMin, xMax, yMax,
				       state->strokeAdjust))
      != splashClipAllOutside) {

    if ((t = state->clip->getXMinI(state->strokeAdjust)) > xMin) {
      xMin = t;
    }
    if ((t = state->clip->getXMaxI(state->strokeAdjust)) < xMax) {
      xMax = t;
    }
    if ((t = state->clip->getYMinI(state->strokeAdjust)) > yMin) {
      yMin = t;
    }
    if ((t = state->clip->getYMaxI(state->strokeAdjust)) < yMax) {
      yMax = t;
    }
    if (xMin > xMax || yMin > yMax) {
      delete scanner;
      delete xPath;
      return splashOk;
    }

    pipeInit(&pipe, pattern, (Guchar)splashRound(alpha * 255),
	     gTrue, gFalse);

    // draw the spans
    if (vectorAntialias && !inShading) {
      for (y = yMin; y <= yMax; ++y) {
	scanner->getSpan(scanBuf, y, xMin, xMax, &xMin2, &xMax2);
	if (xMin2 <= xMax2) {
	  if (clipRes != splashClipAllInside) {
	    state->clip->clipSpan(scanBuf, y, xMin2, xMax2,
				  state->strokeAdjust);
	  }
	  (this->*pipe.run)(&pipe, xMin2, xMax2, y, scanBuf + xMin2, NULL);
	}
      }
    } else {
      for (y = yMin; y <= yMax; ++y) {
	scanner->getSpanBinary(scanBuf, y, xMin, xMax, &xMin2, &xMax2);
	if (xMin2 <= xMax2) {
	  if (clipRes != splashClipAllInside) {
	    state->clip->clipSpanBinary(scanBuf, y, xMin2, xMax2,
					state->strokeAdjust);
	  }
	  (this->*pipe.run)(&pipe, xMin2, xMax2, y, scanBuf + xMin2, NULL);
	}
      }
    }
  }
  opClipRes = clipRes;

  delete scanner;
  delete xPath;
  return splashOk;
}

// Applies various tweaks to a fill path:
// (1) add stroke adjust hints to a filled rectangle
// (2) applies a minimum width to a zero-width filled rectangle (so
//     stroke adjustment works correctly
// (3) convert a degenerate fill ('moveto lineto fill' and 'moveto
//     lineto closepath fill') to a minimum-width filled rectangle
//
// These tweaks only apply to paths with a single subpath.
//
// Returns either the unchanged input path or a new path (in which
// case the returned path must be deleted by the caller).
SplashPath *Splash::tweakFillPath(SplashPath *path) {
  SplashPath *path2;
  SplashCoord xx0, yy0, xx1, yy1, dx, dy, d, wx, wy, w;
  int n;

  if (state->strokeAdjust == splashStrokeAdjustOff || path->hints) {
    return path;
  }

  n = path->getLength();
  if (!((n == 2) ||
	(n == 3 &&
	 path->flags[1] == 0) ||
	(n == 4 &&
	 path->flags[1] == 0 &&
	 path->flags[2] == 0) ||
	(n == 5 &&
	 path->flags[1] == 0 &&
	 path->flags[2] == 0 &&
	 path->flags[3] == 0))) {
    return path;
  }

  path2 = path;

  // degenerate fill (2 or 3 points) or rectangle of (nearly) zero
  // width --> replace with a min-width rectangle and hint
  if (n == 2 ||
      (n == 3 && (path->flags[0] & splashPathClosed)) ||
      (n == 3 && (splashAbs(path->pts[0].x - path->pts[2].x) < 0.001 &&
		  splashAbs(path->pts[0].y - path->pts[2].y) < 0.001)) ||
      ((n == 4 ||
	(n == 5 && (path->flags[0] & splashPathClosed))) &&
       ((splashAbs(path->pts[0].x - path->pts[1].x) < 0.001 &&
	 splashAbs(path->pts[0].y - path->pts[1].y) < 0.001 &&
	 splashAbs(path->pts[2].x - path->pts[3].x) < 0.001 &&
	 splashAbs(path->pts[2].y - path->pts[3].y) < 0.001) ||
	(splashAbs(path->pts[0].x - path->pts[3].x) < 0.001 &&
	 splashAbs(path->pts[0].y - path->pts[3].y) < 0.001 &&
	 splashAbs(path->pts[1].x - path->pts[2].x) < 0.001 &&
	 splashAbs(path->pts[1].y - path->pts[2].y) < 0.001)))) {
    wx = state->matrix[0] + state->matrix[2];
    wy = state->matrix[1] + state->matrix[3];
    w = splashSqrt(wx*wx + wy*wy);
    if (w < 0.001) {
      w = 0;
    } else {
      // min width is 0.1 -- this constant is minWidth * sqrt(2)
      w = (SplashCoord)0.1414 / w;
    }
    xx0 = path->pts[0].x;
    yy0 = path->pts[0].y;
    if (n <= 3) {
      xx1 = path->pts[1].x;
      yy1 = path->pts[1].y;
    } else {
      xx1 = path->pts[2].x;
      yy1 = path->pts[2].y;
    }
    dx = xx1 - xx0;
    dy = yy1 - yy0;
    d = splashSqrt(dx * dx + dy * dy);
    if (d < 0.001) {
      d = 0;
    } else {
      d = w / d;
    }
    dx *= d;
    dy *= d;
    path2 = new SplashPath();
    path2->moveTo(xx0 + dy, yy0 - dx);
    path2->lineTo(xx1 + dy, yy1 - dx);
    path2->lineTo(xx1 - dy, yy1 + dx);
    path2->lineTo(xx0 - dy, yy0 + dx);
    path2->close(gTrue);
    path2->addStrokeAdjustHint(0, 2, 0, 4);
    path2->addStrokeAdjustHint(1, 3, 0, 4);

  // unclosed rectangle --> close and hint
  } else if (n == 4 && !(path->flags[0] & splashPathClosed)) {
    path2->close(gTrue);
    path2->addStrokeAdjustHint(0, 2, 0, 4);
    path2->addStrokeAdjustHint(1, 3, 0, 4);

  // closed rectangle --> hint
  } else if (n == 5 && (path->flags[0] & splashPathClosed)) {
    path2->addStrokeAdjustHint(0, 2, 0, 4);
    path2->addStrokeAdjustHint(1, 3, 0, 4);
  }

  return path2;
}

GBool Splash::pathAllOutside(SplashPath *path) {
  SplashCoord xMin1, yMin1, xMax1, yMax1;
  SplashCoord xMin2, yMin2, xMax2, yMax2;
  SplashCoord x, y;
  int xMinI, yMinI, xMaxI, yMaxI;
  int i;

  xMin1 = xMax1 = path->pts[0].x;
  yMin1 = yMax1 = path->pts[0].y;
  for (i = 1; i < path->length; ++i) {
    if (path->pts[i].x < xMin1) {
      xMin1 = path->pts[i].x;
    } else if (path->pts[i].x > xMax1) {
      xMax1 = path->pts[i].x;
    }
    if (path->pts[i].y < yMin1) {
      yMin1 = path->pts[i].y;
    } else if (path->pts[i].y > yMax1) {
      yMax1 = path->pts[i].y;
    }
  }

  transform(state->matrix, xMin1, yMin1, &x, &y);
  xMin2 = xMax2 = x;
  yMin2 = yMax2 = y;
  transform(state->matrix, xMin1, yMax1, &x, &y);
  if (x < xMin2) {
    xMin2 = x;
  } else if (x > xMax2) {
    xMax2 = x;
  }
  if (y < yMin2) {
    yMin2 = y;
  } else if (y > yMax2) {
    yMax2 = y;
  }
  transform(state->matrix, xMax1, yMin1, &x, &y);
  if (x < xMin2) {
    xMin2 = x;
  } else if (x > xMax2) {
    xMax2 = x;
  }
  if (y < yMin2) {
    yMin2 = y;
  } else if (y > yMax2) {
    yMax2 = y;
  }
  transform(state->matrix, xMax1, yMax1, &x, &y);
  if (x < xMin2) {
    xMin2 = x;
  } else if (x > xMax2) {
    xMax2 = x;
  }
  if (y < yMin2) {
    yMin2 = y;
  } else if (y > yMax2) {
    yMax2 = y;
  }
  // sanity-check the coordinates - xMinI/yMinI/xMaxI/yMaxI are
  // 32-bit integers, so coords need to be < 2^31
  SplashXPath::clampCoords(&xMin2, &yMin2);
  SplashXPath::clampCoords(&xMax2, &yMax2);
  xMinI = splashFloor(xMin2);
  yMinI = splashFloor(yMin2);
  xMaxI = splashFloor(xMax2);
  yMaxI = splashFloor(yMax2);

  return state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI,
			       state->strokeAdjust) ==
         splashClipAllOutside;
}

SplashError Splash::fillChar(SplashCoord x, SplashCoord y,
			     int c, SplashFont *font) {
  SplashGlyphBitmap glyph;
  SplashCoord xt, yt;
  int x0, y0, xFrac, yFrac;
  SplashError err;

  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)) {
    return splashErrNoGlyph;
  }
  err = fillGlyph2(x0, y0, &glyph);
  if (glyph.freeData) {
    gfree(glyph.data);
  }
  return err;
}

SplashError 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);
  return fillGlyph2(x0, y0, glyph);
}

SplashError Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph) {
  SplashPipe pipe;
  SplashClipResult clipRes;
  Guchar alpha;
  Guchar *p;
  int xMin, yMin, xMax, yMax;
  int x, y, xg, yg, xx, t;

  xg = x0 - glyph->x;
  yg = y0 - glyph->y;
  xMin = xg;
  xMax = xg + glyph->w - 1;
  yMin = yg;
  yMax = yg + glyph->h - 1;
  if ((clipRes = state->clip->testRect(xMin, yMin, xMax, yMax,
				       state->strokeAdjust))
      != splashClipAllOutside) {
    pipeInit(&pipe, state->fillPattern,
	     (Guchar)splashRound(state->fillAlpha * 255),
	     gTrue, gFalse);
    if (clipRes == splashClipAllInside) {
      if (glyph->aa) {
	p = glyph->data;
	for (y = yMin; y <= yMax; ++y) {
	  (this->*pipe.run)(&pipe, xMin, xMax, y,
			    glyph->data + (y - yMin) * glyph->w, NULL);
	}
      } else {
	p = glyph->data;
	for (y = yMin; y <= yMax; ++y) {
	  for (x = xMin; x <= xMax; x += 8) {
	    alpha = *p++;
	    for (xx = 0; xx < 8 && x + xx <= xMax; ++xx) {
	      scanBuf[x + xx] = (alpha & 0x80) ? 0xff : 0x00;
	      alpha = (Guchar)(alpha << 1);
	    }
	  }
	  (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL);
	}
      }
    } else {
      if ((t = state->clip->getXMinI(state->strokeAdjust)) > xMin) {
	xMin = t;
      }
      if ((t = state->clip->getXMaxI(state->strokeAdjust)) < xMax) {
	xMax = t;
      }
      if ((t = state->clip->getYMinI(state->strokeAdjust)) > yMin) {
	yMin = t;
      }
      if ((t = state->clip->getYMaxI(state->strokeAdjust)) < yMax) {
	yMax = t;
      }
      if (xMin <= xMax && yMin <= yMax) {
	if (glyph->aa) {
	  for (y = yMin; y <= yMax; ++y) {
	    p = glyph->data + (y - yg) * glyph->w + (xMin - xg);
	    memcpy(scanBuf + xMin, p, xMax - xMin + 1);
	    state->clip->clipSpan(scanBuf, y, xMin, xMax,
				  state->strokeAdjust);
	    (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL);
	  }
	} else {
	  for (y = yMin; y <= yMax; ++y) {
	    p = glyph->data + (y - yg) * ((glyph->w + 7) >> 3)
	      + ((xMin - xg) >> 3);
	    alpha = *p++;
	    xx = (xMin - xg) & 7;
	    alpha = (Guchar)(alpha << xx);
	    for (x = xMin; xx < 8 && x <= xMax; ++x, ++xx) {
	      scanBuf[x] = (alpha & 0x80) ? 255 : 0;
	      alpha = (Guchar)(alpha << 1);
	    }
	    for (; x <= xMax; x += 8) {
	      alpha = *p++;
	      for (xx = 0; xx < 8 && x + xx <= xMax; ++xx) {
		scanBuf[x + xx] = (alpha & 0x80) ? 255 : 0;
		alpha = (Guchar)(alpha << 1);
	      }
	    }
	    state->clip->clipSpanBinary(scanBuf, y, xMin, xMax,
					state->strokeAdjust);
	    (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL);
	  }
	}
      }
    }
  }
  opClipRes = clipRes;

  return splashOk;
}

void Splash::getImageBounds(SplashCoord xyMin, SplashCoord xyMax,
			    int *xyMinI, int *xyMaxI) {
  if (state->strokeAdjust == splashStrokeAdjustOff) {
    // make sure the coords fit in 32-bit ints
#if USE_FIXEDPOINT
    if (xyMin < -32767) {
      xyMin = -32767;
    } else if (xyMin > 32767) {
      xyMin = 32767;
    }
    if (xyMax < -32767) {
      xyMax = -32767;
    } else if (xyMax > 32767) {
      xyMax = 32767;
    }
#else
    if (xyMin < -1e9) {
      xyMin = -1e9;
    } else if (xyMin > 1e9) {
      xyMin = 1e9;
    }
    if (xyMax < -1e9) {
      xyMax = -1e9;
    } else if (xyMax > 1e9) {
      xyMax = 1e9;
    }
#endif
    *xyMinI = splashFloor(xyMin);
    *xyMaxI = splashFloor(xyMax);
    if (*xyMaxI <= *xyMinI) {
      *xyMaxI = *xyMinI + 1;
    }
  } else {
    splashStrokeAdjust(xyMin, xyMax, xyMinI, xyMaxI, state->strokeAdjust);
  }
}

struct SplashDrawImageMaskRowData {
  SplashPipe pipe;
};

// The glyphMode flag is not currently used, but may be useful if the
// stroke adjustment behavior is changed.
SplashError Splash::fillImageMask(GString *imageTag,
				  SplashImageMaskSource src, void *srcData,
				  int w, int h, SplashCoord *mat,
				  GBool glyphMode, GBool interpolate) {
  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]);
  }

  //--- check for singular matrix
  if (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) {
    return splashErrSingularMatrix;
  }

  //--- compute image bbox, check clipping
  GBool flipsOnly = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001;
  GBool horizFlip = gFalse;
  GBool vertFlip = gFalse;
  int xMin, yMin, xMax, yMax;
  if (flipsOnly) {
    horizFlip = mat[0] < 0;
    vertFlip = mat[3] < 0;
    if (vertFlip) {
      if (horizFlip) {    // bottom-up, mirrored
	getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax);
	getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax);
      } else {            // bottom-up
	getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax);
	getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax);
      }
    } else {
      if (horizFlip) {    // top-down, mirrored
	getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax);
	getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax);
      } else {            // top-down
	getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax);
	getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax);
      }
    }
  } else {
    int xx = splashRound(mat[4]);		// (0,0)
    int yy = splashRound(mat[5]);
    xMin = xMax = xx;
    yMin = yMax = yy;
    xx = splashRound(mat[0] + mat[4]);		// (1,0)
    yy = splashRound(mat[1] + mat[5]);
    if (xx < xMin) {
      xMin = xx;
    } else if (xx > xMax) {
      xMax = xx;
    }
    if (yy < yMin) {
      yMin = yy;
    } else if (yy > yMax) {
      yMax = yy;
    }
    xx = splashRound(mat[2] + mat[4]);		// (0,1)
    yy = splashRound(mat[3] + mat[5]);
    if (xx < xMin) {
      xMin = xx;
    } else if (xx > xMax) {
      xMax = xx;
    }
    if (yy < yMin) {
      yMin = yy;
    } else if (yy > yMax) {
      yMax = yy;
    }
    xx = splashRound(mat[0] + mat[2] + mat[4]);	// (1,1)
    yy = splashRound(mat[1] + mat[3] + mat[5]);
    if (xx < xMin) {
      xMin = xx;
    } else if (xx > xMax) {
      xMax = xx;
    }
    if (yy < yMin) {
      yMin = yy;
    } else if (yy > yMax) {
      yMax = yy;
    }
    if (xMax <= xMin) {
      xMax = xMin + 1;
    }
    if (yMax <= yMin) {
      yMax = yMin + 1;
    }
  }
  SplashClipResult clipRes =
      state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1,
			    state->strokeAdjust);
  // If the scaled mask is much wider and/or taller than the clip
  // region, we use the "arbitrary" scaling path, to avoid a
  // potentially very slow loop in the flips-only path (which scans
  // the full width and height of the scaled mask, regardless of the
  // clip region).
  int clipW = state->clip->getXMaxI(state->strokeAdjust)
	      - state->clip->getXMinI(state->strokeAdjust);
  int clipH = state->clip->getYMaxI(state->strokeAdjust)
	      - state->clip->getYMinI(state->strokeAdjust);
  GBool veryLarge = ((xMax - xMin) / 8 > clipW && xMax - xMin > 1000) ||
                    ((yMax - yMin) / 8 > clipH && yMax - yMin > 1000);

  //--- set up the SplashDrawImageMaskRowData object and the pipes
  SplashDrawImageMaskRowData dd;
  pipeInit(&dd.pipe, state->fillPattern,
	   (Guchar)splashRound(state->fillAlpha * 255),
	   gTrue, gFalse);

  //--- choose the drawRow function
  SplashDrawImageMaskRowFunc drawRowFunc;
  if (clipRes == splashClipAllInside) {
    drawRowFunc = &Splash::drawImageMaskRowNoClip;
  } else {
    if (vectorAntialias) {
      drawRowFunc = &Splash::drawImageMaskRowClipAA;
    } else {
      drawRowFunc = &Splash::drawImageMaskRowClipNoAA;
    }
  }

  //--- horizontal/vertical flips only
  if (flipsOnly && !veryLarge) {
    if (clipRes != splashClipAllOutside) {
      int scaledWidth = xMax - xMin;
      int scaledHeight = yMax - yMin;
      ImageMaskScaler scaler(src, srcData, w, h,
			     scaledWidth, scaledHeight, interpolate);
      Guchar *tmpLine = NULL;
      if (horizFlip) {
	tmpLine = (Guchar *)gmalloc(scaledWidth);
      }
      if (vertFlip) {
	if (horizFlip) {    // bottom-up, mirrored
	  for (int y = 0; y < scaledHeight; ++y) {
	    scaler.nextLine();
	    mirrorImageMaskRow(scaler.data(), tmpLine, scaledWidth);
	    (this->*drawRowFunc)(&dd, tmpLine,
				 xMin, yMax - 1 - y, scaledWidth);
	  }
	} else {            // bottom-up
	  for (int y = 0; y < scaledHeight; ++y) {
	    scaler.nextLine();
	    (this->*drawRowFunc)(&dd, scaler.data(),
				 xMin, yMax - 1 - y, scaledWidth);
	  }
	}
      } else {
	if (horizFlip) {    // top-down, mirrored
	  for (int y = 0; y < scaledHeight; ++y) {
	    scaler.nextLine();
	    mirrorImageMaskRow(scaler.data(), tmpLine, scaledWidth);
	    (this->*drawRowFunc)(&dd, tmpLine,
				 xMin, yMin + y, scaledWidth);
	  }
	} else {            // top-down
	  for (int y = 0; y < scaledHeight; ++y) {
	    scaler.nextLine();
	    (this->*drawRowFunc)(&dd, scaler.data(),
				 xMin, yMin + y, scaledWidth);
	  }
	}
      }
      gfree(tmpLine);
    }

  //--- arbitrary transform
  } else {
    // estimate of size of scaled image
    int scaledWidth = splashRound(splashSqrt(mat[0] * mat[0]
					     + mat[1] * mat[1]));
    int scaledHeight = splashRound(splashSqrt(mat[2] * mat[2]
					      + mat[3] * mat[3]));
    if (scaledWidth < 1) {
      scaledWidth = 1;
    }
    if (scaledHeight < 1) {
      scaledHeight = 1;
    }
    GBool downscaling = gTrue;
    if (veryLarge || (scaledWidth >= w && scaledHeight >= h)) {
      downscaling = gFalse;
      scaledWidth = w;
      scaledHeight = h;
    }

    // compute mapping from device space to scaled image space
    SplashCoord mat1[6];
    mat1[0] = mat[0] / scaledWidth;
    mat1[1] = mat[1] / scaledWidth;
    mat1[2] = mat[2] / scaledHeight;
    mat1[3] = mat[3] / scaledHeight;
    mat1[4] = mat[4];
    mat1[5] = mat[5];
    SplashCoord det = mat1[0] * mat1[3] - mat1[1] * mat1[2];
    if (splashAbs(det) < 1e-6) {
      // this should be caught by the singular matrix check in drawImage
      return splashErrSingularMatrix;
    }
    SplashCoord invMat[6];
    invMat[0] = mat1[3] / det;
    invMat[1] = -mat1[1] / det;
    invMat[2] = -mat1[2] / det;
    invMat[3] = mat1[0] / det;
    // the extra "+ 0.5 * (...)" terms are here because the
    // drawImageArbitrary(No)Interp functions multiply by pixel
    // centers, (x + 0.5, y + 0.5)
    invMat[4] = (mat1[2] * mat1[5] - mat1[3] * mat1[4]) / det
                + (invMat[0] + invMat[2]) * 0.5;
    invMat[5] = (mat1[1] * mat1[4] - mat1[0] * mat1[5]) / det
                + (invMat[1] + invMat[3]) * 0.5;

    // if downscaling: store the downscaled image mask
    // if upscaling: store the unscaled image mask
    Guchar *scaledMask = (Guchar *)gmallocn(scaledHeight, scaledWidth);
    if (downscaling) {
      ImageMaskScaler scaler(src, srcData, w, h,
			     scaledWidth, scaledHeight, interpolate);
      Guchar *ptr = scaledMask;
      for (int y = 0; y < scaledHeight; ++y) {
	scaler.nextLine();
	memcpy(ptr, scaler.data(), scaledWidth);
	ptr += scaledWidth;
      }
    } else {
      Guchar *ptr = scaledMask;
      for (int y = 0; y < scaledHeight; ++y) {
	(*src)(srcData, ptr);
	for (int x = 0; x < scaledWidth; ++x) {
	  *ptr = (Guchar)(*ptr * 255);
	  ++ptr;
	}
      }
    }

    // draw it
    if (interpolate) {
      drawImageMaskArbitraryInterp(scaledMask,
				   &dd, drawRowFunc, invMat,
				   scaledWidth, scaledHeight,
				   xMin, yMin, xMax, yMax);
    } else {
      drawImageMaskArbitraryNoInterp(scaledMask,
				     &dd, drawRowFunc, invMat,
				     scaledWidth, scaledHeight,
				     xMin, yMin, xMax, yMax);
    }

    // free the downscaled/unscaled image
    gfree(scaledMask);
  }

  return splashOk;
}

void Splash::drawImageMaskArbitraryNoInterp(
				   Guchar *scaledMask,
				   SplashDrawImageMaskRowData *dd,
				   SplashDrawImageMaskRowFunc drawRowFunc,
				   SplashCoord *invMat,
				   int scaledWidth, int scaledHeight,
				   int xMin, int yMin, int xMax, int yMax) {
  int tt = state->clip->getXMinI(state->strokeAdjust);
  if (tt > xMin) {
    xMin = tt;
  }
  tt = state->clip->getXMaxI(state->strokeAdjust) + 1;
  if (tt < xMax) {
    xMax = tt;
  }
  tt = state->clip->getYMinI(state->strokeAdjust);
  if (tt > yMin) {
    yMin = tt;
  }
  tt = state->clip->getYMaxI(state->strokeAdjust) + 1;
  if (tt < yMax) {
    yMax = tt;
  }
  if (xMax <= xMin || yMax <= yMin) {
    return;
  }

  Guchar *buf = (Guchar *)gmalloc(xMax - xMin);

  for (int y = yMin; y < yMax; ++y) {
    int rowMin = xMax;
    int rowMax = 0;
    for (int x = xMin; x < xMax; ++x) {
      // note: invMat includes a "+0.5" factor so that this is really
      // a multiply by (x+0.5, y+0.5)
      int xx = splashFloor((SplashCoord)x * invMat[0]
			   + (SplashCoord)y * invMat[2] + invMat[4]);
      int yy = splashFloor((SplashCoord)x * invMat[1]
			   + (SplashCoord)y * invMat[3] + invMat[5]);
      if (xx >= 0 && xx < scaledWidth &&
	  yy >= 0 && yy < scaledHeight) {
	Guchar *p = scaledMask + (yy * scaledWidth + xx);
	Guchar *q = buf + (x - xMin);
	*q = *p;
	if (x < rowMin) {
	  rowMin = x;
	}
	rowMax = x + 1;
      }
    }
    if (rowMin < rowMax) {
      (this->*drawRowFunc)(dd, buf + (rowMin - xMin),
			   rowMin, y, rowMax - rowMin);
    }
  }

  gfree(buf);
}

void Splash::drawImageMaskArbitraryInterp(
				   Guchar *scaledMask,
				   SplashDrawImageMaskRowData *dd,
				   SplashDrawImageMaskRowFunc drawRowFunc,
				   SplashCoord *invMat,
				   int scaledWidth, int scaledHeight,
				   int xMin, int yMin, int xMax, int yMax) {
  int tt = state->clip->getXMinI(state->strokeAdjust);
  if (tt > xMin) {
    xMin = tt;
  }
  tt = state->clip->getXMaxI(state->strokeAdjust) + 1;
  if (tt < xMax) {
    xMax = tt;
  }
  tt = state->clip->getYMinI(state->strokeAdjust);
  if (tt > yMin) {
    yMin = tt;
  }
  tt = state->clip->getYMaxI(state->strokeAdjust) + 1;
  if (tt < yMax) {
    yMax = tt;
  }
  if (xMax <= xMin || yMax <= yMin) {
    return;
  }

  Guchar *buf = (Guchar *)gmalloc(xMax - xMin);

  for (int y = yMin; y < yMax; ++y) {
    int rowMin = xMax;
    int rowMax = 0;
    for (int x = xMin; x < xMax; ++x) {
      // note: invMat includes a "+0.5" factor so that this is really
      // a multiply by (x+0.5, y+0.5)
      SplashCoord xs = (SplashCoord)x * invMat[0]
	               + (SplashCoord)y * invMat[2] + invMat[4];
      SplashCoord ys = (SplashCoord)x * invMat[1]
	               + (SplashCoord)y * invMat[3] + invMat[5];
      int x0 = splashFloor(xs - 0.5);
      int x1 = x0 + 1;
      int y0 = splashFloor(ys - 0.5);
      int y1 = y0 + 1;
      if (x1 >= 0 && x0 < scaledWidth && y1 >= 0 && y0 < scaledHeight) {
	SplashCoord sx0 = (SplashCoord)x1 + 0.5 - xs;
	SplashCoord sx1 = (SplashCoord)1 - sx0;
	SplashCoord sy0 = (SplashCoord)y1 + 0.5 - ys;
	SplashCoord sy1 = (SplashCoord)1 - sy0;
	if (x0 < 0) {
	  x0 = 0;
	}
	if (x1 >= scaledWidth) {
	  x1 = scaledWidth - 1;
	}
	if (y0 < 0) {
	  y0 = 0;
	}
	if (y1 >= scaledHeight) {
	  y1 = scaledHeight - 1;
	}
	Guchar *p00 = scaledMask + (y0 * scaledWidth + x0);
	Guchar *p10 = scaledMask + (y0 * scaledWidth + x1);
	Guchar *p01 = scaledMask + (y1 * scaledWidth + x0);
	Guchar *p11 = scaledMask + (y1 * scaledWidth + x1);
	Guchar *q = buf + (x - xMin);
	*q = (Guchar)(int)(sx0 * (sy0 * (int)*p00 + sy1 * (int)*p01) +
			   sx1 * (sy0 * (int)*p10 + sy1 * (int)*p11));
	if (x < rowMin) {
	  rowMin = x;
	}
	rowMax = x + 1;
      }
    }
    if (rowMin < rowMax) {
      (this->*drawRowFunc)(dd, buf + (rowMin - xMin),
			   rowMin, y, rowMax - rowMin);
    }
  }

  gfree(buf);
}

void Splash::mirrorImageMaskRow(Guchar *maskIn, Guchar *maskOut, int width) {
  Guchar *p, *q;

  p = maskIn;
  q = maskOut + (width - 1);
  for (int i = 0; i < width; ++i) {
    *q = *p;
    ++p;
    --q;
  }
}

void Splash::drawImageMaskRowNoClip(SplashDrawImageMaskRowData *data,
				    Guchar *maskData,
				    int x, int y, int width) {
  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, maskData, NULL);
}

void Splash::drawImageMaskRowClipNoAA(SplashDrawImageMaskRowData *data,
				      Guchar *maskData,
				      int x, int y, int width) {
  if (y < 0 || y >= bitmap->height) {
    return;
  }
  if (x < 0) {
    maskData -= x;
    width += x;
    x = 0;
  }
  if (x + width > bitmap->width) {
    width = bitmap->width - x;
  }
  if (width <= 0) {
    return;
  }
  memcpy(scanBuf + x, maskData, width);
  state->clip->clipSpanBinary(scanBuf, y, x, x + width - 1,
			      state->strokeAdjust);
  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
			  scanBuf + x, NULL);
}

void Splash::drawImageMaskRowClipAA(SplashDrawImageMaskRowData *data,
				    Guchar *maskData,
				    int x, int y, int width) {
  if (y < 0 || y >= bitmap->height) {
    return;
  }
  if (x < 0) {
    maskData -= x;
    width += x;
    x = 0;
  }
  if (x + width > bitmap->width) {
    width = bitmap->width - x;
  }
  if (width <= 0) {
    return;
  }
  memcpy(scanBuf + x, maskData, width);
  state->clip->clipSpan(scanBuf, y, x, x + width - 1, state->strokeAdjust);
  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
			  scanBuf + x, NULL);
}

struct SplashDrawImageRowData {
  int nComps;
  GBool srcAlpha;
  SplashPipe pipe;
};

SplashError Splash::drawImage(GString *imageTag,
			      SplashImageSource src, void *srcData,
			      SplashColorMode srcMode, GBool srcAlpha,
			      int w, int h, SplashCoord *mat,
			      GBool interpolate) {
  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
  GBool ok = gFalse;
  int nComps = 0;
  switch (bitmap->mode) {
  case splashModeMono1:
  case splashModeMono8:
    ok = srcMode == splashModeMono8;
    nComps = 1;
    break;
  case splashModeRGB8:
  case splashModeBGR8:
    ok = srcMode == splashModeRGB8;
    nComps = 3;
    break;
#if SPLASH_CMYK
  case splashModeCMYK8:
    ok = srcMode == splashModeCMYK8;
    nComps = 4;
    break;
#endif
  default:
    ok = gFalse;
    break;
  }
  if (!ok) {
    return splashErrModeMismatch;
  }

  //--- check for singular matrix
  if (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) {
    return splashErrSingularMatrix;
  }

  //--- compute image bbox, check clipping
  GBool flipsOnly = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001;
  GBool horizFlip = gFalse;
  GBool vertFlip = gFalse;
  int xMin, yMin, xMax, yMax;
  if (flipsOnly) {
    horizFlip = mat[0] < 0;
    vertFlip = mat[3] < 0;
    if (vertFlip) {
      if (horizFlip) {    // bottom-up, mirrored
	getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax);
	getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax);
      } else {            // bottom-up
	getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax);
	getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax);
      }
    } else {
      if (horizFlip) {    // top-down, mirrored
	getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax);
	getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax);
      } else {            // top-down
	getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax);
	getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax);
      }
    }
  } else {
    int xx = splashRound(mat[4]);		// (0,0)
    int yy = splashRound(mat[5]);
    xMin = xMax = xx;
    yMin = yMax = yy;
    xx = splashRound(mat[0] + mat[4]);		// (1,0)
    yy = splashRound(mat[1] + mat[5]);
    if (xx < xMin) {
      xMin = xx;
    } else if (xx > xMax) {
      xMax = xx;
    }
    if (yy < yMin) {
      yMin = yy;
    } else if (yy > yMax) {
      yMax = yy;
    }
    xx = splashRound(mat[2] + mat[4]);		// (0,1)
    yy = splashRound(mat[3] + mat[5]);
    if (xx < xMin) {
      xMin = xx;
    } else if (xx > xMax) {
      xMax = xx;
    }
    if (yy < yMin) {
      yMin = yy;
    } else if (yy > yMax) {
      yMax = yy;
    }
    xx = splashRound(mat[0] + mat[2] + mat[4]);	// (1,1)
    yy = splashRound(mat[1] + mat[3] + mat[5]);
    if (xx < xMin) {
      xMin = xx;
    } else if (xx > xMax) {
      xMax = xx;
    }
    if (yy < yMin) {
      yMin = yy;
    } else if (yy > yMax) {
      yMax = yy;
    }
    if (xMax <= xMin) {
      xMax = xMin + 1;
    }
    if (yMax <= yMin) {
      yMax = yMin + 1;
    }
  }
  SplashClipResult clipRes =
      state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1,
			    state->strokeAdjust);
  // If the scaled image is much wider and/or taller than the clip
  // region, we use the arbitrary transform path, to avoid a
  // potentially very slow loop in the flips-only path (which scans
  // the full width and height of the scaled image, regardless of the
  // clip region).
  int clipW = state->clip->getXMaxI(state->strokeAdjust)
	      - state->clip->getXMinI(state->strokeAdjust);
  int clipH = state->clip->getYMaxI(state->strokeAdjust)
	      - state->clip->getYMinI(state->strokeAdjust);
  GBool veryLarge = ((xMax - xMin) / 8 > clipW && xMax - xMin > 1000) ||
                    ((yMax - yMin) / 8 > clipH && yMax - yMin > 1000);

  //--- set up the SplashDrawImageRowData object and the pipes
  SplashDrawImageRowData dd;
  dd.nComps = nComps;
  dd.srcAlpha = srcAlpha;
  pipeInit(&dd.pipe, NULL,
	   (Guchar)splashRound(state->fillAlpha * 255),
	   clipRes != splashClipAllInside || srcAlpha,
	   gFalse);

  //--- choose the drawRow function
  SplashDrawImageRowFunc drawRowFunc;
  if (clipRes == splashClipAllInside) {
    if (srcAlpha) {
      drawRowFunc = &Splash::drawImageRowNoClipAlpha;
    } else {
      drawRowFunc = &Splash::drawImageRowNoClipNoAlpha;
    }
  } else {
    if (srcAlpha) {
      if (vectorAntialias) {
	drawRowFunc = &Splash::drawImageRowClipAlphaAA;
      } else {
	drawRowFunc = &Splash::drawImageRowClipAlphaNoAA;
      }
    } else {
      if (vectorAntialias) {
	drawRowFunc = &Splash::drawImageRowClipNoAlphaAA;
      } else {
	drawRowFunc = &Splash::drawImageRowClipNoAlphaNoAA;
      }
    }
  }

  //--- horizontal/vertical flips only
  if (flipsOnly && !veryLarge) {
    if (clipRes != splashClipAllOutside) {
      int scaledWidth = xMax - xMin;
      int scaledHeight = yMax - yMin;
      ImageScaler *scaler = getImageScaler(imageTag, src, srcData,
					   w, h, nComps,
					   scaledWidth, scaledHeight,
					   srcMode, srcAlpha, interpolate);
      Guchar *tmpLine = NULL;
      Guchar *tmpAlphaLine = NULL;
      if (horizFlip) {
	tmpLine = (Guchar *)gmallocn(scaledWidth, nComps);
	if (srcAlpha) {
	  tmpAlphaLine = (Guchar *)gmalloc(scaledWidth);
	}
      }
      if (vertFlip) {
	if (horizFlip) {    // bottom-up, mirrored
	  for (int y = 0; y < scaledHeight; ++y) {
	    scaler->nextLine();
	    mirrorImageRow(scaler->colorData(), scaler->alphaData(),
			   tmpLine, tmpAlphaLine,
			   scaledWidth, nComps, srcAlpha);
	    (this->*drawRowFunc)(&dd, tmpLine, tmpAlphaLine,
				 xMin, yMax - 1 - y, scaledWidth);
	  }
	} else {            // bottom-up
	  for (int y = 0; y < scaledHeight; ++y) {
	    scaler->nextLine();
	    (this->*drawRowFunc)(&dd, scaler->colorData(), scaler->alphaData(),
				 xMin, yMax - 1 - y, scaledWidth);
	  }
	}
      } else {
	if (horizFlip) {    // top-down, mirrored
	  for (int y = 0; y < scaledHeight; ++y) {
	    scaler->nextLine();
	    mirrorImageRow(scaler->colorData(), scaler->alphaData(),
			   tmpLine, tmpAlphaLine,
			   scaledWidth, nComps, srcAlpha);
	    (this->*drawRowFunc)(&dd, tmpLine, tmpAlphaLine,
				 xMin, yMin + y, scaledWidth);
	  }
	} else {            // top-down
	  for (int y = 0; y < scaledHeight; ++y) {
	    scaler->nextLine();
	    (this->*drawRowFunc)(&dd, scaler->colorData(), scaler->alphaData(),
				 xMin, yMin + y, scaledWidth);
	  }
	}
      }
      gfree(tmpLine);
      gfree(tmpAlphaLine);
      delete scaler;
    }

  //--- arbitrary transform
  } else {
    // estimate of size of scaled image
    int scaledWidth = splashRound(splashSqrt(mat[0] * mat[0]
					     + mat[1] * mat[1]));
    int scaledHeight = splashRound(splashSqrt(mat[2] * mat[2]
					      + mat[3] * mat[3]));
    if (scaledWidth < 1) {
      scaledWidth = 1;
    }
    if (scaledHeight < 1) {
      scaledHeight = 1;
    }
    if (veryLarge || (scaledWidth >= w && scaledHeight >= h)) {
      scaledWidth = w;
      scaledHeight = h;
    }

    // compute mapping from device space to scaled image space
    SplashCoord mat1[6];
    mat1[0] = mat[0] / scaledWidth;
    mat1[1] = mat[1] / scaledWidth;
    mat1[2] = mat[2] / scaledHeight;
    mat1[3] = mat[3] / scaledHeight;
    mat1[4] = mat[4];
    mat1[5] = mat[5];
    SplashCoord det = mat1[0] * mat1[3] - mat1[1] * mat1[2];
    if (splashAbs(det) < 1e-6) {
      // this should be caught by the singular matrix check in drawImage
      return splashErrSingularMatrix;
    }
    SplashCoord invMat[6];
    invMat[0] = mat1[3] / det;
    invMat[1] = -mat1[1] / det;
    invMat[2] = -mat1[2] / det;
    invMat[3] = mat1[0] / det;
    // the extra "+ 0.5 * (...)" terms are here because the
    // drawImageArbitrary(No)Interp functions multiply by pixel
    // centers, (x + 0.5, y + 0.5)
    invMat[4] = (mat1[2] * mat1[5] - mat1[3] * mat1[4]) / det
                + (invMat[0] + invMat[2]) * 0.5;
    invMat[5] = (mat1[1] * mat1[4] - mat1[0] * mat1[5]) / det
                + (invMat[1] + invMat[3]) * 0.5;

    Guchar *scaledColor, *scaledAlpha;
    GBool freeScaledImage;
    getScaledImage(imageTag, src, srcData, w, h, nComps,
		   scaledWidth, scaledHeight, srcMode, srcAlpha, interpolate,
		   &scaledColor, &scaledAlpha, &freeScaledImage);

    // draw it
    if (interpolate) {
      drawImageArbitraryInterp(scaledColor, scaledAlpha,
			       &dd, drawRowFunc, invMat,
			       scaledWidth, scaledHeight,
			       xMin, yMin, xMax, yMax,
			       nComps, srcAlpha);
    } else {
      drawImageArbitraryNoInterp(scaledColor, scaledAlpha,
				 &dd, drawRowFunc, invMat,
				 scaledWidth, scaledHeight,
				 xMin, yMin, xMax, yMax,
				 nComps, srcAlpha);
    }

    // free the downscaled/unscaled image
    if (freeScaledImage) {
      gfree(scaledColor);
      gfree(scaledAlpha);
    }
  }

  return splashOk;
}

ImageScaler *Splash::getImageScaler(GString *imageTag,
				    SplashImageSource src, void *srcData,
				    int w, int h, int nComps,
				    int scaledWidth, int scaledHeight,
				    SplashColorMode srcMode,
				    GBool srcAlpha, GBool interpolate) {
  // Notes:
  //
  // * If the scaled image width or height is greater than 2000, we
  //   don't cache it.
  //
  // * Caching is done on the third consecutive use (second
  //   consecutive reuse) of an image; this avoids overhead on the
  //   common case of single-use images.

  if (scaledWidth < 2000 && scaledHeight < 2000 &&
      imageCache->match(imageTag, scaledWidth, scaledHeight,
			srcMode, srcAlpha, interpolate)) {
    if (imageCache->colorData) {
      return new ReplayImageScaler(nComps, srcAlpha, scaledWidth,
				   imageCache->colorData,
				   imageCache->alphaData);
    } else {
      int lineSize;
      if (scaledWidth < INT_MAX / nComps) {
	lineSize = scaledWidth * nComps;
      } else {
	lineSize = -1;
      }
      imageCache->colorData = (Guchar *)gmallocn(scaledHeight, lineSize);
      if (srcAlpha) {
	imageCache->alphaData = (Guchar *)gmallocn(scaledHeight, scaledWidth);
      }
      return new SavingImageScaler(src, srcData,
				   w, h, nComps, srcAlpha,
				   scaledWidth, scaledHeight,
				   interpolate,
				   imageCache->colorData,
				   imageCache->alphaData);
    }
  } else {
    imageCache->reset(imageTag, scaledWidth, scaledHeight,
		      srcMode, srcAlpha, interpolate);
    return new BasicImageScaler(src, srcData,
				w, h, nComps, srcAlpha,
				scaledWidth, scaledHeight,
				interpolate);
  }
}

void Splash::getScaledImage(GString *imageTag,
			    SplashImageSource src, void *srcData,
			    int w, int h, int nComps,
			    int scaledWidth, int scaledHeight,
			    SplashColorMode srcMode,
			    GBool srcAlpha, GBool interpolate,
			    Guchar **scaledColor, Guchar **scaledAlpha,
			    GBool *freeScaledImage) {
  // Notes:
  //
  // * If the scaled image width or height is greater than 2000, we
  //   don't cache it.
  //
  // * This buffers the whole image anyway, so there's no reason to
  //   skip caching on the first reuse.

  if (scaledWidth >= 2000 || scaledHeight >= 2000) {
    int lineSize;
    if (scaledWidth < INT_MAX / nComps) {
      lineSize = scaledWidth * nComps;
    } else {
      lineSize = -1;
    }
    *scaledColor = (Guchar *)gmallocn(scaledHeight, lineSize);
    if (srcAlpha) {
      *scaledAlpha = (Guchar *)gmallocn(scaledHeight, scaledWidth);
    } else {
      *scaledAlpha = NULL;
    }
    *freeScaledImage = gTrue;
    if (scaledWidth == w && scaledHeight == h) {
      Guchar *colorPtr = *scaledColor;
      Guchar *alphaPtr = *scaledAlpha;
      for (int y = 0; y < scaledHeight; ++y) {
	(*src)(srcData, colorPtr, alphaPtr);
	colorPtr += scaledWidth * nComps;
	if (srcAlpha) {
	  alphaPtr += scaledWidth;
	}
      }
    } else {
      BasicImageScaler scaler(src, srcData, w, h, nComps, srcAlpha,
			      scaledWidth, scaledHeight, interpolate);
      Guchar *colorPtr = *scaledColor;
      Guchar *alphaPtr = *scaledAlpha;
      for (int y = 0; y < scaledHeight; ++y) {
	scaler.nextLine();
	memcpy(colorPtr, scaler.colorData(), scaledWidth * nComps);
	colorPtr += scaledWidth * nComps;
	if (srcAlpha) {
	  memcpy(alphaPtr, scaler.alphaData(), scaledWidth);
	  alphaPtr += scaledWidth;
	}
      }
    }
  } else {
    if (!imageCache->match(imageTag, scaledWidth, scaledHeight,
			  srcMode, srcAlpha, interpolate) ||
	!imageCache->colorData) {
      imageCache->reset(imageTag, scaledWidth, scaledHeight,
			srcMode, srcAlpha, interpolate);
      int lineSize;
      if (scaledWidth < INT_MAX / nComps) {
	lineSize = scaledWidth * nComps;
      } else {
	lineSize = -1;
      }
      imageCache->colorData = (Guchar *)gmallocn(scaledHeight, lineSize);
      if (srcAlpha) {
	imageCache->alphaData = (Guchar *)gmallocn(scaledHeight, scaledWidth);
      }
      if (scaledWidth == w && scaledHeight == h) {
	Guchar *colorPtr = imageCache->colorData;
	Guchar *alphaPtr = imageCache->alphaData;
	for (int y = 0; y < scaledHeight; ++y) {
	  (*src)(srcData, colorPtr, alphaPtr);
	  colorPtr += scaledWidth * nComps;
	  if (srcAlpha) {
	    alphaPtr += scaledWidth;
	  }
	}
      } else {
	SavingImageScaler scaler(src, srcData, w, h, nComps, srcAlpha,
				 scaledWidth, scaledHeight, interpolate,
				 imageCache->colorData, imageCache->alphaData);
	Guchar *colorPtr = imageCache->colorData;
	Guchar *alphaPtr = imageCache->alphaData;
	for (int y = 0; y < scaledHeight; ++y) {
	  scaler.nextLine();
	  memcpy(colorPtr, scaler.colorData(), scaledWidth * nComps);
	  colorPtr += scaledWidth * nComps;
	  if (srcAlpha) {
	    memcpy(alphaPtr, scaler.alphaData(), scaledWidth);
	    alphaPtr += scaledWidth;
	  }
	}
      }
    }
    *scaledColor = imageCache->colorData;
    *scaledAlpha = imageCache->alphaData;
    *freeScaledImage = gFalse;
  }
}

void Splash::drawImageArbitraryNoInterp(Guchar *scaledColor,
					Guchar *scaledAlpha,
					SplashDrawImageRowData *dd,
					SplashDrawImageRowFunc drawRowFunc,
					SplashCoord *invMat,
					int scaledWidth, int scaledHeight,
					int xMin, int yMin, int xMax, int yMax,
					int nComps, GBool srcAlpha) {
  int tt = state->clip->getXMinI(state->strokeAdjust);
  if (tt > xMin) {
    xMin = tt;
  }
  tt = state->clip->getXMaxI(state->strokeAdjust) + 1;
  if (tt < xMax) {
    xMax = tt;
  }
  tt = state->clip->getYMinI(state->strokeAdjust);
  if (tt > yMin) {
    yMin = tt;
  }
  tt = state->clip->getYMaxI(state->strokeAdjust) + 1;
  if (tt < yMax) {
    yMax = tt;
  }
  if (xMax <= xMin || yMax <= yMin) {
    return;
  }

  Guchar *colorBuf = (Guchar *)gmallocn(xMax - xMin, nComps);
  Guchar *alphaBuf = NULL;
  if (srcAlpha) {
    alphaBuf = (Guchar *)gmalloc(xMax - xMin);
  }

  for (int y = yMin; y < yMax; ++y) {
    int rowMin = xMax;
    int rowMax = 0;
    for (int x = xMin; x < xMax; ++x) {
      // note: invMat includes a "+0.5" factor so that this is really
      // a multiply by (x+0.5, y+0.5)
      int xx = splashFloor((SplashCoord)x * invMat[0]
			   + (SplashCoord)y * invMat[2] + invMat[4]);
      int yy = splashFloor((SplashCoord)x * invMat[1]
			   + (SplashCoord)y * invMat[3] + invMat[5]);
      if (xx >= 0 && xx < scaledWidth &&
	  yy >= 0 && yy < scaledHeight) {
	Guchar *p = scaledColor + (yy * scaledWidth + xx) * nComps;
	Guchar *q = colorBuf + (x - xMin) * nComps;
	for (int i = 0; i < nComps; ++i) {
	  *q++ = *p++;
	}
	if (srcAlpha) {
	  alphaBuf[x - xMin] = scaledAlpha[yy * scaledWidth + xx];
	}
	if (x < rowMin) {
	  rowMin = x;
	}
	rowMax = x + 1;
      }
    }
    if (rowMin < rowMax) {
      (this->*drawRowFunc)(dd,
			   colorBuf + (rowMin - xMin) * nComps,
			   alphaBuf + (rowMin - xMin),
			   rowMin, y, rowMax - rowMin);
    }
  }

  gfree(colorBuf);
  gfree(alphaBuf);
}

void Splash::drawImageArbitraryInterp(Guchar *scaledColor, Guchar *scaledAlpha,
				      SplashDrawImageRowData *dd,
				      SplashDrawImageRowFunc drawRowFunc,
				      SplashCoord *invMat,
				      int scaledWidth, int scaledHeight,
				      int xMin, int yMin, int xMax, int yMax,
				      int nComps, GBool srcAlpha) {
  int tt = state->clip->getXMinI(state->strokeAdjust);
  if (tt > xMin) {
    xMin = tt;
  }
  tt = state->clip->getXMaxI(state->strokeAdjust) + 1;
  if (tt < xMax) {
    xMax = tt;
  }
  tt = state->clip->getYMinI(state->strokeAdjust);
  if (tt > yMin) {
    yMin = tt;
  }
  tt = state->clip->getYMaxI(state->strokeAdjust) + 1;
  if (tt < yMax) {
    yMax = tt;
  }
  if (xMax <= xMin || yMax <= yMin) {
    return;
  }

  Guchar *colorBuf = (Guchar *)gmallocn(xMax - xMin, nComps);
  Guchar *alphaBuf = NULL;
  if (srcAlpha) {
    alphaBuf = (Guchar *)gmalloc(xMax - xMin);
  }

  for (int y = yMin; y < yMax; ++y) {
    int rowMin = xMax;
    int rowMax = 0;
    for (int x = xMin; x < xMax; ++x) {
      // note: invMat includes a "+0.5" factor so that this is really
      // a multiply by (x+0.5, y+0.5)
      SplashCoord xs = (SplashCoord)x * invMat[0]
	               + (SplashCoord)y * invMat[2] + invMat[4];
      SplashCoord ys = (SplashCoord)x * invMat[1]
	               + (SplashCoord)y * invMat[3] + invMat[5];
      int x0 = splashFloor(xs - 0.5);
      int x1 = x0 + 1;
      int y0 = splashFloor(ys - 0.5);
      int y1 = y0 + 1;
      if (x1 >= 0 && x0 < scaledWidth && y1 >= 0 && y0 < scaledHeight) {
	SplashCoord sx0 = (SplashCoord)x1 + 0.5 - xs;
	SplashCoord sx1 = (SplashCoord)1 - sx0;
	SplashCoord sy0 = (SplashCoord)y1 + 0.5 - ys;
	SplashCoord sy1 = (SplashCoord)1 - sy0;
	if (x0 < 0) {
	  x0 = 0;
	}
	if (x1 >= scaledWidth) {
	  x1 = scaledWidth - 1;
	}
	if (y0 < 0) {
	  y0 = 0;
	}
	if (y1 >= scaledHeight) {
	  y1 = scaledHeight - 1;
	}
	Guchar *p00 = scaledColor + (y0 * scaledWidth + x0) * nComps;
	Guchar *p10 = scaledColor + (y0 * scaledWidth + x1) * nComps;
	Guchar *p01 = scaledColor + (y1 * scaledWidth + x0) * nComps;
	Guchar *p11 = scaledColor + (y1 * scaledWidth + x1) * nComps;
	Guchar *q = colorBuf + (x - xMin) * nComps;
	for (int i = 0; i < nComps; ++i) {
	  *q++ = (Guchar)(int)(sx0 * (sy0 * (int)*p00++ + sy1 * (int)*p01++) +
			       sx1 * (sy0 * (int)*p10++ + sy1 * (int)*p11++));
	}
	if (srcAlpha) {
	  p00 = scaledAlpha + (y0 * scaledWidth + x0);
	  p10 = scaledAlpha + (y0 * scaledWidth + x1);
	  p01 = scaledAlpha + (y1 * scaledWidth + x0);
	  p11 = scaledAlpha + (y1 * scaledWidth + x1);
	  q = alphaBuf + (x - xMin);
	  *q = (Guchar)(int)(sx0 * (sy0 * (int)*p00 + sy1 * (int)*p01) +
			     sx1 * (sy0 * (int)*p10 + sy1 * (int)*p11));
	}
	if (x < rowMin) {
	  rowMin = x;
	}
	rowMax = x + 1;
      }
    }
    if (rowMin < rowMax) {
      (this->*drawRowFunc)(dd,
			   colorBuf + (rowMin - xMin) * nComps,
			   alphaBuf + (rowMin - xMin),
			   rowMin, y, rowMax - rowMin);
    }
  }

  gfree(colorBuf);
  gfree(alphaBuf);
}

void Splash::mirrorImageRow(Guchar *colorIn, Guchar *alphaIn,
			    Guchar *colorOut, Guchar *alphaOut,
			    int width, int nComps, GBool srcAlpha) {
  Guchar *p, *q;

  p = colorIn;
  q = colorOut + (width - 1) * nComps;
  for (int i = 0; i < width; ++i) {
    for (int j = 0; j < nComps; ++j) {
      q[j] = p[j];
    }
    p += nComps;
    q -= nComps;
  }

  if (srcAlpha) {
    p = alphaIn;
    q = alphaOut + (width - 1);
    for (int i = 0; i < width; ++i) {
      *q = *p;
      ++p;
      --q;
    }
  }
}

void Splash::drawImageRowNoClipNoAlpha(SplashDrawImageRowData *data,
				       Guchar *colorData, Guchar *alphaData,
				       int x, int y, int width) {
  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, NULL, colorData);
}

void Splash::drawImageRowNoClipAlpha(SplashDrawImageRowData *data,
				     Guchar *colorData, Guchar *alphaData,
				     int x, int y, int width) {
  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
			  alphaData, colorData);
}

void Splash::drawImageRowClipNoAlphaNoAA(SplashDrawImageRowData *data,
					 Guchar *colorData,
					 Guchar *alphaData,
					 int x, int y, int width) {
  if (y < 0 || y >= bitmap->height) {
    return;
  }
  if (x < 0) {
    colorData -= x * data->nComps;
    width += x;
    x = 0;
  }
  if (x + width > bitmap->width) {
    width = bitmap->width - x;
  }
  if (width <= 0) {
    return;
  }
  memset(scanBuf + x, 0xff, width);
  state->clip->clipSpanBinary(scanBuf, y, x, x + width - 1,
			      state->strokeAdjust);
  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
			  scanBuf + x, colorData);
}

void Splash::drawImageRowClipNoAlphaAA(SplashDrawImageRowData *data,
				       Guchar *colorData,
				       Guchar *alphaData,
				       int x, int y, int width) {
  if (y < 0 || y >= bitmap->height) {
    return;
  }
  if (x < 0) {
    colorData -= x * data->nComps;
    width += x;
    x = 0;
  }
  if (x + width > bitmap->width) {
    width = bitmap->width - x;
  }
  if (width <= 0) {
    return;
  }
  memset(scanBuf + x, 0xff, width);
  state->clip->clipSpan(scanBuf, y, x, x + width - 1, state->strokeAdjust);
  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
			  scanBuf + x, colorData);
}

void Splash::drawImageRowClipAlphaNoAA(SplashDrawImageRowData *data,
				       Guchar *colorData,
				       Guchar *alphaData,
				       int x, int y, int width) {
  if (y < 0 || y >= bitmap->height) {
    return;
  }
  if (x < 0) {
    colorData -= x * data->nComps;
    alphaData -= x;
    width += x;
    x = 0;
  }
  if (x + width > bitmap->width) {
    width = bitmap->width - x;
  }
  if (width <= 0) {
    return;
  }
  memcpy(scanBuf + x, alphaData, width);
  state->clip->clipSpanBinary(scanBuf, y, x, x + width - 1,
			      state->strokeAdjust);
  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
			  scanBuf + x, colorData);
}

void Splash::drawImageRowClipAlphaAA(SplashDrawImageRowData *data,
				     Guchar *colorData,
				     Guchar *alphaData,
				     int x, int y, int width) {
  if (y < 0 || y >= bitmap->height) {
    return;
  }
  if (x < 0) {
    colorData -= x * data->nComps;
    alphaData -= x;
    width += x;
    x = 0;
  }
  if (x + width > bitmap->width) {
    width = bitmap->width - x;
  }
  if (width <= 0) {
    return;
  }
  memcpy(scanBuf + x, alphaData, width);
  state->clip->clipSpan(scanBuf, y, x, x + width - 1, state->strokeAdjust);
  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
			  scanBuf + x, colorData);
}

SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
			      int xDest, int yDest, int w, int h,
			      GBool noClip, GBool nonIsolated) {
  SplashPipe pipe;
  Guchar *mono1Ptr, *lineBuf, *linePtr;
  Guchar mono1Mask, b;
  int x0, x1, x, y0, y1, y, t;

  if (!(src->mode == bitmap->mode ||
	(src->mode == splashModeMono8 && bitmap->mode == splashModeMono1) ||
	(src->mode == splashModeRGB8 && bitmap->mode == splashModeBGR8))) {
    return splashErrModeMismatch;
  }

  pipeInit(&pipe, NULL,
	   (Guchar)splashRound(state->fillAlpha * 255),
	   !noClip || src->alpha != NULL, nonIsolated);
  if (src->mode == splashModeMono1) {
    // in mono1 mode, pipeRun expects the source to be in mono8
    // format, so we need to extract the source color values into
    // scanBuf, expanding them from mono1 to mono8
    if (noClip) {
      if (src->alpha) {
	for (y = 0; y < h; ++y) {
	  mono1Ptr = src->data + (ySrc + y) * src->rowSize + (xSrc >> 3);
	  mono1Mask = (Guchar)(0x80 >> (xSrc & 7));
	  for (x = 0; x < w; ++x) {
	    scanBuf[x] = (*mono1Ptr & mono1Mask) ? 0xff : 0x00;
	    mono1Ptr += mono1Mask & 1;
	    mono1Mask = (Guchar)((mono1Mask << 7) | (mono1Mask >> 1));
	  }
	  // this uses shape instead of alpha, which isn't technically
	  // correct, but works out the same
	  (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
			    src->alpha +
			      (ySrc + y) * src->alphaRowSize + xSrc,
			    scanBuf);
	}
      } else {
	for (y = 0; y < h; ++y) {
	  mono1Ptr = src->data + (ySrc + y) * src->rowSize + (xSrc >> 3);
	  mono1Mask = (Guchar)(0x80 >> (xSrc & 7));
	  for (x = 0; x < w; ++x) {
	    scanBuf[x] = (*mono1Ptr & mono1Mask) ? 0xff : 0x00;
	    mono1Ptr += mono1Mask & 1;
	    mono1Mask = (Guchar)((mono1Mask << 7) | (mono1Mask >> 1));
	  }
	  (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
			    NULL,
			    scanBuf);
	}
      }
    } else {
      x0 = xDest;
      if ((t = state->clip->getXMinI(state->strokeAdjust)) > x0) {
	x0 = t;
      }
      x1 = xDest + w;
      if ((t = state->clip->getXMaxI(state->strokeAdjust) + 1) < x1) {
	x1 = t;
      }
      y0 = yDest;
      if ((t = state->clip->getYMinI(state->strokeAdjust)) > y0) {
	y0 = t;
      }
      y1 = yDest + h;
      if ((t = state->clip->getYMaxI(state->strokeAdjust) + 1) < y1) {
	y1 = t;
      }
      if (x0 < x1 && y0 < y1) {
	if (src->alpha) {
	  for (y = y0; y < y1; ++y) {
	    mono1Ptr = src->data
	               + (ySrc + y - yDest) * src->rowSize
	               + ((xSrc + x0 - xDest) >> 3);
	    mono1Mask = (Guchar)(0x80 >> ((xSrc + x0 - xDest) & 7));
	    for (x = x0; x < x1; ++x) {
	      scanBuf[x] = (*mono1Ptr & mono1Mask) ? 0xff : 0x00;
	      mono1Ptr += mono1Mask & 1;
	      mono1Mask = (Guchar)((mono1Mask << 7) | (mono1Mask >> 1));
	    }
	    memcpy(scanBuf2 + x0,
		   src->alpha + (ySrc + y - yDest) * src->alphaRowSize + 
		     (xSrc + x0 - xDest),
		   x1 - x0);
	    if (!state->clip->clipSpanBinary(scanBuf2, y, x0, x1 - 1,
					     state->strokeAdjust)) {
	      continue;
	    }
	    // this uses shape instead of alpha, which isn't technically
	    // correct, but works out the same
	    (this->*pipe.run)(&pipe, x0, x1 - 1, y,
			      scanBuf2 + x0,
			      scanBuf + x0);
	  }
	} else {
	  for (y = y0; y < y1; ++y) {
	    mono1Ptr = src->data
	               + (ySrc + y - yDest) * src->rowSize
	               + ((xSrc + x0 - xDest) >> 3);
	    mono1Mask = (Guchar)(0x80 >> ((xSrc + x0 - xDest) & 7));
	    for (x = x0; x < x1; ++x) {
	      scanBuf[x] = (*mono1Ptr & mono1Mask) ? 0xff : 0x00;
	      mono1Ptr += mono1Mask & 1;
	      mono1Mask = (Guchar)((mono1Mask << 7) | (mono1Mask >> 1));
	    }
	    memset(scanBuf2 + x0, 0xff, x1 - x0);
	    if (!state->clip->clipSpanBinary(scanBuf2, y, x0, x1 - 1,
					     state->strokeAdjust)) {
	      continue;
	    }
	    (this->*pipe.run)(&pipe, x0, x1 - 1, y,
			      scanBuf2 + x0,
			      scanBuf + x0);
	  }
	}
      }
    }

  } else if (src->mode == splashModeBGR8) {
    // in BGR8 mode, pipeRun expects the source to be in RGB8 format,
    // so we need to swap bytes
    lineBuf = (Guchar *)gmallocn(w, 3);
    if (noClip) {
      if (src->alpha) {
	for (y = 0; y < h; ++y) {
	  memcpy(lineBuf,
		 src->data + (ySrc + y) * src->rowSize + xSrc * 3,
		 w * 3);
	  for (x = 0, linePtr = lineBuf; x < w; ++x, linePtr += 3) {
	    b = linePtr[0];
	    linePtr[0] = linePtr[2];
	    linePtr[2] = b;
	  }
	  // this uses shape instead of alpha, which isn't technically
	  // correct, but works out the same
	  (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
			    src->alpha +
			      (ySrc + y) * src->alphaRowSize + xSrc,
			    lineBuf);
	}
      } else {
	for (y = 0; y < h; ++y) {
	  memcpy(lineBuf,
		 src->data + (ySrc + y) * src->rowSize + xSrc * 3,
		 w * 3);
	  for (x = 0, linePtr = lineBuf; x < w; ++x, linePtr += 3) {
	    b = linePtr[0];
	    linePtr[0] = linePtr[2];
	    linePtr[2] = b;
	  }
	  (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
			    NULL, lineBuf);
	}
      }
    } else {
      x0 = xDest;
      if ((t = state->clip->getXMinI(state->strokeAdjust)) > x0) {
	x0 = t;
      }
      x1 = xDest + w;
      if ((t = state->clip->getXMaxI(state->strokeAdjust) + 1) < x1) {
	x1 = t;
      }
      y0 = yDest;
      if ((t = state->clip->getYMinI(state->strokeAdjust)) > y0) {
	y0 = t;
      }
      y1 = yDest + h;
      if ((t = state->clip->getYMaxI(state->strokeAdjust) + 1) < y1) {
	y1 = t;
      }
      if (x0 < x1 && y0 < y1) {
	if (src->alpha) {
	  for (y = y0; y < y1; ++y) {
	    memcpy(scanBuf + x0,
		   src->alpha + (ySrc + y - yDest) * src->alphaRowSize + 
		     (xSrc + x0 - xDest),
		   x1 - x0);
	    state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust);
	    memcpy(lineBuf,
		   src->data +
		     (ySrc + y - yDest) * src->rowSize +
		     (xSrc + x0 - xDest) * 3,
		   (x1 - x0) * 3);
	    for (x = 0, linePtr = lineBuf; x < x1 - x0; ++x, linePtr += 3) {
	      b = linePtr[0];
	      linePtr[0] = linePtr[2];
	      linePtr[2] = b;
	    }
	    // this uses shape instead of alpha, which isn't technically
	    // correct, but works out the same
	    (this->*pipe.run)(&pipe, x0, x1 - 1, y,
			      scanBuf + x0, lineBuf);
	  }
	} else {
	  for (y = y0; y < y1; ++y) {
	    memset(scanBuf + x0, 0xff, x1 - x0);
	    state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust);
	    memcpy(lineBuf,
		   src->data +
		     (ySrc + y - yDest) * src->rowSize +
		     (xSrc + x0 - xDest) * 3,
		   (x1 - x0) * 3);
	    for (x = 0, linePtr = lineBuf; x < x1 - x0; ++x, linePtr += 3) {
	      b = linePtr[0];
	      linePtr[0] = linePtr[2];
	      linePtr[2] = b;
	    }
	    (this->*pipe.run)(&pipe, x0, x1 - 1, yDest + y,
			      scanBuf + x0,
			      src->data +
			        (ySrc + y - yDest) * src->rowSize +
			        (xSrc + x0 - xDest) * bitmapComps);
	  }
	}
      }
    }
    gfree(lineBuf);

  } else { // src->mode not mono1 or BGR8
    if (noClip) {
      if (src->alpha) {
	for (y = 0; y < h; ++y) {
	  // this uses shape instead of alpha, which isn't technically
	  // correct, but works out the same
	  (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
			    src->alpha +
			      (ySrc + y) * src->alphaRowSize + xSrc,
			    src->data + (ySrc + y) * src->rowSize +
			      xSrc * bitmapComps);
	}
      } else {
	for (y = 0; y < h; ++y) {
	  (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
			    NULL,
			    src->data + (ySrc + y) * src->rowSize +
			      xSrc * bitmapComps);
	}
      }
    } else {
      x0 = xDest;
      if ((t = state->clip->getXMinI(state->strokeAdjust)) > x0) {
	x0 = t;
      }
      x1 = xDest + w;
      if ((t = state->clip->getXMaxI(state->strokeAdjust) + 1) < x1) {
	x1 = t;
      }
      y0 = yDest;
      if ((t = state->clip->getYMinI(state->strokeAdjust)) > y0) {
	y0 = t;
      }
      y1 = yDest + h;
      if ((t = state->clip->getYMaxI(state->strokeAdjust) + 1) < y1) {
	y1 = t;
      }
      if (x0 < x1 && y0 < y1) {
	if (src->alpha) {
	  for (y = y0; y < y1; ++y) {
	    memcpy(scanBuf + x0,
		   src->alpha + (ySrc + y - yDest) * src->alphaRowSize + 
		     (xSrc + x0 - xDest),
		   x1 - x0);
	    state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust);
	    // this uses shape instead of alpha, which isn't technically
	    // correct, but works out the same
	    (this->*pipe.run)(&pipe, x0, x1 - 1, y,
			      scanBuf + x0,
			      src->data +
			        (ySrc + y - yDest) * src->rowSize +
			        (xSrc + x0 - xDest) * bitmapComps);
	  }
	} else {
	  for (y = y0; y < y1; ++y) {
	    memset(scanBuf + x0, 0xff, x1 - x0);
	    state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust);
	    (this->*pipe.run)(&pipe, x0, x1 - 1, yDest + y,
			      scanBuf + x0,
			      src->data +
			        (ySrc + y - yDest) * src->rowSize +
			        (xSrc + x0 - xDest) * bitmapComps);
	  }
	}
      }
    }
  }

  return splashOk;
}

SplashError Splash::compositeWithOverprint(SplashBitmap *src,
					   Guint *srcOverprintMaskBitmap,
					   int xSrc, int ySrc,
					   int xDest, int yDest, int w, int h,
					   GBool noClip, GBool nonIsolated) {
  SplashPipe pipe;
  int x0, x1, y0, y1, y, t;

  if (!(src->mode == bitmap->mode ||
	(src->mode == splashModeMono8 && bitmap->mode == splashModeMono1) ||
	(src->mode == splashModeRGB8 && bitmap->mode == splashModeBGR8))) {
    return splashErrModeMismatch;
  }

  pipeInit(&pipe, NULL,
	   (Guchar)splashRound(state->fillAlpha * 255),
	   !noClip || src->alpha != NULL, nonIsolated, gTrue);

  if (noClip) {
    if (src->alpha) {
      for (y = 0; y < h; ++y) {
	pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap + y * w + xSrc;
	// this uses shape instead of alpha, which isn't technically
	// correct, but works out the same
	(this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
			  src->alpha +
			    (ySrc + y) * src->alphaRowSize + xSrc,
			  src->data + (ySrc + y) * src->rowSize +
			    xSrc * bitmapComps);
      }
    } else {
      for (y = 0; y < h; ++y) {
	pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap + y * w + xSrc;
	(this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
			  NULL,
			  src->data + (ySrc + y) * src->rowSize +
			    xSrc * bitmapComps);
      }
    }
  } else {
    x0 = xDest;
    if ((t = state->clip->getXMinI(state->strokeAdjust)) > x0) {
      x0 = t;
    }
    x1 = xDest + w;
    if ((t = state->clip->getXMaxI(state->strokeAdjust) + 1) < x1) {
      x1 = t;
    }
    y0 = yDest;
    if ((t = state->clip->getYMinI(state->strokeAdjust)) > y0) {
      y0 = t;
    }
    y1 = yDest + h;
    if ((t = state->clip->getYMaxI(state->strokeAdjust) + 1) < y1) {
      y1 = t;
    }
    if (x0 < x1 && y0 < y1) {
      if (src->alpha) {
	for (y = y0; y < y1; ++y) {
	  memcpy(scanBuf + x0,
		 src->alpha + (ySrc + y - yDest) * src->alphaRowSize + 
		   (xSrc + x0 - xDest),
		 x1 - x0);
	  state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust);
	  pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap
	                             + (ySrc + y - yDest) * w
	                             + (xSrc + x0 - xDest);
	  // this uses shape instead of alpha, which isn't technically
	  // correct, but works out the same
	  (this->*pipe.run)(&pipe, x0, x1 - 1, y,
			    scanBuf + x0,
			    src->data +
			      (ySrc + y - yDest) * src->rowSize +
			      (xSrc + x0 - xDest) * bitmapComps);
	}
      } else {
	for (y = y0; y < y1; ++y) {
	  memset(scanBuf + x0, 0xff, x1 - x0);
	  state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust);
	  pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap
	                             + (ySrc + y - yDest) * w
	                             + (xSrc + x0 - xDest);
	  (this->*pipe.run)(&pipe, x0, x1 - 1, y,
			    scanBuf + x0,
			    src->data +
			      (ySrc + y - yDest) * src->rowSize +
			      (xSrc + x0 - xDest) * bitmapComps);
	}
      }
    }
  }

  return splashOk;
}

void Splash::compositeBackground(SplashColorPtr color) {
  SplashColorPtr p;
  Guchar *q;
  Guchar alpha, alpha1, c, color0, color1, color2, mask;
#if SPLASH_CMYK
  Guchar color3;
#endif
  int x, y;

  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->alphaRowSize];
      mask = 0x80;
      for (x = 0; x < bitmap->width; ++x) {
	alpha = *q++;
	if (alpha == 0) {
	  if (color0 & 0x80) {
	    *p |= mask;
	  } else {
	    *p &= (Guchar)~mask;
	  }
	} else if (alpha != 255) {
	  alpha1 = (Guchar)(255 - alpha);
	  c = (*p & mask) ? 0xff : 0x00;
	  c = div255(alpha1 * color0 + alpha * c);
	  if (c & 0x80) {
	    *p |= mask;
	  } else {
	    *p &= (Guchar)~mask;
	  }
	}
	if (!(mask = (Guchar)(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->alphaRowSize];
      for (x = 0; x < bitmap->width; ++x) {
	alpha = *q++;
	if (alpha == 0) {
	  p[0] = color0;
	} else if (alpha != 255) {
	  alpha1 = (Guchar)(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->alphaRowSize];
      for (x = 0; x < bitmap->width; ++x) {
	alpha = *q++;
	if (alpha == 0) {
	  p[0] = color0;
	  p[1] = color1;
	  p[2] = color2;
	} else if (alpha != 255) {
	  alpha1 = (Guchar)(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->alphaRowSize];
      for (x = 0; x < bitmap->width; ++x) {
	alpha = *q++;
	if (alpha == 0) {
	  p[0] = color0;
	  p[1] = color1;
	  p[2] = color2;
	  p[3] = color3;
	} else if (alpha != 255) {
	  alpha1 = (Guchar)(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->alphaRowSize * bitmap->height);
}

SplashError Splash::blitTransparent(SplashBitmap *src, int xSrc, int ySrc,
				    int xDest, int yDest, int w, int h) {
  SplashColorPtr p, q;
  Guchar mask, srcMask;
  int x, y;

  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 = (Guchar)(0x80 >> (xDest & 7));
      q = &src->data[(ySrc + y) * src->rowSize + (xSrc >> 3)];
      srcMask = (Guchar)(0x80 >> (xSrc & 7));
      for (x = 0; x < w; ++x) {
	if (*q & srcMask) {
	  *p |= mask;
	} else {
	  *p &= (Guchar)~mask;
	}
	if (!(mask = (Guchar)(mask >> 1))) {
	  mask = 0x80;
	  ++p;
	}
	if (!(srcMask = (Guchar)(srcMask >> 1))) {
	  srcMask = 0x80;
	  ++q;
	}
      }
    }
    break;
  case splashModeMono8:
    for (y = 0; y < h; ++y) {
      p = &bitmap->data[(yDest + y) * bitmap->rowSize + xDest];
      q = &src->data[(ySrc + y) * src->rowSize + xSrc];
      memcpy(p, q, w);
    }
    break;
  case splashModeRGB8:
  case splashModeBGR8:
    for (y = 0; y < h; ++y) {
      p = &bitmap->data[(yDest + y) * bitmap->rowSize + 3 * xDest];
      q = &src->data[(ySrc + y) * src->rowSize + 3 * xSrc];
      memcpy(p, q, 3 * w);
    }
    break;
#if SPLASH_CMYK
  case splashModeCMYK8:
    for (y = 0; y < h; ++y) {
      p = &bitmap->data[(yDest + y) * bitmap->rowSize + 4 * xDest];
      q = &src->data[(ySrc + y) * src->rowSize + 4 * xSrc];
      memcpy(p, q, 4 * w);
    }
    break;
#endif
  }

  if (bitmap->alpha) {
    for (y = 0; y < h; ++y) {
      q = &bitmap->alpha[(yDest + y) * bitmap->alphaRowSize + xDest];
      memset(q, 0, w);
    }
  }

  return splashOk;
}

SplashError Splash::blitCorrectedAlpha(SplashBitmap *dest, int xSrc, int ySrc,
				       int xDest, int yDest, int w, int h) {
  SplashColorPtr p, q;
  Guchar *alpha0Ptr;
  Guchar alpha0, aSrc, mask, srcMask;
  int x, y;

  if (bitmap->mode != dest->mode ||
      !bitmap->alpha ||
      !dest->alpha ||
      !groupBackBitmap) {
    return splashErrModeMismatch;
  }

  switch (bitmap->mode) {
  case splashModeMono1:
    for (y = 0; y < h; ++y) {
      p = &dest->data[(yDest + y) * dest->rowSize + (xDest >> 3)];
      mask = (Guchar)(0x80 >> (xDest & 7));
      q = &bitmap->data[(ySrc + y) * bitmap->rowSize + (xSrc >> 3)];
      srcMask = (Guchar)(0x80 >> (xSrc & 7));
      for (x = 0; x < w; ++x) {
	if (*q & srcMask) {
	  *p |= mask;
	} else {
	  *p &= (Guchar)~mask;
	}
	if (!(mask = (Guchar)(mask >> 1))) {
	  mask = 0x80;
	  ++p;
	}
	if (!(srcMask = (Guchar)(srcMask >> 1))) {
	  srcMask = 0x80;
	  ++q;
	}
      }
    }
    break;
  case splashModeMono8:
    for (y = 0; y < h; ++y) {
      p = &dest->data[(yDest + y) * dest->rowSize + xDest];
      q = &bitmap->data[(ySrc + y) * bitmap->rowSize + xSrc];
      memcpy(p, q, w);
    }
    break;
  case splashModeRGB8:
  case splashModeBGR8:
    for (y = 0; y < h; ++y) {
      p = &dest->data[(yDest + y) * dest->rowSize + 3 * xDest];
      q = &bitmap->data[(ySrc + y) * bitmap->rowSize + 3 * xSrc];
      memcpy(p, q, 3 * w);
    }
    break;
#if SPLASH_CMYK
  case splashModeCMYK8:
    for (y = 0; y < h; ++y) {
      p = &dest->data[(yDest + y) * dest->rowSize + 4 * xDest];
      q = &bitmap->data[(ySrc + y) * bitmap->rowSize + 4 * xSrc];
      memcpy(p, q, 4 * w);
    }
    break;
#endif
  }

  for (y = 0; y < h; ++y) {
    p = &dest->alpha[(yDest + y) * dest->alphaRowSize + xDest];
    q = &bitmap->alpha[(ySrc + y) * bitmap->alphaRowSize + xSrc];
    alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + ySrc + y)
					  * groupBackBitmap->alphaRowSize +
					(groupBackX + xSrc)];
    for (x = 0; x < w; ++x) {
      alpha0 = *alpha0Ptr++;
      aSrc = *q++;
      *p++ = (Guchar)(alpha0 + aSrc - div255(alpha0 * aSrc));
    }
  }

  return splashOk;
}

SplashPath *Splash::makeStrokePath(SplashPath *path, SplashCoord w,
				   int lineCap, int lineJoin,
				   GBool flatten) {
  SplashPath *pathIn, *dashPath, *pathOut;
  SplashCoord d, dx, dy, wdx, wdy, dxNext, dyNext, wdxNext, wdyNext;
  SplashCoord crossprod, dotprod, miter, m;
  SplashCoord angle, angleNext, dAngle, xc, yc;
  SplashCoord dxJoin, dyJoin, dJoin, kappa;
  SplashCoord cx1, cy1, cx2, cy2, cx3, cy3, cx4, cy4;
  GBool first, last, closed;
  int subpathStart0, subpathStart1, seg, i0, i1, j0, j1, k0, k1;
  int left0, left1, left2, right0, right1, right2, join0, join1, join2;
  int leftFirst, rightFirst, firstPt;

  pathOut = new SplashPath();

  if (path->length == 0) {
    return pathOut;
  }

  if (flatten) {
    pathIn = flattenPath(path, state->matrix, state->flatness);
    if (state->lineDashLength > 0) {
      dashPath = makeDashedPath(pathIn);
      delete pathIn;
      pathIn = dashPath;
      if (pathIn->length == 0) {
	delete pathIn;
	return pathOut;
      }
    }
  } else {
    pathIn = path;
  }

  subpathStart0 = subpathStart1 = 0; // make gcc happy
  seg = 0; // make gcc happy
  closed = gFalse; // make gcc happy
  left0 = left1 = right0 = right1 = join0 = join1 = 0; // make gcc happy
  leftFirst = rightFirst = firstPt = 0; // make gcc happy

  i0 = 0;
  for (i1 = i0;
       !(pathIn->flags[i1] & splashPathLast) &&
	 i1 + 1 < pathIn->length &&
	 pathIn->pts[i1+1].x == pathIn->pts[i1].x &&
	 pathIn->pts[i1+1].y == pathIn->pts[i1].y;
       ++i1) ;

  while (i1 < pathIn->length) {
    if ((first = pathIn->flags[i0] & splashPathFirst)) {
      subpathStart0 = i0;
      subpathStart1 = i1;
      seg = 0;
      closed = pathIn->flags[i0] & splashPathClosed;
    }
    j0 = i1 + 1;
    if (j0 < pathIn->length) {
      for (j1 = j0;
	   !(pathIn->flags[j1] & splashPathLast) &&
	     j1 + 1 < pathIn->length &&
	     pathIn->pts[j1+1].x == pathIn->pts[j1].x &&
	     pathIn->pts[j1+1].y == pathIn->pts[j1].y;
	   ++j1) ;
    } else {
      j1 = j0;
    }
    if (pathIn->flags[i1] & splashPathLast) {
      if (first && lineCap == splashLineCapRound) {
	// special case: zero-length subpath with round line caps -->
	// draw a circle
	pathOut->moveTo(pathIn->pts[i0].x + (SplashCoord)0.5 * w,
			pathIn->pts[i0].y);
	pathOut->curveTo(pathIn->pts[i0].x + (SplashCoord)0.5 * w,
			 pathIn->pts[i0].y + bezierCircle2 * w,
			 pathIn->pts[i0].x + bezierCircle2 * w,
			 pathIn->pts[i0].y + (SplashCoord)0.5 * w,
			 pathIn->pts[i0].x,
			 pathIn->pts[i0].y + (SplashCoord)0.5 * w);
	pathOut->curveTo(pathIn->pts[i0].x - bezierCircle2 * w,
			 pathIn->pts[i0].y + (SplashCoord)0.5 * w,
			 pathIn->pts[i0].x - (SplashCoord)0.5 * w,
			 pathIn->pts[i0].y + bezierCircle2 * w,
			 pathIn->pts[i0].x - (SplashCoord)0.5 * w,
			 pathIn->pts[i0].y);
	pathOut->curveTo(pathIn->pts[i0].x - (SplashCoord)0.5 * w,
			 pathIn->pts[i0].y - bezierCircle2 * w,
			 pathIn->pts[i0].x - bezierCircle2 * w,
			 pathIn->pts[i0].y - (SplashCoord)0.5 * w,
			 pathIn->pts[i0].x,
			 pathIn->pts[i0].y - (SplashCoord)0.5 * w);
	pathOut->curveTo(pathIn->pts[i0].x + bezierCircle2 * w,
			 pathIn->pts[i0].y - (SplashCoord)0.5 * w,
			 pathIn->pts[i0].x + (SplashCoord)0.5 * w,
			 pathIn->pts[i0].y - bezierCircle2 * w,
			 pathIn->pts[i0].x + (SplashCoord)0.5 * w,
			 pathIn->pts[i0].y);
	pathOut->close();
      }
      i0 = j0;
      i1 = j1;
      continue;
    }
    last = pathIn->flags[j1] & splashPathLast;
    if (last) {
      k0 = subpathStart1 + 1;
    } else {
      k0 = j1 + 1;
    }
    for (k1 = k0;
	 !(pathIn->flags[k1] & splashPathLast) &&
	   k1 + 1 < pathIn->length &&
	   pathIn->pts[k1+1].x == pathIn->pts[k1].x &&
	   pathIn->pts[k1+1].y == pathIn->pts[k1].y;
	 ++k1) ;

    // compute the deltas for segment (i1, j0)
#if USE_FIXEDPOINT
    // the 1/d value can be small, which introduces significant
    // inaccuracies in fixed point mode
    d = splashDist(pathIn->pts[i1].x, pathIn->pts[i1].y,
		   pathIn->pts[j0].x, pathIn->pts[j0].y);
    dx = (pathIn->pts[j0].x - pathIn->pts[i1].x) / d;
    dy = (pathIn->pts[j0].y - pathIn->pts[i1].y) / d;
#else
    d = (SplashCoord)1 / splashDist(pathIn->pts[i1].x, pathIn->pts[i1].y,
				    pathIn->pts[j0].x, pathIn->pts[j0].y);
    dx = d * (pathIn->pts[j0].x - pathIn->pts[i1].x);
    dy = d * (pathIn->pts[j0].y - pathIn->pts[i1].y);
#endif
    wdx = (SplashCoord)0.5 * w * dx;
    wdy = (SplashCoord)0.5 * w * dy;

    // draw the start cap
    if (i0 == subpathStart0) {
      firstPt = pathOut->length;
    }
    if (first && !closed) {
      switch (lineCap) {
      case splashLineCapButt:
	pathOut->moveTo(pathIn->pts[i0].x - wdy, pathIn->pts[i0].y + wdx);
	pathOut->lineTo(pathIn->pts[i0].x + wdy, pathIn->pts[i0].y - wdx);
	break;
      case splashLineCapRound:
	pathOut->moveTo(pathIn->pts[i0].x - wdy, pathIn->pts[i0].y + wdx);
	pathOut->curveTo(pathIn->pts[i0].x - wdy - bezierCircle * wdx,
			 pathIn->pts[i0].y + wdx - bezierCircle * wdy,
			 pathIn->pts[i0].x - wdx - bezierCircle * wdy,
			 pathIn->pts[i0].y - wdy + bezierCircle * wdx,
			 pathIn->pts[i0].x - wdx,
			 pathIn->pts[i0].y - wdy);
	pathOut->curveTo(pathIn->pts[i0].x - wdx + bezierCircle * wdy,
			 pathIn->pts[i0].y - wdy - bezierCircle * wdx,
			 pathIn->pts[i0].x + wdy - bezierCircle * wdx,
			 pathIn->pts[i0].y - wdx - bezierCircle * wdy,
			 pathIn->pts[i0].x + wdy,
			 pathIn->pts[i0].y - wdx);
	break;
      case splashLineCapProjecting:
	pathOut->moveTo(pathIn->pts[i0].x - wdx - wdy,
			pathIn->pts[i0].y + wdx - wdy);
	pathOut->lineTo(pathIn->pts[i0].x - wdx + wdy,
			pathIn->pts[i0].y - wdx - wdy);
	break;
      }
    } else {
      pathOut->moveTo(pathIn->pts[i0].x - wdy, pathIn->pts[i0].y + wdx);
      pathOut->lineTo(pathIn->pts[i0].x + wdy, pathIn->pts[i0].y - wdx);
    }

    // draw the left side of the segment rectangle and the end cap
    left2 = pathOut->length - 1;
    if (last && !closed) {
      switch (lineCap) {
      case splashLineCapButt:
	pathOut->lineTo(pathIn->pts[j0].x + wdy, pathIn->pts[j0].y - wdx);
	pathOut->lineTo(pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx);
	break;
      case splashLineCapRound:
	pathOut->lineTo(pathIn->pts[j0].x + wdy, pathIn->pts[j0].y - wdx);
	pathOut->curveTo(pathIn->pts[j0].x + wdy + bezierCircle * wdx,
			 pathIn->pts[j0].y - wdx + bezierCircle * wdy,
			 pathIn->pts[j0].x + wdx + bezierCircle * wdy,
			 pathIn->pts[j0].y + wdy - bezierCircle * wdx,
			 pathIn->pts[j0].x + wdx,
			 pathIn->pts[j0].y + wdy);
	pathOut->curveTo(pathIn->pts[j0].x + wdx - bezierCircle * wdy,
			 pathIn->pts[j0].y + wdy + bezierCircle * wdx,
			 pathIn->pts[j0].x - wdy + bezierCircle * wdx,
			 pathIn->pts[j0].y + wdx + bezierCircle * wdy,
			 pathIn->pts[j0].x - wdy,
			 pathIn->pts[j0].y + wdx);
	break;
      case splashLineCapProjecting:
	pathOut->lineTo(pathIn->pts[j0].x + wdy + wdx,
			pathIn->pts[j0].y - wdx + wdy);
	pathOut->lineTo(pathIn->pts[j0].x - wdy + wdx,
			pathIn->pts[j0].y + wdx + wdy);
	break;
      }
    } else {
      pathOut->lineTo(pathIn->pts[j0].x + wdy, pathIn->pts[j0].y - wdx);
      pathOut->lineTo(pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx);
    }

    // draw the right side of the segment rectangle
    // (NB: if stroke adjustment is enabled, the closepath operation MUST
    // add a segment because this segment is used for a hint)
    right2 = pathOut->length - 1;
    pathOut->close(state->strokeAdjust != splashStrokeAdjustOff);

    // draw the join
    join2 = pathOut->length;
    if (!last || closed) {

      // compute the deltas for segment (j1, k0)
#if USE_FIXEDPOINT
      // the 1/d value can be small, which introduces significant
      // inaccuracies in fixed point mode
      d = splashDist(pathIn->pts[j1].x, pathIn->pts[j1].y,
		     pathIn->pts[k0].x, pathIn->pts[k0].y);
      dxNext = (pathIn->pts[k0].x - pathIn->pts[j1].x) / d;
      dyNext = (pathIn->pts[k0].y - pathIn->pts[j1].y) / d;
#else
      d = (SplashCoord)1 / splashDist(pathIn->pts[j1].x, pathIn->pts[j1].y,
				      pathIn->pts[k0].x, pathIn->pts[k0].y);
      dxNext = d * (pathIn->pts[k0].x - pathIn->pts[j1].x);
      dyNext = d * (pathIn->pts[k0].y - pathIn->pts[j1].y);
#endif
      wdxNext = (SplashCoord)0.5 * w * dxNext;
      wdyNext = (SplashCoord)0.5 * w * dyNext;

      // compute the join parameters
      crossprod = dx * dyNext - dy * dxNext;
      dotprod = -(dx * dxNext + dy * dyNext);
      if (dotprod > 0.9999) {
	// avoid a divide-by-zero -- set miter to something arbitrary
	// such that sqrt(miter) will exceed miterLimit (and m is never
	// used in that situation)
	// (note: the comparison value (0.9999) has to be less than
	// 1-epsilon, where epsilon is the smallest value
	// representable in the fixed point format)
	miter = (state->miterLimit + 1) * (state->miterLimit + 1);
	m = 0;
      } else {
	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 (lineJoin == splashLineJoinRound) {
	// join angle < 180
	if (crossprod < 0) {
	  angle = atan2((double)dx, (double)-dy);
	  angleNext = atan2((double)dxNext, (double)-dyNext);
	  if (angle < angleNext) {
	    angle += 2 * M_PI;
	  }
	  dAngle = (angle  - angleNext) / M_PI;
	  if (dAngle < 0.501) {
	    // span angle is <= 90 degrees -> draw a single arc
	    kappa = dAngle * bezierCircle * w;
	    cx1 = pathIn->pts[j0].x - wdy + kappa * dx;
	    cy1 = pathIn->pts[j0].y + wdx + kappa * dy;
	    cx2 = pathIn->pts[j0].x - wdyNext - kappa * dxNext;
	    cy2 = pathIn->pts[j0].y + wdxNext - kappa * dyNext;
	    pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y);
	    pathOut->lineTo(pathIn->pts[j0].x - wdyNext,
			    pathIn->pts[j0].y + wdxNext);
	    pathOut->curveTo(cx2, cy2, cx1, cy1,
			     pathIn->pts[j0].x - wdy,
			     pathIn->pts[j0].y + wdx);
	  } else {
	    // span angle is > 90 degrees -> split into two arcs
	    dJoin = splashDist(-wdy, wdx, -wdyNext, wdxNext);
	    if (dJoin > 0) {
	      dxJoin = (-wdyNext + wdy) / dJoin;
	      dyJoin = (wdxNext - wdx) / dJoin;
	      xc = pathIn->pts[j0].x
		   + (SplashCoord)0.5 * w
		     * cos((double)((SplashCoord)0.5 * (angle + angleNext)));
	      yc = pathIn->pts[j0].y
		   + (SplashCoord)0.5 * w
		     * sin((double)((SplashCoord)0.5 * (angle + angleNext)));
	      kappa = dAngle * bezierCircle2 * w;
	      cx1 = pathIn->pts[j0].x - wdy + kappa * dx;
	      cy1 = pathIn->pts[j0].y + wdx + kappa * dy;
	      cx2 = xc - kappa * dxJoin;
	      cy2 = yc - kappa * dyJoin;
	      cx3 = xc + kappa * dxJoin;
	      cy3 = yc + kappa * dyJoin;
	      cx4 = pathIn->pts[j0].x - wdyNext - kappa * dxNext;
	      cy4 = pathIn->pts[j0].y + wdxNext - kappa * dyNext;
	      pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y);
	      pathOut->lineTo(pathIn->pts[j0].x - wdyNext,
			      pathIn->pts[j0].y + wdxNext);
	      pathOut->curveTo(cx4, cy4, cx3, cy3, xc, yc);
	      pathOut->curveTo(cx2, cy2, cx1, cy1,
			       pathIn->pts[j0].x - wdy,
			       pathIn->pts[j0].y + wdx);
	    }
	  }

	// join angle >= 180
	} else {
	  angle = atan2((double)-dx, (double)dy);
	  angleNext = atan2((double)-dxNext, (double)dyNext);
	  if (angleNext < angle) {
	    angleNext += 2 * M_PI;
	  }
	  dAngle = (angleNext - angle) / M_PI;
	  if (dAngle < 0.501) {
	    // span angle is <= 90 degrees -> draw a single arc
	    kappa = dAngle * bezierCircle * w;
	      cx1 = pathIn->pts[j0].x + wdy + kappa * dx;
	      cy1 = pathIn->pts[j0].y - wdx + kappa * dy;
	      cx2 = pathIn->pts[j0].x + wdyNext - kappa * dxNext;
	      cy2 = pathIn->pts[j0].y - wdxNext - kappa * dyNext;
	      pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y);
	      pathOut->lineTo(pathIn->pts[j0].x + wdy,
			      pathIn->pts[j0].y - wdx);
	      pathOut->curveTo(cx1, cy1, cx2, cy2,
			       pathIn->pts[j0].x + wdyNext,
			       pathIn->pts[j0].y - wdxNext);
	  } else {
	    // span angle is > 90 degrees -> split into two arcs
	    dJoin = splashDist(wdy, -wdx, wdyNext, -wdxNext);
	    if (dJoin > 0) {
	      dxJoin = (wdyNext - wdy) / dJoin;
	      dyJoin = (-wdxNext + wdx) / dJoin;
	      xc = pathIn->pts[j0].x
		   + (SplashCoord)0.5 * w
		     * cos((double)((SplashCoord)0.5 * (angle + angleNext)));
	      yc = pathIn->pts[j0].y
		   + (SplashCoord)0.5 * w
		     * sin((double)((SplashCoord)0.5 * (angle + angleNext)));
	      kappa = dAngle * bezierCircle2 * w;
	      cx1 = pathIn->pts[j0].x + wdy + kappa * dx;
	      cy1 = pathIn->pts[j0].y - wdx + kappa * dy;
	      cx2 = xc - kappa * dxJoin;
	      cy2 = yc - kappa * dyJoin;
	      cx3 = xc + kappa * dxJoin;
	      cy3 = yc + kappa * dyJoin;
	      cx4 = pathIn->pts[j0].x + wdyNext - kappa * dxNext;
	      cy4 = pathIn->pts[j0].y - wdxNext - kappa * dyNext;
	      pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y);
	      pathOut->lineTo(pathIn->pts[j0].x + wdy,
			      pathIn->pts[j0].y - wdx);
	      pathOut->curveTo(cx1, cy1, cx2, cy2, xc, yc);
	      pathOut->curveTo(cx3, cy3, cx4, cy4,
			       pathIn->pts[j0].x + wdyNext,
			       pathIn->pts[j0].y - wdxNext);
	    }
	  }
	}

      } else {
	pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y);

	// join angle < 180
	if (crossprod < 0) {
	  pathOut->lineTo(pathIn->pts[j0].x - wdyNext,
			  pathIn->pts[j0].y + wdxNext);
	  // miter join inside limit
	  if (lineJoin == splashLineJoinMiter &&
	      splashSqrt(miter) <= state->miterLimit) {
	    pathOut->lineTo(pathIn->pts[j0].x - wdy + wdx * m,
			    pathIn->pts[j0].y + wdx + wdy * m);
	    pathOut->lineTo(pathIn->pts[j0].x - wdy,
			    pathIn->pts[j0].y + wdx);
	  // bevel join or miter join outside limit
	  } else {
	    pathOut->lineTo(pathIn->pts[j0].x - wdy,
			    pathIn->pts[j0].y + wdx);
	  }

	// join angle >= 180
	} else {
	  pathOut->lineTo(pathIn->pts[j0].x + wdy,
			  pathIn->pts[j0].y - wdx);
	  // miter join inside limit
	  if (lineJoin == splashLineJoinMiter &&
	      splashSqrt(miter) <= state->miterLimit) {
	    pathOut->lineTo(pathIn->pts[j0].x + wdy + wdx * m,
			    pathIn->pts[j0].y - wdx + wdy * m);
	    pathOut->lineTo(pathIn->pts[j0].x + wdyNext,
			    pathIn->pts[j0].y - wdxNext);
	  // bevel join or miter join outside limit
	  } else {
	    pathOut->lineTo(pathIn->pts[j0].x + wdyNext,
			    pathIn->pts[j0].y - wdxNext);
	  }
	}
      }

      pathOut->close();
    }

    // add stroke adjustment hints
    if (state->strokeAdjust != splashStrokeAdjustOff) {

      // subpath with one segment
      if (seg == 0 && last) {
	switch (lineCap) {
	case splashLineCapButt:
	  pathOut->addStrokeAdjustHint(firstPt, left2 + 1,
				       firstPt, pathOut->length - 1);
	  break;
	case splashLineCapProjecting:
	  pathOut->addStrokeAdjustHint(firstPt, left2 + 1,
				       firstPt, pathOut->length - 1, gTrue);
	  break;
	case splashLineCapRound:
	  break;
	}
	pathOut->addStrokeAdjustHint(left2, right2,
				     firstPt, pathOut->length - 1);
      } else {

	// start of subpath
	if (seg == 1) {

	  // start cap
	  if (!closed) {
	    switch (lineCap) {
	    case splashLineCapButt:
	      pathOut->addStrokeAdjustHint(firstPt, left1 + 1,
					   firstPt, firstPt + 1);
	      pathOut->addStrokeAdjustHint(firstPt, left1 + 1,
					   right1 + 1, right1 + 1);
	      break;
	    case splashLineCapProjecting:
	      pathOut->addStrokeAdjustHint(firstPt, left1 + 1,
					   firstPt, firstPt + 1, gTrue);
	      pathOut->addStrokeAdjustHint(firstPt, left1 + 1,
					   right1 + 1, right1 + 1, gTrue);
	      break;
	    case splashLineCapRound:
	      break;
	    }
	  }

	  // first segment
	  pathOut->addStrokeAdjustHint(left1, right1, firstPt, left2);
	  pathOut->addStrokeAdjustHint(left1, right1, right2 + 1, right2 + 1);
	}

	// middle of subpath
	if (seg > 1) {
	  pathOut->addStrokeAdjustHint(left1, right1, left0 + 1, right0);
	  pathOut->addStrokeAdjustHint(left1, right1, join0, left2);
	  pathOut->addStrokeAdjustHint(left1, right1, right2 + 1, right2 + 1);
	}

	// end of subpath
	if (last) {

	  if (closed) {
	    // first segment
	    pathOut->addStrokeAdjustHint(leftFirst, rightFirst,
					 left2 + 1, right2);
	    pathOut->addStrokeAdjustHint(leftFirst, rightFirst,
					 join2, pathOut->length - 1);

	    // last segment
	    pathOut->addStrokeAdjustHint(left2, right2,
					 left1 + 1, right1);
	    pathOut->addStrokeAdjustHint(left2, right2,
					 join1, pathOut->length - 1);
	    pathOut->addStrokeAdjustHint(left2, right2,
					 leftFirst - 1, leftFirst);
	    pathOut->addStrokeAdjustHint(left2, right2,
					 rightFirst + 1, rightFirst + 1);

	  } else {

	    // last segment
	    pathOut->addStrokeAdjustHint(left2, right2,
					 left1 + 1, right1);
	    pathOut->addStrokeAdjustHint(left2, right2,
					 join1, pathOut->length - 1);

	    // end cap
	    switch (lineCap) {
	    case splashLineCapButt:
	      pathOut->addStrokeAdjustHint(left2 - 1, left2 + 1,
					   left2 + 1, left2 + 2);
	      break;
	    case splashLineCapProjecting:
	      pathOut->addStrokeAdjustHint(left2 - 1, left2 + 1,
					   left2 + 1, left2 + 2, gTrue);
	      break;
	    case splashLineCapRound:
	      break;
	    }
	  }
	}
      }

      left0 = left1;
      left1 = left2;
      right0 = right1;
      right1 = right2;
      join0 = join1;
      join1 = join2;
      if (seg == 0) {
	leftFirst = left2;
	rightFirst = right2;
      }
    }

    i0 = j0;
    i1 = j1;
    ++seg;
  }

  if (pathIn != path) {
    delete pathIn;
  }

  return pathOut;
}

SplashClipResult Splash::limitRectToClipRect(int *xMin, int *yMin,
					     int *xMax, int *yMax) {
  int t;

  if ((t = state->clip->getXMinI(state->strokeAdjust)) > *xMin) {
    *xMin = t;
  }
  if ((t = state->clip->getXMaxI(state->strokeAdjust) + 1) < *xMax) {
    *xMax = t;
  }
  if ((t = state->clip->getYMinI(state->strokeAdjust)) > *yMin) {
    *yMin = t;
  }
  if ((t = state->clip->getYMaxI(state->strokeAdjust) + 1) < *yMax) {
    *yMax = t;
  }
  if (*xMin >= *xMax || *yMin >= *yMax) {
    return splashClipAllOutside;
  }
  return state->clip->testRect(*xMin, *yMin, *xMax - 1, *yMax - 1,
			       state->strokeAdjust);
}

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" : "");
  }
  if (path->hintsLength == 0) {
    printf("  no hints\n");
  } else {
    for (i = 0; i < path->hintsLength; ++i) {
      printf("  hint %3d: ctrl0=%d ctrl1=%d pts=%d..%d\n",
	     i, path->hints[i].ctrl0, path->hints[i].ctrl1,
	     path->hints[i].firstPt, path->hints[i].lastPt);
    }
  }
}

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 count=%d\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].count);
  }
}

