40 QPdfPageModel(QPdfDocument *doc) : QAbstractListModel(doc)
42 m_roleNames = QAbstractItemModel::roleNames();
43 QMetaEnum rolesMetaEnum = doc->metaObject()->enumerator(doc->metaObject()->indexOfEnumerator(
"PageModelRole"));
44 for (
int r = Qt::UserRole; r <
int(QPdfDocument::PageModelRole::NRoles); ++r) {
45 auto name = QByteArray(rolesMetaEnum.valueToKey(r));
46 name[0] = QtMiscUtils::toAsciiLower(name[0]);
47 m_roleNames.insert(r, name);
49 connect(doc, &QPdfDocument::statusChanged,
this, [
this](QPdfDocument::Status s) {
50 if (s == QPdfDocument::Status::Loading)
52 else if (s == QPdfDocument::Status::Ready)
57 QVariant data(
const QModelIndex &index,
int role)
const override
62 switch (QPdfDocument::PageModelRole(role)) {
63 case QPdfDocument::PageModelRole::Label:
64 return document()->pageLabel(index.row());
65 case QPdfDocument::PageModelRole::PointSize:
66 return document()->pagePointSize(index.row());
67 case QPdfDocument::PageModelRole::NRoles:
72 case Qt::DecorationRole:
73 return pageThumbnail(index.row());
75 return document()->pageLabel(index.row());
81 int rowCount(
const QModelIndex & = QModelIndex())
const override {
return document()->pageCount(); }
83 QHash<
int, QByteArray> roleNames()
const override {
return m_roleNames; }
86 QPdfDocument *document()
const {
return static_cast<QPdfDocument *>(parent()); }
87 QPixmap pageThumbnail(
int page)
const
89 auto it = m_thumbnails.constFind(page);
90 if (it == m_thumbnails.constEnd()) {
91 auto doc = document();
92 auto size = doc->pagePointSize(page);
93 size.scale(128, 128, Qt::KeepAspectRatio);
95 auto image = document()->render(page, size.toSize());
96 QPixmap ret = QPixmap::fromImage(image);
97 m_thumbnails.insert(page, ret);
103 QHash<
int, QByteArray> m_roleNames;
104 mutable QHash<
int, QPixmap> m_thumbnails;
107QPdfDocumentPrivate::QPdfDocumentPrivate()
110 , loadComplete(
false)
111 , status(QPdfDocument::Status::Null)
112 , lastError(QPdfDocument::Error::None)
115 asyncBuffer.setData(QByteArray());
116 asyncBuffer.open(QIODevice::ReadWrite);
118 const QPdfMutexLocker lock;
120 if (libraryRefCount == 0) {
124 qCDebug(qLcDoc) <<
"FPDF_InitLibrary took" << timer.elapsed() <<
"ms";
130 m_GetBlock = fpdf_GetBlock;
133 FX_FILEAVAIL::version = 1;
134 IsDataAvail = fpdf_IsDataAvail;
137 FX_DOWNLOADHINTS::version = 1;
138 AddSegment = fpdf_AddSegment;
182void QPdfDocumentPrivate::updateLastError()
185 lastError = QPdfDocument::Error::None;
189 QPdfMutexLocker lock;
190 const unsigned long error = FPDF_GetLastError();
194 case FPDF_ERR_SUCCESS: lastError = QPdfDocument::Error::None;
break;
195 case FPDF_ERR_UNKNOWN: lastError = QPdfDocument::Error::Unknown;
break;
196 case FPDF_ERR_FILE: lastError = QPdfDocument::Error::FileNotFound;
break;
197 case FPDF_ERR_FORMAT: lastError = QPdfDocument::Error::InvalidFileFormat;
break;
198 case FPDF_ERR_PASSWORD: lastError = QPdfDocument::Error::IncorrectPassword;
break;
199 case FPDF_ERR_SECURITY: lastError = QPdfDocument::Error::UnsupportedSecurityScheme;
break;
203 if (lastError != QPdfDocument::Error::None)
204 qCDebug(qLcDoc) <<
"FPDF error" << error <<
"->" << lastError;
207void QPdfDocumentPrivate::load(QIODevice *newDevice,
bool transferDeviceOwnership)
209 if (transferDeviceOwnership)
210 ownDevice.reset(newDevice);
214 if (newDevice->isSequential()) {
215 sequentialSourceDevice = newDevice;
216 device = &asyncBuffer;
217 QNetworkReply *reply = qobject_cast<QNetworkReply*>(sequentialSourceDevice);
220 setStatus(QPdfDocument::Status::Error);
221 qWarning() <<
"QPdfDocument: Loading from sequential devices only supported with QNetworkAccessManager.";
225 if (reply->isFinished() && reply->error() != QNetworkReply::NoError) {
226 setStatus(QPdfDocument::Status::Error);
230 QObject::connect(reply, &QNetworkReply::finished, q, [
this, reply](){
231 if (reply->error() != QNetworkReply::NoError || reply->bytesAvailable() == 0) {
232 this->setStatus(QPdfDocument::Status::Error);
236 if (reply->header(QNetworkRequest::ContentLengthHeader).isValid())
237 _q_tryLoadingWithSizeFromContentHeader();
239 QObject::connect(reply, SIGNAL(metaDataChanged()), q, SLOT(_q_tryLoadingWithSizeFromContentHeader()));
242 initiateAsyncLoadWithTotalSizeKnown(device->size());
244 setStatus(QPdfDocument::Status::Error);
253 setStatus(QPdfDocument::Status::Error);
257 QPdfMutexLocker lock;
258 const int newPageCount = FPDF_GetPageCount(doc);
260 if (newPageCount != pageCount) {
261 pageCount = newPageCount;
262 emit q->pageCountChanged(pageCount);
263 emit q->pageModelChanged();
268 if (checkPageComplete(0) && (pageCount < 2 || checkPageComplete(1))) {
269 setStatus(QPdfDocument::Status::Ready);
272 setStatus(QPdfDocument::Status::Error);
277void QPdfDocumentPrivate::_q_tryLoadingWithSizeFromContentHeader()
282 const QNetworkReply *networkReply = qobject_cast<QNetworkReply*>(sequentialSourceDevice);
284 setStatus(QPdfDocument::Status::Error);
288 const QVariant contentLength = networkReply->header(QNetworkRequest::ContentLengthHeader);
289 if (!contentLength.isValid()) {
290 setStatus(QPdfDocument::Status::Error);
294 QObject::connect(sequentialSourceDevice, SIGNAL(readyRead()), q, SLOT(_q_copyFromSequentialSourceDevice()));
296 initiateAsyncLoadWithTotalSizeKnown(contentLength.toULongLong());
298 if (sequentialSourceDevice->bytesAvailable())
299 _q_copyFromSequentialSourceDevice();
327void QPdfDocumentPrivate::tryLoadDocument()
329 QPdfMutexLocker lock;
330 switch (FPDFAvail_IsDocAvail(avail,
this)) {
332 qCDebug(qLcDoc) <<
"error loading";
334 case PDF_DATA_NOTAVAIL:
335 qCDebug(qLcDoc) <<
"data not yet available";
336 lastError = QPdfDocument::Error::DataNotYetAvailable;
339 lastError = QPdfDocument::Error::None;
345 doc = FPDFAvail_GetDocument(avail, password);
349 if (lastError != QPdfDocument::Error::None)
350 setStatus(QPdfDocument::Status::Error);
352 if (lastError == QPdfDocument::Error::IncorrectPassword) {
353 FPDF_CloseDocument(doc);
356 setStatus(QPdfDocument::Status::Error);
357 emit q->passwordRequired();
361void QPdfDocumentPrivate::checkComplete()
363 if (!avail || loadComplete)
374 QPdfMutexLocker lock;
376 const int newPageCount = FPDF_GetPageCount(doc);
377 for (
int i = 0; i < newPageCount; ++i) {
378 int result = PDF_DATA_NOTAVAIL;
379 while (result == PDF_DATA_NOTAVAIL) {
380 result = FPDFAvail_IsPageAvail(avail, i,
this);
383 if (result == PDF_DATA_ERROR)
384 loadComplete =
false;
390 if (newPageCount != pageCount) {
391 pageCount = newPageCount;
392 emit q->pageCountChanged(pageCount);
393 emit q->pageModelChanged();
396 setStatus(QPdfDocument::Status::Ready);
400bool QPdfDocumentPrivate::checkPageComplete(
int page)
402 if (page < 0 || page >= pageCount)
408 QPdfMutexLocker lock;
409 int result = PDF_DATA_NOTAVAIL;
410 while (result == PDF_DATA_NOTAVAIL)
411 result = FPDFAvail_IsPageAvail(avail, page,
this);
414 if (result == PDF_DATA_ERROR)
417 return (result != PDF_DATA_ERROR);
450QString QPdfDocumentPrivate::getText(FPDF_TEXTPAGE textPage,
int startIndex,
int count)
const
452 QList<ushort> buf(count + 1);
454 int len = FPDFText_GetText(textPage, startIndex, count, buf.data());
455 Q_ASSERT(len - 1 <= count);
456 return QString::fromUtf16(
reinterpret_cast<
const char16_t *>(buf.constData()), len - 1);
459QPointF QPdfDocumentPrivate::getCharPosition(FPDF_PAGE pdfPage, FPDF_TEXTPAGE textPage,
int charIndex)
const
462 const int count = FPDFText_CountChars(textPage);
463 if (FPDFText_GetCharOrigin(textPage, qMin(count - 1, charIndex), &x, &y))
464 return mapPageToView(pdfPage, x, y);
468QRectF QPdfDocumentPrivate::getCharBox(FPDF_PAGE pdfPage, FPDF_TEXTPAGE textPage,
int charIndex)
const
471 if (FPDFText_GetCharBox(textPage, charIndex, &l, &r, &b, &t))
472 return mapPageToView(pdfPage, l, t, r, b);
483QPointF QPdfDocumentPrivate::mapPageToView(FPDF_PAGE pdfPage,
double x,
double y)
const
485 const auto pageHeight = FPDF_GetPageHeight(pdfPage);
486 const auto pageWidth = FPDF_GetPageWidth(pdfPage);
488 if (FPDF_PageToDevice(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, x, y, &rx, &ry))
489 return QPointF(rx, ry);
501QRectF QPdfDocumentPrivate::mapPageToView(FPDF_PAGE pdfPage,
double left,
double top,
double right,
double bottom)
const
503 const auto pageHeight = FPDF_GetPageHeight(pdfPage);
504 const auto pageWidth = FPDF_GetPageWidth(pdfPage);
505 int xfmLeft, xfmTop, xfmRight, xfmBottom;
506 if ( FPDF_PageToDevice(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, left, top, &xfmLeft, &xfmTop) &&
507 FPDF_PageToDevice(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, right, bottom, &xfmRight, &xfmBottom) )
508 return QRectF(xfmLeft, xfmTop, xfmRight - xfmLeft, xfmBottom - xfmTop);
518QPointF QPdfDocumentPrivate::mapViewToPage(FPDF_PAGE pdfPage, QPointF position)
const
520 const auto pageHeight = FPDF_GetPageHeight(pdfPage);
521 const auto pageWidth = FPDF_GetPageWidth(pdfPage);
523 if (FPDF_DeviceToPage(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, position.x(), position.y(), &rx, &ry))
524 return QPointF(rx, ry);
528QPdfDocumentPrivate::TextPosition QPdfDocumentPrivate::hitTest(
int page, QPointF position)
530 const QPdfMutexLocker lock;
533 FPDF_PAGE pdfPage = FPDF_LoadPage(doc, page);
534 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
535 const QPointF pagePos = mapViewToPage(pdfPage, position);
536 int hitIndex = FPDFText_GetCharIndexAtPos(textPage, pagePos.x(), pagePos.y(),
537 CharacterHitTolerance, CharacterHitTolerance);
539 QPointF charPos = getCharPosition(pdfPage, textPage, hitIndex);
540 if (!charPos.isNull()) {
541 QRectF charBox = getCharBox(pdfPage, textPage, hitIndex);
544 if (qAbs(charBox.right() - position.x()) < qAbs(charPos.x() - position.x())) {
545 charPos.setX(charBox.right());
548 qCDebug(qLcDoc) <<
"on page" << page <<
"@" << position <<
"got char position" << charPos <<
"index" << hitIndex;
549 result = { charPos, charBox.height(), hitIndex };
553 FPDFText_ClosePage(textPage);
554 FPDF_ClosePage(pdfPage);
701QVariant QPdfDocument::metaData(MetaDataField field)
const
706 static QMetaEnum fieldsMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator(
"MetaDataField"));
707 QByteArray fieldName;
709 case MetaDataField::ModificationDate:
710 fieldName =
"ModDate";
713 fieldName = QByteArray(fieldsMetaEnum.valueToKey(
int(field)));
717 QPdfMutexLocker lock;
718 const unsigned long len = FPDF_GetMetaText(d->doc, fieldName.constData(),
nullptr, 0);
720 QList<ushort> buf(len);
721 FPDF_GetMetaText(d->doc, fieldName.constData(), buf.data(), buf.size());
724 QString text = QString::fromUtf16(
reinterpret_cast<
const char16_t *>(buf.data()));
727 case MetaDataField::Title:
728 case MetaDataField::Subject:
729 case MetaDataField::Author:
730 case MetaDataField::Keywords:
731 case MetaDataField::Producer:
732 case MetaDataField::Creator:
734 case MetaDataField::CreationDate:
735 case MetaDataField::ModificationDate:
737 if (text.startsWith(QLatin1String(
"D:")))
739 text.insert(4, QLatin1Char(
'-'));
740 text.insert(7, QLatin1Char(
'-'));
741 text.insert(10, QLatin1Char(
'T'));
742 text.insert(13, QLatin1Char(
':'));
743 text.insert(16, QLatin1Char(
':'));
744 text.replace(QLatin1Char(
'\''), QLatin1Char(
':'));
745 if (text.endsWith(QLatin1Char(
':')))
748 return QDateTime::fromString(text, Qt::ISODate);
866QString QPdfDocument::pageLabel(
int page)
868 const unsigned long len = FPDF_GetPageLabel(d->doc, page,
nullptr, 0);
870 return QString::number(page + 1);
871 QList<
char16_t> buf(len);
872 QPdfMutexLocker lock;
873 FPDF_GetPageLabel(d->doc, page, buf.data(), len);
875 return QString::fromUtf16(buf.constData());
919QImage QPdfDocument::render(
int page, QSize imageSize, QPdfDocumentRenderOptions renderOptions,
920 QImage::Format format,
const QColor &fillColor)
922 if (!d->doc || !d->checkPageComplete(page))
925 const QPdfMutexLocker lock;
928 if (Q_UNLIKELY(qLcDoc().isDebugEnabled()))
930 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
934 QImage result(imageSize, format);
935 result.fill(fillColor);
936 FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(result.width(), result.height(), FPDFBitmap_BGRA, result.bits(), result.bytesPerLine());
938 const QPdfDocumentRenderOptions::RenderFlags renderFlags = renderOptions.renderFlags();
940 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::Annotations)
942 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::OptimizedForLcd)
943 flags |= FPDF_LCD_TEXT;
944 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::Grayscale)
945 flags |= FPDF_GRAYSCALE;
946 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::ForceHalftone)
947 flags |= FPDF_RENDER_FORCEHALFTONE;
948 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::TextAliased)
949 flags |= FPDF_RENDER_NO_SMOOTHTEXT;
950 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::ImageAliased)
951 flags |= FPDF_RENDER_NO_SMOOTHIMAGE;
952 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::PathAliased)
953 flags |= FPDF_RENDER_NO_SMOOTHPATH;
955 if (renderOptions.scaledClipRect().isValid()) {
956 const QRect &clipRect = renderOptions.scaledClipRect();
959 float x0 = clipRect.left();
960 float y0 = clipRect.top();
961 float x1 = clipRect.left();
962 float y1 = clipRect.bottom();
963 float x2 = clipRect.right();
964 float y2 = clipRect.top();
965 QSizeF origSize = pagePointSize(page);
966 QVector2D pageScale(1, 1);
967 if (!renderOptions.scaledSize().isNull()) {
968 pageScale = QVector2D(renderOptions.scaledSize().width() /
float(origSize.width()),
969 renderOptions.scaledSize().height() /
float(origSize.height()));
971 FS_MATRIX matrix {(x2 - x0) / result.width() * pageScale.x(),
972 (y2 - y0) / result.width() * pageScale.x(),
973 (x1 - x0) / result.height() * pageScale.y(),
974 (y1 - y0) / result.height() * pageScale.y(), -x0, -y0};
976 FS_RECTF clipRectF { 0, 0,
float(imageSize.width()),
float(imageSize.height()) };
978 FPDF_RenderPageBitmapWithMatrix(bitmap, pdfPage, &matrix, &clipRectF, flags);
979 qCDebug(qLcDoc) <<
"matrix" << matrix.a << matrix.b << matrix.c << matrix.d << matrix.e << matrix.f;
980 qCDebug(qLcDoc) <<
"page" << page <<
"region" << renderOptions.scaledClipRect()
981 <<
"size" << imageSize <<
"took" << timer.elapsed() <<
"ms";
983 const auto rotation = QPdfDocumentPrivate::toFPDFRotation(renderOptions.rotation());
984 FPDF_RenderPageBitmap(bitmap, pdfPage, 0, 0, result.width(), result.height(), rotation, flags);
985 qCDebug(qLcDoc) <<
"page" << page <<
"size" << imageSize <<
"took" << timer.elapsed() <<
"ms";
988 FPDFBitmap_Destroy(bitmap);
990 FPDF_ClosePage(pdfPage);
998QPdfSelection QPdfDocument::getSelection(
int page, QPointF start, QPointF end)
1000 const QPdfMutexLocker lock;
1001 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
1002 const QPointF pageStart = d->mapViewToPage(pdfPage, start);
1003 const QPointF pageEnd = d->mapViewToPage(pdfPage, end);
1004 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
1005 int startIndex = FPDFText_GetCharIndexAtPos(textPage, pageStart.x(), pageStart.y(),
1006 CharacterHitTolerance, CharacterHitTolerance);
1007 int endIndex = FPDFText_GetCharIndexAtPos(textPage, pageEnd.x(), pageEnd.y(),
1008 CharacterHitTolerance, CharacterHitTolerance);
1010 QPdfSelection result;
1012 if (startIndex >= 0 && endIndex != startIndex) {
1013 if (startIndex > endIndex)
1014 qSwap(startIndex, endIndex);
1018 QRectF endCharBox = d->getCharBox(pdfPage, textPage, endIndex);
1019 if (qAbs(endCharBox.right() - end.x()) < qAbs(endCharBox.x() - end.x()))
1022 int count = endIndex - startIndex;
1023 QString text = d->getText(textPage, startIndex, count);
1024 QList<QPolygonF> bounds;
1026 int rectCount = FPDFText_CountRects(textPage, startIndex, endIndex - startIndex);
1027 for (
int i = 0; i < rectCount; ++i) {
1029 FPDFText_GetRect(textPage, i, &l, &t, &r, &b);
1030 const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b);
1034 hull = hull.united(rect);
1035 bounds << QPolygonF(rect);
1037 qCDebug(qLcDoc) << page << start <<
"->" << end <<
"found" << startIndex <<
"->" << endIndex << text;
1038 result = QPdfSelection(text, bounds, hull, startIndex, endIndex);
1040 qCDebug(qLcDoc) << page << start <<
"->" << end <<
"nothing found";
1043 FPDFText_ClosePage(textPage);
1044 FPDF_ClosePage(pdfPage);
1053QPdfSelection QPdfDocument::getSelectionAtIndex(
int page,
int startIndex,
int maxLength)
1056 if (page < 0 || startIndex < 0 || maxLength < 0)
1058 const QPdfMutexLocker lock;
1059 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
1060 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
1061 int pageCount = FPDFText_CountChars(textPage);
1062 if (startIndex >= pageCount)
1063 return QPdfSelection();
1064 QList<QPolygonF> bounds;
1068 if (maxLength > 0) {
1069 text = d->getText(textPage, startIndex, maxLength);
1070 rectCount = FPDFText_CountRects(textPage, startIndex, text.size());
1071 for (
int i = 0; i < rectCount; ++i) {
1073 FPDFText_GetRect(textPage, i, &l, &t, &r, &b);
1074 const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b);
1078 hull = hull.united(rect);
1079 bounds << QPolygonF(rect);
1082 if (bounds.isEmpty())
1083 hull = QRectF(d->getCharPosition(pdfPage, textPage, startIndex), QSizeF());
1084 qCDebug(qLcDoc) <<
"on page" << page <<
"at index" << startIndex <<
"maxLength" << maxLength
1085 <<
"got" << text.size() <<
"chars," << rectCount <<
"rects within" << hull;
1087 FPDFText_ClosePage(textPage);
1088 FPDF_ClosePage(pdfPage);
1090 return QPdfSelection(text, bounds, hull, startIndex, startIndex + text.size());
1096QPdfSelection QPdfDocument::getAllText(
int page)
1098 const QPdfMutexLocker lock;
1099 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
1100 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
1101 int count = FPDFText_CountChars(textPage);
1103 return QPdfSelection();
1104 QString text = d->getText(textPage, 0, count);
1105 QList<QPolygonF> bounds;
1107 int rectCount = FPDFText_CountRects(textPage, 0, count);
1108 for (
int i = 0; i < rectCount; ++i) {
1110 FPDFText_GetRect(textPage, i, &l, &t, &r, &b);
1111 const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b);
1115 hull = hull.united(rect);
1116 bounds << QPolygonF(rect);
1118 qCDebug(qLcDoc) <<
"on page" << page <<
"got" << count <<
"chars," << rectCount <<
"rects within" << hull;
1120 FPDFText_ClosePage(textPage);
1121 FPDF_ClosePage(pdfPage);
1123 return QPdfSelection(text, bounds, hull, 0, count);