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