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)
|
void PDFDrawWidgetProxy::setPageLayout(PageLayout pageLayout)
|
||||||
{
|
{
|
||||||
if (getPageLayout() != pageLayout)
|
if (getPageLayout() != pageLayout)
|
||||||
|
@ -288,6 +288,11 @@ public:
|
|||||||
/// \param pageIndex Page to scroll to
|
/// \param pageIndex Page to scroll to
|
||||||
void goToPage(PDFInteger pageIndex);
|
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,
|
/// 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.
|
/// and each 1 cm of widget area corresponds to 0.5 cm of the device space area.
|
||||||
PDFReal getZoom() const { return m_zoom; }
|
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)
|
QDataStream& operator>>(QDataStream& stream, PDFTextBlock& block)
|
||||||
{
|
{
|
||||||
stream >> block.m_lines;
|
stream >> block.m_lines;
|
||||||
@ -1459,73 +1535,8 @@ void PDFTextSelectionPainter::draw(QPainter* painter, PDFInteger pageIndex, PDFT
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
PDFTextBlock block = blocks[start.blockIndex];
|
const PDFTextBlock& block = blocks[start.blockIndex];
|
||||||
|
QPainterPath path = block.getCharacterRangeBoundingPath(start, end, matrix, HEIGHT_INCREASE_FACTOR);
|
||||||
// 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);
|
|
||||||
|
|
||||||
QColor penColor = item.color.darker();
|
QColor penColor = item.color.darker();
|
||||||
QColor brushColor = item.color;
|
QColor brushColor = item.color;
|
||||||
|
@ -32,6 +32,7 @@ namespace pdf
|
|||||||
{
|
{
|
||||||
class PDFTextLayout;
|
class PDFTextLayout;
|
||||||
class PDFTextLayoutStorage;
|
class PDFTextLayoutStorage;
|
||||||
|
struct PDFCharacterPointer;
|
||||||
|
|
||||||
struct PDFTextCharacterInfo
|
struct PDFTextCharacterInfo
|
||||||
{
|
{
|
||||||
@ -154,6 +155,18 @@ public:
|
|||||||
|
|
||||||
void applyTransform(const QTransform& matrix);
|
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, const PDFTextBlock& block);
|
||||||
friend QDataStream& operator>>(QDataStream& stream, PDFTextBlock& block);
|
friend QDataStream& operator>>(QDataStream& stream, PDFTextBlock& block);
|
||||||
|
|
||||||
|
@ -315,6 +315,25 @@ void PDFFindTextTool::clearResults()
|
|||||||
getProxy()->repaintNeeded();
|
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)
|
void PDFFindTextTool::setActiveImpl(bool active)
|
||||||
{
|
{
|
||||||
BaseClass::setActiveImpl(active);
|
BaseClass::setActiveImpl(active);
|
||||||
@ -327,7 +346,7 @@ void PDFFindTextTool::setActiveImpl(bool active)
|
|||||||
getProxy()->getTextLayoutCompiler()->makeTextLayout();
|
getProxy()->getTextLayoutCompiler()->makeTextLayout();
|
||||||
|
|
||||||
// Create dialog
|
// 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"));
|
m_dialog->setWindowTitle(tr("Find"));
|
||||||
|
|
||||||
QGridLayout* layout = new QGridLayout(m_dialog);
|
QGridLayout* layout = new QGridLayout(m_dialog);
|
||||||
@ -448,7 +467,7 @@ void PDFFindTextTool::onActionPrevious()
|
|||||||
}
|
}
|
||||||
m_textSelection.dirty();
|
m_textSelection.dirty();
|
||||||
getProxy()->repaintNeeded();
|
getProxy()->repaintNeeded();
|
||||||
getProxy()->goToPage(m_findResults[m_selectedResultIndex].textSelectionItems.front().first.pageIndex);
|
goToCurrentResult();
|
||||||
updateTitle();
|
updateTitle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -460,7 +479,7 @@ void PDFFindTextTool::onActionNext()
|
|||||||
m_selectedResultIndex = (m_selectedResultIndex + 1) % m_findResults.size();
|
m_selectedResultIndex = (m_selectedResultIndex + 1) % m_findResults.size();
|
||||||
m_textSelection.dirty();
|
m_textSelection.dirty();
|
||||||
getProxy()->repaintNeeded();
|
getProxy()->repaintNeeded();
|
||||||
getProxy()->goToPage(m_findResults[m_selectedResultIndex].textSelectionItems.front().first.pageIndex);
|
goToCurrentResult();
|
||||||
updateTitle();
|
updateTitle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -591,6 +610,20 @@ PDFTextSelection PDFFindTextTool::getTextSelectionImpl() const
|
|||||||
return result;
|
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) :
|
PDFSelectTextTool::PDFSelectTextTool(PDFDrawWidgetProxy* proxy, QAction* action, QAction* copyTextAction, QAction* selectAllAction, QAction* deselectAction, QObject* parent) :
|
||||||
BaseClass(proxy, action, parent),
|
BaseClass(proxy, action, parent),
|
||||||
m_copyTextAction(copyTextAction),
|
m_copyTextAction(copyTextAction),
|
||||||
|
@ -183,6 +183,7 @@ private:
|
|||||||
void updateResultsUI();
|
void updateResultsUI();
|
||||||
void updateTitle();
|
void updateTitle();
|
||||||
void clearResults();
|
void clearResults();
|
||||||
|
void goToCurrentResult();
|
||||||
|
|
||||||
QAction* m_prevAction;
|
QAction* m_prevAction;
|
||||||
QAction* m_nextAction;
|
QAction* m_nextAction;
|
||||||
@ -201,6 +202,7 @@ private:
|
|||||||
|
|
||||||
pdf::PDFTextSelection getTextSelection() const { return m_textSelection.get(this, &PDFFindTextTool::getTextSelectionImpl); }
|
pdf::PDFTextSelection getTextSelection() const { return m_textSelection.get(this, &PDFFindTextTool::getTextSelectionImpl); }
|
||||||
pdf::PDFTextSelection getTextSelectionImpl() const;
|
pdf::PDFTextSelection getTextSelectionImpl() const;
|
||||||
|
pdf::PDFTextSelection getTextSelectionSelectedResultOnly() const;
|
||||||
|
|
||||||
struct SearchParameters
|
struct SearchParameters
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user