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 QSvgMarker::drawMarkersForNode(
this, p, states);
131 if (drawingInOnePass)
132 p->setOpacity(oldOpacity);
135bool QSvgPath::separateFillStroke(
const QSvgExtraStates &s)
const
137 return !qFuzzyCompare(s.fillOpacity, s.strokeOpacity);
140QRectF QSvgPath::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
142 return p->transform().mapRect(m_path.controlPointRect());
145QRectF QSvgPath::internalBounds(QPainter *p, QSvgExtraStates &)
const
147 qreal sw = strokeWidth(p);
148 return qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
149 : boundsOnStroke(p, m_path, sw, BoundsMode::Simplistic);
152QRectF QSvgPath::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
154 qreal sw = strokeWidth(p);
155 QRectF rect = qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
156 : boundsOnStroke(p, m_path, sw, BoundsMode::IncludeMiterLimit);
157 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
158 return filterRegion(rect);
161bool QSvgPath::requiresGroupRendering()
const
163 return hasAnyMarker();
166QSvgPolygon::QSvgPolygon(QSvgNode *parent,
const QPolygonF &poly)
167 : QSvgNode(parent), m_poly(poly)
171QRectF QSvgPolygon::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
173 return p->transform().mapRect(m_poly.boundingRect());
176QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &s)
const
178 return internalBounds(p, s, BoundsMode::Simplistic);
181QRectF QSvgPolygon::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
183 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
184 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
185 return filterRegion(rect);
188bool QSvgPolygon::requiresGroupRendering()
const
190 return hasAnyMarker();
193QRectF QSvgPolygon::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
195 qreal sw = strokeWidth(p);
196 if (qFuzzyIsNull(sw)) {
197 return p->transform().map(m_poly).boundingRect();
200 path.addPolygon(m_poly);
201 return boundsOnStroke(p, path, sw, mode);
205void QSvgPolygon::drawCommand(QPainter *p, QSvgExtraStates &states)
207 if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
208 p->drawPoint(m_poly.first());
210 p->drawPolygon(m_poly, states.fillRule);
211 QSvgMarker::drawMarkersForNode(
this, p, states);
214bool QSvgPolygon::separateFillStroke(
const QSvgExtraStates &)
const
219QSvgPolyline::QSvgPolyline(QSvgNode *parent,
const QPolygonF &poly)
220 : QSvgNode(parent), m_poly(poly)
225void QSvgPolyline::drawCommand(QPainter *p, QSvgExtraStates &states)
227 if (p->brush().style() != Qt::NoBrush) {
228 p->drawPolygon(m_poly, states.fillRule);
230 if (m_poly.boundingRect().isNull() && p->pen().capStyle() != Qt::FlatCap)
231 p->drawPoint(m_poly.first());
233 p->drawPolyline(m_poly);
234 QSvgMarker::drawMarkersForNode(
this, p, states);
238bool QSvgPolyline::separateFillStroke(
const QSvgExtraStates &)
const
243QSvgRect::QSvgRect(QSvgNode *node,
const QRectF &rect, qreal rx, qreal ry)
245 m_rect(rect), m_rx(rx), m_ry(ry)
249QRectF QSvgRect::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
251 return p->transform().mapRect(m_rect);
254QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &s)
const
256 return internalBounds(p, s, BoundsMode::Simplistic);
259QRectF QSvgRect::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
261 return filterRegion(internalBounds(p, s, BoundsMode::IncludeMiterLimit));
264QRectF QSvgRect::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
266 qreal sw = strokeWidth(p);
267 if (qFuzzyIsNull(sw)) {
268 return p->transform().mapRect(m_rect);
271 path.addRect(m_rect);
272 return boundsOnStroke(p, path, sw, mode);
276void QSvgRect::drawCommand(QPainter *p, QSvgExtraStates &)
279 p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize);
284bool QSvgRect::separateFillStroke(
const QSvgExtraStates &)
const
289QSvgTspan *
const QSvgText::LINEBREAK = 0;
291QSvgText::QSvgText(QSvgNode *parent,
const QPointF &coord)
302 for (
int i = 0; i < m_tspans.size(); ++i) {
303 if (m_tspans[i] != LINEBREAK)
308void QSvgText::setTextArea(
const QSizeF &size)
314QRectF QSvgText::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
316 QFont font = m_style.font ? m_style.font->qfont() : p->font();
317 QFontMetricsF fm(font);
320 for (
int i = 0; i < m_tspans.size(); ++i) {
321 if (m_tspans.at(i) != LINEBREAK)
322 charCount += m_tspans.at(i)->text().size();
325 QRectF approxMaximumBrect(m_coord.x(),
327 charCount * fm.averageCharWidth(),
328 -m_tspans.size() * fm.height());
329 return p->transform().mapRect(approxMaximumBrect);
332QRectF QSvgText::internalBounds(QPainter *p, QSvgExtraStates &states)
const
335 if (shouldDrawNode(p, states))
336 draw_helper(p, states, &boundingRect);
337 return p->transform().mapRect(boundingRect);
340void QSvgText::drawCommand(QPainter *p, QSvgExtraStates &states)
342 draw_helper(p, states);
345bool QSvgText::shouldDrawNode(QPainter *p, QSvgExtraStates &)
const
347 qsizetype numChars = 0;
348 qreal originalFontSize = p->font().pointSizeF();
349 qreal maxFontSize = originalFontSize;
350 for (
const QSvgTspan *span : std::as_const(m_tspans)) {
351 if (span == LINEBREAK)
354 numChars += span->text().size();
356 QSvgFontStyle *style =
static_cast<QSvgFontStyle *>(span->styleProperty(QSvgStyleProperty::FONT));
357 if (style !=
nullptr && style->qfont().pointSizeF() > maxFontSize)
358 maxFontSize = style->qfont().pointSizeF();
361 QFont font = p->font();
362 font.setPixelSize(maxFontSize);
363 QFontMetricsF fm(font);
365 qCWarning(lcSvgDraw) <<
"Text element too high to lay out, ignoring";
370 qCWarning(lcSvgDraw) <<
"Text element too wide to lay out, ignoring";
377bool QSvgText::separateFillStroke(
const QSvgExtraStates &)
const
382void QSvgText::draw_helper(QPainter *p, QSvgExtraStates &states, QRectF *boundingRect)
const
384 const bool isPainting = (boundingRect ==
nullptr);
385 if (!isPainting || shouldDrawNode(p, states)) {
386 QFont font = p->font();
387 Qt::Alignment alignment = states.textAnchor;
391 qreal px = m_coord.x();
392 qreal py = m_coord.y();
394 if (m_type == Textarea) {
395 if (alignment == Qt::AlignHCenter)
396 px += m_size.width() / 2;
397 else if (alignment == Qt::AlignRight)
398 px += m_size.width();
402 if (m_size.height() != 0)
403 bounds = QRectF(0, py, 1, m_size.height());
405 bool appendSpace =
false;
406 QList<QString> paragraphs;
407 QList<QList<QTextLayout::FormatRange> > formatRanges(1);
408 paragraphs.push_back(QString());
410 for (
int i = 0; i < m_tspans.size(); ++i) {
411 if (m_tspans[i] == LINEBREAK) {
412 if (m_type == Textarea) {
413 if (paragraphs.back().isEmpty()) {
414 font.setPixelSize(font.pointSizeF());
415 font.setHintingPreference(QFont::PreferNoHinting);
417 QTextLayout::FormatRange range;
420 range.format.setFont(font);
421 formatRanges.back().append(range);
423 paragraphs.back().append(QLatin1Char(
' '));;
426 paragraphs.push_back(QString());
427 formatRanges.resize(formatRanges.size() + 1);
430 WhitespaceMode mode = m_tspans[i]->whitespaceMode();
431 m_tspans[i]->applyStyle(p, states);
434 font.setPixelSize(font.pointSizeF());
435 font.setHintingPreference(QFont::PreferNoHinting);
437 QString newText(m_tspans[i]->text());
438 newText.replace(QLatin1Char(
'\t'), QLatin1Char(
' '));
439 newText.replace(QLatin1Char(
'\n'), QLatin1Char(
' '));
441 bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(
' '));
442 if (appendSpace || prependSpace)
443 paragraphs.back().append(QLatin1Char(
' '));
445 bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(
' ')));
447 if (mode == Default) {
448 newText = newText.simplified();
449 if (newText.isEmpty())
450 appendSpaceNext =
false;
453 QTextLayout::FormatRange range;
454 range.start = paragraphs.back().size();
455 range.length = newText.size();
456 range.format.setFont(font);
457 if (p->pen().style() != Qt::NoPen && p->pen().brush() != Qt::NoBrush)
458 range.format.setTextOutline(p->pen());
460 range.format.setForeground(p->brush().style() == Qt::NoBrush
461 ? QColor(Qt::transparent) : p->brush());
464 Q_ASSERT(!formatRanges.back().isEmpty());
465 ++formatRanges.back().back().length;
466 }
else if (prependSpace) {
470 formatRanges.back().append(range);
472 appendSpace = appendSpaceNext;
473 paragraphs.back() += newText;
475 m_tspans[i]->revertStyle(p, states);
479 if (states.svgFont) {
481 QString text = paragraphs.front();
482 for (
int i = 1; i < paragraphs.size(); ++i) {
483 text.append(QLatin1Char(
'\n'));
484 text.append(paragraphs[i]);
487 states.svgFont->draw(
488 p, m_coord, text, p->font().pointSizeF(), states.textAnchor);
491 *boundingRect = states.svgFont->boundingRect(
492 p, m_coord, text, p->font().pointSizeF(), states.textAnchor);
496 for (
int i = 0; i < paragraphs.size(); ++i) {
497 QTextLayout tl(paragraphs[i]);
498 QTextOption op = tl.textOption();
499 op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
500 tl.setTextOption(op);
501 tl.setFormats(formatRanges[i]);
505 QTextLine line = tl.createLine();
508 if (m_size.width() != 0)
509 line.setLineWidth(m_size.width());
513 bool endOfBoundsReached =
false;
514 for (
int i = 0; i < tl.lineCount(); ++i) {
515 QTextLine line = tl.lineAt(i);
518 if (alignment == Qt::AlignHCenter)
519 x -= 0.5 * line.naturalTextWidth();
520 else if (alignment == Qt::AlignRight)
521 x -= line.naturalTextWidth();
523 if (initial && m_type == Text)
527 line.setPosition(QPointF(x, y));
528 brect |= line.naturalTextRect();
531 if ((m_size.width() != 0 && line.naturalTextWidth() > m_size.width())
532 || (m_size.height() != 0 && y + line.height() > m_size.height())) {
535 bounds.setHeight(y - 1);
536 endOfBoundsReached =
true;
540 y += 1.1 * line.height();
543 tl.draw(p, QPointF(px, py), QList<QTextLayout::FormatRange>(), bounds);
545 if (endOfBoundsReached)
549 brect.translate(m_coord);
550 if (bounds.height() > 0)
551 brect.setBottom(qMin(brect.bottom(), bounds.bottom()));
552 *boundingRect = brect;
558void QSvgText::addText(QStringView text)
560 m_tspans.append(
new QSvgTspan(
this,
false));
561 m_tspans.back()->setWhitespaceMode(m_mode);
562 m_tspans.back()->addText(text);
573 if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing))
576 Q_ASSERT(states.nestedUseCount == 0 || states.nestedUseLevel > 0);
577 if (states.nestedUseLevel > 3 && states.nestedUseCount > (256 + states.nestedUseLevel * 2)) {
582 QScopedValueRollback<
bool> inUseGuard(states.inUse,
true);
584 if (!m_start.isNull()) {
585 p->translate(m_start);
587 if (states.nestedUseLevel > 0)
588 ++states.nestedUseCount;
590 QScopedValueRollback<
int> useLevelGuard(states.nestedUseLevel, states.nestedUseLevel + 1);
591 QScopedValueRollback<
bool> recursingGuard(m_recursing,
true);
592 m_link->draw(p, states);
594 if (states.nestedUseLevel == 0)
595 states.nestedUseCount = 0;
597 if (!m_start.isNull()) {
598 p->translate(-m_start);
602QSvgNode::Type QSvgDummyNode::type()
const
604 return FeUnsupported;
607QSvgNode::Type QSvgCircle::type()
const
612QSvgNode::Type QSvgEllipse::type()
const
617QSvgNode::Type QSvgImage::type()
const
622QSvgNode::Type QSvgLine::type()
const
627QSvgNode::Type QSvgPath::type()
const
632QSvgNode::Type QSvgPolygon::type()
const
637QSvgNode::Type QSvgPolyline::type()
const
642QSvgNode::Type QSvgRect::type()
const
647QSvgNode::Type QSvgText::type()
const
665 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
666 QScopedValueRollback<
bool> guard(m_recursing,
true);
667 p->translate(m_start);
668 bounds = m_link->bounds(p, states);
669 p->translate(-m_start);
677 if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) {
678 QScopedValueRollback<
bool> guard(m_recursing,
true);
679 p->translate(m_start);
680 bounds = m_link->decoratedBounds(p, states);
681 p->translate(-m_start);
686QRectF QSvgPolyline::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
688 return p->transform().mapRect(m_poly.boundingRect());
691QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &s)
const
693 return internalBounds(p, s, BoundsMode::Simplistic);
696QRectF QSvgPolyline::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
698 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
699 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
700 return filterRegion(rect);
703bool QSvgPolyline::requiresGroupRendering()
const
705 return hasAnyMarker();
708QRectF QSvgPolyline::internalBounds(QPainter *p, QSvgExtraStates &, BoundsMode mode)
const
710 qreal sw = strokeWidth(p);
711 if (qFuzzyIsNull(sw)) {
712 return p->transform().map(m_poly).boundingRect();
715 path.addPolygon(m_poly);
716 return boundsOnStroke(p, path, sw, mode);
720QRectF QSvgImage::internalBounds(QPainter *p, QSvgExtraStates &)
const
722 return p->transform().mapRect(m_bounds);
725QRectF QSvgLine::internalFastBounds(QPainter *p, QSvgExtraStates &)
const
727 QPointF p1 = p->transform().map(m_line.p1());
728 QPointF p2 = p->transform().map(m_line.p2());
729 qreal minX = qMin(p1.x(), p2.x());
730 qreal minY = qMin(p1.y(), p2.y());
731 qreal maxX = qMax(p1.x(), p2.x());
732 qreal maxY = qMax(p1.y(), p2.y());
733 return QRectF(minX, minY, maxX - minX, maxY - minY);
736QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s)
const
738 return internalBounds(p, s, BoundsMode::Simplistic);
741QRectF QSvgLine::decoratedInternalBounds(QPainter *p, QSvgExtraStates &s)
const
743 QRectF rect = internalBounds(p, s, BoundsMode::IncludeMiterLimit);
744 rect |= QSvgMarker::markersBoundsForNode(
this, p, s);
745 return filterRegion(rect);
748bool QSvgLine::requiresGroupRendering()
const
750 return hasAnyMarker();
753QRectF QSvgLine::internalBounds(QPainter *p, QSvgExtraStates &s, BoundsMode mode)
const
755 qreal sw = strokeWidth(p);
756 if (qFuzzyIsNull(sw)) {
757 return internalFastBounds(p, s);
760 path.moveTo(m_line.p1());
761 path.lineTo(m_line.p2());
762 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