diff options
author | Jordi Pujol Foyo <jordi@vikingsoftware.com> | 2019-12-13 14:27:11 +0100 |
---|---|---|
committer | Jordi Pujol Foyo <jordi@vikingsoftware.com> | 2019-12-19 21:23:43 +0100 |
commit | 9d6b4d11424cdcd6c572cdcc50b3b790b6e673c0 (patch) | |
tree | b51c3f23188465627df655339166f5c1241a5d25 /src/gui/painting | |
parent | 6227a0d2e2e924cf717fa2a3d7c3e1223c4aed5f (diff) |
New features for QPdfWriter
Added new API setDocumentXmpMetadata/documentXmpMetadata and
addFileAttachment
[ChangeLog][QtGui][QPdfWriter] New API to provide external document XMP
metadata and attach files to PDF.
Fixes: QTBUG-78651
Fixes: QTBUG-78764
Change-Id: Ic0b37e8d12899f907001db469080594c14c87655
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
Diffstat (limited to 'src/gui/painting')
-rw-r--r-- | src/gui/painting/qpdf.cpp | 159 | ||||
-rw-r--r-- | src/gui/painting/qpdf_p.h | 21 | ||||
-rw-r--r-- | src/gui/painting/qpdfwriter.cpp | 44 | ||||
-rw-r--r-- | src/gui/painting/qpdfwriter.h | 5 |
4 files changed, 196 insertions, 33 deletions
diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp index 932c3e6f5a..de9fc13331 100644 --- a/src/gui/painting/qpdf.cpp +++ b/src/gui/painting/qpdf.cpp @@ -1390,6 +1390,18 @@ void QPdfEngine::setPdfVersion(PdfVersion version) d->pdfVersion = version; } +void QPdfEngine::setDocumentXmpMetadata(const QByteArray &xmpMetadata) +{ + Q_D(QPdfEngine); + d->xmpDocumentMetadata = xmpMetadata; +} + +QByteArray QPdfEngine::documentXmpMetadata() const +{ + Q_D(const QPdfEngine); + return d->xmpDocumentMetadata; +} + void QPdfEngine::setPageLayout(const QPageLayout &pageLayout) { Q_D(QPdfEngine); @@ -1520,6 +1532,8 @@ bool QPdfEngine::begin(QPaintDevice *pdev) d->xrefPositions.clear(); d->pageRoot = 0; + d->embeddedfilesRoot = 0; + d->namesRoot = 0; d->catalog = 0; d->info = 0; d->graphicsState = 0; @@ -1555,10 +1569,18 @@ bool QPdfEngine::end() d->outDevice = nullptr; } + d->fileCache.clear(); + setActive(false); return true; } +void QPdfEngine::addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType) +{ + Q_D(QPdfEngine); + d->fileCache.push_back({fileName, data, mimeType}); +} + QPdfEnginePrivate::~QPdfEnginePrivate() { qDeleteAll(fonts); @@ -1586,13 +1608,19 @@ void QPdfEnginePrivate::writeHeader() int metaDataObj = -1; int outputIntentObj = -1; + if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) { + metaDataObj = writeXmpDcumentMetaData(); + } if (pdfVersion == QPdfEngine::Version_A1b) { - metaDataObj = writeXmpMetaData(); outputIntentObj = writeOutputIntent(); } catalog = addXrefEntry(-1); pageRoot = requestObject(); + if (!fileCache.isEmpty()) { + namesRoot = requestObject(); + embeddedfilesRoot = requestObject(); + } // catalog { @@ -1602,10 +1630,15 @@ void QPdfEnginePrivate::writeHeader() << "/Type /Catalog\n" << "/Pages " << pageRoot << "0 R\n"; - if (pdfVersion == QPdfEngine::Version_A1b) { - s << "/OutputIntents [" << outputIntentObj << "0 R]\n"; + // Embedded files, if any + if (!fileCache.isEmpty()) + s << "/Names " << embeddedfilesRoot << "0 R\n"; + + if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) s << "/Metadata " << metaDataObj << "0 R\n"; - } + + if (pdfVersion == QPdfEngine::Version_A1b) + s << "/OutputIntents [" << outputIntentObj << "0 R]\n"; s << ">>\n" << "endobj\n"; @@ -1613,6 +1646,12 @@ void QPdfEnginePrivate::writeHeader() write(catalog); } + if (!fileCache.isEmpty()) { + addXrefEntry(embeddedfilesRoot); + xprintf("<</EmbeddedFiles %d 0 R>>\n" + "endobj\n", namesRoot); + } + // graphics state graphicsState = addXrefEntry(-1); xprintf("<<\n" @@ -1664,39 +1703,45 @@ void QPdfEnginePrivate::writeInfo() "endobj\n"); } -int QPdfEnginePrivate::writeXmpMetaData() +int QPdfEnginePrivate::writeXmpDcumentMetaData() { const int metaDataObj = addXrefEntry(-1); + QByteArray metaDataContent; + + if (xmpDocumentMetadata.isEmpty()) { + const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR)); + + const QDateTime now = QDateTime::currentDateTime(); + const QDate date = now.date(); + const QTime time = now.time(); + const QString timeStr = + QString::asprintf("%d-%02d-%02dT%02d:%02d:%02d", + date.year(), date.month(), date.day(), + time.hour(), time.minute(), time.second()); + + const int offset = now.offsetFromUtc(); + const int hours = (offset / 60) / 60; + const int mins = (offset / 60) % 60; + QString tzStr; + if (offset < 0) + tzStr = QString::asprintf("-%02d:%02d", -hours, -mins); + else if (offset > 0) + tzStr = QString::asprintf("+%02d:%02d", hours , mins); + else + tzStr = QLatin1String("Z"); - const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR)); - - const QDateTime now = QDateTime::currentDateTime(); - const QDate date = now.date(); - const QTime time = now.time(); - const QString timeStr = - QString::asprintf("%d-%02d-%02dT%02d:%02d:%02d", - date.year(), date.month(), date.day(), - time.hour(), time.minute(), time.second()); + const QString metaDataDate = timeStr + tzStr; - const int offset = now.offsetFromUtc(); - const int hours = (offset / 60) / 60; - const int mins = (offset / 60) % 60; - QString tzStr; - if (offset < 0) - tzStr = QString::asprintf("-%02d:%02d", -hours, -mins); - else if (offset > 0) - tzStr = QString::asprintf("+%02d:%02d", hours , mins); + QFile metaDataFile(QLatin1String(":/qpdf/qpdfa_metadata.xml")); + metaDataFile.open(QIODevice::ReadOnly); + metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(), + title.toHtmlEscaped(), + creator.toHtmlEscaped(), + metaDataDate).toUtf8(); + } else - tzStr = QLatin1String("Z"); + metaDataContent = xmpDocumentMetadata; - const QString metaDataDate = timeStr + tzStr; - - QFile metaDataFile(QLatin1String(":/qpdf/qpdfa_metadata.xml")); - metaDataFile.open(QIODevice::ReadOnly); - const QByteArray metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(), - title.toHtmlEscaped(), - creator.toHtmlEscaped(), - metaDataDate).toUtf8(); xprintf("<<\n" "/Type /Metadata /Subtype /XML\n" "/Length %d\n" @@ -1774,6 +1819,56 @@ void QPdfEnginePrivate::writePageRoot() "endobj\n"); } +void QPdfEnginePrivate::writeAttachmentRoot() +{ + if (fileCache.isEmpty()) + return; + + QVector<int> attachments; + const int size = fileCache.size(); + for (int i = 0; i < size; ++i) { + auto attachment = fileCache.at(i); + const int attachmentID = addXrefEntry(-1); + xprintf("<<\n"); + if (do_compress) + xprintf("/Filter /FlateDecode\n"); + + const int lenobj = requestObject(); + xprintf("/Length %d 0 R\n", lenobj); + int len = 0; + xprintf(">>\nstream\n"); + len = writeCompressed(attachment.data); + xprintf("\nendstream\n" + "endobj\n"); + addXrefEntry(lenobj); + xprintf("%d\n" + "endobj\n", len); + + attachments.push_back(addXrefEntry(-1)); + xprintf("<<\n" + "/F (%s)", attachment.fileName.toLatin1().constData()); + + xprintf("\n/EF <</F %d 0 R>>\n" + "/Type/Filespec\n" + , attachmentID); + if (!attachment.mimeType.isEmpty()) + xprintf("/Subtype/%s\n", + attachment.mimeType.replace(QLatin1String("/"), + QLatin1String("#2F")).toLatin1().constData()); + xprintf(">>\nendobj\n"); + } + + // names + addXrefEntry(namesRoot); + xprintf("<</Names["); + for (int i = 0; i < size; ++i) { + auto attachment = fileCache.at(i); + printString(attachment.fileName); + xprintf("%d 0 R\n", attachments.at(i)); + } + xprintf("]>>\n" + "endobj\n"); +} void QPdfEnginePrivate::embedFont(QFontSubset *font) { @@ -2031,6 +2126,8 @@ void QPdfEnginePrivate::writeTail() writePage(); writeFonts(); writePageRoot(); + writeAttachmentRoot(); + addXrefEntry(xrefPositions.size(),false); xprintf("xref\n" "0 %d\n" diff --git a/src/gui/painting/qpdf_p.h b/src/gui/painting/qpdf_p.h index 89e549614a..57d70db442 100644 --- a/src/gui/painting/qpdf_p.h +++ b/src/gui/painting/qpdf_p.h @@ -187,6 +187,11 @@ public: void setPdfVersion(PdfVersion version); + void setDocumentXmpMetadata(const QByteArray &xmpMetadata); + QByteArray documentXmpMetadata() const; + + void addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType); + // reimplementations QPaintEngine bool begin(QPaintDevice *pdev) override; bool end() override; @@ -297,9 +302,10 @@ private: int createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha); void writeInfo(); - int writeXmpMetaData(); + int writeXmpDcumentMetaData(); int writeOutputIntent(); void writePageRoot(); + void writeAttachmentRoot(); void writeFonts(); void embedFont(QFontSubset *font); qreal calcUserUnit() const; @@ -324,11 +330,22 @@ private: inline int writeCompressed(const QByteArray &data) { return writeCompressed(data.constData(), data.length()); } int writeCompressed(QIODevice *dev); + struct AttachmentInfo + { + AttachmentInfo (const QString &fileName, const QByteArray &data, const QString &mimeType) + : fileName(fileName), data(data), mimeType(mimeType) {} + QString fileName; + QByteArray data; + QString mimeType; + }; + // various PDF objects - int pageRoot, catalog, info, graphicsState, patternColorSpace; + int pageRoot, embeddedfilesRoot, namesRoot, catalog, info, graphicsState, patternColorSpace; QVector<uint> pages; QHash<qint64, uint> imageCache; QHash<QPair<uint, uint>, uint > alphaCache; + QVector<AttachmentInfo> fileCache; + QByteArray xmpDocumentMetadata; }; QT_END_NAMESPACE diff --git a/src/gui/painting/qpdfwriter.cpp b/src/gui/painting/qpdfwriter.cpp index 35814d146c..4f70fe6ad2 100644 --- a/src/gui/painting/qpdfwriter.cpp +++ b/src/gui/painting/qpdfwriter.cpp @@ -266,6 +266,50 @@ int QPdfWriter::resolution() const return d->engine->resolution(); } +/*! + \since 5.15 + + Sets the document metadata. This metadata is not influenced by the setTitle / setCreator methods, + so is up to the user to keep it consistent. + \a xmpMetadata contains XML formatted metadata to embed into the PDF file. + + \sa documentXmpMetadata() +*/ + +void QPdfWriter::setDocumentXmpMetadata(const QByteArray &xmpMetadata) +{ + Q_D(const QPdfWriter); + d->engine->setDocumentXmpMetadata(xmpMetadata); +} + +/*! + \since 5.15 + + Gets the document metadata, as it was provided with a call to setDocumentXmpMetadata. It will not + return the default metadata. + + \sa setDocumentXmpMetadata() +*/ + +QByteArray QPdfWriter::documentXmpMetadata() const +{ + Q_D(const QPdfWriter); + return d->engine->documentXmpMetadata(); +} + +/*! + \since 5.15 + + Adds \a fileName attachment to the PDF with (optional) \a mimeType + \a data contains the raw file data to embed into the PDF file. +*/ + +void QPdfWriter::addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType) +{ + Q_D(QPdfWriter); + d->engine->addFileAttachment(fileName, data, mimeType); +} + // Defined in QPagedPaintDevice but non-virtual, add QPdfWriter specific doc here #ifdef Q_QDOC /*! diff --git a/src/gui/painting/qpdfwriter.h b/src/gui/painting/qpdfwriter.h index 668081e008..04039a0104 100644 --- a/src/gui/painting/qpdfwriter.h +++ b/src/gui/painting/qpdfwriter.h @@ -75,6 +75,11 @@ public: void setResolution(int resolution); int resolution() const; + void setDocumentXmpMetadata(const QByteArray &xmpMetadata); + QByteArray documentXmpMetadata() const; + + void addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType = QString()); + #ifdef Q_QDOC bool setPageLayout(const QPageLayout &pageLayout); bool setPageSize(const QPageSize &pageSize); |