8#include <qabstracttextdocumentlayout.h>
10#include <qloggingcategory.h>
12#include <qscopedvaluerollback.h>
13#include <qtextcursor.h>
14#include <qtextdocument.h>
15#include <private/qfixed_p.h>
17#include <QElapsedTimer>
18#include <QLoggingCategory>
27#ifndef QT_SVG_MAX_LAYOUT_SIZE
28#define QT_SVG_MAX_LAYOUT_SIZE (qint64(QFIXED_MAX / 2
))
31void QSvgDummyNode::drawCommand(QPainter *, QSvgExtraStates &)
33 qWarning(
"Dummy node not meant to be drawn");
36QSvgEllipse::QSvgEllipse(QSvgNode *parent,
const QRectF &rect)
37 : QSvgNode(parent), m_bounds(rect)
41QRectF QSvgEllipse::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
43 return p->transform().mapRect(m_bounds);
46QRectF QSvgEllipse::internalBounds(QPainter *p, QSvgExtraStates &)
const
49 path.addEllipse(m_bounds);
50 qreal sw = strokeWidth(p);
51 return qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
52 : boundsOnStroke(p, path, sw, BoundsMode::Simplistic);
55QRectF QSvgEllipse::decoratedInternalBounds(QPainter *p, QSvgExtraStates &)
const
58 path.addEllipse(m_bounds);
59 qreal sw = strokeWidth(p);
60 QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
61 : boundsOnStroke(p, path, sw, BoundsMode::IncludeMiterLimit);
62 return filterRegion(rect);
65void QSvgEllipse::drawCommand(QPainter *p, QSvgExtraStates &)
67 p->drawEllipse(m_bounds);
70bool QSvgEllipse::separateFillStroke(
const QSvgExtraStates &)
const
75QSvgImage::QSvgImage(QSvgNode *parent,
77 const QString &filename,
80 , m_filename(filename)
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()));
90void QSvgImage::drawCommand(QPainter *p, QSvgExtraStates &)
92 p->drawImage(m_bounds, m_image);
95QSvgLine::QSvgLine(QSvgNode *parent,
const QLineF &line)
96 : QSvgNode(parent), m_line(line)
100void QSvgLine::drawCommand(QPainter *p, QSvgExtraStates &states)
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(m_line.p1());
109 p->setOpacity(oldOpacity);
111 QSvgMarker::drawMarkersForNode(
this, p, states);
114QSvgPath::QSvgPath(QSvgNode *parent,
const QPainterPath &qpath)
115 : QSvgNode(parent), m_path(qpath)
119void QSvgPath::drawCommand(QPainter *p, QSvgExtraStates &states)
121 const qreal oldOpacity = p->opacity();
122 const bool drawingInOnePass = !separateFillStroke(states);
123 if (drawingInOnePass)
124 p->setOpacity(oldOpacity * states.fillOpacity);
125 m_path.setFillRule(states.fillRule);
126 if (m_path.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
127 p->drawPoint(m_path.boundingRect().topLeft());
130 if (!path().isEmpty())
131 QSvgMarker::drawMarkersForNode(
this, p, states);
132 if (drawingInOnePass)
133 p->setOpacity(oldOpacity);
136bool QSvgPath::separateFillStroke(
const QSvgExtraStates &s)
const
138 return !qFuzzyCompare(s.fillOpacity, s.strokeOpacity);
141QRectF QSvgPath::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
143 return p->transform().mapRect(m_path.controlPointRect());
146QRectF QSvgPath::internalBounds(QPainter *p, QSvgExtraStates &)
const
148 qreal sw = strokeWidth(p);
149 return qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
150 : boundsOnStroke(p, m_path, sw, BoundsMode::Simplistic);
153QRectF QSvgPath::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
155 qreal sw = strokeWidth(p);
156 QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
157 : boundsOnStroke(p, m_path, sw, BoundsMode::IncludeMiterLimit);
158 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
159 return filterRegion(rect);
162bool QSvgPath::requiresGroupRendering()
const
164 return hasAnyMarker();
167QSvgPolygon::QSvgPolygon(QSvgNode *parent,
const QPolygonF &poly)
168 : QSvgNode(parent), m_poly(poly)
172QRectF QSvgPolygon::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
174 return p->transform().mapRect(m_poly.boundingRect());
177QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &s)
const
179 return internalBounds(p, s, BoundsMode::Simplistic);
182QRectF QSvgPolygon::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
184 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
185 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
186 return filterRegion(rect);
189bool QSvgPolygon::requiresGroupRendering()
const
191 return hasAnyMarker();
194QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
196 qreal sw = strokeWidth(p);
197 if (qFuzzyIsNull(sw)) {
198 return p->transform().map(m_poly).boundingRect();
201 path.addPolygon(m_poly);
202 return boundsOnStroke(p, path, sw, mode);
206void QSvgPolygon::drawCommand(QPainter *p, QSvgExtraStates &states)
208 if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
209 p->drawPoint(m_poly.first());
211 p->drawPolygon(m_poly, states.fillRule);
212 QSvgMarker::drawMarkersForNode(
this, p, states);
215bool QSvgPolygon::separateFillStroke(
const QSvgExtraStates &)
const
220QSvgPolyline::QSvgPolyline(QSvgNode *parent,
const QPolygonF &poly)
221 : QSvgNode(parent), m_poly(poly)
226void QSvgPolyline::drawCommand(QPainter *p, QSvgExtraStates &states)
228 if (p->brush().style() != Qt::NoBrush) {
229 p->drawPolygon(m_poly, states.fillRule);
231 if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
232 p->drawPoint(m_poly.first());
234 p->drawPolyline(m_poly);
235 QSvgMarker::drawMarkersForNode(
this, p, states);
239bool QSvgPolyline::separateFillStroke(
const QSvgExtraStates &)
const
244QSvgRect::QSvgRect(QSvgNode *node,
const QRectF &rect, qreal rx, qreal ry)
246 m_rect(rect), m_rx(rx), m_ry(ry)
250QRectF QSvgRect::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
252 return p->transform().mapRect(m_rect);
255QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &s)
const
257 return internalBounds(p, s, BoundsMode::Simplistic);
260QRectF QSvgRect::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
262 return filterRegion(internalBounds(p, s, BoundsMode::IncludeMiterLimit));
265QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
267 qreal sw = strokeWidth(p);
268 if (qFuzzyIsNull(sw)) {
269 return p->transform().mapRect(m_rect);
272 path.addRect(m_rect);
273 return boundsOnStroke(p, path, sw, mode);
277void QSvgRect::drawCommand(QPainter *p, QSvgExtraStates &)
280 p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize);
285bool QSvgRect::separateFillStroke(
const QSvgExtraStates &)
const
290QSvgTspan *
const QSvgText::LINEBREAK = 0;
292QSvgText::QSvgText(QSvgNode *parent,
const QPointF &coord)
303 for (
int i = 0; i < m_tspans.size(); ++i) {
304 if (m_tspans[i] != LINEBREAK)
309void QSvgText::setTextArea(
const QSizeF &size)
315QRectF QSvgText::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
317 QFont font = m_style.font ? m_style.font->qfont() : p->font();
318 QFontMetricsF fm(font);
321 for (
int i = 0; i < m_tspans.size(); ++i) {
322 if (m_tspans.at(i) != LINEBREAK)
323 charCount += m_tspans.at(i)->text().size();
326 QRectF approxMaximumBrect(m_coord.x(),
328 charCount * fm.averageCharWidth(),
329 -m_tspans.size() * fm.height());
330 return p->transform().mapRect(approxMaximumBrect);
333QRectF QSvgText::internalBounds(QPainter *p, QSvgExtraStates &states)
const
336 if (shouldDrawNode(p, states))
337 draw_helper(p, states, &boundingRect);
338 return p->transform().mapRect(boundingRect);
341void QSvgText::drawCommand(QPainter *p, QSvgExtraStates &states)
343 draw_helper(p, states);
346bool QSvgText::shouldDrawNode(QPainter *p, QSvgExtraStates &)
const
348 qsizetype numChars = 0;
349 qreal originalFontSize = p->font().pointSizeF();
350 qreal maxFontSize = originalFontSize;
351 for (
const QSvgTspan *span : std::as_const(m_tspans)) {
352 if (span == LINEBREAK)
355 numChars += span->text().size();
357 QSvgFontStyle *style =
static_cast<QSvgFontStyle *>(span->styleProperty(QSvgStyleProperty::FONT));
358 if (style !=
nullptr && style->qfont().pointSizeF() > maxFontSize)
359 maxFontSize = style->qfont().pointSizeF();
362 QFont font = p->font();
363 font.setPixelSize(maxFontSize);
364 QFontMetricsF fm(font);
366 qCWarning(lcSvgDraw) <<
"Text element too high to lay out, ignoring";
371 qCWarning(lcSvgDraw) <<
"Text element too wide to lay out, ignoring";
378bool QSvgText::separateFillStroke(
const QSvgExtraStates &)
const
383void QSvgText::draw_helper(QPainter *p, QSvgExtraStates &states, QRectF *boundingRect)
const
385 const bool isPainting = (boundingRect ==
nullptr);
386 if (!isPainting || shouldDrawNode(p, states)) {
387 QFont font = p->font();
388 Qt::Alignment alignment = states.textAnchor;
392 qreal px = m_coord.x();
393 qreal py = m_coord.y();
395 if (m_type == Textarea) {
396 if (alignment == Qt::AlignHCenter)
397 px += m_size.width() / 2;
398 else if (alignment == Qt::AlignRight)
399 px += m_size.width();
403 if (m_size.height() != 0)
404 bounds = QRectF(0, py, 1, m_size.height());
406 bool appendSpace =
false;
407 QList<QString> paragraphs;
408 QList<QList<QTextLayout::FormatRange> > formatRanges(1);
409 paragraphs.push_back(QString());
411 for (
int i = 0; i < m_tspans.size(); ++i) {
412 if (m_tspans[i] == LINEBREAK) {
413 if (m_type == Textarea) {
414 if (paragraphs.back().isEmpty()) {
415 font.setPixelSize(font.pointSizeF());
416 font.setHintingPreference(QFont::PreferNoHinting);
418 QTextLayout::FormatRange range;
421 range.format.setFont(font);
422 formatRanges.back().append(range);
424 paragraphs.back().append(QLatin1Char(
' '));;
427 paragraphs.push_back(QString());
428 formatRanges.resize(formatRanges.size() + 1);
431 WhitespaceMode mode = m_tspans[i]->whitespaceMode();
432 m_tspans[i]->applyStyle(p, states);
435 font.setPixelSize(font.pointSizeF());
436 font.setHintingPreference(QFont::PreferNoHinting);
438 QString newText(m_tspans[i]->text());
439 newText.replace(QLatin1Char(
'\t'), QLatin1Char(
' '));
440 newText.replace(QLatin1Char(
'\n'), QLatin1Char(
' '));
442 bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(
' '));
443 if (appendSpace || prependSpace)
444 paragraphs.back().append(QLatin1Char(
' '));
446 bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(
' ')));
448 if (mode == Default) {
449 newText = newText.simplified();
450 if (newText.isEmpty())
451 appendSpaceNext =
false;
454 QTextLayout::FormatRange range;
455 range.start = paragraphs.back().size();
456 range.length = newText.size();
457 range.format.setFont(font);
458 if (p->pen().style() != Qt::NoPen && p->pen().brush() != Qt::NoBrush)
459 range.format.setTextOutline(p->pen());
461 range.format.setForeground(p->brush().style() == Qt::NoBrush
462 ? QColor(Qt::transparent) : p->brush());
465 Q_ASSERT(!formatRanges.back().isEmpty());
466 ++formatRanges.back().back().length;
467 }
else if (prependSpace) {
471 formatRanges.back().append(range);
473 appendSpace = appendSpaceNext;
474 paragraphs.back() += newText;
476 m_tspans[i]->revertStyle(p, states);
480 if (states.svgFont) {
482 QString text = paragraphs.front();
483 for (
int i = 1; i < paragraphs.size(); ++i) {
484 text.append(QLatin1Char(
'\n'));
485 text.append(paragraphs[i]);
488 states.svgFont->draw(
489 p, m_coord, text, p->font().pointSizeF(), states.textAnchor);
492 *boundingRect = states.svgFont->boundingRect(
493 p, m_coord, text, p->font().pointSizeF(), states.textAnchor);
497 for (
int i = 0; i < paragraphs.size(); ++i) {
498 QTextLayout tl(paragraphs[i]);
499 QTextOption op = tl.textOption();
500 op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
501 tl.setTextOption(op);
502 tl.setFormats(formatRanges[i]);
506 QTextLine line = tl.createLine();
509 if (m_size.width() != 0)
510 line.setLineWidth(m_size.width());
514 bool endOfBoundsReached =
false;
515 for (
int i = 0; i < tl.lineCount(); ++i) {
516 QTextLine line = tl.lineAt(i);
519 if (alignment == Qt::AlignHCenter)
520 x -= 0.5 * line.naturalTextWidth();
521 else if (alignment == Qt::AlignRight)
522 x -= line.naturalTextWidth();
524 if (initial && m_type == Text)
528 line.setPosition(QPointF(x, y));
529 brect |= line.naturalTextRect();
532 if ((m_size.width() != 0 && line.naturalTextWidth() > m_size.width())
533 || (m_size.height() != 0 && y + line.height() > m_size.height())) {
536 bounds.setHeight(y - 1);
537 endOfBoundsReached =
true;
541 y += 1.1 * line.height();
544 tl.draw(p, QPointF(px, py), QList<QTextLayout::FormatRange>(), bounds);
546 if (endOfBoundsReached)
550 brect.translate(m_coord);
551 if (bounds.height() > 0)
552 brect.setBottom(qMin(brect.bottom(), bounds.bottom()));
553 *boundingRect = brect;
559void QSvgText::addText(QStringView text)
561 m_tspans.append(
new QSvgTspan(
this,
false));
562 m_tspans.back()->setWhitespaceMode(m_mode);
563 m_tspans.back()->addText(text);
574 if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing))
577 Q_ASSERT(states.nestedUseCount == 0 || states.nestedUseLevel > 0);
578 if (states.nestedUseLevel > 3 && states.nestedUseCount > (256 + states.nestedUseLevel * 2)) {
583 QScopedValueRollback<
bool> inUseGuard(states.inUse,
true);
585 if (!m_start.isNull()) {
586 p->translate(m_start);
588 if (states.nestedUseLevel > 0)
589 ++states.nestedUseCount;
591 QScopedValueRollback<
int> useLevelGuard(states.nestedUseLevel, states.nestedUseLevel + 1);
592 QScopedValueRollback<
bool> recursingGuard(m_recursing,
true);
593 m_link->draw(p, states);
595 if (states.nestedUseLevel == 0)
596 states.nestedUseCount = 0;
598 if (!m_start.isNull()) {
599 p->translate(-m_start);
603QSvgNode::Type QSvgDummyNode::type()
const
605 return FeUnsupported;
608QSvgNode::Type QSvgCircle::type()
const
613QSvgNode::Type QSvgEllipse::type()
const
618QSvgNode::Type QSvgImage::type()
const
623QSvgNode::Type QSvgLine::type()
const
628QSvgNode::Type QSvgPath::type()
const
633QSvgNode::Type QSvgPolygon::type()
const
638QSvgNode::Type QSvgPolyline::type()
const
643QSvgNode::Type QSvgRect::type()
const
648QSvgNode::Type QSvgText::type()
const
666 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
667 QScopedValueRollback<
bool> guard(m_recursing,
true);
668 p->translate(m_start);
669 bounds = m_link->bounds(p, states);
670 p->translate(-m_start);
678 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
679 QScopedValueRollback<
bool> guard(m_recursing,
true);
680 p->translate(m_start);
681 bounds = m_link->decoratedBounds(p, states);
682 p->translate(-m_start);
687QRectF QSvgPolyline::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
689 return p->transform().mapRect(m_poly.boundingRect());
692QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &s)
const
694 return internalBounds(p, s, BoundsMode::Simplistic);
697QRectF QSvgPolyline::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
699 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
700 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
701 return filterRegion(rect);
704bool QSvgPolyline::requiresGroupRendering()
const
706 return hasAnyMarker();
709QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
711 qreal sw = strokeWidth(p);
712 if (qFuzzyIsNull(sw)) {
713 return p->transform().map(m_poly).boundingRect();
716 path.addPolygon(m_poly);
717 return boundsOnStroke(p, path, sw, mode);
721QRectF QSvgImage::internalBounds(QPainter *p, QSvgExtraStates &)
const
723 return p->transform().mapRect(m_bounds);
726QRectF QSvgLine::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
728 QPointF p1 = p->transform().map(m_line.p1());
729 QPointF p2 = p->transform().map(m_line.p2());
730 qreal minX = qMin(p1.x(), p2.x());
731 qreal minY = qMin(p1.y(), p2.y());
732 qreal maxX = qMax(p1.x(), p2.x());
733 qreal maxY = qMax(p1.y(), p2.y());
734 return QRectF(minX, minY, maxX - minX, maxY - minY);
737QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s)
const
739 return internalBounds(p, s, BoundsMode::Simplistic);
742QRectF QSvgLine::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
744 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
745 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
746 return filterRegion(rect);
749bool QSvgLine::requiresGroupRendering()
const
751 return hasAnyMarker();
754QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s, BoundsMode mode)
const
756 qreal sw = strokeWidth(p);
757 if (qFuzzyIsNull(sw)) {
758 return internalFastBounds(p, s);
761 path.moveTo(m_line.p1());
762 path.lineTo(m_line.p2());
763 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