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