Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qpdfsearchmodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qpdfdocument_p.h"
5#include "qpdflink.h"
6#include "qpdfsearchmodel.h"
7#include "qpdfsearchmodel_p.h"
8
9#include "third_party/pdfium/public/fpdf_text.h"
10#include "third_party/pdfium/public/fpdfview.h"
11
12#include <QtCore/qelapsedtimer.h>
13#include <QtCore/qloggingcategory.h>
14#include <QtCore/QMetaEnum>
15
17
18Q_LOGGING_CATEGORY(qLcS, "qt.pdf.search")
19
20static const int UpdateTimerInterval = 100;
21static const int ContextChars = 64;
22
58{
59 QMetaEnum rolesMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("Role"));
60 for (int r = Qt::UserRole; r < int(Role::NRoles); ++r) {
61 QByteArray roleName = QByteArray(rolesMetaEnum.valueToKey(r));
62 if (roleName.isEmpty())
63 continue;
64 roleName[0] = QChar::toLower(roleName[0]);
65 m_roleNames.insert(r, roleName);
66 }
67 connect(this, &QAbstractListModel::dataChanged, this, &QPdfSearchModel::countChanged);
68 connect(this, &QAbstractListModel::modelReset, this, &QPdfSearchModel::countChanged);
69 connect(this, &QAbstractListModel::rowsRemoved, this, &QPdfSearchModel::countChanged);
70 connect(this, &QAbstractListModel::rowsInserted, this, &QPdfSearchModel::countChanged);
71}
72
77
81QHash<int, QByteArray> QPdfSearchModel::roleNames() const
82{
83 return m_roleNames;
84}
85
91int QPdfSearchModel::rowCount(const QModelIndex &parent) const
92{
93 Q_D(const QPdfSearchModel);
95 return d->rowCountSoFar;
96}
97
102{
103 Q_D(const QPdfSearchModel);
104 const auto pi = const_cast<QPdfSearchModelPrivate*>(d)->pageAndIndexForResult(index.row());
105 if (pi.page < 0)
106 return QVariant();
107 switch (Role(role)) {
108 case Role::Page:
109 return pi.page;
111 return pi.index;
112 case Role::Location:
113 return d->searchResults[pi.page][pi.index].location();
115 return d->searchResults[pi.page][pi.index].contextBefore();
117 return d->searchResults[pi.page][pi.index].contextAfter();
118 case Role::NRoles:
119 break;
120 }
121 if (role == Qt::DisplayRole) {
122 const QString ret = d->searchResults[pi.page][pi.index].contextBefore() +
123 QLatin1String("<b>") + d->searchString + QLatin1String("</b>") +
124 d->searchResults[pi.page][pi.index].contextAfter();
125 return ret;
126 }
127 return QVariant();
128}
129
136{
137 return rowCount(QModelIndex());
138}
139
141{
142 Q_D(QPdfSearchModel);
143 d->doSearch(page);
144}
145
151{
152 Q_D(const QPdfSearchModel);
153 return d->searchString;
154}
155
157{
158 Q_D(QPdfSearchModel);
159 if (d->searchString == searchString)
160 return;
161
162 d->searchString = searchString;
164 d->clearResults();
167}
168
172QList<QPdfLink> QPdfSearchModel::resultsOnPage(int page) const
173{
174 Q_D(const QPdfSearchModel);
175 const_cast<QPdfSearchModelPrivate *>(d)->doSearch(page);
176 if (d->searchResults.size() <= page)
177 return {};
178 return d->searchResults[page];
179}
180
186{
187 Q_D(const QPdfSearchModel);
188 const auto pi = const_cast<QPdfSearchModelPrivate*>(d)->pageAndIndexForResult(index);
189 if (pi.page < 0 || index < 0)
190 return {};
191 return d->searchResults[pi.page][pi.index];
192}
193
199{
200 Q_D(const QPdfSearchModel);
201 return d->document;
202}
203
205{
206 Q_D(QPdfSearchModel);
207 if (d->document == document)
208 return;
209
210 disconnect(d->documentConnection);
211 d->documentConnection = connect(document, &QPdfDocument::pageCountChanged, this,
212 [this]() { d_func()->clearResults(); });
213
214 d->document = document;
215 d->clearResults();
217}
218
220{
221 Q_D(QPdfSearchModel);
222 if (event->timerId() != d->updateTimerId)
223 return;
224 if (!d->document || d->nextPageToUpdate >= d->document->pageCount()) {
225 if (d->document)
226 qCDebug(qLcS) << "done updating search results on" << d->searchResults.size() << "pages";
227 killTimer(d->updateTimerId);
228 d->updateTimerId = -1;
229 }
230 d->doSearch(d->nextPageToUpdate++);
231}
232
236
250
252{
253 if (page < 0 || page >= pagesSearched.size() || searchString.isEmpty())
254 return false;
255 if (pagesSearched[page])
256 return true;
257 Q_Q(QPdfSearchModel);
258
259 const QPdfMutexLocker lock;
261 timer.start();
262 FPDF_PAGE pdfPage = FPDF_LoadPage(document->d->doc, page);
263 if (!pdfPage) {
264 qWarning() << "failed to load page" << page;
265 return false;
266 }
267 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
268 if (!textPage) {
269 qWarning() << "failed to load text of page" << page;
270 FPDF_ClosePage(pdfPage);
271 return false;
272 }
273 FPDF_SCHHANDLE sh = FPDFText_FindStart(textPage, searchString.utf16(), 0, 0);
274 QList<QPdfLink> newSearchResults;
275 constexpr double CharacterHitTolerance = 6.0;
276 while (FPDFText_FindNext(sh)) {
277 int idx = FPDFText_GetSchResultIndex(sh);
278 int count = FPDFText_GetSchCount(sh);
279 int rectCount = FPDFText_CountRects(textPage, idx, count);
280 QList<QRectF> rects;
281 int startIndex = -1;
282 int endIndex = -1;
283 for (int r = 0; r < rectCount; ++r) {
284 // get bounding box of search result in page coordinates
285 double left, top, right, bottom;
286 FPDFText_GetRect(textPage, r, &left, &top, &right, &bottom);
287 // deal with any internal PDF transforms and
288 // convert to the 1x (pixels = points) 4th-quadrant coordinate system
289 rects << document->d->mapPageToView(pdfPage, left, top, right, bottom);
290 if (r == 0) {
291 startIndex = FPDFText_GetCharIndexAtPos(textPage, left, top,
293 }
294 if (r == rectCount - 1) {
295 endIndex = FPDFText_GetCharIndexAtPos(textPage, right, top,
297 }
298 qCDebug(qLcS) << rects.last() << "char idx" << startIndex << "->" << endIndex
299 << "from page rect" << left << top << right << bottom;
300 }
301 QString contextBefore, contextAfter;
302 if (startIndex >= 0 || endIndex >= 0) {
303 startIndex = qMax(0, startIndex - ContextChars);
304 endIndex += ContextChars;
305 int count = endIndex - startIndex + 1;
306 if (count > 0) {
307 QList<ushort> buf(count + 1);
308 int len = FPDFText_GetText(textPage, startIndex, count, buf.data());
309 Q_ASSERT(len - 1 <= count); // len is number of characters written, including the terminator
311 reinterpret_cast<const char16_t *>(buf.constData()), len - 1);
312 context = context.replace(QLatin1Char('\n'), QStringLiteral("\u23CE"));
313 context = context.remove(QLatin1Char('\r'));
314 // try to find the search string near the middle of the context if possible
316 if (si < 0)
318 if (si < 0)
319 qWarning() << "search string" << searchString << "not found in context" << context;
320 contextBefore = context.mid(0, si);
321 contextAfter = context.mid(si + searchString.size());
322 }
323 }
324 if (!rects.isEmpty())
325 newSearchResults << QPdfLink(page, rects, contextBefore, contextAfter);
326 }
327 FPDFText_FindClose(sh);
328 FPDFText_ClosePage(textPage);
329 FPDF_ClosePage(pdfPage);
330 qCDebug(qLcS) << searchString << "took" << timer.elapsed() << "ms to find"
331 << newSearchResults.size() << "results on page" << page;
332
333 pagesSearched[page] = true;
334 searchResults[page] = newSearchResults;
335 if (newSearchResults.size() > 0) {
336 int rowsBefore = rowsBeforePage(page);
337 qCDebug(qLcS) << "from row" << rowsBefore << "rowCount" << rowCountSoFar << "increasing by" << newSearchResults.size();
338 rowCountSoFar += newSearchResults.size();
339 q->beginInsertRows(QModelIndex(), rowsBefore, rowsBefore + newSearchResults.size() - 1);
340 q->endInsertRows();
341 }
342 return true;
343}
344
346{
348 return {-1, -1};
349 const int pageCount = document->pageCount();
350 int totalSoFar = 0;
351 int previousTotalSoFar = 0;
352 for (int page = 0; page < pageCount; ++page) {
353 if (!pagesSearched[page])
354 doSearch(page);
355 totalSoFar += searchResults[page].size();
356 if (totalSoFar > resultIndex)
357 return {page, resultIndex - previousTotalSoFar};
358 previousTotalSoFar = totalSoFar;
359 }
360 return {-1, -1};
361}
362
364{
365 int ret = 0;
366 for (int i = 0; i < page; ++i)
367 ret += searchResults[i].size();
368 return ret;
369}
370
372
373#include "moc_qpdfsearchmodel.cpp"
void endResetModel()
Completes a model reset operation.
void modelReset(QPrivateSignal)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles=QList< int >())
This signal is emitted whenever the data in an existing item changes.
void beginResetModel()
Begins a model reset operation.
void rowsInserted(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after rows have been inserted into the model.
void rowsRemoved(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after rows have been removed from the model.
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
\inmodule QtCore
Definition qbytearray.h:57
\inmodule QtCore
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
void resize(qsizetype size)
Definition qlist.h:403
void clear()
Definition qlist.h:434
\inmodule QtCore
\inmodule QtCore
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void killTimer(int id)
Kills the timer with timer identifier, id.
Definition qobject.cpp:1912
QPointF mapPageToView(FPDF_PAGE pdfPage, double x, double y) const
The QPdfDocument class loads a PDF document and renders pages from it.
int pageCount
This property holds the number of pages in the loaded document or 0 if no document is loaded.
void pageCountChanged(int pageCount)
PageAndIndex pageAndIndexForResult(int resultIndex)
QList< QList< QPdfLink > > searchResults
The QPdfSearchModel class searches for a string in a PDF document and holds the results.
void documentChanged()
QString searchString
the string to search for
int rowCount(const QModelIndex &parent) const override
\reimp
QPdfLink resultAtIndex(int index) const
Returns a result found by index in the \l document, regardless of the page on which it was found.
void setDocument(QPdfDocument *document)
void updatePage(int page)
QHash< int, QByteArray > roleNames() const override
\reimp
int count
the number of search results found
~QPdfSearchModel() override
Destroys the model.
QPdfDocument * document
the document to search
void timerEvent(QTimerEvent *event) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
void setSearchString(const QString &searchString)
QVariant data(const QModelIndex &index, int role) const override
\reimp
void searchStringChanged()
Role
\value Page The page number where the search result is found (int).
QList< QPdfLink > resultsOnPage(int page) const
Returns the list of all results found on the given page.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
const ushort * utf16() const
Returns the QString as a '\0\'-terminated array of unsigned shorts.
Definition qstring.cpp:6995
static QString fromUtf16(const char16_t *, qsizetype size=-1)
Definition qstring.cpp:6045
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
\inmodule QtCore
Definition qcoreevent.h:366
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
\inmodule QtCore
Definition qvariant.h:65
Combined button and popup list for selecting options.
@ UserRole
@ DisplayRole
@ CaseInsensitive
static void * context
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
return ret
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLboolean r
[2]
GLdouble GLdouble GLdouble GLdouble top
GLenum GLenum GLsizei count
GLdouble GLdouble right
GLint left
GLint GLint bottom
GLenum GLuint GLenum GLsizei const GLchar * buf
struct _cl_event * event
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLenum GLsizei len
static const double CharacterHitTolerance
static const int ContextChars
static QT_BEGIN_NAMESPACE const int UpdateTimerInterval
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
#define emit
#define Q_UNUSED(x)
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
obj metaObject() -> className()
myObject disconnect()
[26]
QByteArray page
[45]
QTimer * timer
[3]
QReadWriteLock lock
[0]
\inmodule QtCore \reentrant
Definition qchar.h:18