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(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(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(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(&chr, 1);
238 return *this;
239 }
240
241 ByteStream &ByteStream::operator <<(const char *str)
242 {
243 if (handleDirty) prepareBuffer();
244 dev->write(str, strlen(str));
245 return *this;
246 }
247
248 ByteStream &ByteStream::operator <<(const QByteArray &str)
249 {
250 if (handleDirty) prepareBuffer();
251 dev->write(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(chunkSize());
266 dev->write(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(p.x(), buf);
289 *this << buf;
290 qt_real_to_string(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(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(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(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
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(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
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 QList<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, &x, &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, &x, &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(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
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 QList<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, this, 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(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
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, uint 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
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(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
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(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
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->hasPen) {
860 // draw strokes natively in this case for better output
861 if(!d->simplePen && !d->stroker.matrix.isIdentity())
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->simplePen && !d->stroker.matrix.isIdentity())
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
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(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
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(p, QTransform(), d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath);
924 } else {
925 if (d->hasBrush)
926 *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath);
927 if (d->hasPen) {
928 *d->currentPage << "q\n";
929 QBrush b = d->brush;
930 d->brush = d->pen.brush();
931 setBrush();
932 d->stroker.strokePath(p);
933 *d->currentPage << "Q\n";
934 d->brush = b;
935 }
936 }
937}
938
939void QPdfEngine::drawPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QRectF &sr)
940{
941 if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull())
942 return;
943 Q_D(QPdfEngine);
944
945 QBrush b = d->brush;
946
947 QRect sourceRect = sr.toRect();
948 QPixmap pm = sourceRect != pixmap.rect() ? pixmap.copy(sourceRect) : pixmap;
949 QImage image = pm.toImage();
950 bool bitmap = true;
951 const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering);
952 const int object = d->addImage(image, &bitmap, lossless, pm.cacheKey());
953 if (object < 0)
954 return;
955
956 *d->currentPage << "q\n";
957
958 if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) {
959 int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity));
960 if (stateObject)
961 *d->currentPage << "/GState" << stateObject << "gs\n";
962 else
963 *d->currentPage << "/GSa gs\n";
964 } else {
965 *d->currentPage << "/GSa gs\n";
966 }
967
968 *d->currentPage
969 << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(),
970 rectangle.x(), rectangle.y()) * (d->simplePen ? QTransform() : d->stroker.matrix));
971 if (bitmap) {
972 // set current pen as d->brush
973 d->brush = d->pen.brush();
974 }
975 setBrush();
976 d->currentPage->streamImage(image.width(), image.height(), object);
977 *d->currentPage << "Q\n";
978
979 d->brush = b;
980}
981
982void QPdfEngine::drawImage(const QRectF &rectangle, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags)
983{
984 if (sr.isEmpty() || rectangle.isEmpty() || image.isNull())
985 return;
986 Q_D(QPdfEngine);
987
988 QRect sourceRect = sr.toRect();
989 QImage im = sourceRect != image.rect() ? image.copy(sourceRect) : image;
990 bool bitmap = true;
991 const bool lossless = painter()->testRenderHint(QPainter::LosslessImageRendering);
992 const int object = d->addImage(im, &bitmap, lossless, im.cacheKey());
993 if (object < 0)
994 return;
995
996 *d->currentPage << "q\n";
997
998 if ((d->pdfVersion != QPdfEngine::Version_A1b) && (d->opacity != 1.0)) {
999 int stateObject = d->addConstantAlphaObject(qRound(255 * d->opacity), qRound(255 * d->opacity));
1000 if (stateObject)
1001 *d->currentPage << "/GState" << stateObject << "gs\n";
1002 else
1003 *d->currentPage << "/GSa gs\n";
1004 } else {
1005 *d->currentPage << "/GSa gs\n";
1006 }
1007
1008 *d->currentPage
1009 << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(),
1010 rectangle.x(), rectangle.y()) * (d->simplePen ? QTransform() : d->stroker.matrix));
1011 setBrush();
1012 d->currentPage->streamImage(im.width(), im.height(), object);
1013 *d->currentPage << "Q\n";
1014}
1015
1016void QPdfEngine::drawTiledPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QPointF &point)
1017{
1018 Q_D(QPdfEngine);
1019
1020 bool bitmap = (pixmap.depth() == 1);
1021 QBrush b = d->brush;
1022 QPointF bo = d->brushOrigin;
1023 bool hp = d->hasPen;
1024 d->hasPen = false;
1025 bool hb = d->hasBrush;
1026 d->hasBrush = true;
1027
1028 d->brush = QBrush(pixmap);
1029 if (bitmap)
1030 // #### fix bitmap case where we have a brush pen
1031 d->brush.setColor(d->pen.color());
1032
1033 d->brushOrigin = -point;
1034 *d->currentPage << "q\n";
1035 setBrush();
1036
1037 drawRects(&rectangle, 1);
1038 *d->currentPage << "Q\n";
1039
1040 d->hasPen = hp;
1041 d->hasBrush = hb;
1042 d->brush = b;
1043 d->brushOrigin = bo;
1044}
1045
1046void QPdfEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
1047{
1048 Q_D(QPdfEngine);
1049
1050 if (!d->hasPen || (d->clipEnabled && d->allClipped))
1051 return;
1052
1053 if (d->stroker.matrix.type() >= QTransform::TxProject) {
1054 QPaintEngine::drawTextItem(p, textItem);
1055 return;
1056 }
1057
1058 *d->currentPage << "q\n";
1059 if(!d->simplePen)
1060 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1061
1062 bool hp = d->hasPen;
1063 d->hasPen = false;
1064 QBrush b = d->brush;
1065 d->brush = d->pen.brush();
1066 setBrush();
1067
1068 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
1069 Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi);
1070 d->drawTextItem(p, ti);
1071 d->hasPen = hp;
1072 d->brush = b;
1073 *d->currentPage << "Q\n";
1074}
1075
1076void QPdfEngine::drawHyperlink(const QRectF &r, const QUrl &url)
1077{
1078 Q_D(QPdfEngine);
1079
1080 const uint annot = d->addXrefEntry(-1);
1081 const QByteArray urlascii = url.toEncoded();
1082 int len = urlascii.size();
1083 QVarLengthArray<char> url_esc;
1084 url_esc.reserve(len + 1);
1085 for (int j = 0; j < len; j++) {
1086 if (urlascii[j] == '(' || urlascii[j] == ')' || urlascii[j] == '\\')
1087 url_esc.append('\\');
1088 url_esc.append(urlascii[j]);
1089 }
1090 url_esc.append('\0');
1091
1092 char buf[256];
1093 const QRectF rr = d->pageMatrix().mapRect(r);
1094 d->xprintf("<<\n/Type /Annot\n/Subtype /Link\n");
1095
1096 if (d->pdfVersion == QPdfEngine::Version_A1b)
1097 d->xprintf("/F 4\n"); // enable print flag, disable all other
1098
1099 d->xprintf("/Rect [");
1100 d->xprintf("%s ", qt_real_to_string(rr.left(), buf));
1101 d->xprintf("%s ", qt_real_to_string(rr.top(), buf));
1102 d->xprintf("%s ", qt_real_to_string(rr.right(), buf));
1103 d->xprintf("%s", qt_real_to_string(rr.bottom(), buf));
1104 d->xprintf("]\n/Border [0 0 0]\n/A <<\n");
1105 d->xprintf("/Type /Action\n/S /URI\n/URI (%s)\n", url_esc.constData());
1106 d->xprintf(">>\n>>\n");
1107 d->xprintf("endobj\n");
1108 d->currentPage->annotations.append(annot);
1109}
1110
1111void QPdfEngine::updateState(const QPaintEngineState &state)
1112{
1113 Q_D(QPdfEngine);
1114
1115 QPaintEngine::DirtyFlags flags = state.state();
1116
1117 if (flags & DirtyTransform)
1118 d->stroker.matrix = state.transform();
1119
1120 if (flags & DirtyPen) {
1121 if (d->pdfVersion == QPdfEngine::Version_A1b) {
1122 QPen pen = state.pen();
1123
1124 QColor penColor = pen.color();
1125 if (penColor.alpha() != 255)
1126 penColor.setAlpha(255);
1127 pen.setColor(penColor);
1128
1129 QBrush penBrush = pen.brush();
1130 removeTransparencyFromBrush(penBrush);
1131 pen.setBrush(penBrush);
1132
1133 d->pen = pen;
1134 } else {
1135 d->pen = state.pen();
1136 }
1137 d->hasPen = d->pen.style() != Qt::NoPen;
1138 d->stroker.setPen(d->pen, state.renderHints());
1139 QBrush penBrush = d->pen.brush();
1140 bool cosmeticPen = qt_pen_is_cosmetic(d->pen, state.renderHints());
1141 bool oldSimple = d->simplePen;
1142 d->simplePen = (d->hasPen && !cosmeticPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque() && d->opacity == 1.0);
1143 if (oldSimple != d->simplePen)
1144 flags |= DirtyTransform;
1145 } else if (flags & DirtyHints) {
1146 d->stroker.setPen(d->pen, state.renderHints());
1147 }
1148 if (flags & DirtyBrush) {
1149 if (d->pdfVersion == QPdfEngine::Version_A1b) {
1150 QBrush brush = state.brush();
1151 removeTransparencyFromBrush(brush);
1152 d->brush = brush;
1153 } else {
1154 d->brush = state.brush();
1155 }
1156 if (d->brush.color().alpha() == 0 && d->brush.style() == Qt::SolidPattern)
1157 d->brush.setStyle(Qt::NoBrush);
1158 d->hasBrush = d->brush.style() != Qt::NoBrush;
1159 }
1160 if (flags & DirtyBrushOrigin) {
1161 d->brushOrigin = state.brushOrigin();
1162 flags |= DirtyBrush;
1163 }
1164 if (flags & DirtyOpacity) {
1165 d->opacity = state.opacity();
1166 if (d->simplePen && d->opacity != 1.0) {
1167 d->simplePen = false;
1168 flags |= DirtyTransform;
1169 }
1170 }
1171
1172 bool ce = d->clipEnabled;
1173 if (flags & DirtyClipPath) {
1174 d->clipEnabled = true;
1175 updateClipPath(state.clipPath(), state.clipOperation());
1176 } else if (flags & DirtyClipRegion) {
1177 d->clipEnabled = true;
1178 QPainterPath path;
1179 for (const QRect &rect : state.clipRegion())
1180 path.addRect(rect);
1181 updateClipPath(path, state.clipOperation());
1182 flags |= DirtyClipPath;
1183 } else if (flags & DirtyClipEnabled) {
1184 d->clipEnabled = state.isClipEnabled();
1185 }
1186
1187 if (ce != d->clipEnabled)
1188 flags |= DirtyClipPath;
1189 else if (!d->clipEnabled)
1190 flags &= ~DirtyClipPath;
1191
1192 setupGraphicsState(flags);
1193}
1194
1195void QPdfEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags)
1196{
1197 Q_D(QPdfEngine);
1198 if (flags & DirtyClipPath)
1199 flags |= DirtyTransform|DirtyPen|DirtyBrush;
1200
1201 if (flags & DirtyTransform) {
1202 *d->currentPage << "Q\n";
1203 flags |= DirtyPen|DirtyBrush;
1204 }
1205
1206 if (flags & DirtyClipPath) {
1207 *d->currentPage << "Q q\n";
1208
1209 d->allClipped = false;
1210 if (d->clipEnabled && !d->clips.isEmpty()) {
1211 for (int i = 0; i < d->clips.size(); ++i) {
1212 if (d->clips.at(i).isEmpty()) {
1213 d->allClipped = true;
1214 break;
1215 }
1216 }
1217 if (!d->allClipped) {
1218 for (int i = 0; i < d->clips.size(); ++i) {
1219 *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath);
1220 }
1221 }
1222 }
1223 }
1224
1225 if (flags & DirtyTransform) {
1226 *d->currentPage << "q\n";
1227 if (d->simplePen && !d->stroker.matrix.isIdentity())
1228 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1229 }
1230 if (flags & DirtyBrush)
1231 setBrush();
1232 if (d->simplePen && (flags & DirtyPen))
1233 setPen();
1234}
1235
1236extern QPainterPath qt_regionToPath(const QRegion &region);
1237
1238void QPdfEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op)
1239{
1240 Q_D(QPdfEngine);
1241 QPainterPath path = d->stroker.matrix.map(p);
1242 //qDebug() << "updateClipPath: " << d->stroker.matrix << p.boundingRect() << path.boundingRect() << op;
1243
1244 if (op == Qt::NoClip) {
1245 d->clipEnabled = false;
1246 d->clips.clear();
1247 } else if (op == Qt::ReplaceClip) {
1248 d->clips.clear();
1249 d->clips.append(path);
1250 } else if (op == Qt::IntersectClip) {
1251 d->clips.append(path);
1252 } else { // UniteClip
1253 // ask the painter for the current clipping path. that's the easiest solution
1254 path = painter()->clipPath();
1255 path = d->stroker.matrix.map(path);
1256 d->clips.clear();
1257 d->clips.append(path);
1258 }
1259}
1260
1261void QPdfEngine::setPen()
1262{
1263 Q_D(QPdfEngine);
1264 if (d->pen.style() == Qt::NoPen)
1265 return;
1266 QBrush b = d->pen.brush();
1267 Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque());
1268
1269 QColor rgba = b.color();
1270 if (d->grayscale) {
1271 qreal gray = qGray(rgba.rgba())/255.;
1272 *d->currentPage << gray << gray << gray;
1273 } else {
1274 *d->currentPage << rgba.redF()
1275 << rgba.greenF()
1276 << rgba.blueF();
1277 }
1278 *d->currentPage << "SCN\n";
1279
1280 *d->currentPage << d->pen.widthF() << "w ";
1281
1282 int pdfCapStyle = 0;
1283 switch(d->pen.capStyle()) {
1284 case Qt::FlatCap:
1285 pdfCapStyle = 0;
1286 break;
1287 case Qt::SquareCap:
1288 pdfCapStyle = 2;
1289 break;
1290 case Qt::RoundCap:
1291 pdfCapStyle = 1;
1292 break;
1293 default:
1294 break;
1295 }
1296 *d->currentPage << pdfCapStyle << "J ";
1297
1298 int pdfJoinStyle = 0;
1299 switch(d->pen.joinStyle()) {
1300 case Qt::MiterJoin:
1301 case Qt::SvgMiterJoin:
1302 *d->currentPage << qMax(qreal(1.0), d->pen.miterLimit()) << "M ";
1303 pdfJoinStyle = 0;
1304 break;
1305 case Qt::BevelJoin:
1306 pdfJoinStyle = 2;
1307 break;
1308 case Qt::RoundJoin:
1309 pdfJoinStyle = 1;
1310 break;
1311 default:
1312 break;
1313 }
1314 *d->currentPage << pdfJoinStyle << "j ";
1315
1316 *d->currentPage << QPdf::generateDashes(d->pen);
1317}
1318
1319
1320void QPdfEngine::setBrush()
1321{
1322 Q_D(QPdfEngine);
1323 Qt::BrushStyle style = d->brush.style();
1324 if (style == Qt::NoBrush)
1325 return;
1326
1327 bool specifyColor;
1328 int gStateObject = 0;
1329 int patternObject = d->addBrushPattern(d->stroker.matrix, &specifyColor, &gStateObject);
1330 if (!patternObject && !specifyColor)
1331 return;
1332
1333 *d->currentPage << (patternObject ? "/PCSp cs " : "/CSp cs ");
1334 if (specifyColor) {
1335 QColor rgba = d->brush.color();
1336 if (d->grayscale) {
1337 qreal gray = qGray(rgba.rgba())/255.;
1338 *d->currentPage << gray << gray << gray;
1339 } else {
1340 *d->currentPage << rgba.redF()
1341 << rgba.greenF()
1342 << rgba.blueF();
1343 }
1344 }
1345 if (patternObject)
1346 *d->currentPage << "/Pat" << patternObject;
1347 *d->currentPage << "scn\n";
1348
1349 if (gStateObject)
1350 *d->currentPage << "/GState" << gStateObject << "gs\n";
1351 else
1352 *d->currentPage << "/GSa gs\n";
1353}
1354
1355
1356bool QPdfEngine::newPage()
1357{
1358 Q_D(QPdfEngine);
1359 if (!isActive())
1360 return false;
1361 d->newPage();
1362
1363 setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath);
1364 QFile *outfile = qobject_cast<QFile*> (d->outDevice);
1365 if (outfile && outfile->error() != QFile::NoError)
1366 return false;
1367 return true;
1368}
1369
1370QPaintEngine::Type QPdfEngine::type() const
1371{
1372 return QPaintEngine::Pdf;
1373}
1374
1375void QPdfEngine::setResolution(int resolution)
1376{
1377 Q_D(QPdfEngine);
1378 d->resolution = resolution;
1379}
1380
1381int QPdfEngine::resolution() const
1382{
1383 Q_D(const QPdfEngine);
1384 return d->resolution;
1385}
1386
1387void QPdfEngine::setPdfVersion(PdfVersion version)
1388{
1389 Q_D(QPdfEngine);
1390 d->pdfVersion = version;
1391}
1392
1393void QPdfEngine::setDocumentXmpMetadata(const QByteArray &xmpMetadata)
1394{
1395 Q_D(QPdfEngine);
1396 d->xmpDocumentMetadata = xmpMetadata;
1397}
1398
1399QByteArray QPdfEngine::documentXmpMetadata() const
1400{
1401 Q_D(const QPdfEngine);
1402 return d->xmpDocumentMetadata;
1403}
1404
1405void QPdfEngine::setPageLayout(const QPageLayout &pageLayout)
1406{
1407 Q_D(QPdfEngine);
1408 d->m_pageLayout = pageLayout;
1409}
1410
1411void QPdfEngine::setPageSize(const QPageSize &pageSize)
1412{
1413 Q_D(QPdfEngine);
1414 d->m_pageLayout.setPageSize(pageSize);
1415}
1416
1417void QPdfEngine::setPageOrientation(QPageLayout::Orientation orientation)
1418{
1419 Q_D(QPdfEngine);
1420 d->m_pageLayout.setOrientation(orientation);
1421}
1422
1423void QPdfEngine::setPageMargins(const QMarginsF &margins, QPageLayout::Unit units)
1424{
1425 Q_D(QPdfEngine);
1426 d->m_pageLayout.setUnits(units);
1427 d->m_pageLayout.setMargins(margins);
1428}
1429
1430QPageLayout QPdfEngine::pageLayout() const
1431{
1432 Q_D(const QPdfEngine);
1433 return d->m_pageLayout;
1434}
1435
1436// Metrics are in Device Pixels
1437int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const
1438{
1439 Q_D(const QPdfEngine);
1440 int val;
1441 switch (metricType) {
1442 case QPaintDevice::PdmWidth:
1443 val = d->m_pageLayout.paintRectPixels(d->resolution).width();
1444 break;
1445 case QPaintDevice::PdmHeight:
1446 val = d->m_pageLayout.paintRectPixels(d->resolution).height();
1447 break;
1448 case QPaintDevice::PdmDpiX:
1449 case QPaintDevice::PdmDpiY:
1450 val = d->resolution;
1451 break;
1452 case QPaintDevice::PdmPhysicalDpiX:
1453 case QPaintDevice::PdmPhysicalDpiY:
1454 val = 1200;
1455 break;
1456 case QPaintDevice::PdmWidthMM:
1457 val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).width());
1458 break;
1459 case QPaintDevice::PdmHeightMM:
1460 val = qRound(d->m_pageLayout.paintRect(QPageLayout::Millimeter).height());
1461 break;
1462 case QPaintDevice::PdmNumColors:
1463 val = INT_MAX;
1464 break;
1465 case QPaintDevice::PdmDepth:
1466 val = 32;
1467 break;
1468 case QPaintDevice::PdmDevicePixelRatio:
1469 val = 1;
1470 break;
1471 case QPaintDevice::PdmDevicePixelRatioScaled:
1472 val = 1 * QPaintDevice::devicePixelRatioFScale();
1473 break;
1474 default:
1475 qWarning("QPdfWriter::metric: Invalid metric command");
1476 return 0;
1477 }
1478 return val;
1479}
1480
1481QPdfEnginePrivate::QPdfEnginePrivate()
1482 : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false),
1483 pdfVersion(QPdfEngine::Version_1_4),
1484 outDevice(nullptr), ownsDevice(false),
1485 embedFonts(true),
1486 grayscale(false),
1487 m_pageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(10, 10, 10, 10))
1488{
1489 initResources();
1490 resolution = 1200;
1491 currentObject = 1;
1492 currentPage = nullptr;
1493 stroker.stream = nullptr;
1494
1495 streampos = 0;
1496
1497 stream = new QDataStream;
1498}
1499
1500bool QPdfEngine::begin(QPaintDevice *pdev)
1501{
1502 Q_D(QPdfEngine);
1503 d->pdev = pdev;
1504
1505 if (!d->outDevice) {
1506 if (!d->outputFileName.isEmpty()) {
1507 QFile *file = new QFile(d->outputFileName);
1508 if (!file->open(QFile::WriteOnly|QFile::Truncate)) {
1509 delete file;
1510 return false;
1511 }
1512 d->outDevice = file;
1513 } else {
1514 return false;
1515 }
1516 d->ownsDevice = true;
1517 }
1518
1519 d->currentObject = 1;
1520
1521 d->currentPage = new QPdfPage;
1522 d->stroker.stream = d->currentPage;
1523 d->opacity = 1.0;
1524
1525 d->stream->setDevice(d->outDevice);
1526
1527 d->streampos = 0;
1528 d->hasPen = true;
1529 d->hasBrush = false;
1530 d->clipEnabled = false;
1531 d->allClipped = false;
1532
1533 d->xrefPositions.clear();
1534 d->pageRoot = 0;
1535 d->embeddedfilesRoot = 0;
1536 d->namesRoot = 0;
1537 d->catalog = 0;
1538 d->info = 0;
1539 d->graphicsState = 0;
1540 d->patternColorSpace = 0;
1541 d->simplePen = false;
1542
1543 d->pages.clear();
1544 d->imageCache.clear();
1545 d->alphaCache.clear();
1546
1547 setActive(true);
1548 d->writeHeader();
1549 newPage();
1550
1551 return true;
1552}
1553
1554bool QPdfEngine::end()
1555{
1556 Q_D(QPdfEngine);
1557 d->writeTail();
1558
1559 d->stream->setDevice(nullptr);
1560
1561 qDeleteAll(d->fonts);
1562 d->fonts.clear();
1563 delete d->currentPage;
1564 d->currentPage = nullptr;
1565
1566 if (d->outDevice && d->ownsDevice) {
1567 d->outDevice->close();
1568 delete d->outDevice;
1569 d->outDevice = nullptr;
1570 }
1571
1572 d->fileCache.clear();
1573
1574 setActive(false);
1575 return true;
1576}
1577
1578void QPdfEngine::addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType)
1579{
1580 Q_D(QPdfEngine);
1581 d->fileCache.push_back({fileName, data, mimeType});
1582}
1583
1584QPdfEnginePrivate::~QPdfEnginePrivate()
1585{
1586 qDeleteAll(fonts);
1587 delete currentPage;
1588 delete stream;
1589}
1590
1591void QPdfEnginePrivate::writeHeader()
1592{
1593 addXrefEntry(0,false);
1594
1595 // Keep in sync with QPdfEngine::PdfVersion!
1596 static const char mapping[][4] = {
1597 "1.4", // Version_1_4
1598 "1.4", // Version_A1b
1599 "1.6", // Version_1_6
1600 };
1601 static const size_t numMappings = sizeof mapping / sizeof *mapping;
1602 const char *verStr = mapping[size_t(pdfVersion) < numMappings ? pdfVersion : 0];
1603
1604 xprintf("%%PDF-%s\n", verStr);
1605 xprintf("%%\303\242\303\243\n");
1606
1607 writeInfo();
1608
1609 int metaDataObj = -1;
1610 int outputIntentObj = -1;
1611 if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) {
1612 metaDataObj = writeXmpDcumentMetaData();
1613 }
1614 if (pdfVersion == QPdfEngine::Version_A1b) {
1615 outputIntentObj = writeOutputIntent();
1616 }
1617
1618 catalog = addXrefEntry(-1);
1619 pageRoot = requestObject();
1620 if (!fileCache.isEmpty()) {
1621 namesRoot = requestObject();
1622 embeddedfilesRoot = requestObject();
1623 }
1624
1625 // catalog
1626 {
1627 QByteArray catalog;
1628 QPdf::ByteStream s(&catalog);
1629 s << "<<\n"
1630 << "/Type /Catalog\n"
1631 << "/Pages " << pageRoot << "0 R\n";
1632
1633 // Embedded files, if any
1634 if (!fileCache.isEmpty())
1635 s << "/Names " << embeddedfilesRoot << "0 R\n";
1636
1637 if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty())
1638 s << "/Metadata " << metaDataObj << "0 R\n";
1639
1640 if (pdfVersion == QPdfEngine::Version_A1b)
1641 s << "/OutputIntents [" << outputIntentObj << "0 R]\n";
1642
1643 s << ">>\n"
1644 << "endobj\n";
1645
1646 write(catalog);
1647 }
1648
1649 if (!fileCache.isEmpty()) {
1650 addXrefEntry(embeddedfilesRoot);
1651 xprintf("<</EmbeddedFiles %d 0 R>>\n"
1652 "endobj\n", namesRoot);
1653 }
1654
1655 // graphics state
1656 graphicsState = addXrefEntry(-1);
1657 xprintf("<<\n"
1658 "/Type /ExtGState\n"
1659 "/SA true\n"
1660 "/SM 0.02\n"
1661 "/ca 1.0\n"
1662 "/CA 1.0\n"
1663 "/AIS false\n"
1664 "/SMask /None"
1665 ">>\n"
1666 "endobj\n");
1667
1668 // color space for pattern
1669 patternColorSpace = addXrefEntry(-1);
1670 xprintf("[/Pattern /DeviceRGB]\n"
1671 "endobj\n");
1672}
1673
1674void QPdfEnginePrivate::writeInfo()
1675{
1676 info = addXrefEntry(-1);
1677 xprintf("<<\n/Title ");
1678 printString(title);
1679 xprintf("\n/Creator ");
1680 printString(creator);
1681 xprintf("\n/Producer ");
1682 printString(QString::fromLatin1("Qt " QT_VERSION_STR));
1683 QDateTime now = QDateTime::currentDateTime();
1684 QTime t = now.time();
1685 QDate d = now.date();
1686 xprintf("\n/CreationDate (D:%d%02d%02d%02d%02d%02d",
1687 d.year(),
1688 d.month(),
1689 d.day(),
1690 t.hour(),
1691 t.minute(),
1692 t.second());
1693 int offset = now.offsetFromUtc();
1694 int hours = (offset / 60) / 60;
1695 int mins = (offset / 60) % 60;
1696 if (offset < 0)
1697 xprintf("-%02d'%02d')\n", -hours, -mins);
1698 else if (offset > 0)
1699 xprintf("+%02d'%02d')\n", hours , mins);
1700 else
1701 xprintf("Z)\n");
1702 xprintf(">>\n"
1703 "endobj\n");
1704}
1705
1706int QPdfEnginePrivate::writeXmpDcumentMetaData()
1707{
1708 const int metaDataObj = addXrefEntry(-1);
1709 QByteArray metaDataContent;
1710
1711 if (xmpDocumentMetadata.isEmpty()) {
1712 const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR));
1713
1714 const QDateTime now = QDateTime::currentDateTime();
1715 const QDate date = now.date();
1716 const QTime time = now.time();
1717 const QString timeStr =
1718 QString::asprintf("%d-%02d-%02dT%02d:%02d:%02d",
1719 date.year(), date.month(), date.day(),
1720 time.hour(), time.minute(), time.second());
1721
1722 const int offset = now.offsetFromUtc();
1723 const int hours = (offset / 60) / 60;
1724 const int mins = (offset / 60) % 60;
1725 QString tzStr;
1726 if (offset < 0)
1727 tzStr = QString::asprintf("-%02d:%02d", -hours, -mins);
1728 else if (offset > 0)
1729 tzStr = QString::asprintf("+%02d:%02d", hours , mins);
1730 else
1731 tzStr = QLatin1String("Z");
1732
1733 const QString metaDataDate = timeStr + tzStr;
1734
1735 QFile metaDataFile(QLatin1String(":/qpdf/qpdfa_metadata.xml"));
1736 metaDataFile.open(QIODevice::ReadOnly);
1737 metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(),
1738 title.toHtmlEscaped(),
1739 creator.toHtmlEscaped(),
1740 metaDataDate).toUtf8();
1741 }
1742 else
1743 metaDataContent = xmpDocumentMetadata;
1744
1745 xprintf("<<\n"
1746 "/Type /Metadata /Subtype /XML\n"
1747 "/Length %d\n"
1748 ">>\n"
1749 "stream\n", metaDataContent.size());
1750 write(metaDataContent);
1751 xprintf("\nendstream\n"
1752 "endobj\n");
1753
1754 return metaDataObj;
1755}
1756
1757int QPdfEnginePrivate::writeOutputIntent()
1758{
1759 const int colorProfile = addXrefEntry(-1);
1760 {
1761 QFile colorProfileFile(QLatin1String(":/qpdf/sRGB2014.icc"));
1762 colorProfileFile.open(QIODevice::ReadOnly);
1763 const QByteArray colorProfileData = colorProfileFile.readAll();
1764
1765 QByteArray data;
1766 QPdf::ByteStream s(&data);
1767 int length_object = requestObject();
1768
1769 s << "<<\n";
1770 s << "/N 3\n";
1771 s << "/Alternate /DeviceRGB\n";
1772 s << "/Length " << length_object << "0 R\n";
1773 s << "/Filter /FlateDecode\n";
1774 s << ">>\n";
1775 s << "stream\n";
1776 write(data);
1777 const int len = writeCompressed(colorProfileData);
1778 write("\nendstream\n"
1779 "endobj\n");
1780 addXrefEntry(length_object);
1781 xprintf("%d\n"
1782 "endobj\n", len);
1783 }
1784
1785 const int outputIntent = addXrefEntry(-1);
1786 {
1787 xprintf("<<\n");
1788 xprintf("/Type /OutputIntent\n");
1789 xprintf("/S/GTS_PDFA1\n");
1790 xprintf("/OutputConditionIdentifier (sRGB_IEC61966-2-1_black_scaled)\n");
1791 xprintf("/DestOutputProfile %d 0 R\n", colorProfile);
1792 xprintf("/Info(sRGB IEC61966 v2.1 with black scaling)\n");
1793 xprintf("/RegistryName(http://www.color.org)\n");
1794 xprintf(">>\n");
1795 xprintf("endobj\n");
1796 }
1797
1798 return outputIntent;
1799}
1800
1801void QPdfEnginePrivate::writePageRoot()
1802{
1803 addXrefEntry(pageRoot);
1804
1805 xprintf("<<\n"
1806 "/Type /Pages\n"
1807 "/Kids \n"
1808 "[\n");
1809 int size = pages.size();
1810 for (int i = 0; i < size; ++i)
1811 xprintf("%d 0 R\n", pages[i]);
1812 xprintf("]\n");
1813
1814 //xprintf("/Group <</S /Transparency /I true /K false>>\n");
1815 xprintf("/Count %d\n", pages.size());
1816
1817 xprintf("/ProcSet [/PDF /Text /ImageB /ImageC]\n"
1818 ">>\n"
1819 "endobj\n");
1820}
1821
1822void QPdfEnginePrivate::writeAttachmentRoot()
1823{
1824 if (fileCache.isEmpty())
1825 return;
1826
1827 QList<int> attachments;
1828 const int size = fileCache.size();
1829 for (int i = 0; i < size; ++i) {
1830 auto attachment = fileCache.at(i);
1831 const int attachmentID = addXrefEntry(-1);
1832 xprintf("<<\n");
1833 if (do_compress)
1834 xprintf("/Filter /FlateDecode\n");
1835
1836 const int lenobj = requestObject();
1837 xprintf("/Length %d 0 R\n", lenobj);
1838 int len = 0;
1839 xprintf(">>\nstream\n");
1840 len = writeCompressed(attachment.data);
1841 xprintf("\nendstream\n"
1842 "endobj\n");
1843 addXrefEntry(lenobj);
1844 xprintf("%d\n"
1845 "endobj\n", len);
1846
1847 attachments.push_back(addXrefEntry(-1));
1848 xprintf("<<\n"
1849 "/F (%s)", attachment.fileName.toLatin1().constData());
1850
1851 xprintf("\n/EF <</F %d 0 R>>\n"
1852 "/Type/Filespec\n"
1853 , attachmentID);
1854 if (!attachment.mimeType.isEmpty())
1855 xprintf("/Subtype/%s\n",
1856 attachment.mimeType.replace(QLatin1String("/"),
1857 QLatin1String("#2F")).toLatin1().constData());
1858 xprintf(">>\nendobj\n");
1859 }
1860
1861 // names
1862 addXrefEntry(namesRoot);
1863 xprintf("<</Names[");
1864 for (int i = 0; i < size; ++i) {
1865 auto attachment = fileCache.at(i);
1866 printString(attachment.fileName);
1867 xprintf("%d 0 R\n", attachments.at(i));
1868 }
1869 xprintf("]>>\n"
1870 "endobj\n");
1871}
1872
1873void QPdfEnginePrivate::embedFont(QFontSubset *font)
1874{
1875 //qDebug() << "embedFont" << font->object_id;
1876 int fontObject = font->object_id;
1877 QByteArray fontData = font->toTruetype();
1878#ifdef FONT_DUMP
1879 static int i = 0;
1880 QString fileName("font%1.ttf");
1881 fileName = fileName.arg(i++);
1882 QFile ff(fileName);
1883 ff.open(QFile::WriteOnly);
1884 ff.write(fontData);
1885 ff.close();
1886#endif
1887
1888 int fontDescriptor = requestObject();
1889 int fontstream = requestObject();
1890 int cidfont = requestObject();
1891 int toUnicode = requestObject();
1892 int cidset = requestObject();
1893
1894 QFontEngine::Properties properties = font->fontEngine->properties();
1895 QByteArray postscriptName = properties.postscriptName.replace(' ', '_');
1896
1897 {
1898 qreal scale = 1000/properties.emSquare.toReal();
1899 addXrefEntry(fontDescriptor);
1900 QByteArray descriptor;
1901 QPdf::ByteStream s(&descriptor);
1902 s << "<< /Type /FontDescriptor\n"
1903 "/FontName /Q";
1904 int tag = fontDescriptor;
1905 for (int i = 0; i < 5; ++i) {
1906 s << (char)('A' + (tag % 26));
1907 tag /= 26;
1908 }
1909 s << '+' << postscriptName << "\n"
1910 "/Flags " << 4 << "\n"
1911 "/FontBBox ["
1912 << properties.boundingBox.x()*scale
1913 << -(properties.boundingBox.y() + properties.boundingBox.height())*scale
1914 << (properties.boundingBox.x() + properties.boundingBox.width())*scale
1915 << -properties.boundingBox.y()*scale << "]\n"
1916 "/ItalicAngle " << properties.italicAngle.toReal() << "\n"
1917 "/Ascent " << properties.ascent.toReal()*scale << "\n"
1918 "/Descent " << -properties.descent.toReal()*scale << "\n"
1919 "/CapHeight " << properties.capHeight.toReal()*scale << "\n"
1920 "/StemV " << properties.lineWidth.toReal()*scale << "\n"
1921 "/FontFile2 " << fontstream << "0 R\n"
1922 "/CIDSet " << cidset << "0 R\n"
1923 ">>\nendobj\n";
1924 write(descriptor);
1925 }
1926 {
1927 addXrefEntry(fontstream);
1928 QByteArray header;
1929 QPdf::ByteStream s(&header);
1930
1931 int length_object = requestObject();
1932 s << "<<\n"
1933 "/Length1 " << fontData.size() << "\n"
1934 "/Length " << length_object << "0 R\n";
1935 if (do_compress)
1936 s << "/Filter /FlateDecode\n";
1937 s << ">>\n"
1938 "stream\n";
1939 write(header);
1940 int len = writeCompressed(fontData);
1941 write("\nendstream\n"
1942 "endobj\n");
1943 addXrefEntry(length_object);
1944 xprintf("%d\n"
1945 "endobj\n", len);
1946 }
1947 {
1948 addXrefEntry(cidfont);
1949 QByteArray cid;
1950 QPdf::ByteStream s(&cid);
1951 s << "<< /Type /Font\n"
1952 "/Subtype /CIDFontType2\n"
1953 "/BaseFont /" << postscriptName << "\n"
1954 "/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>\n"
1955 "/FontDescriptor " << fontDescriptor << "0 R\n"
1956 "/CIDToGIDMap /Identity\n"
1957 << font->widthArray() <<
1958 ">>\n"
1959 "endobj\n";
1960 write(cid);
1961 }
1962 {
1963 addXrefEntry(toUnicode);
1964 QByteArray touc = font->createToUnicodeMap();
1965 xprintf("<< /Length %d >>\n"
1966 "stream\n", touc.length());
1967 write(touc);
1968 write("\nendstream\n"
1969 "endobj\n");
1970 }
1971 {
1972 addXrefEntry(fontObject);
1973 QByteArray font;
1974 QPdf::ByteStream s(&font);
1975 s << "<< /Type /Font\n"
1976 "/Subtype /Type0\n"
1977 "/BaseFont /" << postscriptName << "\n"
1978 "/Encoding /Identity-H\n"
1979 "/DescendantFonts [" << cidfont << "0 R]\n"
1980 "/ToUnicode " << toUnicode << "0 R"
1981 ">>\n"
1982 "endobj\n";
1983 write(font);
1984 }
1985 {
1986 QByteArray cidSetStream(font->nGlyphs() / 8 + 1, 0);
1987 int byteCounter = 0;
1988 int bitCounter = 0;
1989 for (int i = 0; i < font->nGlyphs(); ++i) {
1990 cidSetStream.data()[byteCounter] |= (1 << (7 - bitCounter));
1991
1992 bitCounter++;
1993 if (bitCounter == 8) {
1994 bitCounter = 0;
1995 byteCounter++;
1996 }
1997 }
1998
1999 addXrefEntry(cidset);
2000 xprintf("<<\n");
2001 xprintf("/Length %d\n", cidSetStream.size());
2002 xprintf(">>\n");
2003 xprintf("stream\n");
2004 write(cidSetStream);
2005 xprintf("\nendstream\n");
2006 xprintf("endobj\n");
2007 }
2008}
2009
2010qreal QPdfEnginePrivate::calcUserUnit() const
2011{
2012 // PDF standards < 1.6 support max 200x200in pages (no UserUnit)
2013 if (pdfVersion < QPdfEngine::Version_1_6)
2014 return 1.0;
2015
2016 const int maxLen = qMax(currentPage->pageSize.width(), currentPage->pageSize.height());
2017 if (maxLen <= 14400)
2018 return 1.0; // for pages up to 200x200in (14400x14400 units) use default scaling
2019
2020 // for larger pages, rescale units so we can have up to 381x381km
2021 return qMin(maxLen / 14400.0, 75000.0);
2022}
2023
2024void QPdfEnginePrivate::writeFonts()
2025{
2026 for (QHash<QFontEngine::FaceId, QFontSubset *>::iterator it = fonts.begin(); it != fonts.end(); ++it) {
2027 embedFont(*it);
2028 delete *it;
2029 }
2030 fonts.clear();
2031}
2032
2033void QPdfEnginePrivate::writePage()
2034{
2035 if (pages.empty())
2036 return;
2037
2038 *currentPage << "Q Q\n";
2039
2040 uint pageStream = requestObject();
2041 uint pageStreamLength = requestObject();
2042 uint resources = requestObject();
2043 uint annots = requestObject();
2044
2045 qreal userUnit = calcUserUnit();
2046
2047 addXrefEntry(pages.constLast());
2048 xprintf("<<\n"
2049 "/Type /Page\n"
2050 "/Parent %d 0 R\n"
2051 "/Contents %d 0 R\n"
2052 "/Resources %d 0 R\n"
2053 "/Annots %d 0 R\n"
2054 "/MediaBox [0 0 %s %s]\n",
2055 pageRoot, pageStream, resources, annots,
2056 // make sure we use the pagesize from when we started the page, since the user may have changed it
2057 QByteArray::number(currentPage->pageSize.width() / userUnit, 'f').constData(),
2058 QByteArray::number(currentPage->pageSize.height() / userUnit, 'f').constData());
2059
2060 if (pdfVersion >= QPdfEngine::Version_1_6)
2061 xprintf("/UserUnit %s\n", QByteArray::number(userUnit, 'f').constData());
2062
2063 xprintf(">>\n"
2064 "endobj\n");
2065
2066 addXrefEntry(resources);
2067 xprintf("<<\n"
2068 "/ColorSpace <<\n"
2069 "/PCSp %d 0 R\n"
2070 "/CSp /DeviceRGB\n"
2071 "/CSpg /DeviceGray\n"
2072 ">>\n"
2073 "/ExtGState <<\n"
2074 "/GSa %d 0 R\n",
2075 patternColorSpace, graphicsState);
2076
2077 for (int i = 0; i < currentPage->graphicStates.size(); ++i)
2078 xprintf("/GState%d %d 0 R\n", currentPage->graphicStates.at(i), currentPage->graphicStates.at(i));
2079 xprintf(">>\n");
2080
2081 xprintf("/Pattern <<\n");
2082 for (int i = 0; i < currentPage->patterns.size(); ++i)
2083 xprintf("/Pat%d %d 0 R\n", currentPage->patterns.at(i), currentPage->patterns.at(i));
2084 xprintf(">>\n");
2085
2086 xprintf("/Font <<\n");
2087 for (int i = 0; i < currentPage->fonts.size();++i)
2088 xprintf("/F%d %d 0 R\n", currentPage->fonts[i], currentPage->fonts[i]);
2089 xprintf(">>\n");
2090
2091 xprintf("/XObject <<\n");
2092 for (int i = 0; i<currentPage->images.size(); ++i) {
2093 xprintf("/Im%d %d 0 R\n", currentPage->images.at(i), currentPage->images.at(i));
2094 }
2095 xprintf(">>\n");
2096
2097 xprintf(">>\n"
2098 "endobj\n");
2099
2100 addXrefEntry(annots);
2101 xprintf("[ ");
2102 for (int i = 0; i<currentPage->annotations.size(); ++i) {
2103 xprintf("%d 0 R ", currentPage->annotations.at(i));
2104 }
2105 xprintf("]\nendobj\n");
2106
2107 addXrefEntry(pageStream);
2108 xprintf("<<\n"
2109 "/Length %d 0 R\n", pageStreamLength); // object number for stream length object
2110 if (do_compress)
2111 xprintf("/Filter /FlateDecode\n");
2112
2113 xprintf(">>\n");
2114 xprintf("stream\n");
2115 QIODevice *content = currentPage->stream();
2116 int len = writeCompressed(content);
2117 xprintf("\nendstream\n"
2118 "endobj\n");
2119
2120 addXrefEntry(pageStreamLength);
2121 xprintf("%d\nendobj\n",len);
2122}
2123
2124void QPdfEnginePrivate::writeTail()
2125{
2126 writePage();
2127 writeFonts();
2128 writePageRoot();
2129 writeAttachmentRoot();
2130
2131 addXrefEntry(xrefPositions.size(),false);
2132 xprintf("xref\n"
2133 "0 %d\n"
2134 "%010d 65535 f \n", xrefPositions.size()-1, xrefPositions[0]);
2135
2136 for (int i = 1; i < xrefPositions.size()-1; ++i)
2137 xprintf("%010d 00000 n \n", xrefPositions[i]);
2138
2139 {
2140 QByteArray trailer;
2141 QPdf::ByteStream s(&trailer);
2142
2143 s << "trailer\n"
2144 << "<<\n"
2145 << "/Size " << xrefPositions.size() - 1 << "\n"
2146 << "/Info " << info << "0 R\n"
2147 << "/Root " << catalog << "0 R\n";
2148
2149 if (pdfVersion == QPdfEngine::Version_A1b) {
2150 const QString uniqueId = QUuid::createUuid().toString();
2151 const QByteArray fileIdentifier = QCryptographicHash::hash(uniqueId.toLatin1(), QCryptographicHash::Md5).toHex();
2152 s << "/ID [ <" << fileIdentifier << "> <" << fileIdentifier << "> ]\n";
2153 }
2154
2155 s << ">>\n"
2156 << "startxref\n" << xrefPositions.constLast() << "\n"
2157 << "%%EOF\n";
2158
2159 write(trailer);
2160 }
2161}
2162
2163int QPdfEnginePrivate::addXrefEntry(int object, bool printostr)
2164{
2165 if (object < 0)
2166 object = requestObject();
2167
2168 if (object>=xrefPositions.size())
2169 xrefPositions.resize(object+1);
2170
2171 xrefPositions[object] = streampos;
2172 if (printostr)
2173 xprintf("%d 0 obj\n",object);
2174
2175 return object;
2176}
2177
2178void QPdfEnginePrivate::printString(const QString &string)
2179{
2180 if (string.isEmpty()) {
2181 write("()");
2182 return;
2183 }
2184
2185 // The 'text string' type in PDF is encoded either as PDFDocEncoding, or
2186 // Unicode UTF-16 with a Unicode byte order mark as the first character
2187 // (0xfeff), with the high-order byte first.
2188 QByteArray array("(\xfe\xff");
2189 const ushort *utf16 = string.utf16();
2190
2191 for (int i=0; i < string.size(); ++i) {
2192 char part[2] = {char((*(utf16 + i)) >> 8), char((*(utf16 + i)) & 0xff)};
2193 for(int j=0; j < 2; ++j) {
2194 if (part[j] == '(' || part[j] == ')' || part[j] == '\\')
2195 array.append('\\');
2196 array.append(part[j]);
2197 }
2198 }
2199 array.append(')');
2200 write(array);
2201}
2202
2203
2204void QPdfEnginePrivate::xprintf(const char* fmt, ...)
2205{
2206 if (!stream)
2207 return;
2208
2209 const int msize = 10000;
2210 char buf[msize];
2211
2212 va_list args;
2213 va_start(args, fmt);
2214 int bufsize = qvsnprintf(buf, msize, fmt, args);
2215 va_end(args);
2216
2217 if (Q_LIKELY(bufsize < msize)) {
2218 stream->writeRawData(buf, bufsize);
2219 } else {
2220 // Fallback for abnormal cases
2221 QScopedArrayPointer<char> tmpbuf(new char[bufsize + 1]);
2222 va_start(args, fmt);
2223 bufsize = qvsnprintf(tmpbuf.data(), bufsize + 1, fmt, args);
2224 va_end(args);
2225 stream->writeRawData(tmpbuf.data(), bufsize);
2226 }
2227 streampos += bufsize;
2228}
2229
2230int QPdfEnginePrivate::writeCompressed(QIODevice *dev)
2231{
2232#ifndef QT_NO_COMPRESS
2233 if (do_compress) {
2234 int size = QPdfPage::chunkSize();
2235 int sum = 0;
2236 ::z_stream zStruct;
2237 zStruct.zalloc = Z_NULL;
2238 zStruct.zfree = Z_NULL;
2239 zStruct.opaque = Z_NULL;
2240 if (::deflateInit(&zStruct, Z_DEFAULT_COMPRESSION) != Z_OK) {
2241 qWarning("QPdfStream::writeCompressed: Error in deflateInit()");
2242 return sum;
2243 }
2244 zStruct.avail_in = 0;
2245 QByteArray in, out;
2246 out.resize(size);
2247 while (!dev->atEnd() || zStruct.avail_in != 0) {
2248 if (zStruct.avail_in == 0) {
2249 in = dev->read(size);
2250 zStruct.avail_in = in.size();
2251 zStruct.next_in = reinterpret_cast<unsigned char*>(in.data());
2252 if (in.size() <= 0) {
2253 qWarning("QPdfStream::writeCompressed: Error in read()");
2254 ::deflateEnd(&zStruct);
2255 return sum;
2256 }
2257 }
2258 zStruct.next_out = reinterpret_cast<unsigned char*>(out.data());
2259 zStruct.avail_out = out.size();
2260 if (::deflate(&zStruct, 0) != Z_OK) {
2261 qWarning("QPdfStream::writeCompressed: Error in deflate()");
2262 ::deflateEnd(&zStruct);
2263 return sum;
2264 }
2265 int written = out.size() - zStruct.avail_out;
2266 stream->writeRawData(out.constData(), written);
2267 streampos += written;
2268 sum += written;
2269 }
2270 int ret;
2271 do {
2272 zStruct.next_out = reinterpret_cast<unsigned char*>(out.data());
2273 zStruct.avail_out = out.size();
2274 ret = ::deflate(&zStruct, Z_FINISH);
2275 if (ret != Z_OK && ret != Z_STREAM_END) {
2276 qWarning("QPdfStream::writeCompressed: Error in deflate()");
2277 ::deflateEnd(&zStruct);
2278 return sum;
2279 }
2280 int written = out.size() - zStruct.avail_out;
2281 stream->writeRawData(out.constData(), written);
2282 streampos += written;
2283 sum += written;
2284 } while (ret == Z_OK);
2285
2286 ::deflateEnd(&zStruct);
2287
2288 return sum;
2289 } else
2290#endif
2291 {
2292 QByteArray arr;
2293 int sum = 0;
2294 while (!dev->atEnd()) {
2295 arr = dev->read(QPdfPage::chunkSize());
2296 stream->writeRawData(arr.constData(), arr.size());
2297 streampos += arr.size();
2298 sum += arr.size();
2299 }
2300 return sum;
2301 }
2302}
2303
2304int QPdfEnginePrivate::writeCompressed(const char *src, int len)
2305{
2306#ifndef QT_NO_COMPRESS
2307 if(do_compress) {
2308 uLongf destLen = len + len/100 + 13; // zlib requirement
2309 Bytef* dest = new Bytef[destLen];
2310 if (Z_OK == ::compress(dest, &destLen, (const Bytef*) src, (uLongf)len)) {
2311 stream->writeRawData((const char*)dest, destLen);
2312 } else {
2313 qWarning("QPdfStream::writeCompressed: Error in compress()");
2314 destLen = 0;
2315 }
2316 delete [] dest;
2317 len = destLen;
2318 } else
2319#endif
2320 {
2321 stream->writeRawData(src,len);
2322 }
2323 streampos += len;
2324 return len;
2325}
2326
2327int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, int depth,
2328 int maskObject, int softMaskObject, bool dct, bool isMono)
2329{
2330 int image = addXrefEntry(-1);
2331 xprintf("<<\n"
2332 "/Type /XObject\n"
2333 "/Subtype /Image\n"
2334 "/Width %d\n"
2335 "/Height %d\n", width, height);
2336
2337 if (depth == 1) {
2338 if (!isMono) {
2339 xprintf("/ImageMask true\n"
2340 "/Decode [1 0]\n");
2341 } else {
2342 xprintf("/BitsPerComponent 1\n"
2343 "/ColorSpace /DeviceGray\n");
2344 }
2345 } else {
2346 xprintf("/BitsPerComponent 8\n"
2347 "/ColorSpace %s\n", (depth == 32) ? "/DeviceRGB" : "/DeviceGray");
2348 }
2349 if (maskObject > 0)
2350 xprintf("/Mask %d 0 R\n", maskObject);
2351 if (softMaskObject > 0)
2352 xprintf("/SMask %d 0 R\n", softMaskObject);
2353
2354 int lenobj = requestObject();
2355 xprintf("/Length %d 0 R\n", lenobj);
2356 if (interpolateImages)
2357 xprintf("/Interpolate true\n");
2358 int len = 0;
2359 if (dct) {
2360 //qDebug("DCT");
2361 xprintf("/Filter /DCTDecode\n>>\nstream\n");
2362 write(data);
2363 len = data.length();
2364 } else {
2365 if (do_compress)
2366 xprintf("/Filter /FlateDecode\n>>\nstream\n");
2367 else
2368 xprintf(">>\nstream\n");
2369 len = writeCompressed(data);
2370 }
2371 xprintf("\nendstream\n"
2372 "endobj\n");
2373 addXrefEntry(lenobj);
2374 xprintf("%d\n"
2375 "endobj\n", len);
2376 return image;
2377}
2378
2379struct QGradientBound {
2380 qreal start;
2381 qreal stop;
2382 int function;
2383 bool reverse;
2384};
2385Q_DECLARE_TYPEINFO(QGradientBound, Q_PRIMITIVE_TYPE);
2386
2387int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha)
2388{
2389 QGradientStops stops = gradient->stops();
2390 if (stops.isEmpty()) {
2391 stops << QGradientStop(0, Qt::black);
2392 stops << QGradientStop(1, Qt::white);
2393 }
2394 if (stops.at(0).first > 0)
2395 stops.prepend(QGradientStop(0, stops.at(0).second));
2396 if (stops.at(stops.size() - 1).first < 1)
2397 stops.append(QGradientStop(1, stops.at(stops.size() - 1).second));
2398
2399 QList<int> functions;
2400 const int numStops = stops.size();
2401 functions.reserve(numStops - 1);
2402 for (int i = 0; i < numStops - 1; ++i) {
2403 int f = addXrefEntry(-1);
2404 QByteArray data;
2405 QPdf::ByteStream s(&data);
2406 s << "<<\n"
2407 "/FunctionType 2\n"
2408 "/Domain [0 1]\n"
2409 "/N 1\n";
2410 if (alpha) {
2411 s << "/C0 [" << stops.at(i).second.alphaF() << "]\n"
2412 "/C1 [" << stops.at(i + 1).second.alphaF() << "]\n";
2413 } else {
2414 s << "/C0 [" << stops.at(i).second.redF() << stops.at(i).second.greenF() << stops.at(i).second.blueF() << "]\n"
2415 "/C1 [" << stops.at(i + 1).second.redF() << stops.at(i + 1).second.greenF() << stops.at(i + 1).second.blueF() << "]\n";
2416 }
2417 s << ">>\n"
2418 "endobj\n";
2419 write(data);
2420 functions << f;
2421 }
2422
2423 QList<QGradientBound> gradientBounds;
2424 gradientBounds.reserve((to - from) * (numStops - 1));
2425
2426 for (int step = from; step < to; ++step) {
2427 if (reflect && step % 2) {
2428 for (int i = numStops - 1; i > 0; --i) {
2429 QGradientBound b;
2430 b.start = step + 1 - qBound(qreal(0.), stops.at(i).first, qreal(1.));
2431 b.stop = step + 1 - qBound(qreal(0.), stops.at(i - 1).first, qreal(1.));
2432 b.function = functions.at(i - 1);
2433 b.reverse = true;
2434 gradientBounds << b;
2435 }
2436 } else {
2437 for (int i = 0; i < numStops - 1; ++i) {
2438 QGradientBound b;
2439 b.start = step + qBound(qreal(0.), stops.at(i).first, qreal(1.));
2440 b.stop = step + qBound(qreal(0.), stops.at(i + 1).first, qreal(1.));
2441 b.function = functions.at(i);
2442 b.reverse = false;
2443 gradientBounds << b;
2444 }
2445 }
2446 }
2447
2448 // normalize bounds to [0..1]
2449 qreal bstart = gradientBounds.at(0).start;
2450 qreal bend = gradientBounds.at(gradientBounds.size() - 1).stop;
2451 qreal norm = 1./(bend - bstart);
2452 for (int i = 0; i < gradientBounds.size(); ++i) {
2453 gradientBounds[i].start = (gradientBounds[i].start - bstart)*norm;
2454 gradientBounds[i].stop = (gradientBounds[i].stop - bstart)*norm;
2455 }
2456
2457 int function;
2458 if (gradientBounds.size() > 1) {
2459 function = addXrefEntry(-1);
2460 QByteArray data;
2461 QPdf::ByteStream s(&data);
2462 s << "<<\n"
2463 "/FunctionType 3\n"
2464 "/Domain [0 1]\n"
2465 "/Bounds [";
2466 for (int i = 1; i < gradientBounds.size(); ++i)
2467 s << gradientBounds.at(i).start;
2468 s << "]\n"
2469 "/Encode [";
2470 for (int i = 0; i < gradientBounds.size(); ++i)
2471 s << (gradientBounds.at(i).reverse ? "1 0 " : "0 1 ");
2472 s << "]\n"
2473 "/Functions [";
2474 for (int i = 0; i < gradientBounds.size(); ++i)
2475 s << gradientBounds.at(i).function << "0 R ";
2476 s << "]\n"
2477 ">>\n"
2478 "endobj\n";
2479 write(data);
2480 } else {
2481 function = functions.at(0);
2482 }
2483 return function;
2484}
2485
2486int QPdfEnginePrivate::generateLinearGradientShader(const QLinearGradient *gradient, const QTransform &matrix, bool alpha)
2487{
2488 QPointF start = gradient->start();
2489 QPointF stop = gradient->finalStop();
2490 QPointF offset = stop - start;
2491 Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode);
2492
2493 int from = 0;
2494 int to = 1;
2495 bool reflect = false;
2496 switch (gradient->spread()) {
2497 case QGradient::PadSpread:
2498 break;
2499 case QGradient::ReflectSpread:
2500 reflect = true;
2501 Q_FALLTHROUGH();
2502 case QGradient::RepeatSpread: {
2503 // calculate required bounds
2504 QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2505 QTransform inv = matrix.inverted();
2506 QPointF page_rect[4] = { inv.map(pageRect.topLeft()),
2507 inv.map(pageRect.topRight()),
2508 inv.map(pageRect.bottomLeft()),
2509 inv.map(pageRect.bottomRight()) };
2510
2511 qreal length = offset.x()*offset.x() + offset.y()*offset.y();
2512
2513 // find the max and min values in offset and orth direction that are needed to cover
2514 // the whole page
2515 from = INT_MAX;
2516 to = INT_MIN;
2517 for (int i = 0; i < 4; ++i) {
2518 qreal off = ((page_rect[i].x() - start.x()) * offset.x() + (page_rect[i].y() - start.y()) * offset.y())/length;
2519 from = qMin(from, qFloor(off));
2520 to = qMax(to, qCeil(off));
2521 }
2522
2523 stop = start + to * offset;
2524 start = start + from * offset;\
2525 break;
2526 }
2527 }
2528
2529 int function = createShadingFunction(gradient, from, to, reflect, alpha);
2530
2531 QByteArray shader;
2532 QPdf::ByteStream s(&shader);
2533 s << "<<\n"
2534 "/ShadingType 2\n"
2535 "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") <<
2536 "/AntiAlias true\n"
2537 "/Coords [" << start.x() << start.y() << stop.x() << stop.y() << "]\n"
2538 "/Extend [true true]\n"
2539 "/Function " << function << "0 R\n"
2540 ">>\n"
2541 "endobj\n";
2542 int shaderObject = addXrefEntry(-1);
2543 write(shader);
2544 return shaderObject;
2545}
2546
2547int QPdfEnginePrivate::generateRadialGradientShader(const QRadialGradient *gradient, const QTransform &matrix, bool alpha)
2548{
2549 QPointF p1 = gradient->center();
2550 qreal r1 = gradient->centerRadius();
2551 QPointF p0 = gradient->focalPoint();
2552 qreal r0 = gradient->focalRadius();
2553
2554 Q_ASSERT(gradient->coordinateMode() == QGradient::LogicalMode);
2555
2556 int from = 0;
2557 int to = 1;
2558 bool reflect = false;
2559 switch (gradient->spread()) {
2560 case QGradient::PadSpread:
2561 break;
2562 case QGradient::ReflectSpread:
2563 reflect = true;
2564 Q_FALLTHROUGH();
2565 case QGradient::RepeatSpread: {
2566 Q_ASSERT(qFuzzyIsNull(r0)); // QPainter emulates if this is not 0
2567
2568 QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2569 QTransform inv = matrix.inverted();
2570 QPointF page_rect[4] = { inv.map(pageRect.topLeft()),
2571 inv.map(pageRect.topRight()),
2572 inv.map(pageRect.bottomLeft()),
2573 inv.map(pageRect.bottomRight()) };
2574
2575 // increase to until the whole page fits into it
2576 bool done = false;
2577 while (!done) {
2578 QPointF center = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y()));
2579 double radius = r0 + to*(r1 - r0);
2580 double r2 = radius*radius;
2581 done = true;
2582 for (int i = 0; i < 4; ++i) {
2583 QPointF off = page_rect[i] - center;
2584 if (off.x()*off.x() + off.y()*off.y() > r2) {
2585 ++to;
2586 done = false;
2587 break;
2588 }
2589 }
2590 }
2591 p1 = QPointF(p0.x() + to*(p1.x() - p0.x()), p0.y() + to*(p1.y() - p0.y()));
2592 r1 = r0 + to*(r1 - r0);
2593 break;
2594 }
2595 }
2596
2597 int function = createShadingFunction(gradient, from, to, reflect, alpha);
2598
2599 QByteArray shader;
2600 QPdf::ByteStream s(&shader);
2601 s << "<<\n"
2602 "/ShadingType 3\n"
2603 "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") <<
2604 "/AntiAlias true\n"
2605 "/Domain [0 1]\n"
2606 "/Coords [" << p0.x() << p0.y() << r0 << p1.x() << p1.y() << r1 << "]\n"
2607 "/Extend [true true]\n"
2608 "/Function " << function << "0 R\n"
2609 ">>\n"
2610 "endobj\n";
2611 int shaderObject = addXrefEntry(-1);
2612 write(shader);
2613 return shaderObject;
2614}
2615
2616int QPdfEnginePrivate::generateGradientShader(const QGradient *gradient, const QTransform &matrix, bool alpha)
2617{
2618 switch (gradient->type()) {
2619 case QGradient::LinearGradient:
2620 return generateLinearGradientShader(static_cast<const QLinearGradient *>(gradient), matrix, alpha);
2621 case QGradient::RadialGradient:
2622 return generateRadialGradientShader(static_cast<const QRadialGradient *>(gradient), matrix, alpha);
2623 case QGradient::ConicalGradient:
2624 Q_UNIMPLEMENTED(); // ### Implement me!
2625 break;
2626 case QGradient::NoGradient:
2627 break;
2628 }
2629 return 0;
2630}
2631
2632int QPdfEnginePrivate::gradientBrush(const QBrush &b, const QTransform &matrix, int *gStateObject)
2633{
2634 const QGradient *gradient = b.gradient();
2635
2636 if (!gradient || gradient->coordinateMode() != QGradient::LogicalMode)
2637 return 0;
2638
2639 QRectF pageRect = m_pageLayout.fullRectPixels(resolution);
2640
2641 QTransform m = b.transform() * matrix;
2642 int shaderObject = generateGradientShader(gradient, m);
2643
2644 QByteArray str;
2645 QPdf::ByteStream s(&str);
2646 s << "<<\n"
2647 "/Type /Pattern\n"
2648 "/PatternType 2\n"
2649 "/Shading " << shaderObject << "0 R\n"
2650 "/Matrix ["
2651 << m.m11()
2652 << m.m12()
2653 << m.m21()
2654 << m.m22()
2655 << m.dx()
2656 << m.dy() << "]\n";
2657 s << ">>\n"
2658 "endobj\n";
2659
2660 int patternObj = addXrefEntry(-1);
2661 write(str);
2662 currentPage->patterns.append(patternObj);
2663
2664 if (!b.isOpaque()) {
2665 bool ca = true;
2666 QGradientStops stops = gradient->stops();
2667 int a = stops.at(0).second.alpha();
2668 for (int i = 1; i < stops.size(); ++i) {
2669 if (stops.at(i).second.alpha() != a) {
2670 ca = false;
2671 break;
2672 }
2673 }
2674 if (ca) {
2675 *gStateObject = addConstantAlphaObject(stops.at(0).second.alpha());
2676 } else {
2677 int alphaShaderObject = generateGradientShader(gradient, m, true);
2678
2679 QByteArray content;
2680 QPdf::ByteStream c(&content);
2681 c << "/Shader" << alphaShaderObject << "sh\n";
2682
2683 QByteArray form;
2684 QPdf::ByteStream f(&form);
2685 f << "<<\n"
2686 "/Type /XObject\n"
2687 "/Subtype /Form\n"
2688 "/BBox [0 0 " << pageRect.width() << pageRect.height() << "]\n"
2689 "/Group <</S /Transparency >>\n"
2690 "/Resources <<\n"
2691 "/Shading << /Shader" << alphaShaderObject << alphaShaderObject << "0 R >>\n"
2692 ">>\n";
2693
2694 f << "/Length " << content.length() << "\n"
2695 ">>\n"
2696 "stream\n"
2697 << content
2698 << "\nendstream\n"
2699 "endobj\n";
2700
2701 int softMaskFormObject = addXrefEntry(-1);
2702 write(form);
2703 *gStateObject = addXrefEntry(-1);
2704 xprintf("<< /SMask << /S /Alpha /G %d 0 R >> >>\n"
2705 "endobj\n", softMaskFormObject);
2706 currentPage->graphicStates.append(*gStateObject);
2707 }
2708 }
2709
2710 return patternObj;
2711}
2712
2713int QPdfEnginePrivate::addConstantAlphaObject(int brushAlpha, int penAlpha)
2714{
2715 if (brushAlpha == 255 && penAlpha == 255)
2716 return 0;
2717 uint object = alphaCache.value(QPair<uint, uint>(brushAlpha, penAlpha), 0);
2718 if (!object) {
2719 object = addXrefEntry(-1);
2720 QByteArray alphaDef;
2721 QPdf::ByteStream s(&alphaDef);
2722 s << "<<\n/ca " << (brushAlpha/qreal(255.)) << '\n';
2723 s << "/CA " << (penAlpha/qreal(255.)) << "\n>>";
2724 xprintf("%s\nendobj\n", alphaDef.constData());
2725 alphaCache.insert(QPair<uint, uint>(brushAlpha, penAlpha), object);
2726 }
2727 if (currentPage->graphicStates.indexOf(object) < 0)
2728 currentPage->graphicStates.append(object);
2729
2730 return object;
2731}
2732
2733
2734int QPdfEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor, int *gStateObject)
2735{
2736 Q_Q(QPdfEngine);
2737
2738 int paintType = 2; // Uncolored tiling
2739 int w = 8;
2740 int h = 8;
2741
2742 *specifyColor = true;
2743 *gStateObject = 0;
2744
2745 QTransform matrix = m;
2746 matrix.translate(brushOrigin.x(), brushOrigin.y());
2747 matrix = matrix * pageMatrix();
2748 //qDebug() << brushOrigin << matrix;
2749
2750 Qt::BrushStyle style = brush.style();
2751 if (style == Qt::LinearGradientPattern || style == Qt::RadialGradientPattern) {// && style <= Qt::ConicalGradientPattern) {
2752 *specifyColor = false;
2753 return gradientBrush(brush, matrix, gStateObject);
2754 }
2755
2756 if ((!brush.isOpaque() && brush.style() < Qt::LinearGradientPattern) || opacity != 1.0)
2757 *gStateObject = addConstantAlphaObject(qRound(brush.color().alpha() * opacity),
2758 qRound(pen.color().alpha() * opacity));
2759
2760 int imageObject = -1;
2761 QByteArray pattern = QPdf::patternForBrush(brush);
2762 if (pattern.isEmpty()) {
2763 if (brush.style() != Qt::TexturePattern)
2764 return 0;
2765 QImage image = brush.textureImage();
2766 bool bitmap = true;
2767 const bool lossless = q->painter()->testRenderHint(QPainter::LosslessImageRendering);
2768 imageObject = addImage(image, &bitmap, lossless, image.cacheKey());
2769 if (imageObject != -1) {
2770 QImage::Format f = image.format();
2771 if (f != QImage::Format_MonoLSB && f != QImage::Format_Mono) {
2772 paintType = 1; // Colored tiling
2773 *specifyColor = false;
2774 }
2775 w = image.width();
2776 h = image.height();
2777 QTransform m(w, 0, 0, -h, 0, h);
2778 QPdf::ByteStream s(&pattern);
2779 s << QPdf::generateMatrix(m);
2780 s << "/Im" << imageObject << " Do\n";
2781 }
2782 }
2783
2784 QByteArray str;
2785 QPdf::ByteStream s(&str);
2786 s << "<<\n"
2787 "/Type /Pattern\n"
2788 "/PatternType 1\n"
2789 "/PaintType " << paintType << "\n"
2790 "/TilingType 1\n"
2791 "/BBox [0 0 " << w << h << "]\n"
2792 "/XStep " << w << "\n"
2793 "/YStep " << h << "\n"
2794 "/Matrix ["
2795 << matrix.m11()
2796 << matrix.m12()
2797 << matrix.m21()
2798 << matrix.m22()
2799 << matrix.dx()
2800 << matrix.dy() << "]\n"
2801 "/Resources \n<< "; // open resource tree
2802 if (imageObject > 0) {
2803 s << "/XObject << /Im" << imageObject << ' ' << imageObject << "0 R >> ";
2804 }
2805 s << ">>\n"
2806 "/Length " << pattern.length() << "\n"
2807 ">>\n"
2808 "stream\n"
2809 << pattern
2810 << "\nendstream\n"
2811 "endobj\n";
2812
2813 int patternObj = addXrefEntry(-1);
2814 write(str);
2815 currentPage->patterns.append(patternObj);
2816 return patternObj;
2817}
2818
2819static inline bool is_monochrome(const QList<QRgb> &colorTable)
2820{
2821 return colorTable.size() == 2
2822 && colorTable.at(0) == QColor(Qt::black).rgba()
2823 && colorTable.at(1) == QColor(Qt::white).rgba()
2824 ;
2825}
2826
2827/*!
2828 * Adds an image to the pdf and return the pdf-object id. Returns -1 if adding the image failed.
2829 */
2830int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless, qint64 serial_no)
2831{
2832 if (img.isNull())
2833 return -1;
2834
2835 int object = imageCache.value(serial_no);
2836 if(object)
2837 return object;
2838
2839 QImage image = img;
2840 QImage::Format format = image.format();
2841
2842 if (pdfVersion == QPdfEngine::Version_A1b) {
2843 if (image.hasAlphaChannel()) {
2844 // transparent images are not allowed in PDF/A-1b, so we convert it to
2845 // a format without alpha channel first
2846
2847 QImage alphaLessImage(image.width(), image.height(), QImage::Format_RGB32);
2848 alphaLessImage.fill(Qt::white);
2849
2850 QPainter p(&alphaLessImage);
2851 p.drawImage(0, 0, image);
2852
2853 image = alphaLessImage;
2854 format = image.format();
2855 }
2856 }
2857
2858 if (image.depth() == 1 && *bitmap && is_monochrome(img.colorTable())) {
2859 if (format == QImage::Format_MonoLSB)
2860 image = image.convertToFormat(QImage::Format_Mono);
2861 format = QImage::Format_Mono;
2862 } else {
2863 *bitmap = false;
2864 if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) {
2865 image = image.convertToFormat(QImage::Format_ARGB32);
2866 format = QImage::Format_ARGB32;
2867 }
2868 }
2869
2870 int w = image.width();
2871 int h = image.height();
2872 int d = image.depth();
2873
2874 if (format == QImage::Format_Mono) {
2875 int bytesPerLine = (w + 7) >> 3;
2876 QByteArray data;
2877 data.resize(bytesPerLine * h);
2878 char *rawdata = data.data();
2879 for (int y = 0; y < h; ++y) {
2880 memcpy(rawdata, image.constScanLine(y), bytesPerLine);
2881 rawdata += bytesPerLine;
2882 }
2883 object = writeImage(data, w, h, d, 0, 0, false, is_monochrome(img.colorTable()));
2884 } else {
2885 QByteArray softMaskData;
2886 bool dct = false;
2887 QByteArray imageData;
2888 bool hasAlpha = false;
2889 bool hasMask = false;
2890
2891 if (QImageWriter::supportedImageFormats().contains("jpeg") && !grayscale && !lossless) {
2892 QBuffer buffer(&imageData);
2893 QImageWriter writer(&buffer, "jpeg");
2894 writer.setQuality(94);
2895 writer.write(image);
2896 dct = true;
2897
2898 if (format != QImage::Format_RGB32) {
2899 softMaskData.resize(w * h);
2900 uchar *sdata = (uchar *)softMaskData.data();
2901 for (int y = 0; y < h; ++y) {
2902 const QRgb *rgb = (const QRgb *)image.constScanLine(y);
2903 for (int x = 0; x < w; ++x) {
2904 uchar alpha = qAlpha(*rgb);
2905 *sdata++ = alpha;
2906 hasMask |= (alpha < 255);
2907 hasAlpha |= (alpha != 0 && alpha != 255);
2908 ++rgb;
2909 }
2910 }
2911 }
2912 } else {
2913 imageData.resize(grayscale ? w * h : 3 * w * h);
2914 uchar *data = (uchar *)imageData.data();
2915 softMaskData.resize(w * h);
2916 uchar *sdata = (uchar *)softMaskData.data();
2917 for (int y = 0; y < h; ++y) {
2918 const QRgb *rgb = (const QRgb *)image.constScanLine(y);
2919 if (grayscale) {
2920 for (int x = 0; x < w; ++x) {
2921 *(data++) = qGray(*rgb);
2922 uchar alpha = qAlpha(*rgb);
2923 *sdata++ = alpha;
2924 hasMask |= (alpha < 255);
2925 hasAlpha |= (alpha != 0 && alpha != 255);
2926 ++rgb;
2927 }
2928 } else {
2929 for (int x = 0; x < w; ++x) {
2930 *(data++) = qRed(*rgb);
2931 *(data++) = qGreen(*rgb);
2932 *(data++) = qBlue(*rgb);
2933 uchar alpha = qAlpha(*rgb);
2934 *sdata++ = alpha;
2935 hasMask |= (alpha < 255);
2936 hasAlpha |= (alpha != 0 && alpha != 255);
2937 ++rgb;
2938 }
2939 }
2940 }
2941 if (format == QImage::Format_RGB32)
2942 hasAlpha = hasMask = false;
2943 }
2944 int maskObject = 0;
2945 int softMaskObject = 0;
2946 if (hasAlpha) {
2947 softMaskObject = writeImage(softMaskData, w, h, 8, 0, 0);
2948 } else if (hasMask) {
2949 // dither the soft mask to 1bit and add it. This also helps PDF viewers
2950 // without transparency support
2951 int bytesPerLine = (w + 7) >> 3;
2952 QByteArray mask(bytesPerLine * h, 0);
2953 uchar *mdata = (uchar *)mask.data();
2954 const uchar *sdata = (const uchar *)softMaskData.constData();
2955 for (int y = 0; y < h; ++y) {
2956 for (int x = 0; x < w; ++x) {
2957 if (*sdata)
2958 mdata[x>>3] |= (0x80 >> (x&7));
2959 ++sdata;
2960 }
2961 mdata += bytesPerLine;
2962 }
2963 maskObject = writeImage(mask, w, h, 1, 0, 0);
2964 }
2965 object = writeImage(imageData, w, h, grayscale ? 8 : 32,
2966 maskObject, softMaskObject, dct);
2967 }
2968 imageCache.insert(serial_no, object);
2969 return object;
2970}
2971
2972void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti)
2973{
2974 Q_Q(QPdfEngine);
2975
2976 if (ti.charFormat.isAnchor()) {
2977 qreal size = ti.fontEngine->fontDef.pixelSize;
2978 int synthesized = ti.fontEngine->synthesized();
2979 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
2980 Q_ASSERT(stretch > qreal(0));
2981
2982 QTransform trans;
2983 // Build text rendering matrix (Trm). We need it to map the text area to user
2984 // space units on the PDF page.
2985 trans = QTransform(size*stretch, 0, 0, size, 0, 0);
2986 // Apply text matrix (Tm).
2987 trans *= QTransform(1,0,0,-1,p.x(),p.y());
2988 // Apply page displacement (Identity for first page).
2989 trans *= stroker.matrix;
2990 // Apply Current Transformation Matrix (CTM)
2991 trans *= pageMatrix();
2992 qreal x1, y1, x2, y2;
2993 trans.map(0, 0, &x1, &y1);
2994 trans.map(ti.width.toReal()/size, (ti.ascent.toReal()-ti.descent.toReal())/size, &x2, &y2);
2995
2996 uint annot = addXrefEntry(-1);
2997 QByteArray x1s, y1s, x2s, y2s;
2998 x1s.setNum(static_cast<double>(x1), 'f');
2999 y1s.setNum(static_cast<double>(y1), 'f');
3000 x2s.setNum(static_cast<double>(x2), 'f');
3001 y2s.setNum(static_cast<double>(y2), 'f');
3002 QByteArray rectData = x1s + ' ' + y1s + ' ' + x2s + ' ' + y2s;
3003 xprintf("<<\n/Type /Annot\n/Subtype /Link\n");
3004
3005 if (pdfVersion == QPdfEngine::Version_A1b)
3006 xprintf("/F 4\n"); // enable print flag, disable all other
3007
3008 xprintf("/Rect [");
3009 xprintf(rectData.constData());
3010#ifdef Q_DEBUG_PDF_LINKS
3011 xprintf("]\n/Border [16 16 1]\n/A <<\n");
3012#else
3013 xprintf("]\n/Border [0 0 0]\n/A <<\n");
3014#endif
3015 xprintf("/Type /Action\n/S /URI\n/URI (%s)\n",
3016 ti.charFormat.anchorHref().toLatin1().constData());
3017 xprintf(">>\n>>\n");
3018 xprintf("endobj\n");
3019
3020 if (!currentPage->annotations.contains(annot)) {
3021 currentPage->annotations.append(annot);
3022 }
3023 }
3024
3025 QFontEngine *fe = ti.fontEngine;
3026
3027 QFontEngine::FaceId face_id = fe->faceId();
3028 bool noEmbed = false;
3029 if (!embedFonts
3030 || face_id.filename.isEmpty()
3031 || fe->fsType & 0x200 /* bitmap embedding only */
3032 || fe->fsType == 2 /* no embedding allowed */) {
3033 *currentPage << "Q\n";
3034 q->QPaintEngine::drawTextItem(p, ti);
3035 *currentPage << "q\n";
3036 if (face_id.filename.isEmpty())
3037 return;
3038 noEmbed = true;
3039 }
3040
3041 QFontSubset *font = fonts.value(face_id, nullptr);
3042 if (!font) {
3043 font = new QFontSubset(fe, requestObject());
3044 font->noEmbed = noEmbed;
3045 }
3046 fonts.insert(face_id, font);
3047
3048 if (!currentPage->fonts.contains(font->object_id))
3049 currentPage->fonts.append(font->object_id);
3050
3051 qreal size = ti.fontEngine->fontDef.pixelSize;
3052
3053 QVarLengthArray<glyph_t> glyphs;
3054 QVarLengthArray<QFixedPoint> positions;
3055 QTransform m = QTransform::fromTranslate(p.x(), p.y());
3056 ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags,
3057 glyphs, positions);
3058 if (glyphs.size() == 0)
3059 return;
3060 int synthesized = ti.fontEngine->synthesized();
3061 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
3062 Q_ASSERT(stretch > qreal(0));
3063
3064 *currentPage << "BT\n"
3065 << "/F" << font->object_id << size << "Tf "
3066 << stretch << (synthesized & QFontEngine::SynthesizedItalic
3067 ? "0 .3 -1 0 0 Tm\n"
3068 : "0 0 -1 0 0 Tm\n");
3069
3070
3071#if 0
3072 // #### implement actual text for complex languages
3073 const unsigned short *logClusters = ti.logClusters;
3074 int pos = 0;
3075 do {
3076 int end = pos + 1;
3077 while (end < ti.num_chars && logClusters[end] == logClusters[pos])
3078 ++end;
3079 *currentPage << "/Span << /ActualText <FEFF";
3080 for (int i = pos; i < end; ++i) {
3081 s << toHex((ushort)ti.chars[i].unicode(), buf);
3082 }
3083 *currentPage << "> >>\n"
3084 "BDC\n"
3085 "<";
3086 int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end];
3087 for (int gs = logClusters[pos]; gs < ge; ++gs)
3088 *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf);
3089 *currentPage << "> Tj\n"
3090 "EMC\n";
3091 pos = end;
3092 } while (pos < ti.num_chars);
3093#else
3094 qreal last_x = 0.;
3095 qreal last_y = 0.;
3096 for (int i = 0; i < glyphs.size(); ++i) {
3097 qreal x = positions[i].x.toReal();
3098 qreal y = positions[i].y.toReal();
3099 if (synthesized & QFontEngine::SynthesizedItalic)
3100 x += .3*y;
3101 x /= stretch;
3102 char buf[5];
3103 int g = font->addGlyph(glyphs[i]);
3104 *currentPage << x - last_x << last_y - y << "Td <"
3105 << QPdf::toHex((ushort)g, buf) << "> Tj\n";
3106 last_x = x;
3107 last_y = y;
3108 }
3109 if (synthesized & QFontEngine::SynthesizedBold) {
3110 *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic
3111 ? "0 .3 -1 0 0 Tm\n"
3112 : "0 0 -1 0 0 Tm\n");
3113 *currentPage << "/Span << /ActualText <> >> BDC\n";
3114 last_x = 0.5*fe->lineThickness().toReal();
3115 last_y = 0.;
3116 for (int i = 0; i < glyphs.size(); ++i) {
3117 qreal x = positions[i].x.toReal();
3118 qreal y = positions[i].y.toReal();
3119 if (synthesized & QFontEngine::SynthesizedItalic)
3120 x += .3*y;
3121 x /= stretch;
3122 char buf[5];
3123 int g = font->addGlyph(glyphs[i]);
3124 *currentPage << x - last_x << last_y - y << "Td <"
3125 << QPdf::toHex((ushort)g, buf) << "> Tj\n";
3126 last_x = x;
3127 last_y = y;
3128 }
3129 *currentPage << "EMC\n";
3130 }
3131#endif
3132
3133 *currentPage << "ET\n";
3134}
3135
3136QTransform QPdfEnginePrivate::pageMatrix() const
3137{
3138 qreal userUnit = calcUserUnit();
3139 qreal scale = 72. / userUnit / resolution;
3140 QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, m_pageLayout.fullRectPoints().height() / userUnit);
3141 if (m_pageLayout.mode() != QPageLayout::FullPageMode) {
3142 QRect r = m_pageLayout.paintRectPixels(resolution);
3143 tmp.translate(r.left(), r.top());
3144 }
3145 return tmp;
3146}
3147
3148void QPdfEnginePrivate::newPage()
3149{
3150 if (currentPage && currentPage->pageSize.isEmpty())
3151 currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3152 writePage();
3153
3154 delete currentPage;
3155 currentPage = new QPdfPage;
3156 currentPage->pageSize = m_pageLayout.fullRectPoints().size();
3157 stroker.stream = currentPage;
3158 pages.append(requestObject());
3159
3160 *currentPage << "/GSa gs /CSp cs /CSp CS\n"
3161 << QPdf::generateMatrix(pageMatrix())
3162 << "q q\n";
3163}
3164
3165QT_END_NAMESPACE
3166
3167#endif // QT_NO_PDF
3168