source: trunk/src/svg/qsvggraphics.cpp@ 13

Last change on this file since 13 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 15.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information (qt-info@nokia.com)
5**
6** This file is part of the QtSvg module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
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.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at qt-sales@nokia.com.
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 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); \
66 command; \
67 p->setOpacity(oldOpacity); \
68 p->setPen(oldPen); \
69 p->setBrush(Qt::NoBrush); \
70 command; \
71 p->setBrush(oldBrush); \
72 revertStyle(p, states);
73
74
75void QSvgAnimation::draw(QPainter *, QSvgExtraStates &)
76{
77 qWarning("<animation> no implemented");
78}
79
80static inline QRectF boundsOnStroke(const QPainterPath &path, qreal width)
81{
82 QPainterPathStroker stroker;
83 stroker.setWidth(width);
84 QPainterPath stroke = stroker.createStroke(path);
85 return stroke.boundingRect();
86}
87
88QSvgCircle::QSvgCircle(QSvgNode *parent, const QRectF &rect)
89 : QSvgNode(parent), m_bounds(rect)
90{
91}
92
93
94QRectF QSvgCircle::bounds() const
95{
96 qreal sw = strokeWidth();
97 if (qFuzzyCompare(sw + 1, 1))
98 return m_bounds;
99 else {
100 QPainterPath path;
101 path.addRect(m_bounds);
102 return boundsOnStroke(path, sw);
103 }
104}
105
106void QSvgCircle::draw(QPainter *p, QSvgExtraStates &states)
107{
108 QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds));
109}
110
111QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path)
112 : QSvgNode(parent), cubic(path)
113{
114 m_cachedBounds = path.boundingRect();
115}
116
117void QSvgArc::draw(QPainter *p, QSvgExtraStates &states)
118{
119 applyStyle(p, states);
120 p->drawPath(cubic);
121 revertStyle(p, states);
122}
123
124QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect)
125 : QSvgNode(parent), m_bounds(rect)
126{
127}
128
129QRectF QSvgEllipse::bounds() const
130{
131 qreal sw = strokeWidth();
132 if (qFuzzyCompare(sw + 1, 1))
133 return m_bounds;
134 else {
135 QPainterPath path;
136 path.addEllipse(m_bounds);
137 return boundsOnStroke(path, sw);
138 }
139}
140
141void QSvgEllipse::draw(QPainter *p, QSvgExtraStates &states)
142{
143 QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds));
144}
145
146QSvgImage::QSvgImage(QSvgNode *parent, const QImage &image,
147 const QRect &bounds)
148 : QSvgNode(parent), m_image(image),
149 m_bounds(bounds)
150{
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());
155}
156
157void QSvgImage::draw(QPainter *p, QSvgExtraStates &states)
158{
159 applyStyle(p, states);
160 p->drawImage(m_bounds, m_image);
161 revertStyle(p, states);
162}
163
164
165QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line)
166 : QSvgNode(parent), m_bounds(line)
167{
168}
169
170
171void QSvgLine::draw(QPainter *p, QSvgExtraStates &states)
172{
173 applyStyle(p, states);
174 p->drawLine(m_bounds);
175 revertStyle(p, states);
176}
177
178QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath)
179 : QSvgNode(parent), m_path(qpath)
180{
181 //m_cachedBounds = m_path.controlPointRect();
182 m_cachedBounds = m_path.boundingRect();
183}
184
185void QSvgPath::draw(QPainter *p, QSvgExtraStates &states)
186{
187 QT_SVG_DRAW_SHAPE(p->drawPath(m_path));
188}
189
190QRectF QSvgPath::bounds() const
191{
192 qreal sw = strokeWidth();
193 if (qFuzzyCompare(sw + 1, 1))
194 return m_cachedBounds;
195 else {
196 return boundsOnStroke(m_path, sw);
197 }
198}
199
200QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly)
201 : QSvgNode(parent), m_poly(poly)
202{
203
204}
205
206QRectF QSvgPolygon::bounds() const
207{
208 qreal sw = strokeWidth();
209 if (qFuzzyCompare(sw + 1, 1))
210 return m_poly.boundingRect();
211 else {
212 QPainterPath path;
213 path.addPolygon(m_poly);
214 return boundsOnStroke(path, sw);
215 }
216}
217
218void QSvgPolygon::draw(QPainter *p, QSvgExtraStates &states)
219{
220 QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly));
221}
222
223
224QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly)
225 : QSvgNode(parent), m_poly(poly)
226{
227
228}
229
230void QSvgPolyline::draw(QPainter *p, QSvgExtraStates &states)
231{
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);
237 p->setPen(save);
238 }
239 p->drawPolyline(m_poly);
240 revertStyle(p, states);
241}
242
243QSvgRect::QSvgRect(QSvgNode *node, const QRectF &rect, int rx, int ry)
244 : QSvgNode(node),
245 m_rect(rect), m_rx(rx), m_ry(ry)
246{
247}
248
249QRectF QSvgRect::bounds() const
250{
251 qreal sw = strokeWidth();
252 if (qFuzzyCompare(sw + 1, 1))
253 return m_rect;
254 else {
255 QPainterPath path;
256 path.addRect(m_rect);
257 return boundsOnStroke(path, sw);
258 }
259}
260
261void QSvgRect::draw(QPainter *p, QSvgExtraStates &states)
262{
263 if (m_rx || m_ry) {
264 QT_SVG_DRAW_SHAPE(p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize));
265 } else {
266 QT_SVG_DRAW_SHAPE(p->drawRect(m_rect));
267 }
268}
269
270QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord)
271 : QSvgNode(parent)
272 , m_coord(coord)
273 , m_textAlignment(Qt::AlignLeft)
274 , m_scale(1)
275 , m_appendSpace(false)
276 , m_type(TEXT)
277 , m_size(0, 0)
278{
279 m_paragraphs.push_back(QString());
280 m_formatRanges.push_back(QList<QTextLayout::FormatRange>());
281}
282
283QSvgText::~QSvgText()
284{
285}
286
287void QSvgText::setTextArea(const QSizeF &size)
288{
289 m_size = size;
290 m_type = TEXTAREA;
291}
292
293//QRectF QSvgText::bounds() const {}
294
295void QSvgText::draw(QPainter *p, QSvgExtraStates &states)
296{
297 applyStyle(p, states);
298
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]);
307 }
308 fontStyle->svgFont()->draw(p, m_coord, text, fontStyle->pointSize(), m_textAlignment);
309 revertStyle(p, states);
310 return;
311 }
312
313 // Scale the font to its correct size.
314 QTransform oldTransform = p->worldTransform();
315 p->scale(1 / m_scale, 1 / m_scale);
316
317 qreal y = 0;
318 bool initial = true;
319 qreal px = m_coord.x() * m_scale;
320 qreal py = m_coord.y() * m_scale;
321 QSizeF scaledSize = m_size * m_scale;
322
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();
328 }
329
330 QRectF bounds;
331 if (m_size.height() != 0)
332 bounds = QRectF(0, 0, 1, scaledSize.height());
333
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]);
340 tl.beginLayout();
341 forever {
342 QTextLine line = tl.createLine();
343 if (!line.isValid())
344 break;
345
346 if (m_size.width() != 0)
347 line.setLineWidth(scaledSize.width());
348 }
349 tl.endLayout();
350
351 bool endOfBoundsReached = false;
352 for (int i = 0; i < tl.lineCount(); ++i) {
353 QTextLine line = tl.lineAt(i);
354
355 qreal x = 0;
356 if (m_textAlignment == Qt::AlignHCenter)
357 x -= line.naturalTextWidth() / 2;
358 else if (m_textAlignment == Qt::AlignRight)
359 x -= line.naturalTextWidth();
360
361 if (initial && m_type == TEXT)
362 y -= line.ascent();
363 initial = false;
364
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())) {
368 bounds.setHeight(y);
369 endOfBoundsReached = true;
370 break;
371 }
372
373 y += 1.1 * line.height();
374 }
375 tl.draw(p, QPointF(px, py), QVector<QTextLayout::FormatRange>(), bounds);
376
377 if (endOfBoundsReached)
378 break;
379 }
380
381 p->setWorldTransform(oldTransform, false);
382 revertStyle(p, states);
383}
384
385void QSvgText::insertText(const QString &text, WhitespaceMode mode)
386{
387 bool isTSpan = (m_formats.count() == 2);
388 QString newText(text);
389 newText.replace(QLatin1Char('\t'), QLatin1Char(' '));
390 newText.replace(QLatin1Char('\n'), QLatin1Char(' '));
391
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(' '));
395
396 bool appendSpaceNext = (!isTSpan && (mode == Default) && newText.endsWith(QLatin1Char(' ')));
397
398 if (mode == Default) {
399 newText = newText.simplified();
400 if (newText.isEmpty())
401 appendSpaceNext = false;
402 }
403
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();
409 if (m_appendSpace) {
410 Q_ASSERT(!m_formatRanges.back().isEmpty());
411 ++m_formatRanges.back().back().length;
412 } else if (prependSpace) {
413 --range.start;
414 ++range.length;
415 }
416 m_formatRanges.back().append(range);
417 }
418
419 m_appendSpace = appendSpaceNext;
420 m_paragraphs.back() += newText;
421}
422
423void QSvgText::insertFormat(const QTextCharFormat &format)
424{
425 QTextCharFormat mergedFormat = format;
426 if (!m_formats.isEmpty()) {
427 mergedFormat = m_formats.top();
428 mergedFormat.merge(format);
429 }
430 m_formats.push(mergedFormat);
431}
432
433void QSvgText::insertLineBreak()
434{
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>());
441 }
442}
443
444void QSvgText::popFormat()
445{
446 if (m_formats.count() > 1)
447 m_formats.pop();
448}
449
450qreal QSvgText::scale() const
451{
452 return m_scale;
453}
454
455void QSvgText::setScale(qreal scale)
456{
457 m_scale = scale;
458}
459
460const QTextCharFormat &QSvgText::topFormat() const
461{
462 return m_formats.top();
463}
464
465void QSvgText::setTextAlignment(const Qt::Alignment &alignment)
466{
467 m_textAlignment = alignment;
468}
469
470QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
471 : QSvgNode(parent), m_link(node), m_start(start)
472{
473
474}
475
476void QSvgUse::draw(QPainter *p, QSvgExtraStates &states)
477{
478 applyStyle(p, states);
479
480 if (!m_start.isNull()) {
481 p->translate(m_start);
482 }
483 m_link->draw(p, states);
484 if (!m_start.isNull()) {
485 p->translate(-m_start);
486 }
487
488 revertStyle(p, states);
489}
490
491void QSvgVideo::draw(QPainter *p, QSvgExtraStates &states)
492{
493 applyStyle(p, states);
494
495 revertStyle(p, states);
496}
497
498QSvgNode::Type QSvgAnimation::type() const
499{
500 return ANIMATION;
501}
502
503QSvgNode::Type QSvgArc::type() const
504{
505 return ARC;
506}
507
508QSvgNode::Type QSvgCircle::type() const
509{
510 return CIRCLE;
511}
512
513QSvgNode::Type QSvgEllipse::type() const
514{
515 return ELLIPSE;
516}
517
518QSvgNode::Type QSvgImage::type() const
519{
520 return IMAGE;
521}
522
523QSvgNode::Type QSvgLine::type() const
524{
525 return LINE;
526}
527
528QSvgNode::Type QSvgPath::type() const
529{
530 return PATH;
531}
532
533QSvgNode::Type QSvgPolygon::type() const
534{
535 return POLYGON;
536}
537
538QSvgNode::Type QSvgPolyline::type() const
539{
540 return POLYLINE;
541}
542
543QSvgNode::Type QSvgRect::type() const
544{
545 return RECT;
546}
547
548QSvgNode::Type QSvgText::type() const
549{
550 return m_type;
551}
552
553QSvgNode::Type QSvgUse::type() const
554{
555 return USE;
556}
557
558QSvgNode::Type QSvgVideo::type() const
559{
560 return VIDEO;
561}
562
563QRectF QSvgUse::bounds() const
564{
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(),
569 m_bounds.width(),
570 m_bounds.height());
571
572 return m_bounds;
573 }
574 return m_bounds;
575}
576
577QRectF QSvgUse::transformedBounds(const QTransform &transform) const
578{
579 QRectF bounds;
580 QTransform t = transform;
581
582 if (m_link) {
583 QSvgTransformStyle *transStyle = m_style.transform;
584 if (transStyle) {
585 t = transStyle->qtransform() * t;
586 }
587 t.translate(m_start.x(), m_start.y());
588
589 bounds = m_link->transformedBounds(t);
590
591 return bounds;
592 }
593 return bounds;
594}
595
596QRectF QSvgPolyline::bounds() const
597{
598 qreal sw = strokeWidth();
599 if (qFuzzyCompare(sw + 1, 1))
600 return m_poly.boundingRect();
601 else {
602 QPainterPath path;
603 path.addPolygon(m_poly);
604 return boundsOnStroke(path, sw);
605 }
606}
607
608QRectF QSvgArc::bounds() const
609{
610 qreal sw = strokeWidth();
611 if (qFuzzyCompare(sw + 1, 1))
612 return m_cachedBounds;
613 else {
614 return boundsOnStroke(cubic, sw);
615 }
616}
617
618QRectF QSvgImage::bounds() const
619{
620 return m_bounds;
621}
622
623QRectF QSvgLine::bounds() const
624{
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);
632 } else {
633 QPainterPath path;
634 path.moveTo(m_bounds.x1(), m_bounds.y1());
635 path.lineTo(m_bounds.x2(), m_bounds.y2());
636 return boundsOnStroke(path, sw);
637 }
638}
639
640QT_END_NAMESPACE
641
642#endif // QT_NO_SVG
Note: See TracBrowser for help on using the repository browser.