From ffc56d38e15e3126c49ff1ac354b2ead3c549f97 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 23 Feb 2019 15:44:14 +0100 Subject: [PATCH] Graphic state operator (gs) --- PdfForQtLib/sources/pdfcolorspaces.cpp | 6 +- PdfForQtLib/sources/pdfdocument.cpp | 30 +++++- PdfForQtLib/sources/pdfdocument.h | 8 +- PdfForQtLib/sources/pdfrenderer.cpp | 139 ++++++++++++++++++++++++- PdfForQtLib/sources/pdfrenderer_impl.h | 19 ++++ PdfForQtViewer/PdfForQtViewer.pro | 2 +- 6 files changed, 194 insertions(+), 10 deletions(-) diff --git a/PdfForQtLib/sources/pdfcolorspaces.cpp b/PdfForQtLib/sources/pdfcolorspaces.cpp index 37b433a..6de0aee 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.cpp +++ b/PdfForQtLib/sources/pdfcolorspaces.cpp @@ -190,7 +190,7 @@ PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByNameImpl(con if (name == COLOR_SPACE_NAME_DEVICE_GRAY || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY) { - if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_GRAY)) + if (colorSpaceDictionary && colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_GRAY)) { return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_GRAY)), recursion); } @@ -201,7 +201,7 @@ PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByNameImpl(con } else if (name == COLOR_SPACE_NAME_DEVICE_RGB || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB) { - if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_RGB)) + if (colorSpaceDictionary && colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_RGB)) { return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_RGB)), recursion); } @@ -212,7 +212,7 @@ PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByNameImpl(con } else if (name == COLOR_SPACE_NAME_DEVICE_CMYK || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK) { - if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_CMYK)) + if (colorSpaceDictionary && colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_CMYK)) { return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_CMYK)), recursion); } diff --git a/PdfForQtLib/sources/pdfdocument.cpp b/PdfForQtLib/sources/pdfdocument.cpp index 82773ca..7a6266e 100644 --- a/PdfForQtLib/sources/pdfdocument.cpp +++ b/PdfForQtLib/sources/pdfdocument.cpp @@ -273,7 +273,7 @@ PDFInteger PDFDocumentDataLoaderDecorator::readInteger(const PDFObject& object, return defaultValue; } -PDFReal PDFDocumentDataLoaderDecorator::readNumber(const PDFObject& object, PDFInteger defaultValue) const +PDFReal PDFDocumentDataLoaderDecorator::readNumber(const PDFObject& object, PDFReal defaultValue) const { const PDFObject& dereferencedObject = m_document->getObject(object); @@ -357,4 +357,32 @@ PDFInteger PDFDocumentDataLoaderDecorator::readIntegerFromDictionary(const PDFDi return defaultValue; } +std::vector PDFDocumentDataLoaderDecorator::readNumberArray(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = m_document->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + std::vector result; + const size_t count = array->getCount(); + result.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + const PDFReal number = readNumber(array->getItem(i), std::numeric_limits::quiet_NaN()); + if (std::isnan(number)) + { + return std::vector(); + } + result.push_back(number); + } + + // We assume, that RVO (return value optimization) will not work for this function + // (multiple return points). + return std::move(result); + } + + return std::vector(); +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfdocument.h b/PdfForQtLib/sources/pdfdocument.h index 7ef2eab..3c93b51 100644 --- a/PdfForQtLib/sources/pdfdocument.h +++ b/PdfForQtLib/sources/pdfdocument.h @@ -96,7 +96,7 @@ public: /// then it is converted to real number. /// \param object Object, can be an indirect reference to object (it is dereferenced) /// \param defaultValue Default value - PDFReal readNumber(const PDFObject& object, PDFInteger defaultValue) const; + PDFReal readNumber(const PDFObject& object, PDFReal defaultValue) const; /// Reads a text string from the object, if it is possible. /// \param object Object, can be an indirect reference to object (it is dereferenced) @@ -186,6 +186,12 @@ public: /// \param defaultValue Default value PDFInteger readIntegerFromDictionary(const PDFDictionary* dictionary, const char* key, PDFInteger defaultValue) const; + /// Reads number array from dictionary. Reads all values. If some value is not + /// real number (or integer number), empty array is returned. Empty array is also returned, + /// if \p object is invalid. + /// \param object Object containing array of numbers + std::vector readNumberArray(const PDFObject& object) const; + private: const PDFDocument* m_document; }; diff --git a/PdfForQtLib/sources/pdfrenderer.cpp b/PdfForQtLib/sources/pdfrenderer.cpp index 9a82b8c..769e803 100644 --- a/PdfForQtLib/sources/pdfrenderer.cpp +++ b/PdfForQtLib/sources/pdfrenderer.cpp @@ -656,12 +656,12 @@ void PDFPageContentProcessor::operatorSetLineWidth(PDFReal lineWidth) updateGraphicState(); } -void PDFPageContentProcessor::operatorSetLineCap(PDFInteger lineCap) +Qt::PenCapStyle PDFPageContentProcessor::convertLineCapToPenCapStyle(PDFInteger lineCap) { lineCap = qBound(0, lineCap, 2); Qt::PenCapStyle penCapStyle = Qt::FlatCap; - switch (penCapStyle) + switch (lineCap) { case 0: { @@ -689,16 +689,42 @@ void PDFPageContentProcessor::operatorSetLineCap(PDFInteger lineCap) } } + return penCapStyle; +} + +PDFInteger PDFPageContentProcessor::convertPenCapStyleToLineCap(Qt::PenCapStyle penCapStyle) +{ + switch (penCapStyle) + { + case Qt::FlatCap: + return 0; + case Qt::SquareCap: + return 2; + case Qt::RoundCap: + return 1; + + default: + break; + } + + // Invalid pen cap style occured + Q_ASSERT(false); + return 0; +} + +void PDFPageContentProcessor::operatorSetLineCap(PDFInteger lineCap) +{ + const Qt::PenCapStyle penCapStyle = convertLineCapToPenCapStyle(lineCap); m_graphicState.setLineCapStyle(penCapStyle); updateGraphicState(); } -void PDFPageContentProcessor::operatorSetLineJoin(PDFInteger lineJoin) +Qt::PenJoinStyle PDFPageContentProcessor::convertLineJoinToPenJoinStyle(PDFInteger lineJoin) { lineJoin = qBound(0, lineJoin, 2); Qt::PenJoinStyle penJoinStyle = Qt::MiterJoin; - switch (penJoinStyle) + switch (lineJoin) { case 0: { @@ -726,6 +752,32 @@ void PDFPageContentProcessor::operatorSetLineJoin(PDFInteger lineJoin) } } + return penJoinStyle; +} + +PDFInteger PDFPageContentProcessor::convertPenJoinStyleToLineJoin(Qt::PenJoinStyle penJoinStyle) +{ + switch (penJoinStyle) + { + case Qt::MiterJoin: + return 0; + case Qt::BevelJoin: + return 2; + case Qt::RoundJoin: + return 1; + + default: + break; + } + + // Invalid pen join style occured + Q_ASSERT(false); + return 0; +} + +void PDFPageContentProcessor::operatorSetLineJoin(PDFInteger lineJoin) +{ + const Qt::PenJoinStyle penJoinStyle = convertLineJoinToPenJoinStyle(lineJoin); m_graphicState.setLineJoinStyle(penJoinStyle); updateGraphicState(); } @@ -784,6 +836,85 @@ void PDFPageContentProcessor::operatorSetFlatness(PDFReal flatness) updateGraphicState(); } +void PDFPageContentProcessor::operatorSetGraphicState(PDFName dictionaryName) +{ + const PDFObject& resources = m_page->getResources(); + if (resources.isDictionary()) + { + const PDFDictionary* resourcesDictionary = resources.getDictionary(); + if (resourcesDictionary->hasKey(PDF_RESOURCE_EXTGSTATE)) + { + const PDFObject& graphicStatesObject = m_document->getObject(resourcesDictionary->get(PDF_RESOURCE_EXTGSTATE)); + if (graphicStatesObject.isDictionary()) + { + const PDFDictionary* graphicStatesDictionary = graphicStatesObject.getDictionary(); + if (graphicStatesDictionary->hasKey(dictionaryName.name)) + { + const PDFObject& graphicStateObject = m_document->getObject(graphicStatesDictionary->get(dictionaryName.name)); + if (graphicStateObject.isDictionary()) + { + const PDFDictionary* graphicStateDictionary = graphicStateObject.getDictionary(); + + PDFDocumentDataLoaderDecorator loader(m_document); + const PDFReal lineWidth = loader.readNumberFromDictionary(graphicStateDictionary, "LW", m_graphicState.getLineWidth()); + const Qt::PenCapStyle penCapStyle = convertLineCapToPenCapStyle(loader.readNumberFromDictionary(graphicStateDictionary, "LC", convertPenCapStyleToLineCap(m_graphicState.getLineCapStyle()))); + const Qt::PenJoinStyle penJoinStyle = convertLineJoinToPenJoinStyle(loader.readNumberFromDictionary(graphicStateDictionary, "LJ", convertPenJoinStyleToLineJoin(m_graphicState.getLineJoinStyle()))); + const PDFReal mitterLimit = loader.readNumberFromDictionary(graphicStateDictionary, "MT", m_graphicState.getMitterLimit()); + + const PDFObject& lineDashPatternObject = m_document->getObject(graphicStateDictionary->get("D")); + if (lineDashPatternObject.isArray()) + { + const PDFArray* lineDashPatternDefinitionArray = lineDashPatternObject.getArray(); + if (lineDashPatternDefinitionArray->getCount() == 2) + { + PDFLineDashPattern pattern(loader.readNumberArray(lineDashPatternDefinitionArray->getItem(0)), loader.readNumber(lineDashPatternDefinitionArray->getItem(1), 0.0)); + m_graphicState.setLineDashPattern(pattern); + } + } + + const PDFObject& renderingIntentObject = m_document->getObject(graphicStateDictionary->get("RI")); + if (renderingIntentObject.isName()) + { + m_graphicState.setRenderingIntent(renderingIntentObject.getString()); + } + + const PDFReal flatness = loader.readNumberFromDictionary(graphicStateDictionary, "FL", m_graphicState.getFlatness()); + const PDFReal smoothness = loader.readNumberFromDictionary(graphicStateDictionary, "SM", m_graphicState.getSmoothness()); + + m_graphicState.setLineWidth(lineWidth); + m_graphicState.setLineCapStyle(penCapStyle); + m_graphicState.setLineJoinStyle(penJoinStyle); + m_graphicState.setMitterLimit(mitterLimit); + m_graphicState.setFlatness(flatness); + m_graphicState.setSmoothness(smoothness); + updateGraphicState(); + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Graphic state '%1' found, but invalid in resource dictionary.").arg(QString::fromLatin1(dictionaryName.name))); + } + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Graphic state '%1' not found in resource dictionary.").arg(QString::fromLatin1(dictionaryName.name))); + } + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid page resource dictionary.")); + } + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid page resource dictionary.")); + } + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid page resource dictionary.")); + } +} + void PDFPageContentProcessor::operatorSaveGraphicState() { performSaveGraphicState(ProcessOrder::BeforeOperation); diff --git a/PdfForQtLib/sources/pdfrenderer_impl.h b/PdfForQtLib/sources/pdfrenderer_impl.h index e92fabc..b74157f 100644 --- a/PdfForQtLib/sources/pdfrenderer_impl.h +++ b/PdfForQtLib/sources/pdfrenderer_impl.h @@ -32,6 +32,7 @@ namespace pdf { +static constexpr const char* PDF_RESOURCE_EXTGSTATE = "ExtGState"; class PDFRendererException : public std::exception { @@ -391,6 +392,24 @@ private: return QColor(); } + /// Converts PDF line cap to Qt's pen cap style. Function always succeeds, + /// if invalid \p lineCap occurs, then some valid pen cap style is returned. + /// \param lineCap PDF Line cap style (see PDF Reference 1.7, values can be 0, 1, and 2) + static Qt::PenCapStyle convertLineCapToPenCapStyle(PDFInteger lineCap); + + /// Convers Qt's pen cap style to PDF's line cap style (defined in the PDF Reference) + /// \param penCapStyle Qt's pen cap style to be converted + static PDFInteger convertPenCapStyleToLineCap(Qt::PenCapStyle penCapStyle); + + /// Converts PDF line join to Qt's pen join style. Function always succeeds, + /// if invalid \p lineJoin occurs, then some valid pen join style is returned. + /// \param lineJoin PDF Line join style (see PDF Reference 1.7, values can be 0, 1, and 2) + static Qt::PenJoinStyle convertLineJoinToPenJoinStyle(PDFInteger lineJoin); + + /// Convers Qt's pen join style to PDF's line join style (defined in the PDF Reference) + /// \param penJoinStyle Qt's pen join style to be converted + static PDFInteger convertPenJoinStyleToLineJoin(Qt::PenJoinStyle penJoinStyle); + // General graphic state w, J, j, M, d, ri, i, gs void operatorSetLineWidth(PDFReal lineWidth); ///< w, sets the line width void operatorSetLineCap(PDFInteger lineCap); ///< J, sets the line cap diff --git a/PdfForQtViewer/PdfForQtViewer.pro b/PdfForQtViewer/PdfForQtViewer.pro index 4517f3f..ca1a123 100644 --- a/PdfForQtViewer/PdfForQtViewer.pro +++ b/PdfForQtViewer/PdfForQtViewer.pro @@ -8,7 +8,7 @@ QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets -TARGET = PdfForQtViewer +TARGET = PdfForQtViewer.exe TEMPLATE = app # The following define makes your compiler emit warnings if you use