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
65static const bool do_compress = false;
66#else
67static 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.
72static const bool interpolateImages = false;
73
74static void initResources()
75{
76 Q_INIT_RESOURCE(qpdf);
77}
78
79QT_BEGIN_NAMESPACE
80
81inline 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
91extern bool qt_isExtendedRadialGradient(const QBrush &brush);
92
93// helper function to remove transparency from brush in PDF/A-1b mode
94static 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 */
133const char *qt_real_to_string(qreal val, char *buf) {
134 const char *ret = buf;
135
136 if (qIsNaN(d: 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
185const 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
210namespace QPdf {
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(mode: QIODevice::ReadWrite | QIODevice::Append);
218 }
219
220 ByteStream::ByteStream(bool fileBacking)
221 : dev(new QBuffer(&ba)),
222 fileBackingEnabled(fileBacking),
223 fileBackingActive(false),
224 handleDirty(false)
225 {
226 dev->open(mode: QIODevice::ReadWrite);
227 }
228
229 ByteStream::~ByteStream()
230 {
231 delete dev;
232 }
233
234 ByteStream &ByteStream::operator <<(char chr)
235 {
236 if (handleDirty) prepareBuffer();
237 dev->write(data: &chr, len: 1);
238 return *this;
239 }
240
241 ByteStream &ByteStream::operator <<(const char *str)
242 {
243 if (handleDirty) prepareBuffer();
244 dev->write(data: str, len: strlen(s: str));
245 return *this;
246 }
247
248 ByteStream &ByteStream::operator <<(const QByteArray &str)
249 {
250 if (handleDirty) prepareBuffer();
251 dev->write(data: str);
252 return *this;
253 }
254
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(maxlen: chunkSize());
266 dev->write(data: buf);
267 }
268 s.dev->seek(pos);
269 return *this;
270 }
271
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
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
286 ByteStream &ByteStream::operator <<(const QPointF &p) {
287 char buf[256];
288 qt_real_to_string(val: p.x(), buf);
289 *this << buf;
290 qt_real_to_string(val: p.y(), buf);
291 *this << buf;
292 return *this;
293 }
294
295 QIODevice *ByteStream::stream()
296 {
297 dev->reset();
298 handleDirty = true;
299 return dev;
300 }
301
302 void ByteStream::clear()
303 {
304 dev->open(mode: QIODevice::ReadWrite | QIODevice::Truncate);
305 }
306
307 void ByteStream::constructor_helper(QByteArray *ba)
308 {
309 delete dev;
310 dev = new QBuffer(ba);
311 dev->open(mode: QIODevice::ReadWrite);
312 }
313
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(maxlen: chunkSize());
326 newFile->write(data: buf);
327 }
328 delete dev;
329 dev = newFile;
330 ba.clear();
331 fileBackingActive = true;
332 }
333 if (dev->pos() != size) {
334 dev->seek(pos: size);
335 handleDirty = false;
336 }
337 }
338}
339
340#define QT_PATH_ELEMENT(elm)
341
342QByteArray 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(i: start).x == path.elementAt(i: i-1).x
357 && path.elementAt(i: start).y == path.elementAt(i: i-1).y)
358 s << "h\n";
359 s << matrix.map(p: QPointF(elm.x, elm.y)) << "m\n";
360 start = i;
361 break;
362 case QPainterPath::LineToElement:
363 s << matrix.map(p: 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(p: QPointF(elm.x, elm.y))
369 << matrix.map(p: QPointF(path.elementAt(i: i+1).x, path.elementAt(i: i+1).y))
370 << matrix.map(p: QPointF(path.elementAt(i: i+2).x, path.elementAt(i: i+2).y))
371 << "c\n";
372 i += 2;
373 break;
374 default:
375 qFatal(msg: "QPdf::generatePath(), unhandled type: %d", elm.type);
376 }
377 }
378 if (start >= 0
379 && path.elementAt(i: start).x == path.elementAt(i: path.elementCount()-1).x
380 && path.elementAt(i: start).y == path.elementAt(i: 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
404QByteArray 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
418QByteArray 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
441static 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
594QByteArray 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
603static 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, tx: &x, ty: &y);
610 *t->stream << x << y << "m\n";
611 t->first = false;
612}
613
614static 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, tx: &x, ty: &y);
619 *t->stream << x << y << "l\n";
620}
621
622static 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(x: c1x, y: c1y, tx: &c1x, ty: &c1y);
630 t->matrix.map(x: c2x, y: c2y, tx: &c2x, ty: &c2y);
631 t->matrix.map(x: ex, y: ey, tx: &ex, ty: &ey);
632 }
633 *t->stream << c1x << c1y
634 << c2x << c2y
635 << ex << ey
636 << "c\n";
637}
638
639QPdf::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
652void 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
683void QPdf::Stroker::strokePath(const QPainterPath &path)
684{
685 if (!stroker)
686 return;
687 first = true;
688
689 stroker->strokePath(path, data: this, matrix: cosmeticPen ? matrix : QTransform());
690 *stream << "h f\n";
691}
692
693QByteArray QPdf::ascii85Encode(const QByteArray &input)
694{
695 int isize = input.size()/4*4;
696 QByteArray output;
697 output.resize(size: 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(size: out-output.data());
746 return output;
747}
748
749const 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
765const 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
782QPdfPage::QPdfPage()
783 : QPdf::ByteStream(true) // Enable file backing
784{
785}
786
787void 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(t: object))
791 images.append(t: object);
792}
793
794
795QPdfEngine::QPdfEngine(QPdfEnginePrivate &dd)
796 : QPaintEngine(dd, qt_pdf_decide_features())
797{
798}
799
800QPdfEngine::QPdfEngine()
801 : QPaintEngine(*new QPdfEnginePrivate(), qt_pdf_decide_features())
802{
803}
804
805void QPdfEngine::setOutputFilename(const QString &filename)
806{
807 Q_D(QPdfEngine);
808 d->outputFileName = filename;
809}
810
811
812void 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(p: points[i]);
821 p.lineTo(p: points[i] + QPointF(0, 0.001));
822 }
823
824 bool hadBrush = d->hasBrush;
825 d->hasBrush = false;
826 drawPath(path: p);
827 d->hasBrush = hadBrush;
828}
829
830void 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(p: lines[i].p1());
839 p.lineTo(p: lines[i].p2());
840 }
841 bool hadBrush = d->hasBrush;
842 d->hasBrush = false;
843 drawPath(path: p);
844 d->hasBrush = hadBrush;
845}
846
847void 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(matrix: 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(rect: rects[i]);
872 drawPath(path: p);
873 }
874}
875
876void 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(p: points[0]);
902 for (int i = 1; i < pointCount; ++i)
903 p.lineTo(p: points[i]);
904
905 if (mode != PolylineMode)
906 p.closeSubpath();
907 drawPath(path: p);
908
909 d->hasBrush = hb;
910}
911
912void 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(path: p, matrix: d->needsTransform ? d->stroker.matrix : QTransform(),
924 flags: d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath);
925 } else {
926 if (d->hasBrush)
927 *d->currentPage << QPdf::generatePath(path: p, matrix: d->stroker.matrix, flags: 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(path: p);
934 *d->currentPage << "Q\n";
935 d->brush = b;
936 }
937 }
938}
939
940void 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(rect: sourceRect) : pixmap;
950 QImage image = pm.toImage();
951 bool bitmap = true;
952 const bool lossless = painter()->testRenderHint(hint: QPainter::LosslessImageRendering);
953 const int object = d->addImage(image, bitmap: &bitmap, lossless, serial_no: 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(brushAlpha: qRound(d: 255 * d->opacity), penAlpha: qRound(d: 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(matrix: 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(w: image.width(), h: image.height(), object);
978 *d->currentPage << "Q\n";
979
980 d->brush = b;
981}
982
983void 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(rect: sourceRect) : image;
991 bool bitmap = true;
992 const bool lossless = painter()->testRenderHint(hint: QPainter::LosslessImageRendering);
993 const int object = d->addImage(image: im, bitmap: &bitmap, lossless, serial_no: 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(brushAlpha: qRound(d: 255 * d->opacity), penAlpha: qRound(d: 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(matrix: 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(w: im.width(), h: im.height(), object);
1014 *d->currentPage << "Q\n";
1015}
1016
1017void 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(rects: &rectangle, rectCount: 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
1047void 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(matrix: 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
1077void QPdfEngine::drawHyperlink(const QRectF &r, const QUrl &url)
1078{
1079 Q_D(QPdfEngine);
1080
1081 const uint annot = d->addXrefEntry(object: -1);
1082 const QByteArray urlascii = url.toEncoded();
1083 int len = urlascii.size();
1084 QVarLengthArray<char> url_esc;
1085 url_esc.reserve(asize: len + 1);
1086 for (int j = 0; j < len; j++) {
1087 if (urlascii[j] == '(' || urlascii[j] == ')' || urlascii[j] == '\\')
1088 url_esc.append(t: '\\');
1089 url_esc.append(t: urlascii[j]);
1090 }
1091 url_esc.append(t: '\0');
1092
1093 char buf[256];
1094 const QRectF rr = d->pageMatrix().mapRect(r);
1095 d->xprintf(fmt: "<<\n/Type /Annot\n/Subtype /Link\n");
1096
1097 if (d->pdfVersion == QPdfEngine::Version_A1b)
1098 d->xprintf(fmt: "/F 4\n"); // enable print flag, disable all other
1099
1100 d->xprintf(fmt: "/Rect [");
1101 d->xprintf(fmt: "%s ", qt_real_to_string(val: rr.left(), buf));
1102 d->xprintf(fmt: "%s ", qt_real_to_string(val: rr.top(), buf));
1103 d->xprintf(fmt: "%s ", qt_real_to_string(val: rr.right(), buf));
1104 d->xprintf(fmt: "%s", qt_real_to_string(val: rr.bottom(), buf));
1105 d->xprintf(fmt: "]\n/Border [0 0 0]\n/A <<\n");
1106 d->xprintf(fmt: "/Type /Action\n/S /URI\n/URI (%s)\n", url_esc.constData());
1107 d->xprintf(fmt: ">>\n>>\n");
1108 d->xprintf(fmt: "endobj\n");
1109 d->currentPage->annotations.append(t: annot);
1110}
1111
1112void 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(brush&: 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(pen: d->pen, hints: 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(pen: d->pen, hints: 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(path: state.clipPath(), op: 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, op: 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
1196void 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(path: d->clips.at(i), matrix: QTransform(), flags: 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(matrix: 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
1242extern QPainterPath qt_regionToPath(const QRegion &region);
1243
1244void 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(t: path);
1256 } else if (op == Qt::IntersectClip) {
1257 d->clips.append(t: 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(p: path);
1262 d->clips.clear();
1263 d->clips.append(t: path);
1264 }
1265}
1266
1267void 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(rgb: 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(a: qreal(1.0), b: 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(pen: d->pen);
1323}
1324
1325
1326void 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(matrix: d->stroker.matrix, specifyColor: &specifyColor, gStateObject: &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(rgb: 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
1362bool 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*> (object: d->outDevice);
1371 if (outfile && outfile->error() != QFile::NoError)
1372 return false;
1373 return true;
1374}
1375
1376QPaintEngine::Type QPdfEngine::type() const
1377{
1378 return QPaintEngine::Pdf;
1379}
1380
1381void QPdfEngine::setResolution(int resolution)
1382{
1383 Q_D(QPdfEngine);
1384 d->resolution = resolution;
1385}
1386
1387int QPdfEngine::resolution() const
1388{
1389 Q_D(const QPdfEngine);
1390 return d->resolution;
1391}
1392
1393void QPdfEngine::setPdfVersion(PdfVersion version)
1394{
1395 Q_D(QPdfEngine);
1396 d->pdfVersion = version;
1397}
1398
1399void QPdfEngine::setDocumentXmpMetadata(const QByteArray &xmpMetadata)
1400{
1401 Q_D(QPdfEngine);
1402 d->xmpDocumentMetadata = xmpMetadata;
1403}
1404
1405QByteArray QPdfEngine::documentXmpMetadata() const
1406{
1407 Q_D(const QPdfEngine);
1408 return d->xmpDocumentMetadata;
1409}
1410
1411void QPdfEngine::setPageLayout(const QPageLayout &pageLayout)
1412{
1413 Q_D(QPdfEngine);
1414 d->m_pageLayout = pageLayout;
1415}
1416
1417void QPdfEngine::setPageSize(const QPageSize &pageSize)
1418{
1419 Q_D(QPdfEngine);
1420 d->m_pageLayout.setPageSize(pageSize);
1421}
1422
1423void QPdfEngine::setPageOrientation(QPageLayout::Orientation orientation)
1424{
1425 Q_D(QPdfEngine);
1426 d->m_pageLayout.setOrientation(orientation);
1427}
1428
1429void 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
1436QPageLayout QPdfEngine::pageLayout() const
1437{
1438 Q_D(const QPdfEngine);
1439 return d->m_pageLayout;
1440}
1441
1442// Metrics are in Device Pixels
1443int 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(resolution: d->resolution).width();
1450 break;
1451 case QPaintDevice::PdmHeight:
1452 val = d->m_pageLayout.paintRectPixels(resolution: 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: d->m_pageLayout.paintRect(units: QPageLayout::Millimeter).width());
1464 break;
1465 case QPaintDevice::PdmHeightMM:
1466 val = qRound(d: d->m_pageLayout.paintRect(units: 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(msg: "QPdfWriter::metric: Invalid metric command");
1482 return 0;
1483 }
1484 return val;
1485}
1486
1487QPdfEnginePrivate::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
1506bool 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(flags: 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
1561bool QPdfEngine::end()
1562{
1563 Q_D(QPdfEngine);
1564 d->writeTail();
1565
1566 d->stream->setDevice(nullptr);
1567
1568 qDeleteAll(c: 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
1585void QPdfEngine::addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType)
1586{
1587 Q_D(QPdfEngine);
1588 d->fileCache.push_back(t: {fileName, data, mimeType});
1589}
1590
1591QPdfEnginePrivate::~QPdfEnginePrivate()
1592{
1593 qDeleteAll(c: fonts);
1594 delete currentPage;
1595 delete stream;
1596}
1597
1598void QPdfEnginePrivate::writeHeader()
1599{
1600 addXrefEntry(object: 0,printostr: 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(fmt: "%%PDF-%s\n", verStr);
1612 xprintf(fmt: "%%\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(object: -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(data: catalog);
1654 }
1655
1656 if (!fileCache.isEmpty()) {
1657 addXrefEntry(object: embeddedfilesRoot);
1658 xprintf(fmt: "<</EmbeddedFiles %d 0 R>>\n"
1659 "endobj\n", namesRoot);
1660 }
1661
1662 // graphics state
1663 graphicsState = addXrefEntry(object: -1);
1664 xprintf(fmt: "<<\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(object: -1);
1677 xprintf(fmt: "[/Pattern /DeviceRGB]\n"
1678 "endobj\n");
1679}
1680
1681void QPdfEnginePrivate::writeInfo()
1682{
1683 info = addXrefEntry(object: -1);
1684 xprintf(fmt: "<<\n/Title ");
1685 printString(string: title);
1686 xprintf(fmt: "\n/Creator ");
1687 printString(string: creator);
1688 xprintf(fmt: "\n/Producer ");
1689 printString(string: QString::fromLatin1(str: "Qt " QT_VERSION_STR));
1690 QDateTime now = QDateTime::currentDateTime();
1691 QTime t = now.time();
1692 QDate d = now.date();
1693 xprintf(fmt: "\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(fmt: "-%02d'%02d')\n", -hours, -mins);
1705 else if (offset > 0)
1706 xprintf(fmt: "+%02d'%02d')\n", hours , mins);
1707 else
1708 xprintf(fmt: "Z)\n");
1709 xprintf(fmt: ">>\n"
1710 "endobj\n");
1711}
1712
1713int QPdfEnginePrivate::writeXmpDcumentMetaData()
1714{
1715 const int metaDataObj = addXrefEntry(object: -1);
1716 QByteArray metaDataContent;
1717
1718 if (xmpDocumentMetadata.isEmpty()) {
1719 const QString producer(QString::fromLatin1(str: "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(format: "%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(format: "-%02d:%02d", -hours, -mins);
1735 else if (offset > 0)
1736 tzStr = QString::asprintf(format: "+%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(flags: QIODevice::ReadOnly);
1744 metaDataContent = QString::fromUtf8(str: metaDataFile.readAll()).arg(args: producer.toHtmlEscaped(),
1745 args: title.toHtmlEscaped(),
1746 args: creator.toHtmlEscaped(),
1747 args: metaDataDate).toUtf8();
1748 }
1749 else
1750 metaDataContent = xmpDocumentMetadata;
1751
1752 xprintf(fmt: "<<\n"
1753 "/Type /Metadata /Subtype /XML\n"
1754 "/Length %d\n"
1755 ">>\n"
1756 "stream\n", metaDataContent.size());
1757 write(data: metaDataContent);
1758 xprintf(fmt: "\nendstream\n"
1759 "endobj\n");
1760
1761 return metaDataObj;
1762}
1763
1764int QPdfEnginePrivate::writeOutputIntent()
1765{
1766 const int colorProfile = addXrefEntry(object: -1);
1767 {
1768 QFile colorProfileFile(QLatin1String(":/qpdf/sRGB2014.icc"));
1769 colorProfileFile.open(flags: 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(data: colorProfileData);
1785 write(data: "\nendstream\n"
1786 "endobj\n");
1787 addXrefEntry(object: length_object);
1788 xprintf(fmt: "%d\n"
1789 "endobj\n", len);
1790 }
1791
1792 const int outputIntent = addXrefEntry(object: -1);
1793 {
1794 xprintf(fmt: "<<\n");
1795 xprintf(fmt: "/Type /OutputIntent\n");
1796 xprintf(fmt: "/S/GTS_PDFA1\n");
1797 xprintf(fmt: "/OutputConditionIdentifier (sRGB_IEC61966-2-1_black_scaled)\n");
1798 xprintf(fmt: "/DestOutputProfile %d 0 R\n", colorProfile);
1799 xprintf(fmt: "/Info(sRGB IEC61966 v2.1 with black scaling)\n");
1800 xprintf(fmt: "/RegistryName(http://www.color.org)\n");
1801 xprintf(fmt: ">>\n");
1802 xprintf(fmt: "endobj\n");
1803 }
1804
1805 return outputIntent;
1806}
1807
1808void QPdfEnginePrivate::writePageRoot()
1809{
1810 addXrefEntry(object: pageRoot);
1811
1812 xprintf(fmt: "<<\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(fmt: "%d 0 R\n", pages[i]);
1819 xprintf(fmt: "]\n");
1820
1821 //xprintf("/Group <</S /Transparency /I true /K false>>\n");
1822 xprintf(fmt: "/Count %d\n", pages.size());
1823
1824 xprintf(fmt: "/ProcSet [/PDF /Text /ImageB /ImageC]\n"
1825 ">>\n"
1826 "endobj\n");
1827}
1828
1829void 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(object: -1);
1839 xprintf(fmt: "<<\n");
1840 if (do_compress)
1841 xprintf(fmt: "/Filter /FlateDecode\n");
1842
1843 const int lenobj = requestObject();
1844 xprintf(fmt: "/Length %d 0 R\n", lenobj);
1845 int len = 0;
1846 xprintf(fmt: ">>\nstream\n");
1847 len = writeCompressed(data: attachment.data);
1848 xprintf(fmt: "\nendstream\n"
1849 "endobj\n");
1850 addXrefEntry(object: lenobj);
1851 xprintf(fmt: "%d\n"
1852 "endobj\n", len);
1853
1854 attachments.push_back(t: addXrefEntry(object: -1));
1855 xprintf(fmt: "<<\n"
1856 "/F (%s)", attachment.fileName.toLatin1().constData());
1857
1858 xprintf(fmt: "\n/EF <</F %d 0 R>>\n"
1859 "/Type/Filespec\n"
1860 , attachmentID);
1861 if (!attachment.mimeType.isEmpty())
1862 xprintf(fmt: "/Subtype/%s\n",
1863 attachment.mimeType.replace(before: QLatin1String("/"),
1864 after: QLatin1String("#2F")).toLatin1().constData());
1865 xprintf(fmt: ">>\nendobj\n");
1866 }
1867
1868 // names
1869 addXrefEntry(object: namesRoot);
1870 xprintf(fmt: "<</Names[");
1871 for (int i = 0; i < size; ++i) {
1872 auto attachment = fileCache.at(i);
1873 printString(string: attachment.fileName);
1874 xprintf(fmt: "%d 0 R\n", attachments.at(i));
1875 }
1876 xprintf(fmt: "]>>\n"
1877 "endobj\n");
1878}
1879
1880void 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(before: ' ', after: '_');
1903
1904 {
1905 qreal scale = 1000/properties.emSquare.toReal();
1906 addXrefEntry(object: 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(data: descriptor);
1932 }
1933 {
1934 addXrefEntry(object: 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(data: header);
1947 int len = writeCompressed(data: fontData);
1948 write(data: "\nendstream\n"
1949 "endobj\n");
1950 addXrefEntry(object: length_object);
1951 xprintf(fmt: "%d\n"
1952 "endobj\n", len);
1953 }
1954 {
1955 addXrefEntry(object: 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(data: cid);
1968 }
1969 {
1970 addXrefEntry(object: toUnicode);
1971 QByteArray touc = font->createToUnicodeMap();
1972 xprintf(fmt: "<< /Length %d >>\n"
1973 "stream\n", touc.length());
1974 write(data: touc);
1975 write(data: "\nendstream\n"
1976 "endobj\n");
1977 }
1978 {
1979 addXrefEntry(object: 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(data: 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(object: cidset);
2007 xprintf(fmt: "<<\n");
2008 xprintf(fmt: "/Length %d\n", cidSetStream.size());
2009 xprintf(fmt: ">>\n");
2010 xprintf(fmt: "stream\n");
2011 write(data: cidSetStream);
2012 xprintf(fmt: "\nendstream\n");
2013 xprintf(fmt: "endobj\n");
2014 }
2015}
2016
2017qreal 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(a: currentPage->pageSize.width(), b: 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(a: maxLen / 14400.0, b: 75000.0);
2029}
2030
2031void QPdfEnginePrivate::writeFonts()
2032{
2033 for (QHash<QFontEngine::FaceId, QFontSubset *>::iterator it = fonts.begin(); it != fonts.end(); ++it) {
2034 embedFont(font: *it);
2035 delete *it;
2036 }
2037 fonts.clear();
2038}
2039
2040void 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(object: pages.constLast());
2055 xprintf(fmt: "<<\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: 'f').constData(),
2065 QByteArray::number(currentPage->pageSize.height() / userUnit, f: 'f').constData());
2066
2067 if (pdfVersion >= QPdfEngine::Version_1_6)
2068 xprintf(fmt: "/UserUnit %s\n", QByteArray::number(userUnit, f: 'f').constData());
2069
2070 xprintf(fmt: ">>\n"
2071 "endobj\n");
2072
2073 addXrefEntry(object: resources);
2074 xprintf(fmt: "<<\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(fmt: "/GState%d %d 0 R\n", currentPage->graphicStates.at(i), currentPage->graphicStates.at(i));
2086 xprintf(fmt: ">>\n");
2087
2088 xprintf(fmt: "/Pattern <<\n");
2089 for (int i = 0; i < currentPage->patterns.size(); ++i)
2090 xprintf(fmt: "/Pat%d %d 0 R\n", currentPage->patterns.at(i), currentPage->patterns.at(i));
2091 xprintf(fmt: ">>\n");
2092
2093 xprintf(fmt: "/Font <<\n");
2094 for (int i = 0; i < currentPage->fonts.size();++i)
2095 xprintf(fmt: "/F%d %d 0 R\n", currentPage->fonts[i], currentPage->fonts[i]);
2096 xprintf(fmt: ">>\n");
2097
2098 xprintf(fmt: "/XObject <<\n");
2099 for (int i = 0; i<currentPage->images.size(); ++i) {
2100 xprintf(fmt: "/Im%d %d 0 R\n", currentPage->images.at(i), currentPage->images.at(i));
2101 }
2102 xprintf(fmt: ">>\n");
2103
2104 xprintf(fmt: ">>\n"
2105 "endobj\n");
2106
2107 addXrefEntry(object: annots);
2108 xprintf(fmt: "[ ");
2109 for (int i = 0; i<currentPage->annotations.size(); ++i) {
2110 xprintf(fmt: "%d 0 R ", currentPage->annotations.at(i));
2111 }
2112 xprintf(fmt: "]\nendobj\n");
2113
2114 addXrefEntry(object: pageStream);
2115 xprintf(fmt: "<<\n"
2116 "/Length %d 0 R\n", pageStreamLength); // object number for stream length object
2117 if (do_compress)
2118 xprintf(fmt: "/Filter /FlateDecode\n");
2119
2120 xprintf(fmt: ">>\n");
2121 xprintf(fmt: "stream\n");
2122 QIODevice *content = currentPage->stream();
2123 int len = writeCompressed(dev: content);
2124 xprintf(fmt: "\nendstream\n"
2125 "endobj\n");
2126
2127 addXrefEntry(object: pageStreamLength);
2128 xprintf(fmt: "%d\nendobj\n",len);
2129}
2130
2131void QPdfEnginePrivate::writeTail()
2132{
2133 writePage();
2134 writeFonts();
2135 writePageRoot();
2136 writeAttachmentRoot();
2137
2138 addXrefEntry(object: xrefPositions.size(),printostr: false);
2139 xprintf(fmt: "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(fmt: "%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(data: uniqueId.toLatin1(), method: 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(data: trailer);
2167 }
2168}
2169
2170int QPdfEnginePrivate::addXrefEntry(int object, bool printostr)
2171{
2172 if (object < 0)
2173 object = requestObject();
2174
2175 if (object>=xrefPositions.size())
2176 xrefPositions.resize(asize: object+1);
2177
2178 xrefPositions[object] = streampos;
2179 if (printostr)
2180 xprintf(fmt: "%d 0 obj\n",object);
2181
2182 return object;
2183}
2184
2185void QPdfEnginePrivate::printString(const QString &string)
2186{
2187 if (string.isEmpty()) {
2188 write(data: "()");
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(c: '\\');
2203 array.append(c: part[j]);
2204 }
2205 }
2206 array.append(c: ')');
2207 write(data: array);
2208}
2209
2210
2211void 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(str: buf, n: msize, fmt, ap: args);
2222 va_end(args);
2223
2224 if (Q_LIKELY(bufsize < msize)) {
2225 stream->writeRawData(buf, len: bufsize);
2226 } else {
2227 // Fallback for abnormal cases
2228 QScopedArrayPointer<char> tmpbuf(new char[bufsize + 1]);
2229 va_start(args, fmt);
2230 bufsize = qvsnprintf(str: tmpbuf.data(), n: bufsize + 1, fmt, ap: args);
2231 va_end(args);
2232 stream->writeRawData(tmpbuf.data(), len: bufsize);
2233 }
2234 streampos += bufsize;
2235}
2236
2237int 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(msg: "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(maxlen: size);
2257 zStruct.avail_in = in.size();
2258 zStruct.next_in = reinterpret_cast<unsigned char*>(in.data());
2259 if (in.size() <= 0) {
2260 qWarning(msg: "QPdfStream::writeCompressed: Error in read()");
2261 ::deflateEnd(strm: &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(strm: &zStruct, flush: 0) != Z_OK) {
2268 qWarning(msg: "QPdfStream::writeCompressed: Error in deflate()");
2269 ::deflateEnd(strm: &zStruct);
2270 return sum;
2271 }
2272 int written = out.size() - zStruct.avail_out;
2273 stream->writeRawData(out.constData(), len: 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(strm: &zStruct, Z_FINISH);
2282 if (ret != Z_OK && ret != Z_STREAM_END) {
2283 qWarning(msg: "QPdfStream::writeCompressed: Error in deflate()");
2284 ::deflateEnd(strm: &zStruct);
2285 return sum;
2286 }
2287 int written = out.size() - zStruct.avail_out;
2288 stream->writeRawData(out.constData(), len: written);
2289 streampos += written;
2290 sum += written;
2291 } while (ret == Z_OK);
2292
2293 ::deflateEnd(strm: &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(maxlen: QPdfPage::chunkSize());
2303 stream->writeRawData(arr.constData(), len: arr.size());
2304 streampos += arr.size();
2305 sum += arr.size();
2306 }
2307 return sum;
2308 }
2309}
2310
2311int 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: &destLen, source: (const Bytef*) src, sourceLen: (uLongf)len)) {
2318 stream->writeRawData((const char*)dest, len: destLen);
2319 } else {
2320 qWarning(msg: "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
2334int 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(object: -1);
2338 xprintf(fmt: "<<\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(fmt: "/ImageMask true\n"
2347 "/Decode [1 0]\n");
2348 } else {
2349 xprintf(fmt: "/BitsPerComponent 1\n"
2350 "/ColorSpace /DeviceGray\n");
2351 }
2352 } else {
2353 xprintf(fmt: "/BitsPerComponent 8\n"
2354 "/ColorSpace %s\n", (depth == 32) ? "/DeviceRGB" : "/DeviceGray");
2355 }
2356 if (maskObject > 0)
2357 xprintf(fmt: "/Mask %d 0 R\n", maskObject);
2358 if (softMaskObject > 0)
2359 xprintf(fmt: "/SMask %d 0 R\n", softMaskObject);
2360
2361 int lenobj = requestObject();
2362 xprintf(fmt: "/Length %d 0 R\n", lenobj);
2363 if (interpolateImages)
2364 xprintf(fmt: "/Interpolate true\n");
2365 int len = 0;
2366 if (dct) {
2367 //qDebug("DCT");
2368 xprintf(fmt: "/Filter /DCTDecode\n>>\nstream\n");
2369 write(data);
2370 len = data.length();
2371 } else {
2372 if (do_compress)
2373 xprintf(fmt: "/Filter /FlateDecode\n>>\nstream\n");
2374 else
2375 xprintf(fmt: ">>\nstream\n");
2376 len = writeCompressed(data);
2377 }
2378 xprintf(fmt: "\nendstream\n"
2379 "endobj\n");
2380 addXrefEntry(object: lenobj);
2381 xprintf(fmt: "%d\n"
2382 "endobj\n", len);
2383 return image;
2384}
2385
2386struct QGradientBound {
2387 qreal start;
2388 qreal stop;
2389 int function;
2390 bool reverse;
2391};
2392Q_DECLARE_TYPEINFO(QGradientBound, Q_PRIMITIVE_TYPE);
2393
2394int 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(i: 0).first > 0)
2402 stops.prepend(t: QGradientStop(0, stops.at(i: 0).second));
2403 if (stops.at(i: stops.size() - 1).first < 1)
2404 stops.append(t: QGradientStop(1, stops.at(i: stops.size() - 1).second));
2405
2406 QVector<int> functions;
2407 const int numStops = stops.size();
2408 functions.reserve(asize: numStops - 1);
2409 for (int i = 0; i < numStops - 1; ++i) {
2410 int f = addXrefEntry(object: -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: 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: i + 1).second.redF() << stops.at(i: i + 1).second.greenF() << stops.at(i: 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(asize: (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(min: qreal(0.), val: stops.at(i).first, max: qreal(1.));
2438 b.stop = step + 1 - qBound(min: qreal(0.), val: stops.at(i: i - 1).first, max: qreal(1.));
2439 b.function = functions.at(i: 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(min: qreal(0.), val: stops.at(i).first, max: qreal(1.));
2447 b.stop = step + qBound(min: qreal(0.), val: stops.at(i: i + 1).first, max: 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(i: 0).start;
2457 qreal bend = gradientBounds.at(i: 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(object: -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(i: 0);
2489 }
2490 return function;
2491}
2492
2493int 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(p: pageRect.topLeft()),
2514 inv.map(p: pageRect.topRight()),
2515 inv.map(p: pageRect.bottomLeft()),
2516 inv.map(p: 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(a: from, b: qFloor(v: off));
2527 to = qMax(a: to, b: qCeil(v: 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(object: -1);
2550 write(data: shader);
2551 return shaderObject;
2552}
2553
2554int 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(p: pageRect.topLeft()),
2578 inv.map(p: pageRect.topRight()),
2579 inv.map(p: pageRect.bottomLeft()),
2580 inv.map(p: 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(object: -1);
2619 write(data: shader);
2620 return shaderObject;
2621}
2622
2623int QPdfEnginePrivate::generateGradientShader(const QGradient *gradient, const QTransform &matrix, bool alpha)
2624{
2625 switch (gradient->type()) {
2626 case QGradient::LinearGradient:
2627 return generateLinearGradientShader(gradient: static_cast<const QLinearGradient *>(gradient), matrix, alpha);
2628 case QGradient::RadialGradient:
2629 return generateRadialGradientShader(gradient: 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
2639int 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, matrix: 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(object: -1);
2668 write(data: str);
2669 currentPage->patterns.append(t: patternObj);
2670
2671 if (!b.isOpaque()) {
2672 bool ca = true;
2673 QGradientStops stops = gradient->stops();
2674 int a = stops.at(i: 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(brushAlpha: stops.at(i: 0).second.alpha());
2683 } else {
2684 int alphaShaderObject = generateGradientShader(gradient, matrix: m, alpha: 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(object: -1);
2709 write(data: form);
2710 *gStateObject = addXrefEntry(object: -1);
2711 xprintf(fmt: "<< /SMask << /S /Alpha /G %d 0 R >> >>\n"
2712 "endobj\n", softMaskFormObject);
2713 currentPage->graphicStates.append(t: *gStateObject);
2714 }
2715 }
2716
2717 return patternObj;
2718}
2719
2720int QPdfEnginePrivate::addConstantAlphaObject(int brushAlpha, int penAlpha)
2721{
2722 if (brushAlpha == 255 && penAlpha == 255)
2723 return 0;
2724 int object = alphaCache.value(akey: QPair<uint, uint>(brushAlpha, penAlpha), adefaultValue: 0);
2725 if (!object) {
2726 object = addXrefEntry(object: -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(fmt: "%s\nendobj\n", alphaDef.constData());
2732 alphaCache.insert(akey: QPair<uint, uint>(brushAlpha, penAlpha), avalue: object);
2733 }
2734 if (currentPage->graphicStates.indexOf(t: object) < 0)
2735 currentPage->graphicStates.append(t: object);
2736
2737 return object;
2738}
2739
2740
2741int 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(dx: brushOrigin.x(), dy: 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(b: brush, matrix, gStateObject);
2761 }
2762
2763 if ((!brush.isOpaque() && brush.style() < Qt::LinearGradientPattern) || opacity != 1.0)
2764 *gStateObject = addConstantAlphaObject(brushAlpha: qRound(d: brush.color().alpha() * opacity),
2765 penAlpha: qRound(d: pen.color().alpha() * opacity));
2766
2767 int imageObject = -1;
2768 QByteArray pattern = QPdf::patternForBrush(b: brush);
2769 if (pattern.isEmpty()) {
2770 if (brush.style() != Qt::TexturePattern)
2771 return 0;
2772 QImage image = brush.textureImage();
2773 bool bitmap = true;
2774 const bool lossless = q->painter()->testRenderHint(hint: QPainter::LosslessImageRendering);
2775 imageObject = addImage(image, bitmap: &bitmap, lossless, serial_no: image.cacheKey());
2776 if (imageObject != -1) {
2777 QImage::Format f = image.format();
2778 if (f != QImage::Format_MonoLSB && f != QImage::Format_Mono) {
2779 paintType = 1; // Colored tiling
2780 *specifyColor = false;
2781 }
2782 w = image.width();
2783 h = image.height();
2784 QTransform m(w, 0, 0, -h, 0, h);
2785 QPdf::ByteStream s(&pattern);
2786 s << QPdf::generateMatrix(matrix: m);
2787 s << "/Im" << imageObject << " Do\n";
2788 }
2789 }
2790
2791 QByteArray str;
2792 QPdf::ByteStream s(&str);
2793 s << "<<\n"
2794 "/Type /Pattern\n"
2795 "/PatternType 1\n"
2796 "/PaintType " << paintType << "\n"
2797 "/TilingType 1\n"
2798 "/BBox [0 0 " << w << h << "]\n"
2799 "/XStep " << w << "\n"
2800 "/YStep " << h << "\n"
2801 "/Matrix ["
2802 << matrix.m11()
2803 << matrix.m12()
2804 << matrix.m21()
2805 << matrix.m22()
2806 << matrix.dx()
2807 << matrix.dy() << "]\n"
2808 "/Resources \n<< "; // open resource tree
2809 if (imageObject > 0) {
2810 s << "/XObject << /Im" << imageObject << ' ' << imageObject << "0 R >> ";
2811 }
2812 s << ">>\n"
2813 "/Length " << pattern.length() << "\n"
2814 ">>\n"
2815 "stream\n"
2816 << pattern
2817 << "\nendstream\n"
2818 "endobj\n";
2819
2820 int patternObj = addXrefEntry(object: -1);
2821 write(data: str);
2822 currentPage->patterns.append(t: patternObj);
2823 return patternObj;
2824}
2825
2826static inline bool is_monochrome(const QVector<QRgb> &colorTable)
2827{
2828 return colorTable.size() == 2
2829 && colorTable.at(i: 0) == QColor(Qt::black).rgba()
2830 && colorTable.at(i: 1) == QColor(Qt::white).rgba()
2831 ;
2832}
2833
2834/*!
2835 * Adds an image to the pdf and return the pdf-object id. Returns -1 if adding the image failed.
2836 */
2837int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless, qint64 serial_no)
2838{
2839 if (img.isNull())
2840 return -1;
2841
2842 int object = imageCache.value(akey: serial_no);
2843 if(object)
2844 return object;
2845
2846 QImage image = img;
2847 QImage::Format format = image.format();
2848
2849 if (pdfVersion == QPdfEngine::Version_A1b) {
2850 if (image.hasAlphaChannel()) {
2851 // transparent images are not allowed in PDF/A-1b, so we convert it to
2852 // a format without alpha channel first
2853
2854 QImage alphaLessImage(image.width(), image.height(), QImage::Format_RGB32);
2855 alphaLessImage.fill(color: Qt::white);
2856
2857 QPainter p(&alphaLessImage);
2858 p.drawImage(x: 0, y: 0, image);
2859
2860 image = alphaLessImage;
2861 format = image.format();
2862 }
2863 }
2864
2865 if (image.depth() == 1 && *bitmap && is_monochrome(colorTable: img.colorTable())) {
2866 if (format == QImage::Format_MonoLSB)
2867 image = image.convertToFormat(f: QImage::Format_Mono);
2868 format = QImage::Format_Mono;
2869 } else {
2870 *bitmap = false;
2871 if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) {
2872 image = image.convertToFormat(f: QImage::Format_ARGB32);
2873 format = QImage::Format_ARGB32;
2874 }
2875 }
2876
2877 int w = image.width();
2878 int h = image.height();
2879 int d = image.depth();
2880
2881 if (format == QImage::Format_Mono) {
2882 int bytesPerLine = (w + 7) >> 3;
2883 QByteArray data;
2884 data.resize(size: bytesPerLine * h);
2885 char *rawdata = data.data();
2886 for (int y = 0; y < h; ++y) {
2887 memcpy(dest: rawdata, src: image.constScanLine(y), n: bytesPerLine);
2888 rawdata += bytesPerLine;
2889 }
2890 object = writeImage(data, width: w, height: h, depth: d, maskObject: 0, softMaskObject: 0, dct: false, isMono: is_monochrome(colorTable: img.colorTable()));
2891 } else {
2892 QByteArray softMaskData;
2893 bool dct = false;
2894 QByteArray imageData;
2895 bool hasAlpha = false;
2896 bool hasMask = false;
2897
2898 if (QImageWriter::supportedImageFormats().contains(t: "jpeg") && !grayscale && !lossless) {
2899 QBuffer buffer(&imageData);
2900 QImageWriter writer(&buffer, "jpeg");
2901 writer.setQuality(94);
2902 writer.write(image);
2903 dct = true;
2904
2905 if (format != QImage::Format_RGB32) {
2906 softMaskData.resize(size: w * h);
2907 uchar *sdata = (uchar *)softMaskData.data();
2908 for (int y = 0; y < h; ++y) {
2909 const QRgb *rgb = (const QRgb *)image.constScanLine(y);
2910 for (int x = 0; x < w; ++x) {
2911 uchar alpha = qAlpha(rgb: *rgb);
2912 *sdata++ = alpha;
2913 hasMask |= (alpha < 255);
2914 hasAlpha |= (alpha != 0 && alpha != 255);
2915 ++rgb;
2916 }
2917 }
2918 }
2919 } else {
2920 imageData.resize(size: grayscale ? w * h : 3 * w * h);
2921 uchar *data = (uchar *)imageData.data();
2922 softMaskData.resize(size: w * h);
2923 uchar *sdata = (uchar *)softMaskData.data();
2924 for (int y = 0; y < h; ++y) {
2925 const QRgb *rgb = (const QRgb *)image.constScanLine(y);
2926 if (grayscale) {
2927 for (int x = 0; x < w; ++x) {
2928 *(data++) = qGray(rgb: *rgb);
2929 uchar alpha = qAlpha(rgb: *rgb);
2930 *sdata++ = alpha;
2931 hasMask |= (alpha < 255);
2932 hasAlpha |= (alpha != 0 && alpha != 255);
2933 ++rgb;
2934 }
2935 } else {
2936 for (int x = 0; x < w; ++x) {
2937 *(data++) = qRed(rgb: *rgb);
2938 *(data++) = qGreen(rgb: *rgb);
2939 *(data++) = qBlue(rgb: *rgb);
2940 uchar alpha = qAlpha(rgb: *rgb);
2941 *sdata++ = alpha;
2942 hasMask |= (alpha < 255);
2943 hasAlpha |= (alpha != 0 && alpha != 255);
2944 ++rgb;
2945 }
2946 }
2947 }
2948 if (format == QImage::Format_RGB32)
2949 hasAlpha = hasMask = false;
2950 }
2951 int maskObject = 0;
2952 int softMaskObject = 0;
2953 if (hasAlpha) {
2954 softMaskObject = writeImage(data: softMaskData, width: w, height: h, depth: 8, maskObject: 0, softMaskObject: 0);
2955 } else if (hasMask) {
2956 // dither the soft mask to 1bit and add it. This also helps PDF viewers
2957 // without transparency support
2958 int bytesPerLine = (w + 7) >> 3;
2959 QByteArray mask(bytesPerLine * h, 0);
2960 uchar *mdata = (uchar *)mask.data();
2961 const uchar *sdata = (const uchar *)softMaskData.constData();
2962 for (int y = 0; y < h; ++y) {
2963 for (int x = 0; x < w; ++x) {
2964 if (*sdata)
2965 mdata[x>>3] |= (0x80 >> (x&7));
2966 ++sdata;
2967 }
2968 mdata += bytesPerLine;
2969 }
2970 maskObject = writeImage(data: mask, width: w, height: h, depth: 1, maskObject: 0, softMaskObject: 0);
2971 }
2972 object = writeImage(data: imageData, width: w, height: h, depth: grayscale ? 8 : 32,
2973 maskObject, softMaskObject, dct);
2974 }
2975 imageCache.insert(akey: serial_no, avalue: object);
2976 return object;
2977}
2978
2979void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti)
2980{
2981 Q_Q(QPdfEngine);
2982
2983 if (ti.charFormat.isAnchor()) {
2984 qreal size = ti.fontEngine->fontDef.pixelSize;
2985 int synthesized = ti.fontEngine->synthesized();
2986 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
2987 Q_ASSERT(stretch > qreal(0));
2988
2989 QTransform trans;
2990 // Build text rendering matrix (Trm). We need it to map the text area to user
2991 // space units on the PDF page.
2992 trans = QTransform(size*stretch, 0, 0, size, 0, 0);
2993 // Apply text matrix (Tm).
2994 trans *= QTransform(1,0,0,-1,p.x(),p.y());
2995 // Apply page displacement (Identity for first page).
2996 trans *= stroker.matrix;
2997 // Apply Current Transformation Matrix (CTM)
2998 trans *= pageMatrix();
2999 qreal x1, y1, x2, y2;
3000 trans.map(x: 0, y: 0, tx: &x1, ty: &y1);
3001 trans.map(x: ti.width.toReal()/size, y: (ti.ascent.toReal()-ti.descent.toReal())/size, tx: &x2, ty: &y2);
3002
3003 uint annot = addXrefEntry(object: -1);
3004 QByteArray x1s, y1s, x2s, y2s;
3005 x1s.setNum(static_cast<double>(x1), f: 'f');
3006 y1s.setNum(static_cast<double>(y1), f: 'f');
3007 x2s.setNum(static_cast<double>(x2), f: 'f');
3008 y2s.setNum(static_cast<double>(y2), f: 'f');
3009 QByteArray rectData = x1s + ' ' + y1s + ' ' + x2s + ' ' + y2s;
3010 xprintf(fmt: "<<\n/Type /Annot\n/Subtype /Link\n");
3011
3012 if (pdfVersion == QPdfEngine::Version_A1b)
3013 xprintf(fmt: "/F 4\n"); // enable print flag, disable all other
3014
3015 xprintf(fmt: "/Rect [");
3016 xprintf(fmt: rectData.constData());
3017#ifdef Q_DEBUG_PDF_LINKS
3018 xprintf("]\n/Border [16 16 1]\n/A <<\n");
3019#else
3020 xprintf(fmt: "]\n/Border [0 0 0]\n/A <<\n");
3021#endif
3022 xprintf(fmt: "/Type /Action\n/S /URI\n/URI (%s)\n",
3023 ti.charFormat.anchorHref().toLatin1().constData());
3024 xprintf(fmt: ">>\n>>\n");
3025 xprintf(fmt: "endobj\n");
3026
3027 if (!currentPage->annotations.contains(t: annot)) {
3028 currentPage->annotations.append(t: annot);
3029 }
3030 }
3031
3032 QFontEngine *fe = ti.fontEngine;
3033
3034 QFontEngine::FaceId face_id = fe->faceId();
3035 bool noEmbed = false;
3036 if (!embedFonts
3037 || face_id.filename.isEmpty()
3038 || fe->fsType & 0x200 /* bitmap embedding only */
3039 || fe->fsType == 2 /* no embedding allowed */) {
3040 *currentPage << "Q\n";
3041 q->QPaintEngine::drawTextItem(p, textItem: ti);
3042 *currentPage << "q\n";
3043 if (face_id.filename.isEmpty())
3044 return;
3045 noEmbed = true;
3046 }
3047
3048 QFontSubset *font = fonts.value(akey: face_id, adefaultValue: 0);
3049 if (!font) {
3050 font = new QFontSubset(fe, requestObject());
3051 font->noEmbed = noEmbed;
3052 }
3053 fonts.insert(akey: face_id, avalue: font);
3054
3055 if (!currentPage->fonts.contains(t: font->object_id))
3056 currentPage->fonts.append(t: font->object_id);
3057
3058 qreal size = ti.fontEngine->fontDef.pixelSize;
3059
3060 QVarLengthArray<glyph_t> glyphs;
3061 QVarLengthArray<QFixedPoint> positions;
3062 QTransform m = QTransform::fromTranslate(dx: p.x(), dy: p.y());
3063 ti.fontEngine->getGlyphPositions(glyphs: ti.glyphs, matrix: m, flags: ti.flags,
3064 glyphs_out&: glyphs, positions);
3065 if (glyphs.size() == 0)
3066 return;
3067 int synthesized = ti.fontEngine->synthesized();
3068 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
3069 Q_ASSERT(stretch > qreal(0));
3070
3071 *currentPage << "BT\n"
3072 << "/F" << font->object_id << size << "Tf "
3073 << stretch << (synthesized & QFontEngine::SynthesizedItalic
3074 ? "0 .3 -1 0 0 Tm\n"
3075 : "0 0 -1 0 0 Tm\n");
3076
3077
3078#if 0
3079 // #### implement actual text for complex languages
3080 const unsigned short *logClusters = ti.logClusters;
3081 int pos = 0;
3082 do {
3083 int end = pos + 1;
3084 while (end < ti.num_chars && logClusters[end] == logClusters[pos])
3085 ++end;
3086 *currentPage << "/Span << /ActualText <FEFF";
3087 for (int i = pos; i < end; ++i) {
3088 s << toHex((ushort)ti.chars[i].unicode(), buf);
3089 }
3090 *currentPage << "> >>\n"
3091 "BDC\n"
3092 "<";
3093 int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end];
3094 for (int gs = logClusters[pos]; gs < ge; ++gs)
3095 *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf);
3096 *currentPage << "> Tj\n"
3097 "EMC\n";
3098 pos = end;
3099 } while (pos < ti.num_chars);
3100#else
3101 qreal last_x = 0.;
3102 qreal last_y = 0.;
3103 for (int i = 0; i < glyphs.size(); ++i) {
3104 qreal x = positions[i].x.toReal();
3105 qreal y = positions[i].y.toReal();
3106 if (synthesized & QFontEngine::SynthesizedItalic)
3107 x += .3*y;
3108 x /= stretch;
3109 char buf[5];
3110 int g = font->addGlyph(index: glyphs[i]);
3111 *currentPage << x - last_x << last_y - y << "Td <"
3112 << QPdf::toHex(u: (ushort)g, buffer: buf) << "> Tj\n";
3113 last_x = x;
3114 last_y = y;
3115 }
3116 if (synthesized & QFontEngine::SynthesizedBold) {
3117 *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic
3118 ? "0 .3 -1 0 0 Tm\n"
3119 : "0 0 -1 0 0 Tm\n");
3120 *currentPage << "/Span << /ActualText <> >> BDC\n";
3121 last_x = 0.5*fe->lineThickness().toReal();
3122 last_y = 0.;
3123 for (int i = 0; i < glyphs.size(); ++i) {
3124 qreal x = positions[i].x.toReal();
3125 qreal y = positions[i].y.toReal();
3126 if (synthesized & QFontEngine::SynthesizedItalic)
3127 x += .3*y;
3128 x /= stretch;
3129 char buf[5];
3130 int g = font->addGlyph(index: glyphs[i]);
3131 *currentPage << x - last_x << last_y - y << "Td <"
3132 << QPdf::toHex(u: (ushort)g, buffer: buf) << "> Tj\n";
3133 last_x = x;
3134 last_y = y;
3135 }
3136 *currentPage << "EMC\n";
3137 }
3138#endif
3139
3140 *currentPage << "ET\n";
3141}
3142
3143QTransform QPdfEnginePrivate::pageMatrix() const
3144{
3145 qreal userUnit = calcUserUnit();
3146 qreal scale = 72. / userUnit / resolution;
3147 QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, m_pageLayout.fullRectPoints().height() / userUnit);
3148 if (m_pageLayout.mode() != QPageLayout::FullPageMode) {
3149 QRect r = m_pageLayout.paintRectPixels(resolution);
3150 tmp.translate(dx: r.left(), dy: r.top());
3151 }
3152 return tmp;
3153}
3154
3155void QPdfEnginePrivate::newPage()
3156{
3157 if (currentPage && currentPage->pageSize.isEmpty())
3158 currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3159 writePage();
3160
3161 delete currentPage;
3162 currentPage = new QPdfPage;
3163 currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3164 stroker.stream = currentPage;
3165 pages.append(t: requestObject());
3166
3167 *currentPage << "/GSa gs /CSp cs /CSp CS\n"
3168 << QPdf::generateMatrix(matrix: pageMatrix())
3169 << "q q\n";
3170}
3171
3172QT_END_NAMESPACE
3173
3174#endif // QT_NO_PDF
3175

source code of qtbase/src/gui/painting/qpdf.cpp