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

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