diff options
Diffstat (limited to 'src/svg/qsvggraphics.cpp')
-rw-r--r-- | src/svg/qsvggraphics.cpp | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/src/svg/qsvggraphics.cpp b/src/svg/qsvggraphics.cpp new file mode 100644 index 0000000..9ff9c26 --- /dev/null +++ b/src/svg/qsvggraphics.cpp @@ -0,0 +1,642 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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) \ + applyStyle(p, states); \ + qreal oldOpacity = p->opacity(); \ + QBrush oldBrush = p->brush(); \ + QPen oldPen = p->pen(); \ + p->setPen(Qt::NoPen); \ + p->setOpacity(oldOpacity * states.fillOpacity); \ + command; \ + p->setOpacity(oldOpacity); \ + p->setPen(oldPen); \ + p->setBrush(Qt::NoBrush); \ + command; \ + p->setBrush(oldBrush); \ + revertStyle(p, states); + + +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 (qFuzzyCompare(sw + 1, 1)) + return m_bounds; + else { + QPainterPath path; + path.addRect(m_bounds); + return boundsOnStroke(path, sw); + } +} + +void QSvgCircle::draw(QPainter *p, QSvgExtraStates &states) +{ + QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds)); +} + +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); + p->drawPath(cubic); + revertStyle(p, states); +} + +QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect) + : QSvgNode(parent), m_bounds(rect) +{ +} + +QRectF QSvgEllipse::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + return m_bounds; + else { + QPainterPath path; + path.addEllipse(m_bounds); + return boundsOnStroke(path, sw); + } +} + +void QSvgEllipse::draw(QPainter *p, QSvgExtraStates &states) +{ + QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds)); +} + +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); + p->drawLine(m_bounds); + revertStyle(p, states); +} + +QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath) + : QSvgNode(parent), m_path(qpath) +{ + //m_cachedBounds = m_path.controlPointRect(); + m_cachedBounds = m_path.boundingRect(); +} + +void QSvgPath::draw(QPainter *p, QSvgExtraStates &states) +{ + QT_SVG_DRAW_SHAPE(p->drawPath(m_path)); +} + +QRectF QSvgPath::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + 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 (qFuzzyCompare(sw + 1, 1)) + return m_poly.boundingRect(); + else { + QPainterPath path; + path.addPolygon(m_poly); + return boundsOnStroke(path, sw); + } +} + +void QSvgPolygon::draw(QPainter *p, QSvgExtraStates &states) +{ + QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly)); +} + + +QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly) + : QSvgNode(parent), m_poly(poly) +{ + +} + +void QSvgPolyline::draw(QPainter *p, QSvgExtraStates &states) +{ + applyStyle(p, states); + if (p->brush().style() != Qt::NoBrush) { + QPen save = p->pen(); + p->setPen(QPen(Qt::NoPen)); + p->drawPolygon(m_poly); + p->setPen(save); + } + p->drawPolyline(m_poly); + 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 (qFuzzyCompare(sw + 1, 1)) + return m_rect; + else { + QPainterPath path; + path.addRect(m_rect); + return boundsOnStroke(path, sw); + } +} + +void QSvgRect::draw(QPainter *p, QSvgExtraStates &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)); + } +} + +QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord) + : QSvgNode(parent) + , m_coord(coord) + , m_textAlignment(Qt::AlignLeft) + , m_scale(1) + , m_appendSpace(false) + , m_type(TEXT) + , m_size(0, 0) +{ + m_paragraphs.push_back(QString()); + m_formatRanges.push_back(QList<QTextLayout::FormatRange>()); +} + +QSvgText::~QSvgText() +{ +} + +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); + + QSvgFontStyle *fontStyle = static_cast<QSvgFontStyle*>( + styleProperty(QSvgStyleProperty::FONT)); + if (fontStyle && fontStyle->svgFont()) { + // SVG fonts not fully supported... + QString text = m_paragraphs.front(); + for (int i = 1; i < m_paragraphs.size(); ++i) { + text.append(QLatin1Char('\n')); + text.append(m_paragraphs[i]); + } + fontStyle->svgFont()->draw(p, m_coord, text, fontStyle->pointSize(), m_textAlignment); + revertStyle(p, states); + return; + } + + // Scale the font to its correct size. + QTransform oldTransform = p->worldTransform(); + p->scale(1 / m_scale, 1 / m_scale); + + qreal y = 0; + bool initial = true; + qreal px = m_coord.x() * m_scale; + qreal py = m_coord.y() * m_scale; + QSizeF scaledSize = m_size * m_scale; + + if (m_type == TEXTAREA) { + if (m_textAlignment == Qt::AlignHCenter) + px += scaledSize.width() / 2; + else if (m_textAlignment == Qt::AlignRight) + px += scaledSize.width(); + } + + QRectF bounds; + if (m_size.height() != 0) + bounds = QRectF(0, 0, 1, scaledSize.height()); + + for (int i = 0; i < m_paragraphs.size(); ++i) { + QTextLayout tl(m_paragraphs[i]); + QTextOption op = tl.textOption(); + op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + tl.setTextOption(op); + tl.setAdditionalFormats(m_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 (m_textAlignment == Qt::AlignHCenter) + x -= line.naturalTextWidth() / 2; + else if (m_textAlignment == Qt::AlignRight) + x -= line.naturalTextWidth(); + + if (initial && m_type == TEXT) + y -= line.ascent(); + initial = false; + + line.setPosition(QPointF(x, y)); + if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width()) + || (m_size.height() != 0 && y + line.height() > scaledSize.height())) { + bounds.setHeight(y); + 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); + revertStyle(p, states); +} + +void QSvgText::insertText(const QString &text, WhitespaceMode mode) +{ + bool isTSpan = (m_formats.count() == 2); + QString newText(text); + newText.replace(QLatin1Char('\t'), QLatin1Char(' ')); + newText.replace(QLatin1Char('\n'), QLatin1Char(' ')); + + bool prependSpace = !m_appendSpace && !isTSpan && (mode == Default) && !m_paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(' ')); + if (m_appendSpace || prependSpace) + m_paragraphs.back().append(QLatin1Char(' ')); + + bool appendSpaceNext = (!isTSpan && (mode == Default) && newText.endsWith(QLatin1Char(' '))); + + if (mode == Default) { + newText = newText.simplified(); + if (newText.isEmpty()) + appendSpaceNext = false; + } + + if (!m_formats.isEmpty()) { + QTextLayout::FormatRange range; + range.start = m_paragraphs.back().length(); + range.length = newText.length(); + range.format = m_formats.top(); + if (m_appendSpace) { + Q_ASSERT(!m_formatRanges.back().isEmpty()); + ++m_formatRanges.back().back().length; + } else if (prependSpace) { + --range.start; + ++range.length; + } + m_formatRanges.back().append(range); + } + + m_appendSpace = appendSpaceNext; + m_paragraphs.back() += newText; +} + +void QSvgText::insertFormat(const QTextCharFormat &format) +{ + QTextCharFormat mergedFormat = format; + if (!m_formats.isEmpty()) { + mergedFormat = m_formats.top(); + mergedFormat.merge(format); + } + m_formats.push(mergedFormat); +} + +void QSvgText::insertLineBreak() +{ + if (m_type == TEXTAREA) { + if (m_paragraphs.back().isEmpty()) + insertText(QLatin1String(" "), Preserve); + m_appendSpace = false; + m_paragraphs.push_back(QString()); + m_formatRanges.push_back(QList<QTextLayout::FormatRange>()); + } +} + +void QSvgText::popFormat() +{ + if (m_formats.count() > 1) + m_formats.pop(); +} + +qreal QSvgText::scale() const +{ + return m_scale; +} + +void QSvgText::setScale(qreal scale) +{ + m_scale = scale; +} + +const QTextCharFormat &QSvgText::topFormat() const +{ + return m_formats.top(); +} + +void QSvgText::setTextAlignment(const Qt::Alignment &alignment) +{ + m_textAlignment = alignment; +} + +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 (qFuzzyCompare(sw + 1, 1)) + return m_poly.boundingRect(); + else { + QPainterPath path; + path.addPolygon(m_poly); + return boundsOnStroke(path, sw); + } +} + +QRectF QSvgArc::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) + return m_cachedBounds; + else { + return boundsOnStroke(cubic, sw); + } +} + +QRectF QSvgImage::bounds() const +{ + return m_bounds; +} + +QRectF QSvgLine::bounds() const +{ + qreal sw = strokeWidth(); + if (qFuzzyCompare(sw + 1, 1)) { + 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 |