diff --git a/PdfForQtLib/sources/pdfcolorspaces.cpp b/PdfForQtLib/sources/pdfcolorspaces.cpp index f3c5ed4..d75afee 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.cpp +++ b/PdfForQtLib/sources/pdfcolorspaces.cpp @@ -275,6 +275,40 @@ PDFColor PDFAbstractColorSpace::convertToColor(const std::vector& compo return result; } +bool PDFAbstractColorSpace::isColorEqual(const PDFColor& color1, const PDFColor& color2, PDFReal tolerance) +{ + const size_t size = color1.size(); + if (size != color2.size()) + { + return false; + } + + for (size_t i = 0; i < size; ++i) + { + if (std::fabs(color1[i] - color2[i]) > tolerance) + { + return false; + } + } + + return true; +} + +PDFColor PDFAbstractColorSpace::mixColors(const PDFColor& color1, const PDFColor& color2, PDFReal ratio) +{ + const size_t size = color1.size(); + Q_ASSERT(size == color2.size()); + + PDFColor result; + result.resize(size); + for (size_t i = 0; i < size; ++i) + { + result[i] = color1[i] * ratio + color2[i] * (1.0 - ratio); + } + + return result; +} + PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary, const PDFDocument* document, const PDFObject& colorSpace, diff --git a/PdfForQtLib/sources/pdfcolorspaces.h b/PdfForQtLib/sources/pdfcolorspaces.h index 3e5b951..0959be0 100644 --- a/PdfForQtLib/sources/pdfcolorspaces.h +++ b/PdfForQtLib/sources/pdfcolorspaces.h @@ -236,6 +236,20 @@ public: /// Converts a vector of real numbers to the PDFColor static PDFColor convertToColor(const std::vector& components); + /// Returns true, if two colors are equal (considering the tolerance). So, if one + /// of the color components differs more than \p tolerance from the another, then + /// false is returned. If colors have different number of components, false is returned. + /// \param color1 First color + /// \param color2 Second color + /// \param tolerance Color tolerance + static bool isColorEqual(const PDFColor& color1, const PDFColor& color2, PDFReal tolerance); + + /// Mix colors according the given ratio. + /// \param color1 First color + /// \param color2 Second color + /// \param ratio Mixing ratio + static PDFColor mixColors(const PDFColor& color1, const PDFColor& color2, PDFReal ratio); + protected: /// Clips the color component to range [0, 1] static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound(0.0, component, 1.0); } diff --git a/PdfForQtLib/sources/pdfflatarray.h b/PdfForQtLib/sources/pdfflatarray.h index ed84e11..319723a 100644 --- a/PdfForQtLib/sources/pdfflatarray.h +++ b/PdfForQtLib/sources/pdfflatarray.h @@ -158,6 +158,25 @@ public: /// Erases the last element from the array inline void pop_back() { resize(size() - 1); } + bool operator==(const PDFFlatArray& other) const + { + const size_t size = this->size(); + if (size != other.size()) + { + return false; + } + + for (size_t i = 0; i < size; ++i) + { + if ((*this)[i] != other[i]) + { + return false; + } + } + + return true; + } + private: size_t getFlatBlockSize() const { return m_flatBlockItemCount; } diff --git a/PdfForQtLib/sources/pdfglobal.h b/PdfForQtLib/sources/pdfglobal.h index 1019544..679442e 100644 --- a/PdfForQtLib/sources/pdfglobal.h +++ b/PdfForQtLib/sources/pdfglobal.h @@ -42,12 +42,18 @@ using PDFReal = double; constexpr PDFInteger PDF_INTEGER_MIN = std::numeric_limits::min() / 100; constexpr PDFInteger PDF_INTEGER_MAX = std::numeric_limits::max() / 100; +constexpr PDFReal PDF_EPSILON = 0.000001; static constexpr bool isValidInteger(PDFInteger integer) { return integer >= PDF_INTEGER_MIN && integer <= PDF_INTEGER_MAX; } +static inline bool isZero(PDFReal value) +{ + return std::fabs(value) < PDF_EPSILON; +} + /// This structure represents a reference to the object - consisting of the /// object number, and generation number. struct PDFObjectReference diff --git a/PdfForQtLib/sources/pdfpagecontentprocessor.cpp b/PdfForQtLib/sources/pdfpagecontentprocessor.cpp index 833f91e..94eeefb 100644 --- a/PdfForQtLib/sources/pdfpagecontentprocessor.cpp +++ b/PdfForQtLib/sources/pdfpagecontentprocessor.cpp @@ -181,7 +181,7 @@ PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page, const PDFDocument* document, const PDFFontCache* fontCache, const PDFOptionalContentActivity* optionalContentActivity, - QMatrix patternBaseMatrix) : + QMatrix pagePointToDevicePointMatrix) : m_page(page), m_document(document), m_fontCache(fontCache), @@ -194,14 +194,15 @@ PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page, m_shadingDictionary(nullptr), m_textBeginEndState(0), m_compatibilityBeginEndState(0), - m_patternBaseMatrix(patternBaseMatrix) + m_patternBaseMatrix(pagePointToDevicePointMatrix), + m_pagePointToDevicePointMatrix(pagePointToDevicePointMatrix) { Q_ASSERT(page); Q_ASSERT(document); QPainterPath pageRectPath; pageRectPath.addRect(m_page->getRotatedMediaBox()); - m_pageBoundingRectDeviceSpace = patternBaseMatrix.map(pageRectPath).boundingRect(); + m_pageBoundingRectDeviceSpace = pagePointToDevicePointMatrix.map(pageRectPath).boundingRect(); initDictionaries(m_page->getResources()); } @@ -548,7 +549,8 @@ void PDFPageContentProcessor::processForm(const QMatrix& matrix, const QRectF& b m_graphicState.setCurrentTransformationMatrix(formMatrix); updateGraphicState(); - PDFTemporaryValueChange patternMatrixGuard(&m_patternBaseMatrix, formMatrix); + QMatrix patternMatrix = formMatrix * m_pagePointToDevicePointMatrix; + PDFTemporaryValueChange patternMatrixGuard(&m_patternBaseMatrix, patternMatrix); // If the clipping box is valid, then use clipping. Clipping box is in the form coordinate system if (boundingBox.isValid()) @@ -2044,7 +2046,7 @@ void PDFPageContentProcessor::operatorTextSetSpacingAndShowText(PDFReal t_w, PDF void PDFPageContentProcessor::operatorShadingPaintShape(PDFPageContentProcessor::PDFOperandName name) { - QMatrix matrix = getGraphicState()->getCurrentTransformationMatrix(); + QMatrix matrix = getCurrentWorldMatrix(); PDFPageContentProcessorStateGuard guard(this); PDFTemporaryValueChange guard2(&m_patternBaseMatrix, matrix); diff --git a/PdfForQtLib/sources/pdfpagecontentprocessor.h b/PdfForQtLib/sources/pdfpagecontentprocessor.h index 4be1a44..e1bf7bb 100644 --- a/PdfForQtLib/sources/pdfpagecontentprocessor.h +++ b/PdfForQtLib/sources/pdfpagecontentprocessor.h @@ -46,7 +46,7 @@ public: const PDFDocument* document, const PDFFontCache* fontCache, const PDFOptionalContentActivity* optionalContentActivity, - QMatrix patternBaseMatrix); + QMatrix pagePointToDevicePointMatrix); virtual ~PDFPageContentProcessor(); enum class Operator @@ -413,6 +413,18 @@ protected: /// Returns true, if graphic content is suppressed bool isContentSuppressed() const; + /// Returns page point to device point matrix + const QMatrix& getPagePointToDevicePointMatrix() const { return m_pagePointToDevicePointMatrix; } + + /// Returns base matrix for patterns + const QMatrix& getPatternBaseMatrix() const { return m_patternBaseMatrix; } + + /// Returns current world matrix (translating actual point to the device point) + QMatrix getCurrentWorldMatrix() const { return getGraphicState()->getCurrentTransformationMatrix() * m_pagePointToDevicePointMatrix; } + + /// Returns page bounding rectangle in device space + const QRectF& getPageBoundingRectDeviceSpace() const { return m_pageBoundingRectDeviceSpace; } + /// Computes visibility of OCG/OCMD - returns false, if it is not suppressed, /// or true, if it is suppressed. virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd); @@ -738,6 +750,9 @@ private: /// with pattern matrix to get transformation from pattern space to device space. QMatrix m_patternBaseMatrix; + /// Matrix mapping page points to the device points + QMatrix m_pagePointToDevicePointMatrix; + /// Bounding rectangle of pages media box in device space coordinates. If drawing rotation /// is zero, then it corresponds to the scaled media box of the page. QRectF m_pageBoundingRectDeviceSpace; diff --git a/PdfForQtLib/sources/pdfpainter.cpp b/PdfForQtLib/sources/pdfpainter.cpp index 509e78a..ef7fb30 100644 --- a/PdfForQtLib/sources/pdfpainter.cpp +++ b/PdfForQtLib/sources/pdfpainter.cpp @@ -16,6 +16,7 @@ // along with PDFForQt. If not, see . #include "pdfpainter.h" +#include "pdfpattern.h" #include @@ -31,8 +32,7 @@ PDFPainter::PDFPainter(QPainter* painter, const PDFOptionalContentActivity* optionalContentActivity) : PDFPageContentProcessor(page, document, fontCache, optionalContentActivity, pagePointToDevicePointMatrix), m_painter(painter), - m_features(features), - m_pagePointToDevicePointMatrix(pagePointToDevicePointMatrix) + m_features(features) { Q_ASSERT(painter); Q_ASSERT(pagePointToDevicePointMatrix.isInvertible()); @@ -60,6 +60,24 @@ void PDFPainter::performPathPainting(const QPainterPath& path, bool stroke, bool 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; + } + // Set antialiasing const bool antialiasing = (text && m_features.testFlag(PDFRenderer::TextAntialiasing)) || (!text && m_features.testFlag(PDFRenderer::Antialiasing)); m_painter->setRenderHint(QPainter::Antialiasing, antialiasing); @@ -153,7 +171,7 @@ void PDFPainter::performUpdateGraphicsState(const PDFPageContentProcessorState& // If current transformation matrix has changed, then update it if (flags.testFlag(PDFPageContentProcessorState::StateCurrentTransformationMatrix)) { - m_painter->setWorldMatrix(state.getCurrentTransformationMatrix() * m_pagePointToDevicePointMatrix, false); + m_painter->setWorldMatrix(getCurrentWorldMatrix(), false); } if (flags.testFlag(PDFPageContentProcessorState::StateStrokeColor) || diff --git a/PdfForQtLib/sources/pdfpainter.h b/PdfForQtLib/sources/pdfpainter.h index 8c31aa0..60e870f 100644 --- a/PdfForQtLib/sources/pdfpainter.h +++ b/PdfForQtLib/sources/pdfpainter.h @@ -74,7 +74,6 @@ private: QPainter* m_painter; PDFRenderer::Features m_features; - QMatrix m_pagePointToDevicePointMatrix; PDFCachedItem m_currentPen; PDFCachedItem m_currentBrush; }; diff --git a/PdfForQtLib/sources/pdfpattern.cpp b/PdfForQtLib/sources/pdfpattern.cpp index d7a8198..0fbdf52 100644 --- a/PdfForQtLib/sources/pdfpattern.cpp +++ b/PdfForQtLib/sources/pdfpattern.cpp @@ -18,6 +18,10 @@ #include "pdfpattern.h" #include "pdfdocument.h" #include "pdfexception.h" +#include "pdfutils.h" +#include "pdfcolorspaces.h" + +#include namespace pdf { @@ -186,4 +190,261 @@ PDFPatternPtr PDFPattern::createShadingPattern(const PDFDictionary* colorSpaceDi return PDFPatternPtr(); } +PDFMesh PDFAxialShading::createMesh(const PDFMeshQualitySettings& settings) const +{ + PDFMesh mesh; + + QPointF p1 = settings.userSpaceToDeviceSpaceMatrix.map(m_startPoint); + QPointF p2 = settings.userSpaceToDeviceSpaceMatrix.map(m_endPoint); + + // Strategy: for simplification, we rotate the line clockwise so we will + // get the shading axis equal to the x-axis. Then we will determine the shading + // area and create mesh according the settings. + QLineF line(p1, p2); + const double angle = line.angleTo(QLineF(0, 0, 1, 0)); + + // Matrix p1p2LCS is local coordinate system of line p1-p2. It transforms + // points on the line to the global coordinate system. So, point (0, 0) will + // map onto p1 and point (length(p1-p2), 0) will map onto p2. + QMatrix p1p2LCS; + p1p2LCS.translate(p1.x(), p1.y()); + p1p2LCS.rotate(angle); + QMatrix p1p2GCS = p1p2LCS.inverted(); + + QPointF p1m = p1p2GCS.map(p1); + QPointF p2m = p1p2GCS.map(p2); + + Q_ASSERT(isZero(p1m.y())); + Q_ASSERT(isZero(p2m.y())); + Q_ASSERT(p1m.x() <= p2m.x()); + + QPainterPath meshingArea; + meshingArea.addPolygon(p1p2GCS.map(settings.deviceSpaceMeshingArea)); + meshingArea.addRect(p1m.x(), p1m.y() - settings.preferredMeshResolution * 0.5, p2m.x() - p1m.x(), settings.preferredMeshResolution); + QRectF meshingRectangle = meshingArea.boundingRect(); + + PDFReal xl = meshingRectangle.left(); + PDFReal xr = meshingRectangle.right(); + PDFReal yt = meshingRectangle.top(); + PDFReal yb = meshingRectangle.bottom(); + + // Create coordinate array filled with stops, where we will determine the color + std::vector xCoords; + xCoords.reserve((xr - xl) / settings.minimalMeshResolution + 3); + xCoords.push_back(xl); + for (PDFReal x = p1m.x(); x <= p2m.x(); x += settings.minimalMeshResolution) + { + if (!qFuzzyCompare(xCoords.back(), x)) + { + xCoords.push_back(x); + } + } + if (!qFuzzyCompare(xCoords.back(), xr)) + { + xCoords.push_back(xr); + } + + const PDFReal tAtStart = m_domainStart; + const PDFReal tAtEnd = m_domainEnd; + const PDFReal tMin = qMin(tAtStart, tAtEnd); + const PDFReal tMax = qMax(tAtStart, tAtEnd); + + const bool isSingleFunction = m_functions.size() == 1; + std::vector colorBuffer(m_colorSpace->getColorComponentCount(), 0.0); + auto getColor = [this, isSingleFunction, &colorBuffer](PDFReal t) -> PDFColor + { + if (isSingleFunction) + { + PDFFunction::FunctionResult result = m_functions.front()->apply(&t, &t + 1, colorBuffer.data(), colorBuffer.data() + colorBuffer.size()); + if (!result) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Error occured during mesh creation of shading: %1").arg(result.errorMessage)); + } + } + else + { + for (size_t i = 0, count = colorBuffer.size(); i < count; ++i) + { + PDFFunction::FunctionResult result = m_functions.front()->apply(&t, &t + 1, colorBuffer.data() + i, colorBuffer.data() + i + 1); + if (!result) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Error occured during mesh creation of shading: %1").arg(result.errorMessage)); + } + } + } + + return PDFAbstractColorSpace::convertToColor(colorBuffer); + }; + + // Determine color of each coordinate + std::vector> coloredCoordinates; + coloredCoordinates.reserve(xCoords.size()); + + for (PDFReal x : xCoords) + { + if (x < p1m.x() - PDF_EPSILON && !m_extendStart) + { + // Move to the next coordinate, this is skipped + continue; + } + + if (x > p2m.x() + PDF_EPSILON && !m_extendEnd) + { + // We are finished no more triangles will occur + break; + } + + // Determine current parameter t + const PDFReal t = interpolate(x, p1m.x(), p2m.x(), tAtStart, tAtEnd); + const PDFReal tBounded = qBound(tMin, t, tMax); + const PDFColor color = getColor(tBounded); + coloredCoordinates.emplace_back(x, color); + } + + // Filter coordinates according the meshing criteria + std::vector> filteredCoordinates; + filteredCoordinates.reserve(coloredCoordinates.size()); + + for (auto it = coloredCoordinates.cbegin(); it != coloredCoordinates.cend(); ++it) + { + // We will skip this coordinate, if both of meshing criteria have been met: + // 1) Color difference is small (lesser than tolerance) + // 2) Distance from previous and next point is less than preffered meshing resolution OR colors are equal + + if (it != coloredCoordinates.cbegin() && std::next(it) != coloredCoordinates.cend()) + { + auto itNext = std::next(it); + + const std::pair& prevItem = filteredCoordinates.back(); + const std::pair& currentItem = *it; + const std::pair& nextItem = *itNext; + + if (prevItem.second == currentItem.second && currentItem.second == nextItem.second) + { + // Colors are same, skip the test + continue; + } + + if (PDFAbstractColorSpace::isColorEqual(prevItem.second, currentItem.second, settings.tolerance) && + PDFAbstractColorSpace::isColorEqual(currentItem.second, nextItem.second, settings.tolerance) && + PDFAbstractColorSpace::isColorEqual(prevItem.second, nextItem.second, settings.tolerance) && + (nextItem.first - prevItem.first < settings.preferredMeshResolution)) + { + continue; + } + } + + filteredCoordinates.push_back(*it); + } + + if (!filteredCoordinates.empty()) + { + size_t vertexCount = filteredCoordinates.size() * 2; + size_t triangleCount = filteredCoordinates.size() * 2 - 2; + + if (m_backgroundColor.isValid()) + { + vertexCount += 8; + triangleCount += 4; + } + mesh.reserve(vertexCount, triangleCount); + + PDFColor previousColor = filteredCoordinates.front().second; + uint32_t topLeft = mesh.addVertex(QPointF(filteredCoordinates.front().first, yt)); + uint32_t bottomLeft = mesh.addVertex(QPointF(filteredCoordinates.front().first, yb)); + for (auto it = std::next(filteredCoordinates.cbegin()); it != filteredCoordinates.cend(); ++it) + { + const std::pair& item = *it; + + uint32_t topRight = mesh.addVertex(QPointF(item.first, yt)); + uint32_t bottomRight = mesh.addVertex(QPointF(item.first, yb)); + + PDFColor mixedColor = PDFAbstractColorSpace::mixColors(previousColor, item.second, 0.5); + QColor color = m_colorSpace->getColor(mixedColor); + mesh.addQuad(topLeft, topRight, bottomRight, bottomLeft, color.rgb()); + + topLeft = topRight; + bottomLeft = bottomRight; + previousColor = item.second; + } + } + + // Create background color triangles + + // 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); + mesh.setBoundingPath(boundingPath); + } + + return mesh; +} + +void PDFMesh::paint(QPainter* painter) const +{ + if (m_triangles.empty()) + { + return; + } + + painter->save(); + painter->setPen(Qt::NoPen); + painter->setRenderHint(QPainter::Antialiasing, true); + + // Set the clipping area, if we have it + if (!m_boundingPath.isEmpty()) + { + painter->setClipPath(m_boundingPath, Qt::IntersectClip); + } + + QColor color; + + // Draw all triangles + for (const Triangle& triangle : m_triangles) + { + if (color != triangle.color) + { + painter->setPen(QColor(triangle.color)); + painter->setBrush(QBrush(triangle.color, Qt::SolidPattern)); + color = triangle.color; + } + + std::array triangleCorners = { m_vertices[triangle.v1], m_vertices[triangle.v2], m_vertices[triangle.v3] }; + painter->drawConvexPolygon(triangleCorners.data(), static_cast(triangleCorners.size())); + } + + painter->restore(); +} + +void PDFMesh::transform(const QMatrix& matrix) +{ + for (QPointF& vertex : m_vertices) + { + vertex = matrix.map(vertex); + } + + m_boundingPath = matrix.map(m_boundingPath); +} + +void PDFMeshQualitySettings::initDefaultResolution() +{ + // We will take 0.5% percent of device space meshing area as minimal resolution (it is ~1.5 mm for + // A4 page) and default resolution 4x number of that. + + Q_ASSERT(deviceSpaceMeshingArea.isValid()); + PDFReal size = qMax(deviceSpaceMeshingArea.width(), deviceSpaceMeshingArea.height()); + minimalMeshResolution = size * 0.005; + preferredMeshResolution = minimalMeshResolution * 4; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfpattern.h b/PdfForQtLib/sources/pdfpattern.h index d8364b4..329a385 100644 --- a/PdfForQtLib/sources/pdfpattern.h +++ b/PdfForQtLib/sources/pdfpattern.h @@ -51,6 +51,87 @@ enum class ShadingType TensorProductPatchMesh = 7 }; +struct PDFMeshQualitySettings +{ + /// Initializes default resolution + void initDefaultResolution(); + + /// Matrix, which transforms user space points (user space is target space of the shading) + /// to the device space of the paint device. + QMatrix userSpaceToDeviceSpaceMatrix; + + /// Rectangle in device space coordinate system, onto which is area meshed. + QRectF deviceSpaceMeshingArea; + + /// Preferred mesh resolution in device space pixels. Mesh will be created in this + /// resolution, if it is smooth enough (no jumps in colors occurs). + PDFReal preferredMeshResolution = 1.0; + + /// Minimal mesh resolution in device space pixels. If jumps in colors occurs (jump + /// is two colors, that differ more than \p color tolerance), then mesh is meshed to + /// minimal mesh resolution. + PDFReal minimalMeshResolution = 1.0; + + /// Color tolerance - 1% by default + PDFReal tolerance = 0.01; +}; + +/// Mesh consisting of triangles +class PDFMesh +{ +public: + explicit PDFMesh() = default; + + struct Triangle + { + uint32_t v1 = 0; + uint32_t v2 = 0; + uint32_t v3 = 0; + + QRgb color; + }; + + /// Adds vertex. Returns index of added vertex. + /// \param vertex Vertex to be added + /// \returns Index of the added vertex + inline uint32_t addVertex(const QPointF& vertex) { const size_t index = m_vertices.size(); m_vertices.emplace_back(vertex); return static_cast(index); } + + /// Adds triangle. Returns index of added triangle. + /// \param triangle Triangle to be added + /// \returns Index of the added vertex + inline uint32_t addTriangle(const Triangle& triangle) { const size_t index = m_triangles.size(); m_triangles.emplace_back(triangle); return static_cast(index); } + + /// Adds quad. Vertices are in clockwise order (so, we have edges v1-v2, v2-v3, v3-v4, v4-v1). + /// \param v1 First vertex (for example, topleft) + /// \param v2 Second vertex (for example, topright) + /// \param v3 Third vertex (for example, bottomright) + /// \param v4 Fourth vertex (for example, bottomleft) + /// \param color Color of the quad. + inline void addQuad(uint32_t v1, uint32_t v2, uint32_t v3, uint32_t v4, QRgb color) { addTriangle({v1, v2, v3, color}); addTriangle({ v1, v3, v4, color}); } + + /// Paints the mesh on the painter + /// \param painter Painter, onto which is mesh drawn + void paint(QPainter* painter) const; + + /// Transforms the mesh according to the matrix transform + /// \param matrix Matrix transform to be performed + void transform(const QMatrix& matrix); + + /// Reserves memory for meshing - both number of vertices and triangles. + /// Use this function, if number of vertices and triangles is known. + /// \param vertexCount Vertex count + /// \param triangleCount Triangle count + void reserve(size_t vertexCount, size_t triangleCount) { m_vertices.reserve(vertexCount); m_triangles.reserve(triangleCount); } + + const QPainterPath& getBoundingPath() const { return m_boundingPath; } + void setBoundingPath(const QPainterPath& path) { m_boundingPath = path; } + +private: + std::vector m_vertices; + std::vector m_triangles; + QPainterPath m_boundingPath; +}; + /// Represents tiling/shading pattern class PDFPattern { @@ -88,7 +169,7 @@ public: const PDFObject& patternGraphicState, bool ignoreBackgroundColor); -private: +protected: QRectF m_boundingBox; QMatrix m_matrix; }; @@ -102,6 +183,10 @@ public: virtual PatternType getType() const override; virtual ShadingType getShadingType() const = 0; + /// Creates a colored mesh using settings + /// \param settings Meshing settings + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings) const = 0; + /// Returns patterns graphic state. This state must be applied before /// the shading pattern is painted to the target device. const PDFObject& getPatternGraphicState() const { return m_patternGraphicState; } @@ -116,7 +201,7 @@ public: /// Returns true, if shading pattern should be anti-aliased bool isAntialiasing() const { return m_antiAlias; } -private: +protected: friend class PDFPattern; PDFObject m_patternGraphicState; @@ -130,7 +215,7 @@ class PDFSingleDimensionShading : public PDFShadingPattern public: explicit PDFSingleDimensionShading() = default; -private: +protected: friend class PDFPattern; std::vector m_functions; @@ -148,6 +233,7 @@ public: explicit PDFAxialShading() = default; virtual ShadingType getShadingType() const override; + virtual PDFMesh createMesh(const PDFMeshQualitySettings& settings) const override; private: friend class PDFPattern;