1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qpdf_p.h"
41 
42 #ifndef QT_NO_PDF
43 
44 #include "qplatformdefs.h"
45 
46 #include <private/qfont_p.h>
47 #include <private/qmath_p.h>
48 #include <private/qpainter_p.h>
49 
50 #include <qbuffer.h>
51 #include <qcryptographichash.h>
52 #include <qdatetime.h>
53 #include <qdebug.h>
54 #include <qfile.h>
55 #include <qimagewriter.h>
56 #include <qnumeric.h>
57 #include <qtemporaryfile.h>
58 #include <quuid.h>
59 
60 #ifndef QT_NO_COMPRESS
61 #include <zlib.h>
62 #endif
63 
64 #ifdef QT_NO_COMPRESS
65 static const bool do_compress = false;
66 #else
67 static const bool do_compress = true;
68 #endif
69 
70 // might be helpful for smooth transforms of images
71 // Can't use it though, as gs generates completely wrong images if this is true.
72 static const bool interpolateImages = false;
73 
initResources()74 static void initResources()
75 {
76     Q_INIT_RESOURCE(qpdf);
77 }
78 
79 QT_BEGIN_NAMESPACE
80 
qt_pdf_decide_features()81 inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features()
82 {
83     QPaintEngine::PaintEngineFeatures f = QPaintEngine::AllFeatures;
84     f &= ~(QPaintEngine::PorterDuff
85            | QPaintEngine::PerspectiveTransform
86            | QPaintEngine::ObjectBoundingModeGradients
87            | QPaintEngine::ConicalGradientFill);
88     return f;
89 }
90 
91 extern bool qt_isExtendedRadialGradient(const QBrush &brush);
92 
93 // helper function to remove transparency from brush in PDF/A-1b mode
removeTransparencyFromBrush(QBrush & brush)94 static void removeTransparencyFromBrush(QBrush &brush)
95 {
96     if (brush.style() == Qt::SolidPattern) {
97         QColor color = brush.color();
98         if (color.alpha() != 255) {
99             color.setAlpha(255);
100             brush.setColor(color);
101         }
102 
103         return;
104     }
105 
106     if (qt_isExtendedRadialGradient(brush)) {
107         brush = QBrush(Qt::black); // the safest we can do so far...
108         return;
109     }
110 
111     if (brush.style() == Qt::LinearGradientPattern
112         || brush.style() == Qt::RadialGradientPattern
113         || brush.style() == Qt::ConicalGradientPattern) {
114 
115         QGradientStops stops = brush.gradient()->stops();
116         for (int i = 0; i < stops.size(); ++i) {
117             if (stops[i].second.alpha() != 255)
118                 stops[i].second.setAlpha(255);
119         }
120 
121         const_cast<QGradient*>(brush.gradient())->setStops(stops);
122         return;
123     }
124 
125     if (brush.style() == Qt::TexturePattern) {
126         // handled inside QPdfEnginePrivate::addImage() already
127         return;
128     }
129 }
130 
131 
132 /* also adds a space at the end of the number */
qt_real_to_string(qreal val,char * buf)133 const char *qt_real_to_string(qreal val, char *buf) {
134     const char *ret = buf;
135 
136     if (qIsNaN(val)) {
137         *(buf++) = '0';
138         *(buf++) = ' ';
139         *buf = 0;
140         return ret;
141     }
142 
143     if (val < 0) {
144         *(buf++) = '-';
145         val = -val;
146     }
147     unsigned int ival = (unsigned int) val;
148     qreal frac = val - (qreal)ival;
149 
150     int ifrac = (int)(frac * 1000000000);
151     if (ifrac == 1000000000) {
152         ++ival;
153         ifrac = 0;
154     }
155     char output[256];
156     int i = 0;
157     while (ival) {
158         output[i] = '0' + (ival % 10);
159         ++i;
160         ival /= 10;
161     }
162     int fact = 100000000;
163     if (i == 0) {
164         *(buf++) = '0';
165     } else {
166         while (i) {
167             *(buf++) = output[--i];
168             fact /= 10;
169             ifrac /= 10;
170         }
171     }
172 
173     if (ifrac) {
174         *(buf++) =  '.';
175         while (fact) {
176             *(buf++) = '0' + ((ifrac/fact) % 10);
177             fact /= 10;
178         }
179     }
180     *(buf++) = ' ';
181     *buf = 0;
182     return ret;
183 }
184 
qt_int_to_string(int val,char * buf)185 const char *qt_int_to_string(int val, char *buf) {
186     const char *ret = buf;
187     if (val < 0) {
188         *(buf++) = '-';
189         val = -val;
190     }
191     char output[256];
192     int i = 0;
193     while (val) {
194         output[i] = '0' + (val % 10);
195         ++i;
196         val /= 10;
197     }
198     if (i == 0) {
199         *(buf++) = '0';
200     } else {
201         while (i)
202             *(buf++) = output[--i];
203     }
204     *(buf++) = ' ';
205     *buf = 0;
206     return ret;
207 }
208 
209 
210 namespace QPdf {
ByteStream(QByteArray * byteArray,bool fileBacking)211     ByteStream::ByteStream(QByteArray *byteArray, bool fileBacking)
212             : dev(new QBuffer(byteArray)),
213             fileBackingEnabled(fileBacking),
214             fileBackingActive(false),
215             handleDirty(false)
216     {
217         dev->open(QIODevice::ReadWrite | QIODevice::Append);
218     }
219 
ByteStream(bool fileBacking)220     ByteStream::ByteStream(bool fileBacking)
221             : dev(new QBuffer(&ba)),
222             fileBackingEnabled(fileBacking),
223             fileBackingActive(false),
224             handleDirty(false)
225     {
226         dev->open(QIODevice::ReadWrite);
227     }
228 
~ByteStream()229     ByteStream::~ByteStream()
230     {
231         delete dev;
232     }
233 
operator <<(char chr)234     ByteStream &ByteStream::operator <<(char chr)
235     {
236         if (handleDirty) prepareBuffer();
237         dev->write(&chr, 1);
238         return *this;
239     }
240 
operator <<(const char * str)241     ByteStream &ByteStream::operator <<(const char *str)
242     {
243         if (handleDirty) prepareBuffer();
244         dev->write(str, strlen(str));
245         return *this;
246     }
247 
operator <<(const QByteArray & str)248     ByteStream &ByteStream::operator <<(const QByteArray &str)
249     {
250         if (handleDirty) prepareBuffer();
251         dev->write(str);
252         return *this;
253     }
254 
operator <<(const ByteStream & src)255     ByteStream &ByteStream::operator <<(const ByteStream &src)
256     {
257         Q_ASSERT(!src.dev->isSequential());
258         if (handleDirty) prepareBuffer();
259         // We do play nice here, even though it looks ugly.
260         // We save the position and restore it afterwards.
261         ByteStream &s = const_cast<ByteStream&>(src);
262         qint64 pos = s.dev->pos();
263         s.dev->reset();
264         while (!s.dev->atEnd()) {
265             QByteArray buf = s.dev->read(chunkSize());
266             dev->write(buf);
267         }
268         s.dev->seek(pos);
269         return *this;
270     }
271 
operator <<(qreal val)272     ByteStream &ByteStream::operator <<(qreal val) {
273         char buf[256];
274         qt_real_to_string(val, buf);
275         *this << buf;
276         return *this;
277     }
278 
operator <<(int val)279     ByteStream &ByteStream::operator <<(int val) {
280         char buf[256];
281         qt_int_to_string(val, buf);
282         *this << buf;
283         return *this;
284     }
285 
operator <<(const QPointF & p)286     ByteStream &ByteStream::operator <<(const QPointF &p) {
287         char buf[256];
288         qt_real_to_string(p.x(), buf);
289         *this << buf;
290         qt_real_to_string(p.y(), buf);
291         *this << buf;
292         return *this;
293     }
294 
stream()295     QIODevice *ByteStream::stream()
296     {
297         dev->reset();
298         handleDirty = true;
299         return dev;
300     }
301 
clear()302     void ByteStream::clear()
303     {
304         dev->open(QIODevice::ReadWrite | QIODevice::Truncate);
305     }
306 
constructor_helper(QByteArray * ba)307     void ByteStream::constructor_helper(QByteArray *ba)
308     {
309         delete dev;
310         dev = new QBuffer(ba);
311         dev->open(QIODevice::ReadWrite);
312     }
313 
prepareBuffer()314     void ByteStream::prepareBuffer()
315     {
316         Q_ASSERT(!dev->isSequential());
317         qint64 size = dev->size();
318         if (fileBackingEnabled && !fileBackingActive
319                 && size > maxMemorySize()) {
320             // Switch to file backing.
321             QTemporaryFile *newFile = new QTemporaryFile;
322             newFile->open();
323             dev->reset();
324             while (!dev->atEnd()) {
325                 QByteArray buf = dev->read(chunkSize());
326                 newFile->write(buf);
327             }
328             delete dev;
329             dev = newFile;
330             ba.clear();
331             fileBackingActive = true;
332         }
333         if (dev->pos() != size) {
334             dev->seek(size);
335             handleDirty = false;
336         }
337     }
338 }
339 
340 #define QT_PATH_ELEMENT(elm)
341 
generatePath(const QPainterPath & path,const QTransform & matrix,PathFlags flags)342 QByteArray QPdf::generatePath(const QPainterPath &path, const QTransform &matrix, PathFlags flags)
343 {
344     QByteArray result;
345     if (!path.elementCount())
346         return result;
347 
348     ByteStream s(&result);
349 
350     int start = -1;
351     for (int i = 0; i < path.elementCount(); ++i) {
352         const QPainterPath::Element &elm = path.elementAt(i);
353         switch (elm.type) {
354         case QPainterPath::MoveToElement:
355             if (start >= 0
356                 && path.elementAt(start).x == path.elementAt(i-1).x
357                 && path.elementAt(start).y == path.elementAt(i-1).y)
358                 s << "h\n";
359             s << matrix.map(QPointF(elm.x, elm.y)) << "m\n";
360             start = i;
361                 break;
362         case QPainterPath::LineToElement:
363             s << matrix.map(QPointF(elm.x, elm.y)) << "l\n";
364             break;
365         case QPainterPath::CurveToElement:
366             Q_ASSERT(path.elementAt(i+1).type == QPainterPath::CurveToDataElement);
367             Q_ASSERT(path.elementAt(i+2).type == QPainterPath::CurveToDataElement);
368             s << matrix.map(QPointF(elm.x, elm.y))
369               << matrix.map(QPointF(path.elementAt(i+1).x, path.elementAt(i+1).y))
370               << matrix.map(QPointF(path.elementAt(i+2).x, path.elementAt(i+2).y))
371               << "c\n";
372             i += 2;
373             break;
374         default:
375             qFatal("QPdf::generatePath(), unhandled type: %d", elm.type);
376         }
377     }
378     if (start >= 0
379         && path.elementAt(start).x == path.elementAt(path.elementCount()-1).x
380         && path.elementAt(start).y == path.elementAt(path.elementCount()-1).y)
381         s << "h\n";
382 
383     Qt::FillRule fillRule = path.fillRule();
384 
385     const char *op = "";
386     switch (flags) {
387     case ClipPath:
388         op = (fillRule == Qt::WindingFill) ? "W n\n" : "W* n\n";
389         break;
390     case FillPath:
391         op = (fillRule == Qt::WindingFill) ? "f\n" : "f*\n";
392         break;
393     case StrokePath:
394         op = "S\n";
395         break;
396     case FillAndStrokePath:
397         op = (fillRule == Qt::WindingFill) ? "B\n" : "B*\n";
398         break;
399     }
400     s << op;
401     return result;
402 }
403 
generateMatrix(const QTransform & matrix)404 QByteArray QPdf::generateMatrix(const QTransform &matrix)
405 {
406     QByteArray result;
407     ByteStream s(&result);
408     s << matrix.m11()
409       << matrix.m12()
410       << matrix.m21()
411       << matrix.m22()
412       << matrix.dx()
413       << matrix.dy()
414       << "cm\n";
415     return result;
416 }
417 
generateDashes(const QPen & pen)418 QByteArray QPdf::generateDashes(const QPen &pen)
419 {
420     QByteArray result;
421     ByteStream s(&result);
422     s << '[';
423 
424     QVector<qreal> dasharray = pen.dashPattern();
425     qreal w = pen.widthF();
426     if (w < 0.001)
427         w = 1;
428     for (int i = 0; i < dasharray.size(); ++i) {
429         qreal dw = dasharray.at(i)*w;
430         if (dw < 0.0001) dw = 0.0001;
431         s << dw;
432     }
433     s << ']';
434     s << pen.dashOffset() * w;
435     s << " d\n";
436     return result;
437 }
438 
439 
440 
441 static const char* const pattern_for_brush[] = {
442     nullptr, // NoBrush
443     nullptr, // SolidPattern
444     "0 J\n"
445     "6 w\n"
446     "[] 0 d\n"
447     "4 0 m\n"
448     "4 8 l\n"
449     "0 4 m\n"
450     "8 4 l\n"
451     "S\n", // Dense1Pattern
452 
453     "0 J\n"
454     "2 w\n"
455     "[6 2] 1 d\n"
456     "0 0 m\n"
457     "0 8 l\n"
458     "8 0 m\n"
459     "8 8 l\n"
460     "S\n"
461     "[] 0 d\n"
462     "2 0 m\n"
463     "2 8 l\n"
464     "6 0 m\n"
465     "6 8 l\n"
466     "S\n"
467     "[6 2] -3 d\n"
468     "4 0 m\n"
469     "4 8 l\n"
470     "S\n", // Dense2Pattern
471 
472     "0 J\n"
473     "2 w\n"
474     "[6 2] 1 d\n"
475     "0 0 m\n"
476     "0 8 l\n"
477     "8 0 m\n"
478     "8 8 l\n"
479     "S\n"
480     "[2 2] -1 d\n"
481     "2 0 m\n"
482     "2 8 l\n"
483     "6 0 m\n"
484     "6 8 l\n"
485     "S\n"
486     "[6 2] -3 d\n"
487     "4 0 m\n"
488     "4 8 l\n"
489     "S\n", // Dense3Pattern
490 
491     "0 J\n"
492     "2 w\n"
493     "[2 2] 1 d\n"
494     "0 0 m\n"
495     "0 8 l\n"
496     "8 0 m\n"
497     "8 8 l\n"
498     "S\n"
499     "[2 2] -1 d\n"
500     "2 0 m\n"
501     "2 8 l\n"
502     "6 0 m\n"
503     "6 8 l\n"
504     "S\n"
505     "[2 2] 1 d\n"
506     "4 0 m\n"
507     "4 8 l\n"
508     "S\n", // Dense4Pattern
509 
510     "0 J\n"
511     "2 w\n"
512     "[2 6] -1 d\n"
513     "0 0 m\n"
514     "0 8 l\n"
515     "8 0 m\n"
516     "8 8 l\n"
517     "S\n"
518     "[2 2] 1 d\n"
519     "2 0 m\n"
520     "2 8 l\n"
521     "6 0 m\n"
522     "6 8 l\n"
523     "S\n"
524     "[2 6] 3 d\n"
525     "4 0 m\n"
526     "4 8 l\n"
527     "S\n", // Dense5Pattern
528 
529     "0 J\n"
530     "2 w\n"
531     "[2 6] -1 d\n"
532     "0 0 m\n"
533     "0 8 l\n"
534     "8 0 m\n"
535     "8 8 l\n"
536     "S\n"
537     "[2 6] 3 d\n"
538     "4 0 m\n"
539     "4 8 l\n"
540     "S\n", // Dense6Pattern
541 
542     "0 J\n"
543     "2 w\n"
544     "[2 6] -1 d\n"
545     "0 0 m\n"
546     "0 8 l\n"
547     "8 0 m\n"
548     "8 8 l\n"
549     "S\n", // Dense7Pattern
550 
551     "1 w\n"
552     "0 4 m\n"
553     "8 4 l\n"
554     "S\n", // HorPattern
555 
556     "1 w\n"
557     "4 0 m\n"
558     "4 8 l\n"
559     "S\n", // VerPattern
560 
561     "1 w\n"
562     "4 0 m\n"
563     "4 8 l\n"
564     "0 4 m\n"
565     "8 4 l\n"
566     "S\n", // CrossPattern
567 
568     "1 w\n"
569     "-1 5 m\n"
570     "5 -1 l\n"
571     "3 9 m\n"
572     "9 3 l\n"
573     "S\n", // BDiagPattern
574 
575     "1 w\n"
576     "-1 3 m\n"
577     "5 9 l\n"
578     "3 -1 m\n"
579     "9 5 l\n"
580     "S\n", // FDiagPattern
581 
582     "1 w\n"
583     "-1 3 m\n"
584     "5 9 l\n"
585     "3 -1 m\n"
586     "9 5 l\n"
587     "-1 5 m\n"
588     "5 -1 l\n"
589     "3 9 m\n"
590     "9 3 l\n"
591     "S\n", // DiagCrossPattern
592 };
593 
patternForBrush(const QBrush & b)594 QByteArray QPdf::patternForBrush(const QBrush &b)
595 {
596     int style = b.style();
597     if (style > Qt::DiagCrossPattern)
598         return QByteArray();
599     return pattern_for_brush[style];
600 }
601 
602 
moveToHook(qfixed x,qfixed y,void * data)603 static void moveToHook(qfixed x, qfixed y, void *data)
604 {
605     QPdf::Stroker *t = (QPdf::Stroker *)data;
606     if (!t->first)
607         *t->stream << "h\n";
608     if (!t->cosmeticPen)
609         t->matrix.map(x, y, &x, &y);
610     *t->stream << x << y << "m\n";
611     t->first = false;
612 }
613 
lineToHook(qfixed x,qfixed y,void * data)614 static void lineToHook(qfixed x, qfixed y, void *data)
615 {
616     QPdf::Stroker *t = (QPdf::Stroker *)data;
617     if (!t->cosmeticPen)
618         t->matrix.map(x, y, &x, &y);
619     *t->stream << x << y << "l\n";
620 }
621 
cubicToHook(qfixed c1x,qfixed c1y,qfixed c2x,qfixed c2y,qfixed ex,qfixed ey,void * data)622 static void cubicToHook(qfixed c1x, qfixed c1y,
623                         qfixed c2x, qfixed c2y,
624                         qfixed ex, qfixed ey,
625                         void *data)
626 {
627     QPdf::Stroker *t = (QPdf::Stroker *)data;
628     if (!t->cosmeticPen) {
629         t->matrix.map(c1x, c1y, &c1x, &c1y);
630         t->matrix.map(c2x, c2y, &c2x, &c2y);
631         t->matrix.map(ex, ey, &ex, &ey);
632     }
633     *t->stream << c1x << c1y
634                << c2x << c2y
635                << ex << ey
636                << "c\n";
637 }
638 
Stroker()639 QPdf::Stroker::Stroker()
640     : stream(nullptr),
641     first(true),
642     dashStroker(&basicStroker)
643 {
644     stroker = &basicStroker;
645     basicStroker.setMoveToHook(moveToHook);
646     basicStroker.setLineToHook(lineToHook);
647     basicStroker.setCubicToHook(cubicToHook);
648     cosmeticPen = true;
649     basicStroker.setStrokeWidth(.1);
650 }
651 
setPen(const QPen & pen,QPainter::RenderHints hints)652 void QPdf::Stroker::setPen(const QPen &pen, QPainter::RenderHints hints)
653 {
654     if (pen.style() == Qt::NoPen) {
655         stroker = nullptr;
656         return;
657     }
658     qreal w = pen.widthF();
659     bool zeroWidth = w < 0.0001;
660     cosmeticPen = qt_pen_is_cosmetic(pen, hints);
661     if (zeroWidth)
662         w = .1;
663 
664     basicStroker.setStrokeWidth(w);
665     basicStroker.setCapStyle(pen.capStyle());
666     basicStroker.setJoinStyle(pen.joinStyle());
667     basicStroker.setMiterLimit(pen.miterLimit());
668 
669     QVector<qreal> dashpattern = pen.dashPattern();
670     if (zeroWidth) {
671         for (int i = 0; i < dashpattern.size(); ++i)
672             dashpattern[i] *= 10.;
673     }
674     if (!dashpattern.isEmpty()) {
675         dashStroker.setDashPattern(dashpattern);
676         dashStroker.setDashOffset(pen.dashOffset());
677         stroker = &dashStroker;
678     } else {
679         stroker = &basicStroker;
680     }
681 }
682 
strokePath(const QPainterPath & path)683 void QPdf::Stroker::strokePath(const QPainterPath &path)
684 {
685     if (!stroker)
686         return;
687     first = true;
688 
689     stroker->strokePath(path, this, cosmeticPen ? matrix : QTransform());
690     *stream << "h f\n";
691 }
692 
ascii85Encode(const QByteArray & input)693 QByteArray QPdf::ascii85Encode(const QByteArray &input)
694 {
695     int isize = input.size()/4*4;
696     QByteArray output;
697     output.resize(input.size()*5/4+7);
698     char *out = output.data();
699     const uchar *in = (const uchar *)input.constData();
700     for (int i = 0; i < isize; i += 4) {
701         uint val = (((uint)in[i])<<24) + (((uint)in[i+1])<<16) + (((uint)in[i+2])<<8) + (uint)in[i+3];
702         if (val == 0) {
703             *out = 'z';
704             ++out;
705         } else {
706             char base[5];
707             base[4] = val % 85;
708             val /= 85;
709             base[3] = val % 85;
710             val /= 85;
711             base[2] = val % 85;
712             val /= 85;
713             base[1] = val % 85;
714             val /= 85;
715             base[0] = val % 85;
716             *(out++) = base[0] + '!';
717             *(out++) = base[1] + '!';
718             *(out++) = base[2] + '!';
719             *(out++) = base[3] + '!';
720             *(out++) = base[4] + '!';
721         }
722     }
723     //write the last few bytes
724     int remaining = input.size() - isize;
725     if (remaining) {
726         uint val = 0;
727         for (int i = isize; i < input.size(); ++i)
728             val = (val << 8) + in[i];
729         val <<= 8*(4-remaining);
730         char base[5];
731         base[4] = val % 85;
732         val /= 85;
733         base[3] = val % 85;
734         val /= 85;
735         base[2] = val % 85;
736         val /= 85;
737         base[1] = val % 85;
738         val /= 85;
739         base[0] = val % 85;
740         for (int i = 0; i < remaining+1; ++i)
741             *(out++) = base[i] + '!';
742     }
743     *(out++) = '~';
744     *(out++) = '>';
745     output.resize(out-output.data());
746     return output;
747 }
748 
toHex(ushort u,char * buffer)749 const char *QPdf::toHex(ushort u, char *buffer)
750 {
751     int i = 3;
752     while (i >= 0) {
753         ushort hex = (u & 0x000f);
754         if (hex < 0x0a)
755             buffer[i] = '0'+hex;
756         else
757             buffer[i] = 'A'+(hex-0x0a);
758         u = u >> 4;
759         i--;
760     }
761     buffer[4] = '\0';
762     return buffer;
763 }
764 
toHex(uchar u,char * buffer)765 const char *QPdf::toHex(uchar u, char *buffer)
766 {
767     int i = 1;
768     while (i >= 0) {
769         ushort hex = (u & 0x000f);
770         if (hex < 0x0a)
771             buffer[i] = '0'+hex;
772         else
773             buffer[i] = 'A'+(hex-0x0a);
774         u = u >> 4;
775         i--;
776     }
777     buffer[2] = '\0';
778     return buffer;
779 }
780 
781 
QPdfPage()782 QPdfPage::QPdfPage()
783     : QPdf::ByteStream(true) // Enable file backing
784 {
785 }
786 
streamImage(int w,int h,int object)787 void QPdfPage::streamImage(int w, int h, int object)
788 {
789     *this << w << "0 0 " << -h << "0 " << h << "cm /Im" << object << " Do\n";
790     if (!images.contains(object))
791         images.append(object);
792 }
793 
794 
QPdfEngine(QPdfEnginePrivate & dd)795 QPdfEngine::QPdfEngine(QPdfEnginePrivate &dd)
796     : QPaintEngine(dd, qt_pdf_decide_features())
797 {
798 }
799 
QPdfEngine()800 QPdfEngine::QPdfEngine()
801     : QPaintEngine(*new QPdfEnginePrivate(), qt_pdf_decide_features())
802 {
803 }
804 
setOutputFilename(const QString & filename)805 void QPdfEngine::setOutputFilename(const QString &filename)
806 {
807     Q_D(QPdfEngine);
808     d->outputFileName = filename;
809 }
810 
811 
drawPoints(const QPointF * points,int pointCount)812 void QPdfEngine::drawPoints (const QPointF *points, int pointCount)
813 {
814     if (!points)
815         return;
816 
817     Q_D(QPdfEngine);
818     QPainterPath p;
819     for (int i=0; i!=pointCount;++i) {
820         p.moveTo(points[i]);
821         p.lineTo(points[i] + QPointF(0, 0.001));
822     }
823 
824     bool hadBrush = d->hasBrush;
825     d->hasBrush = false;
826     drawPath(p);
827     d->hasBrush = hadBrush;
828 }
829 
drawLines(const QLineF * lines,int lineCount)830 void QPdfEngine::drawLines (const QLineF *lines, int lineCount)
831 {
832     if (!lines)
833         return;
834 
835     Q_D(QPdfEngine);
836     QPainterPath p;
837     for (int i=0; i!=lineCount;++i) {
838         p.moveTo(lines[i].p1());
839         p.lineTo(lines[i].p2());
840     }
841     bool hadBrush = d->hasBrush;
842     d->hasBrush = false;
843     drawPath(p);
844     d->hasBrush = hadBrush;
845 }
846 
drawRects(const QRectF * rects,int rectCount)847 void QPdfEngine::drawRects (const QRectF *rects, int rectCount)
848 {
849     if (!rects)
850         return;
851 
852     Q_D(QPdfEngine);
853 
854     if (d->clipEnabled && d->allClipped)
855         return;
856     if (!d->hasPen && !d->hasBrush)
857         return;
858 
859     if ((d->simplePen && !d->needsTransform) || !d->hasPen) {
860         // draw natively in this case for better output
861         if (!d->hasPen && d->needsTransform) // i.e. this is just a fillrect
862             *d->currentPage << "q\n" << QPdf::generateMatrix(d->stroker.matrix);
863         for (int i = 0; i < rectCount; ++i)
864             *d->currentPage << rects[i].x() << rects[i].y() << rects[i].width() << rects[i].height() << "re\n";
865         *d->currentPage << (d->hasPen ? (d->hasBrush ? "B\n" : "S\n") : "f\n");
866         if (!d->hasPen && d->needsTransform)
867             *d->currentPage << "Q\n";
868     } else {
869         QPainterPath p;
870         for (int i=0; i!=rectCount; ++i)
871             p.addRect(rects[i]);
872         drawPath(p);
873     }
874 }
875 
drawPolygon(const QPointF * points,int pointCount,PolygonDrawMode mode)876 void QPdfEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode)
877 {
878     Q_D(QPdfEngine);
879 
880     if (!points || !pointCount)
881         return;
882 
883     bool hb = d->hasBrush;
884     QPainterPath p;
885 
886     switch(mode) {
887         case OddEvenMode:
888             p.setFillRule(Qt::OddEvenFill);
889             break;
890         case ConvexMode:
891         case WindingMode:
892             p.setFillRule(Qt::WindingFill);
893             break;
894         case PolylineMode:
895             d->hasBrush = false;
896             break;
897         default:
898             break;
899     }
900 
901     p.moveTo(points[0]);
902     for (int i = 1; i < pointCount; ++i)
903         p.lineTo(points[i]);
904 
905     if (mode != PolylineMode)
906         p.closeSubpath();
907     drawPath(p);
908 
909     d->hasBrush = hb;
910 }
911 
drawPath(const QPainterPath & p)912 void QPdfEngine::drawPath (const QPainterPath &p)
913 {
914     Q_D(QPdfEngine);
915 
916     if (d->clipEnabled && d->allClipped)
917         return;
918     if (!d->hasPen && !d->hasBrush)
919         return;
920 
921     if (d->simplePen) {
922         // draw strokes natively in this case for better output
923         *d->currentPage << QPdf::generatePath(p, d->needsTransform ? d->stroker.matrix : QTransform(),
924                                               d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath);
925     } else {
926         if (d->hasBrush)
927             *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath);
928         if (d->hasPen) {
929             *d->currentPage << "q\n";
930             QBrush b = d->brush;
931             d->brush = d->pen.brush();
932             setBrush();
933             d->stroker.strokePath(p);
934             *d->currentPage << "Q\n";
935             d->brush = b;
936         }
937     }
938 }
939 
drawPixmap(const QRectF & rectangle,const QPixmap & pixmap,const QRectF & sr)940 void QPdfEngine::drawPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QRectF &sr)
941 {
942     if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull())
943         return;
944     Q_D(QPdfEngine);
945 
946     QBrush b = d->brush;
947 
948     QRect sourceRect = sr.toRect();
949     QPixmap pm = sourceRect != pixmap.rect() ? pixmap.copy(sourceRect) : pixmap;
950     QImage image = pm.toImage();
951     bool bitmap = true;
952     const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering);
953     const int object = d->addImage(image, &bitmap, lossless, pm.cacheKey());
954     if (object < 0)
955         return;
956 
957     *d->currentPage << "q\n";
958 
959     if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) {
960         int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity));
961         if (stateObject)
962             *d->currentPage << "/GState" << stateObject << "gs\n";
963         else
964             *d->currentPage << "/GSa gs\n";
965     } else {
966         *d->currentPage << "/GSa gs\n";
967     }
968 
969     *d->currentPage
970         << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(),
971                                            rectangle.x(), rectangle.y()) * (!d->needsTransform ? QTransform() : d->stroker.matrix));
972     if (bitmap) {
973         // set current pen as d->brush
974         d->brush = d->pen.brush();
975     }
976     setBrush();
977     d->currentPage->streamImage(image.width(), image.height(), object);
978     *d->currentPage << "Q\n";
979 
980     d->brush = b;
981 }
982 
drawImage(const QRectF & rectangle,const QImage & image,const QRectF & sr,Qt::ImageConversionFlags)983 void QPdfEngine::drawImage(const QRectF &rectangle, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags)
984 {
985     if (sr.isEmpty() || rectangle.isEmpty() || image.isNull())
986         return;
987     Q_D(QPdfEngine);
988 
989     QRect sourceRect = sr.toRect();
990     QImage im = sourceRect != image.rect() ? image.copy(sourceRect) : image;
991     bool bitmap = true;
992     const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering);
993     const int object = d->addImage(im, &bitmap, lossless, im.cacheKey());
994     if (object < 0)
995         return;
996 
997     *d->currentPage << "q\n";
998 
999     if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) {
1000         int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity));
1001         if (stateObject)
1002             *d->currentPage << "/GState" << stateObject << "gs\n";
1003         else
1004             *d->currentPage << "/GSa gs\n";
1005     } else {
1006         *d->currentPage << "/GSa gs\n";
1007     }
1008 
1009     *d->currentPage
1010         << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(),
1011                                            rectangle.x(), rectangle.y()) * (!d->needsTransform ? QTransform() : d->stroker.matrix));
1012     setBrush();
1013     d->currentPage->streamImage(im.width(), im.height(), object);
1014     *d->currentPage << "Q\n";
1015 }
1016 
drawTiledPixmap(const QRectF & rectangle,const QPixmap & pixmap,const QPointF & point)1017 void QPdfEngine::drawTiledPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QPointF &point)
1018 {
1019     Q_D(QPdfEngine);
1020 
1021     bool bitmap = (pixmap.depth() == 1);
1022     QBrush b = d->brush;
1023     QPointF bo = d->brushOrigin;
1024     bool hp = d->hasPen;
1025     d->hasPen = false;
1026     bool hb = d->hasBrush;
1027     d->hasBrush = true;
1028 
1029     d->brush = QBrush(pixmap);
1030     if (bitmap)
1031         // #### fix bitmap case where we have a brush pen
1032         d->brush.setColor(d->pen.color());
1033 
1034     d->brushOrigin = -point;
1035     *d->currentPage << "q\n";
1036     setBrush();
1037 
1038     drawRects(&rectangle, 1);
1039     *d->currentPage << "Q\n";
1040 
1041     d->hasPen = hp;
1042     d->hasBrush = hb;
1043     d->brush = b;
1044     d->brushOrigin = bo;
1045 }
1046 
drawTextItem(const QPointF & p,const QTextItem & textItem)1047 void QPdfEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
1048 {
1049     Q_D(QPdfEngine);
1050 
1051     if (!d->hasPen || (d->clipEnabled && d->allClipped))
1052         return;
1053 
1054     if (d->stroker.matrix.type() >= QTransform::TxProject) {
1055         QPaintEngine::drawTextItem(p, textItem);
1056         return;
1057     }
1058 
1059     *d->currentPage << "q\n";
1060     if (d->needsTransform)
1061         *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1062 
1063     bool hp = d->hasPen;
1064     d->hasPen = false;
1065     QBrush b = d->brush;
1066     d->brush = d->pen.brush();
1067     setBrush();
1068 
1069     const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
1070     Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi);
1071     d->drawTextItem(p, ti);
1072     d->hasPen = hp;
1073     d->brush = b;
1074     *d->currentPage << "Q\n";
1075 }
1076 
drawHyperlink(const QRectF & r,const QUrl & url)1077 void QPdfEngine::drawHyperlink(const QRectF &r, const QUrl &url)
1078 {
1079     Q_D(QPdfEngine);
1080 
1081     const uint annot = d->addXrefEntry(-1);
1082     const QByteArray urlascii = url.toEncoded();
1083     int len = urlascii.size();
1084     QVarLengthArray<char> url_esc;
1085     url_esc.reserve(len + 1);
1086     for (int j = 0; j < len; j++) {
1087         if (urlascii[j] == '(' || urlascii[j] == ')' || urlascii[j] == '\\')
1088             url_esc.append('\\');
1089         url_esc.append(urlascii[j]);
1090     }
1091     url_esc.append('\0');
1092 
1093     char buf[256];
1094     const QRectF rr = d->pageMatrix().mapRect(r);
1095     d->xprintf("<<\n/Type /Annot\n/Subtype /Link\n");
1096 
1097     if (d->pdfVersion == QPdfEngine::Version_A1b)
1098         d->xprintf("/F 4\n"); // enable print flag, disable all other
1099 
1100     d->xprintf("/Rect [");
1101     d->xprintf("%s ", qt_real_to_string(rr.left(), buf));
1102     d->xprintf("%s ", qt_real_to_string(rr.top(), buf));
1103     d->xprintf("%s ", qt_real_to_string(rr.right(), buf));
1104     d->xprintf("%s", qt_real_to_string(rr.bottom(), buf));
1105     d->xprintf("]\n/Border [0 0 0]\n/A <<\n");
1106     d->xprintf("/Type /Action\n/S /URI\n/URI (%s)\n", url_esc.constData());
1107     d->xprintf(">>\n>>\n");
1108     d->xprintf("endobj\n");
1109     d->currentPage->annotations.append(annot);
1110 }
1111 
updateState(const QPaintEngineState & state)1112 void QPdfEngine::updateState(const QPaintEngineState &state)
1113 {
1114     Q_D(QPdfEngine);
1115 
1116     QPaintEngine::DirtyFlags flags = state.state();
1117 
1118     if (flags & DirtyTransform)
1119         d->stroker.matrix = state.transform();
1120 
1121     if (flags & DirtyPen) {
1122         if (d->pdfVersion == QPdfEngine::Version_A1b) {
1123             QPen pen = state.pen();
1124 
1125             QColor penColor = pen.color();
1126             if (penColor.alpha() != 255)
1127                 penColor.setAlpha(255);
1128             pen.setColor(penColor);
1129 
1130             QBrush penBrush = pen.brush();
1131             removeTransparencyFromBrush(penBrush);
1132             pen.setBrush(penBrush);
1133 
1134             d->pen = pen;
1135         } else {
1136             d->pen = state.pen();
1137         }
1138         d->hasPen = d->pen.style() != Qt::NoPen;
1139         bool oldCosmetic = d->stroker.cosmeticPen;
1140         d->stroker.setPen(d->pen, state.renderHints());
1141         QBrush penBrush = d->pen.brush();
1142         bool oldSimple = d->simplePen;
1143         d->simplePen = (d->hasPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque() && d->opacity == 1.0);
1144         if (oldSimple != d->simplePen || oldCosmetic != d->stroker.cosmeticPen)
1145             flags |= DirtyTransform;
1146     } else if (flags & DirtyHints) {
1147         d->stroker.setPen(d->pen, state.renderHints());
1148     }
1149     if (flags & DirtyBrush) {
1150         if (d->pdfVersion == QPdfEngine::Version_A1b) {
1151             QBrush brush = state.brush();
1152             removeTransparencyFromBrush(brush);
1153             d->brush = brush;
1154         } else {
1155             d->brush = state.brush();
1156         }
1157         if (d->brush.color().alpha() == 0 && d->brush.style() == Qt::SolidPattern)
1158             d->brush.setStyle(Qt::NoBrush);
1159         d->hasBrush = d->brush.style() != Qt::NoBrush;
1160     }
1161     if (flags & DirtyBrushOrigin) {
1162         d->brushOrigin = state.brushOrigin();
1163         flags |= DirtyBrush;
1164     }
1165     if (flags & DirtyOpacity) {
1166         d->opacity = state.opacity();
1167         if (d->simplePen && d->opacity != 1.0) {
1168             d->simplePen = false;
1169             flags |= DirtyTransform;
1170         }
1171     }
1172 
1173     bool ce = d->clipEnabled;
1174     if (flags & DirtyClipPath) {
1175         d->clipEnabled = true;
1176         updateClipPath(state.clipPath(), state.clipOperation());
1177     } else if (flags & DirtyClipRegion) {
1178         d->clipEnabled = true;
1179         QPainterPath path;
1180         for (const QRect &rect : state.clipRegion())
1181             path.addRect(rect);
1182         updateClipPath(path, state.clipOperation());
1183         flags |= DirtyClipPath;
1184     } else if (flags & DirtyClipEnabled) {
1185         d->clipEnabled = state.isClipEnabled();
1186     }
1187 
1188     if (ce != d->clipEnabled)
1189         flags |= DirtyClipPath;
1190     else if (!d->clipEnabled)
1191         flags &= ~DirtyClipPath;
1192 
1193     setupGraphicsState(flags);
1194 }
1195 
setupGraphicsState(QPaintEngine::DirtyFlags flags)1196 void QPdfEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags)
1197 {
1198     Q_D(QPdfEngine);
1199     if (flags & DirtyClipPath)
1200         flags |= DirtyTransform|DirtyPen|DirtyBrush;
1201 
1202     if (flags & DirtyTransform) {
1203         *d->currentPage << "Q\n";
1204         flags |= DirtyPen|DirtyBrush;
1205     }
1206 
1207     if (flags & DirtyClipPath) {
1208         *d->currentPage << "Q q\n";
1209 
1210         d->allClipped = false;
1211         if (d->clipEnabled && !d->clips.isEmpty()) {
1212             for (int i = 0; i < d->clips.size(); ++i) {
1213                 if (d->clips.at(i).isEmpty()) {
1214                     d->allClipped = true;
1215                     break;
1216                 }
1217             }
1218             if (!d->allClipped) {
1219                 for (int i = 0; i < d->clips.size(); ++i) {
1220                     *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath);
1221                 }
1222             }
1223         }
1224     }
1225 
1226     if (flags & DirtyTransform) {
1227         *d->currentPage << "q\n";
1228         d->needsTransform = false;
1229         if (!d->stroker.matrix.isIdentity()) {
1230             if (d->simplePen && !d->stroker.cosmeticPen)
1231                 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1232             else
1233                 d->needsTransform = true; // I.e. page-wide xf not set, local xf needed
1234         }
1235     }
1236     if (flags & DirtyBrush)
1237         setBrush();
1238     if (d->simplePen && (flags & DirtyPen))
1239         setPen();
1240 }
1241 
1242 extern QPainterPath qt_regionToPath(const QRegion &region);
1243 
updateClipPath(const QPainterPath & p,Qt::ClipOperation op)1244 void QPdfEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op)
1245 {
1246     Q_D(QPdfEngine);
1247     QPainterPath path = d->stroker.matrix.map(p);
1248     //qDebug() << "updateClipPath: " << d->stroker.matrix << p.boundingRect() << path.boundingRect() << op;
1249 
1250     if (op == Qt::NoClip) {
1251         d->clipEnabled = false;
1252         d->clips.clear();
1253     } else if (op == Qt::ReplaceClip) {
1254         d->clips.clear();
1255         d->clips.append(path);
1256     } else if (op == Qt::IntersectClip) {
1257         d->clips.append(path);
1258     } else { // UniteClip
1259         // ask the painter for the current clipping path. that's the easiest solution
1260         path = painter()->clipPath();
1261         path = d->stroker.matrix.map(path);
1262         d->clips.clear();
1263         d->clips.append(path);
1264     }
1265 }
1266 
setPen()1267 void QPdfEngine::setPen()
1268 {
1269     Q_D(QPdfEngine);
1270     if (d->pen.style() == Qt::NoPen)
1271         return;
1272     QBrush b = d->pen.brush();
1273     Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque());
1274 
1275     QColor rgba = b.color();
1276     if (d->grayscale) {
1277         qreal gray = qGray(rgba.rgba())/255.;
1278         *d->currentPage << gray << gray << gray;
1279     } else {
1280         *d->currentPage << rgba.redF()
1281                         << rgba.greenF()
1282                         << rgba.blueF();
1283     }
1284     *d->currentPage << "SCN\n";
1285 
1286     *d->currentPage << d->pen.widthF() << "w ";
1287 
1288     int pdfCapStyle = 0;
1289     switch(d->pen.capStyle()) {
1290     case Qt::FlatCap:
1291         pdfCapStyle = 0;
1292         break;
1293     case Qt::SquareCap:
1294         pdfCapStyle = 2;
1295         break;
1296     case Qt::RoundCap:
1297         pdfCapStyle = 1;
1298         break;
1299     default:
1300         break;
1301     }
1302     *d->currentPage << pdfCapStyle << "J ";
1303 
1304     int pdfJoinStyle = 0;
1305     switch(d->pen.joinStyle()) {
1306     case Qt::MiterJoin:
1307     case Qt::SvgMiterJoin:
1308         *d->currentPage << qMax(qreal(1.0), d->pen.miterLimit()) << "M ";
1309         pdfJoinStyle = 0;
1310         break;
1311     case Qt::BevelJoin:
1312         pdfJoinStyle = 2;
1313         break;
1314     case Qt::RoundJoin:
1315         pdfJoinStyle = 1;
1316         break;
1317     default:
1318         break;
1319     }
1320     *d->currentPage << pdfJoinStyle << "j ";
1321 
1322     *d->currentPage << QPdf::generateDashes(d->pen);
1323 }
1324 
1325 
setBrush()1326 void QPdfEngine::setBrush()
1327 {
1328     Q_D(QPdfEngine);
1329     Qt::BrushStyle style = d->brush.style();
1330     if (style == Qt::NoBrush)
1331         return;
1332 
1333     bool specifyColor;
1334     int gStateObject = 0;
1335     int patternObject = d->addBrushPattern(d->stroker.matrix, &specifyColor, &gStateObject);
1336     if (!patternObject && !specifyColor)
1337         return;
1338 
1339     *d->currentPage << (patternObject ? "/PCSp cs " : "/CSp cs ");
1340     if (specifyColor) {
1341         QColor rgba = d->brush.color();
1342         if (d->grayscale) {
1343             qreal gray = qGray(rgba.rgba())/255.;
1344             *d->currentPage << gray << gray << gray;
1345         } else {
1346             *d->currentPage << rgba.redF()
1347                             << rgba.greenF()
1348                             << rgba.blueF();
1349         }
1350     }
1351     if (patternObject)
1352         *d->currentPage << "/Pat" << patternObject;
1353     *d->currentPage << "scn\n";
1354 
1355     if (gStateObject)
1356         *d->currentPage << "/GState" << gStateObject << "gs\n";
1357     else
1358         *d->currentPage << "/GSa gs\n";
1359 }
1360 
1361 
newPage()1362 bool QPdfEngine::newPage()
1363 {
1364     Q_D(QPdfEngine);
1365     if (!isActive())
1366         return false;
1367     d->newPage();
1368 
1369     setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath);
1370     QFile *outfile = qobject_cast<QFile*> (d->outDevice);
1371     if (outfile && outfile->error() != QFile::NoError)
1372         return false;
1373     return true;
1374 }
1375 
type() const1376 QPaintEngine::Type QPdfEngine::type() const
1377 {
1378     return QPaintEngine::Pdf;
1379 }
1380 
setResolution(int resolution)1381 void QPdfEngine::setResolution(int resolution)
1382 {
1383     Q_D(QPdfEngine);
1384     d->resolution = resolution;
1385 }
1386 
resolution() const1387 int QPdfEngine::resolution() const
1388 {
1389     Q_D(const QPdfEngine);
1390     return d->resolution;
1391 }
1392 
setPdfVersion(PdfVersion version)1393 void QPdfEngine::setPdfVersion(PdfVersion version)
1394 {
1395     Q_D(QPdfEngine);
1396     d->pdfVersion = version;
1397 }
1398 
setDocumentXmpMetadata(const QByteArray & xmpMetadata)1399 void QPdfEngine::setDocumentXmpMetadata(const QByteArray &xmpMetadata)
1400 {
1401     Q_D(QPdfEngine);
1402     d->xmpDocumentMetadata = xmpMetadata;
1403 }
1404 
documentXmpMetadata() const1405 QByteArray QPdfEngine::documentXmpMetadata() const
1406 {
1407     Q_D(const QPdfEngine);
1408     return d->xmpDocumentMetadata;
1409 }
1410 
setPageLayout(const QPageLayout & pageLayout)1411 void QPdfEngine::setPageLayout(const QPageLayout &pageLayout)
1412 {
1413     Q_D(QPdfEngine);
1414     d->m_pageLayout = pageLayout;
1415 }
1416 
setPageSize(const QPageSize & pageSize)1417 void QPdfEngine::setPageSize(const QPageSize &pageSize)
1418 {
1419     Q_D(QPdfEngine);
1420     d->m_pageLayout.setPageSize(pageSize);
1421 }
1422 
setPageOrientation(QPageLayout::Orientation orientation)1423 void QPdfEngine::setPageOrientation(QPageLayout::Orientation orientation)
1424 {
1425     Q_D(QPdfEngine);
1426     d->m_pageLayout.setOrientation(orientation);
1427 }
1428 
setPageMargins(const QMarginsF & margins,QPageLayout::Unit units)1429 void QPdfEngine::setPageMargins(const QMarginsF &margins, QPageLayout::Unit units)
1430 {
1431     Q_D(QPdfEngine);
1432     d->m_pageLayout.setUnits(units);
1433     d->m_pageLayout.setMargins(margins);
1434 }
1435 
pageLayout() const1436 QPageLayout QPdfEngine::pageLayout() const
1437 {
1438     Q_D(const QPdfEngine);
1439     return d->m_pageLayout;
1440 }
1441 
1442 // Metrics are in Device Pixels
metric(QPaintDevice::PaintDeviceMetric metricType) const1443 int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const
1444 {
1445     Q_D(const QPdfEngine);
1446     int val;
1447     switch (metricType) {
1448     case QPaintDevice::PdmWidth:
1449         val = d->m_pageLayout.paintRectPixels(d->resolution).width();
1450         break;
1451     case QPaintDevice::PdmHeight:
1452         val = d->m_pageLayout.paintRectPixels(d->resolution).height();
1453         break;
1454     case QPaintDevice::PdmDpiX:
1455     case QPaintDevice::PdmDpiY:
1456         val = d->resolution;
1457         break;
1458     case QPaintDevice::PdmPhysicalDpiX:
1459     case QPaintDevice::PdmPhysicalDpiY:
1460         val = 1200;
1461         break;
1462     case QPaintDevice::PdmWidthMM:
1463         val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).width());
1464         break;
1465     case QPaintDevice::PdmHeightMM:
1466         val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).height());
1467         break;
1468     case QPaintDevice::PdmNumColors:
1469         val = INT_MAX;
1470         break;
1471     case QPaintDevice::PdmDepth:
1472         val = 32;
1473         break;
1474     case QPaintDevice::PdmDevicePixelRatio:
1475         val = 1;
1476         break;
1477     case QPaintDevice::PdmDevicePixelRatioScaled:
1478         val = 1 * QPaintDevice::devicePixelRatioFScale();
1479         break;
1480     default:
1481         qWarning("QPdfWriter::metric: Invalid metric command");
1482         return 0;
1483     }
1484     return val;
1485 }
1486 
QPdfEnginePrivate()1487 QPdfEnginePrivate::QPdfEnginePrivate()
1488     : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false),
1489       needsTransform(false), pdfVersion(QPdfEngine::Version_1_4),
1490       outDevice(nullptr), ownsDevice(false),
1491       embedFonts(true),
1492       grayscale(false),
1493       m_pageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(10, 10, 10, 10))
1494 {
1495     initResources();
1496     resolution = 1200;
1497     currentObject = 1;
1498     currentPage = nullptr;
1499     stroker.stream = nullptr;
1500 
1501     streampos = 0;
1502 
1503     stream = new QDataStream;
1504 }
1505 
begin(QPaintDevice * pdev)1506 bool QPdfEngine::begin(QPaintDevice *pdev)
1507 {
1508     Q_D(QPdfEngine);
1509     d->pdev = pdev;
1510 
1511     if (!d->outDevice) {
1512         if (!d->outputFileName.isEmpty()) {
1513             QFile *file = new QFile(d->outputFileName);
1514             if (!file->open(QFile::WriteOnly|QFile::Truncate)) {
1515                 delete file;
1516                 return false;
1517             }
1518             d->outDevice = file;
1519         } else {
1520             return false;
1521         }
1522         d->ownsDevice = true;
1523     }
1524 
1525     d->currentObject = 1;
1526 
1527     d->currentPage = new QPdfPage;
1528     d->stroker.stream = d->currentPage;
1529     d->opacity = 1.0;
1530 
1531     d->stream->setDevice(d->outDevice);
1532 
1533     d->streampos = 0;
1534     d->hasPen = true;
1535     d->hasBrush = false;
1536     d->clipEnabled = false;
1537     d->allClipped = false;
1538 
1539     d->xrefPositions.clear();
1540     d->pageRoot = 0;
1541     d->embeddedfilesRoot = 0;
1542     d->namesRoot = 0;
1543     d->catalog = 0;
1544     d->info = 0;
1545     d->graphicsState = 0;
1546     d->patternColorSpace = 0;
1547     d->simplePen = false;
1548     d->needsTransform = false;
1549 
1550     d->pages.clear();
1551     d->imageCache.clear();
1552     d->alphaCache.clear();
1553 
1554     setActive(true);
1555     d->writeHeader();
1556     newPage();
1557 
1558     return true;
1559 }
1560 
end()1561 bool QPdfEngine::end()
1562 {
1563     Q_D(QPdfEngine);
1564     d->writeTail();
1565 
1566     d->stream->setDevice(nullptr);
1567 
1568     qDeleteAll(d->fonts);
1569     d->fonts.clear();
1570     delete d->currentPage;
1571     d->currentPage = nullptr;
1572 
1573     if (d->outDevice && d->ownsDevice) {
1574         d->outDevice->close();
1575         delete d->outDevice;
1576         d->outDevice = nullptr;
1577     }
1578 
1579     d->fileCache.clear();
1580 
1581     setActive(false);
1582     return true;
1583 }
1584 
addFileAttachment(const QString & fileName,const QByteArray & data,const QString & mimeType)1585 void QPdfEngine::addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType)
1586 {
1587     Q_D(QPdfEngine);
1588     d->fileCache.push_back({fileName, data, mimeType});
1589 }
1590 
~QPdfEnginePrivate()1591 QPdfEnginePrivate::~QPdfEnginePrivate()
1592 {
1593     qDeleteAll(fonts);
1594     delete currentPage;
1595     delete stream;
1596 }
1597 
writeHeader()1598 void QPdfEnginePrivate::writeHeader()
1599 {
1600     addXrefEntry(0,false);
1601 
1602     // Keep in sync with QPdfEngine::PdfVersion!
1603     static const char mapping[][4] = {
1604         "1.4", // Version_1_4
1605         "1.4", // Version_A1b
1606         "1.6", // Version_1_6
1607     };
1608     static const size_t numMappings = sizeof mapping / sizeof *mapping;
1609     const char *verStr = mapping[size_t(pdfVersion) < numMappings ? pdfVersion : 0];
1610 
1611     xprintf("%%PDF-%s\n", verStr);
1612     xprintf("%%\303\242\303\243\n");
1613 
1614     writeInfo();
1615 
1616     int metaDataObj = -1;
1617     int outputIntentObj = -1;
1618     if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) {
1619         metaDataObj = writeXmpDcumentMetaData();
1620     }
1621     if (pdfVersion == QPdfEngine::Version_A1b) {
1622         outputIntentObj = writeOutputIntent();
1623     }
1624 
1625     catalog = addXrefEntry(-1);
1626     pageRoot = requestObject();
1627     if (!fileCache.isEmpty()) {
1628         namesRoot = requestObject();
1629         embeddedfilesRoot = requestObject();
1630     }
1631 
1632     // catalog
1633     {
1634         QByteArray catalog;
1635         QPdf::ByteStream s(&catalog);
1636         s << "<<\n"
1637           << "/Type /Catalog\n"
1638           << "/Pages " << pageRoot << "0 R\n";
1639 
1640         // Embedded files, if any
1641         if (!fileCache.isEmpty())
1642             s << "/Names " << embeddedfilesRoot << "0 R\n";
1643 
1644         if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty())
1645             s << "/Metadata " << metaDataObj << "0 R\n";
1646 
1647         if (pdfVersion == QPdfEngine::Version_A1b)
1648             s << "/OutputIntents [" << outputIntentObj << "0 R]\n";
1649 
1650         s << ">>\n"
1651           << "endobj\n";
1652 
1653         write(catalog);
1654     }
1655 
1656     if (!fileCache.isEmpty()) {
1657         addXrefEntry(embeddedfilesRoot);
1658         xprintf("<</EmbeddedFiles %d 0 R>>\n"
1659                 "endobj\n", namesRoot);
1660     }
1661 
1662     // graphics state
1663     graphicsState = addXrefEntry(-1);
1664     xprintf("<<\n"
1665             "/Type /ExtGState\n"
1666             "/SA true\n"
1667             "/SM 0.02\n"
1668             "/ca 1.0\n"
1669             "/CA 1.0\n"
1670             "/AIS false\n"
1671             "/SMask /None"
1672             ">>\n"
1673             "endobj\n");
1674 
1675     // color space for pattern
1676     patternColorSpace = addXrefEntry(-1);
1677     xprintf("[/Pattern /DeviceRGB]\n"
1678             "endobj\n");
1679 }
1680 
writeInfo()1681 void QPdfEnginePrivate::writeInfo()
1682 {
1683     info = addXrefEntry(-1);
1684     xprintf("<<\n/Title ");
1685     printString(title);
1686     xprintf("\n/Creator ");
1687     printString(creator);
1688     xprintf("\n/Producer ");
1689     printString(QString::fromLatin1("Qt " QT_VERSION_STR));
1690     QDateTime now = QDateTime::currentDateTime();
1691     QTime t = now.time();
1692     QDate d = now.date();
1693     xprintf("\n/CreationDate (D:%d%02d%02d%02d%02d%02d",
1694             d.year(),
1695             d.month(),
1696             d.day(),
1697             t.hour(),
1698             t.minute(),
1699             t.second());
1700     int offset = now.offsetFromUtc();
1701     int hours  = (offset / 60) / 60;
1702     int mins   = (offset / 60) % 60;
1703     if (offset < 0)
1704         xprintf("-%02d'%02d')\n", -hours, -mins);
1705     else if (offset > 0)
1706         xprintf("+%02d'%02d')\n", hours , mins);
1707     else
1708         xprintf("Z)\n");
1709     xprintf(">>\n"
1710             "endobj\n");
1711 }
1712 
writeXmpDcumentMetaData()1713 int QPdfEnginePrivate::writeXmpDcumentMetaData()
1714 {
1715     const int metaDataObj = addXrefEntry(-1);
1716     QByteArray metaDataContent;
1717 
1718     if (xmpDocumentMetadata.isEmpty()) {
1719         const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR));
1720 
1721         const QDateTime now = QDateTime::currentDateTime();
1722         const QDate date = now.date();
1723         const QTime time = now.time();
1724         const QString timeStr =
1725                 QString::asprintf("%d-%02d-%02dT%02d:%02d:%02d",
1726                                   date.year(), date.month(), date.day(),
1727                                   time.hour(), time.minute(), time.second());
1728 
1729         const int offset = now.offsetFromUtc();
1730         const int hours  = (offset / 60) / 60;
1731         const int mins   = (offset / 60) % 60;
1732         QString tzStr;
1733         if (offset < 0)
1734             tzStr = QString::asprintf("-%02d:%02d", -hours, -mins);
1735         else if (offset > 0)
1736             tzStr = QString::asprintf("+%02d:%02d", hours , mins);
1737         else
1738             tzStr = QLatin1String("Z");
1739 
1740         const QString metaDataDate = timeStr + tzStr;
1741 
1742         QFile metaDataFile(QLatin1String(":/qpdf/qpdfa_metadata.xml"));
1743         metaDataFile.open(QIODevice::ReadOnly);
1744         metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(),
1745                                                                         title.toHtmlEscaped(),
1746                                                                         creator.toHtmlEscaped(),
1747                                                                         metaDataDate).toUtf8();
1748     }
1749     else
1750         metaDataContent = xmpDocumentMetadata;
1751 
1752     xprintf("<<\n"
1753             "/Type /Metadata /Subtype /XML\n"
1754             "/Length %d\n"
1755             ">>\n"
1756             "stream\n", metaDataContent.size());
1757     write(metaDataContent);
1758     xprintf("\nendstream\n"
1759             "endobj\n");
1760 
1761     return metaDataObj;
1762 }
1763 
writeOutputIntent()1764 int QPdfEnginePrivate::writeOutputIntent()
1765 {
1766     const int colorProfile = addXrefEntry(-1);
1767     {
1768         QFile colorProfileFile(QLatin1String(":/qpdf/sRGB2014.icc"));
1769         colorProfileFile.open(QIODevice::ReadOnly);
1770         const QByteArray colorProfileData = colorProfileFile.readAll();
1771 
1772         QByteArray data;
1773         QPdf::ByteStream s(&data);
1774         int length_object = requestObject();
1775 
1776         s << "<<\n";
1777         s << "/N 3\n";
1778         s << "/Alternate /DeviceRGB\n";
1779         s << "/Length " << length_object << "0 R\n";
1780         s << "/Filter /FlateDecode\n";
1781         s << ">>\n";
1782         s << "stream\n";
1783         write(data);
1784         const int len = writeCompressed(colorProfileData);
1785         write("\nendstream\n"
1786               "endobj\n");
1787         addXrefEntry(length_object);
1788         xprintf("%d\n"
1789                 "endobj\n", len);
1790     }
1791 
1792     const int outputIntent = addXrefEntry(-1);
1793     {
1794         xprintf("<<\n");
1795         xprintf("/Type /OutputIntent\n");
1796         xprintf("/S/GTS_PDFA1\n");
1797         xprintf("/OutputConditionIdentifier (sRGB_IEC61966-2-1_black_scaled)\n");
1798         xprintf("/DestOutputProfile %d 0 R\n", colorProfile);
1799         xprintf("/Info(sRGB IEC61966 v2.1 with black scaling)\n");
1800         xprintf("/RegistryName(http://www.color.org)\n");
1801         xprintf(">>\n");
1802         xprintf("endobj\n");
1803     }
1804 
1805     return outputIntent;
1806 }
1807 
writePageRoot()1808 void QPdfEnginePrivate::writePageRoot()
1809 {
1810     addXrefEntry(pageRoot);
1811 
1812     xprintf("<<\n"
1813             "/Type /Pages\n"
1814             "/Kids \n"
1815             "[\n");
1816     int size = pages.size();
1817     for (int i = 0; i < size; ++i)
1818         xprintf("%d 0 R\n", pages[i]);
1819     xprintf("]\n");
1820 
1821     //xprintf("/Group <</S /Transparency /I true /K false>>\n");
1822     xprintf("/Count %d\n", pages.size());
1823 
1824     xprintf("/ProcSet [/PDF /Text /ImageB /ImageC]\n"
1825             ">>\n"
1826             "endobj\n");
1827 }
1828 
writeAttachmentRoot()1829 void QPdfEnginePrivate::writeAttachmentRoot()
1830 {
1831     if (fileCache.isEmpty())
1832         return;
1833 
1834     QVector<int> attachments;
1835     const int size = fileCache.size();
1836     for (int i = 0; i < size; ++i) {
1837         auto attachment = fileCache.at(i);
1838         const int attachmentID = addXrefEntry(-1);
1839         xprintf("<<\n");
1840         if (do_compress)
1841             xprintf("/Filter /FlateDecode\n");
1842 
1843         const int lenobj = requestObject();
1844         xprintf("/Length %d 0 R\n", lenobj);
1845         int len = 0;
1846         xprintf(">>\nstream\n");
1847         len = writeCompressed(attachment.data);
1848         xprintf("\nendstream\n"
1849                 "endobj\n");
1850         addXrefEntry(lenobj);
1851         xprintf("%d\n"
1852                 "endobj\n", len);
1853 
1854         attachments.push_back(addXrefEntry(-1));
1855         xprintf("<<\n"
1856                 "/F (%s)", attachment.fileName.toLatin1().constData());
1857 
1858         xprintf("\n/EF <</F %d 0 R>>\n"
1859                 "/Type/Filespec\n"
1860                  , attachmentID);
1861         if (!attachment.mimeType.isEmpty())
1862             xprintf("/Subtype/%s\n",
1863                     attachment.mimeType.replace(QLatin1String("/"),
1864                                                 QLatin1String("#2F")).toLatin1().constData());
1865         xprintf(">>\nendobj\n");
1866     }
1867 
1868     // names
1869     addXrefEntry(namesRoot);
1870     xprintf("<</Names[");
1871     for (int i = 0; i < size; ++i) {
1872         auto attachment = fileCache.at(i);
1873         printString(attachment.fileName);
1874         xprintf("%d 0 R\n", attachments.at(i));
1875     }
1876     xprintf("]>>\n"
1877             "endobj\n");
1878 }
1879 
embedFont(QFontSubset * font)1880 void QPdfEnginePrivate::embedFont(QFontSubset *font)
1881 {
1882     //qDebug() << "embedFont" << font->object_id;
1883     int fontObject = font->object_id;
1884     QByteArray fontData = font->toTruetype();
1885 #ifdef FONT_DUMP
1886     static int i = 0;
1887     QString fileName("font%1.ttf");
1888     fileName = fileName.arg(i++);
1889     QFile ff(fileName);
1890     ff.open(QFile::WriteOnly);
1891     ff.write(fontData);
1892     ff.close();
1893 #endif
1894 
1895     int fontDescriptor = requestObject();
1896     int fontstream = requestObject();
1897     int cidfont = requestObject();
1898     int toUnicode = requestObject();
1899     int cidset = requestObject();
1900 
1901     QFontEngine::Properties properties = font->fontEngine->properties();
1902     QByteArray postscriptName = properties.postscriptName.replace(' ', '_');
1903 
1904     {
1905         qreal scale = 1000/properties.emSquare.toReal();
1906         addXrefEntry(fontDescriptor);
1907         QByteArray descriptor;
1908         QPdf::ByteStream s(&descriptor);
1909         s << "<< /Type /FontDescriptor\n"
1910             "/FontName /Q";
1911         int tag = fontDescriptor;
1912         for (int i = 0; i < 5; ++i) {
1913             s << (char)('A' + (tag % 26));
1914             tag /= 26;
1915         }
1916         s <<  '+' << postscriptName << "\n"
1917             "/Flags " << 4 << "\n"
1918             "/FontBBox ["
1919           << properties.boundingBox.x()*scale
1920           << -(properties.boundingBox.y() + properties.boundingBox.height())*scale
1921           << (properties.boundingBox.x() + properties.boundingBox.width())*scale
1922           << -properties.boundingBox.y()*scale  << "]\n"
1923             "/ItalicAngle " << properties.italicAngle.toReal() << "\n"
1924             "/Ascent " << properties.ascent.toReal()*scale << "\n"
1925             "/Descent " << -properties.descent.toReal()*scale << "\n"
1926             "/CapHeight " << properties.capHeight.toReal()*scale << "\n"
1927             "/StemV " << properties.lineWidth.toReal()*scale << "\n"
1928             "/FontFile2 " << fontstream << "0 R\n"
1929             "/CIDSet " << cidset << "0 R\n"
1930             ">>\nendobj\n";
1931         write(descriptor);
1932     }
1933     {
1934         addXrefEntry(fontstream);
1935         QByteArray header;
1936         QPdf::ByteStream s(&header);
1937 
1938         int length_object = requestObject();
1939         s << "<<\n"
1940             "/Length1 " << fontData.size() << "\n"
1941             "/Length " << length_object << "0 R\n";
1942         if (do_compress)
1943             s << "/Filter /FlateDecode\n";
1944         s << ">>\n"
1945             "stream\n";
1946         write(header);
1947         int len = writeCompressed(fontData);
1948         write("\nendstream\n"
1949               "endobj\n");
1950         addXrefEntry(length_object);
1951         xprintf("%d\n"
1952                 "endobj\n", len);
1953     }
1954     {
1955         addXrefEntry(cidfont);
1956         QByteArray cid;
1957         QPdf::ByteStream s(&cid);
1958         s << "<< /Type /Font\n"
1959             "/Subtype /CIDFontType2\n"
1960             "/BaseFont /" << postscriptName << "\n"
1961             "/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>\n"
1962             "/FontDescriptor " << fontDescriptor << "0 R\n"
1963             "/CIDToGIDMap /Identity\n"
1964           << font->widthArray() <<
1965             ">>\n"
1966             "endobj\n";
1967         write(cid);
1968     }
1969     {
1970         addXrefEntry(toUnicode);
1971         QByteArray touc = font->createToUnicodeMap();
1972         xprintf("<< /Length %d >>\n"
1973                 "stream\n", touc.length());
1974         write(touc);
1975         write("\nendstream\n"
1976               "endobj\n");
1977     }
1978     {
1979         addXrefEntry(fontObject);
1980         QByteArray font;
1981         QPdf::ByteStream s(&font);
1982         s << "<< /Type /Font\n"
1983             "/Subtype /Type0\n"
1984             "/BaseFont /" << postscriptName << "\n"
1985             "/Encoding /Identity-H\n"
1986             "/DescendantFonts [" << cidfont << "0 R]\n"
1987             "/ToUnicode " << toUnicode << "0 R"
1988             ">>\n"
1989             "endobj\n";
1990         write(font);
1991     }
1992     {
1993         QByteArray cidSetStream(font->nGlyphs() / 8 + 1, 0);
1994         int byteCounter = 0;
1995         int bitCounter = 0;
1996         for (int i = 0; i < font->nGlyphs(); ++i) {
1997             cidSetStream.data()[byteCounter] |= (1 << (7 - bitCounter));
1998 
1999             bitCounter++;
2000             if (bitCounter == 8) {
2001                 bitCounter = 0;
2002                 byteCounter++;
2003             }
2004         }
2005 
2006         addXrefEntry(cidset);
2007         xprintf("<<\n");
2008         xprintf("/Length %d\n", cidSetStream.size());
2009         xprintf(">>\n");
2010         xprintf("stream\n");
2011         write(cidSetStream);
2012         xprintf("\nendstream\n");
2013         xprintf("endobj\n");
2014     }
2015 }
2016 
calcUserUnit() const2017 qreal QPdfEnginePrivate::calcUserUnit() const
2018 {
2019     // PDF standards < 1.6 support max 200x200in pages (no UserUnit)
2020     if (pdfVersion < QPdfEngine::Version_1_6)
2021         return 1.0;
2022 
2023     const int maxLen = qMax(currentPage->pageSize.width(), currentPage->pageSize.height());
2024     if (maxLen <= 14400)
2025         return 1.0; // for pages up to 200x200in (14400x14400 units) use default scaling
2026 
2027     // for larger pages, rescale units so we can have up to 381x381km
2028     return qMin(maxLen / 14400.0, 75000.0);
2029 }
2030 
writeFonts()2031 void QPdfEnginePrivate::writeFonts()
2032 {
2033     for (QHash<QFontEngine::FaceId, QFontSubset *>::iterator it = fonts.begin(); it != fonts.end(); ++it) {
2034         embedFont(*it);
2035         delete *it;
2036     }
2037     fonts.clear();
2038 }
2039 
writePage()2040 void QPdfEnginePrivate::writePage()
2041 {
2042     if (pages.empty())
2043         return;
2044 
2045     *currentPage << "Q Q\n";
2046 
2047     uint pageStream = requestObject();
2048     uint pageStreamLength = requestObject();
2049     uint resources = requestObject();
2050     uint annots = requestObject();
2051 
2052     qreal userUnit = calcUserUnit();
2053 
2054     addXrefEntry(pages.constLast());
2055     xprintf("<<\n"
2056             "/Type /Page\n"
2057             "/Parent %d 0 R\n"
2058             "/Contents %d 0 R\n"
2059             "/Resources %d 0 R\n"
2060             "/Annots %d 0 R\n"
2061             "/MediaBox [0 0 %s %s]\n",
2062             pageRoot, pageStream, resources, annots,
2063             // make sure we use the pagesize from when we started the page, since the user may have changed it
2064             QByteArray::number(currentPage->pageSize.width() / userUnit, 'f').constData(),
2065             QByteArray::number(currentPage->pageSize.height() / userUnit, 'f').constData());
2066 
2067     if (pdfVersion >= QPdfEngine::Version_1_6)
2068         xprintf("/UserUnit %s\n", QByteArray::number(userUnit, 'f').constData());
2069 
2070     xprintf(">>\n"
2071             "endobj\n");
2072 
2073     addXrefEntry(resources);
2074     xprintf("<<\n"
2075             "/ColorSpace <<\n"
2076             "/PCSp %d 0 R\n"
2077             "/CSp /DeviceRGB\n"
2078             "/CSpg /DeviceGray\n"
2079             ">>\n"
2080             "/ExtGState <<\n"
2081             "/GSa %d 0 R\n",
2082             patternColorSpace, graphicsState);
2083 
2084     for (int i = 0; i < currentPage->graphicStates.size(); ++i)
2085         xprintf("/GState%d %d 0 R\n", currentPage->graphicStates.at(i), currentPage->graphicStates.at(i));
2086     xprintf(">>\n");
2087 
2088     xprintf("/Pattern <<\n");
2089     for (int i = 0; i < currentPage->patterns.size(); ++i)
2090         xprintf("/Pat%d %d 0 R\n", currentPage->patterns.at(i), currentPage->patterns.at(i));
2091     xprintf(">>\n");
2092 
2093     xprintf("/Font <<\n");
2094     for (int i = 0; i < currentPage->fonts.size();++i)
2095         xprintf("/F%d %d 0 R\n", currentPage->fonts[i], currentPage->fonts[i]);
2096     xprintf(">>\n");
2097 
2098     xprintf("/XObject <<\n");
2099     for (int i = 0; i<currentPage->images.size(); ++i) {
2100         xprintf("/Im%d %d 0 R\n", currentPage->images.at(i), currentPage->images.at(i));
2101     }
2102     xprintf(">>\n");
2103 
2104     xprintf(">>\n"
2105             "endobj\n");
2106 
2107     addXrefEntry(annots);
2108     xprintf("[ ");
2109     for (int i = 0; i<currentPage->annotations.size(); ++i) {
2110         xprintf("%d 0 R ", currentPage->annotations.at(i));
2111     }
2112     xprintf("]\nendobj\n");
2113 
2114     addXrefEntry(pageStream);
2115     xprintf("<<\n"
2116             "/Length %d 0 R\n", pageStreamLength); // object number for stream length object
2117     if (do_compress)
2118         xprintf("/Filter /FlateDecode\n");
2119 
2120     xprintf(">>\n");
2121     xprintf("stream\n");
2122     QIODevice *content = currentPage->stream();
2123     int len = writeCompressed(content);
2124     xprintf("\nendstream\n"
2125             "endobj\n");
2126 
2127     addXrefEntry(pageStreamLength);
2128     xprintf("%d\nendobj\n",len);
2129 }
2130 
writeTail()2131 void QPdfEnginePrivate::writeTail()
2132 {
2133     writePage();
2134     writeFonts();
2135     writePageRoot();
2136     writeAttachmentRoot();
2137 
2138     addXrefEntry(xrefPositions.size(),false);
2139     xprintf("xref\n"
2140             "0 %d\n"
2141             "%010d 65535 f \n", xrefPositions.size()-1, xrefPositions[0]);
2142 
2143     for (int i = 1; i < xrefPositions.size()-1; ++i)
2144         xprintf("%010d 00000 n \n", xrefPositions[i]);
2145 
2146     {
2147         QByteArray trailer;
2148         QPdf::ByteStream s(&trailer);
2149 
2150         s << "trailer\n"
2151           << "<<\n"
2152           << "/Size " << xrefPositions.size() - 1 << "\n"
2153           << "/Info " << info << "0 R\n"
2154           << "/Root " << catalog << "0 R\n";
2155 
2156         if (pdfVersion == QPdfEngine::Version_A1b) {
2157             const QString uniqueId = QUuid::createUuid().toString();
2158             const QByteArray fileIdentifier = QCryptographicHash::hash(uniqueId.toLatin1(), QCryptographicHash::Md5).toHex();
2159             s << "/ID [ <" << fileIdentifier << "> <" << fileIdentifier << "> ]\n";
2160         }
2161 
2162         s << ">>\n"
2163           << "startxref\n" << xrefPositions.constLast() << "\n"
2164           << "%%EOF\n";
2165 
2166         write(trailer);
2167     }
2168 }
2169 
addXrefEntry(int object,bool printostr)2170 int QPdfEnginePrivate::addXrefEntry(int object, bool printostr)
2171 {
2172     if (object < 0)
2173         object = requestObject();
2174 
2175     if (object>=xrefPositions.size())
2176         xrefPositions.resize(object+1);
2177 
2178     xrefPositions[object] = streampos;
2179     if (printostr)
2180         xprintf("%d 0 obj\n",object);
2181 
2182     return object;
2183 }
2184 
printString(const QString & string)2185 void QPdfEnginePrivate::printString(const QString &string)
2186 {
2187     if (string.isEmpty()) {
2188         write("()");
2189         return;
2190     }
2191 
2192     // The 'text string' type in PDF is encoded either as PDFDocEncoding, or
2193     // Unicode UTF-16 with a Unicode byte order mark as the first character
2194     // (0xfeff), with the high-order byte first.
2195     QByteArray array("(\xfe\xff");
2196     const ushort *utf16 = string.utf16();
2197 
2198     for (int i=0; i < string.size(); ++i) {
2199         char part[2] = {char((*(utf16 + i)) >> 8), char((*(utf16 + i)) & 0xff)};
2200         for(int j=0; j < 2; ++j) {
2201             if (part[j] == '(' || part[j] == ')' || part[j] == '\\')
2202                 array.append('\\');
2203             array.append(part[j]);
2204         }
2205     }
2206     array.append(')');
2207     write(array);
2208 }
2209 
2210 
xprintf(const char * fmt,...)2211 void QPdfEnginePrivate::xprintf(const char* fmt, ...)
2212 {
2213     if (!stream)
2214         return;
2215 
2216     const int msize = 10000;
2217     char buf[msize];
2218 
2219     va_list args;
2220     va_start(args, fmt);
2221     int bufsize = qvsnprintf(buf, msize, fmt, args);
2222     va_end(args);
2223 
2224     if (Q_LIKELY(bufsize < msize)) {
2225         stream->writeRawData(buf, bufsize);
2226     } else {
2227         // Fallback for abnormal cases
2228         QScopedArrayPointer<char> tmpbuf(new char[bufsize + 1]);
2229         va_start(args, fmt);
2230         bufsize = qvsnprintf(tmpbuf.data(), bufsize + 1, fmt, args);
2231         va_end(args);
2232         stream->writeRawData(tmpbuf.data(), bufsize);
2233     }
2234     streampos += bufsize;
2235 }
2236 
writeCompressed(QIODevice * dev)2237 int QPdfEnginePrivate::writeCompressed(QIODevice *dev)
2238 {
2239 #ifndef QT_NO_COMPRESS
2240     if (do_compress) {
2241         int size = QPdfPage::chunkSize();
2242         int sum = 0;
2243         ::z_stream zStruct;
2244         zStruct.zalloc = Z_NULL;
2245         zStruct.zfree = Z_NULL;
2246         zStruct.opaque = Z_NULL;
2247         if (::deflateInit(&zStruct, Z_DEFAULT_COMPRESSION) != Z_OK) {
2248             qWarning("QPdfStream::writeCompressed: Error in deflateInit()");
2249             return sum;
2250         }
2251         zStruct.avail_in = 0;
2252         QByteArray in, out;
2253         out.resize(size);
2254         while (!dev->atEnd() || zStruct.avail_in != 0) {
2255             if (zStruct.avail_in == 0) {
2256                 in = dev->read(size);
2257                 zStruct.avail_in = in.size();
2258                 zStruct.next_in = reinterpret_cast<unsigned char*>(in.data());
2259                 if (in.size() <= 0) {
2260                     qWarning("QPdfStream::writeCompressed: Error in read()");
2261                     ::deflateEnd(&zStruct);
2262                     return sum;
2263                 }
2264             }
2265             zStruct.next_out = reinterpret_cast<unsigned char*>(out.data());
2266             zStruct.avail_out = out.size();
2267             if (::deflate(&zStruct, 0) != Z_OK) {
2268                 qWarning("QPdfStream::writeCompressed: Error in deflate()");
2269                 ::deflateEnd(&zStruct);
2270                 return sum;
2271             }
2272             int written = out.size() - zStruct.avail_out;
2273             stream->writeRawData(out.constData(), written);
2274             streampos += written;
2275             sum += written;
2276         }
2277         int ret;
2278         do {
2279             zStruct.next_out = reinterpret_cast<unsigned char*>(out.data());
2280             zStruct.avail_out = out.size();
2281             ret = ::deflate(&zStruct, Z_FINISH);
2282             if (ret != Z_OK && ret != Z_STREAM_END) {
2283                 qWarning("QPdfStream::writeCompressed: Error in deflate()");
2284                 ::deflateEnd(&zStruct);
2285                 return sum;
2286             }
2287             int written = out.size() - zStruct.avail_out;
2288             stream->writeRawData(out.constData(), written);
2289             streampos += written;
2290             sum += written;
2291         } while (ret == Z_OK);
2292 
2293         ::deflateEnd(&zStruct);
2294 
2295         return sum;
2296     } else
2297 #endif
2298     {
2299         QByteArray arr;
2300         int sum = 0;
2301         while (!dev->atEnd()) {
2302             arr = dev->read(QPdfPage::chunkSize());
2303             stream->writeRawData(arr.constData(), arr.size());
2304             streampos += arr.size();
2305             sum += arr.size();
2306         }
2307         return sum;
2308     }
2309 }
2310 
writeCompressed(const char * src,int len)2311 int QPdfEnginePrivate::writeCompressed(const char *src, int len)
2312 {
2313 #ifndef QT_NO_COMPRESS
2314     if(do_compress) {
2315         uLongf destLen = len + len/100 + 13; // zlib requirement
2316         Bytef* dest = new Bytef[destLen];
2317         if (Z_OK == ::compress(dest, &destLen, (const Bytef*) src, (uLongf)len)) {
2318             stream->writeRawData((const char*)dest, destLen);
2319         } else {
2320             qWarning("QPdfStream::writeCompressed: Error in compress()");
2321             destLen = 0;
2322         }
2323         delete [] dest;
2324         len = destLen;
2325     } else
2326 #endif
2327     {
2328         stream->writeRawData(src,len);
2329     }
2330     streampos += len;
2331     return len;
2332 }
2333 
writeImage(const QByteArray & data,int width,int height,int depth,int maskObject,int softMaskObject,bool dct,bool isMono)2334 int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, int depth,
2335                                   int maskObject, int softMaskObject, bool dct, bool isMono)
2336 {
2337     int image = addXrefEntry(-1);
2338     xprintf("<<\n"
2339             "/Type /XObject\n"
2340             "/Subtype /Image\n"
2341             "/Width %d\n"
2342             "/Height %d\n", width, height);
2343 
2344     if (depth == 1) {
2345         if (!isMono) {
2346             xprintf("/ImageMask true\n"
2347                     "/Decode [1 0]\n");
2348         } else {
2349             xprintf("/BitsPerComponent 1\n"
2350                     "/ColorSpace /DeviceGray\n");
2351         }
2352     } else {
2353         xprintf("/BitsPerComponent 8\n"
2354                 "/ColorSpace %s\n", (depth == 32) ? "/DeviceRGB" : "/DeviceGray");
2355     }
2356     if (maskObject > 0)
2357         xprintf("/Mask %d 0 R\n", maskObject);
2358     if (softMaskObject > 0)
2359         xprintf("/SMask %d 0 R\n", softMaskObject);
2360 
2361     int lenobj = requestObject();
2362     xprintf("/Length %d 0 R\n", lenobj);
2363     if (interpolateImages)
2364         xprintf("/Interpolate true\n");
2365     int len = 0;
2366     if (dct) {
2367         //qDebug("DCT");
2368         xprintf("/Filter /DCTDecode\n>>\nstream\n");
2369         write(data);
2370         len = data.length();
2371     } else {
2372         if (do_compress)
2373             xprintf("/Filter /FlateDecode\n>>\nstream\n");
2374         else
2375             xprintf(">>\nstream\n");
2376         len = writeCompressed(data);
2377     }
2378     xprintf("\nendstream\n"
2379             "endobj\n");
2380     addXrefEntry(lenobj);
2381     xprintf("%d\n"
2382             "endobj\n", len);
2383     return image;
2384 }
2385 
2386 struct QGradientBound {
2387     qreal start;
2388     qreal stop;
2389     int function;
2390     bool reverse;
2391 };
2392 Q_DECLARE_TYPEINFO(QGradientBound, Q_PRIMITIVE_TYPE);
2393 
createShadingFunction(const QGradient * gradient,int from,int to,bool reflect,bool alpha)2394 int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha)
2395 {
2396     QGradientStops stops = gradient->stops();
2397     if (stops.isEmpty()) {
2398         stops << QGradientStop(0, Qt::black);
2399         stops << QGradientStop(1, Qt::white);
2400     }
2401     if (stops.at(0).first > 0)
2402         stops.prepend(QGradientStop(0, stops.at(0).second));
2403     if (stops.at(stops.size() - 1).first < 1)
2404         stops.append(QGradientStop(1, stops.at(stops.size() - 1).second));
2405 
2406     QVector<int> functions;
2407     const int numStops = stops.size();
2408     functions.reserve(numStops - 1);
2409     for (int i = 0; i < numStops - 1; ++i) {
2410         int f = addXrefEntry(-1);
2411         QByteArray data;
2412         QPdf::ByteStream s(&data);
2413         s << "<<\n"
2414              "/FunctionType 2\n"
2415              "/Domain [0 1]\n"
2416              "/N 1\n";
2417         if (alpha) {
2418             s << "/C0 [" << stops.at(i).second.alphaF() << "]\n"
2419                  "/C1 [" << stops.at(i + 1).second.alphaF() << "]\n";
2420         } else {
2421             s << "/C0 [" << stops.at(i).second.redF() << stops.at(i).second.greenF() <<  stops.at(i).second.blueF() << "]\n"
2422                  "/C1 [" << stops.at(i + 1).second.redF() << stops.at(i + 1).second.greenF() <<  stops.at(i + 1).second.blueF() << "]\n";
2423         }
2424         s << ">>\n"
2425              "endobj\n";
2426         write(data);
2427         functions << f;
2428     }
2429 
2430     QVector<QGradientBound> gradientBounds;
2431     gradientBounds.reserve((to - from) * (numStops - 1));
2432 
2433     for (int step = from; step < to; ++step) {
2434         if (reflect && step % 2) {
2435             for (int i = numStops - 1; i > 0; --i) {
2436                 QGradientBound b;
2437                 b.start = step + 1 - qBound(qreal(0.), stops.at(i).first, qreal(1.));
2438                 b.stop = step + 1 - qBound(qreal(0.), stops.at(i - 1).first, qreal(1.));
2439                 b.function = functions.at(i - 1);
2440                 b.reverse = true;
2441                 gradientBounds << b;
2442             }
2443         } else {
2444             for (int i = 0; i < numStops - 1; ++i) {
2445                 QGradientBound b;
2446                 b.start = step + qBound(qreal(0.), stops.at(i).first, qreal(1.));
2447                 b.stop = step + qBound(qreal(0.), stops.at(i + 1).first, qreal(1.));
2448                 b.function = functions.at(i);
2449                 b.reverse = false;
2450                 gradientBounds << b;
2451             }
2452         }
2453     }
2454 
2455     // normalize bounds to [0..1]
2456     qreal bstart = gradientBounds.at(0).start;
2457     qreal bend = gradientBounds.at(gradientBounds.size() - 1).stop;
2458     qreal norm = 1./(bend - bstart);
2459     for (int i = 0; i < gradientBounds.size(); ++i) {
2460         gradientBounds[i].start = (gradientBounds[i].start - bstart)*norm;
2461         gradientBounds[i].stop = (gradientBounds[i].stop - bstart)*norm;
2462     }
2463 
2464     int function;
2465     if (gradientBounds.size() > 1) {
2466         function = addXrefEntry(-1);
2467         QByteArray data;
2468         QPdf::ByteStream s(&data);
2469         s << "<<\n"
2470              "/FunctionType 3\n"
2471              "/Domain [0 1]\n"
2472              "/Bounds [";
2473         for (int i = 1; i < gradientBounds.size(); ++i)
2474             s << gradientBounds.at(i).start;
2475         s << "]\n"
2476              "/Encode [";
2477         for (int i = 0; i < gradientBounds.size(); ++i)
2478             s << (gradientBounds.at(i).reverse ? "1 0 " : "0 1 ");
2479         s << "]\n"
2480              "/Functions [";
2481         for (int i = 0; i < gradientBounds.size(); ++i)
2482             s << gradientBounds.at(i).function << "0 R ";
2483         s << "]\n"
2484              ">>\n"
2485              "endobj\n";
2486         write(data);
2487     } else {
2488         function = functions.at(0);
2489     }
2490     return function;
2491 }
2492 
generateLinearGradientShader(const QLinearGradient * gradient,const QTransform & matrix,bool alpha)2493 int QPdfEnginePrivate::generateLinearGradientShader(const QLinearGradient *gradient, const QTransform &matrix, bool alpha)
2494 {
2495     QPointF start = gradient->start();
2496     QPointF stop = gradient->finalStop();
2497     QPointF offset = stop - start;
2498     Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode);
2499 
2500     int from = 0;
2501     int to = 1;
2502     bool reflect = false;
2503     switch (gradient->spread()) {
2504     case QGradient::PadSpread:
2505         break;
2506     case QGradient::ReflectSpread:
2507         reflect = true;
2508         Q_FALLTHROUGH();
2509     case QGradient::RepeatSpread: {
2510         // calculate required bounds
2511         QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2512         QTransform inv = matrix.inverted();
2513         QPointF page_rect[4] = { inv.map(pageRect.topLeft()),
2514                                  inv.map(pageRect.topRight()),
2515                                  inv.map(pageRect.bottomLeft()),
2516                                  inv.map(pageRect.bottomRight()) };
2517 
2518         qreal length = offset.x()*offset.x() + offset.y()*offset.y();
2519 
2520         // find the max and min values in offset and orth direction that are needed to cover
2521         // the whole page
2522         from = INT_MAX;
2523         to = INT_MIN;
2524         for (int i = 0; i < 4; ++i) {
2525             qreal off = ((page_rect[i].x() - start.x()) * offset.x() + (page_rect[i].y() - start.y()) * offset.y())/length;
2526             from = qMin(from, qFloor(off));
2527             to = qMax(to, qCeil(off));
2528         }
2529 
2530         stop = start + to * offset;
2531         start = start + from * offset;\
2532         break;
2533     }
2534     }
2535 
2536     int function = createShadingFunction(gradient, from, to, reflect, alpha);
2537 
2538     QByteArray shader;
2539     QPdf::ByteStream s(&shader);
2540     s << "<<\n"
2541         "/ShadingType 2\n"
2542         "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") <<
2543         "/AntiAlias true\n"
2544         "/Coords [" << start.x() << start.y() << stop.x() << stop.y() << "]\n"
2545         "/Extend [true true]\n"
2546         "/Function " << function << "0 R\n"
2547         ">>\n"
2548         "endobj\n";
2549     int shaderObject = addXrefEntry(-1);
2550     write(shader);
2551     return shaderObject;
2552 }
2553 
generateRadialGradientShader(const QRadialGradient * gradient,const QTransform & matrix,bool alpha)2554 int QPdfEnginePrivate::generateRadialGradientShader(const QRadialGradient *gradient, const QTransform &matrix, bool alpha)
2555 {
2556     QPointF p1 = gradient->center();
2557     qreal r1 = gradient->centerRadius();
2558     QPointF p0 = gradient->focalPoint();
2559     qreal r0 = gradient->focalRadius();
2560 
2561     Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode);
2562 
2563     int from = 0;
2564     int to = 1;
2565     bool reflect = false;
2566     switch (gradient->spread()) {
2567     case QGradient::PadSpread:
2568         break;
2569     case QGradient::ReflectSpread:
2570         reflect = true;
2571         Q_FALLTHROUGH();
2572     case QGradient::RepeatSpread: {
2573         Q_ASSERT(qFuzzyIsNull(r0)); // QPainter emulates if this is not 0
2574 
2575         QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2576         QTransform inv = matrix.inverted();
2577         QPointF page_rect[4] = { inv.map(pageRect.topLeft()),
2578                                  inv.map(pageRect.topRight()),
2579                                  inv.map(pageRect.bottomLeft()),
2580                                  inv.map(pageRect.bottomRight()) };
2581 
2582         // increase to until the whole page fits into it
2583         bool done = false;
2584         while (!done) {
2585             QPointF center = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y()));
2586             double radius = r0 + to*(r1 - r0);
2587             double r2 = radius*radius;
2588             done = true;
2589             for (int i = 0; i < 4; ++i) {
2590                 QPointF off = page_rect[i] - center;
2591                 if (off.x()*off.x() + off.y()*off.y() > r2) {
2592                     ++to;
2593                     done = false;
2594                     break;
2595                 }
2596             }
2597         }
2598         p1 = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y()));
2599         r1 = r0 + to*(r1 - r0);
2600         break;
2601     }
2602     }
2603 
2604     int function = createShadingFunction(gradient, from, to, reflect, alpha);
2605 
2606     QByteArray shader;
2607     QPdf::ByteStream s(&shader);
2608     s << "<<\n"
2609         "/ShadingType 3\n"
2610         "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") <<
2611         "/AntiAlias true\n"
2612         "/Domain [0 1]\n"
2613         "/Coords [" << p0.x() << p0.y() << r0 << p1.x() << p1.y() << r1 << "]\n"
2614         "/Extend [true true]\n"
2615         "/Function " << function << "0 R\n"
2616         ">>\n"
2617         "endobj\n";
2618     int shaderObject = addXrefEntry(-1);
2619     write(shader);
2620     return shaderObject;
2621 }
2622 
generateGradientShader(const QGradient * gradient,const QTransform & matrix,bool alpha)2623 int QPdfEnginePrivate::generateGradientShader(const QGradient *gradient, const QTransform &matrix, bool alpha)
2624 {
2625     switch (gradient->type()) {
2626     case QGradient::LinearGradient:
2627         return generateLinearGradientShader(static_cast<const QLinearGradient *>(gradient), matrix, alpha);
2628     case QGradient::RadialGradient:
2629         return generateRadialGradientShader(static_cast<const QRadialGradient *>(gradient), matrix, alpha);
2630     case QGradient::ConicalGradient:
2631         Q_UNIMPLEMENTED(); // ### Implement me!
2632         break;
2633     case QGradient::NoGradient:
2634         break;
2635     }
2636     return 0;
2637 }
2638 
gradientBrush(const QBrush & b,const QTransform & matrix,int * gStateObject)2639 int QPdfEnginePrivate::gradientBrush(const QBrush &b, const QTransform &matrix, int *gStateObject)
2640 {
2641     const QGradient *gradient = b.gradient();
2642 
2643     if (!gradient || gradient->coordinateMode() != QGradient::LogicalMode)
2644         return 0;
2645 
2646     QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2647 
2648     QTransform m = b.transform() * matrix;
2649     int shaderObject = generateGradientShader(gradient, m);
2650 
2651     QByteArray str;
2652     QPdf::ByteStream s(&str);
2653     s << "<<\n"
2654         "/Type /Pattern\n"
2655         "/PatternType 2\n"
2656         "/Shading " << shaderObject << "0 R\n"
2657         "/Matrix ["
2658       << m.m11()
2659       << m.m12()
2660       << m.m21()
2661       << m.m22()
2662       << m.dx()
2663       << m.dy() << "]\n";
2664     s << ">>\n"
2665         "endobj\n";
2666 
2667     int patternObj = addXrefEntry(-1);
2668     write(str);
2669     currentPage->patterns.append(patternObj);
2670 
2671     if (!b.isOpaque()) {
2672         bool ca = true;
2673         QGradientStops stops = gradient->stops();
2674         int a = stops.at(0).second.alpha();
2675         for (int i = 1; i < stops.size(); ++i) {
2676             if (stops.at(i).second.alpha() != a) {
2677                 ca = false;
2678                 break;
2679             }
2680         }
2681         if (ca) {
2682             *gStateObject = addConstantAlphaObject(stops.at(0).second.alpha());
2683         } else {
2684             int alphaShaderObject = generateGradientShader(gradient, m, true);
2685 
2686             QByteArray content;
2687             QPdf::ByteStream c(&content);
2688             c << "/Shader" << alphaShaderObject << "sh\n";
2689 
2690             QByteArray form;
2691             QPdf::ByteStream f(&form);
2692             f << "<<\n"
2693                 "/Type /XObject\n"
2694                 "/Subtype /Form\n"
2695                 "/BBox [0 0 " << pageRect.width() << pageRect.height() << "]\n"
2696                 "/Group <</S /Transparency >>\n"
2697                 "/Resources <<\n"
2698                 "/Shading << /Shader" << alphaShaderObject << alphaShaderObject << "0 R >>\n"
2699                 ">>\n";
2700 
2701             f << "/Length " << content.length() << "\n"
2702                 ">>\n"
2703                 "stream\n"
2704               << content
2705               << "\nendstream\n"
2706                 "endobj\n";
2707 
2708             int softMaskFormObject = addXrefEntry(-1);
2709             write(form);
2710             *gStateObject = addXrefEntry(-1);
2711             xprintf("<< /SMask << /S /Alpha /G %d 0 R >> >>\n"
2712                     "endobj\n", softMaskFormObject);
2713             currentPage->graphicStates.append(*gStateObject);
2714         }
2715     }
2716 
2717     return patternObj;
2718 }
2719 
addConstantAlphaObject(int brushAlpha,int penAlpha)2720 int QPdfEnginePrivate::addConstantAlphaObject(int brushAlpha, int penAlpha)
2721 {
2722     if (brushAlpha == 255 && penAlpha == 255)
2723         return 0;
2724     int object = alphaCache.value(QPair<uint, uint>(brushAlpha, penAlpha), 0);
2725     if (!object) {
2726         object = addXrefEntry(-1);
2727         QByteArray alphaDef;
2728         QPdf::ByteStream s(&alphaDef);
2729         s << "<<\n/ca " << (brushAlpha/qreal(255.)) << '\n';
2730         s << "/CA " << (penAlpha/qreal(255.)) << "\n>>";
2731         xprintf("%s\nendobj\n", alphaDef.constData());
2732         alphaCache.insert(QPair<uint, uint>(brushAlpha, penAlpha), object);
2733     }
2734     if (currentPage->graphicStates.indexOf(object) < 0)
2735         currentPage->graphicStates.append(object);
2736 
2737     return object;
2738 }
2739 
2740 
addBrushPattern(const QTransform & m,bool * specifyColor,int * gStateObject)2741 int QPdfEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor, int *gStateObject)
2742 {
2743     Q_Q(QPdfEngine);
2744 
2745     int paintType = 2; // Uncolored tiling
2746     int w = 8;
2747     int h = 8;
2748 
2749     *specifyColor = true;
2750     *gStateObject = 0;
2751 
2752     QTransform matrix = m;
2753     matrix.translate(brushOrigin.x(), brushOrigin.y());
2754     matrix = matrix * pageMatrix();
2755     //qDebug() << brushOrigin << matrix;
2756 
2757     Qt::BrushStyle style = brush.style();
2758     if (style == Qt::LinearGradientPattern || style == Qt::RadialGradientPattern) {// && style <= Qt::ConicalGradientPattern) {
2759         *specifyColor = false;
2760         return gradientBrush(brush, matrix, gStateObject);
2761     }
2762 
2763     matrix = brush.transform() * matrix;
2764 
2765     if ((!brush.isOpaque() && brush.style() < Qt::LinearGradientPattern) || opacity != 1.0)
2766         *gStateObject = addConstantAlphaObject(qRound(brush.color().alpha() * opacity),
2767                                                qRound(pen.color().alpha() * opacity));
2768 
2769     int imageObject = -1;
2770     QByteArray pattern = QPdf::patternForBrush(brush);
2771     if (pattern.isEmpty()) {
2772         if (brush.style() != Qt::TexturePattern)
2773             return 0;
2774         QImage image = brush.textureImage();
2775         bool bitmap = true;
2776         const bool lossless = q->painter()->testRenderHint(QPainter::LosslessImageRendering);
2777         imageObject = addImage(image, &bitmap, lossless, image.cacheKey());
2778         if (imageObject != -1) {
2779             QImage::Format f = image.format();
2780             if (f != QImage::Format_MonoLSB && f != QImage::Format_Mono) {
2781                 paintType = 1; // Colored tiling
2782                 *specifyColor = false;
2783             }
2784             w = image.width();
2785             h = image.height();
2786             QTransform m(w, 0, 0, -h, 0, h);
2787             QPdf::ByteStream s(&pattern);
2788             s << QPdf::generateMatrix(m);
2789             s << "/Im" << imageObject << " Do\n";
2790         }
2791     }
2792 
2793     QByteArray str;
2794     QPdf::ByteStream s(&str);
2795     s << "<<\n"
2796         "/Type /Pattern\n"
2797         "/PatternType 1\n"
2798         "/PaintType " << paintType << "\n"
2799         "/TilingType 1\n"
2800         "/BBox [0 0 " << w << h << "]\n"
2801         "/XStep " << w << "\n"
2802         "/YStep " << h << "\n"
2803         "/Matrix ["
2804       << matrix.m11()
2805       << matrix.m12()
2806       << matrix.m21()
2807       << matrix.m22()
2808       << matrix.dx()
2809       << matrix.dy() << "]\n"
2810         "/Resources \n<< "; // open resource tree
2811     if (imageObject > 0) {
2812         s << "/XObject << /Im" << imageObject << ' ' << imageObject << "0 R >> ";
2813     }
2814     s << ">>\n"
2815         "/Length " << pattern.length() << "\n"
2816         ">>\n"
2817         "stream\n"
2818       << pattern
2819       << "\nendstream\n"
2820         "endobj\n";
2821 
2822     int patternObj = addXrefEntry(-1);
2823     write(str);
2824     currentPage->patterns.append(patternObj);
2825     return patternObj;
2826 }
2827 
is_monochrome(const QVector<QRgb> & colorTable)2828 static inline bool is_monochrome(const QVector<QRgb> &colorTable)
2829 {
2830     return colorTable.size() == 2
2831         && colorTable.at(0) == QColor(Qt::black).rgba()
2832         && colorTable.at(1) == QColor(Qt::white).rgba()
2833         ;
2834 }
2835 
2836 /*!
2837  * Adds an image to the pdf and return the pdf-object id. Returns -1 if adding the image failed.
2838  */
addImage(const QImage & img,bool * bitmap,bool lossless,qint64 serial_no)2839 int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless, qint64 serial_no)
2840 {
2841     if (img.isNull())
2842         return -1;
2843 
2844     int object = imageCache.value(serial_no);
2845     if(object)
2846         return object;
2847 
2848     QImage image = img;
2849     QImage::Format format = image.format();
2850 
2851     if (pdfVersion == QPdfEngine::Version_A1b) {
2852         if (image.hasAlphaChannel()) {
2853             // transparent images are not allowed in PDF/A-1b, so we convert it to
2854             // a format without alpha channel first
2855 
2856             QImage alphaLessImage(image.width(), image.height(), QImage::Format_RGB32);
2857             alphaLessImage.fill(Qt::white);
2858 
2859             QPainter p(&alphaLessImage);
2860             p.drawImage(0, 0, image);
2861 
2862             image = alphaLessImage;
2863             format = image.format();
2864         }
2865     }
2866 
2867     if (image.depth() == 1 && *bitmap && is_monochrome(img.colorTable())) {
2868         if (format == QImage::Format_MonoLSB)
2869             image = image.convertToFormat(QImage::Format_Mono);
2870         format = QImage::Format_Mono;
2871     } else {
2872         *bitmap = false;
2873         if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) {
2874             image = image.convertToFormat(QImage::Format_ARGB32);
2875             format = QImage::Format_ARGB32;
2876         }
2877     }
2878 
2879     int w = image.width();
2880     int h = image.height();
2881     int d = image.depth();
2882 
2883     if (format == QImage::Format_Mono) {
2884         int bytesPerLine = (w + 7) >> 3;
2885         QByteArray data;
2886         data.resize(bytesPerLine * h);
2887         char *rawdata = data.data();
2888         for (int y = 0; y < h; ++y) {
2889             memcpy(rawdata, image.constScanLine(y), bytesPerLine);
2890             rawdata += bytesPerLine;
2891         }
2892         object = writeImage(data, w, h, d, 0, 0, false, is_monochrome(img.colorTable()));
2893     } else {
2894         QByteArray softMaskData;
2895         bool dct = false;
2896         QByteArray imageData;
2897         bool hasAlpha = false;
2898         bool hasMask = false;
2899 
2900         if (QImageWriter::supportedImageFormats().contains("jpeg") && !grayscale && !lossless) {
2901             QBuffer buffer(&imageData);
2902             QImageWriter writer(&buffer, "jpeg");
2903             writer.setQuality(94);
2904             writer.write(image);
2905             dct = true;
2906 
2907             if (format != QImage::Format_RGB32) {
2908                 softMaskData.resize(w * h);
2909                 uchar *sdata = (uchar *)softMaskData.data();
2910                 for (int y = 0; y < h; ++y) {
2911                     const QRgb *rgb = (const QRgb *)image.constScanLine(y);
2912                     for (int x = 0; x < w; ++x) {
2913                         uchar alpha = qAlpha(*rgb);
2914                         *sdata++ = alpha;
2915                         hasMask |= (alpha < 255);
2916                         hasAlpha |= (alpha != 0 && alpha != 255);
2917                         ++rgb;
2918                     }
2919                 }
2920             }
2921         } else {
2922             imageData.resize(grayscale ? w * h : 3 * w * h);
2923             uchar *data = (uchar *)imageData.data();
2924             softMaskData.resize(w * h);
2925             uchar *sdata = (uchar *)softMaskData.data();
2926             for (int y = 0; y < h; ++y) {
2927                 const QRgb *rgb = (const QRgb *)image.constScanLine(y);
2928                 if (grayscale) {
2929                     for (int x = 0; x < w; ++x) {
2930                         *(data++) = qGray(*rgb);
2931                         uchar alpha = qAlpha(*rgb);
2932                         *sdata++ = alpha;
2933                         hasMask |= (alpha < 255);
2934                         hasAlpha |= (alpha != 0 && alpha != 255);
2935                         ++rgb;
2936                     }
2937                 } else {
2938                     for (int x = 0; x < w; ++x) {
2939                         *(data++) = qRed(*rgb);
2940                         *(data++) = qGreen(*rgb);
2941                         *(data++) = qBlue(*rgb);
2942                         uchar alpha = qAlpha(*rgb);
2943                         *sdata++ = alpha;
2944                         hasMask |= (alpha < 255);
2945                         hasAlpha |= (alpha != 0 && alpha != 255);
2946                         ++rgb;
2947                     }
2948                 }
2949             }
2950             if (format == QImage::Format_RGB32)
2951                 hasAlpha = hasMask = false;
2952         }
2953         int maskObject = 0;
2954         int softMaskObject = 0;
2955         if (hasAlpha) {
2956             softMaskObject = writeImage(softMaskData, w, h, 8, 0, 0);
2957         } else if (hasMask) {
2958             // dither the soft mask to 1bit and add it. This also helps PDF viewers
2959             // without transparency support
2960             int bytesPerLine = (w + 7) >> 3;
2961             QByteArray mask(bytesPerLine * h, 0);
2962             uchar *mdata = (uchar *)mask.data();
2963             const uchar *sdata = (const uchar *)softMaskData.constData();
2964             for (int y = 0; y < h; ++y) {
2965                 for (int x = 0; x < w; ++x) {
2966                     if (*sdata)
2967                         mdata[x>>3] |= (0x80 >> (x&7));
2968                     ++sdata;
2969                 }
2970                 mdata += bytesPerLine;
2971             }
2972             maskObject = writeImage(mask, w, h, 1, 0, 0);
2973         }
2974         object = writeImage(imageData, w, h, grayscale ? 8 : 32,
2975                             maskObject, softMaskObject, dct);
2976     }
2977     imageCache.insert(serial_no, object);
2978     return object;
2979 }
2980 
drawTextItem(const QPointF & p,const QTextItemInt & ti)2981 void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti)
2982 {
2983     Q_Q(QPdfEngine);
2984 
2985     if (ti.charFormat.isAnchor()) {
2986         qreal size = ti.fontEngine->fontDef.pixelSize;
2987         int synthesized = ti.fontEngine->synthesized();
2988         qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
2989         Q_ASSERT(stretch > qreal(0));
2990 
2991         QTransform trans;
2992         // Build text rendering matrix (Trm). We need it to map the text area to user
2993         // space units on the PDF page.
2994         trans = QTransform(size*stretch, 0, 0, size, 0, 0);
2995         // Apply text matrix (Tm).
2996         trans *= QTransform(1,0,0,-1,p.x(),p.y());
2997         // Apply page displacement (Identity for first page).
2998         trans *= stroker.matrix;
2999         // Apply Current Transformation Matrix (CTM)
3000         trans *= pageMatrix();
3001         qreal x1, y1, x2, y2;
3002         trans.map(0, 0, &x1, &y1);
3003         trans.map(ti.width.toReal()/size, (ti.ascent.toReal()-ti.descent.toReal())/size, &x2, &y2);
3004 
3005         uint annot = addXrefEntry(-1);
3006         QByteArray x1s, y1s, x2s, y2s;
3007         x1s.setNum(static_cast<double>(x1), 'f');
3008         y1s.setNum(static_cast<double>(y1), 'f');
3009         x2s.setNum(static_cast<double>(x2), 'f');
3010         y2s.setNum(static_cast<double>(y2), 'f');
3011         QByteArray rectData = x1s + ' ' + y1s + ' ' + x2s + ' ' + y2s;
3012         xprintf("<<\n/Type /Annot\n/Subtype /Link\n");
3013 
3014         if (pdfVersion == QPdfEngine::Version_A1b)
3015             xprintf("/F 4\n"); // enable print flag, disable all other
3016 
3017         xprintf("/Rect [");
3018         xprintf(rectData.constData());
3019 #ifdef Q_DEBUG_PDF_LINKS
3020         xprintf("]\n/Border [16 16 1]\n/A <<\n");
3021 #else
3022         xprintf("]\n/Border [0 0 0]\n/A <<\n");
3023 #endif
3024         xprintf("/Type /Action\n/S /URI\n/URI (%s)\n",
3025                 ti.charFormat.anchorHref().toLatin1().constData());
3026         xprintf(">>\n>>\n");
3027         xprintf("endobj\n");
3028 
3029         if (!currentPage->annotations.contains(annot)) {
3030             currentPage->annotations.append(annot);
3031         }
3032     }
3033 
3034     QFontEngine *fe = ti.fontEngine;
3035 
3036     QFontEngine::FaceId face_id = fe->faceId();
3037     bool noEmbed = false;
3038     if (!embedFonts
3039         || face_id.filename.isEmpty()
3040         || fe->fsType & 0x200 /* bitmap embedding only */
3041         || fe->fsType == 2 /* no embedding allowed */) {
3042         *currentPage << "Q\n";
3043         q->QPaintEngine::drawTextItem(p, ti);
3044         *currentPage << "q\n";
3045         if (face_id.filename.isEmpty())
3046             return;
3047         noEmbed = true;
3048     }
3049 
3050     QFontSubset *font = fonts.value(face_id, 0);
3051     if (!font) {
3052         font = new QFontSubset(fe, requestObject());
3053         font->noEmbed = noEmbed;
3054     }
3055     fonts.insert(face_id, font);
3056 
3057     if (!currentPage->fonts.contains(font->object_id))
3058         currentPage->fonts.append(font->object_id);
3059 
3060     qreal size = ti.fontEngine->fontDef.pixelSize;
3061 
3062     QVarLengthArray<glyph_t> glyphs;
3063     QVarLengthArray<QFixedPoint> positions;
3064     QTransform m = QTransform::fromTranslate(p.x(), p.y());
3065     ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags,
3066                                      glyphs, positions);
3067     if (glyphs.size() == 0)
3068         return;
3069     int synthesized = ti.fontEngine->synthesized();
3070     qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
3071     Q_ASSERT(stretch > qreal(0));
3072 
3073     *currentPage << "BT\n"
3074                  << "/F" << font->object_id << size << "Tf "
3075                  << stretch << (synthesized & QFontEngine::SynthesizedItalic
3076                                 ? "0 .3 -1 0 0 Tm\n"
3077                                 : "0 0 -1 0 0 Tm\n");
3078 
3079 
3080 #if 0
3081     // #### implement actual text for complex languages
3082     const unsigned short *logClusters = ti.logClusters;
3083     int pos = 0;
3084     do {
3085         int end = pos + 1;
3086         while (end < ti.num_chars && logClusters[end] == logClusters[pos])
3087             ++end;
3088         *currentPage << "/Span << /ActualText <FEFF";
3089         for (int i = pos; i < end; ++i) {
3090             s << toHex((ushort)ti.chars[i].unicode(), buf);
3091         }
3092         *currentPage << "> >>\n"
3093             "BDC\n"
3094             "<";
3095         int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end];
3096         for (int gs = logClusters[pos]; gs < ge; ++gs)
3097             *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf);
3098         *currentPage << "> Tj\n"
3099             "EMC\n";
3100         pos = end;
3101     } while (pos < ti.num_chars);
3102 #else
3103     qreal last_x = 0.;
3104     qreal last_y = 0.;
3105     for (int i = 0; i < glyphs.size(); ++i) {
3106         qreal x = positions[i].x.toReal();
3107         qreal y = positions[i].y.toReal();
3108         if (synthesized & QFontEngine::SynthesizedItalic)
3109             x += .3*y;
3110         x /= stretch;
3111         char buf[5];
3112         int g = font->addGlyph(glyphs[i]);
3113         *currentPage << x - last_x << last_y - y << "Td <"
3114                      << QPdf::toHex((ushort)g, buf) << "> Tj\n";
3115         last_x = x;
3116         last_y = y;
3117     }
3118     if (synthesized & QFontEngine::SynthesizedBold) {
3119         *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic
3120                             ? "0 .3 -1 0 0 Tm\n"
3121                             : "0 0 -1 0 0 Tm\n");
3122         *currentPage << "/Span << /ActualText <> >> BDC\n";
3123         last_x = 0.5*fe->lineThickness().toReal();
3124         last_y = 0.;
3125         for (int i = 0; i < glyphs.size(); ++i) {
3126             qreal x = positions[i].x.toReal();
3127             qreal y = positions[i].y.toReal();
3128             if (synthesized & QFontEngine::SynthesizedItalic)
3129                 x += .3*y;
3130             x /= stretch;
3131             char buf[5];
3132             int g = font->addGlyph(glyphs[i]);
3133             *currentPage << x - last_x << last_y - y << "Td <"
3134                         << QPdf::toHex((ushort)g, buf) << "> Tj\n";
3135             last_x = x;
3136             last_y = y;
3137         }
3138         *currentPage << "EMC\n";
3139     }
3140 #endif
3141 
3142     *currentPage << "ET\n";
3143 }
3144 
pageMatrix() const3145 QTransform QPdfEnginePrivate::pageMatrix() const
3146 {
3147     qreal userUnit = calcUserUnit();
3148     qreal scale = 72. / userUnit / resolution;
3149     QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, m_pageLayout.fullRectPoints().height() / userUnit);
3150     if (m_pageLayout.mode() != QPageLayout::FullPageMode) {
3151         QRect r = m_pageLayout.paintRectPixels(resolution);
3152         tmp.translate(r.left(), r.top());
3153     }
3154     return tmp;
3155 }
3156 
newPage()3157 void QPdfEnginePrivate::newPage()
3158 {
3159     if (currentPage && currentPage->pageSize.isEmpty())
3160         currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3161     writePage();
3162 
3163     delete currentPage;
3164     currentPage = new QPdfPage;
3165     currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3166     stroker.stream = currentPage;
3167     pages.append(requestObject());
3168 
3169     *currentPage << "/GSa gs /CSp cs /CSp CS\n"
3170                  << QPdf::generateMatrix(pageMatrix())
3171                  << "q q\n";
3172 }
3173 
3174 QT_END_NAMESPACE
3175 
3176 #endif // QT_NO_PDF
3177