PDF: add a way to customize the output intent

PDF/A-1 and PDF/X-4 require the use of an output intent. Insofar this
has been hardcoded to be sRGB. Instead, expose the relevant setting so
that the user can choose another colorspace, and set the metadata
about the intent.

This work has been kindly sponsored by the QGIS project
(https://qgis.org/).

[ChangeLog][QtGui][QPdfOutputIntent] New class.

Change-Id: Ib3f0620477ddcc8b294a7039c120e89cc318f513
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
(cherry picked from commit e8fcdf9bb6318e75d64179a6682481154cdef14f)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Giuseppe D'Angelo 2024-05-23 09:53:23 +02:00 committed by Qt Cherry-pick Bot
parent e309f0b013
commit 159d63edb5
7 changed files with 336 additions and 19 deletions

View File

@ -206,6 +206,7 @@ qt_internal_add_module(Gui
painting/qpathclipper.cpp painting/qpathclipper_p.h
painting/qpathsimplifier.cpp painting/qpathsimplifier_p.h
painting/qpdf.cpp painting/qpdf_p.h
painting/qpdfoutputintent.cpp painting/qpdfoutputintent.h
painting/qpdfwriter.cpp painting/qpdfwriter.h
painting/qpen.cpp painting/qpen.h painting/qpen_p.h
painting/qpixellayout.cpp painting/qpixellayout_p.h

View File

@ -1914,20 +1914,35 @@ int QPdfEnginePrivate::writeXmpDocumentMetaData(const QDateTime &date)
int QPdfEnginePrivate::writeOutputIntent()
{
const int colorProfile = addXrefEntry(-1);
const int colorProfileEntry = addXrefEntry(-1);
{
QFile colorProfileFile(":/qpdf/sRGB2014.icc"_L1);
bool ok = colorProfileFile.open(QIODevice::ReadOnly);
Q_ASSERT(ok);
const QByteArray colorProfileData = colorProfileFile.readAll();
const QColorSpace profile = outputIntent.outputProfile();
const QByteArray colorProfileData = profile.iccProfile();
QByteArray data;
QPdf::ByteStream s(&data);
int length_object = requestObject();
s << "<<\n";
s << "/N 3\n";
s << "/Alternate /DeviceRGB\n";
switch (profile.colorModel()) {
case QColorSpace::ColorModel::Undefined:
qWarning("QPdfEngine: undefined color model in the output intent profile, assuming RGB");
[[fallthrough]];
case QColorSpace::ColorModel::Rgb:
s << "/N 3\n";
s << "/Alternate /DeviceRGB\n";
break;
case QColorSpace::ColorModel::Gray:
s << "/N 1\n";
s << "/Alternate /DeviceGray\n";
break;
case QColorSpace::ColorModel::Cmyk:
s << "/N 4\n";
s << "/Alternate /DeviceCMYK\n";
break;
}
s << "/Length " << length_object << "0 R\n";
if (do_compress)
s << "/Filter /FlateDecode\n";
@ -1942,10 +1957,10 @@ int QPdfEnginePrivate::writeOutputIntent()
"endobj\n", len);
}
const int outputIntent = addXrefEntry(-1);
const int outputIntentEntry = addXrefEntry(-1);
{
xprintf("<<\n");
xprintf("/Type /OutputIntent\n");
write("<<\n");
write("/Type /OutputIntent\n");
switch (pdfVersion) {
case QPdfEngine::Version_1_4:
@ -1953,22 +1968,37 @@ int QPdfEnginePrivate::writeOutputIntent()
Q_UNREACHABLE(); // no output intent for these versions
break;
case QPdfEngine::Version_A1b:
xprintf("/S/GTS_PDFA1\n");
write("/S/GTS_PDFA1\n");
break;
case QPdfEngine::Version_X4:
xprintf("/S/GTS_PDFX\n");
write("/S/GTS_PDFX\n");
break;
}
xprintf("/OutputConditionIdentifier (sRGB_IEC61966-2-1_black_scaled)\n");
xprintf("/DestOutputProfile %d 0 R\n", colorProfile);
xprintf("/Info(sRGB IEC61966 v2.1 with black scaling)\n");
xprintf("/RegistryName(http://www.color.org)\n");
xprintf(">>\n");
xprintf("endobj\n");
xprintf("/DestOutputProfile %d 0 R\n", colorProfileEntry);
write("/OutputConditionIdentifier ");
printString(outputIntent.outputConditionIdentifier());
write("\n");
write("/Info ");
printString(outputIntent.outputCondition());
write("\n");
write("/OutputCondition ");
printString(outputIntent.outputCondition());
write("\n");
if (const auto registryName = outputIntent.registryName(); !registryName.isEmpty()) {
write("/RegistryName ");
printString(registryName.toString());
write("\n");
}
write(">>\n");
write("endobj\n");
}
return outputIntent;
return outputIntentEntry;
}
void QPdfEnginePrivate::writePageRoot()

View File

@ -27,6 +27,7 @@
#include "private/qpaintengine_p.h"
#include "private/qstroker_p.h"
#include "qpagelayout.h"
#include "qpdfoutputintent.h"
QT_BEGIN_NAMESPACE
@ -267,6 +268,7 @@ public:
QUuid documentId = QUuid::createUuid();
bool embedFonts;
int resolution;
QPdfOutputIntent outputIntent;
// Page layout: size, orientation and margins
QPageLayout m_pageLayout;

View File

@ -0,0 +1,201 @@
// Copyright (C) 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qpdfoutputintent.h"
#ifndef QT_NO_PDF
#include <QtCore/qfile.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qstring.h>
#include <QtCore/qurl.h>
#include <QtGui/qcolorspace.h>
QT_BEGIN_NAMESPACE
class QPdfOutputIntentPrivate : public QSharedData
{
public:
QPdfOutputIntentPrivate()
{
QFile colorProfileFile(QStringLiteral(":/qpdf/sRGB2014.icc"));
bool ok = colorProfileFile.open(QIODevice::ReadOnly);
Q_ASSERT(ok);
outputProfile = QColorSpace::fromIccProfile(colorProfileFile.readAll());
}
QString outputConditionIdentifier = QStringLiteral("sRGB_IEC61966-2-1_black_scaled");
QString outputCondition = QStringLiteral("sRGB IEC61966 v2.1 with black scaling");
QUrl registryName = QStringLiteral("http://www.color.org");
QColorSpace outputProfile;
};
/*!
\class QPdfOutputIntent
\inmodule QtGui
\ingroup painting
\since 6.8
The QPdfOutputIntent class contains metadata that characterize
the printing condition for which painting data has been prepared
when generating a PDF file.
\sa QPdfWriter
*/
/*!
Constructs a new PDF output intent.
*/
QPdfOutputIntent::QPdfOutputIntent()
: d(new QPdfOutputIntentPrivate)
{}
/*!
Constructs a copy of the output intent \a other.
*/
QPdfOutputIntent::QPdfOutputIntent(const QPdfOutputIntent &other) noexcept = default;
/*!
\fn QPdfOutputIntent::QPdfOutputIntent(QPdfOutputIntent &&other) noexcept
Constructs a QPdfOutputIntent object by moving from \a other.
*/
/*!
Assigns the output intent \a other over this intent.
*/
QPdfOutputIntent &QPdfOutputIntent::operator=(const QPdfOutputIntent &other) noexcept = default;
/*!
\fn QPdfOutputIntent &QPdfOutputIntent::operator=(QPdfOutputIntent &&other) noexcept
Move-assigns the output intent \a other over this intent.
*/
/*!
Destroys this output intent.
*/
QPdfOutputIntent::~QPdfOutputIntent() = default;
/*!
\fn void QPdfOutputIntent::swap(QPdfOutputIntent &other) noexcept
Swaps the output intent \a other with this output intent. This
operation is very fast and never fails.
*/
/*!
Returns the identifier of the output condition.
If a registry name is provided, then this identifier should should
match the reference name of an entry in that registry.
The default identifier is \c{sRGB_IEC61966-2-1_black_scaled}.
\sa setOutputConditionIdentifier()
*/
QString QPdfOutputIntent::outputConditionIdentifier() const
{
return d->outputConditionIdentifier;
}
/*!
Sets the identifier of the output condition to \a identifier.
If a registry name is provided, then this identifier should should
match the reference name of an entry in that registry.
\sa setOutputCondition(), setRegistryName()
*/
void QPdfOutputIntent::setOutputConditionIdentifier(const QString &identifier)
{
d.detach();
d->outputConditionIdentifier = identifier;
}
/*!
Returns the human-readable output condition.
This is a string that concisely identifies the characterized
printing condition in a form that will be meaningful to a
human operator.
The default output condition is
\c{sRGB IEC61966 v2.1 with black scaling}.
\sa setOutputCondition()
*/
QString QPdfOutputIntent::outputCondition() const
{
return d->outputCondition;
}
/*!
Sets the human-readable output condition to \a condition.
\sa setOutputConditionIdentifier(), setRegistryName()
*/
void QPdfOutputIntent::setOutputCondition(const QString &condition)
{
d.detach();
d->outputCondition = condition;
}
/*!
Returns the URL of a characterization registry for the intended
printing condition.
The default registry is \c{http://www.color.org}.
\sa setOutputConditionIdentifier()
*/
QUrl QPdfOutputIntent::registryName() const
{
return d->registryName;
}
/*!
Sets the URL of the characterization registry to \a name.
\sa setOutputConditionIdentifier()
*/
void QPdfOutputIntent::setRegistryName(const QUrl &name)
{
d.detach();
d->registryName = name;
}
/*!
Returns the output device profile.
The default profile is the sRGB v2 profile available
from the
\l{https://www.color.org/srgbprofiles.xalter#v2}{International
Color Consortium}.
*/
QColorSpace QPdfOutputIntent::outputProfile() const
{
return d->outputProfile;
}
/*!
Sets the output device profile to \a profile.
\note PDF/X-4 requires all the color specifications in the
document to match the same colorspace of \a profile. It is
the application's responsibility to ensure this is the case.
\sa QColorSpace::fromIccProfile, QPdfWriter::setColorModel
*/
void QPdfOutputIntent::setOutputProfile(const QColorSpace &profile)
{
d.detach();
d->outputProfile = profile;
}
QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QPdfOutputIntentPrivate)
QT_END_NAMESPACE
#endif // QT_NO_PDF

