From 57a9582ffdc5411c60bbfd3a8ed5e1b18c8098d2 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 17 Feb 2019 18:01:22 +0100 Subject: [PATCH] Color setting operators --- PdfForQtLib/sources/pdfcolorspaces.cpp | 261 +++++++++++---- PdfForQtLib/sources/pdfcolorspaces.h | 103 ++++-- PdfForQtLib/sources/pdfflatarray.h | 4 +- PdfForQtLib/sources/pdfrenderer.cpp | 447 ++++++++++++++++++++++++- PdfForQtLib/sources/pdfrenderer_impl.h | 180 ++++++++-- 5 files changed, 886 insertions(+), 109 deletions(-) diff --git a/PdfForQtLib/sources/pdfcolorspaces.cpp b/PdfForQtLib/sources/pdfcolorspaces.cpp index 82eeaf8..37b433a 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.cpp +++ b/PdfForQtLib/sources/pdfcolorspaces.cpp @@ -23,6 +23,11 @@ namespace pdf { +QColor PDFDeviceGrayColorSpace::getDefaultColor() const +{ + return QColor(Qt::black); +} + QColor PDFDeviceGrayColorSpace::getColor(const PDFColor& color) const { Q_ASSERT(color.size() == getColorComponentCount()); @@ -39,6 +44,11 @@ size_t PDFDeviceGrayColorSpace::getColorComponentCount() const return 1; } +QColor PDFDeviceRGBColorSpace::getDefaultColor() const +{ + return QColor(Qt::black); +} + QColor PDFDeviceRGBColorSpace::getColor(const PDFColor& color) const { Q_ASSERT(color.size() == getColorComponentCount()); @@ -51,6 +61,11 @@ size_t PDFDeviceRGBColorSpace::getColorComponentCount() const return 3; } +QColor PDFDeviceCMYKColorSpace::getDefaultColor() const +{ + return QColor(Qt::black); +} + QColor PDFDeviceCMYKColorSpace::getColor(const PDFColor& color) const { Q_ASSERT(color.size() == getColorComponentCount()); @@ -71,16 +86,23 @@ size_t PDFDeviceCMYKColorSpace::getColorComponentCount() const } PDFColorSpacePointer PDFAbstractColorSpace::createColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFObject& colorSpace) + const PDFDocument* document, + const PDFObject& colorSpace) { return createColorSpaceImpl(colorSpaceDictionary, document, colorSpace, COLOR_SPACE_MAX_LEVEL_OF_RECURSION); } +PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByName(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const QByteArray& name) +{ + return createDeviceColorSpaceByNameImpl(colorSpaceDictionary, document, name, COLOR_SPACE_MAX_LEVEL_OF_RECURSION); +} + PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFObject& colorSpace, - int recursion) + const PDFDocument* document, + const PDFObject& colorSpace, + int recursion) { if (--recursion <= 0) { @@ -89,41 +111,7 @@ PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictio if (colorSpace.isName()) { - QByteArray name = colorSpace.getString(); - - if (name == COLOR_SPACE_NAME_DEVICE_GRAY || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY) - { - if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_GRAY)) - { - return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_GRAY)), recursion); - } - else - { - return PDFColorSpacePointer(new PDFDeviceGrayColorSpace()); - } - } - else if (name == COLOR_SPACE_NAME_DEVICE_RGB || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB) - { - if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_RGB)) - { - return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_RGB)), recursion); - } - else - { - return PDFColorSpacePointer(new PDFDeviceRGBColorSpace()); - } - } - else if (name == COLOR_SPACE_NAME_DEVICE_CMYK || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK) - { - if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_CMYK)) - { - return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_CMYK)), recursion); - } - else - { - return PDFColorSpacePointer(new PDFDeviceCMYKColorSpace()); - } - } + return createDeviceColorSpaceByNameImpl(colorSpaceDictionary, document, colorSpace.getString(), recursion); } else if (colorSpace.isArray()) { @@ -175,6 +163,11 @@ PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictio return PDFICCBasedColorSpace::createICCBasedColorSpace(colorSpaceDictionary, document, stream, recursion); } + if (name == COLOR_SPACE_NAME_INDEXED && count == 4) + { + return PDFIndexedColorSpace::createIndexedColorSpace(colorSpaceDictionary, document, array, recursion); + } + // Try to just load by standard way - we can have "standard" color space stored in array return createColorSpaceImpl(colorSpaceDictionary, document, colorSpaceIdentifier, recursion); } @@ -185,6 +178,54 @@ PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictio return PDFColorSpacePointer(); } +PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByNameImpl(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const QByteArray& name, + int recursion) +{ + if (--recursion <= 0) + { + throw PDFParserException(PDFTranslationContext::tr("Can't load color space, because color space structure is too complex.")); + } + + if (name == COLOR_SPACE_NAME_DEVICE_GRAY || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY) + { + if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_GRAY)) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_GRAY)), recursion); + } + else + { + return PDFColorSpacePointer(new PDFDeviceGrayColorSpace()); + } + } + else if (name == COLOR_SPACE_NAME_DEVICE_RGB || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB) + { + if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_RGB)) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_RGB)), recursion); + } + else + { + return PDFColorSpacePointer(new PDFDeviceRGBColorSpace()); + } + } + else if (name == COLOR_SPACE_NAME_DEVICE_CMYK || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK) + { + if (colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_CMYK)) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_CMYK)), recursion); + } + else + { + return PDFColorSpacePointer(new PDFDeviceCMYKColorSpace()); + } + } + + throw PDFParserException(PDFTranslationContext::tr("Invalid color space.")); + return PDFColorSpacePointer(); +} + /// Conversion matrix from XYZ space to RGB space. Values are taken from this article: /// https://en.wikipedia.org/wiki/SRGB#The_sRGB_transfer_function_.28.22gamma.22.29 static constexpr const PDFColorComponentMatrix<3, 3> matrixXYZtoRGB( @@ -198,10 +239,20 @@ PDFColor3 PDFAbstractColorSpace::convertXYZtoRGB(const PDFColor3& xyzColor) return matrixXYZtoRGB * xyzColor; } -PDFCalGrayColorSpace::PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma) : + +QColor PDFXYZColorSpace::getDefaultColor() const +{ + PDFColor color; + const size_t componentCount = getColorComponentCount(); + for (size_t i = 0; i < componentCount; ++i) + { + color.push_back(0.0f); + } + return getColor(color); +} + +PDFXYZColorSpace::PDFXYZColorSpace(PDFColor3 whitePoint) : m_whitePoint(whitePoint), - m_blackPoint(blackPoint), - m_gamma(gamma), m_correctionCoefficients() { PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint); @@ -211,6 +262,14 @@ PDFCalGrayColorSpace::PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 black m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2]; } +PDFCalGrayColorSpace::PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma) : + PDFXYZColorSpace(whitePoint), + m_blackPoint(blackPoint), + m_gamma(gamma) +{ + +} + QColor PDFCalGrayColorSpace::getColor(const PDFColor& color) const { Q_ASSERT(color.size() == getColorComponentCount()); @@ -244,17 +303,12 @@ PDFColorSpacePointer PDFCalGrayColorSpace::createCalGrayColorSpace(const PDFDocu } PDFCalRGBColorSpace::PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix) : - m_whitePoint(whitePoint), + PDFXYZColorSpace(whitePoint), m_blackPoint(blackPoint), m_gamma(gamma), - m_matrix(matrix), - m_correctionCoefficients() + m_matrix(matrix) { - PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint); - m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0]; - m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1]; - m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2]; } QColor PDFCalRGBColorSpace::getColor(const PDFColor& color) const @@ -299,19 +353,14 @@ PDFLabColorSpace::PDFLabColorSpace(PDFColor3 whitePoint, PDFColorComponent aMax, PDFColorComponent bMin, PDFColorComponent bMax) : - m_whitePoint(whitePoint), + PDFXYZColorSpace(whitePoint), m_blackPoint(blackPoint), m_aMin(aMin), m_aMax(aMax), m_bMin(bMin), - m_bMax(bMax), - m_correctionCoefficients() + m_bMax(bMax) { - PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint); - m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0]; - m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1]; - m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2]; } QColor PDFLabColorSpace::getColor(const PDFColor& color) const @@ -384,6 +433,17 @@ PDFICCBasedColorSpace::PDFICCBasedColorSpace(PDFColorSpacePointer alternateColor } +QColor PDFICCBasedColorSpace::getDefaultColor() const +{ + PDFColor color; + const size_t componentCount = getColorComponentCount(); + for (size_t i = 0; i < componentCount; ++i) + { + color.push_back(0.0f); + } + return getColor(color); +} + QColor PDFICCBasedColorSpace::getColor(const PDFColor& color) const { Q_ASSERT(color.size() == getColorComponentCount()); @@ -477,4 +537,91 @@ PDFColorSpacePointer PDFICCBasedColorSpace::createICCBasedColorSpace(const PDFDi return PDFColorSpacePointer(new PDFICCBasedColorSpace(qMove(alternateColorSpace), ranges)); } +PDFIndexedColorSpace::PDFIndexedColorSpace(PDFColorSpacePointer baseColorSpace, QByteArray&& colors, int maxValue) : + m_baseColorSpace(qMove(baseColorSpace)), + m_colors(qMove(colors)), + m_maxValue(maxValue) +{ + +} + +QColor PDFIndexedColorSpace::getDefaultColor() const +{ + return getColor(PDFColor(0.0f)); +} + +QColor PDFIndexedColorSpace::getColor(const PDFColor& color) const +{ + // Indexed color space value must have exactly one component! + Q_ASSERT(color.size() == 1); + + const int colorIndex = qBound(MIN_VALUE, static_cast(color[0]), m_maxValue); + const int componentCount = static_cast(m_baseColorSpace->getColorComponentCount()); + const int byteOffset = colorIndex * componentCount; + + // We must point into the array. Check first and last component. + Q_ASSERT(byteOffset + componentCount - 1 < m_colors.size()); + + PDFColor decodedColor; + const char* bytePointer = m_colors.constData() + byteOffset; + + for (int i = 0; i < componentCount; ++i) + { + const unsigned char value = *bytePointer++; + const PDFColorComponent component = static_cast(value) / 255.0f; + decodedColor.push_back(component); + } + + return m_baseColorSpace->getColor(decodedColor); +} + +size_t PDFIndexedColorSpace::getColorComponentCount() const +{ + return 1; +} + +PDFColorSpacePointer PDFIndexedColorSpace::createIndexedColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFArray* array, + int recursion) +{ + Q_ASSERT(array->getCount() == 4); + + // Read base color space + PDFColorSpacePointer baseColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(1)), recursion); + + if (!baseColorSpace) + { + throw PDFParserException(PDFTranslationContext::tr("Can't determine base color space for indexed color space.")); + } + + // Read maximum value + PDFDocumentDataLoaderDecorator loader(document); + const int maxValue = qBound(MIN_VALUE, loader.readInteger(array->getItem(2), 0), MAX_VALUE); + + // Read stream/byte string with corresponding color values + QByteArray colors; + const PDFObject& colorDataObject = document->getObject(array->getItem(3)); + + if (colorDataObject.isString()) + { + colors = colorDataObject.getString(); + } + else if (colorDataObject.isStream()) + { + colors = document->getDecodedStream(colorDataObject.getStream()); + } + + // Check, if we have enough colors + const int colorCount = maxValue - MIN_VALUE + 1; + const int componentCount = static_cast(baseColorSpace->getColorComponentCount()); + const int byteCount = colorCount * componentCount; + if (byteCount != colors.size()) + { + throw PDFParserException(PDFTranslationContext::tr("Invalid colors for indexed color space. Color space has %1 colors, %2 color components and must have %3 size. Provided size is %4.").arg(colorCount).arg(componentCount).arg(byteCount).arg(colors.size())); + } + + return PDFColorSpacePointer(new PDFIndexedColorSpace(qMove(baseColorSpace), qMove(colors), maxValue)); +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfcolorspaces.h b/PdfForQtLib/sources/pdfcolorspaces.h index 2762f8a..d2e516b 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.h +++ b/PdfForQtLib/sources/pdfcolorspaces.h @@ -25,6 +25,7 @@ namespace pdf { +class PDFArray; class PDFObject; class PDFStream; class PDFDocument; @@ -37,6 +38,8 @@ using PDFColorSpacePointer = QSharedPointer; static constexpr const int COLOR_SPACE_MAX_LEVEL_OF_RECURSION = 12; +static constexpr const char* COLOR_SPACE_DICTIONARY = "ColorSpace"; + static constexpr const char* COLOR_SPACE_NAME_DEVICE_GRAY = "DeviceGray"; static constexpr const char* COLOR_SPACE_NAME_DEVICE_RGB = "DeviceRGB"; static constexpr const char* COLOR_SPACE_NAME_DEVICE_CMYK = "DeviceCMYK"; @@ -53,6 +56,7 @@ static constexpr const char* COLOR_SPACE_NAME_CAL_GRAY = "CalGray"; static constexpr const char* COLOR_SPACE_NAME_CAL_RGB = "CalRGB"; static constexpr const char* COLOR_SPACE_NAME_LAB = "Lab"; static constexpr const char* COLOR_SPACE_NAME_ICCBASED = "ICCBased"; +static constexpr const char* COLOR_SPACE_NAME_INDEXED = "Indexed"; static constexpr const char* CAL_WHITE_POINT = "WhitePoint"; static constexpr const char* CAL_BLACK_POINT = "BlackPoint"; @@ -108,6 +112,7 @@ public: explicit PDFAbstractColorSpace() = default; virtual ~PDFAbstractColorSpace() = default; + virtual QColor getDefaultColor() const = 0; virtual QColor getColor(const PDFColor& color) const = 0; virtual size_t getColorComponentCount() const = 0; @@ -120,6 +125,15 @@ public: const PDFDocument* document, const PDFObject& colorSpace); + /// Creates device color space by name. Color space can be created by this function only, if + /// it is simple - one of the basic device color spaces (gray, RGB or CMYK). + /// \param colorSpaceDictionary Dictionary containing color spaces of the page + /// \param document Document (for loading objects) + /// \param name Name of the color space + static PDFColorSpacePointer createDeviceColorSpaceByName(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const QByteArray& name); + protected: /// Clips the color component to range [0, 1] static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound(0.0, component, 1.0); } @@ -148,6 +162,16 @@ protected: const PDFObject& colorSpace, int recursion); + /// Creates device color space by name. Color space can be created by this function only, if + /// it is simple - one of the basic device color spaces (gray, RGB or CMYK). + /// \param colorSpaceDictionary Dictionary containing color spaces of the page + /// \param document Document (for loading objects) + /// \param name Name of the color space + static PDFColorSpacePointer createDeviceColorSpaceByNameImpl(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const QByteArray& name, + int recursion); + /// Converts XYZ value to the standard RGB value (linear). No gamma correction is applied. /// Default transformation matrix is applied. /// \param xyzColor Color in XYZ space @@ -212,6 +236,7 @@ public: explicit PDFDeviceGrayColorSpace() = default; virtual ~PDFDeviceGrayColorSpace() = default; + virtual QColor getDefaultColor() const override; virtual QColor getColor(const PDFColor& color) const override; virtual size_t getColorComponentCount() const override; }; @@ -222,6 +247,7 @@ public: explicit PDFDeviceRGBColorSpace() = default; virtual ~PDFDeviceRGBColorSpace() = default; + virtual QColor getDefaultColor() const override; virtual QColor getColor(const PDFColor& color) const override; virtual size_t getColorComponentCount() const override; }; @@ -232,11 +258,30 @@ public: explicit PDFDeviceCMYKColorSpace() = default; virtual ~PDFDeviceCMYKColorSpace() = default; + virtual QColor getDefaultColor() const override; virtual QColor getColor(const PDFColor& color) const override; virtual size_t getColorComponentCount() const override; }; -class PDFCalGrayColorSpace : public PDFAbstractColorSpace +class PDFXYZColorSpace : public PDFAbstractColorSpace +{ +public: + virtual QColor getDefaultColor() const override; + +protected: + explicit PDFXYZColorSpace(PDFColor3 whitePoint); + virtual ~PDFXYZColorSpace() = default; + + PDFColor3 m_whitePoint; + + /// What are these coefficients? We want to map white point from XYZ space to white point + /// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white + /// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0) + /// after multiplication by these coefficients. + PDFColor3 m_correctionCoefficients; +}; + +class PDFCalGrayColorSpace : public PDFXYZColorSpace { public: explicit inline PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma); @@ -251,18 +296,11 @@ public: static PDFColorSpacePointer createCalGrayColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); private: - PDFColor3 m_whitePoint; PDFColor3 m_blackPoint; PDFColorComponent m_gamma; - - /// What are these coefficients? We want to map white point from XYZ space to white point - /// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white - /// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0) - /// after multiplication by these coefficients. - PDFColor3 m_correctionCoefficients; }; -class PDFCalRGBColorSpace : public PDFAbstractColorSpace +class PDFCalRGBColorSpace : public PDFXYZColorSpace { public: explicit inline PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix); @@ -277,19 +315,12 @@ public: static PDFColorSpacePointer createCalRGBColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); private: - PDFColor3 m_whitePoint; PDFColor3 m_blackPoint; PDFColor3 m_gamma; PDFColorComponentMatrix_3x3 m_matrix; - - /// What are these coefficients? We want to map white point from XYZ space to white point - /// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white - /// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0) - /// after multiplication by these coefficients. - PDFColor3 m_correctionCoefficients; }; -class PDFLabColorSpace : public PDFAbstractColorSpace +class PDFLabColorSpace : public PDFXYZColorSpace { public: explicit inline PDFLabColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent aMin, PDFColorComponent aMax, PDFColorComponent bMin, PDFColorComponent bMax); @@ -304,18 +335,11 @@ public: static PDFColorSpacePointer createLabColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); private: - PDFColor3 m_whitePoint; PDFColor3 m_blackPoint; PDFColorComponent m_aMin; PDFColorComponent m_aMax; PDFColorComponent m_bMin; PDFColorComponent m_bMax; - - /// What are these coefficients? We want to map white point from XYZ space to white point - /// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white - /// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0) - /// after multiplication by these coefficients. - PDFColor3 m_correctionCoefficients; }; class PDFICCBasedColorSpace : public PDFAbstractColorSpace @@ -328,6 +352,7 @@ public: explicit inline PDFICCBasedColorSpace(PDFColorSpacePointer alternateColorSpace, Ranges range); virtual ~PDFICCBasedColorSpace() = default; + virtual QColor getDefaultColor() const override; virtual QColor getColor(const PDFColor& color) const override; virtual size_t getColorComponentCount() const override; @@ -343,6 +368,36 @@ private: Ranges m_range; }; +class PDFIndexedColorSpace : public PDFAbstractColorSpace +{ +public: + explicit inline PDFIndexedColorSpace(PDFColorSpacePointer baseColorSpace, QByteArray&& colors, int maxValue); + virtual ~PDFIndexedColorSpace() = default; + + virtual QColor getDefaultColor() const override; + virtual QColor getColor(const PDFColor& color) const override; + virtual size_t getColorComponentCount() const override; + + /// Creates indexed color space from provided values. + /// \param colorSpaceDictionary Color space dictionary + /// \param document Document + /// \param array Array with indexed color space definition + /// \param recursion Recursion guard + static PDFColorSpacePointer createIndexedColorSpace(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const PDFArray* array, int recursion); + +private: + static constexpr const int MIN_VALUE = 0; + static constexpr const int MAX_VALUE = 255; + + PDFColorSpacePointer m_baseColorSpace; + QByteArray m_colors; + int m_maxValue; +}; + +// TODO: Implement Separation color space +// TODO: Implement DeviceN color space +// TODO: Implement Pattern color space + } // namespace pdf #endif // PDFCOLORSPACES_H diff --git a/PdfForQtLib/sources/pdfflatarray.h b/PdfForQtLib/sources/pdfflatarray.h index 5713097..a574589 100644 --- a/PdfForQtLib/sources/pdfflatarray.h +++ b/PdfForQtLib/sources/pdfflatarray.h @@ -49,9 +49,9 @@ public: } - template::type = 0> + template::type = 0> explicit inline PDFFlatArray(Arguments... arguments) : - m_flatBlock(arguments...), + m_flatBlock({ arguments... }), m_flatBlockEndIterator(std::next(m_flatBlock.begin(), sizeof...(Arguments))), m_variableBlock() { diff --git a/PdfForQtLib/sources/pdfrenderer.cpp b/PdfForQtLib/sources/pdfrenderer.cpp index 6b05f76..2e5419b 100644 --- a/PdfForQtLib/sources/pdfrenderer.cpp +++ b/PdfForQtLib/sources/pdfrenderer.cpp @@ -178,10 +178,21 @@ QList PDFRenderer::render(QPainter* painter, const QRectF& recta PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page, const PDFDocument* document) : m_page(page), - m_document(document) + m_document(document), + m_colorSpaceDictionary(nullptr) { Q_ASSERT(page); Q_ASSERT(document); + + const PDFObject& resources = m_document->getObject(m_page->getResources()); + if (resources.isDictionary() && resources.getDictionary()->hasKey(COLOR_SPACE_DICTIONARY)) + { + const PDFObject& colorSpace = m_document->getObject(resources.getDictionary()->get(COLOR_SPACE_DICTIONARY)); + if (colorSpace.isDictionary()) + { + m_colorSpaceDictionary = colorSpace.getDictionary(); + } + } } QList PDFPageContentProcessor::processContents() @@ -191,6 +202,23 @@ QList PDFPageContentProcessor::processContents() // Clear the old errors m_errorList.clear(); + // Initialize default color spaces (gray, RGB, CMYK) + try + { + m_deviceGrayColorSpace = PDFAbstractColorSpace::createDeviceColorSpaceByName(m_colorSpaceDictionary, m_document, COLOR_SPACE_NAME_DEVICE_GRAY); + m_deviceRGBColorSpace = PDFAbstractColorSpace::createDeviceColorSpaceByName(m_colorSpaceDictionary, m_document, COLOR_SPACE_NAME_DEVICE_RGB); + m_deviceCMYKColorSpace = PDFAbstractColorSpace::createDeviceColorSpaceByName(m_colorSpaceDictionary, m_document, COLOR_SPACE_NAME_DEVICE_CMYK); + } + catch (PDFParserException exception) + { + m_errorList.append(PDFRenderError(RenderErrorType::Error, exception.getMessage())); + + // Create default color spaces anyway, but do not try to load them... + m_deviceGrayColorSpace.reset(new PDFDeviceGrayColorSpace); + m_deviceRGBColorSpace.reset(new PDFDeviceRGBColorSpace); + m_deviceCMYKColorSpace.reset(new PDFDeviceCMYKColorSpace); + } + if (contents.isArray()) { const PDFArray* array = contents.getArray(); @@ -229,6 +257,17 @@ void PDFPageContentProcessor::performPathPainting(const QPainterPath& path, bool Q_UNUSED(fillRule); } +void PDFPageContentProcessor::performClipping(const QPainterPath& path, Qt::FillRule fillRule) +{ + Q_UNUSED(path); + Q_UNUSED(fillRule); +} + +void PDFPageContentProcessor::performUpdateGraphicsState(const PDFPageContentProcessor::PDFPageContentProcessorState& state) +{ + Q_UNUSED(state); +} + void PDFPageContentProcessor::processContentStream(const PDFStream* stream) { QByteArray content = m_document->getDecodedStream(stream); @@ -388,6 +427,102 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command) break; } + case Operator::ClipEvenOdd: + { + operatorClipEvenOdd(); + break; + } + + case Operator::ClipWinding: + { + operatorClipWinding(); + break; + } + + case Operator::ColorSetStrokingColorSpace: + { + // CS, set current color space for stroking operations + invokeOperator(&PDFPageContentProcessor::operatorColorSetStrokingColorSpace); + break; + } + + case Operator::ColorSetFillingColorSpace: + { + // cs, set current color space for filling operations + invokeOperator(&PDFPageContentProcessor::operatorColorSetFillingColorSpace); + break; + } + + case Operator::ColorSetStrokingColor: + { + // SC, set current stroking color + operatorColorSetStrokingColor(); + break; + } + + case Operator::ColorSetStrokingColorN: + { + // SCN, same as SC, but also supports Pattern, Separation, DeviceN and ICCBased color spaces + operatorColorSetStrokingColorN(); + break; + } + + case Operator::ColorSetFillingColor: + { + // sc, set current filling color + operatorColorSetFillingColor(); + break; + } + + case Operator::ColorSetFillingColorN: + { + // scn, same as sc, but also supports Pattern, Separation, DeviceN and ICCBased color spaces + operatorColorSetFillingColorN(); + break; + } + + case Operator::ColorSetDeviceGrayStroking: + { + // G, set DeviceGray color space for stroking color and set color + invokeOperator(&PDFPageContentProcessor::operatorColorSetDeviceGrayStroking); + break; + } + + case Operator::ColorSetDeviceGrayFilling: + { + // g, set DeviceGray color space for filling color and set color + invokeOperator(&PDFPageContentProcessor::operatorColorSetDeviceGrayFilling); + break; + } + + case Operator::ColorSetDeviceRGBStroking: + { + // RG, set DeviceRGB color space for stroking color and set color + invokeOperator(&PDFPageContentProcessor::operatorColorSetDeviceRGBStroking); + break; + } + + case Operator::ColorSetDeviceRGBFilling: + { + // rg, set DeviceRGB color space for filling color and set color + invokeOperator(&PDFPageContentProcessor::operatorColorSetDeviceRGBFilling); + break; + } + + case Operator::ColorSetDeviceCMYKStroking: + { + // K, set DeviceCMYK color space for stroking color and set color + invokeOperator(&PDFPageContentProcessor::operatorColorSetDeviceCMYKStroking); + break; + } + + case Operator::ColorSetDeviceCMYKFilling: + { + // k, set DeviceCMYK color space for filling color and set color + invokeOperator(&PDFPageContentProcessor::operatorColorSetDeviceCMYKFilling); + break; + } + case Operator::Invalid: { m_errorList.append(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Unknown operator '%1'.").arg(QString::fromLatin1(command)))); @@ -418,6 +553,15 @@ QPointF PDFPageContentProcessor::getCurrentPoint() const return QPointF(); } +void PDFPageContentProcessor::updateGraphicState() +{ + if (m_graphicState.getStateFlags()) + { + performUpdateGraphicsState(m_graphicState); + m_graphicState.setStateFlags(PDFPageContentProcessorState::StateUnchanged); + } +} + template<> PDFReal PDFPageContentProcessor::readOperand(size_t index) const { @@ -443,6 +587,31 @@ PDFReal PDFPageContentProcessor::readOperand(size_t index) const return 0.0; } + +template<> +PDFPageContentProcessor::PDFName PDFPageContentProcessor::readOperand(size_t index) const +{ + if (index < m_operands.size()) + { + const PDFLexicalAnalyzer::Token& token = m_operands[index]; + + switch (token.type) + { + case PDFLexicalAnalyzer::TokenType::Name: + return PDFName{ token.data.toByteArray() }; + + default: + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't read operand (name) on index %1. Operand is of type '%2'.").arg(index + 1).arg(PDFLexicalAnalyzer::getStringFromOperandType(token.type))); + } + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't read operand (name) on index %1. Only %2 operands provided.").arg(index + 1).arg(m_operands.size())); + } + + return PDFName(); +} + void PDFPageContentProcessor::operatorMoveCurrentPoint(PDFReal x, PDFReal y) { m_currentPath.moveTo(x, y);; @@ -572,6 +741,154 @@ void PDFPageContentProcessor::operatorPathClear() m_currentPath = QPainterPath(); } +void PDFPageContentProcessor::operatorClipWinding() +{ + if (!m_currentPath.isEmpty()) + { + m_currentPath.setFillRule(Qt::WindingFill); + performClipping(m_currentPath, Qt::WindingFill); + } +} + +void PDFPageContentProcessor::operatorClipEvenOdd() +{ + if (!m_currentPath.isEmpty()) + { + m_currentPath.setFillRule(Qt::OddEvenFill); + performClipping(m_currentPath, Qt::OddEvenFill); + } +} + +void PDFPageContentProcessor::operatorColorSetStrokingColorSpace(PDFPageContentProcessor::PDFName name) +{ + PDFColorSpacePointer colorSpace = PDFAbstractColorSpace::createColorSpace(m_colorSpaceDictionary, m_document, PDFObject::createName(std::make_shared(QByteArray(name.name)))); + if (colorSpace) + { + // We must also set default color (it can depend on the color space) + m_graphicState.setStrokeColorSpace(colorSpace); + m_graphicState.setStrokeColor(colorSpace->getDefaultColor()); + updateGraphicState(); + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid color space.")); + } +} + +void PDFPageContentProcessor::operatorColorSetFillingColorSpace(PDFName name) +{ + PDFColorSpacePointer colorSpace = PDFAbstractColorSpace::createColorSpace(m_colorSpaceDictionary, m_document, PDFObject::createName(std::make_shared(QByteArray(name.name)))); + if (colorSpace) + { + // We must also set default color (it can depend on the color space) + m_graphicState.setFillColorSpace(colorSpace); + m_graphicState.setFillColor(colorSpace->getDefaultColor()); + updateGraphicState(); + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid color space.")); + } +} + +void PDFPageContentProcessor::operatorColorSetStrokingColor() +{ + const PDFAbstractColorSpace* colorSpace = m_graphicState.getStrokeColorSpace(); + const size_t colorSpaceComponentCount = colorSpace->getColorComponentCount(); + const size_t operandCount = m_operands.size(); + + if (operandCount == colorSpaceComponentCount) + { + PDFColor color; + for (size_t i = 0; i < operandCount; ++i) + { + color.push_back(readOperand(i)); + } + m_graphicState.setStrokeColor(colorSpace->getColor(color)); + updateGraphicState(); + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid color component count. Provided %1, required %2.").arg(operandCount).arg(colorSpaceComponentCount)); + } +} + +void PDFPageContentProcessor::operatorColorSetStrokingColorN() +{ + // TODO: Implement operator SCN + throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Not implemented!")); +} + +void PDFPageContentProcessor::operatorColorSetFillingColor() +{ + const PDFAbstractColorSpace* colorSpace = m_graphicState.getFillColorSpace(); + const size_t colorSpaceComponentCount = colorSpace->getColorComponentCount(); + const size_t operandCount = m_operands.size(); + + if (operandCount == colorSpaceComponentCount) + { + PDFColor color; + for (size_t i = 0; i < operandCount; ++i) + { + color.push_back(readOperand(i)); + } + m_graphicState.setFillColor(colorSpace->getColor(color)); + updateGraphicState(); + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid color component count. Provided %1, required %2.").arg(operandCount).arg(colorSpaceComponentCount)); + } +} + +void PDFPageContentProcessor::operatorColorSetFillingColorN() +{ + // TODO: Implement operator scn + throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Not implemented!")); +} + +void PDFPageContentProcessor::operatorColorSetDeviceGrayStroking(PDFReal gray) +{ + m_graphicState.setStrokeColorSpace(m_deviceGrayColorSpace); + m_graphicState.setStrokeColor(getColorFromColorSpace(m_graphicState.getStrokeColorSpace(), gray)); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorColorSetDeviceGrayFilling(PDFReal gray) +{ + m_graphicState.setFillColorSpace(m_deviceGrayColorSpace); + m_graphicState.setFillColor(getColorFromColorSpace(m_graphicState.getFillColorSpace(), gray)); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorColorSetDeviceRGBStroking(PDFReal r, PDFReal g, PDFReal b) +{ + m_graphicState.setStrokeColorSpace(m_deviceRGBColorSpace); + m_graphicState.setStrokeColor(getColorFromColorSpace(m_graphicState.getStrokeColorSpace(), r, g, b)); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorColorSetDeviceRGBFilling(PDFReal r, PDFReal g, PDFReal b) +{ + m_graphicState.setFillColorSpace(m_deviceRGBColorSpace); + m_graphicState.setFillColor(getColorFromColorSpace(m_graphicState.getFillColorSpace(), r, g, b)); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorColorSetDeviceCMYKStroking(PDFReal c, PDFReal m, PDFReal y, PDFReal k) +{ + m_graphicState.setStrokeColorSpace(m_deviceCMYKColorSpace); + m_graphicState.setStrokeColor(getColorFromColorSpace(m_graphicState.getStrokeColorSpace(), c, m, y, k)); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorColorSetDeviceCMYKFilling(PDFReal c, PDFReal m, PDFReal y, PDFReal k) +{ + m_graphicState.setFillColorSpace(m_deviceCMYKColorSpace); + m_graphicState.setFillColor(getColorFromColorSpace(m_graphicState.getFillColorSpace(), c, m, y, k)); + updateGraphicState(); +} + PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorState() : m_currentTransformationMatrix(), m_fillColorSpace(), @@ -584,7 +901,8 @@ PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorSt m_mitterLimit(10.0), m_renderingIntent(), m_flatness(1.0), - m_smoothness(0.01) + m_smoothness(0.01), + m_stateFlags(StateUnchanged) { m_fillColorSpace.reset(new PDFDeviceGrayColorSpace); m_strokeColorSpace = m_fillColorSpace; @@ -595,4 +913,129 @@ PDFPageContentProcessor::PDFPageContentProcessorState::~PDFPageContentProcessorS } +PDFPageContentProcessor::PDFPageContentProcessorState& PDFPageContentProcessor::PDFPageContentProcessorState::operator=(const PDFPageContentProcessor::PDFPageContentProcessorState& other) +{ + setCurrentTransformationMatrix(other.getCurrentTransformationMatrix()); + setStrokeColorSpace(other.m_strokeColorSpace); + setFillColorSpace(other.m_fillColorSpace); + setStrokeColor(other.getStrokeColor()); + setFillColor(other.getFillColor()); + setLineWidth(other.getLineWidth()); + setLineCapStyle(other.getLineCapStyle()); + setLineJoinStyle(other.getLineJoinStyle()); + setMitterLimit(other.getMitterLimit()); + setRenderingIntent(other.getRenderingIntent()); + setFlatness(other.getFlatness()); + setSmoothness(other.getSmoothness()); + return *this; +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setCurrentTransformationMatrix(const QMatrix& currentTransformationMatrix) +{ + if (m_currentTransformationMatrix != currentTransformationMatrix) + { + m_currentTransformationMatrix = currentTransformationMatrix; + m_stateFlags |= StateCurrentTransformationMatrix; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setStrokeColorSpace(const QSharedPointer& strokeColorSpace) +{ + if (m_strokeColorSpace != strokeColorSpace) + { + m_strokeColorSpace = strokeColorSpace; + m_stateFlags |= StateStrokeColorSpace; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setFillColorSpace(const QSharedPointer& fillColorSpace) +{ + if (m_fillColorSpace != fillColorSpace) + { + m_fillColorSpace = fillColorSpace; + m_stateFlags |= StateFillColorSpace; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setStrokeColor(const QColor& strokeColor) +{ + if (m_strokeColor != strokeColor) + { + m_strokeColor = strokeColor; + m_stateFlags |= StateStrokeColor; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setFillColor(const QColor& fillColor) +{ + if (m_fillColor != fillColor) + { + m_fillColor = fillColor; + m_stateFlags |= StateFillColor; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setLineWidth(PDFReal lineWidth) +{ + if (m_lineWidth != lineWidth) + { + m_lineWidth = lineWidth; + m_stateFlags |= StateLineWidth; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setLineCapStyle(Qt::PenCapStyle lineCapStyle) +{ + if (m_lineCapStyle != lineCapStyle) + { + m_lineCapStyle = lineCapStyle; + m_stateFlags |= StateLineCapStyle; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setLineJoinStyle(Qt::PenJoinStyle lineJoinStyle) +{ + if (m_lineJoinStyle != lineJoinStyle) + { + m_lineJoinStyle = lineJoinStyle; + m_stateFlags |= StateLineJoinStyle; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setMitterLimit(const PDFReal& mitterLimit) +{ + if (m_mitterLimit != mitterLimit) + { + m_mitterLimit = mitterLimit; + m_stateFlags |= StateMitterLimit; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setRenderingIntent(const QByteArray& renderingIntent) +{ + if (m_renderingIntent != renderingIntent) + { + m_renderingIntent = renderingIntent; + m_stateFlags |= StateRenderingIntent; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setFlatness(PDFReal flatness) +{ + if (m_flatness != flatness) + { + m_flatness = flatness; + m_stateFlags |= StateFlatness; + } +} + +void PDFPageContentProcessor::PDFPageContentProcessorState::setSmoothness(PDFReal smoothness) +{ + if (m_smoothness != smoothness) + { + m_smoothness = smoothness; + m_stateFlags |= StateSmoothness; + } +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfrenderer_impl.h b/PdfForQtLib/sources/pdfrenderer_impl.h index b7e1637..85d8926 100644 --- a/PdfForQtLib/sources/pdfrenderer_impl.h +++ b/PdfForQtLib/sources/pdfrenderer_impl.h @@ -129,9 +129,9 @@ public: ColorSetStrokingColorSpace, ///< CS, set current color space for stroking operations ColorSetFillingColorSpace, ///< cs, set current color space for filling operations ColorSetStrokingColor, ///< SC, set current stroking color - ColorSetStrokingColorN, ///< SCN, same as SC, but also supports Pattern, Separtion, DeviceN and ICCBased color spaces + ColorSetStrokingColorN, ///< SCN, same as SC, but also supports Pattern, Separation, DeviceN and ICCBased color spaces ColorSetFillingColor, ///< sc, set current filling color - ColorSetFillingColorN, ///< scn, same as sc, but also supports Pattern, Separtion, DeviceN and ICCBased color spaces + ColorSetFillingColorN, ///< scn, same as sc, but also supports Pattern, Separation, DeviceN and ICCBased color spaces ColorSetDeviceGrayStroking, ///< G, set DeviceGray color space for stroking color and set color ColorSetDeviceGrayFilling, ///< g, set DeviceGray color space for filling color and set color ColorSetDeviceRGBStroking, ///< RG, set DeviceRGB color space for stroking color and set color @@ -167,6 +167,94 @@ public: QList processContents(); protected: + /// Represents graphic state of the PDF (holding current graphic state parameters). + /// Please see PDF Reference 1.7, Chapter 4.3 "Graphic State" + class PDFPageContentProcessorState + { + public: + explicit PDFPageContentProcessorState(); + ~PDFPageContentProcessorState(); + + PDFPageContentProcessorState(const PDFPageContentProcessorState&) = default; + PDFPageContentProcessorState(PDFPageContentProcessorState&&) = default; + + PDFPageContentProcessorState& operator=(PDFPageContentProcessorState&&) = delete; + PDFPageContentProcessorState& operator=(const PDFPageContentProcessorState& other); + + enum StateFlag + { + StateUnchanged = 0x0000, + StateCurrentTransformationMatrix = 0x0001, + StateStrokeColorSpace = 0x0002, + StateFillColorSpace = 0x0004, + StateStrokeColor = 0x0008, + StateFillColor = 0x0010, + StateLineWidth = 0x0020, + StateLineCapStyle = 0x0040, + StateLineJoinStyle = 0x0080, + StateMitterLimit = 0x0100, + StateRenderingIntent = 0x0200, + StateFlatness = 0x0400, + StateSmoothness = 0x0800 + }; + + Q_DECLARE_FLAGS(StateFlags, StateFlag) + + const QMatrix& getCurrentTransformationMatrix() const { return m_currentTransformationMatrix; } + void setCurrentTransformationMatrix(const QMatrix& currentTransformationMatrix); + + const PDFAbstractColorSpace* getStrokeColorSpace() const { return m_strokeColorSpace.data(); } + void setStrokeColorSpace(const QSharedPointer& strokeColorSpace); + + const PDFAbstractColorSpace* getFillColorSpace() const { return m_fillColorSpace.data(); } + void setFillColorSpace(const QSharedPointer& fillColorSpace); + + const QColor& getStrokeColor() const { return m_strokeColor; } + void setStrokeColor(const QColor& strokeColor); + + const QColor& getFillColor() const { return m_fillColor; } + void setFillColor(const QColor& fillColor); + + PDFReal getLineWidth() const { return m_lineWidth; } + void setLineWidth(PDFReal lineWidth); + + Qt::PenCapStyle getLineCapStyle() const { return m_lineCapStyle; } + void setLineCapStyle(Qt::PenCapStyle lineCapStyle); + + Qt::PenJoinStyle getLineJoinStyle() const { return m_lineJoinStyle; } + void setLineJoinStyle(Qt::PenJoinStyle lineJoinStyle); + + PDFReal getMitterLimit() const { return m_mitterLimit; } + void setMitterLimit(const PDFReal& mitterLimit); + + const QByteArray& getRenderingIntent() const { return m_renderingIntent; } + void setRenderingIntent(const QByteArray& renderingIntent); + + PDFReal getFlatness() const { return m_flatness; } + void setFlatness(PDFReal flatness); + + PDFReal getSmoothness() const { return m_smoothness; } + void setSmoothness(PDFReal smoothness); + + StateFlags getStateFlags() const { return m_stateFlags; } + void setStateFlags(StateFlags stateFlags) { m_stateFlags = stateFlags; } + + private: + QMatrix m_currentTransformationMatrix; + PDFColorSpacePointer m_strokeColorSpace; + PDFColorSpacePointer m_fillColorSpace; + QColor m_strokeColor; + QColor m_fillColor; + PDFReal m_lineWidth; + Qt::PenCapStyle m_lineCapStyle; + Qt::PenJoinStyle m_lineJoinStyle; + PDFReal m_mitterLimit; + QByteArray m_renderingIntent; + PDFReal m_flatness; + PDFReal m_smoothness; + StateFlags m_stateFlags; + }; + /// This function has to be implemented in the client drawing implementation, it should /// draw the path according to the parameters. /// \param path Path, which should be drawn (can be emtpy - in that case nothing happens) @@ -175,6 +263,15 @@ protected: /// \param fillRule Fill rule used in the fill mode virtual void performPathPainting(const QPainterPath& path, bool stroke, bool fill, Qt::FillRule fillRule); + /// This function has to be implemented in the client drawing implementation, it should + /// clip along the path (intersect with current clipping path). + virtual void performClipping(const QPainterPath& path, Qt::FillRule fillRule); + + /// This function has to be implemented in the client drawing implementation, it should + /// update the device accordin to the graphic state change. The flags are set when + /// the value differs from the previous graphic state. + virtual void performUpdateGraphicsState(const PDFPageContentProcessorState& state); + private: /// Process the content stream void processContentStream(const PDFStream* stream); @@ -182,12 +279,21 @@ private: /// Processes single command void processCommand(const QByteArray& command); + /// Wrapper for PDF Name + struct PDFName + { + QByteArray name; + }; + template T readOperand(size_t index) const; template<> PDFReal readOperand(size_t index) const; + template<> + PDFName readOperand(size_t index) const; + template inline T readOperand() const { return readOperand(index); } @@ -211,6 +317,28 @@ private: /// exception is thrown. QPointF getCurrentPoint() const; + /// Notifies the updated graphic state. If nothing changed in graphic state, then nothing happens. + void updateGraphicState(); + + template + inline QColor getColorFromColorSpace(const PDFAbstractColorSpace* colorSpace, Operands... operands) + { + + + constexpr const size_t operandCount = sizeof...(Operands); + const size_t colorSpaceComponentCount = colorSpace->getColorComponentCount(); + if (operandCount == colorSpaceComponentCount) + { + return colorSpace->getColor(PDFColor(static_cast(operands)...)); + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid color component count. Provided %1, required %2.").arg(operandCount).arg(colorSpaceComponentCount)); + } + + return QColor(); + } + // Path construction operators void operatorMoveCurrentPoint(PDFReal x, PDFReal y); void operatorLineTo(PDFReal x, PDFReal y); @@ -231,38 +359,42 @@ private: void operatorPathCloseFillStrokeEvenOdd(); void operatorPathClear(); - /// Represents graphic state of the PDF (holding current graphic state parameters). - /// Please see PDF Reference 1.7, Chapter 4.3 "Graphic State" - class PDFPageContentProcessorState - { - public: - explicit PDFPageContentProcessorState(); - ~PDFPageContentProcessorState(); + // Clipping paths: W, W* + void operatorClipWinding(); ///< W, modify current clipping path by intersecting it with current path using "Non zero winding number rule" + void operatorClipEvenOdd(); ///< W*, modify current clipping path by intersecting it with current path using "Even-odd rule" - private: - QMatrix m_currentTransformationMatrix; - QSharedPointer m_fillColorSpace; - QSharedPointer m_strokeColorSpace; - QColor m_fillColor; - QColor m_strokeColor; - PDFReal m_lineWidth; - Qt::PenCapStyle m_lineCapStyle; - Qt::PenJoinStyle m_lineJoinStyle; - PDFReal m_mitterLimit; - QByteArray m_renderingIntent; - PDFReal m_flatness; - PDFReal m_smoothness; - }; + // Color: CS, cs, SC, SCN, sc, scn, G, g, RG, rg, K, k + void operatorColorSetStrokingColorSpace(PDFName name); ///< CS, set current color space for stroking operations + void operatorColorSetFillingColorSpace(PDFName name); ///< cs, set current color space for filling operations + void operatorColorSetStrokingColor(); ///< SC, set current stroking color + void operatorColorSetStrokingColorN(); ///< SCN, same as SC, but also supports Pattern, Separation, DeviceN and ICCBased color spaces + void operatorColorSetFillingColor(); ///< sc, set current filling color + void operatorColorSetFillingColorN(); ///< scn, same as sc, but also supports Pattern, Separation, DeviceN and ICCBased color spaces + void operatorColorSetDeviceGrayStroking(PDFReal gray); ///< G, set DeviceGray color space for stroking color and set color + void operatorColorSetDeviceGrayFilling(PDFReal gray); ///< g, set DeviceGray color space for filling color and set color + void operatorColorSetDeviceRGBStroking(PDFReal r, PDFReal g, PDFReal b); ///< RG, set DeviceRGB color space for stroking color and set color + void operatorColorSetDeviceRGBFilling(PDFReal r, PDFReal g, PDFReal b); ///< rg, set DeviceRGB color space for filling color and set color + void operatorColorSetDeviceCMYKStroking(PDFReal c, PDFReal m, PDFReal y, PDFReal k); ///< K, set DeviceCMYK color space for stroking color and set color + void operatorColorSetDeviceCMYKFilling(PDFReal c, PDFReal m, PDFReal y, PDFReal k); ///< k, set DeviceCMYK color space for filling color and set color const PDFPage* m_page; const PDFDocument* m_document; + const PDFDictionary* m_colorSpaceDictionary; + + // Default color spaces + PDFColorSpacePointer m_deviceGrayColorSpace; + PDFColorSpacePointer m_deviceRGBColorSpace; + PDFColorSpacePointer m_deviceCMYKColorSpace; /// Array with current operand arguments PDFFlatArray m_operands; - /// Stack with current graphic states + /// Stack with saved graphic states std::stack m_stack; + /// Current graphic state + PDFPageContentProcessorState m_graphicState; + /// List of errors QList m_errorList;