From 847d8154bc44ebaacabb64e3675bb40900ee2ce2 Mon Sep 17 00:00:00 2001 From: Oliver Sander Date: Fri, 18 Aug 2017 11:51:06 +0200 Subject: [PATCH 1/3] Replace Splash font rendering by Qt font rendering Previously, the Arthur backend would use Splash code to do its font rendering. That was not a satisfactory solution: Qt can do font rendering directly. Also, the Splash font rendering in the Arthur code had a few bugs, which lead to legible-but-not-pretty fonts. This patch replaces the Splash font rendering by Qt font rendering. Some Splash code will have to remain, because Qt seems unable to do the proper charcode-to-glyph-index transformations. I took a lot of inspiration from Mihai Niculescu's patch at https://lists.freedesktop.org/archives/poppler/2013-June/010370.html That's why the patch adds Mihai's name in the copyright list. --- qt5/src/ArthurOutputDev.cc | 254 +++++++++++++++++++++++++-------------------- qt5/src/ArthurOutputDev.h | 31 +++++- splash/SplashFTFontFile.cc | 4 + splash/SplashFTFontFile.h | 3 + 4 files changed, 177 insertions(+), 115 deletions(-) diff --git a/qt5/src/ArthurOutputDev.cc b/qt5/src/ArthurOutputDev.cc index fe285aa9..ca0e0b27 100644 --- a/qt5/src/ArthurOutputDev.cc +++ b/qt5/src/ArthurOutputDev.cc @@ -22,6 +22,7 @@ // Copyright (C) 2011 Andreas Hartmetz // Copyright (C) 2013 Thomas Freitag // Copyright (C) 2013 Dominik Haumann +// Copyright (C) 2013 Mihai Niculescu // Copyright (C) 2017 Oliver Sander // // To see a description of the changes please see the Changelog file that @@ -50,17 +51,15 @@ #include "ArthurOutputDev.h" #include +#include +#include #include //------------------------------------------------------------------------ #ifdef HAVE_SPLASH #include "splash/SplashFontFileID.h" -#include "splash/SplashFontFile.h" +#include "splash/SplashFTFontFile.h" #include "splash/SplashFontEngine.h" -#include "splash/SplashFont.h" -#include "splash/SplashMath.h" -#include "splash/SplashPath.h" -#include "splash/SplashGlyphBitmap.h" //------------------------------------------------------------------------ // SplashOutFontFileID //------------------------------------------------------------------------ @@ -94,7 +93,6 @@ ArthurOutputDev::ArthurOutputDev(QPainter *painter): { m_currentBrush = QBrush(Qt::SolidPattern); m_fontEngine = 0; - m_font = 0; } ArthurOutputDev::~ArthurOutputDev() @@ -284,6 +282,71 @@ void ArthurOutputDev::updateStrokeOpacity(GfxState *state) void ArthurOutputDev::updateFont(GfxState *state) { + GfxFont *font = state->getFont(); + if (!font) + { + return; + } + + // is the font in the cache? + ArthurFontID fontID = {*font->getID(), state->getFontSize()}; + 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(); + + GfxFontLoc* fontLoc = font->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 = font->readEmbFontFile(xref, &fontDataLen); + + m_rawFont = new QRawFont(QByteArray(fontData, fontDataLen), fontSize); + m_rawFontCache.insert(std::make_pair(fontID,std::unique_ptr(m_rawFont))); + 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(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 GfxFont *gfxFont; GfxFontLoc *fontLoc; @@ -297,15 +360,13 @@ void ArthurOutputDev::updateFont(GfxState *state) char *tmpBuf; int tmpBufLen = 0; int *codeToGID; - double *textMat; - double m11, m12, m21, m22, fontSize; - SplashCoord mat[4]; + SplashCoord mat[4] = {0,0,0,0}; int n; int faceIndex = 0; - SplashCoord matrix[6]; + SplashCoord matrix[6] = {1,0,0,1,0,0}; + SplashFTFontFile* ftFontFile; m_needFontUpdate = false; - m_font = NULL; fileName = NULL; tmpBuf = NULL; fontLoc = NULL; @@ -318,6 +379,9 @@ void ArthurOutputDev::updateFont(GfxState *state) goto err1; } + // Default: no codeToGID table + m_codeToGID = nullptr; + // check the font file cache id = new SplashOutFontFileID(gfxFont->getID()); if ((fontFile = m_fontEngine->getFontFile(id))) { @@ -478,28 +542,15 @@ void ArthurOutputDev::updateFont(GfxState *state) } } - // 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; - - { - QMatrix painterMatrix = m_painter->worldMatrix(); - matrix[0] = painterMatrix.m11(); - matrix[1] = painterMatrix.m12(); - matrix[2] = painterMatrix.m21(); - matrix[3] = painterMatrix.m22(); - matrix[4] = painterMatrix.dx(); - matrix[5] = painterMatrix.dy(); - } + ftFontFile = dynamic_cast(fontFile); + if (ftFontFile) + m_codeToGID = ftFontFile->getCodeToGID(); - // create the scaled font - mat[0] = m11; mat[1] = -m12; - mat[2] = m21; mat[3] = -m22; - m_font = m_fontEngine->getFont(fontFile, mat, matrix); + // 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); delete fontLoc; if (fontsrc && !fontsrc->isFile) @@ -576,92 +627,71 @@ 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) { -#ifdef HAVE_SPLASH -// SplashPath *path; - int render; - - if (m_needFontUpdate) { - updateFont(state); - } - if (!m_font) { - return; - } // check for invisible text -- this is used by Acrobat Capture - render = state->getRender(); - if (render == 3) { + int render = state->getRender(); + if (render == 3 || !m_rawFont) { + qDebug() << "Invisible text found!"; return; } - x -= originX; - y -= originY; - - // fill - if (!(render & 1)) { - SplashPath * fontPath; - fontPath = m_font->getGlyphPath(code); - if (fontPath) { - QPainterPath qPath; - qPath.setFillRule(Qt::WindingFill); - for (int i = 0; i < fontPath->length; ++i) { - // SplashPath.flags: bitwise or allowed - if (fontPath->flags[i] & splashPathLast || fontPath->flags[i] & splashPathClosed) { - qPath.closeSubpath(); - } - if (fontPath->flags[i] & splashPathFirst) { - qPath.moveTo(fontPath->pts[i].x+x,-fontPath->pts[i].y+y); - } - if (fontPath->flags[i] & splashPathCurve) { - qPath.quadTo(fontPath->pts[i].x+x,-fontPath->pts[i].y+y, - fontPath->pts[i+1].x+x,-fontPath->pts[i+1].y+y); - ++i; - } - // FIXME fix this - // else if (fontPath->flags[i] & splashPathArcCW) { - // qDebug() << "Need to implement arc"; - // } - else { - qPath.lineTo(fontPath->pts[i].x+x,-fontPath->pts[i].y+y); - } - } - GfxRGB rgb; - QColor brushColour = m_currentBrush.color(); - state->getFillRGB(&rgb); - brushColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), state->getFillOpacity()); - m_painter->setBrush(brushColour); - m_painter->setPen(Qt::NoPen); - m_painter->drawPath(qPath); - delete fontPath; - } - } - - // stroke - if ((render & 3) == 1 || (render & 3) == 2) { - qDebug() << "no stroke"; - /* - if ((path = m_font->getGlyphPath(code))) { - path->offset((SplashCoord)x1, (SplashCoord)y1); - splash->stroke(path); - delete path; - } - */ - } - - // clip - if (render & 4) { - qDebug() << "no clip"; - /* - path = m_font->getGlyphPath(code); - path->offset((SplashCoord)x1, (SplashCoord)y1); - if (textClipPath) { - textClipPath->append(path); - delete path; - } else { - textClipPath = path; - } - */ + 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->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->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->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->setPen(fontColor); + + // Actually draw the glyph + m_painter->drawGlyphRun(QPointF(-glyphPosition.x(),-glyphPosition.y()), glyphRun); + + // Restore transformation and pen color + m_painter->restore(); } -#endif } GBool ArthurOutputDev::beginType3Char(GfxState *state, double x, double y, diff --git a/qt5/src/ArthurOutputDev.h b/qt5/src/ArthurOutputDev.h index 00a2e08a..dc8b547c 100644 --- a/qt5/src/ArthurOutputDev.h +++ b/qt5/src/ArthurOutputDev.h @@ -19,6 +19,7 @@ // Copyright (C) 2010 Pino Toscano // Copyright (C) 2011 Andreas Hartmetz // Copyright (C) 2013 Thomas Freitag +// Copyright (C) 2013 Mihai Niculescu // Copyright (C) 2017 Oliver Sander // // To see a description of the changes please see the Changelog file that @@ -33,6 +34,9 @@ #pragma interface #endif +#include +#include + #include "goo/gtypes.h" #include "OutputDev.h" #include "GfxState.h" @@ -44,9 +48,9 @@ class GfxPath; class Gfx8BitFont; struct GfxRGB; -class SplashFont; class SplashFontEngine; -struct SplashGlyphBitmap; + +class QRawFont; //------------------------------------------------------------------------ // ArthurOutputDev - Qt 5 QPainter renderer @@ -167,8 +171,29 @@ private: QBrush m_currentBrush; GBool m_needFontUpdate; // set when the font needs to be updated SplashFontEngine *m_fontEngine; - SplashFont *m_font; // current font XRef *xref; // xref table for current document + + // The current font in use + QRawFont* m_rawFont; + + // Identify a font by its 'Ref' and its font size + struct ArthurFontID + { + Ref ref; + double fontSize; + + bool operator<(const ArthurFontID& other) const + { + return (ref.num < other.ref.num) + || ((ref.num == other.ref.num) && (fontSize < other.fontSize)); + } + }; + + // Cache all fonts + std::map > m_rawFontCache; + + // The table that maps character codes to glyph indexes + int* m_codeToGID; }; #endif diff --git a/splash/SplashFTFontFile.cc b/splash/SplashFTFontFile.cc index f0dcf503..5c4f24d3 100644 --- a/splash/SplashFTFontFile.cc +++ b/splash/SplashFTFontFile.cc @@ -145,4 +145,8 @@ SplashFont *SplashFTFontFile::makeFont(SplashCoord *mat, return font; } +int *SplashFTFontFile::getCodeToGID() { + return codeToGID; +} + #endif // HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H diff --git a/splash/SplashFTFontFile.h b/splash/SplashFTFontFile.h index 7a7bb218..25303ccc 100644 --- a/splash/SplashFTFontFile.h +++ b/splash/SplashFTFontFile.h @@ -62,6 +62,9 @@ public: SplashFont *makeFont(SplashCoord *mat, SplashCoord *textMat) override; + // Provide access to the code-to-GID map + int* getCodeToGID(); + private: SplashFTFontFile(SplashFTFontEngine *engineA, -- 2.14.1