View File

@ -0,0 +1,56 @@
// Copyright (C) 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QPDFOUTPUTINTENT_H
#define QPDFOUTPUTINTENT_H
#include <QtGui/qtguiglobal.h>
#ifndef QT_NO_PDF
#include <QtCore/qshareddata.h>
QT_BEGIN_NAMESPACE
class QString;
class QUrl;
class QColorSpace;
class QPdfOutputIntentPrivate;
QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QPdfOutputIntentPrivate, Q_GUI_EXPORT)
class Q_GUI_EXPORT QPdfOutputIntent
{
public:
QPdfOutputIntent();
QPdfOutputIntent(const QPdfOutputIntent &other) noexcept;
QPdfOutputIntent(QPdfOutputIntent &&other) noexcept = default;
QPdfOutputIntent &operator=(const QPdfOutputIntent &other) noexcept;
QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QPdfOutputIntent)
~QPdfOutputIntent();
void swap(QPdfOutputIntent &other) noexcept { d.swap(other.d); }
QString outputConditionIdentifier() const;
void setOutputConditionIdentifier(const QString &identifier);
QString outputCondition() const;
void setOutputCondition(const QString &condition);
QUrl registryName() const;
void setRegistryName(const QUrl &name);
QColorSpace outputProfile() const;
void setOutputProfile(const QColorSpace &profile);
private:
QExplicitlySharedDataPointer<QPdfOutputIntentPrivate> d;
};
Q_DECLARE_SHARED(QPdfOutputIntent)
QT_END_NAMESPACE
#endif // QT_NO_PDF
#endif // QPDFOUTPUTINTENT_H

