57QPdfSearchModel::QPdfSearchModel(QObject *parent)
58 : QAbstractListModel(*(
new QPdfSearchModelPrivate()), parent)
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())
65 roleName[0] = QChar::toLower(roleName[0]);
66 m_roleNames.insert(r, roleName);
68 connect(
this, &QAbstractListModel::dataChanged,
this, &QPdfSearchModel::countChanged);
69 connect(
this, &QAbstractListModel::modelReset,
this, &QPdfSearchModel::countChanged);
70 connect(
this, &QAbstractListModel::rowsRemoved,
this, &QPdfSearchModel::countChanged);
71 connect(
this, &QAbstractListModel::rowsInserted,
this, &QPdfSearchModel::countChanged);
102QVariant QPdfSearchModel::data(
const QModelIndex &index,
int role)
const
104 Q_D(
const QPdfSearchModel);
105 const auto pi =
const_cast<QPdfSearchModelPrivate*>(d)->pageAndIndexForResult(index.row());
108 switch (Role(role)) {
111 case Role::IndexOnPage:
114 return d->searchResults[pi.page][pi.index].location();
115 case Role::ContextBefore:
116 return d->searchResults[pi.page][pi.index].contextBefore();
117 case Role::ContextAfter:
118 return d->searchResults[pi.page][pi.index].contextAfter();
122 if (role == Qt::DisplayRole) {
123 const QString ret = d->searchResults[pi.page][pi.index].contextBefore() +
124 QLatin1String(
"<b>") + d->searchString + QLatin1String(
"</b>") +
125 d->searchResults[pi.page][pi.index].contextAfter();
248void QPdfSearchModel::timerEvent(QTimerEvent *event)
250 Q_D(QPdfSearchModel);
251 if (event->timerId() != d->updateTimerId)
253 if (!d->document || d->nextPageToUpdate >= d->document->pageCount()) {
255 qCDebug(qLcS) <<
"done updating search results on" << d->searchResults.size() <<
"pages";
256 killTimer(d->updateTimerId);
257 d->updateTimerId = -1;
258 d->setStatus(QPdfSearchModel::Status::Finished);
259 }
else if (!d->searchString.isEmpty()) {
260 d->setStatus(QPdfSearchModel::Status::Searching);
262 d->doSearch(d->nextPageToUpdate++);
285 if (page < 0 || page >= pagesSearched.size() || searchString.isEmpty())
287 if (pagesSearched[page])
289 Q_Q(QPdfSearchModel);
291 const QPdfMutexLocker lock;
294 FPDF_PAGE pdfPage = FPDF_LoadPage(document->d->doc, page);
296 qWarning() <<
"failed to load page" << page;
299 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
301 qWarning() <<
"failed to load text of page" << page;
302 FPDF_ClosePage(pdfPage);
305 FPDF_SCHHANDLE sh = FPDFText_FindStart(textPage, searchString.utf16(), 0, 0);
306 QList<QPdfLink> newSearchResults;
307 constexpr double CharacterHitTolerance = 6.0;
308 while (FPDFText_FindNext(sh)) {
309 int idx = FPDFText_GetSchResultIndex(sh);
310 int count = FPDFText_GetSchCount(sh);
311 int rectCount = FPDFText_CountRects(textPage, idx, count);
315 for (
int r = 0; r < rectCount; ++r) {
317 double left, top, right, bottom;
318 FPDFText_GetRect(textPage, r, &left, &top, &right, &bottom);
321 rects << document->d->mapPageToView(pdfPage, left, top, right, bottom);
323 startIndex = FPDFText_GetCharIndexAtPos(textPage, left, top,
324 CharacterHitTolerance, CharacterHitTolerance);
326 if (r == rectCount - 1) {
327 endIndex = FPDFText_GetCharIndexAtPos(textPage, right, top,
328 CharacterHitTolerance, CharacterHitTolerance);
330 qCDebug(qLcS) << rects.last() <<
"char idx" << startIndex <<
"->" << endIndex
331 <<
"from page rect" << left << top << right << bottom;
333 QString contextBefore, contextAfter;
334 if (startIndex >= 0 || endIndex >= 0) {
337 int count = endIndex - startIndex + 1;
339 QList<ushort> buf(count + 1);
340 int len = FPDFText_GetText(textPage, startIndex, count, buf.data());
341 Q_ASSERT(len - 1 <= count);
342 QString context = QString::fromUtf16(
343 reinterpret_cast<
const char16_t *>(buf.constData()), len - 1);
344 context = context.replace(QLatin1Char(
'\n'), QStringLiteral(
"\u23CE"));
345 context = context.remove(QLatin1Char(
'\r'));
347 int si = context.indexOf(searchString,
ContextChars - 5, Qt::CaseInsensitive);
349 si = context.indexOf(searchString, Qt::CaseInsensitive);
351 qCDebug(qLcS) <<
"search string" << searchString <<
"not found in context" << context;
352 contextBefore = context.mid(0, si);
353 contextAfter = context.mid(si + searchString.size());
356 if (!rects.isEmpty())
357 newSearchResults << QPdfLink(page, rects, contextBefore, contextAfter);
359 FPDFText_FindClose(sh);
360 FPDFText_ClosePage(textPage);
361 FPDF_ClosePage(pdfPage);
362 qCDebug(qLcS) << searchString <<
"took" << timer.elapsed() <<
"ms to find"
363 << newSearchResults.size() <<
"results on page" << page;
365 pagesSearched[page] =
true;
366 searchResults[page] = newSearchResults;
367 if (newSearchResults.size() > 0) {
369 qCDebug(qLcS) <<
"from row" << rowsBefore <<
"rowCount" <<
rowCountSoFar <<
"increasing by" << newSearchResults.size();
371 q->beginInsertRows(QModelIndex(), rowsBefore, rowsBefore + newSearchResults.size() - 1);