/****************************************************************************
**
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://www.trolltech.com/products/qt/opensource.html
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://www.trolltech.com/products/qt/licensing.html or contact the
** sales department at sales@trolltech.com.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include "qprintengine_pdf_p.h"

#ifndef QT_NO_PRINTER
#include <qiodevice.h>
#include <qpainter.h>
#include <qbitmap.h>
#include <qpainterpath.h>
#include <qpaintdevice.h>
#include <qfile.h>

#include <time.h>
#include <limits.h>
#include <math.h>
#ifndef QT_NO_COMPRESS
#include <zlib.h>
#endif

// might be helpful for smooth transforms of images
bool QPdfImage::interpolation_ = false;

#ifdef QT_NO_COMPRESS
static const bool do_compress = false;
#else
static const bool do_compress = true;
#endif

static const int resolution = 72;

/* also adds a space at the end of the number */
static const char *qreal_to_string(qreal val, char *buf) {
    const char *ret = buf;
    if (val < 0) {
        *(buf++) = '-';
        val = -val;
    }
    int ival = (int) val;
    qreal frac = val - (qreal)ival;

    int ifrac = (int)(frac * 1000000);
    if (ifrac == 1000000) {
        ++ival;
        ifrac = 0;
    }
    char output[256];
    int i = 0;
    while (ival) {
        output[i] = '0' + (ival % 10);
        ++i;
        ival /= 10;
    }
    if (i == 0) {
        *(buf++) = '0';
    } else {
        while (i)
            *(buf++) = output[--i];
    }

    if (ifrac) {
        *(buf++) =  '.';
        int fact = 100000;
        while (fact) {
            *(buf++) = '0' + ((ifrac/fact) % 10);
            fact /= 10;
        }
    }
    *(buf++) = ' ';
    *buf = 0;
    return ret;
}

static const char *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;
}

class QPdfByteStream
{
public:
    QPdfByteStream(QByteArray *b) :ba(b) {}
    QPdfByteStream &operator <<(char chr) { *ba += chr; return *this; }
    QPdfByteStream &operator <<(const char *str) { *ba += str; return *this; }
    QPdfByteStream &operator <<(const QByteArray &str) { *ba += str; return *this; }
    QPdfByteStream &operator <<(qreal val) { char buf[256]; *ba += qreal_to_string(val, buf); return *this; }
    QPdfByteStream &operator <<(int val) { char buf[256]; *ba += int_to_string(val, buf); return *this; }
private:
    QByteArray *ba;
};

#undef MM
#define MM(n) int((n * 720 + 127) / 254)

#undef IN
#define IN(n) int(n * 72)

struct PaperSize {
    int width, height;
};

static const PaperSize paperSizes[QPrinter::NPageSize] = {
    {  MM(210), MM(297) },      // A4
    {  MM(176), MM(250) },      // B5
    {  IN(8.5), IN(11) },       // Letter
    {  IN(8.5), IN(14) },       // Legal
    {  IN(7.5), IN(10) },       // Executive
    {  MM(841), MM(1189) },     // A0
    {  MM(594), MM(841) },      // A1
    {  MM(420), MM(594) },      // A2
    {  MM(297), MM(420) },      // A3
    {  MM(148), MM(210) },      // A5
    {  MM(105), MM(148) },      // A6
    {  MM(74), MM(105)},        // A7
    {  MM(52), MM(74) },        // A8
    {  MM(37), MM(52) },        // A9
    {  MM(1000), MM(1414) },    // B0
    {  MM(707), MM(1000) },     // B1
    {  MM(31), MM(44) },        // B10
    {  MM(500), MM(707) },      // B2
    {  MM(353), MM(500) },      // B3
    {  MM(250), MM(353) },      // B4
    {  MM(125), MM(176) },      // B6
    {  MM(88), MM(125) },       // B7
    {  MM(62), MM(88) },        // B8
    {  MM(44), MM(62) },        // B9
    {  MM(162),    MM(229) },   // C5E
    {  IN(4.125),  IN(9.5) },   // Comm10E
    {  MM(110),    MM(220) },   // DLE
    {  IN(8.5),    IN(13) },    // Folio
    {  IN(17),     IN(11) },    // Ledger
    {  IN(11),     IN(17) }     // Tabloid
};

inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features()
{
    QPaintEngine::PaintEngineFeatures f = QPaintEngine::AllFeatures;
    f &= ~(QPaintEngine::LinearGradientFill
           | QPaintEngine::RadialGradientFill
           | QPaintEngine::ConicalGradientFill);
    return f;
}

QPdfEngine::QPdfEngine()
    : QPaintEngine(qt_pdf_decide_features()), outFile_(new QFile)
{
    device_ = 0;
    clipping_ = false;
    tofile_ = false;
    backgroundMode = Qt::TransparentMode;
    pixmapnumber_ = 0;

    d = new QPdfEnginePrivate;
    lastBrush_ = new QBrush;
    lastPen_ = new QPen;
    lastMatrix_ = new QMatrix;
    bgBrush_ = new QBrush;
    lastBrushOrig_ = new QPointF;
    lastClipRegion_ = new QRegion;

    pagesize_ = QPrinter::A4;
    QRect r = paperRect();
    d->setDimensions(r.width(),r.height());
}

QPdfEngine::~QPdfEngine()
{
    delete d;

    delete lastBrush_;
    delete lastPen_;
    delete lastMatrix_;
    delete bgBrush_;
    delete lastBrushOrig_;
    delete lastClipRegion_;
    delete outFile_;
}


void QPdfEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value)
{
    switch (key) {
    case PPK_Creator:
        d->creator = value.toString();
        break;
    case PPK_DocumentName:
        d->title = value.toString();
        break;
    case PPK_Orientation:
        d->orientation = QPrinter::Orientation(value.toInt());
        break;
    case PPK_OutputFileName: {
        if (isActive()) {
            qWarning("QPdfEngine::setFileName: Not possible while painting");
            return;
        }
        QString filename = value.toString();

        if (filename.isEmpty())
            return;

        outFile_->setFileName(filename);
        setDevice(outFile_);
    }
        break;
    case PPK_PageSize: {
        pagesize_ = QPrinter::PageSize(value.toInt());
        QRect r = paperRect();
        d->setDimensions(r.width(),r.height());
    }
        break;
    case PPK_FullPage:
        d->fullPage = value.toBool();
        break;
    default:
        break;
    }
}

QVariant QPdfEngine::property(PrintEnginePropertyKey key) const
{
    switch (key) {
    case PPK_ColorMode:
        return QPrinter::Color;
    case PPK_Creator:
        return d->creator;
    case PPK_DocumentName:
        return d->title;
    case PPK_FullPage:
        return d->fullPage;
    case PPK_NumberOfCopies:
        return 1;
    case PPK_Orientation:
        return d->orientation;
    case PPK_OutputFileName:
        return outFile_->fileName();
    case PPK_PageRect:
        return pageRect();
    case PPK_PageSize:
        return pagesize_;
    case PPK_PaperRect:
        return paperRect();
    case PPK_PaperSource:
        return QPrinter::Auto;
    case PPK_Resolution:
        return 600;
    case PPK_SupportedResolutions:
        return QList<QVariant>() << resolution;
    default:
        break;
    }
    return QVariant();
}

void QPdfEngine::setAuthor(const QString &author)
{
    d->author = author;
}

QString QPdfEngine::author() const
{
    return d->author;
}

QRect QPdfEngine::paperRect() const
{
    PaperSize s = paperSizes[pagesize_];
    int w = qRound(s.width);
    int h = qRound(s.height);
    if (d->orientation == QPrinter::Portrait)
        return QRect(0, 0, w, h);
    else
        return QRect(0, 0, h, w);
}

QRect QPdfEngine::pageRect() const
{
    QRect r = paperRect();
    if (d->fullPage)
        return r;
    // would be nice to get better margins than this.
    return QRect(resolution/3, resolution/3, r.width()-2*resolution/3, r.height()-2*resolution/3);
}

void QPdfEngine::setDevice(QIODevice* dev)
{
    if (isActive()) {
        qWarning("QPdfEngine::setDevice: Device cannot be set while painting");
        return;
    }
    device_ = dev;
}

bool QPdfEngine::begin (QPaintDevice *)
{
    if (!device_) {
        qWarning("QPdfEngine::begin: No valid device");
        return false;
    }

    if (device_->isOpen())
        device_->close();
    if(!device_->open(QIODevice::WriteOnly)) {
        qWarning("QPdfEngine::begin: Cannot open IO device");
        return false;
    }

    d->unsetDevice();
    d->setDevice(device_);
    setActive(true);
    d->writeHeader();
    d->newPage();

    return true;
}

