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);
903QImage QPdfDocument::render(
int page, QSize imageSize, QPdfDocumentRenderOptions renderOptions)
905 if (!d->doc || !d->checkPageComplete(page))
908 const QPdfMutexLocker lock;
911 if (Q_UNLIKELY(qLcDoc().isDebugEnabled()))
913 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
917 QImage result(imageSize, QImage::Format_ARGB32);
918 result.fill(Qt::transparent);
919 FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(result.width(), result.height(), FPDFBitmap_BGRA, result.bits(), result.bytesPerLine());
921 const QPdfDocumentRenderOptions::RenderFlags renderFlags = renderOptions.renderFlags();
923 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::Annotations)
925 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::OptimizedForLcd)
926 flags |= FPDF_LCD_TEXT;
927 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::Grayscale)
928 flags |= FPDF_GRAYSCALE;
929 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::ForceHalftone)
930 flags |= FPDF_RENDER_FORCEHALFTONE;
931 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::TextAliased)
932 flags |= FPDF_RENDER_NO_SMOOTHTEXT;
933 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::ImageAliased)
934 flags |= FPDF_RENDER_NO_SMOOTHIMAGE;
935 if (renderFlags & QPdfDocumentRenderOptions::RenderFlag::PathAliased)
936 flags |= FPDF_RENDER_NO_SMOOTHPATH;
938 if (renderOptions.scaledClipRect().isValid()) {
939 const QRect &clipRect = renderOptions.scaledClipRect();
942 float x0 = clipRect.left();
943 float y0 = clipRect.top();
944 float x1 = clipRect.left();
945 float y1 = clipRect.bottom();
946 float x2 = clipRect.right();
947 float y2 = clipRect.top();
948 QSizeF origSize = pagePointSize(page);
949 QVector2D pageScale(1, 1);
950 if (!renderOptions.scaledSize().isNull()) {
951 pageScale = QVector2D(renderOptions.scaledSize().width() /
float(origSize.width()),
952 renderOptions.scaledSize().height() /
float(origSize.height()));
954 FS_MATRIX matrix {(x2 - x0) / result.width() * pageScale.x(),
955 (y2 - y0) / result.width() * pageScale.x(),
956 (x1 - x0) / result.height() * pageScale.y(),
957 (y1 - y0) / result.height() * pageScale.y(), -x0, -y0};
959 FS_RECTF clipRectF { 0, 0,
float(imageSize.width()),
float(imageSize.height()) };
961 FPDF_RenderPageBitmapWithMatrix(bitmap, pdfPage, &matrix, &clipRectF, flags);
962 qCDebug(qLcDoc) <<
"matrix" << matrix.a << matrix.b << matrix.c << matrix.d << matrix.e << matrix.f;
963 qCDebug(qLcDoc) <<
"page" << page <<
"region" << renderOptions.scaledClipRect()
964 <<
"size" << imageSize <<
"took" << timer.elapsed() <<
"ms";
966 const auto rotation = QPdfDocumentPrivate::toFPDFRotation(renderOptions.rotation());
967 FPDF_RenderPageBitmap(bitmap, pdfPage, 0, 0, result.width(), result.height(), rotation, flags);
968 qCDebug(qLcDoc) <<
"page" << page <<
"size" << imageSize <<
"took" << timer.elapsed() <<
"ms";
971 FPDFBitmap_Destroy(bitmap);
973 FPDF_ClosePage(pdfPage);
981QPdfSelection QPdfDocument::getSelection(
int page, QPointF start, QPointF end)
983 const QPdfMutexLocker lock;
984 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
985 const QPointF pageStart = d->mapViewToPage(pdfPage, start);
986 const QPointF pageEnd = d->mapViewToPage(pdfPage, end);
987 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
988 int startIndex = FPDFText_GetCharIndexAtPos(textPage, pageStart.x(), pageStart.y(),
989 CharacterHitTolerance, CharacterHitTolerance);
990 int endIndex = FPDFText_GetCharIndexAtPos(textPage, pageEnd.x(), pageEnd.y(),
991 CharacterHitTolerance, CharacterHitTolerance);
993 QPdfSelection result;
995 if (startIndex >= 0 && endIndex != startIndex) {
996 if (startIndex > endIndex)
997 qSwap(startIndex, endIndex);
1001 QRectF endCharBox = d->getCharBox(pdfPage, textPage, endIndex);
1002 if (qAbs(endCharBox.right() - end.x()) < qAbs(endCharBox.x() - end.x()))
1005 int count = endIndex - startIndex;
1006 QString text = d->getText(textPage, startIndex, count);
1007 QList<QPolygonF> bounds;
1009 int rectCount = FPDFText_CountRects(textPage, startIndex, endIndex - startIndex);
1010 for (
int i = 0; i < rectCount; ++i) {
1012 FPDFText_GetRect(textPage, i, &l, &t, &r, &b);
1013 const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b);
1017 hull = hull.united(rect);
1018 bounds << QPolygonF(rect);
1020 qCDebug(qLcDoc) << page << start <<
"->" << end <<
"found" << startIndex <<
"->" << endIndex << text;
1021 result = QPdfSelection(text, bounds, hull, startIndex, endIndex);
1023 qCDebug(qLcDoc) << page << start <<
"->" << end <<
"nothing found";
1026 FPDFText_ClosePage(textPage);
1027 FPDF_ClosePage(pdfPage);
1036QPdfSelection QPdfDocument::getSelectionAtIndex(
int page,
int startIndex,
int maxLength)
1039 if (page < 0 || startIndex < 0 || maxLength < 0)
1041 const QPdfMutexLocker lock;
1042 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
1043 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
1044 int pageCount = FPDFText_CountChars(textPage);
1045 if (startIndex >= pageCount)
1046 return QPdfSelection();
1047 QList<QPolygonF> bounds;
1051 if (maxLength > 0) {
1052 text = d->getText(textPage, startIndex, maxLength);
1053 rectCount = FPDFText_CountRects(textPage, startIndex, text.size());
1054 for (
int i = 0; i < rectCount; ++i) {
1056 FPDFText_GetRect(textPage, i, &l, &t, &r, &b);
1057 const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b);
1061 hull = hull.united(rect);
1062 bounds << QPolygonF(rect);
1065 if (bounds.isEmpty())
1066 hull = QRectF(d->getCharPosition(pdfPage, textPage, startIndex), QSizeF());
1067 qCDebug(qLcDoc) <<
"on page" << page <<
"at index" << startIndex <<
"maxLength" << maxLength
1068 <<
"got" << text.size() <<
"chars," << rectCount <<
"rects within" << hull;
1070 FPDFText_ClosePage(textPage);
1071 FPDF_ClosePage(pdfPage);
1073 return QPdfSelection(text, bounds, hull, startIndex, startIndex + text.size());
1079QPdfSelection QPdfDocument::getAllText(
int page)
1081 const QPdfMutexLocker lock;
1082 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
1083 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
1084 int count = FPDFText_CountChars(textPage);
1086 return QPdfSelection();
1087 QString text = d->getText(textPage, 0, count);
1088 QList<QPolygonF> bounds;
1090 int rectCount = FPDFText_CountRects(textPage, 0, count);
1091 for (
int i = 0; i < rectCount; ++i) {
1093 FPDFText_GetRect(textPage, i, &l, &t, &r, &b);
1094 const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b);
1098 hull = hull.united(rect);
1099 bounds << QPolygonF(rect);
1101 qCDebug(qLcDoc) <<
"on page" << page <<
"got" << count <<
"chars," << rectCount <<
"rects within" << hull;
1103 FPDFText_ClosePage(textPage);
1104 FPDF_ClosePage(pdfPage);
1106 return QPdfSelection(text, bounds, hull, 0, count);