From 31eae284c2b7aa230136429363131ffcfd8351c5 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 31 Aug 2019 15:55:59 +0200 Subject: [PATCH] Pattern shading refactoring --- PdfForQtLib/sources/pdfcolorspaces.h | 3 +- .../sources/pdfpagecontentprocessor.cpp | 125 ++++++++++++++++-- PdfForQtLib/sources/pdfpagecontentprocessor.h | 15 +++ PdfForQtLib/sources/pdfpainter.cpp | 39 ++---- PdfForQtLib/sources/pdfpainter.h | 3 +- PdfForQtLib/sources/pdfpattern.cpp | 8 +- PdfForQtLib/sources/pdfpattern.h | 7 +- 7 files changed, 151 insertions(+), 49 deletions(-) diff --git a/PdfForQtLib/sources/pdfcolorspaces.h b/PdfForQtLib/sources/pdfcolorspaces.h index 0959be0..1c7ad85 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.h +++ b/PdfForQtLib/sources/pdfcolorspaces.h @@ -210,6 +210,7 @@ public: virtual QColor getColor(const PDFColor& color) const = 0; virtual size_t getColorComponentCount() const = 0; virtual QImage getImage(const PDFImageData& imageData) const; + virtual const PDFPattern* getPattern() const { return nullptr; } /// Checks, if number of color components is OK, and if yes, converts them to the QColor value. /// If they are not OK, exception is thrown. @@ -544,7 +545,7 @@ public: virtual QColor getColor(const PDFColor& color) const override; virtual size_t getColorComponentCount() const override; - const PDFPattern* getPattern() const { return m_pattern.get(); } + virtual const PDFPattern* getPattern() const override { return m_pattern.get(); } private: std::shared_ptr m_pattern; diff --git a/PdfForQtLib/sources/pdfpagecontentprocessor.cpp b/PdfForQtLib/sources/pdfpagecontentprocessor.cpp index 94eeefb..d20a63f 100644 --- a/PdfForQtLib/sources/pdfpagecontentprocessor.cpp +++ b/PdfForQtLib/sources/pdfpagecontentprocessor.cpp @@ -21,6 +21,8 @@ #include "pdfimage.h" #include "pdfpattern.h" +#include + namespace pdf { @@ -309,6 +311,11 @@ void PDFPageContentProcessor::performImagePainting(const QImage& image) Q_UNUSED(image); } +void PDFPageContentProcessor::performMeshPainting(const PDFMesh& mesh) +{ + Q_UNUSED(mesh); +} + void PDFPageContentProcessor::performUpdateGraphicsState(const PDFPageContentProcessorState& state) { if (state.getStateFlags().testFlag(PDFPageContentProcessorState::StateTextFont) || @@ -569,6 +576,104 @@ void PDFPageContentProcessor::processForm(const QMatrix& matrix, const QRectF& b processContent(content); } +void PDFPageContentProcessor::processPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) +{ + if (isContentSuppressed()) + { + // Content is suppressed, do not paint anything + return; + } + + if ((!stroke && !fill) || path.isEmpty()) + { + // No operation requested - either path is empty, or neither stroking or filling + return; + } + + if (fill) + { + const PDFPattern* pattern = getGraphicState()->getFillColorSpace()->getPattern(); + if (pattern) + { + if (const PDFShadingPattern* shadingPatern = pattern->getShadingPattern()) + { + // We must create a mesh and then draw pattern + PDFMeshQualitySettings settings; + settings.deviceSpaceMeshingArea = getPageBoundingRectDeviceSpace(); + settings.userSpaceToDeviceSpaceMatrix = getPatternBaseMatrix(); + settings.initDefaultResolution(); + + PDFMesh mesh = shadingPatern->createMesh(settings); + + // Now, merge the current path to the mesh clipping path + QPainterPath boundingPath = mesh.getBoundingPath(); + boundingPath.addPath(getCurrentWorldMatrix().map(path)); + mesh.setBoundingPath(boundingPath); + + performMeshPainting(mesh); + } + else + { + // TODO: Implement tiling pattern + Q_ASSERT(false); + } + + fill = false; + } + } + + if (stroke) + { + const PDFPattern* pattern = getGraphicState()->getStrokeColorSpace()->getPattern(); + if (pattern) + { + if (const PDFShadingPattern* shadingPatern = pattern->getShadingPattern()) + { + // We must create a mesh and then draw pattern + PDFMeshQualitySettings settings; + settings.deviceSpaceMeshingArea = getPageBoundingRectDeviceSpace(); + settings.userSpaceToDeviceSpaceMatrix = getPatternBaseMatrix(); + settings.initDefaultResolution(); + + PDFMesh mesh = shadingPatern->createMesh(settings); + + // We must stroke the path. + QPainterPathStroker stroker; + stroker.setCapStyle(m_graphicState.getLineCapStyle()); + stroker.setWidth(m_graphicState.getLineWidth()); + stroker.setMiterLimit(m_graphicState.getMitterLimit()); + stroker.setJoinStyle(m_graphicState.getLineJoinStyle()); + + const PDFLineDashPattern& lineDashPattern = m_graphicState.getLineDashPattern(); + if (!lineDashPattern.isSolid()) + { + stroker.setDashPattern(QVector::fromStdVector(lineDashPattern.getDashArray())); + stroker.setDashOffset(lineDashPattern.getDashOffset()); + } + QPainterPath strokedPath = stroker.createStroke(path); + + QPainterPath boundingPath = mesh.getBoundingPath(); + boundingPath.addPath(getCurrentWorldMatrix().map(strokedPath)); + mesh.setBoundingPath(boundingPath); + + performMeshPainting(mesh); + } + else + { + // TODO: Implement tiling pattern + Q_ASSERT(false); + } + + stroke = false; + } + } + + if (stroke || fill) + { + performPathPainting(path, stroke, fill, text, fillRule); + } +} + void PDFPageContentProcessor::processCommand(const QByteArray& command) { Operator op = Operator::Invalid; @@ -1529,7 +1634,7 @@ void PDFPageContentProcessor::operatorPathStroke() if (!m_currentPath.isEmpty()) { m_currentPath.setFillRule(Qt::WindingFill); - performPathPainting(m_currentPath, true, false, false, Qt::WindingFill); + processPathPainting(m_currentPath, true, false, false, Qt::WindingFill); m_currentPath = QPainterPath(); } } @@ -1541,7 +1646,7 @@ void PDFPageContentProcessor::operatorPathCloseStroke() { m_currentPath.closeSubpath(); m_currentPath.setFillRule(Qt::WindingFill); - performPathPainting(m_currentPath, true, false, false, Qt::WindingFill); + processPathPainting(m_currentPath, true, false, false, Qt::WindingFill); m_currentPath = QPainterPath(); } } @@ -1551,7 +1656,7 @@ void PDFPageContentProcessor::operatorPathFillWinding() if (!m_currentPath.isEmpty()) { m_currentPath.setFillRule(Qt::WindingFill); - performPathPainting(m_currentPath, false, true, false, Qt::WindingFill); + processPathPainting(m_currentPath, false, true, false, Qt::WindingFill); m_currentPath = QPainterPath(); } } @@ -1561,7 +1666,7 @@ void PDFPageContentProcessor::operatorPathFillEvenOdd() if (!m_currentPath.isEmpty()) { m_currentPath.setFillRule(Qt::OddEvenFill); - performPathPainting(m_currentPath, false, true, false, Qt::OddEvenFill); + processPathPainting(m_currentPath, false, true, false, Qt::OddEvenFill); m_currentPath = QPainterPath(); } } @@ -1571,7 +1676,7 @@ void PDFPageContentProcessor::operatorPathFillStrokeWinding() if (!m_currentPath.isEmpty()) { m_currentPath.setFillRule(Qt::WindingFill); - performPathPainting(m_currentPath, true, true, false, Qt::WindingFill); + processPathPainting(m_currentPath, true, true, false, Qt::WindingFill); m_currentPath = QPainterPath(); } } @@ -1581,7 +1686,7 @@ void PDFPageContentProcessor::operatorPathFillStrokeEvenOdd() if (!m_currentPath.isEmpty()) { m_currentPath.setFillRule(Qt::OddEvenFill); - performPathPainting(m_currentPath, true, true, false, Qt::OddEvenFill); + processPathPainting(m_currentPath, true, true, false, Qt::OddEvenFill); m_currentPath = QPainterPath(); } } @@ -1592,7 +1697,7 @@ void PDFPageContentProcessor::operatorPathCloseFillStrokeWinding() { m_currentPath.closeSubpath(); m_currentPath.setFillRule(Qt::WindingFill); - performPathPainting(m_currentPath, true, true, false, Qt::WindingFill); + processPathPainting(m_currentPath, true, true, false, Qt::WindingFill); m_currentPath = QPainterPath(); } } @@ -1603,7 +1708,7 @@ void PDFPageContentProcessor::operatorPathCloseFillStrokeEvenOdd() { m_currentPath.closeSubpath(); m_currentPath.setFillRule(Qt::OddEvenFill); - performPathPainting(m_currentPath, true, true, false, Qt::OddEvenFill); + processPathPainting(m_currentPath, true, true, false, Qt::OddEvenFill); m_currentPath = QPainterPath(); } } @@ -2069,7 +2174,7 @@ void PDFPageContentProcessor::operatorShadingPaintShape(PDFPageContentProcessor: deviceBoundingRectPath.addRect(m_pageBoundingRectDeviceSpace); QPainterPath boundingRectPath = inverted.map(deviceBoundingRectPath); - performPathPainting(boundingRectPath, false, true, false, boundingRectPath.fillRule()); + processPathPainting(boundingRectPath, false, true, false, boundingRectPath.fillRule()); } void PDFPageContentProcessor::paintXObjectImage(const PDFStream* stream) @@ -2311,7 +2416,7 @@ void PDFPageContentProcessor::drawText(const TextSequence& textSequence) { QMatrix textRenderingMatrix = adjustMatrix * textMatrix; QPainterPath transformedGlyph = textRenderingMatrix.map(glyphPath); - performPathPainting(transformedGlyph, stroke, fill, true, transformedGlyph.fillRule()); + processPathPainting(transformedGlyph, stroke, fill, true, transformedGlyph.fillRule()); if (clipped) { diff --git a/PdfForQtLib/sources/pdfpagecontentprocessor.h b/PdfForQtLib/sources/pdfpagecontentprocessor.h index e1bf7bb..2cdbfbf 100644 --- a/PdfForQtLib/sources/pdfpagecontentprocessor.h +++ b/PdfForQtLib/sources/pdfpagecontentprocessor.h @@ -34,6 +34,7 @@ namespace pdf { +class PDFMesh; class PDFOptionalContentActivity; static constexpr const char* PDF_RESOURCE_EXTGSTATE = "ExtGState"; @@ -366,6 +367,12 @@ protected: /// \param image Image to be painted virtual void performImagePainting(const QImage& image); + /// This function has to be implemented in the client drawing implementation, it should + /// draw the mesh. Mesh is in device space coordinates (so world transformation matrix + /// is identity matrix). + /// \param mesh Mesh to be drawn + virtual void performMeshPainting(const PDFMesh& mesh); + /// This function has to be implemented in the client drawing implementation, it should /// update the device according to the graphic state change. The flags are set when /// the value differs from the previous graphic state. @@ -449,6 +456,14 @@ private: /// \param content Content stream of the form void processForm(const QMatrix& matrix, const QRectF& boundingBox, const PDFObject& resources, const QByteArray& content); + /// Performs path painting + /// \param path Path, which should be drawn (can be emtpy - in that case nothing happens) + /// \param stroke Stroke the path + /// \param fill Fill the path using given rule + /// \param text Is text being drawn? + /// \param fillRule Fill rule used in the fill mode + void processPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule); + enum class MarkedContentKind { OptionalContent, diff --git a/PdfForQtLib/sources/pdfpainter.cpp b/PdfForQtLib/sources/pdfpainter.cpp index ef7fb30..2155722 100644 --- a/PdfForQtLib/sources/pdfpainter.cpp +++ b/PdfForQtLib/sources/pdfpainter.cpp @@ -47,36 +47,7 @@ PDFPainter::~PDFPainter() void PDFPainter::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) { - // TODO: Implement Pattern features (shading/tiling) - if (isContentSuppressed()) - { - // Content is suppressed, do not paint anything - return; - } - - if ((!stroke && !fill) || path.isEmpty()) - { - // No operation requested - either path is empty, or neither stroking or filling - return; - } - - // TODO: Temporary - if (const PDFPatternColorSpace* ps = dynamic_cast(getGraphicState()->getFillColorSpace())) - { - m_painter->save(); - const PDFAxialShading* pattern = (PDFAxialShading*)ps->getPattern(); - m_painter->setClipPath(path, Qt::IntersectClip); - - PDFMeshQualitySettings settings; - settings.deviceSpaceMeshingArea = getPageBoundingRectDeviceSpace(); - settings.userSpaceToDeviceSpaceMatrix = getPatternBaseMatrix(); - settings.initDefaultResolution(); - - PDFMesh mesh = pattern->createMesh(settings); - mesh.paint(m_painter); - m_painter->restore(); - return; - } + Q_ASSERT(stroke || fill); // Set antialiasing const bool antialiasing = (text && m_features.testFlag(PDFRenderer::TextAntialiasing)) || (!text && m_features.testFlag(PDFRenderer::Antialiasing)); @@ -164,6 +135,14 @@ void PDFPainter::performImagePainting(const QImage& image) m_painter->restore(); } +void PDFPainter::performMeshPainting(const PDFMesh& mesh) +{ + m_painter->save(); + m_painter->setWorldMatrix(QMatrix()); + mesh.paint(m_painter); + m_painter->restore(); +} + void PDFPainter::performUpdateGraphicsState(const PDFPageContentProcessorState& state) { const PDFPageContentProcessorState::StateFlags flags = state.getStateFlags(); diff --git a/PdfForQtLib/sources/pdfpainter.h b/PdfForQtLib/sources/pdfpainter.h index 60e870f..565bf94 100644 --- a/PdfForQtLib/sources/pdfpainter.h +++ b/PdfForQtLib/sources/pdfpainter.h @@ -53,7 +53,8 @@ public: protected: virtual void performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) override; virtual void performClipping(const QPainterPath& path, Qt::FillRule fillRule) override; - virtual void performImagePainting(const QImage& image); + virtual void performImagePainting(const QImage& image) override; + virtual void performMeshPainting(const PDFMesh& mesh) override; virtual void performUpdateGraphicsState(const PDFPageContentProcessorState& state) override; virtual void performSaveGraphicState(ProcessOrder order) override; virtual void performRestoreGraphicState(ProcessOrder order) override; diff --git a/PdfForQtLib/sources/pdfpattern.cpp b/PdfForQtLib/sources/pdfpattern.cpp index 0fbdf52..9e609e5 100644 --- a/PdfForQtLib/sources/pdfpattern.cpp +++ b/PdfForQtLib/sources/pdfpattern.cpp @@ -370,20 +370,16 @@ PDFMesh PDFAxialShading::createMesh(const PDFMeshQualitySettings& settings) cons } // Create background color triangles + // TODO: Create background color for axial shading // Transform mesh to the device space coordinates mesh.transform(p1p2LCS); - // Transform mesh from the device space coordinates to user space coordinates - Q_ASSERT(settings.userSpaceToDeviceSpaceMatrix.isInvertible()); - QMatrix deviceSpaceToUserSpaceMatrix = settings.userSpaceToDeviceSpaceMatrix.inverted(); - mesh.transform(deviceSpaceToUserSpaceMatrix); - // Create bounding path if (m_boundingBox.isValid()) { QPainterPath boundingPath; - boundingPath.addRect(m_boundingBox); + boundingPath.addPolygon(settings.userSpaceToDeviceSpaceMatrix.map(m_boundingBox)); mesh.setBoundingPath(boundingPath); } diff --git a/PdfForQtLib/sources/pdfpattern.h b/PdfForQtLib/sources/pdfpattern.h index 329a385..59004a0 100644 --- a/PdfForQtLib/sources/pdfpattern.h +++ b/PdfForQtLib/sources/pdfpattern.h @@ -29,6 +29,7 @@ namespace pdf { class PDFPattern; +class PDFShadingPattern; using PDFPatternPtr = std::shared_ptr; @@ -140,6 +141,7 @@ public: virtual ~PDFPattern() = default; virtual PatternType getType() const = 0; + virtual const PDFShadingPattern* getShadingPattern() const = 0; /// Returns bounding box in the shadings target coordinate system (not in /// pattern coordinate system). @@ -182,8 +184,11 @@ public: virtual PatternType getType() const override; virtual ShadingType getShadingType() const = 0; + virtual const PDFShadingPattern* getShadingPattern() const override { return this; } - /// Creates a colored mesh using settings + /// Creates a colored mesh using settings. Mesh is generated in device space + /// coordinate system. You must transform the mesh, if you want to + /// use it in another coordiante system. /// \param settings Meshing settings virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings) const = 0;