mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	Issue #116: Navigate to text
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
| @@ -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; } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   | ||||
| @@ -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), | ||||
|   | ||||
| @@ -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 | ||||
|     { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user