From 13bfbd7095730f7fda709f8765aa912fe5624b70 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Fri, 28 Feb 2020 18:56:28 +0100 Subject: [PATCH] Advanced snapping, basics of screenshot tool --- .../sources/pdfdrawspacecontroller.cpp | 11 ++ PdfForQtLib/sources/pdfdrawspacecontroller.h | 3 + PdfForQtLib/sources/pdfsnapper.cpp | 47 +++++++ PdfForQtLib/sources/pdfsnapper.h | 12 ++ PdfForQtLib/sources/pdfutils.h | 12 ++ PdfForQtLib/sources/pdfwidgettool.cpp | 115 +++++++++++++++++- PdfForQtLib/sources/pdfwidgettool.h | 15 ++- 7 files changed, 212 insertions(+), 3 deletions(-) diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp index 69366d3..c08ad5e 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.cpp +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.cpp @@ -1370,4 +1370,15 @@ void IDocumentDrawInterface::drawPostRendering(QPainter* painter, QRect rect) co Q_UNUSED(rect); } +const PDFWidgetSnapshot::SnapshotItem* PDFWidgetSnapshot::getPageSnapshot(PDFInteger pageIndex) const +{ + auto it = std::find_if(items.cbegin(), items.cend(), [pageIndex](const auto& item) { return item.pageIndex == pageIndex; }); + if (it != items.cend()) + { + return &*it; + } + + return nullptr; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfdrawspacecontroller.h b/PdfForQtLib/sources/pdfdrawspacecontroller.h index beb9928..b27445d 100644 --- a/PdfForQtLib/sources/pdfdrawspacecontroller.h +++ b/PdfForQtLib/sources/pdfdrawspacecontroller.h @@ -196,6 +196,9 @@ struct PDFWidgetSnapshot const PDFPrecompiledPage* compiledPage = nullptr; ///< Compiled page (can be nullptr) }; + bool hasPage(PDFInteger pageIndex) const { return getPageSnapshot(pageIndex) != nullptr; } + const SnapshotItem* getPageSnapshot(PDFInteger pageIndex) const; + using SnapshotItems = std::vector; SnapshotItems items; }; diff --git a/PdfForQtLib/sources/pdfsnapper.cpp b/PdfForQtLib/sources/pdfsnapper.cpp index 7e00d24..0859b98 100644 --- a/PdfForQtLib/sources/pdfsnapper.cpp +++ b/PdfForQtLib/sources/pdfsnapper.cpp @@ -179,6 +179,41 @@ void PDFSnapper::buildSnapPoints(const PDFWidgetSnapshot& snapshot) viewportSnapPoint.viewportPoint = item.pageToDeviceMatrix.map(snapPoint.point); m_snapPoints.push_back(qMove(viewportSnapPoint)); } + + // Fill line projections snap points + if (m_currentPage == item.pageIndex && m_referencePoint.has_value()) + { + QPointF referencePoint = *m_referencePoint; + for (const QLineF& line : info->getLines()) + { + // Project point onto line. + const qreal lineLength = line.length(); + QPointF vector = referencePoint - line.p1(); + QPointF tangentVector = (line.p2() - line.p1()) / lineLength; + const qreal absoluteParameter = QPointF::dotProduct(vector, tangentVector); + if (absoluteParameter >= 0 && absoluteParameter <= lineLength) + { + QPointF projectedSnapPoint = line.pointAt(absoluteParameter / lineLength); + const PDFReal tolerance = lineLength * 0.01; + const PDFReal squaredTolerance = tolerance * tolerance; + + // Test, if projected snap point is not already present in snap points + auto testSamePoints = [projectedSnapPoint, squaredTolerance](const ViewportSnapPoint& testedSnapPoint) + { + return isFuzzyComparedPointsSame(projectedSnapPoint, testedSnapPoint.point, squaredTolerance); + }; + if (m_snapPoints.cend() == std::find_if(m_snapPoints.cbegin(), m_snapPoints.cend(), testSamePoints)) + { + ViewportSnapPoint viewportSnapPoint; + viewportSnapPoint.type = SnapType::GeneratedLineProjection; + viewportSnapPoint.point = projectedSnapPoint; + viewportSnapPoint.pageIndex = item.pageIndex; + viewportSnapPoint.viewportPoint = item.pageToDeviceMatrix.map(projectedSnapPoint); + m_snapPoints.push_back(qMove(viewportSnapPoint)); + } + } + } + } } // Third, update snap shot position @@ -205,4 +240,16 @@ QPointF PDFSnapper::getSnappedPoint() const return m_mousePoint; } +void PDFSnapper::setReferencePoint(PDFInteger pageIndex, QPointF pagePoint) +{ + m_currentPage = pageIndex; + m_referencePoint = pagePoint; +} + +void PDFSnapper::clearReferencePoint() +{ + m_currentPage = -1; + m_referencePoint = std::nullopt; +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfsnapper.h b/PdfForQtLib/sources/pdfsnapper.h index ff69303..91ba6eb 100644 --- a/PdfForQtLib/sources/pdfsnapper.h +++ b/PdfForQtLib/sources/pdfsnapper.h @@ -81,6 +81,9 @@ public: /// Returns snap points const std::vector& getSnapPoints() const { return m_snapPoints; } + /// Returns lines + const std::vector& getLines() const { return m_snapLines; } + private: std::vector m_snapPoints; std::vector m_snapLines; @@ -135,6 +138,14 @@ public: /// mouse cursor position is returned. QPointF getSnappedPoint() const; + /// Sets current page index and reference point + /// \param pageIndex Page index + /// \param pagePoint Page point + void setReferencePoint(PDFInteger pageIndex, QPointF pagePoint); + + /// Resets reference point (and current page) + void clearReferencePoint(); + private: struct ViewportSnapPoint : public PDFSnapInfo::SnapPoint @@ -152,6 +163,7 @@ private: std::vector m_snapPoints; std::optional m_snappedPoint; QPointF m_mousePoint; + std::optional m_referencePoint; PDFInteger m_currentPage = -1; int m_snapPointPixelSize = 0; int m_snapPointTolerance = 0; diff --git a/PdfForQtLib/sources/pdfutils.h b/PdfForQtLib/sources/pdfutils.h index c01f2f7..98ca38e 100644 --- a/PdfForQtLib/sources/pdfutils.h +++ b/PdfForQtLib/sources/pdfutils.h @@ -471,6 +471,18 @@ private: T m_q; }; +/// Fuzzy compares two points, with given tolerance (so, if points are at lower distance +/// from each other than squared tolerance, they are considered as same and function returns true). +/// \param p1 First point +/// \param p2 Second point +/// \param squaredTolerance Squared tolerance +static inline bool isFuzzyComparedPointsSame(const QPointF& p1, const QPointF& p2, PDFReal squaredTolerance) +{ + QPointF dp = p2 - p1; + const qreal squaredDistance = QPointF::dotProduct(dp, dp); + return squaredDistance < squaredTolerance; +} + } // namespace pdf #endif // PDFUTILS_H diff --git a/PdfForQtLib/sources/pdfwidgettool.cpp b/PdfForQtLib/sources/pdfwidgettool.cpp index e1d7a58..1fbc213 100644 --- a/PdfForQtLib/sources/pdfwidgettool.cpp +++ b/PdfForQtLib/sources/pdfwidgettool.cpp @@ -52,7 +52,7 @@ PDFWidgetTool::PDFWidgetTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject m_action(action), m_proxy(proxy) { - + updateActions(); } PDFWidgetTool::~PDFWidgetTool() @@ -949,7 +949,8 @@ void PDFMagnifierTool::setMagnifierSize(int magnifierSize) PDFPickTool::PDFPickTool(PDFDrawWidgetProxy* proxy, PDFPickTool::Mode mode, QObject* parent) : BaseClass(proxy, parent), - m_mode(mode) + m_mode(mode), + m_pageIndex(-1) { setCursor(Qt::BlankCursor); m_snapper.setSnapPointPixelSize(PDFWidgetUtils::scaleDPI_x(proxy->getWidget(), 10)); @@ -959,6 +960,36 @@ PDFPickTool::PDFPickTool(PDFDrawWidgetProxy* proxy, PDFPickTool::Mode mode, QObj connect(proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFPickTool::buildSnapPoints); } +void PDFPickTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + + // If we are picking rectangles, then draw current selection rectangle + if (m_mode == Mode::Rectangles && m_pageIndex == pageIndex && !m_pickedPoints.empty()) + { + QPoint p1 = pagePointToDevicePointMatrix.map(m_pickedPoints.back()).toPoint(); + QPoint p2 = m_snapper.getSnappedPoint().toPoint(); + + int xMin = qMin(p1.x(), p2.x()); + int xMax = qMax(p1.x(), p2.x()); + int yMin = qMin(p1.y(), p2.y()); + int yMax = qMax(p1.y(), p2.y()); + + QRect selectionRectangle(xMin, yMin, xMax - xMin, yMax - yMin); + if (selectionRectangle.isValid()) + { + QColor selectionColor(Qt::blue); + selectionColor.setAlphaF(0.25); + painter->fillRect(selectionRectangle, selectionColor); + } + } +} + void PDFPickTool::drawPostRendering(QPainter* painter, QRect rect) const { m_snapper.drawSnapPoints(painter); @@ -983,6 +1014,48 @@ void PDFPickTool::mousePressEvent(QWidget* widget, QMouseEvent* event) { Q_UNUSED(widget); event->accept(); + + if (event->button() == Qt::LeftButton) + { + // Try to perform pick + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(m_snapper.getSnappedPoint().toPoint(), &pagePoint); + if (pageIndex != -1 && // We have picked some point on page + (m_pageIndex == -1 || m_pageIndex == pageIndex)) // We are under current page + { + m_pageIndex = pageIndex; + m_pickedPoints.push_back(pagePoint); + m_snapper.setReferencePoint(pageIndex, pagePoint); + + // Emit signal about picked point + emit pointPicked(pageIndex, pagePoint); + + if (m_mode == Mode::Rectangles && m_pickedPoints.size() == 2) + { + QPointF first = m_pickedPoints.front(); + QPointF second = m_pickedPoints.back(); + + const qreal xMin = qMin(first.x(), second.x()); + const qreal xMax = qMax(first.x(), second.x()); + const qreal yMin = qMin(first.y(), second.y()); + const qreal yMax = qMax(first.y(), second.y()); + + QRectF pageRectangle(xMin, yMin, xMax - xMin, yMax - yMin); + emit rectanglePicked(pageIndex, pageRectangle); + + // We must reset tool, to pick next rectangle + resetTool(); + } + + buildSnapPoints(); + getProxy()->repaintNeeded(); + } + } + else if (event->button() == Qt::RightButton) + { + // Reset tool to enable new picking (right button means reset the tool) + resetTool(); + } } void PDFPickTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) @@ -1012,6 +1085,22 @@ void PDFPickTool::setActiveImpl(bool active) { buildSnapPoints(); } + else + { + // Reset tool to reinitialize it for future use. If tool + // is activated, then it should be in initial state. + resetTool(); + } +} + +void PDFPickTool::resetTool() +{ + m_pickedPoints.clear(); + m_pageIndex = -1; + m_snapper.clearReferencePoint(); + + buildSnapPoints(); + getProxy()->repaintNeeded(); } void PDFPickTool::buildSnapPoints() @@ -1030,6 +1119,28 @@ PDFScreenshotTool::PDFScreenshotTool(PDFDrawWidgetProxy* proxy, QAction* action, { m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFScreenshotTool::onRectanglePicked); +} + +void PDFScreenshotTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + PDFWidgetSnapshot snapshot = getProxy()->getSnapshot(); + if (const PDFWidgetSnapshot::SnapshotItem* pageSnapshot = snapshot.getPageSnapshot(pageIndex)) + { + QRect selectedRectangle = pageSnapshot->pageToDeviceMatrix.mapRect(pageRectangle).toRect(); + if (selectedRectangle.isValid()) + { + QImage image(selectedRectangle.size(), QImage::Format_RGB888); + + { + QPainter painter(&image); + painter.translate(-selectedRectangle.topLeft()); + getProxy()->drawPages(&painter, getProxy()->getWidget()->rect()); + } + + QApplication::clipboard()->setImage(image, QClipboard::Clipboard); + } + } } } // namespace pdf diff --git a/PdfForQtLib/sources/pdfwidgettool.h b/PdfForQtLib/sources/pdfwidgettool.h index 8c995f6..52cbd2a 100644 --- a/PdfForQtLib/sources/pdfwidgettool.h +++ b/PdfForQtLib/sources/pdfwidgettool.h @@ -296,24 +296,35 @@ public: /// \param parent Parent object explicit PDFPickTool(PDFDrawWidgetProxy* proxy, Mode mode, QObject* parent); + virtual void drawPage(QPainter* painter, PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix) const override; virtual void drawPostRendering(QPainter* painter, QRect rect) const override; virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; +signals: + void pointPicked(PDFInteger pageIndex, QPointF pagePoint); + void rectanglePicked(PDFInteger pageIndex, QRectF pageRectangle); + protected: virtual void setActiveImpl(bool active) override; private: + void resetTool(); void buildSnapPoints(); Mode m_mode; PDFSnapper m_snapper; QPoint m_mousePosition; + PDFInteger m_pageIndex; std::vector m_pickedPoints; }; -/// Tool that makes screenshot of page area and copies it to the clipboard +/// Tool that makes screenshot of page area and copies it to the clipboard, +/// using current client area to determine image size. class PDFFORQTLIBSHARED_EXPORT PDFScreenshotTool : public PDFWidgetTool { Q_OBJECT @@ -329,6 +340,8 @@ public: explicit PDFScreenshotTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent); private: + void onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle); + PDFPickTool* m_pickTool; };