View File

@ -7,6 +7,7 @@
#include "qpagedpaintdevice_p.h"
#include "qpdf_p.h"
#include "qpdfoutputintent.h"
#include <QtCore/qfile.h>
#include <QtCore/private/qobject_p.h>
@ -364,6 +365,28 @@ void QPdfWriter::setColorModel(ColorModel model)
d->engine->d_func()->colorModel = static_cast<QPdfEngine::ColorModel>(model);
}
/*!
\since 6.8
Returns the output intent used by this PDF writer.
*/
QPdfOutputIntent QPdfWriter::outputIntent() const
{
Q_D(const QPdfWriter);
return d->engine->d_func()->outputIntent;
}
/*!
\since 6.8
Sets the output intent used by this PDF writer to \a intent.
*/
void QPdfWriter::setOutputIntent(const QPdfOutputIntent &intent)
{
Q_D(QPdfWriter);
d->engine->d_func()->outputIntent = intent;
}
QT_END_NAMESPACE
#include "moc_qpdfwriter.cpp"

View File

@ -15,6 +15,7 @@
QT_BEGIN_NAMESPACE
class QIODevice;
class QPdfOutputIntent;
class QPdfWriterPrivate;
class QUuid;
@ -60,6 +61,9 @@ public:
ColorModel colorModel() const;
void setColorModel(ColorModel model);
QPdfOutputIntent outputIntent() const;
void setOutputIntent(const QPdfOutputIntent &intent);
protected:
QPaintEngine *paintEngine() const override;
int metric(PaintDeviceMetric id) const override;