Qt 6.x
The Qt SDK
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 "qpdflink_p.h"
7#include "qpdfsearchmodel.h"
8#include "qpdfsearchmodel_p.h"
9
10#include "third_party/pdfium/public/fpdf_doc.h"
11#include "third_party/pdfium/public/fpdf_text.h"
12
13#include <QtCore/qelapsedtimer.h>
14#include <QtCore/qloggingcategory.h>
15#include <QtCore/QMetaEnum>
16
18
19Q_LOGGING_CATEGORY(qLcS, "qt.pdf.search")
20
21static const int UpdateTimerInterval = 100;
22static const int ContextChars = 64;
23
59{
60 QMetaEnum rolesMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("Role"));
61 for (int r = Qt::UserRole; r < int(Role::NRoles); ++r) {
62 QByteArray roleName = QByteArray(rolesMetaEnum.valueToKey(r));
63 if (roleName.isEmpty())
64 continue;
65 roleName[0] = QChar::toLower(roleName[0]);
66 m_roleNames.insert(r, roleName);
67 }
68}
69
74
79{
80 return m_roleNames;
81}
82
89{
90 Q_D(const QPdfSearchModel);
92 return d->rowCountSoFar;
93}
94
99{
100 Q_D(const QPdfSearchModel);
101 const auto pi = const_cast<QPdfSearchModelPrivate*>(d)->pageAndIndexForResult(index.row());
102 if (pi.page < 0)
103 return QVariant();
104 switch (Role(role)) {
105 case Role::Page:
106 return pi.page;
108 return pi.index;
109 case Role::Location:
110 return d->searchResults[pi.page][pi.index].location();
112 return d->searchResults[pi.page][pi.index].contextBefore();
114 return d->searchResults[pi.page][pi.index].contextAfter();
115 case Role::NRoles:
116 break;
117 }
118 if (role == Qt::DisplayRole) {
119 const QString ret = d->searchResults[pi.page][pi.index].contextBefore() +
120 QLatin1String("<b>") + d->searchString + QLatin1String("</b>") +
121 d->searchResults[pi.page][pi.index].contextAfter();
122 return ret;
123 }
124 return QVariant();
125}
126
128{
129 Q_D(QPdfSearchModel);
130 d->doSearch(page);
131}
132
138{
139 Q_D(const QPdfSearchModel);
140 return d->searchString;
141}
142
144{
145 Q_D(QPdfSearchModel);
146 if (d->searchString == searchString)
147 return;
148
149 d->searchString = searchString;
151 d->clearResults();
154}
155
160{
161 Q_D(const QPdfSearchModel);
162 const_cast<QPdfSearchModelPrivate *>(d)->doSearch(page);
163 if (d->searchResults.size() <= page)
164 return {};
165 return d->searchResults[page];
166}
167
173{
174 Q_D(const QPdfSearchModel);
175 const auto pi = const_cast<QPdfSearchModelPrivate*>(d)->pageAndIndexForResult(index);
176 if (pi.page < 0)
177 return {};
178 return d->searchResults[pi.page][pi.index];
179}
180
186{
187 Q_D(const QPdfSearchModel);
188 return d->document;
189}
190
192{
193 Q_D(QPdfSearchModel);
194 if (d->document == document)
195 return;
196
197 disconnect(d->documentConnection);
198 d->documentConnection = connect(document, &QPdfDocument::pageCountChanged, this,
200
201 d->document = document;
202 d->clearResults();
204}
205
207{
209
210 Q_D(QPdfSearchModel);
211 d->clearResults();
212}
213
215{
216 Q_D(QPdfSearchModel);
217 if (event->timerId() != d->updateTimerId)
218 return;
219 if (!d->document || d->nextPageToUpdate >= d->document->pageCount()) {
220 if (d->document)
221 qCDebug(qLcS) << "done updating search results on" << d->searchResults.size() << "pages";
222 killTimer(d->updateTimerId);
223 d->updateTimerId = -1;
224 }
225 d->doSearch(d->nextPageToUpdate++);
226}
227
229{
230}
231
233{
234 Q_Q(QPdfSearchModel);
235 rowCountSoFar = 0;
238 if (document) {
241 }
243 updateTimerId = q->startTimer(UpdateTimerInterval);
244}
245
247{
248 if (page < 0 || page >= pagesSearched.size() || searchString.isEmpty())
249 return false;
250 if (pagesSearched[page])
251 return true;
252 Q_Q(QPdfSearchModel);
253
254 const QPdfMutexLocker lock;
256 timer.start();
257 FPDF_PAGE pdfPage = FPDF_LoadPage(document->d->doc, page);
258 if (!pdfPage) {
259 qWarning() << "failed to load page" << page;
260 return false;
261 }
262 double pageHeight = FPDF_GetPageHeight(pdfPage);
263 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
264 if (!textPage) {
265 qWarning() << "failed to load text of page" << page;
266 FPDF_ClosePage(pdfPage);
267 return false;
268 }
269 FPDF_SCHHANDLE sh = FPDFText_FindStart(textPage, searchString.utf16(), 0, 0);
270 QList<QPdfLink> newSearchResults;
271 constexpr double CharacterHitTolerance = 6.0;
272 while (FPDFText_FindNext(sh)) {
273 int idx = FPDFText_GetSchResultIndex(sh);
274 int count = FPDFText_GetSchCount(sh);
275 int rectCount = FPDFText_CountRects(textPage, idx, count);
276 QList<QRectF> rects;
277 int startIndex = -1;
278 int endIndex = -1;
279 for (int r = 0; r < rectCount; ++r) {
280 double left, top, right, bottom;
281 FPDFText_GetRect(textPage, r, &left, &top, &right, &bottom);
282 rects << QRectF(left, pageHeight - top, right - left, top - bottom);
283 if (r == 0) {
284 startIndex = FPDFText_GetCharIndexAtPos(textPage, left, top,
286 }
287 if (r == rectCount - 1) {
288 endIndex = FPDFText_GetCharIndexAtPos(textPage, right, top,
290 }
291 qCDebug(qLcS) << rects.last() << "char idx" << startIndex << "->" << endIndex;
292 }
293 QString contextBefore, contextAfter;
294 if (startIndex >= 0 || endIndex >= 0) {
295 startIndex = qMax(0, startIndex - ContextChars);
296 endIndex += ContextChars;
297 int count = endIndex - startIndex + 1;
298 if (count > 0) {
300 int len = FPDFText_GetText(textPage, startIndex, count, buf.data());
301 Q_ASSERT(len - 1 <= count); // len is number of characters written, including the terminator
303 reinterpret_cast<const char16_t *>(buf.constData()), len - 1);
304 context = context.replace(QLatin1Char('\n'), QStringLiteral("\u23CE"));
305 context = context.remove(QLatin1Char('\r'));
306 // try to find the search string near the middle of the context if possible
308 if (si < 0)
310 if (si < 0)
311 qWarning() << "search string" << searchString << "not found in context" << context;
312 contextBefore = context.mid(0, si);
313 contextAfter = context.mid(si + searchString.size());
314 }
315 }
316 if (!rects.isEmpty())
317 newSearchResults << QPdfLink(page, rects, contextBefore, contextAfter);
318 }
319 FPDFText_FindClose(sh);
320 FPDFText_ClosePage(textPage);
321 FPDF_ClosePage(pdfPage);
322 qCDebug(qLcS) << searchString << "took" << timer.elapsed() << "ms to find"
323 << newSearchResults.size() << "results on page" << page;
324
325 pagesSearched[page] = true;
326 searchResults[page] = newSearchResults;
327 if (newSearchResults.size() > 0) {
328 int rowsBefore = rowsBeforePage(page);
329 qCDebug(qLcS) << "from row" << rowsBefore << "rowCount" << rowCountSoFar << "increasing by" << newSearchResults.size();
330 rowCountSoFar += newSearchResults.size();
331 q->beginInsertRows(QModelIndex(), rowsBefore, rowsBefore + newSearchResults.size() - 1);
332 q->endInsertRows();
333 }
334 return true;
335}
336
338{
340 return {-1, -1};
341 const int pageCount = document->pageCount();
342 int totalSoFar = 0;
343 int previousTotalSoFar = 0;
344 for (int page = 0; page < pageCount; ++page) {
345 if (!pagesSearched[page])
346 doSearch(page);
347 totalSoFar += searchResults[page].size();
348 if (totalSoFar > resultIndex)
349 return {page, resultIndex - previousTotalSoFar};
350 previousTotalSoFar = totalSoFar;
351 }
352 return {-1, -1};
353}
354
356{
357 int ret = 0;
358 for (int i = 0; i < page; ++i)
359 ret += searchResults[i].size();
360 return ret;
361}
362
364
365#include "moc_qpdfsearchmodel.cpp"
void endResetModel()
Completes a model reset operation.
void beginResetModel()
Begins a model reset operation.
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:311
\inmodule QtCore
Definition qbytearray.h:57
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
QChar toLower() const noexcept
Returns the lowercase equivalent if the character is uppercase or titlecase; otherwise returns the ch...
Definition qchar.h:448
\inmodule QtCore
\inmodule QtCore
Definition qhash.h:818
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
T & last()
Definition qlist.h:631
void resize(qsizetype size)
Definition qlist.h:392
void clear()
Definition qlist.h:417
\inmodule QtCore
const char * valueToKey(int value) const
Returns the string that is used as the name of the given enumeration value, or \nullptr if value is n...
\inmodule QtCore
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
void killTimer(int id)
Kills the timer with timer identifier, id.
Definition qobject.cpp:1872
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 onDocumentPageCountChanged(int count)
void setDocument(QPdfDocument *document)
void updatePage(int page)
QHash< int, QByteArray > roleNames() const override
\reimp
~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.
\inmodule QtCore\reentrant
Definition qrect.h:483
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
const ushort * utf16() const
Returns the QString as a '\0\'-terminated array of unsigned shorts.
Definition qstring.cpp:6737
static QString fromUtf16(const char16_t *, qsizetype size=-1)
Definition qstring.cpp:5883
qsizetype size() const
Returns the number of characters in this string.
Definition qstring.h:182
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
\inmodule QtCore
Definition qcoreevent.h:359
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:208
\inmodule QtCore
Definition qvariant.h:64
double pi
[0]
Combined button and popup list for selecting options.
@ UserRole
@ DisplayRole
@ CaseInsensitive
static void * context
#define qWarning
Definition qlogging.h:162
#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
GLenum GLsizei len
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
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)
obj metaObject() -> className()
myObject disconnect()
[26]
QByteArray page
[45]
QTimer * timer
[3]
QReadWriteLock lock
[0]
\inmodule QtCore \reentrant
Definition qchar.h:17
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent