57QPdfSearchModel::QPdfSearchModel(QObject *parent)
58 : QAbstractListModel(*(
new QPdfSearchModelPrivate()), parent)
60 const QMetaEnum rolesMetaEnum = QMetaEnum::fromType<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->id() != 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 = Qt::TimerId::Invalid;
258 d->setStatus(QPdfSearchModel::Status::Finished);
259 }
else if (!d->searchString.isEmpty()) {
260 d->setStatus(QPdfSearchModel::Status::Searching);
262 d->doSearch(d->nextPageToUpdate++);
291 if (page < 0 || page >= pagesSearched.size() || searchString.isEmpty())
293 if (pagesSearched[page])
295 Q_Q(QPdfSearchModel);
297 const QPdfMutexLocker lock;
300 FPDF_PAGE pdfPage = FPDF_LoadPage(document->d->doc, page);
302 qWarning() <<
"failed to load page" << page;
305 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
307 qWarning() <<
"failed to load text of page" << page;
308 FPDF_ClosePage(pdfPage);
311 FPDF_SCHHANDLE sh = FPDFText_FindStart(textPage, searchString.utf16(), 0, 0);
312 QList<QPdfLink> newSearchResults;
313 constexpr double CharacterHitTolerance = 6.0;
314 while (FPDFText_FindNext(sh)) {
315 int idx = FPDFText_GetSchResultIndex(sh);
316 int count = FPDFText_GetSchCount(sh);
317 int rectCount = FPDFText_CountRects(textPage, idx, count);
321 for (
int r = 0; r < rectCount; ++r) {
323 double left, top, right, bottom;
324 FPDFText_GetRect(textPage, r, &left, &top, &right, &bottom);
327 rects << document->d->mapPageToView(pdfPage, left, top, right, bottom);
329 startIndex = FPDFText_GetCharIndexAtPos(textPage, left, top,
330 CharacterHitTolerance, CharacterHitTolerance);
332 if (r == rectCount - 1) {
333 endIndex = FPDFText_GetCharIndexAtPos(textPage, right, top,
334 CharacterHitTolerance, CharacterHitTolerance);
336 qCDebug(qLcS) << rects.last() <<
"char idx" << startIndex <<
"->" << endIndex
337 <<
"from page rect" << left << top << right << bottom;
339 QString contextBefore, contextAfter;
340 if (startIndex >= 0 || endIndex >= 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 contextBefore = context.mid(0, startIndex - contextStart);
357 contextAfter = context.mid(startIndex - contextStart + searchString.size());
358 replaceNewlines(contextBefore);
359 replaceNewlines(contextAfter);
362 if (!rects.isEmpty())
363 newSearchResults << QPdfLink(page, rects, contextBefore, contextAfter);
365 FPDFText_FindClose(sh);
366 FPDFText_ClosePage(textPage);
367 FPDF_ClosePage(pdfPage);
368 qCDebug(qLcS) << searchString <<
"took" << timer.elapsed() <<
"ms to find"
369 << newSearchResults.size() <<
"results on page" << page;
371 pagesSearched[page] =
true;
372 searchResults[page] = newSearchResults;
373 if (newSearchResults.size() > 0) {
375 qCDebug(qLcS) <<
"from row" << rowsBefore <<
"rowCount" <<
rowCountSoFar <<
"increasing by" << newSearchResults.size();
377 q->beginInsertRows(QModelIndex(), rowsBefore, rowsBefore + newSearchResults.size() - 1);