/**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (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 Technology Preview License Agreement accompanying ** this package. ** ** 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.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qplatformdefs.h" #include #include "qpdf_p.h" #include #include #include #include "private/qcups_p.h" #include "qprinterinfo.h" #include #ifdef Q_OS_UNIX #include "private/qcore_unix_p.h" // overrides QT_OPEN #endif QT_BEGIN_NAMESPACE Q_GUI_EXPORT 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 * 1000000000); if (ifrac == 1000000000) { ++ival; ifrac = 0; } char output[256]; int i = 0; while (ival) { output[i] = '0' + (ival % 10); ++i; ival /= 10; } int fact = 100000000; 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 | QIODevice::Append); } 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(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 = ""; 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 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 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]; } Q_GUI_EXPORT 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) { if (!points) return; Q_D(QPdfBaseEngine); QPainterPath p; for (int i=0; i!=pointCount;++i) { p.moveTo(points[i]); p.lineTo(points[i] + QPointF(0, 0.001)); } bool hadBrush = d->hasBrush; d->hasBrush = false; drawPath(p); d->hasBrush = hadBrush; } void QPdfBaseEngine::drawLines (const QLineF *lines, int lineCount) { if (!lines) return; Q_D(QPdfBaseEngine); QPainterPath p; for (int i=0; i!=lineCount;++i) { p.moveTo(lines[i].p1()); p.lineTo(lines[i].p2()); } bool hadBrush = d->hasBrush; d->hasBrush = false; drawPath(p); d->hasBrush = hadBrush; } void QPdfBaseEngine::drawRects (const QRectF *rects, int rectCount) { if (!rects) return; Q_D(QPdfBaseEngine); if (d->useAlphaEngine) { QAlphaPaintEngine::drawRects(rects, rectCount); if (!continueCall()) return; } 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; if (d->stroker.matrix.type() >= QTransform::TxProject) { QPaintEngine::drawTextItem(p, textItem); 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(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 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 (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_CopyCount: // fallthrough 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 (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 margins(value.toList()); Q_ASSERT(margins.size() == 4); d->leftMargin = margins.at(0).toReal(); d->topMargin = margins.at(1).toReal(); d->rightMargin = margins.at(2).toReal(); d->bottomMargin = margins.at(3).toReal(); 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_CopyCount: ret = d->copies; break; case PPK_SupportsMultipleCopies: #if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) if (QCUPSSupport::isAvailable()) ret = true; else #endif ret = false; 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() << 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 margins; if (d->hasCustomPageMargins) { margins << d->leftMargin << d->topMargin << d->rightMargin << d->bottomMargin; } else { const qreal 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) QT_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 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(outDevice)->open(ret.first, QIODevice::WriteOnly); #endif #ifndef QT_NO_LPR } else { QString pr; if (!printerName.isEmpty()) pr = printerName; int fds[2]; if (qt_safe_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); } qt_safe_dup2(fds[0], 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 lprhack; QList 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(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); delete []lpargs; delete []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. QT_CLOSE(0); (void)::sleep(1); ::_exit(0); } // parent process QT_CLOSE(fds[0]); fd = fds[1]; (void)qt_safe_waitpid(pid, 0, 0); if (fd < 0) return false; outDevice = new QFile(); static_cast(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 > options; QVector 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("media", cupsStringPageSize.toLocal8Bit())); } if (copies > 1) { options.append(QPair("copies", QString::number(copies).toLocal8Bit())); } if (collate) { options.append(QPair("Collate", "True")); } if (duplex != QPrinter::DuplexNone) { switch(duplex) { case QPrinter::DuplexNone: break; case QPrinter::DuplexAuto: if (orientation == QPrinter::Portrait) options.append(QPair("sides", "two-sided-long-edge")); else options.append(QPair("sides", "two-sided-short-edge")); break; case QPrinter::DuplexLongSide: options.append(QPair("sides", "two-sided-long-edge")); break; case QPrinter::DuplexShortSide: options.append(QPair("sides", "two-sided-short-edge")); break; } } if (QCUPSSupport::cupsVersion() >= 10300 && orientation == QPrinter::Landscape) { options.append(QPair("landscape", "")); } QStringList::const_iterator it = cupsOptions.constBegin(); while (it != cupsOptions.constEnd()) { options.append(QPair((*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(ti.fontEngine); size = fe->tm.tmHeight; } #endif QVarLengthArray glyphs; QVarLengthArray positions; QTransform m = QTransform::fromTranslate(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 >>\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