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
))
32void QSvgDummyNode::drawCommand(QPainter *, QSvgExtraStates &)
34 qWarning(
"Dummy node not meant to be drawn");
37QSvgEllipse::QSvgEllipse(QSvgNode *parent,
const QRectF &rect)
38 : QSvgNode(parent), m_bounds(rect)
42QRectF QSvgEllipse::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
44 return p->transform().mapRect(m_bounds);
47QRectF QSvgEllipse::internalBounds(QPainter *p, QSvgExtraStates &)
const
50 path.addEllipse(m_bounds);
51 qreal sw = strokeWidth(p);
52 return qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
53 : boundsOnStroke(p, path, sw, BoundsMode::Simplistic);
56QRectF QSvgEllipse::decoratedInternalBounds(QPainter *p, QSvgExtraStates &)
const
59 path.addEllipse(m_bounds);
60 qreal sw = strokeWidth(p);
61 QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect()
62 : boundsOnStroke(p, path, sw, BoundsMode::IncludeMiterLimit);
63 return filterRegion(rect);
66void QSvgEllipse::drawCommand(QPainter *p, QSvgExtraStates &)
68 p->drawEllipse(m_bounds);
71bool QSvgEllipse::separateFillStroke(
const QSvgExtraStates &)
const
76QSvgImage::QSvgImage(QSvgNode *parent,
78 const QString &filename,
81 , m_filename(filename)
85 if (m_bounds.width() == 0.0)
86 m_bounds.setWidth(
static_cast<qreal>(m_image.width()));
87 if (m_bounds.height() == 0.0)
88 m_bounds.setHeight(
static_cast<qreal>(m_image.height()));
91void QSvgImage::drawCommand(QPainter *p, QSvgExtraStates &)
93 p->drawImage(m_bounds, m_image);
96QSvgLine::QSvgLine(QSvgNode *parent,
const QLineF &line)
97 : QSvgNode(parent), m_line(line)
101void QSvgLine::drawCommand(QPainter *p, QSvgExtraStates &states)
103 if (p->pen().widthF() != 0) {
104 qreal oldOpacity = p->opacity();
105 p->setOpacity(oldOpacity * states.strokeOpacity);
106 if (m_line.isNull() && p->pen().capStyle() != Qt::FlatCap)
107 p->drawPoint(m_line.p1());
110 p->setOpacity(oldOpacity);
112 QSvgMarker::drawMarkersForNode(
this, p, states);
115QSvgPath::QSvgPath(QSvgNode *parent,
const QPainterPath &qpath)
116 : QSvgNode(parent), m_path(qpath)
120void QSvgPath::drawCommand(QPainter *p, QSvgExtraStates &states)
122 const qreal oldOpacity = p->opacity();
123 const bool drawingInOnePass = !separateFillStroke(states);
124 if (drawingInOnePass)
125 p->setOpacity(oldOpacity * states.fillOpacity);
126 m_path.setFillRule(states.fillRule);
127 if (m_path.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
128 p->drawPoint(m_path.boundingRect().topLeft());
131 if (!path().isEmpty())
132 QSvgMarker::drawMarkersForNode(
this, p, states);
133 if (drawingInOnePass)
134 p->setOpacity(oldOpacity);
137bool QSvgPath::separateFillStroke(
const QSvgExtraStates &s)
const
139 return !qFuzzyCompare(s.fillOpacity, s.strokeOpacity);
142QRectF QSvgPath::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
144 return p->transform().mapRect(m_path.controlPointRect());
147QRectF QSvgPath::internalBounds(QPainter *p, QSvgExtraStates &)
const
149 qreal sw = strokeWidth(p);
150 return qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
151 : boundsOnStroke(p, m_path, sw, BoundsMode::Simplistic);
154QRectF QSvgPath::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
156 qreal sw = strokeWidth(p);
157 QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
158 : boundsOnStroke(p, m_path, sw, BoundsMode::IncludeMiterLimit);
159 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
160 return filterRegion(rect);
163bool QSvgPath::requiresGroupRendering()
const
165 return hasAnyMarker();
168QSvgPolygon::QSvgPolygon(QSvgNode *parent,
const QPolygonF &poly)
169 : QSvgNode(parent), m_poly(poly)
173QRectF QSvgPolygon::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
175 return p->transform().mapRect(m_poly.boundingRect());
178QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &s)
const
180 return internalBounds(p, s, BoundsMode::Simplistic);
183QRectF QSvgPolygon::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
185 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
186 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
187 return filterRegion(rect);
190bool QSvgPolygon::requiresGroupRendering()
const
192 return hasAnyMarker();
195QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
197 qreal sw = strokeWidth(p);
198 if (qFuzzyIsNull(sw)) {
199 return p->transform().map(m_poly).boundingRect();
202 path.addPolygon(m_poly);
203 return boundsOnStroke(p, path, sw, mode);
207void QSvgPolygon::drawCommand(QPainter *p, QSvgExtraStates &states)
209 if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
210 p->drawPoint(m_poly.first());
212 p->drawPolygon(m_poly, states.fillRule);
213 QSvgMarker::drawMarkersForNode(
this, p, states);
216bool QSvgPolygon::separateFillStroke(
const QSvgExtraStates &)
const
221QSvgPolyline::QSvgPolyline(QSvgNode *parent,
const QPolygonF &poly)
222 : QSvgNode(parent), m_poly(poly)
227void QSvgPolyline::drawCommand(QPainter *p, QSvgExtraStates &states)
229 if (p->brush().style() != Qt::NoBrush) {
230 p->drawPolygon(m_poly, states.fillRule);
232 if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
233 p->drawPoint(m_poly.first());
235 p->drawPolyline(m_poly);
236 QSvgMarker::drawMarkersForNode(
this, p, states);
240bool QSvgPolyline::separateFillStroke(
const QSvgExtraStates &)
const
245QSvgRect::QSvgRect(QSvgNode *node,
const QRectF &rect, qreal rx, qreal ry)
247 m_rect(rect), m_rx(rx), m_ry(ry)
251QRectF QSvgRect::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
253 return p->transform().mapRect(m_rect);
256QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &s)
const
258 return internalBounds(p, s, BoundsMode::Simplistic);
261QRectF QSvgRect::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
263 return filterRegion(internalBounds(p, s, BoundsMode::IncludeMiterLimit));
266QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
268 qreal sw = strokeWidth(p);
269 if (qFuzzyIsNull(sw)) {
270 return p->transform().mapRect(m_rect);
273 path.addRect(m_rect);
274 return boundsOnStroke(p, path, sw, mode);
278void QSvgRect::drawCommand(QPainter *p, QSvgExtraStates &)
281 p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize);
286bool QSvgRect::separateFillStroke(
const QSvgExtraStates &)
const
291QSvgTspan *
const QSvgText::LINEBREAK = 0;
293QSvgText::QSvgText(QSvgNode *parent,
const QPointF &coord)
304 for (
int i = 0; i < m_tspans.size(); ++i) {
305 if (m_tspans[i] != LINEBREAK)
310void QSvgText::setTextArea(
const QSizeF &size)
316QRectF QSvgText::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
318 QFont font = m_style.font ? m_style.font->qfont() : p->font();
319 QFontMetricsF fm(font);
322 for (
int i = 0; i < m_tspans.size(); ++i) {
323 if (m_tspans.at(i) != LINEBREAK)
324 charCount += m_tspans.at(i)->text().size();
327 QRectF approxMaximumBrect(m_coord.x(),
329 charCount * fm.averageCharWidth(),
330 -m_tspans.size() * fm.height());
331 return p->transform().mapRect(approxMaximumBrect);
334QRectF QSvgText::internalBounds(QPainter *p, QSvgExtraStates &states)
const
337 if (shouldDrawNode(p, states))
338 draw_helper(p, states, &boundingRect);
339 return p->transform().mapRect(boundingRect);
342void QSvgText::drawCommand(QPainter *p, QSvgExtraStates &states)
344 draw_helper(p, states);
347bool QSvgText::shouldDrawNode(QPainter *p, QSvgExtraStates &)
const
349 qsizetype numChars = 0;
350 qreal originalFontSize = p->font().pointSizeF();
351 qreal maxFontSize = originalFontSize;
352 for (
const QSvgTspan *span : std::as_const(m_tspans)) {
353 if (span == LINEBREAK)
356 numChars += span->text().size();
358 QSvgFontStyle *style =
static_cast<QSvgFontStyle *>(span->styleProperty(QSvgStyleProperty::FONT));
359 if (style !=
nullptr && style->qfont().pointSizeF() > maxFontSize)
360 maxFontSize = style->qfont().pointSizeF();
363 QFont font = p->font();
364 font.setPixelSize(maxFontSize);
365 QFontMetricsF fm(font);
367 qCWarning(lcSvgDraw) <<
"Text element too high to lay out, ignoring";
372 qCWarning(lcSvgDraw) <<
"Text element too wide to lay out, ignoring";
379bool QSvgText::separateFillStroke(
const QSvgExtraStates &)
const
384void QSvgText::draw_helper(QPainter *p, QSvgExtraStates &states, QRectF *boundingRect)
const
386 const bool isPainting = (boundingRect ==
nullptr);
387 if (!isPainting || shouldDrawNode(p, states)) {
388 QFont font = p->font();
389 Qt::Alignment alignment = states.textAnchor;
393 qreal px = m_coord.x();
394 qreal py = m_coord.y();
396 if (m_type == Textarea) {
397 if (alignment == Qt::AlignHCenter)
398 px += m_size.width() / 2;
399 else if (alignment == Qt::AlignRight)
400 px += m_size.width();
404 if (m_size.height() != 0)
405 bounds = QRectF(0, py, 1, m_size.height());
407 bool appendSpace =
false;
408 QStringList paragraphs;
409 QList<QList<QTextLayout::FormatRange> > formatRanges(1);
410 paragraphs.push_back(QString());
412 for (
int i = 0; i < m_tspans.size(); ++i) {
413 if (m_tspans[i] == LINEBREAK) {
414 if (m_type == Textarea) {
415 if (paragraphs.back().isEmpty()) {
416 font.setPixelSize(font.pointSizeF());
417 font.setHintingPreference(QFont::PreferNoHinting);
419 QTextLayout::FormatRange range;
422 range.format.setFont(font);
423 formatRanges.back().append(range);
425 paragraphs.back().append(QLatin1Char(
' '));;
428 paragraphs.push_back(QString());
429 formatRanges.resize(formatRanges.size() + 1);
432 WhitespaceMode mode = m_tspans[i]->whitespaceMode();
433 m_tspans[i]->applyStyle(p, states);
436 font.setPixelSize(font.pointSizeF());
437 font.setHintingPreference(QFont::PreferNoHinting);
439 QString newText(m_tspans[i]->text());
440 newText.replace(QLatin1Char(
'\t'), QLatin1Char(
' '));
441 newText.replace(QLatin1Char(
'\n'), QLatin1Char(
' '));
443 bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(
' '));
444 if (appendSpace || prependSpace)
445 paragraphs.back().append(QLatin1Char(
' '));
447 bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(
' ')));
449 if (mode == Default) {
450 newText = newText.simplified();
451 if (newText.isEmpty())
452 appendSpaceNext =
false;
455 QTextLayout::FormatRange range;
456 range.start = paragraphs.back().size();
457 range.length = newText.size();
458 range.format.setFont(font);
459 if (p->pen().style() != Qt::NoPen && p->pen().brush() != Qt::NoBrush)
460 range.format.setTextOutline(p->pen());
462 range.format.setForeground(p->brush().style() == Qt::NoBrush
463 ? QColor(Qt::transparent) : p->brush());
466 Q_ASSERT(!formatRanges.back().isEmpty());
467 ++formatRanges.back().back().length;
468 }
else if (prependSpace) {
472 formatRanges.back().append(range);
474 appendSpace = appendSpaceNext;
475 paragraphs.back() += newText;
477 m_tspans[i]->revertStyle(p, states);
481 if (states.svgFont) {
484 m_glyphsToDraw = states.svgFont->toGlyphs(paragraphs.join(QLatin1Char(
'\n')));
486 states.svgFont->draw(p, m_coord, m_glyphsToDraw.value(),
487 p->font().pointSizeF(), states.textAnchor);
489 *boundingRect = states.svgFont->boundingRect(p, m_coord, m_glyphsToDraw.value(),
490 p->font().pointSizeF(), states.textAnchor);
494 for (
int i = 0; i < paragraphs.size(); ++i) {
495 QTextLayout tl(paragraphs[i]);
496 QTextOption op = tl.textOption();
497 op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
498 tl.setTextOption(op);
499 tl.setFormats(formatRanges[i]);
503 QTextLine line = tl.createLine();
506 if (m_size.width() != 0)
507 line.setLineWidth(m_size.width());
511 bool endOfBoundsReached =
false;
512 for (
int i = 0; i < tl.lineCount(); ++i) {
513 QTextLine line = tl.lineAt(i);
516 if (alignment == Qt::AlignHCenter)
517 x -= 0.5 * line.naturalTextWidth();
518 else if (alignment == Qt::AlignRight)
519 x -= line.naturalTextWidth();
521 if (initial && m_type == Text)
525 line.setPosition(QPointF(x, y));
526 brect |= line.naturalTextRect();
529 if ((m_size.width() != 0 && line.naturalTextWidth() > m_size.width())
530 || (m_size.height() != 0 && y + line.height() > m_size.height())) {
533 bounds.setHeight(y - 1);
534 endOfBoundsReached =
true;
538 y += 1.1 * line.height();
541 tl.draw(p, QPointF(px, py), QList<QTextLayout::FormatRange>(), bounds);
543 if (endOfBoundsReached)
547 brect.translate(m_coord);
548 if (bounds.height() > 0)
549 brect.setBottom(qMin(brect.bottom(), bounds.bottom()));
550 *boundingRect = brect;
556void QSvgText::addText(QStringView text)
558 m_tspans.append(
new QSvgTspan(
this,
false));
559 m_tspans.back()->setWhitespaceMode(m_mode);
560 m_tspans.back()->addText(text);
571 if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing))
574 Q_ASSERT(states.nestedUseCount == 0 || states.nestedUseLevel > 0);
575 if (states.nestedUseLevel > 3 && states.nestedUseCount > (256 + states.nestedUseLevel * 2)) {
580 QScopedValueRollback<
bool> inUseGuard(states.inUse,
true);
582 if (!m_start.isNull()) {
583 p->translate(m_start);
585 if (states.nestedUseLevel > 0)
586 ++states.nestedUseCount;
588 QScopedValueRollback<
int> useLevelGuard(states.nestedUseLevel, states.nestedUseLevel + 1);
589 QScopedValueRollback<
bool> recursingGuard(m_recursing,
true);
590 m_link->draw(p, states);
592 if (states.nestedUseLevel == 0)
593 states.nestedUseCount = 0;
595 if (!m_start.isNull()) {
596 p->translate(-m_start);
600QSvgNode::Type QSvgDummyNode::type()
const
602 return FeUnsupported;
605QSvgNode::Type QSvgCircle::type()
const
610QSvgNode::Type QSvgEllipse::type()
const
615QSvgNode::Type QSvgImage::type()
const
620QSvgNode::Type QSvgLine::type()
const
625QSvgNode::Type QSvgPath::type()
const
630QSvgNode::Type QSvgPolygon::type()
const
635QSvgNode::Type QSvgPolyline::type()
const
640QSvgNode::Type QSvgRect::type()
const
645QSvgNode::Type QSvgText::type()
const
663 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
664 QScopedValueRollback<
bool> guard(m_recursing,
true);
665 p->translate(m_start);
666 bounds = m_link->bounds(p, states);
667 p->translate(-m_start);
675 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
676 QScopedValueRollback<
bool> guard(m_recursing,
true);
677 p->translate(m_start);
678 bounds = m_link->decoratedBounds(p, states);
679 p->translate(-m_start);
684QRectF QSvgPolyline::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
686 return p->transform().mapRect(m_poly.boundingRect());
689QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &s)
const
691 return internalBounds(p, s, BoundsMode::Simplistic);
694QRectF QSvgPolyline::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
696 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
697 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
698 return filterRegion(rect);
701bool QSvgPolyline::requiresGroupRendering()
const
703 return hasAnyMarker();
706QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
708 qreal sw = strokeWidth(p);
709 if (qFuzzyIsNull(sw)) {
710 return p->transform().map(m_poly).boundingRect();
713 path.addPolygon(m_poly);
714 return boundsOnStroke(p, path, sw, mode);
718QRectF QSvgImage::internalBounds(QPainter *p, QSvgExtraStates &)
const
720 return p->transform().mapRect(m_bounds);
723QRectF QSvgLine::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
725 QPointF p1 = p->transform().map(m_line.p1());
726 QPointF p2 = p->transform().map(m_line.p2());
727 qreal minX = qMin(p1.x(), p2.x());
728 qreal minY = qMin(p1.y(), p2.y());
729 qreal maxX = qMax(p1.x(), p2.x());
730 qreal maxY = qMax(p1.y(), p2.y());
731 return QRectF(minX, minY, maxX - minX, maxY - minY);
734QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s)
const
736 return internalBounds(p, s, BoundsMode::Simplistic);
739QRectF QSvgLine::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
741 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
742 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
743 return filterRegion(rect);
746bool QSvgLine::requiresGroupRendering()
const
748 return hasAnyMarker();
751QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s, BoundsMode mode)
const
753 qreal sw = strokeWidth(p);
754 if (qFuzzyIsNull(sw)) {
755 return internalFastBounds(p, s);
758 path.moveTo(m_line.p1());
759 path.lineTo(m_line.p2());
760 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