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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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