diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2022-05-30 11:34:17 +0200 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2022-06-04 20:55:31 +0200 |
commit | 35697c1ded0577e88e774917a8ead31d7445465a (patch) | |
tree | c1bd34898b36308e806cfe1d3beae5d90b8449e5 | |
parent | af4f03f51fc647c48968e149799cd35ab161dc55 (diff) |
Add QPdfDocument::pageLabel(int) and pageModel property
This API is available for both C++ and QML.
The pageModel makes it easier to populate item-views with per-page
information, such as thumbnails labeled with page labels.
Fixes: QTBUG-102271
Change-Id: I70df481b378efed0327e7bb89a63c7669daecc70
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
-rw-r--r-- | examples/pdf/multipage/viewer.qml | 13 | ||||
-rw-r--r-- | src/pdf/qpdfdocument.cpp | 101 | ||||
-rw-r--r-- | src/pdf/qpdfdocument.h | 15 | ||||
-rw-r--r-- | src/pdf/qpdfdocument_p.h | 3 | ||||
-rw-r--r-- | tests/auto/pdf/qpdfdocument/test.pdf | bin | 0 -> 76633 bytes | |||
-rw-r--r-- | tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp | 11 | ||||
-rw-r--r-- | tests/manual/quick/pdf/bookmarks-list.qml | 5 | ||||
-rw-r--r-- | tests/manual/quick/pdf/bookmarks.qml | 4 | ||||
-rw-r--r-- | tests/manual/quick/pdf/gridview.qml | 16 | ||||
-rw-r--r-- | tests/manual/quick/pdf/listview.qml | 2 | ||||
-rw-r--r-- | tests/manual/quick/pdf/pdfPageView.qml | 22 |
11 files changed, 168 insertions, 24 deletions
diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index cf94c099b..3a12d9700 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -371,11 +371,13 @@ ApplicationWindow { id: thumbnailsView implicitWidth: parent.width implicitHeight: parent.height - model: doc.pageCount + model: doc.pageModel cellWidth: width / 2 cellHeight: cellWidth + 10 delegate: Item { required property int index + required property string label + required property size pointSize width: thumbnailsView.cellWidth height: thumbnailsView.cellHeight Rectangle { @@ -390,11 +392,10 @@ ApplicationWindow { currentFrame: index asynchronous: true fillMode: Image.PreserveAspectFit - property size naturalSize: doc.pagePointSize(index) - property bool landscape: naturalSize.width > naturalSize.height + property bool landscape: pointSize.width > pointSize.height width: landscape ? thumbnailsView.cellWidth - 6 - : height * naturalSize.width / naturalSize.height - height: landscape ? width * naturalSize.height / naturalSize.width + : height * pointSize.width / pointSize.height + height: landscape ? width * pointSize.height / pointSize.width : thumbnailsView.cellHeight - 14 sourceSize.width: width sourceSize.height: height @@ -404,7 +405,7 @@ ApplicationWindow { id: pageNumber anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter - text: "Page " + (image.currentFrame + 1) + text: label } TapHandler { onTapped: view.goToPage(index) diff --git a/src/pdf/qpdfdocument.cpp b/src/pdf/qpdfdocument.cpp index 8e3cea2da..ee589d0e7 100644 --- a/src/pdf/qpdfdocument.cpp +++ b/src/pdf/qpdfdocument.cpp @@ -65,6 +65,52 @@ QPdfMutexLocker::QPdfMutexLocker() { } +class Q_PDF_EXPORT QPdfPageModel : public QAbstractListModel +{ + Q_OBJECT +public: + QPdfPageModel(QPdfDocument *doc) : QAbstractListModel(doc) + { + m_roleNames = QAbstractItemModel::roleNames(); + QMetaEnum rolesMetaEnum = doc->metaObject()->enumerator(doc->metaObject()->indexOfEnumerator("PageModelRole")); + for (int r = Qt::UserRole; r < int(QPdfDocument::PageModelRole::_Count); ++r) { + auto name = QByteArray(rolesMetaEnum.valueToKey(r)); + name[0] = tolower(name[0]); + m_roleNames.insert(r, name); + } + connect(doc, &QPdfDocument::statusChanged, this, [this](QPdfDocument::Status s) { + if (s == QPdfDocument::Status::Loading) + beginResetModel(); + else if (s == QPdfDocument::Status::Ready) + endResetModel(); + }); + } + + QVariant data(const QModelIndex &index, int role) const override + { + if (!index.isValid()) + return QVariant(); + switch (QPdfDocument::PageModelRole(role)) { + case QPdfDocument::PageModelRole::Label: + return document()->pageLabel(index.row()); + case QPdfDocument::PageModelRole::PointSize: + return document()->pagePointSize(index.row()); + case QPdfDocument::PageModelRole::_Count: + break; + } + return QVariant(); + } + + int rowCount(const QModelIndex & = QModelIndex()) const override { return document()->pageCount(); } + + QHash<int, QByteArray> roleNames() const override { return m_roleNames; } + +private: + QPdfDocument *document() const { return static_cast<QPdfDocument *>(parent()); } + + QHash<int, QByteArray> m_roleNames; +}; + QPdfDocumentPrivate::QPdfDocumentPrivate() : avail(nullptr) , doc(nullptr) @@ -127,6 +173,7 @@ void QPdfDocumentPrivate::clear() if (pageCount != 0) { pageCount = 0; emit q->pageCountChanged(pageCount); + emit q->pageModelChanged(); } loadComplete = false; @@ -220,6 +267,7 @@ void QPdfDocumentPrivate::load(QIODevice *newDevice, bool transferDeviceOwnershi if (newPageCount != pageCount) { pageCount = newPageCount; emit q->pageCountChanged(pageCount); + emit q->pageModelChanged(); } // If it's a local file, and the first couple of pages are available, @@ -349,6 +397,7 @@ void QPdfDocumentPrivate::checkComplete() if (newPageCount != pageCount) { pageCount = newPageCount; emit q->pageCountChanged(pageCount); + emit q->pageModelChanged(); } setStatus(QPdfDocument::Status::Ready); @@ -731,6 +780,57 @@ QSizeF QPdfDocument::pagePointSize(int page) const } /*! + \enum QPdfDocument::PageModelRole + + Roles in pageModel(). + + \value Label The page number to be used for display purposes (QString). + \value PointSize The page size in points (1/72 of an inch) (QSizeF). + \omitvalue _Count +*/ + +/*! + \property QPdfDocument::pageModel + + This property holds an instance of QAbstractListModel to provide + page-specific metadata, containing one row for each page in the document. + + \sa QPdfDocument::PageModelRole +*/ +QAbstractListModel *QPdfDocument::pageModel() +{ + if (!d->pageModel) + d->pageModel = new QPdfPageModel(this); + return d->pageModel; +} + +/*! + Returns the \a page number to be used for display purposes. + + For example, a document may have multiple sections with different numbering. + Perhaps the preface uses roman numerals, the body starts on page 1, and the + appendix starts at A1. Whenever a PDF viewer shows a page number, to avoid + confusing the user it should be the same "number" as is printed on the + corner of the page, rather than the zero-based page index that we use in + APIs (assuming the document author has made the page labels match the + printed numbers). + + If the document does not have custom page numbering, this function returns + \c {page + 1}. +*/ +QString QPdfDocument::pageLabel(int page) +{ + const unsigned long len = FPDF_GetPageLabel(d->doc, page, nullptr, 0); + if (len == 0) + return QString::number(page + 1); + QList<char16_t> buf(len); + QPdfMutexLocker lock; + FPDF_GetPageLabel(d->doc, page, buf.data(), len); + lock.unlock(); + return QString::fromUtf16(buf.constData()); +} + +/*! Renders the \a page into a QImage of size \a imageSize according to the provided \a renderOptions. @@ -964,4 +1064,5 @@ QPdfSelection QPdfDocument::getAllText(int page) QT_END_NAMESPACE +#include "qpdfdocument.moc" #include "moc_qpdfdocument.cpp" diff --git a/src/pdf/qpdfdocument.h b/src/pdf/qpdfdocument.h index 02d8294ac..398b8e365 100644 --- a/src/pdf/qpdfdocument.h +++ b/src/pdf/qpdfdocument.h @@ -43,6 +43,7 @@ #include <QtPdf/qtpdfglobal.h> #include <QtCore/qobject.h> +#include <QtCore/QAbstractListModel> #include <QtGui/qimage.h> #include <QtPdf/qpdfdocumentrenderoptions.h> #include <QtPdf/qpdfselection.h> @@ -59,6 +60,7 @@ class Q_PDF_EXPORT QPdfDocument : public QObject Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged FINAL) Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged FINAL) Q_PROPERTY(Status status READ status NOTIFY statusChanged FINAL) + Q_PROPERTY(QAbstractListModel* pageModel READ pageModel NOTIFY pageModelChanged FINAL) public: enum class Status { @@ -93,6 +95,13 @@ public: }; Q_ENUM(MetaDataField) + enum class PageModelRole { + Label = Qt::UserRole, + PointSize, + _Count + }; + Q_ENUM(PageModelRole) + QPdfDocument() : QPdfDocument(nullptr) {} explicit QPdfDocument(QObject *parent); ~QPdfDocument() override; @@ -115,6 +124,10 @@ public: Q_INVOKABLE QSizeF pagePointSize(int page) const; + Q_INVOKABLE QString pageLabel(int page); + + QAbstractListModel *pageModel(); + QImage render(int page, QSize imageSize, QPdfDocumentRenderOptions options = QPdfDocumentRenderOptions()); Q_INVOKABLE QPdfSelection getSelection(int page, QPointF start, QPointF end); @@ -126,11 +139,13 @@ Q_SIGNALS: void passwordRequired(); void statusChanged(QPdfDocument::Status status); void pageCountChanged(int pageCount); + void pageModelChanged(); private: friend struct QPdfBookmarkModelPrivate; friend class QPdfFile; friend class QPdfLinkModelPrivate; + friend class QPdfPageModel; friend class QPdfSearchModel; friend class QPdfSearchModelPrivate; friend class QQuickPdfSelection; diff --git a/src/pdf/qpdfdocument_p.h b/src/pdf/qpdfdocument_p.h index 4f8fc5993..3fc4c9926 100644 --- a/src/pdf/qpdfdocument_p.h +++ b/src/pdf/qpdfdocument_p.h @@ -71,6 +71,8 @@ public: QPdfMutexLocker(); }; +class QPdfPageModel; + class Q_PDF_PRIVATE_EXPORT QPdfDocumentPrivate: public FPDF_FILEACCESS, public FX_FILEAVAIL, public FX_DOWNLOADHINTS { public: @@ -78,6 +80,7 @@ public: ~QPdfDocumentPrivate(); QPdfDocument *q; + QPdfPageModel *pageModel = nullptr; FPDF_AVAIL avail; FPDF_DOCUMENT doc; diff --git a/tests/auto/pdf/qpdfdocument/test.pdf b/tests/auto/pdf/qpdfdocument/test.pdf Binary files differnew file mode 100644 index 000000000..0832dfbed --- /dev/null +++ b/tests/auto/pdf/qpdfdocument/test.pdf diff --git a/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp b/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp index b44edc703..df437349e 100644 --- a/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp +++ b/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp @@ -69,6 +69,7 @@ private slots: void status(); void passwordClearedOnClose(); void metaData(); + void pageLabels(); private: void consistencyCheck(QPdfDocument &doc) const; @@ -415,6 +416,16 @@ void tst_QPdfDocument::metaData() QCOMPARE(doc.metaData(QPdfDocument::MetaDataField::ModificationDate).toDateTime(), QDateTime(QDate(2016, 8, 8), QTime(8, 3, 6), Qt::UTC)); } +void tst_QPdfDocument::pageLabels() +{ + QPdfDocument doc; + QCOMPARE(doc.load(QFINDTESTDATA("test.pdf")), QPdfDocument::Error::None); + QCOMPARE(doc.pageCount(), 3); + QCOMPARE(doc.pageLabel(0), "Qt"); + QCOMPARE(doc.pageLabel(1), "1"); + QCOMPARE(doc.pageLabel(2), "i"); // i of the tiger! +} + QTEST_MAIN(tst_QPdfDocument) #include "tst_qpdfdocument.moc" diff --git a/tests/manual/quick/pdf/bookmarks-list.qml b/tests/manual/quick/pdf/bookmarks-list.qml index a4c030ea5..fd6c38b54 100644 --- a/tests/manual/quick/pdf/bookmarks-list.qml +++ b/tests/manual/quick/pdf/bookmarks-list.qml @@ -87,11 +87,10 @@ ApplicationWindow { width: parent.width text: model.title background: Item { } - onClicked: image.currentFrame = pageNumber + onClicked: image.currentFrame = page } model: PdfBookmarkModel { document: root.doc - structureMode: PdfBookmarkModel.ListMode } } } @@ -179,6 +178,6 @@ ApplicationWindow { } Text { anchors { bottom: parent.bottom; right: parent.right; margins: 6 } - text: "page " + (image.currentFrame + 1) + " of " + doc.pageCount + text: "page " + doc.pageLabel(image.currentFrame) + " of " + doc.pageCount } } diff --git a/tests/manual/quick/pdf/bookmarks.qml b/tests/manual/quick/pdf/bookmarks.qml index 0d7a84dee..230e3daf4 100644 --- a/tests/manual/quick/pdf/bookmarks.qml +++ b/tests/manual/quick/pdf/bookmarks.qml @@ -88,7 +88,7 @@ ApplicationWindow { clip: true delegate: TreeViewDelegate { width: parent.width - onClicked: image.currentFrame = pageNumber + onClicked: image.currentFrame = page } model: PdfBookmarkModel { document: root.doc @@ -189,6 +189,6 @@ ApplicationWindow { } Label { anchors { bottom: parent.bottom; right: parent.right; margins: 6 } - text: "page " + (image.currentFrame + 1) + " of " + doc.pageCount + text: "page " + doc.pageLabel(image.currentFrame) + " of " + doc.pageCount } } diff --git a/tests/manual/quick/pdf/gridview.qml b/tests/manual/quick/pdf/gridview.qml index 48e34732a..4ad9e4676 100644 --- a/tests/manual/quick/pdf/gridview.qml +++ b/tests/manual/quick/pdf/gridview.qml @@ -68,10 +68,13 @@ Window { id: view anchors.fill: parent anchors.margins: 10 - model: doc.pageCount + model: doc.pageModel cellWidth: cellSize cellHeight: cellSize delegate: Item { + required property int index + required property string label + required property size pointSize width: view.cellWidth height: view.cellHeight Rectangle { @@ -86,10 +89,11 @@ Window { currentFrame: index asynchronous: true fillMode: Image.PreserveAspectFit - property size naturalSize: doc.pagePointSize(index) - property bool landscape: naturalSize.width > naturalSize.height - width: landscape ? Math.min(view.cellWidth, naturalSize.width) : height * naturalSize.width / naturalSize.height - height: landscape ? width * naturalSize.height / naturalSize.width : Math.min(view.cellHeight - pageNumber.height, naturalSize.height) + property bool landscape: pointSize.width > pointSize.height + width: landscape ? Math.min(view.cellWidth, pointSize.width) + : height * pointSize.width / pointSize.height + height: landscape ? width * pointSize.height / pointSize.width + : Math.min(view.cellHeight - pageNumber.height, pointSize.height) sourceSize.width: width sourceSize.height: height } @@ -98,7 +102,7 @@ Window { id: pageNumber anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter - text: "Page " + (image.currentFrame + 1) + text: "Page " + label } } } diff --git a/tests/manual/quick/pdf/listview.qml b/tests/manual/quick/pdf/listview.qml index 65da5d483..3201908ac 100644 --- a/tests/manual/quick/pdf/listview.qml +++ b/tests/manual/quick/pdf/listview.qml @@ -81,7 +81,7 @@ Window { } } Text { - text: "Page " + (image.currentFrame + 1) + text: "Page " + doc.pageLabel(image.currentFrame) } } } diff --git a/tests/manual/quick/pdf/pdfPageView.qml b/tests/manual/quick/pdf/pdfPageView.qml index 393d1f774..7038e61d0 100644 --- a/tests/manual/quick/pdf/pdfPageView.qml +++ b/tests/manual/quick/pdf/pdfPageView.qml @@ -136,18 +136,28 @@ ApplicationWindow { } SpinBox { id: currentPageSB - from: 1 + from: 0 to: document.pageCount editable: true - value: pageView.currentPage + 1 - onValueModified: pageView.goToPage(value - 1) + value: pageView.currentPage + onValueModified: pageView.goToPage(value) + + textFromValue: function(value) { return document.pageLabel(value) } + valueFromText: function(text) { + for (var i = 0; i < document.pageCount; ++i) { + if (document.pageLabel(i).toLowerCase().indexOf(text.toLowerCase()) === 0) + return i + } + return spinBox.value + } + Shortcut { sequence: StandardKey.MoveToPreviousPage - onActivated: pageView.goToPage(currentPageSB.value - 2) + onActivated: pageView.goToPage(currentPageSB.value - 1) } Shortcut { sequence: StandardKey.MoveToNextPage - onActivated: pageView.goToPage(currentPageSB.value) + onActivated: pageView.goToPage(currentPageSB.value + 1) } } ToolButton { @@ -331,7 +341,7 @@ ApplicationWindow { ScrollBar.vertical: ScrollBar { } delegate: ItemDelegate { width: parent ? parent.width : 0 - text: "page " + (page + 1) + ": " + contextBefore + pageView.searchString + contextAfter + text: "page " + document.pageLabel(page) + ": " + contextBefore + pageView.searchString + contextAfter highlighted: ListView.isCurrentItem onClicked: { searchResultsList.currentIndex = index |