Revision control
++ // 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