diff -r 000000000000 -r 1918ee327afb src/svg/qsvggraphics.cpp --- /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 +#include + +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(" 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 paragraphs; + QStack formats; + QVector > formatRanges; + paragraphs.push_back(QString()); + formatRanges.push_back(QList()); + + 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()); + } + } 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(), 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