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