mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Advanced find (algorithms)
This commit is contained in:
@ -92,7 +92,7 @@ private:
|
|||||||
std::map<PDFInteger, CompileTask> m_tasks;
|
std::map<PDFInteger, CompileTask> m_tasks;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PDFAsynchronousTextLayoutCompiler : public QObject
|
class PDFFORQTLIBSHARED_EXPORT PDFAsynchronousTextLayoutCompiler : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@ -130,6 +130,9 @@ public:
|
|||||||
/// \p textLayoutChanged is emitted.
|
/// \p textLayoutChanged is emitted.
|
||||||
void makeTextLayout();
|
void makeTextLayout();
|
||||||
|
|
||||||
|
/// Returns true, if text layout is ready
|
||||||
|
bool isTextLayoutReady() const { return m_textLayouts.has_value(); }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void textLayoutChanged();
|
void textLayoutChanged();
|
||||||
|
|
||||||
|
@ -994,6 +994,7 @@ QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const
|
|||||||
void PDFDrawWidgetProxy::onTextLayoutChanged()
|
void PDFDrawWidgetProxy::onTextLayoutChanged()
|
||||||
{
|
{
|
||||||
emit repaintNeeded();
|
emit repaintNeeded();
|
||||||
|
emit textLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PDFDrawWidgetProxy::isBlockMode() const
|
bool PDFDrawWidgetProxy::isBlockMode() const
|
||||||
|
@ -276,6 +276,7 @@ public:
|
|||||||
const PDFCMSManager* getCMSManager() const;
|
const PDFCMSManager* getCMSManager() const;
|
||||||
PDFProgress* getProgress() const { return m_progress; }
|
PDFProgress* getProgress() const { return m_progress; }
|
||||||
void setProgress(PDFProgress* progress) { m_progress = progress; }
|
void setProgress(PDFProgress* progress) { m_progress = progress; }
|
||||||
|
PDFAsynchronousTextLayoutCompiler* getTextLayoutCompiler() const { return m_textLayoutCompiler; }
|
||||||
|
|
||||||
void setFeatures(PDFRenderer::Features features);
|
void setFeatures(PDFRenderer::Features features);
|
||||||
void setPreferredMeshResolutionRatio(PDFReal ratio);
|
void setPreferredMeshResolutionRatio(PDFReal ratio);
|
||||||
@ -291,6 +292,7 @@ signals:
|
|||||||
void renderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors);
|
void renderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors);
|
||||||
void repaintNeeded();
|
void repaintNeeded();
|
||||||
void pageImageChanged(bool all, const std::vector<PDFInteger>& pages);
|
void pageImageChanged(bool all, const std::vector<PDFInteger>& pages);
|
||||||
|
void textLayoutChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct LayoutItem
|
struct LayoutItem
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2019 Jakub Melka
|
// Copyright (C) 2019-2020 Jakub Melka
|
||||||
//
|
//
|
||||||
// This file is part of PdfForQt.
|
// This file is part of PdfForQt.
|
||||||
//
|
//
|
||||||
@ -593,4 +593,170 @@ QDataStream& operator>>(QDataStream& stream, PDFTextLayoutSettings& settings)
|
|||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PDFTextSelection::PDFTextSelection(PDFTextSelectionItems&& items) :
|
||||||
|
m_items(qMove(items))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFFindResults PDFTextFlow::find(const QString& text, Qt::CaseSensitivity caseSensitivity) const
|
||||||
|
{
|
||||||
|
PDFFindResults results;
|
||||||
|
|
||||||
|
int index = m_text.indexOf(text, 0, caseSensitivity);
|
||||||
|
while (index != -1)
|
||||||
|
{
|
||||||
|
PDFFindResult result;
|
||||||
|
result.matched = text;
|
||||||
|
result.textSelectionItems = getTextSelectionItems(index, text.length());
|
||||||
|
result.context = getContext(index, text.length());
|
||||||
|
|
||||||
|
if (!result.textSelectionItems.empty())
|
||||||
|
{
|
||||||
|
results.emplace_back(qMove(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
index = m_text.indexOf(text, index + 1, caseSensitivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFTextFlow::merge(const PDFTextFlow& next)
|
||||||
|
{
|
||||||
|
m_text += next.m_text;
|
||||||
|
m_characterPointers.insert(m_characterPointers.end(), next.m_characterPointers.cbegin(), next.m_characterPointers.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFTextFlows PDFTextFlow::createTextFlows(const PDFTextLayout& layout, FlowFlags flags, PDFInteger pageIndex)
|
||||||
|
{
|
||||||
|
PDFTextFlows result;
|
||||||
|
|
||||||
|
if (!flags.testFlag(SeparateBlocks))
|
||||||
|
{
|
||||||
|
result.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString lineBreak(" ");
|
||||||
|
if (flags.testFlag(AddLineBreaks))
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
lineBreak = QString("\r\n");
|
||||||
|
#elif defined(Q_OS_UNIX)
|
||||||
|
linebreak = QString("\n");
|
||||||
|
#elif defined(Q_OS_MAC)
|
||||||
|
lineBreak = QString("\r");
|
||||||
|
#else
|
||||||
|
static_assert(false, "Fix this code!");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t textBlockIndex = 0;
|
||||||
|
for (const PDFTextBlock& textBlock : layout.getTextBlocks())
|
||||||
|
{
|
||||||
|
PDFTextFlow currentFlow;
|
||||||
|
|
||||||
|
size_t textLineIndex = 0;
|
||||||
|
for (const PDFTextLine& textLine : textBlock.getLines())
|
||||||
|
{
|
||||||
|
const TextCharacters& characters = textLine.getCharacters();
|
||||||
|
for (size_t i = 0, characterCount = characters.size(); i < characterCount; ++i)
|
||||||
|
{
|
||||||
|
const TextCharacter& currentCharacter = characters[i];
|
||||||
|
if (i > 0 && !currentCharacter.character.isSpace())
|
||||||
|
{
|
||||||
|
// Jakub Melka: try to guess space between letters
|
||||||
|
const TextCharacter& previousCharacter = characters[i - 1];
|
||||||
|
if (!previousCharacter.character.isSpace() && QLineF(previousCharacter.position, currentCharacter.position).length() > previousCharacter.advance * 1.1)
|
||||||
|
{
|
||||||
|
currentFlow.m_text += QChar(' ');
|
||||||
|
currentFlow.m_characterPointers.emplace_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFlow.m_text += currentCharacter.character;
|
||||||
|
|
||||||
|
PDFCharacterPointer pointer;
|
||||||
|
pointer.pageIndex = pageIndex;
|
||||||
|
pointer.blockIndex = textBlockIndex;
|
||||||
|
pointer.lineIndex = textLineIndex;
|
||||||
|
pointer.characterIndex = i;
|
||||||
|
currentFlow.m_characterPointers.emplace_back(qMove(pointer));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove soft hyphen, if it is enabled
|
||||||
|
if (flags.testFlag(RemoveSoftHyphen) && !characters.empty() && currentFlow.m_text.back() == QChar(QChar::SoftHyphen))
|
||||||
|
{
|
||||||
|
currentFlow.m_text.chop(1);
|
||||||
|
currentFlow.m_characterPointers.pop_back();
|
||||||
|
|
||||||
|
if (!flags.testFlag(AddLineBreaks))
|
||||||
|
{
|
||||||
|
// Do not add single empty space - because soft hypen probably breaks a word
|
||||||
|
++textLineIndex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add line break
|
||||||
|
currentFlow.m_text += lineBreak;
|
||||||
|
currentFlow.m_characterPointers.insert(currentFlow.m_characterPointers.end(), lineBreak.length(), PDFCharacterPointer());
|
||||||
|
|
||||||
|
++textLineIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are producing separate blocks, then make flow for each
|
||||||
|
// text block, otherwise join flows.
|
||||||
|
if (flags.testFlag(SeparateBlocks))
|
||||||
|
{
|
||||||
|
result.emplace_back(qMove(currentFlow));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.back().merge(currentFlow);
|
||||||
|
}
|
||||||
|
|
||||||
|
++textBlockIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFTextSelectionItems PDFTextFlow::getTextSelectionItems(size_t index, size_t length) const
|
||||||
|
{
|
||||||
|
PDFTextSelectionItems items;
|
||||||
|
|
||||||
|
auto it = std::next(m_characterPointers.cbegin(), index);
|
||||||
|
auto itEnd = std::next(m_characterPointers.cbegin(), index + length);
|
||||||
|
s
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PDFTextFlow::getContext(size_t index, size_t length) const
|
||||||
|
{
|
||||||
|
Q_ASSERT(length > 0);
|
||||||
|
|
||||||
|
while (index > 0 && m_characterPointers[index - 1].hasSameLine(m_characterPointers[index]))
|
||||||
|
{
|
||||||
|
--index;
|
||||||
|
++length;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t currentEnd = index + length - 1;
|
||||||
|
size_t last = m_characterPointers.size() - 1;
|
||||||
|
while (currentEnd < last && m_characterPointers[currentEnd].hasSameLine(m_characterPointers[currentEnd + 1]))
|
||||||
|
{
|
||||||
|
++currentEnd;
|
||||||
|
++length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_text.mid(int(index), int(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PDFCharacterPointer::hasSameLine(const PDFCharacterPointer& other) const
|
||||||
|
{
|
||||||
|
return pageIndex == other.pageIndex && blockIndex == other.blockIndex && lineIndex == other.lineIndex;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2019 Jakub Melka
|
// Copyright (C) 2019-2020 Jakub Melka
|
||||||
//
|
//
|
||||||
// This file is part of PdfForQt.
|
// This file is part of PdfForQt.
|
||||||
//
|
//
|
||||||
@ -24,9 +24,11 @@
|
|||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <compare>
|
||||||
|
|
||||||
namespace pdf
|
namespace pdf
|
||||||
{
|
{
|
||||||
|
class PDFTextLayout;
|
||||||
|
|
||||||
struct PDFTextCharacterInfo
|
struct PDFTextCharacterInfo
|
||||||
{
|
{
|
||||||
@ -152,6 +154,100 @@ private:
|
|||||||
|
|
||||||
using PDFTextBlocks = std::vector<PDFTextBlock>;
|
using PDFTextBlocks = std::vector<PDFTextBlock>;
|
||||||
|
|
||||||
|
/// Character pointer points to some character in text layout.
|
||||||
|
/// It also has page index to decide, which page the pointer points to.
|
||||||
|
struct PDFCharacterPointer
|
||||||
|
{
|
||||||
|
auto operator<=>(const PDFCharacterPointer&) const = default;
|
||||||
|
|
||||||
|
/// Returns true, if character pointer is valid and points to the correct location
|
||||||
|
bool isValid() const { return pageIndex > -1; }
|
||||||
|
|
||||||
|
/// Returns true, if character belongs to same line
|
||||||
|
bool hasSameLine(const PDFCharacterPointer& other) const;
|
||||||
|
|
||||||
|
int pageIndex = -1;
|
||||||
|
size_t blockIndex = 0;
|
||||||
|
size_t lineIndex = 0;
|
||||||
|
size_t characterIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
using PDFTextSelectionItem = std::pair<PDFCharacterPointer, PDFCharacterPointer>;
|
||||||
|
using PDFTextSelectionItems = std::vector<PDFTextSelectionItem>;
|
||||||
|
|
||||||
|
/// Text selection, can be used across multiple pages.
|
||||||
|
class PDFTextSelection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit PDFTextSelection(PDFTextSelectionItems&& items);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PDFTextSelectionItems m_items;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PDFFindResult
|
||||||
|
{
|
||||||
|
/// Matched string during search
|
||||||
|
QString matched;
|
||||||
|
|
||||||
|
/// Context (text before and after match)
|
||||||
|
QString context;
|
||||||
|
|
||||||
|
/// Matched selection (can be multiple items, if selection
|
||||||
|
/// is spanned between multiple blocks)
|
||||||
|
PDFTextSelectionItems textSelectionItems;
|
||||||
|
};
|
||||||
|
using PDFFindResults = std::vector<PDFFindResult>;
|
||||||
|
|
||||||
|
class PDFTextFlow;
|
||||||
|
using PDFTextFlows = std::vector<PDFTextFlow>;
|
||||||
|
|
||||||
|
/// This class represents a portion of continuous text on the page. It can
|
||||||
|
/// consists of multiple blocks (which follow reading order).
|
||||||
|
class PDFTextFlow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum FlowFlag
|
||||||
|
{
|
||||||
|
None = 0x0000,
|
||||||
|
SeparateBlocks = 0x0001, ///< Create flow for each block
|
||||||
|
RemoveSoftHyphen = 0x0002, ///< Removes 'soft hyphen' unicode character from end-of-line (character 0x00AD)
|
||||||
|
AddLineBreaks = 0x0004, ///< Add line break characters to the end of line
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(FlowFlags, FlowFlag)
|
||||||
|
|
||||||
|
/// Finds simple text in current text flow. All text occurences are returned.
|
||||||
|
/// \param text Text to be found
|
||||||
|
/// \param caseSensitivity Case sensitivity
|
||||||
|
PDFFindResults find(const QString& text, Qt::CaseSensitivity caseSensitivity) const;
|
||||||
|
|
||||||
|
/// Merge data from \p next flow (i.e. connect two consecutive flows)
|
||||||
|
void merge(const PDFTextFlow& next);
|
||||||
|
|
||||||
|
/// Creates text flows from text layout, according to creation flags.
|
||||||
|
/// \param layout Layout, from which is text flow created
|
||||||
|
/// \param flags Flow creation flags
|
||||||
|
/// \param pageIndex Page index
|
||||||
|
static PDFTextFlows createTextFlows(const PDFTextLayout& layout, FlowFlags flags, PDFInteger pageIndex);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Returns text selection from index and length. Returned text selection can also
|
||||||
|
/// be empty (for example, if only single space character is selected, which has
|
||||||
|
/// no counterpart in real text)
|
||||||
|
/// \param index Index of text selection subrange
|
||||||
|
/// \param length Length of text selection
|
||||||
|
PDFTextSelectionItems getTextSelectionItems(size_t index, size_t length) const;
|
||||||
|
|
||||||
|
/// Returns context for text selection (or empty string, if text selection is empty)
|
||||||
|
/// \param index Index of text selection subrange
|
||||||
|
/// \param length Length of text selection
|
||||||
|
QString getContext(size_t index, size_t length) const;
|
||||||
|
|
||||||
|
QString m_text;
|
||||||
|
std::vector<PDFCharacterPointer> m_characterPointers;
|
||||||
|
};
|
||||||
|
|
||||||
/// Text layout of single page. Can handle various fonts, various angles of lines
|
/// Text layout of single page. Can handle various fonts, various angles of lines
|
||||||
/// and vertically oriented text. It performs the "docstrum" algorithm.
|
/// and vertically oriented text. It performs the "docstrum" algorithm.
|
||||||
class PDFTextLayout
|
class PDFTextLayout
|
||||||
|
@ -35,6 +35,7 @@ LIBS += -lPDFForQtLib
|
|||||||
SOURCES += \
|
SOURCES += \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
pdfaboutdialog.cpp \
|
pdfaboutdialog.cpp \
|
||||||
|
pdfadvancedfindwidget.cpp \
|
||||||
pdfdocumentpropertiesdialog.cpp \
|
pdfdocumentpropertiesdialog.cpp \
|
||||||
pdfsendmail.cpp \
|
pdfsendmail.cpp \
|
||||||
pdfsidebarwidget.cpp \
|
pdfsidebarwidget.cpp \
|
||||||
@ -45,6 +46,7 @@ SOURCES += \
|
|||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
pdfaboutdialog.h \
|
pdfaboutdialog.h \
|
||||||
|
pdfadvancedfindwidget.h \
|
||||||
pdfdocumentpropertiesdialog.h \
|
pdfdocumentpropertiesdialog.h \
|
||||||
pdfsendmail.h \
|
pdfsendmail.h \
|
||||||
pdfsidebarwidget.h \
|
pdfsidebarwidget.h \
|
||||||
@ -55,6 +57,7 @@ HEADERS += \
|
|||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
pdfaboutdialog.ui \
|
pdfaboutdialog.ui \
|
||||||
|
pdfadvancedfindwidget.ui \
|
||||||
pdfdocumentpropertiesdialog.ui \
|
pdfdocumentpropertiesdialog.ui \
|
||||||
pdfsidebarwidget.ui \
|
pdfsidebarwidget.ui \
|
||||||
pdfviewermainwindow.ui \
|
pdfviewermainwindow.ui \
|
||||||
|
126
PdfForQtViewer/pdfadvancedfindwidget.cpp
Normal file
126
PdfForQtViewer/pdfadvancedfindwidget.cpp
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright (C) 2020 Jakub Melka
|
||||||
|
//
|
||||||
|
// This file is part of PdfForQt.
|
||||||
|
//
|
||||||
|
// PdfForQt is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// PdfForQt is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "pdfadvancedfindwidget.h"
|
||||||
|
#include "ui_pdfadvancedfindwidget.h"
|
||||||
|
|
||||||
|
#include "pdfcompiler.h"
|
||||||
|
#include "pdfdocument.h"
|
||||||
|
#include "pdfdrawspacecontroller.h"
|
||||||
|
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
namespace pdfviewer
|
||||||
|
{
|
||||||
|
|
||||||
|
PDFAdvancedFindWidget::PDFAdvancedFindWidget(pdf::PDFDrawWidgetProxy* proxy, QWidget* parent) :
|
||||||
|
QWidget(parent),
|
||||||
|
ui(new Ui::PDFAdvancedFindWidget),
|
||||||
|
m_proxy(proxy),
|
||||||
|
m_document(nullptr)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
connect(ui->regularExpressionsCheckbox, &QCheckBox::clicked, this, &PDFAdvancedFindWidget::updateUI);
|
||||||
|
connect(m_proxy, &pdf::PDFDrawWidgetProxy::textLayoutChanged, this, &PDFAdvancedFindWidget::performSearch);
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
PDFAdvancedFindWidget::~PDFAdvancedFindWidget()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFAdvancedFindWidget::setDocument(const pdf::PDFDocument* document)
|
||||||
|
{
|
||||||
|
if (m_document != document)
|
||||||
|
{
|
||||||
|
m_document = document;
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFAdvancedFindWidget::on_searchButton_clicked()
|
||||||
|
{
|
||||||
|
m_parameters.phrase = ui->searchPhraseEdit->text();
|
||||||
|
m_parameters.isCaseSensitive = ui->caseSensitiveCheckBox->isChecked();
|
||||||
|
m_parameters.isWholeWordsOnly = ui->wholeWordsOnlyCheckBox->isChecked();
|
||||||
|
m_parameters.isRegularExpression = ui->regularExpressionsCheckbox->isChecked();
|
||||||
|
m_parameters.isDotMatchingEverything = ui->dotMatchesEverythingCheckBox->isChecked();
|
||||||
|
m_parameters.isMultiline = ui->multilineMatchingCheckBox->isChecked();
|
||||||
|
m_parameters.isSearchFinished = m_parameters.phrase.isEmpty();
|
||||||
|
|
||||||
|
if (m_parameters.isSearchFinished)
|
||||||
|
{
|
||||||
|
// We have nothing to search for
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate regular expression
|
||||||
|
if (m_parameters.isRegularExpression)
|
||||||
|
{
|
||||||
|
QRegularExpression expression(m_parameters.phrase);
|
||||||
|
if (!expression.isValid())
|
||||||
|
{
|
||||||
|
m_parameters.isSearchFinished = true;
|
||||||
|
const int patternErrorOffset = expression.patternErrorOffset();
|
||||||
|
QMessageBox::critical(this, tr("Search error"), tr("Search phrase regular expression has error '%1' near symbol %2.").arg(expression.errorString()).arg(patternErrorOffset));
|
||||||
|
ui->searchPhraseEdit->setFocus();
|
||||||
|
ui->searchPhraseEdit->setSelection(patternErrorOffset, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdf::PDFAsynchronousTextLayoutCompiler* compiler = m_proxy->getTextLayoutCompiler();
|
||||||
|
if (compiler->isTextLayoutReady())
|
||||||
|
{
|
||||||
|
performSearch();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
compiler->makeTextLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFAdvancedFindWidget::updateUI()
|
||||||
|
{
|
||||||
|
const bool enableUI = m_document && m_document->getCatalog()->getPageCount() > 0;
|
||||||
|
const bool enableRegularExpressionUI = enableUI && ui->regularExpressionsCheckbox->isChecked();
|
||||||
|
ui->searchForGroupBox->setEnabled(enableUI);
|
||||||
|
ui->regularExpressionSettingsGroupBox->setEnabled(enableRegularExpressionUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PDFAdvancedFindWidget::performSearch()
|
||||||
|
{
|
||||||
|
if (m_parameters.isSearchFinished)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_parameters.isSearchFinished = true;
|
||||||
|
|
||||||
|
pdf::PDFAsynchronousTextLayoutCompiler* compiler = m_proxy->getTextLayoutCompiler();
|
||||||
|
if (!compiler->isTextLayoutReady())
|
||||||
|
{
|
||||||
|
// Text layout is not ready yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfviewer
|
76
PdfForQtViewer/pdfadvancedfindwidget.h
Normal file
76
PdfForQtViewer/pdfadvancedfindwidget.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (C) 2020 Jakub Melka
|
||||||
|
//
|
||||||
|
// This file is part of PdfForQt.
|
||||||
|
//
|
||||||
|
// PdfForQt is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// PdfForQt is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef PDFADVANCEDFINDWIDGET_H
|
||||||
|
#define PDFADVANCEDFINDWIDGET_H
|
||||||
|
|
||||||
|
#include "pdfglobal.h"
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class PDFAdvancedFindWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace pdf
|
||||||
|
{
|
||||||
|
class PDFDocument;
|
||||||
|
class PDFDrawWidgetProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace pdfviewer
|
||||||
|
{
|
||||||
|
|
||||||
|
class PDFAdvancedFindWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PDFAdvancedFindWidget(pdf::PDFDrawWidgetProxy* proxy, QWidget* parent = nullptr);
|
||||||
|
virtual ~PDFAdvancedFindWidget() override;
|
||||||
|
|
||||||
|
void setDocument(const pdf::PDFDocument* document);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void on_searchButton_clicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateUI();
|
||||||
|
void performSearch();
|
||||||
|
|
||||||
|
struct SearchParameters
|
||||||
|
{
|
||||||
|
QString phrase;
|
||||||
|
bool isCaseSensitive = false;
|
||||||
|
bool isWholeWordsOnly = false;
|
||||||
|
bool isRegularExpression = false;
|
||||||
|
bool isDotMatchingEverything = false;
|
||||||
|
bool isMultiline = false;
|
||||||
|
bool isSearchFinished = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Ui::PDFAdvancedFindWidget* ui;
|
||||||
|
|
||||||
|
pdf::PDFDrawWidgetProxy* m_proxy;
|
||||||
|
const pdf::PDFDocument* m_document;
|
||||||
|
SearchParameters m_parameters;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfviewer
|
||||||
|
|
||||||
|
#endif // PDFADVANCEDFINDWIDGET_H
|
137
PdfForQtViewer/pdfadvancedfindwidget.ui
Normal file
137
PdfForQtViewer/pdfadvancedfindwidget.ui
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>PDFAdvancedFindWidget</class>
|
||||||
|
<widget class="QWidget" name="PDFAdvancedFindWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>881</width>
|
||||||
|
<height>457</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="searchTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Search for</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="searchTabLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="searchForGroupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Search Settings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="searchSettingsGroupBoxLayout" columnstretch="0,1,0">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="searchLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search for:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1" colspan="2">
|
||||||
|
<widget class="QLineEdit" name="searchPhraseEdit"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="2">
|
||||||
|
<widget class="QPushButton" name="searchButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="caseSensitiveCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Case sensitive</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="wholeWordsOnlyCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Whole words only</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="regularExpressionsCheckbox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use regular expressions</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="regularExpressionSettingsGroupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Regular Expression Settings</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="regularExpressionSettingsGroupBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="dotMatchesEverythingCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Dot matches everything (including newline characters)</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="multilineMatchingCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Multiline matching (enables search using '^' and '$' to mark line beginnings/endings)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="resultsTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Results</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="treeWidget">
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">1</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2019 Jakub Melka
|
// Copyright (C) 2019-2020 Jakub Melka
|
||||||
//
|
//
|
||||||
// This file is part of PdfForQt.
|
// This file is part of PdfForQt.
|
||||||
//
|
//
|
||||||
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "pdfaboutdialog.h"
|
#include "pdfaboutdialog.h"
|
||||||
#include "pdfsidebarwidget.h"
|
#include "pdfsidebarwidget.h"
|
||||||
|
#include "pdfadvancedfindwidget.h"
|
||||||
#include "pdfviewersettingsdialog.h"
|
#include "pdfviewersettingsdialog.h"
|
||||||
#include "pdfdocumentpropertiesdialog.h"
|
#include "pdfdocumentpropertiesdialog.h"
|
||||||
|
|
||||||
@ -65,7 +66,10 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||||||
m_CMSManager(new pdf::PDFCMSManager(this)),
|
m_CMSManager(new pdf::PDFCMSManager(this)),
|
||||||
m_settings(new PDFViewerSettings(this)),
|
m_settings(new PDFViewerSettings(this)),
|
||||||
m_pdfWidget(nullptr),
|
m_pdfWidget(nullptr),
|
||||||
|
m_sidebarWidget(nullptr),
|
||||||
m_sidebarDockWidget(nullptr),
|
m_sidebarDockWidget(nullptr),
|
||||||
|
m_advancedFindWidget(nullptr),
|
||||||
|
m_advancedFindDockWidget(nullptr),
|
||||||
m_optionalContentActivity(nullptr),
|
m_optionalContentActivity(nullptr),
|
||||||
m_pageNumberSpinBox(nullptr),
|
m_pageNumberSpinBox(nullptr),
|
||||||
m_pageNumberLabel(nullptr),
|
m_pageNumberLabel(nullptr),
|
||||||
@ -88,6 +92,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||||||
ui->actionQuit->setShortcut(QKeySequence::Quit);
|
ui->actionQuit->setShortcut(QKeySequence::Quit);
|
||||||
ui->actionZoom_In->setShortcut(QKeySequence::ZoomIn);
|
ui->actionZoom_In->setShortcut(QKeySequence::ZoomIn);
|
||||||
ui->actionZoom_Out->setShortcut(QKeySequence::ZoomOut);
|
ui->actionZoom_Out->setShortcut(QKeySequence::ZoomOut);
|
||||||
|
ui->actionFind->setShortcut(QKeySequence::Find);
|
||||||
|
|
||||||
connect(ui->actionOpen, &QAction::triggered, this, &PDFViewerMainWindow::onActionOpenTriggered);
|
connect(ui->actionOpen, &QAction::triggered, this, &PDFViewerMainWindow::onActionOpenTriggered);
|
||||||
connect(ui->actionClose, &QAction::triggered, this, &PDFViewerMainWindow::onActionCloseTriggered);
|
connect(ui->actionClose, &QAction::triggered, this, &PDFViewerMainWindow::onActionCloseTriggered);
|
||||||
@ -172,6 +177,19 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) :
|
|||||||
m_sidebarDockWidget->hide();
|
m_sidebarDockWidget->hide();
|
||||||
connect(m_sidebarWidget, &PDFSidebarWidget::actionTriggered, this, &PDFViewerMainWindow::onActionTriggered);
|
connect(m_sidebarWidget, &PDFSidebarWidget::actionTriggered, this, &PDFViewerMainWindow::onActionTriggered);
|
||||||
|
|
||||||
|
m_advancedFindWidget = new PDFAdvancedFindWidget(m_pdfWidget->getDrawWidgetProxy(), this);
|
||||||
|
m_advancedFindDockWidget = new QDockWidget(tr("Advanced find"), this);
|
||||||
|
m_advancedFindDockWidget->setObjectName("AdvancedFind");
|
||||||
|
m_advancedFindDockWidget->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
|
||||||
|
m_advancedFindDockWidget->setWidget(m_advancedFindWidget);
|
||||||
|
addDockWidget(Qt::BottomDockWidgetArea, m_advancedFindDockWidget);
|
||||||
|
m_advancedFindDockWidget->hide();
|
||||||
|
QAction* toggleAdvancedFindAction = m_advancedFindDockWidget->toggleViewAction();
|
||||||
|
toggleAdvancedFindAction->setObjectName("actionAdvancedFind");
|
||||||
|
toggleAdvancedFindAction->setText(tr("Advanced Find"));
|
||||||
|
toggleAdvancedFindAction->setShortcut(QKeySequence("Ctrl+Shift+F"));
|
||||||
|
ui->menuEdit->insertAction(nullptr, toggleAdvancedFindAction);
|
||||||
|
|
||||||
ui->actionRenderOptionAntialiasing->setData(pdf::PDFRenderer::Antialiasing);
|
ui->actionRenderOptionAntialiasing->setData(pdf::PDFRenderer::Antialiasing);
|
||||||
ui->actionRenderOptionTextAntialiasing->setData(pdf::PDFRenderer::TextAntialiasing);
|
ui->actionRenderOptionTextAntialiasing->setData(pdf::PDFRenderer::TextAntialiasing);
|
||||||
ui->actionRenderOptionSmoothPictures->setData(pdf::PDFRenderer::SmoothImages);
|
ui->actionRenderOptionSmoothPictures->setData(pdf::PDFRenderer::SmoothImages);
|
||||||
@ -858,6 +876,7 @@ void PDFViewerMainWindow::setDocument(const pdf::PDFDocument* document)
|
|||||||
|
|
||||||
m_pdfWidget->setDocument(document, m_optionalContentActivity);
|
m_pdfWidget->setDocument(document, m_optionalContentActivity);
|
||||||
m_sidebarWidget->setDocument(document, m_optionalContentActivity);
|
m_sidebarWidget->setDocument(document, m_optionalContentActivity);
|
||||||
|
m_advancedFindWidget->setDocument(document);
|
||||||
|
|
||||||
if (m_sidebarWidget->isEmpty())
|
if (m_sidebarWidget->isEmpty())
|
||||||
{
|
{
|
||||||
@ -868,6 +887,11 @@ void PDFViewerMainWindow::setDocument(const pdf::PDFDocument* document)
|
|||||||
m_sidebarDockWidget->show();
|
m_sidebarDockWidget->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!document)
|
||||||
|
{
|
||||||
|
m_advancedFindDockWidget->hide();
|
||||||
|
}
|
||||||
|
|
||||||
updateTitle();
|
updateTitle();
|
||||||
updateUI(true);
|
updateUI(true);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2019 Jakub Melka
|
// Copyright (C) 2019-2020 Jakub Melka
|
||||||
//
|
//
|
||||||
// This file is part of PdfForQt.
|
// This file is part of PdfForQt.
|
||||||
//
|
//
|
||||||
@ -56,6 +56,7 @@ class PDFOptionalContentTreeItemModel;
|
|||||||
namespace pdfviewer
|
namespace pdfviewer
|
||||||
{
|
{
|
||||||
class PDFSidebarWidget;
|
class PDFSidebarWidget;
|
||||||
|
class PDFAdvancedFindWidget;
|
||||||
|
|
||||||
class PDFViewerMainWindow : public QMainWindow
|
class PDFViewerMainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
@ -145,6 +146,8 @@ private:
|
|||||||
QString m_currentFile;
|
QString m_currentFile;
|
||||||
PDFSidebarWidget* m_sidebarWidget;
|
PDFSidebarWidget* m_sidebarWidget;
|
||||||
QDockWidget* m_sidebarDockWidget;
|
QDockWidget* m_sidebarDockWidget;
|
||||||
|
PDFAdvancedFindWidget* m_advancedFindWidget;
|
||||||
|
QDockWidget* m_advancedFindDockWidget;
|
||||||
pdf::PDFOptionalContentActivity* m_optionalContentActivity;
|
pdf::PDFOptionalContentActivity* m_optionalContentActivity;
|
||||||
QSpinBox* m_pageNumberSpinBox;
|
QSpinBox* m_pageNumberSpinBox;
|
||||||
QLabel* m_pageNumberLabel;
|
QLabel* m_pageNumberLabel;
|
||||||
|
@ -96,7 +96,14 @@
|
|||||||
<addaction name="actionShow_Text_Blocks"/>
|
<addaction name="actionShow_Text_Blocks"/>
|
||||||
<addaction name="actionShow_Text_Lines"/>
|
<addaction name="actionShow_Text_Lines"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuEdit">
|
||||||
|
<property name="title">
|
||||||
|
<string>Edit</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionFind"/>
|
||||||
|
</widget>
|
||||||
<addaction name="menuFile"/>
|
<addaction name="menuFile"/>
|
||||||
|
<addaction name="menuEdit"/>
|
||||||
<addaction name="menuView"/>
|
<addaction name="menuView"/>
|
||||||
<addaction name="menuGoTo"/>
|
<addaction name="menuGoTo"/>
|
||||||
<addaction name="menuTools"/>
|
<addaction name="menuTools"/>
|
||||||
@ -340,6 +347,11 @@
|
|||||||
<string>Show Text Lines</string>
|
<string>Show Text Lines</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionFind">
|
||||||
|
<property name="text">
|
||||||
|
<string>Find</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<resources>
|
<resources>
|
||||||
|
Reference in New Issue
Block a user