/****************************************************************************
**
** Copyright (C) 2010 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$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, 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.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qsvggraphics_p.h"
#ifndef QT_NO_SVG
#include "qsvgfont_p.h"
#include "qpainter.h"
#include "qtextdocument.h"
#include "qabstracttextdocumentlayout.h"
#include "qtextcursor.h"
#include "qdebug.h"
#include <math.h>
#include <limits.h>
QT_BEGIN_NAMESPACE
#define QT_SVG_DRAW_SHAPE(command) \
qreal oldOpacity = p->opacity(); \
QBrush oldBrush = p->brush(); \
QPen oldPen = p->pen(); \
p->setPen(Qt::NoPen); \
p->setOpacity(oldOpacity * states.fillOpacity); \
command; \
p->setPen(oldPen); \
if (oldPen.widthF() != 0) { \
p->setOpacity(oldOpacity * states.strokeOpacity); \
p->setBrush(Qt::NoBrush); \
command; \
p->setBrush(oldBrush); \
} \
p->setOpacity(oldOpacity);
void QSvgAnimation::draw(QPainter *, QSvgExtraStates &)
{
qWarning("<animation> no implemented");
}
static inline QRectF boundsOnStroke(const QPainterPath &path, qreal width)
{
QPainterPathStroker stroker;
stroker.setWidth(width);
QPainterPath stroke = stroker.createStroke(path);
return stroke.boundingRect();
}
QSvgCircle::QSvgCircle(QSvgNode *parent, const QRectF &rect)
: QSvgNode(parent), m_bounds(rect)
{
}
QRectF QSvgCircle::bounds() const
{
qreal sw = strokeWidth();
if (qFuzzyIsNull(sw))
return m_bounds;
else {
QPainterPath path;
path.addRect(m_bounds);
return boundsOnStroke(path, sw);
}
}
void QSvgCircle::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds));
revertStyle(p, states);
}
QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path)
: QSvgNode(parent), cubic(path)
{
m_cachedBounds = path.boundingRect();
}
void QSvgArc::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
if (p->pen().widthF() != 0) {
qreal oldOpacity = p->opacity();
p->setOpacity(oldOpacity * states.strokeOpacity);
p->drawPath(cubic);
p->setOpacity(oldOpacity);
}
revertStyle(p, states);
}
QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect)
: QSvgNode(parent), m_bounds(rect)
{
}
QRectF QSvgEllipse::bounds() const
{
qreal sw = strokeWidth();
if (qFuzzyIsNull(sw))
return m_bounds;
else {
QPainterPath path;
path.addEllipse(m_bounds);
return boundsOnStroke(path, sw);
}
}
void QSvgEllipse::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds));
revertStyle(p, states);
}
QSvgImage::QSvgImage(QSvgNode *parent, const QImage &image,
const QRect &bounds)
: QSvgNode(parent), m_image(image),
m_bounds(bounds)
{
if (m_bounds.width() == 0)
m_bounds.setWidth(m_image.width());
if (m_bounds.height() == 0)
m_bounds.setHeight(m_image.height());
}
void QSvgImage::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
p->drawImage(m_bounds, m_image);
revertStyle(p, states);
}
QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line)
: QSvgNode(parent), m_bounds(line)
{
}
void QSvgLine::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
if (p->pen().widthF() != 0) {
qreal oldOpacity = p->opacity();
p->setOpacity(oldOpacity * states.strokeOpacity);
p->drawLine(m_bounds);
p->setOpacity(oldOpacity);
}
revertStyle(p, states);
}
QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath)
: QSvgNode(parent), m_path(qpath)
{
}
void QSvgPath::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
m_path.setFillRule(states.fillRule);
QT_SVG_DRAW_SHAPE(p->drawPath(m_path));
revertStyle(p, states);
}
QRectF QSvgPath::bounds() const
{
qreal sw = strokeWidth();
if (qFuzzyIsNull(sw)) {
if (m_cachedBounds.isNull())
//m_cachedBounds = m_path.controlPointRect();
m_cachedBounds = m_path.boundingRect();
return m_cachedBounds;
}
else {
return boundsOnStroke(m_path, sw);
}
}
QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly)
: QSvgNode(parent), m_poly(poly)
{
}
QRectF QSvgPolygon::bounds() const
{
qreal sw = strokeWidth();
if (qFuzzyIsNull(sw))
return m_poly.boundingRect();
else {
QPainterPath path;
path.addPolygon(m_poly);
return boundsOnStroke(path, sw);
}
}
void QSvgPolygon::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly, states.fillRule));
revertStyle(p, states);
}
QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly)
: QSvgNode(parent), m_poly(poly)
{
}
void QSvgPolyline::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
qreal oldOpacity = p->opacity();
if (p->brush().style() != Qt::NoBrush) {
QPen save = p->pen();
p->setPen(QPen(Qt::NoPen));
p->setOpacity(oldOpacity * states.fillOpacity);
p->drawPolygon(m_poly, states.fillRule);
p->setPen(save);
}
if (p->pen().widthF() != 0) {
p->setOpacity(oldOpacity * states.strokeOpacity);
p->drawPolyline(m_poly);
}
p->setOpacity(oldOpacity);
revertStyle(p, states);
}
QSvgRect::QSvgRect(QSvgNode *node, const QRectF &rect, int rx, int ry)
: QSvgNode(node),
m_rect(rect), m_rx(rx), m_ry(ry)
{
}
QRectF QSvgRect::bounds() const
{
qreal sw = strokeWidth();
if (qFuzzyIsNull(sw))
return m_rect;
else {
QPainterPath path;
path.addRect(m_rect);
return boundsOnStroke(path, sw);
}
}
void QSvgRect::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
if (m_rx || m_ry) {
QT_SVG_DRAW_SHAPE(p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize));
} else {
QT_SVG_DRAW_SHAPE(p->drawRect(m_rect));
}
revertStyle(p, states);
}
QSvgTspan * const QSvgText::LINEBREAK = 0;
QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord)
: QSvgNode(parent)
, m_coord(coord)
, m_type(TEXT)
, m_size(0, 0)
, m_mode(Default)
{
}
QSvgText::~QSvgText()
{
for (int i = 0; i < m_tspans.size(); ++i) {
if (m_tspans[i] != LINEBREAK)
delete m_tspans[i];
}
}
void QSvgText::setTextArea(const QSizeF &size)
{
m_size = size;
m_type = TEXTAREA;
}
//QRectF QSvgText::bounds() const {}
void QSvgText::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
qreal oldOpacity = p->opacity();
p->setOpacity(oldOpacity * states.fillOpacity);
// Force the font to have a size of 100 pixels to avoid truncation problems
// when the font is very small.
qreal scale = 100.0 / p->font().pointSizeF();
Qt::Alignment alignment = states.textAnchor;
QTransform oldTransform = p->worldTransform();
p->scale(1 / scale, 1 / scale);
qreal y = 0;
bool initial = true;
qreal px = m_coord.x() * scale;
qreal py = m_coord.y() * scale;
QSizeF scaledSize = m_size * scale;
if (m_type == TEXTAREA) {
if (alignment == Qt::AlignHCenter)
px += scaledSize.width() / 2;
else if (alignment == Qt::AlignRight)
px += scaledSize.width();
}
QRectF bounds;
if (m_size.height() != 0)
bounds = QRectF(0, py, 1, scaledSize.height()); // x and width are not used.
bool appendSpace = false;
QVector<QString> paragraphs;
QStack<QTextCharFormat> formats;
QVector<QList<QTextLayout::FormatRange> > formatRanges;
paragraphs.push_back(QString());
formatRanges.push_back(QList<QTextLayout::FormatRange>());
for (int i = 0; i < m_tspans.size(); ++i) {
if (m_tspans[i] == LINEBREAK) {
if (m_type == TEXTAREA) {
if (paragraphs.back().isEmpty()) {
QFont font = p->font();
font.setPixelSize(font.pointSizeF() * scale);
QTextLayout::FormatRange range;
range.start = 0;
range.length = 1;
range.format.setFont(font);
formatRanges.back().append(range);
paragraphs.back().append(QLatin1Char(' '));;
}
appendSpace = false;
paragraphs.push_back(QString());
formatRanges.push_back(QList<QTextLayout::FormatRange>());
}
} else {
WhitespaceMode mode = m_tspans[i]->whitespaceMode();
m_tspans[i]->applyStyle(p, states);
QFont font = p->font();
font.setPixelSize(font.pointSizeF() * scale);
QString newText(m_tspans[i]->text());
newText.replace(QLatin1Char('\t'), QLatin1Char(' '));
newText.replace(QLatin1Char('\n'), QLatin1Char(' '));
bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(' '));
if (appendSpace || prependSpace)
paragraphs.back().append(QLatin1Char(' '));
bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(' ')));
if (mode == Default) {
newText = newText.simplified();
if (newText.isEmpty())
appendSpaceNext = false;
}
QTextLayout::FormatRange range;
range.start = paragraphs.back().length();
range.length = newText.length();
range.format.setFont(font);
range.format.setTextOutline(p->pen());
range.format.setForeground(p->brush());
if (appendSpace) {
Q_ASSERT(!formatRanges.back().isEmpty());
++formatRanges.back().back().length;
} else if (prependSpace) {
--range.start;
++range.length;
}
formatRanges.back().append(range);
appendSpace = appendSpaceNext;
paragraphs.back() += newText;
m_tspans[i]->revertStyle(p, states);
}
}
if (states.svgFont) {
// SVG fonts not fully supported...
QString text = paragraphs.front();
for (int i = 1; i < paragraphs.size(); ++i) {
text.append(QLatin1Char('\n'));
text.append(paragraphs[i]);
}
states.svgFont->draw(p, m_coord * scale, text, p->font().pointSizeF() * scale, states.textAnchor);
} else {
for (int i = 0; i < paragraphs.size(); ++i) {
QTextLayout tl(paragraphs[i]);
QTextOption op = tl.textOption();
op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
tl.setTextOption(op);
tl.setAdditionalFormats(formatRanges[i]);
tl.beginLayout();
forever {
QTextLine line = tl.createLine();
if (!line.isValid())
break;
if (m_size.width() != 0)
line.setLineWidth(scaledSize.width());
}
tl.endLayout();
bool endOfBoundsReached = false;
for (int i = 0; i < tl.lineCount(); ++i) {
QTextLine line = tl.lineAt(i);
qreal x = 0;
if (alignment == Qt::AlignHCenter)
x -= 0.5 * line.naturalTextWidth();
else if (alignment == Qt::AlignRight)
x -= line.naturalTextWidth();
if (initial && m_type == TEXT)
y -= line.ascent();
initial = false;
line.setPosition(QPointF(x, y));
// Check if the current line fits into the bounding rectangle.
if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width())
|| (m_size.height() != 0 && y + line.height() > scaledSize.height())) {
// I need to set the bounds height to 'y-epsilon' to avoid drawing the current
// line. Since the font is scaled to 100 units, 1 should be a safe epsilon.
bounds.setHeight(y - 1);
endOfBoundsReached = true;
break;
}
y += 1.1 * line.height();
}
tl.draw(p, QPointF(px, py), QVector<QTextLayout::FormatRange>(), bounds);
if (endOfBoundsReached)
break;
}
}
p->setWorldTransform(oldTransform, false);
p->setOpacity(oldOpacity);
revertStyle(p, states);
}
void QSvgText::addText(const QString &text)
{
m_tspans.append(new QSvgTspan(this, false));
m_tspans.back()->setWhitespaceMode(m_mode);
m_tspans.back()->addText(text);
}
QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
: QSvgNode(parent), m_link(node), m_start(start)
{
}
void QSvgUse::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
if (!m_start.isNull()) {
p->translate(m_start);
}
m_link->draw(p, states);
if (!m_start.isNull()) {
p->translate(-m_start);
}
revertStyle(p, states);
}
void QSvgVideo::draw(QPainter *p, QSvgExtraStates &states)
{
applyStyle(p, states);
revertStyle(p, states);
}
QSvgNode::Type QSvgAnimation::type() const
{
return ANIMATION;
}
QSvgNode::Type QSvgArc::type() const
{
return ARC;
}
QSvgNode::Type QSvgCircle::type() const
{
return CIRCLE;
}
QSvgNode::Type QSvgEllipse::type() const
{
return ELLIPSE;
}
QSvgNode::Type QSvgImage::type() const
{
return IMAGE;
}
QSvgNode::Type QSvgLine::type() const
{
return LINE;
}
QSvgNode::Type QSvgPath::type() const
{
return PATH;
}
QSvgNode::Type QSvgPolygon::type() const
{
return POLYGON;
}
QSvgNode::Type QSvgPolyline::type() const
{
return POLYLINE;
}
QSvgNode::Type QSvgRect::type() const
{
return RECT;
}
QSvgNode::Type QSvgText::type() const
{
return m_type;
}
QSvgNode::Type QSvgUse::type() const
{
return USE;
}
QSvgNode::Type QSvgVideo::type() const
{
return VIDEO;
}
QRectF QSvgUse::bounds() const
{
if (m_link && m_bounds.isEmpty()) {
m_bounds = m_link->bounds();
m_bounds = QRectF(m_bounds.x()+m_start.x(),
m_bounds.y()+m_start.y(),
m_bounds.width(),
m_bounds.height());
return m_bounds;
}
return m_bounds;
}
QRectF QSvgUse::transformedBounds(const QTransform &transform) const
{
QRectF bounds;
QTransform t = transform;
if (m_link) {
QSvgTransformStyle *transStyle = m_style.transform;
if (transStyle) {
t = transStyle->qtransform() * t;
}
t.translate(m_start.x(), m_start.y());
bounds = m_link->transformedBounds(t);
return bounds;
}
return bounds;
}
QRectF QSvgPolyline::bounds() const
{
qreal sw = strokeWidth();
if (qFuzzyIsNull(sw))
return m_poly.boundingRect();
else {
QPainterPath path;
path.addPolygon(m_poly);
return boundsOnStroke(path, sw);
}
}
QRectF QSvgArc::bounds() const
{
qreal sw = strokeWidth();
if (qFuzzyIsNull(sw))
return m_cachedBounds;
else {
return boundsOnStroke(cubic, sw);
}
}
QRectF QSvgImage::bounds() const
{
return m_bounds;
}
QRectF QSvgLine::bounds() const
{
qreal sw = strokeWidth();
if (qFuzzyIsNull(sw)) {
qreal minX = qMin(m_bounds.x1(), m_bounds.x2());
qreal minY = qMin(m_bounds.y1(), m_bounds.y2());
qreal maxX = qMax(m_bounds.x1(), m_bounds.x2());
qreal maxY = qMax(m_bounds.y1(), m_bounds.y2());
return QRectF(minX, minY, maxX-minX, maxY-minY);
} else {
QPainterPath path;
path.moveTo(m_bounds.x1(), m_bounds.y1());
path.lineTo(m_bounds.x2(), m_bounds.y2());
return boundsOnStroke(path, sw);
}
}
QT_END_NAMESPACE
#endif // QT_NO_SVG