bool QPdfEngine::end ()
{
    d->writeTail();

    device_->close();
    d->unsetDevice();
    setActive(false);
    return true;
}

void QPdfEngine::drawPoints (const QPointF *points, int pointCount)
{
    if (!points)
        return;

    QPainterPath p;
    for (int i=0; i!=pointCount;++i) {
        p.moveTo(points[i]);
        p.lineTo(points[i]);
    }
    drawPath(p);
}

void QPdfEngine::drawLines (const QLineF *lines, int lineCount)
{
    if (!lines)
        return;

    QPainterPath p;
    for (int i=0; i!=lineCount;++i) {
        p.moveTo(lines[i].p1());
        p.lineTo(lines[i].p2());
    }
    drawPath(p);
}

void QPdfEngine::drawRects (const QRectF *rects, int rectCount)
{
    if (!rects)
        return;

    QPainterPath p;
    for (int i=0; i!=rectCount; ++i) {
        p.addRect(rects[i]);
    }
    drawPath(p);
}

void QPdfEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode)
{
    if (!points || !pointCount)
        return;

    QPainterPath p;
    QBrush br = *lastBrush_;

    switch(mode) {
    case OddEvenMode:
    case ConvexMode:
        p.setFillRule(Qt::OddEvenFill);
        break;
    case WindingMode:
        p.setFillRule(Qt::WindingFill);
        break;
    case PolylineMode:
        updateBrush(QBrush(Qt::NoBrush),*lastBrushOrig_);
        break;
    default:
        break;
    }

    QPolygonF pg;

    for (int i = 0; i != pointCount; ++i)
        pg.append(points[i]);

    p.addPolygon(pg);
    if (mode != PolylineMode)
        p.closeSubpath();
    drawPath(p);
    if (mode == PolylineMode)
        updateBrush(br,*lastBrushOrig_);
}

void QPdfEngine::drawEllipse (const QRectF & rectangle)
{
    QPainterPath p;
    p.addEllipse(rectangle);
    drawPath(p);
}

void QPdfEngine::drawPath (const QPainterPath &p)
{
    if (backgroundMode == Qt::OpaqueMode && lastBrush_->style() != Qt::NoBrush) {
        QBrush tmp = *lastBrush_;
        updateBrush(*bgBrush_,*lastBrushOrig_); //todo origin for bgBrush ?
        drawPathPrivate(p);
        updateBrush(tmp, *lastBrushOrig_);
    }
    drawPathPrivate(p);
}

void QPdfEngine::drawPathPrivate (const QPainterPath &p)
{
    if (p.isEmpty() && !clipping_)
        return;

    QPdfPath* path = new QPdfPath(d->curPen, d->curBrush,
                                  (p.fillRule() == Qt::WindingFill)
                                  ? QPdfPath::FILLNONZERO : QPdfPath::FILLEVENODD);

    if (clipping_)
        path->painttype |= QPdfPath::CLIPPING;

    QPdfPath::SubPath sb;

    QPointF subPathStart;
    QPainterPath::Element lastelm;

    bool dangling = true;
    for (int i=0; i<p.elementCount(); ++i) {
        const QPainterPath::Element &elm  = p.elementAt(i);
        QPdfPath::Element el;

        dangling = false;
        switch (elm.type) {
        case QPainterPath::CurveToDataElement:
            Q_ASSERT(false);
            break;
        case QPainterPath::MoveToElement:
            dangling = true;
            if (i>0) {
                if (subPathStart == QPointF(lastelm.x,lastelm.y))
                    sb.close();
                path->subpaths.append(sb);
                sb = QPdfPath::SubPath();
            }
            sb.start.x = elm.x;
            sb.start.y = elm.y;
            subPathStart = QPointF(elm.x, elm.y);
            sb.initialized = true;
            break;
        case QPainterPath::LineToElement:
            el.setLine(elm.x, elm.y);
            sb.elements.append(el);
            break;
        case QPainterPath::CurveToElement:
            Q_ASSERT(p.elementAt(i+1).type == QPainterPath::CurveToDataElement);
            Q_ASSERT(p.elementAt(i+2).type == QPainterPath::CurveToDataElement);
            el.setCurve(elm.x, elm.y,
                        p.elementAt(i+1).x, p.elementAt(i+1).y,
                        p.elementAt(i+2).x, p.elementAt(i+2).y);
            sb.elements.append(el);
            i += 2; // Skip the next two
        }
        lastelm  = elm;
    }
    if (subPathStart == QPointF(lastelm.x,lastelm.y))
        sb.close();

    if (!dangling)
        path->subpaths.append(sb);

    d->curPage->append(path);
}

void QPdfEngine::drawPixmap (const QRectF & rectangle, const QPixmap & pixmap, const QRectF & sr)
{
    if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull())
        return;
    QPixmap pm = pixmap.copy(sr.toRect());
    adaptMonochromePixmap(pm);

    QImage image = pm.toImage();
    drawImage(rectangle, image, QRectF(0, 0, image.width(), image.height()));
}

void QPdfEngine::drawImage(const QRectF & rectangle, const QImage & image, const QRectF & sr, Qt::ImageConversionFlags)
{
    if (sr.isEmpty() || rectangle.isEmpty() || image.isNull())
        return;

    QImage im = image.copy(sr.toRect());
    QImage::Format format = im.format();
    if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32)
        im = im.convertToFormat(im.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32);

    QMatrix mat = d->curMatrix->lastMatrix();
    updateMatrix(QMatrix(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(),
                         rectangle.x(), rectangle.y()) * mat);
    QPdfImage* img = new QPdfImage(im);
    img->name = "/Im" + QByteArray::number(pixmapnumber_++);
    d->curPage->append(img);
    updateMatrix(mat);
}

void QPdfEngine::drawTiledPixmap (const QRectF & rectangle, const QPixmap & pixmap, const QPointF & point)
{
    QBrush b = *lastBrush_;
    QPointF pt = *lastBrushOrig_;
    QPen pen = *lastPen_;
    updateBrush(QBrush(pixmap),-point);
    updatePen(QPen(Qt::NoPen));
    drawRects(&rectangle, 1);
    updatePen(pen);
    updateBrush(b,pt);
}

void QPdfEngine::updateState(const QPaintEngineState &state)
{
    QPaintEngine::DirtyFlags flags = state.state();
    if (flags & DirtyTransform)
        updateMatrix(state.matrix());
    if (flags & DirtyPen)
        updatePen(state.pen());
    if (flags & DirtyBrush)
        updateBrush(state.brush(), state.brushOrigin());
    if ((flags & DirtyBackground) || (flags & DirtyBackgroundMode))
        updateBackground(state.backgroundMode(), state.backgroundBrush());
#if 0
    if (flags & DirtyFont)
        updateFont(state.font());
#endif

    if (state.state() & DirtyClipEnabled) {
        if (state.isClipEnabled())
            updateClipPath(painter()->clipPath(), Qt::ReplaceClip);
        else
            updateClipPath(QPainterPath(), Qt::NoClip);
    }

    if (flags & DirtyClipPath)
        updateClipPath(state.clipPath(), state.clipOperation());
    if (flags & DirtyClipRegion)
        updateClipRegion(state.clipRegion(), state.clipOperation());
    //     if (flags & DirtyHints) updateRenderHints(state.renderHints());
}

void QPdfEngine::updateClipRegion (const QRegion & region, Qt::ClipOperation op)
{
    QPainterPath p;
    QRegion r = *lastClipRegion_;
    QMatrix mat = d->curMatrix->lastMatrix();
    switch(op) {
    case Qt::NoClip:
        r = QRegion(paperRect());
        mat = QMatrix();
        break;
    case Qt::ReplaceClip:
  	r = region;
        break;
    case Qt::UniteClip:
        r = r.unite(region);
        break;
    case Qt::IntersectClip:
        r = r.intersect(region);
        break;
    default:
        return;
    }
    *lastClipRegion_ = r;

    //todo p.addRegion(r);
    QVector<QRect> rects = r.rects();
    for (int i=0; i<rects.size(); ++i) p.addRect(rects.at(i));

    QRegion reg(p.toFillPolygon().toPolygon(), p.fillRule());
    p = QPainterPath();
    //todo p.addRegion(reg);
    rects = reg.rects();
    for (int i=0; i<rects.size(); ++i) p.addRect(rects.at(i));

    p = mat.map(p);

    if (p.isEmpty())
        p.addRect(-1,-1,1,1);

    clipping_ = true;
    drawPath(p);
    clipping_ = false;
}


void QPdfEngine::updateClipPath(const QPainterPath &path, Qt::ClipOperation op)
{
    QPainterPath p = path;
    if (p.isEmpty()) // dummy path
        p.addRect(0,0,0,0);
    QRegion reg(p.toFillPolygon().toPolygon(), p.fillRule());
    updateClipRegion(reg,op);
}


