/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtGui module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include <QtGui/qprintengine.h> | |
#include <qiodevice.h> | |
#include <qpainter.h> | |
#include <qbitmap.h> | |
#include <qpainterpath.h> | |
#include <qpaintdevice.h> | |
#include <qfile.h> | |
#include <qdebug.h> | |
#include <qimagewriter.h> | |
#include <qbuffer.h> | |
#include <qdatetime.h> | |
#ifndef QT_NO_PRINTER | |
#include <limits.h> | |
#include <math.h> | |
#ifndef QT_NO_COMPRESS | |
#include <zlib.h> | |
#endif | |
#if defined(Q_OS_WINCE) | |
#include "qwinfunctions_wince.h" | |
#endif | |
#include "qprintengine_pdf_p.h" | |
#include "private/qdrawhelper_p.h" | |
QT_BEGIN_NAMESPACE | |
extern qint64 qt_pixmap_id(const QPixmap &pixmap); | |
extern qint64 qt_image_id(const QImage &image); | |
//#define FONT_DUMP | |
// 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; | |
#ifdef QT_NO_COMPRESS | |
static const bool do_compress = false; | |
#else | |
static const bool do_compress = true; | |
#endif | |
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); | |
} | |
inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features() | |
{ | |
QPaintEngine::PaintEngineFeatures f = QPaintEngine::AllFeatures; | |
f &= ~(QPaintEngine::PorterDuff | QPaintEngine::PerspectiveTransform | |
| QPaintEngine::ObjectBoundingModeGradients | |
#ifndef USE_NATIVE_GRADIENTS | |
| QPaintEngine::LinearGradientFill | |
#endif | |
| QPaintEngine::RadialGradientFill | |
| QPaintEngine::ConicalGradientFill); | |
return f; | |
} | |
QPdfEngine::QPdfEngine(QPrinter::PrinterMode m) | |
: QPdfBaseEngine(*new QPdfEnginePrivate(m), qt_pdf_decide_features()) | |
{ | |
state = QPrinter::Idle; | |
} | |
QPdfEngine::~QPdfEngine() | |
{ | |
} | |
bool QPdfEngine::begin(QPaintDevice *pdev) | |
{ | |
Q_D(QPdfEngine); | |
if(!QPdfBaseEngine::begin(pdev)) { | |
state = QPrinter::Error; | |
return false; | |
} | |
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); | |
state = QPrinter::Active; | |
d->writeHeader(); | |
newPage(); | |
return true; | |
} | |
bool QPdfEngine::end() | |
{ | |
Q_D(QPdfEngine); | |
d->writeTail(); | |
d->stream->unsetDevice(); | |
QPdfBaseEngine::end(); | |
setActive(false); | |
state = QPrinter::Idle; | |
return true; | |
} | |
void QPdfEngine::drawPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QRectF &sr) | |
{ | |
if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull()) | |
return; | |
Q_D(QPdfEngine); | |
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 QPdfEngine::drawImage(const QRectF &rectangle, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags) | |
{ | |
if (sr.isEmpty() || rectangle.isEmpty() || image.isNull()) | |
return; | |
Q_D(QPdfEngine); | |
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 QPdfEngine::drawTiledPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QPointF &point) | |
{ | |
Q_D(QPdfEngine); | |
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 QPdfEngine::setBrush() | |
{ | |
Q_D(QPdfEngine); | |
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"; | |
} | |
QPaintEngine::Type QPdfEngine::type() const | |
{ | |
return QPaintEngine::Pdf; | |
} | |
bool QPdfEngine::newPage() | |
{ | |
Q_D(QPdfEngine); | |
if (!isActive()) | |
return false; | |
d->newPage(); | |
return QPdfBaseEngine::newPage(); | |
} | |
QPdfEnginePrivate::QPdfEnginePrivate(QPrinter::PrinterMode m) | |
: QPdfBaseEnginePrivate(m) | |
{ | |
streampos = 0; | |
stream = new QDataStream; | |
pageOrder = QPrinter::FirstPageFirst; | |
orientation = QPrinter::Portrait; | |
fullPage = false; | |
} | |
QPdfEnginePrivate::~QPdfEnginePrivate() | |
{ | |
delete stream; | |
} | |
#ifdef USE_NATIVE_GRADIENTS | |
int QPdfEnginePrivate::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 (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); | |
} | |
} | |
return patternObj; | |
} | |
#endif | |
int QPdfEnginePrivate::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; | |
} | |
int QPdfEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor, int *gStateObject) | |
{ | |
int paintType = 2; // Uncolored tiling | |
int w = 8; | |
int h = 8; | |
*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, qt_pixmap_id(brush.texture())); | |
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; | |
} | |
/*! | |
* Adds an image to the pdf and return the pdf-object id. Returns -1 if adding the image failed. | |
*/ | |
int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, qint64 serial_no) | |
{ | |
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() == 0) { | |
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 QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) | |
{ | |
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); | |
} | |
} | |
QPdfBaseEnginePrivate::drawTextItem(p, ti); | |
} | |
QTransform QPdfEnginePrivate::pageMatrix() const | |
{ | |
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()); | |
} | |
return tmp; | |
} | |
void QPdfEnginePrivate::newPage() | |
{ | |
if (currentPage && currentPage->pageSize.isEmpty()) | |
currentPage->pageSize = QSize(width(), height()); | |
writePage(); | |
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"; | |
} | |
// For strings up to 10000 bytes only ! | |
void QPdfEnginePrivate::xprintf(const char* fmt, ...) | |
{ | |
if (!stream) | |
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 QPdfEnginePrivate::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; | |
} | |
} | |
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); | |
::deflateEnd(&zStruct); | |
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; | |
} | |
} | |
int QPdfEnginePrivate::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; | |
} | |
int QPdfEnginePrivate::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; | |
} | |
void QPdfEnginePrivate::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 QPdfEnginePrivate::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) 2010 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 QPdfEnginePrivate::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 QPdfEnginePrivate::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 | |
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; | |
} | |
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); | |
} | |
} | |
void QPdfEnginePrivate::writeFonts() | |
{ | |
for (QHash<QFontEngine::FaceId, QFontSubset *>::iterator it = fonts.begin(); it != fonts.end(); ++it) { | |
embedFont(*it); | |
delete *it; | |
} | |
fonts.clear(); | |
} | |
void QPdfEnginePrivate::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 QPdfEnginePrivate::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 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; | |
} | |
void QPdfEnginePrivate::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); | |
} | |
QT_END_NAMESPACE | |
#endif // QT_NO_PRINTER |