MuseScore/mscore/svggenerator.cpp
2012-05-26 14:49:10 +02:00

1090 lines
34 KiB
C++

/****************************************************************************
**
** 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 QtSvg 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 "svggenerator.h"
#include "paintengine_p.h"
#if QT_POINTER_SIZE == 8 // 64-bit versions
static uint INTERPOLATE_PIXEL_256(uint x, uint a, uint y, uint b) {
quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a;
t += (((quint64(y)) | ((quint64(y)) << 24)) & 0x00ff00ff00ff00ff) * b;
t >>= 8;
t &= 0x00ff00ff00ff00ff;
return (uint(t)) | (uint(t >> 24));
}
static uint PREMUL(uint x) {
uint a = x >> 24;
quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a;
t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8;
t &= 0x000000ff00ff00ff;
return (uint(t)) | (uint(t >> 24)) | (a << 24);
}
#else // 32-bit versions
static uint INTERPOLATE_PIXEL_256(uint x, uint a, uint y, uint b) {
uint t = (x & 0xff00ff) * a + (y & 0xff00ff) * b;
t >>= 8;
t &= 0xff00ff;
x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b;
x &= 0xff00ff00;
x |= t;
return x;
}
#if defined(Q_CC_RVCT)
# pragma push
# pragma arm
#endif
#if defined(Q_CC_RVCT)
# pragma pop
#endif
static uint PREMUL(uint x) {
uint a = x >> 24;
uint t = (x & 0xff00ff) * a;
t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8;
t &= 0xff00ff;
x = ((x >> 8) & 0xff) * a;
x = (x + ((x >> 8) & 0xff) + 0x80);
x &= 0xff00;
x |= t | (a << 24);
return x;
}
#endif
#define INV_PREMUL(p) \
(qAlpha(p) == 0 ? 0 : \
((qAlpha(p) << 24) \
| (((255*qRed(p))/ qAlpha(p)) << 16) \
| (((255*qGreen(p)) / qAlpha(p)) << 8) \
| ((255*qBlue(p)) / qAlpha(p))))
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());
}
static void translate_dashPattern(QVector<qreal> pattern, const qreal& width, QString *pattern_string)
{
Q_ASSERT(pattern_string);
// Note that SVG operates in absolute lengths, whereas Qt uses a length/width ratio.
foreach (qreal entry, pattern)
*pattern_string += QString::fromLatin1("%1,").arg(entry * width);
pattern_string->chop(1);
}
class SvgPaintEnginePrivate : public QPaintEnginePrivate
{
public:
SvgPaintEnginePrivate()
{
size = QSize();
viewBox = QRectF();
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;
QRectF viewBox;
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 dashPattern, dashOffset;
QString fill, fillOpacity;
} attributes;
};
static inline QPaintEngine::PaintEngineFeatures svgEngineFeatures()
{
return QPaintEngine::PaintEngineFeatures(
QPaintEngine::AllFeatures
& ~QPaintEngine::PatternBrush
& ~QPaintEngine::PerspectiveTransform
& ~QPaintEngine::ConicalGradientFill
& ~QPaintEngine::PorterDuff);
}
class SvgPaintEngine : public QPaintEngine
{
Q_DECLARE_PRIVATE(SvgPaintEngine)
public:
SvgPaintEngine()
: QPaintEngine(*new SvgPaintEnginePrivate,
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 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;
}
QRectF viewBox() const { return d_func()->viewBox; }
void setViewBox(const QRectF &viewBox) {
Q_ASSERT(!isActive());
d_func()->viewBox = viewBox;
}
QString documentTitle() const { return d_func()->attributes.document_title; }
void setDocumentTitle(const QString &title) {
d_func()->attributes.document_title = title;
}
QString documentDescription() const { return d_func()->attributes.document_description; }
void setDocumentDescription(const QString &description) {
d_func()->attributes.document_description = description;
}
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);
const QLinearGradient *grad = static_cast<const QLinearGradient*>(g);
str << QLatin1String("<linearGradient ");
saveGradientUnits(str, g);
if (grad) {
str << QLatin1String("x1=\"") <<grad->start().x()<< QLatin1String("\" ")
<< QLatin1String("y1=\"") <<grad->start().y()<< QLatin1String("\" ")
<< QLatin1String("x2=\"") <<grad->finalStop().x() << QLatin1String("\" ")
<< QLatin1String("y2=\"") <<grad->finalStop().y() << QLatin1String("\" ");
}
str << QLatin1String("id=\"") << d_func()->generateGradientName() << QLatin1String("\">\n");
saveGradientStops(str, g);
str << QLatin1String("</linearGradient>") <<endl;
}
void saveRadialGradientBrush(const QGradient *g)
{
QTextStream str(&d_func()->defs, QIODevice::Append);
const QRadialGradient *grad = static_cast<const QRadialGradient*>(g);
str << QLatin1String("<radialGradient ");
saveGradientUnits(str, g);
if (grad) {
str << QLatin1String("cx=\"") <<grad->center().x()<< QLatin1String("\" ")
<< QLatin1String("cy=\"") <<grad->center().y()<< QLatin1String("\" ")
<< QLatin1String("r=\"") <<grad->radius() << QLatin1String("\" ")
<< QLatin1String("fx=\"") <<grad->focalPoint().x() << QLatin1String("\" ")
<< QLatin1String("fy=\"") <<grad->focalPoint().y() << QLatin1String("\" ");
}
str << QLatin1String("xml:id=\"") <<d_func()->generateGradientName()<< QLatin1String("\">\n");
saveGradientStops(str, g);
str << QLatin1String("</radialGradient>") << endl;
}
void saveConicalGradientBrush(const QGradient *)
{
qWarning("svg's don't support conical gradients!");
}
void saveGradientStops(QTextStream &str, const QGradient *g) {
QGradientStops stops = g->stops();
if (g->interpolationMode() == QGradient::ColorInterpolation) {
bool constantAlpha = true;
int alpha = stops.at(0).second.alpha();
for (int i = 1; i < stops.size(); ++i)
constantAlpha &= (stops.at(i).second.alpha() == alpha);
if (!constantAlpha) {
const qreal spacing = qreal(0.02);
QGradientStops newStops;
QRgb fromColor = PREMUL(stops.at(0).second.rgba());
QRgb toColor;
for (int i = 0; i + 1 < stops.size(); ++i) {
int parts = qCeil((stops.at(i + 1).first - stops.at(i).first) / spacing);
newStops.append(stops.at(i));
toColor = PREMUL(stops.at(i + 1).second.rgba());
if (parts > 1) {
qreal step = (stops.at(i + 1).first - stops.at(i).first) / parts;
for (int j = 1; j < parts; ++j) {
QRgb color = INV_PREMUL(INTERPOLATE_PIXEL_256(fromColor, 256 - 256 * j / parts, toColor, 256 * j / parts));
newStops.append(QGradientStop(stops.at(i).first + j * step, QColor::fromRgba(color)));
}
}
fromColor = toColor;
}
newStops.append(stops.back());
stops = newStops;
}
}
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(" <stop offset=\"")<< stop.first << QLatin1String("\" ")
<< QLatin1String("stop-color=\"") << color << QLatin1String("\" ")
<< QLatin1String("stop-opacity=\"") << stop.second.alphaF() <<QLatin1String("\" />\n");
}
}
void saveGradientUnits(QTextStream &str, const QGradient *gradient)
{
str << QLatin1String("gradientUnits=\"");
if (gradient && gradient->coordinateMode() == QGradient::ObjectBoundingMode)
str << QLatin1String("objectBoundingBox");
else
str << QLatin1String("userSpaceOnUse");
str << QLatin1String("\" ");
}
void generateQtDefaults()
{
*d_func()->stream << QLatin1String("fill=\"none\" ");
*d_func()->stream << QLatin1String("stroke=\"black\" ");
*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=\"")<<color<< QLatin1String("\" ");
stream() << QLatin1String("stroke-opacity=\"")<<colorOpacity<< QLatin1String("\" ");
}
break;
case Qt::DashLine:
case Qt::DotLine:
case Qt::DashDotLine:
case Qt::DashDotDotLine:
case Qt::CustomDashLine: {
QString color, colorOpacity, dashPattern, dashOffset;
qreal penWidth = spen.width() == 0 ? qreal(1) : spen.widthF();
translate_color(spen.color(), &color, &colorOpacity);
translate_dashPattern(spen.dashPattern(), penWidth, &dashPattern);
// SVG uses absolute offset
dashOffset = QString::number(spen.dashOffset() * penWidth);
d_func()->attributes.stroke = color;
d_func()->attributes.strokeOpacity = colorOpacity;
d_func()->attributes.dashPattern = dashPattern;
d_func()->attributes.dashOffset = dashOffset;
stream() << QLatin1String("stroke=\"")<<color<< QLatin1String("\" ");
stream() << QLatin1String("stroke-opacity=\"")<<colorOpacity<< QLatin1String("\" ");
stream() << QLatin1String("stroke-dasharray=\"")<<dashPattern<< QLatin1String("\" ");
stream() << QLatin1String("stroke-dashoffset=\"")<<dashOffset<< QLatin1String("\" ");
break;
}
default:
qWarning("Unsupported pen style");
break;
}
if (spen.widthF() == 0)
stream() <<"stroke-width=\"1\" ";
else
stream() <<"stroke-width=\"" << spen.widthF() << "\" ";
switch (spen.capStyle()) {
case Qt::FlatCap:
stream() << "stroke-linecap=\"butt\" ";
break;
case Qt::SquareCap:
stream() << "stroke-linecap=\"square\" ";
break;
case Qt::RoundCap:
stream() << "stroke-linecap=\"round\" ";
break;
default:
qWarning("Unhandled cap style");
}
switch (spen.joinStyle()) {
case Qt::MiterJoin:
stream() << "stroke-linejoin=\"miter\" "
"stroke-miterlimit=\""<<spen.miterLimit()<<"\" ";
break;
case Qt::BevelJoin:
stream() << "stroke-linejoin=\"bevel\" ";
break;
case Qt::RoundJoin:
stream() << "stroke-linejoin=\"round\" ";
break;
case Qt::SvgMiterJoin:
stream() << "stroke-linejoin=\"miter\" "
"stroke-miterlimit=\""<<spen.miterLimit()<<"\" ";
break;
default:
qWarning("Unhandled join style");
}
}
void qbrushToSvg(const QBrush &sbrush)
{
d_func()->brush = sbrush;
switch (sbrush.style()) {
case Qt::SolidPattern: {
QString color, colorOpacity;
translate_color(sbrush.color(), &color, &colorOpacity);
stream() << "fill=\"" << color << "\" "
"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::NoBrush:
stream() << QLatin1String("fill=\"none\" ");
d_func()->attributes.fill = QLatin1String("none");
d_func()->attributes.fillOpacity = QString();
return;
break;
default:
break;
}
}
void qfontToSvg(const QFont &sfont)
{
Q_D(SvgPaintEngine);
d->font = sfont;
if (d->font.pixelSize() == -1)
d->attributes.font_size = QString::number(d->font.pointSizeF() * d->resolution / 72);
else
d->attributes.font_size = QString::number(d->font.pixelSize());
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 SvgGeneratorPrivate
{
public:
SvgPaintEngine *engine;
uint owns_iodevice : 1;
QString fileName;
};
/*!
\class SvgGenerator
\ingroup painting
\since 4.3
\brief The SvgGenerator class provides a paint device that is used to create SVG drawings.
\reentrant
This paint device represents a Scalable Vector Graphics (SVG) drawing. Like QPrinter, it is
designed as a write-only device that generates output in a specific format.
To write an SVG file, you first need to configure the output by setting the \l fileName
or \l outputDevice properties. It is usually necessary to specify the size of the drawing
by setting the \l size property, and in some cases where the drawing will be included in
another, the \l viewBox property also needs to be set.
\snippet examples/painting/svggenerator/window.cpp configure SVG generator
Other meta-data can be specified by setting the \a title, \a description and \a resolution
properties.
As with other QPaintDevice subclasses, a QPainter object is used to paint onto an instance
of this class:
\snippet examples/painting/svggenerator/window.cpp begin painting
\dots
\snippet examples/painting/svggenerator/window.cpp end painting
Painting is performed in the same way as for any other paint device. However,
it is necessary to use the QPainter::begin() and \l{QPainter::}{end()} to
explicitly begin and end painting on the device.
The \l{SVG Generator Example} shows how the same painting commands can be used
for painting a widget and writing an SVG file.
\sa SvgRenderer, SvgWidget, {About SVG}
*/
/*!
Constructs a new generator.
*/
SvgGenerator::SvgGenerator()
: d_ptr(new SvgGeneratorPrivate)
{
Q_D(SvgGenerator);
d->engine = new SvgPaintEngine;
d->owns_iodevice = false;
}
/*!
Destroys the generator.
*/
SvgGenerator::~SvgGenerator()
{
Q_D(SvgGenerator);
if (d->owns_iodevice)
delete d->engine->outputDevice();
delete d->engine;
}
/*!
\property SvgGenerator::title
\brief the title of the generated SVG drawing
\since 4.5
\sa description
*/
QString SvgGenerator::title() const
{
Q_D(const SvgGenerator);
return d->engine->documentTitle();
}
void SvgGenerator::setTitle(const QString &title)
{
Q_D(SvgGenerator);
d->engine->setDocumentTitle(title);
}
/*!
\property SvgGenerator::description
\brief the description of the generated SVG drawing
\since 4.5
\sa title
*/
QString SvgGenerator::description() const
{
Q_D(const SvgGenerator);
return d->engine->documentDescription();
}
void SvgGenerator::setDescription(const QString &description)
{
Q_D(SvgGenerator);
d->engine->setDocumentDescription(description);
}
/*!
\property SvgGenerator::size
\brief the size of the generated SVG drawing
\since 4.5
By default this property is set to \c{QSize(-1, -1)}, which
indicates that the generator should not output the width and
height attributes of the \c<svg> element.
\note It is not possible to change this property while a
QPainter is active on the generator.
\sa viewBox, resolution
*/
QSize SvgGenerator::size() const
{
Q_D(const SvgGenerator);
return d->engine->size();
}
void SvgGenerator::setSize(const QSize &size)
{
Q_D(SvgGenerator);
if (d->engine->isActive()) {
qWarning("SvgGenerator::setSize(), cannot set size while SVG is being generated");
return;
}
d->engine->setSize(size);
}
/*!
\property SvgGenerator::viewBox
\brief the viewBox of the generated SVG drawing
\since 4.5
By default this property is set to \c{QRect(0, 0, -1, -1)}, which
indicates that the generator should not output the viewBox attribute
of the \c<svg> element.
\note It is not possible to change this property while a
QPainter is active on the generator.
\sa viewBox(), size, resolution
*/
QRectF SvgGenerator::viewBoxF() const
{
Q_D(const SvgGenerator);
return d->engine->viewBox();
}
/*!
\since 4.5
Returns viewBoxF().toRect().
\sa viewBoxF()
*/
QRect SvgGenerator::viewBox() const
{
Q_D(const SvgGenerator);
return d->engine->viewBox().toRect();
}
void SvgGenerator::setViewBox(const QRectF &viewBox)
{
Q_D(SvgGenerator);
if (d->engine->isActive()) {
qWarning("SvgGenerator::setViewBox(), cannot set viewBox while SVG is being generated");
return;
}
d->engine->setViewBox(viewBox);
}
void SvgGenerator::setViewBox(const QRect &viewBox)
{
setViewBox(QRectF(viewBox));
}
/*!
\property SvgGenerator::fileName
\brief the target filename for the generated SVG drawing
\since 4.5
\sa outputDevice
*/
QString SvgGenerator::fileName() const
{
Q_D(const SvgGenerator);
return d->fileName;
}
void SvgGenerator::setFileName(const QString &fileName)
{
Q_D(SvgGenerator);
if (d->engine->isActive()) {
qWarning("SvgGenerator::setFileName(), cannot set file name while 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);
}
/*!
\property SvgGenerator::outputDevice
\brief the output device for the generated SVG drawing
\since 4.5
If both output device and file name are specified, the output device
will have precedence.
\sa fileName
*/
QIODevice *SvgGenerator::outputDevice() const
{
Q_D(const SvgGenerator);
return d->engine->outputDevice();
}
void SvgGenerator::setOutputDevice(QIODevice *outputDevice)
{
Q_D(SvgGenerator);
if (d->engine->isActive()) {
qWarning("SvgGenerator::setOutputDevice(), cannot set output device while SVG is being generated");
return;
}
d->owns_iodevice = false;
d->engine->setOutputDevice(outputDevice);
d->fileName = QString();
}
/*!
\property SvgGenerator::resolution
\brief the resolution of the generated output
\since 4.5
The resolution is specified in dots per inch, and is used to
calculate the physical size of an SVG drawing.
\sa size, viewBox
*/
int SvgGenerator::resolution() const
{
Q_D(const SvgGenerator);
return d->engine->resolution();
}
void SvgGenerator::setResolution(int dpi)
{
Q_D(SvgGenerator);
d->engine->setResolution(dpi);
}
/*!
Returns the paint engine used to render graphics to be converted to SVG
format information.
*/
QPaintEngine *SvgGenerator::paintEngine() const
{
Q_D(const SvgGenerator);
return d->engine;
}
/*!
\reimp
*/
int SvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric) const
{
Q_D(const SvgGenerator);
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 qRound(d->engine->size().height() * 25.4 / d->engine->resolution());
case QPaintDevice::PdmWidthMM:
return qRound(d->engine->size().width() * 25.4 / d->engine->resolution());
case QPaintDevice::PdmNumColors:
return 0xffffffff;
case QPaintDevice::PdmPhysicalDpiX:
return d->engine->resolution();
case QPaintDevice::PdmPhysicalDpiY:
return d->engine->resolution();
default:
qWarning("SvgGenerator::metric(), unhandled metric %d\n", metric);
break;
}
return 0;
}
/*****************************************************************************
* class SvgPaintEngine
*/
bool SvgPaintEngine::begin(QPaintDevice *)
{
Q_D(SvgPaintEngine);
if (!d->outputDevice) {
qWarning("SvgPaintEngine::begin(), no output device");
return false;
}
if (!d->outputDevice->isOpen()) {
if (!d->outputDevice->open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning("SvgPaintEngine::begin(), could not open output device: '%s'",
qPrintable(d->outputDevice->errorString()));
return false;
}
} else if (!d->outputDevice->isWritable()) {
qWarning("SvgPaintEngine::begin(), could not write to read-only output device: '%s'",
qPrintable(d->outputDevice->errorString()));
return false;
}
d->stream = new QTextStream(&d->header);
// stream out the header...
*d->stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << endl << "<svg";
if (d->size.isValid()) {
qreal wmm = d->size.width() * 25.4 / d->resolution;
qreal hmm = d->size.height() * 25.4 / d->resolution;
*d->stream << " width=\"" << wmm << "mm\" height=\"" << hmm << "mm\"" << endl;
}
if (d->viewBox.isValid()) {
*d->stream << " viewBox=\"" << d->viewBox.left() << ' ' << d->viewBox.top();
*d->stream << ' ' << d->viewBox.width() << ' ' << d->viewBox.height() << '\"' << endl;
}
*d->stream << " xmlns=\"http://www.w3.org/2000/svg\""
" xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
" version=\"1.2\" baseProfile=\"tiny\">" << endl;
if (!d->attributes.document_title.isEmpty()) {
*d->stream << "<title>" << d->attributes.document_title << "</title>" << endl;
}
if (!d->attributes.document_description.isEmpty()) {
*d->stream << "<desc>" << d->attributes.document_description << "</desc>" << endl;
}
d->stream->setString(&d->defs);
*d->stream << "<defs>\n";
d->stream->setString(&d->body);
// Start the initial graphics state...
*d->stream << "<g ";
generateQtDefaults();
*d->stream << endl;
return true;
}
bool SvgPaintEngine::end()
{
Q_D(SvgPaintEngine);
d->stream->setString(&d->defs);
*d->stream << "</defs>\n";
d->stream->setDevice(d->outputDevice);
#ifndef QT_NO_TEXTCODEC
d->stream->setCodec(QTextCodec::codecForName("UTF-8"));
#endif
*d->stream << d->header;
*d->stream << d->defs;
*d->stream << d->body;
if (d->afterFirstUpdate)
*d->stream << "</g>" << endl; // close the updateState
*d->stream << "</g>" << endl // close the Qt defaults
<< "</svg>" << endl;
delete d->stream;
return true;
}
void SvgPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm,
const QRectF &sr)
{
drawImage(r, pm.toImage(), sr);
}
void SvgPaintEngine::drawImage(const QRectF &r, const QImage &image,
const QRectF &sr,
Qt::ImageConversionFlag flags)
{
//Q_D(SvgPaintEngine);
Q_UNUSED(sr);
Q_UNUSED(flags);
stream() << "<image ";
stream() << "x=\""<<r.x()<<"\" "
"y=\""<<r.y()<<"\" "
"width=\""<<r.width()<<"\" "
"height=\""<<r.height()<<"\" "
"preserveAspectRatio=\"none\" ";
QByteArray data;
QBuffer buffer(&data);
buffer.open(QBuffer::ReadWrite);
image.save(&buffer, "PNG");
buffer.close();
stream() << "xlink:href=\"data:image/png;base64,"
<< data.toBase64()
<<"\" />\n";
}
void SvgPaintEngine::updateState(const QPaintEngineState &state)
{
Q_D(SvgPaintEngine);
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 << "</g>\n\n";
*d->stream << "<g ";
if (flags & QPaintEngine::DirtyBrush) {
qbrushToSvg(state.brush());
}
if (flags & QPaintEngine::DirtyPen) {
qpenToSvg(state.pen());
}
if (flags & QPaintEngine::DirtyTransform) {
d->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 (!qFuzzyIsNull(state.opacity() - 1))
stream() << "opacity=\""<<state.opacity()<<"\" ";
}
*d->stream << '>' << endl;
d->afterFirstUpdate = true;
}
void SvgPaintEngine::drawPath(const QPainterPath &p)
{
Q_D(SvgPaintEngine);
*d->stream << "<path vector-effect=\""
<< (state->pen().isCosmetic() ? "non-scaling-stroke" : "none")
<< "\" fill-rule=\""
<< (p.fillRule() == Qt::OddEvenFill ? "evenodd" : "nonzero")
<< "\" d=\"";
for (int i=0; i<p.elementCount(); ++i) {
const QPainterPath::Element &e = p.elementAt(i);
switch (e.type) {
case QPainterPath::MoveToElement:
*d->stream << '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 SvgPaintEngine::drawPolygon(const QPointF *points, int pointCount,
PolygonDrawMode mode)
{
Q_ASSERT(pointCount >= 2);
//Q_D(SvgPaintEngine);
QPainterPath path(points[0]);
for (int i=1; i<pointCount; ++i)
path.lineTo(points[i]);
if (mode == PolylineMode) {
stream() << "<polyline fill=\"none\" vector-effect=\""
<< (state->pen().isCosmetic() ? "non-scaling-stroke" : "none")
<< "\" points=\"";
for (int i = 0; i < pointCount; ++i) {
const QPointF &pt = points[i];
stream() << pt.x() << ',' << pt.y() << ' ';
}
stream() << "\" />" <<endl;
} else {
path.closeSubpath();
drawPath(path);
}
}