1// Copyright (C) 2016 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
4#include "qsvggraphics_p.h"
5
6#include "qsvgfont_p.h"
7
8#include <qabstracttextdocumentlayout.h>
9#include <qdebug.h>
10#include <qloggingcategory.h>
11#include <qpainter.h>
12#include <qscopedvaluerollback.h>
13#include <qtextcursor.h>
14#include <qtextdocument.h>
15#include <private/qfixed_p.h>
16
17#include <QElapsedTimer>
18#include <QLoggingCategory>
19
20#include <math.h>
21#include <limits.h>
22
23QT_BEGIN_NAMESPACE
24
25Q_LOGGING_CATEGORY(lcSvgDraw, "qt.svg.draw")
26Q_LOGGING_CATEGORY(lcSvgTiming, "qt.svg.timing")
27
28#define QT_SVG_TIMING_ENTER \
29 QElapsedTimer qtSvgTimer; qtSvgTimer.start();
30
31#define QT_SVG_TIMING_EXIT(TYPE) \
32 if (Q_UNLIKELY(lcSvgTiming().isDebugEnabled())) \
33 qCDebug(lcSvgTiming) << "Drawing" << TYPE << "took" << (qtSvgTimer.nsecsElapsed() / 1000000.0f) << "ms";
34
35#define QT_SVG_DRAW_SHAPE(command) \
36 { qreal oldOpacity = p->opacity(); \
37 QBrush oldBrush = p->brush(); \
38 QPen oldPen = p->pen(); \
39 p->setPen(Qt::NoPen); \
40 p->setOpacity(oldOpacity * states.fillOpacity); \
41 command; \
42 p->setPen(oldPen); \
43 if (oldPen != Qt::NoPen && oldPen.brush() != Qt::NoBrush && oldPen.widthF() != 0) { \
44 p->setOpacity(oldOpacity * states.strokeOpacity); \
45 p->setBrush(Qt::NoBrush); \
46 command; \
47 p->setBrush(oldBrush); \
48 } \
49 p->setOpacity(oldOpacity); }
50
51#ifndef QT_SVG_MAX_LAYOUT_SIZE
52#define QT_SVG_MAX_LAYOUT_SIZE (qint64(QFIXED_MAX / 2))
53#endif
54
55void QSvgAnimation::draw(QPainter *, QSvgExtraStates &)
56{
57 qWarning(msg: "<animation> no implemented");
58}
59
60static inline QRectF boundsOnStroke(QPainter *p, const QPainterPath &path, qreal width)
61{
62 QPainterPathStroker stroker;
63 stroker.setWidth(width);
64 QPainterPath stroke = stroker.createStroke(path);
65 return p->transform().map(p: stroke).boundingRect();
66}
67
68QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect)
69 : QSvgNode(parent), m_bounds(rect)
70{
71}
72
73QRectF QSvgEllipse::fastBounds(QPainter *p, QSvgExtraStates &) const
74{
75 return p->transform().mapRect(m_bounds);
76}
77
78QRectF QSvgEllipse::bounds(QPainter *p, QSvgExtraStates &) const
79{
80 QPainterPath path;
81 path.addEllipse(rect: m_bounds);
82 qreal sw = strokeWidth(p);
83 return qFuzzyIsNull(d: sw) ? p->transform().map(p: path).boundingRect() : boundsOnStroke(p, path, width: sw);
84}
85
86void QSvgEllipse::draw(QPainter *p, QSvgExtraStates &states)
87{
88 QT_SVG_TIMING_ENTER
89 applyStyle(p, states);
90 if (shouldDrawNode(p, states))
91 QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds));
92 revertStyle(p, states);
93 QT_SVG_TIMING_EXIT("Ellipse")
94}
95
96QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path)
97 : QSvgNode(parent), m_path(path)
98{
99}
100
101void QSvgArc::draw(QPainter *p, QSvgExtraStates &states)
102{
103 QT_SVG_TIMING_ENTER
104 applyStyle(p, states);
105 if (shouldDrawNode(p, states)) {
106 if (p->pen().widthF() != 0) {
107 qreal oldOpacity = p->opacity();
108 p->setOpacity(oldOpacity * states.strokeOpacity);
109 p->drawPath(path: m_path);
110 p->setOpacity(oldOpacity);
111 }
112 }
113 revertStyle(p, states);
114 QT_SVG_TIMING_EXIT("Arc")
115}
116
117QSvgImage::QSvgImage(QSvgNode *parent, const QImage &image,
118 const QRectF &bounds)
119 : QSvgNode(parent), m_image(image),
120 m_bounds(bounds)
121{
122 if (m_bounds.width() == 0.0)
123 m_bounds.setWidth(static_cast<qreal>(m_image.width()));
124 if (m_bounds.height() == 0.0)
125 m_bounds.setHeight(static_cast<qreal>(m_image.height()));
126}
127
128void QSvgImage::draw(QPainter *p, QSvgExtraStates &states)
129{
130 QT_SVG_TIMING_ENTER
131 if (shouldDrawNode(p, states)) {
132 applyStyle(p, states);
133 p->drawImage(r: m_bounds, image: m_image);
134 revertStyle(p, states);
135 }
136 QT_SVG_TIMING_EXIT("Image")
137}
138
139
140QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line)
141 : QSvgNode(parent), m_line(line)
142{
143}
144
145
146void QSvgLine::draw(QPainter *p, QSvgExtraStates &states)
147{
148 QT_SVG_TIMING_ENTER
149 applyStyle(p, states);
150 if (shouldDrawNode(p, states)) {
151 if (p->pen().widthF() != 0) {
152 qreal oldOpacity = p->opacity();
153 p->setOpacity(oldOpacity * states.strokeOpacity);
154 p->drawLine(l: m_line);
155 p->setOpacity(oldOpacity);
156 }
157 }
158 revertStyle(p, states);
159 QT_SVG_TIMING_EXIT("Line")
160}
161
162QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath)
163 : QSvgNode(parent), m_path(qpath)
164{
165}
166
167void QSvgPath::draw(QPainter *p, QSvgExtraStates &states)
168{
169 QT_SVG_TIMING_ENTER
170 applyStyle(p, states);
171 if (shouldDrawNode(p, states)) {
172 m_path.setFillRule(states.fillRule);
173 QT_SVG_DRAW_SHAPE(p->drawPath(m_path));
174 }
175 revertStyle(p, states);
176 QT_SVG_TIMING_EXIT("Path")
177}
178
179QRectF QSvgPath::fastBounds(QPainter *p, QSvgExtraStates &) const
180{
181 return p->transform().mapRect(m_path.controlPointRect());
182}
183
184QRectF QSvgPath::bounds(QPainter *p, QSvgExtraStates &) const
185{
186 qreal sw = strokeWidth(p);
187 return qFuzzyIsNull(d: sw) ? p->transform().map(p: m_path).boundingRect()
188 : boundsOnStroke(p, path: m_path, width: sw);
189}
190
191QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly)
192 : QSvgNode(parent), m_poly(poly)
193{
194}
195
196QRectF QSvgPolygon::fastBounds(QPainter *p, QSvgExtraStates &) const
197{
198 return p->transform().mapRect(m_poly.boundingRect());
199}
200
201QRectF QSvgPolygon::bounds(QPainter *p, QSvgExtraStates &) const
202{
203 qreal sw = strokeWidth(p);
204 if (qFuzzyIsNull(d: sw)) {
205 return p->transform().map(a: m_poly).boundingRect();
206 } else {
207 QPainterPath path;
208 path.addPolygon(polygon: m_poly);
209 return boundsOnStroke(p, path, width: sw);
210 }
211}
212
213void QSvgPolygon::draw(QPainter *p, QSvgExtraStates &states)
214{
215 QT_SVG_TIMING_ENTER
216 applyStyle(p, states);
217 if (shouldDrawNode(p, states))
218 QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly, states.fillRule));
219 revertStyle(p, states);
220 QT_SVG_TIMING_EXIT("Polygon")
221}
222
223
224QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly)
225 : QSvgNode(parent), m_poly(poly)
226{
227
228}
229
230void QSvgPolyline::draw(QPainter *p, QSvgExtraStates &states)
231{
232 QT_SVG_TIMING_ENTER
233 applyStyle(p, states);
234 if (shouldDrawNode(p, states)) {
235 qreal oldOpacity = p->opacity();
236 if (p->brush().style() != Qt::NoBrush) {
237 QPen save = p->pen();
238 p->setPen(QPen(Qt::NoPen));
239 p->setOpacity(oldOpacity * states.fillOpacity);
240 p->drawPolygon(polygon: m_poly, fillRule: states.fillRule);
241 p->setPen(save);
242 }
243 if (p->pen().widthF() != 0) {
244 p->setOpacity(oldOpacity * states.strokeOpacity);
245 p->drawPolyline(polyline: m_poly);
246 }
247 p->setOpacity(oldOpacity);
248 }
249 revertStyle(p, states);
250 QT_SVG_TIMING_EXIT("Polyline")
251}
252
253QSvgRect::QSvgRect(QSvgNode *node, const QRectF &rect, int rx, int ry)
254 : QSvgNode(node),
255 m_rect(rect), m_rx(rx), m_ry(ry)
256{
257}
258
259QRectF QSvgRect::fastBounds(QPainter *p, QSvgExtraStates &) const
260{
261 return p->transform().mapRect(m_rect);
262}
263
264QRectF QSvgRect::bounds(QPainter *p, QSvgExtraStates &) const
265{
266 qreal sw = strokeWidth(p);
267 if (qFuzzyIsNull(d: sw)) {
268 return p->transform().mapRect(m_rect);
269 } else {
270 QPainterPath path;
271 path.addRect(rect: m_rect);
272 return boundsOnStroke(p, path, width: sw);
273 }
274}
275
276void QSvgRect::draw(QPainter *p, QSvgExtraStates &states)
277{
278 QT_SVG_TIMING_ENTER
279 applyStyle(p, states);
280 if (shouldDrawNode(p, states)) {
281 if (m_rx || m_ry) {
282 QT_SVG_DRAW_SHAPE(p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize));
283 } else {
284 QT_SVG_DRAW_SHAPE(p->drawRect(m_rect));
285 }
286 }
287 revertStyle(p, states);
288 QT_SVG_TIMING_EXIT("Rect")
289}
290
291QSvgTspan * const QSvgText::LINEBREAK = 0;
292
293QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord)
294 : QSvgNode(parent)
295 , m_coord(coord)
296 , m_type(TEXT)
297 , m_size(0, 0)
298 , m_mode(Default)
299{
300}
301
302QSvgText::~QSvgText()
303{
304 for (int i = 0; i < m_tspans.size(); ++i) {
305 if (m_tspans[i] != LINEBREAK)
306 delete m_tspans[i];
307 }
308}
309
310void QSvgText::setTextArea(const QSizeF &size)
311{
312 m_size = size;
313 m_type = TEXTAREA;
314}
315
316QRectF QSvgText::fastBounds(QPainter *p, QSvgExtraStates &) const
317{
318 QFont font = p->font();
319 QFontMetricsF fm(font);
320
321 int charCount = 0;
322 for (int i = 0; i < m_tspans.size(); ++i)
323 charCount += m_tspans.at(i)->text().size();
324
325 QRectF approxMaximumBrect(m_coord.x(),
326 m_coord.y(),
327 charCount * fm.averageCharWidth(),
328 m_tspans.size() * fm.height());
329 return p->transform().mapRect(approxMaximumBrect);
330}
331
332QRectF QSvgText::bounds(QPainter *p, QSvgExtraStates &states) const
333{
334 QRectF boundingRect;
335 if (precheck(p))
336 draw_helper(p, states, boundingRect: &boundingRect);
337 return p->transform().mapRect(boundingRect);
338}
339
340bool QSvgText::precheck(QPainter *p) const
341{
342 qsizetype numChars = 0;
343 qreal originalFontSize = p->font().pointSizeF();
344 qreal maxFontSize = originalFontSize;
345 for (const QSvgTspan *span : std::as_const(t: m_tspans)) {
346 numChars += span->text().size();
347
348 QSvgFontStyle *style = static_cast<QSvgFontStyle *>(span->styleProperty(type: QSvgStyleProperty::FONT));
349 if (style != nullptr && style->qfont().pointSizeF() > maxFontSize)
350 maxFontSize = style->qfont().pointSizeF();
351 }
352
353 QFont font = p->font();
354 font.setPixelSize((100.0 / originalFontSize) * maxFontSize);
355 QFontMetricsF fm(font);
356 if (m_tspans.size() * fm.height() >= QT_SVG_MAX_LAYOUT_SIZE) {
357 qCWarning(lcSvgDraw) << "Text element too high to lay out, ignoring";
358 return false;
359 }
360
361 if (numChars * fm.maxWidth() >= QT_SVG_MAX_LAYOUT_SIZE) {
362 qCWarning(lcSvgDraw) << "Text element too wide to lay out, ignoring";
363 return false;
364 }
365
366 return true;
367}
368
369void QSvgText::draw(QPainter *p, QSvgExtraStates &states)
370{
371 QT_SVG_TIMING_ENTER
372 if (precheck(p))
373 draw_helper(p, states);
374 QT_SVG_TIMING_EXIT("Text")
375}
376
377void QSvgText::draw_helper(QPainter *p, QSvgExtraStates &states, QRectF *boundingRect) const
378{
379 const bool isPainting = (boundingRect == nullptr);
380 if (isPainting)
381 applyStyle(p, states);
382 if (!isPainting || shouldDrawNode(p, states)) {
383 qreal oldOpacity = p->opacity();
384 p->setOpacity(oldOpacity * states.fillOpacity);
385
386 // Force the font to have a size of 100 pixels to avoid truncation problems
387 // when the font is very small.
388 qreal scale = 100.0 / p->font().pointSizeF();
389 Qt::Alignment alignment = states.textAnchor;
390
391 QTransform oldTransform = p->worldTransform();
392 p->scale(sx: 1 / scale, sy: 1 / scale);
393
394 qreal y = 0;
395 bool initial = true;
396 qreal px = m_coord.x() * scale;
397 qreal py = m_coord.y() * scale;
398 QSizeF scaledSize = m_size * scale;
399
400 if (m_type == TEXTAREA) {
401 if (alignment == Qt::AlignHCenter)
402 px += scaledSize.width() / 2;
403 else if (alignment == Qt::AlignRight)
404 px += scaledSize.width();
405 }
406
407 QRectF bounds;
408 if (m_size.height() != 0)
409 bounds = QRectF(0, py, 1, scaledSize.height()); // x and width are not used.
410
411 bool appendSpace = false;
412 QList<QString> paragraphs;
413 QList<QList<QTextLayout::FormatRange> > formatRanges(1);
414 paragraphs.push_back(t: QString());
415
416 for (int i = 0; i < m_tspans.size(); ++i) {
417 if (m_tspans[i] == LINEBREAK) {
418 if (m_type == TEXTAREA) {
419 if (paragraphs.back().isEmpty()) {
420 QFont font = p->font();
421 font.setPixelSize(font.pointSizeF() * scale);
422
423 QTextLayout::FormatRange range;
424 range.start = 0;
425 range.length = 1;
426 range.format.setFont(font);
427 formatRanges.back().append(t: range);
428
429 paragraphs.back().append(c: QLatin1Char(' '));;
430 }
431 appendSpace = false;
432 paragraphs.push_back(t: QString());
433 formatRanges.resize(size: formatRanges.size() + 1);
434 }
435 } else {
436 WhitespaceMode mode = m_tspans[i]->whitespaceMode();
437 m_tspans[i]->applyStyle(p, states);
438
439 QFont font = p->font();
440 font.setPixelSize(font.pointSizeF() * scale);
441
442 QString newText(m_tspans[i]->text());
443 newText.replace(before: QLatin1Char('\t'), after: QLatin1Char(' '));
444 newText.replace(before: QLatin1Char('\n'), after: QLatin1Char(' '));
445
446 bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(c: QLatin1Char(' '));
447 if (appendSpace || prependSpace)
448 paragraphs.back().append(c: QLatin1Char(' '));
449
450 bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(c: QLatin1Char(' ')));
451
452 if (mode == Default) {
453 newText = newText.simplified();
454 if (newText.isEmpty())
455 appendSpaceNext = false;
456 }
457
458 QTextLayout::FormatRange range;
459 range.start = paragraphs.back().size();
460 range.length = newText.size();
461 range.format.setFont(font);
462 range.format.setTextOutline(p->pen());
463 range.format.setForeground(p->brush());
464
465 if (appendSpace) {
466 Q_ASSERT(!formatRanges.back().isEmpty());
467 ++formatRanges.back().back().length;
468 } else if (prependSpace) {
469 --range.start;
470 ++range.length;
471 }
472 formatRanges.back().append(t: range);
473
474 appendSpace = appendSpaceNext;
475 paragraphs.back() += newText;
476
477 m_tspans[i]->revertStyle(p, states);
478 }
479 }
480
481 if (states.svgFont) {
482 // SVG fonts not fully supported...
483 QString text = paragraphs.front();
484 for (int i = 1; i < paragraphs.size(); ++i) {
485 text.append(c: QLatin1Char('\n'));
486 text.append(s: paragraphs[i]);
487 }
488 states.svgFont->draw(p, point: m_coord * scale, str: text, pixelSize: p->font().pointSizeF() * scale, alignment: states.textAnchor);
489 } else {
490 QRectF brect;
491 for (int i = 0; i < paragraphs.size(); ++i) {
492 QTextLayout tl(paragraphs[i]);
493 QTextOption op = tl.textOption();
494 op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
495 tl.setTextOption(op);
496 tl.setFormats(formatRanges[i]);
497 tl.beginLayout();
498
499 forever {
500 QTextLine line = tl.createLine();
501 if (!line.isValid())
502 break;
503 if (m_size.width() != 0)
504 line.setLineWidth(scaledSize.width());
505 }
506 tl.endLayout();
507
508 bool endOfBoundsReached = false;
509 for (int i = 0; i < tl.lineCount(); ++i) {
510 QTextLine line = tl.lineAt(i);
511
512 qreal x = 0;
513 if (alignment == Qt::AlignHCenter)
514 x -= 0.5 * line.naturalTextWidth();
515 else if (alignment == Qt::AlignRight)
516 x -= line.naturalTextWidth();
517
518 if (initial && m_type == TEXT)
519 y -= line.ascent();
520 initial = false;
521
522 line.setPosition(QPointF(x, y));
523 brect |= line.naturalTextRect();
524
525 // Check if the current line fits into the bounding rectangle.
526 if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width())
527 || (m_size.height() != 0 && y + line.height() > scaledSize.height())) {
528 // I need to set the bounds height to 'y-epsilon' to avoid drawing the current
529 // line. Since the font is scaled to 100 units, 1 should be a safe epsilon.
530 bounds.setHeight(y - 1);
531 endOfBoundsReached = true;
532 break;
533 }
534
535 y += 1.1 * line.height();
536 }
537 if (isPainting)
538 tl.draw(p, pos: QPointF(px, py), selections: QList<QTextLayout::FormatRange>(), clip: bounds);
539
540 if (endOfBoundsReached)
541 break;
542 }
543 if (boundingRect) {
544 brect.translate(p: m_coord * scale);
545 if (bounds.height() > 0)
546 brect.setBottom(qMin(a: brect.bottom(), b: bounds.bottom()));
547 *boundingRect = QTransform::fromScale(dx: 1 / scale, dy: 1 / scale).mapRect(brect);
548 }
549 }
550
551 p->setWorldTransform(matrix: oldTransform, combine: false);
552 p->setOpacity(oldOpacity);
553 }
554 if (isPainting)
555 revertStyle(p, states);
556}
557
558void QSvgText::addText(const QString &text)
559{
560 m_tspans.append(t: new QSvgTspan(this, false));
561 m_tspans.back()->setWhitespaceMode(m_mode);
562 m_tspans.back()->addText(text);
563}
564
565QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
566 : QSvgNode(parent), m_link(node), m_start(start), m_recursing(false)
567{
568
569}
570
571void QSvgUse::draw(QPainter *p, QSvgExtraStates &states)
572{
573 QT_SVG_TIMING_ENTER
574 if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing))
575 return;
576
577 Q_ASSERT(states.nestedUseCount == 0 || states.nestedUseLevel > 0);
578 if (states.nestedUseLevel > 3 && states.nestedUseCount > (256 + states.nestedUseLevel * 2)) {
579 qCDebug(lcSvgDraw, "Too many nested use nodes at #%s!", qPrintable(m_linkId));
580 return;
581 }
582
583 applyStyle(p, states);
584
585 if (!m_start.isNull()) {
586 p->translate(offset: m_start);
587 }
588 if (states.nestedUseLevel > 0)
589 ++states.nestedUseCount;
590 {
591 QScopedValueRollback<int> useLevelGuard(states.nestedUseLevel, states.nestedUseLevel + 1);
592 QScopedValueRollback<bool> recursingGuard(m_recursing, true);
593 m_link->draw(p, states);
594 }
595 if (states.nestedUseLevel == 0)
596 states.nestedUseCount = 0;
597
598 if (!m_start.isNull()) {
599 p->translate(offset: -m_start);
600 }
601
602 revertStyle(p, states);
603 QT_SVG_TIMING_EXIT("Use")
604}
605
606void QSvgVideo::draw(QPainter *p, QSvgExtraStates &states)
607{
608 applyStyle(p, states);
609
610 revertStyle(p, states);
611}
612
613QSvgNode::Type QSvgAnimation::type() const
614{
615 return ANIMATION;
616}
617
618QSvgNode::Type QSvgArc::type() const
619{
620 return ARC;
621}
622
623QSvgNode::Type QSvgCircle::type() const
624{
625 return CIRCLE;
626}
627
628QSvgNode::Type QSvgEllipse::type() const
629{
630 return ELLIPSE;
631}
632
633QSvgNode::Type QSvgImage::type() const
634{
635 return IMAGE;
636}
637
638QSvgNode::Type QSvgLine::type() const
639{
640 return LINE;
641}
642
643QSvgNode::Type QSvgPath::type() const
644{
645 return PATH;
646}
647
648QSvgNode::Type QSvgPolygon::type() const
649{
650 return POLYGON;
651}
652
653QSvgNode::Type QSvgPolyline::type() const
654{
655 return POLYLINE;
656}
657
658QSvgNode::Type QSvgRect::type() const
659{
660 return RECT;
661}
662
663QSvgNode::Type QSvgText::type() const
664{
665 return m_type;
666}
667
668QSvgNode::Type QSvgUse::type() const
669{
670 return USE;
671}
672
673QSvgNode::Type QSvgVideo::type() const
674{
675 return VIDEO;
676}
677
678QRectF QSvgUse::bounds(QPainter *p, QSvgExtraStates &states) const
679{
680 QRectF bounds;
681 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
682 QScopedValueRollback<bool> guard(m_recursing, true);
683 p->translate(offset: m_start);
684 bounds = m_link->transformedBounds(p, states);
685 p->translate(offset: -m_start);
686 }
687 return bounds;
688}
689
690QRectF QSvgPolyline::fastBounds(QPainter *p, QSvgExtraStates &) const
691{
692 return p->transform().mapRect(m_poly.boundingRect());
693}
694
695QRectF QSvgPolyline::bounds(QPainter *p, QSvgExtraStates &) const
696{
697 qreal sw = strokeWidth(p);
698 if (qFuzzyIsNull(d: sw)) {
699 return p->transform().map(a: m_poly).boundingRect();
700 } else {
701 QPainterPath path;
702 path.addPolygon(polygon: m_poly);
703 return boundsOnStroke(p, path, width: sw);
704 }
705}
706
707QRectF QSvgArc::fastBounds(QPainter *p, QSvgExtraStates &) const
708{
709 return p->transform().mapRect(m_path.controlPointRect());
710}
711
712QRectF QSvgArc::bounds(QPainter *p, QSvgExtraStates &) const
713{
714 qreal sw = strokeWidth(p);
715 return qFuzzyIsNull(d: sw) ? p->transform().map(p: m_path).boundingRect()
716 : boundsOnStroke(p, path: m_path, width: sw);
717}
718
719QRectF QSvgImage::bounds(QPainter *p, QSvgExtraStates &) const
720{
721 return p->transform().mapRect(m_bounds);
722}
723
724QRectF QSvgLine::fastBounds(QPainter *p, QSvgExtraStates &) const
725{
726 QPointF p1 = p->transform().map(p: m_line.p1());
727 QPointF p2 = p->transform().map(p: m_line.p2());
728 qreal minX = qMin(a: p1.x(), b: p2.x());
729 qreal minY = qMin(a: p1.y(), b: p2.y());
730 qreal maxX = qMax(a: p1.x(), b: p2.x());
731 qreal maxY = qMax(a: p1.y(), b: p2.y());
732 return QRectF(minX, minY, maxX - minX, maxY - minY);
733}
734
735QRectF QSvgLine::bounds(QPainter *p, QSvgExtraStates &s) const
736{
737 qreal sw = strokeWidth(p);
738 if (qFuzzyIsNull(d: sw)) {
739 return fastBounds(p, s);
740 } else {
741 QPainterPath path;
742 path.moveTo(p: m_line.p1());
743 path.lineTo(p: m_line.p2());
744 return boundsOnStroke(p, path, width: sw);
745 }
746}
747
748QT_END_NAMESPACE
749

source code of qtsvg/src/svg/qsvggraphics.cpp