source: trunk/src/gui/painting/qpdf.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 59.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41#include "qplatformdefs.h"
42#include <qdebug.h>
43#include "qpdf_p.h"
44#include <qfile.h>
45#include <qtemporaryfile.h>
46#include <private/qmath_p.h>
47#include "private/qcups_p.h"
48#include "qprinterinfo.h"
49#include <qnumeric.h>
50
51#ifdef Q_OS_UNIX
52#include "private/qcore_unix_p.h" // overrides QT_OPEN
53#endif
54
55QT_BEGIN_NAMESPACE
56
57Q_GUI_EXPORT extern int qt_defaultDpi();
58
59#ifndef QT_NO_PRINTER
60
61extern QSizeF qt_paperSizeToQSizeF(QPrinter::PaperSize size);
62
63/* also adds a space at the end of the number */
64const char *qt_real_to_string(qreal val, char *buf) {
65 const char *ret = buf;
66
67 if (qIsNaN(val)) {
68 *(buf++) = '0';
69 *(buf++) = ' ';
70 *buf = 0;
71 return ret;
72 }
73
74 if (val < 0) {
75 *(buf++) = '-';
76 val = -val;
77 }
78 unsigned int ival = (unsigned int) val;
79 qreal frac = val - (qreal)ival;
80
81 int ifrac = (int)(frac * 1000000000);
82 if (ifrac == 1000000000) {
83 ++ival;
84 ifrac = 0;
85 }
86 char output[256];
87 int i = 0;
88 while (ival) {
89 output[i] = '0' + (ival % 10);
90 ++i;
91 ival /= 10;
92 }
93 int fact = 100000000;
94 if (i == 0) {
95 *(buf++) = '0';
96 } else {
97 while (i) {
98 *(buf++) = output[--i];
99 fact /= 10;
100 ifrac /= 10;
101 }
102 }
103
104 if (ifrac) {
105 *(buf++) = '.';
106 while (fact) {
107 *(buf++) = '0' + ((ifrac/fact) % 10);
108 fact /= 10;
109 }
110 }
111 *(buf++) = ' ';
112 *buf = 0;
113 return ret;
114}
115
116const char *qt_int_to_string(int val, char *buf) {
117 const char *ret = buf;
118 if (val < 0) {
119 *(buf++) = '-';
120 val = -val;
121 }
122 char output[256];
123 int i = 0;
124 while (val) {
125 output[i] = '0' + (val % 10);
126 ++i;
127 val /= 10;
128 }
129 if (i == 0) {
130 *(buf++) = '0';
131 } else {
132 while (i)
133 *(buf++) = output[--i];
134 }
135 *(buf++) = ' ';
136 *buf = 0;
137 return ret;
138}
139
140
141namespace QPdf {
142 ByteStream::ByteStream(QByteArray *byteArray, bool fileBacking)
143 : dev(new QBuffer(byteArray)),
144 fileBackingEnabled(fileBacking),
145 fileBackingActive(false),
146 handleDirty(false)
147 {
148 dev->open(QIODevice::ReadWrite | QIODevice::Append);
149 }
150
151 ByteStream::ByteStream(bool fileBacking)
152 : dev(new QBuffer(&ba)),
153 fileBackingEnabled(fileBacking),
154 fileBackingActive(false),
155 handleDirty(false)
156 {
157 dev->open(QIODevice::ReadWrite);
158 }
159
160 ByteStream::~ByteStream()
161 {
162 delete dev;
163 }
164
165 ByteStream &ByteStream::operator <<(char chr)
166 {
167 if (handleDirty) prepareBuffer();
168 dev->write(&chr, 1);
169 return *this;
170 }
171
172 ByteStream &ByteStream::operator <<(const char *str)
173 {
174 if (handleDirty) prepareBuffer();
175 dev->write(str, strlen(str));
176 return *this;
177 }
178
179 ByteStream &ByteStream::operator <<(const QByteArray &str)
180 {
181 if (handleDirty) prepareBuffer();
182 dev->write(str);
183 return *this;
184 }
185
186 ByteStream &ByteStream::operator <<(const ByteStream &src)
187 {
188 Q_ASSERT(!src.dev->isSequential());
189 if (handleDirty) prepareBuffer();
190 // We do play nice here, even though it looks ugly.
191 // We save the position and restore it afterwards.
192 ByteStream &s = const_cast<ByteStream&>(src);
193 qint64 pos = s.dev->pos();
194 s.dev->reset();
195 while (!s.dev->atEnd()) {
196 QByteArray buf = s.dev->read(chunkSize());
197 dev->write(buf);
198 }
199 s.dev->seek(pos);
200 return *this;
201 }
202
203 ByteStream &ByteStream::operator <<(qreal val) {
204 char buf[256];
205 qt_real_to_string(val, buf);
206 *this << buf;
207 return *this;
208 }
209
210 ByteStream &ByteStream::operator <<(int val) {
211 char buf[256];
212 qt_int_to_string(val, buf);
213 *this << buf;
214 return *this;
215 }
216
217 ByteStream &ByteStream::operator <<(const QPointF &p) {
218 char buf[256];
219 qt_real_to_string(p.x(), buf);
220 *this << buf;
221 qt_real_to_string(p.y(), buf);
222 *this << buf;
223 return *this;
224 }
225
226 QIODevice *ByteStream::stream()
227 {
228 dev->reset();
229 handleDirty = true;
230 return dev;
231 }
232
233 void ByteStream::clear()
234 {
235 dev->open(QIODevice::ReadWrite | QIODevice::Truncate);
236 }
237
238 void ByteStream::constructor_helper(QByteArray *ba)
239 {
240 delete dev;
241 dev = new QBuffer(ba);
242 dev->open(QIODevice::ReadWrite);
243 }
244
245 void ByteStream::prepareBuffer()
246 {
247 Q_ASSERT(!dev->isSequential());
248 qint64 size = dev->size();
249 if (fileBackingEnabled && !fileBackingActive
250 && size > maxMemorySize()) {
251 // Switch to file backing.
252 QTemporaryFile *newFile = new QTemporaryFile;
253 newFile->open();
254 dev->reset();
255 while (!dev->atEnd()) {
256 QByteArray buf = dev->read(chunkSize());
257 newFile->write(buf);
258 }
259 delete dev;
260 dev = newFile;
261 ba.clear();
262 fileBackingActive = true;
263 }
264 if (dev->pos() != size) {
265 dev->seek(size);
266 handleDirty = false;
267 }
268 }
269}
270
271#define QT_PATH_ELEMENT(elm)
272
273QByteArray QPdf::generatePath(const QPainterPath &path, const QTransform &matrix, PathFlags flags)
274{
275 QByteArray result;
276 if (!path.elementCount())
277 return result;
278
279 ByteStream s(&result);
280
281 int start = -1;
282 for (int i = 0; i < path.elementCount(); ++i) {
283 const QPainterPath::Element &elm = path.elementAt(i);
284 switch (elm.type) {
285 case QPainterPath::MoveToElement:
286 if (start >= 0
287 && path.elementAt(start).x == path.elementAt(i-1).x
288 && path.elementAt(start).y == path.elementAt(i-1).y)
289 s << "h\n";
290 s << matrix.map(QPointF(elm.x, elm.y)) << "m\n";
291 start = i;
292 break;
293 case QPainterPath::LineToElement:
294 s << matrix.map(QPointF(elm.x, elm.y)) << "l\n";
295 break;
296 case QPainterPath::CurveToElement:
297 Q_ASSERT(path.elementAt(i+1).type == QPainterPath::CurveToDataElement);
298 Q_ASSERT(path.elementAt(i+2).type == QPainterPath::CurveToDataElement);
299 s << matrix.map(QPointF(elm.x, elm.y))
300 << matrix.map(QPointF(path.elementAt(i+1).x, path.elementAt(i+1).y))
301 << matrix.map(QPointF(path.elementAt(i+2).x, path.elementAt(i+2).y))
302 << "c\n";
303 i += 2;
304 break;
305 default:
306 qFatal("QPdf::generatePath(), unhandled type: %d", elm.type);
307 }
308 }
309 if (start >= 0
310 && path.elementAt(start).x == path.elementAt(path.elementCount()-1).x
311 && path.elementAt(start).y == path.elementAt(path.elementCount()-1).y)
312 s << "h\n";
313
314 Qt::FillRule fillRule = path.fillRule();
315
316 const char *op = "";
317 switch (flags) {
318 case ClipPath:
319 op = (fillRule == Qt::WindingFill) ? "W n\n" : "W* n\n";
320 break;
321 case FillPath:
322 op = (fillRule == Qt::WindingFill) ? "f\n" : "f*\n";
323 break;
324 case StrokePath:
325 op = "S\n";
326 break;
327 case FillAndStrokePath:
328 op = (fillRule == Qt::WindingFill) ? "B\n" : "B*\n";
329 break;
330 }
331 s << op;
332 return result;
333}
334
335QByteArray QPdf::generateMatrix(const QTransform &matrix)
336{
337 QByteArray result;
338 ByteStream s(&result);
339 s << matrix.m11()
340 << matrix.m12()
341 << matrix.m21()
342 << matrix.m22()
343 << matrix.dx()
344 << matrix.dy()
345 << "cm\n";
346 return result;
347}
348
349QByteArray QPdf::generateDashes(const QPen &pen)
350{
351 QByteArray result;
352 ByteStream s(&result);
353 s << '[';
354
355 QVector<qreal> dasharray = pen.dashPattern();
356 qreal w = pen.widthF();
357 if (w < 0.001)
358 w = 1;
359 for (int i = 0; i < dasharray.size(); ++i) {
360 qreal dw = dasharray.at(i)*w;
361 if (dw < 0.0001) dw = 0.0001;
362 s << dw;
363 }
364 s << ']';
365 //qDebug() << "dasharray: pen has" << dasharray;
366 //qDebug() << " => " << result;
367 return result;
368}
369
370
371
372static const char* pattern_for_brush[] = {
373 0, // NoBrush
374 0, // SolidPattern
375 "0 J\n"
376 "6 w\n"
377 "[] 0 d\n"
378 "4 0 m\n"
379 "4 8 l\n"
380 "0 4 m\n"
381 "8 4 l\n"
382 "S\n", // Dense1Pattern
383
384 "0 J\n"
385 "2 w\n"
386 "[6 2] 1 d\n"
387 "0 0 m\n"
388 "0 8 l\n"
389 "8 0 m\n"
390 "8 8 l\n"
391 "S\n"
392 "[] 0 d\n"
393 "2 0 m\n"
394 "2 8 l\n"
395 "6 0 m\n"
396 "6 8 l\n"
397 "S\n"
398 "[6 2] -3 d\n"
399 "4 0 m\n"
400 "4 8 l\n"
401 "S\n", // Dense2Pattern
402
403 "0 J\n"
404 "2 w\n"
405 "[6 2] 1 d\n"
406 "0 0 m\n"
407 "0 8 l\n"
408 "8 0 m\n"
409 "8 8 l\n"
410 "S\n"
411 "[2 2] -1 d\n"
412 "2 0 m\n"
413 "2 8 l\n"
414 "6 0 m\n"
415 "6 8 l\n"
416 "S\n"
417 "[6 2] -3 d\n"
418 "4 0 m\n"
419 "4 8 l\n"
420 "S\n", // Dense3Pattern
421
422 "0 J\n"
423 "2 w\n"
424 "[2 2] 1 d\n"
425 "0 0 m\n"
426 "0 8 l\n"
427 "8 0 m\n"
428 "8 8 l\n"
429 "S\n"
430 "[2 2] -1 d\n"
431 "2 0 m\n"
432 "2 8 l\n"
433 "6 0 m\n"
434 "6 8 l\n"
435 "S\n"
436 "[2 2] 1 d\n"
437 "4 0 m\n"
438 "4 8 l\n"
439 "S\n", // Dense4Pattern
440
441 "0 J\n"
442 "2 w\n"
443 "[2 6] -1 d\n"
444 "0 0 m\n"
445 "0 8 l\n"
446 "8 0 m\n"
447 "8 8 l\n"
448 "S\n"
449 "[2 2] 1 d\n"
450 "2 0 m\n"
451 "2 8 l\n"
452 "6 0 m\n"
453 "6 8 l\n"
454 "S\n"
455 "[2 6] 3 d\n"
456 "4 0 m\n"
457 "4 8 l\n"
458 "S\n", // Dense5Pattern
459
460 "0 J\n"
461 "2 w\n"
462 "[2 6] -1 d\n"
463 "0 0 m\n"
464 "0 8 l\n"
465 "8 0 m\n"
466 "8 8 l\n"
467 "S\n"
468 "[2 6] 3 d\n"
469 "4 0 m\n"
470 "4 8 l\n"
471 "S\n", // Dense6Pattern
472
473 "0 J\n"
474 "2 w\n"
475 "[2 6] -1 d\n"
476 "0 0 m\n"
477 "0 8 l\n"
478 "8 0 m\n"
479 "8 8 l\n"
480 "S\n", // Dense7Pattern
481
482 "1 w\n"
483 "0 4 m\n"
484 "8 4 l\n"
485 "S\n", // HorPattern
486
487 "1 w\n"
488 "4 0 m\n"
489 "4 8 l\n"
490 "S\n", // VerPattern
491
492 "1 w\n"
493 "4 0 m\n"
494 "4 8 l\n"
495 "0 4 m\n"
496 "8 4 l\n"
497 "S\n", // CrossPattern
498
499 "1 w\n"
500 "-1 5 m\n"
501 "5 -1 l\n"
502 "3 9 m\n"
503 "9 3 l\n"
504 "S\n", // BDiagPattern
505
506 "1 w\n"
507 "-1 3 m\n"
508 "5 9 l\n"
509 "3 -1 m\n"
510 "9 5 l\n"
511 "S\n", // FDiagPattern
512
513 "1 w\n"
514 "-1 3 m\n"
515 "5 9 l\n"
516 "3 -1 m\n"
517 "9 5 l\n"
518 "-1 5 m\n"
519 "5 -1 l\n"
520 "3 9 m\n"
521 "9 3 l\n"
522 "S\n", // DiagCrossPattern
523};
524
525QByteArray QPdf::patternForBrush(const QBrush &b)
526{
527 int style = b.style();
528 if (style > Qt::DiagCrossPattern)
529 return QByteArray();
530 return pattern_for_brush[style];
531}
532
533#ifdef USE_NATIVE_GRADIENTS
534static void writeTriangleLine(uchar *&data, int xpos, int ypos, int xoff, int yoff, uint rgb, uchar flag, bool alpha)
535{
536 data[0] = flag;
537 data[1] = (uchar)(xpos >> 16);
538 data[2] = (uchar)(xpos >> 8);
539 data[3] = (uchar)(xpos >> 0);
540 data[4] = (uchar)(ypos >> 16);
541 data[5] = (uchar)(ypos >> 8);
542 data[6] = (uchar)(ypos >> 0);
543 data += 7;
544 if (alpha) {
545 *data++ = (uchar)qAlpha(rgb);
546 } else {
547 *data++ = (uchar)qRed(rgb);
548 *data++ = (uchar)qGreen(rgb);
549 *data++ = (uchar)qBlue(rgb);
550 }
551 xpos += xoff;
552 ypos += yoff;
553 data[0] = flag;
554 data[1] = (uchar)(xpos >> 16);
555 data[2] = (uchar)(xpos >> 8);
556 data[3] = (uchar)(xpos >> 0);
557 data[4] = (uchar)(ypos >> 16);
558 data[5] = (uchar)(ypos >> 8);
559 data[6] = (uchar)(ypos >> 0);
560 data += 7;
561 if (alpha) {
562 *data++ = (uchar)qAlpha(rgb);
563 } else {
564 *data++ = (uchar)qRed(rgb);
565 *data++ = (uchar)qGreen(rgb);
566 *data++ = (uchar)qBlue(rgb);
567 }
568}
569
570
571QByteArray QPdf::generateLinearGradientShader(const QLinearGradient *gradient, const QPointF *page_rect, bool alpha)
572{
573 // generate list of triangles with colors
574 QPointF start = gradient->start();
575 QPointF stop = gradient->finalStop();
576 QGradientStops stops = gradient->stops();
577 QPointF offset = stop - start;
578 QGradient::Spread spread = gradient->spread();
579
580 if (gradient->spread() == QGradient::ReflectSpread) {
581 offset *= 2;
582 for (int i = stops.size() - 2; i >= 0; --i) {
583 QGradientStop stop = stops.at(i);
584 stop.first = 2. - stop.first;
585 stops.append(stop);
586 }
587 for (int i = 0 ; i < stops.size(); ++i)
588 stops[i].first /= 2.;
589 }
590
591 QPointF orthogonal(offset.y(), -offset.x());
592 qreal length = offset.x()*offset.x() + offset.y()*offset.y();
593
594 // find the max and min values in offset and orth direction that are needed to cover
595 // the whole page
596 int off_min = INT_MAX;
597 int off_max = INT_MIN;
598 qreal ort_min = INT_MAX;
599 qreal ort_max = INT_MIN;
600 for (int i = 0; i < 4; ++i) {
601 qreal off = ((page_rect[i].x() - start.x()) * offset.x() + (page_rect[i].y() - start.y()) * offset.y())/length;
602 qreal ort = ((page_rect[i].x() - start.x()) * orthogonal.x() + (page_rect[i].y() - start.y()) * orthogonal.y())/length;
603 off_min = qMin(off_min, qFloor(off));
604 off_max = qMax(off_max, qCeil(off));
605 ort_min = qMin(ort_min, ort);
606 ort_max = qMax(ort_max, ort);
607 }
608 ort_min -= 1;
609 ort_max += 1;
610
611 start += off_min * offset + ort_min * orthogonal;
612 orthogonal *= (ort_max - ort_min);
613 int num = off_max - off_min;
614
615 QPointF gradient_rect[4] = { start,
616 start + orthogonal,
617 start + num*offset,
618 start + num*offset + orthogonal };
619 qreal xmin = gradient_rect[0].x();
620 qreal xmax = gradient_rect[0].x();
621 qreal ymin = gradient_rect[0].y();
622 qreal ymax = gradient_rect[0].y();
623 for (int i = 1; i < 4; ++i) {
624 xmin = qMin(xmin, gradient_rect[i].x());
625 xmax = qMax(xmax, gradient_rect[i].x());
626 ymin = qMin(ymin, gradient_rect[i].y());
627 ymax = qMax(ymax, gradient_rect[i].y());
628 }
629 xmin -= 1000;
630 xmax += 1000;
631 ymin -= 1000;
632 ymax += 1000;
633 start -= QPointF(xmin, ymin);
634 qreal factor_x = qreal(1<<24)/(xmax - xmin);
635 qreal factor_y = qreal(1<<24)/(ymax - ymin);
636 int xoff = (int)(orthogonal.x()*factor_x);
637 int yoff = (int)(orthogonal.y()*factor_y);
638
639 QByteArray triangles;
640 triangles.resize(spread == QGradient::PadSpread ? 20*(stops.size()+2) : 20*num*stops.size());
641 uchar *data = (uchar *) triangles.data();
642 if (spread == QGradient::PadSpread) {
643 if (off_min > 0 || off_max < 1) {
644 // linear gradient outside of page
645 const QGradientStop &current_stop = off_min > 0 ? stops.at(stops.size()-1) : stops.at(0);
646 uint rgb = current_stop.second.rgba();
647 int xpos = (int)(start.x()*factor_x);
648 int ypos = (int)(start.y()*factor_y);
649 writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, 0, alpha);
650 start += num*offset;
651 xpos = (int)(start.x()*factor_x);
652 ypos = (int)(start.y()*factor_y);
653 writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, 1, alpha);
654 } else {
655 int flag = 0;
656 if (off_min < 0) {
657 uint rgb = stops.at(0).second.rgba();
658 int xpos = (int)(start.x()*factor_x);
659 int ypos = (int)(start.y()*factor_y);
660 writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha);
661 start -= off_min*offset;
662 flag = 1;
663 }
664 for (int s = 0; s < stops.size(); ++s) {
665 const QGradientStop &current_stop = stops.at(s);
666 uint rgb = current_stop.second.rgba();
667 int xpos = (int)(start.x()*factor_x);
668 int ypos = (int)(start.y()*factor_y);
669 writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha);
670 if (s < stops.size()-1)
671 start += offset*(stops.at(s+1).first - stops.at(s).first);
672 flag = 1;
673 }
674 if (off_max > 1) {
675 start += (off_max - 1)*offset;
676 uint rgb = stops.at(stops.size()-1).second.rgba();
677 int xpos = (int)(start.x()*factor_x);
678 int ypos = (int)(start.y()*factor_y);
679 writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha);
680 }
681 }
682 } else {
683 for (int i = 0; i < num; ++i) {
684 uchar flag = 0;
685 for (int s = 0; s < stops.size(); ++s) {
686 uint rgb = stops.at(s).second.rgba();
687 int xpos = (int)(start.x()*factor_x);
688 int ypos = (int)(start.y()*factor_y);
689 writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha);
690 if (s < stops.size()-1)
691 start += offset*(stops.at(s+1).first - stops.at(s).first);
692 flag = 1;
693 }
694 }
695 }
696 triangles.resize((char *)data - triangles.constData());
697
698 QByteArray shader;
699 QPdf::ByteStream s(&shader);
700 s << "<<\n"
701 "/ShadingType 4\n"
702 "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") <<
703 "/AntiAlias true\n"
704 "/BitsPerCoordinate 24\n"
705 "/BitsPerComponent 8\n"
706 "/BitsPerFlag 8\n"
707 "/Decode [" << xmin << xmax << ymin << ymax << (alpha ? "0 1]\n" : "0 1 0 1 0 1]\n") <<
708 "/AntiAlias true\n"
709 "/Length " << triangles.length() << "\n"
710 ">>\n"
711 "stream\n" << triangles << "endstream\n"
712 "endobj\n";
713 return shader;
714}
715#endif
716
717static void moveToHook(qfixed x, qfixed y, void *data)
718{
719 QPdf::Stroker *t = (QPdf::Stroker *)data;
720 if (!t->first)
721 *t->stream << "h\n";
722 if (!t->cosmeticPen)
723 t->matrix.map(x, y, &x, &y);
724 *t->stream << x << y << "m\n";
725 t->first = false;
726}
727
728static void lineToHook(qfixed x, qfixed y, void *data)
729{
730 QPdf::Stroker *t = (QPdf::Stroker *)data;
731 if (!t->cosmeticPen)
732 t->matrix.map(x, y, &x, &y);
733 *t->stream << x << y << "l\n";
734}
735
736static void cubicToHook(qfixed c1x, qfixed c1y,
737 qfixed c2x, qfixed c2y,
738 qfixed ex, qfixed ey,
739 void *data)
740{
741 QPdf::Stroker *t = (QPdf::Stroker *)data;
742 if (!t->cosmeticPen) {
743 t->matrix.map(c1x, c1y, &c1x, &c1y);
744 t->matrix.map(c2x, c2y, &c2x, &c2y);
745 t->matrix.map(ex, ey, &ex, &ey);
746 }
747 *t->stream << c1x << c1y
748 << c2x << c2y
749 << ex << ey
750 << "c\n";
751}
752
753QPdf::Stroker::Stroker()
754 : stream(0),
755 first(true),
756 dashStroker(&basicStroker)
757{
758 stroker = &basicStroker;
759 basicStroker.setMoveToHook(moveToHook);
760 basicStroker.setLineToHook(lineToHook);
761 basicStroker.setCubicToHook(cubicToHook);
762 cosmeticPen = true;
763 basicStroker.setStrokeWidth(.1);
764}
765
766void QPdf::Stroker::setPen(const QPen &pen)
767{
768 if (pen.style() == Qt::NoPen) {
769 stroker = 0;
770 return;
771 }
772 qreal w = pen.widthF();
773 bool zeroWidth = w < 0.0001;
774 cosmeticPen = pen.isCosmetic();
775 if (zeroWidth)
776 w = .1;
777
778 basicStroker.setStrokeWidth(w);
779 basicStroker.setCapStyle(pen.capStyle());
780 basicStroker.setJoinStyle(pen.joinStyle());
781 basicStroker.setMiterLimit(pen.miterLimit());
782
783 QVector<qreal> dashpattern = pen.dashPattern();
784 if (zeroWidth) {
785 for (int i = 0; i < dashpattern.size(); ++i)
786 dashpattern[i] *= 10.;
787 }
788 if (!dashpattern.isEmpty()) {
789 dashStroker.setDashPattern(dashpattern);
790 dashStroker.setDashOffset(pen.dashOffset());
791 stroker = &dashStroker;
792 } else {
793 stroker = &basicStroker;
794 }
795}
796
797void QPdf::Stroker::strokePath(const QPainterPath &path)
798{
799 if (!stroker)
800 return;
801 first = true;
802
803 stroker->strokePath(path, this, cosmeticPen ? matrix : QTransform());
804 *stream << "h f\n";
805}
806
807QByteArray QPdf::ascii85Encode(const QByteArray &input)
808{
809 int isize = input.size()/4*4;
810 QByteArray output;
811 output.resize(input.size()*5/4+7);
812 char *out = output.data();
813 const uchar *in = (const uchar *)input.constData();
814 for (int i = 0; i < isize; i += 4) {
815 uint val = (((uint)in[i])<<24) + (((uint)in[i+1])<<16) + (((uint)in[i+2])<<8) + (uint)in[i+3];
816 if (val == 0) {
817 *out = 'z';
818 ++out;
819 } else {
820 char base[5];
821 base[4] = val % 85;
822 val /= 85;
823 base[3] = val % 85;
824 val /= 85;
825 base[2] = val % 85;
826 val /= 85;
827 base[1] = val % 85;
828 val /= 85;
829 base[0] = val % 85;
830 *(out++) = base[0] + '!';
831 *(out++) = base[1] + '!';
832 *(out++) = base[2] + '!';
833 *(out++) = base[3] + '!';
834 *(out++) = base[4] + '!';
835 }
836 }
837 //write the last few bytes
838 int remaining = input.size() - isize;
839 if (remaining) {
840 uint val = 0;
841 for (int i = isize; i < input.size(); ++i)
842 val = (val << 8) + in[i];
843 val <<= 8*(4-remaining);
844 char base[5];
845 base[4] = val % 85;
846 val /= 85;
847 base[3] = val % 85;
848 val /= 85;
849 base[2] = val % 85;
850 val /= 85;
851 base[1] = val % 85;
852 val /= 85;
853 base[0] = val % 85;
854 for (int i = 0; i < remaining+1; ++i)
855 *(out++) = base[i] + '!';
856 }
857 *(out++) = '~';
858 *(out++) = '>';
859 output.resize(out-output.data());
860 return output;
861}
862
863const char *QPdf::toHex(ushort u, char *buffer)
864{
865 int i = 3;
866 while (i >= 0) {
867 ushort hex = (u & 0x000f);
868 if (hex < 0x0a)
869 buffer[i] = '0'+hex;
870 else
871 buffer[i] = 'A'+(hex-0x0a);
872 u = u >> 4;
873 i--;
874 }
875 buffer[4] = '\0';
876 return buffer;
877}
878
879const char *QPdf::toHex(uchar u, char *buffer)
880{
881 int i = 1;
882 while (i >= 0) {
883 ushort hex = (u & 0x000f);
884 if (hex < 0x0a)
885 buffer[i] = '0'+hex;
886 else
887 buffer[i] = 'A'+(hex-0x0a);
888 u = u >> 4;
889 i--;
890 }
891 buffer[2] = '\0';
892 return buffer;
893}
894
895#define Q_MM(n) int((n * 720 + 127) / 254)
896#define Q_IN(n) int(n * 72)
897
898static const char * const psToStr[QPrinter::NPaperSize+1] =
899{
900 "A4", "B5", "Letter", "Legal", "Executive",
901 "A0", "A1", "A2", "A3", "A5", "A6", "A7", "A8", "A9", "B0", "B1",
902 "B10", "B2", "B3", "B4", "B6", "B7", "B8", "B9", "C5E", "Comm10E",
903 "DLE", "Folio", "Ledger", "Tabloid", 0
904};
905
906QPdf::PaperSize QPdf::paperSize(QPrinter::PaperSize paperSize)
907{
908 QSizeF s = qt_paperSizeToQSizeF(paperSize);
909 PaperSize p = { Q_MM(s.width()), Q_MM(s.height()) };
910 return p;
911}
912
913const char *QPdf::paperSizeToString(QPrinter::PaperSize paperSize)
914{
915 return psToStr[paperSize];
916}
917
918
919// -------------------------- base engine, shared code between PS and PDF -----------------------
920
921QPdfBaseEngine::QPdfBaseEngine(QPdfBaseEnginePrivate &dd, PaintEngineFeatures f)
922 : QAlphaPaintEngine(dd, f)
923{
924 Q_D(QPdfBaseEngine);
925#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
926 if (QCUPSSupport::isAvailable()) {
927 QCUPSSupport cups;
928 const cups_dest_t* printers = cups.availablePrinters();
929 int prnCount = cups.availablePrintersCount();
930
931 for (int i = 0; i < prnCount; ++i) {
932 if (printers[i].is_default) {
933 d->printerName = QString::fromLocal8Bit(printers[i].name);
934 break;
935 }
936 }
937
938 } else
939#endif
940 {
941 d->printerName = QString::fromLocal8Bit(qgetenv("PRINTER"));
942 if (d->printerName.isEmpty())
943 d->printerName = QString::fromLocal8Bit(qgetenv("LPDEST"));
944 if (d->printerName.isEmpty())
945 d->printerName = QString::fromLocal8Bit(qgetenv("NPRINTER"));
946 if (d->printerName.isEmpty())
947 d->printerName = QString::fromLocal8Bit(qgetenv("NGPRINTER"));
948 }
949}
950
951void QPdfBaseEngine::drawPoints (const QPointF *points, int pointCount)
952{
953 if (!points)
954 return;
955
956 Q_D(QPdfBaseEngine);
957 QPainterPath p;
958 for (int i=0; i!=pointCount;++i) {
959 p.moveTo(points[i]);
960 p.lineTo(points[i] + QPointF(0, 0.001));
961 }
962
963 bool hadBrush = d->hasBrush;
964 d->hasBrush = false;
965 drawPath(p);
966 d->hasBrush = hadBrush;
967}
968
969void QPdfBaseEngine::drawLines (const QLineF *lines, int lineCount)
970{
971 if (!lines)
972 return;
973
974 Q_D(QPdfBaseEngine);
975 QPainterPath p;
976 for (int i=0; i!=lineCount;++i) {
977 p.moveTo(lines[i].p1());
978 p.lineTo(lines[i].p2());
979 }
980 bool hadBrush = d->hasBrush;
981 d->hasBrush = false;
982 drawPath(p);
983 d->hasBrush = hadBrush;
984}
985
986void QPdfBaseEngine::drawRects (const QRectF *rects, int rectCount)
987{
988 if (!rects)
989 return;
990
991 Q_D(QPdfBaseEngine);
992 if (d->useAlphaEngine) {
993 QAlphaPaintEngine::drawRects(rects, rectCount);
994 if (!continueCall())
995 return;
996 }
997
998 if (d->clipEnabled && d->allClipped)
999 return;
1000 if (!d->hasPen && !d->hasBrush)
1001 return;
1002
1003 QBrush penBrush = d->pen.brush();
1004 if (d->simplePen || !d->hasPen) {
1005 // draw strokes natively in this case for better output
1006 if(!d->simplePen && !d->stroker.matrix.isIdentity())
1007 *d->currentPage << "q\n" << QPdf::generateMatrix(d->stroker.matrix);
1008 for (int i = 0; i < rectCount; ++i)
1009 *d->currentPage << rects[i].x() << rects[i].y() << rects[i].width() << rects[i].height() << "re\n";
1010 *d->currentPage << (d->hasPen ? (d->hasBrush ? "B\n" : "S\n") : "f\n");
1011 if(!d->simplePen && !d->stroker.matrix.isIdentity())
1012 *d->currentPage << "Q\n";
1013 } else {
1014 QPainterPath p;
1015 for (int i=0; i!=rectCount; ++i)
1016 p.addRect(rects[i]);
1017 drawPath(p);
1018 }
1019}
1020
1021void QPdfBaseEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode)
1022{
1023 Q_D(QPdfBaseEngine);
1024
1025 if (d->useAlphaEngine) {
1026 QAlphaPaintEngine::drawPolygon(points, pointCount, mode);
1027 if (!continueCall())
1028 return;
1029 }
1030
1031 if (!points || !pointCount)
1032 return;
1033
1034 bool hb = d->hasBrush;
1035 QPainterPath p;
1036
1037 switch(mode) {
1038 case OddEvenMode:
1039 p.setFillRule(Qt::OddEvenFill);
1040 break;
1041 case ConvexMode:
1042 case WindingMode:
1043 p.setFillRule(Qt::WindingFill);
1044 break;
1045 case PolylineMode:
1046 d->hasBrush = false;
1047 break;
1048 default:
1049 break;
1050 }
1051
1052 p.moveTo(points[0]);
1053 for (int i = 1; i < pointCount; ++i)
1054 p.lineTo(points[i]);
1055
1056 if (mode != PolylineMode)
1057 p.closeSubpath();
1058 drawPath(p);
1059
1060 d->hasBrush = hb;
1061}
1062
1063void QPdfBaseEngine::drawPath (const QPainterPath &p)
1064{
1065 Q_D(QPdfBaseEngine);
1066
1067 if (d->useAlphaEngine) {
1068 QAlphaPaintEngine::drawPath(p);
1069 if (!continueCall())
1070 return;
1071 }
1072
1073 if (d->clipEnabled && d->allClipped)
1074 return;
1075 if (!d->hasPen && !d->hasBrush)
1076 return;
1077
1078 if (d->simplePen) {
1079 // draw strokes natively in this case for better output
1080 *d->currentPage << QPdf::generatePath(p, QTransform(), d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath);
1081 } else {
1082 if (d->hasBrush)
1083 *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath);
1084 if (d->hasPen) {
1085 *d->currentPage << "q\n";
1086 QBrush b = d->brush;
1087 d->brush = d->pen.brush();
1088 setBrush();
1089 d->stroker.strokePath(p);
1090 *d->currentPage << "Q\n";
1091 d->brush = b;
1092 }
1093 }
1094}
1095
1096void QPdfBaseEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
1097{
1098 Q_D(QPdfBaseEngine);
1099
1100 if (d->useAlphaEngine) {
1101 QAlphaPaintEngine::drawTextItem(p, textItem);
1102 if (!continueCall())
1103 return;
1104 }
1105
1106 if (!d->hasPen || (d->clipEnabled && d->allClipped))
1107 return;
1108
1109 if (d->stroker.matrix.type() >= QTransform::TxProject) {
1110 QPaintEngine::drawTextItem(p, textItem);
1111 return;
1112 }
1113
1114 *d->currentPage << "q\n";
1115 if(!d->simplePen)
1116 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1117
1118 bool hp = d->hasPen;
1119 d->hasPen = false;
1120 QBrush b = d->brush;
1121 d->brush = d->pen.brush();
1122 setBrush();
1123
1124 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
1125 Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi);
1126 d->drawTextItem(p, ti);
1127 d->hasPen = hp;
1128 d->brush = b;
1129 *d->currentPage << "Q\n";
1130}
1131
1132
1133void QPdfBaseEngine::updateState(const QPaintEngineState &state)
1134{
1135 Q_D(QPdfBaseEngine);
1136
1137 if (d->useAlphaEngine) {
1138 QAlphaPaintEngine::updateState(state);
1139 if (!continueCall())
1140 return;
1141 }
1142
1143 QPaintEngine::DirtyFlags flags = state.state();
1144
1145 if (flags & DirtyTransform)
1146 d->stroker.matrix = state.transform();
1147
1148 if (flags & DirtyPen) {
1149 d->pen = state.pen();
1150 d->hasPen = d->pen.style() != Qt::NoPen;
1151 d->stroker.setPen(d->pen);
1152 QBrush penBrush = d->pen.brush();
1153 bool oldSimple = d->simplePen;
1154 d->simplePen = (d->hasPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque());
1155 if (oldSimple != d->simplePen)
1156 flags |= DirtyTransform;
1157 }
1158 if (flags & DirtyBrush) {
1159 d->brush = state.brush();
1160 d->hasBrush = d->brush.style() != Qt::NoBrush;
1161 }
1162 if (flags & DirtyBrushOrigin) {
1163 d->brushOrigin = state.brushOrigin();
1164 flags |= DirtyBrush;
1165 }
1166 if (flags & DirtyOpacity)
1167 d->opacity = state.opacity();
1168
1169 bool ce = d->clipEnabled;
1170 if (flags & DirtyClipPath) {
1171 d->clipEnabled = true;
1172 updateClipPath(state.clipPath(), state.clipOperation());
1173 } else if (flags & DirtyClipRegion) {
1174 d->clipEnabled = true;
1175 QPainterPath path;
1176 QVector<QRect> rects = state.clipRegion().rects();
1177 for (int i = 0; i < rects.size(); ++i)
1178 path.addRect(rects.at(i));
1179 updateClipPath(path, state.clipOperation());
1180 flags |= DirtyClipPath;
1181 } else if (flags & DirtyClipEnabled) {
1182 d->clipEnabled = state.isClipEnabled();
1183 }
1184
1185 if (ce != d->clipEnabled)
1186 flags |= DirtyClipPath;
1187 else if (!d->clipEnabled)
1188 flags &= ~DirtyClipPath;
1189
1190 setupGraphicsState(flags);
1191}
1192
1193void QPdfBaseEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags)
1194{
1195 Q_D(QPdfBaseEngine);
1196 if (flags & DirtyClipPath)
1197 flags |= DirtyTransform|DirtyPen|DirtyBrush;
1198
1199 if (flags & DirtyTransform) {
1200 *d->currentPage << "Q\n";
1201 flags |= DirtyPen|DirtyBrush;
1202 }
1203
1204 if (flags & DirtyClipPath) {
1205 *d->currentPage << "Q q\n";
1206
1207 d->allClipped = false;
1208 if (d->clipEnabled && !d->clips.isEmpty()) {
1209 for (int i = 0; i < d->clips.size(); ++i) {
1210 if (d->clips.at(i).isEmpty()) {
1211 d->allClipped = true;
1212 break;
1213 }
1214 }
1215 if (!d->allClipped) {
1216 for (int i = 0; i < d->clips.size(); ++i) {
1217 *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath);
1218 }
1219 }
1220 }
1221 }
1222
1223 if (flags & DirtyTransform) {
1224 *d->currentPage << "q\n";
1225 if (d->simplePen && !d->stroker.matrix.isIdentity())
1226 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1227 }
1228 if (flags & DirtyBrush)
1229 setBrush();
1230 if (d->simplePen && (flags & DirtyPen))
1231 setPen();
1232}
1233
1234extern QPainterPath qt_regionToPath(const QRegion &region);
1235
1236void QPdfBaseEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op)
1237{
1238 Q_D(QPdfBaseEngine);
1239 QPainterPath path = d->stroker.matrix.map(p);
1240 //qDebug() << "updateClipPath: " << d->stroker.matrix << p.boundingRect() << path.boundingRect() << op;
1241
1242 if (op == Qt::NoClip) {
1243 d->clipEnabled = false;
1244 d->clips.clear();
1245 } else if (op == Qt::ReplaceClip) {
1246 d->clips.clear();
1247 d->clips.append(path);
1248 } else if (op == Qt::IntersectClip) {
1249 d->clips.append(path);
1250 } else { // UniteClip
1251 // ask the painter for the current clipping path. that's the easiest solution
1252 path = painter()->clipPath();
1253 path = d->stroker.matrix.map(path);
1254 d->clips.clear();
1255 d->clips.append(path);
1256 }
1257
1258 if (d->useAlphaEngine) {
1259 // if we have an alpha region, we have to subtract that from the
1260 // any existing clip region since that region will be filled in
1261 // later with images
1262 QPainterPath alphaClip = qt_regionToPath(alphaClipping());
1263 if (!alphaClip.isEmpty()) {
1264 if (!d->clipEnabled) {
1265 QRect r = d->fullPage ? d->paperRect() : d->pageRect();
1266 QPainterPath dev;
1267 dev.addRect(QRect(0, 0, r.width(), r.height()));
1268 if (path.isEmpty())
1269 path = dev;
1270 else
1271 path = path.intersected(dev);
1272 d->clipEnabled = true;
1273 } else {
1274 path = painter()->clipPath();
1275 path = d->stroker.matrix.map(path);
1276 }
1277 path = path.subtracted(alphaClip);
1278 d->clips.clear();
1279 d->clips.append(path);
1280 }
1281 }
1282}
1283
1284void QPdfBaseEngine::setPen()
1285{
1286 Q_D(QPdfBaseEngine);
1287 if (d->pen.style() == Qt::NoPen)
1288 return;
1289 QBrush b = d->pen.brush();
1290 Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque());
1291
1292 QColor rgba = b.color();
1293 if (d->colorMode == QPrinter::GrayScale) {
1294 qreal gray = qGray(rgba.rgba())/255.;
1295 *d->currentPage << gray << gray << gray;
1296 } else {
1297 *d->currentPage << rgba.redF()
1298 << rgba.greenF()
1299 << rgba.blueF();
1300 }
1301 *d->currentPage << "SCN\n";
1302
1303 *d->currentPage << d->pen.widthF() << "w ";
1304
1305 int pdfCapStyle = 0;
1306 switch(d->pen.capStyle()) {
1307 case Qt::FlatCap:
1308 pdfCapStyle = 0;
1309 break;
1310 case Qt::SquareCap:
1311 pdfCapStyle = 2;
1312 break;
1313 case Qt::RoundCap:
1314 pdfCapStyle = 1;
1315 break;
1316 default:
1317 break;
1318 }
1319 *d->currentPage << pdfCapStyle << "J ";
1320
1321 int pdfJoinStyle = 0;
1322 switch(d->pen.joinStyle()) {
1323 case Qt::MiterJoin:
1324 pdfJoinStyle = 0;
1325 break;
1326 case Qt::BevelJoin:
1327 pdfJoinStyle = 2;
1328 break;
1329 case Qt::RoundJoin:
1330 pdfJoinStyle = 1;
1331 break;
1332 default:
1333 break;
1334 }
1335 *d->currentPage << pdfJoinStyle << "j ";
1336
1337 *d->currentPage << QPdf::generateDashes(d->pen) << " 0 d\n";
1338}
1339
1340bool QPdfBaseEngine::newPage()
1341{
1342 Q_D(QPdfBaseEngine);
1343 setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath);
1344 QFile *outfile = qobject_cast<QFile*> (d->outDevice);
1345 if (outfile && outfile->error() != QFile::NoError)
1346 return false;
1347 return true;
1348}
1349
1350
1351int QPdfBaseEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const
1352{
1353 Q_D(const QPdfBaseEngine);
1354 int val;
1355 QRect r = d->fullPage ? d->paperRect() : d->pageRect();
1356 switch (metricType) {
1357 case QPaintDevice::PdmWidth:
1358 val = r.width();
1359 break;
1360 case QPaintDevice::PdmHeight:
1361 val = r.height();
1362 break;
1363 case QPaintDevice::PdmDpiX:
1364 case QPaintDevice::PdmDpiY:
1365 val = d->resolution;
1366 break;
1367 case QPaintDevice::PdmPhysicalDpiX:
1368 case QPaintDevice::PdmPhysicalDpiY:
1369 val = 1200;
1370 break;
1371 case QPaintDevice::PdmWidthMM:
1372 val = qRound(r.width()*25.4/d->resolution);
1373 break;
1374 case QPaintDevice::PdmHeightMM:
1375 val = qRound(r.height()*25.4/d->resolution);
1376 break;
1377 case QPaintDevice::PdmNumColors:
1378 val = INT_MAX;
1379 break;
1380 case QPaintDevice::PdmDepth:
1381 val = 32;
1382 break;
1383 default:
1384 qWarning("QPrinter::metric: Invalid metric command");
1385 return 0;
1386 }
1387 return val;
1388}
1389
1390void QPdfBaseEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value)
1391{
1392 Q_D(QPdfBaseEngine);
1393 switch (int(key)) {
1394 case PPK_CollateCopies:
1395 d->collate = value.toBool();
1396 break;
1397 case PPK_ColorMode:
1398 d->colorMode = QPrinter::ColorMode(value.toInt());
1399 break;
1400 case PPK_Creator:
1401 d->creator = value.toString();
1402 break;
1403 case PPK_DocumentName:
1404 d->title = value.toString();
1405 break;
1406 case PPK_FullPage:
1407 d->fullPage = value.toBool();
1408 break;
1409 case PPK_CopyCount: // fallthrough
1410 case PPK_NumberOfCopies:
1411 d->copies = value.toInt();
1412 break;
1413 case PPK_Orientation:
1414 d->orientation = QPrinter::Orientation(value.toInt());
1415 break;
1416 case PPK_OutputFileName:
1417 d->outputFileName = value.toString();
1418 break;
1419 case PPK_PageOrder:
1420 d->pageOrder = QPrinter::PageOrder(value.toInt());
1421 break;
1422 case PPK_PaperSize:
1423 d->paperSize = QPrinter::PaperSize(value.toInt());
1424 break;
1425 case PPK_PaperSource:
1426 d->paperSource = QPrinter::PaperSource(value.toInt());
1427 break;
1428 case PPK_PrinterName:
1429 d->printerName = value.toString();
1430 break;
1431 case PPK_PrinterProgram:
1432 d->printProgram = value.toString();
1433 break;
1434 case PPK_Resolution:
1435 d->resolution = value.toInt();
1436 break;
1437 case PPK_SelectionOption:
1438 d->selectionOption = value.toString();
1439 break;
1440 case PPK_FontEmbedding:
1441 d->embedFonts = value.toBool();
1442 break;
1443 case PPK_Duplex:
1444 d->duplex = static_cast<QPrinter::DuplexMode> (value.toInt());
1445 break;
1446 case PPK_CupsPageRect:
1447 d->cupsPageRect = value.toRect();
1448 break;
1449 case PPK_CupsPaperRect:
1450 d->cupsPaperRect = value.toRect();
1451 break;
1452 case PPK_CupsOptions:
1453 d->cupsOptions = value.toStringList();
1454 break;
1455 case PPK_CupsStringPageSize:
1456 d->cupsStringPageSize = value.toString();
1457 break;
1458 case PPK_CustomPaperSize:
1459 d->paperSize = QPrinter::Custom;
1460 d->customPaperSize = value.toSizeF();
1461 break;
1462 case PPK_PageMargins:
1463 {
1464 QList<QVariant> margins(value.toList());
1465 Q_ASSERT(margins.size() == 4);
1466 d->leftMargin = margins.at(0).toReal();
1467 d->topMargin = margins.at(1).toReal();
1468 d->rightMargin = margins.at(2).toReal();
1469 d->bottomMargin = margins.at(3).toReal();
1470 d->hasCustomPageMargins = true;
1471 break;
1472 }
1473 default:
1474 break;
1475 }
1476}
1477
1478QVariant QPdfBaseEngine::property(PrintEnginePropertyKey key) const
1479{
1480 Q_D(const QPdfBaseEngine);
1481
1482 QVariant ret;
1483 switch (int(key)) {
1484 case PPK_CollateCopies:
1485 ret = d->collate;
1486 break;
1487 case PPK_ColorMode:
1488 ret = d->colorMode;
1489 break;
1490 case PPK_Creator:
1491 ret = d->creator;
1492 break;
1493 case PPK_DocumentName:
1494 ret = d->title;
1495 break;
1496 case PPK_FullPage:
1497 ret = d->fullPage;
1498 break;
1499 case PPK_CopyCount:
1500 ret = d->copies;
1501 break;
1502 case PPK_SupportsMultipleCopies:
1503#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY)
1504 if (QCUPSSupport::isAvailable())
1505 ret = true;
1506 else
1507#endif
1508 ret = false;
1509 break;
1510 case PPK_NumberOfCopies:
1511#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
1512 if (QCUPSSupport::isAvailable())
1513 ret = 1;
1514 else
1515#endif
1516 ret = d->copies;
1517 break;
1518 case PPK_Orientation:
1519 ret = d->orientation;
1520 break;
1521 case PPK_OutputFileName:
1522 ret = d->outputFileName;
1523 break;
1524 case PPK_PageOrder:
1525 ret = d->pageOrder;
1526 break;
1527 case PPK_PaperSize:
1528 ret = d->paperSize;
1529 break;
1530 case PPK_PaperSource:
1531 ret = d->paperSource;
1532 break;
1533 case PPK_PrinterName:
1534 ret = d->printerName;
1535 break;
1536 case PPK_PrinterProgram:
1537 ret = d->printProgram;
1538 break;
1539 case PPK_Resolution:
1540 ret = d->resolution;
1541 break;
1542 case PPK_SupportedResolutions:
1543 ret = QList<QVariant>() << 72;
1544 break;
1545 case PPK_PaperRect:
1546 ret = d->paperRect();
1547 break;
1548 case PPK_PageRect:
1549 ret = d->pageRect();
1550 break;
1551 case PPK_SelectionOption:
1552 ret = d->selectionOption;
1553 break;
1554 case PPK_FontEmbedding:
1555 ret = d->embedFonts;
1556 break;
1557 case PPK_Duplex:
1558 ret = d->duplex;
1559 break;
1560 case PPK_CupsPageRect:
1561 ret = d->cupsPageRect;
1562 break;
1563 case PPK_CupsPaperRect:
1564 ret = d->cupsPaperRect;
1565 break;
1566 case PPK_CupsOptions:
1567 ret = d->cupsOptions;
1568 break;
1569 case PPK_CupsStringPageSize:
1570 ret = d->cupsStringPageSize;
1571 break;
1572 case PPK_CustomPaperSize:
1573 ret = d->customPaperSize;
1574 break;
1575 case PPK_PageMargins:
1576 {
1577 QList<QVariant> margins;
1578 if (d->hasCustomPageMargins) {
1579 margins << d->leftMargin << d->topMargin
1580 << d->rightMargin << d->bottomMargin;
1581 } else {
1582 const qreal defaultMargin = 10; // ~3.5 mm
1583 margins << defaultMargin << defaultMargin
1584 << defaultMargin << defaultMargin;
1585 }
1586 ret = margins;
1587 break;
1588 }
1589 default:
1590 break;
1591 }
1592 return ret;
1593}
1594
1595QPdfBaseEnginePrivate::QPdfBaseEnginePrivate(QPrinter::PrinterMode m)
1596 : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false),
1597 useAlphaEngine(false),
1598 outDevice(0), fd(-1),
1599 duplex(QPrinter::DuplexNone), collate(false), fullPage(false), embedFonts(true), copies(1),
1600 pageOrder(QPrinter::FirstPageFirst), orientation(QPrinter::Portrait),
1601 paperSize(QPrinter::A4), colorMode(QPrinter::Color), paperSource(QPrinter::Auto),
1602 hasCustomPageMargins(false),
1603 leftMargin(0), topMargin(0), rightMargin(0), bottomMargin(0)
1604{
1605 resolution = 72;
1606 if (m == QPrinter::HighResolution)
1607 resolution = 1200;
1608 else if (m == QPrinter::ScreenResolution)
1609 resolution = qt_defaultDpi();
1610
1611 postscript = false;
1612 currentObject = 1;
1613 currentPage = 0;
1614 stroker.stream = 0;
1615}
1616
1617bool QPdfBaseEngine::begin(QPaintDevice *pdev)
1618{
1619 Q_D(QPdfBaseEngine);
1620 d->pdev = pdev;
1621
1622 d->postscript = false;
1623 d->currentObject = 1;
1624
1625 d->currentPage = new QPdfPage;
1626 d->stroker.stream = d->currentPage;
1627 d->opacity = 1.0;
1628
1629 return d->openPrintDevice();
1630}
1631
1632bool QPdfBaseEngine::end()
1633{
1634 Q_D(QPdfBaseEngine);
1635 qDeleteAll(d->fonts);
1636 d->fonts.clear();
1637 delete d->currentPage;
1638 d->currentPage = 0;
1639
1640 d->closePrintDevice();
1641 return true;
1642}
1643
1644#ifndef QT_NO_LPR
1645static void closeAllOpenFds()
1646{
1647 // hack time... getting the maximum number of open
1648 // files, if possible. if not we assume it's the
1649 // larger of 256 and the fd we got
1650 int i;
1651#if defined(_SC_OPEN_MAX)
1652 i = (int)sysconf(_SC_OPEN_MAX);
1653#elif defined(_POSIX_OPEN_MAX)
1654 i = (int)_POSIX_OPEN_MAX;
1655#elif defined(OPEN_MAX)
1656 i = (int)OPEN_MAX;
1657#else
1658 i = 256;
1659#endif
1660 // leave stdin/out/err untouched
1661 while(--i > 2)
1662 QT_CLOSE(i);
1663}
1664#endif
1665
1666bool QPdfBaseEnginePrivate::openPrintDevice()
1667{
1668 if(outDevice)
1669 return false;
1670
1671 if (!outputFileName.isEmpty()) {
1672 QFile *file = new QFile(outputFileName);
1673 if (! file->open(QFile::WriteOnly|QFile::Truncate)) {
1674 delete file;
1675 return false;
1676 }
1677 outDevice = file;
1678#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
1679 } else if (QCUPSSupport::isAvailable()) {
1680 QCUPSSupport cups;
1681 QPair<int, QString> ret = cups.tempFd();
1682 if (ret.first < 0) {
1683 qWarning("QPdfPrinter: Could not open temporary file to print");
1684 return false;
1685 }
1686#if defined(Q_WS_PM)
1687 // some CUPS versions don't set the mode of the returned fd to O_BINARY
1688 // which is vital for correct PDF generation (consider binary blocks);
1689 // force it here
1690 ::setmode(ret.first, O_BINARY);
1691#endif
1692 cupsTempFile = ret.second;
1693 outDevice = new QFile();
1694 static_cast<QFile *>(outDevice)->open(ret.first, QIODevice::WriteOnly);
1695 // save fd to get the temporary file closed in closePrintDevice()
1696 fd = ret.first;
1697#endif
1698#ifndef QT_NO_LPR
1699 } else {
1700 QString pr;
1701 if (!printerName.isEmpty())
1702 pr = printerName;
1703 int fds[2];
1704 if (qt_safe_pipe(fds) != 0) {
1705 qWarning("QPdfPrinter: Could not open pipe to print");
1706 return false;
1707 }
1708
1709 pid_t pid = fork();
1710 if (pid == 0) { // child process
1711 // if possible, exit quickly, so the actual lp/lpr
1712 // becomes a child of init, and ::waitpid() is
1713 // guaranteed not to wait.
1714 if (fork() > 0) {
1715 closeAllOpenFds();
1716
1717 // try to replace this process with "true" - this prevents
1718 // global destructors from being called (that could possibly
1719 // do wrong things to the parent process)
1720 (void)execlp("true", "true", (char *)0);
1721 (void)execl("/bin/true", "true", (char *)0);
1722 (void)execl("/usr/bin/true", "true", (char *)0);
1723 ::_exit(0);
1724 }
1725 qt_safe_dup2(fds[0], 0, 0);
1726
1727 closeAllOpenFds();
1728
1729 if (!printProgram.isEmpty()) {
1730 if (!selectionOption.isEmpty())
1731 pr.prepend(selectionOption);
1732 else
1733 pr.prepend(QLatin1String("-P"));
1734 (void)execlp(printProgram.toLocal8Bit().data(), printProgram.toLocal8Bit().data(),
1735 pr.toLocal8Bit().data(), (char *)0);
1736 } else {
1737 // if no print program has been specified, be smart
1738 // about the option string too.
1739 QList<QByteArray> lprhack;
1740 QList<QByteArray> lphack;
1741 QByteArray media;
1742 if (!pr.isEmpty() || !selectionOption.isEmpty()) {
1743 if (!selectionOption.isEmpty()) {
1744 QStringList list = selectionOption.split(QLatin1Char(' '));
1745 for (int i = 0; i < list.size(); ++i)
1746 lprhack.append(list.at(i).toLocal8Bit());
1747 lphack = lprhack;
1748 } else {
1749 lprhack.append("-P");
1750 lphack.append("-d");
1751 }
1752 lprhack.append(pr.toLocal8Bit());
1753 lphack.append(pr.toLocal8Bit());
1754 }
1755 lphack.append("-s");
1756
1757 char ** lpargs = new char *[lphack.size()+6];
1758 char lp[] = "lp";
1759 lpargs[0] = lp;
1760 int i;
1761 for (i = 0; i < lphack.size(); ++i)
1762 lpargs[i+1] = (char *)lphack.at(i).constData();
1763#ifndef Q_OS_OSF
1764 if (QPdf::paperSizeToString(paperSize)) {
1765 char dash_o[] = "-o";
1766 lpargs[++i] = dash_o;
1767 lpargs[++i] = const_cast<char *>(QPdf::paperSizeToString(paperSize));
1768 lpargs[++i] = dash_o;
1769 media = "media=";
1770 media += QPdf::paperSizeToString(paperSize);
1771 lpargs[++i] = media.data();
1772 }
1773#endif
1774 lpargs[++i] = 0;
1775 char **lprargs = new char *[lprhack.size()+2];
1776 char lpr[] = "lpr";
1777 lprargs[0] = lpr;
1778 for (int i = 0; i < lprhack.size(); ++i)
1779 lprargs[i+1] = (char *)lprhack[i].constData();
1780 lprargs[lprhack.size() + 1] = 0;
1781 (void)execvp("lp", lpargs);
1782 (void)execvp("lpr", lprargs);
1783 (void)execv("/bin/lp", lpargs);
1784 (void)execv("/bin/lpr", lprargs);
1785 (void)execv("/usr/bin/lp", lpargs);
1786 (void)execv("/usr/bin/lpr", lprargs);
1787
1788 delete []lpargs;
1789 delete []lprargs;
1790 }
1791 // if we couldn't exec anything, close the fd,
1792 // wait for a second so the parent process (the
1793 // child of the GUI process) has exited. then
1794 // exit.
1795 QT_CLOSE(0);
1796 (void)::sleep(1);
1797 ::_exit(0);
1798 }
1799 // parent process
1800 QT_CLOSE(fds[0]);
1801 fd = fds[1];
1802 (void)qt_safe_waitpid(pid, 0, 0);
1803
1804 if (fd < 0)
1805 return false;
1806
1807 outDevice = new QFile();
1808 static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly);
1809#endif
1810 }
1811
1812 return true;
1813}
1814
1815void QPdfBaseEnginePrivate::closePrintDevice()
1816{
1817 if (!outDevice)
1818 return;
1819 outDevice->close();
1820 if (fd >= 0)
1821#if defined(Q_OS_WIN) && defined(_MSC_VER) && _MSC_VER >= 1400
1822 ::_close(fd);
1823#else
1824 ::close(fd);
1825#endif
1826 fd = -1;
1827 delete outDevice;
1828 outDevice = 0;
1829
1830#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
1831 if (!cupsTempFile.isEmpty()) {
1832 QString tempFile = cupsTempFile;
1833 cupsTempFile.clear();
1834 QCUPSSupport cups;
1835
1836 // Set up print options.
1837 QByteArray prnName;
1838 QList<QPair<QByteArray, QByteArray> > options;
1839 QVector<cups_option_t> cupsOptStruct;
1840
1841 if (!printerName.isEmpty()) {
1842 prnName = printerName.toLocal8Bit();
1843 } else {
1844 QPrinterInfo def = QPrinterInfo::defaultPrinter();
1845 if (def.isNull()) {
1846 qWarning("Could not determine printer to print to");
1847 QFile::remove(tempFile);
1848 return;
1849 }
1850 prnName = def.printerName().toLocal8Bit();
1851 }
1852
1853 if (!cupsStringPageSize.isEmpty()) {
1854 options.append(QPair<QByteArray, QByteArray>("media", cupsStringPageSize.toLocal8Bit()));
1855 }
1856
1857 if (copies > 1) {
1858 options.append(QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit()));
1859 }
1860
1861 if (collate) {
1862 options.append(QPair<QByteArray, QByteArray>("Collate", "True"));
1863 }
1864
1865 if (duplex != QPrinter::DuplexNone) {
1866 switch(duplex) {
1867 case QPrinter::DuplexNone: break;
1868 case QPrinter::DuplexAuto:
1869 if (orientation == QPrinter::Portrait)
1870 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
1871 else
1872 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
1873 break;
1874 case QPrinter::DuplexLongSide:
1875 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
1876 break;
1877 case QPrinter::DuplexShortSide:
1878 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
1879 break;
1880 }
1881 }
1882
1883 if (QCUPSSupport::cupsVersion() >= 10300 && orientation == QPrinter::Landscape) {
1884 options.append(QPair<QByteArray, QByteArray>("landscape", ""));
1885 }
1886
1887 QStringList::const_iterator it = cupsOptions.constBegin();
1888 while (it != cupsOptions.constEnd()) {
1889 options.append(QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit()));
1890 it += 2;
1891 }
1892
1893 for (int c = 0; c < options.size(); ++c) {
1894 cups_option_t opt;
1895 opt.name = options[c].first.data();
1896 opt.value = options[c].second.data();
1897 cupsOptStruct.append(opt);
1898 }
1899
1900 // Print the file.
1901 cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0;
1902 cups.printFile(prnName.constData(), tempFile.toLocal8Bit().constData(),
1903 title.toLocal8Bit().constData(), cupsOptStruct.size(), optPtr);
1904
1905 QFile::remove(tempFile);
1906 }
1907#endif
1908}
1909
1910QPdfBaseEnginePrivate::~QPdfBaseEnginePrivate()
1911{
1912 qDeleteAll(fonts);
1913 delete currentPage;
1914}
1915
1916void QPdfBaseEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti)
1917{
1918 Q_Q(QPdfBaseEngine);
1919
1920 QFontEngine *fe = ti.fontEngine;
1921
1922 QFontEngine::FaceId face_id = fe->faceId();
1923 bool noEmbed = false;
1924 if (face_id.filename.isEmpty()
1925 || (!postscript && ((fe->fsType & 0x200) /* bitmap embedding only */
1926 || (fe->fsType == 2) /* no embedding allowed */))) {
1927 *currentPage << "Q\n";
1928 q->QPaintEngine::drawTextItem(p, ti);
1929 *currentPage << "q\n";
1930 if (face_id.filename.isEmpty())
1931 return;
1932 noEmbed = true;
1933 }
1934
1935 QFontSubset *font = fonts.value(face_id, 0);
1936 if (!font) {
1937 font = new QFontSubset(fe, requestObject());
1938 font->noEmbed = noEmbed;
1939 }
1940 fonts.insert(face_id, font);
1941
1942 if (!currentPage->fonts.contains(font->object_id))
1943 currentPage->fonts.append(font->object_id);
1944
1945 qreal size = ti.fontEngine->fontDef.pixelSize;
1946#ifdef Q_WS_WIN
1947 if (ti.fontEngine->type() == QFontEngine::Win) {
1948 QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine);
1949 size = fe->tm.tmHeight;
1950 }
1951#endif
1952
1953 QVarLengthArray<glyph_t> glyphs;
1954 QVarLengthArray<QFixedPoint> positions;
1955 QTransform m = QTransform::fromTranslate(p.x(), p.y());
1956 ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags,
1957 glyphs, positions);
1958 if (glyphs.size() == 0)
1959 return;
1960 int synthesized = ti.fontEngine->synthesized();
1961 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
1962
1963 *currentPage << "BT\n"
1964 << "/F" << font->object_id << size << "Tf "
1965 << stretch << (synthesized & QFontEngine::SynthesizedItalic
1966 ? "0 .3 -1 0 0 Tm\n"
1967 : "0 0 -1 0 0 Tm\n");
1968
1969
1970#if 0
1971 // #### implement actual text for complex languages
1972 const unsigned short *logClusters = ti.logClusters;
1973 int pos = 0;
1974 do {
1975 int end = pos + 1;
1976 while (end < ti.num_chars && logClusters[end] == logClusters[pos])
1977 ++end;
1978 *currentPage << "/Span << /ActualText <FEFF";
1979 for (int i = pos; i < end; ++i) {
1980 s << toHex((ushort)ti.chars[i].unicode(), buf);
1981 }
1982 *currentPage << "> >>\n"
1983 "BDC\n"
1984 "<";
1985 int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end];
1986 for (int gs = logClusters[pos]; gs < ge; ++gs)
1987 *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf);
1988 *currentPage << "> Tj\n"
1989 "EMC\n";
1990 pos = end;
1991 } while (pos < ti.num_chars);
1992#else
1993 qreal last_x = 0.;
1994 qreal last_y = 0.;
1995 for (int i = 0; i < glyphs.size(); ++i) {
1996 qreal x = positions[i].x.toReal();
1997 qreal y = positions[i].y.toReal();
1998 if (synthesized & QFontEngine::SynthesizedItalic)
1999 x += .3*y;
2000 x /= stretch;
2001 char buf[5];
2002 int g = font->addGlyph(glyphs[i]);
2003 *currentPage << x - last_x << last_y - y << "Td <"
2004 << QPdf::toHex((ushort)g, buf) << "> Tj\n";
2005 last_x = x;
2006 last_y = y;
2007 }
2008 if (synthesized & QFontEngine::SynthesizedBold) {
2009 *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic
2010 ? "0 .3 -1 0 0 Tm\n"
2011 : "0 0 -1 0 0 Tm\n");
2012 *currentPage << "/Span << /ActualText <> >> BDC\n";
2013 last_x = 0.5*fe->lineThickness().toReal();
2014 last_y = 0.;
2015 for (int i = 0; i < glyphs.size(); ++i) {
2016 qreal x = positions[i].x.toReal();
2017 qreal y = positions[i].y.toReal();
2018 if (synthesized & QFontEngine::SynthesizedItalic)
2019 x += .3*y;
2020 x /= stretch;
2021 char buf[5];
2022 int g = font->addGlyph(glyphs[i]);
2023 *currentPage << x - last_x << last_y - y << "Td <"
2024 << QPdf::toHex((ushort)g, buf) << "> Tj\n";
2025 last_x = x;
2026 last_y = y;
2027 }
2028 *currentPage << "EMC\n";
2029 }
2030#endif
2031
2032 *currentPage << "ET\n";
2033}
2034
2035QRect QPdfBaseEnginePrivate::paperRect() const
2036{
2037 int w;
2038 int h;
2039 if (paperSize == QPrinter::Custom) {
2040 w = qRound(customPaperSize.width()*resolution/72.);
2041 h = qRound(customPaperSize.height()*resolution/72.);
2042 } else {
2043#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
2044 if (QCUPSSupport::isAvailable() && !cupsPaperRect.isNull()) {
2045 QRect r = cupsPaperRect;
2046 w = r.width();
2047 h = r.height();
2048 } else
2049#endif
2050 {
2051 QPdf::PaperSize s = QPdf::paperSize(paperSize);
2052 w = s.width;
2053 h = s.height;
2054 }
2055 w = qRound(w*resolution/72.);
2056 h = qRound(h*resolution/72.);
2057 }
2058 if (orientation == QPrinter::Portrait)
2059 return QRect(0, 0, w, h);
2060 else
2061 return QRect(0, 0, h, w);
2062}
2063
2064QRect QPdfBaseEnginePrivate::pageRect() const
2065{
2066 if(fullPage)
2067 return paperRect();
2068
2069 QRect r;
2070
2071#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
2072 if (!hasCustomPageMargins && QCUPSSupport::isAvailable() && !cupsPageRect.isNull()) {
2073 r = cupsPageRect;
2074 if (r == cupsPaperRect) {
2075 // if cups doesn't define any margins, give it at least approx 3.5 mm
2076 r = QRect(10, 10, r.width() - 20, r.height() - 20);
2077 }
2078 } else
2079#endif
2080 {
2081 QPdf::PaperSize s;
2082 if (paperSize == QPrinter::Custom) {
2083 s.width = qRound(customPaperSize.width());
2084 s.height = qRound(customPaperSize.height());
2085 } else {
2086 s = QPdf::paperSize(paperSize);
2087 }
2088 if (hasCustomPageMargins)
2089 r = QRect(0, 0, s.width, s.height);
2090 else
2091 r = QRect(72/3, 72/3, s.width - 2*72/3, s.height - 2*72/3);
2092 }
2093
2094 int x = qRound(r.left()*resolution/72.);
2095 int y = qRound(r.top()*resolution/72.);
2096 int w = qRound(r.width()*resolution/72.);
2097 int h = qRound(r.height()*resolution/72.);
2098 if (orientation == QPrinter::Portrait)
2099 r = QRect(x, y, w, h);
2100 else
2101 r = QRect(y, x, h, w);
2102
2103 if (hasCustomPageMargins) {
2104 r.adjust(qRound(leftMargin*(resolution/72.)),
2105 qRound(topMargin*(resolution/72.)),
2106 -qRound(rightMargin*(resolution/72.)),
2107 -qRound(bottomMargin*(resolution/72.)));
2108 }
2109 return r;
2110}
2111
2112#endif
2113
2114QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.