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();
253 if (page < 0 || page >= pagesSearched.size() || searchString.isEmpty())
255 if (pagesSearched[page])
257 Q_Q(QPdfSearchModel);
259 const QPdfMutexLocker lock;
262 FPDF_PAGE pdfPage = FPDF_LoadPage(document->d->doc, page);
264 qWarning() <<
"failed to load page" << page;
267 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
269 qWarning() <<
"failed to load text of page" << page;
270 FPDF_ClosePage(pdfPage);
273 FPDF_SCHHANDLE sh = FPDFText_FindStart(textPage, searchString.utf16(), 0, 0);
274 QList<QPdfLink> newSearchResults;
275 constexpr double CharacterHitTolerance = 6.0;
276 while (FPDFText_FindNext(sh)) {
277 int idx = FPDFText_GetSchResultIndex(sh);
278 int count = FPDFText_GetSchCount(sh);
279 int rectCount = FPDFText_CountRects(textPage, idx, count);
283 for (
int r = 0; r < rectCount; ++r) {
285 double left, top, right, bottom;
286 FPDFText_GetRect(textPage, r, &left, &top, &right, &bottom);
289 rects << document->d->mapPageToView(pdfPage, left, top, right, bottom);
291 startIndex = FPDFText_GetCharIndexAtPos(textPage, left, top,
292 CharacterHitTolerance, CharacterHitTolerance);
294 if (r == rectCount - 1) {
295 endIndex = FPDFText_GetCharIndexAtPos(textPage, right, top,
296 CharacterHitTolerance, CharacterHitTolerance);
298 qCDebug(qLcS) << rects.last() <<
"char idx" << startIndex <<
"->" << endIndex
299 <<
"from page rect" << left << top << right << bottom;
301 QString contextBefore, contextAfter;
302 if (startIndex >= 0 || endIndex >= 0) {
305 int count = endIndex - startIndex + 1;
307 QList<ushort> buf(count + 1);
308 int len = FPDFText_GetText(textPage, startIndex, count, buf.data());
309 Q_ASSERT(len - 1 <= count);
310 QString context = QString::fromUtf16(
311 reinterpret_cast<
const char16_t *>(buf.constData()), len - 1);
312 context = context.replace(QLatin1Char(
'\n'), QStringLiteral(
"\u23CE"));
313 context = context.remove(QLatin1Char(
'\r'));
315 int si = context.indexOf(searchString, ContextChars - 5, Qt::CaseInsensitive);
317 si = context.indexOf(searchString, Qt::CaseInsensitive);
319 qCDebug(qLcS) <<
"search string" << searchString <<
"not found in context" << context;
320 contextBefore = context.mid(0, si);
321 contextAfter = context.mid(si + searchString.size());
324 if (!rects.isEmpty())
325 newSearchResults << QPdfLink(page, rects, contextBefore, contextAfter);
327 FPDFText_FindClose(sh);
328 FPDFText_ClosePage(textPage);
329 FPDF_ClosePage(pdfPage);
330 qCDebug(qLcS) << searchString <<
"took" << timer.elapsed() <<
"ms to find"
331 << newSearchResults.size() <<
"results on page" << page;
333 pagesSearched[page] =
true;
334 searchResults[page] = newSearchResults;
335 if (newSearchResults.size() > 0) {
337 qCDebug(qLcS) <<
"from row" << rowsBefore <<
"rowCount" << rowCountSoFar <<
"increasing by" << newSearchResults.size();
339 q->beginInsertRows(QModelIndex(), rowsBefore, rowsBefore + newSearchResults.size() - 1);