/*
  void QPdfEngine::updateClipPath(const QPainterPath &path, Qt::ClipOperation op)
  {
  QMatrix mat = d->curMatrix->lastMatrix();
  QPainterPath p = path;
  if (p.isEmpty()) // dummy path
  p.addRect(0,0,0,0);

  clipping_ = true;

  switch(op) {
  case Qt::NoClip:
  updateMatrix(QMatrix());
  p.setFillRule(Qt::OddEvenFill);
  p = QPainterPath();
  p.addRect(paperRect());
  drawPath(p);
  updateMatrix(mat);
  break;
  case Qt::ReplaceClip:
  drawPath(mat.map(p));
  break;
  case Qt::UniteClip:
  p.addPath(*lastClipPath_);
  drawPath(mat.map(p));
  break;
  case Qt::IntersectClip:
  clipping_ = false;
  return;
  default:
  break;
  }
  clipping_ = false;
  *lastClipPath_ = p;
  }
*/

void QPdfEngine::updateBrush (const QBrush & brush, const QPointF & origin)
{
    *lastBrushOrig_ = origin;
    setBrush(*d->curBrush,brush,*lastBrushOrig_);
    d->curPage->append(d->curBrush);
    *lastBrush_ = brush;
}

void QPdfEngine::updateBackground (Qt::BGMode bgmode, const QBrush & brush)
{
    backgroundMode = bgmode;
    *bgBrush_ = brush;
}

void QPdfEngine::updatePen (const QPen & pen)
{
    QPen tpen = pen;
    tpen.setWidth((pen.width()) ? pen.width() : 0);


    d->curPage->append(d->curPen->setColor(tpen.color()));
    d->curPage->append(d->curPen->setLineWidth(tpen.width()));

    switch(tpen.capStyle()) {
    case Qt::FlatCap:
        d->curPage->append(d->curPen->setLineCap(0));
        break;
    case Qt::SquareCap:
        d->curPage->append(d->curPen->setLineCap(2));
        break;
    case Qt::RoundCap:
        d->curPage->append(d->curPen->setLineCap(1));
        break;
    default:
        break;
    }

    switch(tpen.joinStyle()) {
    case Qt::MiterJoin:
        d->curPage->append(d->curPen->setLineJoin(0));
        break;
    case Qt::BevelJoin:
        d->curPage->append(d->curPen->setLineJoin(2));
        break;
    case Qt::RoundJoin:
        d->curPage->append(d->curPen->setLineJoin(1));
        break;
    default:
        break;
    }

    d->curPage->append(d->curPen->setDashArray(tpen,0));

    *lastPen_ = tpen;
}

void QPdfEngine::updateMatrix(const QMatrix & matrix)
{
    d->curMatrix->setMatrix(matrix);
    d->curPage->append(d->curMatrix);
    *lastMatrix_ = matrix;

    if (lastBrush_)
        //if (lastBrush_->style() != Qt::NoBrush && lastBrush_->style() != Qt::SolidPattern)
        updateBrush(*lastBrush_, QPointF(0,0));
    //	if (lastPen_)
    //		if (lastPen_->style() != Qt::NoPen)
    //			updatePen(*lastPen_);
}

int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const
{
    int val;
    QRect r = d->fullPage ? paperRect() : pageRect();
    switch (metricType) {
    case QPaintDevice::PdmWidth:
        val = r.width();
        break;
    case QPaintDevice::PdmHeight:
        val = r.height();
        break;
    case QPaintDevice::PdmDpiX:
    case QPaintDevice::PdmDpiY:
    case QPaintDevice::PdmPhysicalDpiX:
    case QPaintDevice::PdmPhysicalDpiY:
        val = 72;
        break;
    case QPaintDevice::PdmWidthMM:
        val = qRound(r.width()*25.4/72.);
        break;
    case QPaintDevice::PdmHeightMM:
        val = qRound(r.height()*25.4/72.);
        break;
    case QPaintDevice::PdmNumColors:
        val = INT_MAX;
        break;
    case QPaintDevice::PdmDepth:
        val = 32;
        break;
    default:
        qWarning("QPdfEngine::metric: Invalid metric command");
        return 0;
    }
    return val;
}

QPaintEngine::Type QPdfEngine::type() const
{
    return QPaintEngine::User;
}

bool QPdfEngine::newPage()
{
    if (isActive()) {
        d->newPage();
        updateMatrix(*lastMatrix_);
        updateBrush(*lastBrush_, *lastBrushOrig_);
        updatePen(*lastPen_);
        return true;
    }
    return false;
}

void QPdfEngine::setBrush (QPdfBrush& pbr, const QBrush & brush, const QPointF & origin)
{
    QRect w = painter()->window();
    QRect vp = paperRect();

    QMatrix tmp(1.0, 0.0, 0.0, -1.0, 0.0, vp.height());

    tmp = d->curMatrix->lastMatrix() * tmp;
    tmp.translate(origin.x(),origin.y());

    switch(brush.style()) {
    case Qt::LinearGradientPattern: {
        Q_ASSERT(brush.gradient() && brush.gradient()->type() == QGradient::LinearGradient);

        const QLinearGradient *lg = static_cast<const QLinearGradient *>(brush.gradient());

        QPointF start = lg->start() + origin;
        QPointF stop = lg->finalStop() + origin;

        QColor c0 = lg->stops().first().second;
        QColor c1 = lg->stops().last().second;

        pbr.setGradient(c0, c1,
                        start.x(),start.y(), stop.x(),stop.y(),
                        w.left(), w.top(), w.width(), w.height(),
                        tmp);
        break;
    }
    case Qt::TexturePattern: {
        QPixmap pm = brush.texture();
        if (!pm.isNull()) {
            adaptMonochromePixmap(pm);
            pbr.setPixmap(pm, tmp);
        }
        break;
    }
    default:
        pbr.setFixed(brush.style(), brush.color(), tmp);
        break;
    }
}

void QPdfEngine::adaptMonochromePixmap(QPixmap& pm)
{
    if (pm.depth() == 1 && lastPen_) { // pm _is_ mask
        QBitmap bm0 = pm.mask();
        QBitmap bm = pm.createMaskFromColor(Qt::color0);

        if (!bm0.isNull()) { // combine masks
            QImage im0 = bm0.toImage();
            QImage im(im0.size(), QImage::Format_ARGB32_Premultiplied);
            for (int i=0; i!=im.width();++i)
                for (int j=0; j!=im.height();++j)
                    if(im0.pixelIndex(i,j) == Qt::color0)
                        im.setPixel(i,j,Qt::color0);
            bm = QBitmap::fromImage(im);
        }
        pm = QPixmap(pm.width(),pm.height());
        pm.fill(lastPen_->color());
        pm.setMask(bm);
    }
}


QPdfStream::QPdfStream()
{
    stream_ = 0;
}

void QPdfStream::setStream(QDataStream& val)
{
    stream_ = &val;
}

uint QPdfStream::write(const char* src, uint len)
{
    if (!stream_)
        return 0;

#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::write(): compress error");
            destLen = 0;
        }
        delete [] dest;
        return (uint)destLen;
    } else
#endif
    {
        stream_->writeRawData(src,len);
        return len;
    }
}

QPdfMatrix::QPdfMatrix()
    : QPdfObject()
{
    type = QPdfObject::MATRIX;
}

void QPdfMatrix::streamMatrix(const QMatrix &m, QPdfByteStream &s)
{
    s << m.m11()
     << m.m12()
     << m.m21()
     << m.m22()
     << m.dx()
     << m.dy()
     << "cm\n";
}

void QPdfMatrix::streamText(QPdfByteStream &stream)
{
    if (matrices_.empty())
        return;

    QMatrix m = matrices_.first();
    matrices_.pop_front();

    streamMatrix(m, stream);
}

QPdfMatrix* QPdfMatrix::setMatrix(QMatrix const& m)
{
    matrices_.append(m);
    return this;
}

QMatrix QPdfMatrix::currentMatrix() const
{
    if (matrices_.empty())
        return QMatrix();
    return matrices_.first();
}

QMatrix QPdfMatrix::lastMatrix() const
{
    if (matrices_.empty())
        return QMatrix();
    return matrices_.last();
}

QPdfImage::QPdfImage()
    : QPdfObject()
{
    init();
}

QPdfImage::QPdfImage(const QImage& im)
    : QPdfObject()
{
    init();
    QImage dummy;
    rawlen_ = convert(im, dummy);
}

QPdfImage::QPdfImage(const QImage& im, const QImage& mask)
    : QPdfObject()
{
    init();
    rawlen_ = convert(im, mask);
}

