Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsvgstyle.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
6#include "qsvgstyle_p.h"
7
8#include "qsvgnode_p.h"
9#include "private/qsvganimate_p.h"
10#include "qsvgdocument_p.h"
11
12#include "qpainter.h"
13#include "qcolor.h"
14
16
17QSvgExtraStates::QSvgExtraStates()
18 : fillOpacity(1.0),
19 strokeOpacity(1.0),
20 svgFont(0),
21 textAnchor(Qt::AlignLeft),
22 fontWeight(QFont::Normal),
23 fillRule(Qt::WindingFill),
24 strokeDashOffset(0),
25 vectorEffect(false),
26 imageRendering(QSvgQualityStyle::ImageRenderingAuto)
27{
28}
29
30QSvgExtraStates::QSvgExtraStates(const QSvgDocument *doc) : QSvgExtraStates()
31{
32 Q_ASSERT(doc);
33 m_doc = doc;
34}
35
36QSvgStyleProperty::~QSvgStyleProperty()
37 = default;
38
39QSvgQualityStyle::QSvgQualityStyle(int color)
40 : m_imageRendering(QSvgQualityStyle::ImageRenderingAuto)
41 , m_oldImageRendering(QSvgQualityStyle::ImageRenderingAuto)
42 , m_imageRenderingSet(0)
43{
44 Q_UNUSED(color);
45}
46
47QSvgQualityStyle::~QSvgQualityStyle()
48 = default;
49
50void QSvgQualityStyle::setImageRendering(ImageRendering hint) {
51 m_imageRendering = hint;
52 m_imageRenderingSet = 1;
53}
54
55void QSvgQualityStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &states)
56{
57 m_oldImageRendering = states.imageRendering;
58 if (m_imageRenderingSet) {
59 states.imageRendering = m_imageRendering;
60 }
61 if (m_imageRenderingSet) {
62 bool smooth = false;
63 if (m_imageRendering == ImageRenderingAuto)
64 // auto (the spec says to prefer quality)
65 smooth = true;
66 else
67 smooth = (m_imageRendering == ImageRenderingOptimizeQuality);
68 p->setRenderHint(QPainter::SmoothPixmapTransform, smooth);
69 }
70}
71
72void QSvgQualityStyle::revert(QPainter *p, QSvgExtraStates &states)
73{
74 if (m_imageRenderingSet) {
75 states.imageRendering = m_oldImageRendering;
76 bool smooth = false;
77 if (m_oldImageRendering == ImageRenderingAuto)
78 smooth = true;
79 else
80 smooth = (m_oldImageRendering == ImageRenderingOptimizeQuality);
81 p->setRenderHint(QPainter::SmoothPixmapTransform, smooth);
82 }
83}
84
85QSvgFillStyle::QSvgFillStyle()
86 : m_fillRuleSet(0)
87 , m_fillOpacitySet(0)
88 , m_fillSet(0)
89{
90}
91
92QSvgFillStyle::~QSvgFillStyle()
93 = default;
94
95void QSvgFillStyle::setFillRule(Qt::FillRule f)
96{
97 m_fillRuleSet = 1;
98 m_fillRule = f;
99}
100
101void QSvgFillStyle::setFillOpacity(qreal opacity)
102{
103 m_fillOpacitySet = 1;
104 m_fillOpacity = opacity;
105}
106
107void QSvgFillStyle::setPaintServer(QSvgPaintServerSharedPtr paintServer)
108{
109 m_paintServer = std::move(paintServer);
110 m_fillSet = 1;
111}
112
113void QSvgFillStyle::setBrush(QBrush brush)
114{
115 m_fill = std::move(brush);
116 m_paintServer.reset();
117 m_fillSet = 1;
118}
119
120void QSvgFillStyle::apply(QPainter *p, const QSvgNode *n, QSvgExtraStates &states)
121{
122 m_oldFill = p->brush();
123 m_oldFillRule = states.fillRule;
124 m_oldFillOpacity = states.fillOpacity;
125
126 if (m_fillRuleSet)
127 states.fillRule = m_fillRule;
128 if (m_fillSet) {
129 if (m_paintServer)
130 p->setBrush(m_paintServer->brush(p, n, states));
131 else
132 p->setBrush(m_fill);
133 }
134 if (m_fillOpacitySet)
135 states.fillOpacity = m_fillOpacity;
136}
137
138void QSvgFillStyle::revert(QPainter *p, QSvgExtraStates &states)
139{
140 if (m_fillOpacitySet)
141 states.fillOpacity = m_oldFillOpacity;
142 if (m_fillSet)
143 p->setBrush(m_oldFill);
144 if (m_fillRuleSet)
145 states.fillRule = m_oldFillRule;
146}
147
148QSvgViewportFillStyle::QSvgViewportFillStyle(const QBrush &brush)
149 : m_viewportFill(brush)
150{
151}
152
153QSvgViewportFillStyle::~QSvgViewportFillStyle()
154 = default;
155
156void QSvgViewportFillStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &)
157{
158 m_oldFill = p->brush();
159 p->setBrush(m_viewportFill);
160}
161
162void QSvgViewportFillStyle::revert(QPainter *p, QSvgExtraStates &)
163{
164 p->setBrush(m_oldFill);
165}
166
167const int QSvgFontStyle::LIGHTER;
168const int QSvgFontStyle::BOLDER;
169
170QSvgFontStyle::QSvgFontStyle()
171 : m_familySet(0)
172 , m_sizeSet(0)
173 , m_styleSet(0)
174 , m_variantSet(0)
175 , m_weightSet(0)
176 , m_textAnchorSet(0)
177{
178}
179
180QSvgFontStyle::~QSvgFontStyle()
181 = default;
182
183void QSvgFontStyle::apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states)
184{
185 m_oldQFont = p->font();
186 m_oldSvgFont = states.svgFont;
187 m_oldTextAnchor = states.textAnchor;
188 m_oldWeight = states.fontWeight;
189
190 if (m_textAnchorSet)
191 states.textAnchor = m_textAnchor;
192
193 QFont font = m_oldQFont;
194 if (m_familySet) {
195 font.setFamilies(m_qfont.families());
196 states.svgFont = node->document()->svgFont(m_qfont.family());
197 }
198
199 if (m_sizeSet)
200 font.setPointSizeF(m_qfont.pointSizeF());
201
202 if (m_styleSet)
203 font.setStyle(m_qfont.style());
204
205 if (m_variantSet)
206 font.setCapitalization(m_qfont.capitalization());
207
208 if (m_weightSet) {
209 if (m_weight == BOLDER) {
210 states.fontWeight = qMin(states.fontWeight, QFont::Weight::Black - 100) + 100;
211 } else if (m_weight == LIGHTER) {
212 states.fontWeight = qMax(states.fontWeight, QFont::Weight::Thin + 100) - 100;
213 } else {
214 states.fontWeight = m_weight;
215 }
216 font.setWeight(QFont::Weight(qBound(static_cast<int>(QFont::Weight::Thin),
217 states.fontWeight,
218 static_cast<int>(QFont::Weight::Black))));
219 }
220
221 p->setFont(font);
222}
223
224void QSvgFontStyle::revert(QPainter *p, QSvgExtraStates &states)
225{
226 p->setFont(m_oldQFont);
227 states.svgFont = m_oldSvgFont;
228 states.textAnchor = m_oldTextAnchor;
229 states.fontWeight = m_oldWeight;
230}
231
232QSvgStrokeStyle::QSvgStrokeStyle()
233 : m_vectorEffect(0)
234 , m_oldVectorEffect(0)
235 , m_strokeSet(0)
236 , m_strokeDashArraySet(0)
237 , m_strokeDashOffsetSet(0)
238 , m_strokeLineCapSet(0)
239 , m_strokeLineJoinSet(0)
240 , m_strokeMiterLimitSet(0)
241 , m_strokeOpacitySet(0)
242 , m_strokeWidthSet(0)
243 , m_vectorEffectSet(0)
244{
245}
246
247QSvgStrokeStyle::~QSvgStrokeStyle()
248 = default;
249
250void QSvgStrokeStyle::apply(QPainter *p, const QSvgNode *n, QSvgExtraStates &states)
251{
252 m_oldStroke = p->pen();
253 m_oldStrokeOpacity = states.strokeOpacity;
254 m_oldStrokeDashOffset = states.strokeDashOffset;
255 m_oldVectorEffect = states.vectorEffect;
256
257 QPen pen = p->pen();
258
259 qreal oldWidth = pen.widthF();
260 qreal width = m_stroke.widthF();
261 if (oldWidth == 0)
262 oldWidth = 1;
263 if (width == 0)
264 width = 1;
265 qreal scale = oldWidth / width;
266
267 if (m_strokeOpacitySet)
268 states.strokeOpacity = m_strokeOpacity;
269
270 if (m_vectorEffectSet)
271 states.vectorEffect = m_vectorEffect;
272
273 if (m_strokeSet) {
274 if (m_paintServer)
275 pen.setBrush(m_paintServer->brush(p, n, states));
276 else
277 pen.setBrush(m_stroke.brush());
278 }
279
280 if (m_strokeWidthSet)
281 pen.setWidthF(m_stroke.widthF());
282
283 bool setDashOffsetNeeded = false;
284
285 if (m_strokeDashOffsetSet) {
286 states.strokeDashOffset = m_strokeDashOffset;
287 setDashOffsetNeeded = true;
288 }
289
290 if (m_strokeDashArraySet) {
291 if (m_stroke.style() == Qt::SolidLine) {
292 pen.setStyle(Qt::SolidLine);
293 } else if (m_strokeWidthSet || oldWidth == 1) {
294 // If both width and dash array was set, the dash array is already scaled correctly.
295 pen.setDashPattern(m_stroke.dashPattern());
296 setDashOffsetNeeded = true;
297 } else {
298 // If dash array was set, but not the width, the dash array has to be scaled with respect to the old width.
299 QList<qreal> dashes = m_stroke.dashPattern();
300 for (int i = 0; i < dashes.size(); ++i)
301 dashes[i] /= oldWidth;
302 pen.setDashPattern(dashes);
303 setDashOffsetNeeded = true;
304 }
305 } else if (m_strokeWidthSet && pen.style() != Qt::SolidLine && scale != 1) {
306 // If the width was set, but not the dash array, the old dash array must be scaled with respect to the new width.
307 QList<qreal> dashes = pen.dashPattern();
308 for (int i = 0; i < dashes.size(); ++i)
309 dashes[i] *= scale;
310 pen.setDashPattern(dashes);
311 setDashOffsetNeeded = true;
312 }
313
314 if (m_strokeLineCapSet)
315 pen.setCapStyle(m_stroke.capStyle());
316 if (m_strokeLineJoinSet)
317 pen.setJoinStyle(m_stroke.joinStyle());
318 if (m_strokeMiterLimitSet)
319 pen.setMiterLimit(m_stroke.miterLimit());
320
321 // You can have dash offset on solid strokes in SVG files, but not in Qt.
322 // QPen::setDashOffset() will set the pen style to Qt::CustomDashLine,
323 // so don't call the method if the pen is solid.
324 if (setDashOffsetNeeded && pen.style() != Qt::SolidLine) {
325 qreal currentWidth = pen.widthF();
326 if (currentWidth == 0)
327 currentWidth = 1;
328 pen.setDashOffset(states.strokeDashOffset / currentWidth);
329 }
330
331 pen.setCosmetic(states.vectorEffect);
332
333 p->setPen(pen);
334}
335
336void QSvgStrokeStyle::revert(QPainter *p, QSvgExtraStates &states)
337{
338 p->setPen(m_oldStroke);
339 states.strokeOpacity = m_oldStrokeOpacity;
340 states.strokeDashOffset = m_oldStrokeDashOffset;
341 states.vectorEffect = m_oldVectorEffect;
342}
343
344void QSvgStrokeStyle::setDashArray(const QList<qreal> &dashes)
345{
346 if (m_strokeWidthSet) {
347 QList<qreal> d = dashes;
348 qreal w = m_stroke.widthF();
349 if (w != 0 && w != 1) {
350 for (int i = 0; i < d.size(); ++i)
351 d[i] /= w;
352 }
353 m_stroke.setDashPattern(d);
354 } else {
355 m_stroke.setDashPattern(dashes);
356 }
357 m_strokeDashArraySet = 1;
358}
359
360QSvgTransformStyle::QSvgTransformStyle(const QTransform &trans)
361 : m_transform(trans)
362{
363}
364
365QSvgTransformStyle::~QSvgTransformStyle()
366 = default;
367
368void QSvgTransformStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &)
369{
370 m_oldWorldTransform.push(p->worldTransform());
371 p->setWorldTransform(m_transform, true);
372}
373
374void QSvgTransformStyle::revert(QPainter *p, QSvgExtraStates &)
375{
376 p->setWorldTransform(m_oldWorldTransform.pop(), false /* don't combine */);
377}
378
379QSvgStyleProperty::Type QSvgQualityStyle::type() const
380{
381 return Quality;
382}
383
384QSvgStyleProperty::Type QSvgFillStyle::type() const
385{
386 return Fill;
387}
388
389QSvgStyleProperty::Type QSvgViewportFillStyle::type() const
390{
391 return ViewportFill;
392}
393
394QSvgStyleProperty::Type QSvgFontStyle::type() const
395{
396 return Font;
397}
398
399QSvgStyleProperty::Type QSvgStrokeStyle::type() const
400{
401 return Stroke;
402}
403
404QSvgStyleProperty::Type QSvgTransformStyle::type() const
405{
406 return Transform;
407}
408
409
410QSvgCompOpStyle::QSvgCompOpStyle(QPainter::CompositionMode mode)
411 : m_mode(mode)
412{
413
414}
415
416QSvgCompOpStyle::~QSvgCompOpStyle()
417 = default;
418
419void QSvgCompOpStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &)
420{
421 m_oldMode = p->compositionMode();
422 p->setCompositionMode(m_mode);
423}
424
425void QSvgCompOpStyle::revert(QPainter *p, QSvgExtraStates &)
426{
427 p->setCompositionMode(m_oldMode);
428}
429
430QSvgStyleProperty::Type QSvgCompOpStyle::type() const
431{
432 return CompOp;
433}
434
435QSvgOffsetStyle::~QSvgOffsetStyle()
436 = default;
437
438void QSvgOffsetStyle::apply(QPainter *, const QSvgNode *, QSvgExtraStates &)
439{
440}
441
442void QSvgOffsetStyle::revert(QPainter *, QSvgExtraStates &)
443{
444}
445
446QSvgStyleProperty::Type QSvgOffsetStyle::type() const
447{
448 return Offset;
449}
450
451QSvgOpacityStyle::QSvgOpacityStyle(qreal opacity)
452 : m_opacity(opacity), m_oldOpacity(0)
453{
454
455}
456
457QSvgOpacityStyle::~QSvgOpacityStyle()
458 = default;
459
460void QSvgOpacityStyle::apply(QPainter *p, const QSvgNode *, QSvgExtraStates &)
461{
462 m_oldOpacity = p->opacity();
463 p->setOpacity(m_opacity * m_oldOpacity);
464}
465
466void QSvgOpacityStyle::revert(QPainter *p, QSvgExtraStates &)
467{
468 p->setOpacity(m_oldOpacity);
469}
470
471QSvgStyleProperty::Type QSvgOpacityStyle::type() const
472{
473 return Opacity;
474}
475
476QSvgStaticStyle::QSvgStaticStyle()
477 = default;
478QSvgStaticStyle::~QSvgStaticStyle()
479 = default;
480
481void QSvgStaticStyle::apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states)
482{
483 for (auto &prop : m_properties) {
484 if (prop)
485 prop->apply(p, node, states);
486 }
487}
488
489void QSvgStaticStyle::revert(QPainter *p, QSvgExtraStates &states)
490{
491 for (auto &prop : m_properties) {
492 if (prop)
493 prop->revert(p, states);
494 }
495}
496
497namespace {
498
499QColor sumValue(const QColor &c1, const QColor &c2)
500{
501 QRgb rgb1 = c1.rgba();
502 QRgb rgb2 = c2.rgba();
503 int sumRed = qRed(rgb1) + qRed(rgb2);
504 int sumGreen = qGreen(rgb1) + qGreen(rgb2);
505 int sumBlue = qBlue(rgb1) + qBlue(rgb2);
506
507 QRgb sumRgb = qRgba(qBound(0, sumRed, 255),
508 qBound(0, sumGreen, 255),
509 qBound(0, sumBlue, 255),
510 255);
511
512 return QColor(sumRgb);
513}
514
515qreal sumValue(qreal value1, qreal value2)
516{
517 qreal sumValue = value1 + value2;
518 return qBound(0.0, sumValue, 1.0);
519}
520
521}
522
523QSvgAnimatedStyle::QSvgAnimatedStyle()
524{
525}
526
527QSvgAnimatedStyle::~QSvgAnimatedStyle()
528{
529}
530
531void QSvgAnimatedStyle::apply(QPainter *p, const QSvgNode *node, QSvgExtraStates &states)
532{
533 QSharedPointer<QSvgAbstractAnimator> animator = node->document()->animator();
534 QList<QSvgAbstractAnimation *> nodeAnims = animator->animationsForNode(node);
535
536 savePaintingState(p, node, states);
537 if (nodeAnims.isEmpty())
538 return;
539
540 QSvgStyleState currentStyle = m_static;
541 for (const QSvgAbstractAnimation *anim : std::as_const(nodeAnims)) {
542 if (!anim->isActive())
543 continue;
544
545 fetchStyleState(anim, currentStyle);
546 }
547
548 applyStyle(p, states, currentStyle);
549}
550
551void QSvgAnimatedStyle::revert(QPainter *p, QSvgExtraStates &states)
552{
553 p->setWorldTransform(m_worldTransform);
554 p->setBrush(m_static.fill);
555 p->setPen(m_static.stroke);
556 p->setOpacity(m_static.opacity);
557 states.fillOpacity = m_static.fillOpacity;
558 states.strokeOpacity = m_static.strokeOpacity;
559}
560
561void QSvgAnimatedStyle::savePaintingState(const QPainter *p, const QSvgNode *node, QSvgExtraStates &states)
562{
563 m_worldTransform = p->worldTransform();
564 if (auto prop = node->style().property(QSvgStyleProperty::Transform))
565 m_static.transform = (static_cast<QSvgTransformStyle*>(prop))->qtransform();
566
567 if (auto prop = node->style().property(QSvgStyleProperty::Offset)) {
568 QSvgOffsetStyle *offset = static_cast<QSvgOffsetStyle*>(prop);
569 m_static.offsetPath = offset->path();
570 m_static.offsetRotateType = offset->rotateType();
571 m_static.offsetRotate = offset->rotateAngle();
572 }
573
574 m_static.fill = p->brush();
575 m_static.stroke = p->pen();
576 m_static.fillOpacity = states.fillOpacity;
577 m_static.strokeOpacity = states.strokeOpacity;
578 m_static.opacity = p->opacity();
579 m_transformToNode = m_static.transform.inverted() * m_worldTransform;
580}
581
582void QSvgAnimatedStyle::fetchStyleState(const QSvgAbstractAnimation *animation, QSvgStyleState &currentStyle)
583{
584 bool replace = animation->animationType() == QSvgAbstractAnimation::CSS ? true :
585 (static_cast<const QSvgAnimateNode *>(animation))->additiveType() == QSvgAnimateNode::Replace;
586
587 QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
588 for (const QSvgAbstractAnimatedProperty *property : std::as_const(properties)) {
589 if (property->propertyName() == QStringLiteral("fill")) {
590 QBrush brush = currentStyle.fill;
591 QColor brushColor = brush.color();
592 QColor animatedColor = property->interpolatedValue().value<QColor>();
593 QColor sumOrReplaceColor = replace ? animatedColor : sumValue(brushColor, animatedColor);
594 brush.setColor(sumOrReplaceColor);
595 currentStyle.fill = brush;
596 } else if (property->propertyName() == QStringLiteral("stroke")) {
597 QPen &pen = currentStyle.stroke;
598 QBrush penBrush = pen.brush();
599 QColor penColor = penBrush.color();
600 QColor animatedColor = property->interpolatedValue().value<QColor>();
601 QColor sumOrReplaceColor = replace ? animatedColor : sumValue(penColor, animatedColor);
602 penBrush.setColor(sumOrReplaceColor);
603 penBrush.setStyle(Qt::SolidPattern);
604 pen.setBrush(penBrush);
605 } else if (property->propertyName() == QStringLiteral("transform")) {
606 QTransform animatedTransform = property->interpolatedValue().value<QTransform>();
607 QTransform sumOrReplaceTransform = replace ? animatedTransform : animatedTransform * currentStyle.transform;
608 currentStyle.transform = sumOrReplaceTransform;
609 } else if (property->propertyName() == QStringLiteral("fill-opacity")) {
610 qreal animatedFillOpacity = property->interpolatedValue().value<qreal>();
611 qreal sumOrReplaceOpacity = replace ? animatedFillOpacity : sumValue(currentStyle.fillOpacity, animatedFillOpacity);
612 currentStyle.fillOpacity = sumOrReplaceOpacity;
613 } else if (property->propertyName() == QStringLiteral("stroke-opacity")) {
614 qreal animatedStrokeOpacity = property->interpolatedValue().value<qreal>();
615 qreal sumOrReplaceOpacity = replace ? animatedStrokeOpacity : sumValue(currentStyle.strokeOpacity, animatedStrokeOpacity);
616 currentStyle.strokeOpacity = sumOrReplaceOpacity;
617 } else if (property->propertyName() == QStringLiteral("opacity")) {
618 qreal animatedOpacity = property->interpolatedValue().value<qreal>();
619 qreal sumOrReplaceOpacity = replace ? animatedOpacity : sumValue(currentStyle.opacity, animatedOpacity);
620 currentStyle.opacity = sumOrReplaceOpacity;
621 } else if (property->propertyName() == QStringLiteral("offset-distance")) {
622 qreal offsetDistance = property->interpolatedValue().value<qreal>();
623 currentStyle.offsetDistance = offsetDistance;
624 }
625 }
626}
627
628void QSvgAnimatedStyle::applyStyle(QPainter *p, QSvgExtraStates &states, const QSvgStyleState &currentStyle)
629{
630 p->setBrush(currentStyle.fill);
631 states.fillOpacity = currentStyle.fillOpacity;
632 p->setPen(currentStyle.stroke);
633 states.strokeOpacity = currentStyle.strokeOpacity;
634 p->setOpacity(currentStyle.opacity);
635
636 QTransform transform = currentStyle.transform;
637 QTransform offset;
638 if (m_static.offsetPath) {
639 qreal offsetDistance = currentStyle.offsetDistance;
640 qreal angle = m_static.offsetPath.value().angleAtPercent(offsetDistance);
641 QPointF position = m_static.offsetPath.value().pointAtPercent(offsetDistance);
642 offset.translate(position.x(), position.y());
643
644 switch (m_static.offsetRotateType) {
645 case QtSvg::OffsetRotateType::Auto:
646 offset.rotate(-angle);
647 break;
648 case QtSvg::OffsetRotateType::Angle:
649 offset.rotate(m_static.offsetRotate);
650 break;
651 case QtSvg::OffsetRotateType::AutoAngle:
652 offset.rotate(m_static.offsetRotate - angle);
653 break;
654 case QtSvg::OffsetRotateType::Reverse:
655 offset.rotate(180 - angle);
656 break;
657 case QtSvg::OffsetRotateType::ReverseAngle:
658 offset.rotate(180 + m_static.offsetRotate - angle);
659 break;
660 }
661 }
662
663 QTransform combinedTransform = transform * offset * m_transformToNode;
664 p->setWorldTransform(combinedTransform);
665}
666
667QT_END_NAMESPACE
Combined button and popup list for selecting options.
#define QStringLiteral(str)
Definition qstring.h:1860