diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 6efc2108ced..3b3dbd13218 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -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 diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp index e019bf7912c..992db03bbc2 100644 --- a/src/gui/painting/qpdf.cpp +++ b/src/gui/painting/qpdf.cpp @@ -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() diff --git a/src/gui/painting/qpdf_p.h b/src/gui/painting/qpdf_p.h index 9632b8149ac..3a1a14cafd0 100644 --- a/src/gui/painting/qpdf_p.h +++ b/src/gui/painting/qpdf_p.h @@ -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; diff --git a/src/gui/painting/qpdfoutputintent.cpp b/src/gui/painting/qpdfoutputintent.cpp new file mode 100644 index 00000000000..a3151f4d470 --- /dev/null +++ b/src/gui/painting/qpdfoutputintent.cpp @@ -0,0 +1,201 @@ +// Copyright (C) 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo +// 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 +#include +#include +#include + +#include + +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 diff --git a/src/gui/painting/qpdfoutputintent.h b/src/gui/painting/qpdfoutputintent.h new file mode 100644 index 00000000000..d3eaffcfc4a --- /dev/null +++ b/src/gui/painting/qpdfoutputintent.h @@ -0,0 +1,56 @@ +// Copyright (C) 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo +// 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 + +#ifndef QT_NO_PDF + +#include + +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 d; +}; + +Q_DECLARE_SHARED(QPdfOutputIntent) + +QT_END_NAMESPACE + +#endif // QT_NO_PDF + +#endif // QPDFOUTPUTINTENT_H diff --git a/src/gui/painting/qpdfwriter.cpp b/src/gui/painting/qpdfwriter.cpp index aa8fe7708f4..be4b73aa640 100644 --- a/src/gui/painting/qpdfwriter.cpp +++ b/src/gui/painting/qpdfwriter.cpp @@ -7,6 +7,7 @@ #include "qpagedpaintdevice_p.h" #include "qpdf_p.h" +#include "qpdfoutputintent.h" #include #include @@ -364,6 +365,28 @@ void QPdfWriter::setColorModel(ColorModel model) d->engine->d_func()->colorModel = static_cast(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" diff --git a/src/gui/painting/qpdfwriter.h b/src/gui/painting/qpdfwriter.h index 74a443bfc29..2938f5c1cf0 100644 --- a/src/gui/painting/qpdfwriter.h +++ b/src/gui/painting/qpdfwriter.h @@ -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;