void QPdfImage::init()
{
    type = QPdfObject::IMAGE;
    stencil = softmask = 0;
    rawdata_ = 0;
    isgray_ = hashardmask_ = ismask_ = false;
    softmaskobj_ = maskobj_ = lenobj_ = -1;
    rawlen_ = -1;
}

QPdfImage::~QPdfImage()
{
    delete stencil;
    delete softmask;
    delete [] rawdata_;
}

void QPdfImage::setMaskObj(int obj)
{
    maskobj_ = obj;
}

void QPdfImage::setSoftMaskObj(int obj)
{
    softmaskobj_ = obj;
}

void QPdfImage::setLenObj(int obj)
{
    lenobj_ = obj;
}

QByteArray QPdfImage::getDefinition()
{
    QByteArray str = "<<\n"
                   "/Type /XObject\n"
                   "/Subtype /Image\n";
    QPdfByteStream s(&str);
    s << "/Width " << w_ << "\n";
    s << "/Height " << h_ << "\n";
    if (ismask_) {
        s << "/ImageMask true\n"
            "/Decode [1 0]\n";
    }
    else {
        s << "/BitsPerComponent " << (ismask_ ? 1 : 8) << "\n"
            "/ColorSpace "
          << ((isgray_ || ismask_) ? "/DeviceGray\n" : "/DeviceRGB\n");
    }
    if (hashardmask_ && maskobj_>0)
        s << "/Mask " << maskobj_ << "0 R\n";
    if (hasSoftMask() && softmaskobj_>0)
        s << "/SMask " << softmaskobj_ << "0 R\n";
    if (lenobj_>0)
        s << "/Length " << lenobj_ << "0 R\n";
    if (interpolation_)
        s << "/Interpolate true\n";
    if (do_compress)
        s << "/Filter /FlateDecode\n";
    s << ">>\n";
    return str;
}


// \return length of raw data in byte
int QPdfImage::convert(const QImage& img, const QImage& mask)
{
    if (img.isNull())
        return -1;

    QImage im = img;

    w_ = im.width();
    h_ = im.height();


    int d = im.depth();

    ismask_ = (d==1); // is monochrome - deal with it as mask

    // ### this needs fixing. Images never have masks, but we can optimise if
    // the alpha channel doesn't have other values apart 0 or 255.
    if (im.hasAlphaChannel()) {
        delete softmask;
        softmask = new QPdfImage;
        softmask->w_ = im.width();
        softmask->h_ = im.height();
        softmask->rawlen_ = w_ * h_;
        softmask->isgray_ = true;
        softmask->rawdata_ = new char[softmask->rawlen_];
        hassoftmask_p = true;
    }
    else if (!mask.isNull()) {
        delete stencil;
        stencil = new QPdfImage(mask);
        stencil->ismask_ = true;
        hashardmask_ = true;
    }

    int rowlen = 0;
    switch(d) {
    case 1:
        rowlen = (im.width() % 8) ? im.width() / 8 + 1 : im.width() / 8;
        break;
    case 32:
        rowlen = 3 * im.width();
        break;
    default:
        qWarning("QPdfImage::convert(): unsupported pixmap depth");
        return -1;
    }

    delete rawdata_;
    rawdata_ = new char[rowlen * im.height()];

    int i,j;
    switch(d) {
    case 1:
        for (i=0; i!=im.height(); ++i)
            memcpy(rawdata_+i*rowlen, im.scanLine(i), rowlen * sizeof(char));
        break;
    case 32: {
        int w = im.width();

        for (i=0; i!=im.height(); ++i)
            for (j=0; j!=w; ++j)
            {
                QRgb rgb = im.pixel(j,i);
                rawdata_[i*rowlen+3*j] = (char)qRed(rgb);
                rawdata_[i*rowlen+3*j+1] = (char)qGreen(rgb);
                rawdata_[i*rowlen+3*j+2] = (char)qBlue(rgb);
                if (hasSoftMask())
                {
                    softmask->rawdata_[i*w+j] = (char)qAlpha(rgb);
                    /*
                      if (!mask.isNull()) // integrate hard mask with soft mask
                      {
                      if (mask.bitOrder() == QImage::LittleEndian)
                      {
                      if (!(*(mask.scanLine(i) + (j >> 3)) & 1 << (j & 7)))
                      softmask->rawdata_[i*w+j] = 0;
                      }
                      else
                      {
                      if (!(*(mask.scanLine(i) + (j >> 3)) & 1 << (7 - (j & 7))))
                      softmask->rawdata_[i*w+j] = 0;
                      }
                      }
                    */
                }
            }
        break;
    }
    default:
        break;
    }

    return rowlen * im.height();
}

void QPdfImage::streamText(QPdfByteStream &stream)
{
    stream << "q\n" << w_ << " 0 0 " << -h_ << " 0 " << h_ << " cm\n"
           << name
           <<" Do\nQ\n";
}



QPdfGradient::QPdfGradient()
    : QPdfObject(), softmask(0), issoftmask_(false), mainobj_(-1),
      funcobj_(-1), smfmobj_(-1), csrgbobj_(-1), csgrayobj_(-1), x0_(0), y0_(0), x1_(0), y1_(0),
      w_(0), h_(0)
{
    type = QPdfObject::GRADIENT;
}

QPdfGradient::~QPdfGradient()
{
    delete softmask;
}

void QPdfGradient::setParameter(const QColor &b, const QColor &e, qreal x0, qreal y0, qreal x1, qreal y1)
{
    beg_ = b;
    end_ = e;
    x0_ = x0;
    y0_ = y0;
    x1_ = x1;
    y1_ = y1;

    // maintain transparent parts
    if (b.alphaF() < 1.0 || e.alphaF() < 1.0) {
        hassoftmask_p = true;
        softmask = new QPdfGradient;
        softmask->x0_ = x0_;
        softmask->y0_ = y0_;
        softmask->x1_ = x1_;
        softmask->y1_ = y1_;
        softmask->beg_.setAlphaF(b.alphaF());
        softmask->end_.setAlphaF(e.alphaF());
        softmask->issoftmask_ = true;
    }
}

void QPdfGradient::setObjects(int mainobj, int funcobj)
{
    mainobj_ = mainobj;
    funcobj_ = funcobj;
}

void QPdfGradient::setSoftMaskObjects(int formobj, int mainobj, int funcobj)
{
    smfmobj_ = formobj;
    softmask->mainobj_ = mainobj;
    softmask->funcobj_ = funcobj;
}

void QPdfGradient::setColorSpaceObject(int obj)
{
    csrgbobj_ = obj;
}

void QPdfGradient::setSoftMaskColorSpaceObject(int obj)
{
    if (softmask)
        softmask->csgrayobj_ = obj;
}

QByteArray QPdfGradient::getSingleMainDefinition()
{
    QByteArray str;
    QPdfByteStream s(&str);
    // main object
    s << "<<\n"
        "/ShadingType 2\n"
        "/ColorSpace "
      << (issoftmask_ ? csgrayobj_ : csrgbobj_) << "0 R\n"
        "/Coords ["
      << x0_
      << y0_
      << x1_
      << y1_ << "]\n"
      << "/Function " << funcobj_ << " 0 R\n"
        "/Extend [true true]\n"
        ">>\n"
        "endobj\n";
    return str;
}

QByteArray QPdfGradient::getSingleFuncDefinition()
{
    QByteArray str;
    QPdfByteStream s(&str);

    // function object
    s << "<<\n"
        "/FunctionType 2\n"
        "/Domain [0.0 1.0]\n"
        "/N 1\n";
    if (issoftmask_) {
        s << "/C0 [" << beg_.alphaF() << "]\n"
            "/C1 [" << end_.alphaF() << "]\n";
    }
    else {
        s << "/C0 ["
          << beg_.redF()
          << beg_.greenF()
          << beg_.blueF()
          << "]\n"
            "/C1 ["
          << end_.redF()
          << end_.greenF()
          << end_.blueF()
          << "]\n";
    }
    s << ">>\n"
        "endobj\n";
    return str;
}

QByteArray QPdfGradient::getMainDefinition()
{
    return getSingleMainDefinition();
}

QByteArray QPdfGradient::getFuncDefinition()
{
    return getSingleFuncDefinition();
}

