From 5c9206d8290232e8adc1a6cdb992a7c19d67269a Mon Sep 17 00:00:00 2001 From: Kim Motoyoshi Kalland Date: Fri, 21 Aug 2009 17:44:57 +0200 Subject: Fixed SVG stroke attributes to work with 'use' tags. In the process of rewriting the stroke handling code, I also fixed gradients on strokes. Task-number: 202426, 250618 Reviewed-by: Trond --- src/svg/qsvggraphics.cpp | 71 +++++-- src/svg/qsvggraphics_p.h | 5 - src/svg/qsvghandler.cpp | 188 +++++++----------- src/svg/qsvgnode.cpp | 4 +- src/svg/qsvgstyle.cpp | 284 +++++++++++++++------------ src/svg/qsvgstyle_p.h | 186 ++++++++++++++---- src/svg/qsvgtinydocument.cpp | 4 +- tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp | 5 +- 8 files changed, 434 insertions(+), 313 deletions(-) diff --git a/src/svg/qsvggraphics.cpp b/src/svg/qsvggraphics.cpp index 40cf06b..6f30b90 100644 --- a/src/svg/qsvggraphics.cpp +++ b/src/svg/qsvggraphics.cpp @@ -56,20 +56,21 @@ 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); +#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 &) @@ -105,7 +106,9 @@ QRectF QSvgCircle::bounds() const 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) @@ -117,7 +120,12 @@ QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path) void QSvgArc::draw(QPainter *p, QSvgExtraStates &states) { applyStyle(p, states); - p->drawPath(cubic); + if (p->pen().widthF() != 0) { + qreal oldOpacity = p->opacity(); + p->setOpacity(oldOpacity * states.strokeOpacity); + p->drawPath(cubic); + p->setOpacity(oldOpacity); + } revertStyle(p, states); } @@ -140,7 +148,9 @@ QRectF QSvgEllipse::bounds() const 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, @@ -171,7 +181,12 @@ QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line) void QSvgLine::draw(QPainter *p, QSvgExtraStates &states) { applyStyle(p, states); - p->drawLine(m_bounds); + if (p->pen().widthF() != 0) { + qreal oldOpacity = p->opacity(); + p->setOpacity(oldOpacity * states.strokeOpacity); + p->drawLine(m_bounds); + p->setOpacity(oldOpacity); + } revertStyle(p, states); } @@ -184,7 +199,10 @@ QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &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 @@ -198,7 +216,7 @@ QRectF QSvgPath::bounds() const } QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly) - : QSvgNode(parent), m_poly(poly), m_fillRule(Qt::WindingFill) + : QSvgNode(parent), m_poly(poly) { } @@ -216,7 +234,9 @@ QRectF QSvgPolygon::bounds() const void QSvgPolygon::draw(QPainter *p, QSvgExtraStates &states) { - QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly, m_fillRule)); + applyStyle(p, states); + QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly, states.fillRule)); + revertStyle(p, states); } @@ -229,13 +249,19 @@ QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &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->drawPolygon(m_poly); + p->setOpacity(oldOpacity * states.fillOpacity); + p->drawPolygon(m_poly, states.fillRule); p->setPen(save); } - p->drawPolyline(m_poly); + if (p->pen().widthF() != 0) { + p->setOpacity(oldOpacity * states.strokeOpacity); + p->drawPolyline(m_poly); + } + p->setOpacity(oldOpacity); revertStyle(p, states); } @@ -259,11 +285,13 @@ QRectF QSvgRect::bounds() const 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; @@ -296,6 +324,8 @@ void QSvgText::setTextArea(const QSizeF &size) 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. @@ -456,6 +486,7 @@ void QSvgText::draw(QPainter *p, QSvgExtraStates &states) } p->setWorldTransform(oldTransform, false); + p->setOpacity(oldOpacity); revertStyle(p, states); } diff --git a/src/svg/qsvggraphics_p.h b/src/svg/qsvggraphics_p.h index 20310e3..02c312b 100644 --- a/src/svg/qsvggraphics_p.h +++ b/src/svg/qsvggraphics_p.h @@ -155,13 +155,8 @@ public: virtual void draw(QPainter *p, QSvgExtraStates &states); virtual Type type() const; virtual QRectF bounds() const; - void setFillRule(Qt::FillRule f) - { - m_fillRule = f; - } private: QPolygonF m_poly; - Qt::FillRule m_fillRule; }; class QSvgPolyline : public QSvgNode diff --git a/src/svg/qsvghandler.cpp b/src/svg/qsvghandler.cpp index adfe468..5c0eda9 100644 --- a/src/svg/qsvghandler.cpp +++ b/src/svg/qsvghandler.cpp @@ -493,8 +493,7 @@ static bool resolveColor(const QString &colorStr, QColor &color, QSvgHandler *ha int(compo[1]), int(compo[2])); return true; - } else if (colorStr == QLatin1String("inherited") || - colorStr == QT_INHERIT) { + } else if (colorStr == QT_INHERIT) { return false; } else if (colorStr == QLatin1String("currentColor")) { color = handler->currentColor(); @@ -649,7 +648,7 @@ static void parseBrush(QSvgNode *node, QString myId = someId(attributes); if (!value.isEmpty() || !fillRule.isEmpty() || !opacity.isEmpty()) { - QSvgFillStyle *prop = new QSvgFillStyle(0); + QSvgFillStyle *prop = new QSvgFillStyle; //fill-rule attribute handling if (!fillRule.isEmpty() && fillRule != QT_INHERIT) { @@ -670,7 +669,8 @@ static void parseBrush(QSvgNode *node, value = value.remove(0, 3); QSvgStyleProperty *style = styleFromUrl(node, value); if (style) { - prop->setFillStyle(style); + if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT) + prop->setFillStyle(reinterpret_cast(style)); } else { QString id = idFromUrl(value); prop->setGradientId(id); @@ -834,154 +834,95 @@ static void parsePen(QSvgNode *node, //qDebug()<<"Node "<type()<<", attrs are "<(node->parent()->styleProperty( - QSvgStyleProperty::STROKE)); - - QPen pen(handler->defaultPen()); - bool stroke = false; - if (inherited) { - pen = inherited->qpen(); - stroke = inherited->strokePresent(); - } - - // stroke-opacity attribute handling - qreal strokeAlpha; - if (!opacity.isEmpty() && opacity != QT_INHERIT) { - strokeAlpha = qMin(qreal(1.0), qMax(qreal(0.0), toDouble(opacity))); - } else { - strokeAlpha = pen.color().alphaF(); - } + QSvgStrokeStyle *prop = new QSvgStrokeStyle; //stroke attribute handling - if (!value.isEmpty() && value != QT_INHERIT) { + if ((!value.isEmpty()) && (value != QT_INHERIT) ) { if (value.startsWith(QLatin1String("url"))) { value = value.remove(0, 3); QSvgStyleProperty *style = styleFromUrl(node, value); if (style) { - if (style->type() == QSvgStyleProperty::GRADIENT) { - QBrush b(*((QSvgGradientStyle*)style)->qgradient()); - pen.setBrush(b); - } else if (style->type() == QSvgStyleProperty::SOLID_COLOR) { - pen.setColor( - ((QSvgSolidColorStyle*)style)->qcolor()); - } - stroke = true; + if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT) + prop->setStyle(reinterpret_cast(style)); } else { - qWarning() << "QSvgHandler::parsePen could not resolve property" << idFromUrl(value); + QString id = idFromUrl(value); + prop->setGradientId(id); + prop->setGradientResolved(false); } - } else if (value == QLatin1String("none")) { - QColor color; //### fixme: dafalut value for color. - color.setAlphaF(strokeAlpha); - pen.setColor(color); - stroke = false; // This is required because, parent may have a valid stroke but the child may have stroke = "none" - } else { + } else if (value != QLatin1String("none")) { QColor color; - if (resolveColor(value, color, handler)) { - color.setAlphaF(strokeAlpha); - pen.setColor(color); - } - stroke = true; + if (resolveColor(value, color, handler)) + prop->setStroke(QBrush(color)); + } else { + prop->setStroke(QBrush(Qt::NoBrush)); } - } else { - QColor color = pen.color(); - color.setAlphaF(strokeAlpha); - pen.setColor(color); } //stroke-width handling + qreal w = 0; if (!width.isEmpty() && width != QT_INHERIT) { - qreal widthF; QSvgHandler::LengthType lt; - widthF = parseLength(width, lt, handler); - pen.setWidthF(widthF); + prop->setWidth(w = parseLength(width, lt, handler)); + } + + //stroke-dasharray + if (!dashArray.isEmpty() && dashArray != QT_INHERIT) { + if (dashArray == QLatin1String("none")) { + prop->setDashArrayNone(); + } else { + const QChar *s = dashArray.constData(); + QVector dashes = parseNumbersList(s); + // if the dash count is odd the dashes should be duplicated + if ((dashes.size() & 1) != 0) + dashes << QVector(dashes); + prop->setDashArray(dashes); + } } //stroke-linejoin attribute handling if (!linejoin.isEmpty()) { if (linejoin == QLatin1String("miter")) - pen.setJoinStyle(Qt::SvgMiterJoin); + prop->setLineJoin(Qt::SvgMiterJoin); else if (linejoin == QLatin1String("round")) - pen.setJoinStyle(Qt::RoundJoin); + prop->setLineJoin(Qt::RoundJoin); else if (linejoin == QLatin1String("bevel")) - pen.setJoinStyle(Qt::BevelJoin); + prop->setLineJoin(Qt::BevelJoin); } //stroke-linecap attribute handling if (!linecap.isEmpty()) { if (linecap == QLatin1String("butt")) - pen.setCapStyle(Qt::FlatCap); + prop->setLineCap(Qt::FlatCap); else if (linecap == QLatin1String("round")) - pen.setCapStyle(Qt::RoundCap); + prop->setLineCap(Qt::RoundCap); else if (linecap == QLatin1String("square")) - pen.setCapStyle(Qt::SquareCap); - } - - //strok-dasharray attribute handling - qreal penw = pen.widthF(); - if (!dashArray.isEmpty() && dashArray != QT_INHERIT) { - const QChar *s = dashArray.constData(); - QVector dashes = parseNumbersList(s); - qreal *d = dashes.data(); - if (penw != 0) - for (int i = 0; i < dashes.size(); ++i) { - *d /= penw; - ++d; - } - // if the dash count is odd the dashes should be duplicated - if (dashes.size() % 2 != 0) - dashes << QVector(dashes); - pen.setDashPattern(dashes); - } else if (inherited) { - QVector dashes(inherited->qpen().dashPattern()); - qreal *d = dashes.data(); - if (!penw) - penw = 1.0; - qreal inheritpenw = inherited->qpen().widthF(); - if (!inheritpenw) - inheritpenw = 1.0; - for ( int i = 0; i < dashes.size(); ++i) { - *d *= (inheritpenw/ penw); - ++d; - } - pen.setDashPattern(dashes); + prop->setLineCap(Qt::SquareCap); } - //stroke-dashoffset attribute handling - if (!dashOffset.isEmpty() && dashOffset != QT_INHERIT) { - qreal doffset = toDouble(dashOffset); - if (penw != 0) - doffset /= penw; - pen.setDashOffset(doffset); - } else if (inherited) { - qreal doffset = pen.dashOffset(); - if (!penw) - penw = 1.0; - qreal inheritpenw = inherited->qpen().widthF(); - if (!inheritpenw) - inheritpenw = 1.0; - doffset *= (inheritpenw/ penw); - pen.setDashOffset(doffset); - } + if (!dashOffset.isEmpty() && dashOffset != QT_INHERIT) + prop->setDashOffset(toDouble(dashOffset)); //vector-effect attribute handling if (!vectorEffect.isEmpty()) { if (vectorEffect == QLatin1String("non-scaling-stroke")) - pen.setCosmetic(true); + prop->setVectorEffect(true); else if (vectorEffect == QLatin1String("none")) - pen.setCosmetic(false); + prop->setVectorEffect(false); } + //stroke-miterlimit if (!miterlimit.isEmpty() && miterlimit != QT_INHERIT) - pen.setMiterLimit(toDouble(miterlimit)); + prop->setMiterLimit(toDouble(miterlimit)); + + //stroke-opacity atttribute handling + if (!opacity.isEmpty() && opacity != QT_INHERIT) + prop->setOpacity(qMin(qreal(1.0), qMax(qreal(0.0), toDouble(opacity)))); - QSvgStrokeStyle *prop = new QSvgStrokeStyle(pen); - prop->setStroke(stroke); node->appendStyleProperty(prop, myId); } } @@ -2770,7 +2711,7 @@ static bool parseStopNode(QSvgStyleProperty *parent, colorStr = QLatin1String("#000000"); } - bool colorOK = constructColor(colorStr, opacityStr, color, handler); + constructColor(colorStr, opacityStr, color, handler); QGradient *grad = style->qgradient(); @@ -2794,8 +2735,6 @@ static bool parseStopNode(QSvgStyleProperty *parent, grad->setColorAt(offset, color); style->setGradientStopsSet(true); - if (!colorOK) - style->addResolve(offset); return true; } @@ -3422,15 +3361,28 @@ bool QSvgHandler::endElement(const QStringRef &localName) QList::iterator itr = ren.begin(); while (itr != ren.end()) { QSvgNode *eleNode = *itr++; - QSvgFillStyle *prop = static_cast(eleNode->styleProperty(QSvgStyleProperty::FILL)); - if (prop && !(prop->isGradientResolved())) { - QString id = prop->getGradientId(); + QSvgFillStyle *fill = static_cast(eleNode->styleProperty(QSvgStyleProperty::FILL)); + if (fill && !(fill->isGradientResolved())) { + QString id = fill->gradientId(); + QSvgStyleProperty *style = structureNode->scopeStyle(id); + if (style) { + if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT) + fill->setFillStyle(reinterpret_cast(style)); + } else { + qWarning("Couldn't resolve property : %s",qPrintable(id)); + fill->setBrush(QBrush(Qt::NoBrush)); + } + } + QSvgStrokeStyle *stroke = static_cast(eleNode->styleProperty(QSvgStyleProperty::STROKE)); + if (stroke && !(stroke->isGradientResolved())) { + QString id = stroke->gradientId(); QSvgStyleProperty *style = structureNode->scopeStyle(id); if (style) { - prop->setFillStyle(style); + if (style->type() == QSvgStyleProperty::SOLID_COLOR || style->type() == QSvgStyleProperty::GRADIENT) + stroke->setStyle(reinterpret_cast(style)); } else { qWarning("Couldn't resolve property : %s",qPrintable(id)); - prop->setBrush(QBrush(Qt::NoBrush)); + stroke->setStroke(QBrush(Qt::NoBrush)); } } } diff --git a/src/svg/qsvgnode.cpp b/src/svg/qsvgnode.cpp index 33907fd..5f323b1 100644 --- a/src/svg/qsvgnode.cpp +++ b/src/svg/qsvgnode.cpp @@ -320,9 +320,9 @@ qreal QSvgNode::strokeWidth() const { QSvgStrokeStyle *stroke = static_cast( styleProperty(QSvgStyleProperty::STROKE)); - if (!stroke || !stroke->strokePresent()) + if (!stroke) return 0; - return stroke->qpen().widthF(); + return stroke->width(); } QT_END_NAMESPACE diff --git a/src/svg/qsvgstyle.cpp b/src/svg/qsvgstyle.cpp index ef63443..6922ad9 100644 --- a/src/svg/qsvgstyle.cpp +++ b/src/svg/qsvgstyle.cpp @@ -59,9 +59,13 @@ QT_BEGIN_NAMESPACE QSvgExtraStates::QSvgExtraStates() : fillOpacity(1.0) + , strokeOpacity(1.0) , svgFont(0) , textAnchor(Qt::AlignLeft) , fontWeight(400) + , fillRule(Qt::WindingFill) + , strokeDashOffset(0) + , vectorEffect(false) { } @@ -69,6 +73,17 @@ QSvgStyleProperty::~QSvgStyleProperty() { } +void QSvgFillStyleProperty::apply(QPainter *, const QRectF &, QSvgNode *, QSvgExtraStates &) +{ + Q_ASSERT(!"This should not be called!"); +} + +void QSvgFillStyleProperty::revert(QPainter *, QSvgExtraStates &) +{ + Q_ASSERT(!"This should not be called!"); +} + + QSvgQualityStyle::QSvgQualityStyle(int color) : m_colorRendering(color) { @@ -83,83 +98,55 @@ void QSvgQualityStyle::revert(QPainter *, QSvgExtraStates &) } -QSvgFillStyle::QSvgFillStyle(const QBrush &brush) - : m_fill(brush) - , m_style(0) - , m_fillRuleSet(false) - , m_fillRule(Qt::WindingFill) - , m_fillOpacitySet(false) - , m_fillOpacity(1.0) - , m_oldOpacity(0) - , m_gradientResolved(true) - , m_fillSet(true) -{ -} - -QSvgFillStyle::QSvgFillStyle(QSvgStyleProperty *style) - : m_style(style) - , m_fillRuleSet(false) +QSvgFillStyle::QSvgFillStyle() + : m_style(0) , m_fillRule(Qt::WindingFill) - , m_fillOpacitySet(false) + , m_oldFillRule(Qt::WindingFill) , m_fillOpacity(1.0) - , m_oldOpacity(0) - , m_gradientResolved(true) - , m_fillSet(style != 0) + , m_oldFillOpacity(0) + , m_gradientResolved(1) + , m_fillRuleSet(0) + , m_fillOpacitySet(0) + , m_fillSet(0) { } void QSvgFillStyle::setFillRule(Qt::FillRule f) { - m_fillRuleSet = true; + m_fillRuleSet = 1; m_fillRule = f; } void QSvgFillStyle::setFillOpacity(qreal opacity) { - m_fillOpacitySet = true; + m_fillOpacitySet = 1; m_fillOpacity = opacity; } -void QSvgFillStyle::setFillStyle(QSvgStyleProperty* style) +void QSvgFillStyle::setFillStyle(QSvgFillStyleProperty* style) { m_style = style; - m_fillSet = true; + m_fillSet = 1; } void QSvgFillStyle::setBrush(QBrush brush) { m_fill = brush; m_style = 0; - m_fillSet = true; + m_fillSet = 1; } -static void recursivelySetFill(QSvgNode *node, Qt::FillRule f) -{ - if (node->type() == QSvgNode::PATH) { - QSvgPath *path = static_cast(node); - path->qpath()->setFillRule(f); - } else if (node->type() == QSvgNode::POLYGON) { - QSvgPolygon *polygon = static_cast(node); - polygon->setFillRule(f); - } else if (node->type() == QSvgNode::G) { - QList renderers = static_cast(node)->renderers(); - foreach(QSvgNode *n, renderers) { - recursivelySetFill(n, f); - } - } -} -void QSvgFillStyle::apply(QPainter *p, const QRectF &rect, QSvgNode *node, QSvgExtraStates &states) +void QSvgFillStyle::apply(QPainter *p, const QRectF &, QSvgNode *, QSvgExtraStates &states) { m_oldFill = p->brush(); - m_oldOpacity = states.fillOpacity; + m_oldFillRule = states.fillRule; + m_oldFillOpacity = states.fillOpacity; - if (m_fillRuleSet) { - recursivelySetFill(node, m_fillRule); - m_fillRuleSet = false;//set it only on the first run - } + if (m_fillRuleSet) + states.fillRule = m_fillRule; if (m_fillSet) { if (m_style) - m_style->apply(p, rect, node, states); + p->setBrush(m_style->brush(p, states)); else p->setBrush(m_fill); } @@ -170,13 +157,11 @@ void QSvgFillStyle::apply(QPainter *p, const QRectF &rect, QSvgNode *node, QSvgE void QSvgFillStyle::revert(QPainter *p, QSvgExtraStates &states) { if (m_fillOpacitySet) - states.fillOpacity = m_oldOpacity; - if (m_fillSet) { - if (m_style) - m_style->revert(p, states); - else - p->setBrush(m_oldFill); - } + states.fillOpacity = m_oldFillOpacity; + if (m_fillSet) + p->setBrush(m_oldFill); + if (m_fillRuleSet) + states.fillRule = m_oldFillRule; } QSvgViewportFillStyle::QSvgViewportFillStyle(const QBrush &brush) @@ -286,47 +271,135 @@ void QSvgFontStyle::revert(QPainter *p, QSvgExtraStates &states) states.fontWeight = m_oldWeight; } -QSvgStrokeStyle::QSvgStrokeStyle(const QPen &pen) - : m_stroke(pen), m_strokePresent(true) +QSvgStrokeStyle::QSvgStrokeStyle() + : m_strokeOpacity(1.0) + , m_oldStrokeOpacity(0.0) + , m_vectorEffect(0) + , m_oldVectorEffect(0) + , m_strokeDashOffset(0) + , m_oldStrokeDashOffset(0) + , m_strokeSet(0) + , m_strokeDashArraySet(0) + , m_strokeDashOffsetSet(0) + , m_strokeLineCapSet(0) + , m_strokeLineJoinSet(0) + , m_strokeMiterLimitSet(0) + , m_strokeOpacitySet(0) + , m_strokeWidthSet(0) + , m_vectorEffectSet(0) { } -void QSvgStrokeStyle::apply(QPainter *p, const QRectF &, QSvgNode *, QSvgExtraStates &) +void QSvgStrokeStyle::apply(QPainter *p, const QRectF &, QSvgNode *, QSvgExtraStates &states) { m_oldStroke = p->pen(); - if (!m_strokePresent || !m_stroke.widthF() || !m_stroke.color().alphaF()) { - p->setPen(Qt::NoPen); - } else { - p->setPen(m_stroke); + m_oldStrokeOpacity = states.strokeOpacity; + m_oldStrokeDashOffset = states.strokeDashOffset; + m_oldVectorEffect = states.vectorEffect; + + QPen pen = p->pen(); + + qreal oldWidth = pen.widthF(); + qreal width = m_stroke.widthF(); + if (oldWidth == 0) + oldWidth = 1; + if (width == 0) + width = 1; + qreal scale = oldWidth / width; + + if (m_strokeOpacitySet) + states.strokeOpacity = m_strokeOpacity; + + if (m_vectorEffectSet) + states.vectorEffect = m_vectorEffect; + + if (m_strokeSet) { + if (m_style) + pen.setBrush(m_style->brush(p, states)); + else + pen.setBrush(m_stroke.brush()); } -} -void QSvgStrokeStyle::revert(QPainter *p, QSvgExtraStates &) -{ - p->setPen(m_oldStroke); + if (m_strokeWidthSet) + pen.setWidthF(m_stroke.widthF()); + + bool setDashOffsetNeeded = false; + + if (m_strokeDashOffsetSet) { + states.strokeDashOffset = m_strokeDashOffset; + setDashOffsetNeeded = true; + } + + if (m_strokeDashArraySet) { + if (m_stroke.style() == Qt::SolidLine) { + pen.setStyle(Qt::SolidLine); + } else if (m_strokeWidthSet || oldWidth == 1) { + // If both width and dash array was set, the dash array is already scaled correctly. + pen.setDashPattern(m_stroke.dashPattern()); + setDashOffsetNeeded = true; + } else { + // If dash array was set, but not the width, the dash array has to be scaled with respect to the old width. + QVector dashes = m_stroke.dashPattern(); + for (int i = 0; i < dashes.size(); ++i) + dashes[i] /= oldWidth; + pen.setDashPattern(dashes); + setDashOffsetNeeded = true; + } + } else if (m_strokeWidthSet && pen.style() != Qt::SolidLine && scale != 1) { + // If the width was set, but not the dash array, the old dash array must be scaled with respect to the new width. + QVector dashes = pen.dashPattern(); + for (int i = 0; i < dashes.size(); ++i) + dashes[i] *= scale; + pen.setDashPattern(dashes); + setDashOffsetNeeded = true; + } + + if (m_strokeLineCapSet) + pen.setCapStyle(m_stroke.capStyle()); + if (m_strokeLineJoinSet) + pen.setJoinStyle(m_stroke.joinStyle()); + if (m_strokeMiterLimitSet) + pen.setMiterLimit(m_stroke.miterLimit()); + + if (setDashOffsetNeeded) { + qreal currentWidth = pen.widthF(); + if (currentWidth == 0) + currentWidth = 1; + pen.setDashOffset(states.strokeDashOffset / currentWidth); + } + + pen.setCosmetic(states.vectorEffect); + + p->setPen(pen); } -QSvgSolidColorStyle::QSvgSolidColorStyle(const QColor &color) - : m_solidColor(color) +void QSvgStrokeStyle::revert(QPainter *p, QSvgExtraStates &states) { + p->setPen(m_oldStroke); + states.strokeOpacity = m_oldStrokeOpacity; + states.strokeDashOffset = m_oldStrokeDashOffset; + states.vectorEffect = m_oldVectorEffect; } -void QSvgSolidColorStyle::apply(QPainter *p, const QRectF &, QSvgNode *, QSvgExtraStates &) +void QSvgStrokeStyle::setDashArray(const QVector &dashes) { - m_oldFill = p->brush(); - m_oldStroke = p->pen(); - QBrush b = m_oldFill; - b.setColor(m_solidColor); - p->setBrush(b); - QPen pen = m_oldStroke; - pen.setColor(m_solidColor); - p->setPen(pen); + if (m_strokeWidthSet) { + QVector d = dashes; + qreal w = m_stroke.widthF(); + if (w != 0 && w != 1) { + for (int i = 0; i < d.size(); ++i) + d[i] /= w; + } + m_stroke.setDashPattern(d); + } else { + m_stroke.setDashPattern(dashes); + } + m_strokeDashArraySet = 1; } -void QSvgSolidColorStyle::revert(QPainter *p, QSvgExtraStates &) +QSvgSolidColorStyle::QSvgSolidColorStyle(const QColor &color) + : m_solidColor(color) { - p->setBrush(m_oldFill); - p->setPen(m_oldStroke); } QSvgGradientStyle::QSvgGradientStyle(QGradient *grad) @@ -334,44 +407,24 @@ QSvgGradientStyle::QSvgGradientStyle(QGradient *grad) { } -void QSvgGradientStyle::apply(QPainter *p, const QRectF &/*rect*/, QSvgNode *, QSvgExtraStates &) +QBrush QSvgGradientStyle::brush(QPainter *, QSvgExtraStates &) { if (!m_link.isEmpty()) { resolveStops(); } - m_oldFill = p->brush(); - - //resolving stop colors - if (!m_resolvePoints.isEmpty()) { - QColor color = p->brush().color(); - if (!color.isValid()) - color = p->pen().color(); - QList::const_iterator itr = m_resolvePoints.constBegin(); - for (; itr != m_resolvePoints.constEnd(); ++itr) { - //qDebug()<<"resolving "<<(*itr)<<" to "<setColorAt(*itr, color); - } - } - // If the gradient is marked as empty, insert transparent black if (!m_gradientStopsSet) { m_gradient->setStops(QGradientStops() << QGradientStop(0.0, QColor(0, 0, 0, 0))); m_gradientStopsSet = true; } - QBrush brush; - brush = QBrush(*m_gradient); + QBrush b(*m_gradient); if (!m_matrix.isIdentity()) - brush.setMatrix(m_matrix); - - p->setBrush(brush); -} + b.setMatrix(m_matrix); -void QSvgGradientStyle::revert(QPainter *p, QSvgExtraStates &) -{ - p->setBrush(m_oldFill); + return b; } @@ -380,11 +433,6 @@ void QSvgGradientStyle::setMatrix(const QMatrix &mat) m_matrix = mat; } -void QSvgGradientStyle::addResolve(qreal offset) -{ - m_resolvePoints.append(offset); -} - QSvgTransformStyle::QSvgTransformStyle(const QTransform &trans) : m_transform(trans) { @@ -490,14 +538,6 @@ void QSvgStyle::apply(QPainter *p, const QRectF &rect, QSvgNode *node, QSvgExtra stroke->apply(p, rect, node, states); } - if (solidColor) { - solidColor->apply(p, rect, node, states); - } - - if (gradient) { - gradient->apply(p, rect, node, states); - } - if (transform) { transform->apply(p, rect, node, states); } @@ -562,14 +602,6 @@ void QSvgStyle::revert(QPainter *p, QSvgExtraStates &states) stroke->revert(p, states); } - if (solidColor) { - solidColor->revert(p, states); - } - - if (gradient) { - gradient->revert(p, states); - } - //animated transforms need to be reverted _before_ //the native transforms if (!animateTransforms.isEmpty()) { @@ -815,7 +847,9 @@ void QSvgAnimateColor::apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgEx if (totalTimeElapsed < m_from || m_finished) return; - qreal animationFrame = (totalTimeElapsed - m_from) / m_to; + qreal animationFrame = 0; + if (m_totalRunningTime != 0) + animationFrame = (totalTimeElapsed - m_from) / m_totalRunningTime; if (m_repeatCount >= 0 && m_repeatCount < animationFrame) { m_finished = true; diff --git a/src/svg/qsvgstyle_p.h b/src/svg/qsvgstyle_p.h index 056b73b..1cff561 100644 --- a/src/svg/qsvgstyle_p.h +++ b/src/svg/qsvgstyle_p.h @@ -143,9 +143,13 @@ struct QSvgExtraStates QSvgExtraStates(); qreal fillOpacity; + qreal strokeOpacity; QSvgFont *svgFont; Qt::Alignment textAnchor; int fontWeight; + Qt::FillRule fillRule; + qreal strokeDashOffset; + bool vectorEffect; // true if pen is cosmetic }; class QSvgStyleProperty : public QSvgRefCounted @@ -173,6 +177,14 @@ public: virtual Type type() const=0; }; +class QSvgFillStyleProperty : public QSvgStyleProperty +{ +public: + virtual QBrush brush(QPainter *p, QSvgExtraStates &states) = 0; + virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); + virtual void revert(QPainter *p, QSvgExtraStates &states); +}; + class QSvgQualityStyle : public QSvgStyleProperty { public: @@ -220,15 +232,14 @@ private: class QSvgFillStyle : public QSvgStyleProperty { public: - QSvgFillStyle(const QBrush &brush); - QSvgFillStyle(QSvgStyleProperty *style); + QSvgFillStyle(); virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); virtual void revert(QPainter *p, QSvgExtraStates &states); virtual Type type() const; void setFillRule(Qt::FillRule f); void setFillOpacity(qreal opacity); - void setFillStyle(QSvgStyleProperty* style); + void setFillStyle(QSvgFillStyleProperty* style); void setBrush(QBrush brush); const QBrush & qbrush() const @@ -246,7 +257,7 @@ public: return m_fillRule; } - QSvgStyleProperty* style() const + QSvgFillStyleProperty* style() const { return m_style; } @@ -256,7 +267,7 @@ public: m_gradientId = Id; } - QString getGradientId() const + QString gradientId() const { return m_gradientId; } @@ -276,16 +287,19 @@ private: // fill-opacity v v 'inherit' | QBrush m_fill; QBrush m_oldFill; - QSvgStyleProperty *m_style; + QSvgFillStyleProperty *m_style; - bool m_fillRuleSet; Qt::FillRule m_fillRule; - bool m_fillOpacitySet; + Qt::FillRule m_oldFillRule; qreal m_fillOpacity; - qreal m_oldOpacity; + qreal m_oldFillOpacity; + QString m_gradientId; - bool m_gradientResolved; - bool m_fillSet; + uint m_gradientResolved : 1; + + uint m_fillRuleSet : 1; + uint m_fillOpacitySet : 1; + uint m_fillSet : 1; }; class QSvgViewportFillStyle : public QSvgStyleProperty @@ -384,36 +398,116 @@ private: Qt::Alignment m_oldTextAnchor; int m_oldWeight; - unsigned m_familySet : 1; - unsigned m_sizeSet : 1; - unsigned m_styleSet : 1; - unsigned m_variantSet : 1; - unsigned m_weightSet : 1; - unsigned m_textAnchorSet : 1; + uint m_familySet : 1; + uint m_sizeSet : 1; + uint m_styleSet : 1; + uint m_variantSet : 1; + uint m_weightSet : 1; + uint m_textAnchorSet : 1; }; class QSvgStrokeStyle : public QSvgStyleProperty { public: - QSvgStrokeStyle(const QPen &pen); + QSvgStrokeStyle(); virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); virtual void revert(QPainter *p, QSvgExtraStates &states); virtual Type type() const; - void setStroke(bool stroke) + void setStroke(QBrush brush) + { + m_stroke.setBrush(brush); + m_style = 0; + m_strokeSet = 1; + } + + void setStyle(QSvgFillStyleProperty *style) + { + m_style = style; + m_strokeSet = 1; + } + + void setDashArray(const QVector &dashes); + + void setDashArrayNone() + { + m_stroke.setStyle(Qt::SolidLine); + m_strokeDashArraySet = 1; + } + + void setDashOffset(qreal offset) { - m_strokePresent = stroke; + m_strokeDashOffset = offset; + m_strokeDashOffsetSet = 1; } - bool strokePresent() const + void setLineCap(Qt::PenCapStyle cap) { - return m_strokePresent; + m_stroke.setCapStyle(cap); + m_strokeLineCapSet = 1; } - const QPen & qpen() const + void setLineJoin(Qt::PenJoinStyle join) { - return m_stroke; + m_stroke.setJoinStyle(join); + m_strokeLineJoinSet = 1; + } + + void setMiterLimit(qreal limit) + { + m_stroke.setMiterLimit(limit); + m_strokeMiterLimitSet = 1; + } + + void setOpacity(qreal opacity) + { + m_strokeOpacity = opacity; + m_strokeOpacitySet = 1; + } + + void setWidth(qreal width) + { + m_stroke.setWidthF(width); + m_strokeWidthSet = 1; + Q_ASSERT(!m_strokeDashArraySet); // set width before dash array. + } + + qreal width() + { + return m_stroke.widthF(); + } + + void setVectorEffect(bool nonScalingStroke) + { + m_vectorEffect = nonScalingStroke; + m_vectorEffectSet = 1; + } + + QSvgFillStyleProperty* style() const + { + return m_style; } + + void setGradientId(const QString &Id) + { + m_gradientId = Id; + } + + QString gradientId() const + { + return m_gradientId; + } + + void setGradientResolved(bool resolved) + { + m_gradientResolved = resolved; + } + + bool isGradientResolved() const + { + return m_gradientResolved; + } + private: // stroke v v 'inherit' | // stroke-dasharray v v 'inherit' | @@ -425,22 +519,44 @@ private: // stroke-width v v 'inherit' | QPen m_stroke; QPen m_oldStroke; - bool m_strokePresent; -}; + qreal m_strokeOpacity; + qreal m_oldStrokeOpacity; + qreal m_strokeDashOffset; + qreal m_oldStrokeDashOffset; + QSvgFillStyleProperty *m_style; + QString m_gradientId; + uint m_gradientResolved : 1; + uint m_vectorEffect : 1; + uint m_oldVectorEffect : 1; + + uint m_strokeSet : 1; + uint m_strokeDashArraySet : 1; + uint m_strokeDashOffsetSet : 1; + uint m_strokeLineCapSet : 1; + uint m_strokeLineJoinSet : 1; + uint m_strokeMiterLimitSet : 1; + uint m_strokeOpacitySet : 1; + uint m_strokeWidthSet : 1; + uint m_vectorEffectSet : 1; +}; -class QSvgSolidColorStyle : public QSvgStyleProperty +class QSvgSolidColorStyle : public QSvgFillStyleProperty { public: QSvgSolidColorStyle(const QColor &color); - virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); - virtual void revert(QPainter *p, QSvgExtraStates &states); virtual Type type() const; const QColor & qcolor() const { return m_solidColor; } + + QBrush brush(QPainter *, QSvgExtraStates &) + { + return m_solidColor; + } + private: // solid-color v x 'inherit' | // solid-opacity v x 'inherit' | @@ -450,13 +566,11 @@ private: QPen m_oldStroke; }; -class QSvgGradientStyle : public QSvgStyleProperty +class QSvgGradientStyle : public QSvgFillStyleProperty { public: QSvgGradientStyle(QGradient *grad); ~QSvgGradientStyle() { delete m_gradient; } - virtual void apply(QPainter *p, const QRectF &, QSvgNode *node, QSvgExtraStates &states); - virtual void revert(QPainter *p, QSvgExtraStates &states); virtual Type type() const; void setStopLink(const QString &link, QSvgTinyDocument *doc); @@ -474,8 +588,6 @@ public: return m_gradient; } - void addResolve(qreal offset); - bool gradientStopsSet() const { return m_gradientStopsSet; @@ -485,12 +597,10 @@ public: { m_gradientStopsSet = set; } + + QBrush brush(QPainter *, QSvgExtraStates &); private: QGradient *m_gradient; - QList m_resolvePoints; - - QBrush m_oldFill; - QMatrix m_matrix; QSvgTinyDocument *m_doc; diff --git a/src/svg/qsvgtinydocument.cpp b/src/svg/qsvgtinydocument.cpp index 614494f..ba610e7 100644 --- a/src/svg/qsvgtinydocument.cpp +++ b/src/svg/qsvgtinydocument.cpp @@ -240,7 +240,9 @@ void QSvgTinyDocument::draw(QPainter *p, const QRectF &bounds) //sets default style on the painter //### not the most optimal way mapSourceToTarget(p, bounds); - p->setPen(Qt::NoPen); + QPen pen(QBrush(Qt::NoBrush), 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + pen.setMiterLimit(4); + p->setPen(pen); p->setBrush(Qt::black); p->setRenderHint(QPainter::Antialiasing); p->setRenderHint(QPainter::SmoothPixmapTransform); diff --git a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp index 3d0d0c3..a57e793 100644 --- a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp +++ b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp @@ -756,7 +756,7 @@ static void opacity_drawSvgAndVerify(const QByteArray &data) void tst_QSvgRenderer::opacity() { - static const char *opacities[] = {"-1,4641", "0", "0.5", "1", "1.337"}; + static const char *opacities[] = {"-1.4641", "0", "0.5", "1", "1.337"}; static const char *firstColors[] = {"#7f7f7f", "#7f7f7f", "#402051", "blue", "#123456"}; static const char *secondColors[] = {"red", "#bad", "#bedead", "#7f7f7f", "#7f7f7f"}; @@ -796,8 +796,6 @@ void tst_QSvgRenderer::opacity() data.append("\"/>"); opacity_drawSvgAndVerify(data); } - // When support for gradients on strokes has been implemented, add the code below. - /* // Stroke-opacity for (int i = 0; i < 5; ++i) { QByteArray data(""); opacity_drawSvgAndVerify(data); } - */ } void tst_QSvgRenderer::paths() -- cgit v0.12