diff options
Diffstat (limited to 'src/gui/painting/qpdf.cpp')
-rw-r--r-- | src/gui/painting/qpdf.cpp | 1666 |
1 files changed, 1137 insertions, 529 deletions
diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp index 958e49907b..da87653ae7 100644 --- a/src/gui/painting/qpdf.cpp +++ b/src/gui/painting/qpdf.cpp @@ -44,18 +44,27 @@ #include <qfile.h> #include <qtemporaryfile.h> #include <private/qmath_p.h> -#include "private/qcups_p.h" -#include "qprinterinfo.h" #include <qnumeric.h> #include "private/qfont_p.h" +#include <qimagewriter.h> +#include "qbuffer.h" +#include "QtCore/qdatetime.h" -#ifdef Q_OS_UNIX -#include "private/qcore_unix_p.h" // overrides QT_OPEN +#ifndef QT_NO_COMPRESS +#include <zlib.h> #endif -QT_BEGIN_NAMESPACE +#ifdef QT_NO_COMPRESS +static const bool do_compress = false; +#else +static const bool do_compress = true; +#endif + +// might be helpful for smooth transforms of images +// Can't use it though, as gs generates completely wrong images if this is true. +static const bool interpolateImages = false; -#ifndef QT_NO_PRINTER +QT_BEGIN_NAMESPACE extern QSizeF qt_paperSizeToQSizeF(QPrinter::PaperSize size); @@ -914,36 +923,22 @@ const char *QPdf::paperSizeToString(QPrinter::PaperSize paperSize) return psToStr[paperSize]; } -// -------------------------- base engine, shared code between PS and PDF ----------------------- +QPdfPage::QPdfPage() + : QPdf::ByteStream(true) // Enable file backing +{ +} + +void QPdfPage::streamImage(int w, int h, int object) +{ + *this << w << "0 0 " << -h << "0 " << h << "cm /Im" << object << " Do\n"; + if (!images.contains(object)) + images.append(object); +} + 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) @@ -1091,6 +1086,89 @@ void QPdfBaseEngine::drawPath (const QPainterPath &p) } } +void QPdfBaseEngine::drawPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QRectF &sr) +{ + if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull()) + return; + Q_D(QPdfBaseEngine); + + QBrush b = d->brush; + + QRect sourceRect = sr.toRect(); + QPixmap pm = sourceRect != pixmap.rect() ? pixmap.copy(sourceRect) : pixmap; + QImage image = pm.toImage(); + bool bitmap = true; + const int object = d->addImage(image, &bitmap, pm.cacheKey()); + if (object < 0) + return; + + *d->currentPage << "q\n/GSa gs\n"; + *d->currentPage + << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(), + rectangle.x(), rectangle.y()) * (d->simplePen ? QTransform() : d->stroker.matrix)); + if (bitmap) { + // set current pen as d->brush + d->brush = d->pen.brush(); + } + setBrush(); + d->currentPage->streamImage(image.width(), image.height(), object); + *d->currentPage << "Q\n"; + + d->brush = b; +} + +void QPdfBaseEngine::drawImage(const QRectF &rectangle, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags) +{ + if (sr.isEmpty() || rectangle.isEmpty() || image.isNull()) + return; + Q_D(QPdfBaseEngine); + + QRect sourceRect = sr.toRect(); + QImage im = sourceRect != image.rect() ? image.copy(sourceRect) : image; + bool bitmap = true; + const int object = d->addImage(im, &bitmap, im.cacheKey()); + if (object < 0) + return; + + *d->currentPage << "q\n/GSa gs\n"; + *d->currentPage + << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(), + rectangle.x(), rectangle.y()) * (d->simplePen ? QTransform() : d->stroker.matrix)); + setBrush(); + d->currentPage->streamImage(im.width(), im.height(), object); + *d->currentPage << "Q\n"; +} + +void QPdfBaseEngine::drawTiledPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QPointF &point) +{ + Q_D(QPdfBaseEngine); + + bool bitmap = (pixmap.depth() == 1); + QBrush b = d->brush; + QPointF bo = d->brushOrigin; + bool hp = d->hasPen; + d->hasPen = false; + bool hb = d->hasBrush; + d->hasBrush = true; + + d->brush = QBrush(pixmap); + if (bitmap) + // #### fix bitmap case where we have a brush pen + d->brush.setColor(d->pen.color()); + + d->brushOrigin = -point; + *d->currentPage << "q\n"; + setBrush(); + + drawRects(&rectangle, 1); + *d->currentPage << "Q\n"; + + d->hasPen = hp; + d->hasBrush = hb; + d->brush = b; + d->brushOrigin = bo; +} + void QPdfBaseEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) { Q_D(QPdfBaseEngine); @@ -1337,9 +1415,48 @@ void QPdfBaseEngine::setPen() *d->currentPage << QPdf::generateDashes(d->pen) << " 0 d\n"; } + +void QPdfBaseEngine::setBrush() +{ + Q_D(QPdfBaseEngine); + Qt::BrushStyle style = d->brush.style(); + if (style == Qt::NoBrush) + return; + + bool specifyColor; + int gStateObject = 0; + int patternObject = d->addBrushPattern(d->stroker.matrix, &specifyColor, &gStateObject); + + *d->currentPage << (patternObject ? "/PCSp cs " : "/CSp cs "); + if (specifyColor) { + QColor rgba = d->brush.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(); + } + } + if (patternObject) + *d->currentPage << "/Pat" << patternObject; + *d->currentPage << "scn\n"; + + if (gStateObject) + *d->currentPage << "/GState" << gStateObject << "gs\n"; + else + *d->currentPage << "/GSa gs\n"; +} + + bool QPdfBaseEngine::newPage() { Q_D(QPdfBaseEngine); + if (!isActive()) + return false; + d->newPage(); + setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath); QFile *outfile = qobject_cast<QFile*> (d->outDevice); if (outfile && outfile->error() != QFile::NoError) @@ -1347,6 +1464,12 @@ bool QPdfBaseEngine::newPage() return true; } +QPaintEngine::Type QPdfBaseEngine::type() const +{ + return QPaintEngine::Pdf; +} + + int QPdfBaseEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const { @@ -1387,210 +1510,6 @@ int QPdfBaseEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const return val; } -void QPdfBaseEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) -{ - Q_D(QPdfBaseEngine); - switch (int(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<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).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 (int(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<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 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), @@ -1612,6 +1531,13 @@ QPdfBaseEnginePrivate::QPdfBaseEnginePrivate(QPrinter::PrinterMode m) currentObject = 1; currentPage = 0; stroker.stream = 0; + + streampos = 0; + + stream = new QDataStream; + pageOrder = QPrinter::FirstPageFirst; + orientation = QPrinter::Portrait; + fullPage = false; } bool QPdfBaseEngine::begin(QPaintDevice *pdev) @@ -1626,289 +1552,1021 @@ bool QPdfBaseEngine::begin(QPaintDevice *pdev) d->stroker.stream = d->currentPage; d->opacity = 1.0; - return d->openPrintDevice(); + d->stream->setDevice(d->outDevice); + + d->streampos = 0; + d->hasPen = true; + d->hasBrush = false; + d->clipEnabled = false; + d->allClipped = false; + + d->xrefPositions.clear(); + d->pageRoot = 0; + d->catalog = 0; + d->info = 0; + d->graphicsState = 0; + d->patternColorSpace = 0; + + d->pages.clear(); + d->imageCache.clear(); + + setActive(true); + d->writeHeader(); + newPage(); + + return true; } bool QPdfBaseEngine::end() { Q_D(QPdfBaseEngine); + d->writeTail(); + + d->stream->unsetDevice(); + qDeleteAll(d->fonts); d->fonts.clear(); delete d->currentPage; d->currentPage = 0; - d->closePrintDevice(); + setActive(false); return true; } -#ifndef QT_NO_LPR -static void closeAllOpenFds() +QPdfBaseEnginePrivate::~QPdfBaseEnginePrivate() { - // 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); + qDeleteAll(fonts); + delete currentPage; + delete stream; } -#endif -bool QPdfBaseEnginePrivate::openPrintDevice() +QRect QPdfBaseEnginePrivate::paperRect() const { - if(outDevice) - return false; - - if (!outputFileName.isEmpty()) { - QFile *file = new QFile(outputFileName); - if (! file->open(QFile::WriteOnly|QFile::Truncate)) { - delete file; - return false; + int w; + int h; + if (paperSize == QPrinter::Custom) { + w = qRound(customPaperSize.width()*resolution/72.); + h = qRound(customPaperSize.height()*resolution/72.); + } else { + if (!cupsPaperRect.isNull()) { + QRect r = cupsPaperRect; + w = r.width(); + h = r.height(); + } else{ + QPdf::PaperSize s = QPdf::paperSize(paperSize); + w = s.width; + h = s.height; } - 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; + 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 (!hasCustomPageMargins && !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); } - 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 (qt_safe_pipe(fds) != 0) { - qWarning("QPdfPrinter: Could not open pipe to print"); - return false; + 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); + } - 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); + 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; +} - 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(); - } +void QPdfBaseEnginePrivate::writeHeader() +{ + addXrefEntry(0,false); + + xprintf("%%PDF-1.4\n"); + + writeInfo(); + + catalog = addXrefEntry(-1); + pageRoot = requestObject(); + xprintf("<<\n" + "/Type /Catalog\n" + "/Pages %d 0 R\n" + ">>\n" + "endobj\n", pageRoot); + + // graphics state + graphicsState = addXrefEntry(-1); + xprintf("<<\n" + "/Type /ExtGState\n" + "/SA true\n" + "/SM 0.02\n" + "/ca 1.0\n" + "/CA 1.0\n" + "/AIS false\n" + "/SMask /None" + ">>\n" + "endobj\n"); + + // color space for pattern + patternColorSpace = addXrefEntry(-1); + xprintf("[/Pattern /DeviceRGB]\n" + "endobj\n"); +} + +void QPdfBaseEnginePrivate::writeInfo() +{ + info = addXrefEntry(-1); + xprintf("<<\n/Title "); + printString(title); + xprintf("\n/Creator "); + printString(creator); + xprintf("\n/Producer "); + printString(QString::fromLatin1("Qt " QT_VERSION_STR " (C) 2011 Nokia Corporation and/or its subsidiary(-ies)")); + QDateTime now = QDateTime::currentDateTime().toUTC(); + QTime t = now.time(); + QDate d = now.date(); + xprintf("\n/CreationDate (D:%d%02d%02d%02d%02d%02d)\n", + d.year(), + d.month(), + d.day(), + t.hour(), + t.minute(), + t.second()); + xprintf(">>\n" + "endobj\n"); +} + +void QPdfBaseEnginePrivate::writePageRoot() +{ + addXrefEntry(pageRoot); + + xprintf("<<\n" + "/Type /Pages\n" + "/Kids \n" + "[\n"); + int size = pages.size(); + for (int i = 0; i < size; ++i) + xprintf("%d 0 R\n", pages[i]); + xprintf("]\n"); + + //xprintf("/Group <</S /Transparency /I true /K false>>\n"); + xprintf("/Count %d\n", pages.size()); + + xprintf("/ProcSet [/PDF /Text /ImageB /ImageC]\n" + ">>\n" + "endobj\n"); +} + + +void QPdfBaseEnginePrivate::embedFont(QFontSubset *font) +{ + //qDebug() << "embedFont" << font->object_id; + int fontObject = font->object_id; + QByteArray fontData = font->toTruetype(); +#ifdef FONT_DUMP + static int i = 0; + QString fileName("font%1.ttf"); + fileName = fileName.arg(i++); + QFile ff(fileName); + ff.open(QFile::WriteOnly); + ff.write(fontData); + ff.close(); #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); + + int fontDescriptor = requestObject(); + int fontstream = requestObject(); + int cidfont = requestObject(); + int toUnicode = requestObject(); + + QFontEngine::Properties properties = font->fontEngine->properties(); + + { + qreal scale = 1000/properties.emSquare.toReal(); + addXrefEntry(fontDescriptor); + QByteArray descriptor; + QPdf::ByteStream s(&descriptor); + s << "<< /Type /FontDescriptor\n" + "/FontName /Q"; + int tag = fontDescriptor; + for (int i = 0; i < 5; ++i) { + s << (char)('A' + (tag % 26)); + tag /= 26; } - // parent process - QT_CLOSE(fds[0]); - fd = fds[1]; - (void)qt_safe_waitpid(pid, 0, 0); + s << '+' << properties.postscriptName << "\n" + "/Flags " << 4 << "\n" + "/FontBBox [" + << properties.boundingBox.x()*scale + << -(properties.boundingBox.y() + properties.boundingBox.height())*scale + << (properties.boundingBox.x() + properties.boundingBox.width())*scale + << -properties.boundingBox.y()*scale << "]\n" + "/ItalicAngle " << properties.italicAngle.toReal() << "\n" + "/Ascent " << properties.ascent.toReal()*scale << "\n" + "/Descent " << -properties.descent.toReal()*scale << "\n" + "/CapHeight " << properties.capHeight.toReal()*scale << "\n" + "/StemV " << properties.lineWidth.toReal()*scale << "\n" + "/FontFile2 " << fontstream << "0 R\n" + ">> endobj\n"; + write(descriptor); + } + { + addXrefEntry(fontstream); + QByteArray header; + QPdf::ByteStream s(&header); + + int length_object = requestObject(); + s << "<<\n" + "/Length1 " << fontData.size() << "\n" + "/Length " << length_object << "0 R\n"; + if (do_compress) + s << "/Filter /FlateDecode\n"; + s << ">>\n" + "stream\n"; + write(header); + int len = writeCompressed(fontData); + write("endstream\n" + "endobj\n"); + addXrefEntry(length_object); + xprintf("%d\n" + "endobj\n", len); + } + { + addXrefEntry(cidfont); + QByteArray cid; + QPdf::ByteStream s(&cid); + s << "<< /Type /Font\n" + "/Subtype /CIDFontType2\n" + "/BaseFont /" << properties.postscriptName << "\n" + "/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>\n" + "/FontDescriptor " << fontDescriptor << "0 R\n" + "/CIDToGIDMap /Identity\n" + << font->widthArray() << + ">>\n" + "endobj\n"; + write(cid); + } + { + addXrefEntry(toUnicode); + QByteArray touc = font->createToUnicodeMap(); + xprintf("<< /Length %d >>\n" + "stream\n", touc.length()); + write(touc); + write("endstream\n" + "endobj\n"); + } + { + addXrefEntry(fontObject); + QByteArray font; + QPdf::ByteStream s(&font); + s << "<< /Type /Font\n" + "/Subtype /Type0\n" + "/BaseFont /" << properties.postscriptName << "\n" + "/Encoding /Identity-H\n" + "/DescendantFonts [" << cidfont << "0 R]\n" + "/ToUnicode " << toUnicode << "0 R" + ">>\n" + "endobj\n"; + write(font); + } +} - if (fd < 0) - return false; - outDevice = new QFile(); - static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly); -#endif +void QPdfBaseEnginePrivate::writeFonts() +{ + for (QHash<QFontEngine::FaceId, QFontSubset *>::iterator it = fonts.begin(); it != fonts.end(); ++it) { + embedFont(*it); + delete *it; } + fonts.clear(); +} - return true; +void QPdfBaseEnginePrivate::writePage() +{ + if (pages.empty()) + return; + + *currentPage << "Q Q\n"; + + uint pageStream = requestObject(); + uint pageStreamLength = requestObject(); + uint resources = requestObject(); + uint annots = requestObject(); + + addXrefEntry(pages.last()); + xprintf("<<\n" + "/Type /Page\n" + "/Parent %d 0 R\n" + "/Contents %d 0 R\n" + "/Resources %d 0 R\n" + "/Annots %d 0 R\n" + "/MediaBox [0 0 %d %d]\n" + ">>\n" + "endobj\n", + pageRoot, pageStream, resources, annots, + // make sure we use the pagesize from when we started the page, since the user may have changed it + currentPage->pageSize.width(), currentPage->pageSize.height()); + + addXrefEntry(resources); + xprintf("<<\n" + "/ColorSpace <<\n" + "/PCSp %d 0 R\n" + "/CSp /DeviceRGB\n" + "/CSpg /DeviceGray\n" + ">>\n" + "/ExtGState <<\n" + "/GSa %d 0 R\n", + patternColorSpace, graphicsState); + + for (int i = 0; i < currentPage->graphicStates.size(); ++i) + xprintf("/GState%d %d 0 R\n", currentPage->graphicStates.at(i), currentPage->graphicStates.at(i)); + xprintf(">>\n"); + + xprintf("/Pattern <<\n"); + for (int i = 0; i < currentPage->patterns.size(); ++i) + xprintf("/Pat%d %d 0 R\n", currentPage->patterns.at(i), currentPage->patterns.at(i)); + xprintf(">>\n"); + + xprintf("/Font <<\n"); + for (int i = 0; i < currentPage->fonts.size();++i) + xprintf("/F%d %d 0 R\n", currentPage->fonts[i], currentPage->fonts[i]); + xprintf(">>\n"); + + xprintf("/XObject <<\n"); + for (int i = 0; i<currentPage->images.size(); ++i) { + xprintf("/Im%d %d 0 R\n", currentPage->images.at(i), currentPage->images.at(i)); + } + xprintf(">>\n"); + + xprintf(">>\n" + "endobj\n"); + + addXrefEntry(annots); + xprintf("[ "); + for (int i = 0; i<currentPage->annotations.size(); ++i) { + xprintf("%d 0 R ", currentPage->annotations.at(i)); + } + xprintf("]\nendobj\n"); + + addXrefEntry(pageStream); + xprintf("<<\n" + "/Length %d 0 R\n", pageStreamLength); // object number for stream length object + if (do_compress) + xprintf("/Filter /FlateDecode\n"); + + xprintf(">>\n"); + xprintf("stream\n"); + QIODevice *content = currentPage->stream(); + int len = writeCompressed(content); + xprintf("endstream\n" + "endobj\n"); + + addXrefEntry(pageStreamLength); + xprintf("%d\nendobj\n",len); +} + +void QPdfBaseEnginePrivate::writeTail() +{ + writePage(); + writeFonts(); + writePageRoot(); + addXrefEntry(xrefPositions.size(),false); + xprintf("xref\n" + "0 %d\n" + "%010d 65535 f \n", xrefPositions.size()-1, xrefPositions[0]); + + for (int i = 1; i < xrefPositions.size()-1; ++i) + xprintf("%010d 00000 n \n", xrefPositions[i]); + + xprintf("trailer\n" + "<<\n" + "/Size %d\n" + "/Info %d 0 R\n" + "/Root %d 0 R\n" + ">>\n" + "startxref\n%d\n" + "%%%%EOF\n", + xrefPositions.size()-1, info, catalog, xrefPositions.last()); +} + +int QPdfBaseEnginePrivate::addXrefEntry(int object, bool printostr) +{ + if (object < 0) + object = requestObject(); + + if (object>=xrefPositions.size()) + xrefPositions.resize(object+1); + + xrefPositions[object] = streampos; + if (printostr) + xprintf("%d 0 obj\n",object); + + return object; } -void QPdfBaseEnginePrivate::closePrintDevice() +void QPdfBaseEnginePrivate::printString(const QString &string) { + // The 'text string' type in PDF is encoded either as PDFDocEncoding, or + // Unicode UTF-16 with a Unicode byte order mark as the first character + // (0xfeff), with the high-order byte first. + QByteArray array("(\xfe\xff"); + const ushort *utf16 = string.utf16(); + + for (int i=0; i < string.size(); ++i) { + char part[2] = {char((*(utf16 + i)) >> 8), char((*(utf16 + i)) & 0xff)}; + for(int j=0; j < 2; ++j) { + if (part[j] == '(' || part[j] == ')' || part[j] == '\\') + array.append('\\'); + array.append(part[j]); + } + } + array.append(")"); + write(array); +} + + +// For strings up to 10000 bytes only ! +void QPdfBaseEnginePrivate::xprintf(const char* fmt, ...) { - if (!outDevice) + if (!stream) 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; + + const int msize = 10000; + char buf[msize]; + + va_list args; + va_start(args, fmt); + int bufsize = qvsnprintf(buf, msize, fmt, args); + + Q_ASSERT(bufsize<msize); + + va_end(args); + + stream->writeRawData(buf, bufsize); + streampos += bufsize; +} + +int QPdfBaseEnginePrivate::writeCompressed(QIODevice *dev) +{ +#ifndef QT_NO_COMPRESS + if (do_compress) { + int size = QPdfPage::chunkSize(); + int sum = 0; + ::z_stream zStruct; + zStruct.zalloc = Z_NULL; + zStruct.zfree = Z_NULL; + zStruct.opaque = Z_NULL; + if (::deflateInit(&zStruct, Z_DEFAULT_COMPRESSION) != Z_OK) { + qWarning("QPdfStream::writeCompressed: Error in deflateInit()"); + return sum; + } + zStruct.avail_in = 0; + QByteArray in, out; + out.resize(size); + while (!dev->atEnd() || zStruct.avail_in != 0) { + if (zStruct.avail_in == 0) { + in = dev->read(size); + zStruct.avail_in = in.size(); + zStruct.next_in = reinterpret_cast<unsigned char*>(in.data()); + if (in.size() <= 0) { + qWarning("QPdfStream::writeCompressed: Error in read()"); + ::deflateEnd(&zStruct); + return sum; + } } - prnName = def.printerName().toLocal8Bit(); + zStruct.next_out = reinterpret_cast<unsigned char*>(out.data()); + zStruct.avail_out = out.size(); + if (::deflate(&zStruct, 0) != Z_OK) { + qWarning("QPdfStream::writeCompressed: Error in deflate()"); + ::deflateEnd(&zStruct); + return sum; + } + int written = out.size() - zStruct.avail_out; + stream->writeRawData(out.constData(), written); + streampos += written; + sum += written; } + int ret; + do { + zStruct.next_out = reinterpret_cast<unsigned char*>(out.data()); + zStruct.avail_out = out.size(); + ret = ::deflate(&zStruct, Z_FINISH); + if (ret != Z_OK && ret != Z_STREAM_END) { + qWarning("QPdfStream::writeCompressed: Error in deflate()"); + ::deflateEnd(&zStruct); + return sum; + } + int written = out.size() - zStruct.avail_out; + stream->writeRawData(out.constData(), written); + streampos += written; + sum += written; + } while (ret == Z_OK); - if (!cupsStringPageSize.isEmpty()) { - options.append(QPair<QByteArray, QByteArray>("media", cupsStringPageSize.toLocal8Bit())); - } + ::deflateEnd(&zStruct); - if (copies > 1) { - options.append(QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit())); + return sum; + } else +#endif + { + QByteArray arr; + int sum = 0; + while (!dev->atEnd()) { + arr = dev->read(QPdfPage::chunkSize()); + stream->writeRawData(arr.constData(), arr.size()); + streampos += arr.size(); + sum += arr.size(); } + return sum; + } +} - if (collate) { - options.append(QPair<QByteArray, QByteArray>("Collate", "True")); +int QPdfBaseEnginePrivate::writeCompressed(const char *src, int len) +{ +#ifndef QT_NO_COMPRESS + if(do_compress) { + uLongf destLen = len + len/100 + 13; // zlib requirement + Bytef* dest = new Bytef[destLen]; + if (Z_OK == ::compress(dest, &destLen, (const Bytef*) src, (uLongf)len)) { + stream->writeRawData((const char*)dest, destLen); + } else { + qWarning("QPdfStream::writeCompressed: Error in compress()"); + destLen = 0; } + delete [] dest; + len = destLen; + } else +#endif + { + stream->writeRawData(src,len); + } + streampos += len; + return len; +} - 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")); +int QPdfBaseEnginePrivate::writeImage(const QByteArray &data, int width, int height, int depth, + int maskObject, int softMaskObject, bool dct) +{ + int image = addXrefEntry(-1); + xprintf("<<\n" + "/Type /XObject\n" + "/Subtype /Image\n" + "/Width %d\n" + "/Height %d\n", width, height); + + if (depth == 1) { + xprintf("/ImageMask true\n" + "/Decode [1 0]\n"); + } else { + xprintf("/BitsPerComponent 8\n" + "/ColorSpace %s\n", (depth == 32) ? "/DeviceRGB" : "/DeviceGray"); + } + if (maskObject > 0) + xprintf("/Mask %d 0 R\n", maskObject); + if (softMaskObject > 0) + xprintf("/SMask %d 0 R\n", softMaskObject); + + int lenobj = requestObject(); + xprintf("/Length %d 0 R\n", lenobj); + if (interpolateImages) + xprintf("/Interpolate true\n"); + int len = 0; + if (dct) { + //qDebug() << "DCT"; + xprintf("/Filter /DCTDecode\n>>\nstream\n"); + write(data); + len = data.length(); + } else { + if (do_compress) + xprintf("/Filter /FlateDecode\n>>\nstream\n"); + else + xprintf(">>\nstream\n"); + len = writeCompressed(data); + } + xprintf("endstream\n" + "endobj\n"); + addXrefEntry(lenobj); + xprintf("%d\n" + "endobj\n", len); + return image; +} + +#ifdef USE_NATIVE_GRADIENTS +int QPdfBaseEnginePrivate::gradientBrush(const QBrush &b, const QMatrix &matrix, int *gStateObject) +{ + const QGradient *gradient = b.gradient(); + if (!gradient) + return 0; + + QTransform inv = matrix.inverted(); + QPointF page_rect[4] = { inv.map(QPointF(0, 0)), + inv.map(QPointF(width_, 0)), + inv.map(QPointF(0, height_)), + inv.map(QPointF(width_, height_)) }; + + bool opaque = b.isOpaque(); + + QByteArray shader; + QByteArray alphaShader; + if (gradient->type() == QGradient::LinearGradient) { + const QLinearGradient *lg = static_cast<const QLinearGradient *>(gradient); + shader = QPdf::generateLinearGradientShader(lg, page_rect); + if (!opaque) + alphaShader = QPdf::generateLinearGradientShader(lg, page_rect, true); + } else { + // ############# + return 0; + } + int shaderObject = addXrefEntry(-1); + write(shader); + + QByteArray str; + QPdf::ByteStream s(&str); + s << "<<\n" + "/Type /Pattern\n" + "/PatternType 2\n" + "/Shading " << shaderObject << "0 R\n" + "/Matrix [" + << matrix.m11() + << matrix.m12() + << matrix.m21() + << matrix.m22() + << matrix.dx() + << matrix.dy() << "]\n"; + s << ">>\n" + "endobj\n"; + + int patternObj = addXrefEntry(-1); + write(str); + currentPage->patterns.append(patternObj); + + if (!opaque) { + bool ca = true; + QGradientStops stops = gradient->stops(); + int a = stops.at(0).second.alpha(); + for (int i = 1; i < stops.size(); ++i) { + if (stops.at(i).second.alpha() != a) { + ca = false; break; } } - - if (QCUPSSupport::cupsVersion() >= 10300 && orientation == QPrinter::Landscape) { - options.append(QPair<QByteArray, QByteArray>("landscape", "")); + if (ca) { + *gStateObject = addConstantAlphaObject(stops.at(0).second.alpha()); + } else { + int alphaShaderObject = addXrefEntry(-1); + write(alphaShader); + + QByteArray content; + QPdf::ByteStream c(&content); + c << "/Shader" << alphaShaderObject << "sh\n"; + + QByteArray form; + QPdf::ByteStream f(&form); + f << "<<\n" + "/Type /XObject\n" + "/Subtype /Form\n" + "/BBox [0 0 " << width_ << height_ << "]\n" + "/Group <</S /Transparency >>\n" + "/Resources <<\n" + "/Shading << /Shader" << alphaShaderObject << alphaShaderObject << "0 R >>\n" + ">>\n"; + + f << "/Length " << content.length() << "\n" + ">>\n" + "stream\n" + << content + << "endstream\n" + "endobj\n"; + + int softMaskFormObject = addXrefEntry(-1); + write(form); + *gStateObject = addXrefEntry(-1); + xprintf("<< /SMask << /S /Alpha /G %d 0 R >> >>\n" + "endobj\n", softMaskFormObject); + currentPage->graphicStates.append(*gStateObject); } + } - QStringList::const_iterator it = cupsOptions.constBegin(); - while (it != cupsOptions.constEnd()) { - options.append(QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit())); - it += 2; - } + return patternObj; +} +#endif - 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); - } +int QPdfBaseEnginePrivate::addConstantAlphaObject(int brushAlpha, int penAlpha) +{ + if (brushAlpha == 255 && penAlpha == 255) + return 0; + int object = alphaCache.value(QPair<uint, uint>(brushAlpha, penAlpha), 0); + if (!object) { + object = addXrefEntry(-1); + QByteArray alphaDef; + QPdf::ByteStream s(&alphaDef); + s << "<<\n/ca " << (brushAlpha/qreal(255.)) << '\n'; + s << "/CA " << (penAlpha/qreal(255.)) << "\n>>"; + xprintf("%s\nendobj\n", alphaDef.constData()); + alphaCache.insert(QPair<uint, uint>(brushAlpha, penAlpha), object); + } + if (currentPage->graphicStates.indexOf(object) < 0) + currentPage->graphicStates.append(object); + + return object; +} - // 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); +int QPdfBaseEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor, int *gStateObject) +{ + int paintType = 2; // Uncolored tiling + int w = 8; + int h = 8; - QFile::remove(tempFile); - } + *specifyColor = true; + *gStateObject = 0; + + QTransform matrix = m; + matrix.translate(brushOrigin.x(), brushOrigin.y()); + matrix = matrix * pageMatrix(); + //qDebug() << brushOrigin << matrix; + + Qt::BrushStyle style = brush.style(); + if (style == Qt::LinearGradientPattern) {// && style <= Qt::ConicalGradientPattern) { +#ifdef USE_NATIVE_GRADIENTS + *specifyColor = false; + return gradientBrush(b, matrix, gStateObject); +#else + return 0; #endif + } + + if ((!brush.isOpaque() && brush.style() < Qt::LinearGradientPattern) || opacity != 1.0) + *gStateObject = addConstantAlphaObject(qRound(brush.color().alpha() * opacity), + qRound(pen.color().alpha() * opacity)); + + int imageObject = -1; + QByteArray pattern = QPdf::patternForBrush(brush); + if (pattern.isEmpty()) { + if (brush.style() != Qt::TexturePattern) + return 0; + QImage image = brush.texture().toImage(); + bool bitmap = true; + imageObject = addImage(image, &bitmap, brush.texture().cacheKey()); + if (imageObject != -1) { + QImage::Format f = image.format(); + if (f != QImage::Format_MonoLSB && f != QImage::Format_Mono) { + paintType = 1; // Colored tiling + *specifyColor = false; + } + w = image.width(); + h = image.height(); + QTransform m(w, 0, 0, -h, 0, h); + QPdf::ByteStream s(&pattern); + s << QPdf::generateMatrix(m); + s << "/Im" << imageObject << " Do\n"; + } + } + + QByteArray str; + QPdf::ByteStream s(&str); + s << "<<\n" + "/Type /Pattern\n" + "/PatternType 1\n" + "/PaintType " << paintType << "\n" + "/TilingType 1\n" + "/BBox [0 0 " << w << h << "]\n" + "/XStep " << w << "\n" + "/YStep " << h << "\n" + "/Matrix [" + << matrix.m11() + << matrix.m12() + << matrix.m21() + << matrix.m22() + << matrix.dx() + << matrix.dy() << "]\n" + "/Resources \n<< "; // open resource tree + if (imageObject > 0) { + s << "/XObject << /Im" << imageObject << ' ' << imageObject << "0 R >> "; + } + s << ">>\n" + "/Length " << pattern.length() << "\n" + ">>\n" + "stream\n" + << pattern + << "endstream\n" + "endobj\n"; + + int patternObj = addXrefEntry(-1); + write(str); + currentPage->patterns.append(patternObj); + return patternObj; } -QPdfBaseEnginePrivate::~QPdfBaseEnginePrivate() +/*! + * Adds an image to the pdf and return the pdf-object id. Returns -1 if adding the image failed. + */ +int QPdfBaseEnginePrivate::addImage(const QImage &img, bool *bitmap, qint64 serial_no) { - qDeleteAll(fonts); - delete currentPage; + if (img.isNull()) + return -1; + + int object = imageCache.value(serial_no); + if(object) + return object; + + QImage image = img; + QImage::Format format = image.format(); + if (image.depth() == 1 && *bitmap && img.colorTable().size() == 2 + && img.colorTable().at(0) == QColor(Qt::black).rgba() + && img.colorTable().at(1) == QColor(Qt::white).rgba()) + { + if (format == QImage::Format_MonoLSB) + image = image.convertToFormat(QImage::Format_Mono); + format = QImage::Format_Mono; + } else { + *bitmap = false; + if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + format = QImage::Format_ARGB32; + } + } + + int w = image.width(); + int h = image.height(); + int d = image.depth(); + + if (format == QImage::Format_Mono) { + int bytesPerLine = (w + 7) >> 3; + QByteArray data; + data.resize(bytesPerLine * h); + char *rawdata = data.data(); + for (int y = 0; y < h; ++y) { + memcpy(rawdata, image.scanLine(y), bytesPerLine); + rawdata += bytesPerLine; + } + object = writeImage(data, w, h, d, 0, 0); + } else { + QByteArray softMaskData; + bool dct = false; + QByteArray imageData; + bool hasAlpha = false; + bool hasMask = false; + + if (QImageWriter::supportedImageFormats().contains("jpeg") && colorMode != QPrinter::GrayScale) { + QBuffer buffer(&imageData); + QImageWriter writer(&buffer, "jpeg"); + writer.setQuality(94); + writer.write(image); + dct = true; + + if (format != QImage::Format_RGB32) { + softMaskData.resize(w * h); + uchar *sdata = (uchar *)softMaskData.data(); + for (int y = 0; y < h; ++y) { + const QRgb *rgb = (const QRgb *)image.scanLine(y); + for (int x = 0; x < w; ++x) { + uchar alpha = qAlpha(*rgb); + *sdata++ = alpha; + hasMask |= (alpha < 255); + hasAlpha |= (alpha != 0 && alpha != 255); + ++rgb; + } + } + } + } else { + imageData.resize(colorMode == QPrinter::GrayScale ? w * h : 3 * w * h); + uchar *data = (uchar *)imageData.data(); + softMaskData.resize(w * h); + uchar *sdata = (uchar *)softMaskData.data(); + for (int y = 0; y < h; ++y) { + const QRgb *rgb = (const QRgb *)image.scanLine(y); + if (colorMode == QPrinter::GrayScale) { + for (int x = 0; x < w; ++x) { + *(data++) = qGray(*rgb); + uchar alpha = qAlpha(*rgb); + *sdata++ = alpha; + hasMask |= (alpha < 255); + hasAlpha |= (alpha != 0 && alpha != 255); + ++rgb; + } + } else { + for (int x = 0; x < w; ++x) { + *(data++) = qRed(*rgb); + *(data++) = qGreen(*rgb); + *(data++) = qBlue(*rgb); + uchar alpha = qAlpha(*rgb); + *sdata++ = alpha; + hasMask |= (alpha < 255); + hasAlpha |= (alpha != 0 && alpha != 255); + ++rgb; + } + } + } + if (format == QImage::Format_RGB32) + hasAlpha = hasMask = false; + } + int maskObject = 0; + int softMaskObject = 0; + if (hasAlpha) { + softMaskObject = writeImage(softMaskData, w, h, 8, 0, 0); + } else if (hasMask) { + // dither the soft mask to 1bit and add it. This also helps PDF viewers + // without transparency support + int bytesPerLine = (w + 7) >> 3; + QByteArray mask(bytesPerLine * h, 0); + uchar *mdata = (uchar *)mask.data(); + const uchar *sdata = (const uchar *)softMaskData.constData(); + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + if (*sdata) + mdata[x>>3] |= (0x80 >> (x&7)); + ++sdata; + } + mdata += bytesPerLine; + } + maskObject = writeImage(mask, w, h, 1, 0, 0); + } + object = writeImage(imageData, w, h, colorMode == QPrinter::GrayScale ? 8 : 32, + maskObject, softMaskObject, dct); + } + imageCache.insert(serial_no, object); + return object; } void QPdfBaseEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) { Q_Q(QPdfBaseEngine); + if (ti.charFormat.isAnchor()) { + 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.tmHeight; + } +#endif + int synthesized = ti.fontEngine->synthesized(); + qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.; + + QTransform trans; + // Build text rendering matrix (Trm). We need it to map the text area to user + // space units on the PDF page. + trans = QTransform(size*stretch, 0, 0, size, 0, 0); + // Apply text matrix (Tm). + trans *= QTransform(1,0,0,-1,p.x(),p.y()); + // Apply page displacement (Identity for first page). + trans *= stroker.matrix; + // Apply Current Transformation Matrix (CTM) + trans *= pageMatrix(); + qreal x1, y1, x2, y2; + trans.map(0, 0, &x1, &y1); + trans.map(ti.width.toReal()/size, (ti.ascent.toReal()-ti.descent.toReal())/size, &x2, &y2); + + uint annot = addXrefEntry(-1); +#ifdef Q_DEBUG_PDF_LINKS + xprintf("<<\n/Type /Annot\n/Subtype /Link\n/Rect [%f %f %f %f]\n/Border [16 16 1]\n/A <<\n", +#else + xprintf("<<\n/Type /Annot\n/Subtype /Link\n/Rect [%f %f %f %f]\n/Border [0 0 0]\n/A <<\n", +#endif + static_cast<double>(x1), + static_cast<double>(y1), + static_cast<double>(x2), + static_cast<double>(y2)); + xprintf("/Type /Action\n/S /URI\n/URI (%s)\n", + ti.charFormat.anchorHref().toLatin1().constData()); + xprintf(">>\n>>\n"); + xprintf("endobj\n"); + + if (!currentPage->annotations.contains(annot)) { + currentPage->annotations.append(annot); + } + } + QFontEngine *fe = ti.fontEngine; QFontEngine::FaceId face_id = fe->faceId(); @@ -2024,83 +2682,33 @@ void QPdfBaseEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &t *currentPage << "ET\n"; } -QRect QPdfBaseEnginePrivate::paperRect() const +QTransform QPdfBaseEnginePrivate::pageMatrix() 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.); + qreal scale = 72./resolution; + QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, height()); + if (!fullPage) { + QRect r = pageRect(); + tmp.translate(r.left(), r.top()); } - if (orientation == QPrinter::Portrait) - return QRect(0, 0, w, h); - else - return QRect(0, 0, h, w); + return tmp; } -QRect QPdfBaseEnginePrivate::pageRect() const +void QPdfBaseEnginePrivate::newPage() { - if(fullPage) - return paperRect(); - - QRect r; + if (currentPage && currentPage->pageSize.isEmpty()) + currentPage->pageSize = QSize(width(), height()); + writePage(); -#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; + delete currentPage; + currentPage = new QPdfPage; + currentPage->pageSize = QSize(width(), height()); + stroker.stream = currentPage; + pages.append(requestObject()); + + *currentPage << "/GSa gs /CSp cs /CSp CS\n" + << QPdf::generateMatrix(pageMatrix()) + << "q q\n"; } -#endif QT_END_NAMESPACE |