diff --git a/Pdf4QtLibCore/sources/pdfblpainter.cpp b/Pdf4QtLibCore/sources/pdfblpainter.cpp index 7edba56..4734f4d 100644 --- a/Pdf4QtLibCore/sources/pdfblpainter.cpp +++ b/Pdf4QtLibCore/sources/pdfblpainter.cpp @@ -84,12 +84,27 @@ private: /// Returns composition operator static BLCompOp getBLCompOp(QPainter::CompositionMode mode); + void drawPathImpl(const QPainterPath& path, bool enableStroke, bool enableFill); + void setFillRule(Qt::FillRule fillRule); void updateFont(QFont newFont); + void setPathFillMode(PolygonDrawMode mode, QPainterPath& path); + void updateClipping(std::optional clipRegion, + std::optional clipPath, + Qt::ClipOperation clipOperation); bool isStrokeActive() const { return m_currentPen.style() != Qt::NoPen; } bool isFillActive() const { return m_currentBrush.style() != Qt::NoBrush; } + enum class ClipMode + { + NoClip, + NotVisible, + NeedsResolve + }; + + ClipMode resolveClipping(const QPainterPath& path) const; + QImage& m_qtOffscreenBuffer; std::optional m_blContext; std::optional m_blOffscreenBuffer; @@ -99,6 +114,13 @@ private: QBrush m_currentBrush; QFont m_currentFont; QRawFont m_currentRawFont; + QTransform m_currentTransform; + + bool m_currentIsClipEnabled = false; + bool m_clipSingleRect = false; + std::optional m_clipRegion; + std::optional m_clipPath; + std::optional m_finalClipPath; }; PDFBLPaintDevice::PDFBLPaintDevice(QImage& offscreenBuffer, bool isMultithreaded) : @@ -247,9 +269,7 @@ void PDFBLPaintEngine::updateState(const QPaintEngineState& updatedState) DirtyBackground = 0x0010, DirtyBackgroundMode = 0x0020, DirtyClipRegion = 0x0080, - DirtyClipPath = 0x0100, - DirtyHints = 0x0200, - DirtyClipEnabled = 0x0800,*/ + DirtyClipPath = 0x0100,*/ if (updatedState.state().testFlag(QPaintEngine::DirtyPen)) { @@ -275,6 +295,7 @@ void PDFBLPaintEngine::updateState(const QPaintEngineState& updatedState) if (updatedState.state().testFlag(QPaintEngine::DirtyTransform)) { + m_currentTransform = updatedState.transform(); m_blContext->setMatrix(getBLMatrix(updatedState.transform())); } @@ -282,6 +303,34 @@ void PDFBLPaintEngine::updateState(const QPaintEngineState& updatedState) { updateFont(updatedState.font()); } + + if (updatedState.state().testFlag(QPaintEngine::DirtyClipEnabled)) + { + m_currentIsClipEnabled = updatedState.isClipEnabled(); + } + + if (updatedState.state().testFlag(QPaintEngine::DirtyHints)) + { + // Do nothing + } + + if (updatedState.state().testAnyFlags(QPaintEngine::DirtyClipPath | QPaintEngine::DirtyClipRegion)) + { + std::optional clipRegion; + std::optional clipPath; + + if (updatedState.state().testFlag(QPaintEngine::DirtyClipRegion)) + { + clipRegion = updatedState.clipRegion(); + } + + if (updatedState.state().testFlag(QPaintEngine::DirtyClipPath)) + { + clipPath = updatedState.clipPath(); + } + + updateClipping(std::move(clipRegion), std::move(clipPath), updatedState.clipOperation()); + } } void PDFBLPaintEngine::drawRects(const QRect* rects, int rectCount) @@ -356,70 +405,52 @@ void PDFBLPaintEngine::drawLines(const QLineF* lines, int lineCount) void PDFBLPaintEngine::drawEllipse(const QRectF& r) { - QPointF c = r.center(); - BLEllipse blEllipse(c.x(), c.y(), r.width() * 0.5, r.height() * 0.5); - - if (isFillActive()) - { - m_blContext->fillEllipse(blEllipse); - } - - if (isStrokeActive()) - { - m_blContext->strokeEllipse(blEllipse); - } + QPainterPath path; + path.addEllipse(r); + drawPathImpl(path, true, true); } void PDFBLPaintEngine::drawEllipse(const QRect& r) { - QPointF c = r.center(); - BLEllipse blEllipse(c.x(), c.y(), r.width() * 0.5, r.height() * 0.5); - - if (isFillActive()) - { - m_blContext->fillEllipse(blEllipse); - } - - if (isStrokeActive()) - { - m_blContext->strokeEllipse(blEllipse); - } -} - -void PDFBLPaintEngine::setFillRule(Qt::FillRule fillRule) -{ - BLFillRule blFillRule{}; - - switch (fillRule) - { - case Qt::OddEvenFill: - blFillRule = BL_FILL_RULE_EVEN_ODD; - break; - - case Qt::WindingFill: - blFillRule = BL_FILL_RULE_NON_ZERO; - break; - - default: - Q_ASSERT(false); - break; - } - - m_blContext->setFillRule(blFillRule); + QPainterPath path; + path.addEllipse(r); + drawPathImpl(path, true, true); } void PDFBLPaintEngine::drawPath(const QPainterPath& path) { + drawPathImpl(path, true, true); +} + +void PDFBLPaintEngine::drawPathImpl(const QPainterPath& path, bool enableStroke, bool enableFill) +{ + QPainterPath transformedPath = m_currentTransform.map(path); + ClipMode clipMode = resolveClipping(transformedPath); + + switch (clipMode) + { + case ClipMode::NoClip: + // Do as normal + break; + + case pdf::PDFBLPaintEngine::ClipMode::NotVisible: + // Graphics is not visible + return; + + case pdf::PDFBLPaintEngine::ClipMode::NeedsResolve: + break; + } + BLPath blPath = getBLPath(path); setFillRule(path.fillRule()); - if (isFillActive()) + if (isFillActive() && enableFill) { m_blContext->fillPath(blPath); } - if (isStrokeActive()) + if (isStrokeActive() && enableStroke) { m_blContext->strokePath(blPath); } @@ -462,34 +493,9 @@ void PDFBLPaintEngine::drawPolygon(const QPointF* points, int pointCount, Polygo polygon.assign(points, points + pointCount); path.addPolygon(polygon); - switch (mode) - { - case QPaintEngine::OddEvenMode: - path.setFillRule(Qt::OddEvenFill); - break; - case QPaintEngine::WindingMode: - path.setFillRule(Qt::WindingFill); - break; - case QPaintEngine::ConvexMode: - path.setFillRule(Qt::OddEvenFill); - break; - case QPaintEngine::PolylineMode: - path.setFillRule(Qt::OddEvenFill); - break; - } + setPathFillMode(mode, path); - setFillRule(path.fillRule()); - BLPath blPath = getBLPath(path); - - if (isFillActive() && mode != QPaintEngine::PolylineMode) - { - m_blContext->fillPath(blPath); - } - - if (isStrokeActive()) - { - m_blContext->strokePath(blPath); - } + drawPathImpl(path, true, mode != QPaintEngine::PolylineMode); } void PDFBLPaintEngine::drawPolygon(const QPoint* points, int pointCount, PolygonDrawMode mode) @@ -499,34 +505,9 @@ void PDFBLPaintEngine::drawPolygon(const QPoint* points, int pointCount, Polygon polygon.assign(points, points + pointCount); path.addPolygon(polygon); - switch (mode) - { - case QPaintEngine::OddEvenMode: - path.setFillRule(Qt::OddEvenFill); - break; - case QPaintEngine::WindingMode: - path.setFillRule(Qt::WindingFill); - break; - case QPaintEngine::ConvexMode: - path.setFillRule(Qt::OddEvenFill); - break; - case QPaintEngine::PolylineMode: - path.setFillRule(Qt::OddEvenFill); - break; - } + setPathFillMode(mode, path); - setFillRule(path.fillRule()); - BLPath blPath = getBLPath(path); - - if (isFillActive() && mode != QPaintEngine::PolylineMode) - { - m_blContext->fillPath(blPath); - } - - if (isStrokeActive()) - { - m_blContext->strokePath(blPath); - } + drawPathImpl(path, true, mode != QPaintEngine::PolylineMode); } void PDFBLPaintEngine::drawPixmap(const QRectF& r, const QPixmap& pm, const QRectF& sr) @@ -932,4 +913,157 @@ BLCompOp PDFBLPaintEngine::getBLCompOp(QPainter::CompositionMode mode) return BL_COMP_OP_SRC_OVER; } +void PDFBLPaintEngine::setPathFillMode(PolygonDrawMode mode, QPainterPath& path) +{ + switch (mode) + { + case QPaintEngine::OddEvenMode: + path.setFillRule(Qt::OddEvenFill); + break; + case QPaintEngine::WindingMode: + path.setFillRule(Qt::WindingFill); + break; + case QPaintEngine::ConvexMode: + path.setFillRule(Qt::OddEvenFill); + break; + case QPaintEngine::PolylineMode: + path.setFillRule(Qt::OddEvenFill); + break; + } +} + +void PDFBLPaintEngine::updateClipping(std::optional clipRegion, + std::optional clipPath, + Qt::ClipOperation clipOperation) +{ + switch (clipOperation) + { + case Qt::NoClip: + m_clipRegion.reset(); + m_clipPath.reset(); + m_finalClipPath.reset(); + m_clipSingleRect = false; + m_blContext->restoreClipping(); + return; + + case Qt::ReplaceClip: + { + m_clipPath = std::move(clipPath); + m_clipRegion = std::move(clipRegion); + break; + } + + case Qt::IntersectClip: + { + if (m_clipPath.has_value()) + { + if (clipPath.has_value()) + { + *m_clipPath = m_clipPath->intersected(*clipPath); + } + } + else + { + m_clipPath = std::move(clipPath); + } + + if (m_clipRegion.has_value()) + { + if (clipRegion.has_value()) + { + *m_clipRegion = m_clipRegion->intersected(*clipRegion); + } + } + else + { + m_clipRegion = std::move(clipRegion); + } + + break; + } + } + + m_clipSingleRect = m_clipRegion.has_value() && !m_clipPath.has_value() && m_clipRegion->rectCount() == 1; + + if (m_clipSingleRect) + { + QRegion transformedRegion = m_currentTransform.map(m_clipRegion.value()); + m_blContext->clipToRect(getBLRect(transformedRegion.boundingRect())); + } + else + { + m_blContext->restoreClipping(); + } + + m_finalClipPath = QPainterPath(); + + QPainterPath clip1; + QPainterPath clip2; + + if (m_clipPath.has_value()) + { + clip1 = m_clipPath.value(); + } + + if (m_clipRegion.has_value()) + { + clip2.addRegion(m_clipRegion.value()); + } + + if (!clip1.isEmpty() && !clip2.isEmpty()) + { + m_finalClipPath = clip1.intersected(clip2); + } + else if (!clip1.isEmpty()) + { + m_finalClipPath = std::move(clip1); + } + else if (!clip2.isEmpty()) + { + m_finalClipPath = std::move(clip2); + } + + m_finalClipPath = m_currentTransform.map(m_finalClipPath.value()); +} + +PDFBLPaintEngine::ClipMode PDFBLPaintEngine::resolveClipping(const QPainterPath& path) const +{ + if (!m_currentIsClipEnabled || m_clipSingleRect || !m_finalClipPath.has_value() || m_finalClipPath->isEmpty()) + { + return ClipMode::NoClip; + } + + QRectF clipRect = m_finalClipPath->controlPointRect(); + QRectF pathRect = path.controlPointRect(); + + if (!pathRect.intersects(clipRect)) + { + return ClipMode::NotVisible; + } + + return ClipMode::NeedsResolve; +} + +void PDFBLPaintEngine::setFillRule(Qt::FillRule fillRule) +{ + BLFillRule blFillRule{}; + + switch (fillRule) + { + case Qt::OddEvenFill: + blFillRule = BL_FILL_RULE_EVEN_ODD; + break; + + case Qt::WindingFill: + blFillRule = BL_FILL_RULE_NON_ZERO; + break; + + default: + Q_ASSERT(false); + break; + } + + m_blContext->setFillRule(blFillRule); +} + } // namespace pdf