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

Last change on this file since 574 was 574, checked in by Dmitry A. Kuminov, 15 years ago

gui: Printing/CUPS: Fixed: Temporary files created by CUPS on request were not always deleted.

File size: 59.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 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
57extern 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
919QByteArray QPdf::stripSpecialCharacters(const QByteArray &string)
920{
921 QByteArray s = string;
922 s.replace(' ', "");
923 s.replace('(', "");
924 s.replace(')', "");
925 s.replace('<', "");
926 s.replace('>', "");
927 s.replace('[', "");
928 s.replace(']', "");
929 s.replace('{', "");
930 s.replace('}', "");
931 s.replace('/', "");
932 s.replace('%', "");
933 return s;
934}
935
936
937// -------------------------- base engine, shared code between PS and PDF -----------------------
938
939QPdfBaseEngine::QPdfBaseEngine(QPdfBaseEnginePrivate &dd, PaintEngineFeatures f)
940 : QAlphaPaintEngine(dd, f)
941{
942 Q_D(QPdfBaseEngine);
943#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
944 if (QCUPSSupport::isAvailable()) {
945 QCUPSSupport cups;
946 const cups_dest_t* printers = cups.availablePrinters();
947 int prnCount = cups.availablePrintersCount();
948
949 for (int i = 0; i < prnCount; ++i) {
950 if (printers[i].is_default) {
951 d->printerName = QString::fromLocal8Bit(printers[i].name);
952 break;
953 }
954 }
955
956 } else
957#endif
958 {
959 d->printerName = QString::fromLocal8Bit(qgetenv("PRINTER"));
960 if (d->printerName.isEmpty())
961 d->printerName = QString::fromLocal8Bit(qgetenv("LPDEST"));
962 if (d->printerName.isEmpty())
963 d->printerName = QString::fromLocal8Bit(qgetenv("NPRINTER"));
964 if (d->printerName.isEmpty())
965 d->printerName = QString::fromLocal8Bit(qgetenv("NGPRINTER"));
966 }
967}
968
969void QPdfBaseEngine::drawPoints (const QPointF *points, int pointCount)
970{
971 if (!points)
972 return;
973
974 QPainterPath p;
975 for (int i=0; i!=pointCount;++i) {
976 p.moveTo(points[i]);
977 p.lineTo(points[i] + QPointF(0, 0.001));
978 }
979 drawPath(p);
980}
981
982void QPdfBaseEngine::drawLines (const QLineF *lines, int lineCount)
983{
984 if (!lines)
985 return;
986
987 QPainterPath p;
988 for (int i=0; i!=lineCount;++i) {
989 p.moveTo(lines[i].p1());
990 p.lineTo(lines[i].p2());
991 }
992 drawPath(p);
993}
994
995void QPdfBaseEngine::drawRects (const QRectF *rects, int rectCount)
996{
997 if (!rects)
998 return;
999
1000 Q_D(QPdfBaseEngine);
1001 if (d->useAlphaEngine) {
1002 QAlphaPaintEngine::drawRects(rects, rectCount);
1003 if (!continueCall())
1004 return;
1005 }
1006
1007 if (d->clipEnabled && d->allClipped)
1008 return;
1009 if (!d->hasPen && !d->hasBrush)
1010 return;
1011
1012 QBrush penBrush = d->pen.brush();
1013 if (d->simplePen || !d->hasPen) {
1014 // draw strokes natively in this case for better output
1015 if(!d->simplePen && !d->stroker.matrix.isIdentity())
1016 *d->currentPage << "q\n" << QPdf::generateMatrix(d->stroker.matrix);
1017 for (int i = 0; i < rectCount; ++i)
1018 *d->currentPage << rects[i].x() << rects[i].y() << rects[i].width() << rects[i].height() << "re\n";
1019 *d->currentPage << (d->hasPen ? (d->hasBrush ? "B\n" : "S\n") : "f\n");
1020 if(!d->simplePen && !d->stroker.matrix.isIdentity())
1021 *d->currentPage << "Q\n";
1022 } else {
1023 QPainterPath p;
1024 for (int i=0; i!=rectCount; ++i)
1025 p.addRect(rects[i]);
1026 drawPath(p);
1027 }
1028}
1029
1030void QPdfBaseEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode)
1031{
1032 Q_D(QPdfBaseEngine);
1033
1034 if (d->useAlphaEngine) {
1035 QAlphaPaintEngine::drawPolygon(points, pointCount, mode);
1036 if (!continueCall())
1037 return;
1038 }
1039
1040 if (!points || !pointCount)
1041 return;
1042
1043 bool hb = d->hasBrush;
1044 QPainterPath p;
1045
1046 switch(mode) {
1047 case OddEvenMode:
1048 p.setFillRule(Qt::OddEvenFill);
1049 break;
1050 case ConvexMode:
1051 case WindingMode:
1052 p.setFillRule(Qt::WindingFill);
1053 break;
1054 case PolylineMode:
1055 d->hasBrush = false;
1056 break;
1057 default:
1058 break;
1059 }
1060
1061 p.moveTo(points[0]);
1062 for (int i = 1; i < pointCount; ++i)
1063 p.lineTo(points[i]);
1064
1065 if (mode != PolylineMode)
1066 p.closeSubpath();
1067 drawPath(p);
1068
1069 d->hasBrush = hb;
1070}
1071
1072void QPdfBaseEngine::drawPath (const QPainterPath &p)
1073{
1074 Q_D(QPdfBaseEngine);
1075
1076 if (d->useAlphaEngine) {
1077 QAlphaPaintEngine::drawPath(p);
1078 if (!continueCall())
1079 return;
1080 }
1081
1082 if (d->clipEnabled && d->allClipped)
1083 return;
1084 if (!d->hasPen && !d->hasBrush)
1085 return;
1086
1087 if (d->simplePen) {
1088 // draw strokes natively in this case for better output
1089 *d->currentPage << QPdf::generatePath(p, QTransform(), d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath);
1090 } else {
1091 if (d->hasBrush)
1092 *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath);
1093 if (d->hasPen) {
1094 *d->currentPage << "q\n";
1095 QBrush b = d->brush;
1096 d->brush = d->pen.brush();
1097 setBrush();
1098 d->stroker.strokePath(p);
1099 *d->currentPage << "Q\n";
1100 d->brush = b;
1101 }
1102 }
1103}
1104
1105void QPdfBaseEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
1106{
1107 Q_D(QPdfBaseEngine);
1108
1109 if (d->useAlphaEngine) {
1110 QAlphaPaintEngine::drawTextItem(p, textItem);
1111 if (!continueCall())
1112 return;
1113 }
1114
1115 if (!d->hasPen || (d->clipEnabled && d->allClipped))
1116 return;
1117
1118 if (d->stroker.matrix.type() >= QTransform::TxProject) {
1119 QPaintEngine::drawTextItem(p, textItem);
1120 return;
1121 }
1122
1123 *d->currentPage << "q\n";
1124 if(!d->simplePen)
1125 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1126
1127 bool hp = d->hasPen;
1128 d->hasPen = false;
1129 QBrush b = d->brush;
1130 d->brush = d->pen.brush();
1131 setBrush();
1132
1133 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
1134 Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi);
1135 d->drawTextItem(p, ti);
1136 d->hasPen = hp;
1137 d->brush = b;
1138 *d->currentPage << "Q\n";
1139}
1140
1141
1142void QPdfBaseEngine::updateState(const QPaintEngineState &state)
1143{
1144 Q_D(QPdfBaseEngine);
1145
1146 if (d->useAlphaEngine) {
1147 QAlphaPaintEngine::updateState(state);
1148 if (!continueCall())
1149 return;
1150 }
1151
1152 QPaintEngine::DirtyFlags flags = state.state();
1153
1154 if (flags & DirtyTransform)
1155 d->stroker.matrix = state.transform();
1156
1157 if (flags & DirtyPen) {
1158 d->pen = state.pen();
1159 d->hasPen = d->pen.style() != Qt::NoPen;
1160 d->stroker.setPen(d->pen);
1161 QBrush penBrush = d->pen.brush();
1162 bool oldSimple = d->simplePen;
1163 d->simplePen = (d->hasPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque());
1164 if (oldSimple != d->simplePen)
1165 flags |= DirtyTransform;
1166 }
1167 if (flags & DirtyBrush) {
1168 d->brush = state.brush();
1169 d->hasBrush = d->brush.style() != Qt::NoBrush;
1170 }
1171 if (flags & DirtyBrushOrigin) {
1172 d->brushOrigin = state.brushOrigin();
1173 flags |= DirtyBrush;
1174 }
1175 if (flags & DirtyOpacity)
1176 d->opacity = state.opacity();
1177
1178 bool ce = d->clipEnabled;
1179 if (flags & DirtyClipPath) {
1180 d->clipEnabled = true;
1181 updateClipPath(state.clipPath(), state.clipOperation());
1182 } else if (flags & DirtyClipRegion) {
1183 d->clipEnabled = true;
1184 QPainterPath path;
1185 QVector<QRect> rects = state.clipRegion().rects();
1186 for (int i = 0; i < rects.size(); ++i)
1187 path.addRect(rects.at(i));
1188 updateClipPath(path, state.clipOperation());
1189 flags |= DirtyClipPath;
1190 } else if (flags & DirtyClipEnabled) {
1191 d->clipEnabled = state.isClipEnabled();
1192 }
1193
1194 if (ce != d->clipEnabled)
1195 flags |= DirtyClipPath;
1196 else if (!d->clipEnabled)
1197 flags &= ~DirtyClipPath;
1198
1199 setupGraphicsState(flags);
1200}
1201
1202void QPdfBaseEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags)
1203{
1204 Q_D(QPdfBaseEngine);
1205 if (flags & DirtyClipPath)
1206 flags |= DirtyTransform|DirtyPen|DirtyBrush;
1207
1208 if (flags & DirtyTransform) {
1209 *d->currentPage << "Q\n";
1210 flags |= DirtyPen|DirtyBrush;
1211 }
1212
1213 if (flags & DirtyClipPath) {
1214 *d->currentPage << "Q q\n";
1215
1216 d->allClipped = false;
1217 if (d->clipEnabled && !d->clips.isEmpty()) {
1218 for (int i = 0; i < d->clips.size(); ++i) {
1219 if (d->clips.at(i).isEmpty()) {
1220 d->allClipped = true;
1221 break;
1222 }
1223 }
1224 if (!d->allClipped) {
1225 for (int i = 0; i < d->clips.size(); ++i) {
1226 *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath);
1227 }
1228 }
1229 }
1230 }
1231
1232 if (flags & DirtyTransform) {
1233 *d->currentPage << "q\n";
1234 if (d->simplePen && !d->stroker.matrix.isIdentity())
1235 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1236 }
1237 if (flags & DirtyBrush)
1238 setBrush();
1239 if (d->simplePen && (flags & DirtyPen))
1240 setPen();
1241}
1242
1243extern QPainterPath qt_regionToPath(const QRegion &region);
1244
1245void QPdfBaseEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op)
1246{
1247 Q_D(QPdfBaseEngine);
1248 QPainterPath path = d->stroker.matrix.map(p);
1249 //qDebug() << "updateClipPath: " << d->stroker.matrix << p.boundingRect() << path.boundingRect() << op;
1250
1251 if (op == Qt::NoClip) {
1252 d->clipEnabled = false;
1253 d->clips.clear();
1254 } else if (op == Qt::ReplaceClip) {
1255 d->clips.clear();
1256 d->clips.append(path);
1257 } else if (op == Qt::IntersectClip) {
1258 d->clips.append(path);
1259 } else { // UniteClip
1260 // ask the painter for the current clipping path. that's the easiest solution
1261 path = painter()->clipPath();
1262 path = d->stroker.matrix.map(path);
1263 d->clips.clear();
1264 d->clips.append(path);
1265 }
1266
1267 if (d->useAlphaEngine) {
1268 // if we have an alpha region, we have to subtract that from the
1269 // any existing clip region since that region will be filled in
1270 // later with images
1271 QPainterPath alphaClip = qt_regionToPath(alphaClipping());
1272 if (!alphaClip.isEmpty()) {
1273 if (!d->clipEnabled) {
1274 QRect r = d->fullPage ? d->paperRect() : d->pageRect();
1275 QPainterPath dev;
1276 dev.addRect(QRect(0, 0, r.width(), r.height()));
1277 if (path.isEmpty())
1278 path = dev;
1279 else
1280 path = path.intersected(dev);
1281 d->clipEnabled = true;
1282 } else {
1283 path = painter()->clipPath();
1284 path = d->stroker.matrix.map(path);
1285 }
1286 path = path.subtracted(alphaClip);
1287 d->clips.clear();
1288 d->clips.append(path);
1289 }
1290 }
1291}
1292
1293void QPdfBaseEngine::setPen()
1294{
1295 Q_D(QPdfBaseEngine);
1296 if (d->pen.style() == Qt::NoPen)
1297 return;
1298 QBrush b = d->pen.brush();
1299 Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque());
1300
1301 QColor rgba = b.color();
1302 if (d->colorMode == QPrinter::GrayScale) {
1303 qreal gray = qGray(rgba.rgba())/255.;
1304 *d->currentPage << gray << gray << gray;
1305 } else {
1306 *d->currentPage << rgba.redF()
1307 << rgba.greenF()
1308 << rgba.blueF();
1309 }
1310 *d->currentPage << "SCN\n";
1311
1312 *d->currentPage << d->pen.widthF() << "w ";
1313
1314 int pdfCapStyle = 0;
1315 switch(d->pen.capStyle()) {
1316 case Qt::FlatCap:
1317 pdfCapStyle = 0;
1318 break;
1319 case Qt::SquareCap:
1320 pdfCapStyle = 2;
1321 break;
1322 case Qt::RoundCap:
1323 pdfCapStyle = 1;
1324 break;
1325 default:
1326 break;
1327 }
1328 *d->currentPage << pdfCapStyle << "J ";
1329
1330 int pdfJoinStyle = 0;
1331 switch(d->pen.joinStyle()) {
1332 case Qt::MiterJoin:
1333 pdfJoinStyle = 0;
1334 break;
1335 case Qt::BevelJoin:
1336 pdfJoinStyle = 2;
1337 break;
1338 case Qt::RoundJoin:
1339 pdfJoinStyle = 1;
1340 break;
1341 default:
1342 break;
1343 }
1344 *d->currentPage << pdfJoinStyle << "j ";
1345
1346 *d->currentPage << QPdf::generateDashes(d->pen) << " 0 d\n";
1347}
1348
1349bool QPdfBaseEngine::newPage()
1350{
1351 Q_D(QPdfBaseEngine);
1352 setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath);
1353 QFile *outfile = qobject_cast<QFile*> (d->outDevice);
1354 if (outfile && outfile->error() != QFile::NoError)
1355 return false;
1356 return true;
1357}
1358
1359
1360int QPdfBaseEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const
1361{
1362 Q_D(const QPdfBaseEngine);
1363 int val;
1364 QRect r = d->fullPage ? d->paperRect() : d->pageRect();
1365 switch (metricType) {
1366 case QPaintDevice::PdmWidth:
1367 val = r.width();
1368 break;
1369 case QPaintDevice::PdmHeight:
1370 val = r.height();
1371 break;
1372 case QPaintDevice::PdmDpiX:
1373 case QPaintDevice::PdmDpiY:
1374 val = d->resolution;
1375 break;
1376 case QPaintDevice::PdmPhysicalDpiX:
1377 case QPaintDevice::PdmPhysicalDpiY:
1378 val = 1200;
1379 break;
1380 case QPaintDevice::PdmWidthMM:
1381 val = qRound(r.width()*25.4/d->resolution);
1382 break;
1383 case QPaintDevice::PdmHeightMM:
1384 val = qRound(r.height()*25.4/d->resolution);
1385 break;
1386 case QPaintDevice::PdmNumColors:
1387 val = INT_MAX;
1388 break;
1389 case QPaintDevice::PdmDepth:
1390 val = 32;
1391 break;
1392 default:
1393 qWarning("QPrinter::metric: Invalid metric command");
1394 return 0;
1395 }
1396 return val;
1397}
1398
1399void QPdfBaseEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value)
1400{
1401 Q_D(QPdfBaseEngine);
1402 switch (key) {
1403 case PPK_CollateCopies:
1404 d->collate = value.toBool();
1405 break;
1406 case PPK_ColorMode:
1407 d->colorMode = QPrinter::ColorMode(value.toInt());
1408 break;
1409 case PPK_Creator:
1410 d->creator = value.toString();
1411 break;
1412 case PPK_DocumentName:
1413 d->title = value.toString();
1414 break;
1415 case PPK_FullPage:
1416 d->fullPage = value.toBool();
1417 break;
1418 case PPK_NumberOfCopies:
1419 d->copies = value.toInt();
1420 break;
1421 case PPK_Orientation:
1422 d->orientation = QPrinter::Orientation(value.toInt());
1423 break;
1424 case PPK_OutputFileName:
1425 d->outputFileName = value.toString();
1426 break;
1427 case PPK_PageOrder:
1428 d->pageOrder = QPrinter::PageOrder(value.toInt());
1429 break;
1430 case PPK_PaperSize:
1431 d->paperSize = QPrinter::PaperSize(value.toInt());
1432 break;
1433 case PPK_PaperSource:
1434 d->paperSource = QPrinter::PaperSource(value.toInt());
1435 break;
1436 case PPK_PrinterName:
1437 d->printerName = value.toString();
1438 break;
1439 case PPK_PrinterProgram:
1440 d->printProgram = value.toString();
1441 break;
1442 case PPK_Resolution:
1443 d->resolution = value.toInt();
1444 break;
1445 case PPK_SelectionOption:
1446 d->selectionOption = value.toString();
1447 break;
1448 case PPK_FontEmbedding:
1449 d->embedFonts = value.toBool();
1450 break;
1451 case PPK_Duplex:
1452 d->duplex = static_cast<QPrinter::DuplexMode> (value.toInt());
1453 break;
1454 case PPK_CupsPageRect:
1455 d->cupsPageRect = value.toRect();
1456 break;
1457 case PPK_CupsPaperRect:
1458 d->cupsPaperRect = value.toRect();
1459 break;
1460 case PPK_CupsOptions:
1461 d->cupsOptions = value.toStringList();
1462 break;
1463 case PPK_CupsStringPageSize:
1464 d->cupsStringPageSize = value.toString();
1465 break;
1466 case PPK_CustomPaperSize:
1467 d->paperSize = QPrinter::Custom;
1468 d->customPaperSize = value.toSizeF();
1469 break;
1470 case PPK_PageMargins:
1471 {
1472 QList<QVariant> margins(value.toList());
1473 Q_ASSERT(margins.size() == 4);
1474 d->leftMargin = margins.at(0).toReal();
1475 d->topMargin = margins.at(1).toReal();
1476 d->rightMargin = margins.at(2).toReal();
1477 d->bottomMargin = margins.at(3).toReal();
1478 d->hasCustomPageMargins = true;
1479 break;
1480 }
1481 default:
1482 break;
1483 }
1484}
1485
1486QVariant QPdfBaseEngine::property(PrintEnginePropertyKey key) const
1487{
1488 Q_D(const QPdfBaseEngine);
1489
1490 QVariant ret;
1491 switch (key) {
1492 case PPK_CollateCopies:
1493 ret = d->collate;
1494 break;
1495 case PPK_ColorMode:
1496 ret = d->colorMode;
1497 break;
1498 case PPK_Creator:
1499 ret = d->creator;
1500 break;
1501 case PPK_DocumentName:
1502 ret = d->title;
1503 break;
1504 case PPK_FullPage:
1505 ret = d->fullPage;
1506 break;
1507 case PPK_NumberOfCopies:
1508#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
1509 if (QCUPSSupport::isAvailable())
1510 ret = 1;
1511 else
1512#endif
1513 ret = d->copies;
1514 break;
1515 case PPK_Orientation:
1516 ret = d->orientation;
1517 break;
1518 case PPK_OutputFileName:
1519 ret = d->outputFileName;
1520 break;
1521 case PPK_PageOrder:
1522 ret = d->pageOrder;
1523 break;
1524 case PPK_PaperSize:
1525 ret = d->paperSize;
1526 break;
1527 case PPK_PaperSource:
1528 ret = d->paperSource;
1529 break;
1530 case PPK_PrinterName:
1531 ret = d->printerName;
1532 break;
1533 case PPK_PrinterProgram:
1534 ret = d->printProgram;
1535 break;
1536 case PPK_Resolution:
1537 ret = d->resolution;
1538 break;
1539 case PPK_SupportedResolutions:
1540 ret = QList<QVariant>() << 72;
1541 break;
1542 case PPK_PaperRect:
1543 ret = d->paperRect();
1544 break;
1545 case PPK_PageRect:
1546 ret = d->pageRect();
1547 break;
1548 case PPK_SelectionOption:
1549 ret = d->selectionOption;
1550 break;
1551 case PPK_FontEmbedding:
1552 ret = d->embedFonts;
1553 break;
1554 case PPK_Duplex:
1555 ret = d->duplex;
1556 break;
1557 case PPK_CupsPageRect:
1558 ret = d->cupsPageRect;
1559 break;
1560 case PPK_CupsPaperRect:
1561 ret = d->cupsPaperRect;
1562 break;
1563 case PPK_CupsOptions:
1564 ret = d->cupsOptions;
1565 break;
1566 case PPK_CupsStringPageSize:
1567 ret = d->cupsStringPageSize;
1568 break;
1569 case PPK_CustomPaperSize:
1570 ret = d->customPaperSize;
1571 break;
1572 case PPK_PageMargins:
1573 {
1574 QList<QVariant> margins;
1575 if (d->hasCustomPageMargins) {
1576 margins << d->leftMargin << d->topMargin
1577 << d->rightMargin << d->bottomMargin;
1578 } else {
1579 const qreal defaultMargin = 10; // ~3.5 mm
1580 margins << defaultMargin << defaultMargin
1581 << defaultMargin << defaultMargin;
1582 }
1583 ret = margins;
1584 break;
1585 }
1586 default:
1587 break;
1588 }
1589 return ret;
1590}
1591
1592QPdfBaseEnginePrivate::QPdfBaseEnginePrivate(QPrinter::PrinterMode m)
1593 : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false),
1594 useAlphaEngine(false),
1595 outDevice(0), fd(-1),
1596 duplex(QPrinter::DuplexNone), collate(false), fullPage(false), embedFonts(true), copies(1),
1597 pageOrder(QPrinter::FirstPageFirst), orientation(QPrinter::Portrait),
1598 paperSize(QPrinter::A4), colorMode(QPrinter::Color), paperSource(QPrinter::Auto),
1599 hasCustomPageMargins(false),
1600 leftMargin(0), topMargin(0), rightMargin(0), bottomMargin(0)
1601{
1602 resolution = 72;
1603 if (m == QPrinter::HighResolution)
1604 resolution = 1200;
1605 else if (m == QPrinter::ScreenResolution)
1606 resolution = qt_defaultDpi();
1607
1608 postscript = false;
1609 currentObject = 1;
1610 currentPage = 0;
1611 stroker.stream = 0;
1612}
1613
1614bool QPdfBaseEngine::begin(QPaintDevice *pdev)
1615{
1616 Q_D(QPdfBaseEngine);
1617 d->pdev = pdev;
1618
1619 d->postscript = false;
1620 d->currentObject = 1;
1621
1622 d->currentPage = new QPdfPage;
1623 d->stroker.stream = d->currentPage;
1624 d->opacity = 1.0;
1625
1626 return d->openPrintDevice();
1627}
1628
1629bool QPdfBaseEngine::end()
1630{
1631 Q_D(QPdfBaseEngine);
1632 qDeleteAll(d->fonts);
1633 d->fonts.clear();
1634 delete d->currentPage;
1635 d->currentPage = 0;
1636
1637 d->closePrintDevice();
1638 return true;
1639}
1640
1641#ifndef QT_NO_LPR
1642static void closeAllOpenFds()
1643{
1644 // hack time... getting the maximum number of open
1645 // files, if possible. if not we assume it's the
1646 // larger of 256 and the fd we got
1647 int i;
1648#if defined(_SC_OPEN_MAX)
1649 i = (int)sysconf(_SC_OPEN_MAX);
1650#elif defined(_POSIX_OPEN_MAX)
1651 i = (int)_POSIX_OPEN_MAX;
1652#elif defined(OPEN_MAX)
1653 i = (int)OPEN_MAX;
1654#else
1655 i = 256;
1656#endif
1657 // leave stdin/out/err untouched
1658 while(--i > 2)
1659 QT_CLOSE(i);
1660}
1661#endif
1662
1663bool QPdfBaseEnginePrivate::openPrintDevice()
1664{
1665 if(outDevice)
1666 return false;
1667
1668 if (!outputFileName.isEmpty()) {
1669 QFile *file = new QFile(outputFileName);
1670 if (! file->open(QFile::WriteOnly|QFile::Truncate)) {
1671 delete file;
1672 return false;
1673 }
1674 outDevice = file;
1675#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
1676 } else if (QCUPSSupport::isAvailable()) {
1677 QCUPSSupport cups;
1678 QPair<int, QString> ret = cups.tempFd();
1679 if (ret.first < 0) {
1680 qWarning("QPdfPrinter: Could not open temporary file to print");
1681 return false;
1682 }
1683 cupsTempFile = ret.second;
1684 outDevice = new QFile();
1685 static_cast<QFile *>(outDevice)->open(ret.first, QIODevice::WriteOnly);
1686 // save fd to get the temporary file closed in closePrintDevice()
1687 fd = ret.first;
1688#endif
1689#ifndef QT_NO_LPR
1690 } else {
1691 QString pr;
1692 if (!printerName.isEmpty())
1693 pr = printerName;
1694 int fds[2];
1695#ifdef Q_WS_PM
1696 if (::pipe(fds) != 0) {
1697#else
1698 if (qt_safe_pipe(fds) != 0) {
1699#endif
1700 qWarning("QPdfPrinter: Could not open pipe to print");
1701 return false;
1702 }
1703
1704 pid_t pid = fork();
1705 if (pid == 0) { // child process
1706 // if possible, exit quickly, so the actual lp/lpr
1707 // becomes a child of init, and ::waitpid() is
1708 // guaranteed not to wait.
1709 if (fork() > 0) {
1710 closeAllOpenFds();
1711
1712 // try to replace this process with "true" - this prevents
1713 // global destructors from being called (that could possibly
1714 // do wrong things to the parent process)
1715 (void)execlp("true", "true", (char *)0);
1716 (void)execl("/bin/true", "true", (char *)0);
1717 (void)execl("/usr/bin/true", "true", (char *)0);
1718 ::_exit(0);
1719 }
1720#ifdef Q_WS_PM
1721 ::dup2(fds[0], 0);
1722#else
1723 qt_safe_dup2(fds[0], 0, 0);
1724#endif
1725
1726 closeAllOpenFds();
1727
1728 if (!printProgram.isEmpty()) {
1729 if (!selectionOption.isEmpty())
1730 pr.prepend(selectionOption);
1731 else
1732 pr.prepend(QLatin1String("-P"));
1733 (void)execlp(printProgram.toLocal8Bit().data(), printProgram.toLocal8Bit().data(),
1734 pr.toLocal8Bit().data(), (char *)0);
1735 } else {
1736 // if no print program has been specified, be smart
1737 // about the option string too.
1738 QList<QByteArray> lprhack;
1739 QList<QByteArray> lphack;
1740 QByteArray media;
1741 if (!pr.isEmpty() || !selectionOption.isEmpty()) {
1742 if (!selectionOption.isEmpty()) {
1743 QStringList list = selectionOption.split(QLatin1Char(' '));
1744 for (int i = 0; i < list.size(); ++i)
1745 lprhack.append(list.at(i).toLocal8Bit());
1746 lphack = lprhack;
1747 } else {
1748 lprhack.append("-P");
1749 lphack.append("-d");
1750 }
1751 lprhack.append(pr.toLocal8Bit());
1752 lphack.append(pr.toLocal8Bit());
1753 }
1754 lphack.append("-s");
1755
1756 char ** lpargs = new char *[lphack.size()+6];
1757 char lp[] = "lp";
1758 lpargs[0] = lp;
1759 int i;
1760 for (i = 0; i < lphack.size(); ++i)
1761 lpargs[i+1] = (char *)lphack.at(i).constData();
1762#ifndef Q_OS_OSF
1763 if (QPdf::paperSizeToString(paperSize)) {
1764 char dash_o[] = "-o";
1765 lpargs[++i] = dash_o;
1766 lpargs[++i] = const_cast<char *>(QPdf::paperSizeToString(paperSize));
1767 lpargs[++i] = dash_o;
1768 media = "media=";
1769 media += QPdf::paperSizeToString(paperSize);
1770 lpargs[++i] = media.data();
1771 }
1772#endif
1773 lpargs[++i] = 0;
1774 char **lprargs = new char *[lprhack.size()+2];
1775 char lpr[] = "lpr";
1776 lprargs[0] = lpr;
1777 for (int i = 0; i < lprhack.size(); ++i)
1778 lprargs[i+1] = (char *)lprhack[i].constData();
1779 lprargs[lprhack.size() + 1] = 0;
1780 (void)execvp("lp", lpargs);
1781 (void)execvp("lpr", lprargs);
1782 (void)execv("/bin/lp", lpargs);
1783 (void)execv("/bin/lpr", lprargs);
1784 (void)execv("/usr/bin/lp", lpargs);
1785 (void)execv("/usr/bin/lpr", lprargs);
1786
1787 delete []lpargs;
1788 delete []lprargs;
1789 }
1790 // if we couldn't exec anything, close the fd,
1791 // wait for a second so the parent process (the
1792 // child of the GUI process) has exited. then
1793 // exit.
1794 QT_CLOSE(0);
1795 (void)::sleep(1);
1796 ::_exit(0);
1797 }
1798 // parent process
1799 QT_CLOSE(fds[0]);
1800 fd = fds[1];
1801#ifdef Q_WS_PM
1802 (void)::waitpid(pid, 0, 0);
1803#else
1804 (void)qt_safe_waitpid(pid, 0, 0);
1805#endif
1806 if (fd < 0)
1807 return false;
1808
1809 outDevice = new QFile();
1810 static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly);
1811#endif
1812 }
1813
1814 return true;
1815}
1816
1817void QPdfBaseEnginePrivate::closePrintDevice()
1818{
1819 if (!outDevice)
1820 return;
1821 outDevice->close();
1822 if (fd >= 0)
1823#if defined(Q_OS_WIN) && defined(_MSC_VER) && _MSC_VER >= 1400
1824 ::_close(fd);
1825#else
1826 ::close(fd);
1827#endif
1828 fd = -1;
1829 delete outDevice;
1830 outDevice = 0;
1831
1832#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
1833 if (!cupsTempFile.isEmpty()) {
1834 QString tempFile = cupsTempFile;
1835 cupsTempFile.clear();
1836 QCUPSSupport cups;
1837
1838 // Set up print options.
1839 QByteArray prnName;
1840 QList<QPair<QByteArray, QByteArray> > options;
1841 QVector<cups_option_t> cupsOptStruct;
1842
1843 if (!printerName.isEmpty()) {
1844 prnName = printerName.toLocal8Bit();
1845 } else {
1846 QPrinterInfo def = QPrinterInfo::defaultPrinter();
1847 if (def.isNull()) {
1848 qWarning("Could not determine printer to print to");
1849 QFile::remove(tempFile);
1850 return;
1851 }
1852 prnName = def.printerName().toLocal8Bit();
1853 }
1854
1855 if (!cupsStringPageSize.isEmpty()) {
1856 options.append(QPair<QByteArray, QByteArray>("media", cupsStringPageSize.toLocal8Bit()));
1857 }
1858
1859 if (copies > 1) {
1860 options.append(QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit()));
1861 }
1862
1863 if (collate) {
1864 options.append(QPair<QByteArray, QByteArray>("Collate", "True"));
1865 }
1866
1867 if (duplex != QPrinter::DuplexNone) {
1868 switch(duplex) {
1869 case QPrinter::DuplexNone: break;
1870 case QPrinter::DuplexAuto:
1871 if (orientation == QPrinter::Portrait)
1872 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
1873 else
1874 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
1875 break;
1876 case QPrinter::DuplexLongSide:
1877 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
1878 break;
1879 case QPrinter::DuplexShortSide:
1880 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
1881 break;
1882 }
1883 }
1884
1885 if (QCUPSSupport::cupsVersion() >= 10300 && orientation == QPrinter::Landscape) {
1886 options.append(QPair<QByteArray, QByteArray>("landscape", ""));
1887 }
1888
1889 QStringList::const_iterator it = cupsOptions.constBegin();
1890 while (it != cupsOptions.constEnd()) {
1891 options.append(QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit()));
1892 it += 2;
1893 }
1894
1895 for (int c = 0; c < options.size(); ++c) {
1896 cups_option_t opt;
1897 opt.name = options[c].first.data();
1898 opt.value = options[c].second.data();
1899 cupsOptStruct.append(opt);
1900 }
1901
1902 // Print the file.
1903 cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0;
1904 cups.printFile(prnName.constData(), tempFile.toLocal8Bit().constData(),
1905 title.toLocal8Bit().constData(), cupsOptStruct.size(), optPtr);
1906
1907 QFile::remove(tempFile);
1908 }
1909#endif
1910}
1911
1912QPdfBaseEnginePrivate::~QPdfBaseEnginePrivate()
1913{
1914 qDeleteAll(fonts);
1915 delete currentPage;
1916}
1917
1918void QPdfBaseEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti)
1919{
1920 Q_Q(QPdfBaseEngine);
1921
1922 QFontEngine *fe = ti.fontEngine;
1923
1924 QFontEngine::FaceId face_id = fe->faceId();
1925 bool noEmbed = false;
1926 if (face_id.filename.isEmpty()
1927 || (!postscript && ((fe->fsType & 0x200) /* bitmap embedding only */
1928 || (fe->fsType == 2) /* no embedding allowed */))) {
1929 *currentPage << "Q\n";
1930 q->QPaintEngine::drawTextItem(p, ti);
1931 *currentPage << "q\n";
1932 if (face_id.filename.isEmpty())
1933 return;
1934 noEmbed = true;
1935 }
1936
1937 QFontSubset *font = fonts.value(face_id, 0);
1938 if (!font) {
1939 font = new QFontSubset(fe, requestObject());
1940 font->noEmbed = noEmbed;
1941 }
1942 fonts.insert(face_id, font);
1943
1944 if (!currentPage->fonts.contains(font->object_id))
1945 currentPage->fonts.append(font->object_id);
1946
1947 qreal size = ti.fontEngine->fontDef.pixelSize;
1948#ifdef Q_WS_WIN
1949 if (ti.fontEngine->type() == QFontEngine::Win) {
1950 QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine);
1951 size = fe->tm.tmHeight;
1952 }
1953#endif
1954
1955 QVarLengthArray<glyph_t> glyphs;
1956 QVarLengthArray<QFixedPoint> positions;
1957 QTransform m = QTransform::fromTranslate(p.x(), p.y());
1958 ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags,
1959 glyphs, positions);
1960 if (glyphs.size() == 0)
1961 return;
1962 int synthesized = ti.fontEngine->synthesized();
1963 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
1964
1965 *currentPage << "BT\n"
1966 << "/F" << font->object_id << size << "Tf "
1967 << stretch << (synthesized & QFontEngine::SynthesizedItalic
1968 ? "0 .3 -1 0 0 Tm\n"
1969 : "0 0 -1 0 0 Tm\n");
1970
1971
1972#if 0
1973 // #### implement actual text for complex languages
1974 const unsigned short *logClusters = ti.logClusters;
1975 int pos = 0;
1976 do {
1977 int end = pos + 1;
1978 while (end < ti.num_chars && logClusters[end] == logClusters[pos])
1979 ++end;
1980 *currentPage << "/Span << /ActualText <FEFF";
1981 for (int i = pos; i < end; ++i) {
1982 s << toHex((ushort)ti.chars[i].unicode(), buf);
1983 }
1984 *currentPage << "> >>\n"
1985 "BDC\n"
1986 "<";
1987 int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end];
1988 for (int gs = logClusters[pos]; gs < ge; ++gs)
1989 *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf);
1990 *currentPage << "> Tj\n"
1991 "EMC\n";
1992 pos = end;
1993 } while (pos < ti.num_chars);
1994#else
1995 qreal last_x = 0.;
1996 qreal last_y = 0.;
1997 for (int i = 0; i < glyphs.size(); ++i) {
1998 qreal x = positions[i].x.toReal();
1999 qreal y = positions[i].y.toReal();
2000 if (synthesized & QFontEngine::SynthesizedItalic)
2001 x += .3*y;
2002 x /= stretch;
2003 char buf[5];
2004 int g = font->addGlyph(glyphs[i]);
2005 *currentPage << x - last_x << last_y - y << "Td <"
2006 << QPdf::toHex((ushort)g, buf) << "> Tj\n";
2007 last_x = x;
2008 last_y = y;
2009 }
2010 if (synthesized & QFontEngine::SynthesizedBold) {
2011 *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic
2012 ? "0 .3 -1 0 0 Tm\n"
2013 : "0 0 -1 0 0 Tm\n");
2014 *currentPage << "/Span << /ActualText <> >> BDC\n";
2015 last_x = 0.5*fe->lineThickness().toReal();
2016 last_y = 0.;
2017 for (int i = 0; i < glyphs.size(); ++i) {
2018 qreal x = positions[i].x.toReal();
2019 qreal y = positions[i].y.toReal();
2020 if (synthesized & QFontEngine::SynthesizedItalic)
2021 x += .3*y;
2022 x /= stretch;
2023 char buf[5];
2024 int g = font->addGlyph(glyphs[i]);
2025 *currentPage << x - last_x << last_y - y << "Td <"
2026 << QPdf::toHex((ushort)g, buf) << "> Tj\n";
2027 last_x = x;
2028 last_y = y;
2029 }
2030 *currentPage << "EMC\n";
2031 }
2032#endif
2033
2034 *currentPage << "ET\n";
2035}
2036
2037QRect QPdfBaseEnginePrivate::paperRect() const
2038{
2039 int w;
2040 int h;
2041 if (paperSize == QPrinter::Custom) {
2042 w = qRound(customPaperSize.width()*resolution/72.);
2043 h = qRound(customPaperSize.height()*resolution/72.);
2044 } else {
2045#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
2046 if (QCUPSSupport::isAvailable() && !cupsPaperRect.isNull()) {
2047 QRect r = cupsPaperRect;
2048 w = r.width();
2049 h = r.height();
2050 } else
2051#endif
2052 {
2053 QPdf::PaperSize s = QPdf::paperSize(paperSize);
2054 w = s.width;
2055 h = s.height;
2056 }
2057 w = qRound(w*resolution/72.);
2058 h = qRound(h*resolution/72.);
2059 }
2060 if (orientation == QPrinter::Portrait)
2061 return QRect(0, 0, w, h);
2062 else
2063 return QRect(0, 0, h, w);
2064}
2065
2066QRect QPdfBaseEnginePrivate::pageRect() const
2067{
2068 if(fullPage)
2069 return paperRect();
2070
2071 QRect r;
2072
2073#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
2074 if (!hasCustomPageMargins && QCUPSSupport::isAvailable() && !cupsPageRect.isNull()) {
2075 r = cupsPageRect;
2076 if (r == cupsPaperRect) {
2077 // if cups doesn't define any margins, give it at least approx 3.5 mm
2078 r = QRect(10, 10, r.width() - 20, r.height() - 20);
2079 }
2080 } else
2081#endif
2082 {
2083 QPdf::PaperSize s;
2084 if (paperSize == QPrinter::Custom) {
2085 s.width = qRound(customPaperSize.width());
2086 s.height = qRound(customPaperSize.height());
2087 } else {
2088 s = QPdf::paperSize(paperSize);
2089 }
2090 if (hasCustomPageMargins)
2091 r = QRect(0, 0, s.width, s.height);
2092 else
2093 r = QRect(72/3, 72/3, s.width - 2*72/3, s.height - 2*72/3);
2094 }
2095
2096 int x = qRound(r.left()*resolution/72.);
2097 int y = qRound(r.top()*resolution/72.);
2098 int w = qRound(r.width()*resolution/72.);
2099 int h = qRound(r.height()*resolution/72.);
2100 if (orientation == QPrinter::Portrait)
2101 r = QRect(x, y, w, h);
2102 else
2103 r = QRect(y, x, h, w);
2104
2105 if (hasCustomPageMargins) {
2106 r.adjust(qRound(leftMargin*(resolution/72.)),
2107 qRound(topMargin*(resolution/72.)),
2108 -qRound(rightMargin*(resolution/72.)),
2109 -qRound(bottomMargin*(resolution/72.)));
2110 }
2111 return r;
2112}
2113
2114#endif
2115
2116QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.