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.cpp319
1 files changed, 286 insertions, 33 deletions
diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp
index afeb198953..e421055ef3 100644
--- a/src/gui/painting/qpdf.cpp
+++ b/src/gui/painting/qpdf.cpp
@@ -42,16 +42,20 @@
#ifndef QT_NO_PDF
#include "qplatformdefs.h"
-#include <qdebug.h>
-#include <qfile.h>
-#include <qtemporaryfile.h>
+
+#include <private/qfont_p.h>
#include <private/qmath_p.h>
#include <private/qpainter_p.h>
-#include <qnumeric.h>
-#include "private/qfont_p.h"
+
+#include <qbuffer.h>
+#include <qcryptographichash.h>
+#include <qdatetime.h>
+#include <qdebug.h>
+#include <qfile.h>
#include <qimagewriter.h>
-#include "qbuffer.h"
-#include "QtCore/qdatetime.h"
+#include <qnumeric.h>
+#include <qtemporaryfile.h>
+#include <quuid.h>
#ifndef QT_NO_COMPRESS
#include <zlib.h>
@@ -79,6 +83,45 @@ inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features()
return f;
}
+extern bool qt_isExtendedRadialGradient(const QBrush &brush);
+
+// helper function to remove transparency from brush in PDF/A-1b mode
+static void removeTransparencyFromBrush(QBrush &brush)
+{
+ if (brush.style() == Qt::SolidPattern) {
+ QColor color = brush.color();
+ if (color.alpha() != 255) {
+ color.setAlpha(255);
+ brush.setColor(color);
+ }
+
+ return;
+ }
+
+ if (qt_isExtendedRadialGradient(brush)) {
+ brush = QBrush(Qt::black); // the safest we can do so far...
+ return;
+ }
+
+ if (brush.style() == Qt::LinearGradientPattern
+ || brush.style() == Qt::RadialGradientPattern
+ || brush.style() == Qt::ConicalGradientPattern) {
+
+ QGradientStops stops = brush.gradient()->stops();
+ for (int i = 0; i < stops.size(); ++i) {
+ if (stops[i].second.alpha() != 255)
+ stops[i].second.setAlpha(255);
+ }
+
+ const_cast<QGradient*>(brush.gradient())->setStops(stops);
+ return;
+ }
+
+ if (brush.style() == Qt::TexturePattern) {
+ // handled inside QPdfEnginePrivate::addImage() already
+ return;
+ }
+}
/* also adds a space at the end of the number */
@@ -1020,7 +1063,12 @@ void QPdfEngine::drawHyperlink(const QRectF &r, const QUrl &url)
char buf[256];
const QRectF rr = d->pageMatrix().mapRect(r);
- d->xprintf("<<\n/Type /Annot\n/Subtype /Link\n/Rect [");
+ d->xprintf("<<\n/Type /Annot\n/Subtype /Link\n");
+
+ if (d->pdfVersion == QPdfEngine::Version_A1b)
+ d->xprintf("/F 4\n"); // enable print flag, disable all other
+
+ d->xprintf("/Rect [");
d->xprintf("%s ", qt_real_to_string(rr.left(), buf));
d->xprintf("%s ", qt_real_to_string(rr.top(), buf));
d->xprintf("%s ", qt_real_to_string(rr.right(), buf));
@@ -1042,7 +1090,22 @@ void QPdfEngine::updateState(const QPaintEngineState &state)
d->stroker.matrix = state.transform();
if (flags & DirtyPen) {
- d->pen = state.pen();
+ if (d->pdfVersion == QPdfEngine::Version_A1b) {
+ QPen pen = state.pen();
+
+ QColor penColor = pen.color();
+ if (penColor.alpha() != 255)
+ penColor.setAlpha(255);
+ pen.setColor(penColor);
+
+ QBrush penBrush = pen.brush();
+ removeTransparencyFromBrush(penBrush);
+ pen.setBrush(penBrush);
+
+ d->pen = pen;
+ } else {
+ d->pen = state.pen();
+ }
d->hasPen = d->pen.style() != Qt::NoPen;
d->stroker.setPen(d->pen, state.renderHints());
QBrush penBrush = d->pen.brush();
@@ -1054,7 +1117,13 @@ void QPdfEngine::updateState(const QPaintEngineState &state)
d->stroker.setPen(d->pen, state.renderHints());
}
if (flags & DirtyBrush) {
- d->brush = state.brush();
+ if (d->pdfVersion == QPdfEngine::Version_A1b) {
+ QBrush brush = state.brush();
+ removeTransparencyFromBrush(brush);
+ d->brush = brush;
+ } else {
+ d->brush = state.brush();
+ }
if (d->brush.color().alpha() == 0 && d->brush.style() == Qt::SolidPattern)
d->brush.setStyle(Qt::NoBrush);
d->hasBrush = d->brush.style() != Qt::NoBrush;
@@ -1286,6 +1355,12 @@ int QPdfEngine::resolution() const
return d->resolution;
}
+void QPdfEngine::setPdfVersion(PdfVersion version)
+{
+ Q_D(QPdfEngine);
+ d->pdfVersion = version;
+}
+
void QPdfEngine::setPageLayout(const QPageLayout &pageLayout)
{
Q_D(QPdfEngine);
@@ -1364,6 +1439,7 @@ int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const
QPdfEnginePrivate::QPdfEnginePrivate()
: clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false),
+ pdfVersion(QPdfEngine::Version_1_4),
outDevice(0), ownsDevice(false),
embedFonts(true),
grayscale(false),
@@ -1465,16 +1541,38 @@ void QPdfEnginePrivate::writeHeader()
addXrefEntry(0,false);
xprintf("%%PDF-1.4\n");
+ xprintf("%%\303\242\303\243\n");
writeInfo();
+ int metaDataObj = -1;
+ int outputIntentObj = -1;
+ if (pdfVersion == QPdfEngine::Version_A1b) {
+ metaDataObj = writeXmpMetaData();
+ outputIntentObj = writeOutputIntent();
+ }
+
catalog = addXrefEntry(-1);
pageRoot = requestObject();
- xprintf("<<\n"
- "/Type /Catalog\n"
- "/Pages %d 0 R\n"
- ">>\n"
- "endobj\n", pageRoot);
+
+ // catalog
+ {
+ QByteArray catalog;
+ QPdf::ByteStream s(&catalog);
+ s << "<<\n"
+ << "/Type /Catalog\n"
+ << "/Pages " << pageRoot << "0 R\n";
+
+ if (pdfVersion == QPdfEngine::Version_A1b) {
+ s << "/OutputIntents [" << outputIntentObj << "0 R]\n";
+ s << "/Metadata " << metaDataObj << "0 R\n";
+ }
+
+ s << ">>\n"
+ << "endobj\n";
+
+ write(catalog);
+ }
// graphics state
graphicsState = addXrefEntry(-1);
@@ -1527,6 +1625,95 @@ void QPdfEnginePrivate::writeInfo()
"endobj\n");
}
+int QPdfEnginePrivate::writeXmpMetaData()
+{
+ const int metaDataObj = addXrefEntry(-1);
+
+ const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR));
+
+ const QDateTime now = QDateTime::currentDateTime();
+ const QDate date = now.date();
+ const QTime time = now.time();
+
+ QString timeStr;
+ timeStr.sprintf("%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.sprintf("-%02d:%02d", -hours, -mins);
+ else if (offset > 0)
+ tzStr.sprintf("+%02d:%02d", hours , mins);
+ else
+ tzStr = QLatin1String("Z");
+
+ const QString metaDataDate = timeStr + tzStr;
+
+ QFile metaDataFile(QLatin1String(":/qpdf/qpdfa_metadata.xml"));
+ metaDataFile.open(QIODevice::ReadOnly);
+ const QByteArray metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(),
+ title.toHtmlEscaped(),
+ creator.toHtmlEscaped(),
+ metaDataDate).toUtf8();
+ xprintf("<<\n"
+ "/Type /Metadata /Subtype /XML\n"
+ "/Length %d\n"
+ ">>\n"
+ "stream\n", metaDataContent.size());
+ write(metaDataContent);
+ xprintf("\nendstream\n"
+ "endobj\n");
+
+ return metaDataObj;
+}
+
+int QPdfEnginePrivate::writeOutputIntent()
+{
+ const int colorProfile = addXrefEntry(-1);
+ {
+ QFile colorProfileFile(QLatin1String(":/qpdf/sRGB2014.icc"));
+ colorProfileFile.open(QIODevice::ReadOnly);
+ const QByteArray colorProfileData = colorProfileFile.readAll();
+
+ QByteArray data;
+ QPdf::ByteStream s(&data);
+ int length_object = requestObject();
+
+ s << "<<\n";
+ s << "/N 3\n";
+ s << "/Alternate /DeviceRGB\n";
+ s << "/Length " << length_object << "0 R\n";
+ s << "/Filter /FlateDecode\n";
+ s << ">>\n";
+ s << "stream\n";
+ write(data);
+ const int len = writeCompressed(colorProfileData);
+ write("\nendstream\n"
+ "endobj\n");
+ addXrefEntry(length_object);
+ xprintf("%d\n"
+ "endobj\n", len);
+ }
+
+ const int outputIntent = addXrefEntry(-1);
+ {
+ xprintf("<<\n");
+ xprintf("/Type /OutputIntent\n");
+ xprintf("/S/GTS_PDFA1\n");
+ 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");
+ }
+
+ return outputIntent;
+}
+
void QPdfEnginePrivate::writePageRoot()
{
addXrefEntry(pageRoot);
@@ -1568,6 +1755,7 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font)
int fontstream = requestObject();
int cidfont = requestObject();
int toUnicode = requestObject();
+ int cidset = requestObject();
QFontEngine::Properties properties = font->fontEngine->properties();
QByteArray postscriptName = properties.postscriptName.replace(' ', '_');
@@ -1597,7 +1785,8 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font)
"/CapHeight " << properties.capHeight.toReal()*scale << "\n"
"/StemV " << properties.lineWidth.toReal()*scale << "\n"
"/FontFile2 " << fontstream << "0 R\n"
- ">> endobj\n";
+ "/CIDSet " << cidset << "0 R\n"
+ ">>\nendobj\n";
write(descriptor);
}
{
@@ -1615,7 +1804,7 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font)
"stream\n";
write(header);
int len = writeCompressed(fontData);
- write("endstream\n"
+ write("\nendstream\n"
"endobj\n");
addXrefEntry(length_object);
xprintf("%d\n"
@@ -1642,7 +1831,7 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font)
xprintf("<< /Length %d >>\n"
"stream\n", touc.length());
write(touc);
- write("endstream\n"
+ write("\nendstream\n"
"endobj\n");
}
{
@@ -1659,6 +1848,29 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font)
"endobj\n";
write(font);
}
+ {
+ QByteArray cidSetStream(font->nGlyphs() / 8 + 1, 0);
+ int byteCounter = 0;
+ int bitCounter = 0;
+ for (int i = 0; i < font->nGlyphs(); ++i) {
+ cidSetStream.data()[byteCounter] |= (1 << (7 - bitCounter));
+
+ bitCounter++;
+ if (bitCounter == 8) {
+ bitCounter = 0;
+ byteCounter++;
+ }
+ }
+
+ addXrefEntry(cidset);
+ xprintf("<<\n");
+ xprintf("/Length %d\n", cidSetStream.size());
+ xprintf(">>\n");
+ xprintf("stream\n");
+ write(cidSetStream);
+ xprintf("\nendstream\n");
+ xprintf("endobj\n");
+ }
}
@@ -1748,7 +1960,7 @@ void QPdfEnginePrivate::writePage()
xprintf("stream\n");
QIODevice *content = currentPage->stream();
int len = writeCompressed(content);
- xprintf("endstream\n"
+ xprintf("\nendstream\n"
"endobj\n");
addXrefEntry(pageStreamLength);
@@ -1768,15 +1980,28 @@ void QPdfEnginePrivate::writeTail()
for (int i = 1; i < xrefPositions.size()-1; ++i)
xprintf("%010d 00000 n \n", xrefPositions[i]);
- xprintf("trailer\n"
- "<<\n"
- "/Size %d\n"
- "/Info %d 0 R\n"
- "/Root %d 0 R\n"
- ">>\n"
- "startxref\n%d\n"
- "%%%%EOF\n",
- xrefPositions.size()-1, info, catalog, xrefPositions.constLast());
+ {
+ QByteArray trailer;
+ QPdf::ByteStream s(&trailer);
+
+ s << "trailer\n"
+ << "<<\n"
+ << "/Size " << xrefPositions.size() - 1 << "\n"
+ << "/Info " << info << "0 R\n"
+ << "/Root " << catalog << "0 R\n";
+
+ if (pdfVersion == QPdfEngine::Version_A1b) {
+ const QString uniqueId = QUuid::createUuid().toString();
+ const QByteArray fileIdentifier = QCryptographicHash::hash(uniqueId.toLatin1(), QCryptographicHash::Md5).toHex();
+ s << "/ID [ <" << fileIdentifier << "> <" << fileIdentifier << "> ]\n";
+ }
+
+ s << ">>\n"
+ << "startxref\n" << xrefPositions.constLast() << "\n"
+ << "%%EOF\n";
+
+ write(trailer);
+ }
}
int QPdfEnginePrivate::addXrefEntry(int object, bool printostr)
@@ -1794,7 +2019,13 @@ int QPdfEnginePrivate::addXrefEntry(int object, bool printostr)
return object;
}
-void QPdfEnginePrivate::printString(const QString &string) {
+void QPdfEnginePrivate::printString(const QString &string)
+{
+ if (string.isEmpty()) {
+ write("()");
+ return;
+ }
+
// The 'text string' type in PDF is encoded either as PDFDocEncoding, or
// Unicode UTF-16 with a Unicode byte order mark as the first character
// (0xfeff), with the high-order byte first.
@@ -1976,7 +2207,7 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height,
xprintf(">>\nstream\n");
len = writeCompressed(data);
}
- xprintf("endstream\n"
+ xprintf("\nendstream\n"
"endobj\n");
addXrefEntry(lenobj);
xprintf("%d\n"
@@ -2303,7 +2534,7 @@ int QPdfEnginePrivate::gradientBrush(const QBrush &b, const QTransform &matrix,
">>\n"
"stream\n"
<< content
- << "endstream\n"
+ << "\nendstream\n"
"endobj\n";
int softMaskFormObject = addXrefEntry(-1);
@@ -2412,7 +2643,7 @@ int QPdfEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor,
">>\n"
"stream\n"
<< pattern
- << "endstream\n"
+ << "\nendstream\n"
"endobj\n";
int patternObj = addXrefEntry(-1);
@@ -2443,6 +2674,23 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, qint64 serial_n
QImage image = img;
QImage::Format format = image.format();
+
+ if (pdfVersion == QPdfEngine::Version_A1b) {
+ if (image.hasAlphaChannel()) {
+ // transparent images are not allowed in PDF/A-1b, so we convert it to
+ // a format without alpha channel first
+
+ QImage alphaLessImage(image.width(), image.height(), QImage::Format_RGB32);
+ alphaLessImage.fill(Qt::white);
+
+ QPainter p(&alphaLessImage);
+ p.drawImage(0, 0, image);
+
+ image = alphaLessImage;
+ format = image.format();
+ }
+ }
+
if (image.depth() == 1 && *bitmap && is_monochrome(img.colorTable())) {
if (format == QImage::Format_MonoLSB)
image = image.convertToFormat(QImage::Format_Mono);
@@ -2588,7 +2836,12 @@ void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti)
x2s.setNum(static_cast<double>(x2), 'f');
y2s.setNum(static_cast<double>(y2), 'f');
QByteArray rectData = x1s + ' ' + y1s + ' ' + x2s + ' ' + y2s;
- xprintf("<<\n/Type /Annot\n/Subtype /Link\n/Rect [");
+ xprintf("<<\n/Type /Annot\n/Subtype /Link\n");
+
+ if (pdfVersion == QPdfEngine::Version_A1b)
+ xprintf("/F 4\n"); // enable print flag, disable all other
+
+ xprintf("/Rect [");
xprintf(rectData.constData());
#ifdef Q_DEBUG_PDF_LINKS
xprintf("]\n/Border [16 16 1]\n/A <<\n");