9#include "qplatformdefs.h"
11#include <private/qcmyk_p.h>
12#include <private/qfont_p.h>
13#include <private/qmath_p.h>
14#include <private/qpainter_p.h>
17#include <qcryptographichash.h>
21#include <qimagewriter.h>
23#include <qtemporaryfile.h>
26#include <qxmlstream.h>
36static const bool do_compress =
false;
47 Q_INIT_RESOURCE(qpdf);
52using namespace Qt::StringLiterals;
56 QPaintEngine::PaintEngineFeatures f = QPaintEngine::AllFeatures;
57 f &= ~(QPaintEngine::PorterDuff
58 | QPaintEngine::PerspectiveTransform
59 | QPaintEngine::ObjectBoundingModeGradients
60 | QPaintEngine::ConicalGradientFill);
69 if (brush.style() == Qt::SolidPattern) {
70 QColor color = brush.color();
71 if (color.alpha() != 255) {
73 brush.setColor(color);
79 if (qt_isExtendedRadialGradient(brush)) {
80 brush = QBrush(Qt::black);
84 if (brush.style() == Qt::LinearGradientPattern
85 || brush.style() == Qt::RadialGradientPattern
86 || brush.style() == Qt::ConicalGradientPattern) {
88 QGradientStops stops = brush.gradient()->stops();
89 for (
int i = 0; i < stops.size(); ++i) {
90 if (stops[i].second.alpha() != 255)
91 stops[i].second.setAlpha(255);
94 const_cast<QGradient*>(brush.gradient())->setStops(stops);
98 if (brush.style() == Qt::TexturePattern) {
107 const char *ret = buf;
109 if (!qIsFinite(val) || std::abs(val) > std::numeric_limits<quint32>::max()) {
120 qreal frac =
std::modf(val, &val);
123 int ifrac = (
int)(frac * 1000000000);
124 if (ifrac == 1000000000) {
131 output[i] =
'0' + (ival % 10);
135 int fact = 100000000;
140 *(buf++) = output[--i];
149 *(buf++) =
'0' + ((ifrac/fact) % 10);
159 const char *ret = buf;
167 output[i] =
'0' + (val % 10);
175 *(buf++) = output[--i];
186 fileBackingEnabled(fileBacking),
187 fileBackingActive(
false),
190 dev->open(QIODevice::ReadWrite | QIODevice::Append);
195 fileBackingEnabled(fileBacking),
196 fileBackingActive(
false),
199 dev->open(QIODevice::ReadWrite);
209 if (handleDirty) prepareBuffer();
216 if (handleDirty) prepareBuffer();
217 dev->write(str, strlen(str));
223 if (handleDirty) prepareBuffer();
230 Q_ASSERT(!src.dev->isSequential());
231 if (handleDirty) prepareBuffer();
235 qint64 pos = s.dev->pos();
237 while (!s.dev->atEnd()) {
247 qt_real_to_string(val, buf);
261 qt_real_to_string(p.x(), buf);
263 qt_real_to_string(p.y(), buf);
277 dev->open(QIODevice::ReadWrite | QIODevice::Truncate);
282 Q_ASSERT(!dev->isSequential());
283 qint64 size = dev->size();
284 if (fileBackingEnabled && !fileBackingActive
287 QTemporaryFile *newFile =
new QTemporaryFile;
288 if (newFile->open()) {
290 while (!dev->atEnd()) {
291 QByteArray buf = dev->read(chunkSize());
297 fileBackingActive =
true;
300 if (dev->pos() != size) {
307#define QT_PATH_ELEMENT(elm)
312 if (!path.elementCount())
318 for (
int i = 0; i < path.elementCount(); ++i) {
319 const QPainterPath::Element &elm = path.elementAt(i);
321 case QPainterPath::MoveToElement:
323 && path.elementAt(start).x == path.elementAt(i-1).x
324 && path.elementAt(start).y == path.elementAt(i-1).y)
326 s << matrix.map(QPointF(elm.x, elm.y)) <<
"m\n";
329 case QPainterPath::LineToElement:
330 s << matrix.map(QPointF(elm.x, elm.y)) <<
"l\n";
332 case QPainterPath::CurveToElement:
333 Q_ASSERT(path.elementAt(i+1).type == QPainterPath::CurveToDataElement);
334 Q_ASSERT(path.elementAt(i+2).type == QPainterPath::CurveToDataElement);
335 s << matrix.map(QPointF(elm.x, elm.y))
336 << matrix.map(QPointF(path.elementAt(i+1).x, path.elementAt(i+1).y))
337 << matrix.map(QPointF(path.elementAt(i+2).x, path.elementAt(i+2).y))
342 qFatal(
"QPdf::generatePath(), unhandled type: %d", elm.type);
346 && path.elementAt(start).x == path.elementAt(path.elementCount()-1).x
347 && path.elementAt(start).y == path.elementAt(path.elementCount()-1).y)
350 Qt::FillRule fillRule = path.fillRule();
355 op = (fillRule == Qt::WindingFill) ?
"W n\n" :
"W* n\n";
358 op = (fillRule == Qt::WindingFill) ?
"f\n" :
"f*\n";
363 case FillAndStrokePath:
364 op = (fillRule == Qt::WindingFill) ?
"B\n" :
"B*\n";
391 QList<qreal> dasharray = pen.dashPattern();
392 qreal w = pen.widthF();
395 for (
int i = 0; i < dasharray.size(); ++i) {
396 qreal dw = dasharray.at(i)*w;
397 if (dw < 0.0001) dw = 0.0001;
401 s << pen.dashOffset() * w;
563 int style = b.style();
564 if (style > Qt::DiagCrossPattern)
576 t->matrix.map(x, y, &x, &y);
585 t->matrix.map(x, y, &x, &y);
590 qfixed c2x, qfixed c2y,
591 qfixed ex, qfixed ey,
596 t->matrix.map(c1x, c1y, &c1x, &c1y);
597 t->matrix.map(c2x, c2y, &c2x, &c2y);
598 t->matrix.map(ex, ey, &ex, &ey);
611 stroker = &basicStroker;
612 basicStroker.setMoveToHook(moveToHook);
613 basicStroker.setLineToHook(lineToHook);
614 basicStroker.setCubicToHook(cubicToHook);
616 basicStroker.setStrokeWidth(.1);
621 if (pen.style() == Qt::NoPen) {
625 qreal w = pen.widthF();
626 bool zeroWidth = w < 0.0001;
631 basicStroker.setStrokeWidth(w);
632 basicStroker.setCapStyle(pen.capStyle());
633 basicStroker.setJoinStyle(pen.joinStyle());
634 basicStroker.setMiterLimit(pen.miterLimit());
636 QList<qreal> dashpattern = pen.dashPattern();
638 for (
int i = 0; i < dashpattern.size(); ++i)
639 dashpattern[i] *= 10.;
641 if (!dashpattern.isEmpty()) {
642 dashStroker.setDashPattern(dashpattern);
643 dashStroker.setDashOffset(pen.dashOffset());
644 stroker = &dashStroker;
646 stroker = &basicStroker;
656 stroker->strokePath(path,
this, cosmeticPen ? matrix : QTransform());
662 int isize = input.size()/4*4;
664 output.resize(input.size()*5/4+7);
665 char *out = output.data();
666 const uchar *in = (
const uchar *)input.constData();
667 for (
int i = 0; i < isize; i += 4) {
668 uint val = (((uint)in[i])<<24) + (((uint)in[i+1])<<16) + (((uint)in[i+2])<<8) + (uint)in[i+3];
683 *(out++) = base[0] +
'!';
684 *(out++) = base[1] +
'!';
685 *(out++) = base[2] +
'!';
686 *(out++) = base[3] +
'!';
687 *(out++) = base[4] +
'!';
691 int remaining = input.size() - isize;
694 for (
int i = isize; i < input.size(); ++i)
695 val = (val << 8) + in[i];
696 val <<= 8*(4-remaining);
707 for (
int i = 0; i < remaining+1; ++i)
708 *(out++) = base[i] +
'!';
712 output.resize(out-output.data());
716const char *
QPdf::toHex(ushort u,
char *buffer)
720 ushort hex = (u & 0x000f);
724 buffer[i] =
'A'+(hex-0x0a);
736 ushort hex = (u & 0x000f);
740 buffer[i] =
'A'+(hex-0x0a);
754void QPdfPage::streamImage(
int w,
int h, uint object)
756 *
this << w <<
"0 0 " << -h <<
"0 " << h <<
"cm /Im" << object <<
" Do\n";
757 if (!images.contains(object))
758 images.append(object);
762QPdfEngine::QPdfEngine(QPdfEnginePrivate &dd)
763 : QPaintEngine(dd, qt_pdf_decide_features())
767QPdfEngine::QPdfEngine()
768 : QPaintEngine(*
new QPdfEnginePrivate(), qt_pdf_decide_features())
772void QPdfEngine::setOutputFilename(
const QString &filename)
775 d->outputFileName = filename;
779void QPdfEngine::drawPoints (
const QPointF *points,
int pointCount)
786 for (
int i=0; i!=pointCount;++i) {
788 p.lineTo(points[i] + QPointF(0, 0.001));
791 bool hadBrush = d->hasBrush;
794 d->hasBrush = hadBrush;
797void QPdfEngine::drawLines (
const QLineF *lines,
int lineCount)
804 for (
int i=0; i!=lineCount;++i) {
805 p.moveTo(lines[i].p1());
806 p.lineTo(lines[i].p2());
808 bool hadBrush = d->hasBrush;
811 d->hasBrush = hadBrush;
814void QPdfEngine::drawRects (
const QRectF *rects,
int rectCount)
821 if (d->clipEnabled && d->allClipped)
823 if (!d->hasPen && !d->hasBrush)
826 if ((d->simplePen && !d->needsTransform) || !d->hasPen) {
828 if (!d->hasPen && d->needsTransform)
829 *d->currentPage <<
"q\n" << QPdf::generateMatrix(d->stroker.matrix);
830 for (
int i = 0; i < rectCount; ++i)
831 *d->currentPage << rects[i].x() << rects[i].y() << rects[i].width() << rects[i].height() <<
"re\n";
832 *d->currentPage << (d->hasPen ? (d->hasBrush ?
"B\n" :
"S\n") :
"f\n");
833 if (!d->hasPen && d->needsTransform)
834 *d->currentPage <<
"Q\n";
837 for (
int i=0; i!=rectCount; ++i)
843void QPdfEngine::drawPolygon(
const QPointF *points,
int pointCount, PolygonDrawMode mode)
847 if (!points || !pointCount)
850 bool hb = d->hasBrush;
855 p.setFillRule(Qt::OddEvenFill);
859 p.setFillRule(Qt::WindingFill);
869 for (
int i = 1; i < pointCount; ++i)
872 if (mode != PolylineMode)
879void QPdfEngine::drawPath (
const QPainterPath &p)
883 if (d->clipEnabled && d->allClipped)
885 if (!d->hasPen && !d->hasBrush)
890 *d->currentPage << QPdf::generatePath(p, d->needsTransform ? d->stroker.matrix : QTransform(),
891 d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath);
894 *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath);
896 *d->currentPage <<
"q\n";
898 d->brush = d->pen.brush();
900 d->stroker.strokePath(p);
901 *d->currentPage <<
"Q\n";
907void QPdfEngine::drawPixmap (
const QRectF &rectangle,
const QPixmap &pixmap,
const QRectF &sr)
909 if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull())
915 QRect sourceRect = sr.toRect();
916 QPixmap pm = sourceRect != pixmap.rect() ? pixmap.copy(sourceRect) : pixmap;
917 QImage image = pm.toImage();
919 const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering);
920 const int object = d->addImage(image, &bitmap, lossless, pm.cacheKey());
924 *d->currentPage <<
"q\n";
926 if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) {
927 int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity));
929 *d->currentPage <<
"/GState" << stateObject <<
"gs\n";
931 *d->currentPage <<
"/GSa gs\n";
933 *d->currentPage <<
"/GSa gs\n";
937 << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(),
938 rectangle.x(), rectangle.y()) * (!d->needsTransform ? QTransform() : d->stroker.matrix));
941 d->brush = d->pen.brush();
944 d->currentPage->streamImage(image.width(), image.height(), object);
945 *d->currentPage <<
"Q\n";
950void QPdfEngine::drawImage(
const QRectF &rectangle,
const QImage &image,
const QRectF &sr, Qt::ImageConversionFlags)
952 if (sr.isEmpty() || rectangle.isEmpty() || image.isNull())
956 QRect sourceRect = sr.toRect();
957 QImage im = sourceRect != image.rect() ? image.copy(sourceRect) : image;
959 const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering);
960 const int object = d->addImage(im, &bitmap, lossless, im.cacheKey());
964 *d->currentPage <<
"q\n";
966 if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) {
967 int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity));
969 *d->currentPage <<
"/GState" << stateObject <<
"gs\n";
971 *d->currentPage <<
"/GSa gs\n";
973 *d->currentPage <<
"/GSa gs\n";
977 << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(),
978 rectangle.x(), rectangle.y()) * (!d->needsTransform ? QTransform() : d->stroker.matrix));
980 d->currentPage->streamImage(im.width(), im.height(), object);
981 *d->currentPage <<
"Q\n";
984void QPdfEngine::drawTiledPixmap (
const QRectF &rectangle,
const QPixmap &pixmap,
const QPointF &point)
988 bool bitmap = (pixmap.depth() == 1);
990 QPointF bo = d->brushOrigin;
993 bool hb = d->hasBrush;
996 d->brush = QBrush(pixmap);
999 d->brush.setColor(d->pen.color());
1001 d->brushOrigin = -point;
1002 *d->currentPage <<
"q\n";
1005 drawRects(&rectangle, 1);
1006 *d->currentPage <<
"Q\n";
1011 d->brushOrigin = bo;
1014void QPdfEngine::drawTextItem(
const QPointF &p,
const QTextItem &textItem)
1018 if (!d->hasPen || (d->clipEnabled && d->allClipped))
1021 if (d->stroker.matrix.type() >= QTransform::TxProject) {
1022 QPaintEngine::drawTextItem(p, textItem);
1026 *d->currentPage <<
"q\n";
1027 if (d->needsTransform)
1028 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1030 bool hp = d->hasPen;
1032 QBrush b = d->brush;
1033 d->brush = d->pen.brush();
1036 const QTextItemInt &ti =
static_cast<
const QTextItemInt &>(textItem);
1037 Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi);
1038 d->drawTextItem(p, ti);
1041 *d->currentPage <<
"Q\n";
1045void QPdfEngine::drawHyperlink(
const QRectF &r,
const QUrl &url)
1052 if (d->pdfVersion == QPdfEngine::Version_X4)
1055 const uint annot = d->addXrefEntry(-1);
1056 const QByteArray urlascii = url.toEncoded();
1057 int len = urlascii.size();
1058 QVarLengthArray<
char> url_esc;
1059 url_esc.reserve(len + 1);
1060 for (
int j = 0; j < len; j++) {
1061 if (urlascii[j] ==
'(' || urlascii[j] ==
')' || urlascii[j] ==
'\\')
1062 url_esc.append(
'\\');
1063 url_esc.append(urlascii[j]);
1065 url_esc.append(
'\0');
1068 const QRectF rr = d->pageMatrix().mapRect(r);
1069 d->xprintf(
"<<\n/Type /Annot\n/Subtype /Link\n");
1071 if (d->pdfVersion == QPdfEngine::Version_A1b)
1072 d->xprintf(
"/F 4\n");
1074 d->xprintf(
"/Rect [");
1075 d->xprintf(
"%s ", qt_real_to_string(rr.left(), buf));
1076 d->xprintf(
"%s ", qt_real_to_string(rr.top(), buf));
1077 d->xprintf(
"%s ", qt_real_to_string(rr.right(), buf));
1078 d->xprintf(
"%s", qt_real_to_string(rr.bottom(), buf));
1079 d->xprintf(
"]\n/Border [0 0 0]\n/A <<\n");
1080 d->xprintf(
"/Type /Action\n/S /URI\n/URI (%s)\n", url_esc.constData());
1081 d->xprintf(
">>\n>>\n");
1082 d->xprintf(
"endobj\n");
1083 d->currentPage->annotations.append(annot);
1086void QPdfEngine::updateState(
const QPaintEngineState &state)
1090 QPaintEngine::DirtyFlags flags = state.state();
1092 if (flags & DirtyHints)
1093 flags |= DirtyBrush;
1095 if (flags & DirtyTransform)
1096 d->stroker.matrix = state.transform();
1098 if (flags & DirtyPen) {
1099 if (d->pdfVersion == QPdfEngine::Version_A1b) {
1100 QPen pen = state.pen();
1102 QColor penColor = pen.color();
1103 if (penColor.alpha() != 255)
1104 penColor.setAlpha(255);
1105 pen.setColor(penColor);
1107 QBrush penBrush = pen.brush();
1108 removeTransparencyFromBrush(penBrush);
1109 pen.setBrush(penBrush);
1113 d->pen = state.pen();
1115 d->hasPen = d->pen.style() != Qt::NoPen;
1116 bool oldCosmetic = d->stroker.cosmeticPen;
1117 d->stroker.setPen(d->pen, state.renderHints());
1118 QBrush penBrush = d->pen.brush();
1119 bool oldSimple = d->simplePen;
1120 d->simplePen = (d->hasPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque() && d->opacity == 1.0);
1121 if (oldSimple != d->simplePen || oldCosmetic != d->stroker.cosmeticPen)
1122 flags |= DirtyTransform;
1123 }
else if (flags & DirtyHints) {
1124 d->stroker.setPen(d->pen, state.renderHints());
1126 if (flags & DirtyBrush) {
1127 if (d->pdfVersion == QPdfEngine::Version_A1b) {
1128 QBrush brush = state.brush();
1129 removeTransparencyFromBrush(brush);
1132 d->brush = state.brush();
1134 if (d->brush.color().alpha() == 0 && d->brush.style() == Qt::SolidPattern)
1135 d->brush.setStyle(Qt::NoBrush);
1136 d->hasBrush = d->brush.style() != Qt::NoBrush;
1138 if (flags & DirtyBrushOrigin) {
1139 d->brushOrigin = state.brushOrigin();
1140 flags |= DirtyBrush;
1142 if (flags & DirtyOpacity) {
1143 d->opacity = state.opacity();
1144 if (d->simplePen && d->opacity != 1.0) {
1145 d->simplePen =
false;
1146 flags |= DirtyTransform;
1150 bool ce = d->clipEnabled;
1151 if (flags & DirtyClipPath) {
1152 d->clipEnabled =
true;
1153 updateClipPath(state.clipPath(), state.clipOperation());
1154 }
else if (flags & DirtyClipRegion) {
1155 d->clipEnabled =
true;
1157 for (
const QRect &rect : state.clipRegion())
1159 updateClipPath(path, state.clipOperation());
1160 flags |= DirtyClipPath;
1161 }
else if (flags & DirtyClipEnabled) {
1162 d->clipEnabled = state.isClipEnabled();
1165 if (ce != d->clipEnabled)
1166 flags |= DirtyClipPath;
1167 else if (!d->clipEnabled)
1168 flags &= ~DirtyClipPath;
1170 setupGraphicsState(flags);
1173void QPdfEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags)
1176 if (flags & DirtyClipPath)
1177 flags |= DirtyTransform|DirtyPen|DirtyBrush;
1179 if (flags & DirtyTransform) {
1180 *d->currentPage <<
"Q\n";
1181 flags |= DirtyPen|DirtyBrush;
1184 if (flags & DirtyClipPath) {
1185 *d->currentPage <<
"Q q\n";
1187 d->allClipped =
false;
1188 if (d->clipEnabled && !d->clips.isEmpty()) {
1189 for (
int i = 0; i < d->clips.size(); ++i) {
1190 if (d->clips.at(i).isEmpty()) {
1191 d->allClipped =
true;
1195 if (!d->allClipped) {
1196 for (
int i = 0; i < d->clips.size(); ++i) {
1197 *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath);
1203 if (flags & DirtyTransform) {
1204 *d->currentPage <<
"q\n";
1205 d->needsTransform =
false;
1206 if (!d->stroker.matrix.isIdentity()) {
1207 if (d->simplePen && !d->stroker.cosmeticPen)
1208 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1210 d->needsTransform =
true;
1213 if (flags & DirtyBrush)
1215 if (d->simplePen && (flags & DirtyPen))
1219void QPdfEngine::updateClipPath(
const QPainterPath &p, Qt::ClipOperation op)
1222 QPainterPath path = d->stroker.matrix.map(p);
1227 d->clipEnabled =
false;
1230 case Qt::ReplaceClip:
1232 d->clips.append(path);
1234 case Qt::IntersectClip:
1235 d->clips.append(path);
1240void QPdfEngine::setPen()
1243 if (d->pen.style() == Qt::NoPen)
1245 QBrush b = d->pen.brush();
1246 Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque());
1248 d->writeColor(QPdfEnginePrivate::ColorDomain::Stroking, b.color());
1249 *d->currentPage <<
"SCN\n";
1250 *d->currentPage << d->pen.widthF() <<
"w ";
1252 int pdfCapStyle = 0;
1253 switch(d->pen.capStyle()) {
1266 *d->currentPage << pdfCapStyle <<
"J ";
1268 int pdfJoinStyle = 0;
1269 switch(d->pen.joinStyle()) {
1271 case Qt::SvgMiterJoin:
1272 *d->currentPage << qMax(qreal(1.0), d->pen.miterLimit()) <<
"M ";
1284 *d->currentPage << pdfJoinStyle <<
"j ";
1286 *d->currentPage << QPdf::generateDashes(d->pen);
1290void QPdfEngine::setBrush()
1293 Qt::BrushStyle style = d->brush.style();
1294 if (style == Qt::NoBrush)
1298 int gStateObject = 0;
1299 int patternObject = d->addBrushPattern(d->stroker.matrix, &specifyColor, &gStateObject);
1300 if (!patternObject && !specifyColor)
1303 const auto domain = patternObject ? QPdfEnginePrivate::ColorDomain::NonStrokingPattern
1304 : QPdfEnginePrivate::ColorDomain::NonStroking;
1305 d->writeColor(domain, specifyColor ? d->brush.color() : QColor());
1307 *d->currentPage <<
"/Pat" << patternObject;
1308 *d->currentPage <<
"scn\n";
1311 *d->currentPage <<
"/GState" << gStateObject <<
"gs\n";
1313 *d->currentPage <<
"/GSa gs\n";
1317bool QPdfEngine::newPage()
1324 setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath);
1325 QFile *outfile = qobject_cast<QFile*> (d->outDevice);
1326 if (outfile && outfile->error() != QFile::NoError)
1331QPaintEngine::Type QPdfEngine::type()
const
1333 return QPaintEngine::Pdf;
1336void QPdfEngine::setResolution(
int resolution)
1339 d->resolution = resolution;
1342int QPdfEngine::resolution()
const
1344 Q_D(
const QPdfEngine);
1345 return d->resolution;
1348void QPdfEngine::setPdfVersion(PdfVersion version)
1351 d->pdfVersion = version;
1354void QPdfEngine::setDocumentXmpMetadata(
const QByteArray &xmpMetadata)
1357 d->xmpDocumentMetadata = xmpMetadata;
1360QByteArray QPdfEngine::documentXmpMetadata()
const
1362 Q_D(
const QPdfEngine);
1363 return d->xmpDocumentMetadata;
1366void QPdfEngine::setPageLayout(
const QPageLayout &pageLayout)
1369 d->m_pageLayout = pageLayout;
1372void QPdfEngine::setPageSize(
const QPageSize &pageSize)
1375 d->m_pageLayout.setPageSize(pageSize);
1378void QPdfEngine::setPageOrientation(QPageLayout::Orientation orientation)
1381 d->m_pageLayout.setOrientation(orientation);
1384void QPdfEngine::setPageMargins(
const QMarginsF &margins, QPageLayout::Unit units)
1387 d->m_pageLayout.setUnits(units);
1388 d->m_pageLayout.setMargins(margins);
1391QPageLayout QPdfEngine::pageLayout()
const
1393 Q_D(
const QPdfEngine);
1394 return d->m_pageLayout;
1398int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType)
const
1400 Q_D(
const QPdfEngine);
1402 switch (metricType) {
1403 case QPaintDevice::PdmWidth:
1404 val = d->m_pageLayout.paintRectPixels(d->resolution).width();
1406 case QPaintDevice::PdmHeight:
1407 val = d->m_pageLayout.paintRectPixels(d->resolution).height();
1409 case QPaintDevice::PdmDpiX:
1410 case QPaintDevice::PdmDpiY:
1411 val = d->resolution;
1413 case QPaintDevice::PdmPhysicalDpiX:
1414 case QPaintDevice::PdmPhysicalDpiY:
1417 case QPaintDevice::PdmWidthMM:
1418 val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).width());
1420 case QPaintDevice::PdmHeightMM:
1421 val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).height());
1423 case QPaintDevice::PdmNumColors:
1426 case QPaintDevice::PdmDepth:
1429 case QPaintDevice::PdmDevicePixelRatio:
1432 case QPaintDevice::PdmDevicePixelRatioScaled:
1433 val = 1 * QPaintDevice::devicePixelRatioFScale();
1436 qWarning(
"QPdfWriter::metric: Invalid metric command");
1442QPdfEnginePrivate::QPdfEnginePrivate()
1443 : clipEnabled(
false), allClipped(
false), hasPen(
true), hasBrush(
false), simplePen(
false),
1444 needsTransform(
false), pdfVersion(QPdfEngine::Version_1_4),
1445 colorModel(QPdfEngine::ColorModel::Auto),
1446 outDevice(
nullptr), ownsDevice(
false),
1448 m_pageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(10, 10, 10, 10))
1453 currentPage =
nullptr;
1454 stroker.stream =
nullptr;
1458 stream =
new QDataStream;
1461bool QPdfEngine::begin(QPaintDevice *pdev)
1466 if (!d->outDevice) {
1467 if (!d->outputFileName.isEmpty()) {
1468 QFile *file =
new QFile(d->outputFileName);
1469 if (!file->open(QFile::WriteOnly|QFile::Truncate)) {
1473 d->outDevice = file;
1477 d->ownsDevice =
true;
1480 d->currentObject = 1;
1482 d->currentPage =
new QPdfPage;
1483 d->stroker.stream = d->currentPage;
1486 d->stream->setDevice(d->outDevice);
1490 d->hasBrush =
false;
1491 d->clipEnabled =
false;
1492 d->allClipped =
false;
1494 d->xrefPositions.clear();
1498 d->attachmentsRoot = 0;
1501 d->graphicsState = 0;
1502 d->patternColorSpaceRGB = 0;
1503 d->patternColorSpaceGrayscale = 0;
1504 d->patternColorSpaceCMYK = 0;
1505 d->simplePen =
false;
1506 d->needsTransform =
false;
1509 d->imageCache.clear();
1510 d->alphaCache.clear();
1519bool QPdfEngine::end()
1524 d->stream->setDevice(
nullptr);
1526 qDeleteAll(d->fonts);
1528 delete d->currentPage;
1529 d->currentPage =
nullptr;
1531 if (d->outDevice && d->ownsDevice) {
1532 d->outDevice->close();
1533 delete d->outDevice;
1534 d->outDevice =
nullptr;
1537 d->destCache.clear();
1538 d->fileCache.clear();
1544void QPdfEngine::addFileAttachment(
const QString &fileName,
const QByteArray &data,
const QString &mimeType)
1547 d->fileCache.push_back({fileName, data, mimeType});
1550QPdfEnginePrivate::~QPdfEnginePrivate()
1557void QPdfEnginePrivate::writeHeader()
1559 addXrefEntry(0,
false);
1562 static const char mapping[][4] = {
1568 static const size_t numMappings =
sizeof mapping /
sizeof *mapping;
1569 const char *verStr = mapping[size_t(pdfVersion) < numMappings ? pdfVersion : 0];
1571 xprintf(
"%%PDF-%s\n", verStr);
1572 xprintf(
"%%\303\242\303\243\n");
1574#if QT_CONFIG(timezone)
1575 const QDateTime now = QDateTime::currentDateTime(QTimeZone::systemTimeZone());
1577 const QDateTime now = QDateTime::currentDateTimeUtc();
1582 const int metaDataObj = writeXmpDocumentMetaData(now);
1583 const int outputIntentObj = [&]() {
1584 switch (pdfVersion) {
1585 case QPdfEngine::Version_1_4:
1586 case QPdfEngine::Version_1_6:
1588 case QPdfEngine::Version_A1b:
1589 case QPdfEngine::Version_X4:
1590 return writeOutputIntent();
1596 catalog = addXrefEntry(-1);
1597 pageRoot = requestObject();
1598 namesRoot = requestObject();
1603 QPdf::ByteStream s(&catalog);
1605 <<
"/Type /Catalog\n"
1606 <<
"/Pages " << pageRoot <<
"0 R\n"
1607 <<
"/Names " << namesRoot <<
"0 R\n";
1609 s <<
"/Metadata " << metaDataObj <<
"0 R\n";
1611 if (outputIntentObj >= 0)
1612 s <<
"/OutputIntents [" << outputIntentObj <<
"0 R]\n";
1621 graphicsState = addXrefEntry(-1);
1623 "/Type /ExtGState\n"
1634 patternColorSpaceRGB = addXrefEntry(-1);
1635 xprintf(
"[/Pattern /DeviceRGB]\n"
1637 patternColorSpaceGrayscale = addXrefEntry(-1);
1638 xprintf(
"[/Pattern /DeviceGray]\n"
1640 patternColorSpaceCMYK = addXrefEntry(-1);
1641 xprintf(
"[/Pattern /DeviceCMYK]\n"
1645QPdfEngine::ColorModel QPdfEnginePrivate::colorModelForColor(
const QColor &color)
const
1647 switch (colorModel) {
1648 case QPdfEngine::ColorModel::RGB:
1649 case QPdfEngine::ColorModel::Grayscale:
1650 case QPdfEngine::ColorModel::CMYK:
1652 case QPdfEngine::ColorModel::Auto:
1653 switch (color.spec()) {
1654 case QColor::Invalid:
1658 case QColor::ExtendedRgb:
1659 return QPdfEngine::ColorModel::RGB;
1661 return QPdfEngine::ColorModel::CMYK;
1667 Q_UNREACHABLE_RETURN(QPdfEngine::ColorModel::RGB);
1670void QPdfEnginePrivate::writeColor(ColorDomain domain,
const QColor &color)
1674 const QPdfEngine::ColorModel actualColorModel = colorModelForColor(color);
1676 switch (actualColorModel) {
1677 case QPdfEngine::ColorModel::RGB:
1679 case ColorDomain::Stroking:
1680 *currentPage <<
"/CSp CS\n";
break;
1681 case ColorDomain::NonStroking:
1682 *currentPage <<
"/CSp cs\n";
break;
1683 case ColorDomain::NonStrokingPattern:
1684 *currentPage <<
"/PCSp cs\n";
break;
1687 case QPdfEngine::ColorModel::Grayscale:
1689 case ColorDomain::Stroking:
1690 *currentPage <<
"/CSpg CS\n";
break;
1691 case ColorDomain::NonStroking:
1692 *currentPage <<
"/CSpg cs\n";
break;
1693 case ColorDomain::NonStrokingPattern:
1694 *currentPage <<
"/PCSpg cs\n";
break;
1697 case QPdfEngine::ColorModel::CMYK:
1699 case ColorDomain::Stroking:
1700 *currentPage <<
"/CSpcmyk CS\n";
break;
1701 case ColorDomain::NonStroking:
1702 *currentPage <<
"/CSpcmyk cs\n";
break;
1703 case ColorDomain::NonStrokingPattern:
1704 *currentPage <<
"/PCSpcmyk cs\n";
break;
1707 case QPdfEngine::ColorModel::Auto:
1708 Q_UNREACHABLE_RETURN();
1712 if (!color.isValid())
1715 switch (actualColorModel) {
1716 case QPdfEngine::ColorModel::RGB:
1717 *currentPage << color.redF()
1721 case QPdfEngine::ColorModel::Grayscale: {
1722 const qreal gray = qGray(color.rgba()) / 255.;
1723 *currentPage << gray;
1726 case QPdfEngine::ColorModel::CMYK:
1727 *currentPage << color.cyanF()
1732 case QPdfEngine::ColorModel::Auto:
1733 Q_UNREACHABLE_RETURN();
1737void QPdfEnginePrivate::writeInfo(
const QDateTime &date)
1739 info = addXrefEntry(-1);
1740 write(
"<<\n/Title ");
1742 write(
"\n/Creator ");
1743 printString(creator);
1744 write(
"\n/Author ");
1745 printString(author);
1746 write(
"\n/Producer ");
1747 printString(QString::fromLatin1(
"Qt " QT_VERSION_STR));
1749 const QTime t = date.time();
1750 const QDate d = date.date();
1752 constexpr size_t formattedDateSize = 26;
1753 char formattedDate[formattedDateSize];
1754 const int year = qBound(0, d.year(), 9999);
1755 auto printedSize = std::snprintf(formattedDate,
1757 "(D:%04d%02d%02d%02d%02d%02d",
1764 const int offset = date.offsetFromUtc();
1765 const int hours = (offset / 60) / 60;
1766 const int mins = (offset / 60) % 60;
1768 std::snprintf(formattedDate + printedSize,
1769 formattedDateSize - printedSize,
1770 "-%02d'%02d')", -hours, -mins);
1771 }
else if (offset > 0) {
1772 std::snprintf(formattedDate + printedSize,
1773 formattedDateSize - printedSize,
1774 "+%02d'%02d')", hours, mins);
1776 std::snprintf(formattedDate + printedSize,
1777 formattedDateSize - printedSize,
1781 write(
"\n/CreationDate ");
1782 write(formattedDate);
1783 write(
"\n/ModDate ");
1784 write(formattedDate);
1786 write(
"\n/Trapped /False\n"
1791int QPdfEnginePrivate::writeXmpDocumentMetaData(
const QDateTime &date)
1793 const int metaDataObj = addXrefEntry(-1);
1794 QByteArray metaDataContent;
1796 if (!xmpDocumentMetadata.isEmpty()) {
1797 metaDataContent = xmpDocumentMetadata;
1799 const QString producer(QString::fromLatin1(
"Qt " QT_VERSION_STR));
1800 const QString metaDataDate = date.toString(Qt::ISODate);
1802 using namespace Qt::Literals;
1803 constexpr QLatin1String xmlNS =
"http://www.w3.org/XML/1998/namespace"_L1;
1805 constexpr QLatin1String adobeNS =
"adobe:ns:meta/"_L1;
1806 constexpr QLatin1String rdfNS =
"http://www.w3.org/1999/02/22-rdf-syntax-ns#"_L1;
1807 constexpr QLatin1String dcNS =
"http://purl.org/dc/elements/1.1/"_L1;
1808 constexpr QLatin1String xmpNS =
"http://ns.adobe.com/xap/1.0/"_L1;
1809 constexpr QLatin1String xmpMMNS =
"http://ns.adobe.com/xap/1.0/mm/"_L1;
1810 constexpr QLatin1String pdfNS =
"http://ns.adobe.com/pdf/1.3/"_L1;
1811 constexpr QLatin1String pdfaidNS =
"http://www.aiim.org/pdfa/ns/id/"_L1;
1812 constexpr QLatin1String pdfxidNS =
"http://www.npes.org/pdfx/ns/id/"_L1;
1814 QBuffer output(&metaDataContent);
1815 output.open(QIODevice::WriteOnly);
1816 output.write(
"<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>");
1818 QXmlStreamWriter w(&output);
1819 w.setAutoFormatting(
true);
1820 w.writeNamespace(adobeNS,
"x");
1821 w.writeNamespace(rdfNS,
"rdf");
1822 w.writeNamespace(dcNS,
"dc");
1823 w.writeNamespace(xmpNS,
"xmp");
1824 w.writeNamespace(xmpMMNS,
"xmpMM");
1825 w.writeNamespace(pdfNS,
"pdf");
1826 w.writeNamespace(pdfaidNS,
"pdfaid");
1827 w.writeNamespace(pdfxidNS,
"pdfxid");
1829 w.writeStartElement(adobeNS,
"xmpmeta");
1830 w.writeStartElement(rdfNS,
"RDF");
1833
1834
1835
1836
1837
1838
1841 w.writeStartElement(rdfNS,
"Description");
1842 w.writeAttribute(rdfNS,
"about",
"");
1843 w.writeStartElement(dcNS,
"title");
1844 w.writeStartElement(rdfNS,
"Alt");
1845 w.writeStartElement(rdfNS,
"li");
1846 w.writeAttribute(xmlNS,
"lang",
"x-default");
1847 w.writeCharacters(title);
1848 w.writeEndElement();
1849 w.writeEndElement();
1850 w.writeEndElement();
1851 w.writeStartElement(dcNS,
"creator");
1852 w.writeStartElement(rdfNS,
"Seq");
1853 w.writeStartElement(rdfNS,
"li");
1854 w.writeCharacters(author);
1855 w.writeEndElement();
1856 w.writeEndElement();
1857 w.writeEndElement();
1858 w.writeEndElement();
1861 w.writeStartElement(rdfNS,
"Description");
1862 w.writeAttribute(rdfNS,
"about",
"");
1863 w.writeAttribute(pdfNS,
"Producer", producer);
1864 w.writeAttribute(pdfNS,
"Trapped",
"False");
1865 w.writeEndElement();
1868 w.writeStartElement(rdfNS,
"Description");
1869 w.writeAttribute(rdfNS,
"about",
"");
1870 w.writeAttribute(xmpNS,
"CreatorTool", creator);
1871 w.writeAttribute(xmpNS,
"CreateDate", metaDataDate);
1872 w.writeAttribute(xmpNS,
"ModifyDate", metaDataDate);
1873 w.writeAttribute(xmpNS,
"MetadataDate", metaDataDate);
1874 w.writeEndElement();
1877 w.writeStartElement(rdfNS,
"Description");
1878 w.writeAttribute(rdfNS,
"about",
"");
1879 w.writeAttribute(xmpMMNS,
"DocumentID",
"uuid:"_L1 + documentId.toString(QUuid::WithoutBraces));
1880 w.writeAttribute(xmpMMNS,
"VersionID",
"1");
1881 w.writeAttribute(xmpMMNS,
"RenditionClass",
"default");
1882 w.writeEndElement();
1885 switch (pdfVersion) {
1886 case QPdfEngine::Version_1_4:
1888 case QPdfEngine::Version_A1b:
1889 w.writeStartElement(rdfNS,
"Description");
1890 w.writeAttribute(rdfNS,
"about",
"");
1891 w.writeAttribute(pdfaidNS,
"part",
"1");
1892 w.writeAttribute(pdfaidNS,
"conformance",
"B");
1893 w.writeEndElement();
1895 case QPdfEngine::Version_1_6:
1897 case QPdfEngine::Version_X4:
1898 w.writeStartElement(rdfNS,
"Description");
1899 w.writeAttribute(rdfNS,
"about",
"");
1900 w.writeAttribute(pdfxidNS,
"GTS_PDFXVersion",
"PDF/X-4");
1901 w.writeEndElement();
1905 w.writeEndElement();
1906 w.writeEndElement();
1908 w.writeEndDocument();
1909 output.write(
"<?xpacket end='w'?>");
1913 "/Type /Metadata /Subtype /XML\n"
1914 "/Length %" PRIdQSIZETYPE
"\n"
1916 "stream\n", metaDataContent.size());
1917 write(metaDataContent);
1918 xprintf(
"\nendstream\n"
1924int QPdfEnginePrivate::writeOutputIntent()
1926 const int colorProfileEntry = addXrefEntry(-1);
1928 const QColorSpace profile = outputIntent.outputProfile();
1929 const QByteArray colorProfileData = profile.iccProfile();
1932 QPdf::ByteStream s(&data);
1933 int length_object = requestObject();
1937 switch (profile.colorModel()) {
1938 case QColorSpace::ColorModel::Undefined:
1939 qWarning(
"QPdfEngine: undefined color model in the output intent profile, assuming RGB");
1941 case QColorSpace::ColorModel::Rgb:
1943 s <<
"/Alternate /DeviceRGB\n";
1945 case QColorSpace::ColorModel::Gray:
1947 s <<
"/Alternate /DeviceGray\n";
1949 case QColorSpace::ColorModel::Cmyk:
1951 s <<
"/Alternate /DeviceCMYK\n";
1955 s <<
"/Length " << length_object <<
"0 R\n";
1957 s <<
"/Filter /FlateDecode\n";
1961 const int len = writeCompressed(colorProfileData);
1962 write(
"\nendstream\n"
1964 addXrefEntry(length_object);
1969 const int outputIntentEntry = addXrefEntry(-1);
1972 write(
"/Type /OutputIntent\n");
1974 switch (pdfVersion) {
1975 case QPdfEngine::Version_1_4:
1976 case QPdfEngine::Version_1_6:
1979 case QPdfEngine::Version_A1b:
1980 write(
"/S/GTS_PDFA1\n");
1982 case QPdfEngine::Version_X4:
1983 write(
"/S/GTS_PDFX\n");
1987 xprintf(
"/DestOutputProfile %d 0 R\n", colorProfileEntry);
1988 write(
"/OutputConditionIdentifier ");
1989 printString(outputIntent.outputConditionIdentifier());
1993 printString(outputIntent.outputCondition());
1996 write(
"/OutputCondition ");
1997 printString(outputIntent.outputCondition());
2000 if (
const auto registryName = outputIntent.registryName(); !registryName.isEmpty()) {
2001 write(
"/RegistryName ");
2002 printString(registryName.toString());
2010 return outputIntentEntry;
2013void QPdfEnginePrivate::writePageRoot()
2015 addXrefEntry(pageRoot);
2021 int size = pages.size();
2022 for (
int i = 0; i < size; ++i)
2023 xprintf(
"%d 0 R\n", pages[i]);
2027 xprintf(
"/Count %" PRIdQSIZETYPE
"\n", pages.size());
2029 xprintf(
"/ProcSet [/PDF /Text /ImageB /ImageC]\n"
2034void QPdfEnginePrivate::writeDestsRoot()
2036 if (destCache.isEmpty())
2039 std::map<QString,
int> destObjects;
2041 for (
const DestInfo &destInfo : std::as_const(destCache)) {
2042 int destObj = addXrefEntry(-1);
2043 xs.setNum(
static_cast<
double>(destInfo.coords.x()),
'f');
2044 ys.setNum(
static_cast<
double>(destInfo.coords.y()),
'f');
2045 xprintf(
"[%d 0 R /XYZ %s %s 0]\n", destInfo.pageObj, xs.constData(), ys.constData());
2046 xprintf(
"endobj\n");
2047 destObjects.insert_or_assign(destInfo.anchor, destObj);
2051 destsRoot = addXrefEntry(-1);
2052 xprintf(
"<<\n/Limits [");
2053 printString(destObjects.begin()->first);
2055 printString(destObjects.rbegin()->first);
2056 xprintf(
"]\n/Names [\n");
2057 for (
const auto &[anchor, destObject] : destObjects) {
2058 printString(anchor);
2059 xprintf(
" %d 0 R\n", destObject);
2065void QPdfEnginePrivate::writeAttachmentRoot()
2067 if (fileCache.isEmpty())
2070 QList<
int> attachments;
2071 const int size = fileCache.size();
2072 for (
int i = 0; i < size; ++i) {
2073 auto attachment = fileCache.at(i);
2074 const int attachmentID = addXrefEntry(-1);
2077 xprintf(
"/Filter /FlateDecode\n");
2079 const int lenobj = requestObject();
2080 xprintf(
"/Length %d 0 R\n", lenobj);
2082 xprintf(
">>\nstream\n");
2083 len = writeCompressed(attachment.data);
2084 xprintf(
"\nendstream\n"
2086 addXrefEntry(lenobj);
2090 attachments.push_back(addXrefEntry(-1));
2093 printString(attachment.fileName);
2095 xprintf(
"\n/EF <</F %d 0 R>>\n"
2098 if (!attachment.mimeType.isEmpty())
2099 xprintf(
"/Subtype/%s\n",
2100 attachment.mimeType.replace(
"/"_L1,
"#2F"_L1).toLatin1().constData());
2101 xprintf(
">>\nendobj\n");
2105 attachmentsRoot = addXrefEntry(-1);
2106 xprintf(
"<</Names[");
2107 for (
int i = 0; i < size; ++i) {
2108 auto attachment = fileCache.at(i);
2109 printString(attachment.fileName);
2110 xprintf(
"%d 0 R\n", attachments.at(i));
2116void QPdfEnginePrivate::writeNamesRoot()
2118 addXrefEntry(namesRoot);
2121 if (attachmentsRoot)
2122 xprintf(
"/EmbeddedFiles %d 0 R\n", attachmentsRoot);
2125 xprintf(
"/Dests %d 0 R\n", destsRoot);
2128 xprintf(
"endobj\n");
2131void QPdfEnginePrivate::embedFont(QFontSubset *font)
2134 int fontObject = font->object_id;
2135 QByteArray fontData = font->toTruetype();
2138 QString fileName(
"font%1.ttf");
2139 fileName = fileName.arg(i++);
2141 ff.open(QFile::WriteOnly);
2146 int fontDescriptor = requestObject();
2147 int fontstream = requestObject();
2148 int cidfont = requestObject();
2149 int toUnicode = requestObject();
2150 int cidset = requestObject();
2152 QFontEngine::Properties properties = font->fontEngine->properties();
2153 QByteArray postscriptName = properties.postscriptName.replace(
' ',
'_');
2156 qreal scale = 1000/properties.emSquare.toReal();
2157 addXrefEntry(fontDescriptor);
2158 QByteArray descriptor;
2159 QPdf::ByteStream s(&descriptor);
2160 s <<
"<< /Type /FontDescriptor\n"
2162 int tag = fontDescriptor;
2163 for (
int i = 0; i < 5; ++i) {
2164 s << (
char)(
'A' + (tag % 26));
2167 s <<
'+' << postscriptName <<
"\n"
2168 "/Flags " << 4 <<
"\n"
2170 << properties.boundingBox.x()*scale
2171 << -(properties.boundingBox.y() + properties.boundingBox.height())*scale
2172 << (properties.boundingBox.x() + properties.boundingBox.width())*scale
2173 << -properties.boundingBox.y()*scale <<
"]\n"
2174 "/ItalicAngle " << properties.italicAngle.toReal() <<
"\n"
2175 "/Ascent " << properties.ascent.toReal()*scale <<
"\n"
2176 "/Descent " << -properties.descent.toReal()*scale <<
"\n"
2177 "/CapHeight " << properties.capHeight.toReal()*scale <<
"\n"
2178 "/StemV " << properties.lineWidth.toReal()*scale <<
"\n"
2179 "/FontFile2 " << fontstream <<
"0 R\n"
2180 "/CIDSet " << cidset <<
"0 R\n"
2185 addXrefEntry(fontstream);
2187 QPdf::ByteStream s(&header);
2189 int length_object = requestObject();
2191 "/Length1 " << fontData.size() <<
"\n"
2192 "/Length " << length_object <<
"0 R\n";
2194 s <<
"/Filter /FlateDecode\n";
2198 int len = writeCompressed(fontData);
2199 write(
"\nendstream\n"
2201 addXrefEntry(length_object);
2206 addXrefEntry(cidfont);
2208 QPdf::ByteStream s(&cid);
2209 s <<
"<< /Type /Font\n"
2210 "/Subtype /CIDFontType2\n"
2211 "/BaseFont /" << postscriptName <<
"\n"
2212 "/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>\n"
2213 "/FontDescriptor " << fontDescriptor <<
"0 R\n"
2214 "/CIDToGIDMap /Identity\n"
2215 << font->widthArray() <<
2221 addXrefEntry(toUnicode);
2222 QByteArray touc = font->createToUnicodeMap();
2223 xprintf(
"<< /Length %" PRIdQSIZETYPE
" >>\n"
2224 "stream\n", touc.size());
2226 write(
"\nendstream\n"
2230 addXrefEntry(fontObject);
2232 QPdf::ByteStream s(&font);
2233 s <<
"<< /Type /Font\n"
2235 "/BaseFont /" << postscriptName <<
"\n"
2236 "/Encoding /Identity-H\n"
2237 "/DescendantFonts [" << cidfont <<
"0 R]\n"
2238 "/ToUnicode " << toUnicode <<
"0 R"
2244 QByteArray cidSetStream(font->nGlyphs() / 8 + 1, 0);
2245 int byteCounter = 0;
2247 for (qsizetype i = 0; i < font->nGlyphs(); ++i) {
2248 cidSetStream.data()[byteCounter] |= (1 << (7 - bitCounter));
2251 if (bitCounter == 8) {
2257 addXrefEntry(cidset);
2259 xprintf(
"/Length %" PRIdQSIZETYPE
"\n", cidSetStream.size());
2261 xprintf(
"stream\n");
2262 write(cidSetStream);
2263 xprintf(
"\nendstream\n");
2264 xprintf(
"endobj\n");
2268qreal QPdfEnginePrivate::calcUserUnit()
const
2271 if (pdfVersion < QPdfEngine::Version_1_6)
2274 const int maxLen = qMax(currentPage->pageSize.width(), currentPage->pageSize.height());
2275 if (maxLen <= 14400)
2279 return qMin(maxLen / 14400.0, 75000.0);
2282void QPdfEnginePrivate::writeFonts()
2284 for (QHash<QFontEngine::FaceId, QFontSubset *>::iterator it = fonts.begin(); it != fonts.end(); ++it) {
2291void QPdfEnginePrivate::writePage()
2296 *currentPage <<
"Q Q\n";
2298 uint pageStream = requestObject();
2299 uint pageStreamLength = requestObject();
2300 uint resources = requestObject();
2301 uint annots = requestObject();
2303 qreal userUnit = calcUserUnit();
2305 addXrefEntry(pages.constLast());
2308 const QByteArray formattedPageWidth = QByteArray::number(currentPage->pageSize.width() / userUnit,
'f');
2309 const QByteArray formattedPageHeight = QByteArray::number(currentPage->pageSize.height() / userUnit,
'f');
2314 "/Contents %d 0 R\n"
2315 "/Resources %d 0 R\n"
2317 "/MediaBox [0 0 %s %s]\n"
2318 "/TrimBox [0 0 %s %s]\n",
2319 pageRoot, pageStream, resources, annots,
2320 formattedPageWidth.constData(),
2321 formattedPageHeight.constData(),
2322 formattedPageWidth.constData(),
2323 formattedPageHeight.constData());
2325 if (pdfVersion >= QPdfEngine::Version_1_6)
2326 xprintf(
"/UserUnit %s\n", QByteArray::number(userUnit,
'f').constData());
2331 addXrefEntry(resources);
2336 "/PCSpcmyk %d 0 R\n"
2338 "/CSpg /DeviceGray\n"
2339 "/CSpcmyk /DeviceCMYK\n"
2343 patternColorSpaceRGB,
2344 patternColorSpaceGrayscale,
2345 patternColorSpaceCMYK,
2348 for (
int i = 0; i < currentPage->graphicStates.size(); ++i)
2349 xprintf(
"/GState%d %d 0 R\n", currentPage->graphicStates.at(i), currentPage->graphicStates.at(i));
2352 xprintf(
"/Pattern <<\n");
2353 for (
int i = 0; i < currentPage->patterns.size(); ++i)
2354 xprintf(
"/Pat%d %d 0 R\n", currentPage->patterns.at(i), currentPage->patterns.at(i));
2357 xprintf(
"/Font <<\n");
2358 for (
int i = 0; i < currentPage->fonts.size();++i)
2359 xprintf(
"/F%d %d 0 R\n", currentPage->fonts[i], currentPage->fonts[i]);
2362 xprintf(
"/XObject <<\n");
2363 for (
int i = 0; i<currentPage->images.size(); ++i) {
2364 xprintf(
"/Im%d %d 0 R\n", currentPage->images.at(i), currentPage->images.at(i));
2371 addXrefEntry(annots);
2373 for (
int i = 0; i<currentPage->annotations.size(); ++i) {
2374 xprintf(
"%d 0 R ", currentPage->annotations.at(i));
2376 xprintf(
"]\nendobj\n");
2378 addXrefEntry(pageStream);
2380 "/Length %d 0 R\n", pageStreamLength);
2382 xprintf(
"/Filter /FlateDecode\n");
2385 xprintf(
"stream\n");
2386 QIODevice *content = currentPage->stream();
2387 int len = writeCompressed(content);
2388 xprintf(
"\nendstream\n"
2391 addXrefEntry(pageStreamLength);
2392 xprintf(
"%d\nendobj\n",len);
2395void QPdfEnginePrivate::writeTail()
2401 writeAttachmentRoot();
2404 addXrefEntry(xrefPositions.size(),
false);
2406 "0 %" PRIdQSIZETYPE
"\n"
2407 "%010d 65535 f \n", xrefPositions.size()-1, xrefPositions[0]);
2409 for (
int i = 1; i < xrefPositions.size()-1; ++i)
2410 xprintf(
"%010d 00000 n \n", xrefPositions[i]);
2414 QPdf::ByteStream s(&trailer);
2418 <<
"/Size " << xrefPositions.size() - 1 <<
"\n"
2419 <<
"/Info " << info <<
"0 R\n"
2420 <<
"/Root " << catalog <<
"0 R\n";
2422 const QByteArray id = documentId.toString(QUuid::WithoutBraces).toUtf8().toHex();
2423 s <<
"/ID [ <" << id <<
"> <" << id <<
"> ]\n";
2426 <<
"startxref\n" << xrefPositions.constLast() <<
"\n"
2433int QPdfEnginePrivate::addXrefEntry(
int object,
bool printostr)
2436 object = requestObject();
2438 if (object>=xrefPositions.size())
2439 xrefPositions.resize(object+1);
2441 xrefPositions[object] = streampos;
2443 xprintf(
"%d 0 obj\n",object);
2448void QPdfEnginePrivate::printString(QStringView string)
2450 if (string.isEmpty()) {
2458 QByteArray array(
"(\xfe\xff");
2459 const char16_t *utf16 = string.utf16();
2461 for (qsizetype i = 0; i < string.size(); ++i) {
2462 char part[2] = {
char((*(utf16 + i)) >> 8),
char((*(utf16 + i)) & 0xff)};
2463 for(
int j=0; j < 2; ++j) {
2464 if (part[j] ==
'(' || part[j] ==
')' || part[j] ==
'\\')
2466 array.append(part[j]);
2474void QPdfEnginePrivate::xprintf(
const char* fmt, ...)
2479 const int msize = 10000;
2483 va_start(args, fmt);
2484 int bufsize = std::vsnprintf(buf, msize, fmt, args);
2487 if (Q_LIKELY(bufsize < msize)) {
2488 stream->writeRawData(buf, bufsize);
2491 QScopedArrayPointer<
char> tmpbuf(
new char[bufsize + 1]);
2492 va_start(args, fmt);
2493 bufsize = std::vsnprintf(tmpbuf.data(), bufsize + 1, fmt, args);
2495 stream->writeRawData(tmpbuf.data(), bufsize);
2497 streampos += bufsize;
2500int QPdfEnginePrivate::writeCompressed(QIODevice *dev)
2502#ifndef QT_NO_COMPRESS
2504 int size = QPdfPage::chunkSize();
2507 zStruct.zalloc = Z_NULL;
2508 zStruct.zfree = Z_NULL;
2509 zStruct.opaque = Z_NULL;
2510 if (::deflateInit(&zStruct, Z_DEFAULT_COMPRESSION) != Z_OK) {
2511 qWarning(
"QPdfStream::writeCompressed: Error in deflateInit()");
2514 zStruct.avail_in = 0;
2517 while (!dev->atEnd() || zStruct.avail_in != 0) {
2518 if (zStruct.avail_in == 0) {
2519 in = dev->read(size);
2520 zStruct.avail_in = in.size();
2521 zStruct.next_in =
reinterpret_cast<
unsigned char*>(in.data());
2522 if (in.size() <= 0) {
2523 qWarning(
"QPdfStream::writeCompressed: Error in read()");
2524 ::deflateEnd(&zStruct);
2528 zStruct.next_out =
reinterpret_cast<
unsigned char*>(out.data());
2529 zStruct.avail_out = out.size();
2530 if (::deflate(&zStruct, 0) != Z_OK) {
2531 qWarning(
"QPdfStream::writeCompressed: Error in deflate()");
2532 ::deflateEnd(&zStruct);
2535 int written = out.size() - zStruct.avail_out;
2536 stream->writeRawData(out.constData(), written);
2537 streampos += written;
2542 zStruct.next_out =
reinterpret_cast<
unsigned char*>(out.data());
2543 zStruct.avail_out = out.size();
2544 ret = ::deflate(&zStruct, Z_FINISH);
2545 if (ret != Z_OK && ret != Z_STREAM_END) {
2546 qWarning(
"QPdfStream::writeCompressed: Error in deflate()");
2547 ::deflateEnd(&zStruct);
2550 int written = out.size() - zStruct.avail_out;
2551 stream->writeRawData(out.constData(), written);
2552 streampos += written;
2554 }
while (ret == Z_OK);
2556 ::deflateEnd(&zStruct);
2564 while (!dev->atEnd()) {
2565 arr = dev->read(QPdfPage::chunkSize());
2566 stream->writeRawData(arr.constData(), arr.size());
2567 streampos += arr.size();
2574int QPdfEnginePrivate::writeCompressed(
const char *src,
int len)
2576#ifndef QT_NO_COMPRESS
2578 const QByteArray data = qCompress(
reinterpret_cast<
const uchar *>(src), len);
2579 constexpr qsizetype HeaderSize = 4;
2580 if (!data.isNull()) {
2581 stream->writeRawData(data.data() + HeaderSize, data.size() - HeaderSize);
2582 len = data.size() - HeaderSize;
2584 qWarning(
"QPdfStream::writeCompressed: Error in compress()");
2590 stream->writeRawData(src,len);
2596int QPdfEnginePrivate::writeImage(
const QByteArray &data,
int width,
int height, WriteImageOption option,
2597 int maskObject,
int softMaskObject,
bool dct,
bool isMono)
2599 int image = addXrefEntry(-1);
2604 "/Height %d\n", width, height);
2607 case WriteImageOption::Monochrome:
2609 xprintf(
"/ImageMask true\n"
2612 xprintf(
"/BitsPerComponent 1\n"
2613 "/ColorSpace /DeviceGray\n");
2616 case WriteImageOption::Grayscale:
2617 xprintf(
"/BitsPerComponent 8\n"
2618 "/ColorSpace /DeviceGray\n");
2620 case WriteImageOption::RGB:
2621 xprintf(
"/BitsPerComponent 8\n"
2622 "/ColorSpace /DeviceRGB\n");
2624 case WriteImageOption::CMYK:
2625 xprintf(
"/BitsPerComponent 8\n"
2626 "/ColorSpace /DeviceCMYK\n");
2631 xprintf(
"/Mask %d 0 R\n", maskObject);
2632 if (softMaskObject > 0)
2633 xprintf(
"/SMask %d 0 R\n", softMaskObject);
2635 int lenobj = requestObject();
2636 xprintf(
"/Length %d 0 R\n", lenobj);
2637 if (interpolateImages)
2638 xprintf(
"/Interpolate true\n");
2642 xprintf(
"/Filter /DCTDecode\n>>\nstream\n");
2647 xprintf(
"/Filter /FlateDecode\n>>\nstream\n");
2649 xprintf(
">>\nstream\n");
2650 len = writeCompressed(data);
2652 xprintf(
"\nendstream\n"
2654 addXrefEntry(lenobj);
2668void QPdfEnginePrivate::ShadingFunctionResult::writeColorSpace(QPdf::ByteStream *stream)
const
2670 *stream <<
"/ColorSpace ";
2671 switch (colorModel) {
2672 case QPdfEngine::ColorModel::RGB:
2673 *stream <<
"/DeviceRGB\n";
break;
2674 case QPdfEngine::ColorModel::Grayscale:
2675 *stream <<
"/DeviceGray\n";
break;
2676 case QPdfEngine::ColorModel::CMYK:
2677 *stream <<
"/DeviceCMYK\n";
break;
2678 case QPdfEngine::ColorModel::Auto:
2679 Q_UNREACHABLE();
break;
2683QPdfEnginePrivate::ShadingFunctionResult
2684QPdfEnginePrivate::createShadingFunction(
const QGradient *gradient,
int from,
int to,
bool reflect,
bool alpha)
2686 QGradientStops stops = gradient->stops();
2687 if (stops.isEmpty()) {
2688 stops << QGradientStop(0, Qt::black);
2689 stops << QGradientStop(1, Qt::white);
2691 if (stops.at(0).first > 0)
2692 stops.prepend(QGradientStop(0, stops.at(0).second));
2693 if (stops.at(stops.size() - 1).first < 1)
2694 stops.append(QGradientStop(1, stops.at(stops.size() - 1).second));
2697 const QColor referenceColor = stops.constFirst().second;
2699 switch (colorModel) {
2700 case QPdfEngine::ColorModel::RGB:
2701 case QPdfEngine::ColorModel::Grayscale:
2702 case QPdfEngine::ColorModel::CMYK:
2704 case QPdfEngine::ColorModel::Auto: {
2707 const QColor::Spec referenceSpec = referenceColor.spec();
2708 bool warned =
false;
2709 for (QGradientStop &stop : stops) {
2710 if (stop.second.spec() != referenceSpec) {
2712 qWarning(
"QPdfEngine: unable to create a gradient between colors of different spec");
2715 stop.second = stop.second.convertTo(referenceSpec);
2722 ShadingFunctionResult result;
2723 result.colorModel = colorModelForColor(referenceColor);
2725 QList<
int> functions;
2726 const int numStops = stops.size();
2727 functions.reserve(numStops - 1);
2728 for (
int i = 0; i < numStops - 1; ++i) {
2729 int f = addXrefEntry(-1);
2731 QPdf::ByteStream s(&data);
2737 s <<
"/C0 [" << stops.at(i).second.alphaF() <<
"]\n"
2738 "/C1 [" << stops.at(i + 1).second.alphaF() <<
"]\n";
2740 switch (result.colorModel) {
2741 case QPdfEngine::ColorModel::RGB:
2742 s <<
"/C0 [" << stops.at(i).second.redF() << stops.at(i).second.greenF() << stops.at(i).second.blueF() <<
"]\n"
2743 "/C1 [" << stops.at(i + 1).second.redF() << stops.at(i + 1).second.greenF() << stops.at(i + 1).second.blueF() <<
"]\n";
2745 case QPdfEngine::ColorModel::Grayscale: {
2746 constexpr qreal normalisationFactor = 1. / 255.;
2747 s <<
"/C0 [" << (qGray(stops.at(i).second.rgba()) * normalisationFactor) <<
"]\n"
2748 "/C1 [" << (qGray(stops.at(i + 1).second.rgba()) * normalisationFactor) <<
"]\n";
2751 case QPdfEngine::ColorModel::CMYK:
2752 s <<
"/C0 [" << stops.at(i).second.cyanF()
2753 << stops.at(i).second.magentaF()
2754 << stops.at(i).second.yellowF()
2755 << stops.at(i).second.blackF() <<
"]\n"
2756 "/C1 [" << stops.at(i + 1).second.cyanF()
2757 << stops.at(i + 1).second.magentaF()
2758 << stops.at(i + 1).second.yellowF()
2759 << stops.at(i + 1).second.blackF() <<
"]\n";
2762 case QPdfEngine::ColorModel::Auto:
2774 QList<QGradientBound> gradientBounds;
2775 gradientBounds.reserve((to - from) * (numStops - 1));
2777 for (
int step = from; step < to; ++step) {
2778 if (reflect && step % 2) {
2779 for (
int i = numStops - 1; i > 0; --i) {
2781 b.start = step + 1 - qBound(qreal(0.), stops.at(i).first, qreal(1.));
2782 b.stop = step + 1 - qBound(qreal(0.), stops.at(i - 1).first, qreal(1.));
2783 b.function = functions.at(i - 1);
2785 gradientBounds << b;
2788 for (
int i = 0; i < numStops - 1; ++i) {
2790 b.start = step + qBound(qreal(0.), stops.at(i).first, qreal(1.));
2791 b.stop = step + qBound(qreal(0.), stops.at(i + 1).first, qreal(1.));
2792 b.function = functions.at(i);
2794 gradientBounds << b;
2800 qreal bstart = gradientBounds.at(0).start;
2801 qreal bend = gradientBounds.at(gradientBounds.size() - 1).stop;
2802 qreal norm = 1./(bend - bstart);
2803 for (
int i = 0; i < gradientBounds.size(); ++i) {
2804 gradientBounds[i].start = (gradientBounds[i].start - bstart)*norm;
2805 gradientBounds[i].stop = (gradientBounds[i].stop - bstart)*norm;
2809 if (gradientBounds.size() > 1) {
2810 function = addXrefEntry(-1);
2812 QPdf::ByteStream s(&data);
2817 for (
int i = 1; i < gradientBounds.size(); ++i)
2818 s << gradientBounds.at(i).start;
2821 for (
int i = 0; i < gradientBounds.size(); ++i)
2822 s << (gradientBounds.at(i).reverse ?
"1 0 " :
"0 1 ");
2825 for (
int i = 0; i < gradientBounds.size(); ++i)
2826 s << gradientBounds.at(i).function <<
"0 R ";
2832 function = functions.at(0);
2834 result.function = function;
2838int QPdfEnginePrivate::generateLinearGradientShader(
const QLinearGradient *gradient,
const QTransform &matrix,
bool alpha)
2840 QPointF start = gradient->start();
2841 QPointF stop = gradient->finalStop();
2842 QPointF offset = stop - start;
2843 Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode);
2847 bool reflect =
false;
2848 switch (gradient->spread()) {
2849 case QGradient::PadSpread:
2851 case QGradient::ReflectSpread:
2854 case QGradient::RepeatSpread: {
2856 QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2857 QTransform inv = matrix.inverted();
2858 QPointF page_rect[4] = { inv.map(pageRect.topLeft()),
2859 inv.map(pageRect.topRight()),
2860 inv.map(pageRect.bottomLeft()),
2861 inv.map(pageRect.bottomRight()) };
2863 qreal length = offset.x()*offset.x() + offset.y()*offset.y();
2869 for (
int i = 0; i < 4; ++i) {
2870 qreal off = ((page_rect[i].x() - start.x()) * offset.x() + (page_rect[i].y() - start.y()) * offset.y())/length;
2871 from = qMin(from, qFloor(off));
2872 to = qMax(to, qCeil(off));
2875 stop = start + to * offset;
2876 start = start + from * offset;
2881 const auto shadingFunctionResult = createShadingFunction(gradient, from, to, reflect, alpha);
2884 QPdf::ByteStream s(&shader);
2889 s <<
"/ColorSpace /DeviceGray\n";
2891 shadingFunctionResult.writeColorSpace(&s);
2893 s <<
"/AntiAlias true\n"
2894 "/Coords [" << start.x() << start.y() << stop.x() << stop.y() <<
"]\n"
2895 "/Extend [true true]\n"
2896 "/Function " << shadingFunctionResult.function <<
"0 R\n"
2899 int shaderObject = addXrefEntry(-1);
2901 return shaderObject;
2904int QPdfEnginePrivate::generateRadialGradientShader(
const QRadialGradient *gradient,
const QTransform &matrix,
bool alpha)
2906 QPointF p1 = gradient->center();
2907 qreal r1 = gradient->centerRadius();
2908 QPointF p0 = gradient->focalPoint();
2909 qreal r0 = gradient->focalRadius();
2911 Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode);
2915 bool reflect =
false;
2916 switch (gradient->spread()) {
2917 case QGradient::PadSpread:
2919 case QGradient::ReflectSpread:
2922 case QGradient::RepeatSpread: {
2923 Q_ASSERT(qFuzzyIsNull(r0));
2925 QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2926 QTransform inv = matrix.inverted();
2927 QPointF page_rect[4] = { inv.map(pageRect.topLeft()),
2928 inv.map(pageRect.topRight()),
2929 inv.map(pageRect.bottomLeft()),
2930 inv.map(pageRect.bottomRight()) };
2935 QPointF center = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y()));
2936 double radius = r0 + to*(r1 - r0);
2937 double r2 = radius*radius;
2939 for (
int i = 0; i < 4; ++i) {
2940 QPointF off = page_rect[i] - center;
2941 if (off.x()*off.x() + off.y()*off.y() > r2) {
2948 p1 = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y()));
2949 r1 = r0 + to*(r1 - r0);
2954 const auto shadingFunctionResult = createShadingFunction(gradient, from, to, reflect, alpha);
2957 QPdf::ByteStream s(&shader);
2962 s <<
"/ColorSpace /DeviceGray\n";
2964 shadingFunctionResult.writeColorSpace(&s);
2966 s <<
"/AntiAlias true\n"
2968 "/Coords [" << p0.x() << p0.y() << r0 << p1.x() << p1.y() << r1 <<
"]\n"
2969 "/Extend [true true]\n"
2970 "/Function " << shadingFunctionResult.function <<
"0 R\n"
2973 int shaderObject = addXrefEntry(-1);
2975 return shaderObject;
2978int QPdfEnginePrivate::generateGradientShader(
const QGradient *gradient,
const QTransform &matrix,
bool alpha)
2980 switch (gradient->type()) {
2981 case QGradient::LinearGradient:
2982 return generateLinearGradientShader(
static_cast<
const QLinearGradient *>(gradient), matrix, alpha);
2983 case QGradient::RadialGradient:
2984 return generateRadialGradientShader(
static_cast<
const QRadialGradient *>(gradient), matrix, alpha);
2985 case QGradient::ConicalGradient:
2988 case QGradient::NoGradient:
2994int QPdfEnginePrivate::gradientBrush(
const QBrush &b,
const QTransform &matrix,
int *gStateObject)
2996 const QGradient *gradient = b.gradient();
2998 if (!gradient || gradient->coordinateMode() != QGradient::LogicalMode)
3001 QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
3003 QTransform m = b.transform() * matrix;
3004 int shaderObject = generateGradientShader(gradient, m);
3007 QPdf::ByteStream s(&str);
3011 "/Shading " << shaderObject <<
"0 R\n"
3022 int patternObj = addXrefEntry(-1);
3024 currentPage->patterns.append(patternObj);
3026 if (!b.isOpaque()) {
3028 QGradientStops stops = gradient->stops();
3029 int a = stops.at(0).second.alpha();
3030 for (
int i = 1; i < stops.size(); ++i) {
3031 if (stops.at(i).second.alpha() != a) {
3037 *gStateObject = addConstantAlphaObject(stops.at(0).second.alpha());
3039 int alphaShaderObject = generateGradientShader(gradient, m,
true);
3042 QPdf::ByteStream c(&content);
3043 c <<
"/Shader" << alphaShaderObject <<
"sh\n";
3046 QPdf::ByteStream f(&form);
3050 "/BBox [0 0 " << pageRect.width() << pageRect.height() <<
"]\n"
3051 "/Group <</S /Transparency >>\n"
3053 "/Shading << /Shader" << alphaShaderObject << alphaShaderObject <<
"0 R >>\n"
3056 f <<
"/Length " << content.size() <<
"\n"
3063 int softMaskFormObject = addXrefEntry(-1);
3065 *gStateObject = addXrefEntry(-1);
3066 xprintf(
"<< /SMask << /S /Alpha /G %d 0 R >> >>\n"
3067 "endobj\n", softMaskFormObject);
3068 currentPage->graphicStates.append(*gStateObject);
3075int QPdfEnginePrivate::addConstantAlphaObject(
int brushAlpha,
int penAlpha)
3077 if (brushAlpha == 255 && penAlpha == 255)
3079 uint object = alphaCache.value(std::pair<uint, uint>(brushAlpha, penAlpha), 0);
3081 object = addXrefEntry(-1);
3082 QByteArray alphaDef;
3083 QPdf::ByteStream s(&alphaDef);
3084 s <<
"<<\n/ca " << (brushAlpha/qreal(255.)) <<
'\n';
3085 s <<
"/CA " << (penAlpha/qreal(255.)) <<
"\n>>";
3086 xprintf(
"%s\nendobj\n", alphaDef.constData());
3087 alphaCache.insert(std::pair<uint, uint>(brushAlpha, penAlpha), object);
3089 if (currentPage->graphicStates.indexOf(object) < 0)
3090 currentPage->graphicStates.append(object);
3096int QPdfEnginePrivate::addBrushPattern(
const QTransform &m,
bool *specifyColor,
int *gStateObject)
3104 *specifyColor =
true;
3107 const Qt::BrushStyle style = brush.style();
3108 const bool isCosmetic = style >= Qt::Dense1Pattern && style <= Qt::DiagCrossPattern
3109 && !q->painter()->testRenderHint(QPainter::NonCosmeticBrushPatterns);
3113 matrix.translate(brushOrigin.x(), brushOrigin.y());
3114 matrix = matrix * pageMatrix();
3116 if (style == Qt::LinearGradientPattern || style == Qt::RadialGradientPattern) {
3117 *specifyColor =
false;
3118 return gradientBrush(brush, matrix, gStateObject);
3122 matrix = brush.transform() * matrix;
3124 if ((!brush.isOpaque() && brush.style() < Qt::LinearGradientPattern) || opacity != 1.0)
3125 *gStateObject = addConstantAlphaObject(qRound(brush.color().alpha() * opacity),
3126 qRound(pen.color().alpha() * opacity));
3128 int imageObject = -1;
3129 QByteArray pattern = QPdf::patternForBrush(brush);
3130 if (pattern.isEmpty()) {
3131 if (brush.style() != Qt::TexturePattern)
3133 QImage image = brush.textureImage();
3135 const bool lossless = q->painter()->testRenderHint(QPainter::LosslessImageRendering);
3136 imageObject = addImage(image, &bitmap, lossless, image.cacheKey());
3137 if (imageObject != -1) {
3138 QImage::Format f = image.format();
3139 if (f != QImage::Format_MonoLSB && f != QImage::Format_Mono) {
3141 *specifyColor =
false;
3145 QTransform m(w, 0, 0, -h, 0, h);
3146 QPdf::ByteStream s(&pattern);
3147 s << QPdf::generateMatrix(m);
3148 s <<
"/Im" << imageObject <<
" Do\n";
3153 QPdf::ByteStream s(&str);
3157 "/PaintType " << paintType <<
"\n"
3159 "/BBox [0 0 " << w << h <<
"]\n"
3160 "/XStep " << w <<
"\n"
3161 "/YStep " << h <<
"\n"
3168 << matrix.dy() <<
"]\n"
3170 if (imageObject > 0) {
3171 s <<
"/XObject << /Im" << imageObject <<
' ' << imageObject <<
"0 R >> ";
3174 "/Length " << pattern.size() <<
"\n"
3181 int patternObj = addXrefEntry(-1);
3183 currentPage->patterns.append(patternObj);
3189 return colorTable.size() == 2
3190 && colorTable.at(0) == QColor(Qt::black).rgba()
3191 && colorTable.at(1) == QColor(Qt::white).rgba()
3196
3197
3198
3199int QPdfEnginePrivate::addImage(
const QImage &img,
bool *bitmap,
bool lossless, qint64 serial_no)
3204 int object = imageCache.value(serial_no);
3209 QImage::Format format = image.format();
3210 const bool grayscale = (colorModel == QPdfEngine::ColorModel::Grayscale);
3212 if (pdfVersion == QPdfEngine::Version_A1b) {
3213 if (image.hasAlphaChannel()) {
3217 QImage alphaLessImage(image.width(), image.height(), QImage::Format_RGB32);
3218 alphaLessImage.setDevicePixelRatio(image.devicePixelRatioF());
3219 alphaLessImage.fill(Qt::white);
3221 QPainter p(&alphaLessImage);
3222 p.drawImage(0, 0, image);
3224 image = alphaLessImage;
3225 format = image.format();
3229 if (image.depth() == 1 && *bitmap && is_monochrome(img.colorTable())) {
3230 if (format == QImage::Format_MonoLSB)
3231 image = image.convertToFormat(QImage::Format_Mono);
3232 format = QImage::Format_Mono;
3235 if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_CMYK8888) {
3236 image = image.convertToFormat(QImage::Format_ARGB32);
3237 format = QImage::Format_ARGB32;
3241 int w = image.width();
3242 int h = image.height();
3244 if (format == QImage::Format_Mono) {
3245 int bytesPerLine = (w + 7) >> 3;
3247 data.resize(bytesPerLine * h);
3248 char *rawdata = data.data();
3249 for (
int y = 0; y < h; ++y) {
3250 memcpy(rawdata, image.constScanLine(y), bytesPerLine);
3251 rawdata += bytesPerLine;
3253 object = writeImage(data, w, h, WriteImageOption::Monochrome, 0, 0,
false, is_monochrome(img.colorTable()));
3255 QByteArray softMaskData;
3257 QByteArray imageData;
3258 bool hasAlpha =
false;
3259 bool hasMask =
false;
3261 if (QImageWriter::supportedImageFormats().contains(
"jpeg") && !grayscale && !lossless) {
3262 QBuffer buffer(&imageData);
3263 QImageWriter writer(&buffer,
"jpeg");
3264 writer.setQuality(94);
3265 if (format == QImage::Format_CMYK8888) {
3267 writer.setSubType(
"CMYK");
3269 writer.write(image);
3272 if (format != QImage::Format_RGB32 && format != QImage::Format_CMYK8888) {
3273 softMaskData.resize(w * h);
3274 uchar *sdata = (uchar *)softMaskData.data();
3275 for (
int y = 0; y < h; ++y) {
3276 const QRgb *rgb = (
const QRgb *)image.constScanLine(y);
3277 for (
int x = 0; x < w; ++x) {
3278 uchar alpha = qAlpha(*rgb);
3280 hasMask |= (alpha < 255);
3281 hasAlpha |= (alpha != 0 && alpha != 255);
3287 if (format == QImage::Format_CMYK8888) {
3288 imageData.resize(grayscale ? w * h : w * h * 4);
3289 uchar *data = (uchar *)imageData.data();
3290 const qsizetype bytesPerLine = image.bytesPerLine();
3292 for (
int y = 0; y < h; ++y) {
3293 const uint *cmyk = (
const uint *)image.constScanLine(y);
3294 for (
int x = 0; x < w; ++x)
3295 *data++ = qGray(QCmyk32::fromCmyk32(*cmyk++).toColor().rgba());
3298 for (
int y = 0; y < h; ++y) {
3299 uchar *start = data + y * w * 4;
3300 memcpy(start, image.constScanLine(y), bytesPerLine);
3304 imageData.resize(grayscale ? w * h : 3 * w * h);
3305 uchar *data = (uchar *)imageData.data();
3306 softMaskData.resize(w * h);
3307 uchar *sdata = (uchar *)softMaskData.data();
3308 for (
int y = 0; y < h; ++y) {
3309 const QRgb *rgb = (
const QRgb *)image.constScanLine(y);
3311 for (
int x = 0; x < w; ++x) {
3312 *(data++) = qGray(*rgb);
3313 uchar alpha = qAlpha(*rgb);
3315 hasMask |= (alpha < 255);
3316 hasAlpha |= (alpha != 0 && alpha != 255);
3320 for (
int x = 0; x < w; ++x) {
3321 *(data++) = qRed(*rgb);
3322 *(data++) = qGreen(*rgb);
3323 *(data++) = qBlue(*rgb);
3324 uchar alpha = qAlpha(*rgb);
3326 hasMask |= (alpha < 255);
3327 hasAlpha |= (alpha != 0 && alpha != 255);
3333 if (format == QImage::Format_RGB32 || format == QImage::Format_CMYK8888)
3334 hasAlpha = hasMask =
false;
3337 int softMaskObject = 0;
3339 softMaskObject = writeImage(softMaskData, w, h, WriteImageOption::Grayscale, 0, 0);
3340 }
else if (hasMask) {
3343 int bytesPerLine = (w + 7) >> 3;
3344 QByteArray mask(bytesPerLine * h, 0);
3345 uchar *mdata = (uchar *)mask.data();
3346 const uchar *sdata = (
const uchar *)softMaskData.constData();
3347 for (
int y = 0; y < h; ++y) {
3348 for (
int x = 0; x < w; ++x) {
3350 mdata[x>>3] |= (0x80 >> (x&7));
3353 mdata += bytesPerLine;
3355 maskObject = writeImage(mask, w, h, WriteImageOption::Monochrome, 0, 0);
3358 const WriteImageOption option = [&]() {
3360 return WriteImageOption::Grayscale;
3361 if (format == QImage::Format_CMYK8888)
3362 return WriteImageOption::CMYK;
3363 return WriteImageOption::RGB;
3366 object = writeImage(imageData, w, h, option,
3367 maskObject, softMaskObject, dct);
3369 imageCache.insert(serial_no, object);
3373void QPdfEnginePrivate::drawTextItem(
const QPointF &p,
const QTextItemInt &ti)
3377 const bool isLink = ti.charFormat.hasProperty(QTextFormat::AnchorHref);
3378 const bool isAnchor = ti.charFormat.hasProperty(QTextFormat::AnchorName);
3382 const bool isX4 = pdfVersion == QPdfEngine::Version_X4;
3383 if ((isLink && !isX4) || isAnchor) {
3384 qreal size = ti.fontEngine->fontDef.pixelSize;
3385 int synthesized = ti.fontEngine->synthesized();
3386 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
3387 Q_ASSERT(stretch > qreal(0));
3392 trans = QTransform(size*stretch, 0, 0, size, 0, 0);
3394 trans *= QTransform(1,0,0,-1,p.x(),p.y());
3396 trans *= stroker.matrix;
3398 trans *= pageMatrix();
3399 qreal x1, y1, x2, y2;
3400 trans.map(0, 0, &x1, &y1);
3401 trans.map(ti.width.toReal()/size, (ti.ascent.toReal()-ti.descent.toReal())/size, &x2, &y2);
3404 uint annot = addXrefEntry(-1);
3405 QByteArray x1s, y1s, x2s, y2s;
3406 x1s.setNum(
static_cast<
double>(x1),
'f');
3407 y1s.setNum(
static_cast<
double>(y1),
'f');
3408 x2s.setNum(
static_cast<
double>(x2),
'f');
3409 y2s.setNum(
static_cast<
double>(y2),
'f');
3410 QByteArray rectData = x1s +
' ' + y1s +
' ' + x2s +
' ' + y2s;
3411 xprintf(
"<<\n/Type /Annot\n/Subtype /Link\n");
3413 if (pdfVersion == QPdfEngine::Version_A1b)
3417 write(rectData.constData());
3418#ifdef Q_DEBUG_PDF_LINKS
3419 xprintf(
"]\n/Border [16 16 1]\n");
3421 xprintf(
"]\n/Border [0 0 0]\n");
3423 const QString link = ti.charFormat.anchorHref();
3424 const bool isInternal = link.startsWith(QLatin1Char(
'#'));
3427 xprintf(
"/Type /Action\n/S /URI\n/URI (%s)\n", link.toLatin1().constData());
3431 printString(link.sliced(1));
3435 xprintf(
"endobj\n");
3437 if (!currentPage->annotations.contains(annot)) {
3438 currentPage->annotations.append(annot);
3441 const QString anchor = ti.charFormat.anchorNames().constFirst();
3442 const uint curPage = pages.last();
3443 destCache.append(DestInfo({ anchor, curPage, QPointF(x1, y2) }));
3447 QFontEngine *fe = ti.fontEngine;
3449 QFontEngine::FaceId face_id = fe->faceId();
3450 bool noEmbed =
false;
3452 || face_id.filename.isEmpty()
3453 || fe->fsType & 0x200
3454 || fe->fsType == 2 ) {
3455 *currentPage <<
"Q\n";
3456 q->QPaintEngine::drawTextItem(p, ti);
3457 *currentPage <<
"q\n";
3458 if (face_id.filename.isEmpty())
3463 QFontSubset *font = fonts.value(face_id,
nullptr);
3465 font =
new QFontSubset(fe, requestObject());
3466 font->noEmbed = noEmbed;
3468 fonts.insert(face_id, font);
3470 if (!currentPage->fonts.contains(font->object_id))
3471 currentPage->fonts.append(font->object_id);
3473 qreal size = ti.fontEngine->fontDef.pixelSize;
3475 QVarLengthArray<glyph_t> glyphs;
3476 QVarLengthArray<QFixedPoint> positions;
3477 QTransform m = QTransform::fromTranslate(p.x(), p.y());
3478 ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags,
3480 if (glyphs.size() == 0)
3482 int synthesized = ti.fontEngine->synthesized();
3483 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
3484 Q_ASSERT(stretch > qreal(0));
3486 *currentPage <<
"BT\n"
3487 <<
"/F" << font->object_id << size <<
"Tf "
3488 << stretch << (synthesized & QFontEngine::SynthesizedItalic
3489 ?
"0 .3 -1 0 0 Tm\n"
3490 :
"0 0 -1 0 0 Tm\n");
3495 const unsigned short *logClusters = ti.logClusters;
3499 while (end < ti.num_chars && logClusters[end] == logClusters[pos])
3501 *currentPage <<
"/Span << /ActualText <FEFF";
3502 for (
int i = pos; i < end; ++i) {
3503 s << toHex((ushort)ti.chars[i].unicode(), buf);
3505 *currentPage <<
"> >>\n"
3508 int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end];
3509 for (
int gs = logClusters[pos]; gs < ge; ++gs)
3510 *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf);
3511 *currentPage <<
"> Tj\n"
3514 }
while (pos < ti.num_chars);
3518 for (
int i = 0; i < glyphs.size(); ++i) {
3519 qreal x = positions[i].x.toReal();
3520 qreal y = positions[i].y.toReal();
3521 if (synthesized & QFontEngine::SynthesizedItalic)
3525 qsizetype g = font->addGlyph(glyphs[i]);
3526 *currentPage << x - last_x << last_y - y <<
"Td <"
3527 << QPdf::toHex((ushort)g, buf) <<
"> Tj\n";
3531 if (synthesized & QFontEngine::SynthesizedBold) {
3532 *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic
3533 ?
"0 .3 -1 0 0 Tm\n"
3534 :
"0 0 -1 0 0 Tm\n");
3535 *currentPage <<
"/Span << /ActualText <> >> BDC\n";
3536 last_x = 0.5*fe->lineThickness().toReal();
3538 for (
int i = 0; i < glyphs.size(); ++i) {
3539 qreal x = positions[i].x.toReal();
3540 qreal y = positions[i].y.toReal();
3541 if (synthesized & QFontEngine::SynthesizedItalic)
3545 qsizetype g = font->addGlyph(glyphs[i]);
3546 *currentPage << x - last_x << last_y - y <<
"Td <"
3547 << QPdf::toHex((ushort)g, buf) <<
"> Tj\n";
3551 *currentPage <<
"EMC\n";
3555 *currentPage <<
"ET\n";
3558QTransform QPdfEnginePrivate::pageMatrix()
const
3560 qreal userUnit = calcUserUnit();
3561 qreal scale = 72. / userUnit / resolution;
3562 QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, m_pageLayout.fullRectPoints().height() / userUnit);
3563 if (m_pageLayout.mode() != QPageLayout::FullPageMode) {
3564 QRect r = m_pageLayout.paintRectPixels(resolution);
3565 tmp.translate(r.left(), r.top());
3570void QPdfEnginePrivate::newPage()
3572 if (currentPage && currentPage->pageSize.isEmpty())
3573 currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3577 currentPage =
new QPdfPage;
3578 currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3579 stroker.stream = currentPage;
3580 pages.append(requestObject());
3582 *currentPage <<
"/GSa gs /CSp cs /CSp CS\n"
3583 << 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)