//======================================================================== // // SplashOutputDev.cc // // Copyright 2003-2013 Glyph & Cog, LLC // //======================================================================== #include #ifdef USE_GCC_PRAGMAS #pragma implementation #endif #include #include #include #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