blob: 38473c37df557b7b88c8255657d050135f577092 [file] [log] [blame]
//========================================================================
//
// ArthurOutputDev.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 Brad Hards <bradh@frogmouth.net>
// Copyright (C) 2005-2009, 2011, 2012, 2014, 2015, 2018 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2008, 2010 Pino Toscano <pino@kde.org>
// Copyright (C) 2009, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com>
// Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com>
// Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2013 Dominik Haumann <dhaumann@kde.org>
// Copyright (C) 2013 Mihai Niculescu <q.quark@gmail.com>
// Copyright (C) 2017, 2018 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.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 Adam Reichold <adam.reichold@t-online.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 <string.h>
#include <math.h>
#include <array>
#include "goo/gfile.h"
#include "GlobalParams.h"
#include "Error.h"
#include "Object.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "Link.h"
#include "FontEncodingTables.h"
#include <fofi/FoFiTrueType.h>
#include "ArthurOutputDev.h"
#include "Page.h"
#include "Gfx.h"
#include "PDFDoc.h"
#include <QtCore/QtDebug>
#include <QRawFont>
#include <QGlyphRun>
#include <QtGui/QPainterPath>
#include <QPicture>
//------------------------------------------------------------------------
#ifdef HAVE_SPLASH
#include "splash/SplashFontFileID.h"
#include "splash/SplashFTFontFile.h"
#include "splash/SplashFontEngine.h"
//------------------------------------------------------------------------
// SplashOutFontFileID
//------------------------------------------------------------------------
class SplashOutFontFileID: public SplashFontFileID {
public:
SplashOutFontFileID(const Ref *rA) { r = *rA; }
~SplashOutFontFileID() {}
GBool matches(SplashFontFileID *id) override {
return ((SplashOutFontFileID *)id)->r.num == r.num &&
((SplashOutFontFileID *)id)->r.gen == r.gen;
}
private:
Ref r;
};
#endif
class ArthurType3Font
{
public:
ArthurType3Font(PDFDoc* doc, Gfx8BitFont* font);
const QPicture& getGlyph(int gid) const;
private:
PDFDoc* m_doc;
Gfx8BitFont* m_font;
mutable std::vector<std::unique_ptr<QPicture> > glyphs;
public:
std::vector<int> codeToGID;
};
ArthurType3Font::ArthurType3Font(PDFDoc* doc, Gfx8BitFont* font)
: m_doc(doc), m_font(font)
{
char *name;
const Dict* charProcs = font->getCharProcs();
// Storage for the rendered glyphs
glyphs.resize(charProcs->getLength());
// Compute the code-to-GID map
char **enc = font->getEncoding();
codeToGID.resize(256);
for (int i = 0; i < 256; ++i) {
codeToGID[i] = 0;
if (charProcs && (name = enc[i])) {
for (int j = 0; j < charProcs->getLength(); j++) {
if (strcmp(name, charProcs->getKey(j)) == 0) {
codeToGID[i] = j;
}
}
}
}
}
const QPicture& ArthurType3Font::getGlyph(int gid) const
{
if (!glyphs[gid]) {
// Glyph has not been rendered before: render it now
// Smallest box that contains all the glyphs from this font
const double* fontBBox = m_font->getFontBBox();
PDFRectangle box(fontBBox[0], fontBBox[1], fontBBox[2], fontBBox[3]);
Dict* resDict = m_font->getResources();
QPainter glyphPainter;
glyphs[gid] = std::make_unique<QPicture>();
glyphPainter.begin(glyphs[gid].get());
auto output_dev = std::make_unique<ArthurOutputDev>(&glyphPainter);
auto gfx = std::make_unique<Gfx>(
m_doc, output_dev.get(), resDict,
&box, // pagebox
nullptr // cropBox
);
output_dev->startDoc(m_doc);
output_dev->startPage (1, gfx->getState(), gfx->getXRef());
const Dict* charProcs = m_font->getCharProcs();
Object charProc = charProcs->getVal(gid);
gfx->display(&charProc);
glyphPainter.end();
}
return *glyphs[gid];
}
//------------------------------------------------------------------------
// ArthurOutputDev
//------------------------------------------------------------------------
ArthurOutputDev::ArthurOutputDev(QPainter *painter):
m_lastTransparencyGroupPicture(nullptr),
m_fontHinting(NoHinting)
{
m_painter.push(painter);
m_currentBrush = QBrush(Qt::SolidPattern);
m_fontEngine = nullptr;
}
ArthurOutputDev::~ArthurOutputDev()
{
#ifdef HAVE_SPLASH
delete m_fontEngine;
#endif
}
void ArthurOutputDev::startDoc(PDFDoc* doc) {
xref = doc->getXRef();
m_doc = doc;
#ifdef HAVE_SPLASH
delete m_fontEngine;
const bool isHintingEnabled = m_fontHinting != NoHinting;
const bool isSlightHinting = m_fontHinting == SlightHinting;
m_fontEngine = new SplashFontEngine(
globalParams->getEnableFreeType(),
isHintingEnabled,
isSlightHinting,
m_painter.top()->testRenderHint(QPainter::TextAntialiasing));
#endif
}
void ArthurOutputDev::startPage(int pageNum, GfxState *state, XRef *)
{}
void ArthurOutputDev::endPage() {
}
void ArthurOutputDev::saveState(GfxState *state)
{
m_currentPenStack.push(m_currentPen);
m_currentBrushStack.push(m_currentBrush);
m_rawFontStack.push(m_rawFont);
m_type3FontStack.push(m_currentType3Font);
m_codeToGIDStack.push(m_codeToGID);
m_painter.top()->save();
}
void ArthurOutputDev::restoreState(GfxState *state)
{
m_painter.top()->restore();
m_codeToGID = m_codeToGIDStack.top();
m_codeToGIDStack.pop();
m_rawFont = m_rawFontStack.top();
m_rawFontStack.pop();
m_currentType3Font = m_type3FontStack.top();
m_type3FontStack.pop();
m_currentBrush = m_currentBrushStack.top();
m_currentBrushStack.pop();
m_currentPen = m_currentPenStack.top();
m_currentPenStack.pop();
}
void ArthurOutputDev::updateAll(GfxState *state)
{
OutputDev::updateAll(state);
m_needFontUpdate = gTrue;
}
// Set CTM (Current Transformation Matrix) to a fixed matrix
void ArthurOutputDev::setDefaultCTM(const double *ctm)
{
m_painter.top()->setTransform(QTransform(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]));
}
// Update the CTM (Current Transformation Matrix), i.e., compose the old
// CTM with a new matrix.
void ArthurOutputDev::updateCTM(GfxState *state, double m11, double m12,
double m21, double m22,
double m31, double m32)
{
updateLineDash(state);
updateLineJoin(state);
updateLineCap(state);
updateLineWidth(state);
QTransform update(m11, m12, m21, m22, m31, m32);
// We could also set (rather than update) the painter transformation to state->getCMT();
m_painter.top()->setTransform(update, true);
}
void ArthurOutputDev::updateLineDash(GfxState *state)
{
double *dashPattern;
int dashLength;
double dashStart;
state->getLineDash(&dashPattern, &dashLength, &dashStart);
// Special handling for zero-length patterns, i.e., solid lines.
// Simply calling QPen::setDashPattern with an empty pattern does *not*
// result in a solid line. Rather, the current pattern is unchanged.
// See the implementation of the setDashPattern method in the file qpen.cpp.
if (dashLength==0)
{
m_currentPen.setStyle(Qt::SolidLine);
m_painter.top()->setPen(m_currentPen);
return;
}
QVector<qreal> pattern(dashLength);
for (int i = 0; i < dashLength; ++i) {
// pdf measures the dash pattern in dots, but Qt uses the
// line width as the unit.
pattern[i] = dashPattern[i] / state->getLineWidth();
}
m_currentPen.setDashPattern(pattern);
m_currentPen.setDashOffset(dashStart);
m_painter.top()->setPen(m_currentPen);
}
void ArthurOutputDev::updateFlatness(GfxState *state)
{
// qDebug() << "updateFlatness";
}
void ArthurOutputDev::updateLineJoin(GfxState *state)
{
switch (state->getLineJoin()) {
case 0:
// The correct style here is Qt::SvgMiterJoin, *not* Qt::MiterJoin.
// The two differ in what to do if the miter limit is exceeded.
// See https://bugs.freedesktop.org/show_bug.cgi?id=102356
m_currentPen.setJoinStyle(Qt::SvgMiterJoin);
break;
case 1:
m_currentPen.setJoinStyle(Qt::RoundJoin);
break;
case 2:
m_currentPen.setJoinStyle(Qt::BevelJoin);
break;
}
m_painter.top()->setPen(m_currentPen);
}
void ArthurOutputDev::updateLineCap(GfxState *state)
{
switch (state->getLineCap()) {
case 0:
m_currentPen.setCapStyle(Qt::FlatCap);
break;
case 1:
m_currentPen.setCapStyle(Qt::RoundCap);
break;
case 2:
m_currentPen.setCapStyle(Qt::SquareCap);
break;
}
m_painter.top()->setPen(m_currentPen);
}
void ArthurOutputDev::updateMiterLimit(GfxState *state)
{
m_currentPen.setMiterLimit(state->getMiterLimit());
m_painter.top()->setPen(m_currentPen);
}
void ArthurOutputDev::updateLineWidth(GfxState *state)
{
m_currentPen.setWidthF(state->getLineWidth());
m_painter.top()->setPen(m_currentPen);
// The updateLineDash method needs to know the line width, but it is sometimes
// called before the updateLineWidth method. To make sure that the last call
// to updateLineDash before a drawing operation is always with the correct line
// width, we call it here, right after a change to the line width.
updateLineDash(state);
}
void ArthurOutputDev::updateFillColor(GfxState *state)
{
GfxRGB rgb;
QColor brushColour = m_currentBrush.color();
state->getFillRGB(&rgb);
brushColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), brushColour.alphaF());
m_currentBrush.setColor(brushColour);
}
void ArthurOutputDev::updateStrokeColor(GfxState *state)
{
GfxRGB rgb;
QColor penColour = m_currentPen.color();
state->getStrokeRGB(&rgb);
penColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), penColour.alphaF());
m_currentPen.setColor(penColour);
m_painter.top()->setPen(m_currentPen);
}
void ArthurOutputDev::updateBlendMode(GfxState * state)
{
GfxBlendMode blendMode = state->getBlendMode();
// missing composition modes in QPainter:
// - CompositionMode_Hue
// - CompositionMode_Color
// - CompositionMode_Luminosity
// - CompositionMode_Saturation
switch(blendMode){
case gfxBlendMultiply:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_Multiply);
break;
case gfxBlendScreen:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_Screen);
break;
case gfxBlendDarken:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_Darken);
break;
case gfxBlendLighten:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_Lighten);
break;
case gfxBlendColorDodge:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_ColorDodge);
break;
case gfxBlendColorBurn:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_ColorBurn);
break;
case gfxBlendHardLight:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_HardLight);
break;
case gfxBlendSoftLight:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_SoftLight);
break;
case gfxBlendDifference:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_Difference);
break;
case gfxBlendExclusion:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_Exclusion);
break;
case gfxBlendColor:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_Plus);
break;
default:
qDebug() << "Unsupported blend mode, falling back to CompositionMode_SourceOver";
case gfxBlendNormal:
m_painter.top()->setCompositionMode(QPainter::CompositionMode_SourceOver);
break;
}
}
void ArthurOutputDev::updateFillOpacity(GfxState *state)
{
QColor brushColour= m_currentBrush.color();
brushColour.setAlphaF(state->getFillOpacity());
m_currentBrush.setColor(brushColour);
}
void ArthurOutputDev::updateStrokeOpacity(GfxState *state)
{
QColor penColour= m_currentPen.color();
penColour.setAlphaF(state->getStrokeOpacity());
m_currentPen.setColor(penColour);
m_painter.top()->setPen(m_currentPen);
}
void ArthurOutputDev::updateFont(GfxState *state)
{
GfxFont *gfxFont = state->getFont();
if (!gfxFont)
{
return;
}
// The key to look in the font caches
ArthurFontID fontID = {*gfxFont->getID(), state->getFontSize()};
// Current font is a type3 font
if (gfxFont->getType() == fontType3)
{
auto cacheEntry = m_type3FontCache.find(fontID);
if (cacheEntry!=m_type3FontCache.end()) {
// Take the font from the cache
m_currentType3Font = cacheEntry->second.get();
} else {
m_currentType3Font = new ArthurType3Font(m_doc, (Gfx8BitFont*)gfxFont);
m_type3FontCache.insert(std::make_pair(fontID,std::unique_ptr<ArthurType3Font>(m_currentType3Font)));
}
return;
}
// Non-type3: is the font in the cache?
auto cacheEntry = m_rawFontCache.find(fontID);
if (cacheEntry!=m_rawFontCache.end()) {
// Take the font from the cache
m_rawFont = cacheEntry->second.get();
} else {
// New font: load it into the cache
float fontSize = state->getFontSize();
std::unique_ptr<GfxFontLoc> fontLoc(gfxFont->locateFont(xref, nullptr));
if (fontLoc) {
// load the font from respective location
switch (fontLoc->locType) {
case gfxFontLocEmbedded: {// if there is an embedded font, read it to memory
int fontDataLen;
const char* fontData = gfxFont->readEmbFontFile(xref, &fontDataLen);
m_rawFont = new QRawFont(QByteArray(fontData, fontDataLen), fontSize);
m_rawFontCache.insert(std::make_pair(fontID,std::unique_ptr<QRawFont>(m_rawFont)));
// Free the font data, it was copied in the QByteArray constructor
free((char*)fontData);
break;
}
case gfxFontLocExternal:{ // font is in an external font file
QString fontFile(fontLoc->path->getCString());
m_rawFont = new QRawFont(fontFile, fontSize);
m_rawFontCache.insert(std::make_pair(fontID,std::unique_ptr<QRawFont>(m_rawFont)));
break;
}
case gfxFontLocResident:{ // font resides in a PS printer
qDebug() << "Font Resident Encoding:" << fontLoc->encoding->getCString() << ", not implemented yet!";
break;
}
}// end switch
} else {
qDebug() << "Font location not found!";
return;
}
}
if (!m_rawFont->isValid()) {
qDebug() << "RawFont is not valid";
}
// *****************************************************************************
// We have now successfully loaded the font into a QRawFont object. This
// allows us to draw all the glyphs in the font. However, what is missing is
// the charcode-to-glyph-index mapping. Apparently, Qt does not provide this
// information at all. We therefore now load the font again, this time into
// a Splash font object. This is wasteful, but I see no other way to access
// the important glyph-index mapping.
// *****************************************************************************
#ifdef HAVE_SPLASH
GfxFontType fontType;
SplashFontFile *fontFile;
SplashFontSrc *fontsrc = nullptr;
FoFiTrueType *ff;
GooString *fileName;
char *tmpBuf;
int tmpBufLen = 0;
int *codeToGID;
SplashCoord mat[4] = {0,0,0,0};
int n;
int faceIndex = 0;
SplashCoord matrix[6] = {1,0,0,1,0,0};
SplashFTFontFile* ftFontFile;
m_needFontUpdate = false;
fileName = nullptr;
tmpBuf = nullptr;
fontType = gfxFont->getType();
if (fontType == fontType3) {
return;
}
// Default: no codeToGID table
m_codeToGID = nullptr;
// check the font file cache
SplashOutFontFileID *id = new SplashOutFontFileID(gfxFont->getID());
if ((fontFile = m_fontEngine->getFontFile(id))) {
delete id;
} else {
std::unique_ptr<GfxFontLoc> fontLoc(gfxFont->locateFont(xref, nullptr));
if (!fontLoc) {
error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'",
gfxFont->getName() ? gfxFont->getName()->getCString()
: "(unnamed)");
goto err2;
}
// embedded font
if (fontLoc->locType == gfxFontLocEmbedded) {
// if there is an embedded font, read it to memory
tmpBuf = gfxFont->readEmbFontFile(xref, &tmpBufLen);
if (! tmpBuf)
goto err2;
// external font
} else { // gfxFontLocExternal
fileName = fontLoc->path;
fontType = fontLoc->fontType;
}
fontsrc = new SplashFontSrc;
if (fileName)
fontsrc->setFile(fileName, gFalse);
else
fontsrc->setBuf(tmpBuf, tmpBufLen, gTrue);
// load the font file
switch (fontType) {
case fontType1:
if (!(fontFile = m_fontEngine->loadType1Font(
id,
fontsrc,
(const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'",
gfxFont->getName() ? gfxFont->getName()->getCString()
: "(unnamed)");
goto err2;
}
break;
case fontType1C:
if (!(fontFile = m_fontEngine->loadType1CFont(
id,
fontsrc,
(const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'",
gfxFont->getName() ? gfxFont->getName()->getCString()
: "(unnamed)");
goto err2;
}
break;
case fontType1COT:
if (!(fontFile = m_fontEngine->loadOpenTypeT1CFont(
id,
fontsrc,
(const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'",
gfxFont->getName() ? gfxFont->getName()->getCString()
: "(unnamed)");
goto err2;
}
break;
case fontTrueType:
case fontTrueTypeOT:
if (fileName)
ff = FoFiTrueType::load(fileName->getCString());
else
ff = FoFiTrueType::make(tmpBuf, tmpBufLen);
if (ff) {
codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff);
n = 256;
delete ff;
} else {
codeToGID = nullptr;
n = 0;
}
if (!(fontFile = m_fontEngine->loadTrueTypeFont(
id,
fontsrc,
codeToGID, n))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'",
gfxFont->getName() ? gfxFont->getName()->getCString()
: "(unnamed)");
goto err2;
}
break;
case fontCIDType0:
case fontCIDType0C:
if (!(fontFile = m_fontEngine->loadCIDFont(
id,
fontsrc))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'",
gfxFont->getName() ? gfxFont->getName()->getCString()
: "(unnamed)");
goto err2;
}
break;
case fontCIDType0COT:
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 = m_fontEngine->loadOpenTypeCFFFont(
id,
fontsrc,
codeToGID, n))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'",
gfxFont->getName() ? gfxFont->getName()->getCString()
: "(unnamed)");
goto err2;
}
break;
case fontCIDType2:
case fontCIDType2OT:
codeToGID = nullptr;
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(Gushort));
}
} else {
if (fileName)
ff = FoFiTrueType::load(fileName->getCString());
else
ff = FoFiTrueType::make(tmpBuf, tmpBufLen);
if (! ff)
goto err2;
codeToGID = ((GfxCIDFont *)gfxFont)->getCodeToGIDMap(ff, &n);
delete ff;
}
if (!(fontFile = m_fontEngine->loadTrueTypeFont(
id,
fontsrc,
codeToGID, n, faceIndex))) {
error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'",
gfxFont->getName() ? gfxFont->getName()->getCString()
: "(unnamed)");
goto err2;
}
break;
default:
// this shouldn't happen
goto err2;
}
}
ftFontFile = dynamic_cast<SplashFTFontFile*>(fontFile);
if (ftFontFile)
m_codeToGID = ftFontFile->getCodeToGID();
// create dummy font
// The font matrices are bogus, but we will never use the glyphs anyway.
// However we need to call m_fontEngine->getFont, in order to have the
// font in the Splash font cache. Otherwise we'd load it again and again.
m_fontEngine->getFont(fontFile, mat, matrix);
if (fontsrc && !fontsrc->isFile)
fontsrc->unref();
return;
err2:
delete id;
#endif
}
static QPainterPath convertPath(GfxState *state, GfxPath *path, Qt::FillRule fillRule)
{
GfxSubpath *subpath;
int i, j;
QPainterPath qPath;
qPath.setFillRule(fillRule);
for (i = 0; i < path->getNumSubpaths(); ++i) {
subpath = path->getSubpath(i);
if (subpath->getNumPoints() > 0) {
qPath.moveTo(subpath->getX(0), subpath->getY(0));
j = 1;
while (j < subpath->getNumPoints()) {
if (subpath->getCurve(j)) {
qPath.cubicTo( subpath->getX(j), subpath->getY(j),
subpath->getX(j+1), subpath->getY(j+1),
subpath->getX(j+2), subpath->getY(j+2));
j += 3;
} else {
qPath.lineTo(subpath->getX(j), subpath->getY(j));
++j;
}
}
if (subpath->isClosed()) {
qPath.closeSubpath();
}
}
}
return qPath;
}
void ArthurOutputDev::stroke(GfxState *state)
{
m_painter.top()->strokePath( convertPath( state, state->getPath(), Qt::OddEvenFill ), m_currentPen );
}
void ArthurOutputDev::fill(GfxState *state)
{
m_painter.top()->fillPath( convertPath( state, state->getPath(), Qt::WindingFill ), m_currentBrush );
}
void ArthurOutputDev::eoFill(GfxState *state)
{
m_painter.top()->fillPath( convertPath( state, state->getPath(), Qt::OddEvenFill ), m_currentBrush );
}
GBool ArthurOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax)
{
double x0, y0, x1, y1;
shading->getCoords(&x0, &y0, &x1, &y1);
// get the clip region bbox
double xMin, yMin, xMax, yMax;
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
// get the function domain
double t0 = shading->getDomain0();
double t1 = shading->getDomain1();
// Max number of splits along the t axis
constexpr int maxSplits = 256;
// Max delta allowed in any color component
const double colorDelta = (dblToCol(1 / 256.0));
// Number of color space components
auto nComps = shading->getColorSpace()->getNComps();
// Helper function to test two color objects for 'almost-equality'
auto isSameGfxColor = [&nComps,&colorDelta](const GfxColor &colorA, const GfxColor &colorB)
{
for (int k = 0; k < nComps; ++k) {
if (abs(colorA.c[k] - colorB.c[k]) > colorDelta) {
return false;
}
}
return true;
};
// Helper function: project a number into an interval
// With C++17 this is part of the standard library
auto clamp = [](double v, double lo, double hi)
{ return std::min(std::max(v,lo), hi); };
// ta stores all parameter values where we evaluate the input shading function.
// In between, QLinearGradient will interpolate linearly.
// We set up the array with three values.
std::array<double, maxSplits+1> ta;
ta[0] = tMin;
std::array<int, maxSplits+1> next;
next[0] = maxSplits / 2;
ta[maxSplits / 2] = 0.5 * (tMin + tMax);
next[maxSplits / 2] = maxSplits;
ta[maxSplits] = tMax;
// compute the color at t = tMin
double tt = clamp(t0 + (t1 - t0) * tMin, t0, t1);
GfxColor color0, color1;
shading->getColor(tt, &color0);
// Construct a gradient object and set its color at one parameter end
QLinearGradient gradient(QPointF(x0 + tMin * (x1 - x0), y0 + tMin * (y1 - y0)),
QPointF(x0 + tMax * (x1 - x0), y0 + tMax * (y1 - y0)));
GfxRGB rgb;
shading->getColorSpace()->getRGB(&color0, &rgb);
QColor qColor(colToByte(rgb.r), colToByte(rgb.g), colToByte(rgb.b));
gradient.setColorAt(0,qColor);
// Look for more relevant parameter values by bisection
int i = 0;
while (i < maxSplits) {
int j = next[i];
while (j > i + 1) {
// Next parameter value to try
tt = clamp(t0 + (t1 - t0) * ta[j], t0, t1);
shading->getColor(tt, &color1);
// j is a good next color stop if the input shading can be approximated well
// on the interval (ta[i], ta[j]) by a linear interpolation.
// We test this by comparing the real color in the middle between ta[i] and ta[j]
// with the linear interpolant there.
auto midPoint = 0.5 * (ta[i] + ta[j]);
GfxColor colorAtMidPoint;
shading->getColor(midPoint, &colorAtMidPoint);
GfxColor linearlyInterpolatedColor;
for (int ii=0; ii<nComps; ii++)
linearlyInterpolatedColor.c[ii] = 0.5 * (color0.c[ii] + color1.c[ii]);
// If the two colors are equal, ta[j] is a good place for the next color stop; take it!
if (isSameGfxColor(colorAtMidPoint, linearlyInterpolatedColor))
break;
// Otherwise: bisect further
int k = (i + j) / 2;
ta[k] = midPoint;
next[i] = k;
next[k] = j;
j = k;
}
// set the color
GfxRGB rgb;
shading->getColorSpace()->getRGB(&color1, &rgb);
qColor.setRgb(colToByte(rgb.r), colToByte(rgb.g), colToByte(rgb.b));
gradient.setColorAt((ta[j] - tMin)/(tMax - tMin), qColor);
// Move to the next parameter region
color0 = color1;
i = next[i];
}
state->moveTo(xMin, yMin);
state->lineTo(xMin, yMax);
state->lineTo(xMax, yMax);
state->lineTo(xMax, yMin);
state->closePath();
// Actually paint the shaded region
QBrush newBrush(gradient);
m_painter.top()->fillPath( convertPath( state, state->getPath(), Qt::WindingFill ), newBrush );
state->clearPath();
// True means: The shaded region has been painted
return gTrue;
}
void ArthurOutputDev::clip(GfxState *state)
{
m_painter.top()->setClipPath(convertPath( state, state->getPath(), Qt::WindingFill ), Qt::IntersectClip );
}
void ArthurOutputDev::eoClip(GfxState *state)
{
m_painter.top()->setClipPath(convertPath( state, state->getPath(), Qt::OddEvenFill ), Qt::IntersectClip );
}
void ArthurOutputDev::drawChar(GfxState *state, double x, double y,
double dx, double dy,
double originX, double originY,
CharCode code, int nBytes, Unicode *u, int uLen) {
// First handle type3 fonts
GfxFont *gfxFont = state->getFont();
GfxFontType fontType = gfxFont->getType();
if (fontType == fontType3) {
/////////////////////////////////////////////////////////////////////
// Draw the QPicture that contains the glyph onto the page
/////////////////////////////////////////////////////////////////////
// Store the QPainter state; we need to modify it temporarily
m_painter.top()->save();
// Make the glyph position the coordinate origin -- that's our center of scaling
m_painter.top()->translate(QPointF(x-originX, y-originY));
const double* mat = gfxFont->getFontMatrix();
QTransform fontMatrix(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
// Scale with the font size
fontMatrix.scale(state->getFontSize(), state->getFontSize());
m_painter.top()->setTransform(fontMatrix,true);
// Apply the text matrix on top
const double *textMat = state->getTextMat();
QTransform textTransform(textMat[0] * state->getHorizScaling(),
textMat[1] * state->getHorizScaling(),
textMat[2],
textMat[3],
0,
0);
m_painter.top()->setTransform(textTransform,true);
// Actually draw the glyph
int gid = m_currentType3Font->codeToGID[code];
m_painter.top()->drawPicture(QPointF(0,0), m_currentType3Font->getGlyph(gid));
// Restore transformation
m_painter.top()->restore();
return;
}
// check for invisible text -- this is used by Acrobat Capture
int render = state->getRender();
if (render == 3 || !m_rawFont) {
qDebug() << "Invisible text found!";
return;
}
if (!(render & 1))
{
quint32 glyphIndex = (m_codeToGID) ? m_codeToGID[code] : code;
QPointF glyphPosition = QPointF(x-originX, y-originY);
// QGlyphRun objects can hold an entire sequence of glyphs, and it would possibly
// be more efficient to simply note the glyph and glyph position here and then
// draw several glyphs at once in the endString method. What keeps us from doing
// that is the transformation below: each glyph needs to be drawn upside down,
// i.e., reflected at its own baseline. Since we have no guarantee that this
// baseline is the same for all glyphs in a string we have to do it one by one.
QGlyphRun glyphRun;
glyphRun.setRawData(&glyphIndex, &glyphPosition, 1);
glyphRun.setRawFont(*m_rawFont);
// Store the QPainter state; we need to modify it temporarily
m_painter.top()->save();
// Apply the text matrix to the glyph. The glyph is not scaled by the font size,
// because the font in m_rawFont already has the correct size.
// Additionally, the CTM is upside down, i.e., it contains a negative Y-scaling
// entry. Therefore, Qt will paint the glyphs upside down. We need to temporarily
// reflect the page at glyphPosition.y().
// Make the glyph position the coordinate origin -- that's our center of scaling
const double *textMat = state->getTextMat();
m_painter.top()->translate(QPointF(glyphPosition.x(),glyphPosition.y()));
QTransform textTransform(textMat[0] * state->getHorizScaling(),
textMat[1] * state->getHorizScaling(),
-textMat[2], // reflect at the horizontal axis,
-textMat[3], // because CTM is upside-down.
0,
0);
m_painter.top()->setTransform(textTransform,true);
// We are painting a filled glyph here. But QPainter uses the pen to draw even filled text,
// not the brush. (see, e.g., http://doc.qt.io/qt-5/qpainter.html#setPen )
// Therefore we have to temporarily overwrite the pen color.
// Since we are drawing a filled glyph, one would really expect to have m_currentBrush
// have the correct color. However, somehow state->getFillRGB can change without
// updateFillColor getting called. Then m_currentBrush may not contain the correct color.
GfxRGB rgb;
state->getFillRGB(&rgb);
QColor fontColor;
fontColor.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), state->getFillOpacity());
m_painter.top()->setPen(fontColor);
// Actually draw the glyph
m_painter.top()->drawGlyphRun(QPointF(-glyphPosition.x(),-glyphPosition.y()), glyphRun);
// Restore transformation and pen color
m_painter.top()->restore();
}
}
void ArthurOutputDev::type3D0(GfxState *state, double wx, double wy)
{
}
void ArthurOutputDev::type3D1(GfxState *state, double wx, double wy,
double llx, double lly, double urx, double ury)
{
}
void ArthurOutputDev::endTextObject(GfxState *state)
{
}
void ArthurOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
int width, int height, GBool invert,
GBool interpolate, GBool inlineImg)
{
auto imgStr = std::make_unique<ImageStream>(
str, width,
1, // numPixelComps
1 // getBits
);
imgStr->reset();
// TODO: Would using QImage::Format_Mono be more efficient here?
QImage image(width, height, QImage::Format_ARGB32);
unsigned int *data = (unsigned int *)image.bits();
int stride = image.bytesPerLine()/4;
QRgb fillColor = m_currentBrush.color().rgb();
for (int y = 0; y < height; y++) {
Guchar *pix = imgStr->getLine();
// Invert the vertical coordinate: y is increasing from top to bottom
// on the page, but y is increasing bottom to top in the picture.
unsigned int* dest = data + (height-1-y) * stride;
for (int x = 0; x < width; x++) {
bool opaque = ((bool)pix[x]) == invert;
dest[x] = (opaque) ? fillColor : 0;
}
}
// At this point, the QPainter coordinate transformation (CTM) is such
// that QRect(0,0,1,1) is exactly the area of the image.
m_painter.top()->drawImage( QRect(0,0,1,1), image );
imgStr->close ();
}
//TODO: lots more work here.
void ArthurOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
int width, int height,
GfxImageColorMap *colorMap,
GBool interpolate, int *maskColors, GBool inlineImg)
{
unsigned int *data;
unsigned int *line;
int x, y;
Guchar *pix;
int i;
QImage image;
int stride;
/* TODO: Do we want to cache these? */
auto imgStr = std::make_unique<ImageStream>(
str, width,
colorMap->getNumPixelComps(),
colorMap->getBits()
);
imgStr->reset();
image = QImage(width, height, QImage::Format_ARGB32);
data = (unsigned int *)image.bits();
stride = image.bytesPerLine()/4;
for (y = 0; y < height; y++) {
pix = imgStr->getLine();
// Invert the vertical coordinate: y is increasing from top to bottom
// on the page, but y is increasing bottom to top in the picture.
line = data+(height-1-y)*stride;
colorMap->getRGBLine(pix, line, width);
if (maskColors) {
for (x = 0; x < width; x++) {
for (i = 0; i < colorMap->getNumPixelComps(); ++i) {
if (pix[i] < maskColors[2*i] * 255||
pix[i] > maskColors[2*i+1] * 255) {
*line = *line | 0xff000000;
break;
}
}
pix += colorMap->getNumPixelComps();
line++;
}
} else {
for (x = 0; x < width; x++) { *line = *line | 0xff000000; line++; }
}
}
// At this point, the QPainter coordinate transformation (CTM) is such
// that QRect(0,0,1,1) is exactly the area of the image.
m_painter.top()->drawImage( QRect(0,0,1,1), image );
}
void ArthurOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
int width, int height,
GfxImageColorMap *colorMap,
GBool interpolate,
Stream *maskStr,
int maskWidth, int maskHeight,
GfxImageColorMap *maskColorMap,
GBool maskInterpolate)
{
// Bail out if the image size doesn't match the mask size. I don't know
// what to do in this case.
if (width!=maskWidth || height!=maskHeight)
{
qDebug() << "Soft mask size does not match image size!";
drawImage(state, ref, str, width, height, colorMap, interpolate, nullptr, gFalse);
return;
}
// Bail out if the mask isn't a single channel. I don't know
// what to do in this case.
if (maskColorMap->getColorSpace()->getNComps() != 1)
{
qDebug() << "Soft mask is not a single 8-bit channel!";
drawImage(state, ref, str, width, height, colorMap, interpolate, nullptr, gFalse);
return;
}
/* TODO: Do we want to cache these? */
auto imgStr = std::make_unique<ImageStream>(
str, width,
colorMap->getNumPixelComps(),
colorMap->getBits()
);
imgStr->reset();
auto maskImageStr = std::make_unique<ImageStream>(
maskStr, maskWidth,
maskColorMap->getNumPixelComps(),
maskColorMap->getBits()
);
maskImageStr->reset();
QImage image(width, height, QImage::Format_ARGB32);
unsigned int *data = (unsigned int *)image.bits();
int stride = image.bytesPerLine()/4;
std::vector<Guchar> maskLine(maskWidth);
for (int y = 0; y < height; y++) {
Guchar *pix = imgStr->getLine();
Guchar *maskPix = maskImageStr->getLine();
// Invert the vertical coordinate: y is increasing from top to bottom
// on the page, but y is increasing bottom to top in the picture.
unsigned int* line = data+(height-1-y)*stride;
colorMap->getRGBLine(pix, line, width);
// Apply the mask values to the image alpha channel
maskColorMap->getGrayLine(maskPix, maskLine.data(), width);
for (int x = 0; x < width; x++)
{
*line = *line | (maskLine[x]<<24);
line++;
}
}
// At this point, the QPainter coordinate transformation (CTM) is such
// that QRect(0,0,1,1) is exactly the area of the image.
m_painter.top()->drawImage( QRect(0,0,1,1), image );
}
void ArthurOutputDev::beginTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/,
GfxColorSpace * /*blendingColorSpace*/,
GBool /*isolated*/, GBool /*knockout*/,
GBool /*forSoftMask*/)
{
// The entire transparency group will be painted into a
// freshly created QPicture object. Since an existing painter
// cannot change its paint device, we need to construct a
// new QPainter object as well.
m_qpictures.push(new QPicture);
m_painter.push(new QPainter(m_qpictures.top()));
}
void ArthurOutputDev::endTransparencyGroup(GfxState * /*state*/)
{
// Stop painting into the group
m_painter.top()->end();
// Kill the painter that has been used for the transparency group
delete(m_painter.top());
m_painter.pop();
// Store the QPicture object that holds the result of the transparency group
// painting. It will be painted and deleted in the method paintTransparencyGroup.
if (m_lastTransparencyGroupPicture)
{
qDebug() << "Found a transparency group that has not been painted";
delete(m_lastTransparencyGroupPicture);
}
m_lastTransparencyGroupPicture = m_qpictures.top();
m_qpictures.pop();
}
void ArthurOutputDev::paintTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/)
{
// Actually draw the transparency group
m_painter.top()->drawPicture(0,0,*m_lastTransparencyGroupPicture);
// And delete it
delete(m_lastTransparencyGroupPicture);
m_lastTransparencyGroupPicture = nullptr;
}