8#include "qplatformdefs.h"
10#include <private/qcmyk_p.h>
11#include <private/qfont_p.h>
12#include <private/qmath_p.h>
13#include <private/qpainter_p.h>
16#include <qcryptographichash.h>
20#include <qimagewriter.h>
22#include <qtemporaryfile.h>
25#include <qxmlstream.h>
35static const bool do_compress =
false;
46 Q_INIT_RESOURCE(qpdf);
51using namespace Qt::StringLiterals;
55 QPaintEngine::PaintEngineFeatures f = QPaintEngine::AllFeatures;
56 f &= ~(QPaintEngine::PorterDuff
57 | QPaintEngine::PerspectiveTransform
58 | QPaintEngine::ObjectBoundingModeGradients
59 | QPaintEngine::ConicalGradientFill);
68 if (brush.style() == Qt::SolidPattern) {
69 QColor color = brush.color();
70 if (color.alpha() != 255) {
72 brush.setColor(color);
78 if (qt_isExtendedRadialGradient(brush)) {
79 brush = QBrush(Qt::black);
83 if (brush.style() == Qt::LinearGradientPattern
84 || brush.style() == Qt::RadialGradientPattern
85 || brush.style() == Qt::ConicalGradientPattern) {
87 QGradientStops stops = brush.gradient()->stops();
88 for (
int i = 0; i < stops.size(); ++i) {
89 if (stops[i].second.alpha() != 255)
90 stops[i].second.setAlpha(255);
93 const_cast<QGradient*>(brush.gradient())->setStops(stops);
97 if (brush.style() == Qt::TexturePattern) {
106 const char *ret = buf;
108 if (!qIsFinite(val) || std::abs(val) > std::numeric_limits<quint32>::max()) {
119 qreal frac =
std::modf(val, &val);
122 int ifrac = (
int)(frac * 1000000000);
123 if (ifrac == 1000000000) {
130 output[i] =
'0' + (ival % 10);
134 int fact = 100000000;
139 *(buf++) = output[--i];
148 *(buf++) =
'0' + ((ifrac/fact) % 10);
158 const char *ret = buf;
166 output[i] =
'0' + (val % 10);
174 *(buf++) = output[--i];
185 fileBackingEnabled(fileBacking),
186 fileBackingActive(
false),
189 dev->open(QIODevice::ReadWrite | QIODevice::Append);
194 fileBackingEnabled(fileBacking),
195 fileBackingActive(
false),
198 dev->open(QIODevice::ReadWrite);
208 if (handleDirty) prepareBuffer();
215 if (handleDirty) prepareBuffer();
216 dev->write(str, strlen(str));
222 if (handleDirty) prepareBuffer();
229 Q_ASSERT(!src.dev->isSequential());
230 if (handleDirty) prepareBuffer();
234 qint64 pos = s.dev->pos();
236 while (!s.dev->atEnd()) {
246 qt_real_to_string(val, buf);
260 qt_real_to_string(p.x(), buf);
262 qt_real_to_string(p.y(), buf);
276 dev->open(QIODevice::ReadWrite | QIODevice::Truncate);
281 Q_ASSERT(!dev->isSequential());
282 qint64 size = dev->size();
283 if (fileBackingEnabled && !fileBackingActive
286 QTemporaryFile *newFile =
new QTemporaryFile;
287 if (newFile->open()) {
289 while (!dev->atEnd()) {
290 QByteArray buf = dev->read(chunkSize());
296 fileBackingActive =
true;
299 if (dev->pos() != size) {
306#define QT_PATH_ELEMENT(elm)
311 if (!path.elementCount())
317 for (
int i = 0; i < path.elementCount(); ++i) {
318 const QPainterPath::Element &elm = path.elementAt(i);
320 case QPainterPath::MoveToElement:
322 && path.elementAt(start).x == path.elementAt(i-1).x
323 && path.elementAt(start).y == path.elementAt(i-1).y)
325 s << matrix.map(QPointF(elm.x, elm.y)) <<
"m\n";
328 case QPainterPath::LineToElement:
329 s << matrix.map(QPointF(elm.x, elm.y)) <<
"l\n";
331 case QPainterPath::CurveToElement:
332 Q_ASSERT(path.elementAt(i+1).type == QPainterPath::CurveToDataElement);
333 Q_ASSERT(path.elementAt(i+2).type == QPainterPath::CurveToDataElement);
334 s << matrix.map(QPointF(elm.x, elm.y))
335 << matrix.map(QPointF(path.elementAt(i+1).x, path.elementAt(i+1).y))
336 << matrix.map(QPointF(path.elementAt(i+2).x, path.elementAt(i+2).y))
341 qFatal(
"QPdf::generatePath(), unhandled type: %d", elm.type);
345 && path.elementAt(start).x == path.elementAt(path.elementCount()-1).x
346 && path.elementAt(start).y == path.elementAt(path.elementCount()-1).y)
349 Qt::FillRule fillRule = path.fillRule();
354 op = (fillRule == Qt::WindingFill) ?
"W n\n" :
"W* n\n";
357 op = (fillRule == Qt::WindingFill) ?
"f\n" :
"f*\n";
362 case FillAndStrokePath:
363 op = (fillRule == Qt::WindingFill) ?
"B\n" :
"B*\n";
390 QList<qreal> dasharray = pen.dashPattern();
391 qreal w = pen.widthF();
394 for (
int i = 0; i < dasharray.size(); ++i) {
395 qreal dw = dasharray.at(i)*w;
396 if (dw < 0.0001) dw = 0.0001;
400 s << pen.dashOffset() * w;
562 int style = b.style();
563 if (style > Qt::DiagCrossPattern)
575 t->matrix.map(x, y, &x, &y);
584 t->matrix.map(x, y, &x, &y);
589 qfixed c2x, qfixed c2y,
590 qfixed ex, qfixed ey,
595 t->matrix.map(c1x, c1y, &c1x, &c1y);
596 t->matrix.map(c2x, c2y, &c2x, &c2y);
597 t->matrix.map(ex, ey, &ex, &ey);
610 stroker = &basicStroker;
611 basicStroker.setMoveToHook(moveToHook);
612 basicStroker.setLineToHook(lineToHook);
613 basicStroker.setCubicToHook(cubicToHook);
615 basicStroker.setStrokeWidth(.1);
620 if (pen.style() == Qt::NoPen) {
624 qreal w = pen.widthF();
625 bool zeroWidth = w < 0.0001;
630 basicStroker.setStrokeWidth(w);
631 basicStroker.setCapStyle(pen.capStyle());
632 basicStroker.setJoinStyle(pen.joinStyle());
633 basicStroker.setMiterLimit(pen.miterLimit());
635 QList<qreal> dashpattern = pen.dashPattern();
637 for (
int i = 0; i < dashpattern.size(); ++i)
638 dashpattern[i] *= 10.;
640 if (!dashpattern.isEmpty()) {
641 dashStroker.setDashPattern(dashpattern);
642 dashStroker.setDashOffset(pen.dashOffset());
643 stroker = &dashStroker;
645 stroker = &basicStroker;
655 stroker->strokePath(path,
this, cosmeticPen ? matrix : QTransform());
661 int isize = input.size()/4*4;
663 output.resize(input.size()*5/4+7);
664 char *out = output.data();
665 const uchar *in = (
const uchar *)input.constData();
666 for (
int i = 0; i < isize; i += 4) {
667 uint val = (((uint)in[i])<<24) + (((uint)in[i+1])<<16) + (((uint)in[i+2])<<8) + (uint)in[i+3];
682 *(out++) = base[0] +
'!';
683 *(out++) = base[1] +
'!';
684 *(out++) = base[2] +
'!';
685 *(out++) = base[3] +
'!';
686 *(out++) = base[4] +
'!';
690 int remaining = input.size() - isize;
693 for (
int i = isize; i < input.size(); ++i)
694 val = (val << 8) + in[i];
695 val <<= 8*(4-remaining);
706 for (
int i = 0; i < remaining+1; ++i)
707 *(out++) = base[i] +
'!';
711 output.resize(out-output.data());
715const char *
QPdf::toHex(ushort u,
char *buffer)
719 ushort hex = (u & 0x000f);
723 buffer[i] =
'A'+(hex-0x0a);
735 ushort hex = (u & 0x000f);
739 buffer[i] =
'A'+(hex-0x0a);
753void QPdfPage::streamImage(
int w,
int h, uint object)
755 *
this << w <<
"0 0 " << -h <<
"0 " << h <<
"cm /Im" << object <<
" Do\n";
756 if (!images.contains(object))
757 images.append(object);
761QPdfEngine::QPdfEngine(QPdfEnginePrivate &dd)
762 : QPaintEngine(dd, qt_pdf_decide_features())
766QPdfEngine::QPdfEngine()
767 : QPaintEngine(*
new QPdfEnginePrivate(), qt_pdf_decide_features())
771void QPdfEngine::setOutputFilename(
const QString &filename)
774 d->outputFileName = filename;
778void QPdfEngine::drawPoints (
const QPointF *points,
int pointCount)
785 for (
int i=0; i!=pointCount;++i) {
787 p.lineTo(points[i] + QPointF(0, 0.001));
790 bool hadBrush = d->hasBrush;
793 d->hasBrush = hadBrush;
796void QPdfEngine::drawLines (
const QLineF *lines,
int lineCount)
803 for (
int i=0; i!=lineCount;++i) {
804 p.moveTo(lines[i].p1());
805 p.lineTo(lines[i].p2());
807 bool hadBrush = d->hasBrush;
810 d->hasBrush = hadBrush;
813void QPdfEngine::drawRects (
const QRectF *rects,
int rectCount)
820 if (d->clipEnabled && d->allClipped)
822 if (!d->hasPen && !d->hasBrush)
825 if ((d->simplePen && !d->needsTransform) || !d->hasPen) {
827 if (!d->hasPen && d->needsTransform)
828 *d->currentPage <<
"q\n" << QPdf::generateMatrix(d->stroker.matrix);
829 for (
int i = 0; i < rectCount; ++i)
830 *d->currentPage << rects[i].x() << rects[i].y() << rects[i].width() << rects[i].height() <<
"re\n";
831 *d->currentPage << (d->hasPen ? (d->hasBrush ?
"B\n" :
"S\n") :
"f\n");
832 if (!d->hasPen && d->needsTransform)
833 *d->currentPage <<
"Q\n";
836 for (
int i=0; i!=rectCount; ++i)
842void QPdfEngine::drawPolygon(
const QPointF *points,
int pointCount, PolygonDrawMode mode)
846 if (!points || !pointCount)
849 bool hb = d->hasBrush;
854 p.setFillRule(Qt::OddEvenFill);
858 p.setFillRule(Qt::WindingFill);
868 for (
int i = 1; i < pointCount; ++i)
871 if (mode != PolylineMode)
878void QPdfEngine::drawPath (
const QPainterPath &p)
882 if (d->clipEnabled && d->allClipped)
884 if (!d->hasPen && !d->hasBrush)
889 *d->currentPage << QPdf::generatePath(p, d->needsTransform ? d->stroker.matrix : QTransform(),
890 d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath);
893 *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath);
895 *d->currentPage <<
"q\n";
897 d->brush = d->pen.brush();
899 d->stroker.strokePath(p);
900 *d->currentPage <<
"Q\n";
906void QPdfEngine::drawPixmap (
const QRectF &rectangle,
const QPixmap &pixmap,
const QRectF &sr)
908 if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull())
914 QRect sourceRect = sr.toRect();
915 QPixmap pm = sourceRect != pixmap.rect() ? pixmap.copy(sourceRect) : pixmap;
916 QImage image = pm.toImage();
918 const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering);
919 const int object = d->addImage(image, &bitmap, lossless, pm.cacheKey());
923 *d->currentPage <<
"q\n";
925 if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) {
926 int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity));
928 *d->currentPage <<
"/GState" << stateObject <<
"gs\n";
930 *d->currentPage <<
"/GSa gs\n";
932 *d->currentPage <<
"/GSa gs\n";
936 << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(),
937 rectangle.x(), rectangle.y()) * (!d->needsTransform ? QTransform() : d->stroker.matrix));
940 d->brush = d->pen.brush();
943 d->currentPage->streamImage(image.width(), image.height(), object);
944 *d->currentPage <<
"Q\n";
949void QPdfEngine::drawImage(
const QRectF &rectangle,
const QImage &image,
const QRectF &sr, Qt::ImageConversionFlags)
951 if (sr.isEmpty() || rectangle.isEmpty() || image.isNull())
955 QRect sourceRect = sr.toRect();
956 QImage im = sourceRect != image.rect() ? image.copy(sourceRect) : image;
958 const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering);
959 const int object = d->addImage(im, &bitmap, lossless, im.cacheKey());
963 *d->currentPage <<
"q\n";
965 if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) {
966 int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity));
968 *d->currentPage <<
"/GState" << stateObject <<
"gs\n";
970 *d->currentPage <<
"/GSa gs\n";
972 *d->currentPage <<
"/GSa gs\n";
976 << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(),
977 rectangle.x(), rectangle.y()) * (!d->needsTransform ? QTransform() : d->stroker.matrix));
979 d->currentPage->streamImage(im.width(), im.height(), object);
980 *d->currentPage <<
"Q\n";
983void QPdfEngine::drawTiledPixmap (
const QRectF &rectangle,
const QPixmap &pixmap,
const QPointF &point)
987 bool bitmap = (pixmap.depth() == 1);
989 QPointF bo = d->brushOrigin;
992 bool hb = d->hasBrush;
995 d->brush = QBrush(pixmap);
998 d->brush.setColor(d->pen.color());
1000 d->brushOrigin = -point;
1001 *d->currentPage <<
"q\n";
1004 drawRects(&rectangle, 1);
1005 *d->currentPage <<
"Q\n";
1010 d->brushOrigin = bo;
1013void QPdfEngine::drawTextItem(
const QPointF &p,
const QTextItem &textItem)
1017 if (!d->hasPen || (d->clipEnabled && d->allClipped))
1020 if (d->stroker.matrix.type() >= QTransform::TxProject) {
1021 QPaintEngine::drawTextItem(p, textItem);
1025 *d->currentPage <<
"q\n";
1026 if (d->needsTransform)
1027 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1029 bool hp = d->hasPen;
1031 QBrush b = d->brush;
1032 d->brush = d->pen.brush();
1035 const QTextItemInt &ti =
static_cast<
const QTextItemInt &>(textItem);
1036 Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi);
1037 d->drawTextItem(p, ti);
1040 *d->currentPage <<
"Q\n";
1044void QPdfEngine::drawHyperlink(
const QRectF &r,
const QUrl &url)
1051 if (d->pdfVersion == QPdfEngine::Version_X4)
1054 const uint annot = d->addXrefEntry(-1);
1055 const QByteArray urlascii = url.toEncoded();
1056 int len = urlascii.size();
1057 QVarLengthArray<
char> url_esc;
1058 url_esc.reserve(len + 1);
1059 for (
int j = 0; j < len; j++) {
1060 if (urlascii[j] ==
'(' || urlascii[j] ==
')' || urlascii[j] ==
'\\')
1061 url_esc.append(
'\\');
1062 url_esc.append(urlascii[j]);
1064 url_esc.append(
'\0');
1067 const QRectF rr = d->pageMatrix().mapRect(r);
1068 d->xprintf(
"<<\n/Type /Annot\n/Subtype /Link\n");
1070 if (d->pdfVersion == QPdfEngine::Version_A1b)
1071 d->xprintf(
"/F 4\n");
1073 d->xprintf(
"/Rect [");
1074 d->xprintf(
"%s ", qt_real_to_string(rr.left(), buf));
1075 d->xprintf(
"%s ", qt_real_to_string(rr.top(), buf));
1076 d->xprintf(
"%s ", qt_real_to_string(rr.right(), buf));
1077 d->xprintf(
"%s", qt_real_to_string(rr.bottom(), buf));
1078 d->xprintf(
"]\n/Border [0 0 0]\n/A <<\n");
1079 d->xprintf(
"/Type /Action\n/S /URI\n/URI (%s)\n", url_esc.constData());
1080 d->xprintf(
">>\n>>\n");
1081 d->xprintf(
"endobj\n");
1082 d->currentPage->annotations.append(annot);
1085void QPdfEngine::updateState(
const QPaintEngineState &state)
1089 QPaintEngine::DirtyFlags flags = state.state();
1091 if (flags & DirtyHints)
1092 flags |= DirtyBrush;
1094 if (flags & DirtyTransform)
1095 d->stroker.matrix = state.transform();
1097 if (flags & DirtyPen) {
1098 if (d->pdfVersion == QPdfEngine::Version_A1b) {
1099 QPen pen = state.pen();
1101 QColor penColor = pen.color();
1102 if (penColor.alpha() != 255)
1103 penColor.setAlpha(255);
1104 pen.setColor(penColor);
1106 QBrush penBrush = pen.brush();
1107 removeTransparencyFromBrush(penBrush);
1108 pen.setBrush(penBrush);
1112 d->pen = state.pen();
1114 d->hasPen = d->pen.style() != Qt::NoPen;
1115 bool oldCosmetic = d->stroker.cosmeticPen;
1116 d->stroker.setPen(d->pen, state.renderHints());
1117 QBrush penBrush = d->pen.brush();
1118 bool oldSimple = d->simplePen;
1119 d->simplePen = (d->hasPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque() && d->opacity == 1.0);
1120 if (oldSimple != d->simplePen || oldCosmetic != d->stroker.cosmeticPen)
1121 flags |= DirtyTransform;
1122 }
else if (flags & DirtyHints) {
1123 d->stroker.setPen(d->pen, state.renderHints());
1125 if (flags & DirtyBrush) {
1126 if (d->pdfVersion == QPdfEngine::Version_A1b) {
1127 QBrush brush = state.brush();
1128 removeTransparencyFromBrush(brush);
1131 d->brush = state.brush();
1133 if (d->brush.color().alpha() == 0 && d->brush.style() == Qt::SolidPattern)
1134 d->brush.setStyle(Qt::NoBrush);
1135 d->hasBrush = d->brush.style() != Qt::NoBrush;
1137 if (flags & DirtyBrushOrigin) {
1138 d->brushOrigin = state.brushOrigin();
1139 flags |= DirtyBrush;
1141 if (flags & DirtyOpacity) {
1142 d->opacity = state.opacity();
1143 if (d->simplePen && d->opacity != 1.0) {
1144 d->simplePen =
false;
1145 flags |= DirtyTransform;
1149 bool ce = d->clipEnabled;
1150 if (flags & DirtyClipPath) {
1151 d->clipEnabled =
true;
1152 updateClipPath(state.clipPath(), state.clipOperation());
1153 }
else if (flags & DirtyClipRegion) {
1154 d->clipEnabled =
true;
1156 for (
const QRect &rect : state.clipRegion())
1158 updateClipPath(path, state.clipOperation());
1159 flags |= DirtyClipPath;
1160 }
else if (flags & DirtyClipEnabled) {
1161 d->clipEnabled = state.isClipEnabled();
1164 if (ce != d->clipEnabled)
1165 flags |= DirtyClipPath;
1166 else if (!d->clipEnabled)
1167 flags &= ~DirtyClipPath;
1169 setupGraphicsState(flags);
1172void QPdfEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags)
1175 if (flags & DirtyClipPath)
1176 flags |= DirtyTransform|DirtyPen|DirtyBrush;
1178 if (flags & DirtyTransform) {
1179 *d->currentPage <<
"Q\n";
1180 flags |= DirtyPen|DirtyBrush;
1183 if (flags & DirtyClipPath) {
1184 *d->currentPage <<
"Q q\n";
1186 d->allClipped =
false;
1187 if (d->clipEnabled && !d->clips.isEmpty()) {
1188 for (
int i = 0; i < d->clips.size(); ++i) {
1189 if (d->clips.at(i).isEmpty()) {
1190 d->allClipped =
true;
1194 if (!d->allClipped) {
1195 for (
int i = 0; i < d->clips.size(); ++i) {
1196 *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath);
1202 if (flags & DirtyTransform) {
1203 *d->currentPage <<
"q\n";
1204 d->needsTransform =
false;
1205 if (!d->stroker.matrix.isIdentity()) {
1206 if (d->simplePen && !d->stroker.cosmeticPen)
1207 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1209 d->needsTransform =
true;
1212 if (flags & DirtyBrush)
1214 if (d->simplePen && (flags & DirtyPen))
1218void QPdfEngine::updateClipPath(
const QPainterPath &p, Qt::ClipOperation op)
1221 QPainterPath path = d->stroker.matrix.map(p);
1226 d->clipEnabled =
false;
1229 case Qt::ReplaceClip:
1231 d->clips.append(path);
1233 case Qt::IntersectClip:
1234 d->clips.append(path);
1239void QPdfEngine::setPen()
1242 if (d->pen.style() == Qt::NoPen)
1244 QBrush b = d->pen.brush();
1245 Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque());
1247 d->writeColor(QPdfEnginePrivate::ColorDomain::Stroking, b.color());
1248 *d->currentPage <<
"SCN\n";
1249 *d->currentPage << d->pen.widthF() <<
"w ";
1251 int pdfCapStyle = 0;
1252 switch(d->pen.capStyle()) {
1265 *d->currentPage << pdfCapStyle <<
"J ";
1267 int pdfJoinStyle = 0;
1268 switch(d->pen.joinStyle()) {
1270 case Qt::SvgMiterJoin:
1271 *d->currentPage << qMax(qreal(1.0), d->pen.miterLimit()) <<
"M ";
1283 *d->currentPage << pdfJoinStyle <<
"j ";
1285 *d->currentPage << QPdf::generateDashes(d->pen);
1289void QPdfEngine::setBrush()
1292 Qt::BrushStyle style = d->brush.style();
1293 if (style == Qt::NoBrush)
1297 int gStateObject = 0;
1298 int patternObject = d->addBrushPattern(d->stroker.matrix, &specifyColor, &gStateObject);
1299 if (!patternObject && !specifyColor)
1302 const auto domain = patternObject ? QPdfEnginePrivate::ColorDomain::NonStrokingPattern
1303 : QPdfEnginePrivate::ColorDomain::NonStroking;
1304 d->writeColor(domain, specifyColor ? d->brush.color() : QColor());
1306 *d->currentPage <<
"/Pat" << patternObject;
1307 *d->currentPage <<
"scn\n";
1310 *d->currentPage <<
"/GState" << gStateObject <<
"gs\n";
1312 *d->currentPage <<
"/GSa gs\n";
1316bool QPdfEngine::newPage()
1323 setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath);
1324 QFile *outfile = qobject_cast<QFile*> (d->outDevice);
1325 if (outfile && outfile->error() != QFile::NoError)
1330QPaintEngine::Type QPdfEngine::type()
const
1332 return QPaintEngine::Pdf;
1335void QPdfEngine::setResolution(
int resolution)
1338 d->resolution = resolution;
1341int QPdfEngine::resolution()
const
1343 Q_D(
const QPdfEngine);
1344 return d->resolution;
1347void QPdfEngine::setPdfVersion(PdfVersion version)
1350 d->pdfVersion = version;
1353void QPdfEngine::setDocumentXmpMetadata(
const QByteArray &xmpMetadata)
1356 d->xmpDocumentMetadata = xmpMetadata;
1359QByteArray QPdfEngine::documentXmpMetadata()
const
1361 Q_D(
const QPdfEngine);
1362 return d->xmpDocumentMetadata;
1365void QPdfEngine::setPageLayout(
const QPageLayout &pageLayout)
1368 d->m_pageLayout = pageLayout;
1371void QPdfEngine::setPageSize(
const QPageSize &pageSize)
1374 d->m_pageLayout.setPageSize(pageSize);
1377void QPdfEngine::setPageOrientation(QPageLayout::Orientation orientation)
1380 d->m_pageLayout.setOrientation(orientation);
1383void QPdfEngine::setPageMargins(
const QMarginsF &margins, QPageLayout::Unit units)
1386 d->m_pageLayout.setUnits(units);
1387 d->m_pageLayout.setMargins(margins);
1390QPageLayout QPdfEngine::pageLayout()
const
1392 Q_D(
const QPdfEngine);
1393 return d->m_pageLayout;
1397int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType)
const
1399 Q_D(
const QPdfEngine);
1401 switch (metricType) {
1402 case QPaintDevice::PdmWidth:
1403 val = d->m_pageLayout.paintRectPixels(d->resolution).width();
1405 case QPaintDevice::PdmHeight:
1406 val = d->m_pageLayout.paintRectPixels(d->resolution).height();
1408 case QPaintDevice::PdmDpiX:
1409 case QPaintDevice::PdmDpiY:
1410 val = d->resolution;
1412 case QPaintDevice::PdmPhysicalDpiX:
1413 case QPaintDevice::PdmPhysicalDpiY:
1416 case QPaintDevice::PdmWidthMM:
1417 val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).width());
1419 case QPaintDevice::PdmHeightMM:
1420 val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).height());
1422 case QPaintDevice::PdmNumColors:
1425 case QPaintDevice::PdmDepth:
1428 case QPaintDevice::PdmDevicePixelRatio:
1431 case QPaintDevice::PdmDevicePixelRatioScaled:
1432 val = 1 * QPaintDevice::devicePixelRatioFScale();
1435 qWarning(
"QPdfWriter::metric: Invalid metric command");
1441QPdfEnginePrivate::QPdfEnginePrivate()
1442 : clipEnabled(
false), allClipped(
false), hasPen(
true), hasBrush(
false), simplePen(
false),
1443 needsTransform(
false), pdfVersion(QPdfEngine::Version_1_4),
1444 colorModel(QPdfEngine::ColorModel::Auto),
1445 outDevice(
nullptr), ownsDevice(
false),
1447 m_pageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(10, 10, 10, 10))
1452 currentPage =
nullptr;
1453 stroker.stream =
nullptr;
1457 stream =
new QDataStream;
1460bool QPdfEngine::begin(QPaintDevice *pdev)
1465 if (!d->outDevice) {
1466 if (!d->outputFileName.isEmpty()) {
1467 QFile *file =
new QFile(d->outputFileName);
1468 if (!file->open(QFile::WriteOnly|QFile::Truncate)) {
1472 d->outDevice = file;
1476 d->ownsDevice =
true;
1479 d->currentObject = 1;
1481 d->currentPage =
new QPdfPage;
1482 d->stroker.stream = d->currentPage;
1485 d->stream->setDevice(d->outDevice);
1489 d->hasBrush =
false;
1490 d->clipEnabled =
false;
1491 d->allClipped =
false;
1493 d->xrefPositions.clear();
1497 d->attachmentsRoot = 0;
1500 d->graphicsState = 0;
1501 d->patternColorSpaceRGB = 0;
1502 d->patternColorSpaceGrayscale = 0;
1503 d->patternColorSpaceCMYK = 0;
1504 d->simplePen =
false;
1505 d->needsTransform =
false;
1508 d->imageCache.clear();
1509 d->alphaCache.clear();
1518bool QPdfEngine::end()
1523 d->stream->setDevice(
nullptr);
1525 qDeleteAll(d->fonts);
1527 delete d->currentPage;
1528 d->currentPage =
nullptr;
1530 if (d->outDevice && d->ownsDevice) {
1531 d->outDevice->close();
1532 delete d->outDevice;
1533 d->outDevice =
nullptr;
1536 d->destCache.clear();
1537 d->fileCache.clear();
1543void QPdfEngine::addFileAttachment(
const QString &fileName,
const QByteArray &data,
const QString &mimeType)
1546 d->fileCache.push_back({fileName, data, mimeType});
1549QPdfEnginePrivate::~QPdfEnginePrivate()
1556void QPdfEnginePrivate::writeHeader()
1558 addXrefEntry(0,
false);
1561 static const char mapping[][4] = {
1567 static const size_t numMappings =
sizeof mapping /
sizeof *mapping;
1568 const char *verStr = mapping[size_t(pdfVersion) < numMappings ? pdfVersion : 0];
1570 xprintf(
"%%PDF-%s\n", verStr);
1571 xprintf(
"%%\303\242\303\243\n");
1573#if QT_CONFIG(timezone)
1574 const QDateTime now = QDateTime::currentDateTime(QTimeZone::systemTimeZone());
1576 const QDateTime now = QDateTime::currentDateTimeUtc();
1581 const int metaDataObj = writeXmpDocumentMetaData(now);
1582 const int outputIntentObj = [&]() {
1583 switch (pdfVersion) {
1584 case QPdfEngine::Version_1_4:
1585 case QPdfEngine::Version_1_6:
1587 case QPdfEngine::Version_A1b:
1588 case QPdfEngine::Version_X4:
1589 return writeOutputIntent();
1595 catalog = addXrefEntry(-1);
1596 pageRoot = requestObject();
1597 namesRoot = requestObject();
1602 QPdf::ByteStream s(&catalog);
1604 <<
"/Type /Catalog\n"
1605 <<
"/Pages " << pageRoot <<
"0 R\n"
1606 <<
"/Names " << namesRoot <<
"0 R\n";
1608 s <<
"/Metadata " << metaDataObj <<
"0 R\n";
1610 if (outputIntentObj >= 0)
1611 s <<
"/OutputIntents [" << outputIntentObj <<
"0 R]\n";
1620 graphicsState = addXrefEntry(-1);
1622 "/Type /ExtGState\n"
1633 patternColorSpaceRGB = addXrefEntry(-1);
1634 xprintf(
"[/Pattern /DeviceRGB]\n"
1636 patternColorSpaceGrayscale = addXrefEntry(-1);
1637 xprintf(
"[/Pattern /DeviceGray]\n"
1639 patternColorSpaceCMYK = addXrefEntry(-1);
1640 xprintf(
"[/Pattern /DeviceCMYK]\n"
1644QPdfEngine::ColorModel QPdfEnginePrivate::colorModelForColor(
const QColor &color)
const
1646 switch (colorModel) {
1647 case QPdfEngine::ColorModel::RGB:
1648 case QPdfEngine::ColorModel::Grayscale:
1649 case QPdfEngine::ColorModel::CMYK:
1651 case QPdfEngine::ColorModel::Auto:
1652 switch (color.spec()) {
1653 case QColor::Invalid:
1657 case QColor::ExtendedRgb:
1658 return QPdfEngine::ColorModel::RGB;
1660 return QPdfEngine::ColorModel::CMYK;
1666 Q_UNREACHABLE_RETURN(QPdfEngine::ColorModel::RGB);
1669void QPdfEnginePrivate::writeColor(ColorDomain domain,
const QColor &color)
1673 const QPdfEngine::ColorModel actualColorModel = colorModelForColor(color);
1675 switch (actualColorModel) {
1676 case QPdfEngine::ColorModel::RGB:
1678 case ColorDomain::Stroking:
1679 *currentPage <<
"/CSp CS\n";
break;
1680 case ColorDomain::NonStroking:
1681 *currentPage <<
"/CSp cs\n";
break;
1682 case ColorDomain::NonStrokingPattern:
1683 *currentPage <<
"/PCSp cs\n";
break;
1686 case QPdfEngine::ColorModel::Grayscale:
1688 case ColorDomain::Stroking:
1689 *currentPage <<
"/CSpg CS\n";
break;
1690 case ColorDomain::NonStroking:
1691 *currentPage <<
"/CSpg cs\n";
break;
1692 case ColorDomain::NonStrokingPattern:
1693 *currentPage <<
"/PCSpg cs\n";
break;
1696 case QPdfEngine::ColorModel::CMYK:
1698 case ColorDomain::Stroking:
1699 *currentPage <<
"/CSpcmyk CS\n";
break;
1700 case ColorDomain::NonStroking:
1701 *currentPage <<
"/CSpcmyk cs\n";
break;
1702 case ColorDomain::NonStrokingPattern:
1703 *currentPage <<
"/PCSpcmyk cs\n";
break;
1706 case QPdfEngine::ColorModel::Auto:
1707 Q_UNREACHABLE_RETURN();
1711 if (!color.isValid())
1714 switch (actualColorModel) {
1715 case QPdfEngine::ColorModel::RGB:
1716 *currentPage << color.redF()
1720 case QPdfEngine::ColorModel::Grayscale: {
1721 const qreal gray = qGray(color.rgba()) / 255.;
1722 *currentPage << gray;
1725 case QPdfEngine::ColorModel::CMYK:
1726 *currentPage << color.cyanF()
1731 case QPdfEngine::ColorModel::Auto:
1732 Q_UNREACHABLE_RETURN();
1736void QPdfEnginePrivate::writeInfo(
const QDateTime &date)
1738 info = addXrefEntry(-1);
1739 write(
"<<\n/Title ");
1741 write(
"\n/Creator ");
1742 printString(creator);
1743 write(
"\n/Author ");
1744 printString(author);
1745 write(
"\n/Producer ");
1746 printString(QString::fromLatin1(
"Qt " QT_VERSION_STR));
1748 const QTime t = date.time();
1749 const QDate d = date.date();
1751 constexpr size_t formattedDateSize = 26;
1752 char formattedDate[formattedDateSize];
1753 const int year = qBound(0, d.year(), 9999);
1754 auto printedSize = std::snprintf(formattedDate,
1756 "(D:%04d%02d%02d%02d%02d%02d",
1763 const int offset = date.offsetFromUtc();
1764 const int hours = (offset / 60) / 60;
1765 const int mins = (offset / 60) % 60;
1767 std::snprintf(formattedDate + printedSize,
1768 formattedDateSize - printedSize,
1769 "-%02d'%02d')", -hours, -mins);
1770 }
else if (offset > 0) {
1771 std::snprintf(formattedDate + printedSize,
1772 formattedDateSize - printedSize,
1773 "+%02d'%02d')", hours, mins);
1775 std::snprintf(formattedDate + printedSize,
1776 formattedDateSize - printedSize,
1780 write(
"\n/CreationDate ");
1781 write(formattedDate);
1782 write(
"\n/ModDate ");
1783 write(formattedDate);
1785 write(
"\n/Trapped /False\n"
1790int QPdfEnginePrivate::writeXmpDocumentMetaData(
const QDateTime &date)
1792 const int metaDataObj = addXrefEntry(-1);
1793 QByteArray metaDataContent;
1795 if (!xmpDocumentMetadata.isEmpty()) {
1796 metaDataContent = xmpDocumentMetadata;
1798 const QString producer(QString::fromLatin1(
"Qt " QT_VERSION_STR));
1799 const QString metaDataDate = date.toString(Qt::ISODate);
1801 using namespace Qt::Literals;
1802 constexpr QLatin1String xmlNS =
"http://www.w3.org/XML/1998/namespace"_L1;
1804 constexpr QLatin1String adobeNS =
"adobe:ns:meta/"_L1;
1805 constexpr QLatin1String rdfNS =
"http://www.w3.org/1999/02/22-rdf-syntax-ns#"_L1;
1806 constexpr QLatin1String dcNS =
"http://purl.org/dc/elements/1.1/"_L1;
1807 constexpr QLatin1String xmpNS =
"http://ns.adobe.com/xap/1.0/"_L1;
1808 constexpr QLatin1String xmpMMNS =
"http://ns.adobe.com/xap/1.0/mm/"_L1;
1809 constexpr QLatin1String pdfNS =
"http://ns.adobe.com/pdf/1.3/"_L1;
1810 constexpr QLatin1String pdfaidNS =
"http://www.aiim.org/pdfa/ns/id/"_L1;
1811 constexpr QLatin1String pdfxidNS =
"http://www.npes.org/pdfx/ns/id/"_L1;
1813 QBuffer output(&metaDataContent);
1814 output.open(QIODevice::WriteOnly);
1815 output.write(
"<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>");
1817 QXmlStreamWriter w(&output);
1818 w.setAutoFormatting(
true);
1819 w.writeNamespace(adobeNS,
"x");
1820 w.writeNamespace(rdfNS,
"rdf");
1821 w.writeNamespace(dcNS,
"dc");
1822 w.writeNamespace(xmpNS,
"xmp");
1823 w.writeNamespace(xmpMMNS,
"xmpMM");
1824 w.writeNamespace(pdfNS,
"pdf");
1825 w.writeNamespace(pdfaidNS,
"pdfaid");
1826 w.writeNamespace(pdfxidNS,
"pdfxid");
1828 w.writeStartElement(adobeNS,
"xmpmeta");
1829 w.writeStartElement(rdfNS,
"RDF");
1832
1833
1834
1835
1836
1837
1840 w.writeStartElement(rdfNS,
"Description");
1841 w.writeAttribute(rdfNS,
"about",
"");
1842 w.writeStartElement(dcNS,
"title");
1843 w.writeStartElement(rdfNS,
"Alt");
1844 w.writeStartElement(rdfNS,
"li");
1845 w.writeAttribute(xmlNS,
"lang",
"x-default");
1846 w.writeCharacters(title);
1847 w.writeEndElement();
1848 w.writeEndElement();
1849 w.writeEndElement();
1850 w.writeStartElement(dcNS,
"creator");
1851 w.writeStartElement(rdfNS,
"Seq");
1852 w.writeStartElement(rdfNS,
"li");
1853 w.writeCharacters(author);
1854 w.writeEndElement();
1855 w.writeEndElement();
1856 w.writeEndElement();
1857 w.writeEndElement();
1860 w.writeStartElement(rdfNS,
"Description");
1861 w.writeAttribute(rdfNS,
"about",
"");
1862 w.writeAttribute(pdfNS,
"Producer", producer);
1863 w.writeAttribute(pdfNS,
"Trapped",
"False");
1864 w.writeEndElement();
1867 w.writeStartElement(rdfNS,
"Description");
1868 w.writeAttribute(rdfNS,
"about",
"");
1869 w.writeAttribute(xmpNS,
"CreatorTool", creator);
1870 w.writeAttribute(xmpNS,
"CreateDate", metaDataDate);
1871 w.writeAttribute(xmpNS,
"ModifyDate", metaDataDate);
1872 w.writeAttribute(xmpNS,
"MetadataDate", metaDataDate);
1873 w.writeEndElement();
1876 w.writeStartElement(rdfNS,
"Description");
1877 w.writeAttribute(rdfNS,
"about",
"");
1878 w.writeAttribute(xmpMMNS,
"DocumentID",
"uuid:"_L1 + documentId.toString(QUuid::WithoutBraces));
1879 w.writeAttribute(xmpMMNS,
"VersionID",
"1");
1880 w.writeAttribute(xmpMMNS,
"RenditionClass",
"default");
1881 w.writeEndElement();
1884 switch (pdfVersion) {
1885 case QPdfEngine::Version_1_4:
1887 case QPdfEngine::Version_A1b:
1888 w.writeStartElement(rdfNS,
"Description");
1889 w.writeAttribute(rdfNS,
"about",
"");
1890 w.writeAttribute(pdfaidNS,
"part",
"1");
1891 w.writeAttribute(pdfaidNS,
"conformance",
"B");
1892 w.writeEndElement();
1894 case QPdfEngine::Version_1_6:
1896 case QPdfEngine::Version_X4:
1897 w.writeStartElement(rdfNS,
"Description");
1898 w.writeAttribute(rdfNS,
"about",
"");
1899 w.writeAttribute(pdfxidNS,
"GTS_PDFXVersion",
"PDF/X-4");
1900 w.writeEndElement();
1904 w.writeEndElement();
1905 w.writeEndElement();
1907 w.writeEndDocument();
1908 output.write(
"<?xpacket end='w'?>");
1912 "/Type /Metadata /Subtype /XML\n"
1913 "/Length %" PRIdQSIZETYPE
"\n"
1915 "stream\n", metaDataContent.size());
1916 write(metaDataContent);
1917 xprintf(
"\nendstream\n"
1923int QPdfEnginePrivate::writeOutputIntent()
1925 const int colorProfileEntry = addXrefEntry(-1);
1927 const QColorSpace profile = outputIntent.outputProfile();
1928 const QByteArray colorProfileData = profile.iccProfile();
1931 QPdf::ByteStream s(&data);
1932 int length_object = requestObject();
1936 switch (profile.colorModel()) {
1937 case QColorSpace::ColorModel::Undefined:
1938 qWarning(
"QPdfEngine: undefined color model in the output intent profile, assuming RGB");
1940 case QColorSpace::ColorModel::Rgb:
1942 s <<
"/Alternate /DeviceRGB\n";
1944 case QColorSpace::ColorModel::Gray:
1946 s <<
"/Alternate /DeviceGray\n";
1948 case QColorSpace::ColorModel::Cmyk:
1950 s <<
"/Alternate /DeviceCMYK\n";
1954 s <<
"/Length " << length_object <<
"0 R\n";
1956 s <<
"/Filter /FlateDecode\n";
1960 const int len = writeCompressed(colorProfileData);
1961 write(
"\nendstream\n"
1963 addXrefEntry(length_object);
1968 const int outputIntentEntry = addXrefEntry(-1);
1971 write(
"/Type /OutputIntent\n");
1973 switch (pdfVersion) {
1974 case QPdfEngine::Version_1_4:
1975 case QPdfEngine::Version_1_6:
1978 case QPdfEngine::Version_A1b:
1979 write(
"/S/GTS_PDFA1\n");
1981 case QPdfEngine::Version_X4:
1982 write(
"/S/GTS_PDFX\n");
1986 xprintf(
"/DestOutputProfile %d 0 R\n", colorProfileEntry);
1987 write(
"/OutputConditionIdentifier ");
1988 printString(outputIntent.outputConditionIdentifier());
1992 printString(outputIntent.outputCondition());
1995 write(
"/OutputCondition ");
1996 printString(outputIntent.outputCondition());
1999 if (
const auto registryName = outputIntent.registryName(); !registryName.isEmpty()) {
2000 write(
"/RegistryName ");
2001 printString(registryName.toString());
2009 return outputIntentEntry;
2012void QPdfEnginePrivate::writePageRoot()
2014 addXrefEntry(pageRoot);
2020 int size = pages.size();
2021 for (
int i = 0; i < size; ++i)
2022 xprintf(
"%d 0 R\n", pages[i]);
2026 xprintf(
"/Count %" PRIdQSIZETYPE
"\n", pages.size());
2028 xprintf(
"/ProcSet [/PDF /Text /ImageB /ImageC]\n"
2033void QPdfEnginePrivate::writeDestsRoot()
2035 if (destCache.isEmpty())
2038 std::map<QString,
int> destObjects;
2040 for (
const DestInfo &destInfo : std::as_const(destCache)) {
2041 int destObj = addXrefEntry(-1);
2042 xs.setNum(
static_cast<
double>(destInfo.coords.x()),
'f');
2043 ys.setNum(
static_cast<
double>(destInfo.coords.y()),
'f');
2044 xprintf(
"[%d 0 R /XYZ %s %s 0]\n", destInfo.pageObj, xs.constData(), ys.constData());
2045 xprintf(
"endobj\n");
2046 destObjects.insert_or_assign(destInfo.anchor, destObj);
2050 destsRoot = addXrefEntry(-1);
2051 xprintf(
"<<\n/Limits [");
2052 printString(destObjects.begin()->first);
2054 printString(destObjects.rbegin()->first);
2055 xprintf(
"]\n/Names [\n");
2056 for (
const auto &[anchor, destObject] : destObjects) {
2057 printString(anchor);
2058 xprintf(
" %d 0 R\n", destObject);
2064void QPdfEnginePrivate::writeAttachmentRoot()
2066 if (fileCache.isEmpty())
2069 QList<
int> attachments;
2070 const int size = fileCache.size();
2071 for (
int i = 0; i < size; ++i) {
2072 auto attachment = fileCache.at(i);
2073 const int attachmentID = addXrefEntry(-1);
2076 xprintf(
"/Filter /FlateDecode\n");
2078 const int lenobj = requestObject();
2079 xprintf(
"/Length %d 0 R\n", lenobj);
2081 xprintf(
">>\nstream\n");
2082 len = writeCompressed(attachment.data);
2083 xprintf(
"\nendstream\n"
2085 addXrefEntry(lenobj);
2089 attachments.push_back(addXrefEntry(-1));
2092 printString(attachment.fileName);
2094 xprintf(
"\n/EF <</F %d 0 R>>\n"
2097 if (!attachment.mimeType.isEmpty())
2098 xprintf(
"/Subtype/%s\n",
2099 attachment.mimeType.replace(
"/"_L1,
"#2F"_L1).toLatin1().constData());
2100 xprintf(
">>\nendobj\n");
2104 attachmentsRoot = addXrefEntry(-1);
2105 xprintf(
"<</Names[");
2106 for (
int i = 0; i < size; ++i) {
2107 auto attachment = fileCache.at(i);
2108 printString(attachment.fileName);
2109 xprintf(
"%d 0 R\n", attachments.at(i));
2115void QPdfEnginePrivate::writeNamesRoot()
2117 addXrefEntry(namesRoot);
2120 if (attachmentsRoot)
2121 xprintf(
"/EmbeddedFiles %d 0 R\n", attachmentsRoot);
2124 xprintf(
"/Dests %d 0 R\n", destsRoot);
2127 xprintf(
"endobj\n");
2130void QPdfEnginePrivate::embedFont(QFontSubset *font)
2133 int fontObject = font->object_id;
2134 QByteArray fontData = font->toTruetype();
2137 QString fileName(
"font%1.ttf");
2138 fileName = fileName.arg(i++);
2140 ff.open(QFile::WriteOnly);
2145 int fontDescriptor = requestObject();
2146 int fontstream = requestObject();
2147 int cidfont = requestObject();
2148 int toUnicode = requestObject();
2149 int cidset = requestObject();
2151 QFontEngine::Properties properties = font->fontEngine->properties();
2152 QByteArray postscriptName = properties.postscriptName.replace(
' ',
'_');
2155 qreal scale = 1000/properties.emSquare.toReal();
2156 addXrefEntry(fontDescriptor);
2157 QByteArray descriptor;
2158 QPdf::ByteStream s(&descriptor);
2159 s <<
"<< /Type /FontDescriptor\n"
2161 int tag = fontDescriptor;
2162 for (
int i = 0; i < 5; ++i) {
2163 s << (
char)(
'A' + (tag % 26));
2166 s <<
'+' << postscriptName <<
"\n"
2167 "/Flags " << 4 <<
"\n"
2169 << properties.boundingBox.x()*scale
2170 << -(properties.boundingBox.y() + properties.boundingBox.height())*scale
2171 << (properties.boundingBox.x() + properties.boundingBox.width())*scale
2172 << -properties.boundingBox.y()*scale <<
"]\n"
2173 "/ItalicAngle " << properties.italicAngle.toReal() <<
"\n"
2174 "/Ascent " << properties.ascent.toReal()*scale <<
"\n"
2175 "/Descent " << -properties.descent.toReal()*scale <<
"\n"
2176 "/CapHeight " << properties.capHeight.toReal()*scale <<
"\n"
2177 "/StemV " << properties.lineWidth.toReal()*scale <<
"\n"
2178 "/FontFile2 " << fontstream <<
"0 R\n"
2179 "/CIDSet " << cidset <<
"0 R\n"
2184 addXrefEntry(fontstream);
2186 QPdf::ByteStream s(&header);
2188 int length_object = requestObject();
2190 "/Length1 " << fontData.size() <<
"\n"
2191 "/Length " << length_object <<
"0 R\n";
2193 s <<
"/Filter /FlateDecode\n";
2197 int len = writeCompressed(fontData);
2198 write(
"\nendstream\n"
2200 addXrefEntry(length_object);
2205 addXrefEntry(cidfont);
2207 QPdf::ByteStream s(&cid);
2208 s <<
"<< /Type /Font\n"
2209 "/Subtype /CIDFontType2\n"
2210 "/BaseFont /" << postscriptName <<
"\n"
2211 "/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>\n"
2212 "/FontDescriptor " << fontDescriptor <<
"0 R\n"
2213 "/CIDToGIDMap /Identity\n"
2214 << font->widthArray() <<
2220 addXrefEntry(toUnicode);
2221 QByteArray touc = font->createToUnicodeMap();
2222 xprintf(
"<< /Length %" PRIdQSIZETYPE
" >>\n"
2223 "stream\n", touc.size());
2225 write(
"\nendstream\n"
2229 addXrefEntry(fontObject);
2231 QPdf::ByteStream s(&font);
2232 s <<
"<< /Type /Font\n"
2234 "/BaseFont /" << postscriptName <<
"\n"
2235 "/Encoding /Identity-H\n"
2236 "/DescendantFonts [" << cidfont <<
"0 R]\n"
2237 "/ToUnicode " << toUnicode <<
"0 R"
2243 QByteArray cidSetStream(font->nGlyphs() / 8 + 1, 0);
2244 int byteCounter = 0;
2246 for (qsizetype i = 0; i < font->nGlyphs(); ++i) {
2247 cidSetStream.data()[byteCounter] |= (1 << (7 - bitCounter));
2250 if (bitCounter == 8) {
2256 addXrefEntry(cidset);
2258 xprintf(
"/Length %" PRIdQSIZETYPE
"\n", cidSetStream.size());
2260 xprintf(
"stream\n");
2261 write(cidSetStream);
2262 xprintf(
"\nendstream\n");
2263 xprintf(
"endobj\n");
2267qreal QPdfEnginePrivate::calcUserUnit()
const
2270 if (pdfVersion < QPdfEngine::Version_1_6)
2273 const int maxLen = qMax(currentPage->pageSize.width(), currentPage->pageSize.height());
2274 if (maxLen <= 14400)
2278 return qMin(maxLen / 14400.0, 75000.0);
2281void QPdfEnginePrivate::writeFonts()
2283 for (QHash<QFontEngine::FaceId, QFontSubset *>::iterator it = fonts.begin(); it != fonts.end(); ++it) {
2290void QPdfEnginePrivate::writePage()
2295 *currentPage <<
"Q Q\n";
2297 uint pageStream = requestObject();
2298 uint pageStreamLength = requestObject();
2299 uint resources = requestObject();
2300 uint annots = requestObject();
2302 qreal userUnit = calcUserUnit();
2304 addXrefEntry(pages.constLast());
2307 const QByteArray formattedPageWidth = QByteArray::number(currentPage->pageSize.width() / userUnit,
'f');
2308 const QByteArray formattedPageHeight = QByteArray::number(currentPage->pageSize.height() / userUnit,
'f');
2313 "/Contents %d 0 R\n"
2314 "/Resources %d 0 R\n"
2316 "/MediaBox [0 0 %s %s]\n"
2317 "/TrimBox [0 0 %s %s]\n",
2318 pageRoot, pageStream, resources, annots,
2319 formattedPageWidth.constData(),
2320 formattedPageHeight.constData(),
2321 formattedPageWidth.constData(),
2322 formattedPageHeight.constData());
2324 if (pdfVersion >= QPdfEngine::Version_1_6)
2325 xprintf(
"/UserUnit %s\n", QByteArray::number(userUnit,
'f').constData());
2330 addXrefEntry(resources);
2335 "/PCSpcmyk %d 0 R\n"
2337 "/CSpg /DeviceGray\n"
2338 "/CSpcmyk /DeviceCMYK\n"
2342 patternColorSpaceRGB,
2343 patternColorSpaceGrayscale,
2344 patternColorSpaceCMYK,
2347 for (
int i = 0; i < currentPage->graphicStates.size(); ++i)
2348 xprintf(
"/GState%d %d 0 R\n", currentPage->graphicStates.at(i), currentPage->graphicStates.at(i));
2351 xprintf(
"/Pattern <<\n");
2352 for (
int i = 0; i < currentPage->patterns.size(); ++i)
2353 xprintf(
"/Pat%d %d 0 R\n", currentPage->patterns.at(i), currentPage->patterns.at(i));
2356 xprintf(
"/Font <<\n");
2357 for (
int i = 0; i < currentPage->fonts.size();++i)
2358 xprintf(
"/F%d %d 0 R\n", currentPage->fonts[i], currentPage->fonts[i]);
2361 xprintf(
"/XObject <<\n");
2362 for (
int i = 0; i<currentPage->images.size(); ++i) {
2363 xprintf(
"/Im%d %d 0 R\n", currentPage->images.at(i), currentPage->images.at(i));
2370 addXrefEntry(annots);
2372 for (
int i = 0; i<currentPage->annotations.size(); ++i) {
2373 xprintf(
"%d 0 R ", currentPage->annotations.at(i));
2375 xprintf(
"]\nendobj\n");
2377 addXrefEntry(pageStream);
2379 "/Length %d 0 R\n", pageStreamLength);
2381 xprintf(
"/Filter /FlateDecode\n");
2384 xprintf(
"stream\n");
2385 QIODevice *content = currentPage->stream();
2386 int len = writeCompressed(content);
2387 xprintf(
"\nendstream\n"
2390 addXrefEntry(pageStreamLength);
2391 xprintf(
"%d\nendobj\n",len);
2394void QPdfEnginePrivate::writeTail()
2400 writeAttachmentRoot();
2403 addXrefEntry(xrefPositions.size(),
false);
2405 "0 %" PRIdQSIZETYPE
"\n"
2406 "%010d 65535 f \n", xrefPositions.size()-1, xrefPositions[0]);
2408 for (
int i = 1; i < xrefPositions.size()-1; ++i)
2409 xprintf(
"%010d 00000 n \n", xrefPositions[i]);
2413 QPdf::ByteStream s(&trailer);
2417 <<
"/Size " << xrefPositions.size() - 1 <<
"\n"
2418 <<
"/Info " << info <<
"0 R\n"
2419 <<
"/Root " << catalog <<
"0 R\n";
2421 const QByteArray id = documentId.toString(QUuid::WithoutBraces).toUtf8().toHex();
2422 s <<
"/ID [ <" << id <<
"> <" << id <<
"> ]\n";
2425 <<
"startxref\n" << xrefPositions.constLast() <<
"\n"
2432int QPdfEnginePrivate::addXrefEntry(
int object,
bool printostr)
2435 object = requestObject();
2437 if (object>=xrefPositions.size())
2438 xrefPositions.resize(object+1);
2440 xrefPositions[object] = streampos;
2442 xprintf(
"%d 0 obj\n",object);
2447void QPdfEnginePrivate::printString(QStringView string)
2449 if (string.isEmpty()) {
2457 QByteArray array(
"(\xfe\xff");
2458 const char16_t *utf16 = string.utf16();
2460 for (qsizetype i = 0; i < string.size(); ++i) {
2461 char part[2] = {
char((*(utf16 + i)) >> 8),
char((*(utf16 + i)) & 0xff)};
2462 for(
int j=0; j < 2; ++j) {
2463 if (part[j] ==
'(' || part[j] ==
')' || part[j] ==
'\\')
2465 array.append(part[j]);
2473void QPdfEnginePrivate::xprintf(
const char* fmt, ...)
2478 const int msize = 10000;
2482 va_start(args, fmt);
2483 int bufsize = std::vsnprintf(buf, msize, fmt, args);
2486 if (Q_LIKELY(bufsize < msize)) {
2487 stream->writeRawData(buf, bufsize);
2490 QScopedArrayPointer<
char> tmpbuf(
new char[bufsize + 1]);
2491 va_start(args, fmt);
2492 bufsize = std::vsnprintf(tmpbuf.data(), bufsize + 1, fmt, args);
2494 stream->writeRawData(tmpbuf.data(), bufsize);
2496 streampos += bufsize;
2499int QPdfEnginePrivate::writeCompressed(QIODevice *dev)
2501#ifndef QT_NO_COMPRESS
2503 int size = QPdfPage::chunkSize();
2506 zStruct.zalloc = Z_NULL;
2507 zStruct.zfree = Z_NULL;
2508 zStruct.opaque = Z_NULL;
2509 if (::deflateInit(&zStruct, Z_DEFAULT_COMPRESSION) != Z_OK) {
2510 qWarning(
"QPdfStream::writeCompressed: Error in deflateInit()");
2513 zStruct.avail_in = 0;
2516 while (!dev->atEnd() || zStruct.avail_in != 0) {
2517 if (zStruct.avail_in == 0) {
2518 in = dev->read(size);
2519 zStruct.avail_in = in.size();
2520 zStruct.next_in =
reinterpret_cast<
unsigned char*>(in.data());
2521 if (in.size() <= 0) {
2522 qWarning(
"QPdfStream::writeCompressed: Error in read()");
2523 ::deflateEnd(&zStruct);
2527 zStruct.next_out =
reinterpret_cast<
unsigned char*>(out.data());
2528 zStruct.avail_out = out.size();
2529 if (::deflate(&zStruct, 0) != Z_OK) {
2530 qWarning(
"QPdfStream::writeCompressed: Error in deflate()");
2531 ::deflateEnd(&zStruct);
2534 int written = out.size() - zStruct.avail_out;
2535 stream->writeRawData(out.constData(), written);
2536 streampos += written;
2541 zStruct.next_out =
reinterpret_cast<
unsigned char*>(out.data());
2542 zStruct.avail_out = out.size();
2543 ret = ::deflate(&zStruct, Z_FINISH);
2544 if (ret != Z_OK && ret != Z_STREAM_END) {
2545 qWarning(
"QPdfStream::writeCompressed: Error in deflate()");
2546 ::deflateEnd(&zStruct);
2549 int written = out.size() - zStruct.avail_out;
2550 stream->writeRawData(out.constData(), written);
2551 streampos += written;
2553 }
while (ret == Z_OK);
2555 ::deflateEnd(&zStruct);
2563 while (!dev->atEnd()) {
2564 arr = dev->read(QPdfPage::chunkSize());
2565 stream->writeRawData(arr.constData(), arr.size());
2566 streampos += arr.size();
2573int QPdfEnginePrivate::writeCompressed(
const char *src,
int len)
2575#ifndef QT_NO_COMPRESS
2577 const QByteArray data = qCompress(
reinterpret_cast<
const uchar *>(src), len);
2578 constexpr qsizetype HeaderSize = 4;
2579 if (!data.isNull()) {
2580 stream->writeRawData(data.data() + HeaderSize, data.size() - HeaderSize);
2581 len = data.size() - HeaderSize;
2583 qWarning(
"QPdfStream::writeCompressed: Error in compress()");
2589 stream->writeRawData(src,len);
2595int QPdfEnginePrivate::writeImage(
const QByteArray &data,
int width,
int height, WriteImageOption option,
2596 int maskObject,
int softMaskObject,
bool dct,
bool isMono)
2598 int image = addXrefEntry(-1);
2603 "/Height %d\n", width, height);
2606 case WriteImageOption::Monochrome:
2608 xprintf(
"/ImageMask true\n"
2611 xprintf(
"/BitsPerComponent 1\n"
2612 "/ColorSpace /DeviceGray\n");
2615 case WriteImageOption::Grayscale:
2616 xprintf(
"/BitsPerComponent 8\n"
2617 "/ColorSpace /DeviceGray\n");
2619 case WriteImageOption::RGB:
2620 xprintf(
"/BitsPerComponent 8\n"
2621 "/ColorSpace /DeviceRGB\n");
2623 case WriteImageOption::CMYK:
2624 xprintf(
"/BitsPerComponent 8\n"
2625 "/ColorSpace /DeviceCMYK\n");
2630 xprintf(
"/Mask %d 0 R\n", maskObject);
2631 if (softMaskObject > 0)
2632 xprintf(
"/SMask %d 0 R\n", softMaskObject);
2634 int lenobj = requestObject();
2635 xprintf(
"/Length %d 0 R\n", lenobj);
2636 if (interpolateImages)
2637 xprintf(
"/Interpolate true\n");
2641 xprintf(
"/Filter /DCTDecode\n>>\nstream\n");
2646 xprintf(
"/Filter /FlateDecode\n>>\nstream\n");
2648 xprintf(
">>\nstream\n");
2649 len = writeCompressed(data);
2651 xprintf(
"\nendstream\n"
2653 addXrefEntry(lenobj);
2667void QPdfEnginePrivate::ShadingFunctionResult::writeColorSpace(QPdf::ByteStream *stream)
const
2669 *stream <<
"/ColorSpace ";
2670 switch (colorModel) {
2671 case QPdfEngine::ColorModel::RGB:
2672 *stream <<
"/DeviceRGB\n";
break;
2673 case QPdfEngine::ColorModel::Grayscale:
2674 *stream <<
"/DeviceGray\n";
break;
2675 case QPdfEngine::ColorModel::CMYK:
2676 *stream <<
"/DeviceCMYK\n";
break;
2677 case QPdfEngine::ColorModel::Auto:
2678 Q_UNREACHABLE();
break;
2682QPdfEnginePrivate::ShadingFunctionResult
2683QPdfEnginePrivate::createShadingFunction(
const QGradient *gradient,
int from,
int to,
bool reflect,
bool alpha)
2685 QGradientStops stops = gradient->stops();
2686 if (stops.isEmpty()) {
2687 stops << QGradientStop(0, Qt::black);
2688 stops << QGradientStop(1, Qt::white);
2690 if (stops.at(0).first > 0)
2691 stops.prepend(QGradientStop(0, stops.at(0).second));
2692 if (stops.at(stops.size() - 1).first < 1)
2693 stops.append(QGradientStop(1, stops.at(stops.size() - 1).second));
2696 const QColor referenceColor = stops.constFirst().second;
2698 switch (colorModel) {
2699 case QPdfEngine::ColorModel::RGB:
2700 case QPdfEngine::ColorModel::Grayscale:
2701 case QPdfEngine::ColorModel::CMYK:
2703 case QPdfEngine::ColorModel::Auto: {
2706 const QColor::Spec referenceSpec = referenceColor.spec();
2707 bool warned =
false;
2708 for (QGradientStop &stop : stops) {
2709 if (stop.second.spec() != referenceSpec) {
2711 qWarning(
"QPdfEngine: unable to create a gradient between colors of different spec");
2714 stop.second = stop.second.convertTo(referenceSpec);
2721 ShadingFunctionResult result;
2722 result.colorModel = colorModelForColor(referenceColor);
2724 QList<
int> functions;
2725 const int numStops = stops.size();
2726 functions.reserve(numStops - 1);
2727 for (
int i = 0; i < numStops - 1; ++i) {
2728 int f = addXrefEntry(-1);
2730 QPdf::ByteStream s(&data);
2736 s <<
"/C0 [" << stops.at(i).second.alphaF() <<
"]\n"
2737 "/C1 [" << stops.at(i + 1).second.alphaF() <<
"]\n";
2739 switch (result.colorModel) {
2740 case QPdfEngine::ColorModel::RGB:
2741 s <<
"/C0 [" << stops.at(i).second.redF() << stops.at(i).second.greenF() << stops.at(i).second.blueF() <<
"]\n"
2742 "/C1 [" << stops.at(i + 1).second.redF() << stops.at(i + 1).second.greenF() << stops.at(i + 1).second.blueF() <<
"]\n";
2744 case QPdfEngine::ColorModel::Grayscale: {
2745 constexpr qreal normalisationFactor = 1. / 255.;
2746 s <<
"/C0 [" << (qGray(stops.at(i).second.rgba()) * normalisationFactor) <<
"]\n"
2747 "/C1 [" << (qGray(stops.at(i + 1).second.rgba()) * normalisationFactor) <<
"]\n";
2750 case QPdfEngine::ColorModel::CMYK:
2751 s <<
"/C0 [" << stops.at(i).second.cyanF()
2752 << stops.at(i).second.magentaF()
2753 << stops.at(i).second.yellowF()
2754 << stops.at(i).second.blackF() <<
"]\n"
2755 "/C1 [" << stops.at(i + 1).second.cyanF()
2756 << stops.at(i + 1).second.magentaF()
2757 << stops.at(i + 1).second.yellowF()
2758 << stops.at(i + 1).second.blackF() <<
"]\n";
2761 case QPdfEngine::ColorModel::Auto:
2773 QList<QGradientBound> gradientBounds;
2774 gradientBounds.reserve((to - from) * (numStops - 1));
2776 for (
int step = from; step < to; ++step) {
2777 if (reflect && step % 2) {
2778 for (
int i = numStops - 1; i > 0; --i) {
2780 b.start = step + 1 - qBound(qreal(0.), stops.at(i).first, qreal(1.));
2781 b.stop = step + 1 - qBound(qreal(0.), stops.at(i - 1).first, qreal(1.));
2782 b.function = functions.at(i - 1);
2784 gradientBounds << b;
2787 for (
int i = 0; i < numStops - 1; ++i) {
2789 b.start = step + qBound(qreal(0.), stops.at(i).first, qreal(1.));
2790 b.stop = step + qBound(qreal(0.), stops.at(i + 1).first, qreal(1.));
2791 b.function = functions.at(i);
2793 gradientBounds << b;
2799 qreal bstart = gradientBounds.at(0).start;
2800 qreal bend = gradientBounds.at(gradientBounds.size() - 1).stop;
2801 qreal norm = 1./(bend - bstart);
2802 for (
int i = 0; i < gradientBounds.size(); ++i) {
2803 gradientBounds[i].start = (gradientBounds[i].start - bstart)*norm;
2804 gradientBounds[i].stop = (gradientBounds[i].stop - bstart)*norm;
2808 if (gradientBounds.size() > 1) {
2809 function = addXrefEntry(-1);
2811 QPdf::ByteStream s(&data);
2816 for (
int i = 1; i < gradientBounds.size(); ++i)
2817 s << gradientBounds.at(i).start;
2820 for (
int i = 0; i < gradientBounds.size(); ++i)
2821 s << (gradientBounds.at(i).reverse ?
"1 0 " :
"0 1 ");
2824 for (
int i = 0; i < gradientBounds.size(); ++i)
2825 s << gradientBounds.at(i).function <<
"0 R ";
2831 function = functions.at(0);
2833 result.function = function;
2837int QPdfEnginePrivate::generateLinearGradientShader(
const QLinearGradient *gradient,
const QTransform &matrix,
bool alpha)
2839 QPointF start = gradient->start();
2840 QPointF stop = gradient->finalStop();
2841 QPointF offset = stop - start;
2842 Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode);
2846 bool reflect =
false;
2847 switch (gradient->spread()) {
2848 case QGradient::PadSpread:
2850 case QGradient::ReflectSpread:
2853 case QGradient::RepeatSpread: {
2855 QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2856 QTransform inv = matrix.inverted();
2857 QPointF page_rect[4] = { inv.map(pageRect.topLeft()),
2858 inv.map(pageRect.topRight()),
2859 inv.map(pageRect.bottomLeft()),
2860 inv.map(pageRect.bottomRight()) };
2862 qreal length = offset.x()*offset.x() + offset.y()*offset.y();
2868 for (
int i = 0; i < 4; ++i) {
2869 qreal off = ((page_rect[i].x() - start.x()) * offset.x() + (page_rect[i].y() - start.y()) * offset.y())/length;
2870 from = qMin(from, qFloor(off));
2871 to = qMax(to, qCeil(off));
2874 stop = start + to * offset;
2875 start = start + from * offset;
2880 const auto shadingFunctionResult = createShadingFunction(gradient, from, to, reflect, alpha);
2883 QPdf::ByteStream s(&shader);
2888 s <<
"/ColorSpace /DeviceGray\n";
2890 shadingFunctionResult.writeColorSpace(&s);
2892 s <<
"/AntiAlias true\n"
2893 "/Coords [" << start.x() << start.y() << stop.x() << stop.y() <<
"]\n"
2894 "/Extend [true true]\n"
2895 "/Function " << shadingFunctionResult.function <<
"0 R\n"
2898 int shaderObject = addXrefEntry(-1);
2900 return shaderObject;
2903int QPdfEnginePrivate::generateRadialGradientShader(
const QRadialGradient *gradient,
const QTransform &matrix,
bool alpha)
2905 QPointF p1 = gradient->center();
2906 qreal r1 = gradient->centerRadius();
2907 QPointF p0 = gradient->focalPoint();
2908 qreal r0 = gradient->focalRadius();
2910 Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode);
2914 bool reflect =
false;
2915 switch (gradient->spread()) {
2916 case QGradient::PadSpread:
2918 case QGradient::ReflectSpread:
2921 case QGradient::RepeatSpread: {
2922 Q_ASSERT(qFuzzyIsNull(r0));
2924 QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2925 QTransform inv = matrix.inverted();
2926 QPointF page_rect[4] = { inv.map(pageRect.topLeft()),
2927 inv.map(pageRect.topRight()),
2928 inv.map(pageRect.bottomLeft()),
2929 inv.map(pageRect.bottomRight()) };
2934 QPointF center = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y()));
2935 double radius = r0 + to*(r1 - r0);
2936 double r2 = radius*radius;
2938 for (
int i = 0; i < 4; ++i) {
2939 QPointF off = page_rect[i] - center;
2940 if (off.x()*off.x() + off.y()*off.y() > r2) {
2947 p1 = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y()));
2948 r1 = r0 + to*(r1 - r0);
2953 const auto shadingFunctionResult = createShadingFunction(gradient, from, to, reflect, alpha);
2956 QPdf::ByteStream s(&shader);
2961 s <<
"/ColorSpace /DeviceGray\n";
2963 shadingFunctionResult.writeColorSpace(&s);
2965 s <<
"/AntiAlias true\n"
2967 "/Coords [" << p0.x() << p0.y() << r0 << p1.x() << p1.y() << r1 <<
"]\n"
2968 "/Extend [true true]\n"
2969 "/Function " << shadingFunctionResult.function <<
"0 R\n"
2972 int shaderObject = addXrefEntry(-1);
2974 return shaderObject;
2977int QPdfEnginePrivate::generateGradientShader(
const QGradient *gradient,
const QTransform &matrix,
bool alpha)
2979 switch (gradient->type()) {
2980 case QGradient::LinearGradient:
2981 return generateLinearGradientShader(
static_cast<
const QLinearGradient *>(gradient), matrix, alpha);
2982 case QGradient::RadialGradient:
2983 return generateRadialGradientShader(
static_cast<
const QRadialGradient *>(gradient), matrix, alpha);
2984 case QGradient::ConicalGradient:
2987 case QGradient::NoGradient:
2993int QPdfEnginePrivate::gradientBrush(
const QBrush &b,
const QTransform &matrix,
int *gStateObject)
2995 const QGradient *gradient = b.gradient();
2997 if (!gradient || gradient->coordinateMode() != QGradient::LogicalMode)
3000 QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
3002 QTransform m = b.transform() * matrix;
3003 int shaderObject = generateGradientShader(gradient, m);
3006 QPdf::ByteStream s(&str);
3010 "/Shading " << shaderObject <<
"0 R\n"
3021 int patternObj = addXrefEntry(-1);
3023 currentPage->patterns.append(patternObj);
3025 if (!b.isOpaque()) {
3027 QGradientStops stops = gradient->stops();
3028 int a = stops.at(0).second.alpha();
3029 for (
int i = 1; i < stops.size(); ++i) {
3030 if (stops.at(i).second.alpha() != a) {
3036 *gStateObject = addConstantAlphaObject(stops.at(0).second.alpha());
3038 int alphaShaderObject = generateGradientShader(gradient, m,
true);
3041 QPdf::ByteStream c(&content);
3042 c <<
"/Shader" << alphaShaderObject <<
"sh\n";
3045 QPdf::ByteStream f(&form);
3049 "/BBox [0 0 " << pageRect.width() << pageRect.height() <<
"]\n"
3050 "/Group <</S /Transparency >>\n"
3052 "/Shading << /Shader" << alphaShaderObject << alphaShaderObject <<
"0 R >>\n"
3055 f <<
"/Length " << content.size() <<
"\n"
3062 int softMaskFormObject = addXrefEntry(-1);
3064 *gStateObject = addXrefEntry(-1);
3065 xprintf(
"<< /SMask << /S /Alpha /G %d 0 R >> >>\n"
3066 "endobj\n", softMaskFormObject);
3067 currentPage->graphicStates.append(*gStateObject);
3074int QPdfEnginePrivate::addConstantAlphaObject(
int brushAlpha,
int penAlpha)
3076 if (brushAlpha == 255 && penAlpha == 255)
3078 uint object = alphaCache.value(std::pair<uint, uint>(brushAlpha, penAlpha), 0);
3080 object = addXrefEntry(-1);
3081 QByteArray alphaDef;
3082 QPdf::ByteStream s(&alphaDef);
3083 s <<
"<<\n/ca " << (brushAlpha/qreal(255.)) <<
'\n';
3084 s <<
"/CA " << (penAlpha/qreal(255.)) <<
"\n>>";
3085 xprintf(
"%s\nendobj\n", alphaDef.constData());
3086 alphaCache.insert(std::pair<uint, uint>(brushAlpha, penAlpha), object);
3088 if (currentPage->graphicStates.indexOf(object) < 0)
3089 currentPage->graphicStates.append(object);
3095int QPdfEnginePrivate::addBrushPattern(
const QTransform &m,
bool *specifyColor,
int *gStateObject)
3103 *specifyColor =
true;
3106 const Qt::BrushStyle style = brush.style();
3107 const bool isCosmetic = style >= Qt::Dense1Pattern && style <= Qt::DiagCrossPattern
3108 && !q->painter()->testRenderHint(QPainter::NonCosmeticBrushPatterns);
3112 matrix.translate(brushOrigin.x(), brushOrigin.y());
3113 matrix = matrix * pageMatrix();
3115 if (style == Qt::LinearGradientPattern || style == Qt::RadialGradientPattern) {
3116 *specifyColor =
false;
3117 return gradientBrush(brush, matrix, gStateObject);
3121 matrix = brush.transform() * matrix;
3123 if ((!brush.isOpaque() && brush.style() < Qt::LinearGradientPattern) || opacity != 1.0)
3124 *gStateObject = addConstantAlphaObject(qRound(brush.color().alpha() * opacity),
3125 qRound(pen.color().alpha() * opacity));
3127 int imageObject = -1;
3128 QByteArray pattern = QPdf::patternForBrush(brush);
3129 if (pattern.isEmpty()) {
3130 if (brush.style() != Qt::TexturePattern)
3132 QImage image = brush.textureImage();
3134 const bool lossless = q->painter()->testRenderHint(QPainter::LosslessImageRendering);
3135 imageObject = addImage(image, &bitmap, lossless, image.cacheKey());
3136 if (imageObject != -1) {
3137 QImage::Format f = image.format();
3138 if (f != QImage::Format_MonoLSB && f != QImage::Format_Mono) {
3140 *specifyColor =
false;
3144 QTransform m(w, 0, 0, -h, 0, h);
3145 QPdf::ByteStream s(&pattern);
3146 s << QPdf::generateMatrix(m);
3147 s <<
"/Im" << imageObject <<
" Do\n";
3152 QPdf::ByteStream s(&str);
3156 "/PaintType " << paintType <<
"\n"
3158 "/BBox [0 0 " << w << h <<
"]\n"
3159 "/XStep " << w <<
"\n"
3160 "/YStep " << h <<
"\n"
3167 << matrix.dy() <<
"]\n"
3169 if (imageObject > 0) {
3170 s <<
"/XObject << /Im" << imageObject <<
' ' << imageObject <<
"0 R >> ";
3173 "/Length " << pattern.size() <<
"\n"
3180 int patternObj = addXrefEntry(-1);
3182 currentPage->patterns.append(patternObj);
3188 return colorTable.size() == 2
3189 && colorTable.at(0) == QColor(Qt::black).rgba()
3190 && colorTable.at(1) == QColor(Qt::white).rgba()
3195
3196
3197int QPdfEnginePrivate::addImage(
const QImage &img,
bool *bitmap,
bool lossless, qint64 serial_no)
3202 int object = imageCache.value(serial_no);
3207 QImage::Format format = image.format();
3208 const bool grayscale = (colorModel == QPdfEngine::ColorModel::Grayscale);
3210 if (pdfVersion == QPdfEngine::Version_A1b) {
3211 if (image.hasAlphaChannel()) {
3215 QImage alphaLessImage(image.width(), image.height(), QImage::Format_RGB32);
3216 alphaLessImage.setDevicePixelRatio(image.devicePixelRatioF());
3217 alphaLessImage.fill(Qt::white);
3219 QPainter p(&alphaLessImage);
3220 p.drawImage(0, 0, image);
3222 image = alphaLessImage;
3223 format = image.format();
3227 if (image.depth() == 1 && *bitmap && is_monochrome(img.colorTable())) {
3228 if (format == QImage::Format_MonoLSB)
3229 image = image.convertToFormat(QImage::Format_Mono);
3230 format = QImage::Format_Mono;
3233 if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_CMYK8888) {
3234 image = image.convertToFormat(QImage::Format_ARGB32);
3235 format = QImage::Format_ARGB32;
3239 int w = image.width();
3240 int h = image.height();
3242 if (format == QImage::Format_Mono) {
3243 int bytesPerLine = (w + 7) >> 3;
3245 data.resize(bytesPerLine * h);
3246 char *rawdata = data.data();
3247 for (
int y = 0; y < h; ++y) {
3248 memcpy(rawdata, image.constScanLine(y), bytesPerLine);
3249 rawdata += bytesPerLine;
3251 object = writeImage(data, w, h, WriteImageOption::Monochrome, 0, 0,
false, is_monochrome(img.colorTable()));
3253 QByteArray softMaskData;
3255 QByteArray imageData;
3256 bool hasAlpha =
false;
3257 bool hasMask =
false;
3259 if (QImageWriter::supportedImageFormats().contains(
"jpeg") && !grayscale && !lossless) {
3260 QBuffer buffer(&imageData);
3261 QImageWriter writer(&buffer,
"jpeg");
3262 writer.setQuality(94);
3263 if (format == QImage::Format_CMYK8888) {
3265 writer.setSubType(
"CMYK");
3267 writer.write(image);
3270 if (format != QImage::Format_RGB32 && format != QImage::Format_CMYK8888) {
3271 softMaskData.resize(w * h);
3272 uchar *sdata = (uchar *)softMaskData.data();
3273 for (
int y = 0; y < h; ++y) {
3274 const QRgb *rgb = (
const QRgb *)image.constScanLine(y);
3275 for (
int x = 0; x < w; ++x) {
3276 uchar alpha = qAlpha(*rgb);
3278 hasMask |= (alpha < 255);
3279 hasAlpha |= (alpha != 0 && alpha != 255);
3285 if (format == QImage::Format_CMYK8888) {
3286 imageData.resize(grayscale ? w * h : w * h * 4);
3287 uchar *data = (uchar *)imageData.data();
3288 const qsizetype bytesPerLine = image.bytesPerLine();
3290 for (
int y = 0; y < h; ++y) {
3291 const uint *cmyk = (
const uint *)image.constScanLine(y);
3292 for (
int x = 0; x < w; ++x)
3293 *data++ = qGray(QCmyk32::fromCmyk32(*cmyk++).toColor().rgba());
3296 for (
int y = 0; y < h; ++y) {
3297 uchar *start = data + y * w * 4;
3298 memcpy(start, image.constScanLine(y), bytesPerLine);
3302 imageData.resize(grayscale ? w * h : 3 * w * h);
3303 uchar *data = (uchar *)imageData.data();
3304 softMaskData.resize(w * h);
3305 uchar *sdata = (uchar *)softMaskData.data();
3306 for (
int y = 0; y < h; ++y) {
3307 const QRgb *rgb = (
const QRgb *)image.constScanLine(y);
3309 for (
int x = 0; x < w; ++x) {
3310 *(data++) = qGray(*rgb);
3311 uchar alpha = qAlpha(*rgb);
3313 hasMask |= (alpha < 255);
3314 hasAlpha |= (alpha != 0 && alpha != 255);
3318 for (
int x = 0; x < w; ++x) {
3319 *(data++) = qRed(*rgb);
3320 *(data++) = qGreen(*rgb);
3321 *(data++) = qBlue(*rgb);
3322 uchar alpha = qAlpha(*rgb);
3324 hasMask |= (alpha < 255);
3325 hasAlpha |= (alpha != 0 && alpha != 255);
3331 if (format == QImage::Format_RGB32 || format == QImage::Format_CMYK8888)
3332 hasAlpha = hasMask =
false;
3335 int softMaskObject = 0;
3337 softMaskObject = writeImage(softMaskData, w, h, WriteImageOption::Grayscale, 0, 0);
3338 }
else if (hasMask) {
3341 int bytesPerLine = (w + 7) >> 3;
3342 QByteArray mask(bytesPerLine * h, 0);
3343 uchar *mdata = (uchar *)mask.data();
3344 const uchar *sdata = (
const uchar *)softMaskData.constData();
3345 for (
int y = 0; y < h; ++y) {
3346 for (
int x = 0; x < w; ++x) {
3348 mdata[x>>3] |= (0x80 >> (x&7));
3351 mdata += bytesPerLine;
3353 maskObject = writeImage(mask, w, h, WriteImageOption::Monochrome, 0, 0);
3356 const WriteImageOption option = [&]() {
3358 return WriteImageOption::Grayscale;
3359 if (format == QImage::Format_CMYK8888)
3360 return WriteImageOption::CMYK;
3361 return WriteImageOption::RGB;
3364 object = writeImage(imageData, w, h, option,
3365 maskObject, softMaskObject, dct);
3367 imageCache.insert(serial_no, object);
3371void QPdfEnginePrivate::drawTextItem(
const QPointF &p,
const QTextItemInt &ti)
3375 const bool isLink = ti.charFormat.hasProperty(QTextFormat::AnchorHref);
3376 const bool isAnchor = ti.charFormat.hasProperty(QTextFormat::AnchorName);
3380 const bool isX4 = pdfVersion == QPdfEngine::Version_X4;
3381 if ((isLink && !isX4) || isAnchor) {
3382 qreal size = ti.fontEngine->fontDef.pixelSize;
3383 int synthesized = ti.fontEngine->synthesized();
3384 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
3385 Q_ASSERT(stretch > qreal(0));
3390 trans = QTransform(size*stretch, 0, 0, size, 0, 0);
3392 trans *= QTransform(1,0,0,-1,p.x(),p.y());
3394 trans *= stroker.matrix;
3396 trans *= pageMatrix();
3397 qreal x1, y1, x2, y2;
3398 trans.map(0, 0, &x1, &y1);
3399 trans.map(ti.width.toReal()/size, (ti.ascent.toReal()-ti.descent.toReal())/size, &x2, &y2);
3402 uint annot = addXrefEntry(-1);
3403 QByteArray x1s, y1s, x2s, y2s;
3404 x1s.setNum(
static_cast<
double>(x1),
'f');
3405 y1s.setNum(
static_cast<
double>(y1),
'f');
3406 x2s.setNum(
static_cast<
double>(x2),
'f');
3407 y2s.setNum(
static_cast<
double>(y2),
'f');
3408 QByteArray rectData = x1s +
' ' + y1s +
' ' + x2s +
' ' + y2s;
3409 xprintf(
"<<\n/Type /Annot\n/Subtype /Link\n");
3411 if (pdfVersion == QPdfEngine::Version_A1b)
3415 write(rectData.constData());
3416#ifdef Q_DEBUG_PDF_LINKS
3417 xprintf(
"]\n/Border [16 16 1]\n");
3419 xprintf(
"]\n/Border [0 0 0]\n");
3421 const QString link = ti.charFormat.anchorHref();
3422 const bool isInternal = link.startsWith(QLatin1Char(
'#'));
3425 xprintf(
"/Type /Action\n/S /URI\n/URI (%s)\n", link.toLatin1().constData());
3429 printString(link.sliced(1));
3433 xprintf(
"endobj\n");
3435 if (!currentPage->annotations.contains(annot)) {
3436 currentPage->annotations.append(annot);
3439 const QString anchor = ti.charFormat.anchorNames().constFirst();
3440 const uint curPage = pages.last();
3441 destCache.append(DestInfo({ anchor, curPage, QPointF(x1, y2) }));
3445 QFontEngine *fe = ti.fontEngine;
3447 QFontEngine::FaceId face_id = fe->faceId();
3448 bool noEmbed =
false;
3450 || face_id.filename.isEmpty()
3451 || fe->fsType & 0x200
3452 || fe->fsType == 2 ) {
3453 *currentPage <<
"Q\n";
3454 q->QPaintEngine::drawTextItem(p, ti);
3455 *currentPage <<
"q\n";
3456 if (face_id.filename.isEmpty())
3461 QFontSubset *font = fonts.value(face_id,
nullptr);
3463 font =
new QFontSubset(fe, requestObject());
3464 font->noEmbed = noEmbed;
3466 fonts.insert(face_id, font);
3468 if (!currentPage->fonts.contains(font->object_id))
3469 currentPage->fonts.append(font->object_id);
3471 qreal size = ti.fontEngine->fontDef.pixelSize;
3473 QVarLengthArray<glyph_t> glyphs;
3474 QVarLengthArray<QFixedPoint> positions;
3475 QTransform m = QTransform::fromTranslate(p.x(), p.y());
3476 ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags,
3478 if (glyphs.size() == 0)
3480 int synthesized = ti.fontEngine->synthesized();
3481 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
3482 Q_ASSERT(stretch > qreal(0));
3484 *currentPage <<
"BT\n"
3485 <<
"/F" << font->object_id << size <<
"Tf "
3486 << stretch << (synthesized & QFontEngine::SynthesizedItalic
3487 ?
"0 .3 -1 0 0 Tm\n"
3488 :
"0 0 -1 0 0 Tm\n");
3493 const unsigned short *logClusters = ti.logClusters;
3497 while (end < ti.num_chars && logClusters[end] == logClusters[pos])
3499 *currentPage <<
"/Span << /ActualText <FEFF";
3500 for (
int i = pos; i < end; ++i) {
3501 s << toHex((ushort)ti.chars[i].unicode(), buf);
3503 *currentPage <<
"> >>\n"
3506 int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end];
3507 for (
int gs = logClusters[pos]; gs < ge; ++gs)
3508 *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf);
3509 *currentPage <<
"> Tj\n"
3512 }
while (pos < ti.num_chars);
3516 for (
int i = 0; i < glyphs.size(); ++i) {
3517 qreal x = positions[i].x.toReal();
3518 qreal y = positions[i].y.toReal();
3519 if (synthesized & QFontEngine::SynthesizedItalic)
3523 qsizetype g = font->addGlyph(glyphs[i]);
3524 *currentPage << x - last_x << last_y - y <<
"Td <"
3525 << QPdf::toHex((ushort)g, buf) <<
"> Tj\n";
3529 if (synthesized & QFontEngine::SynthesizedBold) {
3530 *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic
3531 ?
"0 .3 -1 0 0 Tm\n"
3532 :
"0 0 -1 0 0 Tm\n");
3533 *currentPage <<
"/Span << /ActualText <> >> BDC\n";
3534 last_x = 0.5*fe->lineThickness().toReal();
3536 for (
int i = 0; i < glyphs.size(); ++i) {
3537 qreal x = positions[i].x.toReal();
3538 qreal y = positions[i].y.toReal();
3539 if (synthesized & QFontEngine::SynthesizedItalic)
3543 qsizetype g = font->addGlyph(glyphs[i]);
3544 *currentPage << x - last_x << last_y - y <<
"Td <"
3545 << QPdf::toHex((ushort)g, buf) <<
"> Tj\n";
3549 *currentPage <<
"EMC\n";
3553 *currentPage <<
"ET\n";
3556QTransform QPdfEnginePrivate::pageMatrix()
const
3558 qreal userUnit = calcUserUnit();
3559 qreal scale = 72. / userUnit / resolution;
3560 QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, m_pageLayout.fullRectPoints().height() / userUnit);
3561 if (m_pageLayout.mode() != QPageLayout::FullPageMode) {
3562 QRect r = m_pageLayout.paintRectPixels(resolution);
3563 tmp.translate(r.left(), r.top());
3568void QPdfEnginePrivate::newPage()
3570 if (currentPage && currentPage->pageSize.isEmpty())
3571 currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3575 currentPage =
new QPdfPage;
3576 currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3577 stroker.stream = currentPage;
3578 pages.append(requestObject());
3580 *currentPage <<
"/GSa gs /CSp cs /CSp CS\n"
3581 << QPdf::generateMatrix(pageMatrix())
ByteStream(QByteArray *ba, bool fileBacking=false)
ByteStream(bool fileBacking=false)
static int maxMemorySize()
const char * toHex(ushort u, char *buffer)
QByteArray generatePath(const QPainterPath &path, const QTransform &matrix, PathFlags flags)
QByteArray ascii85Encode(const QByteArray &input)
QByteArray generateDashes(const QPen &pen)
QByteArray patternForBrush(const QBrush &b)
QByteArray generateMatrix(const QTransform &matrix)
Q_GUI_EXPORT bool qt_isExtendedRadialGradient(const QBrush &brush)
const char * qt_int_to_string(int val, char *buf)
static void initResources()
static bool is_monochrome(const QList< QRgb > &colorTable)
Q_DECLARE_TYPEINFO(QGradientBound, Q_PRIMITIVE_TYPE)
static void removeTransparencyFromBrush(QBrush &brush)
static void lineToHook(qfixed x, qfixed y, void *data)
const char * qt_real_to_string(qreal val, char *buf)
constexpr QPaintEngine::PaintEngineFeatures qt_pdf_decide_features()
static const bool interpolateImages
static void moveToHook(qfixed x, qfixed y, void *data)
static const char *const pattern_for_brush[]
static void cubicToHook(qfixed c1x, qfixed c1y, qfixed c2x, qfixed c2y, qfixed ex, qfixed ey, void *data)
static const bool do_compress
void setPen(const QPen &pen, QPainter::RenderHints hints)
void strokePath(const QPainterPath &path)