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

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