58QPdfSearchModel::QPdfSearchModel(QObject *parent)
59 : QAbstractListModel(*(
new QPdfSearchModelPrivate()), parent)
61 const QMetaEnum rolesMetaEnum = QMetaEnum::fromType<Role>();
62 for (
int r = Qt::UserRole; r <
int(Role::NRoles); ++r) {
63 QByteArray roleName = QByteArray(rolesMetaEnum.valueToKey(r));
64 if (roleName.isEmpty())
66 roleName[0] = QChar::toLower(roleName[0]);
67 m_roleNames.insert(r, roleName);
69 connect(
this, &QAbstractListModel::dataChanged,
this, &QPdfSearchModel::countChanged);
70 connect(
this, &QAbstractListModel::modelReset,
this, &QPdfSearchModel::countChanged);
71 connect(
this, &QAbstractListModel::rowsRemoved,
this, &QPdfSearchModel::countChanged);
72 connect(
this, &QAbstractListModel::rowsInserted,
this, &QPdfSearchModel::countChanged);
103QVariant QPdfSearchModel::data(
const QModelIndex &index,
int role)
const
105 Q_D(
const QPdfSearchModel);
106 const auto pi =
const_cast<QPdfSearchModelPrivate*>(d)->pageAndIndexForResult(index.row());
109 switch (Role(role)) {
112 case Role::IndexOnPage:
115 return d->searchResults[pi.page][pi.index].location();
116 case Role::ContextBefore:
117 return d->searchResults[pi.page][pi.index].contextBefore();
118 case Role::ContextAfter:
119 return d->searchResults[pi.page][pi.index].contextAfter();
123 if (role == Qt::DisplayRole) {
124 const QPdfLink &result = d->searchResults[pi.page][pi.index];
127 const QString found = d->matchedText(pi);
128 const QString ret = result.contextBefore() + QLatin1String(
"<b>")
129 + (found.isEmpty() ? d->searchString : found) + QLatin1String(
"</b>")
130 + result.contextAfter();
251void QPdfSearchModel::timerEvent(QTimerEvent *event)
253 Q_D(QPdfSearchModel);
254 if (event->id() != d->updateTimerId)
256 if (!d->document || d->nextPageToUpdate >= d->document->pageCount()) {
258 qCDebug(qLcS) <<
"done updating search results on" << d->searchResults.size() <<
"pages";
259 killTimer(d->updateTimerId);
260 d->updateTimerId = Qt::TimerId::Invalid;
261 d->setStatus(QPdfSearchModel::Status::Finished);
262 }
else if (!d->searchString.isEmpty()) {
263 d->setStatus(QPdfSearchModel::Status::Searching);
265 d->doSearch(d->nextPageToUpdate++);
299 if (page < 0 || page >= pagesSearched.size() || searchString.isEmpty())
301 if (pagesSearched[page])
303 Q_Q(QPdfSearchModel);
305 const QPdfMutexLocker lock;
308 FPDF_PAGE pdfPage = FPDF_LoadPage(document->d->doc, page);
310 qWarning() <<
"failed to load page" << page;
313 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
315 qWarning() <<
"failed to load text of page" << page;
316 FPDF_ClosePage(pdfPage);
319 FPDF_SCHHANDLE sh = FPDFText_FindStart(textPage, searchString.utf16(), 0, 0);
320 QList<QPdfLink> newSearchResults;
321 while (FPDFText_FindNext(sh)) {
324 const int startIndex = FPDFText_GetSchResultIndex(sh);
325 const int matchLength = FPDFText_GetSchCount(sh);
326 const int endIndex = startIndex + matchLength - 1;
327 const int rectCount = FPDFText_CountRects(textPage, startIndex, matchLength);
329 for (
int r = 0; r < rectCount; ++r) {
331 double left, top, right, bottom;
332 FPDFText_GetRect(textPage, r, &left, &top, &right, &bottom);
335 rects << document->d->mapPageToView(pdfPage, left, top, right, bottom);
336 qCDebug(qLcS) << rects.last() <<
"char idx" << startIndex <<
"->" << endIndex
337 <<
"from page rect" << left << top << right << bottom;
339 QString matchedText, contextBefore, contextAfter;
340 if (startIndex >= 0) {
343 int count = contextEnd - contextStart + 1;
345 QList<ushort> buf(count + 1);
346 int len = FPDFText_GetText(textPage, contextStart, count, buf.data());
347 Q_ASSERT(len - 1 <= count);
348 QString context = QString::fromUtf16(
349 reinterpret_cast<
const char16_t *>(buf.constData()), len - 1);
351 const auto replaceNewlines = [&](QString &s) {
352 s.replace(QLatin1Char(
'\n'), QStringLiteral(
"\u23CE"));
353 s.remove(QLatin1Char(
'\r'));
356 matchedText = context.mid(startIndex - contextStart, matchLength);
357 contextBefore = context.mid(0, startIndex - contextStart);
358 contextAfter = context.mid(startIndex - contextStart + matchLength);
359 replaceNewlines(matchedText);
360 replaceNewlines(contextBefore);
361 replaceNewlines(contextAfter);
364 if (!rects.isEmpty())
365 newSearchResults << QPdfLink(page, rects, matchedText, contextBefore, contextAfter);
367 FPDFText_FindClose(sh);
368 FPDFText_ClosePage(textPage);
369 FPDF_ClosePage(pdfPage);
370 qCDebug(qLcS) << searchString <<
"took" << timer.elapsed() <<
"ms to find"
371 << newSearchResults.size() <<
"results on page" << page;
373 pagesSearched[page] =
true;
374 searchResults[page] = newSearchResults;
375 if (newSearchResults.size() > 0) {
377 qCDebug(qLcS) <<
"from row" << rowsBefore <<
"rowCount" <<
rowCountSoFar <<
"increasing by" << newSearchResults.size();
379 q->beginInsertRows(QModelIndex(), rowsBefore, rowsBefore + newSearchResults.size() - 1);