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

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

trunk: Merged in qt 4.6.1 sources.

File size: 58.8 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)
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)
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)
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#endif
1687#ifndef QT_NO_LPR
1688 } else {
1689 QString pr;
1690 if (!printerName.isEmpty())
1691 pr = printerName;
1692 int fds[2];
1693 if (qt_safe_pipe(fds) != 0) {
1694 qWarning("QPdfPrinter: Could not open pipe to print");
1695 return false;
1696 }
1697
1698 pid_t pid = fork();
1699 if (pid == 0) { // child process
1700 // if possible, exit quickly, so the actual lp/lpr
1701 // becomes a child of init, and ::waitpid() is
1702 // guaranteed not to wait.
1703 if (fork() > 0) {
1704 closeAllOpenFds();
1705
1706 // try to replace this process with "true" - this prevents
1707 // global destructors from being called (that could possibly
1708 // do wrong things to the parent process)
1709 (void)execlp("true", "true", (char *)0);
1710 (void)execl("/bin/true", "true", (char *)0);
1711 (void)execl("/usr/bin/true", "true", (char *)0);
1712 ::_exit(0);
1713 }
1714 qt_safe_dup2(fds[0], 0, 0);
1715
1716 closeAllOpenFds();
1717
1718 if (!printProgram.isEmpty()) {
1719 if (!selectionOption.isEmpty())
1720 pr.prepend(selectionOption);
1721 else
1722 pr.prepend(QLatin1String("-P"));
1723 (void)execlp(printProgram.toLocal8Bit().data(), printProgram.toLocal8Bit().data(),
1724 pr.toLocal8Bit().data(), (char *)0);
1725 } else {
1726 // if no print program has been specified, be smart
1727 // about the option string too.
1728 QList<QByteArray> lprhack;
1729 QList<QByteArray> lphack;
1730 QByteArray media;
1731 if (!pr.isEmpty() || !selectionOption.isEmpty()) {
1732 if (!selectionOption.isEmpty()) {
1733 QStringList list = selectionOption.split(QLatin1Char(' '));
1734 for (int i = 0; i < list.size(); ++i)
1735 lprhack.append(list.at(i).toLocal8Bit());
1736 lphack = lprhack;
1737 } else {
1738 lprhack.append("-P");
1739 lphack.append("-d");
1740 }
1741 lprhack.append(pr.toLocal8Bit());
1742 lphack.append(pr.toLocal8Bit());
1743 }
1744 lphack.append("-s");
1745
1746 char ** lpargs = new char *[lphack.size()+6];
1747 char lp[] = "lp";
1748 lpargs[0] = lp;
1749 int i;
1750 for (i = 0; i < lphack.size(); ++i)
1751 lpargs[i+1] = (char *)lphack.at(i).constData();
1752#ifndef Q_OS_OSF
1753 if (QPdf::paperSizeToString(paperSize)) {
1754 char dash_o[] = "-o";
1755 lpargs[++i] = dash_o;
1756 lpargs[++i] = const_cast<char *>(QPdf::paperSizeToString(paperSize));
1757 lpargs[++i] = dash_o;
1758 media = "media=";
1759 media += QPdf::paperSizeToString(paperSize);
1760 lpargs[++i] = media.data();
1761 }
1762#endif
1763 lpargs[++i] = 0;
1764 char **lprargs = new char *[lprhack.size()+2];
1765 char lpr[] = "lpr";
1766 lprargs[0] = lpr;
1767 for (int i = 0; i < lprhack.size(); ++i)
1768 lprargs[i+1] = (char *)lprhack[i].constData();
1769 lprargs[lprhack.size() + 1] = 0;
1770 (void)execvp("lp", lpargs);
1771 (void)execvp("lpr", lprargs);
1772 (void)execv("/bin/lp", lpargs);
1773 (void)execv("/bin/lpr", lprargs);
1774 (void)execv("/usr/bin/lp", lpargs);
1775 (void)execv("/usr/bin/lpr", lprargs);
1776
1777 delete []lpargs;
1778 delete []lprargs;
1779 }
1780 // if we couldn't exec anything, close the fd,
1781 // wait for a second so the parent process (the
1782 // child of the GUI process) has exited. then
1783 // exit.
1784 QT_CLOSE(0);
1785 (void)::sleep(1);
1786 ::_exit(0);
1787 }
1788 // parent process
1789 QT_CLOSE(fds[0]);
1790 fd = fds[1];
1791 (void)qt_safe_waitpid(pid, 0, 0);
1792
1793 if (fd < 0)
1794 return false;
1795
1796 outDevice = new QFile();
1797 static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly);
1798#endif
1799 }
1800
1801 return true;
1802}
1803
1804void QPdfBaseEnginePrivate::closePrintDevice()
1805{
1806 if (!outDevice)
1807 return;
1808 outDevice->close();
1809 if (fd >= 0)
1810#if defined(Q_OS_WIN) && defined(_MSC_VER) && _MSC_VER >= 1400
1811 ::_close(fd);
1812#else
1813 ::close(fd);
1814#endif
1815 fd = -1;
1816 delete outDevice;
1817 outDevice = 0;
1818
1819#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY)
1820 if (!cupsTempFile.isEmpty()) {
1821 QString tempFile = cupsTempFile;
1822 cupsTempFile.clear();
1823 QCUPSSupport cups;
1824
1825 // Set up print options.
1826 QByteArray prnName;
1827 QList<QPair<QByteArray, QByteArray> > options;
1828 QVector<cups_option_t> cupsOptStruct;
1829
1830 if (!printerName.isEmpty()) {
1831 prnName = printerName.toLocal8Bit();
1832 } else {
1833 QPrinterInfo def = QPrinterInfo::defaultPrinter();
1834 if (def.isNull()) {
1835 qWarning("Could not determine printer to print to");
1836 QFile::remove(tempFile);
1837 return;
1838 }
1839 prnName = def.printerName().toLocal8Bit();
1840 }
1841
1842 if (!cupsStringPageSize.isEmpty()) {
1843 options.append(QPair<QByteArray, QByteArray>("media", cupsStringPageSize.toLocal8Bit()));
1844 }
1845
1846 if (copies > 1) {
1847 options.append(QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit()));
1848 }
1849
1850 if (collate) {
1851 options.append(QPair<QByteArray, QByteArray>("Collate", "True"));
1852 }
1853
1854 if (duplex != QPrinter::DuplexNone) {
1855 switch(duplex) {
1856 case QPrinter::DuplexNone: break;
1857 case QPrinter::DuplexAuto:
1858 if (orientation == QPrinter::Portrait)
1859 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
1860 else
1861 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
1862 break;
1863 case QPrinter::DuplexLongSide:
1864 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
1865 break;
1866 case QPrinter::DuplexShortSide:
1867 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
1868 break;
1869 }
1870 }
1871
1872 if (QCUPSSupport::cupsVersion() >= 10300 && orientation == QPrinter::Landscape) {
1873 options.append(QPair<QByteArray, QByteArray>("landscape", ""));
1874 }
1875
1876 QStringList::const_iterator it = cupsOptions.constBegin();
1877 while (it != cupsOptions.constEnd()) {
1878 options.append(QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit()));
1879 it += 2;
1880 }
1881
1882 for (int c = 0; c < options.size(); ++c) {
1883 cups_option_t opt;
1884 opt.name = options[c].first.data();
1885 opt.value = options[c].second.data();
1886 cupsOptStruct.append(opt);
1887 }
1888
1889 // Print the file.
1890 cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0;
1891 cups.printFile(prnName.constData(), tempFile.toLocal8Bit().constData(),
1892 title.toLocal8Bit().constData(), cupsOptStruct.size(), optPtr);
1893
1894 QFile::remove(tempFile);
1895 }
1896#endif
1897}
1898
1899QPdfBaseEnginePrivate::~QPdfBaseEnginePrivate()
1900{
1901 qDeleteAll(fonts);
1902 delete currentPage;
1903}
1904
1905void QPdfBaseEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti)
1906{
1907 Q_Q(QPdfBaseEngine);
1908
1909 QFontEngine *fe = ti.fontEngine;
1910
1911 QFontEngine::FaceId face_id = fe->faceId();
1912 bool noEmbed = false;
1913 if (face_id.filename.isEmpty()
1914 || (!postscript && ((fe->fsType & 0x200) /* bitmap embedding only */
1915 || (fe->fsType == 2) /* no embedding allowed */))) {
1916 *currentPage << "Q\n";
1917 q->QPaintEngine::drawTextItem(p, ti);
1918 *currentPage << "q\n";
1919 if (face_id.filename.isEmpty())
1920 return;
1921 noEmbed = true;
1922 }
1923
1924 QFontSubset *font = fonts.value(face_id, 0);
1925 if (!font) {
1926 font = new QFontSubset(fe, requestObject());
1927 font->noEmbed = noEmbed;
1928 }
1929 fonts.insert(face_id, font);
1930
1931 if (!currentPage->fonts.contains(font->object_id))
1932 currentPage->fonts.append(font->object_id);
1933
1934 qreal size = ti.fontEngine->fontDef.pixelSize;
1935#ifdef Q_WS_WIN
1936 if (ti.fontEngine->type() == QFontEngine::Win) {
1937 QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine);
1938 size = fe->tm.tmHeight;
1939 }
1940#endif
1941
1942 QVarLengthArray<glyph_t> glyphs;
1943 QVarLengthArray<QFixedPoint> positions;
1944 QTransform m = QTransform::fromTranslate(p.x(), p.y());
1945 ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags,
1946 glyphs, positions);
1947 if (glyphs.size() == 0)
1948 return;
1949 int synthesized = ti.fontEngine->synthesized();
1950 qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.;
1951
1952 *currentPage << "BT\n"
1953 << "/F" << font->object_id << size << "Tf "
1954 << stretch << (synthesized & QFontEngine::SynthesizedItalic
1955 ? "0 .3 -1 0 0 Tm\n"
1956 : "0 0 -1 0 0 Tm\n");
1957
1958
1959#if 0
1960 // #### implement actual text for complex languages
1961 const unsigned short *logClusters = ti.logClusters;
1962 int pos = 0;
1963 do {
1964 int end = pos + 1;
1965 while (end < ti.num_chars && logClusters[end] == logClusters[pos])
1966 ++end;
1967 *currentPage << "/Span << /ActualText <FEFF";
1968 for (int i = pos; i < end; ++i) {
1969 s << toHex((ushort)ti.chars[i].unicode(), buf);
1970 }
1971 *currentPage << "> >>\n"
1972 "BDC\n"
1973 "<";
1974 int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end];
1975 for (int gs = logClusters[pos]; gs < ge; ++gs)
1976 *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf);
1977 *currentPage << "> Tj\n"
1978 "EMC\n";
1979 pos = end;
1980 } while (pos < ti.num_chars);
1981#else
1982 qreal last_x = 0.;
1983 qreal last_y = 0.;
1984 for (int i = 0; i < glyphs.size(); ++i) {
1985 qreal x = positions[i].x.toReal();
1986 qreal y = positions[i].y.toReal();
1987 if (synthesized & QFontEngine::SynthesizedItalic)
1988 x += .3*y;
1989 x /= stretch;
1990 char buf[5];
1991 int g = font->addGlyph(glyphs[i]);
1992 *currentPage << x - last_x << last_y - y << "Td <"
1993 << QPdf::toHex((ushort)g, buf) << "> Tj\n";
1994 last_x = x;
1995 last_y = y;
1996 }
1997 if (synthesized & QFontEngine::SynthesizedBold) {
1998 *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic
1999 ? "0 .3 -1 0 0 Tm\n"
2000 : "0 0 -1 0 0 Tm\n");
2001 *currentPage << "/Span << /ActualText <> >> BDC\n";
2002 last_x = 0.5*fe->lineThickness().toReal();
2003 last_y = 0.;
2004 for (int i = 0; i < glyphs.size(); ++i) {
2005 qreal x = positions[i].x.toReal();
2006 qreal y = positions[i].y.toReal();
2007 if (synthesized & QFontEngine::SynthesizedItalic)
2008 x += .3*y;
2009 x /= stretch;
2010 char buf[5];
2011 int g = font->addGlyph(glyphs[i]);
2012 *currentPage << x - last_x << last_y - y << "Td <"
2013 << QPdf::toHex((ushort)g, buf) << "> Tj\n";
2014 last_x = x;
2015 last_y = y;
2016 }
2017 *currentPage << "EMC\n";
2018 }
2019#endif
2020
2021 *currentPage << "ET\n";
2022}
2023
2024QRect QPdfBaseEnginePrivate::paperRect() const
2025{
2026 int w;
2027 int h;
2028 if (paperSize == QPrinter::Custom) {
2029 w = qRound(customPaperSize.width()*resolution/72.);
2030 h = qRound(customPaperSize.height()*resolution/72.);
2031 } else {
2032#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY)
2033 if (QCUPSSupport::isAvailable() && !cupsPaperRect.isNull()) {
2034 QRect r = cupsPaperRect;
2035 w = r.width();
2036 h = r.height();
2037 } else
2038#endif
2039 {
2040 QPdf::PaperSize s = QPdf::paperSize(paperSize);
2041 w = s.width;
2042 h = s.height;
2043 }
2044 w = qRound(w*resolution/72.);
2045 h = qRound(h*resolution/72.);
2046 }
2047 if (orientation == QPrinter::Portrait)
2048 return QRect(0, 0, w, h);
2049 else
2050 return QRect(0, 0, h, w);
2051}
2052
2053QRect QPdfBaseEnginePrivate::pageRect() const
2054{
2055 if(fullPage)
2056 return paperRect();
2057
2058 QRect r;
2059
2060#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY)
2061 if (!hasCustomPageMargins && QCUPSSupport::isAvailable() && !cupsPageRect.isNull()) {
2062 r = cupsPageRect;
2063 if (r == cupsPaperRect) {
2064 // if cups doesn't define any margins, give it at least approx 3.5 mm
2065 r = QRect(10, 10, r.width() - 20, r.height() - 20);
2066 }
2067 } else
2068#endif
2069 {
2070 QPdf::PaperSize s;
2071 if (paperSize == QPrinter::Custom) {
2072 s.width = qRound(customPaperSize.width());
2073 s.height = qRound(customPaperSize.height());
2074 } else {
2075 s = QPdf::paperSize(paperSize);
2076 }
2077 if (hasCustomPageMargins)
2078 r = QRect(0, 0, s.width, s.height);
2079 else
2080 r = QRect(72/3, 72/3, s.width - 2*72/3, s.height - 2*72/3);
2081 }
2082
2083 int x = qRound(r.left()*resolution/72.);
2084 int y = qRound(r.top()*resolution/72.);
2085 int w = qRound(r.width()*resolution/72.);
2086 int h = qRound(r.height()*resolution/72.);
2087 if (orientation == QPrinter::Portrait)
2088 r = QRect(x, y, w, h);
2089 else
2090 r = QRect(y, x, h, w);
2091
2092 if (hasCustomPageMargins) {
2093 r.adjust(qRound(leftMargin*(resolution/72.)),
2094 qRound(topMargin*(resolution/72.)),
2095 -qRound(rightMargin*(resolution/72.)),
2096 -qRound(bottomMargin*(resolution/72.)));
2097 }
2098 return r;
2099}
2100
2101#endif
2102
2103QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.