diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp index d49249b1dda..f44a95ae7c5 100644 --- a/src/gui/painting/qpdf.cpp +++ b/src/gui/painting/qpdf.cpp @@ -44,7 +44,7 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features() +constexpr QPaintEngine::PaintEngineFeatures qt_pdf_decide_features() { QPaintEngine::PaintEngineFeatures f = QPaintEngine::AllFeatures; f &= ~(QPaintEngine::PorterDuff @@ -1239,17 +1239,8 @@ void QPdfEngine::setPen() QBrush b = d->pen.brush(); Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque()); - QColor rgba = b.color(); - if (d->grayscale) { - qreal gray = qGray(rgba.rgba())/255.; - *d->currentPage << gray << gray << gray; - } else { - *d->currentPage << rgba.redF() - << rgba.greenF() - << rgba.blueF(); - } + d->writeColor(QPdfEnginePrivate::ColorDomain::Stroking, b.color()); *d->currentPage << "SCN\n"; - *d->currentPage << d->pen.widthF() << "w "; int pdfCapStyle = 0; @@ -1303,18 +1294,9 @@ void QPdfEngine::setBrush() if (!patternObject && !specifyColor) return; - *d->currentPage << (patternObject ? "/PCSp cs " : "/CSp cs "); - if (specifyColor) { - QColor rgba = d->brush.color(); - if (d->grayscale) { - qreal gray = qGray(rgba.rgba())/255.; - *d->currentPage << gray << gray << gray; - } else { - *d->currentPage << rgba.redF() - << rgba.greenF() - << rgba.blueF(); - } - } + const auto domain = patternObject ? QPdfEnginePrivate::ColorDomain::NonStrokingPattern + : QPdfEnginePrivate::ColorDomain::NonStroking; + d->writeColor(domain, specifyColor ? d->brush.color() : QColor()); if (patternObject) *d->currentPage << "/Pat" << patternObject; *d->currentPage << "scn\n"; @@ -1454,9 +1436,9 @@ int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const QPdfEnginePrivate::QPdfEnginePrivate() : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false), needsTransform(false), pdfVersion(QPdfEngine::Version_1_4), + colorModel(QPdfEngine::ColorModel::RGB), outDevice(nullptr), ownsDevice(false), embedFonts(true), - grayscale(false), m_pageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(10, 10, 10, 10)) { initResources(); @@ -1511,7 +1493,9 @@ bool QPdfEngine::begin(QPaintDevice *pdev) d->catalog = 0; d->info = 0; d->graphicsState = 0; - d->patternColorSpace = 0; + d->patternColorSpaceRGB = 0; + d->patternColorSpaceGrayscale = 0; + d->patternColorSpaceCMYK = 0; d->simplePen = false; d->needsTransform = false; @@ -1629,10 +1613,99 @@ void QPdfEnginePrivate::writeHeader() ">>\n" "endobj\n"); - // color space for pattern - patternColorSpace = addXrefEntry(-1); + // color spaces for pattern + patternColorSpaceRGB = addXrefEntry(-1); xprintf("[/Pattern /DeviceRGB]\n" "endobj\n"); + patternColorSpaceGrayscale = addXrefEntry(-1); + xprintf("[/Pattern /DeviceGray]\n" + "endobj\n"); + patternColorSpaceCMYK = addXrefEntry(-1); + xprintf("[/Pattern /DeviceCMYK]\n" + "endobj\n"); +} + +QPdfEngine::ColorModel QPdfEnginePrivate::colorModelForColor(const QColor &color) const +{ + switch (colorModel) { + case QPdfEngine::ColorModel::RGB: + case QPdfEngine::ColorModel::Grayscale: + case QPdfEngine::ColorModel::CMYK: + return colorModel; + case QPdfEngine::ColorModel::Auto: + switch (color.spec()) { + case QColor::Invalid: + case QColor::Rgb: + case QColor::Hsv: + case QColor::Hsl: + case QColor::ExtendedRgb: + return QPdfEngine::ColorModel::RGB; + case QColor::Cmyk: + return QPdfEngine::ColorModel::CMYK; + } + + break; + } + + Q_UNREACHABLE_RETURN(QPdfEngine::ColorModel::RGB); +} + +void QPdfEnginePrivate::writeColor(ColorDomain domain, const QColor &color) +{ + // Switch to the right colorspace. + // For simplicity: do it even if it redundant (= already in that colorspace) + const QPdfEngine::ColorModel actualColorModel = colorModelForColor(color); + + switch (actualColorModel) { + case QPdfEngine::ColorModel::RGB: + case QPdfEngine::ColorModel::Grayscale: + switch (domain) { + case ColorDomain::Stroking: + *currentPage << "/CSp CS\n"; break; + case ColorDomain::NonStroking: + *currentPage << "/CSp cs\n"; break; + case ColorDomain::NonStrokingPattern: + *currentPage << "/PCSp cs\n"; break; + } + break; + case QPdfEngine::ColorModel::CMYK: + switch (domain) { + case ColorDomain::Stroking: + *currentPage << "/CSpcmyk CS\n"; break; + case ColorDomain::NonStroking: + *currentPage << "/CSpcmyk cs\n"; break; + case ColorDomain::NonStrokingPattern: + *currentPage << "/PCSpcmyk cs\n"; break; + } + break; + case QPdfEngine::ColorModel::Auto: + Q_UNREACHABLE_RETURN(); + } + + // If we also have a color specified, write it out. + if (!color.isValid()) + return; + + switch (actualColorModel) { + case QPdfEngine::ColorModel::RGB: + *currentPage << color.redF() + << color.greenF() + << color.blueF(); + break; + case QPdfEngine::ColorModel::Grayscale: { + const qreal gray = qGray(color.rgba()) / 255.; + *currentPage << gray << gray << gray; + break; + } + case QPdfEngine::ColorModel::CMYK: + *currentPage << color.cyanF() + << color.magentaF() + << color.yellowF() + << color.blackF(); + break; + case QPdfEngine::ColorModel::Auto: + Q_UNREACHABLE_RETURN(); + } } void QPdfEnginePrivate::writeInfo() @@ -2078,12 +2151,18 @@ void QPdfEnginePrivate::writePage() xprintf("<<\n" "/ColorSpace <<\n" "/PCSp %d 0 R\n" + "/PCSpg %d 0 R\n" + "/PCSpcmyk %d 0 R\n" "/CSp /DeviceRGB\n" "/CSpg /DeviceGray\n" + "/CSpcmyk /DeviceCMYK\n" ">>\n" "/ExtGState <<\n" "/GSa %d 0 R\n", - patternColorSpace, graphicsState); + patternColorSpaceRGB, + patternColorSpaceGrayscale, + patternColorSpaceCMYK, + graphicsState); for (int i = 0; i < currentPage->graphicStates.size(); ++i) xprintf("/GState%d %d 0 R\n", currentPage->graphicStates.at(i), currentPage->graphicStates.at(i)); @@ -2396,7 +2475,22 @@ struct QGradientBound { }; Q_DECLARE_TYPEINFO(QGradientBound, Q_PRIMITIVE_TYPE); -int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha) +void QPdfEnginePrivate::ShadingFunctionResult::writeColorSpace(QPdf::ByteStream *stream) const +{ + *stream << "/ColorSpace "; + switch (colorModel) { + case QPdfEngine::ColorModel::RGB: + case QPdfEngine::ColorModel::Grayscale: + *stream << "/DeviceRGB\n"; break; + case QPdfEngine::ColorModel::CMYK: + *stream << "/DeviceCMYK\n"; break; + case QPdfEngine::ColorModel::Auto: + Q_UNREACHABLE(); break; + } +} + +QPdfEnginePrivate::ShadingFunctionResult +QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha) { QGradientStops stops = gradient->stops(); if (stops.isEmpty()) { @@ -2408,6 +2502,35 @@ int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from if (stops.at(stops.size() - 1).first < 1) stops.append(QGradientStop(1, stops.at(stops.size() - 1).second)); + // Color to use which colorspace to use + const QColor referenceColor = stops.constFirst().second; + + switch (colorModel) { + case QPdfEngine::ColorModel::RGB: + case QPdfEngine::ColorModel::Grayscale: + case QPdfEngine::ColorModel::CMYK: + break; + case QPdfEngine::ColorModel::Auto: { + // Make sure that all the stops have the same color spec + // (we don't support anything else) + const QColor::Spec referenceSpec = referenceColor.spec(); + bool warned = false; + for (QGradientStop &stop : stops) { + if (stop.second.spec() != referenceSpec) { + if (!warned) { + qWarning("QPdfEngine: unable to create a gradient between colors of different spec"); + warned = true; + } + stop.second = stop.second.convertTo(referenceSpec); + } + } + break; + } + } + + ShadingFunctionResult result; + result.colorModel = colorModelForColor(referenceColor); + QList functions; const int numStops = stops.size(); functions.reserve(numStops - 1); @@ -2423,8 +2546,30 @@ int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from s << "/C0 [" << stops.at(i).second.alphaF() << "]\n" "/C1 [" << stops.at(i + 1).second.alphaF() << "]\n"; } else { - s << "/C0 [" << stops.at(i).second.redF() << stops.at(i).second.greenF() << stops.at(i).second.blueF() << "]\n" - "/C1 [" << stops.at(i + 1).second.redF() << stops.at(i + 1).second.greenF() << stops.at(i + 1).second.blueF() << "]\n"; + switch (result.colorModel) { + case QPdfEngine::ColorModel::RGB: + case QPdfEngine::ColorModel::Grayscale: + // For backwards compatibility, Grayscale emits RGB colors + s << "/C0 [" << stops.at(i).second.redF() << stops.at(i).second.greenF() << stops.at(i).second.blueF() << "]\n" + "/C1 [" << stops.at(i + 1).second.redF() << stops.at(i + 1).second.greenF() << stops.at(i + 1).second.blueF() << "]\n"; + break; + + case QPdfEngine::ColorModel::CMYK: + s << "/C0 [" << stops.at(i).second.cyanF() + << stops.at(i).second.magentaF() + << stops.at(i).second.yellowF() + << stops.at(i).second.blackF() << "]\n" + "/C1 [" << stops.at(i + 1).second.cyanF() + << stops.at(i + 1).second.magentaF() + << stops.at(i + 1).second.yellowF() + << stops.at(i + 1).second.blackF() << "]\n"; + break; + + case QPdfEngine::ColorModel::Auto: + Q_UNREACHABLE(); + break; + } + } s << ">>\n" "endobj\n"; @@ -2492,7 +2637,8 @@ int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from } else { function = functions.at(0); } - return function; + result.function = function; + return result; } int QPdfEnginePrivate::generateLinearGradientShader(const QLinearGradient *gradient, const QTransform &matrix, bool alpha) @@ -2538,17 +2684,22 @@ int QPdfEnginePrivate::generateLinearGradientShader(const QLinearGradient *gradi } } - int function = createShadingFunction(gradient, from, to, reflect, alpha); + const auto shadingFunctionResult = createShadingFunction(gradient, from, to, reflect, alpha); QByteArray shader; QPdf::ByteStream s(&shader); s << "<<\n" - "/ShadingType 2\n" - "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") << - "/AntiAlias true\n" + "/ShadingType 2\n"; + + if (alpha) + s << "/ColorSpace /DeviceGray\n"; + else + shadingFunctionResult.writeColorSpace(&s); + + s << "/AntiAlias true\n" "/Coords [" << start.x() << start.y() << stop.x() << stop.y() << "]\n" "/Extend [true true]\n" - "/Function " << function << "0 R\n" + "/Function " << shadingFunctionResult.function << "0 R\n" ">>\n" "endobj\n"; int shaderObject = addXrefEntry(-1); @@ -2606,18 +2757,23 @@ int QPdfEnginePrivate::generateRadialGradientShader(const QRadialGradient *gradi } } - int function = createShadingFunction(gradient, from, to, reflect, alpha); + const auto shadingFunctionResult = createShadingFunction(gradient, from, to, reflect, alpha); QByteArray shader; QPdf::ByteStream s(&shader); s << "<<\n" - "/ShadingType 3\n" - "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") << - "/AntiAlias true\n" + "/ShadingType 3\n"; + + if (alpha) + s << "/ColorSpace /DeviceGray\n"; + else + shadingFunctionResult.writeColorSpace(&s); + + s << "/AntiAlias true\n" "/Domain [0 1]\n" "/Coords [" << p0.x() << p0.y() << r0 << p1.x() << p1.y() << r1 << "]\n" "/Extend [true true]\n" - "/Function " << function << "0 R\n" + "/Function " << shadingFunctionResult.function << "0 R\n" ">>\n" "endobj\n"; int shaderObject = addXrefEntry(-1); @@ -2856,6 +3012,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless, QImage image = img; QImage::Format format = image.format(); + const bool grayscale = (colorModel == QPdfEngine::ColorModel::Grayscale); if (pdfVersion == QPdfEngine::Version_A1b) { if (image.hasAlphaChannel()) { diff --git a/src/gui/painting/qpdf_p.h b/src/gui/painting/qpdf_p.h index 2c70ddf6645..b97d0df31fe 100644 --- a/src/gui/painting/qpdf_p.h +++ b/src/gui/painting/qpdf_p.h @@ -142,7 +142,7 @@ class Q_GUI_EXPORT QPdfEngine : public QPaintEngine }; QPdfEngine(); - QPdfEngine(QPdfEnginePrivate &d); + explicit QPdfEngine(QPdfEnginePrivate &d); ~QPdfEngine() {} void setOutputFilename(const QString &filename); @@ -157,6 +157,18 @@ class Q_GUI_EXPORT QPdfEngine : public QPaintEngine void addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType); + // keep in sync with QPdfWriter + enum class ColorModel + { + RGB, + Grayscale, + CMYK, + Auto, + }; + + ColorModel colorModel() const; + void setColorModel(ColorModel model); + // reimplementations QPaintEngine bool begin(QPaintDevice *pdev) override; bool end() override; @@ -240,6 +252,7 @@ class Q_GUI_EXPORT QPdfEnginePrivate : public QPaintEnginePrivate bool needsTransform; qreal opacity; QPdfEngine::PdfVersion pdfVersion; + QPdfEngine::ColorModel colorModel; QHash fonts; @@ -255,7 +268,6 @@ class Q_GUI_EXPORT QPdfEnginePrivate : public QPaintEnginePrivate QString creator; bool embedFonts; int resolution; - bool grayscale; // Page layout: size, orientation and margins QPageLayout m_pageLayout; @@ -265,8 +277,22 @@ class Q_GUI_EXPORT QPdfEnginePrivate : public QPaintEnginePrivate int generateGradientShader(const QGradient *gradient, const QTransform &matrix, bool alpha = false); int generateLinearGradientShader(const QLinearGradient *lg, const QTransform &matrix, bool alpha); int generateRadialGradientShader(const QRadialGradient *gradient, const QTransform &matrix, bool alpha); - int createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha); + struct ShadingFunctionResult + { + int function; + QPdfEngine::ColorModel colorModel; + void writeColorSpace(QPdf::ByteStream *stream) const; + }; + ShadingFunctionResult createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha); + + enum class ColorDomain { + Stroking, + NonStroking, + NonStrokingPattern, + }; + QPdfEngine::ColorModel colorModelForColor(const QColor &color) const; + void writeColor(ColorDomain domain, const QColor &color); void writeInfo(); int writeXmpDcumentMetaData(); int writeOutputIntent(); @@ -316,7 +342,10 @@ class Q_GUI_EXPORT QPdfEnginePrivate : public QPaintEnginePrivate // various PDF objects int pageRoot, namesRoot, destsRoot, attachmentsRoot, catalog, info; - int graphicsState, patternColorSpace; + int graphicsState; + int patternColorSpaceRGB; + int patternColorSpaceGrayscale; + int patternColorSpaceCMYK; QList pages; QHash imageCache; QHash, uint > alphaCache; diff --git a/src/gui/painting/qpdfwriter.cpp b/src/gui/painting/qpdfwriter.cpp index f28a460d7cb..b3b6f3d310b 100644 --- a/src/gui/painting/qpdfwriter.cpp +++ b/src/gui/painting/qpdfwriter.cpp @@ -295,6 +295,52 @@ bool QPdfWriter::newPage() return d->engine->newPage(); } +/*! + \enum QPdfWriter::ColorModel + \since 6.8 + + This enumeration describes the way in which the PDF engine interprets + stroking and filling colors, set as a QPainter's pen or brush (via + QPen and QBrush). + + \value RGB All colors are converted to RGB and saved as such in the + PDF. This is the default. + + \value Grayscale All colors are converted to grayscale. For backwards + compatibility, they are emitted in the PDF output as RGB colors, with + identical quantities of red, green and blue. + + \value CMYK All colors are converted to CMYK and saved as such. + + \value Auto RGB colors are emitted as RGB; CMYK colors are emitted as + CMYK. Colors of any other color spec are converted to RGB. + + \sa QColor, QGradient +*/ + +/*! + \since 6.8 + + Returns the color model used by this PDF writer. + The default is QPdfWriter::ColorModel::RGB. +*/ +QPdfWriter::ColorModel QPdfWriter::colorModel() const +{ + Q_D(const QPdfWriter); + return static_cast(d->engine->d_func()->colorModel); +} + +/*! + \since 6.8 + + Sets the color model used by this PDF writer to \a model. +*/ +void QPdfWriter::setColorModel(ColorModel model) +{ + Q_D(QPdfWriter); + d->engine->d_func()->colorModel = static_cast(model); +} + QT_END_NAMESPACE #include "moc_qpdfwriter.cpp" diff --git a/src/gui/painting/qpdfwriter.h b/src/gui/painting/qpdfwriter.h index 5885c4ef1a6..1a4b607b66c 100644 --- a/src/gui/painting/qpdfwriter.h +++ b/src/gui/painting/qpdfwriter.h @@ -44,6 +44,18 @@ class Q_GUI_EXPORT QPdfWriter : public QObject, public QPagedPaintDevice void addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType = QString()); + enum class ColorModel + { + RGB, + Grayscale, + CMYK, + Auto, + }; + Q_ENUM(ColorModel) + + ColorModel colorModel() const; + void setColorModel(ColorModel model); + protected: QPaintEngine *paintEngine() const override; int metric(PaintDeviceMetric id) const override; diff --git a/src/plugins/printsupport/cups/qcupsprintengine.cpp b/src/plugins/printsupport/cups/qcupsprintengine.cpp index b69d36ab8c6..c75475362ed 100644 --- a/src/plugins/printsupport/cups/qcupsprintengine.cpp +++ b/src/plugins/printsupport/cups/qcupsprintengine.cpp @@ -249,9 +249,12 @@ void QCupsPrintEnginePrivate::changePrinter(const QString &newPrinter) duplex = m_printDevice.defaultDuplexMode(); duplexRequestedExplicitly = false; } - QPrint::ColorMode colorMode = grayscale ? QPrint::GrayScale : QPrint::Color; - if (!m_printDevice.supportedColorModes().contains(colorMode)) - grayscale = m_printDevice.defaultColorMode() == QPrint::GrayScale; + QPrint::ColorMode colorMode = static_cast(printerColorMode()); + if (!m_printDevice.supportedColorModes().contains(colorMode)) { + colorModel = (m_printDevice.defaultColorMode() == QPrint::GrayScale) + ? QPdfEngine::ColorModel::Grayscale + : QPdfEngine::ColorModel::RGB; + } // Get the equivalent page size for this printer as supported names may be different if (m_printDevice.supportedPageSize(m_pageLayout.pageSize()).isValid()) diff --git a/src/printsupport/kernel/qprint_p.h b/src/printsupport/kernel/qprint_p.h index 6c30e388f6d..0a94aa8db37 100644 --- a/src/printsupport/kernel/qprint_p.h +++ b/src/printsupport/kernel/qprint_p.h @@ -68,6 +68,7 @@ namespace QPrint { DuplexShortSide }; + // Note: Keep in sync with QPrinter::ColorMode enum ColorMode { GrayScale, Color diff --git a/src/printsupport/kernel/qprintengine_pdf.cpp b/src/printsupport/kernel/qprintengine_pdf.cpp index daf5010feb5..1e2910464ab 100644 --- a/src/printsupport/kernel/qprintengine_pdf.cpp +++ b/src/printsupport/kernel/qprintengine_pdf.cpp @@ -104,7 +104,14 @@ void QPdfPrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &va d->collate = value.toBool(); break; case PPK_ColorMode: - d->grayscale = (QPrinter::ColorMode(value.toInt()) == QPrinter::GrayScale); + switch (QPrinter::ColorMode(value.toInt())) { + case QPrinter::GrayScale: + d->colorModel = QPdfEngine::ColorModel::Grayscale; + break; + case QPrinter::Color: + d->colorModel = QPdfEngine::ColorModel::RGB; + break; + } break; case PPK_Creator: d->creator = value.toString(); @@ -221,7 +228,7 @@ QVariant QPdfPrintEngine::property(PrintEnginePropertyKey key) const ret = d->collate; break; case PPK_ColorMode: - ret = d->grayscale ? QPrinter::GrayScale : QPrinter::Color; + ret = d->printerColorMode(); break; case PPK_Creator: ret = d->creator; @@ -367,6 +374,22 @@ QPdfPrintEnginePrivate::~QPdfPrintEnginePrivate() { } +QPrinter::ColorMode QPdfPrintEnginePrivate::printerColorMode() const +{ + switch (colorModel) { + case QPdfEngine::ColorModel::RGB: + case QPdfEngine::ColorModel::CMYK: + case QPdfEngine::ColorModel::Auto: + return QPrinter::Color; + case QPdfEngine::ColorModel::Grayscale: + return QPrinter::GrayScale; + } + + Q_UNREACHABLE(); + return QPrinter::Color; +} + + QT_END_NAMESPACE #endif // QT_NO_PRINTER diff --git a/src/printsupport/kernel/qprintengine_pdf_p.h b/src/printsupport/kernel/qprintengine_pdf_p.h index ccef8215e19..dbf50080a4e 100644 --- a/src/printsupport/kernel/qprintengine_pdf_p.h +++ b/src/printsupport/kernel/qprintengine_pdf_p.h @@ -79,6 +79,8 @@ class Q_PRINTSUPPORT_EXPORT QPdfPrintEnginePrivate : public QPdfEnginePrivate QPdfPrintEnginePrivate(QPrinter::PrinterMode m); ~QPdfPrintEnginePrivate(); + QPrinter::ColorMode printerColorMode() const; + virtual bool openPrintDevice(); virtual void closePrintDevice();