diff --git a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp index 6ddcee9..a33623a 100644 --- a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp +++ b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp @@ -1282,6 +1282,47 @@ void PDFDrawWidgetProxy::goToPage(PDFInteger pageIndex) } } +void PDFDrawWidgetProxy::goToPageAndEnsureVisible(PDFInteger pageIndex, QRectF ensureVisibleRect) +{ + PDFDrawSpaceController::LayoutItem layoutItem = m_controller->getLayoutItemForPage(pageIndex); + + if (layoutItem.isValid()) + { + // We have found our page, navigate onto it + if (isBlockMode()) + { + setBlockIndex(layoutItem.blockIndex); + } + + QRectF pageRect = fromDeviceSpace(layoutItem.pageRectMM); + QRectF placedRect = pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); + + const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(layoutItem.pageIndex); + QTransform matrix = QTransform(createPagePointToDevicePointMatrix(page, placedRect)); + + QRect wishedRect = matrix.mapRect(ensureVisibleRect).toRect(); + QRect displayedRect = getWidget()->getDrawWidget()->getWidget()->rect(); + QPoint topRectPoint = wishedRect.topLeft(); + + if (!displayedRect.contains(topRectPoint)) + { + QPoint center = displayedRect.center(); + QPoint p1(center.x(), topRectPoint.y()); + + if (!displayedRect.contains(p1)) + { + scrollByPixels(-QPoint(0, p1.y() - displayedRect.top())); + } + + QPoint p2(topRectPoint.x(), center.y()); + if (!displayedRect.contains(p2)) + { + scrollByPixels(-QPoint(p2.x() - displayedRect.x(), 0)); + } + } + } +} + void PDFDrawWidgetProxy::setPageLayout(PageLayout pageLayout) { if (getPageLayout() != pageLayout) diff --git a/Pdf4QtLib/sources/pdfdrawspacecontroller.h b/Pdf4QtLib/sources/pdfdrawspacecontroller.h index 2986e26..b013120 100644 --- a/Pdf4QtLib/sources/pdfdrawspacecontroller.h +++ b/Pdf4QtLib/sources/pdfdrawspacecontroller.h @@ -288,6 +288,11 @@ public: /// \param pageIndex Page to scroll to void goToPage(PDFInteger pageIndex); + /// Go to the specified page and ensures point on the page is visible + /// \param pageIndex Page to scroll to + /// \param ensureVisibleRect Rectangle on page, which should be visible + void goToPageAndEnsureVisible(PDFInteger pageIndex, QRectF ensureVisibleRect); + /// Returns current zoom from widget space to device space. So, for example 2.00 corresponds to 200% zoom, /// and each 1 cm of widget area corresponds to 0.5 cm of the device space area. PDFReal getZoom() const { return m_zoom; } diff --git a/Pdf4QtLib/sources/pdftextlayout.cpp b/Pdf4QtLib/sources/pdftextlayout.cpp index 4eeca75..aca2d9c 100644 --- a/Pdf4QtLib/sources/pdftextlayout.cpp +++ b/Pdf4QtLib/sources/pdftextlayout.cpp @@ -966,6 +966,82 @@ void PDFTextBlock::applyTransform(const QTransform& matrix) } } +QPainterPath PDFTextBlock::getCharacterRangeBoundingPath(const PDFCharacterPointer& start, + const PDFCharacterPointer& end, + const QTransform& matrix, + PDFReal heightIncreaseFactor) const +{ + QPainterPath path; + + PDFTextBlock block = *this; + + // Fix angle of block, so we will get correct selection rectangles (parallel to lines) + QTransform angleMatrix; + angleMatrix.rotate(block.getAngle()); + block.applyTransform(angleMatrix); + + const size_t lineStart = start.lineIndex; + const size_t lineEnd = end.lineIndex; + Q_ASSERT(lineEnd >= lineStart); + + const PDFTextLines& lines = block.getLines(); + for (size_t lineIndex = lineStart; lineIndex <= lineEnd; ++lineIndex) + { + if (lineIndex >= lines.size()) + { + // Selection is invalid, do nothing + continue; + } + + const PDFTextLine& line = lines[lineIndex]; + const TextCharacters& characters = line.getCharacters(); + + if (characters.empty()) + { + // Selection is invalid, do nothing + continue; + } + + // First determine, which characters will be selected + size_t characterStart = 0; + size_t characterEnd = characters.size() - 1; + + if (lineIndex == lineStart) + { + characterStart = start.characterIndex; + } + if (lineIndex == lineEnd) + { + characterEnd = end.characterIndex; + } + + // Validate indices, then calculate bounding box + if (!(characterStart <= characterEnd && characterEnd < characters.size())) + { + continue; + } + + QRectF boundingBox; + for (size_t i = characterStart; i <= characterEnd; ++i) + { + boundingBox = boundingBox.united(characters[i].boundingBox.boundingRect()); + } + + if (boundingBox.isValid()) + { + // Enlarge height by some percent + PDFReal heightAdvance = boundingBox.height() * heightIncreaseFactor * 0.5; + boundingBox.adjust(0, -heightAdvance, 0, heightAdvance); + path.addRect(boundingBox); + } + } + + QTransform transformMatrix = angleMatrix.inverted() * matrix; + path = transformMatrix.map(path); + + return path; +} + QDataStream& operator>>(QDataStream& stream, PDFTextBlock& block) { stream >> block.m_lines; @@ -1459,73 +1535,8 @@ void PDFTextSelectionPainter::draw(QPainter* painter, PDFInteger pageIndex, PDFT continue; } - PDFTextBlock block = blocks[start.blockIndex]; - - // Fix angle of block, so we will get correct selection rectangles (parallel to lines) - QTransform angleMatrix; - angleMatrix.rotate(block.getAngle()); - block.applyTransform(angleMatrix); - - QPainterPath path; - - const size_t lineStart = start.lineIndex; - const size_t lineEnd = end.lineIndex; - Q_ASSERT(lineEnd >= lineStart); - - const PDFTextLines& lines = block.getLines(); - for (size_t lineIndex = lineStart; lineIndex <= lineEnd; ++lineIndex) - { - if (lineIndex >= lines.size()) - { - // Selection is invalid, do nothing - continue; - } - - const PDFTextLine& line = lines[lineIndex]; - const TextCharacters& characters = line.getCharacters(); - - if (characters.empty()) - { - // Selection is invalid, do nothing - continue; - } - - // First determine, which characters will be selected - size_t characterStart = 0; - size_t characterEnd = characters.size() - 1; - - if (lineIndex == lineStart) - { - characterStart = start.characterIndex; - } - if (lineIndex == lineEnd) - { - characterEnd = end.characterIndex; - } - - // Validate indices, then calculate bounding box - if (!(characterStart <= characterEnd && characterEnd < characters.size())) - { - continue; - } - - QRectF boundingBox; - for (size_t i = characterStart; i <= characterEnd; ++i) - { - boundingBox = boundingBox.united(characters[i].boundingBox.boundingRect()); - } - - if (boundingBox.isValid()) - { - // Enlarge height by some percent - PDFReal heightAdvance = boundingBox.height() * HEIGHT_INCREASE_FACTOR * 0.5; - boundingBox.adjust(0, -heightAdvance, 0, heightAdvance); - path.addRect(boundingBox); - } - } - - QTransform transformMatrix = angleMatrix.inverted() * matrix; - path = transformMatrix.map(path); + const PDFTextBlock& block = blocks[start.blockIndex]; + QPainterPath path = block.getCharacterRangeBoundingPath(start, end, matrix, HEIGHT_INCREASE_FACTOR); QColor penColor = item.color.darker(); QColor brushColor = item.color; diff --git a/Pdf4QtLib/sources/pdftextlayout.h b/Pdf4QtLib/sources/pdftextlayout.h index a614e35..6408745 100644 --- a/Pdf4QtLib/sources/pdftextlayout.h +++ b/Pdf4QtLib/sources/pdftextlayout.h @@ -32,6 +32,7 @@ namespace pdf { class PDFTextLayout; class PDFTextLayoutStorage; +struct PDFCharacterPointer; struct PDFTextCharacterInfo { @@ -154,6 +155,18 @@ public: void applyTransform(const QTransform& matrix); + /// Retrieves the bounding QPainterPath between two specified character positions within this block. + /// The provided character pointers must point to characters within the current block. + /// \param start A reference to the PDFCharacterPointer indicating the start character. + /// \param end A reference to the PDFCharacterPointer indicating the end character. + /// \param matrix Transformation applied to the path + /// \param heightIncreaseFactor Height increase factor for characters + /// \return QPainterPath representing the bounding path between the start and end characters. + QPainterPath getCharacterRangeBoundingPath(const PDFCharacterPointer& start, + const PDFCharacterPointer& end, + const QTransform& matrix, + PDFReal heightIncreaseFactor) const; + friend QDataStream& operator<<(QDataStream& stream, const PDFTextBlock& block); friend QDataStream& operator>>(QDataStream& stream, PDFTextBlock& block); diff --git a/Pdf4QtLib/sources/pdfwidgettool.cpp b/Pdf4QtLib/sources/pdfwidgettool.cpp index dc04ce7..e8b58cb 100644 --- a/Pdf4QtLib/sources/pdfwidgettool.cpp +++ b/Pdf4QtLib/sources/pdfwidgettool.cpp @@ -315,6 +315,25 @@ void PDFFindTextTool::clearResults() getProxy()->repaintNeeded(); } +void PDFFindTextTool::goToCurrentResult() +{ + PDFTextSelection textSelection = getTextSelectionSelectedResultOnly(); + if (!textSelection.isEmpty()) + { + const PDFTextSelectionColoredItem& firstItem = *textSelection.begin(); + PDFTextLayoutGetter textLayoutGetter = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(firstItem.start.pageIndex); + + pdf::PDFTextSelectionPainter textSelectionPainter(&textSelection); + QPainterPath painterPath = textSelectionPainter.prepareGeometry(firstItem.start.pageIndex, textLayoutGetter, QTransform(), nullptr); + + if (!painterPath.isEmpty()) + { + getProxy()->goToPageAndEnsureVisible(firstItem.start.pageIndex, painterPath.boundingRect()); + + } + } +} + void PDFFindTextTool::setActiveImpl(bool active) { BaseClass::setActiveImpl(active); @@ -327,7 +346,7 @@ void PDFFindTextTool::setActiveImpl(bool active) getProxy()->getTextLayoutCompiler()->makeTextLayout(); // Create dialog - m_dialog = new PDFFindTextToolDialog(getProxy(), m_parentDialog, Qt::Popup | Qt::CustomizeWindowHint | Qt::WindowTitleHint); + m_dialog = new PDFFindTextToolDialog(getProxy(), m_parentDialog, Qt::Popup | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); m_dialog->setWindowTitle(tr("Find")); QGridLayout* layout = new QGridLayout(m_dialog); @@ -448,7 +467,7 @@ void PDFFindTextTool::onActionPrevious() } m_textSelection.dirty(); getProxy()->repaintNeeded(); - getProxy()->goToPage(m_findResults[m_selectedResultIndex].textSelectionItems.front().first.pageIndex); + goToCurrentResult(); updateTitle(); } } @@ -460,7 +479,7 @@ void PDFFindTextTool::onActionNext() m_selectedResultIndex = (m_selectedResultIndex + 1) % m_findResults.size(); m_textSelection.dirty(); getProxy()->repaintNeeded(); - getProxy()->goToPage(m_findResults[m_selectedResultIndex].textSelectionItems.front().first.pageIndex); + goToCurrentResult(); updateTitle(); } } @@ -591,6 +610,20 @@ PDFTextSelection PDFFindTextTool::getTextSelectionImpl() const return result; } +PDFTextSelection PDFFindTextTool::getTextSelectionSelectedResultOnly() const +{ + pdf::PDFTextSelection result; + + if (m_selectedResultIndex < m_findResults.size()) + { + const pdf::PDFFindResult& findResult = m_findResults[m_selectedResultIndex]; + result.addItems(findResult.textSelectionItems, Qt::transparent); + } + result.build(); + + return result; +} + PDFSelectTextTool::PDFSelectTextTool(PDFDrawWidgetProxy* proxy, QAction* action, QAction* copyTextAction, QAction* selectAllAction, QAction* deselectAction, QObject* parent) : BaseClass(proxy, action, parent), m_copyTextAction(copyTextAction), diff --git a/Pdf4QtLib/sources/pdfwidgettool.h b/Pdf4QtLib/sources/pdfwidgettool.h index 3b25b78..bcc4d1d 100644 --- a/Pdf4QtLib/sources/pdfwidgettool.h +++ b/Pdf4QtLib/sources/pdfwidgettool.h @@ -183,6 +183,7 @@ private: void updateResultsUI(); void updateTitle(); void clearResults(); + void goToCurrentResult(); QAction* m_prevAction; QAction* m_nextAction; @@ -201,6 +202,7 @@ private: pdf::PDFTextSelection getTextSelection() const { return m_textSelection.get(this, &PDFFindTextTool::getTextSelectionImpl); } pdf::PDFTextSelection getTextSelectionImpl() const; + pdf::PDFTextSelection getTextSelectionSelectedResultOnly() const; struct SearchParameters {