QByteArray QPdfGradient::getSoftMaskFormDefinition()
{
    QByteArray str;

    if (!softmask)
        return str;
    QPdfByteStream s(&str);

    s << "<<\n"
        "/Type /XObject\n"
        "/Subtype /Form\n"
        "/BBox [ "
      << x_ << y_ << w_ << h_ << "]\n"
        "/Group <</S /Transparency >>\n"
        "/Resources <<\n"
        "/Shading <<\n"
      << softMaskName() << " " << softmask->mainobj_ << " 0 R\n"
        ">>\n"
        ">>\n";
    //	QByteArray stream = "/PCSp cs\n";
    //	stream << softMaskName() + " scn\n";

    //	QByteArray stream = "0.3 g\n20 30 10 20 re\n";

    QByteArray stream = softMaskName() + " sh\n";
    s << "/Length " << stream.length() << "\n"
        ">>\n"
        "stream\n"
      << stream
      << "endstream\n"
        "endobj\n";

    return str;
}

QByteArray QPdfGradient::getSoftMaskMainDefinition()
{
    return (softmask) ? softmask->getSingleMainDefinition() : QByteArray();
}

QByteArray QPdfGradient::getSoftMaskFuncDefinition()
{
    return (softmask) ? softmask->getSingleFuncDefinition() : QByteArray();
}

void QPdfGradient::setSoftMaskRange(qreal x, qreal y, qreal w, qreal h)
{
    x_ = x;
    y_ = y;
    w_ = w;
    h_ = h;
}

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
};


QPdfBrush::QPdfBrush(const QByteArray& id)
    : QPdfObject(), id_(id)
{
    type = QPdfObject::BRUSH;
    nobrush_ = true;
}

QPdfBrush::FixedPattern::FixedPattern(const QByteArray& n, int idx, const QColor &col, const QMatrix& mat)
    : rgba(col), patternidx(idx)
{
    name = n;
    matrix = mat;
}

QPdfBrush::GradientPattern::GradientPattern(const QByteArray& n, QPdfGradient* grad, const QMatrix& mat /* = QMatrix */)
    : shader(grad), mainobj_(-1)
{
    name = n;
    matrix = mat;
}

QPdfBrush::PixmapPattern::PixmapPattern(const QByteArray& n, QPdfImage* im, const QMatrix& mat)
    :	image(im)
{
    name = n;
    matrix = mat;
}


QByteArray QPdfBrush::Pattern::defBegin(int ptype, int w, int h)
{
    QByteArray str;
    QPdfByteStream s(&str);
    s << "<<\n";
    s << "/Type /Pattern\n";
    s << "/PatternType 1\n";
    s << "/PaintType " << ptype << "\n";
    s << "/TilingType 1\n";
    s << "/BBox [0 0 " << w << h << "]\n";
    s << "/XStep " << w << "\n";
    s << "/YStep " << h << "\n";
    //TODO bad tiling job of AR here with non-identity matrix
    s << "/Matrix ["
      << matrix.m11()
      << matrix.m12()
      << matrix.m21()
      << matrix.m22()
      << matrix.dx()
      << matrix.dy() << "]\n";
    s << "/Resources \n<<\n"; // open resource tree

    return str;
}

QByteArray QPdfBrush::Pattern::getDefinition(const QByteArray &res)
{
    QByteArray str;
    QPdfByteStream s(&str);
    s << ">>\n"; // close resource tree
    s << "/Length " << res.length() << "\n";
    s << ">>\n";
    s << "stream\n";
    s << res;
    s << "endstream\n";
    s << "endobj\n";
    return str;
}


QByteArray QPdfBrush::FixedPattern::getDefinition()
{
    if (!isTruePattern())
        return QByteArray();

    QByteArray s = defBegin(2, 8, 8);
    s += Pattern::getDefinition(pattern_for_brush[patternidx]);

    return s;
}



QByteArray QPdfBrush::PixmapPattern::getDefinition(int objno)
{
    Q_ASSERT(image);
    QByteArray str = defBegin(1, image->w(), image->h());
    QPdfByteStream s(&str);
    s << "/XObject <<" << image->name << ' ' << objno << "0 R>>\n";

    QMatrix m(image->w(),0,0,-image->h(),0,image->h());
    QByteArray res;
    QPdfByteStream ress(&res);
    QPdfMatrix::streamMatrix(m, ress);
    ress << image->name << " Do\n";
    s << Pattern::getDefinition(res);

    return str;
}

void QPdfBrush::GradientPattern::setMainObj(int obj)
{
    mainobj_ = obj;
}

QByteArray QPdfBrush::GradientPattern::getDefinition()
{
    Q_ASSERT(shader);

    QByteArray str;
    QPdfByteStream s(&str);
    s << "<<\n"
        "/Type /Pattern\n"
        "/PatternType 2\n"
      << "/Matrix ["
      << matrix.m11()
      << matrix.m12()
      << matrix.m21()
      << matrix.m22()
      << matrix.dx()
      << matrix.dy() << "]\n";

    s << "/Shading " << shader->mainObject() << " 0 R\n"
        ">>\n"
        "endobj\n";

    return str;
}

QPdfBrush::~QPdfBrush()
{
    int i;
    for (i=0; i!=pixmaps.size(); ++i)
        delete pixmaps[i].image;
    for (i=0; i!=gradients.size(); ++i)
        delete gradients[i].shader;
}

void QPdfBrush::streamText(QPdfByteStream &s)
{
    if (streamstate_.empty())
        return;

    SUBTYPE sstate = streamstate_.first();
    streamstate_.pop_front();
    if (!alpha_.empty())
        alpha_.pop_front(); // clean-up only

    switch(sstate) {
    case FIXED: {
        if (fixeds.empty())
            break;
        FixedPattern p = fixeds.first();
        if (p.isSolid())
        {
            s << "/CSp cs\n"
              << p.rgba.redF()
              << p.rgba.greenF()
              << p.rgba.blueF()
              << "scn\n";
        }
        else if (!p.isEmpty())
        {
            s << "/PCSp cs\n"
              << p.rgba.redF()
              << p.rgba.greenF()
              << p.rgba.blueF()
              << p.name
              << " scn\n";
        }
        fixeds.pop_front();
        break;
    }
    case GRADIENT: {
        if (gradients.empty())
            break;
        GradientPattern p = gradients.first();
        if (p.shader->hasSoftMask())
            s << p.shader->softMaskGraphicStateName() << " gs\n";
        s << "/PCSp cs\n"
          << p.name
          << " scn\n";
        gradients.pop_front();
        break;
    }
    case PIXMAP: {
        if (pixmaps.empty())
            break;
        PixmapPattern p = pixmaps.first();
        s << "/PCSp cs\n"
          << p.name
          << " scn\n";
        pixmaps.pop_front();
        break;
    }
    }
}

QPdfBrush* QPdfBrush::setFixed(Qt::BrushStyle style, const QColor &rgba, const QMatrix& mat)
{
    nobrush_ = (style == Qt::NoBrush);

    streamstate_.append(FIXED);
    QByteArray s("/Pat");
    s += id_ + QByteArray::number(fixeds.size());

    FixedPattern p(s, style, rgba, mat);

    fixeds.append(p);
    alpha_.append(p.isEmpty() ? -1.0 : rgba.alphaF());
    return this;
}

QPdfBrush* QPdfBrush::setGradient(const QColor &rgba, const QColor &gradrgba,
                                  qreal bx, qreal by, qreal ex, qreal ey,
                                  qreal bbox_xb, qreal bbox_xe, qreal bbox_yb, qreal bbox_ye, const QMatrix& mat)
{
    nobrush_ = false;
    streamstate_.append(GRADIENT);
    QByteArray s("/PatLG");
    s += id_ + QByteArray::number(gradients.size());

    QPdfGradient* grad = new QPdfGradient;
    grad->name = s;
    grad->setParameter(rgba, gradrgba, bx, by, ex, ey);
    grad->setSoftMaskRange(bbox_xb, bbox_xe, bbox_yb, bbox_ye);

    gradients.append(GradientPattern(s, grad, mat));
    alpha_.append(-1.0);
    return this;
}

QPdfBrush* QPdfBrush::setPixmap(const QPixmap& pm, const QMatrix& mat)
{
    nobrush_ = false;
    streamstate_.append(PIXMAP);
    QByteArray s("/PatI");
    s += id_ + QByteArray::number(pixmaps.size());

    QImage im = pm.toImage();
    QImage mask;
    QBitmap bmm = pm.mask();
    if (!bmm.isNull())
        mask = bmm.toImage();

    QPdfImage* img = new QPdfImage(im,mask);
    img->name = "/PImg" + id_ + QByteArray::number(pixmaps.size());

    pixmaps.append(PixmapPattern(s,img,mat));
    alpha_.append(-1.0);
    return this;
}

qreal QPdfBrush::alpha() const
{
    if (alpha_.empty())
        return 1.0;
    return alpha_.last();
}

bool QPdfBrush::isGradient() const
{
    if (streamstate_.empty())
        return false;
    return (GRADIENT == streamstate_.last());
}

bool QPdfBrush::firstIsGradient() const
{
    if (streamstate_.empty())
        return false;
    return (GRADIENT == streamstate_.first());
}

