src/svg/qsvggraphics.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/svg/qsvggraphics.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,675 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 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