10#include <qabstracttextdocumentlayout.h>
12#include <qloggingcategory.h>
14#include <qscopedvaluerollback.h>
15#include <qtextcursor.h>
16#include <qtextdocument.h>
17#include <private/qfixed_p.h>
19#include <QElapsedTimer>
20#include <QLoggingCategory>
28#ifndef QT_SVG_MAX_LAYOUT_SIZE
29#define QT_SVG_MAX_LAYOUT_SIZE (qint64(QFIXED_MAX / 2
))
32QSvgDummyNode::~QSvgDummyNode()
35void QSvgDummyNode::drawCommand(QPainter *, QSvgExtraStates &)
37 qWarning(
"Dummy node not meant to be drawn");
40QSvgEllipse::QSvgEllipse(QSvgNode *parent,
const QRectF &rect)
41 : QSvgNode(parent), m_bounds(rect)
45QSvgEllipse::~QSvgEllipse()
48QRectF QSvgEllipse::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
50 return p->transform().mapRect(m_bounds);
53QRectF QSvgEllipse::internalBounds(QPainter *p, QSvgExtraStates &)
const
56 path.addEllipse(m_bounds);
57 qreal sw = strokeWidth(p);
58 return qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
59 : boundsOnStroke(p, path, sw, BoundsMode::Simplistic);
62QRectF QSvgEllipse::decoratedInternalBounds(QPainter *p, QSvgExtraStates &)
const
65 path.addEllipse(m_bounds);
66 qreal sw = strokeWidth(p);
67 QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
68 : boundsOnStroke(p, path, sw, BoundsMode::IncludeMiterLimit);
69 return filterRegion(rect);
72void QSvgEllipse::drawCommand(QPainter *p, QSvgExtraStates &)
74 p->drawEllipse(m_bounds);
77bool QSvgEllipse::separateFillStroke(
const QPainter *,
const QSvgExtraStates &)
const
82QSvgImage::QSvgImage(QSvgNode *parent,
84 const QString &filename,
87 , m_filename(filename)
91 if (m_bounds.width() == 0.0)
92 m_bounds.setWidth(
static_cast<qreal>(m_image.width()));
93 if (m_bounds.height() == 0.0)
94 m_bounds.setHeight(
static_cast<qreal>(m_image.height()));
97QSvgImage::~QSvgImage()
100void QSvgImage::drawCommand(QPainter *p, QSvgExtraStates &)
102 p->drawImage(m_bounds, m_image);
105QSvgLine::QSvgLine(QSvgNode *parent,
const QLineF &line)
106 : QSvgNode(parent), m_line(line)
113void QSvgLine::drawCommand(QPainter *p, QSvgExtraStates &states)
115 if (p->pen().widthF() != 0) {
116 qreal oldOpacity = p->opacity();
117 p->setOpacity(oldOpacity * states.strokeOpacity);
118 if (m_line.isNull() && p->pen().capStyle() != Qt::FlatCap)
119 p->drawPoint(m_line.p1());
122 p->setOpacity(oldOpacity);
124 QSvgMarker::drawMarkersForNode(
this, p, states);
127QSvgPath::QSvgPath(QSvgNode *parent,
const QPainterPath &qpath)
128 : QSvgNode(parent), m_path(qpath)
135void QSvgPath::drawCommand(QPainter *p, QSvgExtraStates &states)
137 const qreal oldOpacity = p->opacity();
138 const bool drawingInOnePass = !separateFillStroke(p, states);
139 if (drawingInOnePass)
140 p->setOpacity(oldOpacity * states.fillOpacity);
141 m_path.setFillRule(states.fillRule);
142 if (m_path.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
143 p->drawPoint(m_path.boundingRect().topLeft());
146 if (!path().isEmpty())
147 QSvgMarker::drawMarkersForNode(
this, p, states);
148 if (drawingInOnePass)
149 p->setOpacity(oldOpacity);
152bool QSvgPath::separateFillStroke(
const QPainter *p,
const QSvgExtraStates &s)
const
154 return !qFuzzyCompare(s.fillOpacity, s.strokeOpacity)
155 || qFuzzyIsNull(p->pen().widthF());
158QRectF QSvgPath::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
160 return p->transform().mapRect(m_path.controlPointRect());
163QRectF QSvgPath::internalBounds(QPainter *p, QSvgExtraStates &)
const
165 qreal sw = strokeWidth(p);
166 return qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
167 : boundsOnStroke(p, m_path, sw, BoundsMode::Simplistic);
170QRectF QSvgPath::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
172 qreal sw = strokeWidth(p);
173 QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
174 : boundsOnStroke(p, m_path, sw, BoundsMode::IncludeMiterLimit);
175 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
176 return filterRegion(rect);
179bool QSvgPath::requiresGroupRendering()
const
181 return hasAnyMarker();
184QSvgPolygon::QSvgPolygon(QSvgNode *parent,
const QPolygonF &poly)
185 : QSvgNode(parent), m_poly(poly)
189QSvgPolygon::~QSvgPolygon()
192QRectF QSvgPolygon::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
194 return p->transform().mapRect(m_poly.boundingRect());
197QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &s)
const
199 return internalBounds(p, s, BoundsMode::Simplistic);
202QRectF QSvgPolygon::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
204 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
205 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
206 return filterRegion(rect);
209bool QSvgPolygon::requiresGroupRendering()
const
211 return hasAnyMarker();
214QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
216 qreal sw = strokeWidth(p);
217 if (qFuzzyIsNull(sw)) {
218 return p->transform().map(m_poly).boundingRect();
221 path.addPolygon(m_poly);
222 return boundsOnStroke(p, path, sw, mode);
226void QSvgPolygon::drawCommand(QPainter *p, QSvgExtraStates &states)
228 if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
229 p->drawPoint(m_poly.first());
231 p->drawPolygon(m_poly, states.fillRule);
232 QSvgMarker::drawMarkersForNode(
this, p, states);
235bool QSvgPolygon::separateFillStroke(
const QPainter *,
const QSvgExtraStates &)
const
240QSvgPolyline::QSvgPolyline(QSvgNode *parent,
const QPolygonF &poly)
241 : QSvgNode(parent), m_poly(poly)
246QSvgPolyline::~QSvgPolyline()
249void QSvgPolyline::drawCommand(QPainter *p, QSvgExtraStates &states)
251 if (p->brush().style() != Qt::NoBrush) {
252 p->drawPolygon(m_poly, states.fillRule);
254 if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
255 p->drawPoint(m_poly.first());
257 p->drawPolyline(m_poly);
258 QSvgMarker::drawMarkersForNode(
this, p, states);
262bool QSvgPolyline::separateFillStroke(
const QPainter *,
const QSvgExtraStates &)
const
267QSvgRect::QSvgRect(QSvgNode *node,
const QRectF &rect, qreal rx, qreal ry)
269 m_rect(rect), m_rx(rx), m_ry(ry)
276QRectF QSvgRect::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
278 return p->transform().mapRect(m_rect);
281QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &s)
const
283 return internalBounds(p, s, BoundsMode::Simplistic);
286QRectF QSvgRect::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
288 return filterRegion(internalBounds(p, s, BoundsMode::IncludeMiterLimit));
291QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
293 qreal sw = strokeWidth(p);
294 if (qFuzzyIsNull(sw)) {
295 return p->transform().mapRect(m_rect);
298 path.addRect(m_rect);
299 return boundsOnStroke(p, path, sw, mode);
303void QSvgRect::drawCommand(QPainter *p, QSvgExtraStates &)
306 p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize);
311bool QSvgRect::separateFillStroke(
const QPainter *,
const QSvgExtraStates &)
const
316QSvgTspan *
const QSvgText::LINEBREAK = 0;
318QSvgText::QSvgText(QSvgNode *parent,
const QPointF &coord)
329 for (
int i = 0; i < m_tspans.size(); ++i) {
330 if (m_tspans[i] != LINEBREAK)
335void QSvgText::setTextArea(
const QSizeF &size)
341QRectF QSvgText::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
343 QSvgFontStyle *fontProperty =
static_cast<QSvgFontStyle *>(m_style.property(QSvgStyleProperty::Font));
344 QFont font = fontProperty ? fontProperty->qfont() : p->font();
345 QFontMetricsF fm(font);
348 for (
int i = 0; i < m_tspans.size(); ++i) {
349 if (m_tspans.at(i) != LINEBREAK)
350 charCount += m_tspans.at(i)->text().size();
353 QRectF approxMaximumBrect(m_coord.x(),
355 charCount * fm.averageCharWidth(),
356 -m_tspans.size() * fm.height());
357 return p->transform().mapRect(approxMaximumBrect);
360QRectF QSvgText::internalBounds(QPainter *p, QSvgExtraStates &states)
const
363 if (shouldDrawNode(p, states))
364 draw_helper(p, states, &boundingRect);
365 return p->transform().mapRect(boundingRect);
368void QSvgText::drawCommand(QPainter *p, QSvgExtraStates &states)
370 draw_helper(p, states);
373bool QSvgText::shouldDrawNode(QPainter *p, QSvgExtraStates &)
const
375 qsizetype numChars = 0;
376 qreal originalFontSize = p->font().pointSizeF();
377 qreal maxFontSize = originalFontSize;
378 for (
const QSvgTspan *span : std::as_const(m_tspans)) {
379 if (span == LINEBREAK)
382 numChars += span->text().size();
384 QSvgFontStyle *style =
static_cast<QSvgFontStyle *>(span->styleProperty(QSvgStyleProperty::Font));
385 if (style !=
nullptr && style->qfont().pointSizeF() > maxFontSize)
386 maxFontSize = style->qfont().pointSizeF();
389 QFont font = p->font();
390 font.setPixelSize(maxFontSize);
391 QFontMetricsF fm(font);
393 qCWarning(lcSvgDraw) <<
"Text element too high to lay out, ignoring";
398 qCWarning(lcSvgDraw) <<
"Text element too wide to lay out, ignoring";
405bool QSvgText::separateFillStroke(
const QPainter *,
const QSvgExtraStates &)
const
410void QSvgText::draw_helper(QPainter *p, QSvgExtraStates &states, QRectF *boundingRect)
const
412 const bool isPainting = (boundingRect ==
nullptr);
413 if (!isPainting || shouldDrawNode(p, states)) {
414 QFont font = p->font();
415 Qt::Alignment alignment = states.textAnchor;
419 qreal px = m_coord.x();
420 qreal py = m_coord.y();
422 if (m_type == Textarea) {
423 if (alignment == Qt::AlignHCenter)
424 px += m_size.width() / 2;
425 else if (alignment == Qt::AlignRight)
426 px += m_size.width();
430 if (m_size.height() != 0)
431 bounds = QRectF(0, py, 1, m_size.height());
433 bool appendSpace =
false;
434 QStringList paragraphs;
435 QList<QList<QTextLayout::FormatRange> > formatRanges(1);
436 paragraphs.push_back(QString());
438 for (
int i = 0; i < m_tspans.size(); ++i) {
439 if (m_tspans[i] == LINEBREAK) {
440 if (m_type == Textarea) {
441 if (paragraphs.back().isEmpty()) {
442 font.setPixelSize(font.pointSizeF());
443 font.setHintingPreference(QFont::PreferNoHinting);
445 QTextLayout::FormatRange range;
448 range.format.setFont(font);
449 formatRanges.back().append(range);
451 paragraphs.back().append(QLatin1Char(
' '));;
454 paragraphs.push_back(QString());
455 formatRanges.resize(formatRanges.size() + 1);
458 WhitespaceMode mode = m_tspans[i]->whitespaceMode();
459 m_tspans[i]->applyStyle(p, states);
462 font.setPixelSize(font.pointSizeF());
463 font.setHintingPreference(QFont::PreferNoHinting);
465 QString newText(m_tspans[i]->text());
466 newText.replace(QLatin1Char(
'\t'), QLatin1Char(
' '));
467 newText.replace(QLatin1Char(
'\n'), QLatin1Char(
' '));
469 bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(
' '));
470 if (appendSpace || prependSpace)
471 paragraphs.back().append(QLatin1Char(
' '));
473 bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(
' ')));
475 if (mode == Default) {
476 newText = newText.simplified();
477 if (newText.isEmpty())
478 appendSpaceNext =
false;
481 QTextLayout::FormatRange range;
482 range.start = paragraphs.back().size();
483 range.length = newText.size();
484 range.format.setFont(font);
485 if (p->pen().style() != Qt::NoPen && p->pen().brush() != Qt::NoBrush)
486 range.format.setTextOutline(p->pen());
488 range.format.setForeground(p->brush().style() == Qt::NoBrush
489 ? QColor(Qt::transparent) : p->brush());
492 Q_ASSERT(!formatRanges.back().isEmpty());
493 ++formatRanges.back().back().length;
494 }
else if (prependSpace) {
498 formatRanges.back().append(range);
500 appendSpace = appendSpaceNext;
501 paragraphs.back() += newText;
503 m_tspans[i]->revertStyle(p, states);
507 if (states.svgFont) {
510 m_glyphsToDraw = states.svgFont->toGlyphs(paragraphs.join(QLatin1Char(
'\n')));
512 states.svgFont->draw(p, m_coord, m_glyphsToDraw.value(),
513 p->font().pointSizeF(), states.textAnchor);
515 *boundingRect = states.svgFont->boundingRect(p, m_coord, m_glyphsToDraw.value(),
516 p->font().pointSizeF(), states.textAnchor);
520 for (
int i = 0; i < paragraphs.size(); ++i) {
521 QTextLayout tl(paragraphs[i]);
522 QTextOption op = tl.textOption();
523 op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
524 tl.setTextOption(op);
525 tl.setFormats(formatRanges[i]);
529 QTextLine line = tl.createLine();
532 if (m_size.width() != 0)
533 line.setLineWidth(m_size.width());
537 bool endOfBoundsReached =
false;
538 for (
int i = 0; i < tl.lineCount(); ++i) {
539 QTextLine line = tl.lineAt(i);
542 if (alignment == Qt::AlignHCenter)
543 x -= 0.5 * line.naturalTextWidth();
544 else if (alignment == Qt::AlignRight)
545 x -= line.naturalTextWidth();
547 if (initial && m_type == Text)
551 line.setPosition(QPointF(x, y));
552 brect |= line.naturalTextRect();
555 if ((m_size.width() != 0 && line.naturalTextWidth() > m_size.width())
556 || (m_size.height() != 0 && y + line.height() > m_size.height())) {
559 bounds.setHeight(y - 1);
560 endOfBoundsReached =
true;
564 y += 1.1 * line.height();
567 tl.draw(p, QPointF(px, py), QList<QTextLayout::FormatRange>(), bounds);
569 if (endOfBoundsReached)
573 brect.translate(m_coord);
574 if (bounds.height() > 0)
575 brect.setBottom(qMin(brect.bottom(), bounds.bottom()));
576 *boundingRect = brect;
582void QSvgText::addText(QStringView text)
584 m_tspans.append(
new QSvgTspan(
this,
false));
585 m_tspans.back()->setWhitespaceMode(m_mode);
586 m_tspans.back()->addText(text);
589QSvgTspan::~QSvgTspan()
603 if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing))
606 Q_ASSERT(states.nestedUseCount == 0 || states.nestedUseLevel > 0);
607 if (states.nestedUseLevel > 3 && states.nestedUseCount > (256 + states.nestedUseLevel * 2)) {
612 QScopedValueRollback<
bool> inUseGuard(states.inUse,
true);
614 if (!m_start.isNull()) {
615 p->translate(m_start);
617 if (states.nestedUseLevel > 0)
618 ++states.nestedUseCount;
620 QScopedValueRollback<
int> useLevelGuard(states.nestedUseLevel, states.nestedUseLevel + 1);
621 QScopedValueRollback<
bool> recursingGuard(m_recursing,
true);
622 m_link->draw(p, states);
624 if (states.nestedUseLevel == 0)
625 states.nestedUseCount = 0;
627 if (!m_start.isNull()) {
628 p->translate(-m_start);
632QSvgNode::Type QSvgDummyNode::type()
const
634 return FeUnsupported;
638QSvgCircle::~QSvgCircle()
641QSvgNode::Type QSvgCircle::type()
const
646QSvgNode::Type QSvgEllipse::type()
const
651QSvgNode::Type QSvgImage::type()
const
656QSvgNode::Type QSvgLine::type()
const
661QSvgNode::Type QSvgPath::type()
const
666QSvgNode::Type QSvgPolygon::type()
const
671QSvgNode::Type QSvgPolyline::type()
const
676QSvgNode::Type QSvgRect::type()
const
681QSvgNode::Type QSvgText::type()
const
702 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
703 QScopedValueRollback<
bool> guard(m_recursing,
true);
704 p->translate(m_start);
705 bounds = m_link->bounds(p, states);
706 p->translate(-m_start);
714 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
715 QScopedValueRollback<
bool> guard(m_recursing,
true);
716 p->translate(m_start);
717 bounds = m_link->decoratedBounds(p, states);
718 p->translate(-m_start);
723QRectF QSvgPolyline::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
725 return p->transform().mapRect(m_poly.boundingRect());
728QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &s)
const
730 return internalBounds(p, s, BoundsMode::Simplistic);
733QRectF QSvgPolyline::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
735 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
736 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
737 return filterRegion(rect);
740bool QSvgPolyline::requiresGroupRendering()
const
742 return hasAnyMarker();
745QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
747 qreal sw = strokeWidth(p);
748 if (qFuzzyIsNull(sw)) {
749 return p->transform().map(m_poly).boundingRect();
752 path.addPolygon(m_poly);
753 return boundsOnStroke(p, path, sw, mode);
757QRectF QSvgImage::internalBounds(QPainter *p, QSvgExtraStates &)
const
759 return p->transform().mapRect(m_bounds);
762QRectF QSvgLine::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
764 QPointF p1 = p->transform().map(m_line.p1());
765 QPointF p2 = p->transform().map(m_line.p2());
766 qreal minX = qMin(p1.x(), p2.x());
767 qreal minY = qMin(p1.y(), p2.y());
768 qreal maxX = qMax(p1.x(), p2.x());
769 qreal maxY = qMax(p1.y(), p2.y());
770 return QRectF(minX, minY, maxX - minX, maxY - minY);
773QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s)
const
775 return internalBounds(p, s, BoundsMode::Simplistic);
778QRectF QSvgLine::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
780 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
781 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
782 return filterRegion(rect);
785bool QSvgLine::requiresGroupRendering()
const
787 return hasAnyMarker();
790QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s, BoundsMode mode)
const
792 qreal sw = strokeWidth(p);
793 if (qFuzzyIsNull(sw)) {
794 return internalFastBounds(p, s);
797 path.moveTo(m_line.p1());
798 path.lineTo(m_line.p2());
799 return boundsOnStroke(p, path, sw, mode);
QRectF decoratedInternalBounds(QPainter *p, QSvgExtraStates &states) const override
void drawCommand(QPainter *p, QSvgExtraStates &states) override
QRectF internalBounds(QPainter *p, QSvgExtraStates &states) const override
QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *link)
Type type() const override
Type type() const override
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define qPrintable(string)
#define QT_SVG_MAX_LAYOUT_SIZE