QPdfPen::QPdfPen()
    : QPdfObject()
{
    type = QPdfObject::PEN;
}

void QPdfPen::streamText(QPdfByteStream &s)
{
    if (streamstate_.empty())
        return;

    SUBTYPE sstate = streamstate_.first();
    streamstate_.pop_front();

    switch(sstate) {
    case LINEWIDTH:
        if (lw_.empty())
            break;
        s << lw_.first() << "w\n";
        lw_.pop_front();
        break;
    case LINECAP:
        if (lc_.empty())
            break;
        s << lc_.first() << " J\n";
        lc_.pop_front();
        break;
    case LINEJOIN:
        if (lj_.empty())
            break;
        s << lj_.first() << " j\n";
        lj_.pop_front();
        break;
    case MITERLIMIT:
        if (ml_.empty())
            break;
        s << ml_.first() << "M\n";
        ml_.pop_front();
        break;
    case COLOR:
        {
            if (col_.empty())
                break;
            QColor rgba = col_.first();
            s << "/CSp CS\n"
              << rgba.redF()
              << rgba.greenF()
              << rgba.blueF()
              << "SCN\n";
            col_.pop_front();
        }
        break;
    case DASHARRAY:
        {
            if (da_.empty() || stroking_.empty())
                break;
            s << "[";

            for (int i=0; i!=da_.first().seq.size(); ++i)
                s << da_.first().seq[i];
            s << "] "
              << da_.first().phase
              << "d\n";
            da_.pop_front();
            stroking_.pop_front();
            break;
        }
    default:
        break;
    }
}

QPdfPen* QPdfPen::setLineWidth(qreal v)
{
    streamstate_.append(LINEWIDTH);
    lw_.append((v<0) ? 0 : v);
    return this;
}

QPdfPen* QPdfPen::setLineCap(unsigned v)
{
    streamstate_.append(LINECAP);
    lc_.append((v>2) ? 0 : v);
    return this;
}

QPdfPen* QPdfPen::setLineJoin(unsigned v)
{
    streamstate_.append(LINEJOIN);
    lj_.append((v>2) ? 0 : v);
    return this;
}

QPdfPen* QPdfPen::setMiterLimit(qreal v)
{
    streamstate_.append(MITERLIMIT);
    ml_.append(v);
    return this;
}

QPdfPen* QPdfPen::setColor(const QColor &rgba)
{
    streamstate_.append(COLOR);
    col_.append(rgba);
    return this;
}

qreal QPdfPen::alpha() const
{
    if (!stroking())
        return -1.0;
    if (col_.empty())
        return 1.0;
    return col_.last().alphaF();
}

QPdfPen* QPdfPen::setDashArray(const QPen& pen, qreal phase)
{
    QVector<qreal> sequence;

    qreal capsize = 0;
    qreal lw = qMax(pen.width(),1);

    switch(pen.capStyle()) {
    case Qt::RoundCap:
    case Qt::SquareCap:
        capsize = qMax(lw,qreal(1));
        break;
    default:
        break;
    };

    bool stroke = true;
    switch (pen.style()) {
    case Qt::NoPen:
        stroke = false;
        break;
    case Qt::SolidLine:
        break;
    case Qt::DashLine:
        sequence.append(3*lw-capsize); // 3 full
        sequence.append(1*lw+capsize); // 1 gap
        break;
    case Qt::DotLine:
        sequence.append(qMax(1*lw-capsize,qreal(0))); // 1 full
        sequence.append(1*lw+capsize); // 1 gap
        break;
    case Qt::DashDotLine:
        sequence.append(3*lw-capsize); // 3 full
        sequence.append(1*lw+capsize); // 1 gap
        sequence.append(qMax(1*lw-capsize,qreal(0))); // 1 full
        sequence.append(1*lw+capsize); // 1 gap
        break;
    case Qt::DashDotDotLine:
        sequence.append(3*lw-capsize);
        sequence.append(1*lw+capsize);
        sequence.append(1*lw-capsize);
        sequence.append(1*lw+capsize);
        sequence.append(qMax(1*lw-capsize,qreal(0))); // safety
        sequence.append(1*lw+capsize);
        break;
    case Qt::CustomDashLine:
    case Qt::MPenStyle:
        // avoid compiler warning
        break;
    }

    streamstate_.append(DASHARRAY);
    stroking_.append(stroke);
    da_.append(DashArray(sequence, phase));
    return this;
}

bool QPdfPen::stroking() const
{
    if (stroking_.empty())
        return true;
    return stroking_.last();
}

QPdfPath::QPdfPath(const QPdfPen* pen, const QPdfBrush* brush, int brushflags,  bool closed)
    : QPdfObject(), ca_(1.0), CA_(1.0), alphaobj_(-1), gradientstrokealpha_(false)
{
    type = QPdfObject::PATH;

    painttype = (closed) ? CLOSE : NONE;

    if (pen && pen->stroking())
        painttype |= STROKE;

    if (brush && !brush->noBrush())
        painttype |= brushflags;

    ca_ = (brush) ? brush->alpha() : -1.0;
    CA_ = (pen) ? pen->alpha() : -1.0;
    gradientstrokealpha_ = (brush) ? brush->isGradient() : false;
}

QByteArray QPdfPath::paintOperator() const
{
    QByteArray s;

    if (painttype & CLIPPING) {
        if (painttype & FILLNONZERO)
            s += "W n\n";
        else
            s += "W* n\n";
        return s;
    }

    if (painttype & STROKE) {
        bool nsa = !hasTrueStrokeAlpha(); // don't use PDF combined operators in this case
        if (painttype & CLOSE) {
            if (painttype & FILLNONZERO)
                s = (nsa) ? "b" : "f";
            else if (painttype & FILLEVENODD)
                s = (nsa) ? "b*" : "f*";
            else
                s = "s";
        }
        else if (painttype & FILLNONZERO)
            s = (nsa) ? "B" : "f";
        else if (painttype & FILLEVENODD)
            s = (nsa) ? "B*" : "f*";
        else
            s = "S";
    }
    else if (painttype & FILLNONZERO)
        s = "f";
    else if (painttype & FILLEVENODD)
        s = "f*";
    else
        return QByteArray("n");

    return s;
}

void QPdfPath::streamCoreText(QPdfByteStream &s) const
{
    for (int i=0; i != subpaths.size(); ++i) {
        const SubPath& spath = subpaths[i];
        Q_ASSERT(spath.initialized);

        s << spath.start.x
          << spath.start.y
          << "m\n";
        for (int j=0; j!= spath.elements.size(); ++j) {
            switch (spath.elements[j].type) {
            case Element::LINE:
                s << spath.elements[j].line.x
                  << spath.elements[j].line.y
                  << "l\n";
                break;
            case Element::CURVE:
                s << spath.elements[j].curve.x1
                  << spath.elements[j].curve.y1
                  << spath.elements[j].curve.x2
                  << spath.elements[j].curve.y2
                  << spath.elements[j].curve.xnew
                  << spath.elements[j].curve.ynew
                  << "c\n";
                break;
            default:
                break;
            }
        }
        if (spath.closed)
            s << "h\n";
    }
}

void QPdfPath::streamText(QPdfByteStream &s)
{
    QByteArray paintop = paintOperator();
    Q_ASSERT(!paintop.isEmpty());

    if (!alphaname_.isEmpty())
        s << alphaname_ << " gs\n";
    else
        s << "/GSa gs\n";

    streamCoreText(s);
    s << ' ' << paintop << "\n";

    // 2nd run to get Qt's fill->stroke sequential behavior
    if (hasTrueStrokeAlpha()) {
        if (paintop != "S" && paintop != "s") {
            if (painttype & CLOSE)
                paintop = "s";
            else
                paintop = "S";
            streamCoreText(s);
            s << ' ' << paintop << "\n";
        }
    }
}

QByteArray QPdfPath::getAlphaDefinition() const
{
    QByteArray str;
    if (!hasTrueAlpha())
        return str;
    QPdfByteStream s(&str);

    s << "<<\n";
    if (hasTrueStrokeAlpha())
        s << "/CA " << CA_ << "\n";
    if (hasTrueNonStrokeAlpha())
        s << "/ca " << ca_ << "\n";
    s << ">>\n"
        "endobj\n";

    return str;
}

QPdfPage::QPdfPage()
    : QPdfObject()
{
    width_ = 0;
    height_ = 0;
}

void QPdfPage::destroy()
{
    for (int i = 0; i != gobjects_.size(); ++i) {
        if (gobjects_[i])
            if (0 == --gobjects_[i]->appended)
                delete gobjects_[i];
    }
    delete this;
}

