56QPdfSearchModel::QPdfSearchModel(QObject *parent)
57 : QAbstractListModel(*(
new QPdfSearchModelPrivate()), parent)
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())
64 roleName[0] = QChar::toLower(roleName[0]);
65 m_roleNames.insert(r, roleName);
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);
101QVariant QPdfSearchModel::data(
const QModelIndex &index,
int role)
const
103 Q_D(
const QPdfSearchModel);
104 const auto pi =
const_cast<QPdfSearchModelPrivate*>(d)->pageAndIndexForResult(index.row());
107 switch (Role(role)) {
110 case Role::IndexOnPage:
113 return d->searchResults[pi.page][pi.index].location();
114 case Role::ContextBefore:
115 return d->searchResults[pi.page][pi.index].contextBefore();
116 case Role::ContextAfter:
117 return d->searchResults[pi.page][pi.index].contextAfter();
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();
247void QPdfSearchModel::timerEvent(QTimerEvent *event)
249 Q_D(QPdfSearchModel);
250 if (event->timerId() != d->updateTimerId)
252 if (!d->document || d->nextPageToUpdate >= d->document->pageCount()) {
254 qCDebug(qLcS) <<
"done updating search results on" << d->searchResults.size() <<
"pages";
255 killTimer(d->updateTimerId);
256 d->updateTimerId = -1;
257 d->setStatus(QPdfSearchModel::Status::Finished);
258 }
else if (!d->searchString.isEmpty()) {
259 d->setStatus(QPdfSearchModel::Status::Searching);
261 d->doSearch(d->nextPageToUpdate++);
284 if (page < 0 || page >= pagesSearched.size() || searchString.isEmpty())
286 if (pagesSearched[page])
288 Q_Q(QPdfSearchModel);
290 const QPdfMutexLocker lock;
293 FPDF_PAGE pdfPage = FPDF_LoadPage(document->d->doc, page);
295 qWarning() <<
"failed to load page" << page;
298 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
300 qWarning() <<
"failed to load text of page" << page;
301 FPDF_ClosePage(pdfPage);
304 FPDF_SCHHANDLE sh = FPDFText_FindStart(textPage, searchString.utf16(), 0, 0);
305 QList<QPdfLink> newSearchResults;
306 constexpr double CharacterHitTolerance = 6.0;
307 while (FPDFText_FindNext(sh)) {
308 int idx = FPDFText_GetSchResultIndex(sh);
309 int count = FPDFText_GetSchCount(sh);
310 int rectCount = FPDFText_CountRects(textPage, idx, count);
314 for (
int r = 0; r < rectCount; ++r) {
316 double left, top, right, bottom;
317 FPDFText_GetRect(textPage, r, &left, &top, &right, &bottom);
320 rects << document->d->mapPageToView(pdfPage, left, top, right, bottom);
322 startIndex = FPDFText_GetCharIndexAtPos(textPage, left, top,
323 CharacterHitTolerance, CharacterHitTolerance);
325 if (r == rectCount - 1) {
326 endIndex = FPDFText_GetCharIndexAtPos(textPage, right, top,
327 CharacterHitTolerance, CharacterHitTolerance);
329 qCDebug(qLcS) << rects.last() <<
"char idx" << startIndex <<
"->" << endIndex
330 <<
"from page rect" << left << top << right << bottom;
332 QString contextBefore, contextAfter;
333 if (startIndex >= 0 || endIndex >= 0) {
336 int count = endIndex - startIndex + 1;
338 QList<ushort> buf(count + 1);
339 int len = FPDFText_GetText(textPage, startIndex, count, buf.data());
340 Q_ASSERT(len - 1 <= count);
341 QString context = QString::fromUtf16(
342 reinterpret_cast<
const char16_t *>(buf.constData()), len - 1);
343 context = context.replace(QLatin1Char(
'\n'), QStringLiteral(
"\u23CE"));
344 context = context.remove(QLatin1Char(
'\r'));
346 int si = context.indexOf(searchString,
ContextChars - 5, Qt::CaseInsensitive);
348 si = context.indexOf(searchString, Qt::CaseInsensitive);
350 qCDebug(qLcS) <<
"search string" << searchString <<
"not found in context" << context;
351 contextBefore = context.mid(0, si);
352 contextAfter = context.mid(si + searchString.size());
355 if (!rects.isEmpty())
356 newSearchResults << QPdfLink(page, rects, contextBefore, contextAfter);
358 FPDFText_FindClose(sh);
359 FPDFText_ClosePage(textPage);
360 FPDF_ClosePage(pdfPage);
361 qCDebug(qLcS) << searchString <<
"took" << timer.elapsed() <<
"ms to find"
362 << newSearchResults.size() <<
"results on page" << page;
364 pagesSearched[page] =
true;
365 searchResults[page] = newSearchResults;
366 if (newSearchResults.size() > 0) {
368 qCDebug(qLcS) <<
"from row" << rowsBefore <<
"rowCount" <<
rowCountSoFar <<
"increasing by" << newSearchResults.size();
370 q->beginInsertRows(QModelIndex(), rowsBefore, rowsBefore + newSearchResults.size() - 1);