| //======================================================================== |
| // |
| // 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, 2019 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 <fofi/FoFiType1C.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> |
| |
| |
| 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); |
| |
| auto error = FT_Init_FreeType(&m_ftLibrary); |
| if (error) |
| { |
| qCritical() << "An error occurred will initializing the FreeType library"; |
| } |
| |
| // as of FT 2.1.8, CID fonts are indexed by CID instead of GID |
| FT_Int major, minor, patch; |
| FT_Library_Version(m_ftLibrary, &major, &minor, &patch); |
| m_useCIDs = major > 2 || |
| (major == 2 && (minor > 1 || (minor == 1 && patch > 7))); |
| } |
| |
| ArthurOutputDev::~ArthurOutputDev() |
| { |
| for (auto& codeToGID : m_codeToGIDCache) { |
| gfree(const_cast<int*>(codeToGID.second)); |
| } |
| |
| FT_Done_FreeType(m_ftLibrary); |
| } |
| |
| void ArthurOutputDev::startDoc(PDFDoc* doc) { |
| xref = doc->getXRef(); |
| m_doc = doc; |
| |
| if (!globalParams->getEnableFreeType()) { |
| qCritical() << "Arthur backend will not render text without FreeType, but it is disabled!"; |
| } |
| |
| for (auto& codeToGID : m_codeToGIDCache) { |
| gfree(const_cast<int*>(codeToGID.second)); |
| } |
| m_codeToGIDCache.clear(); |
| } |
| |
| 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 = true; |
| } |
| |
| // 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->c_str()); |
| 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->c_str() << ", 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. Therefore, we need to figure it ourselves, using |
| // FoFi and FreeType. |
| // ***************************************************************************** |
| |
| m_needFontUpdate = false; |
| |
| GfxFontType fontType = gfxFont->getType(); |
| |
| // Default: no codeToGID table |
| m_codeToGID = nullptr; |
| |
| // check the font file cache |
| Ref id = *gfxFont->getID(); |
| |
| auto codeToGIDIt = m_codeToGIDCache.find(id); |
| |
| if (codeToGIDIt != m_codeToGIDCache.end()) { |
| |
| m_codeToGID = codeToGIDIt->second; |
| |
| } else { |
| |
| std::unique_ptr<char[], void(*)(char*)> tmpBuf(nullptr, [](char* b){ free(b); }); |
| int tmpBufLen = 0; |
| |
| 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()->c_str() |
| : "(unnamed)"); |
| return; |
| } |
| |
| // embedded font |
| if (fontLoc->locType == gfxFontLocEmbedded) { |
| // if there is an embedded font, read it to memory |
| tmpBuf.reset(gfxFont->readEmbFontFile(xref, &tmpBufLen)); |
| if (! tmpBuf) { |
| return; |
| } |
| |
| // external font |
| } else { // gfxFontLocExternal |
| // Hmm, fontType has already been set to gfxFont->getType() above. |
| // Can it really assume a different value here? |
| fontType = fontLoc->fontType; |
| } |
| |
| switch (fontType) { |
| case fontType1: |
| case fontType1C: |
| case fontType1COT: |
| { |
| // Load the font face using FreeType |
| const int faceIndex = 0; // We always load the zero-th face from a font |
| FT_Face freeTypeFace; |
| |
| if (fontLoc->locType != gfxFontLocEmbedded) { |
| if (FT_New_Face(m_ftLibrary, fontLoc->path->c_str(), faceIndex, &freeTypeFace)) { |
| error(errSyntaxError, -1, "Couldn't create a FreeType face for '{0:s}'", |
| gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); |
| return; |
| } |
| } else { |
| if (FT_New_Memory_Face(m_ftLibrary, (const FT_Byte *)tmpBuf.get(), tmpBufLen, faceIndex, &freeTypeFace)) { |
| error(errSyntaxError, -1, "Couldn't create a FreeType face for '{0:s}'", |
| gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)"); |
| return; |
| } |
| } |
| |
| const char *name; |
| |
| int *codeToGID = (int *)gmallocn(256, sizeof(int)); |
| for (int i = 0; i < 256; ++i) { |
| codeToGID[i] = 0; |
| if ((name = ((const char **)((Gfx8BitFont *)gfxFont)->getEncoding())[i])) { |
| codeToGID[i] = (int)FT_Get_Name_Index(freeTypeFace, (char *)name); |
| if (codeToGID[i] == 0) { |
| name = GfxFont::getAlternateName(name); |
| if (name) { |
| codeToGID[i] = FT_Get_Name_Index(freeTypeFace, (char *)name); |
| } |
| } |
| } |
| } |
| |
| FT_Done_Face(freeTypeFace); |
| |
| m_codeToGIDCache[id] = codeToGID; |
| |
| break; |
| } |
| case fontTrueType: |
| case fontTrueTypeOT: |
| { |
| auto ff = (fontLoc->locType != gfxFontLocEmbedded) |
| ? std::unique_ptr<FoFiTrueType>(FoFiTrueType::load(fontLoc->path->c_str())) |
| : std::unique_ptr<FoFiTrueType>(FoFiTrueType::make(tmpBuf.get(), tmpBufLen)); |
| |
| m_codeToGIDCache[id] = (ff) ? ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff.get()) : nullptr; |
| |
| break; |
| } |
| case fontCIDType0: |
| case fontCIDType0C: |
| { |
| int *cidToGIDMap = nullptr; |
| int nCIDs = 0; |
| |
| // check for a CFF font |
| if (!m_useCIDs) { |
| auto ff = (fontLoc->locType != gfxFontLocEmbedded) |
| ? std::unique_ptr<FoFiType1C>(FoFiType1C::load(fontLoc->path->c_str())) |
| : std::unique_ptr<FoFiType1C>(FoFiType1C::make(tmpBuf.get(), tmpBufLen)); |
| |
| cidToGIDMap = (ff) ? ff->getCIDToGIDMap(&nCIDs) : nullptr; |
| } |
| |
| m_codeToGIDCache[id] = cidToGIDMap; |
| |
| break; |
| } |
| case fontCIDType0COT: |
| { |
| int* codeToGID = nullptr; |
| |
| if (((GfxCIDFont *)gfxFont)->getCIDToGID()) { |
| int codeToGIDLen = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); |
| codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int)); |
| memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), |
| codeToGIDLen * sizeof(int)); |
| } |
| |
| int *cidToGIDMap = nullptr; |
| int nCIDs = 0; |
| |
| if (!codeToGID && !m_useCIDs) { |
| auto ff = (fontLoc->locType != gfxFontLocEmbedded) |
| ? std::unique_ptr<FoFiTrueType>(FoFiTrueType::load(fontLoc->path->c_str())) |
| : std::unique_ptr<FoFiTrueType>(FoFiTrueType::make(tmpBuf.get(), tmpBufLen)); |
| |
| if (ff && ff->isOpenTypeCFF()) { |
| cidToGIDMap = ff->getCIDToGIDMap(&nCIDs); |
| } |
| } |
| |
| m_codeToGIDCache[id] = codeToGID ? codeToGID : cidToGIDMap; |
| |
| break; |
| } |
| case fontCIDType2: |
| case fontCIDType2OT: |
| { |
| int* codeToGID = nullptr; |
| int codeToGIDLen = 0; |
| if (((GfxCIDFont *)gfxFont)->getCIDToGID()) { |
| codeToGIDLen = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); |
| if (codeToGIDLen) { |
| codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int)); |
| memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), |
| codeToGIDLen * sizeof(int)); |
| } |
| } else { |
| auto ff = (fontLoc->locType != gfxFontLocEmbedded) |
| ? std::unique_ptr<FoFiTrueType>(FoFiTrueType::load(fontLoc->path->c_str())) |
| : std::unique_ptr<FoFiTrueType>(FoFiTrueType::make(tmpBuf.get(), tmpBufLen)); |
| if (! ff) { |
| return; |
| } |
| codeToGID = ((GfxCIDFont *)gfxFont)->getCodeToGIDMap(ff.get(), &codeToGIDLen); |
| } |
| |
| m_codeToGIDCache[id] = codeToGID; |
| |
| break; |
| } |
| default: |
| // this shouldn't happen |
| return; |
| } |
| |
| m_codeToGID = m_codeToGIDCache[id]; |
| } |
| } |
| |
| 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 ); |
| } |
| |
| bool 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 true; |
| } |
| |
| 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, bool invert, |
| bool interpolate, bool 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 = reinterpret_cast<unsigned int *>(image.bits()); |
| int stride = image.bytesPerLine()/4; |
| |
| QRgb fillColor = m_currentBrush.color().rgb(); |
| |
| for (int y = 0; y < height; y++) { |
| |
| unsigned char *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, |
| bool interpolate, int *maskColors, bool inlineImg) |
| { |
| unsigned int *data; |
| unsigned int *line; |
| int x, y; |
| unsigned char *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 = reinterpret_cast<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, |
| bool interpolate, |
| Stream *maskStr, |
| int maskWidth, int maskHeight, |
| GfxImageColorMap *maskColorMap, |
| bool 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, false); |
| 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, false); |
| 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 = reinterpret_cast<unsigned int *>(image.bits()); |
| int stride = image.bytesPerLine()/4; |
| |
| std::vector<unsigned char> maskLine(maskWidth); |
| |
| for (int y = 0; y < height; y++) { |
| |
| unsigned char *pix = imgStr->getLine(); |
| unsigned char *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*/, |
| bool /*isolated*/, bool /*knockout*/, |
| bool /*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; |
| } |
| |