void QPdfPage::streamText(QPdfByteStream &s)
{
    s << "q\n";
    QByteArray cm,cp;

    QMatrix lm;
    bool grad = false;
    QByteArray gradcmd;

    for (int i = 0; i != gobjects_.size(); ++i) {
        switch(gobjects_[i]->type) {
        case MATRIX:
            if (lm == ((QPdfMatrix*)gobjects_[i])->currentMatrix()) {
                gobjects_[i]->streamText(s);
                break;
            }
            lm = ((QPdfMatrix*)gobjects_[i])->currentMatrix();
            {
                cm = QByteArray();
                QPdfByteStream stream(&cm);
                gobjects_[i]->streamText(stream);
            }
            s << "Q q\n"; // retrieves original matrix, (PDF syntax has no means to handle matrices besides concatenating)
            s << cp << cm; // ... but this destroys also the actual clipping path, so set them again (_before_ the transformation)
            break;
        case PATH:
            if (((QPdfPath*)gobjects_[i])->painttype & QPdfPath::CLIPPING) {
                cp = QByteArray();
                QPdfByteStream stream(&cp);
                gobjects_[i]->streamText(stream);
                s << "Q q\n";
                s << cp << cm;
            } else {
                gobjects_[i]->streamText(s);
            }
            break;
        case IMAGE:
            gobjects_[i]->streamText(s);
            break;
        case BRUSH:
        case PEN: {
            if (!predType(i, BRUSH) && !predType(i,PEN))
                s << "Q\n";

            if (gobjects_[i]->type == BRUSH) {
                QPdfBrush* br = (QPdfBrush*)gobjects_[i];
                grad = br->firstIsGradient();
                if (grad) {
                    gradcmd = QByteArray();
                    QPdfByteStream stream(&gradcmd);
                    gobjects_[i]->streamText(stream);
                }
                else
                    gobjects_[i]->streamText(s);
            } else {
                gobjects_[i]->streamText(s);
            }

            if (!nextType(i, BRUSH) && !nextType(i,PEN)) {
                s << "q\n";
                if (nextType(i, PATH) || nextType(i,IMAGE) || nextType(i, MATRIX)) {
                    s << cp << cm;
                    if (grad)
                        s << gradcmd;
                }
            }
            break;
        }
        default:
            break;
        }
    }
    s << "Q\n";
}

bool QPdfPage::predType(int i, QPdfObject::TYPE t)
{
    return ((i>0 && gobjects_[i-1]->type == t)) ? true : false;
}
bool QPdfPage::nextType(int i, QPdfObject::TYPE t)
{
    return ((i+1<gobjects_.size() && gobjects_[i+1]->type == t)) ? true : false;
}

QPdfObject* QPdfPage::append(QPdfObject* val, bool protect)
{
    if (!val)
        return 0;
    if (protect)
        val->appended = -1;

    gobjects_.append(val);
    if (val->type == QPdfObject::IMAGE)
        images.append((QPdfImage*)val);
    else if (val->type == QPdfObject::PATH)
        paths.append((QPdfPath*)val);

    if (val->appended>=0)
        ++val->appended;

    return val;
}

QPdfEnginePrivate::QPdfEnginePrivate()
{
    currentObject = 1;
    width_ = 0;
    height_ = 0;
    options_ = 0;
    streampos_ = 0;
    landscape_ = false;

    curPage = new QPdfPage;

    curMatrix = new QPdfMatrix;
    curPen = new QPdfPen;
    curBrush = new QPdfBrush;

    stream_ = new QDataStream;
    pageOrder = QPrinter::FirstPageFirst;
    orientation = QPrinter::Portrait;
    fullPage = false;
}

QPdfEnginePrivate::~QPdfEnginePrivate()
{
    curPage->destroy();
    delete curMatrix;
    delete curPen;
    delete curBrush;
    delete stream_;
}

// For strings up to 10000 bytes only !
int QPdfEnginePrivate::xprintf(const char* fmt, ...)
{
    if (!stream_)
        return 0;

    const int msize = 10000;
    static char buf[msize];

    va_list args;
    va_start(args, fmt);
    int bufsize = vsprintf(buf, fmt, args);

    Q_ASSERT(bufsize<msize);

    va_end(args);

    stream_->writeRawData(buf,bufsize);
    streampos_ += bufsize;
    return bufsize;
}

void QPdfEnginePrivate::setDevice(QIODevice* device)
{
    stream_->setDevice(device);
    streampos_ = 0;
}

void QPdfEnginePrivate::unsetDevice()
{
    stream_->unsetDevice();
}

void QPdfEnginePrivate::writeHeader()
{
    addXrefEntry(0,false);

    xprintf("%%PDF-1.4\n%\342\343\317\323\n");

    writeInfo();
    writeCatalog();

    graphicsState = requestObject();
    patternColorSpace = requestObject();
    colorSpace = requestObject();
    colorSpaceGray = requestObject();
}

void QPdfEnginePrivate::writeInfo()
{
    time_t now;
    tm *newtime;

    time(&now);
    newtime = gmtime(&now);
    QByteArray y;

    if (newtime && newtime->tm_year+1900 > 1992)
        y += QByteArray::number(newtime->tm_year+1900);

    info = addXrefEntry(-1);
    xprintf("<<\n"
            "/Title (%s)\n"
            "/Author (%s)\n"
            "/Creator (%s)\n"
            "/Producer (Qt %s (C) 1992-%s Trolltech AS)\n",
            title.toUtf8().constData(), author.toUtf8().constData(), creator.toUtf8().constData(),
            qVersion(), y.constData());

    if(!newtime){
        xprintf(">>\n"
                "endobj\n");
        return;
    }

    xprintf("/CreationDate (D:%d%02d%02d%02d%02d%02d)\n"
            ">>\n"
            "endobj\n",
            newtime->tm_year+1900,
            newtime->tm_mon+1,
            newtime->tm_mday,
            newtime->tm_hour,
            newtime->tm_min,
            newtime->tm_sec);
}


// Create catalog and page structure - 2nd and 3th PDF object

void QPdfEnginePrivate::writeCatalog()
{
    catalog = addXrefEntry(-1);
    pageRoot = requestObject();
    xprintf("<<\n"
            "/Type /Catalog\n"
            "/Pages %d 0 R\n"
            ">>\n"
            "endobj\n",pageRoot);
}

void QPdfEnginePrivate::writePageRoot()
{
    addXrefEntry(pageRoot);
    int viewport[4] = {0,0,width_,height_};

    xprintf("<<\n"
            "/Type /Pages\n"
            "/Kids \n");

    xprintf("[\n");
    int size = pages.size();
    for (int i = 0; i < size; ++i)
        xprintf("%d 0 R\n",pages[pageOrder == QPrinter::FirstPageFirst ? i : size-i-1]);
    xprintf("]\n");

    //xprintf("/Group <</S /Transparency /I true /K false>>\n");

    xprintf("/Count %d\n"
            "/MediaBox [%d %d %d %d]\n",
            pages.size(), viewport[0], viewport[1], viewport[2], viewport[3]);

    xprintf("/ProcSet [/PDF /Text /ImageB /ImageC]\n"
            ">>\n"
            "endobj\n"
        );

    // graphics state

    addXrefEntry(graphicsState);
    xprintf("<<\n"
            "/Type /ExtGState\n"
            "/SA true\n"
            "/SM 0.02\n"
            "/BG2 /Default\n"
            "/BM /Normal\n"
            "/UCR2 /Default\n"
            "/TR2 /Default\n"
            "/ca 1.0\n"
            "/CA 1.0\n"
            "/AIS false\n"
            ">>\n"
            "endobj\n");

    // color space for pattern

    addXrefEntry(patternColorSpace);
    xprintf("[/Pattern %d 0 R]\n"
            "endobj\n", colorSpace);

    addXrefEntry(colorSpace);
    xprintf("[ /CalRGB\n"
            "<<\n"
            "/WhitePoint [0.9505 1.0000 1.0890]\n"
            "/Gamma [1.8000 1.8000 1.8000]\n"
            "/Matrix [ 0.4497 0.2446 0.0252 0.3163 0.6720 0.1412 0.1845 0.0833 0.9227]\n"
            //"/Matrix [ 1 0 0 0 1 0 0 0 1]\n"
            ">>\n"
            "]\n"
            "endobj\n");

    addXrefEntry(colorSpaceGray);
    xprintf("[ /CalGray\n"
            "<<\n"
            "/WhitePoint [0.9505 1.0000 1.0890]\n"
            ">>\n"
            "]\n"
            "endobj\n");
}

