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

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

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

File size: 59.3 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 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
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 Q_D(QPdfBaseEngine);
975 QPainterPath p;
976 for (int i=0; i!=pointCount;++i) {
977 p.moveTo(points[i]);
978 p.lineTo(points[i] + QPointF(0, 0.001));
979 }
980
981 bool hadBrush = d->hasBrush;
982 d->hasBrush = false;
983 drawPath(p);
984 d->hasBrush = hadBrush;
985}
986
987void QPdfBaseEngine::drawLines (const QLineF *lines, int lineCount)
988{
989 if (!lines)
990 return;
991
992 Q_D(QPdfBaseEngine);
993 QPainterPath p;
994 for (int i=0; i!=lineCount;++i) {
995 p.moveTo(lines[i].p1());
996 p.lineTo(lines[i].p2());
997 }
998 bool hadBrush = d->hasBrush;
999 d->hasBrush = false;
1000 drawPath(p);
1001 d->hasBrush = hadBrush;
1002}
1003
1004void QPdfBaseEngine::drawRects (const QRectF *rects, int rectCount)
1005{
1006 if (!rects)
1007 return;
1008
1009 Q_D(QPdfBaseEngine);
1010 if (d->useAlphaEngine) {
1011 QAlphaPaintEngine::drawRects(rects, rectCount);
1012 if (!continueCall())
1013 return;
1014 }
1015
1016 if (d->clipEnabled && d->allClipped)
1017 return;
1018 if (!d->hasPen && !d->hasBrush)
1019 return;
1020
1021 QBrush penBrush = d->pen.brush();
1022 if (d->simplePen || !d->hasPen) {
1023 // draw strokes natively in this case for better output
1024 if(!d->simplePen && !d->stroker.matrix.isIdentity())
1025 *d->currentPage << "q\n" << QPdf::generateMatrix(d->stroker.matrix);
1026 for (int i = 0; i < rectCount; ++i)
1027 *d->currentPage << rects[i].x() << rects[i].y() << rects[i].width() << rects[i].height() << "re\n";
1028 *d->currentPage << (d->hasPen ? (d->hasBrush ? "B\n" : "S\n") : "f\n");
1029 if(!d->simplePen && !d->stroker.matrix.isIdentity())
1030 *d->currentPage << "Q\n";
1031 } else {
1032 QPainterPath p;
1033 for (int i=0; i!=rectCount; ++i)
1034 p.addRect(rects[i]);
1035 drawPath(p);
1036 }
1037}
1038
1039void QPdfBaseEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode)
1040{
1041 Q_D(QPdfBaseEngine);
1042
1043 if (d->useAlphaEngine) {
1044 QAlphaPaintEngine::drawPolygon(points, pointCount, mode);
1045 if (!continueCall())
1046 return;
1047 }
1048
1049 if (!points || !pointCount)
1050 return;
1051
1052 bool hb = d->hasBrush;
1053 QPainterPath p;
1054
1055 switch(mode) {
1056 case OddEvenMode:
1057 p.setFillRule(Qt::OddEvenFill);
1058 break;
1059 case ConvexMode:
1060 case WindingMode:
1061 p.setFillRule(Qt::WindingFill);
1062 break;
1063 case PolylineMode:
1064 d->hasBrush = false;
1065 break;
1066 default:
1067 break;
1068 }
1069
1070 p.moveTo(points[0]);
1071 for (int i = 1; i < pointCount; ++i)
1072 p.lineTo(points[i]);
1073
1074 if (mode != PolylineMode)
1075 p.closeSubpath();
1076 drawPath(p);
1077
1078 d->hasBrush = hb;
1079}
1080
1081void QPdfBaseEngine::drawPath (const QPainterPath &p)
1082{
1083 Q_D(QPdfBaseEngine);
1084
1085 if (d->useAlphaEngine) {
1086 QAlphaPaintEngine::drawPath(p);
1087 if (!continueCall())
1088 return;
1089 }
1090
1091 if (d->clipEnabled && d->allClipped)
1092 return;
1093 if (!d->hasPen && !d->hasBrush)
1094 return;
1095
1096 if (d->simplePen) {
1097 // draw strokes natively in this case for better output
1098 *d->currentPage << QPdf::generatePath(p, QTransform(), d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath);
1099 } else {
1100 if (d->hasBrush)
1101 *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath);
1102 if (d->hasPen) {
1103 *d->currentPage << "q\n";
1104 QBrush b = d->brush;
1105 d->brush = d->pen.brush();
1106 setBrush();
1107 d->stroker.strokePath(p);
1108 *d->currentPage << "Q\n";
1109 d->brush = b;
1110 }
1111 }
1112}
1113
1114void QPdfBaseEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
1115{
1116 Q_D(QPdfBaseEngine);
1117
1118 if (d->useAlphaEngine) {
1119 QAlphaPaintEngine::drawTextItem(p, textItem);
1120 if (!continueCall())
1121 return;
1122 }
1123
1124 if (!d->hasPen || (d->clipEnabled && d->allClipped))
1125 return;
1126
1127 if (d->stroker.matrix.type() >= QTransform::TxProject) {
1128 QPaintEngine::drawTextItem(p, textItem);
1129 return;
1130 }
1131
1132 *d->currentPage << "q\n";
1133 if(!d->simplePen)
1134 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1135
1136 bool hp = d->hasPen;
1137 d->hasPen = false;
1138 QBrush b = d->brush;
1139 d->brush = d->pen.brush();
1140 setBrush();
1141
1142 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
1143 Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi);
1144 d->drawTextItem(p, ti);
1145 d->hasPen = hp;
1146 d->brush = b;
1147 *d->currentPage << "Q\n";
1148}
1149
1150
1151void QPdfBaseEngine::updateState(const QPaintEngineState &state)
1152{
1153 Q_D(QPdfBaseEngine);
1154
1155 if (d->useAlphaEngine) {
1156 QAlphaPaintEngine::updateState(state);
1157 if (!continueCall())
1158 return;
1159 }
1160
1161 QPaintEngine::DirtyFlags flags = state.state();
1162
1163 if (flags & DirtyTransform)
1164 d->stroker.matrix = state.transform();
1165
1166 if (flags & DirtyPen) {
1167 d->pen = state.pen();
1168 d->hasPen = d->pen.style() != Qt::NoPen;
1169 d->stroker.setPen(d->pen);
1170 QBrush penBrush = d->pen.brush();
1171 bool oldSimple = d->simplePen;
1172 d->simplePen = (d->hasPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque());
1173 if (oldSimple != d->simplePen)
1174 flags |= DirtyTransform;
1175 }
1176 if (flags & DirtyBrush) {
1177 d->brush = state.brush();
1178 d->hasBrush = d->brush.style() != Qt::NoBrush;
1179 }
1180 if (flags & DirtyBrushOrigin) {
1181 d->brushOrigin = state.brushOrigin();
1182 flags |= DirtyBrush;
1183 }
1184 if (flags & DirtyOpacity)
1185 d->opacity = state.opacity();
1186
1187 bool ce = d->clipEnabled;
1188 if (flags & DirtyClipPath) {
1189 d->clipEnabled = true;
1190 updateClipPath(state.clipPath(), state.clipOperation());
1191 } else if (flags & DirtyClipRegion) {
1192 d->clipEnabled = true;
1193 QPainterPath path;
1194 QVector<QRect> rects = state.clipRegion().rects();
1195 for (int i = 0; i < rects.size(); ++i)
1196 path.addRect(rects.at(i));
1197 updateClipPath(path, state.clipOperation());
1198 flags |= DirtyClipPath;
1199 } else if (flags & DirtyClipEnabled) {
1200 d->clipEnabled = state.isClipEnabled();
1201 }
1202
1203 if (ce != d->clipEnabled)
1204 flags |= DirtyClipPath;
1205 else if (!d->clipEnabled)
1206 flags &= ~DirtyClipPath;
1207
1208 setupGraphicsState(flags);
1209}
1210
1211void QPdfBaseEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags)
1212{
1213 Q_D(QPdfBaseEngine);
1214 if (flags & DirtyClipPath)
1215 flags |= DirtyTransform|DirtyPen|DirtyBrush;
1216
1217 if (flags & DirtyTransform) {
1218 *d->currentPage << "Q\n";
1219 flags |= DirtyPen|DirtyBrush;
1220 }
1221
1222 if (flags & DirtyClipPath) {
1223 *d->currentPage << "Q q\n";
1224
1225 d->allClipped = false;
1226 if (d->clipEnabled && !d->clips.isEmpty()) {
1227 for (int i = 0; i < d->clips.size(); ++i) {
1228 if (d->clips.at(i).isEmpty()) {
1229 d->allClipped = true;
1230 break;
1231 }
1232 }
1233 if (!d->allClipped) {
1234 for (int i = 0; i < d->clips.size(); ++i) {
1235 *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath);
1236 }
1237 }
1238 }
1239 }
1240
1241 if (flags & DirtyTransform) {
1242 *d->currentPage << "q\n";
1243 if (d->simplePen && !d->stroker.matrix.isIdentity())
1244 *d->currentPage << QPdf::generateMatrix(d->stroker.matrix);
1245 }
1246 if (flags & DirtyBrush)
1247 setBrush();
1248 if (d->simplePen && (flags & DirtyPen))
1249 setPen();
1250}
1251
1252extern QPainterPath qt_regionToPath(const QRegion &region);
1253
1254void QPdfBaseEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op)
1255{
1256 Q_D(QPdfBaseEngine);
1257 QPainterPath path = d->stroker.matrix.map(p);
1258 //qDebug() << "updateClipPath: " << d->stroker.matrix << p.boundingRect() << path.boundingRect() << op;
1259
1260 if (op == Qt::NoClip) {
1261 d->clipEnabled = false;
1262 d->clips.clear();
1263 } else if (op == Qt::ReplaceClip) {
1264 d->clips.clear();
1265 d->clips.append(path);
1266 } else if (op == Qt::IntersectClip) {
1267 d->clips.append(path);
1268 } else { // UniteClip
1269 // ask the painter for the current clipping path. that's the easiest solution
1270 path = painter()->clipPath();
1271 path = d->stroker.matrix.map(path);
1272 d->clips.clear();
1273 d->clips.append(path);
1274 }
1275
1276 if (d->useAlphaEngine) {
1277 // if we have an alpha region, we have to subtract that from the
1278 // any existing clip region since that region will be filled in
1279 // later with images
1280 QPainterPath alphaClip = qt_regionToPath(alphaClipping());
1281 if (!alphaClip.isEmpty()) {
1282 if (!d->clipEnabled) {
1283 QRect r = d->fullPage ? d->paperRect() : d->pageRect();
1284 QPainterPath dev;
1285 dev.addRect(QRect(0, 0, r.width(), r.height()));
1286 if (path.isEmpty())
1287 path = dev;
1288 else
1289 path = path.intersected(dev);
1290 d->clipEnabled = true;
1291 } else {
1292 path = painter()->clipPath();
1293 path = d->stroker.matrix.map(path);
1294 }
1295 path = path.subtracted(alphaClip);
1296 d->clips.clear();
1297 d->clips.append(path);
1298 }
1299 }
1300}
1301
1302void QPdfBaseEngine::setPen()
1303{
1304 Q_D(QPdfBaseEngine);
1305 if (d->pen.style() == Qt::NoPen)
1306 return;
1307 QBrush b = d->pen.brush();
1308 Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque());
1309
1310 QColor rgba = b.color();
1311 if (d->colorMode == QPrinter::GrayScale) {
1312 qreal gray = qGray(rgba.rgba())/255.;
1313 *d->currentPage << gray << gray << gray;
1314 } else {
1315 *d->currentPage << rgba.redF()
1316 << rgba.greenF()
1317 << rgba.blueF();
1318 }
1319 *d->currentPage << "SCN\n";
1320
1321 *d->currentPage << d->pen.widthF() << "w ";
1322
1323 int pdfCapStyle = 0;
1324 switch(d->pen.capStyle()) {
1325 case Qt::FlatCap:
1326 pdfCapStyle = 0;
1327 break;
1328 case Qt::SquareCap:
1329 pdfCapStyle = 2;
1330 break;
1331 case Qt::RoundCap:
1332 pdfCapStyle = 1;
1333 break;
1334 default:
1335 break;
1336 }
1337 *d->currentPage << pdfCapStyle << "J ";
1338
1339 int pdfJoinStyle = 0;
1340 switch(d->pen.joinStyle()) {
1341 case Qt::MiterJoin:
1342 pdfJoinStyle = 0;
1343 break;
1344 case Qt::BevelJoin:
1345 pdfJoinStyle = 2;
1346 break;
1347 case Qt::RoundJoin:
1348 pdfJoinStyle = 1;
1349 break;
1350 default:
1351 break;
1352 }
1353 *d->currentPage << pdfJoinStyle << "j ";
1354
1355 *d->currentPage << QPdf::generateDashes(d->pen) << " 0 d\n";
1356}
1357
1358bool QPdfBaseEngine::newPage()
1359{
1360 Q_D(QPdfBaseEngine);
1361 setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath);
1362 QFile *outfile = qobject_cast<QFile*> (d->outDevice);
1363 if (outfile && outfile->error() != QFile::NoError)
1364 return false;
1365 return true;
1366}
1367
1368
1369int QPdfBaseEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const
1370{
1371 Q_D(const QPdfBaseEngine);
1372 int val;
1373 QRect r = d->fullPage ? d->paperRect() : d->pageRect();
1374 switch (metricType) {
1375 case QPaintDevice::PdmWidth:
1376 val = r.width();
1377 break;
1378 case QPaintDevice::PdmHeight:
1379 val = r.height();
1380 break;
1381 case QPaintDevice::PdmDpiX:
1382 case QPaintDevice::PdmDpiY:
1383 val = d->resolution;
1384 break;
1385 case QPaintDevice::PdmPhysicalDpiX:
1386 case QPaintDevice::PdmPhysicalDpiY:
1387 val = 1200;
1388 break;
1389 case QPaintDevice::PdmWidthMM:
1390 val = qRound(r.width()*25.4/d->resolution);
1391 break;
1392 case QPaintDevice::PdmHeightMM:
1393 val = qRound(r.height()*25.4/d->resolution);
1394 break;
1395 case QPaintDevice::PdmNumColors:
1396 val = INT_MAX;
1397 break;
1398 case QPaintDevice::PdmDepth:
1399 val = 32;
1400 break;
1401 default:
1402 qWarning("QPrinter::metric: Invalid metric command");
1403 return 0;
1404 }
1405 return val;
1406}
1407
1408void QPdfBaseEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value)
1409{
1410 Q_D(QPdfBaseEngine);
1411 switch (key) {
1412 case PPK_CollateCopies:
1413 d->collate = value.toBool();
1414 break;
1415 case PPK_ColorMode:
1416 d->colorMode = QPrinter::ColorMode(value.toInt());
1417 break;
1418 case PPK_Creator:
1419 d->creator = value.toString();
1420 break;
1421 case PPK_DocumentName:
1422 d->title = value.toString();
1423 break;
1424 case PPK_FullPage:
1425 d->fullPage = value.toBool();
1426 break;
1427 case PPK_NumberOfCopies:
1428 d->copies = value.toInt();
1429 break;
1430 case PPK_Orientation:
1431 d->orientation = QPrinter::Orientation(value.toInt());
1432 break;
1433 case PPK_OutputFileName:
1434 d->outputFileName = value.toString();
1435 break;
1436 case PPK_PageOrder:
1437 d->pageOrder = QPrinter::PageOrder(value.toInt());
1438 break;
1439 case PPK_PaperSize:
1440 d->paperSize = QPrinter::PaperSize(value.toInt());
1441 break;
1442 case PPK_PaperSource:
1443 d->paperSource = QPrinter::PaperSource(value.toInt());
1444 break;
1445 case PPK_PrinterName:
1446 d->printerName = value.toString();
1447 break;
1448 case PPK_PrinterProgram:
1449 d->printProgram = value.toString();
1450 break;
1451 case PPK_Resolution:
1452 d->resolution = value.toInt();
1453 break;
1454 case PPK_SelectionOption:
1455 d->selectionOption = value.toString();
1456 break;
1457 case PPK_FontEmbedding:
1458 d->embedFonts = value.toBool();
1459 break;
1460 case PPK_Duplex:
1461 d->duplex = static_cast<QPrinter::DuplexMode> (value.toInt());
1462 break;
1463 case PPK_CupsPageRect:
1464 d->cupsPageRect = value.toRect();
1465 break;
1466 case PPK_CupsPaperRect:
1467 d->cupsPaperRect = value.toRect();
1468 break;
1469 case PPK_CupsOptions:
1470 d->cupsOptions = value.toStringList();
1471 break;
1472 case PPK_CupsStringPageSize:
1473 d->cupsStringPageSize = value.toString();
1474 break;
1475 case PPK_CustomPaperSize:
1476 d->paperSize = QPrinter::Custom;
1477 d->customPaperSize = value.toSizeF();
1478 break;
1479 case PPK_PageMargins:
1480 {
1481 QList<QVariant> margins(value.toList());
1482 Q_ASSERT(margins.size() == 4);
1483 d->leftMargin = margins.at(0).toReal();
1484 d->topMargin = margins.at(1).toReal();
1485 d->rightMargin = margins.at(2).toReal();
1486 d->bottomMargin = margins.at(3).toReal();
1487 d->hasCustomPageMargins = true;
1488 break;
1489 }
1490 default:
1491 break;
1492 }
1493}
1494
1495QVariant QPdfBaseEngine::property(PrintEnginePropertyKey key) const
1496{
1497 Q_D(const QPdfBaseEngine);
1498
1499 QVariant ret;
1500 switch (key) {
1501 case PPK_CollateCopies:
1502 ret = d->collate;
1503 break;
1504 case PPK_ColorMode:
1505 ret = d->colorMode;
1506 break;
1507 case PPK_Creator:
1508 ret = d->creator;
1509 break;
1510 case PPK_DocumentName:
1511 ret = d->title;
1512 break;
1513 case PPK_FullPage:
1514 ret = d->fullPage;
1515 break;
1516 case PPK_NumberOfCopies:
1517#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
1518 if (QCUPSSupport::isAvailable())
1519 ret = 1;
1520 else
1521#endif
1522 ret = d->copies;
1523 break;
1524 case PPK_Orientation:
1525 ret = d->orientation;
1526 break;
1527 case PPK_OutputFileName:
1528 ret = d->outputFileName;
1529 break;
1530 case PPK_PageOrder:
1531 ret = d->pageOrder;
1532 break;
1533 case PPK_PaperSize:
1534 ret = d->paperSize;
1535 break;
1536 case PPK_PaperSource:
1537 ret = d->paperSource;
1538 break;
1539 case PPK_PrinterName:
1540 ret = d->printerName;
1541 break;
1542 case PPK_PrinterProgram:
1543 ret = d->printProgram;
1544 break;
1545 case PPK_Resolution:
1546 ret = d->resolution;
1547 break;
1548 case PPK_SupportedResolutions:
1549 ret = QList<QVariant>() << 72;
1550 break;
1551 case PPK_PaperRect:
1552 ret = d->paperRect();
1553 break;
1554 case PPK_PageRect:
1555 ret = d->pageRect();
1556 break;
1557 case PPK_SelectionOption:
1558 ret = d->selectionOption;
1559 break;
1560 case PPK_FontEmbedding:
1561 ret = d->embedFonts;
1562 break;
1563 case PPK_Duplex:
1564 ret = d->duplex;
1565 break;
1566 case PPK_CupsPageRect:
1567 ret = d->cupsPageRect;
1568 break;
1569 case PPK_CupsPaperRect:
1570 ret = d->cupsPaperRect;
1571 break;
1572 case PPK_CupsOptions:
1573 ret = d->cupsOptions;
1574 break;
1575 case PPK_CupsStringPageSize:
1576 ret = d->cupsStringPageSize;
1577 break;
1578 case PPK_CustomPaperSize:
1579 ret = d->customPaperSize;
1580 break;
1581 case PPK_PageMargins:
1582 {
1583 QList<QVariant> margins;
1584 if (d->hasCustomPageMargins) {
1585 margins << d->leftMargin << d->topMargin
1586 << d->rightMargin << d->bottomMargin;
1587 } else {
1588 const qreal defaultMargin = 10; // ~3.5 mm
1589 margins << defaultMargin << defaultMargin
1590 << defaultMargin << defaultMargin;
1591 }
1592 ret = margins;
1593 break;
1594 }
1595 default:
1596 break;
1597 }
1598 return ret;
1599}
1600
1601QPdfBaseEnginePrivate::QPdfBaseEnginePrivate(QPrinter::PrinterMode m)
1602 : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false),
1603 useAlphaEngine(false),
1604 outDevice(0), fd(-1),
1605 duplex(QPrinter::DuplexNone), collate(false), fullPage(false), embedFonts(true), copies(1),
1606 pageOrder(QPrinter::FirstPageFirst), orientation(QPrinter::Portrait),
1607 paperSize(QPrinter::A4), colorMode(QPrinter::Color), paperSource(QPrinter::Auto),
1608 hasCustomPageMargins(false),
1609 leftMargin(0), topMargin(0), rightMargin(0), bottomMargin(0)
1610{
1611 resolution = 72;
1612 if (m == QPrinter::HighResolution)
1613 resolution = 1200;
1614 else if (m == QPrinter::ScreenResolution)
1615 resolution = qt_defaultDpi();
1616
1617 postscript = false;
1618 currentObject = 1;
1619 currentPage = 0;
1620 stroker.stream = 0;
1621}
1622
1623bool QPdfBaseEngine::begin(QPaintDevice *pdev)
1624{
1625 Q_D(QPdfBaseEngine);
1626 d->pdev = pdev;
1627
1628 d->postscript = false;
1629 d->currentObject = 1;
1630
1631 d->currentPage = new QPdfPage;
1632 d->stroker.stream = d->currentPage;
1633 d->opacity = 1.0;
1634
1635 return d->openPrintDevice();
1636}
1637
1638bool QPdfBaseEngine::end()
1639{
1640 Q_D(QPdfBaseEngine);
1641 qDeleteAll(d->fonts);
1642 d->fonts.clear();
1643 delete d->currentPage;
1644 d->currentPage = 0;
1645
1646 d->closePrintDevice();
1647 return true;
1648}
1649
1650#ifndef QT_NO_LPR
1651static void closeAllOpenFds()
1652{
1653 // hack time... getting the maximum number of open
1654 // files, if possible. if not we assume it's the
1655 // larger of 256 and the fd we got
1656 int i;
1657#if defined(_SC_OPEN_MAX)
1658 i = (int)sysconf(_SC_OPEN_MAX);
1659#elif defined(_POSIX_OPEN_MAX)
1660 i = (int)_POSIX_OPEN_MAX;
1661#elif defined(OPEN_MAX)
1662 i = (int)OPEN_MAX;
1663#else
1664 i = 256;
1665#endif
1666 // leave stdin/out/err untouched
1667 while(--i > 2)
1668 QT_CLOSE(i);
1669}
1670#endif
1671
1672bool QPdfBaseEnginePrivate::openPrintDevice()
1673{
1674 if(outDevice)
1675 return false;
1676
1677 if (!outputFileName.isEmpty()) {
1678 QFile *file = new QFile(outputFileName);
1679 if (! file->open(QFile::WriteOnly|QFile::Truncate)) {
1680 delete file;
1681 return false;
1682 }
1683 outDevice = file;
1684#if !defined(QT_NO_CUPS) && (!defined(QT_NO_LIBRARY) || defined(Q_WS_PM))
1685 } else if (QCUPSSupport::isAvailable()) {
1686 QCUPSSupport cups;
1687 QPair<int, QString> ret = cups.tempFd();
1688 if (ret.first < 0) {
1689 qWarning("QPdfPrinter: Could not open temporary file to print");
1690 return false;
1691 }
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.