summaryrefslogtreecommitdiffstats
path: root/src/gui/painting/qpdf.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/painting/qpdf.cpp')
-rw-r--r--src/gui/painting/qpdf.cpp471
1 files changed, 338 insertions, 133 deletions
diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp
index 02fd2fdeea..716cf35ee6 100644
--- a/src/gui/painting/qpdf.cpp
+++ b/src/gui/painting/qpdf.cpp
@@ -7,6 +7,7 @@
#include "qplatformdefs.h"
+#include <private/qcmyk_p.h>
#include <private/qfont_p.h>
#include <private/qmath_p.h>
#include <private/qpainter_p.h>
@@ -19,8 +20,11 @@
#include <qimagewriter.h>
#include <qnumeric.h>
#include <qtemporaryfile.h>
+#include <qtimezone.h>
#include <quuid.h>
+#include <map>
+
#ifndef QT_NO_COMPRESS
#include <zlib.h>
#endif
@@ -44,7 +48,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
@@ -99,7 +103,7 @@ static void removeTransparencyFromBrush(QBrush &brush)
const char *qt_real_to_string(qreal val, char *buf) {
const char *ret = buf;
- if (qIsNaN(val)) {
+ if (!qIsFinite(val) || std::abs(val) > std::numeric_limits<quint32>::max()) {
*(buf++) = '0';
*(buf++) = ' ';
*buf = 0;
@@ -110,8 +114,8 @@ const char *qt_real_to_string(qreal val, char *buf) {
*(buf++) = '-';
val = -val;
}
- unsigned int ival = (unsigned int) val;
- qreal frac = val - (qreal)ival;
+ qreal frac = std::modf(val, &val);
+ quint32 ival(val);
int ifrac = (int)(frac * 1000000000);
if (ifrac == 1000000000) {
@@ -270,13 +274,6 @@ namespace QPdf {
dev->open(QIODevice::ReadWrite | QIODevice::Truncate);
}
- void ByteStream::constructor_helper(QByteArray *ba)
- {
- delete dev;
- dev = new QBuffer(ba);
- dev->open(QIODevice::ReadWrite);
- }
-
void ByteStream::prepareBuffer()
{
Q_ASSERT(!dev->isSequential());
@@ -285,16 +282,17 @@ namespace QPdf {
&& size > maxMemorySize()) {
// Switch to file backing.
QTemporaryFile *newFile = new QTemporaryFile;
- newFile->open();
- dev->reset();
- while (!dev->atEnd()) {
- QByteArray buf = dev->read(chunkSize());
- newFile->write(buf);
+ if (newFile->open()) {
+ dev->reset();
+ while (!dev->atEnd()) {
+ QByteArray buf = dev->read(chunkSize());
+ newFile->write(buf);
+ }
+ delete dev;
+ dev = newFile;
+ ba.clear();
+ fileBackingActive = true;
}
- delete dev;
- dev = newFile;
- ba.clear();
- fileBackingActive = true;
}
if (dev->pos() != size) {
dev->seek(size);
@@ -1040,6 +1038,7 @@ void QPdfEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
*d->currentPage << "Q\n";
}
+// Used by QtWebKit
void QPdfEngine::drawHyperlink(const QRectF &r, const QUrl &url)
{
Q_D(QPdfEngine);
@@ -1239,17 +1238,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 +1293,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 +1435,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::Auto),
outDevice(nullptr), ownsDevice(false),
embedFonts(true),
- grayscale(false),
m_pageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(10, 10, 10, 10))
{
initResources();
@@ -1511,7 +1492,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;
@@ -1585,7 +1568,7 @@ void QPdfEnginePrivate::writeHeader()
int metaDataObj = -1;
int outputIntentObj = -1;
if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) {
- metaDataObj = writeXmpDcumentMetaData();
+ metaDataObj = writeXmpDocumentMetaData();
}
if (pdfVersion == QPdfEngine::Version_A1b) {
outputIntentObj = writeOutputIntent();
@@ -1629,10 +1612,108 @@ 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:
+ 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::Grayscale:
+ switch (domain) {
+ case ColorDomain::Stroking:
+ *currentPage << "/CSpg CS\n"; break;
+ case ColorDomain::NonStroking:
+ *currentPage << "/CSpg cs\n"; break;
+ case ColorDomain::NonStrokingPattern:
+ *currentPage << "/PCSpg 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;
+ 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()
@@ -1663,11 +1744,12 @@ void QPdfEnginePrivate::writeInfo()
xprintf("+%02d'%02d')\n", hours , mins);
else
xprintf("Z)\n");
+ xprintf("/Trapped /False\n");
xprintf(">>\n"
"endobj\n");
}
-int QPdfEnginePrivate::writeXmpDcumentMetaData()
+int QPdfEnginePrivate::writeXmpDocumentMetaData()
{
const int metaDataObj = addXrefEntry(-1);
QByteArray metaDataContent;
@@ -1675,29 +1757,16 @@ int QPdfEnginePrivate::writeXmpDcumentMetaData()
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 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 = "Z"_L1;
-
- const QString metaDataDate = timeStr + tzStr;
+#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);
- metaDataFile.open(QIODevice::ReadOnly);
+ bool ok = metaDataFile.open(QIODevice::ReadOnly);
+ Q_ASSERT(ok);
metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(),
title.toHtmlEscaped(),
creator.toHtmlEscaped(),
@@ -1723,7 +1792,8 @@ int QPdfEnginePrivate::writeOutputIntent()
const int colorProfile = addXrefEntry(-1);
{
QFile colorProfileFile(":/qpdf/sRGB2014.icc"_L1);
- colorProfileFile.open(QIODevice::ReadOnly);
+ bool ok = colorProfileFile.open(QIODevice::ReadOnly);
+ Q_ASSERT(ok);
const QByteArray colorProfileData = colorProfileFile.readAll();
QByteArray data;
@@ -1734,7 +1804,8 @@ int QPdfEnginePrivate::writeOutputIntent()
s << "/N 3\n";
s << "/Alternate /DeviceRGB\n";
s << "/Length " << length_object << "0 R\n";
- s << "/Filter /FlateDecode\n";
+ if (do_compress)
+ s << "/Filter /FlateDecode\n";
s << ">>\n";
s << "stream\n";
write(data);
@@ -1788,7 +1859,7 @@ void QPdfEnginePrivate::writeDestsRoot()
if (destCache.isEmpty())
return;
- QHash<QString, int> destObjects;
+ std::map<QString, int> destObjects;
QByteArray xs, ys;
for (const DestInfo &destInfo : std::as_const(destCache)) {
int destObj = addXrefEntry(-1);
@@ -1796,21 +1867,19 @@ void QPdfEnginePrivate::writeDestsRoot()
ys.setNum(static_cast<double>(destInfo.coords.y()), 'f');
xprintf("[%d 0 R /XYZ %s %s 0]\n", destInfo.pageObj, xs.constData(), ys.constData());
xprintf("endobj\n");
- destObjects.insert(destInfo.anchor, destObj);
+ destObjects.insert_or_assign(destInfo.anchor, destObj);
}
// names
destsRoot = addXrefEntry(-1);
- QStringList anchors = destObjects.keys();
- anchors.sort();
xprintf("<<\n/Limits [");
- printString(anchors.constFirst());
+ printString(destObjects.begin()->first);
xprintf(" ");
- printString(anchors.constLast());
+ printString(destObjects.rbegin()->first);
xprintf("]\n/Names [\n");
- for (const QString &anchor : std::as_const(anchors)) {
+ for (const auto &[anchor, destObject] : destObjects) {
printString(anchor);
- xprintf(" %d 0 R\n", destObjects[anchor]);
+ xprintf(" %d 0 R\n", destObject);
}
xprintf("]\n>>\n"
"endobj\n");
@@ -1843,7 +1912,8 @@ void QPdfEnginePrivate::writeAttachmentRoot()
attachments.push_back(addXrefEntry(-1));
xprintf("<<\n"
- "/F (%s)", attachment.fileName.toLatin1().constData());
+ "/F ");
+ printString(attachment.fileName);
xprintf("\n/EF <</F %d 0 R>>\n"
"/Type/Filespec\n"
@@ -2056,17 +2126,24 @@ void QPdfEnginePrivate::writePage()
qreal userUnit = calcUserUnit();
addXrefEntry(pages.constLast());
+
+ // make sure we use the pagesize from when we started the page, since the user may have changed it
+ const QByteArray formattedPageWidth = QByteArray::number(currentPage->pageSize.width() / userUnit, 'f');
+ const QByteArray formattedPageHeight = QByteArray::number(currentPage->pageSize.height() / userUnit, 'f');
+
xprintf("<<\n"
"/Type /Page\n"
"/Parent %d 0 R\n"
"/Contents %d 0 R\n"
"/Resources %d 0 R\n"
"/Annots %d 0 R\n"
- "/MediaBox [0 0 %s %s]\n",
+ "/MediaBox [0 0 %s %s]\n"
+ "/TrimBox [0 0 %s %s]\n",
pageRoot, pageStream, resources, annots,
- // make sure we use the pagesize from when we started the page, since the user may have changed it
- QByteArray::number(currentPage->pageSize.width() / userUnit, 'f').constData(),
- QByteArray::number(currentPage->pageSize.height() / userUnit, 'f').constData());
+ formattedPageWidth.constData(),
+ formattedPageHeight.constData(),
+ formattedPageWidth.constData(),
+ formattedPageHeight.constData());
if (pdfVersion >= QPdfEngine::Version_1_6)
xprintf("/UserUnit %s\n", QByteArray::number(userUnit, 'f').constData());
@@ -2078,12 +2155,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));
@@ -2336,7 +2419,7 @@ int QPdfEnginePrivate::writeCompressed(const char *src, int len)
return len;
}
-int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, int depth,
+int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, WriteImageOption option,
int maskObject, int softMaskObject, bool dct, bool isMono)
{
int image = addXrefEntry(-1);
@@ -2346,7 +2429,8 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height,
"/Width %d\n"
"/Height %d\n", width, height);
- if (depth == 1) {
+ switch (option) {
+ case WriteImageOption::Monochrome:
if (!isMono) {
xprintf("/ImageMask true\n"
"/Decode [1 0]\n");
@@ -2354,10 +2438,21 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height,
xprintf("/BitsPerComponent 1\n"
"/ColorSpace /DeviceGray\n");
}
- } else {
+ break;
+ case WriteImageOption::Grayscale:
xprintf("/BitsPerComponent 8\n"
- "/ColorSpace %s\n", (depth == 32) ? "/DeviceRGB" : "/DeviceGray");
+ "/ColorSpace /DeviceGray\n");
+ break;
+ case WriteImageOption::RGB:
+ xprintf("/BitsPerComponent 8\n"
+ "/ColorSpace /DeviceRGB\n");
+ break;
+ case WriteImageOption::CMYK:
+ xprintf("/BitsPerComponent 8\n"
+ "/ColorSpace /DeviceCMYK\n");
+ break;
}
+
if (maskObject > 0)
xprintf("/Mask %d 0 R\n", maskObject);
if (softMaskObject > 0)
@@ -2396,7 +2491,23 @@ 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:
+ *stream << "/DeviceRGB\n"; break;
+ case QPdfEngine::ColorModel::Grayscale:
+ *stream << "/DeviceGray\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 +2519,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<int> functions;
const int numStops = stops.size();
functions.reserve(numStops - 1);
@@ -2423,8 +2563,31 @@ 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:
+ 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::Grayscale:
+ s << "/C0 [" << (qGray(stops.at(i).second.rgba()) / 255.) << "]\n"
+ "/C1 [" << (qGray(stops.at(i + 1).second.rgba()) / 255.) << "]\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 +2655,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 +2702,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 +2775,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 +3030,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()) {
@@ -2879,7 +3054,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
format = QImage::Format_Mono;
} else {
*bitmap = false;
- if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) {
+ if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_CMYK8888) {
image = image.convertToFormat(QImage::Format_ARGB32);
format = QImage::Format_ARGB32;
}
@@ -2887,7 +3062,6 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
int w = image.width();
int h = image.height();
- int d = image.depth();
if (format == QImage::Format_Mono) {
int bytesPerLine = (w + 7) >> 3;
@@ -2898,7 +3072,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
memcpy(rawdata, image.constScanLine(y), bytesPerLine);
rawdata += bytesPerLine;
}
- object = writeImage(data, w, h, d, 0, 0, false, is_monochrome(img.colorTable()));
+ object = writeImage(data, w, h, WriteImageOption::Monochrome, 0, 0, false, is_monochrome(img.colorTable()));
} else {
QByteArray softMaskData;
bool dct = false;
@@ -2910,10 +3084,14 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
QBuffer buffer(&imageData);
QImageWriter writer(&buffer, "jpeg");
writer.setQuality(94);
+ if (format == QImage::Format_CMYK8888) {
+ // PDFs require CMYK colors not to be inverted in the JPEG encoding
+ writer.setSubType("CMYK");
+ }
writer.write(image);
dct = true;
- if (format != QImage::Format_RGB32) {
+ if (format != QImage::Format_RGB32 && format != QImage::Format_CMYK8888) {
softMaskData.resize(w * h);
uchar *sdata = (uchar *)softMaskData.data();
for (int y = 0; y < h; ++y) {
@@ -2928,41 +3106,59 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
}
}
} else {
- imageData.resize(grayscale ? w * h : 3 * w * h);
- uchar *data = (uchar *)imageData.data();
- softMaskData.resize(w * h);
- uchar *sdata = (uchar *)softMaskData.data();
- for (int y = 0; y < h; ++y) {
- const QRgb *rgb = (const QRgb *)image.constScanLine(y);
+ if (format == QImage::Format_CMYK8888) {
+ imageData.resize(grayscale ? w * h : w * h * 4);
+ uchar *data = (uchar *)imageData.data();
+ const qsizetype bytesPerLine = image.bytesPerLine();
if (grayscale) {
- for (int x = 0; x < w; ++x) {
- *(data++) = qGray(*rgb);
- uchar alpha = qAlpha(*rgb);
- *sdata++ = alpha;
- hasMask |= (alpha < 255);
- hasAlpha |= (alpha != 0 && alpha != 255);
- ++rgb;
+ for (int y = 0; y < h; ++y) {
+ const uint *cmyk = (const uint *)image.constScanLine(y);
+ for (int x = 0; x < w; ++x)
+ *data++ = qGray(QCmyk32::fromCmyk32(*cmyk++).toColor().rgba());
}
} else {
- for (int x = 0; x < w; ++x) {
- *(data++) = qRed(*rgb);
- *(data++) = qGreen(*rgb);
- *(data++) = qBlue(*rgb);
- uchar alpha = qAlpha(*rgb);
- *sdata++ = alpha;
- hasMask |= (alpha < 255);
- hasAlpha |= (alpha != 0 && alpha != 255);
- ++rgb;
+ for (int y = 0; y < h; ++y) {
+ uchar *start = data + y * w * 4;
+ memcpy(start, image.constScanLine(y), bytesPerLine);
+ }
+ }
+ } else {
+ imageData.resize(grayscale ? w * h : 3 * w * h);
+ uchar *data = (uchar *)imageData.data();
+ softMaskData.resize(w * h);
+ uchar *sdata = (uchar *)softMaskData.data();
+ for (int y = 0; y < h; ++y) {
+ const QRgb *rgb = (const QRgb *)image.constScanLine(y);
+ if (grayscale) {
+ for (int x = 0; x < w; ++x) {
+ *(data++) = qGray(*rgb);
+ uchar alpha = qAlpha(*rgb);
+ *sdata++ = alpha;
+ hasMask |= (alpha < 255);
+ hasAlpha |= (alpha != 0 && alpha != 255);
+ ++rgb;
+ }
+ } else {
+ for (int x = 0; x < w; ++x) {
+ *(data++) = qRed(*rgb);
+ *(data++) = qGreen(*rgb);
+ *(data++) = qBlue(*rgb);
+ uchar alpha = qAlpha(*rgb);
+ *sdata++ = alpha;
+ hasMask |= (alpha < 255);
+ hasAlpha |= (alpha != 0 && alpha != 255);
+ ++rgb;
+ }
}
}
}
- if (format == QImage::Format_RGB32)
+ if (format == QImage::Format_RGB32 || format == QImage::Format_CMYK8888)
hasAlpha = hasMask = false;
}
int maskObject = 0;
int softMaskObject = 0;
if (hasAlpha) {
- softMaskObject = writeImage(softMaskData, w, h, 8, 0, 0);
+ softMaskObject = writeImage(softMaskData, w, h, WriteImageOption::Grayscale, 0, 0);
} else if (hasMask) {
// dither the soft mask to 1bit and add it. This also helps PDF viewers
// without transparency support
@@ -2978,9 +3174,18 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
}
mdata += bytesPerLine;
}
- maskObject = writeImage(mask, w, h, 1, 0, 0);
+ maskObject = writeImage(mask, w, h, WriteImageOption::Monochrome, 0, 0);
}
- object = writeImage(imageData, w, h, grayscale ? 8 : 32,
+
+ const WriteImageOption option = [&]() {
+ if (grayscale)
+ return WriteImageOption::Grayscale;
+ if (format == QImage::Format_CMYK8888)
+ return WriteImageOption::CMYK;
+ return WriteImageOption::RGB;
+ }();
+
+ object = writeImage(imageData, w, h, option,
maskObject, softMaskObject, dct);
}
imageCache.insert(serial_no, object);