void QPdfEnginePrivate::newPage()
{
    flushPage();

    curPage->destroy();
    curPage = new QPdfPage;

    delete curMatrix; curMatrix = new QPdfMatrix;
    delete curPen; curPen = new QPdfPen;
    delete curBrush; curBrush = new QPdfBrush;

    curPage->append(curMatrix->setMatrix(QMatrix()),true);
    curPage->append(curPen->setColor(QColor()),true);
    curPage->append(curBrush->setFixed(Qt::NoBrush, QColor()),true);

    pages.append(requestObject());
}


void QPdfEnginePrivate::flushPage()
{
    if (pages.empty())
        return;

    QPdfStream cs;
    cs.setStream(*stream_);

    uint pageStream = requestObject();
    uint pageStreamLength = requestObject();
    uint resources = requestObject();

    addXrefEntry(pages.last());
    xprintf("<<\n"
            "/Type /Page\n"
            "/Parent %d 0 R\n"
            "/Contents %d 0 R\n"
            "/Resources %d 0 R\n"
            ">>\n"
            "endobj\n",
            pageRoot, pageStream, resources);


    addXrefEntry(resources);
    xprintf("<<\n"
            "/ColorSpace <<\n"
            "/PCSp %d 0 R\n"
            "/CSp %d 0 R\n"
            "/CSpg %d 0 R\n"
            "/DefaultRGB %d 0 R\n"
            "/DefaultGray %d 0 R\n"
            ">>\n"
            "/ExtGState <<\n"
            "/GSa %d 0 R\n",
            patternColorSpace, colorSpace, colorSpaceGray, colorSpace, colorSpaceGray, graphicsState);

    int i;

    // Graphic states for const alphas < 1

    for (i=0; i<curPage->paths.size();++i) {
        QPdfPath* p = curPage->paths[i];

        if (p->hasTrueAlpha()) {
            p->setAlpha("/GStr", requestObject());
            xprintf("%s %d 0 R\n", p->alphaName().constData(), p->alphaObject());
        }
    }

    // Graphic states for transparent gradients

    for (i=0; i<curBrush->gradients.size();++i) {
        QPdfGradient* sh = curBrush->gradients[i].shader;
        sh->setColorSpaceObject(colorSpace);
        sh->setSoftMaskColorSpaceObject(colorSpaceGray);
        if (sh->hasSoftMask()) {
            sh->setSoftMaskObjects(requestObject(),requestObject(),requestObject());
            xprintf("%s <</Type /ExtGState /SMask <</S /Alpha /G %d 0 R>> >>\n"
                    ,sh->softMaskGraphicStateName().constData(),sh->softMaskFormObject());
        }
    }
    xprintf(">>\n");

    // brushes with fixed pattern

    xprintf("/Pattern <<\n");

    QVector<uint> fno;
    for (i=0; i<curBrush->fixeds.size();++i) {
        QPdfBrush::FixedPattern p = curBrush->fixeds[i];

        if (!p.isTruePattern())
            continue;

        fno.append(requestObject());
        xprintf("%s %d 0 R\n",
                curBrush->fixeds[i].name.constData(),fno.last());
    }

    // ... pixmap brushes

    QVector<uint> pno;
    for (i=0; i<curBrush->pixmaps.size();++i) {
        pno.append(requestObject());
        xprintf("%s %d 0 R\n",
                curBrush->pixmaps[i].name.constData(),pno.last());
    }

    // ... linear gradient brushes

    for (i=0; i<curBrush->gradients.size();++i) {
        int obj = requestObject();
        curBrush->gradients[i].setMainObj(obj);
        xprintf("%s %d 0 R\n",
                curBrush->gradients[i].name.constData(), obj);
    }

    xprintf(">>\n");


    // ... images

    xprintf("/XObject <<\n");
    QVector<int> iv;
    for (i=0; i<curPage->images.size();++i) {
        iv.append(requestObject());
        xprintf("%s %d 0 R\n",
                curPage->images[i]->name.constData(),iv.last());
    }
    xprintf(">>\n");

    // close resource dictionary

    xprintf(">>\n"
            "endobj\n");


    // write associated objects

    // Graphic states for const alphas < 1

    for (i=0; i<curPage->paths.size();++i) {
        QPdfPath* p = curPage->paths[i];

        if (p->hasTrueAlpha()) {
            addXrefEntry(p->alphaObject());
            xprintf(p->getAlphaDefinition().constData());
        }
    }

    // fixed brushes

    int k = 0;
    for (i=0; i<curBrush->fixeds.size(); ++i) {
        QPdfBrush::FixedPattern p = curBrush->fixeds[i];

        if (!p.isTruePattern())
            continue;

        addXrefEntry(fno[k++]);
        xprintf(p.getDefinition().constData());
    }

    // gradient

    // write shader and associated function objects

    for (i=0; i<curBrush->gradients.size();++i) {
        QPdfBrush::GradientPattern p = curBrush->gradients[i];
        p.shader->setObjects(requestObject(), requestObject());

        addXrefEntry(p.getMainObj());
        xprintf(p.getDefinition().constData());

        addXrefEntry(p.shader->mainObject());
        xprintf(p.shader->getMainDefinition().constData());
        addXrefEntry(p.shader->functionObject());
        xprintf(p.shader->getFuncDefinition().constData());

        if (p.shader->hasSoftMask()) {
            addXrefEntry(p.shader->softMaskFormObject());
            xprintf(p.shader->getSoftMaskFormDefinition().constData());
            addXrefEntry(p.shader->softMaskMainObject());
            xprintf(p.shader->getSoftMaskMainDefinition().constData());
            addXrefEntry(p.shader->softMaskFunctionObject());
            xprintf(p.shader->getSoftMaskFuncDefinition().constData());
        }
    }

    // image

    // gather pattern images (add pixmap brushes to the remaining images)

    QVector<QPdfImage*> images = curPage->images;
    for (i=0; i<curBrush->pixmaps.size();++i) {
        QPdfBrush::PixmapPattern p = curBrush->pixmaps[i];
        addXrefEntry(pno[i]);
        iv.append(requestObject());
        xprintf(p.getDefinition(iv.last()).constData());
        images.append(curBrush->pixmaps[i].image);
    }

    // write all image objects

    for (i=0; i<images.size();++i) {
        addXrefEntry(iv[i]);
        QPdfImage* im = images[i];

        if (im->hasHardMask())
            im->setMaskObj(requestObject());
        if (im->hasSoftMask())
            im->setSoftMaskObj(requestObject());
        im->setLenObj(requestObject());
        xprintf("%sstream\n", im->getDefinition().constData());
        int len = streampos_;
        streampos_ += (int)cs.write(im->data(), im->rawLength());
        len = streampos_-len;
        xprintf("endstream\n"
                "endobj\n");
        addXrefEntry(im->lenObj());
        xprintf("%d\n"
                "endobj\n", len);

        // ... image masks

        if (im->hasHardMask()) {
            addXrefEntry(im->hardMaskObj());
            QPdfImage* im2 = im->stencil;
            im2->setLenObj(requestObject());
            xprintf("%sstream\n", im2->getDefinition().constData());
            len = streampos_;
            streampos_ += (int)cs.write(im2->data(), im2->rawLength());
            len = streampos_-len;
            xprintf("endstream\n"
                    "endobj\n");
            addXrefEntry(im2->lenObj());
            xprintf("%d\n"
                    "endobj\n", len);
        }
        if (im->hasSoftMask()) {
            addXrefEntry(im->softMaskObj());
            QPdfImage* im2 = im->softmask;
            im2->setLenObj(requestObject());
            xprintf("%sstream\n", im2->getDefinition().constData());
            len = streampos_;
            streampos_ += (int)cs.write(im2->data(), im2->rawLength());
            len = streampos_-len;
            xprintf("endstream\n"
                    "endobj\n");
            addXrefEntry(im2->lenObj());
            xprintf("%d\n"
                    "endobj\n", len);
        }
    }


    // open page stream object

    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");
    QByteArray s;
    QPdfByteStream stream(&s);
    stream << "/GSa gs\n"
        "/CSp cs /CSp CS\n";
    QMatrix tmp(1.0, 0.0, // m11, m12
                0.0, -1.0, // m21, m22
                0.0, height_); // dx, dy
    if (!fullPage)
        tmp.translate(resolution/3, resolution/3);
    QPdfMatrix::streamMatrix(tmp, stream);
    curPage->streamText(stream);
    int len = streampos_;
    streampos_ += (int)cs.write(s.constData(), s.size());
    len = streampos_-len;
    xprintf("endstream\n"
            "endobj\n");

    addXrefEntry(pageStreamLength);
    xprintf("%d\n"
            "endobj\n",len);
}

void QPdfEnginePrivate::writeTail()
{
    flushPage();
    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 QPdfEnginePrivate::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;
}

#endif // QT_NO_PRINTER
