//======================================================================== // // SplashOutputDev.cc // // Copyright 2003 Glyph & Cog, LLC // //======================================================================== //======================================================================== // // Modified under the Poppler project - http://poppler.freedesktop.org // // All changes made under the Poppler project to this file are licensed // under GPL version 2 or later // // Copyright (C) 2005 Takashi Iwai // Copyright (C) 2006 Stefan Schweizer // Copyright (C) 2006-2020 Albert Astals Cid // Copyright (C) 2006 Krzysztof Kowalczyk // Copyright (C) 2006 Scott Turner // Copyright (C) 2007 Koji Otani // Copyright (C) 2009 Petr Gajdos // Copyright (C) 2009-2016, 2020 Thomas Freitag // Copyright (C) 2009 Carlos Garcia Campos // Copyright (C) 2009, 2014-2016, 2019 William Bader // Copyright (C) 2010 Patrick Spendrin // Copyright (C) 2010 Brian Cameron // Copyright (C) 2010 Paweł Wiejacha // Copyright (C) 2010 Christian Feuersänger // Copyright (C) 2011 Andreas Hartmetz // Copyright (C) 2011 Andrea Canciani // Copyright (C) 2011, 2012, 2017 Adrian Johnson // Copyright (C) 2013 Lu Wang // Copyright (C) 2013 Li Junling // Copyright (C) 2014 Ed Porras // Copyright (C) 2014 Richard PALO // Copyright (C) 2015 Tamas Szekeres // Copyright (C) 2015 Kenji Uno // Copyright (C) 2016 Takahiro Hashimoto // Copyright (C) 2017 Even Rouault // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, . Work sponsored by the LiMux project of the city of Munich // Copyright (C) 2018, 2019 Stefan Brüns // Copyright (C) 2018 Adam Reichold // Copyright (C) 2019 Christian Persch // Copyright (C) 2020 Oliver Sander // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git // //======================================================================== #include #include #include #include "goo/gfile.h" #include "GlobalParams.h" #include "Error.h" #include "Object.h" #include "Gfx.h" #include "GfxFont.h" #include "Page.h" #include "PDFDoc.h" #include "Link.h" #include "FontEncodingTables.h" #include "fofi/FoFiTrueType.h" #include "splash/SplashBitmap.h" #include "splash/SplashGlyphBitmap.h" #include "splash/SplashPattern.h" #include "splash/SplashScreen.h" #include "splash/SplashPath.h" #include "splash/SplashState.h" #include "splash/SplashErrorCodes.h" #include "splash/SplashFontEngine.h" #include "splash/SplashFont.h" #include "splash/SplashFontFile.h" #include "splash/SplashFontFileID.h" #include "splash/SplashMath.h" #include "splash/Splash.h" #include "SplashOutputDev.h" #include static const double s_minLineWidth = 0.0; static inline void convertGfxColor(SplashColorPtr dest, const SplashColorMode colorMode, const GfxColorSpace *colorSpace, const GfxColor *src) { SplashColor color; GfxGray gray; GfxRGB rgb; GfxCMYK cmyk; GfxColor deviceN; // make gcc happy color[0] = color[1] = color[2] = 0; color[3] = 0; switch (colorMode) { case splashModeMono1: case splashModeMono8: colorSpace->getGray(src, &gray); color[0] = colToByte(gray); break; case splashModeXBGR8: color[3] = 255; // fallthrough case splashModeBGR8: case splashModeRGB8: colorSpace->getRGB(src, &rgb); color[0] = colToByte(rgb.r); color[1] = colToByte(rgb.g); color[2] = colToByte(rgb.b); break; case splashModeCMYK8: colorSpace->getCMYK(src, &cmyk); color[0] = colToByte(cmyk.c); color[1] = colToByte(cmyk.m); color[2] = colToByte(cmyk.y); color[3] = colToByte(cmyk.k); break; case splashModeDeviceN8: colorSpace->getDeviceN(src, &deviceN); for (int i = 0; i < SPOT_NCOMPS + 4; i++) color[i] = colToByte(deviceN.c[i]); break; } splashColorCopy(dest, color); } // Copy a color according to the color mode. // Use convertGfxShortColor() below when the destination is a bitmap // to avoid overwriting cells. // Calling this in SplashGouraudPattern::getParameterizedColor() fixes bug 90570. // Use convertGfxColor() above when the destination is an array of SPOT_NCOMPS+4 bytes, // to ensure that everything is initialized. static inline void convertGfxShortColor(SplashColorPtr dest, const SplashColorMode colorMode, const GfxColorSpace *colorSpace, const GfxColor *src) { switch (colorMode) { case splashModeMono1: case splashModeMono8: { GfxGray gray; colorSpace->getGray(src, &gray); dest[0] = colToByte(gray); } break; case splashModeXBGR8: dest[3] = 255; // fallthrough case splashModeBGR8: case splashModeRGB8: { GfxRGB rgb; colorSpace->getRGB(src, &rgb); dest[0] = colToByte(rgb.r); dest[1] = colToByte(rgb.g); dest[2] = colToByte(rgb.b); } break; case splashModeCMYK8: { GfxCMYK cmyk; colorSpace->getCMYK(src, &cmyk); dest[0] = colToByte(cmyk.c); dest[1] = colToByte(cmyk.m); dest[2] = colToByte(cmyk.y); dest[3] = colToByte(cmyk.k); } break; case splashModeDeviceN8: { GfxColor deviceN; colorSpace->getDeviceN(src, &deviceN); for (int i = 0; i < SPOT_NCOMPS + 4; i++) dest[i] = colToByte(deviceN.c[i]); } break; } } //------------------------------------------------------------------------ // SplashGouraudPattern //------------------------------------------------------------------------ SplashGouraudPattern::SplashGouraudPattern(bool bDirectColorTranslationA, GfxState *stateA, GfxGouraudTriangleShading *shadingA) { state = stateA; shading = shadingA; bDirectColorTranslation = bDirectColorTranslationA; gfxMode = shadingA->getColorSpace()->getMode(); } SplashGouraudPattern::~SplashGouraudPattern() { } void SplashGouraudPattern::getNonParametrizedTriangle(int i, SplashColorMode mode, double *x0, double *y0, SplashColorPtr color0, double *x1, double *y1, SplashColorPtr color1, double *x2, double *y2, SplashColorPtr color2) { GfxColor c0, c1, c2; shading->getTriangle(i, x0, y0, &c0, x1, y1, &c1, x2, y2, &c2); const GfxColorSpace *srcColorSpace = shading->getColorSpace(); convertGfxColor(color0, mode, srcColorSpace, &c0); convertGfxColor(color1, mode, srcColorSpace, &c1); convertGfxColor(color2, mode, srcColorSpace, &c2); } void SplashGouraudPattern::getParameterizedColor(double colorinterp, SplashColorMode mode, SplashColorPtr dest) { GfxColor src; shading->getParameterizedColor(colorinterp, &src); if (bDirectColorTranslation) { const int colorComps = splashColorModeNComps[mode]; for (int m = 0; m < colorComps; ++m) dest[m] = colToByte(src.c[m]); } else { GfxColorSpace *srcColorSpace = shading->getColorSpace(); convertGfxShortColor(dest, mode, srcColorSpace, &src); } } //------------------------------------------------------------------------ // SplashFunctionPattern //------------------------------------------------------------------------ SplashFunctionPattern::SplashFunctionPattern(SplashColorMode colorModeA, GfxState *stateA, GfxFunctionShading *shadingA) { Matrix ctm; SplashColor defaultColor; GfxColor srcColor; const double *matrix = shadingA->getMatrix(); shading = shadingA; state = stateA; colorMode = colorModeA; state->getCTM(&ctm); double a1 = ctm.m[0]; double b1 = ctm.m[1]; double c1 = ctm.m[2]; double d1 = ctm.m[3]; ctm.m[0] = matrix[0] * a1 + matrix[1] * c1; ctm.m[1] = matrix[0] * b1 + matrix[1] * d1; ctm.m[2] = matrix[2] * a1 + matrix[3] * c1; ctm.m[3] = matrix[2] * b1 + matrix[3] * d1; ctm.m[4] = matrix[4] * a1 + matrix[5] * c1 + ctm.m[4]; ctm.m[5] = matrix[4] * b1 + matrix[5] * d1 + ctm.m[5]; ctm.invertTo(&ictm); gfxMode = shadingA->getColorSpace()->getMode(); shadingA->getColorSpace()->getDefaultColor(&srcColor); shadingA->getDomain(&xMin, &yMin, &xMax, &yMax); convertGfxColor(defaultColor, colorModeA, shadingA->getColorSpace(), &srcColor); } SplashFunctionPattern::~SplashFunctionPattern() { } bool SplashFunctionPattern::getColor(int x, int y, SplashColorPtr c) { GfxColor gfxColor; double xc, yc; ictm.transform(x, y, &xc, &yc); if (xc < xMin || xc > xMax || yc < yMin || yc > yMax) return false; shading->getColor(xc, yc, &gfxColor); convertGfxColor(c, colorMode, shading->getColorSpace(), &gfxColor); return true; } //------------------------------------------------------------------------ // SplashUnivariatePattern //------------------------------------------------------------------------ SplashUnivariatePattern::SplashUnivariatePattern(SplashColorMode colorModeA, GfxState *stateA, GfxUnivariateShading *shadingA) { Matrix ctm; double xMin, yMin, xMax, yMax; shading = shadingA; state = stateA; colorMode = colorModeA; state->getCTM(&ctm); ctm.invertTo(&ictm); // get the function domain t0 = shading->getDomain0(); t1 = shading->getDomain1(); dt = t1 - t0; stateA->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); shadingA->setupCache(&ctm, xMin, yMin, xMax, yMax); gfxMode = shadingA->getColorSpace()->getMode(); } SplashUnivariatePattern::~SplashUnivariatePattern() { } bool SplashUnivariatePattern::getColor(int x, int y, SplashColorPtr c) { GfxColor gfxColor; double xc, yc, t; ictm.transform(x, y, &xc, &yc); if (!getParameter(xc, yc, &t)) return false; const int filled = shading->getColor(t, &gfxColor); if (unlikely(filled < shading->getColorSpace()->getNComps())) { for (int i = filled; i < shading->getColorSpace()->getNComps(); ++i) gfxColor.c[i] = 0; } convertGfxColor(c, colorMode, shading->getColorSpace(), &gfxColor); return true; } bool SplashUnivariatePattern::testPosition(int x, int y) { double xc, yc, t; ictm.transform(x, y, &xc, &yc); if (!getParameter(xc, yc, &t)) return false; return (t0 < t1) ? (t > t0 && t < t1) : (t > t1 && t < t0); } //------------------------------------------------------------------------ // SplashRadialPattern //------------------------------------------------------------------------ #define RADIAL_EPSILON (1. / 1024 / 1024) SplashRadialPattern::SplashRadialPattern(SplashColorMode colorModeA, GfxState *stateA, GfxRadialShading *shadingA) : SplashUnivariatePattern(colorModeA, stateA, shadingA) { SplashColor defaultColor; GfxColor srcColor; shadingA->getCoords(&x0, &y0, &r0, &dx, &dy, &dr); dx -= x0; dy -= y0; dr -= r0; a = dx * dx + dy * dy - dr * dr; if (fabs(a) > RADIAL_EPSILON) inva = 1.0 / a; shadingA->getColorSpace()->getDefaultColor(&srcColor); convertGfxColor(defaultColor, colorModeA, shadingA->getColorSpace(), &srcColor); } SplashRadialPattern::~SplashRadialPattern() { } bool SplashRadialPattern::getParameter(double xs, double ys, double *t) { double b, c, s0, s1; // We want to solve this system of equations: // // 1. (x - xc(s))^2 + (y -yc(s))^2 = rc(s)^2 // 2. xc(s) = x0 + s * (x1 - xo) // 3. yc(s) = y0 + s * (y1 - yo) // 4. rc(s) = r0 + s * (r1 - ro) // // To simplify the system a little, we translate // our coordinates to have the origin in (x0,y0) xs -= x0; ys -= y0; // Then we have to solve the equation: // A*s^2 - 2*B*s + C = 0 // where // A = dx^2 + dy^2 - dr^2 // B = xs*dx + ys*dy + r0*dr // C = xs^2 + ys^2 - r0^2 b = xs * dx + ys * dy + r0 * dr; c = xs * xs + ys * ys - r0 * r0; if (fabs(a) <= RADIAL_EPSILON) { // A is 0, thus the equation simplifies to: // -2*B*s + C = 0 // If B is 0, we can either have no solution or an indeterminate // equation, thus we behave as if we had an invalid solution if (fabs(b) <= RADIAL_EPSILON) return false; s0 = s1 = 0.5 * c / b; } else { double d; d = b * b - a * c; if (d < 0) return false; d = sqrt(d); s0 = b + d; s1 = b - d; // If A < 0, one of the two solutions will have negative radius, // thus it will be ignored. Otherwise we know that s1 <= s0 // (because d >=0 implies b - d <= b + d), so if both are valid it // will be the true solution. s0 *= inva; s1 *= inva; } if (r0 + s0 * dr >= 0) { if (0 <= s0 && s0 <= 1) { *t = t0 + dt * s0; return true; } else if (s0 < 0 && shading->getExtend0()) { *t = t0; return true; } else if (s0 > 1 && shading->getExtend1()) { *t = t1; return true; } } if (r0 + s1 * dr >= 0) { if (0 <= s1 && s1 <= 1) { *t = t0 + dt * s1; return true; } else if (s1 < 0 && shading->getExtend0()) { *t = t0; return true; } else if (s1 > 1 && shading->getExtend1()) { *t = t1; return true; } } return false; } #undef RADIAL_EPSILON //------------------------------------------------------------------------ // SplashAxialPattern //------------------------------------------------------------------------ SplashAxialPattern::SplashAxialPattern(SplashColorMode colorModeA, GfxState *stateA, GfxAxialShading *shadingA) : SplashUnivariatePattern(colorModeA, stateA, shadingA) { SplashColor defaultColor; GfxColor srcColor; shadingA->getCoords(&x0, &y0, &x1, &y1); dx = x1 - x0; dy = y1 - y0; const double mul_denominator = (dx * dx + dy * dy); if (unlikely(mul_denominator == 0)) { mul = 0; } else { mul = 1 / mul_denominator; } shadingA->getColorSpace()->getDefaultColor(&srcColor); convertGfxColor(defaultColor, colorModeA, shadingA->getColorSpace(), &srcColor); } SplashAxialPattern::~SplashAxialPattern() { } bool SplashAxialPattern::getParameter(double xc, double yc, double *t) { double s; xc -= x0; yc -= y0; s = (xc * dx + yc * dy) * mul; if (0 <= s && s <= 1) { *t = t0 + dt * s; } else if (s < 0 && shading->getExtend0()) { *t = t0; } else if (s > 1 && shading->getExtend1()) { *t = t1; } else { return false; } return true; } //------------------------------------------------------------------------ // Type 3 font cache size parameters #define type3FontCacheAssoc 8 #define type3FontCacheMaxSets 8 #define type3FontCacheSize (128 * 1024) //------------------------------------------------------------------------ // Divide a 16-bit value (in [0, 255*255]) by 255, returning an 8-bit result. static inline unsigned char div255(int x) { return (unsigned char)((x + (x >> 8) + 0x80) >> 8); } //------------------------------------------------------------------------ // Blend functions //------------------------------------------------------------------------ static void splashOutBlendMultiply(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { blend[i] = (dest[i] * src[i]) / 255; } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } } static void splashOutBlendScreen(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { blend[i] = dest[i] + src[i] - (dest[i] * src[i]) / 255; } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } } static void splashOutBlendOverlay(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { blend[i] = dest[i] < 0x80 ? (src[i] * 2 * dest[i]) / 255 : 255 - 2 * ((255 - src[i]) * (255 - dest[i])) / 255; } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } } static void splashOutBlendDarken(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { blend[i] = dest[i] < src[i] ? dest[i] : src[i]; } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } } static void splashOutBlendLighten(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { blend[i] = dest[i] > src[i] ? dest[i] : src[i]; } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } } static void splashOutBlendColorDodge(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i, x; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { if (src[i] == 255) { blend[i] = 255; } else { x = (dest[i] * 255) / (255 - src[i]); blend[i] = x <= 255 ? x : 255; } } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } } static void splashOutBlendColorBurn(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i, x; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { if (src[i] == 0) { blend[i] = 0; } else { x = ((255 - dest[i]) * 255) / src[i]; blend[i] = x <= 255 ? 255 - x : 0; } } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } } static void splashOutBlendHardLight(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { blend[i] = src[i] < 0x80 ? (dest[i] * 2 * src[i]) / 255 : 255 - 2 * ((255 - dest[i]) * (255 - src[i])) / 255; } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } } static void splashOutBlendSoftLight(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i, x; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { if (src[i] < 0x80) { blend[i] = dest[i] - (255 - 2 * src[i]) * dest[i] * (255 - dest[i]) / (255 * 255); } else { 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] = dest[i] + (2 * src[i] - 255) * (x - dest[i]) / 255; } } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } } static void splashOutBlendDifference(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { blend[i] = dest[i] < src[i] ? src[i] - dest[i] : dest[i] - src[i]; } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } if (cm == splashModeDeviceN8) { for (i = 4; i < splashColorModeNComps[cm]; ++i) { if (dest[i] == 0 && src[i] == 0) blend[i] = 0; } } } static void splashOutBlendExclusion(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i; if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; } } { for (i = 0; i < splashColorModeNComps[cm]; ++i) { blend[i] = dest[i] + src[i] - (2 * dest[i] * src[i]) / 255; } } if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) { for (i = 0; i < splashColorModeNComps[cm]; ++i) { dest[i] = 255 - dest[i]; src[i] = 255 - src[i]; blend[i] = 255 - blend[i]; } } if (cm == splashModeDeviceN8) { for (i = 4; i < splashColorModeNComps[cm]; ++i) { if (dest[i] == 0 && src[i] == 0) blend[i] = 0; } } } 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, unsigned char *rOut, unsigned char *gOut, unsigned char *bOut) { int lum, rgbMin, rgbMax; 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; } if (rgbMin < 0) { *rOut = (unsigned char)(lum + ((rIn - lum) * lum) / (lum - rgbMin)); *gOut = (unsigned char)(lum + ((gIn - lum) * lum) / (lum - rgbMin)); *bOut = (unsigned char)(lum + ((bIn - lum) * lum) / (lum - rgbMin)); } else if (rgbMax > 255) { *rOut = (unsigned char)(lum + ((rIn - lum) * (255 - lum)) / (rgbMax - lum)); *gOut = (unsigned char)(lum + ((gIn - lum) * (255 - lum)) / (rgbMax - lum)); *bOut = (unsigned char)(lum + ((bIn - lum) * (255 - lum)) / (rgbMax - lum)); } else { *rOut = rIn; *gOut = gIn; *bOut = bIn; } } static void setLum(unsigned char rIn, unsigned char gIn, unsigned char bIn, int lum, unsigned char *rOut, unsigned char *gOut, unsigned char *bOut) { int d; d = lum - getLum(rIn, gIn, bIn); clipColor(rIn + d, gIn + d, bIn + d, rOut, gOut, bOut); } static void setSat(unsigned char rIn, unsigned char gIn, unsigned char bIn, int sat, unsigned char *rOut, unsigned char *gOut, unsigned char *bOut) { int rgbMin, rgbMid, rgbMax; unsigned char *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 = (unsigned char)((rgbMid - rgbMin) * sat) / (rgbMax - rgbMin); *maxOut = (unsigned char)sat; } else { *midOut = *maxOut = 0; } *minOut = 0; } static void splashOutBlendHue(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { unsigned char r0, g0, b0; unsigned char r1, g1, b1; int i; SplashColor src2, dest2; switch (cm) { case splashModeMono1: case splashModeMono8: blend[0] = dest[0]; break; case splashModeXBGR8: src[3] = 255; // fallthrough 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; case splashModeCMYK8: case splashModeDeviceN8: for (i = 0; i < 4; i++) { // convert to additive src2[i] = 0xff - src[i]; dest2[i] = 0xff - dest[i]; } // NB: inputs have already been converted to additive mode setSat(src2[0], src2[1], src2[2], getSat(dest2[0], dest2[1], dest2[2]), &r0, &g0, &b0); setLum(r0, g0, b0, getLum(dest2[0], dest2[1], dest2[2]), &r1, &g1, &b1); blend[0] = r1; blend[1] = g1; blend[2] = b1; blend[3] = dest2[3]; for (i = 0; i < 4; i++) { // convert back to subtractive blend[i] = 0xff - blend[i]; } break; } } static void splashOutBlendSaturation(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { unsigned char r0, g0, b0; unsigned char r1, g1, b1; int i; SplashColor src2, dest2; switch (cm) { case splashModeMono1: case splashModeMono8: blend[0] = dest[0]; break; case splashModeXBGR8: src[3] = 255; // fallthrough 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; case splashModeCMYK8: case splashModeDeviceN8: for (i = 0; i < 4; i++) { // convert to additive src2[i] = 0xff - src[i]; dest2[i] = 0xff - dest[i]; } setSat(dest2[0], dest2[1], dest2[2], getSat(src2[0], src2[1], src2[2]), &r0, &g0, &b0); setLum(r0, g0, b0, getLum(dest2[0], dest2[1], dest2[2]), &r1, &g1, &b1); blend[0] = r1; blend[1] = g1; blend[2] = b1; blend[3] = dest2[3]; for (i = 0; i < 4; i++) { // convert back to subtractive blend[i] = 0xff - blend[i]; } break; } } static void splashOutBlendColor(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { unsigned char r, g, b; int i; SplashColor src2, dest2; switch (cm) { case splashModeMono1: case splashModeMono8: blend[0] = dest[0]; break; case splashModeXBGR8: src[3] = 255; // fallthrough 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; case splashModeCMYK8: case splashModeDeviceN8: for (i = 0; i < 4; i++) { // convert to additive src2[i] = 0xff - src[i]; dest2[i] = 0xff - dest[i]; } setLum(src2[0], src2[1], src2[2], getLum(dest2[0], dest2[1], dest2[2]), &r, &g, &b); blend[0] = r; blend[1] = g; blend[2] = b; blend[3] = dest2[3]; for (i = 0; i < 4; i++) { // convert back to subtractive blend[i] = 0xff - blend[i]; } break; } } static void splashOutBlendLuminosity(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { unsigned char r, g, b; int i; SplashColor src2, dest2; switch (cm) { case splashModeMono1: case splashModeMono8: blend[0] = dest[0]; break; case splashModeXBGR8: src[3] = 255; // fallthrough 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; case splashModeCMYK8: case splashModeDeviceN8: for (i = 0; i < 4; i++) { // convert to additive src2[i] = 0xff - src[i]; dest2[i] = 0xff - dest[i]; } setLum(dest2[0], dest2[1], dest2[2], getLum(src2[0], src2[1], src2[2]), &r, &g, &b); blend[0] = r; blend[1] = g; blend[2] = b; blend[3] = src2[3]; for (i = 0; i < 4; i++) { // convert back to subtractive blend[i] = 0xff - blend[i]; } break; } } // NB: This must match the GfxBlendMode enum defined in GfxState.h. static const SplashBlendFunc splashOutBlendFuncs[] = { nullptr, &splashOutBlendMultiply, &splashOutBlendScreen, &splashOutBlendOverlay, &splashOutBlendDarken, &splashOutBlendLighten, &splashOutBlendColorDodge, &splashOutBlendColorBurn, &splashOutBlendHardLight, &splashOutBlendSoftLight, &splashOutBlendDifference, &splashOutBlendExclusion, &splashOutBlendHue, &splashOutBlendSaturation, &splashOutBlendColor, &splashOutBlendLuminosity }; //------------------------------------------------------------------------ // SplashOutFontFileID //------------------------------------------------------------------------ class SplashOutFontFileID : public SplashFontFileID { public: SplashOutFontFileID(const Ref *rA) { r = *rA; } ~SplashOutFontFileID() override; bool matches(SplashFontFileID *id) override { return ((SplashOutFontFileID *)id)->r == r; } private: Ref r; }; SplashOutFontFileID::~SplashOutFontFileID() = default; //------------------------------------------------------------------------ // T3FontCache //------------------------------------------------------------------------ struct T3FontCacheTag { unsigned short code; unsigned short mru; // valid bit (0x8000) and MRU index }; class T3FontCache { public: T3FontCache(const Ref *fontID, double m11A, double m12A, double m21A, double m22A, int glyphXA, int glyphYA, int glyphWA, int glyphHA, bool validBBoxA, bool aa); ~T3FontCache(); T3FontCache(const T3FontCache &) = delete; T3FontCache &operator=(const T3FontCache &) = delete; bool matches(const Ref *idA, double m11A, double m12A, double m21A, double m22A) { return fontID == *idA && 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 bool 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) unsigned char *cacheData; // glyph pixmap cache T3FontCacheTag *cacheTags; // cache tags, i.e., char codes }; T3FontCache::T3FontCache(const Ref *fontIDA, double m11A, double m12A, double m21A, double m22A, int glyphXA, int glyphYA, int glyphWA, int glyphHA, bool validBBoxA, bool aa) { 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) if (glyphW > INT_MAX / glyphH || glyphW <= 0 || glyphH <= 0 || glyphW * glyphH > 100000) { glyphW = glyphH = 100; validBBox = false; } if (aa) { glyphSize = glyphW * glyphH; } else { glyphSize = ((glyphW + 7) >> 3) * glyphH; } cacheAssoc = type3FontCacheAssoc; for (cacheSets = type3FontCacheMaxSets; cacheSets > 1 && cacheSets * cacheAssoc * glyphSize > type3FontCacheSize; cacheSets >>= 1) ; if (glyphSize < 10485760 / cacheAssoc / cacheSets) { cacheData = (unsigned char *)gmallocn_checkoverflow(cacheSets * cacheAssoc, glyphSize); } else { error(errSyntaxWarning, -1, "Not creating cacheData for T3FontCache, it asked for too much memory.\n" " This could teoretically result in wrong rendering,\n" " but most probably the document is bogus.\n" " Please report a bug if you think the rendering may be wrong because of this."); cacheData = nullptr; } if (cacheData != nullptr) { cacheTags = (T3FontCacheTag *)gmallocn(cacheSets * cacheAssoc, sizeof(T3FontCacheTag)); for (int i = 0; i < cacheSets * cacheAssoc; ++i) { cacheTags[i].mru = i & (cacheAssoc - 1); } } else { cacheTags = nullptr; } } T3FontCache::~T3FontCache() { gfree(cacheData); gfree(cacheTags); } struct T3GlyphStack { unsigned short code; // character code bool haveDx; // set after seeing a d0/d1 operator bool 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 unsigned char *cacheData; // pointer to cache data for the glyph //----- saved state SplashBitmap *origBitmap; Splash *origSplash; double origCTM4, origCTM5; T3GlyphStack *next; // next object on stack }; //------------------------------------------------------------------------ // SplashTransparencyGroup //------------------------------------------------------------------------ struct SplashTransparencyGroup { int tx, ty; // translation coordinates SplashBitmap *tBitmap; // bitmap for transparency group SplashBitmap *softmask; // bitmap for softmasks GfxColorSpace *blendingColorSpace; bool isolated; //----- for knockout SplashBitmap *shape; bool knockout; SplashCoord knockoutOpacity; bool fontAA; //----- saved state SplashBitmap *origBitmap; Splash *origSplash; SplashTransparencyGroup *next; }; //------------------------------------------------------------------------ // SplashOutputDev //------------------------------------------------------------------------ SplashOutputDev::SplashOutputDev(SplashColorMode colorModeA, int bitmapRowPadA, bool reverseVideoA, SplashColorPtr paperColorA, bool bitmapTopDownA, SplashThinLineMode thinLineMode, bool overprintPreviewA) { colorMode = colorModeA; bitmapRowPad = bitmapRowPadA; bitmapTopDown = bitmapTopDownA; fontAntialias = true; vectorAntialias = true; overprintPreview = overprintPreviewA; enableFreeType = true; enableFreeTypeHinting = false; enableSlightHinting = false; setupScreenParams(72.0, 72.0); reverseVideo = reverseVideoA; if (paperColorA != nullptr) { splashColorCopy(paperColor, paperColorA); } else { splashClearColor(paperColor); } skipHorizText = false; skipRotatedText = false; keepAlphaChannel = paperColorA == nullptr; doc = nullptr; bitmap = new SplashBitmap(1, 1, bitmapRowPad, colorMode, colorMode != splashModeMono1, bitmapTopDown); splash = new Splash(bitmap, vectorAntialias, &screenParams); splash->setMinLineWidth(s_minLineWidth); splash->setThinLineMode(thinLineMode); splash->clear(paperColor, 0); fontEngine = nullptr; nT3Fonts = 0; t3GlyphStack = nullptr; font = nullptr; needFontUpdate = false; textClipPath = nullptr; transpGroupStack = nullptr; xref = nullptr; } void SplashOutputDev::setupScreenParams(double hDPI, double vDPI) { screenParams.size = -1; screenParams.dotRadius = -1; screenParams.gamma = (SplashCoord)1.0; screenParams.blackThreshold = (SplashCoord)0.0; screenParams.whiteThreshold = (SplashCoord)1.0; // 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; } delete textClipPath; } void SplashOutputDev::startDoc(PDFDoc *docA) { int i; doc = docA; if (fontEngine) { delete fontEngine; } fontEngine = new SplashFontEngine(enableFreeType, enableFreeTypeHinting, enableSlightHinting, getFontAntialias() && colorMode != splashModeMono1); for (i = 0; i < nT3Fonts; ++i) { delete t3FontCache[i]; } nT3Fonts = 0; } void SplashOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefA) { int w, h; SplashCoord mat[6]; SplashColor color; xref = xrefA; 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; } SplashThinLineMode thinLineMode = splashThinLineDefault; if (splash) { thinLineMode = splash->getThinLineMode(); delete splash; splash = nullptr; } if (!bitmap || w != bitmap->getWidth() || h != bitmap->getHeight()) { if (bitmap) { delete bitmap; bitmap = nullptr; } bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, colorMode != splashModeMono1, bitmapTopDown); if (!bitmap->getDataPtr()) { delete bitmap; w = h = 1; bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, colorMode != splashModeMono1, bitmapTopDown); } } splash = new Splash(bitmap, vectorAntialias, &screenParams); splash->setThinLineMode(thinLineMode); splash->setMinLineWidth(s_minLineWidth); if (state) { const double *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 splashModeXBGR8: color[3] = 255; // fallthrough case splashModeRGB8: case splashModeBGR8: color[0] = color[1] = color[2] = 0; break; case splashModeCMYK8: color[0] = color[1] = color[2] = color[3] = 0; break; case splashModeDeviceN8: splashClearColor(color); break; } splash->setStrokePattern(new SplashSolidColor(color)); splash->setFillPattern(new SplashSolidColor(color)); splash->setLineCap(splashLineCapButt); splash->setLineJoin(splashLineJoinMiter); splash->setLineDash(nullptr, 0, 0); splash->setMiterLimit(10); splash->setFlatness(1); // the SA parameter supposedly defaults to false, but Acrobat // apparently hardwires it to true splash->setStrokeAdjust(true); splash->clear(paperColor, 0); } void SplashOutputDev::endPage() { if (colorMode != splashModeMono1 && !keepAlphaChannel) { splash->compositeBackground(paperColor); } } void SplashOutputDev::saveState(GfxState *state) { splash->saveState(); if (t3GlyphStack && !t3GlyphStack->haveDx) { t3GlyphStack->doNotCache = true; error(errSyntaxWarning, -1, "Save (q) operator before d0/d1 in Type 3 glyph"); } } void SplashOutputDev::restoreState(GfxState *state) { splash->restoreState(); needFontUpdate = true; if (t3GlyphStack && !t3GlyphStack->haveDx) { t3GlyphStack->doNotCache = true; 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); updateFillColorSpace(state); updateFillColor(state); updateStrokeColorSpace(state); updateStrokeColor(state); needFontUpdate = true; } void SplashOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32) { SplashCoord mat[6]; const double *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 splash->setStrokeAdjust(state->getStrokeAdjust()); #endif } void SplashOutputDev::updateFillColorSpace(GfxState *state) { if (colorMode == splashModeDeviceN8) state->getFillColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS); } void SplashOutputDev::updateStrokeColorSpace(GfxState *state) { if (colorMode == splashModeDeviceN8) state->getStrokeColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS); } void SplashOutputDev::updateFillColor(GfxState *state) { GfxGray gray; GfxRGB rgb; GfxCMYK cmyk; GfxColor deviceN; switch (colorMode) { case splashModeMono1: case splashModeMono8: state->getFillGray(&gray); splash->setFillPattern(getColor(gray)); break; case splashModeXBGR8: case splashModeRGB8: case splashModeBGR8: state->getFillRGB(&rgb); splash->setFillPattern(getColor(&rgb)); break; case splashModeCMYK8: state->getFillCMYK(&cmyk); splash->setFillPattern(getColor(&cmyk)); break; case splashModeDeviceN8: state->getFillDeviceN(&deviceN); splash->setFillPattern(getColor(&deviceN)); break; } } void SplashOutputDev::updateStrokeColor(GfxState *state) { GfxGray gray; GfxRGB rgb; GfxCMYK cmyk; GfxColor deviceN; switch (colorMode) { case splashModeMono1: case splashModeMono8: state->getStrokeGray(&gray); splash->setStrokePattern(getColor(gray)); break; case splashModeXBGR8: case splashModeRGB8: case splashModeBGR8: state->getStrokeRGB(&rgb); splash->setStrokePattern(getColor(&rgb)); break; case splashModeCMYK8: state->getStrokeCMYK(&cmyk); splash->setStrokePattern(getColor(&cmyk)); break; case splashModeDeviceN8: state->getStrokeDeviceN(&deviceN); splash->setStrokePattern(getColor(&deviceN)); break; } } SplashPattern *SplashOutputDev::getColor(GfxGray gray) { SplashColor color; if (reverseVideo) { gray = gfxColorComp1 - gray; } color[0] = colToByte(gray); return new SplashSolidColor(color); } SplashPattern *SplashOutputDev::getColor(GfxRGB *rgb) { GfxColorComp r, g, b; SplashColor color; 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 (colorMode == splashModeXBGR8) color[3] = 255; return new SplashSolidColor(color); } SplashPattern *SplashOutputDev::getColor(GfxCMYK *cmyk) { SplashColor color; color[0] = colToByte(cmyk->c); color[1] = colToByte(cmyk->m); color[2] = colToByte(cmyk->y); color[3] = colToByte(cmyk->k); return new SplashSolidColor(color); } SplashPattern *SplashOutputDev::getColor(GfxColor *deviceN) { SplashColor color; for (int i = 0; i < 4 + SPOT_NCOMPS; i++) color[i] = colToByte(deviceN->c[i]); return new SplashSolidColor(color); } void SplashOutputDev::getMatteColor(SplashColorMode colorMode, GfxImageColorMap *colorMap, const GfxColor *matteColorIn, SplashColor matteColor) { GfxGray gray; GfxRGB rgb; GfxCMYK cmyk; GfxColor deviceN; switch (colorMode) { case splashModeMono1: case splashModeMono8: colorMap->getColorSpace()->getGray(matteColorIn, &gray); matteColor[0] = colToByte(gray); break; case splashModeRGB8: case splashModeBGR8: colorMap->getColorSpace()->getRGB(matteColorIn, &rgb); matteColor[0] = colToByte(rgb.r); matteColor[1] = colToByte(rgb.g); matteColor[2] = colToByte(rgb.b); break; case splashModeXBGR8: colorMap->getColorSpace()->getRGB(matteColorIn, &rgb); matteColor[0] = colToByte(rgb.r); matteColor[1] = colToByte(rgb.g); matteColor[2] = colToByte(rgb.b); matteColor[3] = 255; break; case splashModeCMYK8: colorMap->getColorSpace()->getCMYK(matteColorIn, &cmyk); matteColor[0] = colToByte(cmyk.c); matteColor[1] = colToByte(cmyk.m); matteColor[2] = colToByte(cmyk.y); matteColor[3] = colToByte(cmyk.k); break; case splashModeDeviceN8: colorMap->getColorSpace()->getDeviceN(matteColorIn, &deviceN); for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) matteColor[cp] = colToByte(deviceN.c[cp]); break; } } void SplashOutputDev::setOverprintMask(GfxColorSpace *colorSpace, bool overprintFlag, int overprintMode, const GfxColor *singleColor, bool grayIndexed) { unsigned int mask; GfxCMYK cmyk; bool additive = false; int i; if (colorSpace->getMode() == csIndexed) { setOverprintMask(((GfxIndexedColorSpace *)colorSpace)->getBase(), overprintFlag, overprintMode, singleColor, grayIndexed); return; } if (overprintFlag && overprintPreview) { mask = colorSpace->getOverprintMask(); if (singleColor && overprintMode && colorSpace->getMode() == csDeviceCMYK) { colorSpace->getCMYK(singleColor, &cmyk); if (cmyk.c == 0) { mask &= ~1; } if (cmyk.m == 0) { mask &= ~2; } if (cmyk.y == 0) { mask &= ~4; } if (cmyk.k == 0) { mask &= ~8; } } if (grayIndexed) { mask &= ~7; } else if (colorSpace->getMode() == csSeparation) { GfxSeparationColorSpace *deviceSep = (GfxSeparationColorSpace *)colorSpace; additive = deviceSep->getName()->cmp("All") != 0 && mask == 0x0f && !deviceSep->isNonMarking(); } else if (colorSpace->getMode() == csDeviceN) { GfxDeviceNColorSpace *deviceNCS = (GfxDeviceNColorSpace *)colorSpace; additive = mask == 0x0f && !deviceNCS->isNonMarking(); for (i = 0; i < deviceNCS->getNComps() && additive; i++) { if (deviceNCS->getColorantName(i) == "Cyan") { additive = false; } else if (deviceNCS->getColorantName(i) == "Magenta") { additive = false; } else if (deviceNCS->getColorantName(i) == "Yellow") { additive = false; } else if (deviceNCS->getColorantName(i) == "Black") { additive = false; } } } } else { mask = 0xffffffff; } splash->setOverprintMask(mask, additive); } void SplashOutputDev::updateBlendMode(GfxState *state) { splash->setBlendFunc(splashOutBlendFuncs[state->getBlendMode()]); } void SplashOutputDev::updateFillOpacity(GfxState *state) { splash->setFillAlpha((SplashCoord)state->getFillOpacity()); if (transpGroupStack != nullptr && (SplashCoord)state->getFillOpacity() < transpGroupStack->knockoutOpacity) { transpGroupStack->knockoutOpacity = (SplashCoord)state->getFillOpacity(); } } void SplashOutputDev::updateStrokeOpacity(GfxState *state) { splash->setStrokeAlpha((SplashCoord)state->getStrokeOpacity()); if (transpGroupStack != nullptr && (SplashCoord)state->getStrokeOpacity() < transpGroupStack->knockoutOpacity) { transpGroupStack->knockoutOpacity = (SplashCoord)state->getStrokeOpacity(); } } void SplashOutputDev::updatePatternOpacity(GfxState *state) { splash->setPatternAlpha((SplashCoord)state->getStrokeOpacity(), (SplashCoord)state->getFillOpacity()); } void SplashOutputDev::clearPatternOpacity(GfxState *state) { splash->clearPatternAlpha(); } void SplashOutputDev::updateFillOverprint(GfxState *state) { splash->setFillOverprint(state->getFillOverprint()); } void SplashOutputDev::updateStrokeOverprint(GfxState *state) { splash->setStrokeOverprint(state->getStrokeOverprint()); } void SplashOutputDev::updateOverprintMode(GfxState *state) { splash->setOverprintMode(state->getOverprintMode()); } void SplashOutputDev::updateTransfer(GfxState *state) { Function **transfer; unsigned char 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] = (unsigned char)(y * 255.0 + 0.5); transfer[1]->transform(&x, &y); green[i] = (unsigned char)(y * 255.0 + 0.5); transfer[2]->transform(&x, &y); blue[i] = (unsigned char)(y * 255.0 + 0.5); transfer[3]->transform(&x, &y); gray[i] = (unsigned char)(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] = (unsigned char)(y * 255.0 + 0.5); } } } else { for (i = 0; i < 256; ++i) { red[i] = green[i] = blue[i] = gray[i] = (unsigned char)i; } } splash->setTransfer(red, green, blue, gray); } void SplashOutputDev::updateFont(GfxState * /*state*/) { needFontUpdate = true; } void SplashOutputDev::doUpdateFont(GfxState *state) { GfxFont *gfxFont; GfxFontLoc *fontLoc; GfxFontType fontType; SplashOutFontFileID *id = nullptr; SplashFontFile *fontFile; SplashFontSrc *fontsrc = nullptr; FoFiTrueType *ff; GooString *fileName; char *tmpBuf; int tmpBufLen; const double *textMat; double m11, m12, m21, m22, fontSize; int faceIndex = 0; SplashCoord mat[4]; bool recreateFont = false; bool doAdjustFontMatrix = false; needFontUpdate = false; font = nullptr; fileName = nullptr; tmpBuf = nullptr; fontLoc = nullptr; if (!(gfxFont = state->getFont())) { goto err1; } fontType = gfxFont->getType(); if (fontType == fontType3) { goto err1; } // sanity-check the font size - skip anything larger than 10 inches // (this avoids problems allocating memory for the font cache) if (state->getTransformedFontSize() > 10 * (state->getHDPI() + state->getVDPI())) { goto err1; } // check the font file cache reload: delete id; delete fontLoc; fontLoc = nullptr; if (fontsrc && !fontsrc->isFile) { fontsrc->unref(); fontsrc = nullptr; } id = new SplashOutFontFileID(gfxFont->getID()); if ((fontFile = fontEngine->getFontFile(id))) { delete id; } else { if (!(fontLoc = gfxFont->locateFont((xref) ? xref : doc->getXRef(), nullptr))) { error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); goto err2; } // embedded font if (fontLoc->locType == gfxFontLocEmbedded) { // if there is an embedded font, read it to memory tmpBuf = gfxFont->readEmbFontFile((xref) ? xref : doc->getXRef(), &tmpBufLen); if (!tmpBuf) goto err2; // external font } else { // gfxFontLocExternal fileName = fontLoc->path; fontType = fontLoc->fontType; doAdjustFontMatrix = true; } fontsrc = new SplashFontSrc; if (fileName) fontsrc->setFile(fileName, false); else fontsrc->setBuf(tmpBuf, tmpBufLen, true); // load the font file switch (fontType) { case fontType1: if (!(fontFile = fontEngine->loadType1Font(id, fontsrc, (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); if (gfxFont->invalidateEmbeddedFont()) goto reload; goto err2; } break; case fontType1C: if (!(fontFile = fontEngine->loadType1CFont(id, fontsrc, (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); if (gfxFont->invalidateEmbeddedFont()) goto reload; goto err2; } break; case fontType1COT: if (!(fontFile = fontEngine->loadOpenTypeT1CFont(id, fontsrc, (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); if (gfxFont->invalidateEmbeddedFont()) goto reload; goto err2; } break; case fontTrueType: case fontTrueTypeOT: { if (fileName) ff = FoFiTrueType::load(fileName->c_str()); else ff = FoFiTrueType::make(tmpBuf, tmpBufLen); int *codeToGID; const int n = ff ? 256 : 0; if (ff) { codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff); 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 (int i = 0; i < 256; ++i) { if (codeToGID[i] == 0) { codeToGID[i] = -1; } } } } else { codeToGID = nullptr; } if (!(fontFile = fontEngine->loadTrueTypeFont(id, fontsrc, codeToGID, n))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); if (gfxFont->invalidateEmbeddedFont()) goto reload; goto err2; } break; } case fontCIDType0: case fontCIDType0C: if (!(fontFile = fontEngine->loadCIDFont(id, fontsrc))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); if (gfxFont->invalidateEmbeddedFont()) goto reload; goto err2; } break; case fontCIDType0COT: { int *codeToGID; int n; if (((GfxCIDFont *)gfxFont)->getCIDToGID()) { n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); codeToGID = (int *)gmallocn(n, sizeof(int)); memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), n * sizeof(int)); } else { codeToGID = nullptr; n = 0; } if (!(fontFile = fontEngine->loadOpenTypeCFFFont(id, fontsrc, codeToGID, n))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); gfree(codeToGID); if (gfxFont->invalidateEmbeddedFont()) goto reload; goto err2; } break; } case fontCIDType2: case fontCIDType2OT: { int *codeToGID = nullptr; int n = 0; if (((GfxCIDFont *)gfxFont)->getCIDToGID()) { n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); if (n) { codeToGID = (int *)gmallocn(n, sizeof(int)); memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), n * sizeof(int)); } } else { if (fileName) ff = FoFiTrueType::load(fileName->c_str()); else ff = FoFiTrueType::make(tmpBuf, tmpBufLen); if (!ff) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); goto err2; } codeToGID = ((GfxCIDFont *)gfxFont)->getCodeToGIDMap(ff, &n); delete ff; } if (!(fontFile = fontEngine->loadTrueTypeFont(id, fontsrc, codeToGID, n, faceIndex))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); if (gfxFont->invalidateEmbeddedFont()) goto reload; goto err2; } break; } default: // this shouldn't happen goto err2; } fontFile->doAdjustMatrix = doAdjustFontMatrix; } // get the font matrix textMat = state->getTextMat(); fontSize = state->getFontSize(); m11 = textMat[0] * fontSize * state->getHorizScaling(); m12 = textMat[1] * fontSize * state->getHorizScaling(); m21 = textMat[2] * fontSize; m22 = textMat[3] * fontSize; // create the scaled font mat[0] = m11; mat[1] = m12; mat[2] = m21; mat[3] = m22; font = fontEngine->getFont(fontFile, mat, splash->getMatrix()); // for substituted fonts: adjust the font matrix -- compare the // width of 'm' in the original font and the substituted font if (fontFile->doAdjustMatrix && !gfxFont->isCIDFont()) { double w1, w2, w3; CharCode code; const char *name; for (code = 0; code < 256; ++code) { if ((name = ((Gfx8BitFont *)gfxFont)->getCharName(code)) && name[0] == 'm' && name[1] == '\0') { break; } } if (code < 256) { w1 = ((Gfx8BitFont *)gfxFont)->getWidth(code); w2 = font->getGlyphAdvance(code); w3 = ((Gfx8BitFont *)gfxFont)->getWidth(0); if (!gfxFont->isSymbolic() && w2 > 0 && w1 > w3) { // if real font is substantially narrower than substituted // font, reduce the font size accordingly if (w1 > 0.01 && w1 < 0.9 * w2) { w1 /= w2; m11 *= w1; m21 *= w1; recreateFont = true; } } } } if (recreateFont) { mat[0] = m11; mat[1] = m12; mat[2] = m21; mat[3] = m22; font = fontEngine->getFont(fontFile, mat, splash->getMatrix()); } delete fontLoc; if (fontsrc && !fontsrc->isFile) fontsrc->unref(); return; err2: delete id; delete fontLoc; err1: if (fontsrc && !fontsrc->isFile) fontsrc->unref(); return; } void SplashOutputDev::stroke(GfxState *state) { if (state->getStrokeColorSpace()->isNonMarking()) { return; } setOverprintMask(state->getStrokeColorSpace(), state->getStrokeOverprint(), state->getOverprintMode(), state->getStrokeColor()); SplashPath path = convertPath(state, state->getPath(), false); splash->stroke(&path); } void SplashOutputDev::fill(GfxState *state) { if (state->getFillColorSpace()->isNonMarking()) { return; } setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(), state->getOverprintMode(), state->getFillColor()); SplashPath path = convertPath(state, state->getPath(), true); splash->fill(&path, false); } void SplashOutputDev::eoFill(GfxState *state) { if (state->getFillColorSpace()->isNonMarking()) { return; } setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(), state->getOverprintMode(), state->getFillColor()); SplashPath path = convertPath(state, state->getPath(), true); splash->fill(&path, true); } void SplashOutputDev::clip(GfxState *state) { SplashPath path = convertPath(state, state->getPath(), true); splash->clipToPath(&path, false); } void SplashOutputDev::eoClip(GfxState *state) { SplashPath path = convertPath(state, state->getPath(), true); splash->clipToPath(&path, true); } void SplashOutputDev::clipToStrokePath(GfxState *state) { SplashPath *path2; SplashPath path = convertPath(state, state->getPath(), false); path2 = splash->makeStrokePath(&path, state->getLineWidth()); splash->clipToPath(path2, false); delete path2; } SplashPath SplashOutputDev::convertPath(GfxState *state, const GfxPath *path, bool dropEmptySubpaths) { SplashPath sPath; int n, i, j; n = dropEmptySubpaths ? 1 : 0; for (i = 0; i < path->getNumSubpaths(); ++i) { const GfxSubpath *subpath = path->getSubpath(i); if (subpath->getNumPoints() > n) { sPath.reserve(subpath->getNumPoints() + 1); 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, const Unicode *u, int uLen) { SplashPath *path; int render; bool doFill, doStroke, doClip, strokeAdjust; double m[4]; bool horiz; 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; } } // 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 = nullptr; SplashCoord lineWidth = splash->getLineWidth(); if (doStroke && lineWidth == 0.0) splash->setLineWidth(1 / state->getVDPI()); 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 = false; // make gcc happy if (doStroke) { strokeAdjust = splash->getStrokeAdjust(); splash->setStrokeAdjust(false); } // fill and stroke if (doFill && doStroke) { if (path) { setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(), state->getOverprintMode(), state->getFillColor()); splash->fill(path, false); setOverprintMask(state->getStrokeColorSpace(), state->getStrokeOverprint(), state->getOverprintMode(), state->getStrokeColor()); splash->stroke(path); } // fill } else if (doFill) { setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(), state->getOverprintMode(), state->getFillColor()); splash->fillChar((SplashCoord)x, (SplashCoord)y, code, font); // stroke } else if (doStroke) { if (path) { setOverprintMask(state->getStrokeColorSpace(), state->getStrokeOverprint(), state->getOverprintMode(), state->getStrokeColor()); splash->stroke(path); } } splash->setLineWidth(lineWidth); // clip if (doClip) { if (path) { if (textClipPath) { textClipPath->append(path); } else { textClipPath = path; path = nullptr; } } } if (doStroke) { splash->setStrokeAdjust(strokeAdjust); } if (path) { delete path; } } bool SplashOutputDev::beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, const Unicode *u, int uLen) { GfxFont *gfxFont; const Ref *fontID; const double *ctm, *bbox; T3FontCache *t3Font; T3GlyphStack *t3gs; bool validBBox; double m[4]; bool horiz; double x1, y1, xMin, yMin, xMax, yMax, xt, yt; int i, j; // check for invisible text -- this is used by Acrobat Capture if (state->getRender() == 3) { // this is a bit of cheating, we say yes, font is already on cache // so we actually skip the rendering of it return true; } 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 true; } } if (!(gfxFont = state->getFont())) { return false; } 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) { t3gs = t3GlyphStack; while (t3gs != nullptr) { if (t3gs->cache == t3FontCache[nT3Fonts - 1]) { error(errSyntaxWarning, -1, "t3FontCache reaches limit but font still on stack in SplashOutputDev::beginType3Char"); return true; } t3gs = t3gs->next; } delete t3FontCache[nT3Fonts - 1]; --nT3Fonts; } for (j = nT3Fonts; 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 = false; } 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 = true; } 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 != nullptr) { 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 true; } } } // push a new Type 3 glyph record t3gs = new T3GlyphStack(); t3gs->next = t3GlyphStack; t3GlyphStack = t3gs; t3GlyphStack->code = code; t3GlyphStack->cache = t3Font; t3GlyphStack->cacheTag = nullptr; t3GlyphStack->cacheData = nullptr; t3GlyphStack->haveDx = false; t3GlyphStack->doNotCache = false; return false; } void SplashOutputDev::endType3Char(GfxState *state) { T3GlyphStack *t3gs; if (t3GlyphStack->cacheTag) { memcpy(t3GlyphStack->cacheData, bitmap->getDataPtr(), t3GlyphStack->cache->glyphSize); delete bitmap; delete splash; bitmap = t3GlyphStack->origBitmap; splash = t3GlyphStack->origSplash; const double *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); } t3gs = t3GlyphStack; t3GlyphStack = t3gs->next; delete t3gs; } void SplashOutputDev::type3D0(GfxState *state, double wx, double wy) { if (likely(t3GlyphStack != nullptr)) { t3GlyphStack->haveDx = true; } else { error(errSyntaxWarning, -1, "t3GlyphStack was null in SplashOutputDev::type3D0"); } } void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) { T3FontCache *t3Font; SplashColor color; double xt, yt, xMin, xMax, yMin, yMax, x1, y1; int i, j; // ignore multiple d0/d1 operators if (!t3GlyphStack || t3GlyphStack->haveDx) { return; } t3GlyphStack->haveDx = true; // don't cache if we got a gsave/grestore before the d1 if (t3GlyphStack->doNotCache) { return; } if (unlikely(t3GlyphStack == nullptr)) { error(errSyntaxWarning, -1, "t3GlyphStack was null in SplashOutputDev::type3D1"); return; } if (unlikely(t3GlyphStack->origBitmap != nullptr)) { error(errSyntaxWarning, -1, "t3GlyphStack origBitmap was not null in SplashOutputDev::type3D1"); return; } if (unlikely(t3GlyphStack->origSplash != nullptr)) { error(errSyntaxWarning, -1, "t3GlyphStack origSplash was not null in SplashOutputDev::type3D1"); 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; } if (t3Font->cacheTags == nullptr) 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; const double *ctm = state->getCTM(); t3GlyphStack->origCTM4 = ctm[4]; t3GlyphStack->origCTM5 = ctm[5]; // create the temporary bitmap if (colorMode == splashModeMono1) { bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1, splashModeMono1, false); splash = new Splash(bitmap, false, t3GlyphStack->origSplash->getScreen()); color[0] = 0; splash->clear(color); color[0] = 0xff; } else { bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1, splashModeMono8, false); splash = new Splash(bitmap, vectorAntialias, t3GlyphStack->origSplash->getScreen()); color[0] = 0x00; splash->clear(color); color[0] = 0xff; } splash->setMinLineWidth(s_minLineWidth); splash->setThinLineMode(splashThinLineDefault); splash->setFillPattern(new SplashSolidColor(color)); splash->setStrokePattern(new SplashSolidColor(color)); //~ this should copy other state from t3GlyphStack->origSplash? state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3], -t3Font->glyphX, -t3Font->glyphY); updateCTM(state, 0, 0, 0, 0, 0, 0); } void SplashOutputDev::drawType3Glyph(GfxState *state, T3FontCache *t3Font, T3FontCacheTag * /*tag*/, unsigned char *data) { SplashGlyphBitmap glyph; setOverprintMask(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 = false; splash->fillGlyph(0, 0, &glyph); } void SplashOutputDev::beginTextObject(GfxState *state) { } void SplashOutputDev::endTextObject(GfxState *state) { if (textClipPath) { splash->clipToPath(textClipPath, false); delete textClipPath; textClipPath = nullptr; } } struct SplashOutImageMaskData { ImageStream *imgStr; bool invert; int width, height, y; }; bool SplashOutputDev::imageMaskSrc(void *data, SplashColorPtr line) { SplashOutImageMaskData *imgMaskData = (SplashOutImageMaskData *)data; unsigned char *p; SplashColorPtr q; int x; if (imgMaskData->y == imgMaskData->height) { return false; } if (!(p = imgMaskData->imgStr->getLine())) { return false; } for (x = 0, q = line; x < imgMaskData->width; ++x) { *q++ = *p++ ^ imgMaskData->invert; } ++imgMaskData->y; return true; } void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg) { SplashCoord mat[6]; SplashOutImageMaskData imgMaskData; if (state->getFillColorSpace()->isNonMarking()) { return; } setOverprintMask(state->getFillColorSpace(), state->getFillOverprint(), state->getOverprintMode(), state->getFillColor()); const double *ctm = state->getCTM(); for (int i = 0; i < 6; ++i) { if (!std::isfinite(ctm[i])) return; } 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]; imgMaskData.imgStr = new ImageStream(str, width, 1, 1); imgMaskData.imgStr->reset(); imgMaskData.invert = invert ? false : true; imgMaskData.width = width; imgMaskData.height = height; imgMaskData.y = 0; splash->fillImageMask(&imageMaskSrc, &imgMaskData, width, height, mat, t3GlyphStack != nullptr); if (inlineImg) { while (imgMaskData.y < height) { imgMaskData.imgStr->getLine(); ++imgMaskData.y; } } delete imgMaskData.imgStr; str->close(); } void SplashOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool inlineImg, double *baseMatrix) { const double *ctm; SplashCoord mat[6]; SplashOutImageMaskData imgMaskData; Splash *maskSplash; SplashColor maskColor; double bbox[4] = { 0, 0, 1, 1 }; // default; if (state->getFillColorSpace()->isNonMarking()) { return; } ctm = state->getCTM(); for (int i = 0; i < 6; ++i) { if (!std::isfinite(ctm[i])) return; } beginTransparencyGroup(state, bbox, nullptr, false, false, false); baseMatrix[4] -= transpGroupStack->tx; baseMatrix[5] -= transpGroupStack->ty; 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]; imgMaskData.imgStr = new ImageStream(str, width, 1, 1); imgMaskData.imgStr->reset(); imgMaskData.invert = invert ? false : true; imgMaskData.width = width; imgMaskData.height = height; imgMaskData.y = 0; transpGroupStack->softmask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, false); maskSplash = new Splash(transpGroupStack->softmask, vectorAntialias); maskColor[0] = 0; maskSplash->clear(maskColor); maskColor[0] = 0xff; maskSplash->setFillPattern(new SplashSolidColor(maskColor)); maskSplash->fillImageMask(&imageMaskSrc, &imgMaskData, width, height, mat, t3GlyphStack != nullptr); delete maskSplash; delete imgMaskData.imgStr; str->close(); } void SplashOutputDev::unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix) { double bbox[4] = { 0, 0, 1, 1 }; // dummy /* transfer mask to alpha channel! */ // memcpy(maskBitmap->getAlphaPtr(), maskBitmap->getDataPtr(), bitmap->getRowSize() * bitmap->getHeight()); // memset(maskBitmap->getDataPtr(), 0, bitmap->getRowSize() * bitmap->getHeight()); if (transpGroupStack->softmask != nullptr) { unsigned char *dest = bitmap->getAlphaPtr(); unsigned char *src = transpGroupStack->softmask->getDataPtr(); for (int c = 0; c < transpGroupStack->softmask->getRowSize() * transpGroupStack->softmask->getHeight(); c++) { dest[c] = src[c]; } delete transpGroupStack->softmask; transpGroupStack->softmask = nullptr; } endTransparencyGroup(state); baseMatrix[4] += transpGroupStack->tx; baseMatrix[5] += transpGroupStack->ty; paintTransparencyGroup(state, bbox); } struct SplashOutImageData { ImageStream *imgStr; GfxImageColorMap *colorMap; SplashColorPtr lookup; const int *maskColors; SplashColorMode colorMode; int width, height, y; ImageStream *maskStr; GfxImageColorMap *maskColorMap; SplashColor matteColor; }; #ifdef USE_CMS bool SplashOutputDev::useIccImageSrc(void *data) { SplashOutImageData *imgData = (SplashOutImageData *)data; if (!imgData->lookup && imgData->colorMap->getColorSpace()->getMode() == csICCBased && imgData->colorMap->getBits() != 1) { GfxICCBasedColorSpace *colorSpace = (GfxICCBasedColorSpace *)imgData->colorMap->getColorSpace(); switch (imgData->colorMode) { case splashModeMono1: case splashModeMono8: if (colorSpace->getAlt() != nullptr && colorSpace->getAlt()->getMode() == csDeviceGray) return true; break; case splashModeXBGR8: case splashModeRGB8: case splashModeBGR8: if (colorSpace->getAlt() != nullptr && colorSpace->getAlt()->getMode() == csDeviceRGB) return true; break; case splashModeCMYK8: if (colorSpace->getAlt() != nullptr && colorSpace->getAlt()->getMode() == csDeviceCMYK) return true; break; case splashModeDeviceN8: if (colorSpace->getAlt() != nullptr && colorSpace->getAlt()->getMode() == csDeviceN) return true; break; } } return false; } #endif // Clip x to lie in [0, 255]. static inline unsigned char clip255(int x) { return x < 0 ? 0 : x > 255 ? 255 : x; } bool SplashOutputDev::imageSrc(void *data, SplashColorPtr colorLine, unsigned char * /*alphaLine*/) { SplashOutImageData *imgData = (SplashOutImageData *)data; unsigned char *p; SplashColorPtr q, col; GfxRGB rgb; GfxGray gray; GfxCMYK cmyk; GfxColor deviceN; int nComps, x; if (imgData->y == imgData->height) { return false; } if (!(p = imgData->imgStr->getLine())) { int destComps = 1; if (imgData->colorMode == splashModeRGB8 || imgData->colorMode == splashModeBGR8) destComps = 3; else if (imgData->colorMode == splashModeXBGR8) destComps = 4; else if (imgData->colorMode == splashModeCMYK8) destComps = 4; else if (imgData->colorMode == splashModeDeviceN8) destComps = SPOT_NCOMPS + 4; memset(colorLine, 0, imgData->width * destComps); return false; } nComps = imgData->colorMap->getNumPixelComps(); 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; case splashModeXBGR8: 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; 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; case splashModeDeviceN8: for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) { col = &imgData->lookup[(SPOT_NCOMPS + 4) * *p]; for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) *q++ = col[cp]; } break; } } else { switch (imgData->colorMode) { case splashModeMono1: case splashModeMono8: for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { imgData->colorMap->getGray(p, &gray); *q++ = colToByte(gray); } break; case splashModeRGB8: case splashModeBGR8: if (imgData->colorMap->useRGBLine()) { imgData->colorMap->getRGBLine(p, (unsigned char *)colorLine, imgData->width); } else { for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { imgData->colorMap->getRGB(p, &rgb); *q++ = colToByte(rgb.r); *q++ = colToByte(rgb.g); *q++ = colToByte(rgb.b); } } break; case splashModeXBGR8: if (imgData->colorMap->useRGBLine()) { imgData->colorMap->getRGBXLine(p, (unsigned char *)colorLine, imgData->width); } else { for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { imgData->colorMap->getRGB(p, &rgb); *q++ = colToByte(rgb.r); *q++ = colToByte(rgb.g); *q++ = colToByte(rgb.b); *q++ = 255; } } break; case splashModeCMYK8: if (imgData->colorMap->useCMYKLine()) { imgData->colorMap->getCMYKLine(p, (unsigned char *)colorLine, imgData->width); } else { for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { imgData->colorMap->getCMYK(p, &cmyk); *q++ = colToByte(cmyk.c); *q++ = colToByte(cmyk.m); *q++ = colToByte(cmyk.y); *q++ = colToByte(cmyk.k); } } break; case splashModeDeviceN8: if (imgData->colorMap->useDeviceNLine()) { imgData->colorMap->getDeviceNLine(p, (unsigned char *)colorLine, imgData->width); } else { for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { imgData->colorMap->getDeviceN(p, &deviceN); for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) *q++ = colToByte(deviceN.c[cp]); } } break; } } if (imgData->maskStr != nullptr && (p = imgData->maskStr->getLine()) != nullptr) { int destComps = splashColorModeNComps[imgData->colorMode]; int convComps = (imgData->colorMode == splashModeXBGR8) ? 3 : destComps; imgData->maskColorMap->getGrayLine(p, p, imgData->width); for (x = 0, q = colorLine; x < imgData->width; ++x, p++, q += destComps) { for (int cp = 0; cp < convComps; cp++) { q[cp] = (*p) ? clip255(imgData->matteColor[cp] + (int)(q[cp] - imgData->matteColor[cp]) * 255 / *p) : imgData->matteColor[cp]; } } } ++imgData->y; return true; } #ifdef USE_CMS bool SplashOutputDev::iccImageSrc(void *data, SplashColorPtr colorLine, unsigned char * /*alphaLine*/) { SplashOutImageData *imgData = (SplashOutImageData *)data; unsigned char *p; int nComps; if (imgData->y == imgData->height) { return false; } if (!(p = imgData->imgStr->getLine())) { int destComps = 1; if (imgData->colorMode == splashModeRGB8 || imgData->colorMode == splashModeBGR8) destComps = 3; else if (imgData->colorMode == splashModeXBGR8) destComps = 4; else if (imgData->colorMode == splashModeCMYK8) destComps = 4; else if (imgData->colorMode == splashModeDeviceN8) destComps = SPOT_NCOMPS + 4; memset(colorLine, 0, imgData->width * destComps); return false; } if (imgData->colorMode == splashModeXBGR8) { SplashColorPtr q; int x; for (x = 0, q = colorLine; x < imgData->width; ++x) { *q++ = *p++; *q++ = *p++; *q++ = *p++; *q++ = 255; } } else { nComps = imgData->colorMap->getNumPixelComps(); memcpy(colorLine, p, imgData->width * nComps); } ++imgData->y; return true; } void SplashOutputDev::iccTransform(void *data, SplashBitmap *bitmap) { SplashOutImageData *imgData = (SplashOutImageData *)data; int nComps = imgData->colorMap->getNumPixelComps(); unsigned char *colorLine = (unsigned char *)gmalloc(nComps * bitmap->getWidth()); unsigned char *rgbxLine = (imgData->colorMode == splashModeXBGR8) ? (unsigned char *)gmalloc(3 * bitmap->getWidth()) : nullptr; for (int i = 0; i < bitmap->getHeight(); i++) { unsigned char *p = bitmap->getDataPtr() + i * bitmap->getRowSize(); switch (imgData->colorMode) { case splashModeMono1: case splashModeMono8: imgData->colorMap->getGrayLine(p, colorLine, bitmap->getWidth()); memcpy(p, colorLine, nComps * bitmap->getWidth()); break; case splashModeRGB8: case splashModeBGR8: imgData->colorMap->getRGBLine(p, colorLine, bitmap->getWidth()); memcpy(p, colorLine, nComps * bitmap->getWidth()); break; case splashModeCMYK8: imgData->colorMap->getCMYKLine(p, colorLine, bitmap->getWidth()); memcpy(p, colorLine, nComps * bitmap->getWidth()); break; case splashModeDeviceN8: imgData->colorMap->getDeviceNLine(p, colorLine, bitmap->getWidth()); memcpy(p, colorLine, nComps * bitmap->getWidth()); break; case splashModeXBGR8: unsigned char *q; unsigned char *b = p; int x; for (x = 0, q = rgbxLine; x < bitmap->getWidth(); ++x, b += 4) { *q++ = b[2]; *q++ = b[1]; *q++ = b[0]; } imgData->colorMap->getRGBLine(rgbxLine, colorLine, bitmap->getWidth()); b = p; for (x = 0, q = colorLine; x < bitmap->getWidth(); ++x, b += 4) { b[2] = *q++; b[1] = *q++; b[0] = *q++; } break; } } gfree(colorLine); if (rgbxLine != nullptr) gfree(rgbxLine); } #endif bool SplashOutputDev::alphaImageSrc(void *data, SplashColorPtr colorLine, unsigned char *alphaLine) { SplashOutImageData *imgData = (SplashOutImageData *)data; unsigned char *p, *aq; SplashColorPtr q, col; GfxRGB rgb; GfxGray gray; GfxCMYK cmyk; GfxColor deviceN; unsigned char alpha; int nComps, x, i; if (imgData->y == imgData->height) { return false; } if (!(p = imgData->imgStr->getLine())) { return false; } nComps = imgData->colorMap->getNumPixelComps(); for (x = 0, q = colorLine, 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; } } if (imgData->lookup) { switch (imgData->colorMode) { case splashModeMono1: case splashModeMono8: *q++ = imgData->lookup[*p]; break; case splashModeRGB8: case splashModeBGR8: col = &imgData->lookup[3 * *p]; *q++ = col[0]; *q++ = col[1]; *q++ = col[2]; break; case splashModeXBGR8: col = &imgData->lookup[4 * *p]; *q++ = col[0]; *q++ = col[1]; *q++ = col[2]; *q++ = 255; break; case splashModeCMYK8: col = &imgData->lookup[4 * *p]; *q++ = col[0]; *q++ = col[1]; *q++ = col[2]; *q++ = col[3]; break; case splashModeDeviceN8: col = &imgData->lookup[(SPOT_NCOMPS + 4) * *p]; for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) *q++ = col[cp]; break; } *aq++ = alpha; } else { switch (imgData->colorMode) { case splashModeMono1: case splashModeMono8: imgData->colorMap->getGray(p, &gray); *q++ = colToByte(gray); break; case splashModeXBGR8: case splashModeRGB8: case splashModeBGR8: imgData->colorMap->getRGB(p, &rgb); *q++ = colToByte(rgb.r); *q++ = colToByte(rgb.g); *q++ = colToByte(rgb.b); if (imgData->colorMode == splashModeXBGR8) *q++ = 255; break; case splashModeCMYK8: imgData->colorMap->getCMYK(p, &cmyk); *q++ = colToByte(cmyk.c); *q++ = colToByte(cmyk.m); *q++ = colToByte(cmyk.y); *q++ = colToByte(cmyk.k); break; case splashModeDeviceN8: imgData->colorMap->getDeviceN(p, &deviceN); for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) *q++ = colToByte(deviceN.c[cp]); break; } *aq++ = alpha; } } ++imgData->y; return true; } struct TilingSplashOutBitmap { SplashBitmap *bitmap; SplashPattern *pattern; SplashColorMode colorMode; int paintType; int repeatX; int repeatY; int y; }; bool SplashOutputDev::tilingBitmapSrc(void *data, SplashColorPtr colorLine, unsigned char *alphaLine) { TilingSplashOutBitmap *imgData = (TilingSplashOutBitmap *)data; if (imgData->y == imgData->bitmap->getHeight()) { imgData->repeatY--; if (imgData->repeatY == 0) return false; imgData->y = 0; } if (imgData->paintType == 1) { const SplashColorMode cMode = imgData->bitmap->getMode(); SplashColorPtr q = colorLine; // For splashModeBGR8 and splashModeXBGR8 we need to use getPixel // for the others we can use raw access if (cMode == splashModeBGR8 || cMode == splashModeXBGR8) { for (int m = 0; m < imgData->repeatX; m++) { for (int x = 0; x < imgData->bitmap->getWidth(); x++) { imgData->bitmap->getPixel(x, imgData->y, q); q += splashColorModeNComps[cMode]; } } } else { const int n = imgData->bitmap->getRowSize(); SplashColorPtr p; for (int m = 0; m < imgData->repeatX; m++) { p = imgData->bitmap->getDataPtr() + imgData->y * imgData->bitmap->getRowSize(); for (int x = 0; x < n; ++x) { *q++ = *p++; } } } if (alphaLine != nullptr) { SplashColorPtr aq = alphaLine; SplashColorPtr p; const int n = imgData->bitmap->getWidth() - 1; for (int m = 0; m < imgData->repeatX; m++) { p = imgData->bitmap->getAlphaPtr() + imgData->y * imgData->bitmap->getWidth(); for (int x = 0; x < n; ++x) { *aq++ = *p++; } // This is a hack, because of how Splash antialias works if we overwrite the // last alpha pixel of the tile most/all of the files look much better *aq++ = (n == 0) ? *p : *(p - 1); } } } else { SplashColor col, pat; SplashColorPtr dest = colorLine; for (int m = 0; m < imgData->repeatX; m++) { for (int x = 0; x < imgData->bitmap->getWidth(); x++) { imgData->bitmap->getPixel(x, imgData->y, col); imgData->pattern->getColor(x, imgData->y, pat); for (int i = 0; i < splashColorModeNComps[imgData->colorMode]; ++i) { if (imgData->colorMode == splashModeCMYK8 || imgData->colorMode == splashModeDeviceN8) dest[i] = div255(pat[i] * (255 - col[0])); else dest[i] = 255 - div255((255 - pat[i]) * (255 - col[0])); } dest += splashColorModeNComps[imgData->colorMode]; } } if (alphaLine != nullptr) { const int y = (imgData->y == imgData->bitmap->getHeight() - 1 && imgData->y > 50) ? imgData->y - 1 : imgData->y; SplashColorPtr aq = alphaLine; SplashColorPtr p; const int n = imgData->bitmap->getWidth(); for (int m = 0; m < imgData->repeatX; m++) { p = imgData->bitmap->getAlphaPtr() + y * imgData->bitmap->getWidth(); for (int x = 0; x < n; ++x) { *aq++ = *p++; } } } } ++imgData->y; return true; } void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool inlineImg) { SplashCoord mat[6]; SplashOutImageData imgData; SplashColorMode srcMode; SplashImageSource src; SplashICCTransform tf; GfxGray gray; GfxRGB rgb; GfxCMYK cmyk; bool grayIndexed = false; GfxColor deviceN; unsigned char pix; int n, i; const double *ctm = state->getCTM(); for (i = 0; i < 6; ++i) { if (!std::isfinite(ctm[i])) return; } 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.maskColors = maskColors; imgData.colorMode = colorMode; imgData.width = width; imgData.height = height; imgData.maskStr = nullptr; imgData.maskColorMap = nullptr; imgData.y = 0; // special case for one-channel (monochrome/gray/separation) images: // build a lookup table here imgData.lookup = nullptr; if (colorMap->getNumPixelComps() == 1) { n = 1 << colorMap->getBits(); switch (colorMode) { case splashModeMono1: case splashModeMono8: imgData.lookup = (SplashColorPtr)gmalloc(n); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getGray(&pix, &gray); imgData.lookup[i] = colToByte(gray); } break; case splashModeRGB8: case splashModeBGR8: imgData.lookup = (SplashColorPtr)gmallocn(n, 3); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getRGB(&pix, &rgb); imgData.lookup[3 * i] = colToByte(rgb.r); imgData.lookup[3 * i + 1] = colToByte(rgb.g); imgData.lookup[3 * i + 2] = colToByte(rgb.b); } break; case splashModeXBGR8: imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(n, 4); if (likely(imgData.lookup != nullptr)) { for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getRGB(&pix, &rgb); imgData.lookup[4 * i] = colToByte(rgb.r); imgData.lookup[4 * i + 1] = colToByte(rgb.g); imgData.lookup[4 * i + 2] = colToByte(rgb.b); imgData.lookup[4 * i + 3] = 255; } } break; case splashModeCMYK8: grayIndexed = colorMap->getColorSpace()->getMode() != csDeviceGray; imgData.lookup = (SplashColorPtr)gmallocn(n, 4); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getCMYK(&pix, &cmyk); if (cmyk.c != 0 || cmyk.m != 0 || cmyk.y != 0) { grayIndexed = false; } 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; case splashModeDeviceN8: colorMap->getColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS); grayIndexed = colorMap->getColorSpace()->getMode() != csDeviceGray; imgData.lookup = (SplashColorPtr)gmallocn(n, SPOT_NCOMPS + 4); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getCMYK(&pix, &cmyk); if (cmyk.c != 0 || cmyk.m != 0 || cmyk.y != 0) { grayIndexed = false; } colorMap->getDeviceN(&pix, &deviceN); for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) imgData.lookup[(SPOT_NCOMPS + 4) * i + cp] = colToByte(deviceN.c[cp]); } break; } } setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(), state->getOverprintMode(), nullptr, grayIndexed); if (colorMode == splashModeMono1) { srcMode = splashModeMono8; } else { srcMode = colorMode; } #ifdef USE_CMS src = maskColors ? &alphaImageSrc : useIccImageSrc(&imgData) ? &iccImageSrc : &imageSrc; tf = maskColors == nullptr && useIccImageSrc(&imgData) ? &iccTransform : nullptr; #else src = maskColors ? &alphaImageSrc : &imageSrc; tf = nullptr; #endif splash->drawImage(src, tf, &imgData, srcMode, maskColors ? true : false, width, height, mat, interpolate); if (inlineImg) { while (imgData.y < height) { imgData.imgStr->getLine(); ++imgData.y; } } gfree(imgData.lookup); delete imgData.imgStr; str->close(); } struct SplashOutMaskedImageData { ImageStream *imgStr; GfxImageColorMap *colorMap; SplashBitmap *mask; SplashColorPtr lookup; SplashColorMode colorMode; int width, height, y; }; bool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine, unsigned char *alphaLine) { SplashOutMaskedImageData *imgData = (SplashOutMaskedImageData *)data; unsigned char *p, *aq; SplashColorPtr q, col; GfxRGB rgb; GfxGray gray; GfxCMYK cmyk; GfxColor deviceN; unsigned char alpha; unsigned char *maskPtr; int maskBit; int nComps, x; if (imgData->y == imgData->height) { return false; } if (!(p = imgData->imgStr->getLine())) { return false; } nComps = imgData->colorMap->getNumPixelComps(); maskPtr = imgData->mask->getDataPtr() + imgData->y * imgData->mask->getRowSize(); maskBit = 0x80; for (x = 0, q = colorLine, aq = alphaLine; x < imgData->width; ++x, p += nComps) { alpha = (*maskPtr & maskBit) ? 0xff : 0x00; if (!(maskBit >>= 1)) { ++maskPtr; maskBit = 0x80; } if (imgData->lookup) { switch (imgData->colorMode) { case splashModeMono1: case splashModeMono8: *q++ = imgData->lookup[*p]; break; case splashModeRGB8: case splashModeBGR8: col = &imgData->lookup[3 * *p]; *q++ = col[0]; *q++ = col[1]; *q++ = col[2]; break; case splashModeXBGR8: col = &imgData->lookup[4 * *p]; *q++ = col[0]; *q++ = col[1]; *q++ = col[2]; *q++ = 255; break; case splashModeCMYK8: col = &imgData->lookup[4 * *p]; *q++ = col[0]; *q++ = col[1]; *q++ = col[2]; *q++ = col[3]; break; case splashModeDeviceN8: col = &imgData->lookup[(SPOT_NCOMPS + 4) * *p]; for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) *q++ = col[cp]; break; } *aq++ = alpha; } else { switch (imgData->colorMode) { case splashModeMono1: case splashModeMono8: imgData->colorMap->getGray(p, &gray); *q++ = colToByte(gray); break; case splashModeXBGR8: case splashModeRGB8: case splashModeBGR8: imgData->colorMap->getRGB(p, &rgb); *q++ = colToByte(rgb.r); *q++ = colToByte(rgb.g); *q++ = colToByte(rgb.b); if (imgData->colorMode == splashModeXBGR8) *q++ = 255; break; case splashModeCMYK8: imgData->colorMap->getCMYK(p, &cmyk); *q++ = colToByte(cmyk.c); *q++ = colToByte(cmyk.m); *q++ = colToByte(cmyk.y); *q++ = colToByte(cmyk.k); break; case splashModeDeviceN8: imgData->colorMap->getDeviceN(p, &deviceN); for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) *q++ = colToByte(deviceN.c[cp]); break; } *aq++ = alpha; } } ++imgData->y; return true; } void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool maskInterpolate) { GfxImageColorMap *maskColorMap; SplashCoord mat[6]; SplashOutMaskedImageData imgData; SplashOutImageMaskData imgMaskData; SplashColorMode srcMode; SplashBitmap *maskBitmap; Splash *maskSplash; SplashColor maskColor; GfxGray gray; GfxRGB rgb; GfxCMYK cmyk; GfxColor deviceN; unsigned char pix; int n, i; colorMap->getColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS); setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(), state->getOverprintMode(), nullptr); // If the mask is higher resolution than the image, use // drawSoftMaskedImage() instead. if (maskWidth > width || maskHeight > height) { Object maskDecode(new Array((xref) ? xref : doc->getXRef())); maskDecode.arrayAdd(Object(maskInvert ? 0 : 1)); maskDecode.arrayAdd(Object(maskInvert ? 1 : 0)); maskColorMap = new GfxImageColorMap(1, &maskDecode, new GfxDeviceGrayColorSpace()); drawSoftMaskedImage(state, ref, str, width, height, colorMap, interpolate, maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate); 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 ? false : true; imgMaskData.width = maskWidth; imgMaskData.height = maskHeight; imgMaskData.y = 0; maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1, false); if (!maskBitmap->getDataPtr()) { delete maskBitmap; width = height = 1; maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1, false); } maskSplash = new Splash(maskBitmap, false); maskColor[0] = 0; maskSplash->clear(maskColor); maskColor[0] = 0xff; maskSplash->setFillPattern(new SplashSolidColor(maskColor)); maskSplash->fillImageMask(&imageMaskSrc, &imgMaskData, maskWidth, maskHeight, mat, false); delete imgMaskData.imgStr; maskStr->close(); delete maskSplash; //----- draw the source image const double *ctm = state->getCTM(); for (i = 0; i < 6; ++i) { if (!std::isfinite(ctm[i])) { delete maskBitmap; return; } } 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.mask = maskBitmap; imgData.colorMode = colorMode; 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 = nullptr; if (colorMap->getNumPixelComps() == 1) { n = 1 << colorMap->getBits(); switch (colorMode) { case splashModeMono1: case splashModeMono8: imgData.lookup = (SplashColorPtr)gmalloc(n); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getGray(&pix, &gray); imgData.lookup[i] = colToByte(gray); } break; case splashModeRGB8: case splashModeBGR8: imgData.lookup = (SplashColorPtr)gmallocn(n, 3); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getRGB(&pix, &rgb); imgData.lookup[3 * i] = colToByte(rgb.r); imgData.lookup[3 * i + 1] = colToByte(rgb.g); imgData.lookup[3 * i + 2] = colToByte(rgb.b); } break; case splashModeXBGR8: imgData.lookup = (SplashColorPtr)gmallocn(n, 4); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getRGB(&pix, &rgb); imgData.lookup[4 * i] = colToByte(rgb.r); imgData.lookup[4 * i + 1] = colToByte(rgb.g); imgData.lookup[4 * i + 2] = colToByte(rgb.b); imgData.lookup[4 * i + 3] = 255; } break; case splashModeCMYK8: imgData.lookup = (SplashColorPtr)gmallocn(n, 4); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getCMYK(&pix, &cmyk); 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; case splashModeDeviceN8: imgData.lookup = (SplashColorPtr)gmallocn(n, SPOT_NCOMPS + 4); for (i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getDeviceN(&pix, &deviceN); for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) imgData.lookup[(SPOT_NCOMPS + 4) * i + cp] = colToByte(deviceN.c[cp]); } break; } } if (colorMode == splashModeMono1) { srcMode = splashModeMono8; } else { srcMode = colorMode; } splash->drawImage(&maskedImageSrc, nullptr, &imgData, srcMode, true, width, height, mat, interpolate); delete maskBitmap; gfree(imgData.lookup); delete imgData.imgStr; str->close(); } } void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object * /* ref */, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, bool maskInterpolate) { SplashCoord mat[6]; SplashOutImageData imgData; SplashOutImageData imgMaskData; SplashColorMode srcMode; SplashBitmap *maskBitmap; Splash *maskSplash; SplashColor maskColor; GfxGray gray; GfxRGB rgb; GfxCMYK cmyk; GfxColor deviceN; unsigned char pix; colorMap->getColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS); setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(), state->getOverprintMode(), nullptr); const double *ctm = state->getCTM(); for (int i = 0; i < 6; ++i) { if (!std::isfinite(ctm[i])) return; } 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]; //----- set up the soft mask if (maskColorMap->getMatteColor() != nullptr) { int maskChars; if (checkedMultiply(maskWidth, maskHeight, &maskChars)) { return; } unsigned char *data = (unsigned char *)gmalloc(maskChars); maskStr->reset(); const int readChars = maskStr->doGetChars(maskChars, data); if (unlikely(readChars < maskChars)) { memset(&data[readChars], 0, maskChars - readChars); } maskStr->close(); maskStr = new AutoFreeMemStream((char *)data, 0, maskChars, maskStr->getDictObject()->copy()); } imgMaskData.imgStr = new ImageStream(maskStr, maskWidth, maskColorMap->getNumPixelComps(), maskColorMap->getBits()); imgMaskData.imgStr->reset(); imgMaskData.colorMap = maskColorMap; imgMaskData.maskColors = nullptr; imgMaskData.colorMode = splashModeMono8; imgMaskData.width = maskWidth; imgMaskData.height = maskHeight; imgMaskData.y = 0; imgMaskData.maskStr = nullptr; imgMaskData.maskColorMap = nullptr; const unsigned imgMaskDataLookupSize = 1 << maskColorMap->getBits(); imgMaskData.lookup = (SplashColorPtr)gmalloc(imgMaskDataLookupSize); for (unsigned i = 0; i < imgMaskDataLookupSize; ++i) { pix = (unsigned char)i; maskColorMap->getGray(&pix, &gray); imgMaskData.lookup[i] = colToByte(gray); } maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, false); maskSplash = new Splash(maskBitmap, vectorAntialias); maskColor[0] = 0; maskSplash->clear(maskColor); maskSplash->drawImage(&imageSrc, nullptr, &imgMaskData, splashModeMono8, false, maskWidth, maskHeight, mat, maskInterpolate); delete imgMaskData.imgStr; if (maskColorMap->getMatteColor() == nullptr) { 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.maskColors = nullptr; imgData.colorMode = colorMode; imgData.width = width; imgData.height = height; imgData.maskStr = nullptr; imgData.maskColorMap = nullptr; if (maskColorMap->getMatteColor() != nullptr) { getMatteColor(colorMode, colorMap, maskColorMap->getMatteColor(), imgData.matteColor); imgData.maskColorMap = maskColorMap; imgData.maskStr = new ImageStream(maskStr, maskWidth, maskColorMap->getNumPixelComps(), maskColorMap->getBits()); imgData.maskStr->reset(); } imgData.y = 0; // special case for one-channel (monochrome/gray/separation) images: // build a lookup table here imgData.lookup = nullptr; if (colorMap->getNumPixelComps() == 1) { const unsigned n = 1 << colorMap->getBits(); switch (colorMode) { case splashModeMono1: case splashModeMono8: imgData.lookup = (SplashColorPtr)gmalloc(n); for (unsigned i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getGray(&pix, &gray); imgData.lookup[i] = colToByte(gray); } break; case splashModeRGB8: case splashModeBGR8: imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(n, 3); if (likely(imgData.lookup != nullptr)) { for (unsigned i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getRGB(&pix, &rgb); imgData.lookup[3 * i] = colToByte(rgb.r); imgData.lookup[3 * i + 1] = colToByte(rgb.g); imgData.lookup[3 * i + 2] = colToByte(rgb.b); } } break; case splashModeXBGR8: imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(n, 4); if (likely(imgData.lookup != nullptr)) { for (unsigned i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getRGB(&pix, &rgb); imgData.lookup[4 * i] = colToByte(rgb.r); imgData.lookup[4 * i + 1] = colToByte(rgb.g); imgData.lookup[4 * i + 2] = colToByte(rgb.b); imgData.lookup[4 * i + 3] = 255; } } break; case splashModeCMYK8: imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(n, 4); if (likely(imgData.lookup != nullptr)) { for (unsigned i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getCMYK(&pix, &cmyk); 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; case splashModeDeviceN8: imgData.lookup = (SplashColorPtr)gmallocn_checkoverflow(n, SPOT_NCOMPS + 4); if (likely(imgData.lookup != nullptr)) { for (unsigned i = 0; i < n; ++i) { pix = (unsigned char)i; colorMap->getDeviceN(&pix, &deviceN); for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) imgData.lookup[(SPOT_NCOMPS + 4) * i + cp] = colToByte(deviceN.c[cp]); } } break; } } if (colorMode == splashModeMono1) { srcMode = splashModeMono8; } else { srcMode = colorMode; } splash->drawImage(&imageSrc, nullptr, &imgData, srcMode, false, width, height, mat, interpolate); splash->setSoftMask(nullptr); gfree(imgData.lookup); delete imgData.maskStr; delete imgData.imgStr; if (maskColorMap->getMatteColor() != nullptr) { maskStr->close(); delete maskStr; } str->close(); } bool SplashOutputDev::checkTransparencyGroup(GfxState *state, bool knockout) { if (state->getFillOpacity() != 1 || state->getStrokeOpacity() != 1 || state->getAlphaIsShape() || state->getBlendMode() != gfxBlendNormal || splash->getSoftMask() != nullptr || knockout) return true; return transpGroupStack != nullptr && transpGroupStack->shape != nullptr; } void SplashOutputDev::beginTransparencyGroup(GfxState *state, const double *bbox, GfxColorSpace *blendingColorSpace, bool isolated, bool knockout, bool forSoftMask) { SplashTransparencyGroup *transpGroup; SplashColor color; double xMin, yMin, xMax, yMax, x, y; int tx, ty, w, h; // 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; } tx = (int)floor(xMin); if (tx < 0) { tx = 0; } else if (tx >= bitmap->getWidth()) { tx = bitmap->getWidth() - 1; } ty = (int)floor(yMin); if (ty < 0) { ty = 0; } else if (ty >= bitmap->getHeight()) { ty = bitmap->getHeight() - 1; } w = (int)ceil(xMax) - tx + 1; if (tx + w > bitmap->getWidth()) { w = bitmap->getWidth() - tx; } if (w < 1) { w = 1; } h = (int)ceil(yMax) - ty + 1; if (ty + h > bitmap->getHeight()) { h = bitmap->getHeight() - ty; } if (h < 1) { h = 1; } // push a new stack entry transpGroup = new SplashTransparencyGroup(); transpGroup->softmask = nullptr; transpGroup->tx = tx; transpGroup->ty = ty; transpGroup->blendingColorSpace = blendingColorSpace; transpGroup->isolated = isolated; transpGroup->shape = (knockout && !isolated) ? SplashBitmap::copy(bitmap) : nullptr; transpGroup->knockout = (knockout && isolated); transpGroup->knockoutOpacity = 1.0; transpGroup->next = transpGroupStack; transpGroupStack = transpGroup; // save state transpGroup->origBitmap = bitmap; transpGroup->origSplash = splash; transpGroup->fontAA = fontEngine->getAA(); //~ this handles the blendingColorSpace arg for soft masks, but //~ not yet for transparency groups // switch to the blending color space if (forSoftMask && isolated && 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; } else if (blendingColorSpace->getMode() == csDeviceCMYK || (blendingColorSpace->getMode() == csICCBased && blendingColorSpace->getNComps() == 4)) { colorMode = splashModeCMYK8; } } // create the temporary bitmap bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, true, bitmapTopDown, bitmap->getSeparationList()); if (!bitmap->getDataPtr()) { delete bitmap; w = h = 1; bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, true, bitmapTopDown); } splash = new Splash(bitmap, vectorAntialias, transpGroup->origSplash->getScreen()); if (transpGroup->next != nullptr && transpGroup->next->knockout) { fontEngine->setAA(false); } splash->setThinLineMode(transpGroup->origSplash->getThinLineMode()); splash->setMinLineWidth(s_minLineWidth); //~ Acrobat apparently copies at least the fill and stroke colors, and //~ maybe other state(?) -- but not the clipping path (and not sure //~ what else) //~ [this is likely the same situation as in type3D1()] splash->setFillPattern(transpGroup->origSplash->getFillPattern()->copy()); splash->setStrokePattern(transpGroup->origSplash->getStrokePattern()->copy()); if (isolated) { splashClearColor(color); if (colorMode == splashModeXBGR8) color[3] = 255; splash->clear(color, 0); } else { SplashBitmap *shape = (knockout) ? transpGroup->shape : (transpGroup->next != nullptr && transpGroup->next->shape != nullptr) ? transpGroup->next->shape : transpGroup->origBitmap; int shapeTx = (knockout) ? tx : (transpGroup->next != nullptr && transpGroup->next->shape != nullptr) ? transpGroup->next->tx + tx : tx; int shapeTy = (knockout) ? ty : (transpGroup->next != nullptr && transpGroup->next->shape != nullptr) ? transpGroup->next->ty + ty : ty; splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h); splash->setInNonIsolatedGroup(shape, shapeTx, shapeTy); } transpGroup->tBitmap = bitmap; state->shiftCTMAndClip(-tx, -ty); updateCTM(state, 0, 0, 0, 0, 0, 0); } void SplashOutputDev::endTransparencyGroup(GfxState *state) { // restore state delete splash; bitmap = transpGroupStack->origBitmap; colorMode = bitmap->getMode(); splash = transpGroupStack->origSplash; state->shiftCTMAndClip(transpGroupStack->tx, transpGroupStack->ty); updateCTM(state, 0, 0, 0, 0, 0, 0); } void SplashOutputDev::paintTransparencyGroup(GfxState *state, const double *bbox) { SplashBitmap *tBitmap; SplashTransparencyGroup *transpGroup; bool isolated; int tx, ty; tx = transpGroupStack->tx; ty = transpGroupStack->ty; 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 (tx < bitmap->getWidth() && ty < bitmap->getHeight()) { SplashCoord knockoutOpacity = (transpGroupStack->next != nullptr) ? transpGroupStack->next->knockoutOpacity : transpGroupStack->knockoutOpacity; splash->setOverprintMask(0xffffffff, false); splash->composite(tBitmap, 0, 0, tx, ty, tBitmap->getWidth(), tBitmap->getHeight(), false, !isolated, transpGroupStack->next != nullptr && transpGroupStack->next->knockout, knockoutOpacity); fontEngine->setAA(transpGroupStack->fontAA); if (transpGroupStack->next != nullptr && transpGroupStack->next->shape != nullptr) { transpGroupStack->next->knockout = true; } } // pop the stack transpGroup = transpGroupStack; transpGroupStack = transpGroup->next; if (transpGroupStack != nullptr && transpGroup->knockoutOpacity < transpGroupStack->knockoutOpacity) { transpGroupStack->knockoutOpacity = transpGroup->knockoutOpacity; } delete transpGroup->shape; delete transpGroup; delete tBitmap; } void SplashOutputDev::setSoftMask(GfxState *state, const double *bbox, bool alpha, Function *transferFunc, GfxColor *backdropColor) { SplashBitmap *softMask, *tBitmap; Splash *tSplash; SplashTransparencyGroup *transpGroup; SplashColor color; SplashColorPtr p; GfxGray gray; GfxRGB rgb; GfxCMYK cmyk; GfxColor deviceN; double lum, lum2; int tx, ty, x, y; tx = transpGroupStack->tx; ty = transpGroupStack->ty; tBitmap = transpGroupStack->tBitmap; // composite with backdrop color 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->getScreen()); switch (tBitmap->getMode()) { case splashModeMono1: // transparency is not supported in mono1 mode break; case splashModeMono8: transpGroupStack->blendingColorSpace->getGray(backdropColor, &gray); color[0] = colToByte(gray); tSplash->compositeBackground(color); break; case splashModeXBGR8: color[3] = 255; // fallthrough case splashModeRGB8: case splashModeBGR8: transpGroupStack->blendingColorSpace->getRGB(backdropColor, &rgb); color[0] = colToByte(rgb.r); color[1] = colToByte(rgb.g); color[2] = colToByte(rgb.b); tSplash->compositeBackground(color); break; case splashModeCMYK8: transpGroupStack->blendingColorSpace->getCMYK(backdropColor, &cmyk); color[0] = colToByte(cmyk.c); color[1] = colToByte(cmyk.m); color[2] = colToByte(cmyk.y); color[3] = colToByte(cmyk.k); tSplash->compositeBackground(color); break; case splashModeDeviceN8: transpGroupStack->blendingColorSpace->getDeviceN(backdropColor, &deviceN); for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) color[cp] = colToByte(deviceN.c[cp]); tSplash->compositeBackground(color); break; } delete tSplash; } } softMask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, false); unsigned char fill = 0; if (transpGroupStack->blendingColorSpace) { transpGroupStack->blendingColorSpace->getGray(backdropColor, &gray); fill = colToByte(gray); } memset(softMask->getDataPtr(), fill, softMask->getRowSize() * softMask->getHeight()); p = softMask->getDataPtr() + ty * softMask->getRowSize() + tx; int xMax = tBitmap->getWidth(); int yMax = tBitmap->getHeight(); if (xMax > bitmap->getWidth() - tx) xMax = bitmap->getWidth() - tx; if (yMax > bitmap->getHeight() - ty) yMax = bitmap->getHeight() - ty; for (y = 0; y < yMax; ++y) { for (x = 0; x < xMax; ++x) { if (alpha) { if (transferFunc) { lum = tBitmap->getAlpha(x, y) / 255.0; transferFunc->transform(&lum, &lum2); p[x] = (int)(lum2 * 255.0 + 0.5); } else p[x] = tBitmap->getAlpha(x, y); } else { tBitmap->getPixel(x, y, color); // convert to luminosity switch (tBitmap->getMode()) { case splashModeMono1: case splashModeMono8: lum = color[0] / 255.0; break; case splashModeXBGR8: case splashModeRGB8: case splashModeBGR8: lum = (0.3 / 255.0) * color[0] + (0.59 / 255.0) * color[1] + (0.11 / 255.0) * color[2]; break; case splashModeCMYK8: case splashModeDeviceN8: lum = (1 - color[3] / 255.0) - (0.3 / 255.0) * color[0] - (0.59 / 255.0) * color[1] - (0.11 / 255.0) * color[2]; if (lum < 0) { lum = 0; } break; } if (transferFunc) { transferFunc->transform(&lum, &lum2); } else { lum2 = lum; } p[x] = (int)(lum2 * 255.0 + 0.5); } } p += softMask->getRowSize(); } splash->setSoftMask(softMask); // pop the stack transpGroup = transpGroupStack; transpGroupStack = transpGroup->next; delete transpGroup; delete tBitmap; } void SplashOutputDev::clearSoftMask(GfxState *state) { splash->setSoftMask(nullptr); } 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); return ret; } #if 1 //~tmp: turn off anti-aliasing temporarily bool SplashOutputDev::getVectorAntialias() { return splash->getVectorAntialias(); } void SplashOutputDev::setVectorAntialias(bool vaa) { vaa = vaa && colorMode != splashModeMono1; vectorAntialias = vaa; splash->setVectorAntialias(vaa); } #endif void SplashOutputDev::setFreeTypeHinting(bool enable, bool enableSlightHintingA) { enableFreeTypeHinting = enable; enableSlightHinting = enableSlightHintingA; } bool SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *catalog, Object *str, const double *ptm, int paintType, int /*tilingType*/, Dict *resDict, const double *mat, const double *bbox, int x0, int y0, int x1, int y1, double xStep, double yStep) { PDFRectangle box; Splash *formerSplash = splash; SplashBitmap *formerBitmap = bitmap; double width, height; int surface_width, surface_height, result_width, result_height, i; int repeatX, repeatY; SplashCoord matc[6]; Matrix m1; const double *ctm; double savedCTM[6]; double kx, ky, sx, sy; bool retValue = false; width = bbox[2] - bbox[0]; height = bbox[3] - bbox[1]; if (xStep != width || yStep != height) return false; // calculate offsets ctm = state->getCTM(); for (i = 0; i < 6; ++i) { savedCTM[i] = ctm[i]; } state->concatCTM(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); state->concatCTM(1, 0, 0, 1, bbox[0], bbox[1]); ctm = state->getCTM(); for (i = 0; i < 6; ++i) { if (!std::isfinite(ctm[i])) { state->setCTM(savedCTM[0], savedCTM[1], savedCTM[2], savedCTM[3], savedCTM[4], savedCTM[5]); return false; } } matc[4] = x0 * xStep * ctm[0] + y0 * yStep * ctm[2] + ctm[4]; matc[5] = x0 * xStep * ctm[1] + y0 * yStep * ctm[3] + ctm[5]; if (splashAbs(ctm[1]) > splashAbs(ctm[0])) { kx = -ctm[1]; ky = ctm[2] - (ctm[0] * ctm[3]) / ctm[1]; } else { kx = ctm[0]; ky = ctm[3] - (ctm[1] * ctm[2]) / ctm[0]; } result_width = (int)ceil(fabs(kx * width * (x1 - x0))); result_height = (int)ceil(fabs(ky * height * (y1 - y0))); kx = state->getHDPI() / 72.0; ky = state->getVDPI() / 72.0; m1.m[0] = (ptm[0] == 0) ? fabs(ptm[2]) * kx : fabs(ptm[0]) * kx; m1.m[1] = 0; m1.m[2] = 0; m1.m[3] = (ptm[3] == 0) ? fabs(ptm[1]) * ky : fabs(ptm[3]) * ky; m1.m[4] = 0; m1.m[5] = 0; m1.transform(width, height, &kx, &ky); surface_width = (int)ceil(fabs(kx)); surface_height = (int)ceil(fabs(ky)); sx = (double)result_width / (surface_width * (x1 - x0)); sy = (double)result_height / (surface_height * (y1 - y0)); m1.m[0] *= sx; m1.m[3] *= sy; m1.transform(width, height, &kx, &ky); if (fabs(kx) < 1 && fabs(ky) < 1) { kx = std::min(kx, ky); ky = 2 / kx; m1.m[0] *= ky; m1.m[3] *= ky; m1.transform(width, height, &kx, &ky); surface_width = (int)ceil(fabs(kx)); surface_height = (int)ceil(fabs(ky)); repeatX = x1 - x0; repeatY = y1 - y0; } else { if ((unsigned long)surface_width * surface_height > 0x800000L) { state->setCTM(savedCTM[0], savedCTM[1], savedCTM[2], savedCTM[3], savedCTM[4], savedCTM[5]); return false; } while (fabs(kx) > 16384 || fabs(ky) > 16384) { // limit pattern bitmap size m1.m[0] /= 2; m1.m[3] /= 2; m1.transform(width, height, &kx, &ky); } surface_width = (int)ceil(fabs(kx)); surface_height = (int)ceil(fabs(ky)); // adjust repeat values to completely fill region if (unlikely(surface_width == 0 || surface_height == 0)) { state->setCTM(savedCTM[0], savedCTM[1], savedCTM[2], savedCTM[3], savedCTM[4], savedCTM[5]); return false; } repeatX = result_width / surface_width; repeatY = result_height / surface_height; if (surface_width * repeatX < result_width) repeatX++; if (surface_height * repeatY < result_height) repeatY++; if (x1 - x0 > repeatX) repeatX = x1 - x0; if (y1 - y0 > repeatY) repeatY = y1 - y0; } // restore CTM and calculate rotate and scale with rounded matrix state->setCTM(savedCTM[0], savedCTM[1], savedCTM[2], savedCTM[3], savedCTM[4], savedCTM[5]); state->concatCTM(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); state->concatCTM(width * repeatX, 0, 0, height * repeatY, bbox[0], bbox[1]); ctm = state->getCTM(); matc[0] = ctm[0]; matc[1] = ctm[1]; matc[2] = ctm[2]; matc[3] = ctm[3]; if (surface_width == 0 || surface_height == 0 || repeatX * repeatY <= 4) { state->setCTM(savedCTM[0], savedCTM[1], savedCTM[2], savedCTM[3], savedCTM[4], savedCTM[5]); return false; } m1.transform(bbox[0], bbox[1], &kx, &ky); m1.m[4] = -kx; m1.m[5] = -ky; box.x1 = bbox[0]; box.y1 = bbox[1]; box.x2 = bbox[2]; box.y2 = bbox[3]; std::unique_ptr gfx = std::make_unique(doc, this, resDict, &box, nullptr, nullptr, nullptr, gfxA); // set pattern transformation matrix gfx->getState()->setCTM(m1.m[0], m1.m[1], m1.m[2], m1.m[3], m1.m[4], m1.m[5]); if (splashAbs(matc[1]) > splashAbs(matc[0])) { kx = -matc[1]; ky = matc[2] - (matc[0] * matc[3]) / matc[1]; } else { kx = matc[0]; ky = matc[3] - (matc[1] * matc[2]) / matc[0]; } result_width = surface_width * repeatX; result_height = surface_height * repeatY; kx = result_width / (fabs(kx) + 1); ky = result_height / (fabs(ky) + 1); state->concatCTM(kx, 0, 0, ky, 0, 0); ctm = state->getCTM(); matc[0] = ctm[0]; matc[1] = ctm[1]; matc[2] = ctm[2]; matc[3] = ctm[3]; const bool doFastBlit = matc[0] > 0 && matc[1] == 0 && matc[2] == 0 && matc[3] > 0; bitmap = new SplashBitmap(surface_width, surface_height, 1, (paintType == 1 || doFastBlit) ? colorMode : splashModeMono8, true); if (bitmap->getDataPtr() == nullptr) { SplashBitmap *tBitmap = bitmap; bitmap = formerBitmap; delete tBitmap; state->setCTM(savedCTM[0], savedCTM[1], savedCTM[2], savedCTM[3], savedCTM[4], savedCTM[5]); return false; } splash = new Splash(bitmap, true); updateCTM(gfx->getState(), m1.m[0], m1.m[1], m1.m[2], m1.m[3], m1.m[4], m1.m[5]); if (paintType == 2) { SplashColor clearColor; clearColor[0] = (colorMode == splashModeCMYK8 || colorMode == splashModeDeviceN8) ? 0x00 : 0xFF; splash->clear(clearColor, 0); } else { splash->clear(paperColor, 0); } splash->setThinLineMode(formerSplash->getThinLineMode()); splash->setMinLineWidth(s_minLineWidth); if (doFastBlit) { // drawImage would colorize the greyscale pattern in tilingBitmapSrc buffer accessor while tiling. // blitImage can't, it has no buffer accessor. We instead colorize the pattern prototype in advance. splash->setFillPattern(formerSplash->getFillPattern()->copy()); splash->setStrokePattern(formerSplash->getStrokePattern()->copy()); } gfx->display(str); delete splash; splash = formerSplash; TilingSplashOutBitmap imgData; imgData.bitmap = bitmap; imgData.paintType = paintType; imgData.pattern = splash->getFillPattern(); imgData.colorMode = colorMode; imgData.y = 0; imgData.repeatX = repeatX; imgData.repeatY = repeatY; SplashBitmap *tBitmap = bitmap; bitmap = formerBitmap; if (doFastBlit) { // draw the tiles for (int y = 0; y < imgData.repeatY; ++y) { for (int x = 0; x < imgData.repeatX; ++x) { x0 = splashFloor(matc[4]) + x * tBitmap->getWidth(); y0 = splashFloor(matc[5]) + y * tBitmap->getHeight(); splash->blitImage(tBitmap, true, x0, y0); } } retValue = true; } else { retValue = splash->drawImage(&tilingBitmapSrc, nullptr, &imgData, colorMode, true, result_width, result_height, matc, false, true) == splashOk; } delete tBitmap; return retValue; } bool SplashOutputDev::gouraudTriangleShadedFill(GfxState *state, GfxGouraudTriangleShading *shading) { GfxColorSpaceMode shadingMode = shading->getColorSpace()->getMode(); bool bDirectColorTranslation = false; // triggers an optimization. switch (colorMode) { case splashModeRGB8: bDirectColorTranslation = (shadingMode == csDeviceRGB); break; case splashModeCMYK8: case splashModeDeviceN8: bDirectColorTranslation = (shadingMode == csDeviceCMYK); break; default: break; } // restore vector antialias because we support it here SplashGouraudPattern splashShading(bDirectColorTranslation, state, shading); const bool vaa = getVectorAntialias(); setVectorAntialias(true); const bool retVal = splash->gouraudTriangleShadedFill(&splashShading); setVectorAntialias(vaa); return retVal; } bool SplashOutputDev::univariateShadedFill(GfxState *state, SplashUnivariatePattern *pattern, double tMin, double tMax) { double xMin, yMin, xMax, yMax; bool vaa = getVectorAntialias(); // restore vector antialias because we support it here setVectorAntialias(true); bool retVal = false; // get the clip region bbox if (pattern->getShading()->getHasBBox()) { pattern->getShading()->getBBox(&xMin, &yMin, &xMax, &yMax); } else { state->getClipBBox(&xMin, &yMin, &xMax, &yMax); xMin = floor(xMin); yMin = floor(yMin); xMax = ceil(xMax); yMax = ceil(yMax); { Matrix ctm, ictm; double x[4], y[4]; int i; state->getCTM(&ctm); ctm.invertTo(&ictm); ictm.transform(xMin, yMin, &x[0], &y[0]); ictm.transform(xMax, yMin, &x[1], &y[1]); ictm.transform(xMin, yMax, &x[2], &y[2]); ictm.transform(xMax, yMax, &x[3], &y[3]); xMin = xMax = x[0]; yMin = yMax = y[0]; for (i = 1; i < 4; i++) { xMin = std::min(xMin, x[i]); yMin = std::min(yMin, y[i]); xMax = std::max(xMax, x[i]); yMax = std::max(yMax, y[i]); } } } // fill the region state->moveTo(xMin, yMin); state->lineTo(xMax, yMin); state->lineTo(xMax, yMax); state->lineTo(xMin, yMax); state->closePath(); SplashPath path = convertPath(state, state->getPath(), true); pattern->getShading()->getColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS); setOverprintMask(pattern->getShading()->getColorSpace(), state->getFillOverprint(), state->getOverprintMode(), nullptr); // If state->getStrokePattern() is set, then the current clipping region // is a stroke path. retVal = (splash->shadedFill(&path, pattern->getShading()->getHasBBox(), pattern, (state->getStrokePattern() != nullptr)) == splashOk); state->clearPath(); setVectorAntialias(vaa); return retVal; } bool SplashOutputDev::functionShadedFill(GfxState *state, GfxFunctionShading *shading) { SplashFunctionPattern *pattern = new SplashFunctionPattern(colorMode, state, shading); double xMin, yMin, xMax, yMax; bool vaa = getVectorAntialias(); // restore vector antialias because we support it here setVectorAntialias(true); bool retVal = false; // get the clip region bbox if (pattern->getShading()->getHasBBox()) { pattern->getShading()->getBBox(&xMin, &yMin, &xMax, &yMax); } else { state->getClipBBox(&xMin, &yMin, &xMax, &yMax); xMin = floor(xMin); yMin = floor(yMin); xMax = ceil(xMax); yMax = ceil(yMax); { Matrix ctm, ictm; double x[4], y[4]; int i; state->getCTM(&ctm); ctm.invertTo(&ictm); ictm.transform(xMin, yMin, &x[0], &y[0]); ictm.transform(xMax, yMin, &x[1], &y[1]); ictm.transform(xMin, yMax, &x[2], &y[2]); ictm.transform(xMax, yMax, &x[3], &y[3]); xMin = xMax = x[0]; yMin = yMax = y[0]; for (i = 1; i < 4; i++) { xMin = std::min(xMin, x[i]); yMin = std::min(yMin, y[i]); xMax = std::max(xMax, x[i]); yMax = std::max(yMax, y[i]); } } } // fill the region state->moveTo(xMin, yMin); state->lineTo(xMax, yMin); state->lineTo(xMax, yMax); state->lineTo(xMin, yMax); state->closePath(); SplashPath path = convertPath(state, state->getPath(), true); pattern->getShading()->getColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS); setOverprintMask(pattern->getShading()->getColorSpace(), state->getFillOverprint(), state->getOverprintMode(), nullptr); // If state->getStrokePattern() is set, then the current clipping region // is a stroke path. retVal = (splash->shadedFill(&path, pattern->getShading()->getHasBBox(), pattern, (state->getStrokePattern() != nullptr)) == splashOk); state->clearPath(); setVectorAntialias(vaa); delete pattern; return retVal; } bool SplashOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax) { SplashAxialPattern *pattern = new SplashAxialPattern(colorMode, state, shading); bool retVal = univariateShadedFill(state, pattern, tMin, tMax); delete pattern; return retVal; } bool SplashOutputDev::radialShadedFill(GfxState *state, GfxRadialShading *shading, double tMin, double tMax) { SplashRadialPattern *pattern = new SplashRadialPattern(colorMode, state, shading); bool retVal = univariateShadedFill(state, pattern, tMin, tMax); delete pattern; return retVal; }