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>bb10
parent
6227a0d2e2
commit
9d6b4d1142
|
|
@ -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;
|
||||
|
||||
const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR));
|
||||
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 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);
|
||||
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 metaDataDate = timeStr + tzStr;
|
||||
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/*!
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -112,6 +112,29 @@ void tst_QPdfWriter::basics()
|
|||
|
||||
QCOMPARE(writer.pageLayout().fullRect(QPageLayout::Millimeter), QRectF(0, 0, 297, 210));
|
||||
QCOMPARE(writer.pageLayout().paintRect(QPageLayout::Millimeter), QRectF(50, 50, 197, 110));
|
||||
|
||||
QByteArray metadata (
|
||||
"<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>\n"
|
||||
"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n"
|
||||
" <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\"\n"
|
||||
" <rdf:Description xmlns:dc=\"http://purl.org/dc/elements/1.1/\" rdf:about=\"\">\n"
|
||||
" <dc:title>\n"
|
||||
" <rdf:Alt>\n"
|
||||
" <rdf:li xml:lang=\"x-default\">TITLE</rdf:li>\n"
|
||||
" </rdf:Alt>\n"
|
||||
" </dc:title>\n"
|
||||
" </rdf:Description>\n"
|
||||
" <rdf:Description xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\" rdf:about="" xmp:CreatorTool=\"OUR_OWN_XMP\" xmp:CreateDate=\"2019-12-16T00:00:00+01:00\" xmp:ModifyDate=\"2019-12-16T00:00:00+01:00\"/>\n"
|
||||
" <rdf:Description xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\" rdf:about="" pdf:Producer=\"MetaType Info Producer\"/>\n"
|
||||
" <rdf:Description xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\" rdf:about=\"THI IS ALL ABOUT\" pdfaid:part=\"1\" pdfaid:conformance=\"B\"/>\n"
|
||||
" </rdf:RDF>\n"
|
||||
"</x:xmpmeta>\n"
|
||||
"<?xpacket end='w'?>\n"
|
||||
);
|
||||
|
||||
QCOMPARE(writer.documentXmpMetadata(), QByteArray());
|
||||
writer.setDocumentXmpMetadata(metadata);
|
||||
QCOMPARE(writer.documentXmpMetadata(), metadata);
|
||||
}
|
||||
|
||||
// Test the old page metrics methods, see also QPrinter tests for the same.
|
||||
|
|
|
|||
Loading…
Reference in New Issue