1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt SVG module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qsvggraphics_p.h" |
41 | |
42 | #include "qsvgfont_p.h" |
43 | |
44 | #include <qabstracttextdocumentlayout.h> |
45 | #include <qdebug.h> |
46 | #include <qloggingcategory.h> |
47 | #include <qpainter.h> |
48 | #include <qscopedvaluerollback.h> |
49 | #include <qtextcursor.h> |
50 | #include <qtextdocument.h> |
51 | |
52 | #include <math.h> |
53 | #include <limits.h> |
54 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | Q_LOGGING_CATEGORY(lcSvgDraw, "qt.svg.draw" ) |
58 | |
59 | #define QT_SVG_DRAW_SHAPE(command) \ |
60 | qreal oldOpacity = p->opacity(); \ |
61 | QBrush oldBrush = p->brush(); \ |
62 | QPen oldPen = p->pen(); \ |
63 | p->setPen(Qt::NoPen); \ |
64 | p->setOpacity(oldOpacity * states.fillOpacity); \ |
65 | command; \ |
66 | p->setPen(oldPen); \ |
67 | if (oldPen != Qt::NoPen && oldPen.brush() != Qt::NoBrush && oldPen.widthF() != 0) { \ |
68 | p->setOpacity(oldOpacity * states.strokeOpacity); \ |
69 | p->setBrush(Qt::NoBrush); \ |
70 | command; \ |
71 | p->setBrush(oldBrush); \ |
72 | } \ |
73 | p->setOpacity(oldOpacity); |
74 | |
75 | |
76 | void QSvgAnimation::(QPainter *, QSvgExtraStates &) |
77 | { |
78 | qWarning(msg: "<animation> no implemented" ); |
79 | } |
80 | |
81 | static inline QRectF boundsOnStroke(QPainter *p, const QPainterPath &path, qreal width) |
82 | { |
83 | QPainterPathStroker stroker; |
84 | stroker.setWidth(width); |
85 | QPainterPath stroke = stroker.createStroke(path); |
86 | return p->transform().map(p: stroke).boundingRect(); |
87 | } |
88 | |
89 | QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect) |
90 | : QSvgNode(parent), m_bounds(rect) |
91 | { |
92 | } |
93 | |
94 | |
95 | QRectF QSvgEllipse::(QPainter *p, QSvgExtraStates &) const |
96 | { |
97 | QPainterPath path; |
98 | path.addEllipse(rect: m_bounds); |
99 | qreal sw = strokeWidth(p); |
100 | return qFuzzyIsNull(d: sw) ? p->transform().map(p: path).boundingRect() : boundsOnStroke(p, path, width: sw); |
101 | } |
102 | |
103 | void QSvgEllipse::(QPainter *p, QSvgExtraStates &states) |
104 | { |
105 | applyStyle(p, states); |
106 | QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds)); |
107 | revertStyle(p, states); |
108 | } |
109 | |
110 | QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path) |
111 | : QSvgNode(parent), m_path(path) |
112 | { |
113 | } |
114 | |
115 | void QSvgArc::(QPainter *p, QSvgExtraStates &states) |
116 | { |
117 | applyStyle(p, states); |
118 | if (p->pen().widthF() != 0) { |
119 | qreal oldOpacity = p->opacity(); |
120 | p->setOpacity(oldOpacity * states.strokeOpacity); |
121 | p->drawPath(path: m_path); |
122 | p->setOpacity(oldOpacity); |
123 | } |
124 | revertStyle(p, states); |
125 | } |
126 | |
127 | QSvgImage::QSvgImage(QSvgNode *parent, const QImage &image, |
128 | const QRectF &bounds) |
129 | : QSvgNode(parent), m_image(image), |
130 | m_bounds(bounds) |
131 | { |
132 | if (m_bounds.width() == 0.0) |
133 | m_bounds.setWidth(static_cast<qreal>(m_image.width())); |
134 | if (m_bounds.height() == 0.0) |
135 | m_bounds.setHeight(static_cast<qreal>(m_image.height())); |
136 | } |
137 | |
138 | void QSvgImage::(QPainter *p, QSvgExtraStates &states) |
139 | { |
140 | applyStyle(p, states); |
141 | p->drawImage(r: m_bounds, image: m_image); |
142 | revertStyle(p, states); |
143 | } |
144 | |
145 | |
146 | QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line) |
147 | : QSvgNode(parent), m_line(line) |
148 | { |
149 | } |
150 | |
151 | |
152 | void QSvgLine::(QPainter *p, QSvgExtraStates &states) |
153 | { |
154 | applyStyle(p, states); |
155 | if (p->pen().widthF() != 0) { |
156 | qreal oldOpacity = p->opacity(); |
157 | p->setOpacity(oldOpacity * states.strokeOpacity); |
158 | p->drawLine(l: m_line); |
159 | p->setOpacity(oldOpacity); |
160 | } |
161 | revertStyle(p, states); |
162 | } |
163 | |
164 | QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath) |
165 | : QSvgNode(parent), m_path(qpath) |
166 | { |
167 | } |
168 | |
169 | void QSvgPath::(QPainter *p, QSvgExtraStates &states) |
170 | { |
171 | applyStyle(p, states); |
172 | m_path.setFillRule(states.fillRule); |
173 | QT_SVG_DRAW_SHAPE(p->drawPath(m_path)); |
174 | revertStyle(p, states); |
175 | } |
176 | |
177 | QRectF QSvgPath::(QPainter *p, QSvgExtraStates &) const |
178 | { |
179 | qreal sw = strokeWidth(p); |
180 | return qFuzzyIsNull(d: sw) ? p->transform().map(p: m_path).boundingRect() |
181 | : boundsOnStroke(p, path: m_path, width: sw); |
182 | } |
183 | |
184 | QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly) |
185 | : QSvgNode(parent), m_poly(poly) |
186 | { |
187 | } |
188 | |
189 | QRectF QSvgPolygon::(QPainter *p, QSvgExtraStates &) const |
190 | { |
191 | qreal sw = strokeWidth(p); |
192 | if (qFuzzyIsNull(d: sw)) { |
193 | return p->transform().map(a: m_poly).boundingRect(); |
194 | } else { |
195 | QPainterPath path; |
196 | path.addPolygon(polygon: m_poly); |
197 | return boundsOnStroke(p, path, width: sw); |
198 | } |
199 | } |
200 | |
201 | void QSvgPolygon::(QPainter *p, QSvgExtraStates &states) |
202 | { |
203 | applyStyle(p, states); |
204 | QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly, states.fillRule)); |
205 | revertStyle(p, states); |
206 | } |
207 | |
208 | |
209 | QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly) |
210 | : QSvgNode(parent), m_poly(poly) |
211 | { |
212 | |
213 | } |
214 | |
215 | void QSvgPolyline::(QPainter *p, QSvgExtraStates &states) |
216 | { |
217 | applyStyle(p, states); |
218 | qreal oldOpacity = p->opacity(); |
219 | if (p->brush().style() != Qt::NoBrush) { |
220 | QPen save = p->pen(); |
221 | p->setPen(QPen(Qt::NoPen)); |
222 | p->setOpacity(oldOpacity * states.fillOpacity); |
223 | p->drawPolygon(polygon: m_poly, fillRule: states.fillRule); |
224 | p->setPen(save); |
225 | } |
226 | if (p->pen().widthF() != 0) { |
227 | p->setOpacity(oldOpacity * states.strokeOpacity); |
228 | p->drawPolyline(polyline: m_poly); |
229 | } |
230 | p->setOpacity(oldOpacity); |
231 | revertStyle(p, states); |
232 | } |
233 | |
234 | QSvgRect::QSvgRect(QSvgNode *node, const QRectF &rect, int rx, int ry) |
235 | : QSvgNode(node), |
236 | m_rect(rect), m_rx(rx), m_ry(ry) |
237 | { |
238 | } |
239 | |
240 | QRectF QSvgRect::(QPainter *p, QSvgExtraStates &) const |
241 | { |
242 | qreal sw = strokeWidth(p); |
243 | if (qFuzzyIsNull(d: sw)) { |
244 | return p->transform().mapRect(m_rect); |
245 | } else { |
246 | QPainterPath path; |
247 | path.addRect(rect: m_rect); |
248 | return boundsOnStroke(p, path, width: sw); |
249 | } |
250 | } |
251 | |
252 | void QSvgRect::(QPainter *p, QSvgExtraStates &states) |
253 | { |
254 | applyStyle(p, states); |
255 | if (m_rx || m_ry) { |
256 | QT_SVG_DRAW_SHAPE(p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize)); |
257 | } else { |
258 | QT_SVG_DRAW_SHAPE(p->drawRect(m_rect)); |
259 | } |
260 | revertStyle(p, states); |
261 | } |
262 | |
263 | QSvgTspan * const QSvgText::LINEBREAK = 0; |
264 | |
265 | QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord) |
266 | : QSvgNode(parent) |
267 | , m_coord(coord) |
268 | , m_type(TEXT) |
269 | , m_size(0, 0) |
270 | , m_mode(Default) |
271 | { |
272 | } |
273 | |
274 | QSvgText::~QSvgText() |
275 | { |
276 | for (int i = 0; i < m_tspans.size(); ++i) { |
277 | if (m_tspans[i] != LINEBREAK) |
278 | delete m_tspans[i]; |
279 | } |
280 | } |
281 | |
282 | void QSvgText::setTextArea(const QSizeF &size) |
283 | { |
284 | m_size = size; |
285 | m_type = TEXTAREA; |
286 | } |
287 | |
288 | //QRectF QSvgText::bounds(QPainter *p, QSvgExtraStates &) const {} |
289 | |
290 | void QSvgText::(QPainter *p, QSvgExtraStates &states) |
291 | { |
292 | applyStyle(p, states); |
293 | qreal oldOpacity = p->opacity(); |
294 | p->setOpacity(oldOpacity * states.fillOpacity); |
295 | |
296 | // Force the font to have a size of 100 pixels to avoid truncation problems |
297 | // when the font is very small. |
298 | qreal scale = 100.0 / p->font().pointSizeF(); |
299 | Qt::Alignment alignment = states.textAnchor; |
300 | |
301 | QTransform oldTransform = p->worldTransform(); |
302 | p->scale(sx: 1 / scale, sy: 1 / scale); |
303 | |
304 | qreal y = 0; |
305 | bool initial = true; |
306 | qreal px = m_coord.x() * scale; |
307 | qreal py = m_coord.y() * scale; |
308 | QSizeF scaledSize = m_size * scale; |
309 | |
310 | if (m_type == TEXTAREA) { |
311 | if (alignment == Qt::AlignHCenter) |
312 | px += scaledSize.width() / 2; |
313 | else if (alignment == Qt::AlignRight) |
314 | px += scaledSize.width(); |
315 | } |
316 | |
317 | QRectF bounds; |
318 | if (m_size.height() != 0) |
319 | bounds = QRectF(0, py, 1, scaledSize.height()); // x and width are not used. |
320 | |
321 | bool appendSpace = false; |
322 | QVector<QString> paragraphs; |
323 | QVector<QVector<QTextLayout::FormatRange> > formatRanges(1); |
324 | paragraphs.push_back(t: QString()); |
325 | |
326 | for (int i = 0; i < m_tspans.size(); ++i) { |
327 | if (m_tspans[i] == LINEBREAK) { |
328 | if (m_type == TEXTAREA) { |
329 | if (paragraphs.back().isEmpty()) { |
330 | QFont font = p->font(); |
331 | font.setPixelSize(font.pointSizeF() * scale); |
332 | |
333 | QTextLayout::FormatRange range; |
334 | range.start = 0; |
335 | range.length = 1; |
336 | range.format.setFont(font); |
337 | formatRanges.back().append(t: range); |
338 | |
339 | paragraphs.back().append(c: QLatin1Char(' '));; |
340 | } |
341 | appendSpace = false; |
342 | paragraphs.push_back(t: QString()); |
343 | formatRanges.resize(asize: formatRanges.size() + 1); |
344 | } |
345 | } else { |
346 | WhitespaceMode mode = m_tspans[i]->whitespaceMode(); |
347 | m_tspans[i]->applyStyle(p, states); |
348 | |
349 | QFont font = p->font(); |
350 | font.setPixelSize(font.pointSizeF() * scale); |
351 | |
352 | QString newText(m_tspans[i]->text()); |
353 | newText.replace(before: QLatin1Char('\t'), after: QLatin1Char(' ')); |
354 | newText.replace(before: QLatin1Char('\n'), after: QLatin1Char(' ')); |
355 | |
356 | bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(c: QLatin1Char(' ')); |
357 | if (appendSpace || prependSpace) |
358 | paragraphs.back().append(c: QLatin1Char(' ')); |
359 | |
360 | bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(c: QLatin1Char(' '))); |
361 | |
362 | if (mode == Default) { |
363 | newText = newText.simplified(); |
364 | if (newText.isEmpty()) |
365 | appendSpaceNext = false; |
366 | } |
367 | |
368 | QTextLayout::FormatRange range; |
369 | range.start = paragraphs.back().length(); |
370 | range.length = newText.length(); |
371 | range.format.setFont(font); |
372 | range.format.setTextOutline(p->pen()); |
373 | range.format.setForeground(p->brush()); |
374 | |
375 | if (appendSpace) { |
376 | Q_ASSERT(!formatRanges.back().isEmpty()); |
377 | ++formatRanges.back().back().length; |
378 | } else if (prependSpace) { |
379 | --range.start; |
380 | ++range.length; |
381 | } |
382 | formatRanges.back().append(t: range); |
383 | |
384 | appendSpace = appendSpaceNext; |
385 | paragraphs.back() += newText; |
386 | |
387 | m_tspans[i]->revertStyle(p, states); |
388 | } |
389 | } |
390 | |
391 | if (states.svgFont) { |
392 | // SVG fonts not fully supported... |
393 | QString text = paragraphs.front(); |
394 | for (int i = 1; i < paragraphs.size(); ++i) { |
395 | text.append(c: QLatin1Char('\n')); |
396 | text.append(s: paragraphs[i]); |
397 | } |
398 | states.svgFont->draw(p, point: m_coord * scale, str: text, pixelSize: p->font().pointSizeF() * scale, alignment: states.textAnchor); |
399 | } else { |
400 | for (int i = 0; i < paragraphs.size(); ++i) { |
401 | QTextLayout tl(paragraphs[i]); |
402 | QTextOption op = tl.textOption(); |
403 | op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); |
404 | tl.setTextOption(op); |
405 | tl.setFormats(formatRanges[i]); |
406 | tl.beginLayout(); |
407 | |
408 | forever { |
409 | QTextLine line = tl.createLine(); |
410 | if (!line.isValid()) |
411 | break; |
412 | if (m_size.width() != 0) |
413 | line.setLineWidth(scaledSize.width()); |
414 | } |
415 | tl.endLayout(); |
416 | |
417 | bool endOfBoundsReached = false; |
418 | for (int i = 0; i < tl.lineCount(); ++i) { |
419 | QTextLine line = tl.lineAt(i); |
420 | |
421 | qreal x = 0; |
422 | if (alignment == Qt::AlignHCenter) |
423 | x -= 0.5 * line.naturalTextWidth(); |
424 | else if (alignment == Qt::AlignRight) |
425 | x -= line.naturalTextWidth(); |
426 | |
427 | if (initial && m_type == TEXT) |
428 | y -= line.ascent(); |
429 | initial = false; |
430 | |
431 | line.setPosition(QPointF(x, y)); |
432 | |
433 | // Check if the current line fits into the bounding rectangle. |
434 | if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width()) |
435 | || (m_size.height() != 0 && y + line.height() > scaledSize.height())) { |
436 | // I need to set the bounds height to 'y-epsilon' to avoid drawing the current |
437 | // line. Since the font is scaled to 100 units, 1 should be a safe epsilon. |
438 | bounds.setHeight(y - 1); |
439 | endOfBoundsReached = true; |
440 | break; |
441 | } |
442 | |
443 | y += 1.1 * line.height(); |
444 | } |
445 | tl.draw(p, pos: QPointF(px, py), selections: QVector<QTextLayout::FormatRange>(), clip: bounds); |
446 | |
447 | if (endOfBoundsReached) |
448 | break; |
449 | } |
450 | } |
451 | |
452 | p->setWorldTransform(matrix: oldTransform, combine: false); |
453 | p->setOpacity(oldOpacity); |
454 | revertStyle(p, states); |
455 | } |
456 | |
457 | void QSvgText::addText(const QString &text) |
458 | { |
459 | m_tspans.append(t: new QSvgTspan(this, false)); |
460 | m_tspans.back()->setWhitespaceMode(m_mode); |
461 | m_tspans.back()->addText(text); |
462 | } |
463 | |
464 | QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node) |
465 | : QSvgNode(parent), m_link(node), m_start(start), m_recursing(false) |
466 | { |
467 | |
468 | } |
469 | |
470 | void QSvgUse::(QPainter *p, QSvgExtraStates &states) |
471 | { |
472 | if (Q_UNLIKELY(!m_link || isDescendantOf(m_link) || m_recursing)) |
473 | return; |
474 | |
475 | Q_ASSERT(states.nestedUseCount == 0 || states.nestedUseLevel > 0); |
476 | if (states.nestedUseLevel > 3 && states.nestedUseCount > (256 + states.nestedUseLevel * 2)) { |
477 | qCDebug(lcSvgDraw, "Too many nested use nodes at #%s!" , qPrintable(m_linkId)); |
478 | return; |
479 | } |
480 | |
481 | applyStyle(p, states); |
482 | |
483 | if (!m_start.isNull()) { |
484 | p->translate(offset: m_start); |
485 | } |
486 | if (states.nestedUseLevel > 0) |
487 | ++states.nestedUseCount; |
488 | { |
489 | QScopedValueRollback<int> useLevelGuard(states.nestedUseLevel, states.nestedUseLevel + 1); |
490 | QScopedValueRollback<bool> recursingGuard(m_recursing, true); |
491 | m_link->draw(p, states); |
492 | } |
493 | if (states.nestedUseLevel == 0) |
494 | states.nestedUseCount = 0; |
495 | |
496 | if (!m_start.isNull()) { |
497 | p->translate(offset: -m_start); |
498 | } |
499 | |
500 | revertStyle(p, states); |
501 | } |
502 | |
503 | void QSvgVideo::(QPainter *p, QSvgExtraStates &states) |
504 | { |
505 | applyStyle(p, states); |
506 | |
507 | revertStyle(p, states); |
508 | } |
509 | |
510 | QSvgNode::Type QSvgAnimation::type() const |
511 | { |
512 | return ANIMATION; |
513 | } |
514 | |
515 | QSvgNode::Type QSvgArc::type() const |
516 | { |
517 | return ARC; |
518 | } |
519 | |
520 | QSvgNode::Type QSvgCircle::type() const |
521 | { |
522 | return CIRCLE; |
523 | } |
524 | |
525 | QSvgNode::Type QSvgEllipse::type() const |
526 | { |
527 | return ELLIPSE; |
528 | } |
529 | |
530 | QSvgNode::Type QSvgImage::type() const |
531 | { |
532 | return IMAGE; |
533 | } |
534 | |
535 | QSvgNode::Type QSvgLine::type() const |
536 | { |
537 | return LINE; |
538 | } |
539 | |
540 | QSvgNode::Type QSvgPath::type() const |
541 | { |
542 | return PATH; |
543 | } |
544 | |
545 | QSvgNode::Type QSvgPolygon::type() const |
546 | { |
547 | return POLYGON; |
548 | } |
549 | |
550 | QSvgNode::Type QSvgPolyline::type() const |
551 | { |
552 | return POLYLINE; |
553 | } |
554 | |
555 | QSvgNode::Type QSvgRect::type() const |
556 | { |
557 | return RECT; |
558 | } |
559 | |
560 | QSvgNode::Type QSvgText::type() const |
561 | { |
562 | return m_type; |
563 | } |
564 | |
565 | QSvgNode::Type QSvgUse::type() const |
566 | { |
567 | return USE; |
568 | } |
569 | |
570 | QSvgNode::Type QSvgVideo::type() const |
571 | { |
572 | return VIDEO; |
573 | } |
574 | |
575 | QRectF QSvgUse::(QPainter *p, QSvgExtraStates &states) const |
576 | { |
577 | QRectF bounds; |
578 | if (Q_LIKELY(m_link && !isDescendantOf(m_link) && !m_recursing)) { |
579 | QScopedValueRollback<bool> guard(m_recursing, true); |
580 | p->translate(offset: m_start); |
581 | bounds = m_link->transformedBounds(p, states); |
582 | p->translate(offset: -m_start); |
583 | } |
584 | return bounds; |
585 | } |
586 | |
587 | QRectF QSvgPolyline::(QPainter *p, QSvgExtraStates &) const |
588 | { |
589 | qreal sw = strokeWidth(p); |
590 | if (qFuzzyIsNull(d: sw)) { |
591 | return p->transform().map(a: m_poly).boundingRect(); |
592 | } else { |
593 | QPainterPath path; |
594 | path.addPolygon(polygon: m_poly); |
595 | return boundsOnStroke(p, path, width: sw); |
596 | } |
597 | } |
598 | |
599 | QRectF QSvgArc::(QPainter *p, QSvgExtraStates &) const |
600 | { |
601 | qreal sw = strokeWidth(p); |
602 | return qFuzzyIsNull(d: sw) ? p->transform().map(p: m_path).boundingRect() |
603 | : boundsOnStroke(p, path: m_path, width: sw); |
604 | } |
605 | |
606 | QRectF QSvgImage::(QPainter *p, QSvgExtraStates &) const |
607 | { |
608 | return p->transform().mapRect(m_bounds); |
609 | } |
610 | |
611 | QRectF QSvgLine::(QPainter *p, QSvgExtraStates &) const |
612 | { |
613 | qreal sw = strokeWidth(p); |
614 | if (qFuzzyIsNull(d: sw)) { |
615 | QPointF p1 = p->transform().map(p: m_line.p1()); |
616 | QPointF p2 = p->transform().map(p: m_line.p2()); |
617 | qreal minX = qMin(a: p1.x(), b: p2.x()); |
618 | qreal minY = qMin(a: p1.y(), b: p2.y()); |
619 | qreal maxX = qMax(a: p1.x(), b: p2.x()); |
620 | qreal maxY = qMax(a: p1.y(), b: p2.y()); |
621 | return QRectF(minX, minY, maxX - minX, maxY - minY); |
622 | } else { |
623 | QPainterPath path; |
624 | path.moveTo(p: m_line.p1()); |
625 | path.lineTo(p: m_line.p2()); |
626 | return boundsOnStroke(p, path, width: sw); |
627 | } |
628 | } |
629 | |
630 | QT_END_NAMESPACE |
631 | |