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