diff options
Diffstat (limited to 'src/pdf')
63 files changed, 8285 insertions, 0 deletions
diff --git a/src/pdf/api/qpdfbookmarkmodel.h b/src/pdf/api/qpdfbookmarkmodel.h new file mode 100644 index 000000000..8e06a1547 --- /dev/null +++ b/src/pdf/api/qpdfbookmarkmodel.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFBOOKMARKMODEL_H +#define QPDFBOOKMARKMODEL_H + +#include <QtPdf/qtpdfglobal.h> +#include <QtCore/qabstractitemmodel.h> + +QT_BEGIN_NAMESPACE + +class QPdfDocument; +class QPdfBookmarkModelPrivate; + +class Q_PDF_EXPORT QPdfBookmarkModel : public QAbstractItemModel +{ + Q_OBJECT + + Q_PROPERTY(QPdfDocument* document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(StructureMode structureMode READ structureMode WRITE setStructureMode NOTIFY structureModeChanged) + +public: + enum StructureMode + { + TreeMode, + ListMode + }; + Q_ENUM(StructureMode) + + enum Role + { + TitleRole = Qt::DisplayRole, + LevelRole = Qt::UserRole, + PageNumberRole + }; + Q_ENUM(Role) + + explicit QPdfBookmarkModel(QObject *parent = nullptr); + + QPdfDocument* document() const; + void setDocument(QPdfDocument *document); + + StructureMode structureMode() const; + void setStructureMode(StructureMode mode); + + QVariant data(const QModelIndex &index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QHash<int, QByteArray> roleNames() const override; + +Q_SIGNALS: + void documentChanged(QPdfDocument *document); + void structureModeChanged(QPdfBookmarkModel::StructureMode structureMode); + +private: + Q_DECLARE_PRIVATE(QPdfBookmarkModel) + + Q_PRIVATE_SLOT(d_func(), void _q_documentStatusChanged()) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/pdf/api/qpdfdestination.h b/src/pdf/api/qpdfdestination.h new file mode 100644 index 000000000..f9c186ff6 --- /dev/null +++ b/src/pdf/api/qpdfdestination.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFDESTINATION_H +#define QPDFDESTINATION_H + +#include <QtPdf/qtpdfglobal.h> +#include <QtCore/qdebug.h> +#include <QtCore/qobject.h> +#include <QtCore/qpoint.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_NAMESPACE + +class QPdfDestinationPrivate; + +class Q_PDF_EXPORT QPdfDestination +{ + Q_GADGET + Q_PROPERTY(bool valid READ isValid) + Q_PROPERTY(int page READ page) + Q_PROPERTY(QPointF location READ location) + Q_PROPERTY(qreal zoom READ zoom) + +public: + ~QPdfDestination(); + QPdfDestination(const QPdfDestination &other); + QPdfDestination &operator=(const QPdfDestination &other); + QPdfDestination(QPdfDestination &&other) noexcept; + QPdfDestination &operator=(QPdfDestination &&other) noexcept { swap(other); return *this; } + void swap(QPdfDestination &other) noexcept { d.swap(other.d); } + bool isValid() const; + int page() const; + QPointF location() const; + qreal zoom() const; + +protected: + QPdfDestination(); + QPdfDestination(int page, QPointF location, qreal zoom); + QPdfDestination(QPdfDestinationPrivate *d); + friend class QPdfDocument; + friend class QQuickPdfNavigationStack; + +protected: + QExplicitlySharedDataPointer<QPdfDestinationPrivate> d; +}; + +Q_PDF_EXPORT QDebug operator<<(QDebug, const QPdfDestination &); + +QT_END_NAMESPACE + +#endif // QPDFDESTINATION_H diff --git a/src/pdf/api/qpdfdestination_p.h b/src/pdf/api/qpdfdestination_p.h new file mode 100644 index 000000000..3520fb795 --- /dev/null +++ b/src/pdf/api/qpdfdestination_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFDESTINATION_P_H +#define QPDFDESTINATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QPointF> + +QT_BEGIN_NAMESPACE + +class QPdfDestinationPrivate : public QSharedData +{ +public: + QPdfDestinationPrivate() = default; + QPdfDestinationPrivate(int page, QPointF location, qreal zoom) + : page(page), + location(location), + zoom(zoom) { } + + int page = -1; + QPointF location; + qreal zoom = 1; +}; + +QT_END_NAMESPACE + +#endif // QPDFDESTINATION_P_H diff --git a/src/pdf/api/qpdfdocument.h b/src/pdf/api/qpdfdocument.h new file mode 100644 index 000000000..f80a7832b --- /dev/null +++ b/src/pdf/api/qpdfdocument.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFDOCUMENT_H +#define QPDFDOCUMENT_H + +#include <QtPdf/qtpdfglobal.h> + +#include <QtCore/qobject.h> +#include <QtGui/qimage.h> +#include <QtPdf/qpdfdocumentrenderoptions.h> +#include <QtPdf/qpdfselection.h> + +QT_BEGIN_NAMESPACE + +class QPdfDocumentPrivate; +class QNetworkReply; + +class Q_PDF_EXPORT QPdfDocument : public QObject +{ + Q_OBJECT + + 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) + +public: + enum Status { + Null, + Loading, + Ready, + Unloading, + Error + }; + Q_ENUM(Status) + + enum DocumentError { + NoError, + UnknownError, + DataNotYetAvailableError, + FileNotFoundError, + InvalidFileFormatError, + IncorrectPasswordError, + UnsupportedSecuritySchemeError + }; + Q_ENUM(DocumentError) + + enum MetaDataField { + Title, + Subject, + Author, + Keywords, + Producer, + Creator, + CreationDate, + ModificationDate + }; + Q_ENUM(MetaDataField) + + explicit QPdfDocument(QObject *parent = nullptr); + ~QPdfDocument(); + + DocumentError load(const QString &fileName); + + Status status() const; + + void load(QIODevice *device); + void setPassword(const QString &password); + QString password() const; + + QVariant metaData(MetaDataField field) const; + + DocumentError error() const; + + void close(); + + int pageCount() const; + + QSizeF pageSize(int page) const; + + QImage render(int page, QSize imageSize, QPdfDocumentRenderOptions options = QPdfDocumentRenderOptions()); + + Q_INVOKABLE QPdfSelection getSelection(int page, QPointF start, QPointF end); + Q_INVOKABLE QPdfSelection getAllText(int page); + +Q_SIGNALS: + void passwordChanged(); + void passwordRequired(); + void statusChanged(QPdfDocument::Status status); + void pageCountChanged(int pageCount); + +private: + friend class QPdfBookmarkModelPrivate; + friend class QPdfLinkModelPrivate; + friend class QPdfSearchModel; + friend class QPdfSearchModelPrivate; + + Q_PRIVATE_SLOT(d, void _q_tryLoadingWithSizeFromContentHeader()) + Q_PRIVATE_SLOT(d, void _q_copyFromSequentialSourceDevice()) + QScopedPointer<QPdfDocumentPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QPDFDOCUMENT_H diff --git a/src/pdf/api/qpdfdocument_p.h b/src/pdf/api/qpdfdocument_p.h new file mode 100644 index 000000000..b69b6f19e --- /dev/null +++ b/src/pdf/api/qpdfdocument_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFDOCUMENT_P_H +#define QPDFDOCUMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qpdfdocument.h" + +#include "third_party/pdfium/public/fpdfview.h" +#include "third_party/pdfium/public/fpdf_dataavail.h" + +#include <QtCore/qbuffer.h> +#include <QtCore/qmutex.h> +#include <QtCore/qpointer.h> +#include <QtNetwork/qnetworkreply.h> + +QT_BEGIN_NAMESPACE + +class QPdfMutexLocker : public QMutexLocker +{ +public: + QPdfMutexLocker(); +}; + +class QPdfDocumentPrivate: public FPDF_FILEACCESS, public FX_FILEAVAIL, public FX_DOWNLOADHINTS +{ +public: + QPdfDocumentPrivate(); + ~QPdfDocumentPrivate(); + + QPdfDocument *q; + + FPDF_AVAIL avail; + FPDF_DOCUMENT doc; + bool loadComplete; + + QPointer<QIODevice> device; + QScopedPointer<QIODevice> ownDevice; + QBuffer asyncBuffer; + QPointer<QIODevice> sequentialSourceDevice; + QByteArray password; + + QPdfDocument::Status status; + QPdfDocument::DocumentError lastError; + int pageCount; + + void clear(); + + void load(QIODevice *device, bool ownDevice); + void loadAsync(QIODevice *device); + + void _q_tryLoadingWithSizeFromContentHeader(); + void initiateAsyncLoadWithTotalSizeKnown(quint64 totalSize); + void _q_copyFromSequentialSourceDevice(); + void tryLoadDocument(); + void checkComplete(); + bool checkPageComplete(int page); + void setStatus(QPdfDocument::Status status); + + static FPDF_BOOL fpdf_IsDataAvail(struct _FX_FILEAVAIL* pThis, size_t offset, size_t size); + static int fpdf_GetBlock(void* param, unsigned long position, unsigned char* pBuf, unsigned long size); + static void fpdf_AddSegment(struct _FX_DOWNLOADHINTS* pThis, size_t offset, size_t size); + void updateLastError(); + QString getText(FPDF_TEXTPAGE textPage, int startIndex, int count); +}; + +QT_END_NAMESPACE + +#endif // QPDFDOCUMENT_P_H diff --git a/src/pdf/api/qpdfdocumentrenderoptions.h b/src/pdf/api/qpdfdocumentrenderoptions.h new file mode 100644 index 000000000..cafb4716f --- /dev/null +++ b/src/pdf/api/qpdfdocumentrenderoptions.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFDOCUMENTRENDEROPTIONS_H +#define QPDFDOCUMENTRENDEROPTIONS_H + +#include <QtPdf/qpdfnamespace.h> +#include <QtCore/qobject.h> +#include <QtCore/qrect.h> + +QT_BEGIN_NAMESPACE + +class QPdfDocumentRenderOptions +{ +public: + Q_DECL_CONSTEXPR QPdfDocumentRenderOptions() noexcept : m_renderFlags(0), m_rotation(0), m_reserved(0) {} + + Q_DECL_CONSTEXPR QPdf::Rotation rotation() const noexcept { return static_cast<QPdf::Rotation>(m_rotation); } + Q_DECL_RELAXED_CONSTEXPR void setRotation(QPdf::Rotation r) noexcept { m_rotation = r; } + + Q_DECL_CONSTEXPR QPdf::RenderFlags renderFlags() const noexcept { return static_cast<QPdf::RenderFlags>(m_renderFlags); } + Q_DECL_RELAXED_CONSTEXPR void setRenderFlags(QPdf::RenderFlags r) noexcept { m_renderFlags = r; } + + Q_DECL_CONSTEXPR QRect scaledClipRect() const noexcept { return m_clipRect; } + Q_DECL_RELAXED_CONSTEXPR void setScaledClipRect(const QRect &r) noexcept { m_clipRect = r; } + + Q_DECL_CONSTEXPR QSize scaledSize() const noexcept { return m_scaledSize; } + Q_DECL_RELAXED_CONSTEXPR void setScaledSize(const QSize &s) noexcept { m_scaledSize = s; } + +private: + friend Q_DECL_CONSTEXPR inline bool operator==(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) noexcept; + + QRect m_clipRect; + QSize m_scaledSize; + + quint32 m_renderFlags : 8; + quint32 m_rotation : 3; + quint32 m_reserved : 21; + quint32 m_reserved2 = 0; +}; + +Q_DECLARE_TYPEINFO(QPdfDocumentRenderOptions, Q_PRIMITIVE_TYPE); + +Q_DECL_CONSTEXPR inline bool operator==(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) noexcept +{ + return lhs.m_clipRect == rhs.m_clipRect && lhs.m_scaledSize == rhs.m_scaledSize && + lhs.m_renderFlags == rhs.m_renderFlags && lhs.m_rotation == rhs.m_rotation && + lhs.m_reserved == rhs.m_reserved && lhs.m_reserved2 == rhs.m_reserved2; // fix -Wunused-private-field +} + +Q_DECL_CONSTEXPR inline bool operator!=(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) noexcept +{ + return !operator==(lhs, rhs); +} + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPdfDocumentRenderOptions) + +#endif // QPDFDOCUMENTRENDEROPTIONS_H diff --git a/src/pdf/api/qpdflinkmodel_p.h b/src/pdf/api/qpdflinkmodel_p.h new file mode 100644 index 000000000..cf9c0aad4 --- /dev/null +++ b/src/pdf/api/qpdflinkmodel_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFLINKMODEL_P_H +#define QPDFLINKMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qtpdfglobal.h" +#include "qpdfdocument.h" + +#include <QObject> +#include <QAbstractListModel> + +QT_BEGIN_NAMESPACE + +class QPdfLinkModelPrivate; + +class Q_PDF_EXPORT QPdfLinkModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(QPdfDocument *document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(int page READ page WRITE setPage NOTIFY pageChanged) + +public: + enum class Role : int { + Rect = Qt::UserRole, + Url, + Page, + Location, + Zoom, + _Count + }; + Q_ENUM(Role) + explicit QPdfLinkModel(QObject *parent = nullptr); + ~QPdfLinkModel(); + + QPdfDocument *document() const; + + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + int page() const; + +public Q_SLOTS: + void setDocument(QPdfDocument *document); + void setPage(int page); + +Q_SIGNALS: + void documentChanged(); + void pageChanged(int page); + +private Q_SLOTS: + void onStatusChanged(QPdfDocument::Status status); + +private: + QHash<int, QByteArray> m_roleNames; + Q_DECLARE_PRIVATE(QPdfLinkModel) +}; + +QT_END_NAMESPACE + +#endif // QPDFLINKMODEL_P_H diff --git a/src/pdf/api/qpdflinkmodel_p_p.h b/src/pdf/api/qpdflinkmodel_p_p.h new file mode 100644 index 000000000..3e44f1651 --- /dev/null +++ b/src/pdf/api/qpdflinkmodel_p_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFLINKMODEL_P_P_H +#define QPDFLINKMODEL_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qpdflinkmodel_p.h" +#include <private/qabstractitemmodel_p.h> + +#include "third_party/pdfium/public/fpdfview.h" + +#include <QUrl> + +QT_BEGIN_NAMESPACE + +class QPdfLinkModelPrivate: public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QPdfLinkModel) + +public: + QPdfLinkModelPrivate(); + + void update(); + + struct Link { + // where it is on the current page + QRectF rect; + int textStart = -1; + int textCharCount = 0; + // destination inside PDF + int page = -1; // -1 means look at the url instead + QPointF location; + qreal zoom = 1; + // web destination + QUrl url; + + QString toString() const; + }; + + QPdfDocument *document = nullptr; + QVector<Link> links; + int page = 0; +}; + +QT_END_NAMESPACE + +#endif // QPDFLINKMODEL_P_P_H diff --git a/src/pdf/api/qpdfnamespace.h b/src/pdf/api/qpdfnamespace.h new file mode 100644 index 000000000..e76d0abd9 --- /dev/null +++ b/src/pdf/api/qpdfnamespace.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFNAMESPACE_H +#define QPDFNAMESPACE_H + +#include <QtCore/qobject.h> + +QT_BEGIN_NAMESPACE + +namespace QPdf { + Q_NAMESPACE + + enum Rotation { + Rotate0, + Rotate90, + Rotate180, + Rotate270 + }; + Q_ENUM_NS(Rotation) + + enum RenderFlag { + NoRenderFlags = 0x000, + RenderAnnotations = 0x001, + RenderOptimizedForLcd = 0x002, + RenderGrayscale = 0x004, + RenderForceHalftone = 0x008, + RenderTextAliased = 0x010, + RenderImageAliased = 0x020, + RenderPathAliased = 0x040 + }; + Q_FLAG_NS(RenderFlag) + Q_DECLARE_FLAGS(RenderFlags, RenderFlag) +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QPdf::RenderFlags) + +QT_END_NAMESPACE +#endif diff --git a/src/pdf/api/qpdfpagenavigation.h b/src/pdf/api/qpdfpagenavigation.h new file mode 100644 index 000000000..0f416bf77 --- /dev/null +++ b/src/pdf/api/qpdfpagenavigation.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFPAGENAVIGATION_H +#define QPDFPAGENAVIGATION_H + +#include <QtPdf/qtpdfglobal.h> +#include <QtCore/qobject.h> + +QT_BEGIN_NAMESPACE + +class QPdfDocument; +class QPdfPageNavigationPrivate; + +class Q_PDF_EXPORT QPdfPageNavigation : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPdfDocument* document READ document WRITE setDocument NOTIFY documentChanged) + + Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged) + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged) + Q_PROPERTY(bool canGoToPreviousPage READ canGoToPreviousPage NOTIFY canGoToPreviousPageChanged) + Q_PROPERTY(bool canGoToNextPage READ canGoToNextPage NOTIFY canGoToNextPageChanged) + +public: + explicit QPdfPageNavigation(QObject *parent = nullptr); + ~QPdfPageNavigation(); + + QPdfDocument* document() const; + void setDocument(QPdfDocument *document); + + int currentPage() const; + void setCurrentPage(int currentPage); + + int pageCount() const; + + bool canGoToPreviousPage() const; + bool canGoToNextPage() const; + +public Q_SLOTS: + void goToPreviousPage(); + void goToNextPage(); + +Q_SIGNALS: + void documentChanged(QPdfDocument *document); + void currentPageChanged(int currentPage); + void pageCountChanged(int pageCount); + void canGoToPreviousPageChanged(bool canGo); + void canGoToNextPageChanged(bool canGo); + +private: + Q_DECLARE_PRIVATE(QPdfPageNavigation) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/pdf/api/qpdfpagerenderer.h b/src/pdf/api/qpdfpagerenderer.h new file mode 100644 index 000000000..c7b8de0df --- /dev/null +++ b/src/pdf/api/qpdfpagerenderer.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFPAGERENDERER_H +#define QPDFPAGERENDERER_H + +#include <QtPdf/qtpdfglobal.h> + +#include <QtCore/qobject.h> +#include <QtCore/qsize.h> +#include <QtPdf/qpdfdocumentrenderoptions.h> + +QT_BEGIN_NAMESPACE + +class QPdfDocument; +class QPdfPageRendererPrivate; + +class Q_PDF_EXPORT QPdfPageRenderer : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPdfDocument* document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(RenderMode renderMode READ renderMode WRITE setRenderMode NOTIFY renderModeChanged) + +public: + enum class RenderMode + { + MultiThreaded, + SingleThreaded + }; + Q_ENUM(RenderMode) + + explicit QPdfPageRenderer(QObject *parent = nullptr); + ~QPdfPageRenderer() override; + + RenderMode renderMode() const; + void setRenderMode(RenderMode mode); + + QPdfDocument* document() const; + void setDocument(QPdfDocument *document); + + quint64 requestPage(int pageNumber, QSize imageSize, + QPdfDocumentRenderOptions options = QPdfDocumentRenderOptions()); + +Q_SIGNALS: + void documentChanged(QPdfDocument *document); + void renderModeChanged(RenderMode renderMode); + + void pageRendered(int pageNumber, QSize imageSize, const QImage &image, + QPdfDocumentRenderOptions options, quint64 requestId); + +private: + Q_DECLARE_PRIVATE(QPdfPageRenderer) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/pdf/api/qpdfsearchmodel.h b/src/pdf/api/qpdfsearchmodel.h new file mode 100644 index 000000000..eb0fb831f --- /dev/null +++ b/src/pdf/api/qpdfsearchmodel.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFSEARCHMODEL_H +#define QPDFSEARCHMODEL_H + +#include <QtPdf/qtpdfglobal.h> + +#include <QtCore/qabstractitemmodel.h> +#include <QtPdf/qpdfdocument.h> +#include <QtPdf/qpdfsearchresult.h> + +QT_BEGIN_NAMESPACE + +class QPdfSearchModelPrivate; + +class Q_PDF_EXPORT QPdfSearchModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(QPdfDocument *document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY searchStringChanged) + +public: + enum class Role : int { + Page = Qt::UserRole, + IndexOnPage, + Location, + ContextBefore, + ContextAfter, + _Count + }; + Q_ENUM(Role) + explicit QPdfSearchModel(QObject *parent = nullptr); + ~QPdfSearchModel(); + + QVector<QPdfSearchResult> resultsOnPage(int page) const; + QPdfSearchResult resultAtIndex(int index) const; + + QPdfDocument *document() const; + QString searchString() const; + + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + +public Q_SLOTS: + void setSearchString(QString searchString); + void setDocument(QPdfDocument *document); + +Q_SIGNALS: + void documentChanged(); + void searchStringChanged(); + +protected: + void updatePage(int page); + void timerEvent(QTimerEvent *event) override; + +private: + QHash<int, QByteArray> m_roleNames; + Q_DECLARE_PRIVATE(QPdfSearchModel) +}; + +QT_END_NAMESPACE + +#endif // QPDFSEARCHMODEL_H diff --git a/src/pdf/api/qpdfsearchmodel_p.h b/src/pdf/api/qpdfsearchmodel_p.h new file mode 100644 index 000000000..2a23706b2 --- /dev/null +++ b/src/pdf/api/qpdfsearchmodel_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFSEARCHMODEL_P_H +#define QPDFSEARCHMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qpdfsearchmodel.h" +#include "qpdfsearchresult_p.h" +#include <private/qabstractitemmodel_p.h> + +#include "third_party/pdfium/public/fpdfview.h" + +QT_BEGIN_NAMESPACE + +class QPdfSearchModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QPdfSearchModel) + +public: + QPdfSearchModelPrivate(); + void clearResults(); + bool doSearch(int page); + + struct PageAndIndex { + int page; + int index; + }; + PageAndIndex pageAndIndexForResult(int resultIndex); + int rowsBeforePage(int page); + + QPdfDocument *document = nullptr; + QString searchString; + QVector<bool> pagesSearched; + QVector<QVector<QPdfSearchResult>> searchResults; + int rowCountSoFar = 0; + int updateTimerId = -1; + int nextPageToUpdate = 0; +}; + +QT_END_NAMESPACE + +#endif // QPDFSEARCHMODEL_P_H diff --git a/src/pdf/api/qpdfsearchresult.h b/src/pdf/api/qpdfsearchresult.h new file mode 100644 index 000000000..2dfca2dc4 --- /dev/null +++ b/src/pdf/api/qpdfsearchresult.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFSEARCHRESULT_H +#define QPDFSEARCHRESULT_H + +#include <QtCore/qdebug.h> +#include <QtCore/qrect.h> +#include <QtCore/qvector.h> +#include <QtPdf/qpdfdestination.h> + +QT_BEGIN_NAMESPACE + +class QPdfSearchResultPrivate; + +class Q_PDF_EXPORT QPdfSearchResult : public QPdfDestination +{ + Q_GADGET + Q_PROPERTY(QString contextBefore READ contextBefore) + Q_PROPERTY(QString contextAfter READ contextAfter) + Q_PROPERTY(QVector<QRectF> rectangles READ rectangles) + +public: + QPdfSearchResult(); + ~QPdfSearchResult() {} + + QString contextBefore() const; + QString contextAfter() const; + QVector<QRectF> rectangles() const; + +private: + QPdfSearchResult(int page, QVector<QRectF> rects, QString contextBefore, QString contextAfter); + QPdfSearchResult(QPdfSearchResultPrivate *d); + friend class QPdfDocument; + friend class QPdfSearchModelPrivate; + friend class QQuickPdfNavigationStack; +}; + +Q_PDF_EXPORT QDebug operator<<(QDebug, const QPdfSearchResult &); + +QT_END_NAMESPACE + +#endif // QPDFSEARCHRESULT_H diff --git a/src/pdf/api/qpdfsearchresult_p.h b/src/pdf/api/qpdfsearchresult_p.h new file mode 100644 index 000000000..615dce4e0 --- /dev/null +++ b/src/pdf/api/qpdfsearchresult_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFSEARCHRESULT_P_H +#define QPDFSEARCHRESULT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qpdfdestination_p.h" + +QT_BEGIN_NAMESPACE + +class QPdfSearchResultPrivate : public QPdfDestinationPrivate +{ +public: + QPdfSearchResultPrivate() = default; + QPdfSearchResultPrivate(int page, QVector<QRectF> rects, QString contextBefore, QString contextAfter) : + QPdfDestinationPrivate(page, rects.first().topLeft(), 0), + contextBefore(contextBefore), + contextAfter(contextAfter), + rects(rects) {} + + QString contextBefore; + QString contextAfter; + QVector<QRectF> rects; +}; + +QT_END_NAMESPACE + +#endif // QPDFSEARCHRESULT_P_H diff --git a/src/pdf/api/qpdfselection.h b/src/pdf/api/qpdfselection.h new file mode 100644 index 000000000..5a6a1cddc --- /dev/null +++ b/src/pdf/api/qpdfselection.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFSELECTION_H +#define QPDFSELECTION_H + +#include <QtPdf/qtpdfglobal.h> + +#include <QtCore/qobject.h> +#include <QtCore/qshareddata.h> +#include <QtGui/qclipboard.h> +#include <QtGui/qpolygon.h> + +QT_BEGIN_NAMESPACE + +class QPdfSelectionPrivate; + +class Q_PDF_EXPORT QPdfSelection +{ + Q_GADGET + Q_PROPERTY(bool valid READ isValid) + Q_PROPERTY(QVector<QPolygonF> bounds READ bounds) + Q_PROPERTY(QString text READ text) + +public: + ~QPdfSelection(); + QPdfSelection(const QPdfSelection &other); + QPdfSelection &operator=(const QPdfSelection &other); + QPdfSelection(QPdfSelection &&other) noexcept; + QPdfSelection &operator=(QPdfSelection &&other) noexcept { swap(other); return *this; } + void swap(QPdfSelection &other) noexcept { d.swap(other.d); } + bool isValid() const; + QVector<QPolygonF> bounds() const; + QString text() const; +#if QT_CONFIG(clipboard) + void copyToClipboard(QClipboard::Mode mode = QClipboard::Clipboard) const; +#endif + +private: + QPdfSelection(); + QPdfSelection(const QString &text, QVector<QPolygonF> bounds); + QPdfSelection(QPdfSelectionPrivate *d); + friend class QPdfDocument; + friend class QQuickPdfSelection; + +private: + QExplicitlySharedDataPointer<QPdfSelectionPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QPDFSELECTION_H diff --git a/src/pdf/api/qpdfselection_p.h b/src/pdf/api/qpdfselection_p.h new file mode 100644 index 000000000..37145f7f9 --- /dev/null +++ b/src/pdf/api/qpdfselection_p.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFSELECTION_P_H +#define QPDFSELECTION_P_H + +#include <QPolygonF> +#include <QVector> + +QT_BEGIN_NAMESPACE + +class QPdfSelectionPrivate : public QSharedData +{ +public: + QPdfSelectionPrivate() = default; + QPdfSelectionPrivate(const QString &text, QVector<QPolygonF> bounds) + : text(text), + bounds(bounds) { } + + QString text; + QVector<QPolygonF> bounds; +}; + +QT_END_NAMESPACE + +#endif // QPDFSELECTION_P_H diff --git a/src/pdf/api/qtpdfglobal.h b/src/pdf/api/qtpdfglobal.h new file mode 100644 index 000000000..223ec4bcb --- /dev/null +++ b/src/pdf/api/qtpdfglobal.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTPDFGLOBAL_H +#define QTPDFGLOBAL_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +#ifndef Q_PDF_EXPORT +# ifndef QT_STATIC +# if defined(QT_BUILD_PDF_LIB) +# define Q_PDF_EXPORT Q_DECL_EXPORT +# else +# define Q_PDF_EXPORT Q_DECL_IMPORT +# endif +# else +# define Q_PDF_EXPORT +# endif +#endif + +QT_END_NAMESPACE + +#endif // QTPDFGLOBAL_H + diff --git a/src/pdf/configure.json b/src/pdf/configure.json new file mode 100644 index 000000000..ddc0e99dc --- /dev/null +++ b/src/pdf/configure.json @@ -0,0 +1,64 @@ +{ + "module": "pdf", + "depends" : [ "buildtools-private" ], + "testDir": "../../config.tests", + "condition": "features.build-qtpdf && features.webengine-qtpdf-support", + "libraries": { + }, + "tests": { + }, + "features": { + "pdf-v8": { + "label": "Support V8", + "purpose": "Enables javascript support.", + "autoDetect": "false", + "condition": "!config.ios", + "output": ["privateFeature" ] + }, + "pdf-xfa": { + "label": "Support XFA", + "purpose": "Enables XFA support.", + "condition": "features.pdf-v8", + "output": ["privateFeature" ] + }, + "pdf-xfa-bmp": { + "label": "Support XFA-BMP", + "purpose": "Enables XFA-BMP support.", + "condition": "features.pdf-xfa", + "output": ["privateFeature" ] + }, + "pdf-xfa-gif": { + "label": "Support XFA-GIF", + "purpose": "Enables XFA-GIF support.", + "condition": "features.pdf-xfa", + "output": ["privateFeature" ] + }, + "pdf-xfa-png": { + "label": "Support XFA-PNG", + "purpose": "Enables XFA-PNG support.", + "condition": "features.pdf-xfa", + "output": ["privateFeature" ] + }, + "pdf-xfa-tiff": { + "label": "Support XFA-TIFF", + "purpose": "Enables XFA-TIFF support.", + "condition": "features.pdf-xfa", + "output": ["privateFeature" ] + } + }, + "report": [ + ], + "summary": [ + { + "section": "Qt PDF", + "entries": [ + "pdf-v8", + "pdf-xfa", + "pdf-xfa-bmp", + "pdf-xfa-gif", + "pdf-xfa-png", + "pdf-xfa-tiff" + ] + } + ] +} diff --git a/src/pdf/doc/qtpdf.qdocconf b/src/pdf/doc/qtpdf.qdocconf new file mode 100644 index 000000000..7a77105c9 --- /dev/null +++ b/src/pdf/doc/qtpdf.qdocconf @@ -0,0 +1,58 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) + +project = QtPdf +description = Qt Pdf Reference Documentation +version = $QT_VERSION + +qhp.projects = QtPdf + +qhp.QtPdf.file = qtpdf.qhp +qhp.QtPdf.namespace = org.qt-project.qtpdf.$QT_VERSION_TAG +qhp.QtPdf.virtualFolder = qtpdf +qhp.QtPdf.indexTitle = Qt PDF +qhp.QtPdf.indexRoot = + +qhp.QtPdf.filterAttributes = qtpdf $QT_VERSION qtrefdoc +qhp.QtPdf.customFilters.Qt.name = QtPdf $QT_VERSION +qhp.QtPdf.customFilters.Qt.filterAttributes = qtpdf $QT_VERSION + +qhp.QtPdf.subprojects = classes qmltypes examples + +qhp.QtPdf.subprojects.classes.title = C++ Classes +qhp.QtPdf.subprojects.classes.indexTitle = Qt PDF C++ Classes +qhp.QtPdf.subprojects.classes.selectors = class fake:headerfile +qhp.QtPdf.subprojects.classes.sortPages = true + +qhp.QtPdf.subprojects.qmltypes.title = QML Types +qhp.QtPdf.subprojects.qmltypes.indexTitle = Qt Quick PDF QML Types +qhp.QtPdf.subprojects.qmltypes.selectors = qmltype +qhp.QtPdf.subprojects.qmltypes.sortPages = true + +qhp.QtPdf.subprojects.examples.title = Examples +qhp.QtPdf.subprojects.examples.indexTitle = Qt PDF Examples +qhp.QtPdf.subprojects.examples.selectors = doc:example +qhp.QtPdf.subprojects.examples.sortPages = true + +depends += qtcore \ + qtwidgets \ + qtgui \ + qtdoc \ + qmake \ + qtdesigner \ + qtquick \ + qtcmake + +headerdirs += ../api \ + ../quick + +sourcedirs += .. \ + ../quick + +exampledirs += ../../../examples/pdfwidgets \ + snippets/ + +imagedirs += images + +navigation.landingpage = "Qt PDF" +navigation.cppclassespage = "Qt PDF C++ Classes" +navigation.qmltypespage = "Qt WebEngine QML Types" diff --git a/src/pdf/doc/snippets/qtpdf-build.cmake b/src/pdf/doc/snippets/qtpdf-build.cmake new file mode 100644 index 000000000..d46b9c3ee --- /dev/null +++ b/src/pdf/doc/snippets/qtpdf-build.cmake @@ -0,0 +1,2 @@ +find_package(Qt5 COMPONENTS Pdf REQUIRED) +target_link_libraries(mytarget Qt5::Pdf) diff --git a/src/pdf/doc/snippets/qtpdf_build_snippet.qdoc b/src/pdf/doc/snippets/qtpdf_build_snippet.qdoc new file mode 100644 index 000000000..25593b1ee --- /dev/null +++ b/src/pdf/doc/snippets/qtpdf_build_snippet.qdoc @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [0] +QT += pdf +//! [0] + + +//! [1] +#include <QtPdf> +//! [1] diff --git a/src/pdf/doc/src/qtpdf-examples.qdoc b/src/pdf/doc/src/qtpdf-examples.qdoc new file mode 100644 index 000000000..9daa0e7f8 --- /dev/null +++ b/src/pdf/doc/src/qtpdf-examples.qdoc @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \group qtpdf-examples + \ingroup all-examples + + \title Qt PDF Examples + \brief Using the classes and types in the Qt PDF module. + + The following examples illustrate how to use the C++ classes and QML types + in the \l{Qt PDF} module to render PDF documents. +*/ diff --git a/src/pdf/doc/src/qtpdf-index.qdoc b/src/pdf/doc/src/qtpdf-index.qdoc new file mode 100644 index 000000000..b32787eb5 --- /dev/null +++ b/src/pdf/doc/src/qtpdf-index.qdoc @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page qtpdf-index.html + \title Qt PDF + + \brief Renders pages from PDF documents. + + The Qt PDF module contains classes and functions for rendering + PDF documents. The \l QPdfDocument class loads a PDF document + and renders pages from it according to the options provided by + the \l QPdfDocumentRenderOptions class. The \l QPdfPageRenderer + class manages a queue that collects all render requests. The + \l QPdfPageNavigation class handles the navigation through a + PDF document. + + \include module-use.qdocinc using qt module + \quotefile qtpdf-build.cmake + + See also the \l{Build with CMake} overview. + + \section2 Building with qmake + + To include the definitions of the module's classes, use the + following directive: + + \snippet qtpdf_build_snippet.qdoc 1 + + To link against the module, add this line to your qmake project file: + + \snippet qtpdf_build_snippet.qdoc 0 + + \section1 Articles and Guides + + \list + \li \l{Qt PDF Overview} + \endlist + + \section1 Examples + + \list + \li \l{Qt PDF Examples} + \endlist + + \section1 API Reference + + \list + \li \l{Qt PDF C++ Classes} + \li \l{Qt Quick PDF QML Types} + \endlist +*/ diff --git a/src/pdf/doc/src/qtpdf-module.qdoc b/src/pdf/doc/src/qtpdf-module.qdoc new file mode 100644 index 000000000..4170deb38 --- /dev/null +++ b/src/pdf/doc/src/qtpdf-module.qdoc @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/*! + \module QtPdf + \title Qt PDF C++ Classes + \brief Renders pages from PDF documents. + \since 5.14 + \ingroup qtwebengine-modules + \ingroup modules + + The Qt PDF module contains classes and functions for rendering + PDF documents. + + To include the definitions of the module's classes, use the + following directive: + + \snippet qtpdf_build_snippet.qdoc 1 + + \if !defined(qtforpython) + To link against the module, add this line to your qmake project file: + + \snippet qtpdf_build_snippet.qdoc 0 + \endif +*/ diff --git a/src/pdf/gn_run.pro b/src/pdf/gn_run.pro new file mode 100644 index 000000000..7fd4d4ffc --- /dev/null +++ b/src/pdf/gn_run.pro @@ -0,0 +1,70 @@ +include($$QTWEBENGINE_OUT_ROOT/src/buildtools/qtbuildtools-config.pri) +QT_FOR_CONFIG += buildtools-private + +TEMPLATE = aux + +qtConfig(debug_and_release): CONFIG += debug_and_release build_all + +qtConfig(webengine-system-ninja) { + QT_TOOL.ninja.binary = ninja +} else { + QT_TOOL.ninja.binary = $$shell_quote($$shell_path($$ninjaPath())) +} + +win32 { + # Add the gnuwin32/bin subdir of qt5.git to PATH. Needed for calling bison and friends. + gnuwin32path.name = PATH + gnuwin32path.value = $$shell_path($$clean_path($$QTWEBENGINE_ROOT/../gnuwin32/bin)) + gnuwin32path.CONFIG += prepend + exists($$gnuwin32path.value): QT_TOOL_ENV = gnuwin32path +} + +qtPrepareTool(NINJA, ninja) +QT_TOOL_ENV = + +build_pass|!debug_and_release { + gn_binary = gn + + runninja.target = run_ninja + + # fixme: refine args + gn_args = $$gnArgs() + + include($$QTWEBENGINE_ROOT/src/buildtools/config/pdf.pri) + + # fixme: qtwebengine_target + gn_args += "qtwebengine_target=\"$$system_path($$OUT_PWD/$$getConfigDir()):QtPdf\"" + + # fixme: + !qtConfig(webengine-system-gn) { + gn_binary = $$system_quote($$system_path($$gnPath())) + } + + gn_args = $$system_quote($$gn_args) + gn_src_root = $$system_quote($$system_path($$QTWEBENGINE_ROOT/$$getChromiumSrcDir())) + gn_build_root = $$system_quote($$system_path($$OUT_PWD/$$getConfigDir())) + gn_python = "--script-executable=$$pythonPathForSystem()" + gn_run = $$gn_binary gen $$gn_build_root $$gn_python --args=$$gn_args --root=$$gn_src_root + + message("Running: $$gn_run ") + !system($$gn_run) { + error("GN run error!") + } + + ninjaflags = $$(NINJAFLAGS) + isEmpty(ninjaflags):!silent: ninjaflags = "-v" + + runninja.commands = $$NINJA $$ninjaflags -C $$gn_build_root QtPdf + QMAKE_EXTRA_TARGETS += runninja + + build_pass:build_all: default_target.target = all + else: default_target.target = first + default_target.depends = runninja + QMAKE_EXTRA_TARGETS += default_target +} + +!build_pass:debug_and_release { + # Special GNU make target for the meta Makefile that ensures that our debug and release Makefiles won't both run ninja in parallel. + notParallel.target = .NOTPARALLEL + QMAKE_EXTRA_TARGETS += notParallel +} diff --git a/src/pdf/jsbridge.cpp b/src/pdf/jsbridge.cpp new file mode 100644 index 000000000..33d3b2465 --- /dev/null +++ b/src/pdf/jsbridge.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "fpdfsdk/javascript/JS_Runtime_Stub.cpp" + diff --git a/src/pdf/pdf.pro b/src/pdf/pdf.pro new file mode 100644 index 000000000..c5513ce93 --- /dev/null +++ b/src/pdf/pdf.pro @@ -0,0 +1,16 @@ +TEMPLATE = subdirs +pdfcore.file = pdfcore.pro +pdfcore_generator.file = pdfcore_generator.pro +gn_run.file = gn_run.pro + +gn_run.depends = pdfcore_generator +pdfcore.depends = gn_run +quick.depends = pdfcore + +SUBDIRS += \ + pdfcore_generator \ + gn_run \ + pdfcore \ + quick + + diff --git a/src/pdf/pdfcore.pro b/src/pdf/pdfcore.pro new file mode 100644 index 000000000..c87722b7e --- /dev/null +++ b/src/pdf/pdfcore.pro @@ -0,0 +1,93 @@ +TARGET = QtPdf +MODULE = pdf + +QT += gui core core-private +QT_PRIVATE += network + +TEMPLATE = lib + +INCLUDEPATH += $$QTWEBENGINE_ROOT/src/pdf +CHROMIUM_SRC_DIR = $$QTWEBENGINE_ROOT/$$getChromiumSrcDir() +CHROMIUM_GEN_DIR = $$OUT_PWD/../$$getConfigDir()/gen +INCLUDEPATH += $$QTWEBENGINE_ROOT/src/pdf \ + $$CHROMIUM_GEN_DIR \ + $$CHROMIUM_SRC_DIR \ + api + +DEFINES += QT_BUILD_PDF_LIB +win32: DEFINES += NOMINMAX + +linking_pri = $$OUT_PWD/$$getConfigDir()/$${TARGET}.pri +!include($$linking_pri) { + error("Could not find the linking information that gn should have generated.") +} + +isEmpty(NINJA_OBJECTS): error("Missing object files from QtPdf linking pri.") +isEmpty(NINJA_LFLAGS): error("Missing linker flags from QtPdf linking pri") +isEmpty(NINJA_LIBS): error("Missing library files from QtPdf linking pri") + +NINJA_OBJECTS = $$eval($$list($$NINJA_OBJECTS)) +RSP_FILE = $$OUT_PWD/$$getConfigDir()/$${TARGET}.rsp +for(object, NINJA_OBJECTS): RSP_CONTENT += $$object +write_file($$RSP_FILE, RSP_CONTENT) + +macos:LIBS_PRIVATE += -Wl,-filelist,$$shell_quote($$RSP_FILE) +linux:LIBS_PRIVATE += @$$RSP_FILE + +# QTBUG-58710 add main rsp file on windows +win32:QMAKE_LFLAGS += @$$RSP_FILE + +!isEmpty(NINJA_ARCHIVES) { + linux: LIBS_PRIVATE += -Wl,--start-group $$NINJA_ARCHIVES -Wl,--end-group + else: LIBS_PRIVATE += $$NINJA_ARCHIVES +} + +LIBS_PRIVATE += $$NINJA_LIB_DIRS $$NINJA_LIBS + +QMAKE_DOCS = $$PWD/doc/qtpdf.qdocconf + +gcc { + QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter +} + +msvc { + QMAKE_CXXFLAGS_WARN_ON += -wd"4100" +} + +ios: OBJECTS += $$NINJA_OBJECTS + +SOURCES += \ + qpdfbookmarkmodel.cpp \ + qpdfdestination.cpp \ + qpdfdocument.cpp \ + qpdflinkmodel.cpp \ + qpdfpagenavigation.cpp \ + qpdfpagerenderer.cpp \ + qpdfsearchmodel.cpp \ + qpdfsearchresult.cpp \ + qpdfselection.cpp \ + +# all "public" headers must be in "api" for sync script and to hide auto generated headers +# by Chromium in case of in-source build + +HEADERS += \ + api/qpdfbookmarkmodel.h \ + api/qpdfdestination.h \ + api/qpdfdestination_p.h \ + api/qpdfdocument.h \ + api/qpdfdocument_p.h \ + api/qpdfdocumentrenderoptions.h \ + api/qtpdfglobal.h \ + api/qpdflinkmodel_p.h \ + api/qpdflinkmodel_p_p.h \ + api/qpdfnamespace.h \ + api/qpdfpagenavigation.h \ + api/qpdfpagerenderer.h \ + api/qpdfsearchmodel.h \ + api/qpdfsearchmodel_p.h \ + api/qpdfsearchresult.h \ + api/qpdfsearchresult_p.h \ + api/qpdfselection.h \ + api/qpdfselection_p.h \ + +load(qt_module) diff --git a/src/pdf/pdfcore_generator.pro b/src/pdf/pdfcore_generator.pro new file mode 100644 index 000000000..c8eb13b81 --- /dev/null +++ b/src/pdf/pdfcore_generator.pro @@ -0,0 +1,15 @@ +qtConfig(debug_and_release): CONFIG += debug_and_release + +TARGET = QtPdf +TEMPLATE = lib +CONFIG = gn_generator $$CONFIG +ios: CONFIG -=static # note we still do static on ios when linking +GN_SRC_DIR = $$PWD +GN_FILE = $$OUT_PWD/$$getConfigDir()/BUILD.gn +GN_FIND_MOCABLES_SCRIPT = $$shell_path($$QTWEBENGINE_ROOT/tools/scripts/gn_find_mocables.py) +GN_RUN_BINARY_SCRIPT = $$shell_path($$QTWEBENGINE_ROOT/tools/scripts/gn_run_binary.py) +GN_IMPORTS = $$PWD/qtpdf.gni +GN_CREATE_PRI = true +QMAKE_INTERNAL_INCLUDED_FILES = $$GN_IMPORTS $$GN_INCLUDES $$GN_FILE + + diff --git a/src/pdf/qpdfbookmarkmodel.cpp b/src/pdf/qpdfbookmarkmodel.cpp new file mode 100644 index 000000000..c9c365568 --- /dev/null +++ b/src/pdf/qpdfbookmarkmodel.cpp @@ -0,0 +1,363 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfbookmarkmodel.h" + +#include "qpdfdocument.h" +#include "qpdfdocument_p.h" + +#include "third_party/pdfium/public/fpdf_doc.h" +#include "third_party/pdfium/public/fpdfview.h" + +#include <QPointer> +#include <QScopedPointer> +#include <private/qabstractitemmodel_p.h> + +QT_BEGIN_NAMESPACE + +class BookmarkNode +{ +public: + explicit BookmarkNode(BookmarkNode *parentNode = nullptr) + : m_parentNode(parentNode) + , m_level(0) + , m_pageNumber(0) + { + } + + ~BookmarkNode() + { + clear(); + } + + void clear() + { + qDeleteAll(m_childNodes); + m_childNodes.clear(); + } + + void appendChild(BookmarkNode *child) + { + m_childNodes.append(child); + } + + BookmarkNode *child(int row) const + { + return m_childNodes.at(row); + } + + int childCount() const + { + return m_childNodes.count(); + } + + int row() const + { + if (m_parentNode) + return m_parentNode->m_childNodes.indexOf(const_cast<BookmarkNode*>(this)); + + return 0; + } + + BookmarkNode *parentNode() const + { + return m_parentNode; + } + + QString title() const + { + return m_title; + } + + void setTitle(const QString &title) + { + m_title = title; + } + + int level() const + { + return m_level; + } + + void setLevel(int level) + { + m_level = level; + } + + int pageNumber() const + { + return m_pageNumber; + } + + void setPageNumber(int pageNumber) + { + m_pageNumber = pageNumber; + } + +private: + QVector<BookmarkNode*> m_childNodes; + BookmarkNode *m_parentNode; + + QString m_title; + int m_level; + int m_pageNumber; +}; + + +class QPdfBookmarkModelPrivate : public QAbstractItemModelPrivate +{ +public: + QPdfBookmarkModelPrivate() + : QAbstractItemModelPrivate() + , m_rootNode(new BookmarkNode(nullptr)) + , m_document(nullptr) + , m_structureMode(QPdfBookmarkModel::TreeMode) + { + } + + void rebuild() + { + Q_Q(QPdfBookmarkModel); + + const bool documentAvailable = (m_document && m_document->status() == QPdfDocument::Ready); + + if (documentAvailable) { + q->beginResetModel(); + m_rootNode->clear(); + QPdfMutexLocker lock; + appendChildNode(m_rootNode.data(), nullptr, 0, m_document->d->doc); + lock.unlock(); + q->endResetModel(); + } else { + if (m_rootNode->childCount() == 0) { + return; + } else { + q->beginResetModel(); + m_rootNode->clear(); + q->endResetModel(); + } + } + } + + void appendChildNode(BookmarkNode *parentBookmarkNode, FPDF_BOOKMARK parentBookmark, int level, FPDF_DOCUMENT document) + { + FPDF_BOOKMARK bookmark = FPDFBookmark_GetFirstChild(document, parentBookmark); + + while (bookmark) { + BookmarkNode *childBookmarkNode = nullptr; + + if (m_structureMode == QPdfBookmarkModel::TreeMode) { + childBookmarkNode = new BookmarkNode(parentBookmarkNode); + parentBookmarkNode->appendChild(childBookmarkNode); + } else if (m_structureMode == QPdfBookmarkModel::ListMode) { + childBookmarkNode = new BookmarkNode(m_rootNode.data()); + m_rootNode->appendChild(childBookmarkNode); + } + + const int titleLength = int(FPDFBookmark_GetTitle(bookmark, nullptr, 0)); + + QVector<ushort> titleBuffer(titleLength); + FPDFBookmark_GetTitle(bookmark, titleBuffer.data(), quint32(titleBuffer.length())); + + const FPDF_DEST dest = FPDFBookmark_GetDest(document, bookmark); + const int pageNumber = FPDFDest_GetDestPageIndex(document, dest); + + childBookmarkNode->setTitle(QString::fromUtf16(titleBuffer.data())); + childBookmarkNode->setLevel(level); + childBookmarkNode->setPageNumber(pageNumber); + + // recurse down + appendChildNode(childBookmarkNode, bookmark, level + 1, document); + + bookmark = FPDFBookmark_GetNextSibling(document, bookmark); + } + } + + void _q_documentStatusChanged() + { + rebuild(); + } + + Q_DECLARE_PUBLIC(QPdfBookmarkModel) + + QScopedPointer<BookmarkNode> m_rootNode; + QPointer<QPdfDocument> m_document; + QPdfBookmarkModel::StructureMode m_structureMode; +}; + + +QPdfBookmarkModel::QPdfBookmarkModel(QObject *parent) + : QAbstractItemModel(*new QPdfBookmarkModelPrivate, parent) +{ +} + +QPdfDocument* QPdfBookmarkModel::document() const +{ + Q_D(const QPdfBookmarkModel); + + return d->m_document; +} + +void QPdfBookmarkModel::setDocument(QPdfDocument *document) +{ + Q_D(QPdfBookmarkModel); + + if (d->m_document == document) + return; + + if (d->m_document) + disconnect(d->m_document, SIGNAL(statusChanged(QPdfDocument::Status)), this, SLOT(_q_documentStatusChanged())); + + d->m_document = document; + emit documentChanged(d->m_document); + + if (d->m_document) + connect(d->m_document, SIGNAL(statusChanged(QPdfDocument::Status)), this, SLOT(_q_documentStatusChanged())); + + d->rebuild(); +} + +QPdfBookmarkModel::StructureMode QPdfBookmarkModel::structureMode() const +{ + Q_D(const QPdfBookmarkModel); + + return d->m_structureMode; +} + +void QPdfBookmarkModel::setStructureMode(StructureMode mode) +{ + Q_D(QPdfBookmarkModel); + + if (d->m_structureMode == mode) + return; + + d->m_structureMode = mode; + emit structureModeChanged(d->m_structureMode); + + d->rebuild(); +} + +int QPdfBookmarkModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QHash<int, QByteArray> QPdfBookmarkModel::roleNames() const +{ + QHash<int, QByteArray> names; + + names[TitleRole] = "title"; + names[LevelRole] = "level"; + names[PageNumberRole] = "pageNumber"; + + return names; +} + +QVariant QPdfBookmarkModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + const BookmarkNode *node = static_cast<BookmarkNode*>(index.internalPointer()); + switch (role) { + case TitleRole: + return node->title(); + case LevelRole: + return node->level(); + case PageNumberRole: + return node->pageNumber(); + default: + return QVariant(); + } +} + +QModelIndex QPdfBookmarkModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QPdfBookmarkModel); + + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + BookmarkNode *parentNode; + + if (!parent.isValid()) + parentNode = d->m_rootNode.data(); + else + parentNode = static_cast<BookmarkNode*>(parent.internalPointer()); + + BookmarkNode *childNode = parentNode->child(row); + if (childNode) + return createIndex(row, column, childNode); + else + return QModelIndex(); +} + +QModelIndex QPdfBookmarkModel::parent(const QModelIndex &index) const +{ + Q_D(const QPdfBookmarkModel); + + if (!index.isValid()) + return QModelIndex(); + + const BookmarkNode *childNode = static_cast<BookmarkNode*>(index.internalPointer()); + BookmarkNode *parentNode = childNode->parentNode(); + + if (parentNode == d->m_rootNode.data()) + return QModelIndex(); + + return createIndex(parentNode->row(), 0, parentNode); +} + +int QPdfBookmarkModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QPdfBookmarkModel); + + if (parent.column() > 0) + return 0; + + BookmarkNode *parentNode = nullptr; + + if (!parent.isValid()) + parentNode = d->m_rootNode.data(); + else + parentNode = static_cast<BookmarkNode*>(parent.internalPointer()); + + return parentNode->childCount(); +} + +QT_END_NAMESPACE + +#include "moc_qpdfbookmarkmodel.cpp" diff --git a/src/pdf/qpdfdestination.cpp b/src/pdf/qpdfdestination.cpp new file mode 100644 index 000000000..b70e031ca --- /dev/null +++ b/src/pdf/qpdfdestination.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfdestination.h" +#include "qpdfdestination_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QPdfDestination + \since 5.15 + \inmodule QtPdf + + \brief The QPdfDestination class defines a location on a page in a PDF + document, and a suggested zoom level at which it is intended to be viewed. +*/ + +/*! + Constructs an invalid Destination. + + \sa valid +*/ +QPdfDestination::QPdfDestination() + : d(new QPdfDestinationPrivate()) +{ +} + +QPdfDestination::QPdfDestination(int page, QPointF location, qreal zoom) + : d(new QPdfDestinationPrivate(page, location, zoom)) +{ +} + +QPdfDestination::QPdfDestination(QPdfDestinationPrivate *d) + : d(d) +{ +} + +QPdfDestination::QPdfDestination(const QPdfDestination &other) + : d(other.d) +{ +} + +QPdfDestination::QPdfDestination(QPdfDestination &&other) noexcept + : d(std::move(other.d)) +{ +} + +QPdfDestination::~QPdfDestination() +{ +} + +QPdfDestination &QPdfDestination::operator=(const QPdfDestination &other) +{ + d = other.d; + return *this; +} + +/*! + \property QPdfDestination::valid + + This property holds whether the destination is valid. +*/ +bool QPdfDestination::isValid() const +{ + return d->page >= 0; +} + +/*! + \property QPdfDestination::page + + This property holds the page number. +*/ +int QPdfDestination::page() const +{ + return d->page; +} + +/*! + \property QPdfDestination::location + + This property holds the location on the page, in units of points. +*/ +QPointF QPdfDestination::location() const +{ + return d->location; +} + +/*! + \property QPdfDestination::zoom + + This property holds the suggested magnification level, where 1.0 means default scale + (1 pixel = 1 point). +*/ +qreal QPdfDestination::zoom() const +{ + return d->zoom; +} + +QDebug operator<<(QDebug dbg, const QPdfDestination& dest) +{ + QDebugStateSaver saver(dbg); + dbg.nospace(); + dbg << "QPdfDestination(page=" << dest.page() + << " location=" << dest.location() + << " zoom=" << dest.zoom(); + dbg << ')'; + return dbg; +} + +QT_END_NAMESPACE + +#include "moc_qpdfdestination.cpp" diff --git a/src/pdf/qpdfdocument.cpp b/src/pdf/qpdfdocument.cpp new file mode 100644 index 000000000..89b27da8b --- /dev/null +++ b/src/pdf/qpdfdocument.cpp @@ -0,0 +1,803 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfdocument.h" +#include "qpdfdocument_p.h" + +#include "third_party/pdfium/public/fpdf_doc.h" +#include "third_party/pdfium/public/fpdf_text.h" + +#include <QDateTime> +#include <QDebug> +#include <QElapsedTimer> +#include <QFile> +#include <QHash> +#include <QLoggingCategory> +#include <QMutex> +#include <QVector2D> + +QT_BEGIN_NAMESPACE + +// The library is not thread-safe at all, it has a lot of global variables. +Q_GLOBAL_STATIC_WITH_ARGS(QMutex, pdfMutex, (QMutex::Recursive)); +static int libraryRefCount; +static const double CharacterHitTolerance = 6.0; +Q_LOGGING_CATEGORY(qLcDoc, "qt.pdf.document") + +QPdfMutexLocker::QPdfMutexLocker() + : QMutexLocker(pdfMutex()) +{ +} + +QPdfDocumentPrivate::QPdfDocumentPrivate() + : avail(nullptr) + , doc(nullptr) + , loadComplete(false) + , status(QPdfDocument::Null) + , lastError(QPdfDocument::NoError) + , pageCount(0) +{ + asyncBuffer.setData(QByteArray()); + asyncBuffer.open(QIODevice::ReadWrite); + + const QPdfMutexLocker lock; + + if (libraryRefCount == 0) + FPDF_InitLibrary(); + ++libraryRefCount; + + // FPDF_FILEACCESS setup + m_Param = this; + m_GetBlock = fpdf_GetBlock; + + // FX_FILEAVAIL setup + FX_FILEAVAIL::version = 1; + IsDataAvail = fpdf_IsDataAvail; + + // FX_DOWNLOADHINTS setup + FX_DOWNLOADHINTS::version = 1; + AddSegment = fpdf_AddSegment; +} + +QPdfDocumentPrivate::~QPdfDocumentPrivate() +{ + q->close(); + + const QPdfMutexLocker lock; + + if (!--libraryRefCount) + FPDF_DestroyLibrary(); +} + +void QPdfDocumentPrivate::clear() +{ + QPdfMutexLocker lock; + + if (doc) + FPDF_CloseDocument(doc); + doc = nullptr; + + if (avail) + FPDFAvail_Destroy(avail); + avail = nullptr; + lock.unlock(); + + if (pageCount != 0) { + pageCount = 0; + emit q->pageCountChanged(pageCount); + } + + loadComplete = false; + + asyncBuffer.close(); + asyncBuffer.setData(QByteArray()); + asyncBuffer.open(QIODevice::ReadWrite); + + if (sequentialSourceDevice) + sequentialSourceDevice->disconnect(q); +} + +void QPdfDocumentPrivate::updateLastError() +{ + if (doc) { + lastError = QPdfDocument::NoError; + return; + } + + QPdfMutexLocker lock; + const unsigned long error = FPDF_GetLastError(); + lock.unlock(); + + switch (error) { + case FPDF_ERR_SUCCESS: lastError = QPdfDocument::NoError; break; + case FPDF_ERR_UNKNOWN: lastError = QPdfDocument::UnknownError; break; + case FPDF_ERR_FILE: lastError = QPdfDocument::FileNotFoundError; break; + case FPDF_ERR_FORMAT: lastError = QPdfDocument::InvalidFileFormatError; break; + case FPDF_ERR_PASSWORD: lastError = QPdfDocument::IncorrectPasswordError; break; + case FPDF_ERR_SECURITY: lastError = QPdfDocument::UnsupportedSecuritySchemeError; break; + default: + Q_UNREACHABLE(); + } +} + +void QPdfDocumentPrivate::load(QIODevice *newDevice, bool transferDeviceOwnership) +{ + if (transferDeviceOwnership) + ownDevice.reset(newDevice); + else + ownDevice.reset(); + + if (newDevice->isSequential()) { + sequentialSourceDevice = newDevice; + device = &asyncBuffer; + QNetworkReply *reply = qobject_cast<QNetworkReply*>(sequentialSourceDevice); + + if (!reply) { + setStatus(QPdfDocument::Error); + qWarning() << "QPdfDocument: Loading from sequential devices only supported with QNetworkAccessManager."; + return; + } + + if (reply->isFinished() && reply->error() != QNetworkReply::NoError) { + setStatus(QPdfDocument::Error); + return; + } + + QObject::connect(reply, &QNetworkReply::finished, q, [this, reply](){ + if (reply->error() != QNetworkReply::NoError || reply->bytesAvailable() == 0) { + this->setStatus(QPdfDocument::Error); + } + }); + + if (reply->header(QNetworkRequest::ContentLengthHeader).isValid()) + _q_tryLoadingWithSizeFromContentHeader(); + else + QObject::connect(reply, SIGNAL(metaDataChanged()), q, SLOT(_q_tryLoadingWithSizeFromContentHeader())); + } else { + device = newDevice; + initiateAsyncLoadWithTotalSizeKnown(device->size()); + if (!avail) { + setStatus(QPdfDocument::Error); + return; + } + + if (!doc) + tryLoadDocument(); + + if (!doc) { + updateLastError(); + setStatus(QPdfDocument::Error); + return; + } + + QPdfMutexLocker lock; + const int newPageCount = FPDF_GetPageCount(doc); + lock.unlock(); + if (newPageCount != pageCount) { + pageCount = newPageCount; + emit q->pageCountChanged(pageCount); + } + + // If it's a local file, and the first couple of pages are available, + // probably the whole document is available. + if (checkPageComplete(0) && (pageCount < 2 || checkPageComplete(1))) { + setStatus(QPdfDocument::Ready); + } else { + updateLastError(); + setStatus(QPdfDocument::Error); + } + } +} + +void QPdfDocumentPrivate::_q_tryLoadingWithSizeFromContentHeader() +{ + if (avail) + return; + + const QNetworkReply *networkReply = qobject_cast<QNetworkReply*>(sequentialSourceDevice); + if (!networkReply) { + setStatus(QPdfDocument::Error); + return; + } + + const QVariant contentLength = networkReply->header(QNetworkRequest::ContentLengthHeader); + if (!contentLength.isValid()) { + setStatus(QPdfDocument::Error); + return; + } + + QObject::connect(sequentialSourceDevice, SIGNAL(readyRead()), q, SLOT(_q_copyFromSequentialSourceDevice())); + + initiateAsyncLoadWithTotalSizeKnown(contentLength.toULongLong()); + + if (sequentialSourceDevice->bytesAvailable()) + _q_copyFromSequentialSourceDevice(); +} + +void QPdfDocumentPrivate::initiateAsyncLoadWithTotalSizeKnown(quint64 totalSize) +{ + // FPDF_FILEACCESS setup + m_FileLen = totalSize; + + const QPdfMutexLocker lock; + + avail = FPDFAvail_Create(this, this); +} + +void QPdfDocumentPrivate::_q_copyFromSequentialSourceDevice() +{ + if (loadComplete) + return; + + const QByteArray data = sequentialSourceDevice->read(sequentialSourceDevice->bytesAvailable()); + if (data.isEmpty()) + return; + + asyncBuffer.seek(asyncBuffer.size()); + asyncBuffer.write(data); + + checkComplete(); +} + +void QPdfDocumentPrivate::tryLoadDocument() +{ + QPdfMutexLocker lock; + switch (FPDFAvail_IsDocAvail(avail, this)) { + case PDF_DATA_ERROR: + qCDebug(qLcDoc) << "error loading"; + break; + case PDF_DATA_NOTAVAIL: + qCDebug(qLcDoc) << "data not yet available"; + lastError = QPdfDocument::DataNotYetAvailableError; + setStatus(QPdfDocument::Error); + break; + case PDF_DATA_AVAIL: + // all good + break; + } + + Q_ASSERT(!doc); + + doc = FPDFAvail_GetDocument(avail, password); + lock.unlock(); + + updateLastError(); + + if (lastError == QPdfDocument::IncorrectPasswordError) { + FPDF_CloseDocument(doc); + doc = nullptr; + + setStatus(QPdfDocument::Error); + emit q->passwordRequired(); + } +} + +void QPdfDocumentPrivate::checkComplete() +{ + if (!avail || loadComplete) + return; + + if (!doc) + tryLoadDocument(); + + if (!doc) + return; + + loadComplete = true; + + QPdfMutexLocker lock; + + const int newPageCount = FPDF_GetPageCount(doc); + for (int i = 0; i < newPageCount; ++i) { + int result = PDF_DATA_NOTAVAIL; + while (result == PDF_DATA_NOTAVAIL) { + result = FPDFAvail_IsPageAvail(avail, i, this); + } + + if (result == PDF_DATA_ERROR) + loadComplete = false; + } + + lock.unlock(); + + if (loadComplete) { + if (newPageCount != pageCount) { + pageCount = newPageCount; + emit q->pageCountChanged(pageCount); + } + + setStatus(QPdfDocument::Ready); + } +} + +bool QPdfDocumentPrivate::checkPageComplete(int page) +{ + if (page < 0 || page >= pageCount) + return false; + + if (loadComplete) + return true; + + QPdfMutexLocker lock; + int result = PDF_DATA_NOTAVAIL; + while (result == PDF_DATA_NOTAVAIL) + result = FPDFAvail_IsPageAvail(avail, page, this); + lock.unlock(); + + if (result == PDF_DATA_ERROR) + updateLastError(); + + return (result != PDF_DATA_ERROR); +} + +void QPdfDocumentPrivate::setStatus(QPdfDocument::Status documentStatus) +{ + if (status == documentStatus) + return; + + status = documentStatus; + emit q->statusChanged(status); +} + +FPDF_BOOL QPdfDocumentPrivate::fpdf_IsDataAvail(_FX_FILEAVAIL *pThis, size_t offset, size_t size) +{ + QPdfDocumentPrivate *d = static_cast<QPdfDocumentPrivate*>(pThis); + return offset + size <= static_cast<quint64>(d->device->size()); +} + +int QPdfDocumentPrivate::fpdf_GetBlock(void *param, unsigned long position, unsigned char *pBuf, unsigned long size) +{ + QPdfDocumentPrivate *d = static_cast<QPdfDocumentPrivate*>(reinterpret_cast<FPDF_FILEACCESS*>(param)); + d->device->seek(position); + return qMax(qint64(0), d->device->read(reinterpret_cast<char *>(pBuf), size)); + +} + +void QPdfDocumentPrivate::fpdf_AddSegment(_FX_DOWNLOADHINTS *pThis, size_t offset, size_t size) +{ + Q_UNUSED(pThis); + Q_UNUSED(offset); + Q_UNUSED(size); +} + +QString QPdfDocumentPrivate::getText(FPDF_TEXTPAGE textPage, int startIndex, int count) +{ + QVector<ushort> buf(count + 1); + // TODO is that enough space in case one unicode character is more than one in utf-16? + int len = FPDFText_GetText(textPage, startIndex, count, buf.data()); + Q_ASSERT(len - 1 <= count); // len is number of characters written, including the terminator + return QString::fromUtf16(buf.constData(), len - 1); +} + +/*! + \class QPdfDocument + \since 5.10 + \inmodule QtPdf + + \brief The QPdfDocument class loads a PDF document and renders pages from it. +*/ + +/*! + Constructs a new document with parent object \a parent. +*/ +QPdfDocument::QPdfDocument(QObject *parent) + : QObject(parent) + , d(new QPdfDocumentPrivate) +{ + d->q = this; +} + +/*! + Destroys the document. +*/ +QPdfDocument::~QPdfDocument() +{ +} + +QPdfDocument::DocumentError QPdfDocument::load(const QString &fileName) +{ + qCDebug(qLcDoc) << "loading" << fileName; + + close(); + + d->setStatus(QPdfDocument::Loading); + + QScopedPointer<QFile> f(new QFile(fileName)); + if (!f->open(QIODevice::ReadOnly)) { + d->lastError = FileNotFoundError; + d->setStatus(QPdfDocument::Error); + } else { + d->load(f.take(), /*transfer ownership*/true); + } + return d->lastError; +} + +/*! + \enum QPdfDocument::Status + + This enum describes the current status of the document. + + \value Null The initial status after the document has been created or after it has been closed. + \value Loading The status after load() has been called and before the document is fully loaded. + \value Ready The status when the document is fully loaded and its data can be accessed. + \value Unloading The status after close() has been called on an open document. + At this point the document is still valid and all its data can be accessed. + \value Error The status after Loading, if loading has failed. + + \sa QPdfDocument::status() +*/ + +/*! + Returns the current status of the document. +*/ +QPdfDocument::Status QPdfDocument::status() const +{ + return d->status; +} + +void QPdfDocument::load(QIODevice *device) +{ + close(); + + d->setStatus(QPdfDocument::Loading); + + d->load(device, /*transfer ownership*/false); +} + +void QPdfDocument::setPassword(const QString &password) +{ + const QByteArray newPassword = password.toUtf8(); + + if (d->password == newPassword) + return; + + d->password = newPassword; + emit passwordChanged(); +} + +QString QPdfDocument::password() const +{ + return QString::fromUtf8(d->password); +} + +/*! + \enum QPdfDocument::MetaDataField + + This enum describes the available fields of meta data. + + \value Title The document's title as QString. + \value Author The name of the person who created the document as QString. + \value Subject The subject of the document as QString. + \value Keywords Keywords associated with the document as QString. + \value Creator If the document was converted to PDF from another format, + the name of the conforming product that created the original document + from which it was converted as QString. + \value Producer If the document was converted to PDF from another format, + the name of the conforming product that converted it to PDF as QString. + \value CreationDate The date and time the document was created as QDateTime. + \value ModificationDate The date and time the document was most recently modified as QDateTime. + + \sa QPdfDocument::metaData() +*/ + +/*! + Returns the meta data of the document for the given \a field. +*/ +QVariant QPdfDocument::metaData(MetaDataField field) const +{ + if (!d->doc) + return QString(); + + QByteArray fieldName; + switch (field) { + case Title: + fieldName = "Title"; + break; + case Subject: + fieldName = "Subject"; + break; + case Author: + fieldName = "Author"; + break; + case Keywords: + fieldName = "Keywords"; + break; + case Producer: + fieldName = "Producer"; + break; + case Creator: + fieldName = "Creator"; + break; + case CreationDate: + fieldName = "CreationDate"; + break; + case ModificationDate: + fieldName = "ModDate"; + break; + } + + QPdfMutexLocker lock; + const unsigned long len = FPDF_GetMetaText(d->doc, fieldName.constData(), nullptr, 0); + + QVector<ushort> buf(len); + FPDF_GetMetaText(d->doc, fieldName.constData(), buf.data(), buf.length()); + lock.unlock(); + + QString text = QString::fromUtf16(buf.data()); + + switch (field) { + case Title: // fall through + case Subject: + case Author: + case Keywords: + case Producer: + case Creator: + return text; + case CreationDate: // fall through + case ModificationDate: + // convert a "D:YYYYMMDDHHmmSSOHH'mm'" into "YYYY-MM-DDTHH:mm:ss+HH:mm" + if (text.startsWith(QLatin1String("D:"))) + text = text.mid(2); + text.insert(4, QLatin1Char('-')); + text.insert(7, QLatin1Char('-')); + text.insert(10, QLatin1Char('T')); + text.insert(13, QLatin1Char(':')); + text.insert(16, QLatin1Char(':')); + text.replace(QLatin1Char('\''), QLatin1Char(':')); + if (text.endsWith(QLatin1Char(':'))) + text.chop(1); + + return QDateTime::fromString(text, Qt::ISODate); + } + + return QVariant(); +} + +QPdfDocument::DocumentError QPdfDocument::error() const +{ + return d->lastError; +} + +/*! + Closes the document. +*/ +void QPdfDocument::close() +{ + if (!d->doc) + return; + + d->setStatus(Unloading); + + d->clear(); + + if (!d->password.isEmpty()) { + d->password.clear(); + emit passwordChanged(); + } + + d->setStatus(Null); +} + +/*! + Returns the amount of pages for the loaded document or \c 0 if + no document is loaded. +*/ +int QPdfDocument::pageCount() const +{ + return d->pageCount; +} + +QSizeF QPdfDocument::pageSize(int page) const +{ + QSizeF result; + if (!d->doc || !d->checkPageComplete(page)) + return result; + + const QPdfMutexLocker lock; + + FPDF_GetPageSizeByIndex(d->doc, page, &result.rwidth(), &result.rheight()); + return result; +} + +/*! + Renders the \a page into a QImage of size \a imageSize according to the + provided \a renderOptions. + + Returns the rendered page or an empty image in case of an error. + + Note: If the \a imageSize does not match the aspect ratio of the page in the + PDF document, the page is rendered scaled, so that it covers the + complete \a imageSize. +*/ +QImage QPdfDocument::render(int page, QSize imageSize, QPdfDocumentRenderOptions renderOptions) +{ + if (!d->doc || !d->checkPageComplete(page)) + return QImage(); + + const QPdfMutexLocker lock; + + QElapsedTimer timer; + if (Q_UNLIKELY(qLcDoc().isDebugEnabled())) + timer.start(); + FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page); + if (!pdfPage) + return QImage(); + + QImage result(imageSize, QImage::Format_ARGB32); + result.fill(Qt::transparent); + FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(result.width(), result.height(), FPDFBitmap_BGRA, result.bits(), result.bytesPerLine()); + + int rotation = 0; + switch (renderOptions.rotation()) { + case QPdf::Rotate0: + rotation = 0; + break; + case QPdf::Rotate90: + rotation = 1; + break; + case QPdf::Rotate180: + rotation = 2; + break; + case QPdf::Rotate270: + rotation = 3; + break; + } + + const QPdf::RenderFlags renderFlags = renderOptions.renderFlags(); + int flags = 0; + if (renderFlags & QPdf::RenderAnnotations) + flags |= FPDF_ANNOT; + if (renderFlags & QPdf::RenderOptimizedForLcd) + flags |= FPDF_LCD_TEXT; + if (renderFlags & QPdf::RenderGrayscale) + flags |= FPDF_GRAYSCALE; + if (renderFlags & QPdf::RenderForceHalftone) + flags |= FPDF_RENDER_FORCEHALFTONE; + if (renderFlags & QPdf::RenderTextAliased) + flags |= FPDF_RENDER_NO_SMOOTHTEXT; + if (renderFlags & QPdf::RenderImageAliased) + flags |= FPDF_RENDER_NO_SMOOTHIMAGE; + if (renderFlags & QPdf::RenderPathAliased) + flags |= FPDF_RENDER_NO_SMOOTHPATH; + + if (renderOptions.scaledClipRect().isValid()) { + const QRect &clipRect = renderOptions.scaledClipRect(); + + // TODO take rotation into account, like cpdf_page.cpp lines 145-178 + float x0 = clipRect.left(); + float y0 = clipRect.top(); + float x1 = clipRect.left(); + float y1 = clipRect.bottom(); + float x2 = clipRect.right(); + float y2 = clipRect.top(); + QSizeF origSize = pageSize(page); + QVector2D pageScale(1, 1); + if (!renderOptions.scaledSize().isNull()) { + pageScale = QVector2D(renderOptions.scaledSize().width() / float(origSize.width()), + renderOptions.scaledSize().height() / float(origSize.height())); + } + FS_MATRIX matrix {(x2 - x0) / result.width() * pageScale.x(), + (y2 - y0) / result.width() * pageScale.x(), + (x1 - x0) / result.height() * pageScale.y(), + (y1 - y0) / result.height() * pageScale.y(), -x0, -y0}; + + FS_RECTF clipRectF { 0, 0, float(imageSize.width()), float(imageSize.height()) }; + + FPDF_RenderPageBitmapWithMatrix(bitmap, pdfPage, &matrix, &clipRectF, flags); + qCDebug(qLcDoc) << "matrix" << matrix.a << matrix.b << matrix.c << matrix.d << matrix.e << matrix.f; + qCDebug(qLcDoc) << "page" << page << "region" << renderOptions.scaledClipRect() + << "size" << imageSize << "took" << timer.elapsed() << "ms"; + } else { + FPDF_RenderPageBitmap(bitmap, pdfPage, 0, 0, result.width(), result.height(), rotation, flags); + qCDebug(qLcDoc) << "page" << page << "size" << imageSize << "took" << timer.elapsed() << "ms"; + } + + FPDFBitmap_Destroy(bitmap); + + FPDF_ClosePage(pdfPage); + return result; +} + +/*! + Returns information about the text on the given \a page that can be found + between the given \a start and \a end points, if any. +*/ +QPdfSelection QPdfDocument::getSelection(int page, QPointF start, QPointF end) +{ + const QPdfMutexLocker lock; + FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page); + double pageHeight = FPDF_GetPageHeight(pdfPage); + FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); + int startIndex = FPDFText_GetCharIndexAtPos(textPage, start.x(), pageHeight - start.y(), + CharacterHitTolerance, CharacterHitTolerance); + int endIndex = FPDFText_GetCharIndexAtPos(textPage, end.x(), pageHeight - end.y(), + CharacterHitTolerance, CharacterHitTolerance); + if (startIndex >= 0 && endIndex != startIndex) { + if (startIndex > endIndex) + qSwap(startIndex, endIndex); + int count = endIndex - startIndex + 1; + QString text = d->getText(textPage, startIndex, count); + QVector<QPolygonF> bounds; + int rectCount = FPDFText_CountRects(textPage, startIndex, endIndex - startIndex); + for (int i = 0; i < rectCount; ++i) { + double l, r, b, t; + FPDFText_GetRect(textPage, i, &l, &t, &r, &b); + QPolygonF poly; + poly << QPointF(l, pageHeight - t); + poly << QPointF(r, pageHeight - t); + poly << QPointF(r, pageHeight - b); + poly << QPointF(l, pageHeight - b); + poly << QPointF(l, pageHeight - t); + bounds << poly; + } + qCDebug(qLcDoc) << page << start << "->" << end << "found" << startIndex << "->" << endIndex << text; + return QPdfSelection(text, bounds); + } + + qCDebug(qLcDoc) << page << start << "->" << end << "nothing found"; + return QPdfSelection(); +} + +QPdfSelection QPdfDocument::getAllText(int page) +{ + const QPdfMutexLocker lock; + FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page); + double pageHeight = FPDF_GetPageHeight(pdfPage); + FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); + int count = FPDFText_CountChars(textPage); + if (count < 1) + return QPdfSelection(); + QString text = d->getText(textPage, 0, count); + QVector<QPolygonF> bounds; + int rectCount = FPDFText_CountRects(textPage, 0, count); + for (int i = 0; i < rectCount; ++i) { + double l, r, b, t; + FPDFText_GetRect(textPage, i, &l, &t, &r, &b); + QPolygonF poly; + poly << QPointF(l, pageHeight - t); + poly << QPointF(r, pageHeight - t); + poly << QPointF(r, pageHeight - b); + poly << QPointF(l, pageHeight - b); + poly << QPointF(l, pageHeight - t); + bounds << poly; + } + qCDebug(qLcDoc) << "on page" << page << "got" << count << "chars" << rectCount << "rects"; + return QPdfSelection(text, bounds); +} + +QT_END_NAMESPACE + +#include "moc_qpdfdocument.cpp" diff --git a/src/pdf/qpdfdocumentrenderoptions.qdoc b/src/pdf/qpdfdocumentrenderoptions.qdoc new file mode 100644 index 000000000..cc5083f9d --- /dev/null +++ b/src/pdf/qpdfdocumentrenderoptions.qdoc @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfdocumentrenderoptions.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QPdfDocumentRenderOptions + \since 5.10 + \inmodule QtPdf + + \brief The QPdfDocumentRenderOptions class holds the options to render a page from a PDF document. + + \sa QPdfDocument +*/ + +/*! + \fn QPdfDocumentRenderOptions::QPdfDocumentRenderOptions() + + Constructs a QPdfDocumentRenderOptions object. +*/ + +/*! + \fn QPdf::Rotation QPdfDocumentRenderOptions::rotation() const + + Returns the rotation used for rendering a page from a PDF document. + + \sa setRotation() +*/ + +/*! + \fn void QPdfDocumentRenderOptions::setRotation(QPdf::Rotation rotation) + + Sets the \a rotation used for rendering a page from a PDF document. + + \sa rotation() +*/ + +/*! + \fn QPdf::RenderFlags QPdfDocumentRenderOptions::renderFlags() const + + Returns the special flags used for rendering a page from a PDF document. + + \sa setRenderFlags() +*/ + +/*! + \fn void QPdfDocumentRenderOptions::setRenderFlags(QPdf::RenderFlags flags) + + Sets the special \a flags used for rendering a page from a PDF document. + + \sa renderFlags() +*/ + +/*! + \fn QRect QPdfDocumentRenderOptions::scaledClipRect() const + + Returns the rectangular region to be clipped from the page after having + been scaled to \l scaledSize(). + + \sa setScaledClipRect() +*/ + +/*! + \fn void QPdfDocumentRenderOptions::setScaledClipRect(QRect rect) + + Sets the region \a rect to be clipped from the page after having been + scaled to \l scaledSize(). + + \sa scaledClipRect() +*/ + +/*! + \fn QRect QPdfDocumentRenderOptions::scaledSize() const + + Returns the \a size of the page to be rendered, in pixels. + + \sa setScaledSize() +*/ + +/*! + \fn void QPdfDocumentRenderOptions::setScaledSize(QSize size) + + Sets the \a size of the page to be rendered, in pixels. + + \sa scaledSize() +*/ + +/*! + \fn bool operator!=(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) + \relates QPdfDocumentRenderOptions + + Returns \c true if the options \a lhs and \a rhs are different, otherwise + returns \c false. +*/ + +/*! + \fn bool operator==(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) + \relates QPdfDocumentRenderOptions + + Returns \c true if the options \a lhs and \a rhs are equal, + otherwise returns \c false. +*/ + +QT_END_NAMESPACE diff --git a/src/pdf/qpdflinkmodel.cpp b/src/pdf/qpdflinkmodel.cpp new file mode 100644 index 000000000..96e6ddd5c --- /dev/null +++ b/src/pdf/qpdflinkmodel.cpp @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdflinkmodel_p.h" +#include "qpdflinkmodel_p_p.h" +#include "qpdfdocument_p.h" + +#include "third_party/pdfium/public/fpdf_doc.h" +#include "third_party/pdfium/public/fpdf_text.h" + +#include <QLoggingCategory> +#include <QMetaEnum> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcLink, "qt.pdf.links") + +QPdfLinkModel::QPdfLinkModel(QObject *parent) + : QAbstractListModel(*(new QPdfLinkModelPrivate()), parent) +{ + QMetaEnum rolesMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("Role")); + for (int r = Qt::UserRole; r < int(Role::_Count); ++r) + m_roleNames.insert(r, QByteArray(rolesMetaEnum.valueToKey(r)).toLower()); +} + +QPdfLinkModel::~QPdfLinkModel() {} + +QHash<int, QByteArray> QPdfLinkModel::roleNames() const +{ + return m_roleNames; +} + +int QPdfLinkModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QPdfLinkModel); + Q_UNUSED(parent) + return d->links.count(); +} + +QVariant QPdfLinkModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QPdfLinkModel); + const QPdfLinkModelPrivate::Link &link = d->links.at(index.row()); + switch (Role(role)) { + case Role::Rect: + return link.rect; + case Role::Url: + return link.url; + case Role::Page: + return link.page; + case Role::Location: + return link.location; + case Role::Zoom: + return link.zoom; + case Role::_Count: + break; + } + if (role == Qt::DisplayRole) + return link.toString(); + return QVariant(); +} + +QPdfDocument *QPdfLinkModel::document() const +{ + Q_D(const QPdfLinkModel); + return d->document; +} + +void QPdfLinkModel::setDocument(QPdfDocument *document) +{ + Q_D(QPdfLinkModel); + if (d->document == document) + return; + disconnect(d->document, &QPdfDocument::statusChanged, this, &QPdfLinkModel::onStatusChanged); + connect(document, &QPdfDocument::statusChanged, this, &QPdfLinkModel::onStatusChanged); + d->document = document; + emit documentChanged(); + if (page()) + setPage(0); + else + d->update(); +} + +int QPdfLinkModel::page() const +{ + Q_D(const QPdfLinkModel); + return d->page; +} + +void QPdfLinkModel::setPage(int page) +{ + Q_D(QPdfLinkModel); + if (d->page == page) + return; + + d->page = page; + emit pageChanged(page); + d->update(); +} + +QPdfLinkModelPrivate::QPdfLinkModelPrivate() : QAbstractItemModelPrivate() +{ +} + +void QPdfLinkModelPrivate::update() +{ + Q_Q(QPdfLinkModel); + if (!document || !document->d->doc) + return; + auto doc = document->d->doc; + const QPdfMutexLocker lock; + FPDF_PAGE pdfPage = FPDF_LoadPage(doc, page); + if (!pdfPage) { + qWarning() << "failed to load page" << page; + return; + } + double pageHeight = FPDF_GetPageHeight(pdfPage); + q->beginResetModel(); + links.clear(); + + // Iterate the ordinary links + int linkStart = 0; + bool ok = true; + while (ok) { + FPDF_LINK linkAnnot; + ok = FPDFLink_Enumerate(pdfPage, &linkStart, &linkAnnot); + if (!ok) + break; + FS_RECTF rect; + ok = FPDFLink_GetAnnotRect(linkAnnot, &rect); + if (!ok) + break; + Link linkData; + linkData.rect = QRectF(rect.left, pageHeight - rect.top, + rect.right - rect.left, rect.top - rect.bottom); + FPDF_DEST dest = FPDFLink_GetDest(doc, linkAnnot); + FPDF_ACTION action = FPDFLink_GetAction(linkAnnot); + if (FPDFAction_GetType(action) != PDFACTION_GOTO) { + qWarning() << "link action type" << FPDFAction_GetType(action) << "is not yet supported"; + continue; + } + linkData.page = FPDFDest_GetDestPageIndex(doc, dest); + FPDF_BOOL hasX, hasY, hasZoom; + FS_FLOAT x, y, zoom; + ok = FPDFDest_GetLocationInPage(dest, &hasX, &hasY, &hasZoom, &x, &y, &zoom); + if (!ok) + break; + if (hasX && hasY) + linkData.location = QPointF(x, pageHeight - y); + if (hasZoom) + linkData.zoom = zoom; + links << linkData; + } + + // Iterate the web links + FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); + if (textPage) { + FPDF_PAGELINK webLinks = FPDFLink_LoadWebLinks(textPage); + if (webLinks) { + int count = FPDFLink_CountWebLinks(webLinks); + for (int i = 0; i < count; ++i) { + Link linkData; + int len = FPDFLink_GetURL(webLinks, i, nullptr, 0); + if (len < 1) { + qWarning() << "URL" << i << "has length" << len; + } else { + QVector<unsigned short> buf(len); + int got = FPDFLink_GetURL(webLinks, i, buf.data(), len); + Q_ASSERT(got == len); + linkData.url = QString::fromUtf16(buf.data(), got - 1); + } + FPDFLink_GetTextRange(webLinks, i, &linkData.textStart, &linkData.textCharCount); + len = FPDFLink_CountRects(webLinks, i); + for (int r = 0; r < len; ++r) { + double left, top, right, bottom; + bool success = FPDFLink_GetRect(webLinks, i, r, &left, &top, &right, &bottom); + if (success) { + linkData.rect = QRectF(left, pageHeight - top, right - left, top - bottom); + links << linkData; + } + } + } + FPDFLink_CloseWebLinks(webLinks); + } + FPDFText_ClosePage(textPage); + } + + // All done + FPDF_ClosePage(pdfPage); + if (Q_UNLIKELY(qLcLink().isDebugEnabled())) { + for (const Link &l : links) + qCDebug(qLcLink) << l.rect << l.toString(); + } + q->endResetModel(); +} + +void QPdfLinkModel::onStatusChanged(QPdfDocument::Status status) +{ + Q_D(QPdfLinkModel); + qCDebug(qLcLink) << "sees document statusChanged" << status; + if (status == QPdfDocument::Ready) + d->update(); +} + +QString QPdfLinkModelPrivate::Link::toString() const +{ + QString ret; + if (page >= 0) + return QLatin1String("page ") + QString::number(page) + + QLatin1String(" location ") + QString::number(location.x()) + QLatin1Char(',') + QString::number(location.y()) + + QLatin1String(" zoom ") + QString::number(zoom); + else + return url.toString(); +} + +QT_END_NAMESPACE + +#include "moc_qpdflinkmodel_p.cpp" diff --git a/src/pdf/qpdfnamespace.qdoc b/src/pdf/qpdfnamespace.qdoc new file mode 100644 index 000000000..96bb090e9 --- /dev/null +++ b/src/pdf/qpdfnamespace.qdoc @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \namespace QPdf + \inmodule QtPdf + \keyword QPdf Namespace + + \brief The QPdf namespace contains miscellaneous identifiers + used throughout the QtPdf module. +*/ + +/*! + \enum QPdf::Rotation + + This enum describes the rotation of the page for rendering. + + \value Rotate0 Do not rotate (the default) + \value Rotate90 Rotate 90 degrees clockwise + \value Rotate180 Rotate 180 degrees + \value Rotate270 Rotate 270 degrees clockwise + + \sa QPdfDocument::render() +*/ +/*! + \enum QPdf::RenderFlag + + This enum is used to describe how a page should be rendered. + + \value NoRenderFlags The default value, representing no flags. + \value RenderAnnotations The page is rendered with annotations. + \value RenderOptimizedForLcd The text of the page is rendered optimized for LCD display. + \value RenderGrayscale The page is rendered grayscale. + \value RenderForceHalftone Always use halftones for rendering if the output image is stretched. + \value RenderTextAliased Anti-aliasing is disabled for rendering text. + \value RenderImageAliased Anti-aliasing is disabled for rendering images. + \value RenderPathAliased Anti-aliasing is disabled for rendering paths. + + \sa QPdfDocument::render() +*/ + diff --git a/src/pdf/qpdfpagenavigation.cpp b/src/pdf/qpdfpagenavigation.cpp new file mode 100644 index 000000000..497c1c2eb --- /dev/null +++ b/src/pdf/qpdfpagenavigation.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfpagenavigation.h" + +#include "qpdfdocument.h" + +#include <private/qobject_p.h> + +#include <QPointer> + +QT_BEGIN_NAMESPACE + +class QPdfPageNavigationPrivate : public QObjectPrivate +{ +public: + QPdfPageNavigationPrivate() + : QObjectPrivate() + { + } + + void update() + { + Q_Q(QPdfPageNavigation); + + const bool documentAvailable = m_document && m_document->status() == QPdfDocument::Ready; + + if (documentAvailable) { + const int newPageCount = m_document->pageCount(); + if (m_pageCount != newPageCount) { + m_pageCount = newPageCount; + emit q->pageCountChanged(m_pageCount); + } + } else { + if (m_pageCount != 0) { + m_pageCount = 0; + emit q->pageCountChanged(m_pageCount); + } + } + + if (m_currentPage != 0) { + m_currentPage = 0; + emit q->currentPageChanged(m_currentPage); + } + + updatePrevNext(); + } + + void updatePrevNext() + { + Q_Q(QPdfPageNavigation); + + const bool hasPreviousPage = m_currentPage > 0; + const bool hasNextPage = m_currentPage < (m_pageCount - 1); + + if (m_canGoToPreviousPage != hasPreviousPage) { + m_canGoToPreviousPage = hasPreviousPage; + emit q->canGoToPreviousPageChanged(m_canGoToPreviousPage); + } + + if (m_canGoToNextPage != hasNextPage) { + m_canGoToNextPage = hasNextPage; + emit q->canGoToNextPageChanged(m_canGoToNextPage); + } + } + + void documentStatusChanged() + { + update(); + } + + Q_DECLARE_PUBLIC(QPdfPageNavigation) + + QPointer<QPdfDocument> m_document = nullptr; + int m_currentPage = 0; + int m_pageCount = 0; + bool m_canGoToPreviousPage = false; + bool m_canGoToNextPage = false; + + QMetaObject::Connection m_documentStatusChangedConnection; +}; + +/*! + \class QPdfPageNavigation + \since 5.10 + \inmodule QtPdf + + \brief The QPdfPageNavigation class handles the navigation through a PDF document. + + \sa QPdfDocument +*/ + + +/*! + Constructs a page navigation object with parent object \a parent. +*/ +QPdfPageNavigation::QPdfPageNavigation(QObject *parent) + : QObject(*new QPdfPageNavigationPrivate, parent) +{ +} + +/*! + Destroys the page navigation object. +*/ +QPdfPageNavigation::~QPdfPageNavigation() +{ +} + +/*! + \property QPdfPageNavigation::document + \brief The document instance on which this object navigates. + + By default, this property is \c nullptr. + + \sa document(), setDocument(), QPdfDocument +*/ + +/*! + Returns the document on which this object navigates, or a \c nullptr + if none has set before. + + \sa QPdfDocument +*/ +QPdfDocument* QPdfPageNavigation::document() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_document; +} + +/*! + Sets the \a document this object navigates on. + + After a new document has been set, the currentPage will be \c 0. + + \sa QPdfDocument +*/ +void QPdfPageNavigation::setDocument(QPdfDocument *document) +{ + Q_D(QPdfPageNavigation); + + if (d->m_document == document) + return; + + if (d->m_document) + disconnect(d->m_documentStatusChangedConnection); + + d->m_document = document; + emit documentChanged(d->m_document); + + if (d->m_document) + d->m_documentStatusChangedConnection = connect(d->m_document.data(), &QPdfDocument::statusChanged, this, [d](){ d->documentStatusChanged(); }); + + d->update(); +} + +/*! + \property QPdfPageNavigation::currentPage + \brief The current page number in the document. + + \sa currentPage(), setCurrentPage() +*/ + +/*! + Returns the current page number or \c 0 if there is no document set. + + After a document has been loaded, the currentPage will always be \c 0. +*/ +int QPdfPageNavigation::currentPage() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_currentPage; +} + +/*! + \fn void QPdfPageNavigation::setCurrentPage(int page) + + Sets the current \a page number. +*/ +void QPdfPageNavigation::setCurrentPage(int newPage) +{ + Q_D(QPdfPageNavigation); + + if (newPage < 0 || newPage >= d->m_pageCount) + return; + + if (d->m_currentPage == newPage) + return; + + d->m_currentPage = newPage; + emit currentPageChanged(d->m_currentPage); + + d->updatePrevNext(); +} + +/*! + \property QPdfPageNavigation::pageCount + \brief The number of pages in the document. + + \sa pageCount() +*/ + +/*! + Returns the number of pages in the document or \c 0 if there + is no document set. +*/ +int QPdfPageNavigation::pageCount() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_pageCount; +} + +/*! + \property QPdfPageNavigation::canGoToPreviousPage + \brief Indicates whether there is a page before the current page. + + \sa canGoToPreviousPage(), goToPreviousPage() +*/ + +/*! + Returns whether there is a page before the current one. +*/ +bool QPdfPageNavigation::canGoToPreviousPage() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_canGoToPreviousPage; +} + +/*! + \property QPdfPageNavigation::canGoToNextPage + \brief Indicates whether there is a page after the current page. + + \sa canGoToNextPage(), goToNextPage() +*/ + +/*! + Returns whether there is a page after the current one. +*/ +bool QPdfPageNavigation::canGoToNextPage() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_canGoToNextPage; +} + +/*! + Changes the current page to the previous page. + + If there is no previous page in the document, nothing happens. + + \sa canGoToPreviousPage +*/ +void QPdfPageNavigation::goToPreviousPage() +{ + Q_D(QPdfPageNavigation); + + if (d->m_currentPage > 0) + setCurrentPage(d->m_currentPage - 1); +} + +/*! + Changes the current page to the next page. + + If there is no next page in the document, nothing happens. + + \sa canGoToNextPage +*/ +void QPdfPageNavigation::goToNextPage() +{ + Q_D(QPdfPageNavigation); + + if (d->m_currentPage < d->m_pageCount - 1) + setCurrentPage(d->m_currentPage + 1); +} + +QT_END_NAMESPACE + +#include "moc_qpdfpagenavigation.cpp" diff --git a/src/pdf/qpdfpagerenderer.cpp b/src/pdf/qpdfpagerenderer.cpp new file mode 100644 index 000000000..31d9f4e1e --- /dev/null +++ b/src/pdf/qpdfpagerenderer.cpp @@ -0,0 +1,361 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfpagerenderer.h" + +#include <private/qobject_p.h> +#include <QMutex> +#include <QPdfDocument> +#include <QPointer> +#include <QThread> + +QT_BEGIN_NAMESPACE + +class RenderWorker : public QObject +{ + Q_OBJECT + +public: + RenderWorker(); + ~RenderWorker(); + + void setDocument(QPdfDocument *document); + +public Q_SLOTS: + void requestPage(quint64 requestId, int page, QSize imageSize, + QPdfDocumentRenderOptions options); + +Q_SIGNALS: + void pageRendered(int page, QSize imageSize, const QImage &image, + QPdfDocumentRenderOptions options, quint64 requestId); + +private: + QPointer<QPdfDocument> m_document; + QMutex m_mutex; +}; + +class QPdfPageRendererPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QPdfPageRenderer) + +public: + QPdfPageRendererPrivate(); + ~QPdfPageRendererPrivate(); + + void handleNextRequest(); + void requestFinished(int page, QSize imageSize, const QImage &image, + QPdfDocumentRenderOptions options, quint64 requestId); + + QPdfPageRenderer::RenderMode m_renderMode = QPdfPageRenderer::RenderMode::SingleThreaded; + QPointer<QPdfDocument> m_document; + + struct PageRequest + { + quint64 id; + int pageNumber; + QSize imageSize; + QPdfDocumentRenderOptions options; + }; + + QVector<PageRequest> m_requests; + QVector<PageRequest> m_pendingRequests; + quint64 m_requestIdCounter = 1; + + QThread *m_renderThread = nullptr; + QScopedPointer<RenderWorker> m_renderWorker; +}; + +Q_DECLARE_TYPEINFO(QPdfPageRendererPrivate::PageRequest, Q_PRIMITIVE_TYPE); + + +RenderWorker::RenderWorker() + : m_document(nullptr) +{ +} + +RenderWorker::~RenderWorker() +{ +} + +void RenderWorker::setDocument(QPdfDocument *document) +{ + const QMutexLocker locker(&m_mutex); + + if (m_document == document) + return; + + m_document = document; +} + +void RenderWorker::requestPage(quint64 requestId, int pageNumber, QSize imageSize, + QPdfDocumentRenderOptions options) +{ + const QMutexLocker locker(&m_mutex); + + if (!m_document || m_document->status() != QPdfDocument::Ready) + return; + + const QImage image = m_document->render(pageNumber, imageSize, options); + + emit pageRendered(pageNumber, imageSize, image, options, requestId); +} + + +QPdfPageRendererPrivate::QPdfPageRendererPrivate() + : QObjectPrivate() + , m_renderWorker(new RenderWorker) +{ +} + +QPdfPageRendererPrivate::~QPdfPageRendererPrivate() +{ + if (m_renderThread) { + m_renderThread->quit(); + m_renderThread->wait(); + } +} + +void QPdfPageRendererPrivate::handleNextRequest() +{ + if (m_requests.isEmpty()) + return; + + const PageRequest request = m_requests.takeFirst(); + m_pendingRequests.append(request); + + QMetaObject::invokeMethod(m_renderWorker.data(), "requestPage", Qt::QueuedConnection, + Q_ARG(quint64, request.id), Q_ARG(int, request.pageNumber), + Q_ARG(QSize, request.imageSize), Q_ARG(QPdfDocumentRenderOptions, + request.options)); +} + +void QPdfPageRendererPrivate::requestFinished(int page, QSize imageSize, const QImage &image, QPdfDocumentRenderOptions options, quint64 requestId) +{ + Q_UNUSED(image); + Q_UNUSED(requestId); + const auto it = std::find_if(m_pendingRequests.begin(), m_pendingRequests.end(), + [page, imageSize, options](const PageRequest &request){ return request.pageNumber == page && request.imageSize == imageSize && request.options == options; }); + + if (it != m_pendingRequests.end()) + m_pendingRequests.erase(it); +} + +/*! + \class QPdfPageRenderer + \since 5.11 + \inmodule QtPdf + + \brief The QPdfPageRenderer class encapsulates the rendering of pages of a PDF document. + + The QPdfPageRenderer contains a queue that collects all render requests that are invoked through + requestPage(). Depending on the configured RenderMode the QPdfPageRenderer processes this queue + in the main UI thread on next event loop invocation (\c RenderMode::SingleThreaded) or in a separate worker thread + (\c RenderMode::MultiThreaded) and emits the result through the pageRendered() signal for each request once + the rendering is done. + + \sa QPdfDocument +*/ + + +/*! + Constructs a page renderer object with parent object \a parent. +*/ +QPdfPageRenderer::QPdfPageRenderer(QObject *parent) + : QObject(*new QPdfPageRendererPrivate(), parent) +{ + Q_D(QPdfPageRenderer); + + qRegisterMetaType<QPdfDocumentRenderOptions>(); + + connect(d->m_renderWorker.data(), &RenderWorker::pageRendered, this, + [this,d](int page, QSize imageSize, const QImage &image, QPdfDocumentRenderOptions options, quint64 requestId) { + d->requestFinished(page, imageSize, image, options, requestId); + emit pageRendered(page, imageSize, image, options, requestId); + d->handleNextRequest(); + }); +} + +/*! + Destroys the page renderer object. +*/ +QPdfPageRenderer::~QPdfPageRenderer() +{ +} + +/*! + \enum QPdfPageRenderer::RenderMode + + This enum describes how the pages are rendered. + + \value MultiThreaded All pages are rendered in a separate worker thread. + \value SingleThreaded All pages are rendered in the main UI thread (default). + + \sa renderMode(), setRenderMode() +*/ + +/*! + \property QPdfPageRenderer::renderMode + \brief The mode the renderer uses to render the pages. + + By default, this property is \c RenderMode::SingleThreaded. + + \sa setRenderMode(), RenderMode +*/ + +/*! + Returns the mode of how the pages are rendered. + + \sa RenderMode +*/ +QPdfPageRenderer::RenderMode QPdfPageRenderer::renderMode() const +{ + Q_D(const QPdfPageRenderer); + + return d->m_renderMode; +} + +/*! + Sets the mode of how the pages are rendered to \a mode. + + \sa RenderMode +*/ +void QPdfPageRenderer::setRenderMode(RenderMode mode) +{ + Q_D(QPdfPageRenderer); + + if (d->m_renderMode == mode) + return; + + d->m_renderMode = mode; + emit renderModeChanged(d->m_renderMode); + + if (d->m_renderMode == RenderMode::MultiThreaded) { + d->m_renderThread = new QThread; + d->m_renderWorker->moveToThread(d->m_renderThread); + d->m_renderThread->start(); + } else { + d->m_renderThread->quit(); + d->m_renderThread->wait(); + delete d->m_renderThread; + d->m_renderThread = nullptr; + + // pulling the object from another thread should be fine, once that thread is deleted + d->m_renderWorker->moveToThread(this->thread()); + } +} + +/*! + \property QPdfPageRenderer::document + \brief The document instance this object renders the pages from. + + By default, this property is \c nullptr. + + \sa document(), setDocument(), QPdfDocument +*/ + +/*! + Returns the document this objects renders the pages from, or a \c nullptr + if none has been set before. + + \sa QPdfDocument +*/ +QPdfDocument* QPdfPageRenderer::document() const +{ + Q_D(const QPdfPageRenderer); + + return d->m_document; +} + +/*! + Sets the \a document this object renders the pages from. + + \sa QPdfDocument +*/ +void QPdfPageRenderer::setDocument(QPdfDocument *document) +{ + Q_D(QPdfPageRenderer); + + if (d->m_document == document) + return; + + d->m_document = document; + emit documentChanged(d->m_document); + + d->m_renderWorker->setDocument(d->m_document); +} + +/*! + Requests the renderer to render the page \a pageNumber into a QImage of size \a imageSize + according to the provided \a options. + + Once the rendering is done the pageRendered() signal is emitted with the result as parameters. + + The return value is an ID that uniquely identifies the render request. If a request with the + same parameters is still in the queue, the ID of that queued request is returned. +*/ +quint64 QPdfPageRenderer::requestPage(int pageNumber, QSize imageSize, + QPdfDocumentRenderOptions options) +{ + Q_D(QPdfPageRenderer); + + if (!d->m_document || d->m_document->status() != QPdfDocument::Ready) + return 0; + + for (const auto &request : qAsConst(d->m_pendingRequests)) { + if (request.pageNumber == pageNumber + && request.imageSize == imageSize + && request.options == options) + return request.id; + } + + const auto id = d->m_requestIdCounter++; + + QPdfPageRendererPrivate::PageRequest request; + request.id = id; + request.pageNumber = pageNumber; + request.imageSize = imageSize; + request.options = options; + + d->m_requests.append(request); + + d->handleNextRequest(); + + return id; +} + +QT_END_NAMESPACE + +#include "qpdfpagerenderer.moc" diff --git a/src/pdf/qpdfsearchmodel.cpp b/src/pdf/qpdfsearchmodel.cpp new file mode 100644 index 000000000..27b7833fc --- /dev/null +++ b/src/pdf/qpdfsearchmodel.cpp @@ -0,0 +1,324 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfdestination.h" +#include "qpdfdocument_p.h" +#include "qpdfsearchmodel.h" +#include "qpdfsearchmodel_p.h" +#include "qpdfsearchresult_p.h" + +#include "third_party/pdfium/public/fpdf_doc.h" +#include "third_party/pdfium/public/fpdf_text.h" + +#include <QtCore/qelapsedtimer.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/QMetaEnum> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcS, "qt.pdf.search") + +static const int UpdateTimerInterval = 100; +static const int ContextChars = 64; +static const double CharacterHitTolerance = 6.0; + +QPdfSearchModel::QPdfSearchModel(QObject *parent) + : QAbstractListModel(*(new QPdfSearchModelPrivate()), parent) +{ + QMetaEnum rolesMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("Role")); + for (int r = Qt::UserRole; r < int(Role::_Count); ++r) { + QByteArray roleName = QByteArray(rolesMetaEnum.valueToKey(r)); + if (roleName.isEmpty()) + continue; + roleName[0] = QChar::toLower(roleName[0]); + m_roleNames.insert(r, roleName); + } +} + +QPdfSearchModel::~QPdfSearchModel() {} + +QHash<int, QByteArray> QPdfSearchModel::roleNames() const +{ + return m_roleNames; +} + +int QPdfSearchModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QPdfSearchModel); + Q_UNUSED(parent) + return d->rowCountSoFar; +} + +QVariant QPdfSearchModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QPdfSearchModel); + const auto pi = const_cast<QPdfSearchModelPrivate*>(d)->pageAndIndexForResult(index.row()); + if (pi.page < 0) + return QVariant(); + switch (Role(role)) { + case Role::Page: + return pi.page; + case Role::IndexOnPage: + return pi.index; + case Role::Location: + return d->searchResults[pi.page][pi.index].location(); + case Role::ContextBefore: + return d->searchResults[pi.page][pi.index].contextBefore(); + case Role::ContextAfter: + return d->searchResults[pi.page][pi.index].contextAfter(); + case Role::_Count: + break; + } + if (role == Qt::DisplayRole) { + const QString ret = d->searchResults[pi.page][pi.index].contextBefore() + + QLatin1String("<b>") + d->searchString + QLatin1String("</b>") + + d->searchResults[pi.page][pi.index].contextAfter(); + return ret; + } + return QVariant(); +} + +void QPdfSearchModel::updatePage(int page) +{ + Q_D(QPdfSearchModel); + d->doSearch(page); +} + +QString QPdfSearchModel::searchString() const +{ + Q_D(const QPdfSearchModel); + return d->searchString; +} + +void QPdfSearchModel::setSearchString(QString searchString) +{ + Q_D(QPdfSearchModel); + if (d->searchString == searchString) + return; + + d->searchString = searchString; + beginResetModel(); + d->clearResults(); + emit searchStringChanged(); + endResetModel(); +} + +QVector<QPdfSearchResult> QPdfSearchModel::resultsOnPage(int page) const +{ + Q_D(const QPdfSearchModel); + const_cast<QPdfSearchModelPrivate *>(d)->doSearch(page); + if (d->searchResults.count() <= page) + return {}; + return d->searchResults[page]; +} + +QPdfSearchResult QPdfSearchModel::resultAtIndex(int index) const +{ + Q_D(const QPdfSearchModel); + const auto pi = const_cast<QPdfSearchModelPrivate*>(d)->pageAndIndexForResult(index); + if (pi.page < 0) + return QPdfSearchResult(); + return d->searchResults[pi.page][pi.index]; +} + +QPdfDocument *QPdfSearchModel::document() const +{ + Q_D(const QPdfSearchModel); + return d->document; +} + +void QPdfSearchModel::setDocument(QPdfDocument *document) +{ + Q_D(QPdfSearchModel); + if (d->document == document) + return; + + d->document = document; + d->clearResults(); + emit documentChanged(); +} + +void QPdfSearchModel::timerEvent(QTimerEvent *event) +{ + Q_D(QPdfSearchModel); + if (event->timerId() != d->updateTimerId) + return; + if (!d->document || d->nextPageToUpdate >= d->document->pageCount()) { + if (d->document) + qCDebug(qLcS, "done updating search results on %d pages", d->searchResults.count()); + killTimer(d->updateTimerId); + d->updateTimerId = -1; + } + d->doSearch(d->nextPageToUpdate++); +} + +QPdfSearchModelPrivate::QPdfSearchModelPrivate() : QAbstractItemModelPrivate() +{ +} + +void QPdfSearchModelPrivate::clearResults() +{ + Q_Q(QPdfSearchModel); + rowCountSoFar = 0; + searchResults.clear(); + pagesSearched.clear(); + if (document) { + searchResults.resize(document->pageCount()); + pagesSearched.resize(document->pageCount()); + } else { + searchResults.resize(0); + pagesSearched.resize(0); + } + nextPageToUpdate = 0; + updateTimerId = q->startTimer(UpdateTimerInterval); +} + +bool QPdfSearchModelPrivate::doSearch(int page) +{ + if (page < 0 || page >= pagesSearched.count() || searchString.isEmpty()) + return false; + if (pagesSearched[page]) + return true; + Q_Q(QPdfSearchModel); + + const QPdfMutexLocker lock; + QElapsedTimer timer; + timer.start(); + FPDF_PAGE pdfPage = FPDF_LoadPage(document->d->doc, page); + if (!pdfPage) { + qWarning() << "failed to load page" << page; + return false; + } + double pageHeight = FPDF_GetPageHeight(pdfPage); + FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); + if (!textPage) { + qWarning() << "failed to load text of page" << page; + FPDF_ClosePage(pdfPage); + return false; + } + FPDF_SCHHANDLE sh = FPDFText_FindStart(textPage, searchString.utf16(), 0, 0); + QVector<QPdfSearchResult> newSearchResults; + while (FPDFText_FindNext(sh)) { + int idx = FPDFText_GetSchResultIndex(sh); + int count = FPDFText_GetSchCount(sh); + int rectCount = FPDFText_CountRects(textPage, idx, count); + QVector<QRectF> rects; + int startIndex = -1; + int endIndex = -1; + for (int r = 0; r < rectCount; ++r) { + double left, top, right, bottom; + FPDFText_GetRect(textPage, r, &left, &top, &right, &bottom); + rects << QRectF(left, pageHeight - top, right - left, top - bottom); + if (r == 0) { + startIndex = FPDFText_GetCharIndexAtPos(textPage, left, top, + CharacterHitTolerance, CharacterHitTolerance); + } + if (r == rectCount - 1) { + endIndex = FPDFText_GetCharIndexAtPos(textPage, right, top, + CharacterHitTolerance, CharacterHitTolerance); + } + qCDebug(qLcS) << rects.last() << "char idx" << startIndex << "->" << endIndex; + } + QString contextBefore, contextAfter; + if (startIndex >= 0 || endIndex >= 0) { + startIndex = qMax(0, startIndex - ContextChars); + endIndex += ContextChars; + int count = endIndex - startIndex + 1; + if (count > 0) { + QVector<ushort> buf(count + 1); + int len = FPDFText_GetText(textPage, startIndex, count, buf.data()); + Q_ASSERT(len - 1 <= count); // len is number of characters written, including the terminator + QString context = QString::fromUtf16(buf.constData(), len - 1); + context = context.replace(QLatin1Char('\n'), QStringLiteral("\u23CE")); + context = context.remove(QLatin1Char('\r')); + // try to find the search string near the middle of the context if possible + int si = context.indexOf(searchString, ContextChars - 5, Qt::CaseInsensitive); + if (si < 0) + si = context.indexOf(searchString, Qt::CaseInsensitive); + if (si < 0) + qWarning() << "search string" << searchString << "not found in context" << context; + contextBefore = context.mid(0, si); + contextAfter = context.mid(si + searchString.length()); + } + } + if (!rects.isEmpty()) + newSearchResults << QPdfSearchResult(page, rects, contextBefore, contextAfter); + } + FPDFText_FindClose(sh); + FPDFText_ClosePage(textPage); + FPDF_ClosePage(pdfPage); + qCDebug(qLcS) << searchString << "took" << timer.elapsed() << "ms to find" + << newSearchResults.count() << "results on page" << page; + + pagesSearched[page] = true; + searchResults[page] = newSearchResults; + if (newSearchResults.count() > 0) { + int rowsBefore = rowsBeforePage(page); + qCDebug(qLcS) << "from row" << rowsBefore << "rowCount" << rowCountSoFar << "increasing by" << newSearchResults.count(); + rowCountSoFar += newSearchResults.count(); + q->beginInsertRows(QModelIndex(), rowsBefore, rowsBefore + newSearchResults.count() - 1); + q->endInsertRows(); + } + return true; +} + +QPdfSearchModelPrivate::PageAndIndex QPdfSearchModelPrivate::pageAndIndexForResult(int resultIndex) +{ + const int pageCount = document->pageCount(); + int totalSoFar = 0; + int previousTotalSoFar = 0; + for (int page = 0; page < pageCount; ++page) { + if (!pagesSearched[page]) + doSearch(page); + totalSoFar += searchResults[page].count(); + if (totalSoFar > resultIndex) + return {page, resultIndex - previousTotalSoFar}; + previousTotalSoFar = totalSoFar; + } + return {-1, -1}; +} + +int QPdfSearchModelPrivate::rowsBeforePage(int page) +{ + int ret = 0; + for (int i = 0; i < page; ++i) + ret += searchResults[i].count(); + return ret; +} + +QT_END_NAMESPACE + +#include "moc_qpdfsearchmodel.cpp" diff --git a/src/pdf/qpdfsearchresult.cpp b/src/pdf/qpdfsearchresult.cpp new file mode 100644 index 000000000..53da1c165 --- /dev/null +++ b/src/pdf/qpdfsearchresult.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfsearchresult.h" +#include "qpdfsearchresult_p.h" + +QT_BEGIN_NAMESPACE + +QPdfSearchResult::QPdfSearchResult() : + QPdfSearchResult(new QPdfSearchResultPrivate()) { } + +QPdfSearchResult::QPdfSearchResult(int page, QVector<QRectF> rects, QString contextBefore, QString contextAfter) : + QPdfSearchResult(new QPdfSearchResultPrivate(page, rects, contextBefore, contextAfter)) { } + +QPdfSearchResult::QPdfSearchResult(QPdfSearchResultPrivate *d) : + QPdfDestination(static_cast<QPdfDestinationPrivate *>(d)) { } + +QString QPdfSearchResult::contextBefore() const +{ + return static_cast<QPdfSearchResultPrivate *>(d.data())->contextBefore; +} + +QString QPdfSearchResult::contextAfter() const +{ + return static_cast<QPdfSearchResultPrivate *>(d.data())->contextAfter; +} + +QVector<QRectF> QPdfSearchResult::rectangles() const +{ + return static_cast<QPdfSearchResultPrivate *>(d.data())->rects; +} + +QDebug operator<<(QDebug dbg, const QPdfSearchResult &searchResult) +{ + QDebugStateSaver saver(dbg); + dbg.nospace(); + dbg << "QPdfSearchResult(page=" << searchResult.page() + << " contextBefore=" << searchResult.contextBefore() + << " contextAfter=" << searchResult.contextAfter() + << " rects=" << searchResult.rectangles(); + dbg << ')'; + return dbg; +} + +QT_END_NAMESPACE + +#include "moc_qpdfsearchresult.cpp" diff --git a/src/pdf/qpdfselection.cpp b/src/pdf/qpdfselection.cpp new file mode 100644 index 000000000..e334f0fb6 --- /dev/null +++ b/src/pdf/qpdfselection.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfselection.h" +#include "qpdfselection_p.h" +#include <QGuiApplication> + +QT_BEGIN_NAMESPACE + +/*! + \class QPdfSelection + \since 5.15 + \inmodule QtPdf + + \brief The QPdfSelection class defines a range of text that has been selected + on one page in a PDF document, and its geometric boundaries. + + \sa QPdfDocument::getSelection() +*/ + +/*! + Constructs an invalid selection. + + \sa valid +*/ +QPdfSelection::QPdfSelection() + : d(new QPdfSelectionPrivate()) +{ +} + +/*! + \internal + Constructs a selection including the range of characters that make up the + \a text string, and which take up space on the page within the polygon + regions given in \a bounds. +*/ +QPdfSelection::QPdfSelection(const QString &text, QVector<QPolygonF> bounds) + : d(new QPdfSelectionPrivate(text, bounds)) +{ +} + +QPdfSelection::QPdfSelection(QPdfSelectionPrivate *d) + : d(d) +{ +} + +QPdfSelection::QPdfSelection(const QPdfSelection &other) + : d(other.d) +{ +} + +QPdfSelection::QPdfSelection(QPdfSelection &&other) noexcept + : d(std::move(other.d)) +{ +} + +QPdfSelection::~QPdfSelection() +{ +} + +QPdfSelection &QPdfSelection::operator=(const QPdfSelection &other) +{ + d = other.d; + return *this; +} + +/*! + \property QPdfSelection::valid + + This property holds whether the selection is valid. +*/ +bool QPdfSelection::isValid() const +{ + return !d->bounds.isEmpty(); +} + +/*! + \property QPdfSelection::bounds + + This property holds a set of regions that the selected text occupies on the + page, represented as polygons. The coordinate system for the polygons has + the origin at the upper-left corner of the page, and the units are + \l {https://en.wikipedia.org/wiki/Point_(typography)}{points}. + + \note For now, the polygons returned from \l QPdfDocument::getSelection() + are always rectangles; but in the future it may be possible to represent + more complex regions. +*/ +QVector<QPolygonF> QPdfSelection::bounds() const +{ + return d->bounds; +} + +/*! + \property QPdfSelection::text + + This property holds the selected text. +*/ +QString QPdfSelection::text() const +{ + return d->text; +} + +#if QT_CONFIG(clipboard) +/*! + Copies \l text to the \l {QGuiApplication::clipboard()}{system clipboard}. +*/ +void QPdfSelection::copyToClipboard(QClipboard::Mode mode) const +{ + QGuiApplication::clipboard()->setText(d->text, mode); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qpdfselection.cpp" diff --git a/src/pdf/qtpdf.gni b/src/pdf/qtpdf.gni new file mode 100644 index 000000000..c31f3e9a0 --- /dev/null +++ b/src/pdf/qtpdf.gni @@ -0,0 +1,7 @@ +include_dirs = [ +] + +deps = [ + "//third_party/pdfium" +] + diff --git a/src/pdf/quick/plugin.cpp b/src/pdf/quick/plugin.cpp new file mode 100644 index 000000000..670fe0bf9 --- /dev/null +++ b/src/pdf/quick/plugin.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml/qqml.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlextensionplugin.h> +#include "qquickpdfdocument_p.h" +#include "qquickpdflinkmodel_p.h" +#include "qquickpdfnavigationstack_p.h" +#include "qquickpdfsearchmodel_p.h" +#include "qquickpdfselection_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmlmodule QtQuick.Pdf 5.15 + \title Qt Quick PDF QML Types + \ingroup qmlmodules + \brief Provides QML types for handling PDF documents. + + This QML module contains types for handling PDF documents. + + To use the types in this module, import the module with the following line: + + \code + import QtQuick.Pdf 5.15 + \endcode +*/ + +class QtQuick2PdfPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) + +public: + QtQuick2PdfPlugin() : QQmlExtensionPlugin() { } + + void initializeEngine(QQmlEngine *engine, const char *uri) override { + Q_UNUSED(uri); +#ifdef QT_STATIC + Q_UNUSED(engine); +#else + engine->addImportPath(QStringLiteral("qrc:/")); +#endif + } + + void registerTypes(const char *uri) override { + Q_ASSERT(QLatin1String(uri) == QLatin1String("QtQuick.Pdf")); + + // Register the latest version, even if there are no new types or new revisions for existing types yet. + qmlRegisterModule(uri, 2, QT_VERSION_MINOR); + + qmlRegisterType<QQuickPdfDocument>(uri, 5, 15, "PdfDocument"); + qmlRegisterType<QQuickPdfLinkModel>(uri, 5, 15, "PdfLinkModel"); + qmlRegisterType<QQuickPdfNavigationStack>(uri, 5, 15, "PdfNavigationStack"); + qmlRegisterType<QQuickPdfSearchModel>(uri, 5, 15, "PdfSearchModel"); + qmlRegisterType<QQuickPdfSelection>(uri, 5, 15, "PdfSelection"); + + qmlRegisterType(QUrl("qrc:/qt-project.org/qtpdf/qml/PdfPageView.qml"), uri, 5, 15, "PdfPageView"); + qmlRegisterType(QUrl("qrc:/qt-project.org/qtpdf/qml/PdfMultiPageView.qml"), uri, 5, 15, "PdfMultiPageView"); + qmlRegisterType(QUrl("qrc:/qt-project.org/qtpdf/qml/PdfScrollablePageView.qml"), uri, 5, 15, "PdfScrollablePageView"); + } +}; + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/pdf/quick/plugins.qmltypes b/src/pdf/quick/plugins.qmltypes new file mode 100644 index 000000000..a30361d33 --- /dev/null +++ b/src/pdf/quick/plugins.qmltypes @@ -0,0 +1,52 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -nonrelocatable QtQuick.Pdf 5.14' + +Module { + dependencies: [ + "QtGraphicalEffects 1.12", + "QtQuick 2.14", + "QtQuick.Controls 2.14", + "QtQuick.Controls.Fusion 2.14", + "QtQuick.Controls.Fusion.impl 2.14", + "QtQuick.Controls.Imagine 2.14", + "QtQuick.Controls.Imagine.impl 2.14", + "QtQuick.Controls.Material 2.14", + "QtQuick.Controls.Material.impl 2.14", + "QtQuick.Controls.Universal 2.14", + "QtQuick.Controls.Universal.impl 2.12", + "QtQuick.Controls.impl 2.14", + "QtQuick.Shapes 1.14", + "QtQuick.Templates 2.14", + "QtQuick.Window 2.2" + ] + Component { + name: "QQuickPdfDocument" + prototype: "QObject" + exports: ["QtQuick.Pdf/PdfDocument 5.14"] + exportMetaObjectRevisions: [0] + Property { name: "source"; type: "QUrl" } + Property { name: "pageCount"; type: "int"; isReadonly: true } + Property { name: "password"; type: "string" } + Property { name: "status"; type: "QPdfDocument::Status"; isReadonly: true } + Property { name: "title"; type: "string"; isReadonly: true } + Property { name: "subject"; type: "string"; isReadonly: true } + Property { name: "author"; type: "string"; isReadonly: true } + Property { name: "keywords"; type: "string"; isReadonly: true } + Property { name: "producer"; type: "string"; isReadonly: true } + Property { name: "creator"; type: "string"; isReadonly: true } + Property { name: "creationDate"; type: "QDateTime"; isReadonly: true } + Property { name: "modificationDate"; type: "QDateTime"; isReadonly: true } + Signal { name: "passwordRequired" } + Signal { name: "metaDataLoaded" } + Method { + name: "pagePointSize" + type: "QSizeF" + Parameter { name: "page"; type: "int" } + } + } +} diff --git a/src/pdf/quick/qml/+material/PdfStyle.qml b/src/pdf/quick/qml/+material/PdfStyle.qml new file mode 100644 index 000000000..12df30466 --- /dev/null +++ b/src/pdf/quick/qml/+material/PdfStyle.qml @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQml 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Controls.Material 2.14 +import QtQuick.Shapes 1.14 + +QtObject { + property Control prototypeControl: Control { } + function withAlpha(color, alpha) { + return Qt.hsla(color.hslHue, color.hslSaturation, color.hslLightness, alpha) + } + property color selectionColor: withAlpha(prototypeControl.palette.highlight, 0.5) + property color pageSearchResultsColor: withAlpha(Qt.lighter(Material.accentColor, 1.5), 0.5) + property color currentSearchResultStrokeColor: Material.accentColor + property real currentSearchResultStrokeWidth: 2 + property color linkUnderscoreColor: prototypeControl.palette.link + property real linkUnderscoreStrokeWidth: 1 + property var linkUnderscoreStrokeStyle: ShapePath.DashLine + property var linkUnderscoreDashPattern: [ 1, 4 ] +} diff --git a/src/pdf/quick/qml/+universal/PdfStyle.qml b/src/pdf/quick/qml/+universal/PdfStyle.qml new file mode 100644 index 000000000..e92f2a080 --- /dev/null +++ b/src/pdf/quick/qml/+universal/PdfStyle.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQml 2.14 +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Controls.Universal 2.14 +import QtQuick.Shapes 1.14 + +QtObject { + property Control prototypeControl: Control { } + function withAlpha(color, alpha) { + return Qt.hsla(color.hslHue, color.hslSaturation, color.hslLightness, alpha) + } + property color selectionColor: withAlpha(prototypeControl.palette.highlight, 0.5) + property color pageSearchResultsColor: withAlpha(Qt.lighter(Universal.accent, 1.5), 0.5) + property color currentSearchResultStrokeColor: Universal.accent + property real currentSearchResultStrokeWidth: 2 + property color linkUnderscoreColor: prototypeControl.palette.link + property real linkUnderscoreStrokeWidth: 1 + property var linkUnderscoreStrokeStyle: ShapePath.DashLine + property var linkUnderscoreDashPattern: [ 1, 4 ] +} diff --git a/src/pdf/quick/qml/PdfMultiPageView.qml b/src/pdf/quick/qml/PdfMultiPageView.qml new file mode 100644 index 000000000..70bb5454f --- /dev/null +++ b/src/pdf/quick/qml/PdfMultiPageView.qml @@ -0,0 +1,379 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Pdf 5.15 +import QtQuick.Shapes 1.14 +import QtQuick.Window 2.14 + +Item { + // public API + // TODO 5.15: required property + property var document: undefined + property bool debug: false + + property string selectedText + function selectAll() { + var currentItem = tableView.itemAtPos(0, tableView.contentY + root.height / 2) + if (currentItem !== null) + currentItem.selection.selectAll() + } + function copySelectionToClipboard() { + var currentItem = tableView.itemAtPos(0, tableView.contentY + root.height / 2) + if (debug) + console.log("currentItem", currentItem, "sel", currentItem.selection.text) + if (currentItem !== null) + currentItem.selection.copyToClipboard() + } + + // page navigation + property alias currentPage: navigationStack.currentPage + property alias backEnabled: navigationStack.backAvailable + property alias forwardEnabled: navigationStack.forwardAvailable + function back() { navigationStack.back() } + function forward() { navigationStack.forward() } + function goToPage(page) { + if (page === navigationStack.currentPage) + return + goToLocation(page, Qt.point(0, 0), 0) + } + function goToLocation(page, location, zoom) { + if (zoom > 0) + root.renderScale = zoom + navigationStack.push(page, location, zoom) + searchModel.currentPage = page + } + + // page scaling + property real renderScale: 1 + property real pageRotation: 0 + function resetScale() { root.renderScale = 1 } + function scaleToWidth(width, height) { + root.renderScale = width / (tableView.rot90 ? tableView.firstPagePointSize.height : tableView.firstPagePointSize.width) + } + function scaleToPage(width, height) { + var windowAspect = width / height + var pageAspect = tableView.firstPagePointSize.width / tableView.firstPagePointSize.height + if (tableView.rot90) { + if (windowAspect > pageAspect) { + root.renderScale = height / tableView.firstPagePointSize.width + } else { + root.renderScale = width / tableView.firstPagePointSize.height + } + } else { + if (windowAspect > pageAspect) { + root.renderScale = height / tableView.firstPagePointSize.height + } else { + root.renderScale = width / tableView.firstPagePointSize.width + } + } + } + + // text search + property alias searchModel: searchModel + property alias searchString: searchModel.searchString + function searchBack() { --searchModel.currentResult } + function searchForward() { ++searchModel.currentResult } + + id: root + PdfStyle { id: style } + TableView { + id: tableView + anchors.fill: parent + anchors.leftMargin: 2 + model: root.document === undefined ? 0 : root.document.pageCount + rowSpacing: 6 + property real rotationNorm: Math.round((360 + (root.pageRotation % 360)) % 360) + property bool rot90: rotationNorm == 90 || rotationNorm == 270 + onRot90Changed: forceLayout() + property size firstPagePointSize: document === undefined ? Qt.size(0, 0) : document.pagePointSize(0) + contentWidth: document === undefined ? 0 : (rot90 ? document.maxPageHeight : document.maxPageWidth) * root.renderScale + vscroll.width + 2 + // workaround for missing function (see https://codereview.qt-project.org/c/qt/qtdeclarative/+/248464) + function itemAtPos(x, y, includeSpacing) { + // we don't care about x (assume col 0), and assume includeSpacing is true + var ret = null + for (var i = 0; i < contentItem.children.length; ++i) { + var child = contentItem.children[i]; + if (root.debug) + console.log(child, "@y", child.y) + if (child.y < y && (!ret || child.y > ret.y)) + ret = child + } + if (root.debug && ret !== null) + console.log("given y", y, "found", ret, "@", ret.y) + return ret // the delegate with the largest y that is less than the given y + } + rowHeightProvider: function(row) { return (rot90 ? document.pagePointSize(row).width : document.pagePointSize(row).height) * root.renderScale } + delegate: Rectangle { + id: pageHolder + color: root.debug ? "beige" : "transparent" + Text { + visible: root.debug + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + rotation: -90; text: pageHolder.width.toFixed(1) + "x" + pageHolder.height.toFixed(1) + "\n" + + image.width.toFixed(1) + "x" + image.height.toFixed(1) + } + implicitWidth: Math.max(root.width, (tableView.rot90 ? document.maxPageHeight : document.maxPageWidth) * root.renderScale) + implicitHeight: tableView.rot90 ? image.width : image.height + onImplicitWidthChanged: tableView.forceLayout() + objectName: "page " + index + property int delegateIndex: row // expose the context property for JS outside of the delegate + property alias selection: selection + Rectangle { + id: paper + width: image.width + height: image.height + rotation: root.pageRotation + anchors.centerIn: pinch.active ? undefined : parent + property size pagePointSize: document.pagePointSize(index) + property real pageScale: image.paintedWidth / pagePointSize.width + Image { + id: image + source: document.source + currentFrame: index + asynchronous: true + fillMode: Image.PreserveAspectFit + width: paper.pagePointSize.width * root.renderScale + height: paper.pagePointSize.height * root.renderScale + property real renderScale: root.renderScale + property real oldRenderScale: 1 + onRenderScaleChanged: { + image.sourceSize.width = paper.pagePointSize.width * renderScale + image.sourceSize.height = 0 + paper.scale = 1 + searchHighlights.update() + } + } + Shape { + anchors.fill: parent + visible: image.status === Image.Ready + onVisibleChanged: searchHighlights.update() + ShapePath { + strokeWidth: -1 + fillColor: style.pageSearchResultsColor + scale: Qt.size(paper.pageScale, paper.pageScale) + PathMultiline { + id: searchHighlights + function update() { + // paths could be a binding, but we need to be able to "kick" it sometimes + paths = searchModel.boundingPolygonsOnPage(index) + } + } + } + Connections { + target: searchModel + // whenever the highlights on the _current_ page change, they actually need to change on _all_ pages + // (usually because the search string has changed) + function onCurrentPageBoundingPolygonsChanged() { searchHighlights.update() } + } + ShapePath { + strokeWidth: -1 + fillColor: style.selectionColor + scale: Qt.size(paper.pageScale, paper.pageScale) + PathMultiline { + paths: selection.geometry + } + } + } + Shape { + anchors.fill: parent + visible: image.status === Image.Ready && searchModel.currentPage === index + ShapePath { + strokeWidth: style.currentSearchResultStrokeWidth + strokeColor: style.currentSearchResultStrokeColor + fillColor: "transparent" + scale: Qt.size(paper.pageScale, paper.pageScale) + PathMultiline { + paths: searchModel.currentResultBoundingPolygons + } + } + } + PinchHandler { + id: pinch + minimumScale: 0.1 + maximumScale: root.renderScale < 4 ? 2 : 1 + minimumRotation: root.pageRotation + maximumRotation: root.pageRotation + enabled: image.sourceSize.width < 5000 + onActiveChanged: + if (active) { + paper.z = 10 + } else { + paper.z = 0 + var centroidInPoints = Qt.point(pinch.centroid.position.x / root.renderScale, + pinch.centroid.position.y / root.renderScale) + var centroidInFlickable = tableView.mapFromItem(paper, pinch.centroid.position.x, pinch.centroid.position.y) + var newSourceWidth = image.sourceSize.width * paper.scale + var ratio = newSourceWidth / image.sourceSize.width + if (root.debug) + console.log("pinch ended on page", index, "with centroid", pinch.centroid.position, centroidInPoints, "wrt flickable", centroidInFlickable, + "page at", pageHolder.x.toFixed(2), pageHolder.y.toFixed(2), + "contentX/Y were", tableView.contentX.toFixed(2), tableView.contentY.toFixed(2)) + if (ratio > 1.1 || ratio < 0.9) { + var centroidOnPage = Qt.point(centroidInPoints.x * root.renderScale * ratio, centroidInPoints.y * root.renderScale * ratio) + paper.scale = 1 + paper.x = 0 + paper.y = 0 + root.renderScale *= ratio + tableView.forceLayout() + if (tableView.rotationNorm == 0) { + tableView.contentX = pageHolder.x + tableView.originX + centroidOnPage.x - centroidInFlickable.x + tableView.contentY = pageHolder.y + tableView.originY + centroidOnPage.y - centroidInFlickable.y + } else if (tableView.rotationNorm == 90) { + tableView.contentX = pageHolder.x + tableView.originX + image.height - centroidOnPage.y - centroidInFlickable.x + tableView.contentY = pageHolder.y + tableView.originY + centroidOnPage.x - centroidInFlickable.y + } else if (tableView.rotationNorm == 180) { + tableView.contentX = pageHolder.x + tableView.originX + image.width - centroidOnPage.x - centroidInFlickable.x + tableView.contentY = pageHolder.y + tableView.originY + image.height - centroidOnPage.y - centroidInFlickable.y + } else if (tableView.rotationNorm == 270) { + tableView.contentX = pageHolder.x + tableView.originX + centroidOnPage.y - centroidInFlickable.x + tableView.contentY = pageHolder.y + tableView.originY + image.width - centroidOnPage.x - centroidInFlickable.y + } + if (root.debug) + console.log("contentX/Y adjusted to", tableView.contentX.toFixed(2), tableView.contentY.toFixed(2), "y @top", pageHolder.y) + tableView.returnToBounds() + } + } + grabPermissions: PointerHandler.CanTakeOverFromAnything + } + DragHandler { + id: textSelectionDrag + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + target: null + } + TapHandler { + id: tapHandler + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + } + Repeater { + model: PdfLinkModel { + id: linkModel + document: root.document + page: image.currentFrame + } + delegate: Shape { + x: rect.x * paper.pageScale + y: rect.y * paper.pageScale + width: rect.width * paper.pageScale + height: rect.height * paper.pageScale + ShapePath { + strokeWidth: style.linkUnderscoreStrokeWidth + strokeColor: style.linkUnderscoreColor + strokeStyle: style.linkUnderscoreStrokeStyle + dashPattern: style.linkUnderscoreDashPattern + startX: 0; startY: height + PathLine { x: width; y: height } + } + MouseArea { // TODO switch to TapHandler / HoverHandler in 5.15 + id: linkMA + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onClicked: { + if (page >= 0) + root.goToLocation(page, location, zoom) + else + Qt.openUrlExternally(url) + } + } + ToolTip { + visible: linkMA.containsMouse + delay: 1000 + text: page >= 0 ? + ("page " + (page + 1) + + " location " + location.x.toFixed(1) + ", " + location.y.toFixed(1) + + " zoom " + zoom) : url + } + } + } + } + PdfSelection { + id: selection + document: root.document + page: image.currentFrame + fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / paper.pageScale, + textSelectionDrag.centroid.pressPosition.y / paper.pageScale) + toPoint: Qt.point(textSelectionDrag.centroid.position.x / paper.pageScale, + textSelectionDrag.centroid.position.y / paper.pageScale) + hold: !textSelectionDrag.active && !tapHandler.pressed + onTextChanged: root.selectedText = text + } + } + ScrollBar.vertical: ScrollBar { + id: vscroll + property bool moved: false + onPositionChanged: moved = true + onActiveChanged: { + var currentItem = tableView.itemAtPos(0, tableView.contentY + root.height / 2) + var currentPage = currentItem.delegateIndex + var currentLocation = Qt.point((tableView.contentX - currentItem.x + root.width / 2) / root.renderScale, + (tableView.contentY - currentItem.y + root.height / 2) / root.renderScale) + if (active) { + moved = false + navigationStack.push(currentPage, currentLocation, root.renderScale) + } else if (moved) { + navigationStack.update(currentPage, currentLocation, root.renderScale) + } + } + } + ScrollBar.horizontal: ScrollBar { } + } + onRenderScaleChanged: { + tableView.forceLayout() + var currentItem = tableView.itemAtPos(tableView.contentX + root.width / 2, tableView.contentY + root.height / 2) + if (currentItem !== undefined) + navigationStack.update(currentItem.delegateIndex, Qt.point(currentItem.x / renderScale, currentItem.y / renderScale), renderScale) + } + PdfNavigationStack { + id: navigationStack + onJumped: { + root.renderScale = zoom + tableView.contentX = Math.max(0, location.x - root.width / 2) * root.renderScale + tableView.contentY = tableView.originY + root.document.heightSumBeforePage(page, tableView.rowSpacing / root.renderScale) * root.renderScale + if (root.debug) { + console.log("going to page", page, + "@y", root.document.heightSumBeforePage(page, tableView.rowSpacing / root.renderScale) * root.renderScale, + "ended up @", tableView.contentY, "originY is", tableView.originY) + } + } + } + PdfSearchModel { + id: searchModel + document: root.document === undefined ? null : root.document + onCurrentPageChanged: if (currentPage != navigationStack.currentPage) root.goToPage(currentPage) + } +} diff --git a/src/pdf/quick/qml/PdfPageView.qml b/src/pdf/quick/qml/PdfPageView.qml new file mode 100644 index 000000000..b90ad2d7f --- /dev/null +++ b/src/pdf/quick/qml/PdfPageView.qml @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Pdf 5.15 +import QtQuick.Shapes 1.14 +import Qt.labs.animation 1.0 + +Rectangle { + // public API + // TODO 5.15: required property + property var document: undefined + property alias status: image.status + + property alias selectedText: selection.text + function selectAll() { + selection.selectAll() + } + function copySelectionToClipboard() { + selection.copyToClipboard() + } + + // page navigation + property alias currentPage: navigationStack.currentPage + property alias backEnabled: navigationStack.backAvailable + property alias forwardEnabled: navigationStack.forwardAvailable + function back() { navigationStack.back() } + function forward() { navigationStack.forward() } + function goToPage(page) { goToLocation(page, Qt.point(0, 0), 0) } + function goToLocation(page, location, zoom) { + if (zoom > 0) + root.renderScale = zoom + navigationStack.push(page, location, zoom) + } + + // page scaling + property real renderScale: 1 + property alias sourceSize: image.sourceSize + function resetScale() { + image.sourceSize.width = 0 + image.sourceSize.height = 0 + root.x = 0 + root.y = 0 + root.scale = 1 + } + function scaleToWidth(width, height) { + var halfRotation = Math.abs(root.rotation % 180) + image.sourceSize = Qt.size((halfRotation > 45 && halfRotation < 135) ? height : width, 0) + root.x = 0 + root.y = 0 + image.centerInSize = Qt.size(width, height) + image.centerOnLoad = true + image.vCenterOnLoad = (halfRotation > 45 && halfRotation < 135) + root.scale = 1 + } + function scaleToPage(width, height) { + var windowAspect = width / height + var halfRotation = Math.abs(root.rotation % 180) + var pagePointSize = document.pagePointSize(navigationStack.currentPage) + if (halfRotation > 45 && halfRotation < 135) { + // rotated 90 or 270º + var pageAspect = pagePointSize.height / pagePointSize.width + if (windowAspect > pageAspect) { + image.sourceSize = Qt.size(height, 0) + } else { + image.sourceSize = Qt.size(0, width) + } + } else { + var pageAspect = pagePointSize.width / pagePointSize.height + if (windowAspect > pageAspect) { + image.sourceSize = Qt.size(0, height) + } else { + image.sourceSize = Qt.size(width, 0) + } + } + image.centerInSize = Qt.size(width, height) + image.centerOnLoad = true + image.vCenterOnLoad = true + root.scale = 1 + } + + // text search + property alias searchModel: searchModel + property alias searchString: searchModel.searchString + function searchBack() { --searchModel.currentResult } + function searchForward() { ++searchModel.currentResult } + + // implementation + id: root + width: image.width + height: image.height + + PdfSelection { + id: selection + document: root.document + page: navigationStack.currentPage + fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / image.pageScale, textSelectionDrag.centroid.pressPosition.y / image.pageScale) + toPoint: Qt.point(textSelectionDrag.centroid.position.x / image.pageScale, textSelectionDrag.centroid.position.y / image.pageScale) + hold: !textSelectionDrag.active && !tapHandler.pressed + } + + PdfSearchModel { + id: searchModel + document: root.document === undefined ? null : root.document + onCurrentPageChanged: root.goToPage(currentPage) + } + + PdfNavigationStack { + id: navigationStack + onCurrentPageChanged: searchModel.currentPage = currentPage + // TODO onCurrentLocationChanged: position currentLocation.x and .y in middle // currentPageChanged() MUST occur first! + onCurrentZoomChanged: root.renderScale = currentZoom + // TODO deal with horizontal location (need WheelHandler or Flickable probably) + } + + Image { + id: image + currentFrame: navigationStack.currentPage + source: document.status === PdfDocument.Ready ? document.source : "" + asynchronous: true + fillMode: Image.PreserveAspectFit + property bool centerOnLoad: false + property bool vCenterOnLoad: false + property size centerInSize + property real pageScale: image.paintedWidth / document.pagePointSize(navigationStack.currentPage).width + function reRenderIfNecessary() { + var newSourceWidth = image.sourceSize.width * root.scale + var ratio = newSourceWidth / image.sourceSize.width + if (ratio > 1.1 || ratio < 0.9) { + image.sourceSize.width = newSourceWidth + image.sourceSize.height = 0 + root.scale = 1 + } + } + onStatusChanged: + if (status == Image.Ready && centerOnLoad) { + root.x = (centerInSize.width - image.implicitWidth) / 2 + root.y = vCenterOnLoad ? (centerInSize.height - image.implicitHeight) / 2 : 0 + centerOnLoad = false + vCenterOnLoad = false + } + } + onRenderScaleChanged: { + image.sourceSize.width = document.pagePointSize(navigationStack.currentPage).width * renderScale + image.sourceSize.height = 0 + root.scale = 1 + } + + Shape { + anchors.fill: parent + opacity: 0.25 + visible: image.status === Image.Ready + ShapePath { + strokeWidth: 1 + strokeColor: "cyan" + fillColor: "steelblue" + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: searchModel.currentPageBoundingPolygons + } + } + ShapePath { + strokeWidth: 1 + strokeColor: "orange" + fillColor: "cyan" + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: searchModel.currentResultBoundingPolygons + } + } + ShapePath { + fillColor: "orange" + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: selection.geometry + } + } + } + + Repeater { + model: PdfLinkModel { + id: linkModel + document: root.document + page: navigationStack.currentPage + } + delegate: Rectangle { + color: "transparent" + border.color: "lightgrey" + x: rect.x * image.pageScale + y: rect.y * image.pageScale + width: rect.width * image.pageScale + height: rect.height * image.pageScale + MouseArea { // TODO switch to TapHandler / HoverHandler in 5.15 + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (page >= 0) + navigationStack.push(page, Qt.point(0, 0), root.renderScale) + else + Qt.openUrlExternally(url) + } + } + } + } + + PinchHandler { + id: pinch + minimumScale: 0.1 + maximumScale: 10 + minimumRotation: 0 + maximumRotation: 0 + onActiveChanged: if (!active) image.reRenderIfNecessary() + grabPermissions: PinchHandler.TakeOverForbidden // don't allow takeover if pinch has started + } + DragHandler { + id: pageMovingTouchDrag + acceptedDevices: PointerDevice.TouchScreen + } + DragHandler { + id: pageMovingMiddleMouseDrag + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + acceptedButtons: Qt.MiddleButton + snapMode: DragHandler.NoSnap + } + DragHandler { + id: textSelectionDrag + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + target: null + } + TapHandler { + id: tapHandler + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + } + // prevent it from being scrolled out of view + BoundaryRule on x { + minimum: 100 - root.width + maximum: root.parent.width - 100 + } + BoundaryRule on y { + minimum: 100 - root.height + maximum: root.parent.height - 100 + } +} diff --git a/src/pdf/quick/qml/PdfScrollablePageView.qml b/src/pdf/quick/qml/PdfScrollablePageView.qml new file mode 100644 index 000000000..6076e57df --- /dev/null +++ b/src/pdf/quick/qml/PdfScrollablePageView.qml @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Pdf 5.15 +import QtQuick.Shapes 1.14 +import Qt.labs.animation 1.0 + +Flickable { + // public API + // TODO 5.15: required property + property var document: undefined + property bool debug: false + property alias status: image.status + + property alias selectedText: selection.text + function selectAll() { + selection.selectAll() + } + function copySelectionToClipboard() { + selection.copyToClipboard() + } + + // page navigation + property alias currentPage: navigationStack.currentPage + property alias backEnabled: navigationStack.backAvailable + property alias forwardEnabled: navigationStack.forwardAvailable + function back() { navigationStack.back() } + function forward() { navigationStack.forward() } + function goToPage(page) { + if (page === navigationStack.currentPage) + return + goToLocation(page, Qt.point(0, 0), 0) + } + function goToLocation(page, location, zoom) { + if (zoom > 0) + root.renderScale = zoom + navigationStack.push(page, location, zoom) + } + + // page scaling + property real renderScale: 1 + property real pageRotation: 0 + property alias sourceSize: image.sourceSize + function resetScale() { + paper.scale = 1 + root.renderScale = 1 + } + function scaleToWidth(width, height) { + var pagePointSize = document.pagePointSize(navigationStack.currentPage) + root.renderScale = root.width / (paper.rot90 ? pagePointSize.height : pagePointSize.width) + if (debug) + console.log("scaling", pagePointSize, "to fit", root.width, "rotated?", paper.rot90, "scale", root.renderScale) + root.contentX = 0 + root.contentY = 0 + } + function scaleToPage(width, height) { + var pagePointSize = document.pagePointSize(navigationStack.currentPage) + root.renderScale = Math.min( + root.width / (paper.rot90 ? pagePointSize.height : pagePointSize.width), + root.height / (paper.rot90 ? pagePointSize.width : pagePointSize.height) ) + root.contentX = 0 + root.contentY = 0 + } + + // text search + property alias searchModel: searchModel + property alias searchString: searchModel.searchString + function searchBack() { --searchModel.currentResult } + function searchForward() { ++searchModel.currentResult } + + // implementation + id: root + PdfStyle { id: style } + contentWidth: paper.width + contentHeight: paper.height + ScrollBar.vertical: ScrollBar { + onActiveChanged: + if (!active ) { + var currentLocation = Qt.point((root.contentX + root.width / 2) / root.renderScale, + (root.contentY + root.height / 2) / root.renderScale) + navigationStack.update(navigationStack.currentPage, currentLocation, root.renderScale) + } + } + ScrollBar.horizontal: ScrollBar { + onActiveChanged: + if (!active ) { + var currentLocation = Qt.point((root.contentX + root.width / 2) / root.renderScale, + (root.contentY + root.height / 2) / root.renderScale) + navigationStack.update(navigationStack.currentPage, currentLocation, root.renderScale) + } + } + + onRenderScaleChanged: { + image.sourceSize.width = document.pagePointSize(navigationStack.currentPage).width * renderScale + image.sourceSize.height = 0 + paper.scale = 1 + var currentLocation = Qt.point((root.contentX + root.width / 2) / root.renderScale, + (root.contentY + root.height / 2) / root.renderScale) + navigationStack.update(navigationStack.currentPage, currentLocation, root.renderScale) + } + + PdfSelection { + id: selection + document: root.document + page: navigationStack.currentPage + fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / image.pageScale, + textSelectionDrag.centroid.pressPosition.y / image.pageScale) + toPoint: Qt.point(textSelectionDrag.centroid.position.x / image.pageScale, + textSelectionDrag.centroid.position.y / image.pageScale) + hold: !textSelectionDrag.active && !tapHandler.pressed + } + + PdfSearchModel { + id: searchModel + document: root.document === undefined ? null : root.document + onCurrentPageChanged: root.goToPage(currentPage) + } + + PdfNavigationStack { + id: navigationStack + onJumped: { + root.renderScale = zoom + root.contentX = Math.max(0, location.x * root.renderScale - root.width / 2) + root.contentY = Math.max(0, location.y * root.renderScale - root.height / 2) + if (root.debug) + console.log("going to zoom", zoom, "loc", location, + "on page", page, "ended up @", root.contentX + ", " + root.contentY) + } + onCurrentPageChanged: searchModel.currentPage = currentPage + } + + Rectangle { + id: paper + width: rot90 ? image.height : image.width + height: rot90 ? image.width : image.height + property real rotationModulus: Math.abs(root.pageRotation % 180) + property bool rot90: rotationModulus > 45 && rotationModulus < 135 + + Image { + id: image + currentFrame: navigationStack.currentPage + source: document.status === PdfDocument.Ready ? document.source : "" + asynchronous: true + fillMode: Image.PreserveAspectFit + rotation: root.pageRotation + anchors.centerIn: parent + property real pageScale: image.paintedWidth / document.pagePointSize(navigationStack.currentPage).width + + Shape { + anchors.fill: parent + visible: image.status === Image.Ready + ShapePath { + strokeWidth: -1 + fillColor: style.pageSearchResultsColor + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: searchModel.currentPageBoundingPolygons + } + } + ShapePath { + strokeWidth: style.currentSearchResultStrokeWidth + strokeColor: style.currentSearchResultStrokeColor + fillColor: "transparent" + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: searchModel.currentResultBoundingPolygons + } + } + ShapePath { + fillColor: style.selectionColor + scale: Qt.size(image.pageScale, image.pageScale) + PathMultiline { + paths: selection.geometry + } + } + } + + Repeater { + model: PdfLinkModel { + id: linkModel + document: root.document + page: navigationStack.currentPage + } + delegate: Shape { + x: rect.x * image.pageScale + y: rect.y * image.pageScale + width: rect.width * image.pageScale + height: rect.height * image.pageScale + ShapePath { + strokeWidth: style.linkUnderscoreStrokeWidth + strokeColor: style.linkUnderscoreColor + strokeStyle: style.linkUnderscoreStrokeStyle + dashPattern: style.linkUnderscoreDashPattern + startX: 0; startY: height + PathLine { x: width; y: height } + } + MouseArea { // TODO switch to TapHandler / HoverHandler in 5.15 + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (page >= 0) + navigationStack.push(page, Qt.point(0, 0), root.renderScale) + else + Qt.openUrlExternally(url) + } + } + } + } + DragHandler { + id: textSelectionDrag + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + target: null + } + TapHandler { + id: tapHandler + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + } + } + + PinchHandler { + id: pinch + minimumScale: 0.1 + maximumScale: root.renderScale < 4 ? 2 : 1 + minimumRotation: 0 + maximumRotation: 0 + enabled: image.sourceSize.width < 5000 + onActiveChanged: + if (!active) { + var centroidInPoints = Qt.point(pinch.centroid.position.x / root.renderScale, + pinch.centroid.position.y / root.renderScale) + var centroidInFlickable = root.mapFromItem(paper, pinch.centroid.position.x, pinch.centroid.position.y) + var newSourceWidth = image.sourceSize.width * paper.scale + var ratio = newSourceWidth / image.sourceSize.width + if (root.debug) + console.log("pinch ended with centroid", pinch.centroid.position, centroidInPoints, "wrt flickable", centroidInFlickable, + "page at", paper.x.toFixed(2), paper.y.toFixed(2), + "contentX/Y were", root.contentX.toFixed(2), root.contentY.toFixed(2)) + if (ratio > 1.1 || ratio < 0.9) { + var centroidOnPage = Qt.point(centroidInPoints.x * root.renderScale * ratio, centroidInPoints.y * root.renderScale * ratio) + paper.scale = 1 + paper.x = 0 + paper.y = 0 + root.contentX = centroidOnPage.x - centroidInFlickable.x + root.contentY = centroidOnPage.y - centroidInFlickable.y + root.renderScale *= ratio // onRenderScaleChanged calls navigationStack.update() so we don't need to here + if (root.debug) + console.log("contentX/Y adjusted to", root.contentX.toFixed(2), root.contentY.toFixed(2)) + } else { + paper.x = 0 + paper.y = 0 + } + } + grabPermissions: PointerHandler.CanTakeOverFromAnything + } + } +} diff --git a/src/pdf/quick/qml/PdfStyle.qml b/src/pdf/quick/qml/PdfStyle.qml new file mode 100644 index 000000000..090465ce6 --- /dev/null +++ b/src/pdf/quick/qml/PdfStyle.qml @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQml 2.14 +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Shapes 1.14 + +QtObject { + property Control prototypeControl: Control { } + function withAlpha(color, alpha) { + return Qt.hsla(color.hslHue, color.hslSaturation, color.hslLightness, alpha) + } + property color selectionColor: withAlpha(prototypeControl.palette.highlight, 0.5) + property color pageSearchResultsColor: "#80B0C4DE" + property color currentSearchResultStrokeColor: "cyan" + property real currentSearchResultStrokeWidth: 2 + property color linkUnderscoreColor: prototypeControl.palette.link + property real linkUnderscoreStrokeWidth: 1 + property var linkUnderscoreStrokeStyle: ShapePath.DashLine + property var linkUnderscoreDashPattern: [ 1, 4 ] +} diff --git a/src/pdf/quick/qmldir b/src/pdf/quick/qmldir new file mode 100644 index 000000000..65fa95cda --- /dev/null +++ b/src/pdf/quick/qmldir @@ -0,0 +1,4 @@ +module QtQuick.Pdf +plugin pdfplugin +classname QtQuick2PdfPlugin +typeinfo plugins.qmltypes diff --git a/src/pdf/quick/qquickpdfdocument.cpp b/src/pdf/quick/qquickpdfdocument.cpp new file mode 100644 index 000000000..3d5f0fa10 --- /dev/null +++ b/src/pdf/quick/qquickpdfdocument.cpp @@ -0,0 +1,306 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpdfdocument_p.h" +#include <QQuickItem> +#include <QQmlEngine> +#include <QStandardPaths> +#include <private/qguiapplication_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype PdfDocument + \instantiates QQuickPdfDocument + \inqmlmodule QtQuick.Pdf + \ingroup pdf + \brief A representation of a PDF document. + \since 5.15 + + PdfDocument provides access to PDF document meta-information. + It is not necessary for rendering, as it is enough to use an + \l Image with source set to the URL of the PDF. +*/ + +/*! + Constructs a PDF document. +*/ +QQuickPdfDocument::QQuickPdfDocument(QObject *parent) + : QObject(parent) +{ + connect(&m_doc, &QPdfDocument::passwordChanged, this, &QQuickPdfDocument::passwordChanged); + connect(&m_doc, &QPdfDocument::passwordRequired, this, &QQuickPdfDocument::passwordRequired); + connect(&m_doc, &QPdfDocument::statusChanged, [=] (QPdfDocument::Status status) { + emit statusChanged(); + if (status == QPdfDocument::Ready) + emit metaDataChanged(); + }); + connect(&m_doc, &QPdfDocument::pageCountChanged, this, &QQuickPdfDocument::pageCountChanged); +} + +void QQuickPdfDocument::componentComplete() +{ + if (m_doc.error() == QPdfDocument::IncorrectPasswordError) + emit passwordRequired(); +} + +/*! + \qmlproperty url PdfDocument::source + + This property holds a URL pointing to the PDF file to be loaded. + + \note At this time, only local filesystem URLs are supported. +*/ +void QQuickPdfDocument::setSource(QUrl source) +{ + if (m_source == source) + return; + + m_source = source; + m_maxPageWidthHeight = QSizeF(); + emit sourceChanged(); + if (source.scheme() == QLatin1String("qrc")) + m_doc.load(QLatin1Char(':') + source.path()); + else + m_doc.load(source.path()); +} + +/*! + \qmlproperty string PdfDocument::error + + This property holds a translated string representation of the current + error, if any. + + \sa status +*/ +QString QQuickPdfDocument::error() const +{ + switch (m_doc.error()) { + case QPdfDocument::NoError: + return tr("no error"); + break; + case QPdfDocument::UnknownError: + break; + case QPdfDocument::DataNotYetAvailableError: + return tr("data not yet available"); + break; + case QPdfDocument::FileNotFoundError: + return tr("file not found"); + break; + case QPdfDocument::InvalidFileFormatError: + return tr("invalid file format"); + break; + case QPdfDocument::IncorrectPasswordError: + return tr("incorrect password"); + break; + case QPdfDocument::UnsupportedSecuritySchemeError: + return tr("unsupported security scheme"); + break; + } + return tr("unknown error"); +} + +/*! + \qmlproperty bool PdfDocument::password + + This property holds the document password. If the passwordRequired() + signal is emitted, the UI should prompt the user and then set this + property so that document opening can continue. +*/ +void QQuickPdfDocument::setPassword(const QString &password) +{ + if (m_doc.password() == password) + return; + m_doc.setPassword(password); + if (source().isValid() && source().isLocalFile()) + m_doc.load(source().path()); +} + +/*! + \qmlproperty int PdfDocument::pageCount + + This property holds the number of pages the PDF contains. +*/ + +/*! + \qmlsignal PdfDocument::passwordRequired() + + This signal is emitted when the PDF requires a password in order to open. + The UI in a typical PDF viewer should prompt the user for the password + and then set the password property when the user has provided it. +*/ + +/*! + \qmlmethod size PdfDocument::pagePointSize(int page) + + Returns the size of the given \a page in points. +*/ +QSizeF QQuickPdfDocument::pagePointSize(int page) const +{ + return m_doc.pageSize(page); +} + +qreal QQuickPdfDocument::maxPageWidth() const +{ + const_cast<QQuickPdfDocument *>(this)->updateMaxPageSize(); + return m_maxPageWidthHeight.width(); +} + +qreal QQuickPdfDocument::maxPageHeight() const +{ + const_cast<QQuickPdfDocument *>(this)->updateMaxPageSize(); + return m_maxPageWidthHeight.height(); +} + +/*! + \internal + \qmlmethod size PdfDocument::heightSumBeforePage(int page) + + Returns the sum of the heights, in points, of all sets of \a facingPages + pages from 0 to the given \a page, exclusive. + + That is, if the pages were laid out end-to-end in adjacent sets of + \a facingPages, what would be the distance in points from the top of the + first page to the top of the given page. +*/ +// Workaround for lack of something analogous to ListView.positionViewAtIndex() in TableView +qreal QQuickPdfDocument::heightSumBeforePage(int page, qreal spacing, int facingPages) const +{ + qreal ret = 0; + for (int i = 0; i < page; i+= facingPages) { + if (i + facingPages > page) + break; + qreal facingPagesHeight = 0; + for (int j = i; j < i + facingPages; ++j) + facingPagesHeight = qMax(facingPagesHeight, pagePointSize(j).height()); + ret += facingPagesHeight + spacing; + } + return ret; +} + +void QQuickPdfDocument::updateMaxPageSize() +{ + if (m_maxPageWidthHeight.isValid()) + return; + qreal w = 0; + qreal h = 0; + const int count = pageCount(); + for (int i = 0; i < count; ++i) { + auto size = pagePointSize(i); + w = qMax(w, size.width()); + h = qMax(w, size.height()); + } + m_maxPageWidthHeight = QSizeF(w, h); +} + +/*! + \qmlproperty real PdfDocument::maxPageWidth + + This property holds the width of the widest page in the document, in points. +*/ + +/*! + \qmlproperty real PdfDocument::maxPageHeight + + This property holds the height of the tallest page in the document, in points. +*/ + +/*! + \qmlproperty string PdfDocument::title + + This property holds the document's title. A typical viewer UI can bind this + to the \c Window.title property. +*/ + +/*! + \qmlproperty string PdfDocument::author + + This property holds the name of the person who created the document. +*/ + +/*! + \qmlproperty string PdfDocument::subject + + This property holds the subject of the document. +*/ + +/*! + \qmlproperty string PdfDocument::keywords + + This property holds the keywords associated with the document. +*/ + +/*! + \qmlproperty string PdfDocument::creator + + If the document was converted to PDF from another format, this property + holds the name of the software that created the original document. +*/ + +/*! + \qmlproperty string PdfDocument::producer + + If the document was converted to PDF from another format, this property + holds the name of the software that converted it to PDF. +*/ + +/*! + \qmlproperty string PdfDocument::creationDate + + This property holds the date and time the document was created. +*/ + +/*! + \qmlproperty string PdfDocument::modificationDate + + This property holds the date and time the document was most recently + modified. +*/ + +/*! + \qmlproperty enum PdfDocument::status + + This property tells the current status of the document. The possible values are: + + \value PdfDocument.Null The initial status after the document has been created or after it has been closed. + \value PdfDocument.Loading The status after load() has been called and before the document is fully loaded. + \value PdfDocument.Ready The status when the document is fully loaded and its data can be accessed. + \value PdfDocument.Unloading The status after close() has been called on an open document. + At this point the document is still valid and all its data can be accessed. + \value PdfDocument.Error The status after Loading, if loading has failed. +*/ + +QT_END_NAMESPACE diff --git a/src/pdf/quick/qquickpdfdocument_p.h b/src/pdf/quick/qquickpdfdocument_p.h new file mode 100644 index 000000000..cefa4f756 --- /dev/null +++ b/src/pdf/quick/qquickpdfdocument_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPDFDOCUMENT_P_H +#define QQUICKPDFDOCUMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtPdf/QPdfDocument> +#include <QDateTime> +#include <QJSValue> +#include <QQmlParserStatus> +#include <QUrl> +#include <QVariant> + +QT_BEGIN_NAMESPACE + +class QQuickPdfDocument : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged FINAL) + Q_PROPERTY(qreal maxPageWidth READ maxPageWidth NOTIFY metaDataChanged) + Q_PROPERTY(qreal maxPageHeight READ maxPageHeight NOTIFY metaDataChanged) + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged FINAL) + Q_PROPERTY(QPdfDocument::Status status READ status NOTIFY statusChanged FINAL) + Q_PROPERTY(QString error READ error NOTIFY statusChanged FINAL) + + Q_PROPERTY(QString title READ title NOTIFY metaDataChanged) + Q_PROPERTY(QString subject READ subject NOTIFY metaDataChanged) + Q_PROPERTY(QString author READ author NOTIFY metaDataChanged) + Q_PROPERTY(QString keywords READ keywords NOTIFY metaDataChanged) + Q_PROPERTY(QString producer READ producer NOTIFY metaDataChanged) + Q_PROPERTY(QString creator READ creator NOTIFY metaDataChanged) + Q_PROPERTY(QDateTime creationDate READ creationDate NOTIFY metaDataChanged) + Q_PROPERTY(QDateTime modificationDate READ modificationDate NOTIFY metaDataChanged) + +public: + explicit QQuickPdfDocument(QObject *parent = nullptr); + + void classBegin() override {} + void componentComplete() override; + + QUrl source() const { return m_source; } + void setSource(QUrl source); + + int pageCount() const { return m_doc.pageCount(); } + QPdfDocument::Status status() const { return m_doc.status(); } + + QString error() const; + + QString password() const { return m_doc.password(); } + void setPassword(const QString &password); + + QString title() { return m_doc.metaData(QPdfDocument::Title).toString(); } + QString author() { return m_doc.metaData(QPdfDocument::Author).toString(); } + QString subject() { return m_doc.metaData(QPdfDocument::Subject).toString(); } + QString keywords() { return m_doc.metaData(QPdfDocument::Keywords).toString(); } + QString producer() { return m_doc.metaData(QPdfDocument::Producer).toString(); } + QString creator() { return m_doc.metaData(QPdfDocument::Creator).toString(); } + QDateTime creationDate() { return m_doc.metaData(QPdfDocument::CreationDate).toDateTime(); } + QDateTime modificationDate() { return m_doc.metaData(QPdfDocument::ModificationDate).toDateTime(); } + + Q_INVOKABLE QSizeF pagePointSize(int page) const; + qreal maxPageWidth() const; + qreal maxPageHeight() const; + Q_INVOKABLE qreal heightSumBeforePage(int page, qreal spacing = 0, int facingPages = 1) const; + +Q_SIGNALS: + void sourceChanged(); + void passwordChanged(); + void passwordRequired(); + void statusChanged(); + void pageCountChanged(); + void metaDataChanged(); + +private: + QPdfDocument &document() { return m_doc; } + void updateMaxPageSize(); + +private: + QUrl m_source; + QPdfDocument m_doc; + QSizeF m_maxPageWidthHeight; + + friend class QQuickPdfLinkModel; + friend class QQuickPdfSearchModel; + friend class QQuickPdfSelection; + + Q_DISABLE_COPY(QQuickPdfDocument) +}; + +QT_END_NAMESPACE + +#endif // QQUICKPDFDOCUMENT_P_H diff --git a/src/pdf/quick/qquickpdflinkmodel.cpp b/src/pdf/quick/qquickpdflinkmodel.cpp new file mode 100644 index 000000000..f2ff3fd22 --- /dev/null +++ b/src/pdf/quick/qquickpdflinkmodel.cpp @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpdflinkmodel_p.h" +#include <QQuickItem> +#include <QQmlEngine> +#include <QStandardPaths> +#include <private/qguiapplication_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype PdfLinkModel + \instantiates QQuickPdfLinkModel + \inqmlmodule QtQuick.Pdf + \ingroup pdf + \brief A representation of links within a PDF document. + \since 5.15 + + PdfLinkModel provides the geometry and the destination for each link + that the specified \l page contains. + + The available model roles are: + + \value rect + Bounding rectangle around the link. + \value url + If the link is a web link, the URL for that; otherwise an empty URL. + \value page + If the link is an internal link, the page number to which the link should jump; otherwise \c {-1}. + \value location + If the link is an internal link, the location on the page to which the link should jump. + \value zoom + If the link is an internal link, the intended zoom level on the destination page. + + Normally it will be used with \l {QtQuick::Repeater}{Repeater} to visualize + the links and provide the ability to click them: + + \qml + Repeater { + model: PdfLinkModel { + document: root.document + page: image.currentFrame + } + delegate: Rectangle { + color: "transparent" + border.color: "lightgrey" + x: rect.x + y: rect.y + width: rect.width + height: rect.height + HoverHandler { cursorShape: Qt.PointingHandCursor } + TapHandler { + onTapped: { + if (page >= 0) + image.currentFrame = page + else + Qt.openUrlExternally(url) + } + } + } + } + \endqml + + \note General-purpose PDF viewing capabilities are provided by + \l PdfScrollablePageView and \l PdfMultiPageView. PdfLinkModel is only needed + when building PDF view components from scratch. +*/ + +QQuickPdfLinkModel::QQuickPdfLinkModel(QObject *parent) + : QPdfLinkModel(parent) +{ +} + +/*! + \qmlproperty PdfDocument PdfLinkModel::document + + This property holds the PDF document in which links are to be found. +*/ +QQuickPdfDocument *QQuickPdfLinkModel::document() const +{ + return m_quickDocument; +} + +void QQuickPdfLinkModel::setDocument(QQuickPdfDocument *document) +{ + if (document == m_quickDocument) + return; + m_quickDocument = document; + QPdfLinkModel::setDocument(&document->m_doc); +} + +/*! + \qmlproperty int PdfLinkModel::page + + This property holds the page number on which links are to be found. +*/ + +QT_END_NAMESPACE diff --git a/src/pdf/quick/qquickpdflinkmodel_p.h b/src/pdf/quick/qquickpdflinkmodel_p.h new file mode 100644 index 000000000..23ad6c8c1 --- /dev/null +++ b/src/pdf/quick/qquickpdflinkmodel_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPDFLINKMODEL_P_H +#define QQUICKPDFLINKMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickpdfdocument_p.h" +#include "../api/qpdflinkmodel_p.h" + +#include <QVariant> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickPdfLinkModel : public QPdfLinkModel +{ + Q_OBJECT + Q_PROPERTY(QQuickPdfDocument *document READ document WRITE setDocument NOTIFY documentChanged) + +public: + explicit QQuickPdfLinkModel(QObject *parent = nullptr); + + QQuickPdfDocument *document() const; + void setDocument(QQuickPdfDocument *document); + +signals: + void documentChanged(); + +private: + void updateResults(); + +private: + QQuickPdfDocument *m_quickDocument; + QVector<QPolygonF> m_linksGeometry; + + Q_DISABLE_COPY(QQuickPdfLinkModel) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPdfLinkModel) + +#endif // QQUICKPDFLINKMODEL_P_H diff --git a/src/pdf/quick/qquickpdfnavigationstack.cpp b/src/pdf/quick/qquickpdfnavigationstack.cpp new file mode 100644 index 000000000..7ba317557 --- /dev/null +++ b/src/pdf/quick/qquickpdfnavigationstack.cpp @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpdfnavigationstack_p.h" +#include <QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcNav, "qt.pdf.navigationstack") + +/*! + \qmltype PdfNavigationStack + \instantiates QQuickPdfNavigationStack + \inqmlmodule QtQuick.Pdf + \ingroup pdf + \brief History of the destinations visited within a PDF Document. + \since 5.15 + + PdfNavigationStack remembers which destinations the user has visited in a PDF + document, and provides the ability to traverse backward and forward. +*/ + +QQuickPdfNavigationStack::QQuickPdfNavigationStack(QObject *parent) + : QObject(parent) +{ + push(0, QPointF(), 1); +} + +/*! + \qmlmethod void PdfNavigationStack::forward() + + Goes back to the page, location and zoom level that was being viewed before + back() was called, and then emits the \l jumped() signal. + + If a new destination was pushed since the last time \l back() was called, + the forward() function does nothing, because there is a branch in the + timeline which causes the "future" to be lost. +*/ +void QQuickPdfNavigationStack::forward() +{ + if (m_currentHistoryIndex >= m_pageHistory.count() - 1) + return; + bool backAvailableWas = backAvailable(); + bool forwardAvailableWas = forwardAvailable(); + QPointF currentLocationWas = currentLocation(); + qreal currentZoomWas = currentZoom(); + ++m_currentHistoryIndex; + m_changing = true; + emit jumped(currentPage(), currentLocation(), currentZoom()); + if (currentZoomWas != currentZoom()) + emit currentZoomChanged(); + emit currentPageChanged(); + if (currentLocationWas != currentLocation()) + emit currentLocationChanged(); + if (!backAvailableWas) + emit backAvailableChanged(); + if (forwardAvailableWas != forwardAvailable()) + emit forwardAvailableChanged(); + m_changing = false; +} + +/*! + \qmlmethod void PdfNavigationStack::back() + + Pops the stack, updates the \l currentPage, \l currentLocation and + \l currentZoom properties to the most-recently-viewed destination, and then + emits the \l jumped() signal. +*/ +void QQuickPdfNavigationStack::back() +{ + if (m_currentHistoryIndex <= 0) + return; + bool backAvailableWas = backAvailable(); + bool forwardAvailableWas = forwardAvailable(); + QPointF currentLocationWas = currentLocation(); + qreal currentZoomWas = currentZoom(); + --m_currentHistoryIndex; + m_changing = true; + emit jumped(currentPage(), currentLocation(), currentZoom()); + if (currentZoomWas != currentZoom()) + emit currentZoomChanged(); + emit currentPageChanged(); + if (currentLocationWas != currentLocation()) + emit currentLocationChanged(); + if (backAvailableWas != backAvailable()) + emit backAvailableChanged(); + if (!forwardAvailableWas) + emit forwardAvailableChanged(); + m_changing = false; +} + +/*! + \qmlproperty int PdfNavigationStack::currentPage + + This property holds the current page that is being viewed. + If there is no current page, it holds \c -1. +*/ +int QQuickPdfNavigationStack::currentPage() const +{ + if (m_currentHistoryIndex < 0 || m_currentHistoryIndex >= m_pageHistory.count()) + return -1; + return m_pageHistory.at(m_currentHistoryIndex)->page; +} + +/*! + \qmlproperty point PdfNavigationStack::currentLocation + + This property holds the current location on the page that is being viewed. +*/ +QPointF QQuickPdfNavigationStack::currentLocation() const +{ + if (m_currentHistoryIndex < 0 || m_currentHistoryIndex >= m_pageHistory.count()) + return QPointF(); + return m_pageHistory.at(m_currentHistoryIndex)->location; +} + +/*! + \qmlproperty real PdfNavigationStack::currentZoom + + This property holds the magnification scale on the page that is being viewed. +*/ +qreal QQuickPdfNavigationStack::currentZoom() const +{ + if (m_currentHistoryIndex < 0 || m_currentHistoryIndex >= m_pageHistory.count()) + return 1; + return m_pageHistory.at(m_currentHistoryIndex)->zoom; +} + +/*! + \qmlmethod void PdfNavigationStack::push(int page, point location, qreal zoom) + + Adds the given destination, consisting of \a page, \a location and \a zoom, + to the history of visited locations. + + If forwardAvailable is \c true, calling this function represents a branch + in the timeline which causes the "future" to be lost, and therefore + forwardAvailable will change to \c false. +*/ +void QQuickPdfNavigationStack::push(int page, QPointF location, qreal zoom) +{ + if (page == currentPage() && location == currentLocation() && zoom == currentZoom()) + return; + if (qFuzzyIsNull(zoom)) + zoom = currentZoom(); + bool backAvailableWas = backAvailable(); + bool forwardAvailableWas = forwardAvailable(); + if (!m_changing) { + if (m_currentHistoryIndex >= 0 && forwardAvailableWas) + m_pageHistory.remove(m_currentHistoryIndex + 1, m_pageHistory.count() - m_currentHistoryIndex - 1); + m_pageHistory.append(QExplicitlySharedDataPointer<QPdfDestinationPrivate>(new QPdfDestinationPrivate(page, location, zoom))); + m_currentHistoryIndex = m_pageHistory.count() - 1; + } + emit currentZoomChanged(); + emit currentPageChanged(); + emit currentLocationChanged(); + if (m_changing) + return; + if (!backAvailableWas) + emit backAvailableChanged(); + if (forwardAvailableWas) + emit forwardAvailableChanged(); + emit jumped(page, location, zoom); + qCDebug(qLcNav) << "push: index" << m_currentHistoryIndex << "page" << page + << "@" << location << "zoom" << zoom << "-> history" << + [this]() { + QStringList ret; + for (auto d : m_pageHistory) + ret << QString::number(d->page); + return ret.join(','); + }(); +} + +/*! + \qmlmethod void PdfNavigationStack::update(int page, point location, qreal zoom) + + Modifies the current destination, consisting of \a page, \a location and \a zoom. + + This can be called periodically while the user is manually moving around + the document, so that after back() is called, forward() will jump back to + the most-recently-viewed destination rather than the destination that was + last specified by push(). + + The \c currentZoomChanged, \c currentPageChanged and \c currentLocationChanged + signals will be emitted if the respective properties are actually changed. + The \l jumped signal is not emitted, because this operation + represents smooth movement rather than a navigational jump. +*/ +void QQuickPdfNavigationStack::update(int page, QPointF location, qreal zoom) +{ + if (m_currentHistoryIndex < 0 || m_currentHistoryIndex >= m_pageHistory.count()) + return; + int currentPageWas = currentPage(); + QPointF currentLocationWas = currentLocation(); + qreal currentZoomWas = currentZoom(); + if (page == currentPageWas && location == currentLocationWas && zoom == currentZoomWas) + return; + m_pageHistory[m_currentHistoryIndex]->page = page; + m_pageHistory[m_currentHistoryIndex]->location = location; + m_pageHistory[m_currentHistoryIndex]->zoom = zoom; + if (currentZoomWas != zoom) + emit currentZoomChanged(); + if (currentPageWas != page) + emit currentPageChanged(); + if (currentLocationWas != location) + emit currentLocationChanged(); + qCDebug(qLcNav) << "update: index" << m_currentHistoryIndex << "page" << page + << "@" << location << "zoom" << zoom << "-> history" << + [this]() { + QStringList ret; + for (auto d : m_pageHistory) + ret << QString::number(d->page); + return ret.join(','); + }(); +} + +bool QQuickPdfNavigationStack::backAvailable() const +{ + return m_currentHistoryIndex > 0; +} + +bool QQuickPdfNavigationStack::forwardAvailable() const +{ + return m_currentHistoryIndex < m_pageHistory.count() - 1; +} + +/*! + \qmlsignal PdfNavigationStack::jumped(int page, point location, qreal zoom) + + This signal is emitted when forward(), back() or push() is called, but not + when update() is called. +*/ + +QT_END_NAMESPACE diff --git a/src/pdf/quick/qquickpdfnavigationstack_p.h b/src/pdf/quick/qquickpdfnavigationstack_p.h new file mode 100644 index 000000000..8d7102fb1 --- /dev/null +++ b/src/pdf/quick/qquickpdfnavigationstack_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPDFNAVIGATIONSTACK_P_H +#define QQUICKPDFNAVIGATIONSTACK_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickpdfdocument_p.h" +#include "../api/qpdfdestination_p.h" + +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickPdfNavigationStack : public QObject +{ + Q_OBJECT + Q_PROPERTY(int currentPage READ currentPage NOTIFY currentPageChanged) + Q_PROPERTY(QPointF currentLocation READ currentLocation NOTIFY currentLocationChanged) + Q_PROPERTY(qreal currentZoom READ currentZoom NOTIFY currentZoomChanged) + Q_PROPERTY(bool backAvailable READ backAvailable NOTIFY backAvailableChanged) + Q_PROPERTY(bool forwardAvailable READ forwardAvailable NOTIFY forwardAvailableChanged) + +public: + explicit QQuickPdfNavigationStack(QObject *parent = nullptr); + + Q_INVOKABLE void push(int page, QPointF location, qreal zoom); + Q_INVOKABLE void update(int page, QPointF location, qreal zoom); + Q_INVOKABLE void forward(); + Q_INVOKABLE void back(); + + int currentPage() const; + QPointF currentLocation() const; + qreal currentZoom() const; + + bool backAvailable() const; + bool forwardAvailable() const; + +Q_SIGNALS: + void currentPageChanged(); + void currentLocationChanged(); + void currentZoomChanged(); + void backAvailableChanged(); + void forwardAvailableChanged(); + void jumped(int page, QPointF location, qreal zoom); + +private: + QVector<QExplicitlySharedDataPointer<QPdfDestinationPrivate>> m_pageHistory; + int m_currentHistoryIndex = 0; + bool m_changing = false; + + Q_DISABLE_COPY(QQuickPdfNavigationStack) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPdfNavigationStack) + +#endif // QQUICKPDFNAVIGATIONSTACK_P_H diff --git a/src/pdf/quick/qquickpdfsearchmodel.cpp b/src/pdf/quick/qquickpdfsearchmodel.cpp new file mode 100644 index 000000000..a4b457841 --- /dev/null +++ b/src/pdf/quick/qquickpdfsearchmodel.cpp @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpdfsearchmodel_p.h" +#include <QtCore/qloggingcategory.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcS, "qt.pdf.search") + +/*! + \qmltype PdfSearchModel + \instantiates QQuickPdfSearchModel + \inqmlmodule QtQuick.Pdf + \ingroup pdf + \brief A representation of text search results within a PDF Document. + \since 5.15 + + PdfSearchModel provides the ability to search for text strings within a + document and get the geometric locations of matches on each page. +*/ + +QQuickPdfSearchModel::QQuickPdfSearchModel(QObject *parent) + : QPdfSearchModel(parent) +{ + connect(this, &QPdfSearchModel::searchStringChanged, + this, &QQuickPdfSearchModel::onResultsChanged); +} + +QQuickPdfDocument *QQuickPdfSearchModel::document() const +{ + return m_quickDocument; +} + +void QQuickPdfSearchModel::setDocument(QQuickPdfDocument *document) +{ + if (document == m_quickDocument || !document) + return; + + m_quickDocument = document; + QPdfSearchModel::setDocument(&document->m_doc); +} + +/*! + \qmlproperty list<list<point>> PdfSearchModel::currentResultBoundingPolygons + + A set of paths in a form that can be bound to the \c paths property of a + \l {QtQuick::PathMultiline}{PathMultiline} instance to render a batch of + rectangles around the regions comprising the search result \l currentResult + on \l currentPage. This is normally used to highlight one search result + at a time, in a UI that allows stepping through the results: + + \qml + PdfDocument { + id: doc + } + PdfSearchModel { + id: searchModel + document: doc + currentPage: view.currentPage + currentResult: ... + } + Shape { + ShapePath { + PathMultiline { + paths: searchModel.currentResultBoundingPolygons + } + } + } + \endqml + + \sa PathMultiline +*/ +QVector<QPolygonF> QQuickPdfSearchModel::currentResultBoundingPolygons() const +{ + QVector<QPolygonF> ret; + const auto &results = const_cast<QQuickPdfSearchModel *>(this)->resultsOnPage(m_currentPage); + if (m_currentResult < 0 || m_currentResult >= results.count()) + return ret; + const auto result = results[m_currentResult]; + for (auto rect : result.rectangles()) + ret << QPolygonF(rect); + return ret; +} + +void QQuickPdfSearchModel::onResultsChanged() +{ + emit currentPageBoundingPolygonsChanged(); + emit currentResultBoundingPolygonsChanged(); +} + +/*! + \qmlproperty list<list<point>> PdfSearchModel::currentPageBoundingPolygons + + A set of paths in a form that can be bound to the \c paths property of a + \l {QtQuick::PathMultiline}{PathMultiline} instance to render a batch of + rectangles around all the regions where search results are found on + \l currentPage: + + \qml + PdfDocument { + id: doc + } + PdfSearchModel { + id: searchModel + document: doc + } + Shape { + ShapePath { + PathMultiline { + paths: searchModel.matchGeometry(view.currentPage) + } + } + } + \endqml + + \sa PathMultiline +*/ +QVector<QPolygonF> QQuickPdfSearchModel::currentPageBoundingPolygons() const +{ + return const_cast<QQuickPdfSearchModel *>(this)->boundingPolygonsOnPage(m_currentPage); +} + +/*! + \qmlfunction list<list<point>> PdfSearchModel::boundingPolygonsOnPage(int page) + + Returns a set of paths in a form that can be bound to the \c paths property of a + \l {QtQuick::PathMultiline}{PathMultiline} instance to render a batch of + rectangles around all the locations where search results are found: + + \qml + PdfDocument { + id: doc + } + PdfSearchModel { + id: searchModel + document: doc + } + Shape { + ShapePath { + PathMultiline { + paths: searchModel.matchGeometry(view.currentPage) + } + } + } + \endqml + + \sa PathMultiline +*/ +QVector<QPolygonF> QQuickPdfSearchModel::boundingPolygonsOnPage(int page) +{ + if (!document() || searchString().isEmpty() || page < 0 || page > document()->pageCount()) + return {}; + + updatePage(page); + + QVector<QPolygonF> ret; + auto m = QPdfSearchModel::resultsOnPage(page); + for (auto result : m) { + for (auto rect : result.rectangles()) + ret << QPolygonF(rect); + } + + return ret; +} + +/*! + \qmlproperty int PdfSearchModel::currentPage + + The page on which \l currentMatchGeometry should provide filtered search results. +*/ +void QQuickPdfSearchModel::setCurrentPage(int currentPage) +{ + if (m_currentPage == currentPage) + return; + + if (currentPage < 0) + currentPage = document()->pageCount() - 1; + else if (currentPage >= document()->pageCount()) + currentPage = 0; + + m_currentPage = currentPage; + if (!m_suspendSignals) { + emit currentPageChanged(); + onResultsChanged(); + } +} + +/*! + \qmlproperty int PdfSearchModel::currentResult + + The result index on \l currentPage for which \l currentResultBoundingPolygons + should provide the regions to highlight. +*/ +void QQuickPdfSearchModel::setCurrentResult(int currentResult) +{ + if (m_currentResult == currentResult) + return; + + int currentResultWas = currentResult; + int currentPageWas = m_currentPage; + if (currentResult < 0) { + setCurrentPage(m_currentPage - 1); + while (resultsOnPage(m_currentPage).count() == 0 && m_currentPage != currentPageWas) { + m_suspendSignals = true; + setCurrentPage(m_currentPage - 1); + } + if (m_suspendSignals) { + emit currentPageChanged(); + m_suspendSignals = false; + } + const auto results = resultsOnPage(m_currentPage); + currentResult = results.count() - 1; + } else { + const auto results = resultsOnPage(m_currentPage); + if (currentResult >= results.count()) { + setCurrentPage(m_currentPage + 1); + while (resultsOnPage(m_currentPage).count() == 0 && m_currentPage != currentPageWas) { + m_suspendSignals = true; + setCurrentPage(m_currentPage + 1); + } + if (m_suspendSignals) { + emit currentPageChanged(); + m_suspendSignals = false; + } + currentResult = 0; + } + } + qCDebug(qLcS) << "currentResult was" << m_currentResult + << "requested" << currentResultWas << "on page" << currentPageWas + << "->" << currentResult << "on page" << m_currentPage; + + m_currentResult = currentResult; + emit currentResultChanged(); + emit currentResultBoundingPolygonsChanged(); +} + +/*! + \qmlproperty string PdfSearchModel::searchString + + The string to search for. +*/ + +QT_END_NAMESPACE diff --git a/src/pdf/quick/qquickpdfsearchmodel_p.h b/src/pdf/quick/qquickpdfsearchmodel_p.h new file mode 100644 index 000000000..3e05f80e3 --- /dev/null +++ b/src/pdf/quick/qquickpdfsearchmodel_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPDFSEARCHMODEL_P_H +#define QQUICKPDFSEARCHMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickpdfdocument_p.h" +#include "../api/qpdfsearchmodel.h" + +#include <QtCore/qvariant.h> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickPdfSearchModel : public QPdfSearchModel +{ + Q_OBJECT + Q_PROPERTY(QQuickPdfDocument *document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged) + Q_PROPERTY(int currentResult READ currentResult WRITE setCurrentResult NOTIFY currentResultChanged) + Q_PROPERTY(QVector<QPolygonF> currentPageBoundingPolygons READ currentPageBoundingPolygons NOTIFY currentPageBoundingPolygonsChanged) + Q_PROPERTY(QVector<QPolygonF> currentResultBoundingPolygons READ currentResultBoundingPolygons NOTIFY currentResultBoundingPolygonsChanged) + +public: + explicit QQuickPdfSearchModel(QObject *parent = nullptr); + + QQuickPdfDocument *document() const; + void setDocument(QQuickPdfDocument * document); + + Q_INVOKABLE QVector<QPolygonF> boundingPolygonsOnPage(int page); + + int currentPage() const { return m_currentPage; } + void setCurrentPage(int currentPage); + + int currentResult() const { return m_currentResult; } + void setCurrentResult(int currentResult); + + QVector<QPolygonF> currentPageBoundingPolygons() const; + QVector<QPolygonF> currentResultBoundingPolygons() const; + +signals: + void documentChanged(); + void currentPageChanged(); + void currentResultChanged(); + void currentPageBoundingPolygonsChanged(); + void currentResultBoundingPolygonsChanged(); + +private: + void updateResults(); + void onResultsChanged(); + +private: + QQuickPdfDocument *m_quickDocument = nullptr; + int m_currentPage = 0; + int m_currentResult = 0; + bool m_suspendSignals = false; + + Q_DISABLE_COPY(QQuickPdfSearchModel) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPdfSearchModel) +QML_DECLARE_TYPE(QPdfSelection) + +#endif // QQUICKPDFSEARCHMODEL_P_H diff --git a/src/pdf/quick/qquickpdfselection.cpp b/src/pdf/quick/qquickpdfselection.cpp new file mode 100644 index 000000000..5371e85e5 --- /dev/null +++ b/src/pdf/quick/qquickpdfselection.cpp @@ -0,0 +1,284 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpdfselection_p.h" +#include "qquickpdfdocument_p.h" +#include <QClipboard> +#include <QQuickItem> +#include <QQmlEngine> +#include <QStandardPaths> +#include <private/qguiapplication_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype PdfSelection + \instantiates QQuickPdfSelection + \inqmlmodule QtQuick.Pdf + \ingroup pdf + \brief A representation of a text selection within a PDF Document. + \since 5.15 + + PdfSelection provides the text string and its geometry within a bounding box + from one point to another. +*/ + +/*! + Constructs a SearchModel. +*/ +QQuickPdfSelection::QQuickPdfSelection(QObject *parent) + : QObject(parent) +{ +} + +QQuickPdfDocument *QQuickPdfSelection::document() const +{ + return m_document; +} + +void QQuickPdfSelection::setDocument(QQuickPdfDocument *document) +{ + if (m_document == document) + return; + + if (m_document) { + disconnect(m_document, &QQuickPdfDocument::sourceChanged, + this, &QQuickPdfSelection::resetPoints); + } + m_document = document; + emit documentChanged(); + resetPoints(); + connect(m_document, &QQuickPdfDocument::sourceChanged, + this, &QQuickPdfSelection::resetPoints); +} + +/*! + \qmlproperty list<list<point>> PdfSelection::geometry + + A set of paths in a form that can be bound to the \c paths property of a + \l {QtQuick::PathMultiline}{PathMultiline} instance to render a batch of + rectangles around the text regions that are included in the selection: + + \qml + PdfDocument { + id: doc + } + PdfSelection { + id: selection + document: doc + fromPoint: textSelectionDrag.centroid.pressPosition + toPoint: textSelectionDrag.centroid.position + hold: !textSelectionDrag.active + } + Shape { + ShapePath { + PathMultiline { + paths: selection.geometry + } + } + } + DragHandler { + id: textSelectionDrag + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + target: null + } + \endqml + + \sa PathMultiline +*/ +QVector<QPolygonF> QQuickPdfSelection::geometry() const +{ + return m_geometry; +} + +void QQuickPdfSelection::selectAll() +{ + QPdfSelection sel = m_document->m_doc.getAllText(m_page); + if (sel.text() != m_text) { + m_text = sel.text(); + if (QGuiApplication::clipboard()->supportsSelection()) + sel.copyToClipboard(QClipboard::Selection); + emit textChanged(); + } + + if (sel.bounds() != m_geometry) { + m_geometry = sel.bounds(); + emit geometryChanged(); + } +} + +void QQuickPdfSelection::resetPoints() +{ + bool wasHolding = m_hold; + m_hold = false; + setFromPoint(QPointF()); + setToPoint(QPointF()); + m_hold = wasHolding; +} + +/*! + \qmlproperty int PdfSelection::page + + The page number on which to search. + + \sa QtQuick::Image::currentFrame +*/ +int QQuickPdfSelection::page() const +{ + return m_page; +} + +void QQuickPdfSelection::setPage(int page) +{ + if (m_page == page) + return; + + m_page = page; + emit pageChanged(); + resetPoints(); +} + +/*! + \qmlproperty point PdfSelection::fromPoint + + The beginning location, in \l {https://en.wikipedia.org/wiki/Point_(typography)}{points} + from the upper-left corner of the page, from which to find selected text. + This can be bound to a scaled version of the \c centroid.pressPosition + of a \l DragHandler to begin selecting text from the position where the user + presses the mouse button and begins dragging, for example. +*/ +QPointF QQuickPdfSelection::fromPoint() const +{ + return m_fromPoint; +} + +void QQuickPdfSelection::setFromPoint(QPointF fromPoint) +{ + if (m_hold || m_fromPoint == fromPoint) + return; + + m_fromPoint = fromPoint; + emit fromPointChanged(); + updateResults(); +} + +/*! + \qmlproperty point PdfSelection::toPoint + + The ending location, in \l {https://en.wikipedia.org/wiki/Point_(typography)}{points} + from the upper-left corner of the page, from which to find selected text. + This can be bound to a scaled version of the \c centroid.position + of a \l DragHandler to end selection of text at the position where the user + is currently dragging the mouse, for example. +*/ +QPointF QQuickPdfSelection::toPoint() const +{ + return m_toPoint; +} + +void QQuickPdfSelection::setToPoint(QPointF toPoint) +{ + if (m_hold || m_toPoint == toPoint) + return; + + m_toPoint = toPoint; + emit toPointChanged(); + updateResults(); +} + +/*! + \qmlproperty bool PdfSelection::hold + + Controls whether to hold the existing selection regardless of changes to + \l fromPoint and \l toPoint. This property can be set to \c true when the mouse + or touchpoint is released, so that the selection is not lost due to the + point bindings changing. +*/ +bool QQuickPdfSelection::hold() const +{ + return m_hold; +} + +void QQuickPdfSelection::setHold(bool hold) +{ + if (m_hold == hold) + return; + + m_hold = hold; + emit holdChanged(); +} + +/*! + \qmlproperty string PdfSelection::string + + The string found. +*/ +QString QQuickPdfSelection::text() const +{ + return m_text; +} + +#if QT_CONFIG(clipboard) +/*! + \qmlmethod void PdfSelection::copyToClipboard() + + Copies plain text from the \l string property to the system clipboard. +*/ +void QQuickPdfSelection::copyToClipboard() const +{ + QGuiApplication::clipboard()->setText(m_text); +} +#endif + +void QQuickPdfSelection::updateResults() +{ + if (!m_document) + return; + QPdfSelection sel = m_document->document().getSelection(m_page, m_fromPoint, m_toPoint); + if (sel.text() != m_text) { + m_text = sel.text(); + if (QGuiApplication::clipboard()->supportsSelection()) + sel.copyToClipboard(QClipboard::Selection); + emit textChanged(); + } + + if (sel.bounds() != m_geometry) { + m_geometry = sel.bounds(); + emit geometryChanged(); + } +} + +QT_END_NAMESPACE diff --git a/src/pdf/quick/qquickpdfselection_p.h b/src/pdf/quick/qquickpdfselection_p.h new file mode 100644 index 000000000..bb4a50fed --- /dev/null +++ b/src/pdf/quick/qquickpdfselection_p.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPDFSELECTION_P_H +#define QQUICKPDFSELECTION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QPointF> +#include <QPolygonF> +#include <QVariant> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickPdfDocument; + +class QQuickPdfSelection : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickPdfDocument *document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(int page READ page WRITE setPage NOTIFY pageChanged) + Q_PROPERTY(QPointF fromPoint READ fromPoint WRITE setFromPoint NOTIFY fromPointChanged) + Q_PROPERTY(QPointF toPoint READ toPoint WRITE setToPoint NOTIFY toPointChanged) + Q_PROPERTY(bool hold READ hold WRITE setHold NOTIFY holdChanged) + + Q_PROPERTY(QString text READ text NOTIFY textChanged) + Q_PROPERTY(QVector<QPolygonF> geometry READ geometry NOTIFY geometryChanged) + +public: + explicit QQuickPdfSelection(QObject *parent = nullptr); + + QQuickPdfDocument *document() const; + void setDocument(QQuickPdfDocument * document); + int page() const; + void setPage(int page); + QPointF fromPoint() const; + void setFromPoint(QPointF fromPoint); + QPointF toPoint() const; + void setToPoint(QPointF toPoint); + bool hold() const; + void setHold(bool hold); + + QString text() const; + QVector<QPolygonF> geometry() const; + + Q_INVOKABLE void selectAll(); +#if QT_CONFIG(clipboard) + Q_INVOKABLE void copyToClipboard() const; +#endif + +signals: + void documentChanged(); + void pageChanged(); + void fromPointChanged(); + void toPointChanged(); + void holdChanged(); + void textChanged(); + void geometryChanged(); + +private: + void resetPoints(); + void updateResults(); + +private: + QQuickPdfDocument *m_document = nullptr; + QPointF m_fromPoint; + QPointF m_toPoint; + QString m_text; + QVector<QPolygonF> m_geometry; + int m_page = 0; + bool m_hold = false; + + Q_DISABLE_COPY(QQuickPdfSelection) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPdfSelection) +\ +#endif // QQUICKPDFSELECTION_P_H diff --git a/src/pdf/quick/quick.pro b/src/pdf/quick/quick.pro new file mode 100644 index 000000000..b62b80346 --- /dev/null +++ b/src/pdf/quick/quick.pro @@ -0,0 +1,34 @@ +CXX_MODULE = qml +TARGET = pdfplugin +TARGETPATH = QtQuick/Pdf +IMPORT_VERSION = 1.0 + +#QMAKE_DOCS = $$PWD/doc/qtquickpdf.qdocconf + +PDF_QML_FILES = \ + qml/PdfMultiPageView.qml \ + qml/PdfPageView.qml \ + qml/PdfScrollablePageView.qml \ + +QML_FILES += $$PDF_QML_FILES qmldir + +RESOURCES += resources.qrc + +SOURCES += \ + plugin.cpp \ + qquickpdfdocument.cpp \ + qquickpdflinkmodel.cpp \ + qquickpdfnavigationstack.cpp \ + qquickpdfsearchmodel.cpp \ + qquickpdfselection.cpp \ + +HEADERS += \ + qquickpdfdocument_p.h \ + qquickpdflinkmodel_p.h \ + qquickpdfnavigationstack_p.h \ + qquickpdfsearchmodel_p.h \ + qquickpdfselection_p.h \ + +QT += pdf quick-private gui gui-private core core-private qml qml-private + +load(qml_plugin) diff --git a/src/pdf/quick/resources.qrc b/src/pdf/quick/resources.qrc new file mode 100644 index 000000000..8270a2028 --- /dev/null +++ b/src/pdf/quick/resources.qrc @@ -0,0 +1,10 @@ +<RCC> + <qresource prefix="/qt-project.org/qtpdf"> + <file>qml/+material/PdfStyle.qml</file> + <file>qml/+universal/PdfStyle.qml</file> + <file>qml/PdfStyle.qml</file> + <file>qml/PdfMultiPageView.qml</file> + <file>qml/PdfPageView.qml</file> + <file>qml/PdfScrollablePageView.qml</file> + </qresource> +</RCC> |