Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qsvgstyle_p.h"
#include "qsvgnode_p.h"
#include "private/qsvganimate_p.h"
#include "qsvgdocument_p.h"
#include "qpainter.h"
#include "qcolor.h"
QT_BEGIN_NAMESPACE
QSvgExtraStates::QSvgExtraStates()
: imageRendering(QSvgQualityStyle::ImageRenderingAuto)
{
}
QSvgExtraStates::QSvgExtraStates(const QSvgDocument *doc) : QSvgExtraStates()
{
Q_ASSERT(doc);
m_doc = doc;
}
QSvgStyleProperty::~QSvgStyleProperty()
= default;
QSvgQualityStyle::QSvgQualityStyle(int color)
: m_imageRendering(QSvgQualityStyle::ImageRenderingAuto)
, m_oldImageRendering(QSvgQualityStyle::ImageRenderingAuto)
, m_imageRenderingSet(0)
{
Q_UNUSED(color);
}
QSvgQualityStyle::~QSvgQualityStyle()
= default;
void QSvgQualityStyle::setImageRendering(ImageRendering hint) {
m_imageRendering = hint;
m_imageRenderingSet = 1;
}
void QSvgQualityStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &states)
{
m_oldImageRendering = states.imageRendering;
if (m_imageRenderingSet) {
states.imageRendering = m_imageRendering;
}
if (m_imageRenderingSet) {
bool smooth = false;
if (m_imageRendering == ImageRenderingAuto)
// auto (the spec says to prefer quality)
smooth = true;
else
smooth = (m_imageRendering == ImageRenderingOptimizeQuality);
p->setRenderHint(QPainter::SmoothPixmapTransform, smooth);
}
}
void QSvgQualityStyle::revert(QPainter *p, QSvgExtraStates &states)
{
if (m_imageRenderingSet) {
states.imageRendering = m_oldImageRendering;
bool smooth = false;
if (m_oldImageRendering == ImageRenderingAuto)
smooth = true;
else
smooth = (m_oldImageRendering == ImageRenderingOptimizeQuality);
p->setRenderHint(QPainter::SmoothPixmapTransform, smooth);
}
}
QSvgFillStyle::QSvgFillStyle()
: m_fillRuleSet(0)
, m_fillOpacitySet(0)
, m_fillSet(0)
{
}
QSvgFillStyle::~QSvgFillStyle()
= default;
void QSvgFillStyle::setFillRule(Qt::FillRule f)
{
m_fillRuleSet = 1;
m_fillRule = f;
}
void QSvgFillStyle::setFillOpacity(qreal opacity)
{
m_fillOpacitySet = 1;
m_fillOpacity = opacity;
}
void QSvgFillStyle::setPaintServer(QSvgPaintServerSharedPtr paintServer)
{
m_paintServer = std::move(paintServer);
m_fillSet = 1;
}
void QSvgFillStyle::setBrush(QBrush brush)
{
m_fill = std::move(brush);
m_paintServer.reset();
m_fillSet = 1;
}
void QSvgFillStyle::apply(QPainter *p, const QSvgNode *n, QSvgExtraStates &states)
{
m_oldFill = p->brush();
m_oldFillRule = states.fillRule;
m_oldFillOpacity = states.fillOpacity;
if (m_fillRuleSet)
states.fillRule = m_fillRule;
if (m_fillSet) {
if (m_paintServer)
p->setBrush(m_paintServer->brush(p, n, states));
else
p->setBrush(m_fill);
}
if (m_fillOpacitySet)
states.fillOpacity = m_fillOpacity;
}
void QSvgFillStyle::revert(QPainter *p, QSvgExtraStates &states)
{
if (m_fillOpacitySet)
states.fillOpacity = m_oldFillOpacity;
if (m_fillSet)
p->setBrush(m_oldFill);
if (m_fillRuleSet)
states.fillRule = m_oldFillRule;
}
QSvgViewportFillStyle::QSvgViewportFillStyle(const QBrush &brush)
: m_viewportFill(brush)
{
}
QSvgViewportFillStyle::~QSvgViewportFillStyle()
= default;
void QSvgViewportFillStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &)
{
m_oldFill = p->brush();
p->setBrush(m_viewportFill);
}
void QSvgViewportFillStyle::revert(QPainter *p, QSvgExtraStates &)
{
p->setBrush(m_oldFill);
}
const int QSvgFontStyle::LIGHTER;
const int QSvgFontStyle::BOLDER;
QSvgFontStyle::QSvgFontStyle()
: m_familySet(0)
, m_sizeSet(0)
, m_styleSet(0)
, m_variantSet(0)
, m_weightSet(0)
, m_textAnchorSet(0)
{
}
QSvgFontStyle::~QSvgFontStyle()
= default;
void QSvgFontStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &states)
{
m_oldQFont = p->font();
m_oldSvgFont = states.svgFont;
m_oldTextAnchor = states.textAnchor;
m_oldWeight = states.fontWeight;
if (m_textAnchorSet)
states.textAnchor = m_textAnchor;
QFont font = m_oldQFont;
if (m_familySet) {
font.setFamilies(m_qfont.families());
states.svgFont = states.doc()->svgFont(m_qfont.family());
}
if (m_sizeSet)
font.setPointSizeF(m_qfont.pointSizeF());
if (m_styleSet)
font.setStyle(m_qfont.style());
if (m_variantSet)
font.setCapitalization(m_qfont.capitalization());
if (m_weightSet) {
if (m_weight == BOLDER) {
states.fontWeight = qMin(states.fontWeight, QFont::Weight::Black - 100) + 100;
} else if (m_weight == LIGHTER) {
states.fontWeight = qMax(states.fontWeight, QFont::Weight::Thin + 100) - 100;
} else {
states.fontWeight = m_weight;
}
font.setWeight(QFont::Weight(qBound(static_cast<int>(QFont::Weight::Thin),
states.fontWeight,
static_cast<int>(QFont::Weight::Black))));
}
p->setFont(font);
}
void QSvgFontStyle::revert(QPainter *p, QSvgExtraStates &states)
{
p->setFont(m_oldQFont);
states.svgFont = m_oldSvgFont;
states.textAnchor = m_oldTextAnchor;
states.fontWeight = m_oldWeight;
}
QSvgStrokeStyle::QSvgStrokeStyle()
: m_vectorEffect(0)
, m_oldVectorEffect(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)
{
}
QSvgStrokeStyle::~QSvgStrokeStyle()
= default;
void QSvgStrokeStyle::apply(QPainter *p, const QSvgNode *n, QSvgExtraStates &states)
{
m_oldStroke = p->pen();
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_paintServer)
pen.setBrush(m_paintServer->brush(p, n, states));
else
pen.setBrush(m_stroke.brush());
}
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.
QList<qreal> 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.
QList<qreal> 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());
// You can have dash offset on solid strokes in SVG files, but not in Qt.
// QPen::setDashOffset() will set the pen style to Qt::CustomDashLine,
// so don't call the method if the pen is solid.
if (setDashOffsetNeeded && pen.style() != Qt::SolidLine) {
qreal currentWidth = pen.widthF();
if (currentWidth == 0)
currentWidth = 1;
pen.setDashOffset(states.strokeDashOffset / currentWidth);
}
pen.setCosmetic(states.vectorEffect);
p->setPen(pen);
}
void QSvgStrokeStyle::revert(QPainter *p, QSvgExtraStates &states)
{
p->setPen(m_oldStroke);
states.strokeOpacity = m_oldStrokeOpacity;
states.strokeDashOffset = m_oldStrokeDashOffset;
states.vectorEffect = m_oldVectorEffect;
}
void QSvgStrokeStyle::setDashArray(const QList<qreal> &dashes)
{
if (m_strokeWidthSet) {
QList<qreal> 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;
}
QSvgTransformStyle::QSvgTransformStyle(const QTransform &trans)
: m_transform(trans)
{
}
QSvgTransformStyle::~QSvgTransformStyle()
= default;
void QSvgTransformStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &)
{
m_oldWorldTransform.push(p->worldTransform());
p->setWorldTransform(m_transform, true);
}
void QSvgTransformStyle::revert(QPainter *p, QSvgExtraStates &)
{
p->setWorldTransform(m_oldWorldTransform.pop(), false /* don't combine */);
}
QSvgStyleProperty::Type QSvgQualityStyle::type() const
{
return Quality;
}
QSvgStyleProperty::Type QSvgFillStyle::type() const
{
return Fill;
}
QSvgStyleProperty::Type QSvgViewportFillStyle::type() const
{
return ViewportFill;
}
QSvgStyleProperty::Type QSvgFontStyle::type() const
{
return Font;
}
QSvgStyleProperty::Type QSvgStrokeStyle::type() const
{
return Stroke;
}
QSvgStyleProperty::Type QSvgTransformStyle::type() const
{
return Transform;
}
QSvgCompOpStyle::QSvgCompOpStyle(QPainter::CompositionMode mode)
: m_mode(mode)
{
}
QSvgCompOpStyle::~QSvgCompOpStyle()
= default;
void QSvgCompOpStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &)
{
m_oldMode = p->compositionMode();
p->setCompositionMode(m_mode);
}
void QSvgCompOpStyle::revert(QPainter *p, QSvgExtraStates &)
{
p->setCompositionMode(m_oldMode);
}
QSvgStyleProperty::Type QSvgCompOpStyle::type() const
{
return CompOp;
}
QSvgOffsetStyle::~QSvgOffsetStyle()
= default;
void QSvgOffsetStyle::apply(QPainter *, const QSvgNode *, QSvgExtraStates &)
{
}
void QSvgOffsetStyle::revert(QPainter *, QSvgExtraStates &)
{
}
QSvgStyleProperty::Type QSvgOffsetStyle::type() const
{
return Offset;
}
QSvgOpacityStyle::QSvgOpacityStyle(qreal opacity)
: m_opacity(opacity)
{
}
QSvgOpacityStyle::~QSvgOpacityStyle()
= default;
void QSvgOpacityStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &)
{
m_oldOpacity = p->opacity();
p->setOpacity(m_opacity * m_oldOpacity);
}
void QSvgOpacityStyle::revert(QPainter *p, QSvgExtraStates &)
{
p->setOpacity(m_oldOpacity);
}
QSvgStyleProperty::Type QSvgOpacityStyle::type() const
{
return Opacity;
}
QSvgStaticStyle::QSvgStaticStyle()
= default;
QSvgStaticStyle::~QSvgStaticStyle()
= default;
void QSvgStaticStyle::apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states)
{
for (auto &prop : m_properties) {
if (prop)
prop->apply(p, node, states);
}
}
void QSvgStaticStyle::revert(QPainter *p, QSvgExtraStates &states)
{
for (auto &prop : m_properties) {
if (prop)
prop->revert(p, states);
}
}
namespace {
QColor sumValue(const QColor &c1, const QColor &c2)
{
QRgb rgb1 = c1.rgba();
QRgb rgb2 = c2.rgba();
int sumRed = qRed(rgb1) + qRed(rgb2);
int sumGreen = qGreen(rgb1) + qGreen(rgb2);
int sumBlue = qBlue(rgb1) + qBlue(rgb2);
QRgb sumRgb = qRgba(qBound(0, sumRed, 255),
qBound(0, sumGreen, 255),
qBound(0, sumBlue, 255),
255);
return QColor(sumRgb);
}
qreal sumValue(qreal value1, qreal value2)
{
qreal sumValue = value1 + value2;
return qBound(0.0, sumValue, 1.0);
}
}
QSvgAnimatedStyle::QSvgAnimatedStyle()
{
}
QSvgAnimatedStyle::~QSvgAnimatedStyle()
{
}
void QSvgAnimatedStyle::apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states)
{
QSharedPointer<QSvgAbstractAnimator> animator = states.doc()->animator();
QList<QSvgAbstractAnimation *> nodeAnims = animator->animationsForNode(node);
savePaintingState(p, node, states);
if (nodeAnims.isEmpty())
return;
QSvgStyleState currentStyle = m_static;
for (const QSvgAbstractAnimation *anim : std::as_const(nodeAnims)) {
if (!anim->isActive())
continue;
fetchStyleState(anim, currentStyle);
}
applyStyle(p, states, currentStyle);
}
void QSvgAnimatedStyle::revert(QPainter *p, QSvgExtraStates &states)
{
p->setWorldTransform(m_worldTransform);
p->setBrush(m_static.fill);
p->setPen(m_static.stroke);
p->setOpacity(m_static.opacity);
states.fillOpacity = m_static.fillOpacity;
states.strokeOpacity = m_static.strokeOpacity;
}
void QSvgAnimatedStyle::savePaintingState(const QPainter *p, const QSvgNode *node, QSvgExtraStates &states)
{
m_worldTransform = p->worldTransform();
if (auto prop = node->style().property(QSvgStyleProperty::Transform))
m_static.transform = (static_cast<QSvgTransformStyle*>(prop))->qtransform();
if (auto prop = node->style().property(QSvgStyleProperty::Offset)) {
QSvgOffsetStyle *offset = static_cast<QSvgOffsetStyle*>(prop);
m_static.offsetPath = offset->path();
m_static.offsetRotateType = offset->rotateType();
m_static.offsetRotate = offset->rotateAngle();
}
m_static.fill = p->brush();
m_static.stroke = p->pen();
m_static.fillOpacity = states.fillOpacity;
m_static.strokeOpacity = states.strokeOpacity;
m_static.opacity = p->opacity();
m_transformToNode = m_static.transform.inverted() * m_worldTransform;
}
void QSvgAnimatedStyle::fetchStyleState(const QSvgAbstractAnimation *animation, QSvgStyleState ¤tStyle)
{
bool replace = animation->animationType() == QSvgAbstractAnimation::CSS ? true :
(static_cast<const QSvgAnimateNode *>(animation))->additiveType() == QSvgAnimateNode::Replace;
QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
for (const QSvgAbstractAnimatedProperty *property : std::as_const(properties)) {
if (property->propertyName() == QStringLiteral("fill")) {
QBrush brush = currentStyle.fill;
QColor brushColor = brush.color();
QColor animatedColor = property->interpolatedValue().value<QColor>();
QColor sumOrReplaceColor = replace ? animatedColor : sumValue(brushColor, animatedColor);
brush.setColor(sumOrReplaceColor);
currentStyle.fill = brush;
} else if (property->propertyName() == QStringLiteral("stroke")) {
QPen &pen = currentStyle.stroke;
QBrush penBrush = pen.brush();
QColor penColor = penBrush.color();
QColor animatedColor = property->interpolatedValue().value<QColor>();
QColor sumOrReplaceColor = replace ? animatedColor : sumValue(penColor, animatedColor);
penBrush.setColor(sumOrReplaceColor);
penBrush.setStyle(Qt::SolidPattern);
pen.setBrush(penBrush);
} else if (property->propertyName() == QStringLiteral("transform")) {
QTransform animatedTransform = property->interpolatedValue().value<QTransform>();
QTransform sumOrReplaceTransform = replace ? animatedTransform : animatedTransform * currentStyle.transform;
currentStyle.transform = sumOrReplaceTransform;
} else if (property->propertyName() == QStringLiteral("fill-opacity")) {
qreal animatedFillOpacity = property->interpolatedValue().value<qreal>();
qreal sumOrReplaceOpacity = replace ? animatedFillOpacity : sumValue(currentStyle.fillOpacity, animatedFillOpacity);
currentStyle.fillOpacity = sumOrReplaceOpacity;
} else if (property->propertyName() == QStringLiteral("stroke-opacity")) {
qreal animatedStrokeOpacity = property->interpolatedValue().value<qreal>();
qreal sumOrReplaceOpacity = replace ? animatedStrokeOpacity : sumValue(currentStyle.strokeOpacity, animatedStrokeOpacity);
currentStyle.strokeOpacity = sumOrReplaceOpacity;
} else if (property->propertyName() == QStringLiteral("opacity")) {
qreal animatedOpacity = property->interpolatedValue().value<qreal>();
qreal sumOrReplaceOpacity = replace ? animatedOpacity : sumValue(currentStyle.opacity, animatedOpacity);
currentStyle.opacity = sumOrReplaceOpacity;
} else if (property->propertyName() == QStringLiteral("offset-distance")) {
qreal offsetDistance = property->interpolatedValue().value<qreal>();
currentStyle.offsetDistance = offsetDistance;
}
}
}
void QSvgAnimatedStyle::applyStyle(QPainter *p, QSvgExtraStates &states, const QSvgStyleState ¤tStyle)
{
p->setBrush(currentStyle.fill);
states.fillOpacity = currentStyle.fillOpacity;
p->setPen(currentStyle.stroke);
states.strokeOpacity = currentStyle.strokeOpacity;
p->setOpacity(currentStyle.opacity);
QTransform transform = currentStyle.transform;
QTransform offset;
if (m_static.offsetPath) {
qreal offsetDistance = currentStyle.offsetDistance;
qreal angle = m_static.offsetPath.value().angleAtPercent(offsetDistance);
QPointF position = m_static.offsetPath.value().pointAtPercent(offsetDistance);
offset.translate(position.x(), position.y());
switch (m_static.offsetRotateType) {
case QtSvg::OffsetRotateType::Auto:
offset.rotate(-angle);
break;
case QtSvg::OffsetRotateType::Angle:
offset.rotate(m_static.offsetRotate);
break;
case QtSvg::OffsetRotateType::AutoAngle:
offset.rotate(m_static.offsetRotate - angle);
break;
case QtSvg::OffsetRotateType::Reverse:
offset.rotate(180 - angle);
break;
case QtSvg::OffsetRotateType::ReverseAngle:
offset.rotate(180 + m_static.offsetRotate - angle);
break;
}
}
QTransform combinedTransform = transform * offset * m_transformToNode;
p->setWorldTransform(combinedTransform);
}
QT_END_NAMESPACE