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