1 /****************************************************************************
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtSvg module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
11 ** Licensees holding valid Qt Commercial licenses may use this file in
12 ** accordance with the Qt Commercial License Agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and Nokia.
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** GNU General Public License Usage
29 ** Alternatively, this file may be used under the terms of the GNU
30 ** General Public License version 3.0 as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL included in the
32 ** packaging of this file. Please review the following information to
33 ** ensure the GNU General Public License version 3.0 requirements will be
34 ** met: http://www.gnu.org/copyleft/gpl.html.
36 ** If you have questions regarding the use of this file, please contact
37 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "qsvggraphics_p.h"
46 #include "qsvgfont_p.h"
49 #include "qtextdocument.h"
50 #include "qabstracttextdocumentlayout.h"
51 #include "qtextcursor.h"
59 #define QT_SVG_DRAW_SHAPE(command) \
60 applyStyle(p, states); \
61 qreal oldOpacity = p->opacity(); \
62 QBrush oldBrush = p->brush(); \
63 QPen oldPen = p->pen(); \
64 p->setPen(Qt::NoPen); \
65 p->setOpacity(oldOpacity * states.fillOpacity); \
67 p->setOpacity(oldOpacity); \
69 p->setBrush(Qt::NoBrush); \
71 p->setBrush(oldBrush); \
72 revertStyle(p, states);
75 void QSvgAnimation::draw(QPainter
*, QSvgExtraStates
&)
77 qWarning("<animation> no implemented");
80 static inline QRectF
boundsOnStroke(const QPainterPath
&path
, qreal width
)
82 QPainterPathStroker stroker
;
83 stroker
.setWidth(width
);
84 QPainterPath stroke
= stroker
.createStroke(path
);
85 return stroke
.boundingRect();
88 QSvgCircle::QSvgCircle(QSvgNode
*parent
, const QRectF
&rect
)
89 : QSvgNode(parent
), m_bounds(rect
)
94 QRectF
QSvgCircle::bounds() const
96 qreal sw
= strokeWidth();
97 if (qFuzzyCompare(sw
+ 1, 1))
101 path
.addRect(m_bounds
);
102 return boundsOnStroke(path
, sw
);
106 void QSvgCircle::draw(QPainter
*p
, QSvgExtraStates
&states
)
108 QT_SVG_DRAW_SHAPE(p
->drawEllipse(m_bounds
));
111 QSvgArc::QSvgArc(QSvgNode
*parent
, const QPainterPath
&path
)
112 : QSvgNode(parent
), cubic(path
)
114 m_cachedBounds
= path
.boundingRect();
117 void QSvgArc::draw(QPainter
*p
, QSvgExtraStates
&states
)
119 applyStyle(p
, states
);
121 revertStyle(p
, states
);
124 QSvgEllipse::QSvgEllipse(QSvgNode
*parent
, const QRectF
&rect
)
125 : QSvgNode(parent
), m_bounds(rect
)
129 QRectF
QSvgEllipse::bounds() const
131 qreal sw
= strokeWidth();
132 if (qFuzzyCompare(sw
+ 1, 1))
136 path
.addEllipse(m_bounds
);
137 return boundsOnStroke(path
, sw
);
141 void QSvgEllipse::draw(QPainter
*p
, QSvgExtraStates
&states
)
143 QT_SVG_DRAW_SHAPE(p
->drawEllipse(m_bounds
));
146 QSvgImage::QSvgImage(QSvgNode
*parent
, const QImage
&image
,
148 : QSvgNode(parent
), m_image(image
),
151 if (m_bounds
.width() == 0)
152 m_bounds
.setWidth(m_image
.width());
153 if (m_bounds
.height() == 0)
154 m_bounds
.setHeight(m_image
.height());
157 void QSvgImage::draw(QPainter
*p
, QSvgExtraStates
&states
)
159 applyStyle(p
, states
);
160 p
->drawImage(m_bounds
, m_image
);
161 revertStyle(p
, states
);
165 QSvgLine::QSvgLine(QSvgNode
*parent
, const QLineF
&line
)
166 : QSvgNode(parent
), m_bounds(line
)
171 void QSvgLine::draw(QPainter
*p
, QSvgExtraStates
&states
)
173 applyStyle(p
, states
);
174 p
->drawLine(m_bounds
);
175 revertStyle(p
, states
);
178 QSvgPath::QSvgPath(QSvgNode
*parent
, const QPainterPath
&qpath
)
179 : QSvgNode(parent
), m_path(qpath
)
181 //m_cachedBounds = m_path.controlPointRect();
182 m_cachedBounds
= m_path
.boundingRect();
185 void QSvgPath::draw(QPainter
*p
, QSvgExtraStates
&states
)
187 QT_SVG_DRAW_SHAPE(p
->drawPath(m_path
));
190 QRectF
QSvgPath::bounds() const
192 qreal sw
= strokeWidth();
193 if (qFuzzyCompare(sw
+ 1, 1))
194 return m_cachedBounds
;
196 return boundsOnStroke(m_path
, sw
);
200 QSvgPolygon::QSvgPolygon(QSvgNode
*parent
, const QPolygonF
&poly
)
201 : QSvgNode(parent
), m_poly(poly
)
206 QRectF
QSvgPolygon::bounds() const
208 qreal sw
= strokeWidth();
209 if (qFuzzyCompare(sw
+ 1, 1))
210 return m_poly
.boundingRect();
213 path
.addPolygon(m_poly
);
214 return boundsOnStroke(path
, sw
);
218 void QSvgPolygon::draw(QPainter
*p
, QSvgExtraStates
&states
)
220 QT_SVG_DRAW_SHAPE(p
->drawPolygon(m_poly
));
224 QSvgPolyline::QSvgPolyline(QSvgNode
*parent
, const QPolygonF
&poly
)
225 : QSvgNode(parent
), m_poly(poly
)
230 void QSvgPolyline::draw(QPainter
*p
, QSvgExtraStates
&states
)
232 applyStyle(p
, states
);
233 if (p
->brush().style() != Qt::NoBrush
) {
234 QPen save
= p
->pen();
235 p
->setPen(QPen(Qt::NoPen
));
236 p
->drawPolygon(m_poly
);
239 p
->drawPolyline(m_poly
);
240 revertStyle(p
, states
);
243 QSvgRect::QSvgRect(QSvgNode
*node
, const QRectF
&rect
, int rx
, int ry
)
245 m_rect(rect
), m_rx(rx
), m_ry(ry
)
249 QRectF
QSvgRect::bounds() const
251 qreal sw
= strokeWidth();
252 if (qFuzzyCompare(sw
+ 1, 1))
256 path
.addRect(m_rect
);
257 return boundsOnStroke(path
, sw
);
261 void QSvgRect::draw(QPainter
*p
, QSvgExtraStates
&states
)
264 QT_SVG_DRAW_SHAPE(p
->drawRoundedRect(m_rect
, m_rx
, m_ry
, Qt::RelativeSize
));
266 QT_SVG_DRAW_SHAPE(p
->drawRect(m_rect
));
270 QSvgText::QSvgText(QSvgNode
*parent
, const QPointF
&coord
)
273 , m_textAlignment(Qt::AlignLeft
)
275 , m_appendSpace(false)
279 m_paragraphs
.push_back(QString());
280 m_formatRanges
.push_back(QList
<QTextLayout::FormatRange
>());
283 QSvgText::~QSvgText()
287 void QSvgText::setTextArea(const QSizeF
&size
)
293 //QRectF QSvgText::bounds() const {}
295 void QSvgText::draw(QPainter
*p
, QSvgExtraStates
&states
)
297 applyStyle(p
, states
);
299 QSvgFontStyle
*fontStyle
= static_cast<QSvgFontStyle
*>(
300 styleProperty(QSvgStyleProperty::FONT
));
301 if (fontStyle
&& fontStyle
->svgFont()) {
302 // SVG fonts not fully supported...
303 QString text
= m_paragraphs
.front();
304 for (int i
= 1; i
< m_paragraphs
.size(); ++i
) {
305 text
.append(QLatin1Char('\n'));
306 text
.append(m_paragraphs
[i
]);
308 fontStyle
->svgFont()->draw(p
, m_coord
, text
, fontStyle
->pointSize(), m_textAlignment
);
309 revertStyle(p
, states
);
313 // Scale the font to its correct size.
314 QTransform oldTransform
= p
->worldTransform();
315 p
->scale(1 / m_scale
, 1 / m_scale
);
319 qreal px
= m_coord
.x() * m_scale
;
320 qreal py
= m_coord
.y() * m_scale
;
321 QSizeF scaledSize
= m_size
* m_scale
;
323 if (m_type
== TEXTAREA
) {
324 if (m_textAlignment
== Qt::AlignHCenter
)
325 px
+= scaledSize
.width() / 2;
326 else if (m_textAlignment
== Qt::AlignRight
)
327 px
+= scaledSize
.width();
331 if (m_size
.height() != 0)
332 bounds
= QRectF(0, 0, 1, scaledSize
.height());
334 for (int i
= 0; i
< m_paragraphs
.size(); ++i
) {
335 QTextLayout
tl(m_paragraphs
[i
]);
336 QTextOption op
= tl
.textOption();
337 op
.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere
);
338 tl
.setTextOption(op
);
339 tl
.setAdditionalFormats(m_formatRanges
[i
]);
342 QTextLine line
= tl
.createLine();
346 if (m_size
.width() != 0)
347 line
.setLineWidth(scaledSize
.width());
351 bool endOfBoundsReached
= false;
352 for (int i
= 0; i
< tl
.lineCount(); ++i
) {
353 QTextLine line
= tl
.lineAt(i
);
356 if (m_textAlignment
== Qt::AlignHCenter
)
357 x
-= line
.naturalTextWidth() / 2;
358 else if (m_textAlignment
== Qt::AlignRight
)
359 x
-= line
.naturalTextWidth();
361 if (initial
&& m_type
== TEXT
)
365 line
.setPosition(QPointF(x
, y
));
366 if ((m_size
.width() != 0 && line
.naturalTextWidth() > scaledSize
.width())
367 || (m_size
.height() != 0 && y
+ line
.height() > scaledSize
.height())) {
369 endOfBoundsReached
= true;
373 y
+= 1.1 * line
.height();
375 tl
.draw(p
, QPointF(px
, py
), QVector
<QTextLayout::FormatRange
>(), bounds
);
377 if (endOfBoundsReached
)
381 p
->setWorldTransform(oldTransform
, false);
382 revertStyle(p
, states
);
385 void QSvgText::insertText(const QString
&text
, WhitespaceMode mode
)
387 bool isTSpan
= (m_formats
.count() == 2);
388 QString
newText(text
);
389 newText
.replace(QLatin1Char('\t'), QLatin1Char(' '));
390 newText
.replace(QLatin1Char('\n'), QLatin1Char(' '));
392 bool prependSpace
= !m_appendSpace
&& !isTSpan
&& (mode
== Default
) && !m_paragraphs
.back().isEmpty() && newText
.startsWith(QLatin1Char(' '));
393 if (m_appendSpace
|| prependSpace
)
394 m_paragraphs
.back().append(QLatin1Char(' '));
396 bool appendSpaceNext
= (!isTSpan
&& (mode
== Default
) && newText
.endsWith(QLatin1Char(' ')));
398 if (mode
== Default
) {
399 newText
= newText
.simplified();
400 if (newText
.isEmpty())
401 appendSpaceNext
= false;
404 if (!m_formats
.isEmpty()) {
405 QTextLayout::FormatRange range
;
406 range
.start
= m_paragraphs
.back().length();
407 range
.length
= newText
.length();
408 range
.format
= m_formats
.top();
410 Q_ASSERT(!m_formatRanges
.back().isEmpty());
411 ++m_formatRanges
.back().back().length
;
412 } else if (prependSpace
) {
416 m_formatRanges
.back().append(range
);
419 m_appendSpace
= appendSpaceNext
;
420 m_paragraphs
.back() += newText
;
423 void QSvgText::insertFormat(const QTextCharFormat
&format
)
425 QTextCharFormat mergedFormat
= format
;
426 if (!m_formats
.isEmpty()) {
427 mergedFormat
= m_formats
.top();
428 mergedFormat
.merge(format
);
430 m_formats
.push(mergedFormat
);
433 void QSvgText::insertLineBreak()
435 if (m_type
== TEXTAREA
) {
436 if (m_paragraphs
.back().isEmpty())
437 insertText(QLatin1String(" "), Preserve
);
438 m_appendSpace
= false;
439 m_paragraphs
.push_back(QString());
440 m_formatRanges
.push_back(QList
<QTextLayout::FormatRange
>());
444 void QSvgText::popFormat()
446 if (m_formats
.count() > 1)
450 qreal
QSvgText::scale() const
455 void QSvgText::setScale(qreal scale
)
460 const QTextCharFormat
&QSvgText::topFormat() const
462 return m_formats
.top();
465 void QSvgText::setTextAlignment(const Qt::Alignment
&alignment
)
467 m_textAlignment
= alignment
;
470 QSvgUse::QSvgUse(const QPointF
&start
, QSvgNode
*parent
, QSvgNode
*node
)
471 : QSvgNode(parent
), m_link(node
), m_start(start
)
476 void QSvgUse::draw(QPainter
*p
, QSvgExtraStates
&states
)
478 applyStyle(p
, states
);
480 if (!m_start
.isNull()) {
481 p
->translate(m_start
);
483 m_link
->draw(p
, states
);
484 if (!m_start
.isNull()) {
485 p
->translate(-m_start
);
488 revertStyle(p
, states
);
491 void QSvgVideo::draw(QPainter
*p
, QSvgExtraStates
&states
)
493 applyStyle(p
, states
);
495 revertStyle(p
, states
);
498 QSvgNode::Type
QSvgAnimation::type() const
503 QSvgNode::Type
QSvgArc::type() const
508 QSvgNode::Type
QSvgCircle::type() const
513 QSvgNode::Type
QSvgEllipse::type() const
518 QSvgNode::Type
QSvgImage::type() const
523 QSvgNode::Type
QSvgLine::type() const
528 QSvgNode::Type
QSvgPath::type() const
533 QSvgNode::Type
QSvgPolygon::type() const
538 QSvgNode::Type
QSvgPolyline::type() const
543 QSvgNode::Type
QSvgRect::type() const
548 QSvgNode::Type
QSvgText::type() const
553 QSvgNode::Type
QSvgUse::type() const
558 QSvgNode::Type
QSvgVideo::type() const
563 QRectF
QSvgUse::bounds() const
565 if (m_link
&& m_bounds
.isEmpty()) {
566 m_bounds
= m_link
->bounds();
567 m_bounds
= QRectF(m_bounds
.x()+m_start
.x(),
568 m_bounds
.y()+m_start
.y(),
577 QRectF
QSvgUse::transformedBounds(const QTransform
&transform
) const
580 QTransform t
= transform
;
583 QSvgTransformStyle
*transStyle
= m_style
.transform
;
585 t
= transStyle
->qtransform() * t
;
587 t
.translate(m_start
.x(), m_start
.y());
589 bounds
= m_link
->transformedBounds(t
);
596 QRectF
QSvgPolyline::bounds() const
598 qreal sw
= strokeWidth();
599 if (qFuzzyCompare(sw
+ 1, 1))
600 return m_poly
.boundingRect();
603 path
.addPolygon(m_poly
);
604 return boundsOnStroke(path
, sw
);
608 QRectF
QSvgArc::bounds() const
610 qreal sw
= strokeWidth();
611 if (qFuzzyCompare(sw
+ 1, 1))
612 return m_cachedBounds
;
614 return boundsOnStroke(cubic
, sw
);
618 QRectF
QSvgImage::bounds() const
623 QRectF
QSvgLine::bounds() const
625 qreal sw
= strokeWidth();
626 if (qFuzzyCompare(sw
+ 1, 1)) {
627 qreal minX
= qMin(m_bounds
.x1(), m_bounds
.x2());
628 qreal minY
= qMin(m_bounds
.y1(), m_bounds
.y2());
629 qreal maxX
= qMax(m_bounds
.x1(), m_bounds
.x2());
630 qreal maxY
= qMax(m_bounds
.y1(), m_bounds
.y2());
631 return QRectF(minX
, minY
, maxX
-minX
, maxY
-minY
);
634 path
.moveTo(m_bounds
.x1(), m_bounds
.y1());
635 path
.lineTo(m_bounds
.x2(), m_bounds
.y2());
636 return boundsOnStroke(path
, sw
);