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 QSvgAnimation::drawCommand(QPainter *, QSvgExtraStates &)
32{
33 qWarning(msg: "<animation> not implemented");
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((100.0 / originalFontSize) * 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
371void QSvgText::draw_helper(QPainter *p, QSvgExtraStates &states, QRectF *boundingRect) const
372{
373 const bool isPainting = (boundingRect == nullptr);
374 if (!isPainting || shouldDrawNode(p, states)) {
375 qreal oldOpacity = p->opacity();
376 p->setOpacity(oldOpacity * states.fillOpacity);
377
378 // Force the font to have a size of 100 pixels to avoid truncation problems
379 // when the font is very small.
380 QFont font = p->font();
381 qreal scale = 100.0 / font.pointSizeF();
382 Qt::Alignment alignment = states.textAnchor;
383
384 QTransform oldTransform = p->worldTransform();
385 p->scale(sx: 1 / scale, sy: 1 / scale);
386
387 qreal y = 0;
388 bool initial = true;
389 qreal px = m_coord.x() * scale;
390 qreal py = m_coord.y() * scale;
391 QSizeF scaledSize = m_size * scale;
392
393 if (m_type == Textarea) {
394 if (alignment == Qt::AlignHCenter)
395 px += scaledSize.width() / 2;
396 else if (alignment == Qt::AlignRight)
397 px += scaledSize.width();
398 }
399
400 QRectF bounds;
401 if (m_size.height() != 0)
402 bounds = QRectF(0, py, 1, scaledSize.height()); // x and width are not used.
403
404 bool appendSpace = false;
405 QList<QString> paragraphs;
406 QList<QList<QTextLayout::FormatRange> > formatRanges(1);
407 paragraphs.push_back(t: QString());
408
409 for (int i = 0; i < m_tspans.size(); ++i) {
410 if (m_tspans[i] == LINEBREAK) {
411 if (m_type == Textarea) {
412 if (paragraphs.back().isEmpty()) {
413 font.setPixelSize(font.pointSizeF() * scale);
414
415 QTextLayout::FormatRange range;
416 range.start = 0;
417 range.length = 1;
418 range.format.setFont(font);
419 formatRanges.back().append(t: range);
420
421 paragraphs.back().append(c: QLatin1Char(' '));;
422 }
423 appendSpace = false;
424 paragraphs.push_back(t: QString());
425 formatRanges.resize(size: formatRanges.size() + 1);
426 }
427 } else {
428 WhitespaceMode mode = m_tspans[i]->whitespaceMode();
429 m_tspans[i]->applyStyle(p, states);
430
431 font = p->font();
432 font.setPixelSize(font.pointSizeF() * scale);
433
434 QString newText(m_tspans[i]->text());
435 newText.replace(before: QLatin1Char('\t'), after: QLatin1Char(' '));
436 newText.replace(before: QLatin1Char('\n'), after: QLatin1Char(' '));
437
438 bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(c: QLatin1Char(' '));
439 if (appendSpace || prependSpace)
440 paragraphs.back().append(c: QLatin1Char(' '));
441
442 bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(c: QLatin1Char(' ')));
443
444 if (mode == Default) {
445 newText = newText.simplified();
446 if (newText.isEmpty())
447 appendSpaceNext = false;
448 }
449
450 QTextLayout::FormatRange range;
451 range.start = paragraphs.back().size();
452 range.length = newText.size();
453 range.format.setFont(font);
454 range.format.setTextOutline(p->pen());
455 range.format.setForeground(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 * scale, str: text, pixelSize: p->font().pointSizeF() * scale, alignment: states.textAnchor);
483 }
484 if (boundingRect) {
485 *boundingRect = states.svgFont->boundingRect(
486 p, point: m_coord * scale, str: text, pixelSize: p->font().pointSizeF() * scale, 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(scaledSize.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() > scaledSize.width())
526 || (m_size.height() != 0 && y + line.height() > scaledSize.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 * scale);
544 if (bounds.height() > 0)
545 brect.setBottom(qMin(a: brect.bottom(), b: bounds.bottom()));
546 *boundingRect = QTransform::fromScale(dx: 1 / scale, dy: 1 / scale).mapRect(brect);
547 }
548 }
549
550 p->setWorldTransform(matrix: oldTransform, combine: false);
551 p->setOpacity(oldOpacity);
552 }
553}
554
555void QSvgText::addText(const QString &text)
556{
557 m_tspans.append(t: new QSvgTspan(this, false));
558 m_tspans.back()->setWhitespaceMode(m_mode);
559 m_tspans.back()->addText(text);
560}
561
562QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
563 : QSvgNode(parent), m_link(node), m_start(start), m_recursing(false)
564{
565
566}
567
568void QSvgUse::drawCommand(QPainter *p, QSvgExtraStates &states)
569{
570 if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing))
571 return;
572
573 Q_ASSERT(states.nestedUseCount == 0 || states.nestedUseLevel > 0);
574 if (states.nestedUseLevel > 3 && states.nestedUseCount > (256 + states.nestedUseLevel * 2)) {
575 qCDebug(lcSvgDraw, "Too many nested use nodes at #%s!", qPrintable(m_linkId));
576 return;
577 }
578
579 QScopedValueRollback<bool> inUseGuard(states.inUse, true);
580
581 if (!m_start.isNull()) {
582 p->translate(offset: m_start);
583 }
584 if (states.nestedUseLevel > 0)
585 ++states.nestedUseCount;
586 {
587 QScopedValueRollback<int> useLevelGuard(states.nestedUseLevel, states.nestedUseLevel + 1);
588 QScopedValueRollback<bool> recursingGuard(m_recursing, true);
589 m_link->draw(p, states);
590 }
591 if (states.nestedUseLevel == 0)
592 states.nestedUseCount = 0;
593
594 if (!m_start.isNull()) {
595 p->translate(offset: -m_start);
596 }
597}
598
599QSvgNode::Type QSvgAnimation::type() const
600{
601 return Animation;
602}
603
604QSvgNode::Type QSvgCircle::type() const
605{
606 return Circle;
607}
608
609QSvgNode::Type QSvgEllipse::type() const
610{
611 return Ellipse;
612}
613
614QSvgNode::Type QSvgImage::type() const
615{
616 return Image;
617}
618
619QSvgNode::Type QSvgLine::type() const
620{
621 return Line;
622}
623
624QSvgNode::Type QSvgPath::type() const
625{
626 return Path;
627}
628
629QSvgNode::Type QSvgPolygon::type() const
630{
631 return Polygon;
632}
633
634QSvgNode::Type QSvgPolyline::type() const
635{
636 return Polyline;
637}
638
639QSvgNode::Type QSvgRect::type() const
640{
641 return Rect;
642}
643
644QSvgNode::Type QSvgText::type() const
645{
646 return m_type;
647}
648
649QSvgNode::Type QSvgUse::type() const
650{
651 return Use;
652}
653
654QSvgNode::Type QSvgVideo::type() const
655{
656 return Video;
657}
658
659QRectF QSvgUse::internalBounds(QPainter *p, QSvgExtraStates &states) const
660{
661 QRectF bounds;
662 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
663 QScopedValueRollback<bool> guard(m_recursing, true);
664 p->translate(offset: m_start);
665 bounds = m_link->bounds(p, states);
666 p->translate(offset: -m_start);
667 }
668 return bounds;
669}
670
671QRectF QSvgUse::decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const
672{
673 QRectF bounds;
674 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
675 QScopedValueRollback<bool> guard(m_recursing, true);
676 p->translate(offset: m_start);
677 bounds = m_link->decoratedBounds(p, states);
678 p->translate(offset: -m_start);
679 }
680 return bounds;
681}
682
683QRectF QSvgPolyline::internalFastBounds(QPainter *p, QSvgExtraStates &) const
684{
685 return p->transform().mapRect(m_poly.boundingRect());
686}
687
688QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &s) const
689{
690 return internalBounds(p, states&: s, mode: BoundsMode::Simplistic);
691}
692
693QRectF QSvgPolyline::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
694{
695 QRectF rect = internalBounds(p, states&: s, mode: BoundsMode::IncludeMiterLimit);
696 rect |= QSvgMarker::markersBoundsForNode(node: this, p, states&: s);
697 return filterRegion(bounds: rect);
698}
699
700bool QSvgPolyline::requiresGroupRendering() const
701{
702 return hasAnyMarker();
703}
704
705QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode) const
706{
707 qreal sw = strokeWidth(p);
708 if (qFuzzyIsNull(d: sw)) {
709 return p->transform().map(a: m_poly).boundingRect();
710 } else {
711 QPainterPath path;
712 path.addPolygon(polygon: m_poly);
713 return boundsOnStroke(p, path, width: sw, mode);
714 }
715}
716
717QRectF QSvgImage::internalBounds(QPainter *p, QSvgExtraStates &) const
718{
719 return p->transform().mapRect(m_bounds);
720}
721
722QRectF QSvgLine::internalFastBounds(QPainter *p, QSvgExtraStates &) const
723{
724 QPointF p1 = p->transform().map(p: m_line.p1());
725 QPointF p2 = p->transform().map(p: m_line.p2());
726 qreal minX = qMin(a: p1.x(), b: p2.x());
727 qreal minY = qMin(a: p1.y(), b: p2.y());
728 qreal maxX = qMax(a: p1.x(), b: p2.x());
729 qreal maxY = qMax(a: p1.y(), b: p2.y());
730 return QRectF(minX, minY, maxX - minX, maxY - minY);
731}
732
733QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s) const
734{
735 return internalBounds(p, states&: s, mode: BoundsMode::Simplistic);
736}
737
738QRectF QSvgLine::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s) const
739{
740 QRectF rect = internalBounds(p, states&: s, mode: BoundsMode::IncludeMiterLimit);
741 rect |= QSvgMarker::markersBoundsForNode(node: this, p, states&: s);
742 return filterRegion(bounds: rect);
743}
744
745bool QSvgLine::requiresGroupRendering() const
746{
747 return hasAnyMarker();
748}
749
750QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s, BoundsMode mode) const
751{
752 qreal sw = strokeWidth(p);
753 if (qFuzzyIsNull(d: sw)) {
754 return internalFastBounds(p, s);
755 } else {
756 QPainterPath path;
757 path.moveTo(p: m_line.p1());
758 path.lineTo(p: m_line.p2());
759 return boundsOnStroke(p, path, width: sw, mode);
760 }
761}
762
763QT_END_NAMESPACE
764

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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