diff options
-rw-r--r-- | src/pdf/pdf.pro | 3 | ||||
-rw-r--r-- | src/pdf/qpdfdocument.cpp | 168 | ||||
-rw-r--r-- | src/pdf/qpdfdocument.h | 17 | ||||
-rw-r--r-- | src/pdf/qpdfdocument_p.h | 23 | ||||
-rw-r--r-- | tests/auto/pdf/qpdfdocument/qpdfdocument.pro | 2 | ||||
-rw-r--r-- | tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp | 44 |
6 files changed, 225 insertions, 32 deletions
diff --git a/src/pdf/pdf.pro b/src/pdf/pdf.pro index 4c3244c87..811a9468d 100644 --- a/src/pdf/pdf.pro +++ b/src/pdf/pdf.pro @@ -1,5 +1,6 @@ TARGET = QtQPdf -QT = gui core network +QT += gui core +QT_PRIVATE += network TEMPLATE = lib CONFIG += c++11 INCLUDEPATH += ../3rdparty/pdfium/fpdfsdk/include diff --git a/src/pdf/qpdfdocument.cpp b/src/pdf/qpdfdocument.cpp index d6aed7451..3eeef297f 100644 --- a/src/pdf/qpdfdocument.cpp +++ b/src/pdf/qpdfdocument.cpp @@ -11,7 +11,9 @@ Q_GLOBAL_STATIC_WITH_ARGS(QMutex, pdfMutex, (QMutex::Recursive)); static int libraryRefCount; QPdfDocumentPrivate::QPdfDocumentPrivate() - : doc(0) + : avail(0) + , doc(0) + , lastError(QPdfDocument::NoError) { QMutexLocker lock(pdfMutex()); if (libraryRefCount == 0) @@ -21,25 +23,58 @@ QPdfDocumentPrivate::QPdfDocumentPrivate() // 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() { QMutexLocker lock(pdfMutex()); - if (doc) - FPDF_CloseDocument(doc); - doc = 0; + + clear(); if (!--libraryRefCount) FPDF_DestroyLibrary(); } -QPdfDocument::Error QPdfDocumentPrivate::load(QIODevice *newDevice, bool transferDeviceOwnership, const QString &documentPassword) +void QPdfDocumentPrivate::clear() { - QMutexLocker lock(pdfMutex()); - if (doc) FPDF_CloseDocument(doc); + doc = 0; + + if (avail) + FPDFAvail_Destroy(avail); + avail = 0; + + asyncBuffer.close(); + asyncBuffer.setData(QByteArray()); + asyncBuffer.open(QIODevice::ReadWrite); +} + +void QPdfDocumentPrivate::setErrorCode() +{ + switch (FPDF_GetLastError()) { + 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(); + } +} + +QPdfDocument::Error QPdfDocumentPrivate::load(QIODevice *newDevice, bool transferDeviceOwnership, const QString &documentPassword) +{ + clear(); if (transferDeviceOwnership) ownDevice.reset(newDevice); @@ -56,17 +91,66 @@ QPdfDocument::Error QPdfDocumentPrivate::load(QIODevice *newDevice, bool transfe password = documentPassword.toUtf8(); doc = FPDF_LoadCustomDocument(this, password.constData()); - switch (FPDF_GetLastError()) { - case FPDF_ERR_SUCCESS: return QPdfDocument::NoError; - case FPDF_ERR_UNKNOWN: return QPdfDocument::UnknownError; - case FPDF_ERR_FILE: return QPdfDocument::FileNotFoundError; - case FPDF_ERR_FORMAT: return QPdfDocument::InvalidFileFormatError; - case FPDF_ERR_PASSWORD: return QPdfDocument::IncorrectPasswordError; - case FPDF_ERR_SECURITY: return QPdfDocument::UnsupportedSecuritySchemeError; - default: - Q_UNREACHABLE(); + setErrorCode(); + return lastError; +} + +void QPdfDocumentPrivate::_q_initiateAsyncLoad() +{ + QMutexLocker lock(pdfMutex()); + if (avail) + return; + + QVariant contentLength = remoteDevice->header(QNetworkRequest::ContentLengthHeader); + if (!contentLength.isValid()) + return; + + // FPDF_FILEACCESS setup + m_FileLen = contentLength.toULongLong(); + + QObject::connect(remoteDevice, SIGNAL(readyRead()), q, SLOT(_q_readFromDevice())); + + avail = FPDFAvail_Create(this, this); + + if (remoteDevice->bytesAvailable()) + _q_readFromDevice(); +} + +void QPdfDocumentPrivate::_q_readFromDevice() +{ + QMutexLocker lock(pdfMutex()); + QByteArray data = remoteDevice->read(remoteDevice->bytesAvailable()); + if (data.isEmpty()) + return; + asyncBuffer.seek(asyncBuffer.size()); + asyncBuffer.write(data); + + if (!doc) { + tryLoadDocument(); + } +} + +void QPdfDocumentPrivate::tryLoadDocument() +{ + if (!FPDFAvail_IsDocAvail(avail, this)) + return; + + Q_ASSERT(!doc); + + doc = FPDFAvail_GetDocument(avail, password); + if (!doc) { + setErrorCode(); + if (lastError == QPdfDocument::IncorrectPasswordError) + emit q->passwordRequired(); } - return QPdfDocument::UnknownError; + if (doc) + emit q->documentReady(); +} + +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->asyncBuffer.size()); } int QPdfDocumentPrivate::fpdf_GetBlock(void *param, unsigned long position, unsigned char *pBuf, unsigned long size) @@ -77,10 +161,18 @@ int QPdfDocumentPrivate::fpdf_GetBlock(void *param, unsigned long position, unsi } +void QPdfDocumentPrivate::fpdf_AddSegment(_FX_DOWNLOADHINTS *pThis, size_t offset, size_t size) +{ + Q_UNUSED(pThis); + Q_UNUSED(offset); + Q_UNUSED(size); +} + QPdfDocument::QPdfDocument(QObject *parent) : QObject(parent) , d(new QPdfDocumentPrivate) { + d->q = this; } QPdfDocument::~QPdfDocument() @@ -89,14 +181,54 @@ QPdfDocument::~QPdfDocument() QPdfDocument::Error QPdfDocument::load(const QString &fileName, const QString &password) { + QMutexLocker lock(pdfMutex()); return d->load(new QFile(fileName), /*transfer ownership*/true, password); } QPdfDocument::Error QPdfDocument::load(QIODevice *device, const QString &password) { + QMutexLocker lock(pdfMutex()); return d->load(device, /*transfer ownership*/false, password); } +void QPdfDocument::loadAsynchronously(QNetworkReply *device) +{ + QMutexLocker lock(pdfMutex()); + d->clear(); + + d->ownDevice.reset(); + d->device = &d->asyncBuffer; + + if (d->remoteDevice) + d->remoteDevice->disconnect(this); + + d->remoteDevice = device; + + if (d->remoteDevice->header(QNetworkRequest::ContentLengthHeader).isValid()) + d->_q_initiateAsyncLoad(); + else + connect(d->remoteDevice, SIGNAL(metaDataChanged()), this, SLOT(_q_initiateAsyncLoad())); +} + +void QPdfDocument::setPassword(const QString &password) +{ + QMutexLocker lock(pdfMutex()); + d->password = password.toUtf8(); + + if (!d->doc && d->avail) + d->tryLoadDocument(); +} + +QString QPdfDocument::password() const +{ + return QString::fromUtf8(d->password); +} + +QPdfDocument::Error QPdfDocument::error() const +{ + return d->lastError; +} + int QPdfDocument::pageCount() const { if (!d->doc) @@ -135,3 +267,5 @@ QImage QPdfDocument::render(int page, const QSizeF &pageSize) FPDFBitmap_Destroy(bitmap); return result; } + +#include "moc_qpdfdocument.cpp" diff --git a/src/pdf/qpdfdocument.h b/src/pdf/qpdfdocument.h index b33f0fe29..df6e69fd9 100644 --- a/src/pdf/qpdfdocument.h +++ b/src/pdf/qpdfdocument.h @@ -6,11 +6,13 @@ #include "qtpdfglobal.h" class QPdfDocumentPrivate; +class QNetworkReply; class Q_PDF_EXPORT QPdfDocument : public QObject { Q_OBJECT - Q_PROPERTY(int pageCount READ pageCount FINAL) + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged FINAL) + Q_PROPERTY(QString password READ password WRITE setPassword FINAL) public: enum Error { @@ -28,13 +30,26 @@ public: Error load(const QString &fileName, const QString &password = QString()); Error load(QIODevice *device, const QString &password = QString()); + void loadAsynchronously(QNetworkReply *device); + void setPassword(const QString &password); + QString password() const; + + Error error() const; + int pageCount() const; QSizeF pageSize(int page) const; QImage render(int page, const QSizeF &pageSize); +Q_SIGNALS: + void passwordRequired(); + void documentReady(); + void pageCountChanged(); + private: + Q_PRIVATE_SLOT(d, void _q_initiateAsyncLoad()) + Q_PRIVATE_SLOT(d, void _q_readFromDevice()) QScopedPointer<QPdfDocumentPrivate> d; }; diff --git a/src/pdf/qpdfdocument_p.h b/src/pdf/qpdfdocument_p.h index 7f9b10220..ec9e2e3ae 100644 --- a/src/pdf/qpdfdocument_p.h +++ b/src/pdf/qpdfdocument_p.h @@ -5,24 +5,41 @@ #include "fpdf_dataavail.h" #include "qpdfdocument.h" -#include <qiodevice.h> +#include <qbuffer.h> +#include <qnetworkreply.h> -class QPdfDocumentPrivate: public FPDF_FILEACCESS +class QPdfDocumentPrivate: public FPDF_FILEACCESS, public FX_FILEAVAIL, public FX_DOWNLOADHINTS { public: QPdfDocumentPrivate(); ~QPdfDocumentPrivate(); + QPdfDocument *q; + + FPDF_AVAIL avail; FPDF_DOCUMENT doc; - QIODevice *device; + QPointer<QIODevice> device; QScopedPointer<QIODevice> ownDevice; + QBuffer asyncBuffer; + QPointer<QNetworkReply> remoteDevice; QByteArray password; + QPdfDocument::Error lastError; + + void clear(); + QPdfDocument::Error load(QIODevice *device, bool ownDevice, const QString &documentPassword); + void loadAsync(QIODevice *device); + + void _q_initiateAsyncLoad(); + void _q_readFromDevice(); + void tryLoadDocument(); static 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 setErrorCode(); }; #endif // QPDFDOCUMENT_P_H diff --git a/tests/auto/pdf/qpdfdocument/qpdfdocument.pro b/tests/auto/pdf/qpdfdocument/qpdfdocument.pro index 28e56e0bd..8382a25e3 100644 --- a/tests/auto/pdf/qpdfdocument/qpdfdocument.pro +++ b/tests/auto/pdf/qpdfdocument/qpdfdocument.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qpdfdocument -QT += pdf printsupport testlib +QT += pdf printsupport testlib network macx:CONFIG -= app_bundle SOURCES += tst_qpdfdocument.cpp diff --git a/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp b/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp index a51c17296..c0fe93cef 100644 --- a/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp +++ b/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp @@ -5,6 +5,9 @@ #include <QPdfDocument> #include <QPrinter> #include <QTemporaryFile> +#include <QNetworkAccessManager> +#include <QNetworkRequest> +#include <QNetworkReply> class tst_QPdfDocument: public QObject { @@ -14,6 +17,7 @@ public: private slots: void pageCount(); void loadFromIODevice(); + void loadAsync(); }; struct TemporaryPdf: public QTemporaryFile @@ -28,17 +32,21 @@ TemporaryPdf::TemporaryPdf() open(); pageLayout = QPageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF()); - QPrinter printer; - printer.setOutputFormat(QPrinter::PdfFormat); - printer.setOutputFileName(fileName()); - printer.setPageLayout(pageLayout); - { - QPainter painter(&printer); - painter.drawText(100, 100, QStringLiteral("Hello Page 1")); - printer.newPage(); - painter.drawText(100, 100, QStringLiteral("Hello Page 2")); + QPrinter printer; + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(fileName()); + printer.setPageLayout(pageLayout); + + { + QPainter painter(&printer); + painter.drawText(100, 100, QStringLiteral("Hello Page 1")); + printer.newPage(); + painter.drawText(100, 100, QStringLiteral("Hello Page 2")); + } } + + seek(0); } void tst_QPdfDocument::pageCount() @@ -61,6 +69,24 @@ void tst_QPdfDocument::loadFromIODevice() QCOMPARE(doc.pageCount(), 2); } +void tst_QPdfDocument::loadAsync() +{ + TemporaryPdf tempPdf; + + QNetworkAccessManager nam; + + QUrl url = QUrl::fromLocalFile(tempPdf.fileName()); + QScopedPointer<QNetworkReply> reply(nam.get(QNetworkRequest(url))); + + QPdfDocument doc; + QSignalSpy readySpy(&doc, SIGNAL(documentReady())); + + doc.loadAsynchronously(reply.data()); + + QCOMPARE(readySpy.count(), 1); + QCOMPARE(doc.pageCount(), 2); +} + QTEST_MAIN(tst_QPdfDocument) #include "tst_qpdfdocument.moc" |