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

Last change on this file since 651 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 18.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtSvg module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
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.
15**
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.
23**
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.
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 have questions regarding the use of this file, please contact
37** Nokia at qt-info@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 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(const QPainterPath &path, qreal width)
82{
83 QPainterPathStroker stroker;
84 stroker.setWidth(width);
85 QPainterPath stroke = stroker.createStroke(path);
86 return stroke.boundingRect();
87}
88
89QSvgCircle::QSvgCircle(QSvgNode *parent, const QRectF &rect)
90 : QSvgNode(parent), m_bounds(rect)
91{
92}
93
94
95QRectF QSvgCircle::bounds() const
96{
97 qreal sw = strokeWidth();
98 if (qFuzzyIsNull(sw))
99 return m_bounds;
100 else {
101 QPainterPath path;
102 path.addRect(m_bounds);
103 return boundsOnStroke(path, sw);
104 }
105}
106
107void QSvgCircle::draw(QPainter *p, QSvgExtraStates &states)
108{
109 applyStyle(p, states);
110 QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds));
111 revertStyle(p, states);
112}
113
114QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path)
115 : QSvgNode(parent), cubic(path)
116{
117 m_cachedBounds = path.boundingRect();
118}
119
120void QSvgArc::draw(QPainter *p, QSvgExtraStates &states)
121{
122 applyStyle(p, states);
123 if (p->pen().widthF() != 0) {
124 qreal oldOpacity = p->opacity();
125 p->setOpacity(oldOpacity * states.strokeOpacity);
126 p->drawPath(cubic);
127 p->setOpacity(oldOpacity);
128 }
129 revertStyle(p, states);
130}
131
132QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect)
133 : QSvgNode(parent), m_bounds(rect)
134{
135}
136
137QRectF QSvgEllipse::bounds() const
138{
139 qreal sw = strokeWidth();
140 if (qFuzzyIsNull(sw))
141 return m_bounds;
142 else {
143 QPainterPath path;
144 path.addEllipse(m_bounds);
145 return boundsOnStroke(path, sw);
146 }
147}
148
149void QSvgEllipse::draw(QPainter *p, QSvgExtraStates &states)
150{
151 applyStyle(p, states);
152 QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds));
153 revertStyle(p, states);
154}
155
156QSvgImage::QSvgImage(QSvgNode *parent, const QImage &image,
157 const QRect &bounds)
158 : QSvgNode(parent), m_image(image),
159 m_bounds(bounds)
160{
161 if (m_bounds.width() == 0)
162 m_bounds.setWidth(m_image.width());
163 if (m_bounds.height() == 0)
164 m_bounds.setHeight(m_image.height());
165}
166
167void QSvgImage::draw(QPainter *p, QSvgExtraStates &states)
168{
169 applyStyle(p, states);
170 p->drawImage(m_bounds, m_image);
171 revertStyle(p, states);
172}
173
174
175QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line)
176 : QSvgNode(parent), m_bounds(line)
177{
178}
179
180
181void QSvgLine::draw(QPainter *p, QSvgExtraStates &states)
182{
183 applyStyle(p, states);
184 if (p->pen().widthF() != 0) {
185 qreal oldOpacity = p->opacity();
186 p->setOpacity(oldOpacity * states.strokeOpacity);
187 p->drawLine(m_bounds);
188 p->setOpacity(oldOpacity);
189 }
190 revertStyle(p, states);
191}
192
193QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath)
194 : QSvgNode(parent), m_path(qpath)
195{
196}
197
198void QSvgPath::draw(QPainter *p, QSvgExtraStates &states)
199{
200 applyStyle(p, states);
201 m_path.setFillRule(states.fillRule);
202 QT_SVG_DRAW_SHAPE(p->drawPath(m_path));
203 revertStyle(p, states);
204}
205
206QRectF QSvgPath::bounds() const
207{
208 qreal sw = strokeWidth();
209 if (qFuzzyIsNull(sw)) {
210 if (m_cachedBounds.isNull())
211 //m_cachedBounds = m_path.controlPointRect();
212 m_cachedBounds = m_path.boundingRect();
213
214 return m_cachedBounds;
215 }
216 else {
217 return boundsOnStroke(m_path, sw);
218 }
219}
220
221QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly)
222 : QSvgNode(parent), m_poly(poly)
223{
224}
225
226QRectF QSvgPolygon::bounds() const
227{
228 qreal sw = strokeWidth();
229 if (qFuzzyIsNull(sw))
230 return m_poly.boundingRect();
231 else {
232 QPainterPath path;
233 path.addPolygon(m_poly);
234 return boundsOnStroke(path, sw);
235 }
236}
237
238void QSvgPolygon::draw(QPainter *p, QSvgExtraStates &states)
239{
240 applyStyle(p, states);
241 QT_SVG_DRAW_SHAPE(p->drawPolygon(m_poly, states.fillRule));
242 revertStyle(p, states);
243}
244
245
246QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly)
247 : QSvgNode(parent), m_poly(poly)
248{
249
250}
251
252void QSvgPolyline::draw(QPainter *p, QSvgExtraStates &states)
253{
254 applyStyle(p, states);
255 qreal oldOpacity = p->opacity();
256 if (p->brush().style() != Qt::NoBrush) {
257 QPen save = p->pen();
258 p->setPen(QPen(Qt::NoPen));
259 p->setOpacity(oldOpacity * states.fillOpacity);
260 p->drawPolygon(m_poly, states.fillRule);
261 p->setPen(save);
262 }
263 if (p->pen().widthF() != 0) {
264 p->setOpacity(oldOpacity * states.strokeOpacity);
265 p->drawPolyline(m_poly);
266 }
267 p->setOpacity(oldOpacity);
268 revertStyle(p, states);
269}
270
271QSvgRect::QSvgRect(QSvgNode *node, const QRectF &rect, int rx, int ry)
272 : QSvgNode(node),
273 m_rect(rect), m_rx(rx), m_ry(ry)
274{
275}
276
277QRectF QSvgRect::bounds() const
278{
279 qreal sw = strokeWidth();
280 if (qFuzzyIsNull(sw))
281 return m_rect;
282 else {
283 QPainterPath path;
284 path.addRect(m_rect);
285 return boundsOnStroke(path, sw);
286 }
287}
288
289void QSvgRect::draw(QPainter *p, QSvgExtraStates &states)
290{
291 applyStyle(p, states);
292 if (m_rx || m_ry) {
293 QT_SVG_DRAW_SHAPE(p->drawRoundedRect(m_rect, m_rx, m_ry, Qt::RelativeSize));
294 } else {
295 QT_SVG_DRAW_SHAPE(p->drawRect(m_rect));
296 }
297 revertStyle(p, states);
298}
299
300QSvgTspan * const QSvgText::LINEBREAK = 0;
301
302QSvgText::QSvgText(QSvgNode *parent, const QPointF &coord)
303 : QSvgNode(parent)
304 , m_coord(coord)
305 , m_type(TEXT)
306 , m_size(0, 0)
307 , m_mode(Default)
308{
309}
310
311QSvgText::~QSvgText()
312{
313 for (int i = 0; i < m_tspans.size(); ++i) {
314 if (m_tspans[i] != LINEBREAK)
315 delete m_tspans[i];
316 }
317}
318
319void QSvgText::setTextArea(const QSizeF &size)
320{
321 m_size = size;
322 m_type = TEXTAREA;
323}
324
325//QRectF QSvgText::bounds() const {}
326
327void QSvgText::draw(QPainter *p, QSvgExtraStates &states)
328{
329 applyStyle(p, states);
330 qreal oldOpacity = p->opacity();
331 p->setOpacity(oldOpacity * states.fillOpacity);
332
333 // Force the font to have a size of 100 pixels to avoid truncation problems
334 // when the font is very small.
335 qreal scale = 100.0 / p->font().pointSizeF();
336 Qt::Alignment alignment = states.textAnchor;
337
338 QTransform oldTransform = p->worldTransform();
339 p->scale(1 / scale, 1 / scale);
340
341 qreal y = 0;
342 bool initial = true;
343 qreal px = m_coord.x() * scale;
344 qreal py = m_coord.y() * scale;
345 QSizeF scaledSize = m_size * scale;
346
347 if (m_type == TEXTAREA) {
348 if (alignment == Qt::AlignHCenter)
349 px += scaledSize.width() / 2;
350 else if (alignment == Qt::AlignRight)
351 px += scaledSize.width();
352 }
353
354 QRectF bounds;
355 if (m_size.height() != 0)
356 bounds = QRectF(0, py, 1, scaledSize.height()); // x and width are not used.
357
358 bool appendSpace = false;
359 QVector<QString> paragraphs;
360 QStack<QTextCharFormat> formats;
361 QVector<QList<QTextLayout::FormatRange> > formatRanges;
362 paragraphs.push_back(QString());
363 formatRanges.push_back(QList<QTextLayout::FormatRange>());
364
365 for (int i = 0; i < m_tspans.size(); ++i) {
366 if (m_tspans[i] == LINEBREAK) {
367 if (m_type == TEXTAREA) {
368 if (paragraphs.back().isEmpty()) {
369 QFont font = p->font();
370 font.setPixelSize(font.pointSizeF() * scale);
371
372 QTextLayout::FormatRange range;
373 range.start = 0;
374 range.length = 1;
375 range.format.setFont(font);
376 formatRanges.back().append(range);
377
378 paragraphs.back().append(QLatin1Char(' '));;
379 }
380 appendSpace = false;
381 paragraphs.push_back(QString());
382 formatRanges.push_back(QList<QTextLayout::FormatRange>());
383 }
384 } else {
385 WhitespaceMode mode = m_tspans[i]->whitespaceMode();
386 m_tspans[i]->applyStyle(p, states);
387
388 QFont font = p->font();
389 font.setPixelSize(font.pointSizeF() * scale);
390
391 QString newText(m_tspans[i]->text());
392 newText.replace(QLatin1Char('\t'), QLatin1Char(' '));
393 newText.replace(QLatin1Char('\n'), QLatin1Char(' '));
394
395 bool prependSpace = !appendSpace && !m_tspans[i]->isTspan() && (mode == Default) && !paragraphs.back().isEmpty() && newText.startsWith(QLatin1Char(' '));
396 if (appendSpace || prependSpace)
397 paragraphs.back().append(QLatin1Char(' '));
398
399 bool appendSpaceNext = (!m_tspans[i]->isTspan() && (mode == Default) && newText.endsWith(QLatin1Char(' ')));
400
401 if (mode == Default) {
402 newText = newText.simplified();
403 if (newText.isEmpty())
404 appendSpaceNext = false;
405 }
406
407 QTextLayout::FormatRange range;
408 range.start = paragraphs.back().length();
409 range.length = newText.length();
410 range.format.setFont(font);
411 range.format.setTextOutline(p->pen());
412 range.format.setForeground(p->brush());
413
414 if (appendSpace) {
415 Q_ASSERT(!formatRanges.back().isEmpty());
416 ++formatRanges.back().back().length;
417 } else if (prependSpace) {
418 --range.start;
419 ++range.length;
420 }
421 formatRanges.back().append(range);
422
423 appendSpace = appendSpaceNext;
424 paragraphs.back() += newText;
425
426 m_tspans[i]->revertStyle(p, states);
427 }
428 }
429
430 if (states.svgFont) {
431 // SVG fonts not fully supported...
432 QString text = paragraphs.front();
433 for (int i = 1; i < paragraphs.size(); ++i) {
434 text.append(QLatin1Char('\n'));
435 text.append(paragraphs[i]);
436 }
437 states.svgFont->draw(p, m_coord * scale, text, p->font().pointSizeF() * scale, states.textAnchor);
438 } else {
439 for (int i = 0; i < paragraphs.size(); ++i) {
440 QTextLayout tl(paragraphs[i]);
441 QTextOption op = tl.textOption();
442 op.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
443 tl.setTextOption(op);
444 tl.setAdditionalFormats(formatRanges[i]);
445 tl.beginLayout();
446
447 forever {
448 QTextLine line = tl.createLine();
449 if (!line.isValid())
450 break;
451 if (m_size.width() != 0)
452 line.setLineWidth(scaledSize.width());
453 }
454 tl.endLayout();
455
456 bool endOfBoundsReached = false;
457 for (int i = 0; i < tl.lineCount(); ++i) {
458 QTextLine line = tl.lineAt(i);
459
460 qreal x = 0;
461 if (alignment == Qt::AlignHCenter)
462 x -= 0.5 * line.naturalTextWidth();
463 else if (alignment == Qt::AlignRight)
464 x -= line.naturalTextWidth();
465
466 if (initial && m_type == TEXT)
467 y -= line.ascent();
468 initial = false;
469
470 line.setPosition(QPointF(x, y));
471
472 // Check if the current line fits into the bounding rectangle.
473 if ((m_size.width() != 0 && line.naturalTextWidth() > scaledSize.width())
474 || (m_size.height() != 0 && y + line.height() > scaledSize.height())) {
475 // I need to set the bounds height to 'y-epsilon' to avoid drawing the current
476 // line. Since the font is scaled to 100 units, 1 should be a safe epsilon.
477 bounds.setHeight(y - 1);
478 endOfBoundsReached = true;
479 break;
480 }
481
482 y += 1.1 * line.height();
483 }
484 tl.draw(p, QPointF(px, py), QVector<QTextLayout::FormatRange>(), bounds);
485
486 if (endOfBoundsReached)
487 break;
488 }
489 }
490
491 p->setWorldTransform(oldTransform, false);
492 p->setOpacity(oldOpacity);
493 revertStyle(p, states);
494}
495
496void QSvgText::addText(const QString &text)
497{
498 m_tspans.append(new QSvgTspan(this, false));
499 m_tspans.back()->setWhitespaceMode(m_mode);
500 m_tspans.back()->addText(text);
501}
502
503QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node)
504 : QSvgNode(parent), m_link(node), m_start(start)
505{
506
507}
508
509void QSvgUse::draw(QPainter *p, QSvgExtraStates &states)
510{
511 applyStyle(p, states);
512
513 if (!m_start.isNull()) {
514 p->translate(m_start);
515 }
516 m_link->draw(p, states);
517 if (!m_start.isNull()) {
518 p->translate(-m_start);
519 }
520
521 revertStyle(p, states);
522}
523
524void QSvgVideo::draw(QPainter *p, QSvgExtraStates &states)
525{
526 applyStyle(p, states);
527
528 revertStyle(p, states);
529}
530
531QSvgNode::Type QSvgAnimation::type() const
532{
533 return ANIMATION;
534}
535
536QSvgNode::Type QSvgArc::type() const
537{
538 return ARC;
539}
540
541QSvgNode::Type QSvgCircle::type() const
542{
543 return CIRCLE;
544}
545
546QSvgNode::Type QSvgEllipse::type() const
547{
548 return ELLIPSE;
549}
550
551QSvgNode::Type QSvgImage::type() const
552{
553 return IMAGE;
554}
555
556QSvgNode::Type QSvgLine::type() const
557{
558 return LINE;
559}
560
561QSvgNode::Type QSvgPath::type() const
562{
563 return PATH;
564}
565
566QSvgNode::Type QSvgPolygon::type() const
567{
568 return POLYGON;
569}
570
571QSvgNode::Type QSvgPolyline::type() const
572{
573 return POLYLINE;
574}
575
576QSvgNode::Type QSvgRect::type() const
577{
578 return RECT;
579}
580
581QSvgNode::Type QSvgText::type() const
582{
583 return m_type;
584}
585
586QSvgNode::Type QSvgUse::type() const
587{
588 return USE;
589}
590
591QSvgNode::Type QSvgVideo::type() const
592{
593 return VIDEO;
594}
595
596QRectF QSvgUse::bounds() const
597{
598 if (m_link && m_bounds.isEmpty()) {
599 m_bounds = m_link->bounds();
600 m_bounds = QRectF(m_bounds.x()+m_start.x(),
601 m_bounds.y()+m_start.y(),
602 m_bounds.width(),
603 m_bounds.height());
604
605 return m_bounds;
606 }
607 return m_bounds;
608}
609
610QRectF QSvgUse::transformedBounds(const QTransform &transform) const
611{
612 QRectF bounds;
613 QTransform t = transform;
614
615 if (m_link) {
616 QSvgTransformStyle *transStyle = m_style.transform;
617 if (transStyle) {
618 t = transStyle->qtransform() * t;
619 }
620 t.translate(m_start.x(), m_start.y());
621
622 bounds = m_link->transformedBounds(t);
623
624 return bounds;
625 }
626 return bounds;
627}
628
629QRectF QSvgPolyline::bounds() const
630{
631 qreal sw = strokeWidth();
632 if (qFuzzyIsNull(sw))
633 return m_poly.boundingRect();
634 else {
635 QPainterPath path;
636 path.addPolygon(m_poly);
637 return boundsOnStroke(path, sw);
638 }
639}
640
641QRectF QSvgArc::bounds() const
642{
643 qreal sw = strokeWidth();
644 if (qFuzzyIsNull(sw))
645 return m_cachedBounds;
646 else {
647 return boundsOnStroke(cubic, sw);
648 }
649}
650
651QRectF QSvgImage::bounds() const
652{
653 return m_bounds;
654}
655
656QRectF QSvgLine::bounds() const
657{
658 qreal sw = strokeWidth();
659 if (qFuzzyIsNull(sw)) {
660 qreal minX = qMin(m_bounds.x1(), m_bounds.x2());
661 qreal minY = qMin(m_bounds.y1(), m_bounds.y2());
662 qreal maxX = qMax(m_bounds.x1(), m_bounds.x2());
663 qreal maxY = qMax(m_bounds.y1(), m_bounds.y2());
664 return QRectF(minX, minY, maxX-minX, maxY-minY);
665 } else {
666 QPainterPath path;
667 path.moveTo(m_bounds.x1(), m_bounds.y1());
668 path.lineTo(m_bounds.x2(), m_bounds.y2());
669 return boundsOnStroke(path, sw);
670 }
671}
672
673QT_END_NAMESPACE
674
675#endif // QT_NO_SVG
Note: See TracBrowser for help on using the repository browser.