Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qpdfdocument.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qpdfdocument.h"
5#include "qpdfdocument_p.h"
6
7#include "third_party/pdfium/public/fpdf_doc.h"
8#include "third_party/pdfium/public/fpdf_text.h"
9
10#include <QDateTime>
11#include <QDebug>
12#include <QElapsedTimer>
13#include <QFile>
14#include <QHash>
15#include <QLoggingCategory>
16#include <QMetaEnum>
17#include <QMutex>
18#include <QPixmap>
19#include <QVector2D>
20
21#include <QtCore/private/qtools_p.h>
22
24
26static int libraryRefCount;
27static const double CharacterHitTolerance = 16.0;
28Q_LOGGING_CATEGORY(qLcDoc, "qt.pdf.document")
29
31 : std::unique_lock<QRecursiveMutex>(*pdfMutex())
32{
33}
34
35class Q_PDF_EXPORT QPdfPageModel : public QAbstractListModel
36{
38public:
40 {
41 m_roleNames = QAbstractItemModel::roleNames();
42 QMetaEnum rolesMetaEnum = doc->metaObject()->enumerator(doc->metaObject()->indexOfEnumerator("PageModelRole"));
44 auto name = QByteArray(rolesMetaEnum.valueToKey(r));
46 m_roleNames.insert(r, name);
47 }
50 beginResetModel();
52 endResetModel();
53 });
54 }
55
56 QVariant data(const QModelIndex &index, int role) const override
57 {
58 if (!index.isValid())
59 return QVariant();
60
61 switch (QPdfDocument::PageModelRole(role)) {
63 return document()->pageLabel(index.row());
65 return document()->pagePointSize(index.row());
67 break;
68 }
69
70 switch (role) {
72 return pageThumbnail(index.row());
73 case Qt::DisplayRole:
74 return document()->pageLabel(index.row());
75 }
76
77 return QVariant();
78 }
79
80 int rowCount(const QModelIndex & = QModelIndex()) const override { return document()->pageCount(); }
81
82 QHash<int, QByteArray> roleNames() const override { return m_roleNames; }
83
84private:
85 QPdfDocument *document() const { return static_cast<QPdfDocument *>(parent()); }
86 QPixmap pageThumbnail(int page) const
87 {
88 auto it = m_thumbnails.constFind(page);
89 if (it == m_thumbnails.constEnd()) {
90 auto doc = document();
91 auto size = doc->pagePointSize(page);
92 size.scale(128, 128, Qt::KeepAspectRatio);
93 // TODO use QPdfPageRenderer for threading?
94 auto image = document()->render(page, size.toSize());
96 m_thumbnails.insert(page, ret);
97 return ret;
98 }
99 return it.value();
100 }
101
102 QHash<int, QByteArray> m_roleNames;
103 mutable QHash<int, QPixmap> m_thumbnails;
104};
105
107 : avail(nullptr)
108 , doc(nullptr)
109 , loadComplete(false)
110 , status(QPdfDocument::Status::Null)
111 , lastError(QPdfDocument::Error::None)
112 , pageCount(0)
113{
116
117 const QPdfMutexLocker lock;
118
119 if (libraryRefCount == 0) {
121 timer.start();
122 FPDF_InitLibrary();
123 qCDebug(qLcDoc) << "FPDF_InitLibrary took" << timer.elapsed() << "ms";
124 }
126
127 // FPDF_FILEACCESS setup
128 m_Param = this;
129 m_GetBlock = fpdf_GetBlock;
130
131 // FX_FILEAVAIL setup
132 FX_FILEAVAIL::version = 1;
133 IsDataAvail = fpdf_IsDataAvail;
134
135 // FX_DOWNLOADHINTS setup
136 FX_DOWNLOADHINTS::version = 1;
137 AddSegment = fpdf_AddSegment;
138}
139
141{
142 q->close();
143
144 const QPdfMutexLocker lock;
145
146 if (!--libraryRefCount) {
147 qCDebug(qLcDoc) << "FPDF_DestroyLibrary";
148 FPDF_DestroyLibrary();
149 }
150}
151
153{
155
156 if (doc)
157 FPDF_CloseDocument(doc);
158 doc = nullptr;
159
160 if (avail)
161 FPDFAvail_Destroy(avail);
162 avail = nullptr;
163 lock.unlock();
164
165 if (pageCount != 0) {
166 pageCount = 0;
167 emit q->pageCountChanged(pageCount);
168 emit q->pageModelChanged();
169 }
170
171 loadComplete = false;
172
176
179}
180
182{
183 if (doc) {
185 return;
186 }
187
189 const unsigned long error = FPDF_GetLastError();
190 lock.unlock();
191
192 switch (error) {
193 case FPDF_ERR_SUCCESS: lastError = QPdfDocument::Error::None; break;
194 case FPDF_ERR_UNKNOWN: lastError = QPdfDocument::Error::Unknown; break;
195 case FPDF_ERR_FILE: lastError = QPdfDocument::Error::FileNotFound; break;
196 case FPDF_ERR_FORMAT: lastError = QPdfDocument::Error::InvalidFileFormat; break;
197 case FPDF_ERR_PASSWORD: lastError = QPdfDocument::Error::IncorrectPassword; break;
198 case FPDF_ERR_SECURITY: lastError = QPdfDocument::Error::UnsupportedSecurityScheme; break;
199 default:
200 Q_UNREACHABLE();
201 }
203 qCDebug(qLcDoc) << "FPDF error" << error << "->" << lastError;
204}
205
206void QPdfDocumentPrivate::load(QIODevice *newDevice, bool transferDeviceOwnership)
207{
208 if (transferDeviceOwnership)
209 ownDevice.reset(newDevice);
210 else
212
213 if (newDevice->isSequential()) {
214 sequentialSourceDevice = newDevice;
216 QNetworkReply *reply = qobject_cast<QNetworkReply*>(sequentialSourceDevice);
217
218 if (!reply) {
220 qWarning() << "QPdfDocument: Loading from sequential devices only supported with QNetworkAccessManager.";
221 return;
222 }
223
226 return;
227 }
228
231 this->setStatus(QPdfDocument::Status::Error);
232 }
233 });
234
237 else
239 } else {
240 device = newDevice;
242 if (!avail) {
244 return;
245 }
246
247 if (!doc)
249
250 if (!doc) {
253 return;
254 }
255
257 const int newPageCount = FPDF_GetPageCount(doc);
258 lock.unlock();
259 if (newPageCount != pageCount) {
260 pageCount = newPageCount;
261 emit q->pageCountChanged(pageCount);
262 emit q->pageModelChanged();
263 }
264
265 // If it's a local file, and the first couple of pages are available,
266 // probably the whole document is available.
267 if (checkPageComplete(0) && (pageCount < 2 || checkPageComplete(1))) {
269 } else {
272 }
273 }
274}
275
277{
278 if (avail)
279 return;
280
281 const QNetworkReply *networkReply = qobject_cast<QNetworkReply*>(sequentialSourceDevice);
282 if (!networkReply) {
284 return;
285 }
286
287 const QVariant contentLength = networkReply->header(QNetworkRequest::ContentLengthHeader);
288 if (!contentLength.isValid()) {
290 return;
291 }
292
294
296
299}
300
302{
303 // FPDF_FILEACCESS setup
304 m_FileLen = totalSize;
305
306 const QPdfMutexLocker lock;
307
308 avail = FPDFAvail_Create(this, this);
309}
310
325
327{
329 switch (FPDFAvail_IsDocAvail(avail, this)) {
330 case PDF_DATA_ERROR:
331 qCDebug(qLcDoc) << "error loading";
332 break;
333 case PDF_DATA_NOTAVAIL:
334 qCDebug(qLcDoc) << "data not yet available";
336 break;
337 case PDF_DATA_AVAIL:
339 break;
340 }
341
342 Q_ASSERT(!doc);
343
344 doc = FPDFAvail_GetDocument(avail, password);
345 lock.unlock();
346
350
352 FPDF_CloseDocument(doc);
353 doc = nullptr;
354
356 emit q->passwordRequired();
357 }
358}
359
361{
362 if (!avail || loadComplete)
363 return;
364
365 if (!doc)
367
368 if (!doc)
369 return;
370
371 loadComplete = true;
372
374
375 const int newPageCount = FPDF_GetPageCount(doc);
376 for (int i = 0; i < newPageCount; ++i) {
377 int result = PDF_DATA_NOTAVAIL;
378 while (result == PDF_DATA_NOTAVAIL) {
379 result = FPDFAvail_IsPageAvail(avail, i, this);
380 }
381
382 if (result == PDF_DATA_ERROR)
383 loadComplete = false;
384 }
385
386 lock.unlock();
387
388 if (loadComplete) {
389 if (newPageCount != pageCount) {
390 pageCount = newPageCount;
391 emit q->pageCountChanged(pageCount);
392 emit q->pageModelChanged();
393 }
394
396 }
397}
398
400{
401 if (page < 0 || page >= pageCount)
402 return false;
403
404 if (loadComplete)
405 return true;
406
408 int result = PDF_DATA_NOTAVAIL;
409 while (result == PDF_DATA_NOTAVAIL)
410 result = FPDFAvail_IsPageAvail(avail, page, this);
411 lock.unlock();
412
413 if (result == PDF_DATA_ERROR)
415
416 return (result != PDF_DATA_ERROR);
417}
418
420{
421 if (status == documentStatus)
422 return;
423
424 status = documentStatus;
425 emit q->statusChanged(status);
426}
427
428FPDF_BOOL QPdfDocumentPrivate::fpdf_IsDataAvail(_FX_FILEAVAIL *pThis, size_t offset, size_t size)
429{
430 QPdfDocumentPrivate *d = static_cast<QPdfDocumentPrivate*>(pThis);
431 return offset + size <= static_cast<quint64>(d->device->size());
432}
433
434int QPdfDocumentPrivate::fpdf_GetBlock(void *param, unsigned long position, unsigned char *pBuf, unsigned long size)
435{
436 QPdfDocumentPrivate *d = static_cast<QPdfDocumentPrivate*>(reinterpret_cast<FPDF_FILEACCESS*>(param));
438 return qMax(qint64(0), d->device->read(reinterpret_cast<char *>(pBuf), size));
439
440}
441
442void QPdfDocumentPrivate::fpdf_AddSegment(_FX_DOWNLOADHINTS *pThis, size_t offset, size_t size)
443{
444 Q_UNUSED(pThis);
446 Q_UNUSED(size);
447}
448
449QString QPdfDocumentPrivate::getText(FPDF_TEXTPAGE textPage, int startIndex, int count) const
450{
451 QList<ushort> buf(count + 1);
452 // TODO is that enough space in case one unicode character is more than one in utf-16?
453 int len = FPDFText_GetText(textPage, startIndex, count, buf.data());
454 Q_ASSERT(len - 1 <= count); // len is number of characters written, including the terminator
455 return QString::fromUtf16(reinterpret_cast<const char16_t *>(buf.constData()), len - 1);
456}
457
458QPointF QPdfDocumentPrivate::getCharPosition(FPDF_PAGE pdfPage, FPDF_TEXTPAGE textPage, int charIndex) const
459{
460 double x, y;
461 const int count = FPDFText_CountChars(textPage);
462 if (FPDFText_GetCharOrigin(textPage, qMin(count - 1, charIndex), &x, &y))
463 return mapPageToView(pdfPage, x, y);
464 return {};
465}
466
467QRectF QPdfDocumentPrivate::getCharBox(FPDF_PAGE pdfPage, FPDF_TEXTPAGE textPage, int charIndex) const
468{
469 double l, t, r, b;
470 if (FPDFText_GetCharBox(textPage, charIndex, &l, &r, &b, &t))
471 return mapPageToView(pdfPage, l, t, r, b);
472 return {};
473}
474
482QPointF QPdfDocumentPrivate::mapPageToView(FPDF_PAGE pdfPage, double x, double y) const
483{
484 const auto pageHeight = FPDF_GetPageHeight(pdfPage);
485 const auto pageWidth = FPDF_GetPageWidth(pdfPage);
486 int rx, ry;
487 if (FPDF_PageToDevice(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, x, y, &rx, &ry))
488 return QPointF(rx, ry);
489 return {};
490}
491
500QRectF QPdfDocumentPrivate::mapPageToView(FPDF_PAGE pdfPage, double left, double top, double right, double bottom) const
501{
502 const auto pageHeight = FPDF_GetPageHeight(pdfPage);
503 const auto pageWidth = FPDF_GetPageWidth(pdfPage);
504 int xfmLeft, xfmTop, xfmRight, xfmBottom;
505 if ( FPDF_PageToDevice(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, left, top, &xfmLeft, &xfmTop) &&
506 FPDF_PageToDevice(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, right, bottom, &xfmRight, &xfmBottom) )
507 return QRectF(xfmLeft, xfmTop, xfmRight - xfmLeft, xfmBottom - xfmTop);
508 return {};
509}
510
518{
519 const auto pageHeight = FPDF_GetPageHeight(pdfPage);
520 const auto pageWidth = FPDF_GetPageWidth(pdfPage);
521 double rx, ry;
522 if (FPDF_DeviceToPage(pdfPage, 0, 0, qRound(pageWidth), qRound(pageHeight), 0, position.x(), position.y(), &rx, &ry))
523 return QPointF(rx, ry);
524 return {};
525}
526
528{
529 const QPdfMutexLocker lock;
530
532 FPDF_PAGE pdfPage = FPDF_LoadPage(doc, page);
533 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
534 const QPointF pagePos = mapViewToPage(pdfPage, position);
535 int hitIndex = FPDFText_GetCharIndexAtPos(textPage, pagePos.x(), pagePos.y(),
537 if (hitIndex >= 0) {
538 QPointF charPos = getCharPosition(pdfPage, textPage, hitIndex);
539 if (!charPos.isNull()) {
540 QRectF charBox = getCharBox(pdfPage, textPage, hitIndex);
541 // If the given position is past the end of the line, i.e. if the right edge of the found character's
542 // bounding box is closer to it than the left edge is, we say that we "hit" the next character index after
543 if (qAbs(charBox.right() - position.x()) < qAbs(charPos.x() - position.x())) {
544 charPos.setX(charBox.right());
545 ++hitIndex;
546 }
547 qCDebug(qLcDoc) << "on page" << page << "@" << position << "got char position" << charPos << "index" << hitIndex;
548 result = { charPos, charBox.height(), hitIndex };
549 }
550 }
551
552 FPDFText_ClosePage(textPage);
553 FPDF_ClosePage(pdfPage);
554
555 return result;
556}
557
570 : QObject(parent)
572{
573 d->q = this;
574}
575
582
587{
588 qCDebug(qLcDoc) << "loading" << fileName;
589
590 close();
591
593
594 std::unique_ptr<QFile> f(new QFile(fileName));
595 if (!f->open(QIODevice::ReadOnly)) {
598 } else {
599 d->load(f.release(), /*transfer ownership*/true);
600 }
601 return d->lastError;
602}
603
608QString QPdfDocument::fileName() const
609{
610 const QFile *f = qobject_cast<QFile *>(d->device.data());
611 if (f)
612 return f->fileName();
613 return QString();
614}
615
637{
638 return d->status;
639}
640
645{
646 close();
647
649
650 d->load(device, /*transfer ownership*/false);
651}
652
662{
663 const QByteArray newPassword = password.toUtf8();
664
665 if (d->password == newPassword)
666 return;
667
668 d->password = newPassword;
670}
671
676
701{
702 if (!d->doc)
703 return QString();
704
705 static QMetaEnum fieldsMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("MetaDataField"));
706 QByteArray fieldName;
707 switch (field) {
709 fieldName = "ModDate";
710 break;
711 default:
712 fieldName = QByteArray(fieldsMetaEnum.valueToKey(int(field)));
713 break;
714 }
715
717 const unsigned long len = FPDF_GetMetaText(d->doc, fieldName.constData(), nullptr, 0);
718
719 QList<ushort> buf(len);
720 FPDF_GetMetaText(d->doc, fieldName.constData(), buf.data(), buf.size());
721 lock.unlock();
722
723 QString text = QString::fromUtf16(reinterpret_cast<const char16_t *>(buf.data()));
724
725 switch (field) {
726 case MetaDataField::Title: // fall through
732 return text;
733 case MetaDataField::CreationDate: // fall through
735 // convert a "D:YYYYMMDDHHmmSSOHH'mm'" into "YYYY-MM-DDTHH:mm:ss+HH:mm"
736 if (text.startsWith(QLatin1String("D:")))
737 text = text.mid(2);
738 text.insert(4, QLatin1Char('-'));
739 text.insert(7, QLatin1Char('-'));
740 text.insert(10, QLatin1Char('T'));
741 text.insert(13, QLatin1Char(':'));
742 text.insert(16, QLatin1Char(':'));
743 text.replace(QLatin1Char('\''), QLatin1Char(':'));
744 if (text.endsWith(QLatin1Char(':')))
745 text.chop(1);
746
747 return QDateTime::fromString(text, Qt::ISODate);
748 }
749
750 return QVariant();
751}
752
774{
775 return d->lastError;
776}
777
782{
783 if (!d->doc)
784 return;
785
787
788 d->clear();
789
790 if (!d->password.isEmpty()) {
791 d->password.clear();
793 }
794
796}
797
805{
806 return d->pageCount;
807}
808
813{
815 if (!d->doc || !d->checkPageComplete(page))
816 return result;
817
818 const QPdfMutexLocker lock;
819
820 FPDF_GetPageSizeByIndex(d->doc, page, &result.rwidth(), &result.rheight());
821 return result;
822}
823
843{
844 if (!d->pageModel)
845 d->pageModel = new QPdfPageModel(this);
846 return d->pageModel;
847}
848
866{
867 const unsigned long len = FPDF_GetPageLabel(d->doc, page, nullptr, 0);
868 if (len == 0)
869 return QString::number(page + 1);
870 QList<char16_t> buf(len);
872 FPDF_GetPageLabel(d->doc, page, buf.data(), len);
873 lock.unlock();
874 return QString::fromUtf16(buf.constData());
875}
876
884{
885 for (int i = 0; i < d->pageCount; ++i) {
886 if (pageLabel(i) == label)
887 return i;
888 }
889 return -1;
890}
891
903{
904 if (!d->doc || !d->checkPageComplete(page))
905 return QImage();
906
907 const QPdfMutexLocker lock;
908
910 if (Q_UNLIKELY(qLcDoc().isDebugEnabled()))
911 timer.start();
912 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
913 if (!pdfPage)
914 return QImage();
915
918 FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(result.width(), result.height(), FPDFBitmap_BGRA, result.bits(), result.bytesPerLine());
919
920 const QPdfDocumentRenderOptions::RenderFlags renderFlags = renderOptions.renderFlags();
921 int flags = 0;
923 flags |= FPDF_ANNOT;
925 flags |= FPDF_LCD_TEXT;
927 flags |= FPDF_GRAYSCALE;
929 flags |= FPDF_RENDER_FORCEHALFTONE;
931 flags |= FPDF_RENDER_NO_SMOOTHTEXT;
933 flags |= FPDF_RENDER_NO_SMOOTHIMAGE;
935 flags |= FPDF_RENDER_NO_SMOOTHPATH;
936
937 if (renderOptions.scaledClipRect().isValid()) {
938 const QRect &clipRect = renderOptions.scaledClipRect();
939
940 // TODO take rotation into account, like cpdf_page.cpp lines 145-178
941 float x0 = clipRect.left();
942 float y0 = clipRect.top();
943 float x1 = clipRect.left();
944 float y1 = clipRect.bottom();
945 float x2 = clipRect.right();
946 float y2 = clipRect.top();
947 QSizeF origSize = pagePointSize(page);
948 QVector2D pageScale(1, 1);
949 if (!renderOptions.scaledSize().isNull()) {
950 pageScale = QVector2D(renderOptions.scaledSize().width() / float(origSize.width()),
951 renderOptions.scaledSize().height() / float(origSize.height()));
952 }
953 FS_MATRIX matrix {(x2 - x0) / result.width() * pageScale.x(),
954 (y2 - y0) / result.width() * pageScale.x(),
955 (x1 - x0) / result.height() * pageScale.y(),
956 (y1 - y0) / result.height() * pageScale.y(), -x0, -y0};
957
958 FS_RECTF clipRectF { 0, 0, float(imageSize.width()), float(imageSize.height()) };
959
960 FPDF_RenderPageBitmapWithMatrix(bitmap, pdfPage, &matrix, &clipRectF, flags);
961 qCDebug(qLcDoc) << "matrix" << matrix.a << matrix.b << matrix.c << matrix.d << matrix.e << matrix.f;
962 qCDebug(qLcDoc) << "page" << page << "region" << renderOptions.scaledClipRect()
963 << "size" << imageSize << "took" << timer.elapsed() << "ms";
964 } else {
965 const auto rotation = QPdfDocumentPrivate::toFPDFRotation(renderOptions.rotation());
966 FPDF_RenderPageBitmap(bitmap, pdfPage, 0, 0, result.width(), result.height(), rotation, flags);
967 qCDebug(qLcDoc) << "page" << page << "size" << imageSize << "took" << timer.elapsed() << "ms";
968 }
969
970 FPDFBitmap_Destroy(bitmap);
971
972 FPDF_ClosePage(pdfPage);
973 return result;
974}
975
981{
982 const QPdfMutexLocker lock;
983 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
984 const QPointF pageStart = d->mapViewToPage(pdfPage, start);
985 const QPointF pageEnd = d->mapViewToPage(pdfPage, end);
986 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
987 int startIndex = FPDFText_GetCharIndexAtPos(textPage, pageStart.x(), pageStart.y(),
989 int endIndex = FPDFText_GetCharIndexAtPos(textPage, pageEnd.x(), pageEnd.y(),
991
993
994 if (startIndex >= 0 && endIndex != startIndex) {
995 if (startIndex > endIndex)
996 qSwap(startIndex, endIndex);
997
998 // If the given end position is past the end of the line, i.e. if the right edge of the last character's
999 // bounding box is closer to it than the left edge is, then extend the char range by one
1000 QRectF endCharBox = d->getCharBox(pdfPage, textPage, endIndex);
1001 if (qAbs(endCharBox.right() - end.x()) < qAbs(endCharBox.x() - end.x()))
1002 ++endIndex;
1003
1004 int count = endIndex - startIndex;
1005 QString text = d->getText(textPage, startIndex, count);
1006 QList<QPolygonF> bounds;
1007 QRectF hull;
1008 int rectCount = FPDFText_CountRects(textPage, startIndex, endIndex - startIndex);
1009 for (int i = 0; i < rectCount; ++i) {
1010 double l, r, b, t;
1011 FPDFText_GetRect(textPage, i, &l, &t, &r, &b);
1012 const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b);
1013 if (hull.isNull())
1014 hull = rect;
1015 else
1016 hull = hull.united(rect);
1017 bounds << QPolygonF(rect);
1018 }
1019 qCDebug(qLcDoc) << page << start << "->" << end << "found" << startIndex << "->" << endIndex << text;
1020 result = QPdfSelection(text, bounds, hull, startIndex, endIndex);
1021 } else {
1022 qCDebug(qLcDoc) << page << start << "->" << end << "nothing found";
1023 }
1024
1025 FPDFText_ClosePage(textPage);
1026 FPDF_ClosePage(pdfPage);
1027
1028 return result;
1029}
1030
1036{
1037
1038 if (page < 0 || startIndex < 0 || maxLength < 0)
1039 return {};
1040 const QPdfMutexLocker lock;
1041 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
1042 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
1043 int pageCount = FPDFText_CountChars(textPage);
1044 if (startIndex >= pageCount)
1045 return QPdfSelection();
1046 QList<QPolygonF> bounds;
1047 QRectF hull;
1048 int rectCount = 0;
1049 QString text;
1050 if (maxLength > 0) {
1051 text = d->getText(textPage, startIndex, maxLength);
1052 rectCount = FPDFText_CountRects(textPage, startIndex, text.size());
1053 for (int i = 0; i < rectCount; ++i) {
1054 double l, r, b, t;
1055 FPDFText_GetRect(textPage, i, &l, &t, &r, &b);
1056 const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b);
1057 if (hull.isNull())
1058 hull = rect;
1059 else
1060 hull = hull.united(rect);
1061 bounds << QPolygonF(rect);
1062 }
1063 }
1064 if (bounds.isEmpty())
1065 hull = QRectF(d->getCharPosition(pdfPage, textPage, startIndex), QSizeF());
1066 qCDebug(qLcDoc) << "on page" << page << "at index" << startIndex << "maxLength" << maxLength
1067 << "got" << text.size() << "chars," << rectCount << "rects within" << hull;
1068
1069 FPDFText_ClosePage(textPage);
1070 FPDF_ClosePage(pdfPage);
1071
1072 return QPdfSelection(text, bounds, hull, startIndex, startIndex + text.size());
1073}
1074
1079{
1080 const QPdfMutexLocker lock;
1081 FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page);
1082 FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage);
1083 int count = FPDFText_CountChars(textPage);
1084 if (count < 1)
1085 return QPdfSelection();
1086 QString text = d->getText(textPage, 0, count);
1087 QList<QPolygonF> bounds;
1088 QRectF hull;
1089 int rectCount = FPDFText_CountRects(textPage, 0, count);
1090 for (int i = 0; i < rectCount; ++i) {
1091 double l, r, b, t;
1092 FPDFText_GetRect(textPage, i, &l, &t, &r, &b);
1093 const QRectF rect = d->mapPageToView(pdfPage, l, t, r, b);
1094 if (hull.isNull())
1095 hull = rect;
1096 else
1097 hull = hull.united(rect);
1098 bounds << QPolygonF(rect);
1099 }
1100 qCDebug(qLcDoc) << "on page" << page << "got" << count << "chars," << rectCount << "rects within" << hull;
1101
1102 FPDFText_ClosePage(textPage);
1103 FPDF_ClosePage(pdfPage);
1104
1105 return QPdfSelection(text, bounds, hull, 0, count);
1106}
1107
1109
1110#include "qpdfdocument.moc"
1111#include "moc_qpdfdocument.cpp"
IOBluetoothDevice * device
virtual QHash< int, QByteArray > roleNames() const
bool open(OpenMode openMode) override
\reimp
Definition qbuffer.cpp:295
void setData(const QByteArray &data)
Sets the contents of the internal buffer to be data.
Definition qbuffer.cpp:259
void close() override
\reimp
Definition qbuffer.cpp:315
qint64 size() const override
\reimp
Definition qbuffer.cpp:331
bool seek(qint64 off) override
\reimp
Definition qbuffer.cpp:340
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
void clear()
Clears the contents of the byte array and makes it null.
\inmodule QtCore
\inmodule QtCore
Definition qfile.h:93
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual qint64 size() const
For open random-access devices, this function returns the size of the device.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
virtual qint64 bytesAvailable() const
Returns the number of bytes that are available for reading.
virtual bool seek(qint64 pos)
For random-access devices, this function sets the current position to pos, returning true on success,...
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
\inmodule QtGui
Definition qimage.h:37
@ Format_ARGB32
Definition qimage.h:47
\inmodule QtCore
\inmodule QtCore
The QNetworkReply class contains the data and headers for a request sent with QNetworkAccessManager.
bool isFinished() const
NetworkError error() const
Returns the error that was found during the processing of this request.
QVariant header(QNetworkRequest::KnownHeaders header) const
Returns the value of the known header header, if that header was sent by the remote server.
void finished()
This signal is emitted when the reply has finished processing.
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
TextPosition hitTest(int page, QPointF position)
void _q_copyFromSequentialSourceDevice()
QPointF getCharPosition(FPDF_PAGE pdfPage, FPDF_TEXTPAGE textPage, int charIndex) const
void _q_tryLoadingWithSizeFromContentHeader()
QPointer< QIODevice > device
void load(QIODevice *device, bool ownDevice)
static void fpdf_AddSegment(struct _FX_DOWNLOADHINTS *pThis, size_t offset, size_t size)
QString getText(FPDF_TEXTPAGE textPage, int startIndex, int count) const
static FPDF_BOOL fpdf_IsDataAvail(struct _FX_FILEAVAIL *pThis, size_t offset, size_t size)
QPointF mapPageToView(FPDF_PAGE pdfPage, double x, double y) const
QPdfDocument::Status status
QRectF getCharBox(FPDF_PAGE pdfPage, FPDF_TEXTPAGE textPage, int charIndex) const
bool checkPageComplete(int page)
void initiateAsyncLoadWithTotalSizeKnown(quint64 totalSize)
QScopedPointer< QIODevice > ownDevice
static constexpr QFPDFRotation toFPDFRotation(QPdfDocumentRenderOptions::Rotation rotation)
QPointer< QIODevice > sequentialSourceDevice
static int fpdf_GetBlock(void *param, unsigned long position, unsigned char *pBuf, unsigned long size)
QPdfPageModel * pageModel
QPdfDocument::Error lastError
QPointF mapViewToPage(FPDF_PAGE pdfPage, QPointF position) const
void setStatus(QPdfDocument::Status status)
The QPdfDocument class loads a PDF document and renders pages from it.
Error error() const
Returns the type of error if \l status is Error, or NoError if there is no error.
QString password
This property holds the document password.
QImage render(int page, QSize imageSize, QPdfDocumentRenderOptions options=QPdfDocumentRenderOptions())
Renders the page into a QImage of size imageSize according to the provided renderOptions.
Status status
This property holds the current status of the document.
void close()
Closes the document.
MetaDataField
This enum describes the available fields of meta data.
QAbstractListModel * pageModel
This property holds an instance of QAbstractListModel to provide page-specific metadata,...
void passwordChanged()
Q_INVOKABLE QPdfSelection getAllText(int page)
Returns all the text and its bounds on the given page.
Status
This enum describes the current status of the document.
~QPdfDocument() override
Destroys the document.
Q_INVOKABLE QPdfSelection getSelectionAtIndex(int page, int startIndex, int maxLength)
Returns information about the text on the given page that can be found beginning at the given startIn...
PageModelRole
Roles in pageModel().
int pageCount
This property holds the number of pages in the loaded document or 0 if no document is loaded.
Error
This enum describes the error while attempting the last operation on the document.
Q_INVOKABLE int pageIndexForLabel(const QString &label)
Returns the index of the page that has the label, or -1 if not found.
Q_INVOKABLE QPdfSelection getSelection(int page, QPointF start, QPointF end)
Returns information about the text on the given page that can be found between the given start and en...
QVariant metaData(MetaDataField field) const
Returns the meta data of the document for the given field.
Q_INVOKABLE QString pageLabel(int page)
Returns the page number to be used for display purposes.
void setPassword(const QString &password)
Error load(const QString &fileName)
Loads the document contents from fileName.
void statusChanged(QPdfDocument::Status status)
friend class QPdfPageModel
Q_INVOKABLE QSizeF pagePointSize(int page) const
Returns the size of page page in points (1/72 of an inch).
int rowCount(const QModelIndex &=QModelIndex()) const override
Returns the number of rows under the given parent.
QPdfPageModel(QPdfDocument *doc)
QHash< int, QByteArray > roleNames() const override
QVariant data(const QModelIndex &index, int role) const override
Returns the data stored under the given role for the item referred to by the index.
The QPdfSelection class defines a range of text that has been selected on one page in a PDF document,...
Returns a copy of the pixmap that is transformed using the given transformation transform and transfo...
Definition qpixmap.h:27
static QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags=Qt::AutoColor)
Converts the given image to a pixmap using the specified flags to control the conversion.
Definition qpixmap.cpp:1437
\inmodule QtCore\reentrant
Definition qpoint.h:217
T * data() const noexcept
Definition qpointer.h:73
The QPolygonF class provides a list of points using floating point precision.
Definition qpolygon.h:96
\inmodule QtCore\reentrant
Definition qrect.h:484
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:182
constexpr int top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:176
constexpr int left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:173
constexpr int right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:179
\inmodule QtCore
Definition qmutex.h:309
void reset(T *other=nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval< T * >())))
Deletes the existing object it is pointing to (if any), and sets its pointer to other.
const_iterator constEnd() const noexcept
Definition qset.h:143
const_iterator constFind(const T &value) const
Definition qset.h:161
\inmodule QtCore
Definition qsize.h:208
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5455
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3824
void chop(qsizetype n)
Removes n characters from the end of the string.
Definition qstring.cpp:6340
QString mid(qsizetype position, qsizetype n=-1) const &
Definition qstring.cpp:5300
static QString fromUtf16(const char16_t *, qsizetype size=-1)
Definition qstring.cpp:6045
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition qstring.cpp:5506
QString & insert(qsizetype i, QChar c)
Definition qstring.cpp:3132
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
QByteArray toUtf8() const &
Definition qstring.h:634
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
\inmodule QtCore
Definition qvariant.h:65
bool isValid() const
Returns true if the storage type of this variant is not QMetaType::UnknownType; otherwise returns fal...
Definition qvariant.h:714
qulonglong toULongLong(bool *ok=nullptr) const
Returns the variant as an unsigned long long int if the variant has type() \l QMetaType::ULongLong,...
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:31
QString text
QSet< QString >::iterator it
rect
[4]
Combined button and popup list for selecting options.
constexpr char toAsciiLower(char ch) noexcept
Definition qtools_p.h:87
@ KeepAspectRatio
@ transparent
Definition qnamespace.h:47
@ UserRole
@ DecorationRole
@ DisplayRole
@ ISODate
emscripten::val document()
Definition qwasmdom.h:49
Definition image.cpp:4
#define Q_UNLIKELY(x)
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
@ None
Definition qhash.cpp:531
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
return ret
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
#define SLOT(a)
Definition qobjectdefs.h:52
#define SIGNAL(a)
Definition qobjectdefs.h:53
GLboolean GLboolean GLboolean b
GLint GLint GLint GLint GLint x
[0]
GLuint GLfloat GLfloat GLfloat GLfloat y1
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLboolean r
[2]
GLuint GLuint end
GLuint GLfloat GLfloat GLfloat x1
GLdouble GLdouble GLdouble GLdouble top
GLenum GLenum GLsizei count
GLdouble GLdouble right
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLfloat GLfloat f
GLint left
GLuint GLsizei const GLchar * label
[43]
GLint GLint bottom
GLuint GLfloat x0
GLenum GLuint GLenum GLsizei const GLchar * buf
GLbitfield flags
GLuint start
GLenum const GLint * param
GLenum GLuint GLintptr offset
GLuint name
GLuint GLfloat GLfloat y0
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei imageSize
GLint y
GLdouble s
[6]
Definition qopenglext.h:235
GLfixed GLfixed GLfixed y2
GLsizei maxLength
GLsizei GLfixed GLfixed GLfixed GLfixed const GLubyte * bitmap
GLuint GLenum matrix
GLfixed GLfixed x2
GLdouble GLdouble t
Definition qopenglext.h:243
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLuint64EXT * result
[6]
GLenum GLsizei len
static QT_BEGIN_NAMESPACE int libraryRefCount
static const double CharacterHitTolerance
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QT_BEGIN_NAMESPACE constexpr void qSwap(T &value1, T &value2) noexcept(std::is_nothrow_swappable_v< T >)
Definition qswap.h:20
#define Q_OBJECT
#define emit
#define Q_UNUSED(x)
unsigned long long quint64
Definition qtypes.h:61
long long qint64
Definition qtypes.h:60
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
obj metaObject() -> className()
QObject::connect nullptr
QByteArray page
[45]
QTimer * timer
[3]
QReadWriteLock lock
[0]
p ry()++
p rx()++
QNetworkReply * reply
\inmodule QtCore \reentrant
Definition qchar.h:18