/****************************************************************************
**
** Copyright (C) 1992-2007 Trolltech ASA. All rights reserved.
**
** This file is part of the QtSVG 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://trolltech.com/products/qt/licenses/licensing/opensource/
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** In addition, as a special exception, Trolltech gives you certain
** additional rights. These rights are described in the Trolltech GPL
** Exception version 1.0, which can be found at
** http://www.trolltech.com/products/qt/gplexception/ and in the file
** GPL_EXCEPTION.txt in this package.
**
** In addition, as a special exception, Trolltech, as the sole copyright
** holder for Qt Designer, grants users of the Qt/Eclipse Integration
** plug-in the right for the Qt/Eclipse Integration to link to
** functionality provided by Qt Designer and its related libraries.
**
** Trolltech reserves all rights not expressly granted herein.
**
** Trolltech ASA (c) 2007
**
** 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 "qsvggenerator.h"
#include "qpainterpath.h"
#include "private/qpaintengine_p.h"
#include "private/qtextengine_p.h"
#include "qfile.h"
#include "qtextstream.h"
#include "qbuffer.h"
#include "qdebug.h"
static void translate_color(const QColor &color, QString *color_string,
QString *opacity_string)
{
Q_ASSERT(color_string);
Q_ASSERT(opacity_string);
*color_string =
QString::fromLatin1("#%1%2%3")
.arg(color.red(), 2, 16, QLatin1Char('0'))
.arg(color.green(), 2, 16, QLatin1Char('0'))
.arg(color.blue(), 2, 16, QLatin1Char('0'));
*opacity_string = QString::number(color.alphaF());
}
class QSvgPaintEnginePrivate : public QPaintEnginePrivate
{
public:
QSvgPaintEnginePrivate()
{
size = QSize(100, 100);
outputDevice = 0;
resolution = 72;
attributes.document_title = QLatin1String("Qt Svg Document");
attributes.document_description = QLatin1String("Generated with Qt");
attributes.font_family = QLatin1String("serif");
attributes.font_size = QLatin1String("10pt");
attributes.font_style = QLatin1String("normal");
attributes.font_weight = QLatin1String("normal");
afterFirstUpdate = false;
numGradients = 0;
}
QSize size;
QIODevice *outputDevice;
QTextStream *stream;
int resolution;
QString header;
QString defs;
QString body;
bool afterFirstUpdate;
QBrush brush;
QPen pen;
QMatrix matrix;
QFont font;
QString generateGradientName() {
++numGradients;
currentGradientName = QString::fromLatin1("gradient%1").arg(numGradients);
return currentGradientName;
}
QString currentGradientName;
int numGradients;
struct _attributes {
QString document_title;
QString document_description;
QString font_weight;
QString font_size;
QString font_family;
QString font_style;
QString stroke, strokeOpacity;
QString fill, fillOpacity;
} attributes;
};
static inline QPaintEngine::PaintEngineFeatures svgEngineFeatures()
{
return QPaintEngine::PaintEngineFeatures(
QPaintEngine::AllFeatures
& ~QPaintEngine::PerspectiveTransform
& ~QPaintEngine::ConicalGradientFill
& ~QPaintEngine::PorterDuff);
}
class QSvgPaintEngine : public QPaintEngine
{
Q_DECLARE_PRIVATE(QSvgPaintEngine)
public:
QSvgPaintEngine()
: QPaintEngine(*new QSvgPaintEnginePrivate,
svgEngineFeatures())
{
}
bool begin(QPaintDevice *device);
bool end();
void updateState(const QPaintEngineState &state);
void popGroup();
void drawPath(const QPainterPath &path);
void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr);
void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode);
void drawTextItem(const QPointF &pt, const QTextItem &item);
void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr,
Qt::ImageConversionFlag = Qt::AutoColor);
QPaintEngine::Type type() const { return QPaintEngine::SVG; }
QSize size() const { return d_func()->size; }
void setSize(const QSize &size) {
Q_ASSERT(!isActive());
d_func()->size = size;
}
QIODevice *outputDevice() const { return d_func()->outputDevice; }
void setOutputDevice(QIODevice *device) {
Q_ASSERT(!isActive());
d_func()->outputDevice = device;
}
int resolution() { return d_func()->resolution; }
void setResolution(int resolution) {
Q_ASSERT(!isActive());
d_func()->resolution = resolution;
}
void saveLinearGradientBrush(const QGradient *g)
{
QTextStream str(&d_func()->defs, QIODevice::Append);
str << QLatin1String("(g);
if (grad) {
str << QLatin1String("x1 = \"") <start().x()<< QLatin1String("\" ")
<< QLatin1String("y1 = \"") <start().y()<< QLatin1String("\" ")
<< QLatin1String("x2 = \"") <finalStop().x() << QLatin1String("\" ")
<< QLatin1String("y2 = \"") <finalStop().y() << QLatin1String("\" ");
}
str << QLatin1String("id=\"") << d_func()->generateGradientName() << QLatin1String("\">\n");
saveGradientStops(str, g);
str << QLatin1String("") <defs, QIODevice::Append);
str << QLatin1String("(g);
if (grad) {
str << QLatin1String("cx = \"") <center().x()<< QLatin1String("\" ")
<< QLatin1String("cy = \"") <center().y()<< QLatin1String("\" ")
<< QLatin1String("r = \"") <radius() << QLatin1String("\" ")
<< QLatin1String("fx = \"") <focalPoint().x() << QLatin1String("\" ")
<< QLatin1String("fy = \"") <focalPoint().y() << QLatin1String("\" ");
}
str << QLatin1String(" xml:id=\"") <generateGradientName()<< QLatin1String("\">\n");
saveGradientStops(str, g);
str << QLatin1String("") << endl;
}
void saveConicalGradientBrush(const QGradient *)
{
qWarning("svg's don't support conical gradients!");
}
void saveTextureBrush(const QBrush &)
{
qWarning("texture brushes not yet supported");
}
void saveGradientStops(QTextStream &str, const QGradient *g) {
QGradientStops stops = g->stops();
foreach(QGradientStop stop, stops) {
QString color =
QString::fromLatin1("#%1%2%3")
.arg(stop.second.red(), 2, 16, QLatin1Char('0'))
.arg(stop.second.green(), 2, 16, QLatin1Char('0'))
.arg(stop.second.blue(), 2, 16, QLatin1Char('0'));
str << QLatin1String(" \n");
}
}
void generateQtDefaults()
{
*d_func()->stream << QLatin1String("fill=\"none\" ");
*d_func()->stream << QLatin1String("stroke=\"black\" ");
*d_func()->stream << QLatin1String("vector-effect=\"non-scaling-stroke\" ");
*d_func()->stream << QLatin1String("stroke-width=\"1\" ");
*d_func()->stream << QLatin1String("fill-rule=\"evenodd\" ");
*d_func()->stream << QLatin1String("stroke-linecap=\"square\" ");
*d_func()->stream << QLatin1String("stroke-linejoin=\"bevel\" ");
*d_func()->stream << QLatin1String(">\n");
}
inline QTextStream &stream()
{
return *d_func()->stream;
}
void qpenToSvg(const QPen &spen)
{
QString width;
d_func()->pen = spen;
switch (spen.style()) {
case Qt::NoPen:
stream() << QLatin1String("stroke=\"none\" ");
d_func()->attributes.stroke = QLatin1String("none");
d_func()->attributes.strokeOpacity = QString();
return;
break;
case Qt::SolidLine: {
QString color, colorOpacity;
translate_color(spen.color(), &color,
&colorOpacity);
d_func()->attributes.stroke = color;
d_func()->attributes.strokeOpacity = colorOpacity;
stream() << QLatin1String("stroke=\"")<brush = sbrush;
switch (sbrush.style()) {
case Qt::SolidPattern: {
QString color, colorOpacity;
translate_color(sbrush.color(), &color, &colorOpacity);
stream() << "fill=\"" << color << "\" ";
stream() << "fill-opacity=\""
<< colorOpacity << "\" ";
d_func()->attributes.fill = color;
d_func()->attributes.fillOpacity = colorOpacity;
}
break;
case Qt::LinearGradientPattern:
saveLinearGradientBrush(sbrush.gradient());
d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName);
d_func()->attributes.fillOpacity = QString();
stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
break;
case Qt::RadialGradientPattern:
saveRadialGradientBrush(sbrush.gradient());
d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName);
d_func()->attributes.fillOpacity = QString();
stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
break;
case Qt::ConicalGradientPattern:
saveConicalGradientBrush(sbrush.gradient());
d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName);
d_func()->attributes.fillOpacity = QString();
stream() << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
break;
case Qt::TexturePattern:
saveTextureBrush(sbrush);
break;
case Qt::NoBrush:
stream() << QLatin1String("fill=\"none\" ");
d_func()->attributes.fill = QLatin1String("none");
d_func()->attributes.fillOpacity = QString();
return;
break;
default:
qWarning("unhandled brush style");
break;
}
}
void qfontToSvg(const QFont &sfont)
{
Q_D(QSvgPaintEngine);
d->font = sfont;
d->attributes.font_size = QString::number(d->font.pointSize()) + QLatin1String("pt");
int svgWeight = d->font.weight();
switch (svgWeight) {
case QFont::Light:
svgWeight = 100;
break;
case QFont::Normal:
svgWeight = 400;
break;
case QFont::Bold:
svgWeight = 700;
break;
default:
svgWeight *= 10;
}
d->attributes.font_weight = QString::number(svgWeight);
d->attributes.font_family = d->font.family();
d->attributes.font_style = d->font.italic() ? QLatin1String("italic") : QLatin1String("normal");
*d->stream << "font-family=\"" << d->attributes.font_family << "\" "
<< "font-size=\"" << d->attributes.font_size << "\" "
<< "font-weight=\"" << d->attributes.font_weight << "\" "
<< "font-style=\"" << d->attributes.font_style << "\" "
<< endl;
}
};
class QSvgGeneratorPrivate
{
public:
QSvgPaintEngine *engine;
uint owns_iodevice : 1;
QString fileName;
};
/*!
\class QSvgGenerator
\ingroup multimedia
\since 4.3
\brief The QSvgGenerator class provides a paint device that is used to create SVG drawings.
\sa QSvgRenderer, QSvgWidget
*/
/*!
Constructs a new generator.
*/
QSvgGenerator::QSvgGenerator()
: d_ptr(new QSvgGeneratorPrivate)
{
Q_D(QSvgGenerator);
d->engine = new QSvgPaintEngine;
d->owns_iodevice = false;
}
/*!
Destroys the generator.
*/
QSvgGenerator::~QSvgGenerator()
{
Q_D(QSvgGenerator);
if (d->owns_iodevice)
delete d->engine->outputDevice();
delete d->engine;
}
/*!
Returns the size of the generated SVG.
*/
QSize QSvgGenerator::size() const
{
return d_func()->engine->size();
}
/*!
Sets the size of the generated SVG to \a size.
It is not possible to set the size while the SVG is being generated.
*/
void QSvgGenerator::setSize(const QSize &size)
{
Q_D(QSvgGenerator);
if (d->engine->isActive()) {
qWarning("QSvgGenerator::setSize(), cannot set size while svg is being generated");
return;
}
d->engine->setSize(size);
}
/*!
Sets the target filename for generated SVGs to \a fileName.
\sa setOutputDevice()
*/
void QSvgGenerator::setFileName(const QString &fileName)
{
Q_D(QSvgGenerator);
if (d->engine->isActive()) {
qWarning("QSvgGenerator::setFileName(), cannot set fileName svg is being generated");
return;
}
if (d->owns_iodevice)
delete d->engine->outputDevice();
d->owns_iodevice = true;
d->fileName = fileName;
QFile *file = new QFile(fileName);
d->engine->setOutputDevice(file);
}
/*!
Returns the target output device for generated SVGs.
*/
QIODevice *QSvgGenerator::outputDevice() const
{
Q_D(const QSvgGenerator);
return d->engine->outputDevice();
}
/*!
Sets the output device for generated SVGs to \a outputDevice.
If both output device and file name are specified, the output device
will have precedence.
*/
void QSvgGenerator::setOutputDevice(QIODevice *outputDevice)
{
Q_D(QSvgGenerator);
if (d->engine->isActive()) {
qWarning("QSvgGenerator::setOutputDevice(), cannot set output device svg is being generated");
return;
}
d->owns_iodevice = false;
d->engine->setOutputDevice(outputDevice);
d->fileName = QString();
}
/*!
Returns the paint engine used to render graphics to be converted to SVG
format information.
*/
QPaintEngine *QSvgGenerator::paintEngine() const
{
Q_D(const QSvgGenerator);
return d->engine;
}
/*!
\reimp
*/
int QSvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric) const
{
Q_D(const QSvgGenerator);
switch (metric) {
case QPaintDevice::PdmDepth:
return 32;
case QPaintDevice::PdmWidth:
return d->engine->size().width();
case QPaintDevice::PdmHeight:
return d->engine->size().height();
case QPaintDevice::PdmDpiX:
return d->engine->resolution();
case QPaintDevice::PdmDpiY:
return d->engine->resolution();
case QPaintDevice::PdmHeightMM:
return int(d->engine->size().height() / d->engine->resolution() * 2.54);
case QPaintDevice::PdmWidthMM:
return int(d->engine->size().width() / d->engine->resolution() * 2.54);
case QPaintDevice::PdmNumColors:
return 0xffffffff;
case QPaintDevice::PdmPhysicalDpiX:
return d->engine->resolution();
case QPaintDevice::PdmPhysicalDpiY:
return d->engine->resolution();
default:
qWarning("QSvgGenerator::metric(), unhandled metric %d\n", metric);
break;
}
return 0;
}
/*!
\fn QString QSvgGenerator::fileName() const
Returns the name of the file to be created by the generator.
*/
QString QSvgGenerator::fileName() const
{
Q_D(const QSvgGenerator);
return d->fileName;
}
/*!
\fn void QSvgGenerator::setResolution(int resolution)
Sets the resolution of the generated output to \a resolution.
The argument is specified in dots per inch.
The resolution is used to calculate the physical size of
an SVG drawing.
*/
void QSvgGenerator::setResolution(int dpi)
{
Q_D(QSvgGenerator);
d->engine->setResolution(dpi);
}
/*!
Returns the resolution of the generated output in dots per inch.
*/
int QSvgGenerator::resolution() const
{
Q_D(const QSvgGenerator);
return d->engine->resolution();
}
/*****************************************************************************
* class QSvgPaintEngine
*/
bool QSvgPaintEngine::begin(QPaintDevice *)
{
Q_D(QSvgPaintEngine);
if (!d->outputDevice) {
qWarning("QSvgPaintEngine::begin(), no output device");
return false;
}
if (!d->outputDevice->isOpen() &&
!d->outputDevice->open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning("QSvgPaintEngine::begin(), could not open output device: '%s'",
qPrintable(d->outputDevice->errorString()));
return false;
}
d->stream = new QTextStream(&d->header);
int w = d->size.width();
int h = d->size.height();
qreal wmm = w * 2.54 / d->resolution;
qreal hmm = h * 2.54 / d->resolution;
// stream out the header...
*d->stream << "" << endl;
*d->stream << "" << endl;
delete d->stream;
return true;
}
void QSvgPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm,
const QRectF &sr)
{
drawImage(r, pm.toImage(), sr);
}
void QSvgPaintEngine::drawImage(const QRectF &r, const QImage &image,
const QRectF &sr,
Qt::ImageConversionFlag flags)
{
//Q_D(QSvgPaintEngine);
Q_UNUSED(sr);
Q_UNUSED(flags);
stream() << "\n";
}
void QSvgPaintEngine::updateState(const QPaintEngineState &state)
{
Q_D(QSvgPaintEngine);
QPaintEngine::DirtyFlags flags = state.state();
// always stream full gstate, which is not required, but...
flags |= QPaintEngine::AllDirty;
// close old state and start a new one...
if (d->afterFirstUpdate)
*d->stream << "\n\n";
*d->stream << "matrix = state.matrix();
*d->stream << "transform=\"matrix(" << d->matrix.m11() << ","
<< d->matrix.m12() << ","
<< d->matrix.m21() << "," << d->matrix.m22() << ","
<< d->matrix.dx() << "," << d->matrix.dy()
<< ")\""
<< endl;
}
if (flags & QPaintEngine::DirtyFont) {
qfontToSvg(state.font());
}
if (flags & QPaintEngine::DirtyOpacity) {
if (!qFuzzyCompare(state.opacity(), 1))
stream() << "opacity=\""<stream << ">" << endl;
d->afterFirstUpdate = true;
}
void QSvgPaintEngine::drawPath(const QPainterPath &p)
{
Q_D(QSvgPaintEngine);
*d->stream << "stream << "fill-rule=";
if (p.fillRule() == Qt::OddEvenFill)
*d->stream << "\"evenodd\" ";
else
*d->stream << "\"nonzero\" ";
*d->stream << "d=\"";
for (int i=0; istream << "M" << e.x << "," << e.y;
break;
case QPainterPath::LineToElement:
*d->stream << "L" << e.x << "," << e.y;
break;
case QPainterPath::CurveToElement:
*d->stream << "C" << e.x << "," << e.y;
++i;
while (i < p.elementCount()) {
const QPainterPath::Element &e = p.elementAt(i);
if (e.type != QPainterPath::CurveToDataElement) {
--i;
break;
} else
*d->stream << " ";
*d->stream << e.x << "," << e.y;
++i;
}
break;
default:
break;
}
if (i != p.elementCount() - 1) {
*d->stream << " ";
}
}
*d->stream << "\"/>" << endl;
}
void QSvgPaintEngine::drawPolygon(const QPointF *points, int pointCount,
PolygonDrawMode mode)
{
Q_ASSERT(pointCount >= 2);
//Q_D(QSvgPaintEngine);
QPainterPath path(points[0]);
for (int i=1; i" <pen.style() == Qt::NoPen)
return;
const QTextItemInt &ti = static_cast(textItem);
QString s = QString::fromRawData(ti.chars, ti.num_chars);
*d->stream << "attributes.stroke << "\" "
<< "fill-opacity=\"" << d->attributes.strokeOpacity << "\" "
<< "stroke=\"none\" "
<< "x=\"" << pt.x() << "\" y=\"" << pt.y() << "\" ";
qfontToSvg(textItem.font());
*d->stream << " >"
<< Qt::escape(s)
<< ""
<< endl;
}