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

Last change on this file since 5 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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