summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2022-05-30 11:34:17 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2022-06-04 20:55:31 +0200
commit35697c1ded0577e88e774917a8ead31d7445465a (patch)
treec1bd34898b36308e806cfe1d3beae5d90b8449e5
parentaf4f03f51fc647c48968e149799cd35ab161dc55 (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.qml13
-rw-r--r--src/pdf/qpdfdocument.cpp101
-rw-r--r--src/pdf/qpdfdocument.h15
-rw-r--r--src/pdf/qpdfdocument_p.h3
-rw-r--r--tests/auto/pdf/qpdfdocument/test.pdfbin0 -> 76633 bytes
-rw-r--r--tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp11
-rw-r--r--tests/manual/quick/pdf/bookmarks-list.qml5
-rw-r--r--tests/manual/quick/pdf/bookmarks.qml4
-rw-r--r--tests/manual/quick/pdf/gridview.qml16
-rw-r--r--tests/manual/quick/pdf/listview.qml2
-rw-r--r--tests/manual/quick/pdf/pdfPageView.qml22
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
new file mode 100644
index 000000000..0832dfbed
--- /dev/null
+++ b/tests/auto/pdf/qpdfdocument/test.pdf
Binary files differ
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