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
55QT_BEGIN_NAMESPACE
56
57Q_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
76void QSvgAnimation::draw(QPainter *, QSvgExtraStates &)
77{
78 qWarning(msg: "<animation> no implemented");
79}
80
81static 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
89QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect)
90 : QSvgNode(parent), m_bounds(rect)
91{
92}
93
94
95QRectF QSvgEllipse::bounds(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
103void QSvgEllipse::draw(QPainter *p, QSvgExtraStates &states)
104{
105 applyStyle(p, states);
106 QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds));
107 revertStyle(p, states);
108}
109
110QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path)
111 : QSvgNode(parent), m_path(path)
112{
113}
114
115void QSvgArc::draw(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
127QSvgImage::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
138void QSvgImage::draw(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
146QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line)
147 : QSvgNode(parent), m_line(line)
148{
149}
150
151
152void QSvgLine::draw(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
164QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath)
165 : QSvgNode(parent), m_path(qpath)
166{
167}
168
169void QSvgPath::draw(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
177QRectF QSvgPath::bounds(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
184QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly)
185 : QSvgNode(parent), m_poly(poly)
186{
187}
188
189QRectF QSvgPolygon::bounds(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
201void QSvgPolygon::draw(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
209QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly)
210 : QSvgNode(parent), m_poly(poly)
211{
212
213}
214
215void QSvgPolyline::draw(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
234QSvgRect::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
240QRectF QSvgRect::bounds(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
252void QSvgRect::draw(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
263QSvgTspan * const QSvgText::LINEBREAK = 0;
264
265QSvgText::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
274QSvgText::~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
282void 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
290void QSvgText::draw(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
457void 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
464QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
465 : QSvgNode(parent), m_link(node), m_start(start), m_recursing(false)
466{
467
468}
469
470void QSvgUse::draw(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
503void QSvgVideo::draw(QPainter *p, QSvgExtraStates &states)
504{
505 applyStyle(p, states);
506
507 revertStyle(p, states);
508}
509
510QSvgNode::Type QSvgAnimation::type() const
511{
512 return ANIMATION;
513}
514
515QSvgNode::Type QSvgArc::type() const
516{
517 return ARC;
518}
519
520QSvgNode::Type QSvgCircle::type() const
521{
522 return CIRCLE;
523}
524
525QSvgNode::Type QSvgEllipse::type() const
526{
527 return ELLIPSE;
528}
529
530QSvgNode::Type QSvgImage::type() const
531{
532 return IMAGE;
533}
534
535QSvgNode::Type QSvgLine::type() const
536{
537 return LINE;
538}
539
540QSvgNode::Type QSvgPath::type() const
541{
542 return PATH;
543}
544
545QSvgNode::Type QSvgPolygon::type() const
546{
547 return POLYGON;
548}
549
550QSvgNode::Type QSvgPolyline::type() const
551{
552 return POLYLINE;
553}
554
555QSvgNode::Type QSvgRect::type() const
556{
557 return RECT;
558}
559
560QSvgNode::Type QSvgText::type() const
561{
562 return m_type;
563}
564
565QSvgNode::Type QSvgUse::type() const
566{
567 return USE;
568}
569
570QSvgNode::Type QSvgVideo::type() const
571{
572 return VIDEO;
573}
574
575QRectF QSvgUse::bounds(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
587QRectF QSvgPolyline::bounds(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
599QRectF QSvgArc::bounds(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
606QRectF QSvgImage::bounds(QPainter *p, QSvgExtraStates &) const
607{
608 return p->transform().mapRect(m_bounds);
609}
610
611QRectF QSvgLine::bounds(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
630QT_END_NAMESPACE
631

source code of qtsvg/src/svg/qsvggraphics.cpp