Issue #116: Navigate to text

This commit is contained in:
Jakub Melka 2023-12-02 15:40:11 +01:00
parent a892c192fc
commit 288eead65e
6 changed files with 175 additions and 70 deletions

View File

@ -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)

View File

@ -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; }

View File

@ -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;

View File

@ -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);

View File

@ -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),

View File

@ -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
{