1/****************************************************************************
2**
3** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the QtSvg 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 Digia. For licensing terms and
14** conditions see http://qt.digia.com/licensing. For further information
15** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 2.1 requirements
23** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24**
25** In addition, as a special exception, Digia gives you certain additional
26** rights. These rights are described in the Digia Qt LGPL Exception
27** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28**
29** GNU General Public License Usage
30** Alternatively, this file may be used under the terms of the GNU
31** General Public License version 3.0 as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL included in the
33** packaging of this file. Please review the following information to
34** ensure the GNU General Public License version 3.0 requirements will be
35** met: http://www.gnu.org/copyleft/gpl.html.
36**
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qsvggraphics_p.h"
43
44#ifndef QT_NO_SVG
45
46#include "qsvgfont_p.h"
47
48#include "qpainter.h"
49#include "qtextdocument.h"
50#include "qabstracttextdocumentlayout.h"
51#include "qtextcursor.h"
52#include "qdebug.h"
53
54#include <math.h>
55#include <limits.h>
56
57QT_BEGIN_NAMESPACE
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.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("<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(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(m_bounds);
99 qreal sw = strokeWidth(p);
100 return qFuzzyIsNull(sw) ? p->transform().map(path).boundingRect() : boundsOnStroke(p, path, 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(m_path);
122 p->setOpacity(oldOpacity);
123 }
124 revertStyle(p, states);
125}
126
127QSvgImage::QSvgImage(QSvgNode *parent, const QImage &image,
128 const QRect &bounds)
129 : QSvgNode(parent), m_image(image),
130 m_bounds(bounds)
131{
132 if (m_bounds.width() == 0)
133 m_bounds.setWidth(m_image.width());
134 if (m_bounds.height() == 0)
135 m_bounds.setHeight(m_image.height());
136}
137
138void QSvgImage::draw(QPainter *p, QSvgExtraStates &states)
139{
140 applyStyle(p, states);
141 p->drawImage(m_bounds, 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(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(sw) ? p->transform().map(m_path).boundingRect()
181 : boundsOnStroke(p, m_path, 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(sw)) {
193 return p->transform().map(m_poly).boundingRect();
194 } else {
195 QPainterPath path;
196 path.addPolygon(m_poly);
197 return boundsOnStroke(p, path, 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(m_poly, states.fillRule);
224 p->setPen(save);
225 }
226 if (p->pen().widthF() != 0) {
227 p->setOpacity(oldOpacity * states.strokeOpacity);
228 p->drawPolyline(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(sw)) {
244 return p->transform().mapRect(m_rect);
245 } else {
246 QPainterPath path;
247 path.addRect(m_rect);
248 return boundsOnStroke(p, path, 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(1 / scale, 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 QStack<QTextCharFormat> formats;
324 QVector<QList<QTextLayout::FormatRange> > formatRanges;
325 paragraphs.push_back(QString());
326 formatRanges.push_back(QList<QTextLayout::FormatRange>());
327
328 for (int i = 0; i < m_tspans.size(); ++i) {
329 if (m_tspans[i] == LINEBREAK) {
330 if (m_type == TEXTAREA) {
331 if (paragraphs.back().isEmpty()) {
332 QFont font = p->font();
333 font.setPixelSize(font.pointSizeF() * scale);
334
335 QTextLayout::FormatRange range;
336 range.start = 0;
337 range.length = 1;
338 range.format.setFont(font);
339 formatRanges.back().append(range);
340
341 paragraphs.back().append(QLatin1Char(' '));;
342 }
343 appendSpace = false;
344 paragraphs.push_back(QString());
345 formatRanges.push_back(QList<QTextLayout::FormatRange>());
346 }
347 } else {
348 WhitespaceMode mode = m_tspans[i]->whitespaceMode();
349 m_tspans[i]->applyStyle(p, states);
350
351 QFont font = p->font();
352 font.setPixelSize(font.pointSizeF() * scale);
353
354 QString newText(m_tspans[i]->text());
355 newText.replace(QLatin1Char('\t'), QLatin1Char(' '));
356 newText.replace(QLatin1Char('\n'), QLatin1Char(' '));
357
358 bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(' '));
359 if (appendSpace || prependSpace)
360 paragraphs.back().append(QLatin1Char(' '));
361
362 bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(' ')));
363
364 if (mode == Default) {
365 newText = newText.simplified();
366 if (newText.isEmpty())
367 appendSpaceNext = false;
368 }
369
370 QTextLayout::FormatRange range;
371 range.start = paragraphs.back().length();
372 range.length = newText.length();
373 range.format.setFont(font);
374 range.format.setTextOutline(p->pen());
375 range.format.setForeground(p->brush());
376
377 if (appendSpace) {
378 Q_ASSERT(!formatRanges.back().isEmpty());
379 ++formatRanges.back().back().length;
380 } else if (prependSpace) {
381 --range.start;
382 ++range.length;
383 }
384 formatRanges.back().append(range);
385
386 appendSpace = appendSpaceNext;
387 paragraphs.back() += newText;
388
389 m_tspans[i]->revertStyle(p, states);
390 }
391 }
392
393 if (states.svgFont) {
394 // SVG fonts not fully supported...
395 QString text = paragraphs.front();
396 for (int i = 1; i < paragraphs.size(); ++i) {
397 text.append(QLatin1Char('\n'));
398 text.append(paragraphs[i]);
399 }
400 states.svgFont->draw(p, m_coord * scale, text, p->font().pointSizeF() * scale, states.textAnchor);
401 } else {
402 for (int i = 0; i < paragraphs.size(); ++i) {
403 QTextLayout tl(paragraphs[i]);
404 QTextOption op = tl.textOption();
405 op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
406 tl.setTextOption(op);
407 tl.setAdditionalFormats(formatRanges[i]);
408 tl.beginLayout();
409
410 forever {
411 QTextLine line = tl.createLine();
412 if (!line.isValid())
413 break;
414 if (m_size.width() != 0)
415 line.setLineWidth(scaledSize.width());
416 }
417 tl.endLayout();
418
419 bool endOfBoundsReached = false;
420 for (int i = 0; i < tl.lineCount(); ++i) {
421 QTextLine line = tl.lineAt(i);
422
423 qreal x = 0;
424 if (alignment == Qt::AlignHCenter)
425 x -= 0.5 * line.naturalTextWidth();
426 else if (alignment == Qt::AlignRight)
427 x -= line.naturalTextWidth();
428
429 if (initial && m_type == TEXT)
430 y -= line.ascent();
431 initial = false;
432
433 line.setPosition(QPointF(x, y));
434
435 // Check if the current line fits into the bounding rectangle.
436 if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width())
437 || (m_size.height() != 0 && y + line.height() > scaledSize.height())) {
438 // I need to set the bounds height to 'y-epsilon' to avoid drawing the current
439 // line. Since the font is scaled to 100 units, 1 should be a safe epsilon.
440 bounds.setHeight(y - 1);
441 endOfBoundsReached = true;
442 break;
443 }
444
445 y += 1.1 * line.height();
446 }
447 tl.draw(p, QPointF(px, py), QVector<QTextLayout::FormatRange>(), bounds);
448
449 if (endOfBoundsReached)
450 break;
451 }
452 }
453
454 p->setWorldTransform(oldTransform, false);
455 p->setOpacity(oldOpacity);
456 revertStyle(p, states);
457}
458
459void QSvgText::addText(const QString &text)
460{
461 m_tspans.append(new QSvgTspan(this, false));
462 m_tspans.back()->setWhitespaceMode(m_mode);
463 m_tspans.back()->addText(text);
464}
465
466QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
467 : QSvgNode(parent), m_link(node), m_start(start)
468{
469
470}
471
472void QSvgUse::draw(QPainter *p, QSvgExtraStates &states)
473{
474 applyStyle(p, states);
475
476 if (!m_start.isNull()) {
477 p->translate(m_start);
478 }
479 m_link->draw(p, states);
480 if (!m_start.isNull()) {
481 p->translate(-m_start);
482 }
483
484 revertStyle(p, states);
485}
486
487void QSvgVideo::draw(QPainter *p, QSvgExtraStates &states)
488{
489 applyStyle(p, states);
490
491 revertStyle(p, states);
492}
493
494QSvgNode::Type QSvgAnimation::type() const
495{
496 return ANIMATION;
497}
498
499QSvgNode::Type QSvgArc::type() const
500{
501 return ARC;
502}
503
504QSvgNode::Type QSvgCircle::type() const
505{
506 return CIRCLE;
507}
508
509QSvgNode::Type QSvgEllipse::type() const
510{
511 return ELLIPSE;
512}
513
514QSvgNode::Type QSvgImage::type() const
515{
516 return IMAGE;
517}
518
519QSvgNode::Type QSvgLine::type() const
520{
521 return LINE;
522}
523
524QSvgNode::Type QSvgPath::type() const
525{
526 return PATH;
527}
528
529QSvgNode::Type QSvgPolygon::type() const
530{
531 return POLYGON;
532}
533
534QSvgNode::Type QSvgPolyline::type() const
535{
536 return POLYLINE;
537}
538
539QSvgNode::Type QSvgRect::type() const
540{
541 return RECT;
542}
543
544QSvgNode::Type QSvgText::type() const
545{
546 return m_type;
547}
548
549QSvgNode::Type QSvgUse::type() const
550{
551 return USE;
552}
553
554QSvgNode::Type QSvgVideo::type() const
555{
556 return VIDEO;
557}
558
559QRectF QSvgUse::bounds(QPainter *p, QSvgExtraStates &states) const
560{
561 QRectF bounds;
562 if (m_link) {
563 p->translate(m_start);
564 bounds = m_link->transformedBounds(p, states);
565 p->translate(-m_start);
566 }
567 return bounds;
568}
569
570QRectF QSvgPolyline::bounds(QPainter *p, QSvgExtraStates &) const
571{
572 qreal sw = strokeWidth(p);
573 if (qFuzzyIsNull(sw)) {
574 return p->transform().map(m_poly).boundingRect();
575 } else {
576 QPainterPath path;
577 path.addPolygon(m_poly);
578 return boundsOnStroke(p, path, sw);
579 }
580}
581
582QRectF QSvgArc::bounds(QPainter *p, QSvgExtraStates &) const
583{
584 qreal sw = strokeWidth(p);
585 return qFuzzyIsNull(sw) ? p->transform().map(m_path).boundingRect()
586 : boundsOnStroke(p, m_path, sw);
587}
588
589QRectF QSvgImage::bounds(QPainter *p, QSvgExtraStates &) const
590{
591 return p->transform().mapRect(m_bounds);
592}
593
594QRectF QSvgLine::bounds(QPainter *p, QSvgExtraStates &) const
595{
596 qreal sw = strokeWidth(p);
597 if (qFuzzyIsNull(sw)) {
598 QPointF p1 = p->transform().map(m_line.p1());
599 QPointF p2 = p->transform().map(m_line.p2());
600 qreal minX = qMin(p1.x(), p2.x());
601 qreal minY = qMin(p1.y(), p2.y());
602 qreal maxX = qMax(p1.x(), p2.x());
603 qreal maxY = qMax(p1.y(), p2.y());
604 return QRectF(minX, minY, maxX - minX, maxY - minY);
605 } else {
606 QPainterPath path;
607 path.moveTo(m_line.p1());
608 path.lineTo(m_line.p2());
609 return boundsOnStroke(p, path, sw);
610 }
611}
612
613QT_END_NAMESPACE
614
615#endif // QT_NO_SVG
616