| //======================================================================== |
| // |
| // 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 <tiwai@suse.de> |
| // Copyright (C) 2006 Stefan Schweizer <genstef@gentoo.org> |
| // Copyright (C) 2006-2022, 2024 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2006 Krzysztof Kowalczyk <kkowalczyk@gmail.com> |
| // Copyright (C) 2006 Scott Turner <scotty1024@mac.com> |
| // Copyright (C) 2007 Koji Otani <sho@bbr.jp> |
| // Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com> |
| // Copyright (C) 2009-2016, 2020, 2022, 2023 Thomas Freitag <Thomas.Freitag@alfa.de> |
| // Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org> |
| // Copyright (C) 2009, 2014-2016, 2019 William Bader <williambader@hotmail.com> |
| // Copyright (C) 2010 Patrick Spendrin <ps_ml@gmx.de> |
| // Copyright (C) 2010 Brian Cameron <brian.cameron@oracle.com> |
| // Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com> |
| // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com> |
| // Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com> |
| // Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com> |
| // Copyright (C) 2011, 2012, 2017 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com> |
| // Copyright (C) 2013 Li Junling <lijunling@sina.com> |
| // Copyright (C) 2014 Ed Porras <ed@moto-research.com> |
| // Copyright (C) 2014 Richard PALO <richard@netbsd.org> |
| // Copyright (C) 2015 Tamas Szekeres <szekerest@gmail.com> |
| // Copyright (C) 2015 Kenji Uno <ku@digitaldolphins.jp> |
| // Copyright (C) 2016 Takahiro Hashimoto <kenya888.en@gmail.com> |
| // Copyright (C) 2017, 2021, 2024 Even Rouault <even.rouault@spatialys.com> |
| // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich |
| // Copyright (C) 2018, 2019 Stefan Brüns <stefan.bruens@rwth-aachen.de> |
| // Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de> |
| // Copyright (C) 2019 Christian Persch <chpe@src.gnome.org> |
| // Copyright (C) 2020-2022 Oliver Sander <oliver.sander@tu-dresden.de> |
| // |
| // 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 <config.h> |
| |
| #include <cstring> |
| #include <cmath> |
| #include <vector> |
| #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 <algorithm> |
| |
| 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) |
| { |
| // (int)(0.3 * r + 0.59 * g + 0.11 * b) = |
| // (int)(256 / 256 * 0.3 * r + 256 / 256 * 0.59 * g + 256 / 256 * 0.11 * b) |
| // (int)((77 * r + 151 * g + 28 * b) / 256) = // round! |
| return (int)((r * 77 + g * 151 + b * 28 + 0x80) >> 8); |
| } |
| |
| static int getSat(int r, int g, int b) |
| { |
| int rgbMin = std::min({ r, g, b }); |
| int rgbMax = std::max({ r, g, b }); |
| |
| return rgbMax - rgbMin; |
| } |
| |
| static void clipColor(int rIn, int gIn, int bIn, unsigned char *rOut, unsigned char *gOut, unsigned char *bOut) |
| { |
| int lum = getLum(rIn, gIn, bIn); |
| int rgbMin = std::min({ rIn, bIn, gIn }); |
| int rgbMax = std::max({ rIn, bIn, gIn }); |
| |
| if (rgbMin < 0) { |
| *rOut = (unsigned char)std::clamp(lum + ((rIn - lum) * lum) / (lum - rgbMin), 0, 255); |
| *gOut = (unsigned char)std::clamp(lum + ((gIn - lum) * lum) / (lum - rgbMin), 0, 255); |
| *bOut = (unsigned char)std::clamp(lum + ((bIn - lum) * lum) / (lum - rgbMin), 0, 255); |
| } else if (rgbMax > 255) { |
| *rOut = (unsigned char)std::clamp(lum + ((rIn - lum) * (255 - lum)) / (rgbMax - lum), 0, 255); |
| *gOut = (unsigned char)std::clamp(lum + ((gIn - lum) * (255 - lum)) / (rgbMax - lum), 0, 255); |
| *bOut = (unsigned char)std::clamp(lum + ((bIn - lum) * (255 - lum)) / (rgbMax - lum), 0, 255); |
| } 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)std::clamp(((rgbMid - rgbMin) * sat) / (rgbMax - rgbMin), 0, 255); |
| *maxOut = (unsigned char)std::clamp(sat, 0, 255); |
| } 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: |
| explicit 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({}, 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 dashStart; |
| |
| const std::vector<double> &dashPattern = state->getLineDash(&dashStart); |
| |
| std::vector<SplashCoord> dash(dashPattern.size()); |
| for (std::vector<double>::size_type i = 0; i < dashPattern.size(); ++i) { |
| dash[i] = (SplashCoord)dashPattern[i]; |
| if (dash[i] < 0) { |
| dash[i] = 0; |
| } |
| } |
| splash->setLineDash(std::move(dash), (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 && colorSpace->getMode() != csDeviceN) { |
| 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) |
| { |
| GfxFontType fontType; |
| SplashOutFontFileID *id = nullptr; |
| SplashFontFile *fontFile; |
| SplashFontSrc *fontsrc = nullptr; |
| 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; |
| |
| GfxFont *const gfxFont = state->getFont().get(); |
| if (!gfxFont) { |
| 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; |
| if (fontsrc && !fontsrc->isFile) { |
| fontsrc->unref(); |
| fontsrc = nullptr; |
| } |
| |
| id = new SplashOutFontFileID(gfxFont->getID()); |
| if ((fontFile = fontEngine->getFontFile(id))) { |
| delete id; |
| |
| } else { |
| |
| std::optional<GfxFontLoc> fontLoc = gfxFont->locateFont((xref) ? xref : doc->getXRef(), nullptr); |
| if (!fontLoc) { |
| error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); |
| goto err2; |
| } |
| |
| // embedded font |
| std::string fileName; |
| std::optional<std::vector<unsigned char>> tmpBuf; |
| |
| if (fontLoc->locType == gfxFontLocEmbedded) { |
| // if there is an embedded font, read it to memory |
| tmpBuf = gfxFont->readEmbFontFile((xref) ? xref : doc->getXRef()); |
| if (!tmpBuf) { |
| goto err2; |
| } |
| |
| // external font |
| } else { // gfxFontLocExternal |
| fileName = fontLoc->path; |
| fontType = fontLoc->fontType; |
| doAdjustFontMatrix = true; |
| } |
| |
| fontsrc = new SplashFontSrc; |
| if (!fileName.empty()) { |
| fontsrc->setFile(fileName); |
| } else { |
| fontsrc->setBuf(std::move(tmpBuf.value())); |
| } |
| |
| // 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: { |
| std::unique_ptr<FoFiTrueType> ff; |
| if (!fileName.empty()) { |
| ff = FoFiTrueType::load(fileName.c_str()); |
| } else { |
| ff = FoFiTrueType::make(fontsrc->buf.data(), fontsrc->buf.size()); |
| } |
| int *codeToGID; |
| const int n = ff ? 256 : 0; |
| if (ff) { |
| codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff.get()); |
| // 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 { |
| std::unique_ptr<FoFiTrueType> ff; |
| if (!fileName.empty()) { |
| ff = FoFiTrueType::load(fileName.c_str()); |
| } else { |
| ff = FoFiTrueType::make(fontsrc->buf.data(), fontsrc->buf.size()); |
| } |
| 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.get(), &n); |
| } |
| 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()); |
| } |
| |
| if (fontsrc && !fontsrc->isFile) { |
| fontsrc->unref(); |
| } |
| return; |
| |
| err2: |
| delete id; |
| 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) |
| { |
| std::shared_ptr<const 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) { |
| if (!imgMaskData.imgStr->getLine()) { |
| break; |
| } |
| ++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 |
| |
| if (!transpGroupStack) { |
| return; |
| } |
| |
| /* 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_checkoverflow(n); |
| if (likely(imgData.lookup != nullptr)) { |
| 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_checkoverflow(n, 3); |
| if (likely(imgData.lookup != nullptr)) { |
| 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_checkoverflow(n, 4); |
| if (likely(imgData.lookup != nullptr)) { |
| 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_checkoverflow(n, SPOT_NCOMPS + 4); |
| if (likely(imgData.lookup != nullptr)) { |
| 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, std::make_unique<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 *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; |
| } |
| } |
| |
| SplashBitmap *softMask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, false); |
| if (!softMask->getDataPtr()) { |
| delete softMask; |
| softMask = new SplashBitmap(1, 1, 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 > softMask->getWidth() - tx) { |
| xMax = softMask->getWidth() - tx; |
| } |
| if (yMax > softMask->getHeight() - ty) { |
| yMax = softMask->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, GfxTilingPattern *tPat, const double *mat, 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; |
| const double *bbox = tPat->getBBox(); |
| const double *ptm = tPat->getMatrix(); |
| const int paintType = tPat->getPaintType(); |
| Dict *resDict = tPat->getResDict(); |
| |
| 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] = std::max(fabs(ptm[0]), fabs(ptm[2])) * kx; |
| m1.m[1] = 0; |
| m1.m[2] = 0; |
| m1.m[3] = std::max(fabs(ptm[1]), 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<double>(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; |
| while ((unsigned long)repeatX * repeatY > 0x800000L) { |
| // try to avoid bogus memory allocation size |
| if (repeatX > 1) { |
| repeatX /= 2; |
| } |
| if (repeatY > 1) { |
| repeatY /= 2; |
| } |
| } |
| } 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> gfx = std::make_unique<Gfx>(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(tPat->getContentStream()); |
| 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; |
| if (!retValue) { |
| state->setCTM(savedCTM[0], savedCTM[1], savedCTM[2], savedCTM[3], savedCTM[4], savedCTM[5]); |
| } |
| 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<double>(xMin, x[i]); |
| yMin = std::min<double>(yMin, y[i]); |
| xMax = std::max<double>(xMax, x[i]); |
| yMax = std::max<double>(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<double>(xMin, x[i]); |
| yMin = std::min<double>(yMin, y[i]); |
| xMax = std::max<double>(xMax, x[i]); |
| yMax = std::max<double>(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; |
| } |