diff options
Diffstat (limited to 'src/gui/painting/qpdf.cpp')
-rw-r--r-- | src/gui/painting/qpdf.cpp | 2087 |
1 files changed, 2087 insertions, 0 deletions
diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp new file mode 100644 index 0000000..b010209 --- /dev/null +++ b/src/gui/painting/qpdf.cpp @@ -0,0 +1,2087 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qplatformdefs.h" +#include <qdebug.h> +#include "qpdf_p.h" +#include <qfile.h> +#include <qtemporaryfile.h> +#include <private/qmath_p.h> +#include "private/qcups_p.h" +#include "qprinterinfo.h" +#include <qnumeric.h> + +QT_BEGIN_NAMESPACE + +extern int qt_defaultDpi(); + +#ifndef QT_NO_PRINTER + +extern QSizeF qt_paperSizeToQSizeF(QPrinter::PaperSize size); + +/* also adds a space at the end of the number */ +const char *qt_real_to_string(qreal val, char *buf) { + const char *ret = buf; + + if (qIsNaN(val)) { + *(buf++) = '0'; + *(buf++) = ' '; + *buf = 0; + return ret; + } + + if (val < 0) { + *(buf++) = '-'; + val = -val; + } + unsigned int ival = (unsigned int) val; + qreal frac = val - (qreal)ival; + + int ifrac = (int)(frac * 1000000); + if (ifrac == 1000000) { + ++ival; + ifrac = 0; + } + char output[256]; + int i = 0; + while (ival) { + output[i] = '0' + (ival % 10); + ++i; + ival /= 10; + } + int fact = 100000; + if (i == 0) { + *(buf++) = '0'; + } else { + while (i) { + *(buf++) = output[--i]; + fact /= 10; + ifrac /= 10; + } + } + + if (ifrac) { + *(buf++) = '.'; + while (fact) { + *(buf++) = '0' + ((ifrac/fact) % 10); + fact /= 10; + } + } + *(buf++) = ' '; + *buf = 0; + return ret; +} + +const char *qt_int_to_string(int val, char *buf) { + const char *ret = buf; + if (val < 0) { + *(buf++) = '-'; + val = -val; + } + char output[256]; + int i = 0; + while (val) { + output[i] = '0' + (val % 10); + ++i; + val /= 10; + } + if (i == 0) { + *(buf++) = '0'; + } else { + while (i) + *(buf++) = output[--i]; + } + *(buf++) = ' '; + *buf = 0; + return ret; +} + + +namespace QPdf { + ByteStream::ByteStream(QByteArray *byteArray, bool fileBacking) + : dev(new QBuffer(byteArray)), + fileBackingEnabled(fileBacking), + fileBackingActive(false), + handleDirty(false) + { + dev->open(QIODevice::ReadWrite); + } + + ByteStream::ByteStream(bool fileBacking) + : dev(new QBuffer(&ba)), + fileBackingEnabled(fileBacking), + fileBackingActive(false), + handleDirty(false) + { + dev->open(QIODevice::ReadWrite); + } + + ByteStream::~ByteStream() + { + delete dev; + } + + ByteStream &ByteStream::operator <<(char chr) + { + if (handleDirty) prepareBuffer(); + dev->write(&chr, 1); + return *this; + } + + ByteStream &ByteStream::operator <<(const char *str) + { + if (handleDirty) prepareBuffer(); + dev->write(str, strlen(str)); + return *this; + } + + ByteStream &ByteStream::operator <<(const QByteArray &str) + { + if (handleDirty) prepareBuffer(); + dev->write(str); + return *this; + } + + ByteStream &ByteStream::operator <<(const ByteStream &src) + { + Q_ASSERT(!src.dev->isSequential()); + if (handleDirty) prepareBuffer(); + // We do play nice here, even though it looks ugly. + // We save the position and restore it afterwards. + ByteStream &s = const_cast<ByteStream&>(src); + qint64 pos = s.dev->pos(); + s.dev->reset(); + while (!s.dev->atEnd()) { + QByteArray buf = s.dev->read(chunkSize()); + dev->write(buf); + } + s.dev->seek(pos); + return *this; + } + + ByteStream &ByteStream::operator <<(qreal val) { + char buf[256]; + qt_real_to_string(val, buf); + *this << buf; + return *this; + } + + ByteStream &ByteStream::operator <<(int val) { + char buf[256]; + qt_int_to_string(val, buf); + *this << buf; + return *this; + } + + ByteStream &ByteStream::operator <<(const QPointF &p) { + char buf[256]; + qt_real_to_string(p.x(), buf); + *this << buf; + qt_real_to_string(p.y(), buf); + *this << buf; + return *this; + } + + QIODevice *ByteStream::stream() + { + dev->reset(); + handleDirty = true; + return dev; + } + + void ByteStream::clear() + { + dev->open(QIODevice::ReadWrite | QIODevice::Truncate); + } + + void ByteStream::constructor_helper(QByteArray *ba) + { + delete dev; + dev = new QBuffer(ba); + dev->open(QIODevice::ReadWrite); + } + + void ByteStream::prepareBuffer() + { + Q_ASSERT(!dev->isSequential()); + qint64 size = dev->size(); + if (fileBackingEnabled && !fileBackingActive + && size > maxMemorySize()) { + // Switch to file backing. + QTemporaryFile *newFile = new QTemporaryFile; + newFile->open(); + dev->reset(); + while (!dev->atEnd()) { + QByteArray buf = dev->read(chunkSize()); + newFile->write(buf); + } + delete dev; + dev = newFile; + ba.clear(); + fileBackingActive = true; + } + if (dev->pos() != size) { + dev->seek(size); + handleDirty = false; + } + } +} + +#define QT_PATH_ELEMENT(elm) + +QByteArray QPdf::generatePath(const QPainterPath &path, const QTransform &matrix, PathFlags flags) +{ + QByteArray result; + if (!path.elementCount()) + return result; + + ByteStream s(&result); + + int start = -1; + for (int i = 0; i < path.elementCount(); ++i) { + const QPainterPath::Element &elm = path.elementAt(i); + switch (elm.type) { + case QPainterPath::MoveToElement: + if (start >= 0 + && path.elementAt(start).x == path.elementAt(i-1).x + && path.elementAt(start).y == path.elementAt(i-1).y) + s << "h\n"; + s << matrix.map(QPointF(elm.x, elm.y)) << "m\n"; + start = i; + break; + case QPainterPath::LineToElement: + s << matrix.map(QPointF(elm.x, elm.y)) << "l\n"; + break; + case QPainterPath::CurveToElement: + Q_ASSERT(path.elementAt(i+1).type == QPainterPath::CurveToDataElement); + Q_ASSERT(path.elementAt(i+2).type == QPainterPath::CurveToDataElement); + s << matrix.map(QPointF(elm.x, elm.y)) + << matrix.map(QPointF(path.elementAt(i+1).x, path.elementAt(i+1).y)) + << matrix.map(QPointF(path.elementAt(i+2).x, path.elementAt(i+2).y)) + << "c\n"; + i += 2; + break; + default: + qFatal("QPdf::generatePath(), unhandled type: %d", elm.type); + } + } + if (start >= 0 + && path.elementAt(start).x == path.elementAt(path.elementCount()-1).x + && path.elementAt(start).y == path.elementAt(path.elementCount()-1).y) + s << "h\n"; + + Qt::FillRule fillRule = path.fillRule(); + + const char *op = 0; + switch (flags) { + case ClipPath: + op = (fillRule == Qt::WindingFill) ? "W n\n" : "W* n\n"; + break; + case FillPath: + op = (fillRule == Qt::WindingFill) ? "f\n" : "f*\n"; + break; + case StrokePath: + op = "S\n"; + break; + case FillAndStrokePath: + op = (fillRule == Qt::WindingFill) ? "B\n" : "B*\n"; + break; + } + s << op; + return result; +} + +QByteArray QPdf::generateMatrix(const QTransform &matrix) +{ + QByteArray result; + ByteStream s(&result); + s << matrix.m11() + << matrix.m12() + << matrix.m21() + << matrix.m22() + << matrix.dx() + << matrix.dy() + << "cm\n"; + return result; +} + +QByteArray QPdf::generateDashes(const QPen &pen) +{ + QByteArray result; + ByteStream s(&result); + s << "["; + + QVector<qreal> dasharray = pen.dashPattern(); + qreal w = pen.widthF(); + if (w < 0.001) + w = 1; + for (int i = 0; i < dasharray.size(); ++i) { + qreal dw = dasharray.at(i)*w; + if (dw < 0.0001) dw = 0.0001; + s << dw; + } + s << "]"; + //qDebug() << "dasharray: pen has" << dasharray; + //qDebug() << " => " << result; + return result; +} + + + +static const char* pattern_for_brush[] = { + 0, // NoBrush + 0, // SolidPattern + "0 J\n" + "6 w\n" + "[] 0 d\n" + "4 0 m\n" + "4 8 l\n" + "0 4 m\n" + "8 4 l\n" + "S\n", // Dense1Pattern + + "0 J\n" + "2 w\n" + "[6 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[] 0 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[6 2] -3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense2Pattern + + "0 J\n" + "2 w\n" + "[6 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] -1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[6 2] -3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense3Pattern + + "0 J\n" + "2 w\n" + "[2 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] -1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[2 2] 1 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense4Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] 1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[2 6] 3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense5Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 6] 3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense6Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n", // Dense7Pattern + + "1 w\n" + "0 4 m\n" + "8 4 l\n" + "S\n", // HorPattern + + "1 w\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // VerPattern + + "1 w\n" + "4 0 m\n" + "4 8 l\n" + "0 4 m\n" + "8 4 l\n" + "S\n", // CrossPattern + + "1 w\n" + "-1 5 m\n" + "5 -1 l\n" + "3 9 m\n" + "9 3 l\n" + "S\n", // BDiagPattern + + "1 w\n" + "-1 3 m\n" + "5 9 l\n" + "3 -1 m\n" + "9 5 l\n" + "S\n", // FDiagPattern + + "1 w\n" + "-1 3 m\n" + "5 9 l\n" + "3 -1 m\n" + "9 5 l\n" + "-1 5 m\n" + "5 -1 l\n" + "3 9 m\n" + "9 3 l\n" + "S\n", // DiagCrossPattern +}; + +QByteArray QPdf::patternForBrush(const QBrush &b) +{ + int style = b.style(); + if (style > Qt::DiagCrossPattern) + return QByteArray(); + return pattern_for_brush[style]; +} + +#ifdef USE_NATIVE_GRADIENTS +static void writeTriangleLine(uchar *&data, int xpos, int ypos, int xoff, int yoff, uint rgb, uchar flag, bool alpha) +{ + data[0] = flag; + data[1] = (uchar)(xpos >> 16); + data[2] = (uchar)(xpos >> 8); + data[3] = (uchar)(xpos >> 0); + data[4] = (uchar)(ypos >> 16); + data[5] = (uchar)(ypos >> 8); + data[6] = (uchar)(ypos >> 0); + data += 7; + if (alpha) { + *data++ = (uchar)qAlpha(rgb); + } else { + *data++ = (uchar)qRed(rgb); + *data++ = (uchar)qGreen(rgb); + *data++ = (uchar)qBlue(rgb); + } + xpos += xoff; + ypos += yoff; + data[0] = flag; + data[1] = (uchar)(xpos >> 16); + data[2] = (uchar)(xpos >> 8); + data[3] = (uchar)(xpos >> 0); + data[4] = (uchar)(ypos >> 16); + data[5] = (uchar)(ypos >> 8); + data[6] = (uchar)(ypos >> 0); + data += 7; + if (alpha) { + *data++ = (uchar)qAlpha(rgb); + } else { + *data++ = (uchar)qRed(rgb); + *data++ = (uchar)qGreen(rgb); + *data++ = (uchar)qBlue(rgb); + } +} + + +QByteArray QPdf::generateLinearGradientShader(const QLinearGradient *gradient, const QPointF *page_rect, bool alpha) +{ + // generate list of triangles with colors + QPointF start = gradient->start(); + QPointF stop = gradient->finalStop(); + QGradientStops stops = gradient->stops(); + QPointF offset = stop - start; + QGradient::Spread spread = gradient->spread(); + + if (gradient->spread() == QGradient::ReflectSpread) { + offset *= 2; + for (int i = stops.size() - 2; i >= 0; --i) { + QGradientStop stop = stops.at(i); + stop.first = 2. - stop.first; + stops.append(stop); + } + for (int i = 0 ; i < stops.size(); ++i) + stops[i].first /= 2.; + } + + QPointF orthogonal(offset.y(), -offset.x()); + qreal length = offset.x()*offset.x() + offset.y()*offset.y(); + + // find the max and min values in offset and orth direction that are needed to cover + // the whole page + int off_min = INT_MAX; + int off_max = INT_MIN; + qreal ort_min = INT_MAX; + qreal ort_max = INT_MIN; + for (int i = 0; i < 4; ++i) { + qreal off = ((page_rect[i].x() - start.x()) * offset.x() + (page_rect[i].y() - start.y()) * offset.y())/length; + qreal ort = ((page_rect[i].x() - start.x()) * orthogonal.x() + (page_rect[i].y() - start.y()) * orthogonal.y())/length; + off_min = qMin(off_min, qFloor(off)); + off_max = qMax(off_max, qCeil(off)); + ort_min = qMin(ort_min, ort); + ort_max = qMax(ort_max, ort); + } + ort_min -= 1; + ort_max += 1; + + start += off_min * offset + ort_min * orthogonal; + orthogonal *= (ort_max - ort_min); + int num = off_max - off_min; + + QPointF gradient_rect[4] = { start, + start + orthogonal, + start + num*offset, + start + num*offset + orthogonal }; + qreal xmin = gradient_rect[0].x(); + qreal xmax = gradient_rect[0].x(); + qreal ymin = gradient_rect[0].y(); + qreal ymax = gradient_rect[0].y(); + for (int i = 1; i < 4; ++i) { + xmin = qMin(xmin, gradient_rect[i].x()); + xmax = qMax(xmax, gradient_rect[i].x()); + ymin = qMin(ymin, gradient_rect[i].y()); + ymax = qMax(ymax, gradient_rect[i].y()); + } + xmin -= 1000; + xmax += 1000; + ymin -= 1000; + ymax += 1000; + start -= QPointF(xmin, ymin); + qreal factor_x = qreal(1<<24)/(xmax - xmin); + qreal factor_y = qreal(1<<24)/(ymax - ymin); + int xoff = (int)(orthogonal.x()*factor_x); + int yoff = (int)(orthogonal.y()*factor_y); + + QByteArray triangles; + triangles.resize(spread == QGradient::PadSpread ? 20*(stops.size()+2) : 20*num*stops.size()); + uchar *data = (uchar *) triangles.data(); + if (spread == QGradient::PadSpread) { + if (off_min > 0 || off_max < 1) { + // linear gradient outside of page + const QGradientStop ¤t_stop = off_min > 0 ? stops.at(stops.size()-1) : stops.at(0); + uint rgb = current_stop.second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, 0, alpha); + start += num*offset; + xpos = (int)(start.x()*factor_x); + ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, 1, alpha); + } else { + int flag = 0; + if (off_min < 0) { + uint rgb = stops.at(0).second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha); + start -= off_min*offset; + flag = 1; + } + for (int s = 0; s < stops.size(); ++s) { + const QGradientStop ¤t_stop = stops.at(s); + uint rgb = current_stop.second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha); + if (s < stops.size()-1) + start += offset*(stops.at(s+1).first - stops.at(s).first); + flag = 1; + } + if (off_max > 1) { + start += (off_max - 1)*offset; + uint rgb = stops.at(stops.size()-1).second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha); + } + } + } else { + for (int i = 0; i < num; ++i) { + uchar flag = 0; + for (int s = 0; s < stops.size(); ++s) { + uint rgb = stops.at(s).second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha); + if (s < stops.size()-1) + start += offset*(stops.at(s+1).first - stops.at(s).first); + flag = 1; + } + } + } + triangles.resize((char *)data - triangles.constData()); + + QByteArray shader; + QPdf::ByteStream s(&shader); + s << "<<\n" + "/ShadingType 4\n" + "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") << + "/AntiAlias true\n" + "/BitsPerCoordinate 24\n" + "/BitsPerComponent 8\n" + "/BitsPerFlag 8\n" + "/Decode [" << xmin << xmax << ymin << ymax << (alpha ? "0 1]\n" : "0 1 0 1 0 1]\n") << + "/AntiAlias true\n" + "/Length " << triangles.length() << "\n" + ">>\n" + "stream\n" << triangles << "endstream\n" + "endobj\n"; + return shader; +} +#endif + +static void moveToHook(qfixed x, qfixed y, void *data) +{ + QPdf::Stroker *t = (QPdf::Stroker *)data; + if (!t->first) + *t->stream << "h\n"; + if (!t->cosmeticPen) + t->matrix.map(x, y, &x, &y); + *t->stream << x << y << "m\n"; + t->first = false; +} + +static void lineToHook(qfixed x, qfixed y, void *data) +{ + QPdf::Stroker *t = (QPdf::Stroker *)data; + if (!t->cosmeticPen) + t->matrix.map(x, y, &x, &y); + *t->stream << x << y << "l\n"; +} + +static void cubicToHook(qfixed c1x, qfixed c1y, + qfixed c2x, qfixed c2y, + qfixed ex, qfixed ey, + void *data) +{ + QPdf::Stroker *t = (QPdf::Stroker *)data; + if (!t->cosmeticPen) { + t->matrix.map(c1x, c1y, &c1x, &c1y); + t->matrix.map(c2x, c2y, &c2x, &c2y); + t->matrix.map(ex, ey, &ex, &ey); + } + *t->stream << c1x << c1y + << c2x << c2y + << ex << ey + << "c\n"; +} + +QPdf::Stroker::Stroker() + : stream(0), + first(true), + dashStroker(&basicStroker) +{ + stroker = &basicStroker; + basicStroker.setMoveToHook(moveToHook); + basicStroker.setLineToHook(lineToHook); + basicStroker.setCubicToHook(cubicToHook); + cosmeticPen = true; + basicStroker.setStrokeWidth(.1); +} + +void QPdf::Stroker::setPen(const QPen &pen) +{ + if (pen.style() == Qt::NoPen) { + stroker = 0; + return; + } + qreal w = pen.widthF(); + bool zeroWidth = w < 0.0001; + cosmeticPen = pen.isCosmetic(); + if (zeroWidth) + w = .1; + + basicStroker.setStrokeWidth(w); + basicStroker.setCapStyle(pen.capStyle()); + basicStroker.setJoinStyle(pen.joinStyle()); + basicStroker.setMiterLimit(pen.miterLimit()); + + QVector<qreal> dashpattern = pen.dashPattern(); + if (zeroWidth) { + for (int i = 0; i < dashpattern.size(); ++i) + dashpattern[i] *= 10.; + } + if (!dashpattern.isEmpty()) { + dashStroker.setDashPattern(dashpattern); + dashStroker.setDashOffset(pen.dashOffset()); + stroker = &dashStroker; + } else { + stroker = &basicStroker; + } +} + +void QPdf::Stroker::strokePath(const QPainterPath &path) +{ + if (!stroker) + return; + first = true; + + stroker->strokePath(path, this, cosmeticPen ? matrix : QTransform()); + *stream << "h f\n"; +} + +QByteArray QPdf::ascii85Encode(const QByteArray &input) +{ + int isize = input.size()/4*4; + QByteArray output; + output.resize(input.size()*5/4+7); + char *out = output.data(); + const uchar *in = (const uchar *)input.constData(); + for (int i = 0; i < isize; i += 4) { + uint val = (((uint)in[i])<<24) + (((uint)in[i+1])<<16) + (((uint)in[i+2])<<8) + (uint)in[i+3]; + if (val == 0) { + *out = 'z'; + ++out; + } else { + char base[5]; + base[4] = val % 85; + val /= 85; + base[3] = val % 85; + val /= 85; + base[2] = val % 85; + val /= 85; + base[1] = val % 85; + val /= 85; + base[0] = val % 85; + *(out++) = base[0] + '!'; + *(out++) = base[1] + '!'; + *(out++) = base[2] + '!'; + *(out++) = base[3] + '!'; + *(out++) = base[4] + '!'; + } + } + //write the last few bytes + int remaining = input.size() - isize; + if (remaining) { + uint val = 0; + for (int i = isize; i < input.size(); ++i) + val = (val << 8) + in[i]; + val <<= 8*(4-remaining); + char base[5]; + base[4] = val % 85; + val /= 85; + base[3] = val % 85; + val /= 85; + base[2] = val % 85; + val /= 85; + base[1] = val % 85; + val /= 85; + base[0] = val % 85; + for (int i = 0; i < remaining+1; ++i) + *(out++) = base[i] + '!'; + } + *(out++) = '~'; + *(out++) = '>'; + output.resize(out-output.data()); + return output; +} + +const char *QPdf::toHex(ushort u, char *buffer) +{ + int i = 3; + while (i >= 0) { + ushort hex = (u & 0x000f); + if (hex < 0x0a) + buffer[i] = '0'+hex; + else + buffer[i] = 'A'+(hex-0x0a); + u = u >> 4; + i--; + } + buffer[4] = '\0'; + return buffer; +} + +const char *QPdf::toHex(uchar u, char *buffer) +{ + int i = 1; + while (i >= 0) { + ushort hex = (u & 0x000f); + if (hex < 0x0a) + buffer[i] = '0'+hex; + else + buffer[i] = 'A'+(hex-0x0a); + u = u >> 4; + i--; + } + buffer[2] = '\0'; + return buffer; +} + +#define Q_MM(n) int((n * 720 + 127) / 254) +#define Q_IN(n) int(n * 72) + +static const char * const psToStr[QPrinter::NPaperSize+1] = +{ + "A4", "B5", "Letter", "Legal", "Executive", + "A0", "A1", "A2", "A3", "A5", "A6", "A7", "A8", "A9", "B0", "B1", + "B10", "B2", "B3", "B4", "B6", "B7", "B8", "B9", "C5E", "Comm10E", + "DLE", "Folio", "Ledger", "Tabloid", 0 +}; + +QPdf::PaperSize QPdf::paperSize(QPrinter::PaperSize paperSize) +{ + QSizeF s = qt_paperSizeToQSizeF(paperSize); + PaperSize p = { Q_MM(s.width()), Q_MM(s.height()) }; + return p; +} + +const char *QPdf::paperSizeToString(QPrinter::PaperSize paperSize) +{ + return psToStr[paperSize]; +} + + +QByteArray QPdf::stripSpecialCharacters(const QByteArray &string) +{ + QByteArray s = string; + s.replace(" ", ""); + s.replace("(", ""); + s.replace(")", ""); + s.replace("<", ""); + s.replace(">", ""); + s.replace("[", ""); + s.replace("]", ""); + s.replace("{", ""); + s.replace("}", ""); + s.replace("/", ""); + s.replace("%", ""); + return s; +} + + +// -------------------------- base engine, shared code between PS and PDF ----------------------- + +QPdfBaseEngine::QPdfBaseEngine(QPdfBaseEnginePrivate &dd, PaintEngineFeatures f) + : QAlphaPaintEngine(dd, f) +{ + Q_D(QPdfBaseEngine); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::isAvailable()) { + QCUPSSupport cups; + const cups_dest_t* printers = cups.availablePrinters(); + int prnCount = cups.availablePrintersCount(); + + for (int i = 0; i < prnCount; ++i) { + if (printers[i].is_default) { + d->printerName = QString::fromLocal8Bit(printers[i].name); + break; + } + } + + } else +#endif + { + d->printerName = QString::fromLocal8Bit(qgetenv("PRINTER")); + if (d->printerName.isEmpty()) + d->printerName = QString::fromLocal8Bit(qgetenv("LPDEST")); + if (d->printerName.isEmpty()) + d->printerName = QString::fromLocal8Bit(qgetenv("NPRINTER")); + if (d->printerName.isEmpty()) + d->printerName = QString::fromLocal8Bit(qgetenv("NGPRINTER")); + } +} + +void QPdfBaseEngine::drawPoints (const QPointF *points, int pointCount) +{ + Q_D(QPdfBaseEngine); + if (!points || !d->hasPen) + return; + + QPainterPath p; + for (int i=0; i!=pointCount;++i) { + p.moveTo(points[i]); + p.lineTo(points[i] + QPointF(0, 0.001)); + } + drawPath(p); +} + +void QPdfBaseEngine::drawLines (const QLineF *lines, int lineCount) +{ + if (!lines) + return; + + QPainterPath p; + for (int i=0; i!=lineCount;++i) { + p.moveTo(lines[i].p1()); + p.lineTo(lines[i].p2()); + } + drawPath(p); +} + +void QPdfBaseEngine::drawRects (const QRectF *rects, int rectCount) +{ + if (!rects) + return; + + Q_D(QPdfBaseEngine); + if (d->clipEnabled && d->allClipped) + return; + if (!d->hasPen && !d->hasBrush) + return; + + QBrush penBrush = d->pen.brush(); + if (d->simplePen || !d->hasPen) { + // draw strokes natively in this case for better output + if(!d->simplePen && !d->stroker.matrix.isIdentity()) + *d->currentPage << "q\n" << QPdf::generateMatrix(d->stroker.matrix); + for (int i = 0; i < rectCount; ++i) + *d->currentPage << rects[i].x() << rects[i].y() << rects[i].width() << rects[i].height() << "re\n"; + *d->currentPage << (d->hasPen ? (d->hasBrush ? "B\n" : "S\n") : "f\n"); + if(!d->simplePen && !d->stroker.matrix.isIdentity()) + *d->currentPage << "Q\n"; + } else { + QPainterPath p; + for (int i=0; i!=rectCount; ++i) + p.addRect(rects[i]); + drawPath(p); + } +} + +void QPdfBaseEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QPdfBaseEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawPolygon(points, pointCount, mode); + if (!continueCall()) + return; + } + + if (!points || !pointCount) + return; + + bool hb = d->hasBrush; + QPainterPath p; + + switch(mode) { + case OddEvenMode: + p.setFillRule(Qt::OddEvenFill); + break; + case ConvexMode: + case WindingMode: + p.setFillRule(Qt::WindingFill); + break; + case PolylineMode: + d->hasBrush = false; + break; + default: + break; + } + + p.moveTo(points[0]); + for (int i = 1; i < pointCount; ++i) + p.lineTo(points[i]); + + if (mode != PolylineMode) + p.closeSubpath(); + drawPath(p); + + d->hasBrush = hb; +} + +void QPdfBaseEngine::drawPath (const QPainterPath &p) +{ + Q_D(QPdfBaseEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawPath(p); + if (!continueCall()) + return; + } + + if (d->clipEnabled && d->allClipped) + return; + if (!d->hasPen && !d->hasBrush) + return; + + if (d->simplePen) { + // draw strokes natively in this case for better output + *d->currentPage << QPdf::generatePath(p, QTransform(), d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath); + } else { + if (d->hasBrush) + *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath); + if (d->hasPen) { + *d->currentPage << "q\n"; + QBrush b = d->brush; + d->brush = d->pen.brush(); + setBrush(); + d->stroker.strokePath(p); + *d->currentPage << "Q\n"; + d->brush = b; + } + } +} + +void QPdfBaseEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + Q_D(QPdfBaseEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawTextItem(p, textItem); + if (!continueCall()) + return; + } + + if (!d->hasPen || (d->clipEnabled && d->allClipped)) + return; + + *d->currentPage << "q\n"; + if(!d->simplePen) + *d->currentPage << QPdf::generateMatrix(d->stroker.matrix); + + bool hp = d->hasPen; + d->hasPen = false; + QBrush b = d->brush; + d->brush = d->pen.brush(); + setBrush(); + + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi); + d->drawTextItem(p, ti); + d->hasPen = hp; + d->brush = b; + *d->currentPage << "Q\n"; +} + + +void QPdfBaseEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QPdfBaseEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::updateState(state); + if (!continueCall()) + return; + } + + QPaintEngine::DirtyFlags flags = state.state(); + + if (flags & DirtyTransform) + d->stroker.matrix = state.transform(); + + if (flags & DirtyPen) { + d->pen = state.pen(); + d->hasPen = d->pen.style() != Qt::NoPen; + d->stroker.setPen(d->pen); + QBrush penBrush = d->pen.brush(); + bool oldSimple = d->simplePen; + d->simplePen = (d->hasPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque()); + if (oldSimple != d->simplePen) + flags |= DirtyTransform; + } + if (flags & DirtyBrush) { + d->brush = state.brush(); + d->hasBrush = d->brush.style() != Qt::NoBrush; + } + if (flags & DirtyBrushOrigin) { + d->brushOrigin = state.brushOrigin(); + flags |= DirtyBrush; + } + if (flags & DirtyOpacity) + d->opacity = state.opacity(); + + bool ce = d->clipEnabled; + if (flags & DirtyClipPath) { + d->clipEnabled = true; + updateClipPath(state.clipPath(), state.clipOperation()); + } else if (flags & DirtyClipRegion) { + d->clipEnabled = true; + QPainterPath path; + QVector<QRect> rects = state.clipRegion().rects(); + for (int i = 0; i < rects.size(); ++i) + path.addRect(rects.at(i)); + updateClipPath(path, state.clipOperation()); + flags |= DirtyClipPath; + } else if (flags & DirtyClipEnabled) { + d->clipEnabled = state.isClipEnabled(); + } + + if (ce != d->clipEnabled) + flags |= DirtyClipPath; + else if (!d->clipEnabled) + flags &= ~DirtyClipPath; + + setupGraphicsState(flags); +} + +void QPdfBaseEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags) +{ + Q_D(QPdfBaseEngine); + if (flags & DirtyClipPath) + flags |= DirtyTransform|DirtyPen|DirtyBrush; + + if (flags & DirtyTransform) { + *d->currentPage << "Q\n"; + flags |= DirtyPen|DirtyBrush; + } + + if (flags & DirtyClipPath) { + *d->currentPage << "Q q\n"; + + d->allClipped = false; + if (d->clipEnabled && !d->clips.isEmpty()) { + for (int i = 0; i < d->clips.size(); ++i) { + if (d->clips.at(i).isEmpty()) { + d->allClipped = true; + break; + } + } + if (!d->allClipped) { + for (int i = 0; i < d->clips.size(); ++i) { + *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath); + } + } + } + } + + if (flags & DirtyTransform) { + *d->currentPage << "q\n"; + if (d->simplePen && !d->stroker.matrix.isIdentity()) + *d->currentPage << QPdf::generateMatrix(d->stroker.matrix); + } + if (flags & DirtyBrush) + setBrush(); + if (d->simplePen && (flags & DirtyPen)) + setPen(); +} + +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +void QPdfBaseEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op) +{ + Q_D(QPdfBaseEngine); + QPainterPath path = d->stroker.matrix.map(p); + //qDebug() << "updateClipPath: " << d->stroker.matrix << p.boundingRect() << path.boundingRect() << op; + + if (op == Qt::NoClip) { + d->clipEnabled = false; + d->clips.clear(); + } else if (op == Qt::ReplaceClip) { + d->clips.clear(); + d->clips.append(path); + } else if (op == Qt::IntersectClip) { + d->clips.append(path); + } else { // UniteClip + // ask the painter for the current clipping path. that's the easiest solution + path = painter()->clipPath(); + path = d->stroker.matrix.map(path); + d->clips.clear(); + d->clips.append(path); + } + + if (d->useAlphaEngine) { + // if we have an alpha region, we have to subtract that from the + // any existing clip region since that region will be filled in + // later with images + QPainterPath alphaClip = qt_regionToPath(alphaClipping()); + if (!alphaClip.isEmpty()) { + if (!d->clipEnabled) { + QRect r = d->fullPage ? d->paperRect() : d->pageRect(); + QPainterPath dev; + dev.addRect(QRect(0, 0, r.width(), r.height())); + if (path.isEmpty()) + path = dev; + else + path = path.intersected(dev); + d->clipEnabled = true; + } else { + path = painter()->clipPath(); + path = d->stroker.matrix.map(path); + } + path = path.subtracted(alphaClip); + d->clips.clear(); + d->clips.append(path); + } + } +} + +void QPdfBaseEngine::setPen() +{ + Q_D(QPdfBaseEngine); + if (d->pen.style() == Qt::NoPen) + return; + QBrush b = d->pen.brush(); + Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque()); + + QColor rgba = b.color(); + if (d->colorMode == QPrinter::GrayScale) { + qreal gray = qGray(rgba.rgba())/255.; + *d->currentPage << gray << gray << gray; + } else { + *d->currentPage << rgba.redF() + << rgba.greenF() + << rgba.blueF(); + } + *d->currentPage << "SCN\n"; + + *d->currentPage << d->pen.widthF() << "w "; + + int pdfCapStyle = 0; + switch(d->pen.capStyle()) { + case Qt::FlatCap: + pdfCapStyle = 0; + break; + case Qt::SquareCap: + pdfCapStyle = 2; + break; + case Qt::RoundCap: + pdfCapStyle = 1; + break; + default: + break; + } + *d->currentPage << pdfCapStyle << "J "; + + int pdfJoinStyle = 0; + switch(d->pen.joinStyle()) { + case Qt::MiterJoin: + pdfJoinStyle = 0; + break; + case Qt::BevelJoin: + pdfJoinStyle = 2; + break; + case Qt::RoundJoin: + pdfJoinStyle = 1; + break; + default: + break; + } + *d->currentPage << pdfJoinStyle << "j "; + + *d->currentPage << QPdf::generateDashes(d->pen) << " 0 d\n"; +} + +bool QPdfBaseEngine::newPage() +{ + Q_D(QPdfBaseEngine); + setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath); + QFile *outfile = qobject_cast<QFile*> (d->outDevice); + if (outfile && outfile->error() != QFile::NoError) + return false; + return true; +} + + +int QPdfBaseEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const +{ + Q_D(const QPdfBaseEngine); + int val; + QRect r = d->fullPage ? d->paperRect() : d->pageRect(); + switch (metricType) { + case QPaintDevice::PdmWidth: + val = r.width(); + break; + case QPaintDevice::PdmHeight: + val = r.height(); + break; + case QPaintDevice::PdmDpiX: + case QPaintDevice::PdmDpiY: + val = d->resolution; + break; + case QPaintDevice::PdmPhysicalDpiX: + case QPaintDevice::PdmPhysicalDpiY: + val = 1200; + break; + case QPaintDevice::PdmWidthMM: + val = qRound(r.width()*25.4/d->resolution); + break; + case QPaintDevice::PdmHeightMM: + val = qRound(r.height()*25.4/d->resolution); + break; + case QPaintDevice::PdmNumColors: + val = INT_MAX; + break; + case QPaintDevice::PdmDepth: + val = 32; + break; + default: + qWarning("QPrinter::metric: Invalid metric command"); + return 0; + } + return val; +} + +void QPdfBaseEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) +{ + Q_D(QPdfBaseEngine); + switch (key) { + case PPK_CollateCopies: + d->collate = value.toBool(); + break; + case PPK_ColorMode: + d->colorMode = QPrinter::ColorMode(value.toInt()); + break; + case PPK_Creator: + d->creator = value.toString(); + break; + case PPK_DocumentName: + d->title = value.toString(); + break; + case PPK_FullPage: + d->fullPage = value.toBool(); + break; + case PPK_NumberOfCopies: + d->copies = value.toInt(); + break; + case PPK_Orientation: + d->orientation = QPrinter::Orientation(value.toInt()); + break; + case PPK_OutputFileName: + d->outputFileName = value.toString(); + break; + case PPK_PageOrder: + d->pageOrder = QPrinter::PageOrder(value.toInt()); + break; + case PPK_PaperSize: + d->paperSize = QPrinter::PaperSize(value.toInt()); + break; + case PPK_PaperSource: + d->paperSource = QPrinter::PaperSource(value.toInt()); + break; + case PPK_PrinterName: + d->printerName = value.toString(); + break; + case PPK_PrinterProgram: + d->printProgram = value.toString(); + break; + case PPK_Resolution: + d->resolution = value.toInt(); + break; + case PPK_SelectionOption: + d->selectionOption = value.toString(); + break; + case PPK_FontEmbedding: + d->embedFonts = value.toBool(); + break; + case PPK_Duplex: + d->duplex = static_cast<QPrinter::DuplexMode> (value.toInt()); + break; + case PPK_CupsPageRect: + d->cupsPageRect = value.toRect(); + break; + case PPK_CupsPaperRect: + d->cupsPaperRect = value.toRect(); + break; + case PPK_CupsOptions: + d->cupsOptions = value.toStringList(); + break; + case PPK_CupsStringPageSize: + d->cupsStringPageSize = value.toString(); + break; + case PPK_CustomPaperSize: + d->paperSize = QPrinter::Custom; + d->customPaperSize = value.toSizeF(); + break; + case PPK_PageMargins: + { + QList<QVariant> margins(value.toList()); + Q_ASSERT(margins.size() == 4); + d->leftMargin = margins.at(0).toDouble(); + d->topMargin = margins.at(1).toDouble(); + d->rightMargin = margins.at(2).toDouble(); + d->bottomMargin = margins.at(3).toDouble(); + d->hasCustomPageMargins = true; + break; + } + default: + break; + } +} + +QVariant QPdfBaseEngine::property(PrintEnginePropertyKey key) const +{ + Q_D(const QPdfBaseEngine); + + QVariant ret; + switch (key) { + case PPK_CollateCopies: + ret = d->collate; + break; + case PPK_ColorMode: + ret = d->colorMode; + break; + case PPK_Creator: + ret = d->creator; + break; + case PPK_DocumentName: + ret = d->title; + break; + case PPK_FullPage: + ret = d->fullPage; + break; + case PPK_NumberOfCopies: +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::isAvailable()) + ret = 1; + else +#endif + ret = d->copies; + break; + case PPK_Orientation: + ret = d->orientation; + break; + case PPK_OutputFileName: + ret = d->outputFileName; + break; + case PPK_PageOrder: + ret = d->pageOrder; + break; + case PPK_PaperSize: + ret = d->paperSize; + break; + case PPK_PaperSource: + ret = d->paperSource; + break; + case PPK_PrinterName: + ret = d->printerName; + break; + case PPK_PrinterProgram: + ret = d->printProgram; + break; + case PPK_Resolution: + ret = d->resolution; + break; + case PPK_SupportedResolutions: + ret = QList<QVariant>() << 72; + break; + case PPK_PaperRect: + ret = d->paperRect(); + break; + case PPK_PageRect: + ret = d->pageRect(); + break; + case PPK_SelectionOption: + ret = d->selectionOption; + break; + case PPK_FontEmbedding: + ret = d->embedFonts; + break; + case PPK_Duplex: + ret = d->duplex; + break; + case PPK_CupsPageRect: + ret = d->cupsPageRect; + break; + case PPK_CupsPaperRect: + ret = d->cupsPaperRect; + break; + case PPK_CupsOptions: + ret = d->cupsOptions; + break; + case PPK_CupsStringPageSize: + ret = d->cupsStringPageSize; + break; + case PPK_CustomPaperSize: + ret = d->customPaperSize; + break; + case PPK_PageMargins: + { + QList<QVariant> margins; + if (d->hasCustomPageMargins) { + margins << d->leftMargin << d->topMargin + << d->rightMargin << d->bottomMargin; + } else { + const int defaultMargin = 10; // ~3.5 mm + margins << defaultMargin << defaultMargin + << defaultMargin << defaultMargin; + } + ret = margins; + break; + } + default: + break; + } + return ret; +} + +QPdfBaseEnginePrivate::QPdfBaseEnginePrivate(QPrinter::PrinterMode m) + : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false), + useAlphaEngine(false), + outDevice(0), fd(-1), + duplex(QPrinter::DuplexNone), collate(false), fullPage(false), embedFonts(true), copies(1), + pageOrder(QPrinter::FirstPageFirst), orientation(QPrinter::Portrait), + paperSize(QPrinter::A4), colorMode(QPrinter::Color), paperSource(QPrinter::Auto), + hasCustomPageMargins(false), + leftMargin(0), topMargin(0), rightMargin(0), bottomMargin(0) +{ + resolution = 72; + if (m == QPrinter::HighResolution) + resolution = 1200; + else if (m == QPrinter::ScreenResolution) + resolution = qt_defaultDpi(); + + postscript = false; + currentObject = 1; + currentPage = 0; + stroker.stream = 0; +} + +bool QPdfBaseEngine::begin(QPaintDevice *pdev) +{ + Q_D(QPdfBaseEngine); + d->pdev = pdev; + + d->postscript = false; + d->currentObject = 1; + + d->currentPage = new QPdfPage; + d->stroker.stream = d->currentPage; + d->opacity = 1.0; + + return d->openPrintDevice(); +} + +bool QPdfBaseEngine::end() +{ + Q_D(QPdfBaseEngine); + qDeleteAll(d->fonts); + d->fonts.clear(); + delete d->currentPage; + d->currentPage = 0; + + d->closePrintDevice(); + return true; +} + +#ifndef QT_NO_LPR +static void closeAllOpenFds() +{ + // hack time... getting the maximum number of open + // files, if possible. if not we assume it's the + // larger of 256 and the fd we got + int i; +#if defined(_SC_OPEN_MAX) + i = (int)sysconf(_SC_OPEN_MAX); +#elif defined(_POSIX_OPEN_MAX) + i = (int)_POSIX_OPEN_MAX; +#elif defined(OPEN_MAX) + i = (int)OPEN_MAX; +#else + i = 256; +#endif + // leave stdin/out/err untouched + while(--i > 2) + ::close(i); +} +#endif + +bool QPdfBaseEnginePrivate::openPrintDevice() +{ + if(outDevice) + return false; + + if (!outputFileName.isEmpty()) { + QFile *file = new QFile(outputFileName); + if (! file->open(QFile::WriteOnly|QFile::Truncate)) { + delete file; + return false; + } + outDevice = file; +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + } else if (QCUPSSupport::isAvailable()) { + QCUPSSupport cups; + QPair<int, QString> ret = cups.tempFd(); + if (ret.first < 0) { + qWarning("QPdfPrinter: Could not open temporary file to print"); + return false; + } + cupsTempFile = ret.second; + outDevice = new QFile(); + static_cast<QFile *>(outDevice)->open(ret.first, QIODevice::WriteOnly); +#endif +#ifndef QT_NO_LPR + } else { + QString pr; + if (!printerName.isEmpty()) + pr = printerName; + int fds[2]; + if (pipe(fds) != 0) { + qWarning("QPdfPrinter: Could not open pipe to print"); + return false; + } + + pid_t pid = fork(); + if (pid == 0) { // child process + // if possible, exit quickly, so the actual lp/lpr + // becomes a child of init, and ::waitpid() is + // guaranteed not to wait. + if (fork() > 0) { + closeAllOpenFds(); + + // try to replace this process with "true" - this prevents + // global destructors from being called (that could possibly + // do wrong things to the parent process) + (void)execlp("true", "true", (char *)0); + (void)execl("/bin/true", "true", (char *)0); + (void)execl("/usr/bin/true", "true", (char *)0); + ::exit(0); + } + dup2(fds[0], 0); + + closeAllOpenFds(); + + if (!printProgram.isEmpty()) { + if (!selectionOption.isEmpty()) + pr.prepend(selectionOption); + else + pr.prepend(QLatin1String("-P")); + (void)execlp(printProgram.toLocal8Bit().data(), printProgram.toLocal8Bit().data(), + pr.toLocal8Bit().data(), (char *)0); + } else { + // if no print program has been specified, be smart + // about the option string too. + QList<QByteArray> lprhack; + QList<QByteArray> lphack; + QByteArray media; + if (!pr.isEmpty() || !selectionOption.isEmpty()) { + if (!selectionOption.isEmpty()) { + QStringList list = selectionOption.split(QLatin1Char(' ')); + for (int i = 0; i < list.size(); ++i) + lprhack.append(list.at(i).toLocal8Bit()); + lphack = lprhack; + } else { + lprhack.append("-P"); + lphack.append("-d"); + } + lprhack.append(pr.toLocal8Bit()); + lphack.append(pr.toLocal8Bit()); + } + lphack.append("-s"); + + char ** lpargs = new char *[lphack.size()+6]; + char lp[] = "lp"; + lpargs[0] = lp; + int i; + for (i = 0; i < lphack.size(); ++i) + lpargs[i+1] = (char *)lphack.at(i).constData(); +#ifndef Q_OS_OSF + if (QPdf::paperSizeToString(paperSize)) { + char dash_o[] = "-o"; + lpargs[++i] = dash_o; + lpargs[++i] = const_cast<char *>(QPdf::paperSizeToString(paperSize)); + lpargs[++i] = dash_o; + media = "media="; + media += QPdf::paperSizeToString(paperSize); + lpargs[++i] = media.data(); + } +#endif + lpargs[++i] = 0; + char **lprargs = new char *[lprhack.size()+2]; + char lpr[] = "lpr"; + lprargs[0] = lpr; + for (int i = 0; i < lprhack.size(); ++i) + lprargs[i+1] = (char *)lprhack[i].constData(); + lprargs[lprhack.size() + 1] = 0; + (void)execvp("lp", lpargs); + (void)execvp("lpr", lprargs); + (void)execv("/bin/lp", lpargs); + (void)execv("/bin/lpr", lprargs); + (void)execv("/usr/bin/lp", lpargs); + (void)execv("/usr/bin/lpr", lprargs); + } + // if we couldn't exec anything, close the fd, + // wait for a second so the parent process (the + // child of the GUI process) has exited. then + // exit. + ::close(0); + (void)::sleep(1); + ::exit(0); + } + // parent process + ::close(fds[0]); + fd = fds[1]; + (void)::waitpid(pid, 0, 0); + + if (fd < 0) + return false; + + outDevice = new QFile(); + static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly); +#endif + } + + return true; +} + +void QPdfBaseEnginePrivate::closePrintDevice() +{ + if (!outDevice) + return; + outDevice->close(); + if (fd >= 0) +#if defined(Q_OS_WIN) && defined(_MSC_VER) && _MSC_VER >= 1400 + ::_close(fd); +#else + ::close(fd); +#endif + fd = -1; + delete outDevice; + outDevice = 0; + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (!cupsTempFile.isEmpty()) { + QString tempFile = cupsTempFile; + cupsTempFile.clear(); + QCUPSSupport cups; + + // Set up print options. + QByteArray prnName; + QList<QPair<QByteArray, QByteArray> > options; + QVector<cups_option_t> cupsOptStruct; + + if (!printerName.isEmpty()) { + prnName = printerName.toLocal8Bit(); + } else { + QPrinterInfo def = QPrinterInfo::defaultPrinter(); + if (def.isNull()) { + qWarning("Could not determine printer to print to"); + QFile::remove(tempFile); + return; + } + prnName = def.printerName().toLocal8Bit(); + } + + if (!cupsStringPageSize.isEmpty()) { + options.append(QPair<QByteArray, QByteArray>("media", cupsStringPageSize.toLocal8Bit())); + } + + if (copies > 1) { + options.append(QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit())); + } + + if (collate) { + options.append(QPair<QByteArray, QByteArray>("Collate", "True")); + } + + if (duplex != QPrinter::DuplexNone) { + switch(duplex) { + case QPrinter::DuplexNone: break; + case QPrinter::DuplexAuto: + if (orientation == QPrinter::Portrait) + options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge")); + else + options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge")); + break; + case QPrinter::DuplexLongSide: + options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge")); + break; + case QPrinter::DuplexShortSide: + options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge")); + break; + } + } + + if (QCUPSSupport::cupsVersion() >= 10300 && orientation == QPrinter::Landscape) { + options.append(QPair<QByteArray, QByteArray>("landscape", "")); + } + + QStringList::const_iterator it = cupsOptions.constBegin(); + while (it != cupsOptions.constEnd()) { + options.append(QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit())); + it += 2; + } + + for (int c = 0; c < options.size(); ++c) { + cups_option_t opt; + opt.name = options[c].first.data(); + opt.value = options[c].second.data(); + cupsOptStruct.append(opt); + } + + // Print the file. + cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0; + cups.printFile(prnName.constData(), tempFile.toLocal8Bit().constData(), + title.toLocal8Bit().constData(), cupsOptStruct.size(), optPtr); + + QFile::remove(tempFile); + } +#endif +} + +QPdfBaseEnginePrivate::~QPdfBaseEnginePrivate() +{ + qDeleteAll(fonts); + delete currentPage; +} + +void QPdfBaseEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) +{ + Q_Q(QPdfBaseEngine); + + QFontEngine *fe = ti.fontEngine; + + QFontEngine::FaceId face_id = fe->faceId(); + bool noEmbed = false; + if (face_id.filename.isEmpty() + || (!postscript && ((fe->fsType & 0x200) /* bitmap embedding only */ + || (fe->fsType == 2) /* no embedding allowed */))) { + *currentPage << "Q\n"; + q->QPaintEngine::drawTextItem(p, ti); + *currentPage << "q\n"; + if (face_id.filename.isEmpty()) + return; + noEmbed = true; + } + + QFontSubset *font = fonts.value(face_id, 0); + if (!font) { + font = new QFontSubset(fe, requestObject()); + font->noEmbed = noEmbed; + } + fonts.insert(face_id, font); + + if (!currentPage->fonts.contains(font->object_id)) + currentPage->fonts.append(font->object_id); + + qreal size = ti.fontEngine->fontDef.pixelSize; +#ifdef Q_WS_WIN + if (ti.fontEngine->type() == QFontEngine::Win) { + QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine); + size = fe->tm.w.tmHeight; + } +#endif + + QVarLengthArray<glyph_t> glyphs; + QVarLengthArray<QFixedPoint> positions; + QTransform m; + m.translate(p.x(), p.y()); + ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags, + glyphs, positions); + if (glyphs.size() == 0) + return; + int synthesized = ti.fontEngine->synthesized(); + qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.; + + *currentPage << "BT\n" + << "/F" << font->object_id << size << "Tf " + << stretch << (synthesized & QFontEngine::SynthesizedItalic + ? "0 .3 -1 0 0 Tm\n" + : "0 0 -1 0 0 Tm\n"); + + +#if 0 + // #### implement actual text for complex languages + const unsigned short *logClusters = ti.logClusters; + int pos = 0; + do { + int end = pos + 1; + while (end < ti.num_chars && logClusters[end] == logClusters[pos]) + ++end; + *currentPage << "/Span << /ActualText <FEFF"; + for (int i = pos; i < end; ++i) { + s << toHex((ushort)ti.chars[i].unicode(), buf); + } + *currentPage << "> >>\n" + "BDC\n" + "<"; + int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end]; + for (int gs = logClusters[pos]; gs < ge; ++gs) + *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf); + *currentPage << "> Tj\n" + "EMC\n"; + pos = end; + } while (pos < ti.num_chars); +#else + qreal last_x = 0.; + qreal last_y = 0.; + for (int i = 0; i < glyphs.size(); ++i) { + qreal x = positions[i].x.toReal(); + qreal y = positions[i].y.toReal(); + if (synthesized & QFontEngine::SynthesizedItalic) + x += .3*y; + x /= stretch; + char buf[5]; + int g = font->addGlyph(glyphs[i]); + *currentPage << x - last_x << last_y - y << "Td <" + << QPdf::toHex((ushort)g, buf) << "> Tj\n"; + last_x = x; + last_y = y; + } + if (synthesized & QFontEngine::SynthesizedBold) { + *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic + ? "0 .3 -1 0 0 Tm\n" + : "0 0 -1 0 0 Tm\n"); + *currentPage << "/Span << /ActualText <> >> BDC\n"; + last_x = 0.5*fe->lineThickness().toReal(); + last_y = 0.; + for (int i = 0; i < glyphs.size(); ++i) { + qreal x = positions[i].x.toReal(); + qreal y = positions[i].y.toReal(); + if (synthesized & QFontEngine::SynthesizedItalic) + x += .3*y; + x /= stretch; + char buf[5]; + int g = font->addGlyph(glyphs[i]); + *currentPage << x - last_x << last_y - y << "Td <" + << QPdf::toHex((ushort)g, buf) << "> Tj\n"; + last_x = x; + last_y = y; + } + *currentPage << "EMC\n"; + } +#endif + + *currentPage << "ET\n"; +} + +QRect QPdfBaseEnginePrivate::paperRect() const +{ + int w; + int h; + if (paperSize == QPrinter::Custom) { + w = qRound(customPaperSize.width()*resolution/72.); + h = qRound(customPaperSize.height()*resolution/72.); + } else { +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::isAvailable() && !cupsPaperRect.isNull()) { + QRect r = cupsPaperRect; + w = r.width(); + h = r.height(); + } else +#endif + { + QPdf::PaperSize s = QPdf::paperSize(paperSize); + w = s.width; + h = s.height; + } + w = qRound(w*resolution/72.); + h = qRound(h*resolution/72.); + } + if (orientation == QPrinter::Portrait) + return QRect(0, 0, w, h); + else + return QRect(0, 0, h, w); +} + +QRect QPdfBaseEnginePrivate::pageRect() const +{ + if(fullPage) + return paperRect(); + + QRect r; + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (!hasCustomPageMargins && QCUPSSupport::isAvailable() && !cupsPageRect.isNull()) { + r = cupsPageRect; + if (r == cupsPaperRect) { + // if cups doesn't define any margins, give it at least approx 3.5 mm + r = QRect(10, 10, r.width() - 20, r.height() - 20); + } + } else +#endif + { + QPdf::PaperSize s; + if (paperSize == QPrinter::Custom) { + s.width = qRound(customPaperSize.width()); + s.height = qRound(customPaperSize.height()); + } else { + s = QPdf::paperSize(paperSize); + } + if (hasCustomPageMargins) + r = QRect(0, 0, s.width, s.height); + else + r = QRect(72/3, 72/3, s.width - 2*72/3, s.height - 2*72/3); + } + + int x = qRound(r.left()*resolution/72.); + int y = qRound(r.top()*resolution/72.); + int w = qRound(r.width()*resolution/72.); + int h = qRound(r.height()*resolution/72.); + if (orientation == QPrinter::Portrait) + r = QRect(x, y, w, h); + else + r = QRect(y, x, h, w); + + if (hasCustomPageMargins) { + r.adjust(qRound(leftMargin*(resolution/72.)), + qRound(topMargin*(resolution/72.)), + -qRound(rightMargin*(resolution/72.)), + -qRound(bottomMargin*(resolution/72.))); + } + return r; +} + +#endif + +QT_END_NAMESPACE |