blob: edab1c3cf6b1412218c337143b2753de75b68660 [file] [log] [blame] [edit]
//========================================================================
//
// 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>
// Copyright (C) 2024 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
//
// 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(const SplashFontFileID &id) const override { return static_cast<const 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;
SplashFontFile *fontFile;
std::unique_ptr<SplashOutFontFileID> id;
SplashFontSrc *fontsrc = nullptr;
const double *textMat;
double m11, m12, m21, m22, fontSize;
SplashCoord mat[4];
bool recreateFont = false;
bool doAdjustFontMatrix = false;
needFontUpdate = false;
font = nullptr;
GfxFont *const gfxFont = state->getFont().get();
if (!gfxFont) {
goto err;
}
fontType = gfxFont->getType();
if (fontType == fontType3) {
goto err;
}
// 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 err;
}
// check the font file cache
reload:
if (fontsrc && !fontsrc->isFile) {
fontsrc->unref();
fontsrc = nullptr;
}
id = std::make_unique<SplashOutFontFileID>(gfxFont->getID());
if ((fontFile = fontEngine->getFontFile(*id))) {
id.reset();
} 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 err;
}
// 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 err;
}
// 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(std::move(id), fontsrc, (const char **)((Gfx8BitFont *)gfxFont)->getEncoding(), fontLoc->fontNum))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
if (gfxFont->invalidateEmbeddedFont()) {
goto reload;
}
goto err;
}
break;
case fontType1C:
if (!(fontFile = fontEngine->loadType1CFont(std::move(id), fontsrc, (const char **)((Gfx8BitFont *)gfxFont)->getEncoding(), fontLoc->fontNum))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
if (gfxFont->invalidateEmbeddedFont()) {
goto reload;
}
goto err;
}
break;
case fontType1COT:
if (!(fontFile = fontEngine->loadOpenTypeT1CFont(std::move(id), fontsrc, (const char **)((Gfx8BitFont *)gfxFont)->getEncoding(), fontLoc->fontNum))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
if (gfxFont->invalidateEmbeddedFont()) {
goto reload;
}
goto err;
}
break;
case fontTrueType:
case fontTrueTypeOT: {
std::unique_ptr<FoFiTrueType> ff;
if (!fileName.empty()) {
ff = FoFiTrueType::load(fileName.c_str(), fontLoc->fontNum);
} else {
ff = FoFiTrueType::make(fontsrc->buf.data(), fontsrc->buf.size(), fontLoc->fontNum);
}
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(std::move(id), fontsrc, codeToGID, n, fontLoc->fontNum))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
if (gfxFont->invalidateEmbeddedFont()) {
goto reload;
}
goto err;
}
break;
}
case fontCIDType0:
case fontCIDType0C:
if (!(fontFile = fontEngine->loadCIDFont(std::move(id), fontsrc, fontLoc->fontNum))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
if (gfxFont->invalidateEmbeddedFont()) {
goto reload;
}
goto err;
}
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(std::move(id), fontsrc, codeToGID, n, fontLoc->fontNum))) {
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 err;
}
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(), fontLoc->fontNum);
} else {
ff = FoFiTrueType::make(fontsrc->buf.data(), fontsrc->buf.size(), fontLoc->fontNum);
}
if (!ff) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
goto err;
}
codeToGID = ((GfxCIDFont *)gfxFont)->getCodeToGIDMap(ff.get(), &n);
}
if (!(fontFile = fontEngine->loadTrueTypeFont(std::move(id), fontsrc, codeToGID, n, fontLoc->fontNum))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
if (gfxFont->invalidateEmbeddedFont()) {
goto reload;
}
goto err;
}
break;
}
default:
// this shouldn't happen
goto err;
}
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;
err:
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;
}