diff options
| author | Calvin Morrison <calvin@pobox.com> | 2023-04-05 14:13:39 -0400 | 
|---|---|---|
| committer | Calvin Morrison <calvin@pobox.com> | 2023-04-05 14:13:39 -0400 | 
| commit | 835e373b3eeaabcd0621ed6798ab500f37982fae (patch) | |
| tree | dfa16b0e2e1b4956b38f693220eac4e607802133 /xpdf/SplashOutputDev.cc | |
Diffstat (limited to 'xpdf/SplashOutputDev.cc')
| -rw-r--r-- | xpdf/SplashOutputDev.cc | 4550 | 
1 files changed, 4550 insertions, 0 deletions
diff --git a/xpdf/SplashOutputDev.cc b/xpdf/SplashOutputDev.cc new file mode 100644 index 0000000..d758e25 --- /dev/null +++ b/xpdf/SplashOutputDev.cc @@ -0,0 +1,4550 @@ +//======================================================================== +// +// SplashOutputDev.cc +// +// Copyright 2003-2013 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <string.h> +#include <math.h> +#include <limits.h> +#include "gmempp.h" +#include "gfile.h" +#include "Trace.h" +#include "GlobalParams.h" +#include "Error.h" +#include "Object.h" +#include "Gfx.h" +#include "GfxFont.h" +#include "ShadingImage.h" +#include "Link.h" +#include "CharCodeToUnicode.h" +#include "FontEncodingTables.h" +#include "BuiltinFont.h" +#include "BuiltinFontTables.h" +#include "FoFiTrueType.h" +#include "FoFiType1C.h" +#include "JPXStream.h" +#include "SplashBitmap.h" +#include "SplashGlyphBitmap.h" +#include "SplashPattern.h" +#include "SplashScreen.h" +#include "SplashPath.h" +#include "SplashState.h" +#include "SplashErrorCodes.h" +#include "SplashFontEngine.h" +#include "SplashFont.h" +#include "SplashFontFile.h" +#include "SplashFontFileID.h" +#include "Splash.h" +#include "SplashOutputDev.h" + +#ifdef VMS +#if (__VMS_VER < 70000000) +extern "C" int unlink(char *filename); +#endif +#endif + +//------------------------------------------------------------------------ + +// max tile size (used in tilingPatternFill()) +// - Adobe uses a resolution-independent threshold here, of 6M sq pts +// - xpdf uses a resolution-dependent max, but with different values +//   on 32-bit and 64-bit systems +#if SplashBitmapRowSizeMax == INT_MAX +#  define maxTileSize 200000000 +#else +#  define maxTileSize 2000000000 +#endif + +//------------------------------------------------------------------------ + +// Type 3 font cache size parameters +#define type3FontCacheAssoc   8 +#define type3FontCacheMaxSets 8 +#define type3FontCacheSize    (128*1024) + +// Map StrokeAdjustMode (from GlobalParams) to SplashStrokeAdjustMode +// (for Splash). +static SplashStrokeAdjustMode mapStrokeAdjustMode[3] = { +  splashStrokeAdjustOff, +  splashStrokeAdjustNormal, +  splashStrokeAdjustCAD +}; + +//------------------------------------------------------------------------ + +// 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; +} + + +//------------------------------------------------------------------------ +// Blend functions +//------------------------------------------------------------------------ + +static void splashOutBlendMultiply(SplashColorPtr src, SplashColorPtr dest, +				   SplashColorPtr blend, SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    blend[i] = (Guchar)((dest[i] * src[i]) / 255); +  } +} + +static void splashOutBlendScreen(SplashColorPtr src, SplashColorPtr dest, +				 SplashColorPtr blend, SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    blend[i] = (Guchar)(dest[i] + src[i] - (dest[i] * src[i]) / 255); +  } +} + +// note: this is the same as HardLight, with src/dest reversed +static void splashOutBlendOverlay(SplashColorPtr src, SplashColorPtr dest, +				  SplashColorPtr blend, SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    // the spec says "if Cs <= 0.5" -- note that 0x80 is 128/255=0.5020 +    blend[i] = dest[i] < 0x80 +                 ? (Guchar)((src[i] * 2 * dest[i]) / 255) +                 : (Guchar)(255 - 2 * ((255 - src[i]) * (255 - dest[i])) / 255); +  } +} + +static void splashOutBlendDarken(SplashColorPtr src, SplashColorPtr dest, +				 SplashColorPtr blend, SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    blend[i] = dest[i] < src[i] ? dest[i] : src[i]; +  } +} + +static void splashOutBlendLighten(SplashColorPtr src, SplashColorPtr dest, +				  SplashColorPtr blend, SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    blend[i] = dest[i] > src[i] ? dest[i] : src[i]; +  } +} + +static void splashOutBlendColorDodge(SplashColorPtr src, SplashColorPtr dest, +				     SplashColorPtr blend, +				     SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    if (dest[i] == 0) { +      blend[i] = 0; +    } else if (dest[i] >= 255 - src[i]) { +      blend[i] = 255; +    } else { +      blend[i] = (Guchar)((dest[i] * 255) / (255 - src[i])); +    } +  } +} + +static void splashOutBlendColorBurn(SplashColorPtr src, SplashColorPtr dest, +				    SplashColorPtr blend, SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    if (dest[i] == 255) { +      blend[i] = 255; +    } else if (255 - dest[i] >= src[i]) { +      blend[i] = 0; +    } else { +      blend[i] = (Guchar)(255 - (((255 - dest[i]) * 255) / src[i])); +    } +  } +} + +static void splashOutBlendHardLight(SplashColorPtr src, SplashColorPtr dest, +				    SplashColorPtr blend, SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    // the spec says "if Cs <= 0.5" -- note that 0x80 is 128/255=0.5020 +    blend[i] = src[i] < 0x80 +                 ? (Guchar)((dest[i] * 2 * src[i]) / 255) +                 : (Guchar)(255 - 2 * ((255 - dest[i]) * (255 - src[i])) / 255); +  } +} + +static void splashOutBlendSoftLight(SplashColorPtr src, SplashColorPtr dest, +				    SplashColorPtr blend, SplashColorMode cm) { +  int i, x; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    // the spec says "if Cs <= 0.5" -- note that 0x80 is 128/255=0.5020 +    if (src[i] < 0x80) { +      blend[i] = (Guchar)(dest[i] - +			  (255 - 2 * src[i]) * dest[i] * (255 - dest[i]) / +			    (255 * 255)); +    } else { +      // the spec says "if Cb <= 0.25" -- note that 0x40 is 64/255=0.2510 +      if (dest[i] < 0x40) { +	x = (((((16 * dest[i] - 12 * 255) * dest[i]) / 255) +	      + 4 * 255) * dest[i]) / 255; +      } else { +	x = (int)sqrt(255.0 * dest[i]); +      } +      blend[i] = (Guchar)(dest[i] + (2 * src[i] - 255) * (x - dest[i]) / 255); +    } +  } +} + +static void splashOutBlendDifference(SplashColorPtr src, SplashColorPtr dest, +				     SplashColorPtr blend, +				     SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    blend[i] = dest[i] < src[i] ? (Guchar)(src[i] - dest[i]) +			        : (Guchar)(dest[i] - src[i]); +  } +} + +static void splashOutBlendExclusion(SplashColorPtr src, SplashColorPtr dest, +				    SplashColorPtr blend, SplashColorMode cm) { +  int i; + +  for (i = 0; i < splashColorModeNComps[cm]; ++i) { +    blend[i] = (Guchar)(dest[i] + src[i] - (2 * dest[i] * src[i]) / 255); +  } +} + +static int getLum(int r, int g, int b) { +  return (int)(0.3 * r + 0.59 * g + 0.11 * b); +} + +static int getSat(int r, int g, int b) { +  int rgbMin, rgbMax; + +  rgbMin = rgbMax = r; +  if (g < rgbMin) { +    rgbMin = g; +  } else if (g > rgbMax) { +    rgbMax = g; +  } +  if (b < rgbMin) { +    rgbMin = b; +  } else if (b > rgbMax) { +    rgbMax = b; +  } +  return rgbMax - rgbMin; +} + +static void clipColor(int rIn, int gIn, int bIn, +		      Guchar *rOut, Guchar *gOut, Guchar *bOut) { +  int lum, rgbMin, rgbMax, r, g, b; + +  lum = getLum(rIn, gIn, bIn); +  rgbMin = rgbMax = rIn; +  if (gIn < rgbMin) { +    rgbMin = gIn; +  } else if (gIn > rgbMax) { +    rgbMax = gIn; +  } +  if (bIn < rgbMin) { +    rgbMin = bIn; +  } else if (bIn > rgbMax) { +    rgbMax = bIn; +  } +  r = rIn; +  g = gIn; +  b = bIn; +  if (rgbMin < 0) { +    r = lum + ((r - lum) * lum) / (lum - rgbMin); +    g = lum + ((g - lum) * lum) / (lum - rgbMin); +    b = lum + ((b - lum) * lum) / (lum - rgbMin); +  } +  if (rgbMax > 255) { +    r = lum + ((r - lum) * (255 - lum)) / (rgbMax - lum); +    g = lum + ((g - lum) * (255 - lum)) / (rgbMax - lum); +    b = lum + ((b - lum) * (255 - lum)) / (rgbMax - lum); +  } +  *rOut = (Guchar)r; +  *gOut = (Guchar)g; +  *bOut = (Guchar)b; +} + +static void setLum(Guchar rIn, Guchar gIn, Guchar bIn, int lum, +		   Guchar *rOut, Guchar *gOut, Guchar *bOut) { +  int d; + +  d = lum - getLum(rIn, gIn, bIn); +  clipColor(rIn + d, gIn + d, bIn + d, rOut, gOut, bOut); +} + +static void setSat(Guchar rIn, Guchar gIn, Guchar bIn, int sat, +		   Guchar *rOut, Guchar *gOut, Guchar *bOut) { +  int rgbMin, rgbMid, rgbMax; +  Guchar *minOut, *midOut, *maxOut; + +  if (rIn < gIn) { +    rgbMin = rIn;  minOut = rOut; +    rgbMid = gIn;  midOut = gOut; +  } else { +    rgbMin = gIn;  minOut = gOut; +    rgbMid = rIn;  midOut = rOut; +  } +  if (bIn > rgbMid) { +    rgbMax = bIn;  maxOut = bOut; +  } else if (bIn > rgbMin) { +    rgbMax = rgbMid;  maxOut = midOut; +    rgbMid = bIn;     midOut = bOut; +  } else { +    rgbMax = rgbMid;  maxOut = midOut; +    rgbMid = rgbMin;  midOut = minOut; +    rgbMin = bIn;     minOut = bOut; +  } +  if (rgbMax > rgbMin) { +    *midOut = (Guchar)(((rgbMid - rgbMin) * sat) / (rgbMax - rgbMin)); +    *maxOut = (Guchar)sat; +  } else { +    *midOut = *maxOut = 0; +  } +  *minOut = 0; +} + +static void splashOutBlendHue(SplashColorPtr src, SplashColorPtr dest, +			      SplashColorPtr blend, SplashColorMode cm) { +  Guchar r0, g0, b0; + +  switch (cm) { +  case splashModeMono1: +  case splashModeMono8: +    blend[0] = dest[0]; +    break; +  case splashModeRGB8: +  case splashModeBGR8: +    setSat(src[0], src[1], src[2], getSat(dest[0], dest[1], dest[2]), +	   &r0, &g0, &b0); +    setLum(r0, g0, b0, getLum(dest[0], dest[1], dest[2]), +	   &blend[0], &blend[1], &blend[2]); +    break; +#if SPLASH_CMYK +  case splashModeCMYK8: +    // NB: inputs have already been converted to additive mode +    setSat(src[0], src[1], src[2], getSat(dest[0], dest[1], dest[2]), +	   &r0, &g0, &b0); +    setLum(r0, g0, b0, getLum(dest[0], dest[1], dest[2]), +	   &blend[0], &blend[1], &blend[2]); +    blend[3] = dest[3]; +    break; +#endif +  } +} + +static void splashOutBlendSaturation(SplashColorPtr src, SplashColorPtr dest, +				     SplashColorPtr blend, +				     SplashColorMode cm) { +  Guchar r0, g0, b0; + +  switch (cm) { +  case splashModeMono1: +  case splashModeMono8: +    blend[0] = dest[0]; +    break; +  case splashModeRGB8: +  case splashModeBGR8: +    setSat(dest[0], dest[1], dest[2], getSat(src[0], src[1], src[2]), +	   &r0, &g0, &b0); +    setLum(r0, g0, b0, getLum(dest[0], dest[1], dest[2]), +	   &blend[0], &blend[1], &blend[2]); +    break; +#if SPLASH_CMYK +  case splashModeCMYK8: +    // NB: inputs have already been converted to additive mode +    setSat(dest[0], dest[1], dest[2], getSat(src[0], src[1], src[2]), +	   &r0, &g0, &b0); +    setLum(r0, g0, b0, getLum(dest[0], dest[1], dest[2]), +	   &blend[0], &blend[1], &blend[2]); +    blend[3] = dest[3]; +    break; +#endif +  } +} + +static void splashOutBlendColor(SplashColorPtr src, SplashColorPtr dest, +				SplashColorPtr blend, SplashColorMode cm) { + +  switch (cm) { +  case splashModeMono1: +  case splashModeMono8: +    blend[0] = dest[0]; +    break; +  case splashModeRGB8: +  case splashModeBGR8: +    setLum(src[0], src[1], src[2], getLum(dest[0], dest[1], dest[2]), +	   &blend[0], &blend[1], &blend[2]); +    break; +#if SPLASH_CMYK +  case splashModeCMYK8: +    // NB: inputs have already been converted to additive mode +    setLum(src[0], src[1], src[2], getLum(dest[0], dest[1], dest[2]), +	   &blend[0], &blend[1], &blend[2]); +    blend[3] = dest[3]; +    break; +#endif +  } +} + +static void splashOutBlendLuminosity(SplashColorPtr src, SplashColorPtr dest, +				     SplashColorPtr blend, +				     SplashColorMode cm) { + +  switch (cm) { +  case splashModeMono1: +  case splashModeMono8: +    blend[0] = dest[0]; +    break; +  case splashModeRGB8: +  case splashModeBGR8: +    setLum(dest[0], dest[1], dest[2], getLum(src[0], src[1], src[2]), +	   &blend[0], &blend[1], &blend[2]); +    break; +#if SPLASH_CMYK +  case splashModeCMYK8: +    // NB: inputs have already been converted to additive mode +    setLum(dest[0], dest[1], dest[2], getLum(src[0], src[1], src[2]), +	   &blend[0], &blend[1], &blend[2]); +    blend[3] = src[3]; +    break; +#endif +  } +} + +// NB: This must match the GfxBlendMode enum defined in GfxState.h. +SplashBlendFunc splashOutBlendFuncs[] = { +  NULL, +  &splashOutBlendMultiply, +  &splashOutBlendScreen, +  &splashOutBlendOverlay, +  &splashOutBlendDarken, +  &splashOutBlendLighten, +  &splashOutBlendColorDodge, +  &splashOutBlendColorBurn, +  &splashOutBlendHardLight, +  &splashOutBlendSoftLight, +  &splashOutBlendDifference, +  &splashOutBlendExclusion, +  &splashOutBlendHue, +  &splashOutBlendSaturation, +  &splashOutBlendColor, +  &splashOutBlendLuminosity +}; + + +//------------------------------------------------------------------------ +// SplashOutFontFileID +//------------------------------------------------------------------------ + +class SplashOutFontFileID: public SplashFontFileID { +public: + +  SplashOutFontFileID(Ref *rA) { +    r = *rA; +    substIdx = -1; +    oblique = 0; +  } + +  ~SplashOutFontFileID() {} + +  GBool matches(SplashFontFileID *id) { +    return ((SplashOutFontFileID *)id)->r.num == r.num && +           ((SplashOutFontFileID *)id)->r.gen == r.gen; +  } + +  void setOblique(double obliqueA) { oblique = obliqueA; } +  double getOblique() { return oblique; } +  void setSubstIdx(int substIdxA) { substIdx = substIdxA; } +  int getSubstIdx() { return substIdx; } + +private: + +  Ref r; +  double oblique; +  int substIdx; +}; + +//------------------------------------------------------------------------ +// T3FontCache +//------------------------------------------------------------------------ + +struct T3FontCacheTag { +  Gushort code; +  Gushort mru;			// valid bit (0x8000) and MRU index +}; + +class T3FontCache { +public: + +  T3FontCache(Ref *fontID, double m11A, double m12A, +	      double m21A, double m22A, +	      int glyphXA, int glyphYA, int glyphWA, int glyphHA, +	      GBool validBBoxA, GBool aa); +  ~T3FontCache(); +  GBool matches(Ref *idA, double m11A, double m12A, +		double m21A, double m22A) +    { return fontID.num == idA->num && fontID.gen == idA->gen && +	     m11 == m11A && m12 == m12A && m21 == m21A && m22 == m22A; } + +  Ref fontID;			// PDF font ID +  double m11, m12, m21, m22;	// transform matrix +  int glyphX, glyphY;		// pixel offset of glyph bitmaps +  int glyphW, glyphH;		// size of glyph bitmaps, in pixels +  GBool validBBox;		// false if the bbox was [0 0 0 0] +  int glyphSize;		// size of glyph bitmaps, in bytes +  int cacheSets;		// number of sets in cache +  int cacheAssoc;		// cache associativity (glyphs per set) +  Guchar *cacheData;		// glyph pixmap cache +  T3FontCacheTag *cacheTags;	// cache tags, i.e., char codes +  int refCount;			// active reference count for this T3 font +}; + +T3FontCache::T3FontCache(Ref *fontIDA, double m11A, double m12A, +			 double m21A, double m22A, +			 int glyphXA, int glyphYA, int glyphWA, int glyphHA, +			 GBool validBBoxA, GBool aa) { +  int i; + +  fontID = *fontIDA; +  m11 = m11A; +  m12 = m12A; +  m21 = m21A; +  m22 = m22A; +  glyphX = glyphXA; +  glyphY = glyphYA; +  glyphW = glyphWA; +  glyphH = glyphHA; +  validBBox = validBBoxA; +  // sanity check for excessively large glyphs (which most likely +  // indicate an incorrect BBox) +  i = glyphW * glyphH; +  if (i > 100000 || glyphW > INT_MAX / glyphH || glyphW <= 0 || glyphH <= 0) { +    glyphW = glyphH = 100; +    validBBox = gFalse; +  } +  if (aa) { +    glyphSize = glyphW * glyphH; +  } else { +    glyphSize = ((glyphW + 7) >> 3) * glyphH; +  } +  cacheAssoc = type3FontCacheAssoc; +  for (cacheSets = type3FontCacheMaxSets; +       cacheSets > 1 && +	 cacheSets * cacheAssoc * glyphSize > type3FontCacheSize; +       cacheSets >>= 1) ; +  cacheData = (Guchar *)gmallocn(cacheSets * cacheAssoc, glyphSize); +  cacheTags = (T3FontCacheTag *)gmallocn(cacheSets * cacheAssoc, +					 sizeof(T3FontCacheTag)); +  for (i = 0; i < cacheSets * cacheAssoc; ++i) { +    cacheTags[i].mru = (Gushort)(i & (cacheAssoc - 1)); +  } +  refCount = 0; +} + +T3FontCache::~T3FontCache() { +  gfree(cacheData); +  gfree(cacheTags); +} + +struct T3GlyphStack { +  Gushort code;			// character code + +  GBool haveDx;			// set after seeing a d0/d1 operator +  GBool doNotCache;		// set if we see a gsave/grestore before +				//   the d0/d1 + +  //----- cache info +  T3FontCache *cache;		// font cache for the current font +  T3FontCacheTag *cacheTag;	// pointer to cache tag for the glyph +  Guchar *cacheData;		// pointer to cache data for the glyph + +  //----- saved state +  SplashBitmap *origBitmap; +  Splash *origSplash; +  double origCTM4, origCTM5; +  SplashStrokeAdjustMode savedStrokeAdjust; + +  T3GlyphStack *next;		// next object on stack +}; + +//------------------------------------------------------------------------ +// SplashTransparencyGroup +//------------------------------------------------------------------------ + +struct SplashTransparencyGroup { +  int tx, ty;			// translation coordinates +  SplashBitmap *tBitmap;	// bitmap for transparency group +  GfxColorSpace *blendingColorSpace; +  GBool isolated; + +  //----- modified region in tBitmap +  int modXMin, modYMin, modXMax, modYMax; + +  //----- saved state +  SplashBitmap *origBitmap; +  Splash *origSplash; +  SplashBitmap *backdropBitmap; + +  SplashTransparencyGroup *next; +}; + +//------------------------------------------------------------------------ +// SplashOutputDev +//------------------------------------------------------------------------ + +SplashOutputDev::SplashOutputDev(SplashColorMode colorModeA, +				 int bitmapRowPadA, +				 GBool reverseVideoA, +				 SplashColorPtr paperColorA, +				 GBool bitmapTopDownA, +				 GBool allowAntialiasA) { +  colorMode = colorModeA; +  bitmapRowPad = bitmapRowPadA; +  bitmapTopDown = bitmapTopDownA; +  bitmapUpsideDown = gFalse; +  noComposite = gFalse; +  allowAntialias = allowAntialiasA; +  vectorAntialias = allowAntialias && +		      globalParams->getVectorAntialias() && +		      colorMode != splashModeMono1; +  setupScreenParams(72.0, 72.0); +  reverseVideo = reverseVideoA; +  splashColorCopy(paperColor, paperColorA); +  skipHorizText = gFalse; +  skipRotatedText = gFalse; + +  xref = NULL; + +  bitmap = new SplashBitmap(1, 1, bitmapRowPad, colorMode, +			    colorMode != splashModeMono1, bitmapTopDown, NULL); +  splash = new Splash(bitmap, vectorAntialias, NULL, &screenParams); +  splash->setMinLineWidth(globalParams->getMinLineWidth()); +  splash->setStrokeAdjust( +		 mapStrokeAdjustMode[globalParams->getStrokeAdjust()]); +  splash->setEnablePathSimplification( +		 globalParams->getEnablePathSimplification()); +  splash->clear(paperColor, 0); + +  fontEngine = NULL; + +  nT3Fonts = 0; +  t3GlyphStack = NULL; + +  font = NULL; +  needFontUpdate = gFalse; +  textClipPath = NULL; + +  transpGroupStack = NULL; + +  nestCount = 0; + +  startPageCbk = NULL; +  startPageCbkData = NULL; +} + + +void SplashOutputDev::setupScreenParams(double hDPI, double vDPI) { +  screenParams.size = globalParams->getScreenSize(); +  screenParams.dotRadius = globalParams->getScreenDotRadius(); +  screenParams.gamma = (SplashCoord)globalParams->getScreenGamma(); +  screenParams.blackThreshold = +      (SplashCoord)globalParams->getScreenBlackThreshold(); +  screenParams.whiteThreshold = +      (SplashCoord)globalParams->getScreenWhiteThreshold(); +  switch (globalParams->getScreenType()) { +  case screenDispersed: +    screenParams.type = splashScreenDispersed; +    if (screenParams.size < 0) { +      screenParams.size = 4; +    } +    break; +  case screenClustered: +    screenParams.type = splashScreenClustered; +    if (screenParams.size < 0) { +      screenParams.size = 10; +    } +    break; +  case screenStochasticClustered: +    screenParams.type = splashScreenStochasticClustered; +    if (screenParams.size < 0) { +      screenParams.size = 64; +    } +    if (screenParams.dotRadius < 0) { +      screenParams.dotRadius = 2; +    } +    break; +  case screenUnset: +  default: +    // use clustered dithering for resolution >= 300 dpi +    // (compare to 299.9 to avoid floating point issues) +    if (hDPI > 299.9 && vDPI > 299.9) { +      screenParams.type = splashScreenStochasticClustered; +      if (screenParams.size < 0) { +	screenParams.size = 64; +      } +      if (screenParams.dotRadius < 0) { +	screenParams.dotRadius = 2; +      } +    } else { +      screenParams.type = splashScreenDispersed; +      if (screenParams.size < 0) { +	screenParams.size = 4; +      } +    } +  } +} + +SplashOutputDev::~SplashOutputDev() { +  int i; + +  for (i = 0; i < nT3Fonts; ++i) { +    delete t3FontCache[i]; +  } +  if (fontEngine) { +    delete fontEngine; +  } +  if (splash) { +    delete splash; +  } +  if (bitmap) { +    delete bitmap; +  } +  if (textClipPath) { +    delete textClipPath; +  } +} + +void SplashOutputDev::startDoc(XRef *xrefA) { +  int i; + +  xref = xrefA; +  if (fontEngine) { +    delete fontEngine; +  } +  fontEngine = new SplashFontEngine( +#if HAVE_FREETYPE_H +				    globalParams->getEnableFreeType(), +				    globalParams->getDisableFreeTypeHinting() +				      ? splashFTNoHinting : 0, +#endif +				    allowAntialias && +				      globalParams->getAntialias() && +				      colorMode != splashModeMono1); +  for (i = 0; i < nT3Fonts; ++i) { +    delete t3FontCache[i]; +  } +  nT3Fonts = 0; +} + +void SplashOutputDev::startPage(int pageNum, GfxState *state) { +  int w, h; +  double *ctm; +  SplashCoord mat[6]; +  SplashColor color; + +  if (state) { +    setupScreenParams(state->getHDPI(), state->getVDPI()); +    w = (int)(state->getPageWidth() + 0.5); +    if (w <= 0) { +      w = 1; +    } +    h = (int)(state->getPageHeight() + 0.5); +    if (h <= 0) { +      h = 1; +    } +  } else { +    w = h = 1; +  } +  if (splash) { +    delete splash; +    splash = NULL; +  } +  if (!bitmap || w != bitmap->getWidth() || h != bitmap->getHeight()) { +    if (bitmap) { +      delete bitmap; +      bitmap = NULL; +    } +    traceMessage("page bitmap"); +    bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, +			      colorMode != splashModeMono1, bitmapTopDown, +			      NULL); +  } +  splash = new Splash(bitmap, vectorAntialias, NULL, &screenParams); +  splash->setMinLineWidth(globalParams->getMinLineWidth()); +  splash->setEnablePathSimplification( +		 globalParams->getEnablePathSimplification()); +  if (state) { +    ctm = state->getCTM(); +    mat[0] = (SplashCoord)ctm[0]; +    mat[1] = (SplashCoord)ctm[1]; +    mat[2] = (SplashCoord)ctm[2]; +    mat[3] = (SplashCoord)ctm[3]; +    mat[4] = (SplashCoord)ctm[4]; +    mat[5] = (SplashCoord)ctm[5]; +    splash->setMatrix(mat); +  } +  switch (colorMode) { +  case splashModeMono1: +  case splashModeMono8: +    color[0] = 0; +    break; +  case splashModeRGB8: +  case splashModeBGR8: +    color[0] = color[1] = color[2] = 0; +    break; +#if SPLASH_CMYK +  case splashModeCMYK8: +    color[0] = color[1] = color[2] = color[3] = 0; +    break; +#endif +  } +  splash->setStrokePattern(new SplashSolidColor(color)); +  splash->setFillPattern(new SplashSolidColor(color)); +  splash->setLineCap(splashLineCapButt); +  splash->setLineJoin(splashLineJoinMiter); +  splash->setLineDash(NULL, 0, 0); +  splash->setMiterLimit(10); +  splash->setFlatness(1); +  // the SA parameter supposedly defaults to false, but Acrobat +  // apparently hardwires it to true +  splash->setStrokeAdjust( +	      mapStrokeAdjustMode[globalParams->getStrokeAdjust()]); +  splash->clear(paperColor, 0); +  reverseVideoInvertImages = globalParams->getReverseVideoInvertImages(); +  if (startPageCbk) { +    (*startPageCbk)(startPageCbkData); +  } +} + +void SplashOutputDev::endPage() { +  if (colorMode != splashModeMono1 && !noComposite) { +    splash->compositeBackground(paperColor); +  } +} + +void SplashOutputDev::saveState(GfxState *state) { +  splash->saveState(); +  if (t3GlyphStack && !t3GlyphStack->haveDx) { +    t3GlyphStack->doNotCache = gTrue; +    error(errSyntaxWarning, -1, +	  "Save (q) operator before d0/d1 in Type 3 glyph"); +  } +} + +void SplashOutputDev::restoreState(GfxState *state) { +  splash->restoreState(); +  needFontUpdate = gTrue; +  if (t3GlyphStack && !t3GlyphStack->haveDx) { +    t3GlyphStack->doNotCache = gTrue; +    error(errSyntaxWarning, -1, +	  "Restore (Q) operator before d0/d1 in Type 3 glyph"); +  } +} + +void SplashOutputDev::updateAll(GfxState *state) { +  updateLineDash(state); +  updateLineJoin(state); +  updateLineCap(state); +  updateLineWidth(state); +  updateFlatness(state); +  updateMiterLimit(state); +  updateStrokeAdjust(state); +  updateFillColor(state); +  updateStrokeColor(state); +  needFontUpdate = gTrue; +} + +void SplashOutputDev::updateCTM(GfxState *state, double m11, double m12, +				double m21, double m22, +				double m31, double m32) { +  double *ctm; +  SplashCoord mat[6]; + +  ctm = state->getCTM(); +  mat[0] = (SplashCoord)ctm[0]; +  mat[1] = (SplashCoord)ctm[1]; +  mat[2] = (SplashCoord)ctm[2]; +  mat[3] = (SplashCoord)ctm[3]; +  mat[4] = (SplashCoord)ctm[4]; +  mat[5] = (SplashCoord)ctm[5]; +  splash->setMatrix(mat); +} + +void SplashOutputDev::updateLineDash(GfxState *state) { +  double *dashPattern; +  int dashLength; +  double dashStart; +  SplashCoord dash[20]; +  int i; + +  state->getLineDash(&dashPattern, &dashLength, &dashStart); +  if (dashLength > 20) { +    dashLength = 20; +  } +  for (i = 0; i < dashLength; ++i) { +    dash[i] = (SplashCoord)dashPattern[i]; +    if (dash[i] < 0) { +      dash[i] = 0; +    } +  } +  splash->setLineDash(dash, dashLength, (SplashCoord)dashStart); +} + +void SplashOutputDev::updateFlatness(GfxState *state) { +#if 0 // Acrobat ignores the flatness setting, and always renders curves +      // with a fairly small flatness value +  splash->setFlatness(state->getFlatness()); +#endif +} + +void SplashOutputDev::updateLineJoin(GfxState *state) { +  splash->setLineJoin(state->getLineJoin()); +} + +void SplashOutputDev::updateLineCap(GfxState *state) { +  splash->setLineCap(state->getLineCap()); +} + +void SplashOutputDev::updateMiterLimit(GfxState *state) { +  splash->setMiterLimit(state->getMiterLimit()); +} + +void SplashOutputDev::updateLineWidth(GfxState *state) { +  splash->setLineWidth(state->getLineWidth()); +} + +void SplashOutputDev::updateStrokeAdjust(GfxState *state) { +#if 0 // the SA parameter supposedly defaults to false, but Acrobat +      // apparently hardwires it to true +  if (state->getStrokeAdjust()) { +    if (globalParams->getStrokeAdjustMode() == strokeAdjustCAD) { +      splash->setStrokeAdjust(splashStrokeAdjustCAD); +    } else { +      splash->setStrokeAdjust(splashStrokeAdjustNormal); +    } +  } else { +    splash->setStrokeAdjust(splashStrokeAdjustOff); +  } +#endif +} + + +void SplashOutputDev::updateFillColor(GfxState *state) { +  GfxGray gray; +  GfxRGB rgb; +#if SPLASH_CMYK +  GfxCMYK cmyk; +#endif + +  switch (colorMode) { +  case splashModeMono1: +  case splashModeMono8: +    state->getFillGray(&gray); +    splash->setFillPattern(getColor(gray)); +    break; +  case splashModeRGB8: +  case splashModeBGR8: +    state->getFillRGB(&rgb); +    splash->setFillPattern(getColor(&rgb)); +    break; +#if SPLASH_CMYK +  case splashModeCMYK8: +    state->getFillCMYK(&cmyk); +    splash->setFillPattern(getColor(&cmyk)); +    break; +#endif +  } +} + +void SplashOutputDev::updateStrokeColor(GfxState *state) { +  GfxGray gray; +  GfxRGB rgb; +#if SPLASH_CMYK +  GfxCMYK cmyk; +#endif + +  switch (colorMode) { +  case splashModeMono1: +  case splashModeMono8: +    state->getStrokeGray(&gray); +    splash->setStrokePattern(getColor(gray)); +    break; +  case splashModeRGB8: +  case splashModeBGR8: +    state->getStrokeRGB(&rgb); +    splash->setStrokePattern(getColor(&rgb)); +    break; +#if SPLASH_CMYK +  case splashModeCMYK8: +    state->getStrokeCMYK(&cmyk); +    splash->setStrokePattern(getColor(&cmyk)); +    break; +#endif +  } +} + + +SplashPattern *SplashOutputDev::getColor(GfxGray gray) { +  SplashColor color; + +  getColor(gray, color); +  return new SplashSolidColor(color); +} + +SplashPattern *SplashOutputDev::getColor(GfxRGB *rgb) { +  SplashColor color; + +  getColor(rgb, color); +  return new SplashSolidColor(color); +} + +#if SPLASH_CMYK +SplashPattern *SplashOutputDev::getColor(GfxCMYK *cmyk) { +  SplashColor color; + +  getColor(cmyk, color); +  return new SplashSolidColor(color); +} +#endif + + +void SplashOutputDev::getColor(GfxGray gray, SplashColorPtr color) { +  if (reverseVideo) { +    gray = gfxColorComp1 - gray; +  } +  color[0] = colToByte(gray); +} + +void SplashOutputDev::getColor(GfxRGB *rgb, SplashColorPtr color) { +  GfxColorComp r, g, b; + +  if (reverseVideo) { +    r = gfxColorComp1 - rgb->r; +    g = gfxColorComp1 - rgb->g; +    b = gfxColorComp1 - rgb->b; +  } else { +    r = rgb->r; +    g = rgb->g; +    b = rgb->b; +  } +  color[0] = colToByte(r); +  color[1] = colToByte(g); +  color[2] = colToByte(b); +} + +#if SPLASH_CMYK +void SplashOutputDev::getColor(GfxCMYK *cmyk, SplashColorPtr color) { +  color[0] = colToByte(cmyk->c); +  color[1] = colToByte(cmyk->m); +  color[2] = colToByte(cmyk->y); +  color[3] = colToByte(cmyk->k); +} +#endif + + + +void SplashOutputDev::setOverprintMask(GfxState *state, +				       GfxColorSpace *colorSpace, +				       GBool overprintFlag, +				       int overprintMode, +				       GfxColor *singleColor) { +#if SPLASH_CMYK +  Guint mask; +  GfxCMYK cmyk; + +  if (overprintFlag && globalParams->getOverprintPreview()) { +    mask = colorSpace->getOverprintMask(); +    // The OPM (overprintMode) setting is only relevant when the color +    // space is DeviceCMYK or is "implicitly converted to DeviceCMYK". +    // Per the PDF spec, this happens with ICCBased color spaces only +    // if the profile matches the output device. +    if (singleColor && overprintMode && +	colorSpace->getMode() == csDeviceCMYK) { +      colorSpace->getCMYK(singleColor, &cmyk, state->getRenderingIntent()); +      if (cmyk.c == 0) { +	mask &= ~1; +      } +      if (cmyk.m == 0) { +	mask &= ~2; +      } +      if (cmyk.y == 0) { +	mask &= ~4; +      } +      if (cmyk.k == 0) { +	mask &= ~8; +      } +    } +  } else { +    mask = 0xffffffff; +  } +  splash->setOverprintMask(mask); +#endif + +} + +void SplashOutputDev::updateBlendMode(GfxState *state) { +  splash->setBlendFunc(splashOutBlendFuncs[state->getBlendMode()]); +} + +void SplashOutputDev::updateFillOpacity(GfxState *state) { +  splash->setFillAlpha((SplashCoord)state->getFillOpacity()); +} + +void SplashOutputDev::updateStrokeOpacity(GfxState *state) { +  splash->setStrokeAlpha((SplashCoord)state->getStrokeOpacity()); +} + +void SplashOutputDev::updateRenderingIntent(GfxState *state) { +  updateFillColor(state); +  updateStrokeColor(state); +} + +void SplashOutputDev::updateTransfer(GfxState *state) { +  Function **transfer; +  Guchar red[256], green[256], blue[256], gray[256]; +  double x, y; +  int i; + +  transfer = state->getTransfer(); +  if (transfer[0] && +      transfer[0]->getInputSize() == 1 && +      transfer[0]->getOutputSize() == 1) { +    if (transfer[1] && +	transfer[1]->getInputSize() == 1 && +	transfer[1]->getOutputSize() == 1 && +	transfer[2] && +	transfer[2]->getInputSize() == 1 && +	transfer[2]->getOutputSize() == 1 && +	transfer[3] && +	transfer[3]->getInputSize() == 1 && +	transfer[3]->getOutputSize() == 1) { +      for (i = 0; i < 256; ++i) { +	x = i / 255.0; +	transfer[0]->transform(&x, &y); +	red[i] = (Guchar)(y * 255.0 + 0.5); +	transfer[1]->transform(&x, &y); +	green[i] = (Guchar)(y * 255.0 + 0.5); +	transfer[2]->transform(&x, &y); +	blue[i] = (Guchar)(y * 255.0 + 0.5); +	transfer[3]->transform(&x, &y); +	gray[i] = (Guchar)(y * 255.0 + 0.5); +      } +    } else { +      for (i = 0; i < 256; ++i) { +	x = i / 255.0; +	transfer[0]->transform(&x, &y); +	red[i] = green[i] = blue[i] = gray[i] = (Guchar)(y * 255.0 + 0.5); +      } +    } +  } else { +    for (i = 0; i < 256; ++i) { +      red[i] = green[i] = blue[i] = gray[i] = (Guchar)i; +    } +  } +  splash->setTransfer(red, green, blue, gray); +} + +void SplashOutputDev::updateFont(GfxState *state) { +  needFontUpdate = gTrue; +} + +void SplashOutputDev::doUpdateFont(GfxState *state) { +  GfxFont *gfxFont; +  GfxFontLoc *fontLoc; +  GfxFontType fontType; +  SplashOutFontFileID *id; +  SplashFontFile *fontFile; +  int fontNum; +  FoFiTrueType *ff; +  FoFiType1C *ffT1C; +  Ref embRef; +  Object refObj, strObj; +#if LOAD_FONTS_FROM_MEM +  GString *fontBuf; +  FILE *extFontFile; +#else +  GString *tmpFileName, *fileName; +  FILE *tmpFile; +#endif +  char blk[4096]; +  int *codeToGID; +  CharCodeToUnicode *ctu; +  double *textMat; +  double m11, m12, m21, m22, fontSize, oblique; +  double fsx, fsy, w, fontScaleMin, fontScaleAvg, fontScale; +  Gushort ww; +  SplashCoord mat[4]; +  char *name, *start; +  Unicode uBuf[8]; +  int substIdx, n, code, cmap, cmapPlatform, cmapEncoding, length, i; + +  needFontUpdate = gFalse; +  font = NULL; +#if LOAD_FONTS_FROM_MEM +  fontBuf = NULL; +#else +  tmpFileName = NULL; +  fileName = NULL; +#endif +  substIdx = -1; + +  if (!(gfxFont = state->getFont())) { +    goto err1; +  } +  fontType = gfxFont->getType(); +  if (fontType == fontType3) { +    goto err1; +  } + +  // sanity-check the font size: skip anything larger than 10k x 10k, +  // to avoid problems allocating a bitmap (note that code in +  // SplashFont disables caching at a smaller size than this) +  state->textTransformDelta(state->getFontSize(), state->getFontSize(), +			    &fsx, &fsy); +  state->transformDelta(fsx, fsy, &fsx, &fsy); +  if (fabs(fsx) > 20000 || fabs(fsy) > 20000) { +    goto err1; +  } + +  // check the font file cache +  id = new SplashOutFontFileID(gfxFont->getID()); +  if (fontEngine->checkForBadFontFile(id)) { +    goto err2; +  } +  if ((fontFile = fontEngine->getFontFile(id))) { +    delete id; + +  } else { + +    fontNum = 0; + +    if (!(fontLoc = gfxFont->locateFont(xref, gFalse))) { +      error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'", +	    gfxFont->getName() ? gfxFont->getName()->getCString() +	                       : "(unnamed)"); +      goto err2; +    } + +    // embedded font +    if (fontLoc->locType == gfxFontLocEmbedded) { +      gfxFont->getEmbeddedFontID(&embRef); +#if LOAD_FONTS_FROM_MEM +      fontBuf = new GString(); +      refObj.initRef(embRef.num, embRef.gen); +      refObj.fetch(xref, &strObj); +      refObj.free(); +      if (!strObj.isStream()) { +	error(errSyntaxError, -1, "Embedded font object is wrong type"); +	strObj.free(); +	delete fontLoc; +	goto err2; +      } +      strObj.streamReset(); +      while ((n = strObj.streamGetBlock(blk, sizeof(blk))) > 0) { +	fontBuf->append(blk, n); +      } +      strObj.streamClose(); +      strObj.free(); +#else +      if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) { +	error(errIO, -1, "Couldn't create temporary font file"); +	delete fontLoc; +	goto err2; +      } +      refObj.initRef(embRef.num, embRef.gen); +      refObj.fetch(xref, &strObj); +      refObj.free(); +      if (!strObj.isStream()) { +	error(errSyntaxError, -1, "Embedded font object is wrong type"); +	strObj.free(); +	fclose(tmpFile); +	delete fontLoc; +	goto err2; +      } +      strObj.streamReset(); +      while ((n = strObj.streamGetBlock(blk, sizeof(blk))) > 0) { +	fwrite(blk, 1, n, tmpFile); +      } +      strObj.streamClose(); +      strObj.free(); +      fclose(tmpFile); +      fileName = tmpFileName; +#endif + +    // external font +    } else { // gfxFontLocExternal +#if LOAD_FONTS_FROM_MEM +      if (!(extFontFile = fopen(fontLoc->path->getCString(), "rb"))) { +	error(errSyntaxError, -1, "Couldn't open external font file '{0:t}'", +	      fontLoc->path); +	delete fontLoc; +	goto err2; +      } +      fontBuf = new GString(); +      while ((n = fread(blk, 1, sizeof(blk), extFontFile)) > 0) { +	fontBuf->append(blk, n); +      } +      fclose(extFontFile); +#else +      fileName = fontLoc->path; +#endif +      fontNum = fontLoc->fontNum; +      if (fontLoc->substIdx >= 0) { +	id->setSubstIdx(fontLoc->substIdx); +      } +      if (fontLoc->oblique != 0) { +	id->setOblique(fontLoc->oblique); +      } +    } + +    // load the font file +    switch (fontLoc->fontType) { +    case fontType1: +      if (!(fontFile = fontEngine->loadType1Font( +		   id, +#if LOAD_FONTS_FROM_MEM +		   fontBuf, +#else +		   fileName->getCString(), +		   fileName == tmpFileName, +#endif +		   (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { +	error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", +	      gfxFont->getName() ? gfxFont->getName()->getCString() +	                         : "(unnamed)"); +	delete fontLoc; +	goto err1; +      } +      break; +    case fontType1C: +#if LOAD_FONTS_FROM_MEM +      if ((ffT1C = FoFiType1C::make(fontBuf->getCString(), +				    fontBuf->getLength()))) { +#else +      if ((ffT1C = FoFiType1C::load(fileName->getCString()))) { +#endif +	codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ffT1C); +	delete ffT1C; +      } else { +	codeToGID = NULL; +      } +      if (!(fontFile = fontEngine->loadType1CFont( +		   id, +#if LOAD_FONTS_FROM_MEM +		   fontBuf, +#else +		   fileName->getCString(), +		   fileName == tmpFileName, +#endif +		   codeToGID, +		   (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { +	error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", +	      gfxFont->getName() ? gfxFont->getName()->getCString() +	                         : "(unnamed)"); +	delete fontLoc; +	goto err1; +      } +      break; +    case fontType1COT: +      codeToGID = NULL; +#if LOAD_FONTS_FROM_MEM +      if ((ff = FoFiTrueType::make(fontBuf->getCString(), fontBuf->getLength(), +				   fontNum, gTrue))) { +#else +	if ((ff = FoFiTrueType::load(fileName->getCString(), +				     fontNum, gTrue))) { +#endif +	if (ff->getCFFBlock(&start, &length) && +	    (ffT1C = FoFiType1C::make(start, length))) { +	  codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ffT1C); +	  delete ffT1C; +	} +	delete ff; +      } +      if (!(fontFile = fontEngine->loadOpenTypeT1CFont( +		   id, +#if LOAD_FONTS_FROM_MEM +		   fontBuf, +#else +		   fileName->getCString(), +		   fileName == tmpFileName, +#endif +		   codeToGID, +		   (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { +	error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", +	      gfxFont->getName() ? gfxFont->getName()->getCString() +	                         : "(unnamed)"); +	delete fontLoc; +	goto err1; +      } +      break; +    case fontTrueType: +    case fontTrueTypeOT: +#if LOAD_FONTS_FROM_MEM +      if ((ff = FoFiTrueType::make(fontBuf->getCString(), fontBuf->getLength(), +				   fontNum))) { +#else +      if ((ff = FoFiTrueType::load(fileName->getCString(), fontNum))) { +#endif +	codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff); +	n = 256; +	delete ff; +	// if we're substituting for a non-TrueType font, we need to mark +	// all notdef codes as "do not draw" (rather than drawing TrueType +	// notdef glyphs) +	if (gfxFont->getType() != fontTrueType && +	    gfxFont->getType() != fontTrueTypeOT) { +	  for (i = 0; i < 256; ++i) { +	    if (codeToGID[i] == 0) { +	      codeToGID[i] = -1; +	    } +	  } +	} +      } else { +	codeToGID = NULL; +	n = 0; +      } +      if (!(fontFile = fontEngine->loadTrueTypeFont( +			   id, +#if LOAD_FONTS_FROM_MEM +			   fontBuf, +#else +			   fileName->getCString(), +			   fileName == tmpFileName, +#endif +			   fontNum, codeToGID, n, +			   gfxFont->getEmbeddedFontName() +			     ? gfxFont->getEmbeddedFontName()->getCString() +			     : (char *)NULL))) { +	error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", +	      gfxFont->getName() ? gfxFont->getName()->getCString() +	                         : "(unnamed)"); +	delete fontLoc; +	goto err1; +      } +      break; +    case fontCIDType0: +    case fontCIDType0C: +      if (((GfxCIDFont *)gfxFont)->getCIDToGID()) { +	n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); +	codeToGID = (int *)gmallocn(n, sizeof(int)); +	memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), +	       n * sizeof(int)); +      } else { +	codeToGID = NULL; +	n = 0; +      } +      if (!(fontFile = fontEngine->loadCIDFont( +			   id, +#if LOAD_FONTS_FROM_MEM +			   fontBuf, +#else +			   fileName->getCString(), +			   fileName == tmpFileName, +#endif +			   codeToGID, n))) { + +	error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", +	      gfxFont->getName() ? gfxFont->getName()->getCString() +	                         : "(unnamed)"); +	delete fontLoc; +	goto err1; +      } +      break; +    case fontCIDType0COT: +      codeToGID = NULL; +      n = 0; +      if (fontLoc->locType == gfxFontLocEmbedded) { +	if (((GfxCIDFont *)gfxFont)->getCIDToGID()) { +	  n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); +	  codeToGID = (int *)gmallocn(n, sizeof(int)); +	  memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), +		 n * sizeof(int)); +	} +      } else if (globalParams->getMapExtTrueTypeFontsViaUnicode()) { +	// create a CID-to-GID mapping, via Unicode +	if ((ctu = ((GfxCIDFont *)gfxFont)->getToUnicode())) { +#if LOAD_FONTS_FROM_MEM +	  if ((ff = FoFiTrueType::make(fontBuf->getCString(), +				       fontBuf->getLength(), fontNum))) { +#else +	  if ((ff = FoFiTrueType::load(fileName->getCString(), fontNum))) { +#endif +	    // look for a Unicode cmap +	    for (cmap = 0; cmap < ff->getNumCmaps(); ++cmap) { +	      cmapPlatform = ff->getCmapPlatform(cmap); +	      cmapEncoding = ff->getCmapEncoding(cmap); +	      if ((cmapPlatform == 3 && cmapEncoding == 1) || +		  (cmapPlatform == 0 && cmapEncoding <= 4)) { +		break; +	      } +	    } +	    if (cmap < ff->getNumCmaps()) { +	      // map CID -> Unicode -> GID +	      if (ctu->isIdentity()) { +		n = 65536; +	      } else { +		n = ctu->getLength(); +	      } +	      codeToGID = (int *)gmallocn(n, sizeof(int)); +	      for (code = 0; code < n; ++code) { +		if (ctu->mapToUnicode(code, uBuf, 8) > 0) { +		  codeToGID[code] = ff->mapCodeToGID(cmap, uBuf[0]); +		} else { +		  codeToGID[code] = -1; +		} +	      } +	    } +	    delete ff; +	  } +	  ctu->decRefCnt(); +	} else { +	  error(errSyntaxError, -1, +		"Couldn't find a mapping to Unicode for font '{0:s}'", +		gfxFont->getName() ? gfxFont->getName()->getCString() +		                   : "(unnamed)"); +	} +      } +      if (!(fontFile = fontEngine->loadOpenTypeCFFFont( +			   id, +#if LOAD_FONTS_FROM_MEM +			   fontBuf, +#else +			   fileName->getCString(), +			   fileName == tmpFileName, +#endif +			   codeToGID, n))) { +	error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", +	      gfxFont->getName() ? gfxFont->getName()->getCString() +	                         : "(unnamed)"); +	delete fontLoc; +	goto err1; +      } +      break; +    case fontCIDType2: +    case fontCIDType2OT: +      codeToGID = NULL; +      n = 0; +      if (fontLoc->locType == gfxFontLocEmbedded) { +	if (((GfxCIDFont *)gfxFont)->getCIDToGID()) { +	  n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); +	  codeToGID = (int *)gmallocn(n, sizeof(int)); +	  memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), +		 n * sizeof(int)); +	} +      } else if (globalParams->getMapExtTrueTypeFontsViaUnicode() && +		 !((GfxCIDFont *)gfxFont)->usesIdentityEncoding()) { +	// create a CID-to-GID mapping, via Unicode +	if ((ctu = ((GfxCIDFont *)gfxFont)->getToUnicode())) { +#if LOAD_FONTS_FROM_MEM +	  if ((ff = FoFiTrueType::make(fontBuf->getCString(), +				       fontBuf->getLength(), fontNum))) { +#else +	  if ((ff = FoFiTrueType::load(fileName->getCString(), fontNum))) { +#endif +	    // look for a Unicode cmap +	    for (cmap = 0; cmap < ff->getNumCmaps(); ++cmap) { +	      cmapPlatform = ff->getCmapPlatform(cmap); +	      cmapEncoding = ff->getCmapEncoding(cmap); +	      if ((cmapPlatform == 3 && cmapEncoding == 1) || +		  (cmapPlatform == 0 && cmapEncoding <= 4)) { +		break; +	      } +	    } +	    if (cmap < ff->getNumCmaps()) { +	      // map CID -> Unicode -> GID +	      if (ctu->isIdentity()) { +		n = 65536; +	      } else { +		n = ctu->getLength(); +	      } +	      codeToGID = (int *)gmallocn(n, sizeof(int)); +	      for (code = 0; code < n; ++code) { +		if (ctu->mapToUnicode(code, uBuf, 8) > 0) { +		  codeToGID[code] = ff->mapCodeToGID(cmap, uBuf[0]); +		} else { +		  codeToGID[code] = -1; +		} +	      } +	    } +	    delete ff; +	  } +	  ctu->decRefCnt(); +	} else { +	  error(errSyntaxError, -1, +		"Couldn't find a mapping to Unicode for font '{0:s}'", +		gfxFont->getName() ? gfxFont->getName()->getCString() +		                   : "(unnamed)"); +	} +      } +      if (!(fontFile = fontEngine->loadTrueTypeFont( +			   id, +#if LOAD_FONTS_FROM_MEM +			   fontBuf, +#else +			   fileName->getCString(), +			   fileName == tmpFileName, +#endif +			   fontNum, codeToGID, n, +			   gfxFont->getEmbeddedFontName() +			     ? gfxFont->getEmbeddedFontName()->getCString() +			     : (char *)NULL))) { +	error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", +	      gfxFont->getName() ? gfxFont->getName()->getCString() +	                         : "(unnamed)"); +	delete fontLoc; +	goto err1; +      } +      break; +    default: +      // this shouldn't happen +      delete fontLoc; +      goto err2; +    } + +    delete fontLoc; +  } + +  // get the font matrix +  textMat = state->getTextMat(); +  fontSize = state->getFontSize(); +  oblique = ((SplashOutFontFileID *)fontFile->getID())->getOblique(); +  m11 = state->getHorizScaling() * textMat[0]; +  m12 = state->getHorizScaling() * textMat[1]; +  m21 = oblique * m11 + textMat[2]; +  m22 = oblique * m12 + textMat[3]; +  m11 *= fontSize; +  m12 *= fontSize; +  m21 *= fontSize; +  m22 *= fontSize; + +  // for substituted fonts: adjust the font matrix -- compare the +  // widths of letters and digits (A-Z, a-z, 0-9) in the original font +  // and the substituted font +  substIdx = ((SplashOutFontFileID *)fontFile->getID())->getSubstIdx(); +  if (substIdx >= 0 && substIdx < 12) { +    fontScaleMin = 1; +    fontScaleAvg = 0; +    n = 0; +    for (code = 0; code < 256; ++code) { +      if ((name = ((Gfx8BitFont *)gfxFont)->getCharName(code)) && +	  name[0] && !name[1] && +	  ((name[0] >= 'A' && name[0] <= 'Z') || +	   (name[0] >= 'a' && name[0] <= 'z') || +	   (name[0] >= '0' && name[0] <= '9'))) { +	w = ((Gfx8BitFont *)gfxFont)->getWidth((Guchar)code); +	if (builtinFontSubst[substIdx]->widths->getWidth(name, &ww) && +	    w > 0.01 && ww > 10) { +	  w /= ww * 0.001; +	  if (w < fontScaleMin) { +	    fontScaleMin = w; +	  } +	  fontScaleAvg += w; +	  ++n; +	} +      } +    } +    // if real font is narrower than substituted font, reduce the font +    // size accordingly -- this currently uses a scale factor halfway +    // between the minimum and average computed scale factors, which +    // is a bit of a kludge, but seems to produce mostly decent +    // results +    if (n) { +      fontScaleAvg /= n; +      if (fontScaleAvg < 1) { +	fontScale = 0.5 * (fontScaleMin + fontScaleAvg); +	m11 *= fontScale; +	m12 *= fontScale; +      } +    } +  } + +  // create the scaled font +  mat[0] = m11;  mat[1] = m12; +  mat[2] = m21;  mat[3] = m22; +  font = fontEngine->getFont(fontFile, mat, splash->getMatrix()); + +#if !LOAD_FONTS_FROM_MEM +  if (tmpFileName) { +    delete tmpFileName; +  } +#endif +  return; + + err2: +  delete id; + err1: +#if LOAD_FONTS_FROM_MEM +  if (fontBuf) { +    delete fontBuf; +  } +#else +  if (tmpFileName) { +    unlink(tmpFileName->getCString()); +    delete tmpFileName; +  } +#endif +  return; +} + +void SplashOutputDev::stroke(GfxState *state) { +  SplashPath *path; + +  if (state->getStrokeColorSpace()->isNonMarking()) { +    return; +  } +  setOverprintMask(state, state->getStrokeColorSpace(), +		   state->getStrokeOverprint(), state->getOverprintMode(), +		   state->getStrokeColor()); +  path = convertPath(state, state->getPath(), gFalse); +  splash->stroke(path); +  delete path; +} + +void SplashOutputDev::fill(GfxState *state) { +  SplashPath *path; + +  if (state->getFillColorSpace()->isNonMarking()) { +    return; +  } +  setOverprintMask(state, state->getFillColorSpace(), +		   state->getFillOverprint(), state->getOverprintMode(), +		   state->getFillColor()); +  path = convertPath(state, state->getPath(), gTrue); +  splash->fill(path, gFalse); +  delete path; +} + +void SplashOutputDev::eoFill(GfxState *state) { +  SplashPath *path; + +  if (state->getFillColorSpace()->isNonMarking()) { +    return; +  } +  setOverprintMask(state, state->getFillColorSpace(), +		   state->getFillOverprint(), state->getOverprintMode(), +		   state->getFillColor()); +  path = convertPath(state, state->getPath(), gTrue); +  splash->fill(path, gTrue); +  delete path; +} + +void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, +					Object *strRef, +					int paintType, int tilingType, +					Dict *resDict, +					double *mat, double *bbox, +					int x0, int y0, int x1, int y1, +					double xStep, double yStep) { +  SplashBitmap *origBitmap, *tileBitmap; +  Splash *origSplash; +  SplashColor color; +  Guint *overprintMaskBitmap; +  double *ctm; +  double ictm[6], tileMat[6], mat1[6], mat2[6]; +  double tileXMin, tileYMin, tileXMax, tileYMax; +  double xStepX, xStepY, yStepX, yStepY; +  double adjXMin, adjYMin; +  double sx, sy; +  double clipXMin, clipYMin, clipXMax, clipYMax, clipXC, clipYC; +  double tx, ty, idet, txMin, tyMin, txMax, tyMax; +  int tileW, tileH, tileSize; +  int ixMin, ixMax, iyMin, iyMax, ix, iy, x, y; +  int i; + +  // Notes: +  // - PTM = pattern matrix = transform from pattern space to default +  //         user space (default for most recent page or form) +  // - BTM = transform from default user space to device space +  // +  // This function is called with: +  // - mat = PTM * BTM * iCTM = transform from pattern space to +  //         current user space + +  // transform the four corners of the pattern bbox from pattern space +  // to device space and compute the device space bbox +  state->transform(bbox[0] * mat[0] + bbox[1] * mat[2] + mat[4], +		   bbox[0] * mat[1] + bbox[1] * mat[3] + mat[5], +		   &tx, &ty); +  tileXMin = tileXMax = tx; +  tileYMin = tileYMax = ty; +  state->transform(bbox[2] * mat[0] + bbox[1] * mat[2] + mat[4], +		   bbox[2] * mat[1] + bbox[1] * mat[3] + mat[5], +		   &tx, &ty); +  if (tx < tileXMin) { +    tileXMin = tx; +  } else if (tx > tileXMax) { +    tileXMax = tx; +  } +  if (ty < tileYMin) { +    tileYMin = ty; +  } else if (ty > tileYMax) { +    tileYMax = ty; +  } +  state->transform(bbox[2] * mat[0] + bbox[3] * mat[2] + mat[4], +		   bbox[2] * mat[1] + bbox[3] * mat[3] + mat[5], +		   &tx, &ty); +  if (tx < tileXMin) { +    tileXMin = tx; +  } else if (tx > tileXMax) { +    tileXMax = tx; +  } +  if (ty < tileYMin) { +    tileYMin = ty; +  } else if (ty > tileYMax) { +    tileYMax = ty; +  } +  state->transform(bbox[0] * mat[0] + bbox[3] * mat[2] + mat[4], +		   bbox[0] * mat[1] + bbox[3] * mat[3] + mat[5], +		   &tx, &ty); +  if (tx < tileXMin) { +    tileXMin = tx; +  } else if (tx > tileXMax) { +    tileXMax = tx; +  } +  if (ty < tileYMin) { +    tileYMin = ty; +  } else if (ty > tileYMax) { +    tileYMax = ty; +  } +  if (tileXMin == tileXMax || tileYMin == tileYMax) { +    return; +  } +  tileW = (int)(tileXMax - tileXMin + 0.5); +  tileH = (int)(tileYMax - tileYMin + 0.5); +  if (tileW < 1) { +    tileW = 1; +  } +  if (tileH < 1) { +    tileH = 1; +  } + +  // check for an excessively large tile size +  tileSize = tileW * tileH; +  if (tileXMax - tileXMin + 0.5 > (double)INT_MAX || +      tileYMax - tileYMin + 0.5 > (double)INT_MAX || +      tileW > INT_MAX / tileH || +      tileSize > maxTileSize) { +    mat1[0] = mat[0]; +    mat1[1] = mat[1]; +    mat1[2] = mat[2]; +    mat1[3] = mat[3]; +    for (iy = y0; iy < y1; ++iy) { +      for (ix = x0; ix < x1; ++ix) { +	tx = ix * xStep; +	ty = iy * yStep; +	mat1[4] = tx * mat[0] + ty * mat[2] + mat[4]; +	mat1[5] = tx * mat[1] + ty * mat[3] + mat[5]; +	gfx->drawForm(strRef, resDict, mat1, bbox); +      } +    } +    return; +  } + +  // transform XStep and YStep to device space +  state->transformDelta(xStep * mat[0], xStep * mat[1], &xStepX, &xStepY); +  state->transformDelta(yStep * mat[2], yStep * mat[3], &yStepX, &yStepY); + +  // get the clipping bbox (in device space) +  state->getClipBBox(&clipXMin, &clipYMin, &clipXMax, &clipYMax); + +  // compute tiling parameters +  idet = xStepX * yStepY - yStepX * xStepY; +  if (tilingType == 2 || idet == 0) { +    adjXMin = tileXMin; +    adjYMin = tileYMin; +    sx = 1; +    sy = 1; +  } else { +    // reposition the pattern origin to the center of the clipping bbox +    idet = 1 / idet; +    clipXC = 0.5 * (clipXMin + clipXMax); +    clipYC = 0.5 * (clipYMin + clipYMax); +    ix = (int)floor((yStepX * (tileYMin - clipYC) +		     - (tileXMin - clipXC) * yStepY) * idet + 0.5); +    iy = (int)floor((xStepX * (clipYC - tileYMin) +		     - (clipXC - tileXMin) * xStepY) * idet + 0.5); +    adjXMin = (int)floor(tileXMin + ix * xStepX + iy * yStepX + 0.5); +    adjYMin = (int)floor(tileYMin + ix * xStepY + iy * yStepY + 0.5); +    sx = tileW / (tileXMax - tileXMin); +    sy = tileH / (tileYMax - tileYMin); +    xStepX = (int)floor(sx * xStepX + 0.5); +    xStepY = (int)floor(sy * xStepY + 0.5); +    yStepX = (int)floor(sx * yStepX + 0.5); +    yStepY = (int)floor(sy * yStepY + 0.5); +  } + +  // compute tiling range: +  // - look at the four corners of the clipping bbox +  // - solve for the (ix,iy) tile position at each corner +  // - take the min and max values for ix, iy +  idet = xStepX * yStepY - xStepY * yStepX; +  if (idet == 0) { +    return; +  } +  idet = 1 / idet; +  // LL corner +  tx = idet * (yStepY * (clipXMin - tileW - 1 - adjXMin) +	       - yStepX * (clipYMax + 1 - adjYMin)); +  ty = idet * (xStepX * (clipYMax + 1 - adjYMin) +	       - xStepY * (clipXMin - tileW - 1 - adjXMin)); +  txMin = txMax = tx; +  tyMin = tyMax = ty; +  // LR corner +  tx = idet * (yStepY * (clipXMax + 1 - adjXMin) +	       - yStepX * (clipYMax + 1 - adjYMin)); +  ty = idet * (xStepX * (clipYMax + 1 - adjYMin) +	       - xStepY * (clipXMax + 1 - adjXMin)); +  if (tx < txMin) { +    txMin = tx; +  } else if (tx > txMax) { +    txMax = tx; +  } +  if (ty < tyMin) { +    tyMin = ty; +  } else if (ty > tyMax) { +    tyMax = ty; +  } +  // UL corner +  tx = idet * (yStepY * (clipXMin - tileW - 1 - adjXMin) +	       - yStepX * (clipYMin - tileH - 1 - adjYMin)); +  ty = idet * (xStepX * (clipYMin - tileH - 1 - adjYMin) +	       - xStepY * (clipXMin - tileW - 1 - adjXMin)); +  if (tx < txMin) { +    txMin = tx; +  } else if (tx > txMax) { +    txMax = tx; +  } +  if (ty < tyMin) { +    tyMin = ty; +  } else if (ty > tyMax) { +    tyMax = ty; +  } +  // UR corner +  tx = idet * (yStepY * (clipXMax + 1 - adjXMin) +	       - yStepX * (clipYMin - tileH - 1 - adjYMin)); +  ty = idet * (xStepX * (clipYMin - tileH - 1 - adjYMin) +	       - xStepY * (clipXMax + 1 - adjXMin)); +  if (tx < txMin) { +    txMin = tx; +  } else if (tx > txMax) { +    txMax = tx; +  } +  if (ty < tyMin) { +    tyMin = ty; +  } else if (ty > tyMax) { +    tyMax = ty; +  } +  ixMin = (int)ceil(txMin); +  ixMax = (int)floor(txMax) + 1; +  iyMin = (int)ceil(tyMin); +  iyMax = (int)floor(tyMax) + 1; + +  // special case: pattern tile is larger than clipping bbox +  if (ixMax - ixMin == 1 && iyMax - iyMin == 1) { +    // reduce the tile size to just the clipping bbox -- this improves +    // performance in cases where just a small portion of one tile is +    // needed +    tileW = (int)(clipXMax - clipXMin + 0.5); +    tileH = (int)(clipYMax - clipYMin + 0.5); +    if (tileW < 1) { +      tileW = 1; +    } +    if (tileH < 1) { +      tileH = 1; +    } +    tileXMin += clipXMin - (adjXMin + ixMin * xStepX + iyMin * yStepX); +    tileYMin += clipYMin - (adjYMin + ixMin * xStepY + iyMin * yStepY); +    ixMin = 0; +    iyMin = 0; +    ixMax = 1; +    iyMax = 1; +    adjXMin = clipXMin; +    adjYMin = clipYMin; +  } + +  // compute tile matrix = PTM * BTM * Mtranslate * Mscale * iCTM +  //                     = mat * CTM * Mtranslate * Mscale * iCTM +  ctm = state->getCTM(); +  idet = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); +  ictm[0] = ctm[3] * idet; +  ictm[1] = -ctm[1] * idet; +  ictm[2] = -ctm[2] * idet; +  ictm[3] = ctm[0] * idet; +  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * idet; +  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * idet; +  // mat * CTM +  mat1[0] = mat[0] * ctm[0] + mat[1] * ctm[2]; +  mat1[1] = mat[0] * ctm[1] + mat[1] * ctm[3]; +  mat1[2] = mat[2] * ctm[0] + mat[3] * ctm[2]; +  mat1[3] = mat[2] * ctm[1] + mat[3] * ctm[3]; +  mat1[4] = mat[4] * ctm[0] + mat[5] * ctm[2] + ctm[4]; +  mat1[5] = mat[4] * ctm[1] + mat[5] * ctm[3] + ctm[5]; +  // mat * CTM * (Mtranslate * Mscale) +  mat2[0] = mat1[0] * sx; +  mat2[1] = mat1[1] * sy; +  mat2[2] = mat1[2] * sx; +  mat2[3] = mat1[3] * sy; +  mat2[4] = mat1[4] * sx - sx * tileXMin; +  mat2[5] = mat1[5] * sy - sy * tileYMin; +  // mat * CTM * (Mtranslate * Mscale) * iCTM +  tileMat[0] = mat2[0] * ictm[0] + mat2[1] * ictm[2]; +  tileMat[1] = mat2[0] * ictm[1] + mat2[1] * ictm[3]; +  tileMat[2] = mat2[2] * ictm[0] + mat2[3] * ictm[2]; +  tileMat[3] = mat2[2] * ictm[1] + mat2[3] * ictm[3]; +  tileMat[4] = mat2[4] * ictm[0] + mat2[5] * ictm[2] + ictm[4]; +  tileMat[5] = mat2[4] * ictm[1] + mat2[5] * ictm[3] + ictm[5]; + +  // create a temporary bitmap +  origBitmap = bitmap; +  origSplash = splash; +  traceMessage("tiling pattern bitmap"); +  bitmap = tileBitmap = new SplashBitmap(tileW, tileH, bitmapRowPad, +					 colorMode, gTrue, bitmapTopDown, +					 origBitmap); +  splash = new Splash(bitmap, vectorAntialias, +		      origSplash->getImageCache(), origSplash->getScreen()); +  for (i = 0; i < splashMaxColorComps; ++i) { +    color[i] = 0; +  } +  splash->clear(color); +#if SPLASH_CMYK +  // if we're doing overprint preview, we need to track the overprint +  // mask at each pixel in the tile bitmap +  if (globalParams->getOverprintPreview() && +      colorMode == splashModeCMYK8) { +    overprintMaskBitmap = (Guint *)gmallocn(tileH, tileW * (int)sizeof(Guint)); +    memset(overprintMaskBitmap, 0, tileH * tileW * sizeof(Guint)); +    splash->setOverprintMaskBitmap(overprintMaskBitmap); +  } else { +    overprintMaskBitmap = NULL; +  } +#else // SPLASH_CMYK +  overprintMaskBitmap = NULL; +#endif // SPLASH_CMYK +  splash->setMinLineWidth(globalParams->getMinLineWidth()); +  splash->setStrokeAdjust( +		 mapStrokeAdjustMode[globalParams->getStrokeAdjust()]); +  splash->setEnablePathSimplification( +		 globalParams->getEnablePathSimplification()); +  ++nestCount; + +  // copy the fill color (for uncolored tiling patterns) +  // (and stroke color, to handle buggy PDF files) +  // -- Acrobat apparently doesn't copy the full state here +  splash->setFillPattern(origSplash->getFillPattern()->copy()); +  splash->setStrokePattern(origSplash->getStrokePattern()->copy()); + +  // reset the clip rectangle +  state->resetDevClipRect(0, 0, tileW, tileH); + +  // render the tile +  gfx->drawForm(strRef, resDict, tileMat, bbox); + +  // restore the original bitmap +  --nestCount; +  delete splash; +  bitmap = origBitmap; +  splash = origSplash; +  splash->setOverprintMask(0xffffffff); + +  // draw the tiles +  if (tileW == 1 && tileH == 1 && +      fabs(xStepX * yStepY - xStepY * yStepX) < 0.9) { +    // if the tile is 1x1 pixel, and the stepping completely fills the +    // area, just composite the 1x1 image across the clip region +    // (this avoids performance problems in cases where the step size +    // is very small) (we compare to 0.9 instead of 1.0 to avoid fp +    // jitter issues) +    ixMin = (int)floor(clipXMin); +    ixMax = (int)floor(clipXMax) + 1; +    iyMin = (int)floor(clipYMin); +    iyMax = (int)floor(clipYMax) + 1; +    for (iy = iyMin; iy < iyMax; ++iy) { +      for (ix = ixMin; ix < ixMax; ++ix) { +	splash->composite(tileBitmap, 0, 0, ix, iy, tileW, tileH, +			  gFalse, gFalse); +      } +    } +  } else { +    for (iy = iyMin; iy < iyMax; ++iy) { +      for (ix = ixMin; ix < ixMax; ++ix) { +	x = (int)floor(adjXMin + ix * xStepX + iy * yStepX + 0.5); +	y = (int)floor(adjYMin + ix * xStepY + iy * yStepY + 0.5); +	if (overprintMaskBitmap) { +	  splash->compositeWithOverprint(tileBitmap, overprintMaskBitmap, +					 0, 0, x, y, tileW, tileH, +					 gFalse, gFalse); +	} else { +	  splash->composite(tileBitmap, 0, 0, x, y, tileW, tileH, +			    gFalse, gFalse); +	} +      } +    } +  } + +  gfree(overprintMaskBitmap); +  delete tileBitmap; +} + +GBool SplashOutputDev::shadedFill(GfxState *state, GfxShading *shading) { + +  // generate the bitmap +  SplashColorMode srcMode; +  if (colorMode == splashModeMono1) { +    srcMode = splashModeMono8; +  } else if (colorMode == splashModeBGR8) { +    srcMode = splashModeRGB8; +  } else { +    srcMode = colorMode; +  } +  int x, y; +  SplashBitmap *tBitmap = ShadingImage::generateBitmap(state, shading, srcMode, +						       reverseVideo, +						       splash, bitmap, &x, &y); +  if (!tBitmap) { +    // clip region is empty - nothing to draw +    return gTrue; +  } + +  // check clipping and composite the bitmap +  int xMin = x; +  int yMin = y; +  int xMax = x + tBitmap->getWidth(); +  int yMax = y + tBitmap->getHeight(); +  SplashClipResult clipRes = splash->limitRectToClipRect(&xMin, &yMin, +							 &xMax, &yMax); +  if (clipRes != splashClipAllOutside) { +    setOverprintMask(state, state->getFillColorSpace(), +		     state->getFillOverprint(), state->getOverprintMode(), +		     NULL); +    splash->composite(tBitmap, xMin - x, yMin - y, xMin, yMin, +		      xMax - xMin, yMax - yMin, +		      clipRes == splashClipAllInside, gFalse); +  } + +  delete tBitmap; + +  return gTrue; +} + +void SplashOutputDev::clip(GfxState *state) { +  SplashPath *path; + +  path = convertPath(state, state->getPath(), gTrue); +  splash->clipToPath(path, gFalse); +  delete path; +} + +void SplashOutputDev::eoClip(GfxState *state) { +  SplashPath *path; + +  path = convertPath(state, state->getPath(), gTrue); +  splash->clipToPath(path, gTrue); +  delete path; +} + +void SplashOutputDev::clipToStrokePath(GfxState *state) { +  SplashPath *path, *path2; + +  path = convertPath(state, state->getPath(), gFalse); +  path2 = splash->makeStrokePath(path, state->getLineWidth(), +				 state->getLineCap(), state->getLineJoin()); +  delete path; +  splash->clipToPath(path2, gFalse); +  delete path2; +} + +SplashPath *SplashOutputDev::convertPath(GfxState *state, GfxPath *path, +					 GBool dropEmptySubpaths) { +  SplashPath *sPath; +  GfxSubpath *subpath; +  int n, i, j; + +  n = dropEmptySubpaths ? 1 : 0; +  sPath = new SplashPath(); +  for (i = 0; i < path->getNumSubpaths(); ++i) { +    subpath = path->getSubpath(i); +    if (subpath->getNumPoints() > n) { +      sPath->moveTo((SplashCoord)subpath->getX(0), +		    (SplashCoord)subpath->getY(0)); +      j = 1; +      while (j < subpath->getNumPoints()) { +	if (subpath->getCurve(j)) { +	  sPath->curveTo((SplashCoord)subpath->getX(j), +			 (SplashCoord)subpath->getY(j), +			 (SplashCoord)subpath->getX(j+1), +			 (SplashCoord)subpath->getY(j+1), +			 (SplashCoord)subpath->getX(j+2), +			 (SplashCoord)subpath->getY(j+2)); +	  j += 3; +	} else { +	  sPath->lineTo((SplashCoord)subpath->getX(j), +			(SplashCoord)subpath->getY(j)); +	  ++j; +	} +      } +      if (subpath->isClosed()) { +	sPath->close(); +      } +    } +  } +  return sPath; +} + +void SplashOutputDev::drawChar(GfxState *state, double x, double y, +			       double dx, double dy, +			       double originX, double originY, +			       CharCode code, int nBytes, +			       Unicode *u, int uLen) { +  SplashPath *path; +  int render; +  GBool doFill, doStroke, doClip; +  SplashStrokeAdjustMode strokeAdjust; +  double m[4]; +  GBool horiz; + +  if (skipHorizText || skipRotatedText) { +    state->getFontTransMat(&m[0], &m[1], &m[2], &m[3]); +    // this matches the 'diagonal' test in TextPage::updateFont() +    horiz = m[0] > 0 && fabs(m[1]) < 0.001 && +            fabs(m[2]) < 0.001 && m[3] < 0; +    if ((skipHorizText && horiz) || (skipRotatedText && !horiz)) { +      return; +    } +  } + +  // check for invisible text -- this is used by Acrobat Capture +  render = state->getRender(); +  if (render == 3) { +    return; +  } + +  if (needFontUpdate) { +    doUpdateFont(state); +  } +  if (!font) { +    return; +  } + +  x -= originX; +  y -= originY; + +  doFill = !(render & 1) && !state->getFillColorSpace()->isNonMarking(); +  doStroke = ((render & 3) == 1 || (render & 3) == 2) && +             !state->getStrokeColorSpace()->isNonMarking(); +  doClip = render & 4; + +  path = NULL; +  if (doStroke || doClip) { +    if ((path = font->getGlyphPath(code))) { +      path->offset((SplashCoord)x, (SplashCoord)y); +    } +  } + +  // don't use stroke adjustment when stroking text -- the results +  // tend to be ugly (because characters with horizontal upper or +  // lower edges get misaligned relative to the other characters) +  strokeAdjust = splashStrokeAdjustOff; // make gcc happy +  if (doStroke) { +    strokeAdjust = splash->getStrokeAdjust(); +    splash->setStrokeAdjust(splashStrokeAdjustOff); +  } + +  // fill and stroke +  if (doFill && doStroke) { +    if (path) { +      setOverprintMask(state, state->getFillColorSpace(), +		       state->getFillOverprint(), state->getOverprintMode(), +		       state->getFillColor()); +      splash->fill(path, gFalse); +      setOverprintMask(state, state->getStrokeColorSpace(), +		       state->getStrokeOverprint(), state->getOverprintMode(), +		       state->getStrokeColor()); +      splash->stroke(path); +    } + +  // fill +  } else if (doFill) { +    setOverprintMask(state, state->getFillColorSpace(), +		     state->getFillOverprint(), state->getOverprintMode(), +		     state->getFillColor()); +    splash->fillChar((SplashCoord)x, (SplashCoord)y, code, font); + +  // stroke +  } else if (doStroke) { +    if (path) { +      setOverprintMask(state, state->getStrokeColorSpace(), +		       state->getStrokeOverprint(), state->getOverprintMode(), +		       state->getStrokeColor()); +      splash->stroke(path); +    } +  } + +  // clip +  if (doClip) { +    if (path) { +      if (textClipPath) { +	textClipPath->append(path); +      } else { +	textClipPath = path; +	path = NULL; +      } +    } +  } + +  if (doStroke) { +    splash->setStrokeAdjust(strokeAdjust); +  } + +  if (path) { +    delete path; +  } +} + +GBool SplashOutputDev::beginType3Char(GfxState *state, double x, double y, +				      double dx, double dy, +				      CharCode code, Unicode *u, int uLen) { +  GfxFont *gfxFont; +  Ref *fontID; +  double *ctm, *bbox; +  T3FontCache *t3Font; +  T3GlyphStack *t3gs; +  GBool validBBox; +  double m[4]; +  GBool horiz; +  double x1, y1, xMin, yMin, xMax, yMax, xt, yt; +  int render, i, j; + +  if (skipHorizText || skipRotatedText) { +    state->getFontTransMat(&m[0], &m[1], &m[2], &m[3]); +    horiz = m[0] > 0 && fabs(m[1]) < 0.001 && +            fabs(m[2]) < 0.001 && m[3] < 0; +    if ((skipHorizText && horiz) || (skipRotatedText && !horiz)) { +      return gTrue; +    } +  } + +  // check for invisible text +  render = state->getRender(); +  if (render == 3 || render == 7) { +    return gTrue; +  } + +  if (!(gfxFont = state->getFont())) { +    return gTrue; +  } +  fontID = gfxFont->getID(); +  ctm = state->getCTM(); +  state->transform(0, 0, &xt, &yt); + +  // is it the first (MRU) font in the cache? +  if (!(nT3Fonts > 0 && +	t3FontCache[0]->matches(fontID, ctm[0], ctm[1], ctm[2], ctm[3]))) { + +    // is the font elsewhere in the cache? +    for (i = 1; i < nT3Fonts; ++i) { +      if (t3FontCache[i]->matches(fontID, ctm[0], ctm[1], ctm[2], ctm[3])) { +	t3Font = t3FontCache[i]; +	for (j = i; j > 0; --j) { +	  t3FontCache[j] = t3FontCache[j - 1]; +	} +	t3FontCache[0] = t3Font; +	break; +      } +    } +    if (i >= nT3Fonts) { + +      // create new entry in the font cache +      if (nT3Fonts < splashOutT3FontCacheSize) { +	for (j = nT3Fonts; j > 0; --j) { +	  t3FontCache[j] = t3FontCache[j - 1]; +	} +      } else { +	for (j = nT3Fonts - 1; j >= 0; --j) { +	  if (t3FontCache[j]->refCount == 0) { +	    break; +	  } +	} +	if (j < 0) { +	  error(errSyntaxError, -1, "Type 3 fonts nested too deeply"); +	  return gTrue; +	} +	delete t3FontCache[j]; +	--nT3Fonts; +	for (; j > 0; --j) { +	  t3FontCache[j] = t3FontCache[j - 1]; +	} +      } +      ++nT3Fonts; +      bbox = gfxFont->getFontBBox(); +      if (bbox[0] == 0 && bbox[1] == 0 && bbox[2] == 0 && bbox[3] == 0) { +	// unspecified bounding box -- just take a guess +	xMin = xt - 5; +	xMax = xMin + 30; +	yMax = yt + 15; +	yMin = yMax - 45; +	validBBox = gFalse; +      } else { +	state->transform(bbox[0], bbox[1], &x1, &y1); +	xMin = xMax = x1; +	yMin = yMax = y1; +	state->transform(bbox[0], bbox[3], &x1, &y1); +	if (x1 < xMin) { +	  xMin = x1; +	} else if (x1 > xMax) { +	  xMax = x1; +	} +	if (y1 < yMin) { +	  yMin = y1; +	} else if (y1 > yMax) { +	  yMax = y1; +	} +	state->transform(bbox[2], bbox[1], &x1, &y1); +	if (x1 < xMin) { +	  xMin = x1; +	} else if (x1 > xMax) { +	  xMax = x1; +	} +	if (y1 < yMin) { +	  yMin = y1; +	} else if (y1 > yMax) { +	  yMax = y1; +	} +	state->transform(bbox[2], bbox[3], &x1, &y1); +	if (x1 < xMin) { +	  xMin = x1; +	} else if (x1 > xMax) { +	  xMax = x1; +	} +	if (y1 < yMin) { +	  yMin = y1; +	} else if (y1 > yMax) { +	  yMax = y1; +	} +	validBBox = gTrue; +      } +      t3FontCache[0] = new T3FontCache(fontID, ctm[0], ctm[1], ctm[2], ctm[3], +	                               (int)floor(xMin - xt) - 2, +				       (int)floor(yMin - yt) - 2, +				       (int)ceil(xMax) - (int)floor(xMin) + 4, +				       (int)ceil(yMax) - (int)floor(yMin) + 4, +				       validBBox, +				       colorMode != splashModeMono1); +    } +  } +  t3Font = t3FontCache[0]; + +  // is the glyph in the cache? +  i = (code & (t3Font->cacheSets - 1)) * t3Font->cacheAssoc; +  for (j = 0; j < t3Font->cacheAssoc; ++j) { +    if ((t3Font->cacheTags[i+j].mru & 0x8000) && +	t3Font->cacheTags[i+j].code == code) { +      drawType3Glyph(state, t3Font, &t3Font->cacheTags[i+j], +		     t3Font->cacheData + (i+j) * t3Font->glyphSize); +      return gTrue; +    } +  } + +  if (t3Font->refCount > 1000) { +    error(errSyntaxError, -1, "Type 3 CharProcs nested too deeply"); +    return gTrue; +  } +  ++t3Font->refCount; + +  // push a new Type 3 glyph record +  t3gs = new T3GlyphStack(); +  t3gs->next = t3GlyphStack; +  t3GlyphStack = t3gs; +  t3GlyphStack->code = (Gushort)code; +  t3GlyphStack->cache = t3Font; +  t3GlyphStack->cacheTag = NULL; +  t3GlyphStack->cacheData = NULL; +  t3GlyphStack->haveDx = gFalse; +  t3GlyphStack->doNotCache = gFalse; +#if 1 //~t3-sa +  t3GlyphStack->savedStrokeAdjust = splash->getStrokeAdjust(); +  splash->setStrokeAdjust(splashStrokeAdjustOff); +#endif + +  return gFalse; +} + +void SplashOutputDev::endType3Char(GfxState *state) { +  T3GlyphStack *t3gs; +  double *ctm; + +  if (t3GlyphStack->cacheTag) { +    --nestCount; +    memcpy(t3GlyphStack->cacheData, bitmap->getDataPtr(), +	   t3GlyphStack->cache->glyphSize); +    delete bitmap; +    delete splash; +    bitmap = t3GlyphStack->origBitmap; +    colorMode = bitmap->getMode(); +    splash = t3GlyphStack->origSplash; +    ctm = state->getCTM(); +    state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3], +		  t3GlyphStack->origCTM4, t3GlyphStack->origCTM5); +    updateCTM(state, 0, 0, 0, 0, 0, 0); +    drawType3Glyph(state, t3GlyphStack->cache, +		   t3GlyphStack->cacheTag, t3GlyphStack->cacheData); +  } +#if 1 //~t3-sa +  splash->setStrokeAdjust(t3GlyphStack->savedStrokeAdjust); +#endif +  t3gs = t3GlyphStack; +  t3GlyphStack = t3gs->next; +  --t3gs->cache->refCount; +  delete t3gs; +} + +void SplashOutputDev::type3D0(GfxState *state, double wx, double wy) { +  if (!t3GlyphStack) { +    error(errSyntaxError, -1, +	  "Encountered d0 operator outside of Type 3 CharProc"); +    return; +  } +  t3GlyphStack->haveDx = gTrue; +} + +void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, +			      double llx, double lly, double urx, double ury) { +  double *ctm; +  T3FontCache *t3Font; +  SplashColor color; +  double xt, yt, xMin, xMax, yMin, yMax, x1, y1; +  int i, j; + +  if (!t3GlyphStack) { +    error(errSyntaxError, -1, +	  "Encountered d1 operator outside of Type 3 CharProc"); +    return; +  } + +  // ignore multiple d0/d1 operators +  if (t3GlyphStack->haveDx) { +    return; +  } +  t3GlyphStack->haveDx = gTrue; +  // don't cache if we got a gsave/grestore before the d1 +  if (t3GlyphStack->doNotCache) { +    return; +  } + +  t3Font = t3GlyphStack->cache; + +  // check for a valid bbox +  state->transform(0, 0, &xt, &yt); +  state->transform(llx, lly, &x1, &y1); +  xMin = xMax = x1; +  yMin = yMax = y1; +  state->transform(llx, ury, &x1, &y1); +  if (x1 < xMin) { +    xMin = x1; +  } else if (x1 > xMax) { +    xMax = x1; +  } +  if (y1 < yMin) { +    yMin = y1; +  } else if (y1 > yMax) { +    yMax = y1; +  } +  state->transform(urx, lly, &x1, &y1); +  if (x1 < xMin) { +    xMin = x1; +  } else if (x1 > xMax) { +    xMax = x1; +  } +  if (y1 < yMin) { +    yMin = y1; +  } else if (y1 > yMax) { +    yMax = y1; +  } +  state->transform(urx, ury, &x1, &y1); +  if (x1 < xMin) { +    xMin = x1; +  } else if (x1 > xMax) { +    xMax = x1; +  } +  if (y1 < yMin) { +    yMin = y1; +  } else if (y1 > yMax) { +    yMax = y1; +  } +  if (xMin - xt < t3Font->glyphX || +      yMin - yt < t3Font->glyphY || +      xMax - xt > t3Font->glyphX + t3Font->glyphW || +      yMax - yt > t3Font->glyphY + t3Font->glyphH) { +    if (t3Font->validBBox) { +      error(errSyntaxWarning, -1, "Bad bounding box in Type 3 glyph"); +    } +    return; +  } + +  // allocate a cache entry +  i = (t3GlyphStack->code & (t3Font->cacheSets - 1)) * t3Font->cacheAssoc; +  for (j = 0; j < t3Font->cacheAssoc; ++j) { +    if ((t3Font->cacheTags[i+j].mru & 0x7fff) == t3Font->cacheAssoc - 1) { +      t3Font->cacheTags[i+j].mru = 0x8000; +      t3Font->cacheTags[i+j].code = t3GlyphStack->code; +      t3GlyphStack->cacheTag = &t3Font->cacheTags[i+j]; +      t3GlyphStack->cacheData = t3Font->cacheData + (i+j) * t3Font->glyphSize; +    } else { +      ++t3Font->cacheTags[i+j].mru; +    } +  } + +  // save state +  t3GlyphStack->origBitmap = bitmap; +  t3GlyphStack->origSplash = splash; +  ctm = state->getCTM(); +  t3GlyphStack->origCTM4 = ctm[4]; +  t3GlyphStack->origCTM5 = ctm[5]; + +  // create the temporary bitmap +  if (colorMode == splashModeMono1) { +    colorMode = splashModeMono1; +    traceMessage("T3 glyph bitmap"); +    bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1, +			      splashModeMono1, gFalse, gTrue, bitmap); +    splash = new Splash(bitmap, gFalse, +			t3GlyphStack->origSplash->getImageCache(),  +			t3GlyphStack->origSplash->getScreen()); +    color[0] = 0; +    splash->clear(color); +    color[0] = 0xff; +  } else { +    colorMode = splashModeMono8; +    traceMessage("T3 glyph bitmap"); +    bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1, +			      splashModeMono8, gFalse, gTrue, bitmap); +    splash = new Splash(bitmap, vectorAntialias, +			t3GlyphStack->origSplash->getImageCache(),  +			t3GlyphStack->origSplash->getScreen()); +    color[0] = 0x00; +    splash->clear(color); +    color[0] = 0xff; +  } +  splash->setMinLineWidth(globalParams->getMinLineWidth()); +  splash->setStrokeAdjust(t3GlyphStack->origSplash->getStrokeAdjust()); +  splash->setEnablePathSimplification( +		 globalParams->getEnablePathSimplification()); +  copyState(t3GlyphStack->origSplash, gFalse); +  splash->setFillPattern(new SplashSolidColor(color)); +  splash->setStrokePattern(new SplashSolidColor(color)); +  state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3], +		-t3Font->glyphX, -t3Font->glyphY); +  updateCTM(state, 0, 0, 0, 0, 0, 0); +  ++nestCount; +} + +void SplashOutputDev::drawType3Glyph(GfxState *state, T3FontCache *t3Font, +				     T3FontCacheTag *tag, Guchar *data) { +  SplashGlyphBitmap glyph; + +  setOverprintMask(state, state->getFillColorSpace(), +		   state->getFillOverprint(), state->getOverprintMode(), +		   state->getFillColor()); +  glyph.x = -t3Font->glyphX; +  glyph.y = -t3Font->glyphY; +  glyph.w = t3Font->glyphW; +  glyph.h = t3Font->glyphH; +  glyph.aa = colorMode != splashModeMono1; +  glyph.data = data; +  glyph.freeData = gFalse; +  splash->fillGlyph(0, 0, &glyph); +} + +void SplashOutputDev::endTextObject(GfxState *state) { +  if (textClipPath) { +    splash->clipToPath(textClipPath, gFalse); +    delete textClipPath; +    textClipPath = NULL; +  } +} + +struct SplashOutImageMaskData { +  ImageStream *imgStr; +  Guchar invert; +  int width, height, y; +}; + +GBool SplashOutputDev::imageMaskSrc(void *data, Guchar *line) { +  SplashOutImageMaskData *imgMaskData = (SplashOutImageMaskData *)data; +  Guchar *p; +  SplashColorPtr q; +  int x; + +  if (imgMaskData->y == imgMaskData->height || +      !(p = imgMaskData->imgStr->getLine())) { +    memset(line, 0, imgMaskData->width); +    return gFalse; +  } +  for (x = 0, q = line; x < imgMaskData->width; ++x) { +    *q++ = *p++ ^ imgMaskData->invert; +  } +  ++imgMaskData->y; +  return gTrue; +} + +void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, +				    int width, int height, GBool invert, +				    GBool inlineImg, GBool interpolate) { +  double *ctm; +  SplashCoord mat[6]; +  SplashOutImageMaskData imgMaskData; +  GString *imgTag; + +  if (state->getFillColorSpace()->isNonMarking()) { +    return; +  } +  setOverprintMask(state, state->getFillColorSpace(), +		   state->getFillOverprint(), state->getOverprintMode(), +		   state->getFillColor()); + +  ctm = state->getCTM(); +  mat[0] = ctm[0]; +  mat[1] = ctm[1]; +  mat[2] = -ctm[2]; +  mat[3] = -ctm[3]; +  mat[4] = ctm[2] + ctm[4]; +  mat[5] = ctm[3] + ctm[5]; + +  reduceImageResolution(str, ctm, &width, &height); + +  imgMaskData.imgStr = new ImageStream(str, width, 1, 1); +  imgMaskData.imgStr->reset(); +  imgMaskData.invert = invert ? 0 : 1; +  imgMaskData.width = width; +  imgMaskData.height = height; +  imgMaskData.y = 0; + +  imgTag = makeImageTag(ref, gfxRenderingIntentRelativeColorimetric, NULL); +  splash->fillImageMask(imgTag, +			&imageMaskSrc, &imgMaskData, width, height, mat, +			t3GlyphStack != NULL, interpolate, +			globalParams->getImageMaskAntialias()); + +  if (inlineImg) { +    while (imgMaskData.y < height) { +      imgMaskData.imgStr->getLine(); +      ++imgMaskData.y; +    } +  } + +  delete imgTag; +  delete imgMaskData.imgStr; +  str->close(); +} + +void SplashOutputDev::setSoftMaskFromImageMask(GfxState *state, +					       Object *ref, Stream *str, +					       int width, int height, +					       GBool invert, +					       GBool inlineImg, +					       GBool interpolate) { +  double *ctm; +  SplashCoord mat[6]; +  SplashOutImageMaskData imgMaskData; +  SplashBitmap *maskBitmap; +  Splash *maskSplash; +  SplashColor maskColor; +  GString *imgTag; + +  ctm = state->getCTM(); +  mat[0] = ctm[0]; +  mat[1] = ctm[1]; +  mat[2] = -ctm[2]; +  mat[3] = -ctm[3]; +  mat[4] = ctm[2] + ctm[4]; +  mat[5] = ctm[3] + ctm[5]; +  reduceImageResolution(str, ctm, &width, &height); +  imgMaskData.imgStr = new ImageStream(str, width, 1, 1); +  imgMaskData.imgStr->reset(); +  imgMaskData.invert = invert ? 0 : 1; +  imgMaskData.width = width; +  imgMaskData.height = height; +  imgMaskData.y = 0; +  traceMessage("image mask soft mask bitmap"); +  maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), +				1, splashModeMono8, gFalse, gTrue, bitmap); +  maskSplash = new Splash(maskBitmap, gTrue, splash->getImageCache()); +  maskSplash->setStrokeAdjust( +		     mapStrokeAdjustMode[globalParams->getStrokeAdjust()]); +  maskSplash->setEnablePathSimplification( +		     globalParams->getEnablePathSimplification()); +  if (splash->getSoftMask()) { +    maskSplash->setSoftMask(splash->getSoftMask(), gFalse); +  } +  clearMaskRegion(state, maskSplash, 0, 0, 1, 1); +  maskColor[0] = 0xff; +  maskSplash->setFillPattern(new SplashSolidColor(maskColor)); +  imgTag = makeImageTag(ref, gfxRenderingIntentRelativeColorimetric, NULL); +  maskSplash->fillImageMask(imgTag, &imageMaskSrc, &imgMaskData, +			    width, height, mat, gFalse, interpolate, +			    globalParams->getImageMaskAntialias()); +  delete imgTag; +  delete imgMaskData.imgStr; +  str->close(); +  delete maskSplash; +  splash->setSoftMask(maskBitmap); +} + +struct SplashOutImageData { +  ImageStream *imgStr; +  GfxImageColorMap *colorMap; +  GfxRenderingIntent ri; +  SplashColorPtr lookup; +  int *maskColors; +  SplashColorMode colorMode; +  GBool invert; +  int width, height, y; +}; + +GBool SplashOutputDev::imageSrc(void *data, SplashColorPtr colorLine, +				Guchar *alphaLine) { +  SplashOutImageData *imgData = (SplashOutImageData *)data; +  Guchar *p; +  SplashColorPtr q, col; +  int n, x; + +  if (imgData->y == imgData->height || +      !(p = imgData->imgStr->getLine())) { +    memset(colorLine, 0, +	   imgData->width * splashColorModeNComps[imgData->colorMode]); +    return gFalse; +  } + +  if (imgData->lookup) { +    switch (imgData->colorMode) { +    case splashModeMono1: +    case splashModeMono8: +      for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) { +	*q++ = imgData->lookup[*p]; +      } +      break; +    case splashModeRGB8: +    case splashModeBGR8: +      for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) { +	col = &imgData->lookup[3 * *p]; +	*q++ = col[0]; +	*q++ = col[1]; +	*q++ = col[2]; +      } +      break; +#if SPLASH_CMYK +    case splashModeCMYK8: +      for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) { +	col = &imgData->lookup[4 * *p]; +	*q++ = col[0]; +	*q++ = col[1]; +	*q++ = col[2]; +	*q++ = col[3]; +      } +      break; +#endif +    } +  } else { +    switch (imgData->colorMode) { +    case splashModeMono1: +    case splashModeMono8: +      imgData->colorMap->getGrayByteLine(p, colorLine, imgData->width, +					 imgData->ri); +      break; +    case splashModeRGB8: +    case splashModeBGR8: +      imgData->colorMap->getRGBByteLine(p, colorLine, imgData->width, +					imgData->ri); +      break; +#if SPLASH_CMYK +    case splashModeCMYK8: +      imgData->colorMap->getCMYKByteLine(p, colorLine, imgData->width, +					 imgData->ri); +      break; +#endif +    } +  } + +  if (imgData->invert) { +    n = imgData->width * splashColorModeNComps[imgData->colorMode]; +    for (x = 0, p = colorLine; x < n; ++x, ++p) { +      *p ^= 0xff; +    } +  } + +  ++imgData->y; +  return gTrue; +} + +GBool SplashOutputDev::alphaImageSrc(void *data, SplashColorPtr colorLine, +				     Guchar *alphaLine) { +  SplashOutImageData *imgData = (SplashOutImageData *)data; +  Guchar *p0, *p, *aq; +  SplashColorPtr q, col; +  Guchar alpha; +  int nComps, x, n, i; + +  if (imgData->y == imgData->height || +      !(p0 = imgData->imgStr->getLine())) { +    memset(colorLine, 0, +	   imgData->width * splashColorModeNComps[imgData->colorMode]); +    memset(alphaLine, 0, imgData->width); +    return gFalse; +  } + +  nComps = imgData->colorMap->getNumPixelComps(); + +  if (imgData->lookup) { +    switch (imgData->colorMode) { +    case splashModeMono1: +    case splashModeMono8: +      for (x = 0, p = p0, q = colorLine; x < imgData->width; ++x, ++p) { +	*q++ = imgData->lookup[*p]; +      } +      break; +    case splashModeRGB8: +    case splashModeBGR8: +      for (x = 0, p = p0, q = colorLine; x < imgData->width; ++x, ++p) { +	col = &imgData->lookup[3 * *p]; +	*q++ = col[0]; +	*q++ = col[1]; +	*q++ = col[2]; +      } +      break; +#if SPLASH_CMYK +    case splashModeCMYK8: +      for (x = 0, p = p0, q = colorLine; x < imgData->width; ++x, ++p) { +	col = &imgData->lookup[4 * *p]; +	*q++ = col[0]; +	*q++ = col[1]; +	*q++ = col[2]; +	*q++ = col[3]; +      } +      break; +#endif +    } +  } else { +    switch (imgData->colorMode) { +    case splashModeMono1: +    case splashModeMono8: +      imgData->colorMap->getGrayByteLine(p0, colorLine, imgData->width, +					 imgData->ri); +      break; +    case splashModeRGB8: +    case splashModeBGR8: +      imgData->colorMap->getRGBByteLine(p0, colorLine, imgData->width, +					imgData->ri); +      break; +#if SPLASH_CMYK +    case splashModeCMYK8: +      imgData->colorMap->getCMYKByteLine(p0, colorLine, imgData->width, +					 imgData->ri); +      break; +#endif +    } +  } + +  for (x = 0, p = p0, aq = alphaLine; x < imgData->width; ++x, p += nComps) { +    alpha = 0; +    for (i = 0; i < nComps; ++i) { +      if (p[i] < imgData->maskColors[2*i] || +	  p[i] > imgData->maskColors[2*i+1]) { +	alpha = 0xff; +	break; +      } +    } +    *aq++ = alpha; +  } + +  if (imgData->invert) { +    n = imgData->width * splashColorModeNComps[imgData->colorMode]; +    for (x = 0, p = colorLine; x < n; ++x, ++p) { +      *p ^= 0xff; +    } +  } + +  ++imgData->y; +  return gTrue; +} + + +void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, +				int width, int height, +				GfxImageColorMap *colorMap, +				int *maskColors, GBool inlineImg, +				GBool interpolate) { +  double *ctm; +  SplashCoord mat[6]; +  SplashOutImageData imgData; +  SplashColorMode srcMode; +  SplashImageSource src; +  GString *imgTag; +  GfxGray gray; +  GfxRGB rgb; +#if SPLASH_CMYK +  GfxCMYK cmyk; +#endif +  Guchar pix; +  int n, i; + +  setOverprintMask(state, colorMap->getColorSpace(), +		   state->getFillOverprint(), state->getOverprintMode(), +		   NULL); + +  ctm = state->getCTM(); +  mat[0] = ctm[0]; +  mat[1] = ctm[1]; +  mat[2] = -ctm[2]; +  mat[3] = -ctm[3]; +  mat[4] = ctm[2] + ctm[4]; +  mat[5] = ctm[3] + ctm[5]; + +  reduceImageResolution(str, ctm, &width, &height); + +  imgData.imgStr = new ImageStream(str, width, +				   colorMap->getNumPixelComps(), +				   colorMap->getBits()); +  imgData.imgStr->reset(); +  imgData.colorMap = colorMap; +  imgData.ri = state->getRenderingIntent(); +  imgData.maskColors = maskColors; +  imgData.colorMode = colorMode; +  imgData.invert = reverseVideo && reverseVideoInvertImages; +  imgData.width = width; +  imgData.height = height; +  imgData.y = 0; + +  // special case for one-channel (monochrome/gray/separation) images: +  // build a lookup table here +  imgData.lookup = NULL; +  if (colorMap->getNumPixelComps() == 1) { +    if (colorMap->getBits() <= 8) { +      n = 1 << colorMap->getBits(); +    } else { +      // GfxImageColorMap and ImageStream compress 16-bit samples to 8-bit +      n = 1 << 8; +    } +    switch (colorMode) { +    case splashModeMono1: +    case splashModeMono8: +      imgData.lookup = (SplashColorPtr)gmalloc(n); +      for (i = 0; i < n; ++i) { +	pix = (Guchar)i; +	colorMap->getGray(&pix, &gray, state->getRenderingIntent()); +	imgData.lookup[i] = colToByte(gray); +      } +      break; +    case splashModeRGB8: +    case splashModeBGR8: +      imgData.lookup = (SplashColorPtr)gmallocn(n, 3); +      for (i = 0; i < n; ++i) { +	pix = (Guchar)i; +	colorMap->getRGB(&pix, &rgb, state->getRenderingIntent()); +	imgData.lookup[3*i] = colToByte(rgb.r); +	imgData.lookup[3*i+1] = colToByte(rgb.g); +	imgData.lookup[3*i+2] = colToByte(rgb.b); +      } +      break; +#if SPLASH_CMYK +    case splashModeCMYK8: +      imgData.lookup = (SplashColorPtr)gmallocn(n, 4); +      for (i = 0; i < n; ++i) { +	pix = (Guchar)i; +	colorMap->getCMYK(&pix, &cmyk, state->getRenderingIntent()); +	imgData.lookup[4*i] = colToByte(cmyk.c); +	imgData.lookup[4*i+1] = colToByte(cmyk.m); +	imgData.lookup[4*i+2] = colToByte(cmyk.y); +	imgData.lookup[4*i+3] = colToByte(cmyk.k); +      } +      break; +#endif +    } +  } + +  if (colorMode == splashModeMono1) { +    srcMode = splashModeMono8; +  } else if (colorMode == splashModeBGR8) { +    srcMode = splashModeRGB8; +  } else { +    srcMode = colorMode; +  } +  src = maskColors ? &alphaImageSrc : &imageSrc; +  imgTag = makeImageTag(ref, state->getRenderingIntent(), +			colorMap->getColorSpace()); +  splash->drawImage(imgTag, +		    src, &imgData, srcMode, maskColors ? gTrue : gFalse, +		    width, height, mat, interpolate); +  if (inlineImg) { +    while (imgData.y < height) { +      imgData.imgStr->getLine(); +      ++imgData.y; +    } +  } + +  delete imgTag; +  gfree(imgData.lookup); +  delete imgData.imgStr; +  str->close(); +} + +struct SplashOutMaskedImageData { +  ImageStream *imgStr; +  GfxImageColorMap *colorMap; +  GfxRenderingIntent ri; +  SplashBitmap *mask; +  SplashColorPtr lookup; +  SplashColorMode colorMode; +  GBool invert; +  int width, height, y; +}; + +GBool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine, +				      Guchar *alphaLine) { +  SplashOutMaskedImageData *imgData = (SplashOutMaskedImageData *)data; +  Guchar *p, *aq; +  SplashColorPtr q, col; +  static Guchar bitToByte[2] = {0x00, 0xff}; +  Guchar *maskPtr; +  int maskShift; +  int n, x; + +  if (imgData->y == imgData->height || +      !(p = imgData->imgStr->getLine())) { +    memset(colorLine, 0, +	   imgData->width * splashColorModeNComps[imgData->colorMode]); +    memset(alphaLine, 0, imgData->width); +    return gFalse; +  } + +  maskPtr = imgData->mask->getDataPtr() + +              imgData->y * imgData->mask->getRowSize(); +  aq = alphaLine; +  for (x = 0; x <= imgData->width - 8; x += 8) { +    aq[0] = bitToByte[(*maskPtr >> 7) & 1]; +    aq[1] = bitToByte[(*maskPtr >> 6) & 1]; +    aq[2] = bitToByte[(*maskPtr >> 5) & 1]; +    aq[3] = bitToByte[(*maskPtr >> 4) & 1]; +    aq[4] = bitToByte[(*maskPtr >> 3) & 1]; +    aq[5] = bitToByte[(*maskPtr >> 2) & 1]; +    aq[6] = bitToByte[(*maskPtr >> 1) & 1]; +    aq[7] = bitToByte[*maskPtr & 1]; +    aq += 8; +    ++maskPtr; +  } +  maskShift = 7; +  for (; x < imgData->width; ++x) { +    *aq++ = bitToByte[(*maskPtr >> maskShift) & 1]; +    --maskShift; +  } + +  if (imgData->lookup) { +    switch (imgData->colorMode) { +    case splashModeMono1: +    case splashModeMono8: +      for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) { +	*q++ = imgData->lookup[*p]; +      } +      break; +    case splashModeRGB8: +    case splashModeBGR8: +      for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) { +	col = &imgData->lookup[3 * *p]; +	*q++ = col[0]; +	*q++ = col[1]; +	*q++ = col[2]; +      } +      break; +#if SPLASH_CMYK +    case splashModeCMYK8: +      for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) { +	col = &imgData->lookup[4 * *p]; +	*q++ = col[0]; +	*q++ = col[1]; +	*q++ = col[2]; +	*q++ = col[3]; +      } +      break; +#endif +    } +  } else { +    switch (imgData->colorMode) { +    case splashModeMono1: +    case splashModeMono8: +      imgData->colorMap->getGrayByteLine(p, colorLine, imgData->width, +					 imgData->ri); +      break; +    case splashModeRGB8: +    case splashModeBGR8: +      imgData->colorMap->getRGBByteLine(p, colorLine, imgData->width, +					imgData->ri); +      break; +#if SPLASH_CMYK +    case splashModeCMYK8: +      imgData->colorMap->getCMYKByteLine(p, colorLine, imgData->width, +					 imgData->ri); +      break; +#endif +    } +  } + +  if (imgData->invert) { +    n = imgData->width * splashColorModeNComps[imgData->colorMode]; +    for (x = 0, p = colorLine; x < n; ++x, ++p) { +      *p ^= 0xff; +    } +  } + +  ++imgData->y; +  return gTrue; +} + + +void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, +				      Stream *str, int width, int height, +				      GfxImageColorMap *colorMap, +				      Object *maskRef, Stream *maskStr, +				      int maskWidth, int maskHeight, +				      GBool maskInvert, GBool interpolate) { +  GfxImageColorMap *maskColorMap; +  Object maskDecode, decodeLow, decodeHigh; +  double *ctm; +  SplashCoord mat[6]; +  SplashOutMaskedImageData imgData; +  SplashOutImageMaskData imgMaskData; +  SplashColorMode srcMode; +  SplashBitmap *maskBitmap; +  Splash *maskSplash; +  GString *imgTag; +  SplashColor maskColor; +  GfxGray gray; +  GfxRGB rgb; +#if SPLASH_CMYK +  GfxCMYK cmyk; +#endif +  Guchar pix; +  int n, i; + +  setOverprintMask(state, colorMap->getColorSpace(), +		   state->getFillOverprint(), state->getOverprintMode(), +		   NULL); + +  ctm = state->getCTM(); +  reduceImageResolution(str, ctm, &width, &height); +  reduceImageResolution(maskStr, ctm, &maskWidth, &maskHeight); + +  // If the mask is higher resolution than the image, use +  // drawSoftMaskedImage() instead. +  if (maskWidth > width || maskHeight > height) { +    decodeLow.initInt(maskInvert ? 0 : 1); +    decodeHigh.initInt(maskInvert ? 1 : 0); +    maskDecode.initArray(xref); +    maskDecode.arrayAdd(&decodeLow); +    maskDecode.arrayAdd(&decodeHigh); +    maskColorMap = new GfxImageColorMap(1, &maskDecode, +					new GfxDeviceGrayColorSpace()); +    maskDecode.free(); +    drawSoftMaskedImage(state, ref, str, width, height, colorMap, +			maskRef, maskStr, maskWidth, maskHeight, maskColorMap, +			NULL, interpolate); +    delete maskColorMap; + +  } else { + +    //----- scale the mask image to the same size as the source image + +    mat[0] = (SplashCoord)width; +    mat[1] = 0; +    mat[2] = 0; +    mat[3] = (SplashCoord)height; +    mat[4] = 0; +    mat[5] = 0; +    imgMaskData.imgStr = new ImageStream(maskStr, maskWidth, 1, 1); +    imgMaskData.imgStr->reset(); +    imgMaskData.invert = maskInvert ? 0 : 1; +    imgMaskData.width = maskWidth; +    imgMaskData.height = maskHeight; +    imgMaskData.y = 0; +    traceMessage("masked image bitmap"); +    maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1, +				  gFalse, gTrue, bitmap); +    maskSplash = new Splash(maskBitmap, gFalse, splash->getImageCache()); +    maskSplash->setStrokeAdjust( +		       mapStrokeAdjustMode[globalParams->getStrokeAdjust()]); +    maskSplash->setEnablePathSimplification( +		       globalParams->getEnablePathSimplification()); +    maskColor[0] = 0; +    maskSplash->clear(maskColor); +    maskColor[0] = 0xff; +    maskSplash->setFillPattern(new SplashSolidColor(maskColor)); +    // use "glyph mode" here to get the correct scaled size +    maskSplash->fillImageMask(NULL, &imageMaskSrc, &imgMaskData, +			      maskWidth, maskHeight, mat, gTrue, interpolate, +			      globalParams->getImageMaskAntialias()); +    delete imgMaskData.imgStr; +    maskStr->close(); +    delete maskSplash; + +    //----- draw the source image + +    mat[0] = ctm[0]; +    mat[1] = ctm[1]; +    mat[2] = -ctm[2]; +    mat[3] = -ctm[3]; +    mat[4] = ctm[2] + ctm[4]; +    mat[5] = ctm[3] + ctm[5]; + +    imgData.imgStr = new ImageStream(str, width, +				     colorMap->getNumPixelComps(), +				     colorMap->getBits()); +    imgData.imgStr->reset(); +    imgData.colorMap = colorMap; +    imgData.ri = state->getRenderingIntent(); +    imgData.mask = maskBitmap; +    imgData.colorMode = colorMode; +    imgData.invert = reverseVideo && reverseVideoInvertImages; +    imgData.width = width; +    imgData.height = height; +    imgData.y = 0; + +    // special case for one-channel (monochrome/gray/separation) images: +    // build a lookup table here +    imgData.lookup = NULL; +    if (colorMap->getNumPixelComps() == 1) { +      if (colorMap->getBits() <= 8) { +	n = 1 << colorMap->getBits(); +      } else { +	// GfxImageColorMap and ImageStream compress 16-bit samples to 8-bit +	n = 1 << 8; +      } +      switch (colorMode) { +      case splashModeMono1: +      case splashModeMono8: +	imgData.lookup = (SplashColorPtr)gmalloc(n); +	for (i = 0; i < n; ++i) { +	  pix = (Guchar)i; +	  colorMap->getGray(&pix, &gray, state->getRenderingIntent()); +	  imgData.lookup[i] = colToByte(gray); +	} +	break; +      case splashModeRGB8: +      case splashModeBGR8: +	imgData.lookup = (SplashColorPtr)gmallocn(n, 3); +	for (i = 0; i < n; ++i) { +	  pix = (Guchar)i; +	  colorMap->getRGB(&pix, &rgb, state->getRenderingIntent()); +	  imgData.lookup[3*i] = colToByte(rgb.r); +	  imgData.lookup[3*i+1] = colToByte(rgb.g); +	  imgData.lookup[3*i+2] = colToByte(rgb.b); +	} +	break; +#if SPLASH_CMYK +      case splashModeCMYK8: +	imgData.lookup = (SplashColorPtr)gmallocn(n, 4); +	for (i = 0; i < n; ++i) { +	  pix = (Guchar)i; +	  colorMap->getCMYK(&pix, &cmyk, state->getRenderingIntent()); +	  imgData.lookup[4*i] = colToByte(cmyk.c); +	  imgData.lookup[4*i+1] = colToByte(cmyk.m); +	  imgData.lookup[4*i+2] = colToByte(cmyk.y); +	  imgData.lookup[4*i+3] = colToByte(cmyk.k); +	} +	break; +#endif +      } +    } + +    if (colorMode == splashModeMono1) { +      srcMode = splashModeMono8; +    } else if (colorMode == splashModeBGR8) { +      srcMode = splashModeRGB8; +    } else { +      srcMode = colorMode; +    } +    imgTag = makeImageTag(ref, state->getRenderingIntent(), +			  colorMap->getColorSpace()); +    splash->drawImage(imgTag, +		      &maskedImageSrc, &imgData, srcMode, gTrue, +		      width, height, mat, interpolate); + +    delete imgTag; +    delete maskBitmap; +    gfree(imgData.lookup); +    delete imgData.imgStr; +    str->close(); +  } +} + +struct SplashOutSoftMaskMatteImageData { +  ImageStream *imgStr; +  ImageStream *maskStr; +  GfxImageColorMap *colorMap; +  GfxRenderingIntent ri; +  Guchar matte[gfxColorMaxComps]; +  SplashColorMode colorMode; +  GBool invert; +  int width, height, y; +}; + +GBool SplashOutputDev::softMaskMatteImageSrc(void *data, +					     SplashColorPtr colorLine, +					     Guchar *alphaLine) { +  SplashOutSoftMaskMatteImageData *imgData = +      (SplashOutSoftMaskMatteImageData *)data; +  Guchar *p, *ap, *aq; +  SplashColorPtr q; +  GfxRGB rgb; +  GfxGray gray; +#if SPLASH_CMYK +  GfxCMYK cmyk; +#endif +  Guchar alpha; +  int nComps, n, x; + +  if (imgData->y == imgData->height || +      !(p = imgData->imgStr->getLine()) || +      !(ap = imgData->maskStr->getLine())) { +    memset(colorLine, 0, +	   imgData->width * splashColorModeNComps[imgData->colorMode]); +    memset(alphaLine, 0, imgData->width); +    return gFalse; +  } + +  nComps = imgData->colorMap->getNumPixelComps(); + +  for (x = 0, q = colorLine, aq = alphaLine; +       x < imgData->width; +       ++x, p += nComps, ++ap) { +    alpha = *ap; +    switch (imgData->colorMode) { +    case splashModeMono1: +    case splashModeMono8: +      if (alpha) { +	imgData->colorMap->getGray(p, &gray, imgData->ri); +	*q++ = clip255(imgData->matte[0] + +		       (255 * (colToByte(gray) - imgData->matte[0])) / alpha); +      } else { +	*q++ = 0; +      } +      break; +    case splashModeRGB8: +    case splashModeBGR8: +      if (alpha) { +	imgData->colorMap->getRGB(p, &rgb, imgData->ri); +	*q++ = clip255(imgData->matte[0] + +		       (255 * (colToByte(rgb.r) - imgData->matte[0])) / alpha); +	*q++ = clip255(imgData->matte[1] + +		       (255 * (colToByte(rgb.g) - imgData->matte[1])) / alpha); +	*q++ = clip255(imgData->matte[2] + +		       (255 * (colToByte(rgb.b) - imgData->matte[2])) / alpha); +      } else { +	*q++ = 0; +	*q++ = 0; +	*q++ = 0; +      } +      break; +#if SPLASH_CMYK +    case splashModeCMYK8: +      if (alpha) { +	imgData->colorMap->getCMYK(p, &cmyk, imgData->ri); +	*q++ = clip255(imgData->matte[0] + +		       (255 * (colToByte(cmyk.c) - imgData->matte[0])) +			 / alpha); +	*q++ = clip255(imgData->matte[1] + +		       (255 * (colToByte(cmyk.m) - imgData->matte[1])) +			 / alpha); +	*q++ = clip255(imgData->matte[2] + +		       (255 * (colToByte(cmyk.y) - imgData->matte[2])) +			 / alpha); +	*q++ = clip255(imgData->matte[3] + +		       (255 * (colToByte(cmyk.k) - imgData->matte[3])) +		         / alpha); +      } else { +	*q++ = 0; +	*q++ = 0; +	*q++ = 0; +	*q++ = 0; +      } +      break; +#endif +    } +    *aq++ = alpha; +  } + +  if (imgData->invert) { +    n = imgData->width * splashColorModeNComps[imgData->colorMode]; +    for (x = 0, p = colorLine; x < n; ++x, ++p) { +      *p ^= 0xff; +    } +  } + +  ++imgData->y; +  return gTrue; +} + + +void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, +					  Stream *str, int width, int height, +					  GfxImageColorMap *colorMap, +					  Object *maskRef, Stream *maskStr, +					  int maskWidth, int maskHeight, +					  GfxImageColorMap *maskColorMap, +					  double *matte, GBool interpolate) { +  double *ctm; +  SplashCoord mat[6]; +  SplashOutImageData imgData; +  SplashOutImageData imgMaskData; +  SplashOutSoftMaskMatteImageData matteImgData; +  GString *imgTag; +  SplashColorMode srcMode; +  SplashBitmap *maskBitmap; +  Splash *maskSplash; +  GfxColor matteColor; +  GfxGray gray; +  GfxRGB rgb; +#if SPLASH_CMYK +  GfxCMYK cmyk; +#endif +  Guchar pix; +  int n, i; + +  setOverprintMask(state, colorMap->getColorSpace(), +		   state->getFillOverprint(), state->getOverprintMode(), +		   NULL); + +  ctm = state->getCTM(); +  mat[0] = ctm[0]; +  mat[1] = ctm[1]; +  mat[2] = -ctm[2]; +  mat[3] = -ctm[3]; +  mat[4] = ctm[2] + ctm[4]; +  mat[5] = ctm[3] + ctm[5]; + +  if (colorMode == splashModeMono1) { +    srcMode = splashModeMono8; +  } else if (colorMode == splashModeBGR8) { +    srcMode = splashModeRGB8; +  } else { +    srcMode = colorMode; +  } + +  //----- handle a preblended image + +  if (matte && width == maskWidth && height == maskHeight) { + +    // the image and mask must be the same size, so don't call +    // reduceImageResolution(), which might result in different +    // reductions (e.g., if the image filter supports resolution +    // reduction but the mask filter doesn't) + +    matteImgData.imgStr = new ImageStream(str, width, +					  colorMap->getNumPixelComps(), +					  colorMap->getBits()); +    matteImgData.imgStr->reset(); +    matteImgData.maskStr = new ImageStream(maskStr, maskWidth, +					   maskColorMap->getNumPixelComps(), +					   maskColorMap->getBits()); +    matteImgData.maskStr->reset(); +    matteImgData.colorMap = colorMap; +    matteImgData.ri = state->getRenderingIntent(); +    n = colorMap->getNumPixelComps(); +    for (i = 0; i < n; ++i) { +      matteColor.c[i] = dblToCol(matte[i]); +    } +    switch (colorMode) { +    case splashModeMono1: +    case splashModeMono8: +      colorMap->getColorSpace()->getGray(&matteColor, &gray, +					 state->getRenderingIntent()); +      matteImgData.matte[0] = colToByte(gray); +      break; +    case splashModeRGB8: +    case splashModeBGR8: +      colorMap->getColorSpace()->getRGB(&matteColor, &rgb, +					state->getRenderingIntent()); +      matteImgData.matte[0] = colToByte(rgb.r); +      matteImgData.matte[1] = colToByte(rgb.g); +      matteImgData.matte[2] = colToByte(rgb.b); +      break; +#if SPLASH_CMYK +    case splashModeCMYK8: +      colorMap->getColorSpace()->getCMYK(&matteColor, &cmyk, +					 state->getRenderingIntent()); +      matteImgData.matte[0] = colToByte(cmyk.c); +      matteImgData.matte[1] = colToByte(cmyk.m); +      matteImgData.matte[2] = colToByte(cmyk.y); +      matteImgData.matte[3] = colToByte(cmyk.k); +      break; +#endif +    } +    //~ could add the matteImgData.lookup special case +    matteImgData.colorMode = colorMode; +    matteImgData.invert = reverseVideo && reverseVideoInvertImages; +    matteImgData.width = width; +    matteImgData.height = height; +    matteImgData.y = 0; +    imgTag = makeImageTag(ref, state->getRenderingIntent(), +			  colorMap->getColorSpace()); +    splash->drawImage(imgTag, &softMaskMatteImageSrc, &matteImgData, +		      srcMode, gTrue, width, height, mat, interpolate); +    delete imgTag; +    delete matteImgData.maskStr; +    delete matteImgData.imgStr; +    maskStr->close(); +    str->close(); + +  } else { + +    reduceImageResolution(str, ctm, &width, &height); +    reduceImageResolution(maskStr, ctm, &maskWidth, &maskHeight); + +    //----- set up the soft mask + +    imgMaskData.imgStr = new ImageStream(maskStr, maskWidth, +					 maskColorMap->getNumPixelComps(), +					 maskColorMap->getBits()); +    imgMaskData.imgStr->reset(); +    imgMaskData.colorMap = maskColorMap; +    imgMaskData.ri = state->getRenderingIntent(); +    imgMaskData.maskColors = NULL; +    imgMaskData.colorMode = splashModeMono8; +    imgMaskData.invert = gFalse; +    imgMaskData.width = maskWidth; +    imgMaskData.height = maskHeight; +    imgMaskData.y = 0; +    if (maskColorMap->getBits() <= 8) { +      n = 1 << maskColorMap->getBits(); +    } else { +      // GfxImageColorMap and ImageStream compress 16-bit samples to 8-bit +      n = 1 << 8; +    } +    imgMaskData.lookup = (SplashColorPtr)gmalloc(n); +    for (i = 0; i < n; ++i) { +      pix = (Guchar)i; +      maskColorMap->getGray(&pix, &gray, state->getRenderingIntent()); +      imgMaskData.lookup[i] = colToByte(gray); +    } +    traceMessage("soft masked image bitmap"); +    maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), +				  1, splashModeMono8, gFalse, gTrue, bitmap); +    maskSplash = new Splash(maskBitmap, vectorAntialias, +			    splash->getImageCache()); +    maskSplash->setStrokeAdjust( +		       mapStrokeAdjustMode[globalParams->getStrokeAdjust()]); +    maskSplash->setEnablePathSimplification( +		       globalParams->getEnablePathSimplification()); +    clearMaskRegion(state, maskSplash, 0, 0, 1, 1); +    maskSplash->drawImage(NULL, +			  &imageSrc, &imgMaskData, splashModeMono8, gFalse, +			  maskWidth, maskHeight, mat, interpolate); +    delete imgMaskData.imgStr; +    maskStr->close(); +    gfree(imgMaskData.lookup); +    delete maskSplash; +    splash->setSoftMask(maskBitmap); + +    //----- draw the source image + +    imgData.imgStr = new ImageStream(str, width, +				     colorMap->getNumPixelComps(), +				     colorMap->getBits()); +    imgData.imgStr->reset(); +    imgData.colorMap = colorMap; +    imgData.ri = state->getRenderingIntent(); +    imgData.maskColors = NULL; +    imgData.colorMode = colorMode; +    imgData.invert = reverseVideo && reverseVideoInvertImages; +    imgData.width = width; +    imgData.height = height; +    imgData.y = 0; + +    // special case for one-channel (monochrome/gray/separation) images: +    // build a lookup table here +    imgData.lookup = NULL; +    if (colorMap->getNumPixelComps() == 1) { +      if (colorMap->getBits() <= 8) { +	n = 1 << colorMap->getBits(); +      } else { +	// GfxImageColorMap and ImageStream compress 16-bit samples to 8-bit +	n = 1 << 8; +      } +      switch (colorMode) { +      case splashModeMono1: +      case splashModeMono8: +	imgData.lookup = (SplashColorPtr)gmalloc(n); +	for (i = 0; i < n; ++i) { +	  pix = (Guchar)i; +	  colorMap->getGray(&pix, &gray, state->getRenderingIntent()); +	  imgData.lookup[i] = colToByte(gray); +	} +	break; +      case splashModeRGB8: +      case splashModeBGR8: +	imgData.lookup = (SplashColorPtr)gmallocn(n, 3); +	for (i = 0; i < n; ++i) { +	  pix = (Guchar)i; +	  colorMap->getRGB(&pix, &rgb, state->getRenderingIntent()); +	  imgData.lookup[3*i] = colToByte(rgb.r); +	  imgData.lookup[3*i+1] = colToByte(rgb.g); +	  imgData.lookup[3*i+2] = colToByte(rgb.b); +	} +	break; +#if SPLASH_CMYK +      case splashModeCMYK8: +	imgData.lookup = (SplashColorPtr)gmallocn(n, 4); +	for (i = 0; i < n; ++i) { +	  pix = (Guchar)i; +	  colorMap->getCMYK(&pix, &cmyk, state->getRenderingIntent()); +	  imgData.lookup[4*i] = colToByte(cmyk.c); +	  imgData.lookup[4*i+1] = colToByte(cmyk.m); +	  imgData.lookup[4*i+2] = colToByte(cmyk.y); +	  imgData.lookup[4*i+3] = colToByte(cmyk.k); +	} +	break; +#endif +      } +    } + +    imgTag = makeImageTag(ref, state->getRenderingIntent(), +			  colorMap->getColorSpace()); +    splash->drawImage(imgTag, +		      &imageSrc, &imgData, srcMode, gFalse, width, height, mat, +		      interpolate); + +    splash->setSoftMask(NULL); +    delete imgTag; +    gfree(imgData.lookup); +    delete imgData.imgStr; + + +    str->close(); +  } +} + +GString *SplashOutputDev::makeImageTag(Object *ref, GfxRenderingIntent ri, +				       GfxColorSpace *colorSpace) { +  if (!ref || !ref->isRef() || +      (colorSpace && colorSpace->isDefaultColorSpace())) { +    return NULL; +  } +  return GString::format("{0:d}_{1:d}_{2:d}", +			 ref->getRefNum(), ref->getRefGen(), (int)ri); +} + +void SplashOutputDev::reduceImageResolution(Stream *str, double *ctm, +					    int *width, int *height) { +  double sw, sh; +  int reduction; + +  if (str->getKind() == strJPX && +      *width >= 256 && +      *height >= 256 && +      *width * *height > 10000000) { +    sw = (double)*width / (fabs(ctm[0]) + fabs(ctm[1])); +    sh = (double)*height / (fabs(ctm[2]) + fabs(ctm[3])); +    if (sw > 8 && sh > 8) { +      reduction = 3; +    } else if (sw > 4 && sh > 4) { +      reduction = 2; +    } else if (sw > 2 && sh > 2) { +      reduction = 1; +    } else { +      reduction = 0; +    } +    if (reduction > 0) { +      ((JPXStream *)str)->reduceResolution(reduction); +      *width >>= reduction; +      *height >>= reduction; +    } +  } +} + +void SplashOutputDev::clearMaskRegion(GfxState *state, +				      Splash *maskSplash, +				      double xMin, double yMin, +				      double xMax, double yMax) { +  SplashBitmap *maskBitmap; +  double xxMin, yyMin, xxMax, yyMax, xx, yy; +  int xxMinI, yyMinI, xxMaxI, yyMaxI, y, n; +  Guchar *p; + +  maskBitmap = maskSplash->getBitmap(); +  xxMin = maskBitmap->getWidth(); +  xxMax = 0; +  yyMin = maskBitmap->getHeight(); +  yyMax = 0; +  state->transform(xMin, yMin, &xx, &yy); +  if (xx < xxMin) { xxMin = xx; } +  if (xx > xxMax) { xxMax = xx; } +  if (yy < yyMin) { yyMin = yy; } +  if (yy > yyMax) { yyMax = yy; } +  state->transform(xMin, yMax, &xx, &yy); +  if (xx < xxMin) { xxMin = xx; } +  if (xx > xxMax) { xxMax = xx; } +  if (yy < yyMin) { yyMin = yy; } +  if (yy > yyMax) { yyMax = yy; } +  state->transform(xMax, yMin, &xx, &yy); +  if (xx < xxMin) { xxMin = xx; } +  if (xx > xxMax) { xxMax = xx; } +  if (yy < yyMin) { yyMin = yy; } +  if (yy > yyMax) { yyMax = yy; } +  state->transform(xMax, yMax, &xx, &yy); +  if (xx < xxMin) { xxMin = xx; } +  if (xx > xxMax) { xxMax = xx; } +  if (yy < yyMin) { yyMin = yy; } +  if (yy > yyMax) { yyMax = yy; } +  xxMinI = (int)floor(xxMin); +  if (xxMinI < 0) { +    xxMinI = 0; +  } +  xxMaxI = (int)ceil(xxMax); +  if (xxMaxI > maskBitmap->getWidth()) { +    xxMaxI = maskBitmap->getWidth(); +  } +  yyMinI = (int)floor(yyMin); +  if (yyMinI < 0) { +    yyMinI = 0; +  } +  yyMaxI = (int)ceil(yyMax); +  if (yyMaxI > maskBitmap->getHeight()) { +    yyMaxI = maskBitmap->getHeight(); +  } +  p = maskBitmap->getDataPtr() + yyMinI * maskBitmap->getRowSize(); +  if (maskBitmap->getMode() == splashModeMono1) { +    n = (xxMaxI + 7) / 8 - xxMinI / 8; +    p += xxMinI / 8; +  } else { +    n = xxMaxI - xxMinI; +    p += xxMinI; +  } +  if (xxMaxI > xxMinI) { +    for (y = yyMinI; y < yyMaxI; ++y) { +      memset(p, 0, n); +      p += maskBitmap->getRowSize(); +    } +  } +} + +GBool SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox, +					      GfxColorSpace *blendingColorSpace, +					      GBool isolated, GBool knockout, +					      GBool forSoftMask) { +  SplashTransparencyGroup *transpGroup; +  SplashBitmap *backdropBitmap; +  SplashColor color; +  double xMin, yMin, xMax, yMax, x, y; +  int bw, bh, tx, ty, w, h, i; + +  // transform the bbox +  state->transform(bbox[0], bbox[1], &x, &y); +  xMin = xMax = x; +  yMin = yMax = y; +  state->transform(bbox[0], bbox[3], &x, &y); +  if (x < xMin) { +    xMin = x; +  } else if (x > xMax) { +    xMax = x; +  } +  if (y < yMin) { +    yMin = y; +  } else if (y > yMax) { +    yMax = y; +  } +  state->transform(bbox[2], bbox[1], &x, &y); +  if (x < xMin) { +    xMin = x; +  } else if (x > xMax) { +    xMax = x; +  } +  if (y < yMin) { +    yMin = y; +  } else if (y > yMax) { +    yMax = y; +  } +  state->transform(bbox[2], bbox[3], &x, &y); +  if (x < xMin) { +    xMin = x; +  } else if (x > xMax) { +    xMax = x; +  } +  if (y < yMin) { +    yMin = y; +  } else if (y > yMax) { +    yMax = y; +  } + +  // clip the box +  x = splash->getClip()->getXMin(); +  if (x > xMin) { +    xMin = x; +  } +  x = splash->getClip()->getXMax(); +  if (x < xMax) { +    xMax = x; +  } +  y = splash->getClip()->getYMin(); +  if (y > yMin) { +    yMin = y; +  } +  y = splash->getClip()->getYMax(); +  if (y < yMax) { +    yMax = y; +  } + +  // convert box coords to integers +  bw = bitmap->getWidth(); +  bh = bitmap->getHeight(); +  tx = (int)floor(xMin); +  if (tx < 0) { +    tx = 0; +  } else if (tx >= bw) { +    tx = bw - 1; +  } +  ty = (int)floor(yMin); +  if (ty < 0) { +    ty = 0; +  } else if (ty >= bh) { +    ty = bh - 1; +  } +  w = (int)ceil(xMax) - tx + 1; +  // NB bw and tx are both non-negative, so 'bw - tx' can't overflow +  if (bw - tx < w) { +    w = bw - tx; +  } +  if (w < 1) { +    w = 1; +  } +  h = (int)ceil(yMax) - ty + 1; +  // NB bh and ty are both non-negative, so 'bh - ty' can't overflow +  if (bh - ty < h) { +    h = bh - ty; +  } +  if (h < 1) { +    h = 1; +  } + +  // optimization: a non-isolated group drawn with alpha=1 and +  // Blend=Normal and backdrop alpha=0 is equivalent to drawing +  // directly onto the backdrop (i.e., a regular non-t-group Form) +  // notes: +  // - if we are already in a non-isolated group, it means the +  //   backdrop alpha is non-zero (otherwise the parent non-isolated +  //   group would have been optimized away) +  // - if there is a soft mask in place, then source alpha is not 1 +  //   (i.e., source alpha = fillOpacity * softMask) +  // - both the parent and child groups must be non-knockout +  if (!isolated && +      !splash->getInNonIsolatedGroup() && +      !knockout && +      !splash->getInKnockoutGroup() && +      !forSoftMask && +      !splash->getSoftMask() && +      state->getFillOpacity() == 1 && +      state->getBlendMode() == gfxBlendNormal && +      splash->checkTransparentRect(tx, ty, w, h)) { +    return gFalse; +  } + +  // push a new stack entry +  transpGroup = new SplashTransparencyGroup(); +  transpGroup->tx = tx; +  transpGroup->ty = ty; +  transpGroup->blendingColorSpace = blendingColorSpace; +  transpGroup->isolated = isolated; +  transpGroup->next = transpGroupStack; +  transpGroupStack = transpGroup; + +  // save state +  transpGroup->origBitmap = bitmap; +  transpGroup->origSplash = splash; + +  //~ this handles the blendingColorSpace arg for soft masks, but +  //~   not yet for transparency groups + +  // switch to the blending color space +  if (forSoftMask && isolated && !knockout && blendingColorSpace) { +    if (blendingColorSpace->getMode() == csDeviceGray || +	blendingColorSpace->getMode() == csCalGray || +	(blendingColorSpace->getMode() == csICCBased && +	 blendingColorSpace->getNComps() == 1)) { +      colorMode = splashModeMono8; +    } else if (blendingColorSpace->getMode() == csDeviceRGB || +	       blendingColorSpace->getMode() == csCalRGB || +	       (blendingColorSpace->getMode() == csICCBased && +		blendingColorSpace->getNComps() == 3)) { +      //~ does this need to use BGR8? +      colorMode = splashModeRGB8; +#if SPLASH_CMYK +    } else if (blendingColorSpace->getMode() == csDeviceCMYK || +	       (blendingColorSpace->getMode() == csICCBased && +		blendingColorSpace->getNComps() == 4)) { +      colorMode = splashModeCMYK8; +#endif +    } +  } + +  // create the temporary bitmap +  traceMessage("t-group bitmap"); +  bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, gTrue, +			    bitmapTopDown, transpGroup->origBitmap);  +  splash = new Splash(bitmap, vectorAntialias, +		      transpGroup->origSplash->getImageCache(), +		      transpGroup->origSplash->getScreen()); +  splash->setMinLineWidth(globalParams->getMinLineWidth()); +  splash->setStrokeAdjust( +		 mapStrokeAdjustMode[globalParams->getStrokeAdjust()]); +  splash->setEnablePathSimplification( +		 globalParams->getEnablePathSimplification()); +  copyState(transpGroup->origSplash, gTrue); +  if (!isolated || knockout) { +    // non-isolated and knockout groups nested in another group will +    // read the parent group bitmap, so we need to force any deferred +    // initialization on the parent +    transpGroup->origSplash->forceDeferredInit(ty, h); +  } +  if (isolated) { +    // isolated group +    backdropBitmap = transpGroup->origBitmap; +    transpGroup->backdropBitmap = NULL; +    if (forSoftMask) { +      // setSoftMask uses the whole bitmap, not just the mod region, +      // so we can't use the deferred initialization optimization +      for (i = 0; i < splashMaxColorComps; ++i) { +	color[i] = 0; +      } +      splash->clear(color, 0); +      splash->setInTransparencyGroup(backdropBitmap, tx, ty, +				     splashGroupDestPreInit, +				     gFalse, knockout); +    } else { +      splash->setInTransparencyGroup(backdropBitmap, tx, ty, +				     splashGroupDestInitZero, +				     gFalse, knockout); +    } +  } else if (transpGroup->origBitmap->getAlphaPtr() && +	     transpGroup->origSplash->getInNonIsolatedGroup() && +	     colorMode != splashModeMono1) { +    // non-isolated group drawn in another non-isolated group: +    // compute a backdrop bitmap with corrected alpha values +    traceMessage("t-group backdrop bitmap"); +    backdropBitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, gTrue, +				      bitmapTopDown, transpGroup->origBitmap); +    transpGroup->origSplash->blitCorrectedAlpha(backdropBitmap, +						tx, ty, 0, 0, w, h); +    transpGroup->backdropBitmap = backdropBitmap; +    if (forSoftMask) { +      // setSoftMask uses the whole bitmap, not just the mod region, +      // so we can't use the deferred initialization optimization +      splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h); +      splash->setInTransparencyGroup(backdropBitmap, 0, 0, +				     splashGroupDestPreInit, +				     gTrue, knockout); +    } else { +      splash->setInTransparencyGroup(backdropBitmap, 0, 0, +				     splashGroupDestInitCopy, +				     gTrue, knockout); +    } +  } else { +    // other non-isolated group +    backdropBitmap = transpGroup->origBitmap; +    transpGroup->backdropBitmap = NULL; +    if (forSoftMask) { +      // setSoftMask uses the whole bitmap, not just the mod region, +      // so we can't use the deferred initialization optimization +      splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h); +      splash->setInTransparencyGroup(backdropBitmap, tx, ty, +				     splashGroupDestPreInit, +				     gTrue, knockout); +    } else { +      splash->setInTransparencyGroup(backdropBitmap, tx, ty, +				     splashGroupDestInitCopy, +				     gTrue, knockout); +    } +  } +  splash->clearModRegion(); +  transpGroup->tBitmap = bitmap; +#if 1 //~tmp +  if (knockout) { +    splash->setInShading(gTrue); +  } +#endif +  state->shiftCTM(-tx, -ty); +  updateCTM(state, 0, 0, 0, 0, 0, 0); +  ++nestCount; + +  return gTrue; +} + +void SplashOutputDev::endTransparencyGroup(GfxState *state) { +  splash->getModRegion(&transpGroupStack->modXMin, &transpGroupStack->modYMin, +		       &transpGroupStack->modXMax, &transpGroupStack->modYMax); + +  // restore state +  --nestCount; +  delete splash; +  bitmap = transpGroupStack->origBitmap; +  colorMode = bitmap->getMode(); +  splash = transpGroupStack->origSplash; +  state->shiftCTM(transpGroupStack->tx, transpGroupStack->ty); +  updateCTM(state, 0, 0, 0, 0, 0, 0); +} + +void SplashOutputDev::paintTransparencyGroup(GfxState *state, double *bbox) { +  SplashBitmap *tBitmap; +  SplashTransparencyGroup *transpGroup; +  GBool isolated; +  int xSrc, ySrc, xDest, yDest, w, h; + +  xSrc = transpGroupStack->modXMin; +  ySrc = transpGroupStack->modYMin; +  xDest = transpGroupStack->tx + transpGroupStack->modXMin; +  yDest = transpGroupStack->ty + transpGroupStack->modYMin; +  w = transpGroupStack->modXMax - transpGroupStack->modXMin + 1; +  h = transpGroupStack->modYMax - transpGroupStack->modYMin + 1; +  tBitmap = transpGroupStack->tBitmap; +  isolated = transpGroupStack->isolated; + +  // paint the transparency group onto the parent bitmap +  // - the clip path was set in the parent's state) +  if (xDest < bitmap->getWidth() && yDest < bitmap->getHeight() && +      w > 0 && h > 0) { +    splash->setOverprintMask(0xffffffff); +    splash->composite(tBitmap, xSrc, ySrc, xDest, yDest, w, h, +		      gFalse, !isolated); +  } + +  // free the temporary backdrop bitmap +  if (transpGroupStack->backdropBitmap) { +    delete transpGroupStack->backdropBitmap; +  } + +  // pop the stack +  transpGroup = transpGroupStack; +  transpGroupStack = transpGroup->next; +  delete transpGroup; + +  delete tBitmap; +} + +void SplashOutputDev::setSoftMask(GfxState *state, double *bbox, +				  GBool alpha, Function *transferFunc, +				  GfxColor *backdropColor) { +  SplashBitmap *softMask, *tBitmap; +  Splash *tSplash; +  SplashTransparencyGroup *transpGroup; +  SplashColor color; +  SplashColorPtr p, colorPtr, colorPtr2; +  Guchar *alphaPtr; +  GfxGray gray; +  GfxRGB rgb; +#if SPLASH_CMYK +  GfxCMYK cmyk; +#endif +  double backdrop, backdrop2, lum, lum2; +  Guchar lum8; +  SplashBitmapRowSize rowSize; +  int tw, th, tNComps, tx, ty, x, y; + +  tx = transpGroupStack->tx; +  ty = transpGroupStack->ty; +  tBitmap = transpGroupStack->tBitmap; + +  // composite with backdrop color +  backdrop = 0; +  if (!alpha && tBitmap->getMode() != splashModeMono1) { +    //~ need to correctly handle the case where no blending color +    //~ space is given +    if (transpGroupStack->blendingColorSpace) { +      tSplash = new Splash(tBitmap, vectorAntialias, +			   transpGroupStack->origSplash->getImageCache(), +			   transpGroupStack->origSplash->getScreen()); +      tSplash->setStrokeAdjust( +		      mapStrokeAdjustMode[globalParams->getStrokeAdjust()]); +      tSplash->setEnablePathSimplification( +		      globalParams->getEnablePathSimplification()); +      switch (tBitmap->getMode()) { +      case splashModeMono1: +	// transparency is not supported in mono1 mode +	break; +      case splashModeMono8: +	transpGroupStack->blendingColorSpace->getGray( +		    backdropColor, &gray, state->getRenderingIntent()); +	backdrop = colToDbl(gray); +	color[0] = colToByte(gray); +	tSplash->compositeBackground(color); +	break; +      case splashModeRGB8: +      case splashModeBGR8: +	transpGroupStack->blendingColorSpace->getRGB( +		    backdropColor, &rgb, state->getRenderingIntent()); +	backdrop = 0.3 * colToDbl(rgb.r) + +	           0.59 * colToDbl(rgb.g) + +	           0.11 * colToDbl(rgb.b); +	color[0] = colToByte(rgb.r); +	color[1] = colToByte(rgb.g); +	color[2] = colToByte(rgb.b); +	tSplash->compositeBackground(color); +	break; +#if SPLASH_CMYK +      case splashModeCMYK8: +	transpGroupStack->blendingColorSpace->getCMYK( +		    backdropColor, &cmyk, state->getRenderingIntent()); +	backdrop = (1 - colToDbl(cmyk.k)) +	           - 0.3 * colToDbl(cmyk.c) +	           - 0.59 * colToDbl(cmyk.m) +	           - 0.11 * colToDbl(cmyk.y); +	if (backdrop < 0) { +	  backdrop = 0; +	} +	color[0] = colToByte(cmyk.c); +	color[1] = colToByte(cmyk.m); +	color[2] = colToByte(cmyk.y); +	color[3] = colToByte(cmyk.k); +	tSplash->compositeBackground(color); +	break; +#endif +      } +      delete tSplash; +    } +  } +  if (transferFunc) { +    transferFunc->transform(&backdrop, &backdrop2); +  } else { +    backdrop2 = backdrop; +  } + +  traceMessage("soft mask bitmap"); +  softMask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), +			      1, splashModeMono8, gFalse, gTrue, bitmap); +  memset(softMask->getDataPtr(), (int)(backdrop2 * 255.0 + 0.5), +	 softMask->getRowSize() * softMask->getHeight()); +  if (tx < softMask->getWidth() && ty < softMask->getHeight()) { +    p = softMask->getDataPtr() + ty * softMask->getRowSize() + tx; +    tw = tBitmap->getWidth(); +    th = tBitmap->getHeight(); +    rowSize = softMask->getRowSize(); +    if (alpha) { +      alphaPtr = tBitmap->getAlphaPtr(); +      for (y = 0; y < th; ++y) { +	for (x = 0; x < tw; ++x) { +	  lum = *alphaPtr++ / 255.0; +	  if (transferFunc) { +	    transferFunc->transform(&lum, &lum2); +	  } else { +	    lum2 = lum; +	  } +	  p[x] = (Guchar)(lum2 * 255.0 + 0.5); +	} +	p += rowSize; +      } +    } else { +      colorPtr = tBitmap->getDataPtr(); +      tNComps = splashColorModeNComps[tBitmap->getMode()]; +      lum8 = 0;  // make gcc happy +      for (y = 0; y < th; ++y) { +	colorPtr2 = colorPtr; +	for (x = 0; x < tw; ++x) { +	  // convert to luminosity +	  switch (tBitmap->getMode()) { +	  case splashModeMono1: +	    lum8 = 0; +	    break; +	  case splashModeMono8: +	    lum8 = colorPtr2[0]; +	    break; +	  case splashModeRGB8: +	  case splashModeBGR8: +	    // [0.3, 0.59, 0.11] * 255 = [77, 150, 28] +	    lum8 = div255(77 * colorPtr2[0] + +			  150 * colorPtr2[1] + +			  28 * colorPtr2[1]); +	    break; +#if SPLASH_CMYK +	  case splashModeCMYK8: +	    lum8 = clip255(255 - colorPtr2[3] +			   - div255(77 * colorPtr2[0] + +				    150 * colorPtr2[1] + +				    28 * colorPtr2[2])); +	    break; +#endif +	  } +	  if (transferFunc) { +	    lum = lum8 / 255.0; +	    transferFunc->transform(&lum, &lum2); +	    lum8 = (Guchar)(lum2 * 255.0 + 0.5); +	  } +	  p[x] = lum8; +	  colorPtr2 += tNComps; +	} +	p += rowSize; +	colorPtr += tBitmap->getRowSize(); +      } +    } +  } +  splash->setSoftMask(softMask); + +  // free the temporary backdrop bitmap +  if (transpGroupStack->backdropBitmap) { +    delete transpGroupStack->backdropBitmap; +  } + +  // pop the stack +  transpGroup = transpGroupStack; +  transpGroupStack = transpGroup->next; +  delete transpGroup; + +  delete tBitmap; +} + +void SplashOutputDev::clearSoftMask(GfxState *state) { +  splash->setSoftMask(NULL); +} + +void SplashOutputDev::setPaperColor(SplashColorPtr paperColorA) { +  splashColorCopy(paperColor, paperColorA); +} + +int SplashOutputDev::getBitmapWidth() { +  return bitmap->getWidth(); +} + +int SplashOutputDev::getBitmapHeight() { +  return bitmap->getHeight(); +} + +SplashBitmap *SplashOutputDev::takeBitmap() { +  SplashBitmap *ret; + +  ret = bitmap; +  bitmap = new SplashBitmap(1, 1, bitmapRowPad, colorMode, +			    colorMode != splashModeMono1, bitmapTopDown, NULL); +  return ret; +} + +void SplashOutputDev::getModRegion(int *xMin, int *yMin, +				   int *xMax, int *yMax) { +  splash->getModRegion(xMin, yMin, xMax, yMax); +} + +void SplashOutputDev::clearModRegion() { +  splash->clearModRegion(); +} + +void SplashOutputDev::setFillColor(int r, int g, int b) { +  GfxRGB rgb; +  GfxGray gray; +#if SPLASH_CMYK +  GfxCMYK cmyk; +#endif + +  rgb.r = byteToCol((Guchar)r); +  rgb.g = byteToCol((Guchar)g); +  rgb.b = byteToCol((Guchar)b); +  switch (colorMode) { +  case splashModeMono1: +  case splashModeMono8: +    gray = (GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.g + 0.5); +    if (gray > gfxColorComp1) { +      gray = gfxColorComp1; +    } +    splash->setFillPattern(getColor(gray)); +    break; +  case splashModeRGB8: +  case splashModeBGR8: +    splash->setFillPattern(getColor(&rgb)); +    break; +#if SPLASH_CMYK +  case splashModeCMYK8: +    cmyk.c = gfxColorComp1 - rgb.r; +    cmyk.m = gfxColorComp1 - rgb.g; +    cmyk.y = gfxColorComp1 - rgb.b; +    cmyk.k = 0; +    splash->setFillPattern(getColor(&cmyk)); +    break; +#endif +  } +} + +SplashFont *SplashOutputDev::getFont(GString *name, SplashCoord *textMatA) { +  Ref ref; +  SplashOutFontFileID *id; +  GfxFontLoc *fontLoc; +#if LOAD_FONTS_FROM_MEM +  GString *fontBuf; +  FILE *extFontFile; +  char blk[4096]; +  int n; +#endif +  SplashFontFile *fontFile; +  SplashFont *fontObj; +  FoFiTrueType *ff; +  int *codeToGID; +  Unicode u; +  SplashCoord textMat[4]; +  SplashCoord oblique; +  int cmap, cmapPlatform, cmapEncoding, i; + +  for (i = 0; i < nBuiltinFonts; ++i) { +    if (!name->cmp(builtinFonts[i].name)) { +      break; +    } +  } +  if (i == nBuiltinFonts) { +    return NULL; +  } +  ref.num = i; +  ref.gen = -1; +  id = new SplashOutFontFileID(&ref); + +  // check the font file cache +  if ((fontFile = fontEngine->getFontFile(id))) { +    delete id; + +  // load the font file +  } else { +    if (!(fontLoc = GfxFont::locateBase14Font(name))) { +      return NULL; +    } +#if LOAD_FONTS_FROM_MEM +    fontBuf = NULL; +    if (fontLoc->fontType == fontType1 || +	fontLoc->fontType == fontTrueType) { +      if (!(extFontFile = fopen(fontLoc->path->getCString(), "rb"))) { +	delete fontLoc; +	delete id; +	return NULL; +      } +      fontBuf = new GString(); +      while ((n = fread(blk, 1, sizeof(blk), extFontFile)) > 0) { +	fontBuf->append(blk, n); +      } +      fclose(extFontFile); +    } +#endif +    if (fontLoc->fontType == fontType1) { +      fontFile = fontEngine->loadType1Font(id, +#if LOAD_FONTS_FROM_MEM +					   fontBuf, +#else +					   fontLoc->path->getCString(), +					   gFalse, +#endif +					   winAnsiEncoding); +    } else if (fontLoc->fontType == fontTrueType) { +#if LOAD_FONTS_FROM_MEM +      if (!(ff = FoFiTrueType::make(fontBuf->getCString(), +				    fontBuf->getLength(), +				    fontLoc->fontNum))) { +#else +      if (!(ff = FoFiTrueType::load(fontLoc->path->getCString(), +				    fontLoc->fontNum))) { +#endif +	delete fontLoc; +	delete id; +	return NULL; +      } +      for (cmap = 0; cmap < ff->getNumCmaps(); ++cmap) { +	cmapPlatform = ff->getCmapPlatform(cmap); +	cmapEncoding = ff->getCmapEncoding(cmap); +	if ((cmapPlatform == 3 && cmapEncoding == 1) || +	    (cmapPlatform == 0 && cmapEncoding <= 4)) { +	  break; +	} +      } +      if (cmap == ff->getNumCmaps()) { +	delete ff; +	delete fontLoc; +	delete id; +	return NULL; +      } +      codeToGID = (int *)gmallocn(256, sizeof(int)); +      for (i = 0; i < 256; ++i) { +	codeToGID[i] = 0; +	if (winAnsiEncoding[i] && +	    (u = globalParams->mapNameToUnicode(winAnsiEncoding[i]))) { +	  codeToGID[i] = ff->mapCodeToGID(cmap, u); +	} +      } +      delete ff; +      fontFile = fontEngine->loadTrueTypeFont(id, +#if LOAD_FONTS_FROM_MEM +					      fontBuf, +#else +					      fontLoc->path->getCString(), +					      gFalse, +#endif +					      fontLoc->fontNum, +					      codeToGID, 256, NULL); +    } else { +      delete fontLoc; +      delete id; +      return NULL; +    } +    delete fontLoc; +  } +  if (!fontFile) { +    return NULL; +  } + +  // create the scaled font +  oblique = (SplashCoord) +              ((SplashOutFontFileID *)fontFile->getID())->getOblique(); +  textMat[0] = (SplashCoord)textMatA[0]; +  textMat[1] = (SplashCoord)textMatA[1]; +  textMat[2] = oblique * textMatA[0] + textMatA[2]; +  textMat[3] = oblique * textMatA[1] + textMatA[3]; +  fontObj = fontEngine->getFont(fontFile, textMat, splash->getMatrix()); + +  return fontObj; +} + +// This is called when initializing a temporary Splash object for Type +// 3 characters and transparency groups.  Acrobat apparently copies at +// least the fill and stroke colors, and the line parameters. +//~ not sure what else should be copied -- the PDF spec is unclear +//~   - fill and stroke alpha? +void SplashOutputDev::copyState(Splash *oldSplash, GBool copyColors) { +  // cached Type 3 chars set a color, so no need to copy the color here +  if (copyColors) { +    splash->setFillPattern(oldSplash->getFillPattern()->copy()); +    splash->setStrokePattern(oldSplash->getStrokePattern()->copy()); +  } +  splash->setLineDash(oldSplash->getLineDash(), +		      oldSplash->getLineDashLength(), +		      oldSplash->getLineDashPhase()); +  splash->setLineCap(oldSplash->getLineCap()); +  splash->setLineJoin(oldSplash->getLineJoin()); +  splash->setLineWidth(oldSplash->getLineWidth()); +} + +#if 1 //~tmp: turn off anti-aliasing temporarily +// This was originally used with gradient shadings -- that's no longer +// necessary, now that shadings are all converted to device space +// images.  It's still used with knockout groups, however, because the +// rasterizer doesn't properly separate opacity and shape. +void SplashOutputDev::setInShading(GBool sh) { +  splash->setInShading(sh); +} +#endif  | 
