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