PDF: add support for PDF/X-4
PDF/X-4 is a subset of PDF 1.6, aimed at printing fidelity. We can support it with a few refactorings of the existing code in QPdfEngine. * Add the new PDF version to QPagedPaintDevice / QPdfEngine. * Always write the XMP metadata, no matter what's the PDF version used. XMP used to be written only for PDF/A-1b, but it's supported by PDF 1.4 and 1.6 so there's little reason not to write it. * While at it, ditch the search&replace approach for the metadata and use QXmlStreamWriter instead, since it gives us extra flexibility that we need (emit different tags depending on the PDF version in use). * The old code had a bug where the timestamps in the XMP metadata and the document information dictionary could fall out of sync. Just use one datetime object in both places. * Add /ModDate and xmp:ModifyDate (required). * Add the required attributes in the xmpMM namespace. * Add a way to set the document ID to a custom UUID, and use it in the XMP metadata as well as in the /ID in the trailer. Emit the ID unconditionally, as it's been available since PDF 1.1. * Emit the output intent for both PDF/A-1b and /X-4. This will be amended in a future commit to let the user choose the colorspace. The only missing bit is §6.5.4 of the PDF/X-4 spec. This imposes that all symbolic TrueType fonts shall *not* specify an Encoding, and have exactly one encoding in the cmap table. This is basically requiring what §5.5.5 in PDF 1.6 only suggests (page 400). However it seems that we are not embedding a cmap table when extracting a font subset, and that's already violating PDF/A-1b anyhow. This is tracked by QTBUG-125405. This work has been kindly sponsored by the QGIS project (https://qgis.org/). [ChangeLog][QtGui][QPdfWriter] Support for PDF/X-4 has been added. Task-number: QTBUG-125405 Change-Id: Ia81f29b07b819eca5767c9f17692d92a3010f5ad Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io> Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io> (cherry picked from commit 2fbece8a73cb2d2692c78c38e1576c0c9c62fce7) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
5397e0ddd1
commit
a30591603b
@ -309,7 +309,6 @@ if(QT_FEATURE_pdf)
|
|||||||
)
|
)
|
||||||
set(qpdf_resource_files
|
set(qpdf_resource_files
|
||||||
"../3rdparty/icc/sRGB2014.icc"
|
"../3rdparty/icc/sRGB2014.icc"
|
||||||
"painting/qpdfa_metadata.xml"
|
|
||||||
)
|
)
|
||||||
qt_internal_extend_target(Gui
|
qt_internal_extend_target(Gui
|
||||||
ATTRIBUTION_FILE_DIR_PATHS
|
ATTRIBUTION_FILE_DIR_PATHS
|
||||||
|
@ -67,6 +67,9 @@ QPagedPaintDevicePrivate *QPagedPaintDevice::dd()
|
|||||||
|
|
||||||
\value PdfVersion_1_6 A PDF 1.6 compatible document is produced.
|
\value PdfVersion_1_6 A PDF 1.6 compatible document is produced.
|
||||||
This value was added in Qt 5.12.
|
This value was added in Qt 5.12.
|
||||||
|
|
||||||
|
\value [since 6.8] PdfVersion_X4 A PDF/X-4 compatible document is
|
||||||
|
produced.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -25,7 +25,12 @@ public:
|
|||||||
virtual bool newPage() = 0;
|
virtual bool newPage() = 0;
|
||||||
|
|
||||||
// keep in sync with QPdfEngine::PdfVersion!
|
// keep in sync with QPdfEngine::PdfVersion!
|
||||||
enum PdfVersion { PdfVersion_1_4, PdfVersion_A1b, PdfVersion_1_6 };
|
enum PdfVersion {
|
||||||
|
PdfVersion_1_4,
|
||||||
|
PdfVersion_A1b,
|
||||||
|
PdfVersion_1_6,
|
||||||
|
PdfVersion_X4,
|
||||||
|
};
|
||||||
|
|
||||||
virtual bool setPageLayout(const QPageLayout &pageLayout);
|
virtual bool setPageLayout(const QPageLayout &pageLayout);
|
||||||
virtual bool setPageSize(const QPageSize &pageSize);
|
virtual bool setPageSize(const QPageSize &pageSize);
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include <qtemporaryfile.h>
|
#include <qtemporaryfile.h>
|
||||||
#include <qtimezone.h>
|
#include <qtimezone.h>
|
||||||
#include <quuid.h>
|
#include <quuid.h>
|
||||||
|
#include <qxmlstream.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
@ -1043,6 +1044,12 @@ void QPdfEngine::drawHyperlink(const QRectF &r, const QUrl &url)
|
|||||||
{
|
{
|
||||||
Q_D(QPdfEngine);
|
Q_D(QPdfEngine);
|
||||||
|
|
||||||
|
// PDF/X-4 (§ 6.17) does not allow annotations that don't lie
|
||||||
|
// outside the BleedBox/TrimBox, so don't emit an hyperlink
|
||||||
|
// annotation at all.
|
||||||
|
if (d->pdfVersion == QPdfEngine::Version_X4)
|
||||||
|
return;
|
||||||
|
|
||||||
const uint annot = d->addXrefEntry(-1);
|
const uint annot = d->addXrefEntry(-1);
|
||||||
const QByteArray urlascii = url.toEncoded();
|
const QByteArray urlascii = url.toEncoded();
|
||||||
int len = urlascii.size();
|
int len = urlascii.size();
|
||||||
@ -1556,6 +1563,7 @@ void QPdfEnginePrivate::writeHeader()
|
|||||||
"1.4", // Version_1_4
|
"1.4", // Version_1_4
|
||||||
"1.4", // Version_A1b
|
"1.4", // Version_A1b
|
||||||
"1.6", // Version_1_6
|
"1.6", // Version_1_6
|
||||||
|
"1.6", // Version_X4
|
||||||
};
|
};
|
||||||
static const size_t numMappings = sizeof mapping / sizeof *mapping;
|
static const size_t numMappings = sizeof mapping / sizeof *mapping;
|
||||||
const char *verStr = mapping[size_t(pdfVersion) < numMappings ? pdfVersion : 0];
|
const char *verStr = mapping[size_t(pdfVersion) < numMappings ? pdfVersion : 0];
|
||||||
@ -1563,17 +1571,28 @@ void QPdfEnginePrivate::writeHeader()
|
|||||||
xprintf("%%PDF-%s\n", verStr);
|
xprintf("%%PDF-%s\n", verStr);
|
||||||
xprintf("%%\303\242\303\243\n");
|
xprintf("%%\303\242\303\243\n");
|
||||||
|
|
||||||
writeInfo();
|
#if QT_CONFIG(timezone)
|
||||||
|
const QDateTime now = QDateTime::currentDateTime(QTimeZone::systemTimeZone());
|
||||||
|
#else
|
||||||
|
const QDateTime now = QDateTime::currentDateTimeUtc();
|
||||||
|
#endif
|
||||||
|
|
||||||
int metaDataObj = -1;
|
writeInfo(now);
|
||||||
int outputIntentObj = -1;
|
|
||||||
if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) {
|
const int metaDataObj = writeXmpDocumentMetaData(now);
|
||||||
metaDataObj = writeXmpDocumentMetaData();
|
const int outputIntentObj = [&]() {
|
||||||
}
|
switch (pdfVersion) {
|
||||||
if (pdfVersion == QPdfEngine::Version_A1b) {
|
case QPdfEngine::Version_1_4:
|
||||||
outputIntentObj = writeOutputIntent();
|
case QPdfEngine::Version_1_6:
|
||||||
|
break;
|
||||||
|
case QPdfEngine::Version_A1b:
|
||||||
|
case QPdfEngine::Version_X4:
|
||||||
|
return writeOutputIntent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}();
|
||||||
|
|
||||||
catalog = addXrefEntry(-1);
|
catalog = addXrefEntry(-1);
|
||||||
pageRoot = requestObject();
|
pageRoot = requestObject();
|
||||||
namesRoot = requestObject();
|
namesRoot = requestObject();
|
||||||
@ -1587,10 +1606,9 @@ void QPdfEnginePrivate::writeHeader()
|
|||||||
<< "/Pages " << pageRoot << "0 R\n"
|
<< "/Pages " << pageRoot << "0 R\n"
|
||||||
<< "/Names " << namesRoot << "0 R\n";
|
<< "/Names " << namesRoot << "0 R\n";
|
||||||
|
|
||||||
if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty())
|
|
||||||
s << "/Metadata " << metaDataObj << "0 R\n";
|
s << "/Metadata " << metaDataObj << "0 R\n";
|
||||||
|
|
||||||
if (pdfVersion == QPdfEngine::Version_A1b)
|
if (outputIntentObj >= 0)
|
||||||
s << "/OutputIntents [" << outputIntentObj << "0 R]\n";
|
s << "/OutputIntents [" << outputIntentObj << "0 R]\n";
|
||||||
|
|
||||||
s << ">>\n"
|
s << ">>\n"
|
||||||
@ -1716,64 +1734,171 @@ void QPdfEnginePrivate::writeColor(ColorDomain domain, const QColor &color)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QPdfEnginePrivate::writeInfo()
|
void QPdfEnginePrivate::writeInfo(const QDateTime &date)
|
||||||
{
|
{
|
||||||
info = addXrefEntry(-1);
|
info = addXrefEntry(-1);
|
||||||
xprintf("<<\n/Title ");
|
write("<<\n/Title ");
|
||||||
printString(title);
|
printString(title);
|
||||||
xprintf("\n/Creator ");
|
write("\n/Creator ");
|
||||||
printString(creator);
|
printString(creator);
|
||||||
xprintf("\n/Producer ");
|
write("\n/Producer ");
|
||||||
printString(QString::fromLatin1("Qt " QT_VERSION_STR));
|
printString(QString::fromLatin1("Qt " QT_VERSION_STR));
|
||||||
QDateTime now = QDateTime::currentDateTime();
|
|
||||||
QTime t = now.time();
|
const QTime t = date.time();
|
||||||
QDate d = now.date();
|
const QDate d = date.date();
|
||||||
xprintf("\n/CreationDate (D:%d%02d%02d%02d%02d%02d",
|
// (D:YYYYMMDDHHmmSSOHH'mm')
|
||||||
d.year(),
|
constexpr size_t formattedDateSize = 26;
|
||||||
|
char formattedDate[formattedDateSize];
|
||||||
|
const int year = qBound(0, d.year(), 9999); // ASN.1, max 4 digits
|
||||||
|
auto printedSize = qsnprintf(formattedDate,
|
||||||
|
formattedDateSize,
|
||||||
|
"(D:%04d%02d%02d%02d%02d%02d",
|
||||||
|
year,
|
||||||
d.month(),
|
d.month(),
|
||||||
d.day(),
|
d.day(),
|
||||||
t.hour(),
|
t.hour(),
|
||||||
t.minute(),
|
t.minute(),
|
||||||
t.second());
|
t.second());
|
||||||
int offset = now.offsetFromUtc();
|
const int offset = date.offsetFromUtc();
|
||||||
int hours = (offset / 60) / 60;
|
const int hours = (offset / 60) / 60;
|
||||||
int mins = (offset / 60) % 60;
|
const int mins = (offset / 60) % 60;
|
||||||
if (offset < 0)
|
if (offset < 0) {
|
||||||
xprintf("-%02d'%02d')\n", -hours, -mins);
|
qsnprintf(formattedDate + printedSize,
|
||||||
else if (offset > 0)
|
formattedDateSize - printedSize,
|
||||||
xprintf("+%02d'%02d')\n", hours , mins);
|
"-%02d'%02d')", -hours, -mins);
|
||||||
else
|
} else if (offset > 0) {
|
||||||
xprintf("Z)\n");
|
qsnprintf(formattedDate + printedSize,
|
||||||
xprintf("/Trapped /False\n");
|
formattedDateSize - printedSize,
|
||||||
xprintf(">>\n"
|
"+%02d'%02d')", hours, mins);
|
||||||
|
} else {
|
||||||
|
qsnprintf(formattedDate + printedSize,
|
||||||
|
formattedDateSize - printedSize,
|
||||||
|
"Z)");
|
||||||
|
}
|
||||||
|
|
||||||
|
write("\n/CreationDate ");
|
||||||
|
write(formattedDate);
|
||||||
|
write("\n/ModDate ");
|
||||||
|
write(formattedDate);
|
||||||
|
|
||||||
|
write("\n/Trapped /False\n"
|
||||||
|
"2\n"
|
||||||
"endobj\n");
|
"endobj\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
int QPdfEnginePrivate::writeXmpDocumentMetaData()
|
int QPdfEnginePrivate::writeXmpDocumentMetaData(const QDateTime &date)
|
||||||
{
|
{
|
||||||
const int metaDataObj = addXrefEntry(-1);
|
const int metaDataObj = addXrefEntry(-1);
|
||||||
QByteArray metaDataContent;
|
QByteArray metaDataContent;
|
||||||
|
|
||||||
if (xmpDocumentMetadata.isEmpty()) {
|
if (!xmpDocumentMetadata.isEmpty()) {
|
||||||
const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR));
|
|
||||||
|
|
||||||
#if QT_CONFIG(timezone)
|
|
||||||
const QDateTime now = QDateTime::currentDateTime(QTimeZone::systemTimeZone());
|
|
||||||
#else
|
|
||||||
const QDateTime now = QDateTime::currentDateTimeUtc();
|
|
||||||
#endif
|
|
||||||
const QString metaDataDate = now.toString(Qt::ISODate);
|
|
||||||
|
|
||||||
QFile metaDataFile(":/qpdf/qpdfa_metadata.xml"_L1);
|
|
||||||
bool ok = metaDataFile.open(QIODevice::ReadOnly);
|
|
||||||
Q_ASSERT(ok);
|
|
||||||
metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(),
|
|
||||||
title.toHtmlEscaped(),
|
|
||||||
creator.toHtmlEscaped(),
|
|
||||||
metaDataDate).toUtf8();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
metaDataContent = xmpDocumentMetadata;
|
metaDataContent = xmpDocumentMetadata;
|
||||||
|
} else {
|
||||||
|
const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR));
|
||||||
|
const QString metaDataDate = date.toString(Qt::ISODate);
|
||||||
|
|
||||||
|
using namespace Qt::Literals;
|
||||||
|
constexpr QLatin1String xmlNS = "http://www.w3.org/XML/1998/namespace"_L1;
|
||||||
|
|
||||||
|
constexpr QLatin1String adobeNS = "adobe:ns:meta/"_L1;
|
||||||
|
constexpr QLatin1String rdfNS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"_L1;
|
||||||
|
constexpr QLatin1String dcNS = "http://purl.org/dc/elements/1.1/"_L1;
|
||||||
|
constexpr QLatin1String xmpNS = "http://ns.adobe.com/xap/1.0/"_L1;
|
||||||
|
constexpr QLatin1String xmpMMNS = "http://ns.adobe.com/xap/1.0/mm/"_L1;
|
||||||
|
constexpr QLatin1String pdfNS = "http://ns.adobe.com/pdf/1.3/"_L1;
|
||||||
|
constexpr QLatin1String pdfaidNS = "http://www.aiim.org/pdfa/ns/id/"_L1;
|
||||||
|
constexpr QLatin1String pdfxidNS = "http://www.npes.org/pdfx/ns/id/"_L1;
|
||||||
|
|
||||||
|
QBuffer output(&metaDataContent);
|
||||||
|
output.open(QIODevice::WriteOnly);
|
||||||
|
output.write("<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>");
|
||||||
|
|
||||||
|
QXmlStreamWriter w(&output);
|
||||||
|
w.setAutoFormatting(true);
|
||||||
|
w.writeNamespace(adobeNS, "x");
|
||||||
|
w.writeNamespace(rdfNS, "rdf");
|
||||||
|
w.writeNamespace(dcNS, "dc");
|
||||||
|
w.writeNamespace(xmpNS, "xmp");
|
||||||
|
w.writeNamespace(xmpMMNS, "xmpMM");
|
||||||
|
w.writeNamespace(pdfNS, "pdf");
|
||||||
|
w.writeNamespace(pdfaidNS, "pdfaid");
|
||||||
|
w.writeNamespace(pdfxidNS, "pdfxid");
|
||||||
|
|
||||||
|
w.writeStartElement(adobeNS, "xmpmeta");
|
||||||
|
w.writeStartElement(rdfNS, "RDF");
|
||||||
|
|
||||||
|
/*
|
||||||
|
XMP says: "The recommended approach is to have either a
|
||||||
|
single rdf:Description element containing all XMP
|
||||||
|
properties or a separate rdf:Description element for each
|
||||||
|
XMP property namespace."
|
||||||
|
We do the the latter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// DC
|
||||||
|
w.writeStartElement(rdfNS, "Description");
|
||||||
|
w.writeAttribute(rdfNS, "about", "");
|
||||||
|
w.writeStartElement(dcNS, "title");
|
||||||
|
w.writeStartElement(rdfNS, "Alt");
|
||||||
|
w.writeStartElement(rdfNS, "li");
|
||||||
|
w.writeAttribute(xmlNS, "lang", "x-default");
|
||||||
|
w.writeCharacters(title);
|
||||||
|
w.writeEndElement();
|
||||||
|
w.writeEndElement();
|
||||||
|
w.writeEndElement();
|
||||||
|
w.writeEndElement();
|
||||||
|
|
||||||
|
// PDF
|
||||||
|
w.writeStartElement(rdfNS, "Description");
|
||||||
|
w.writeAttribute(rdfNS, "about", "");
|
||||||
|
w.writeAttribute(pdfNS, "Producer", producer);
|
||||||
|
w.writeAttribute(pdfNS, "Trapped", "false");
|
||||||
|
w.writeEndElement();
|
||||||
|
|
||||||
|
// XMP
|
||||||
|
w.writeStartElement(rdfNS, "Description");
|
||||||
|
w.writeAttribute(rdfNS, "about", "");
|
||||||
|
w.writeAttribute(xmpNS, "CreatorTool", creator);
|
||||||
|
w.writeAttribute(xmpNS, "CreateDate", metaDataDate);
|
||||||
|
w.writeAttribute(xmpNS, "ModifyDate", metaDataDate);
|
||||||
|
w.writeAttribute(xmpNS, "MetadataDate", metaDataDate);
|
||||||
|
w.writeEndElement();
|
||||||
|
|
||||||
|
// XMPMM
|
||||||
|
w.writeStartElement(rdfNS, "Description");
|
||||||
|
w.writeAttribute(rdfNS, "about", "");
|
||||||
|
w.writeAttribute(xmpMMNS, "DocumentID", "uuid:"_L1 + documentId.toString(QUuid::WithoutBraces));
|
||||||
|
w.writeAttribute(xmpMMNS, "VersionID", "1");
|
||||||
|
w.writeAttribute(xmpMMNS, "RenditionClass", "default");
|
||||||
|
w.writeEndElement();
|
||||||
|
|
||||||
|
// Version-specific
|
||||||
|
switch (pdfVersion) {
|
||||||
|
case QPdfEngine::Version_1_4:
|
||||||
|
break;
|
||||||
|
case QPdfEngine::Version_A1b:
|
||||||
|
w.writeStartElement(rdfNS, "Description");
|
||||||
|
w.writeAttribute(rdfNS, "about", "");
|
||||||
|
w.writeAttribute(pdfaidNS, "part", "1");
|
||||||
|
w.writeAttribute(pdfaidNS, "conformance", "B");
|
||||||
|
w.writeEndElement();
|
||||||
|
break;
|
||||||
|
case QPdfEngine::Version_1_6:
|
||||||
|
break;
|
||||||
|
case QPdfEngine::Version_X4:
|
||||||
|
w.writeStartElement(rdfNS, "Description");
|
||||||
|
w.writeAttribute(rdfNS, "about", "");
|
||||||
|
w.writeAttribute(pdfxidNS, "GTS_PDFXVersion", "PDF/X-4");
|
||||||
|
w.writeEndElement();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
w.writeEndElement(); // </RDF>
|
||||||
|
w.writeEndElement(); // </xmpmeta>
|
||||||
|
|
||||||
|
w.writeEndDocument();
|
||||||
|
output.write("<?xpacket end='w'?>");
|
||||||
|
}
|
||||||
|
|
||||||
xprintf("<<\n"
|
xprintf("<<\n"
|
||||||
"/Type /Metadata /Subtype /XML\n"
|
"/Type /Metadata /Subtype /XML\n"
|
||||||
@ -1821,7 +1946,20 @@ int QPdfEnginePrivate::writeOutputIntent()
|
|||||||
{
|
{
|
||||||
xprintf("<<\n");
|
xprintf("<<\n");
|
||||||
xprintf("/Type /OutputIntent\n");
|
xprintf("/Type /OutputIntent\n");
|
||||||
|
|
||||||
|
switch (pdfVersion) {
|
||||||
|
case QPdfEngine::Version_1_4:
|
||||||
|
case QPdfEngine::Version_1_6:
|
||||||
|
Q_UNREACHABLE(); // no output intent for these versions
|
||||||
|
break;
|
||||||
|
case QPdfEngine::Version_A1b:
|
||||||
xprintf("/S/GTS_PDFA1\n");
|
xprintf("/S/GTS_PDFA1\n");
|
||||||
|
break;
|
||||||
|
case QPdfEngine::Version_X4:
|
||||||
|
xprintf("/S/GTS_PDFX\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
xprintf("/OutputConditionIdentifier (sRGB_IEC61966-2-1_black_scaled)\n");
|
xprintf("/OutputConditionIdentifier (sRGB_IEC61966-2-1_black_scaled)\n");
|
||||||
xprintf("/DestOutputProfile %d 0 R\n", colorProfile);
|
xprintf("/DestOutputProfile %d 0 R\n", colorProfile);
|
||||||
xprintf("/Info(sRGB IEC61966 v2.1 with black scaling)\n");
|
xprintf("/Info(sRGB IEC61966 v2.1 with black scaling)\n");
|
||||||
@ -2242,11 +2380,8 @@ void QPdfEnginePrivate::writeTail()
|
|||||||
<< "/Info " << info << "0 R\n"
|
<< "/Info " << info << "0 R\n"
|
||||||
<< "/Root " << catalog << "0 R\n";
|
<< "/Root " << catalog << "0 R\n";
|
||||||
|
|
||||||
if (pdfVersion == QPdfEngine::Version_A1b) {
|
const QByteArray id = documentId.toString(QUuid::WithoutBraces).toUtf8().toHex();
|
||||||
const QString uniqueId = QUuid::createUuid().toString();
|
s << "/ID [ <" << id << "> <" << id << "> ]\n";
|
||||||
const QByteArray fileIdentifier = QCryptographicHash::hash(uniqueId.toLatin1(), QCryptographicHash::Md5).toHex();
|
|
||||||
s << "/ID [ <" << fileIdentifier << "> <" << fileIdentifier << "> ]\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
s << ">>\n"
|
s << ">>\n"
|
||||||
<< "startxref\n" << xrefPositions.constLast() << "\n"
|
<< "startxref\n" << xrefPositions.constLast() << "\n"
|
||||||
@ -3198,7 +3333,11 @@ void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti)
|
|||||||
|
|
||||||
const bool isLink = ti.charFormat.hasProperty(QTextFormat::AnchorHref);
|
const bool isLink = ti.charFormat.hasProperty(QTextFormat::AnchorHref);
|
||||||
const bool isAnchor = ti.charFormat.hasProperty(QTextFormat::AnchorName);
|
const bool isAnchor = ti.charFormat.hasProperty(QTextFormat::AnchorName);
|
||||||
if (isLink || isAnchor) {
|
// PDF/X-4 (§ 6.17) does not allow annotations that don't lie
|
||||||
|
// outside the BleedBox/TrimBox, so don't emit an hyperlink
|
||||||
|
// annotation at all.
|
||||||
|
const bool isX4 = pdfVersion == QPdfEngine::Version_X4;
|
||||||
|
if ((isLink && !isX4) || isAnchor) {
|
||||||
qreal size = ti.fontEngine->fontDef.pixelSize;
|
qreal size = ti.fontEngine->fontDef.pixelSize;
|
||||||
int synthesized = ti.fontEngine->synthesized();
|
int synthesized = ti.fontEngine->synthesized();
|
||||||
qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
|
qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "QtCore/qlist.h"
|
#include "QtCore/qlist.h"
|
||||||
#include "QtCore/qstring.h"
|
#include "QtCore/qstring.h"
|
||||||
|
#include "QtCore/quuid.h"
|
||||||
#include "private/qfontengine_p.h"
|
#include "private/qfontengine_p.h"
|
||||||
#include "private/qfontsubset_p.h"
|
#include "private/qfontsubset_p.h"
|
||||||
#include "private/qpaintengine_p.h"
|
#include "private/qpaintengine_p.h"
|
||||||
@ -134,7 +135,8 @@ public:
|
|||||||
{
|
{
|
||||||
Version_1_4,
|
Version_1_4,
|
||||||
Version_A1b,
|
Version_A1b,
|
||||||
Version_1_6
|
Version_1_6,
|
||||||
|
Version_X4,
|
||||||
};
|
};
|
||||||
|
|
||||||
QPdfEngine();
|
QPdfEngine();
|
||||||
@ -262,6 +264,7 @@ public:
|
|||||||
QString outputFileName;
|
QString outputFileName;
|
||||||
QString title;
|
QString title;
|
||||||
QString creator;
|
QString creator;
|
||||||
|
QUuid documentId = QUuid::createUuid();
|
||||||
bool embedFonts;
|
bool embedFonts;
|
||||||
int resolution;
|
int resolution;
|
||||||
|
|
||||||
@ -289,8 +292,8 @@ private:
|
|||||||
|
|
||||||
QPdfEngine::ColorModel colorModelForColor(const QColor &color) const;
|
QPdfEngine::ColorModel colorModelForColor(const QColor &color) const;
|
||||||
void writeColor(ColorDomain domain, const QColor &color);
|
void writeColor(ColorDomain domain, const QColor &color);
|
||||||
void writeInfo();
|
void writeInfo(const QDateTime &date);
|
||||||
int writeXmpDocumentMetaData();
|
int writeXmpDocumentMetaData(const QDateTime &date);
|
||||||
int writeOutputIntent();
|
int writeOutputIntent();
|
||||||
void writePageRoot();
|
void writePageRoot();
|
||||||
void writeDestsRoot();
|
void writeDestsRoot();
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
|
|
||||||
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
|
||||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about="">
|
|
||||||
<dc:title>
|
|
||||||
<rdf:Alt>
|
|
||||||
<rdf:li xml:lang="x-default">%2</rdf:li>
|
|
||||||
</rdf:Alt>
|
|
||||||
</dc:title>
|
|
||||||
</rdf:Description>
|
|
||||||
<rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about="" xmp:CreatorTool="%3" xmp:CreateDate="%4" xmp:ModifyDate="%4"/>
|
|
||||||
<rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about="" pdf:Producer="%1"/>
|
|
||||||
<rdf:Description xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/" rdf:about="" pdfaid:part="1" pdfaid:conformance="B"/>
|
|
||||||
</rdf:RDF>
|
|
||||||
</x:xmpmeta>
|
|
||||||
<?xpacket end='w'?>
|
|
@ -188,6 +188,27 @@ void QPdfWriter::setCreator(const QString &creator)
|
|||||||
d->engine->d_func()->creator = creator;
|
d->engine->d_func()->creator = creator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 6.8
|
||||||
|
Returns the ID of the document. By default, the ID is a
|
||||||
|
randomly generated UUID.
|
||||||
|
*/
|
||||||
|
QUuid QPdfWriter::documentId() const
|
||||||
|
{
|
||||||
|
Q_D(const QPdfWriter);
|
||||||
|
return d->engine->d_func()->documentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\since 6.8
|
||||||
|
Sets the ID of the document to \a documentId.
|
||||||
|
*/
|
||||||
|
void QPdfWriter::setDocumentId(const QUuid &documentId)
|
||||||
|
{
|
||||||
|
Q_D(QPdfWriter);
|
||||||
|
d->engine->d_func()->documentId = documentId;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\reimp
|
\reimp
|
||||||
*/
|
*/
|
||||||
|
@ -16,6 +16,7 @@ QT_BEGIN_NAMESPACE
|
|||||||
|
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
class QPdfWriterPrivate;
|
class QPdfWriterPrivate;
|
||||||
|
class QUuid;
|
||||||
|
|
||||||
class Q_GUI_EXPORT QPdfWriter : public QObject, public QPagedPaintDevice
|
class Q_GUI_EXPORT QPdfWriter : public QObject, public QPagedPaintDevice
|
||||||
{
|
{
|
||||||
@ -34,6 +35,9 @@ public:
|
|||||||
QString creator() const;
|
QString creator() const;
|
||||||
void setCreator(const QString &creator);
|
void setCreator(const QString &creator);
|
||||||
|
|
||||||
|
QUuid documentId() const;
|
||||||
|
void setDocumentId(const QUuid &documentId);
|
||||||
|
|
||||||
bool newPage() override;
|
bool newPage() override;
|
||||||
|
|
||||||
void setResolution(int resolution);
|
void setResolution(int resolution);
|
||||||
|
@ -548,9 +548,13 @@ void tst_QPrinter::taskQTBUG4497_reusePrinterOnDifferentFiles()
|
|||||||
QByteArray file1Line = file1.readLine();
|
QByteArray file1Line = file1.readLine();
|
||||||
QByteArray file2Line = file2.readLine();
|
QByteArray file2Line = file2.readLine();
|
||||||
|
|
||||||
if (!file1Line.contains("CreationDate"))
|
if (!file1Line.startsWith("/CreationDate ") &&
|
||||||
|
!file1Line.startsWith("/ModDate ") &&
|
||||||
|
!file1Line.startsWith("/ID "))
|
||||||
|
{
|
||||||
QCOMPARE(file1Line, file2Line);
|
QCOMPARE(file1Line, file2Line);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QVERIFY(file1.atEnd());
|
QVERIFY(file1.atEnd());
|
||||||
QVERIFY(file2.atEnd());
|
QVERIFY(file2.atEnd());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user