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 | |
57 | QT_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 | |
76 | void QSvgAnimation::(QPainter *, QSvgExtraStates &) |
77 | { |
78 | qWarning("<animation> no implemented" ); |
79 | } |
80 | |
81 | static 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 | |
89 | QSvgEllipse::QSvgEllipse(QSvgNode *parent, const QRectF &rect) |
90 | : QSvgNode(parent), m_bounds(rect) |
91 | { |
92 | } |
93 | |
94 | |
95 | QRectF QSvgEllipse::(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 | |
103 | void QSvgEllipse::(QPainter *p, QSvgExtraStates &states) |
104 | { |
105 | applyStyle(p, states); |
106 | QT_SVG_DRAW_SHAPE(p->drawEllipse(m_bounds)); |
107 | revertStyle(p, states); |
108 | } |
109 | |
110 | QSvgArc::QSvgArc(QSvgNode *parent, const QPainterPath &path) |
111 | : QSvgNode(parent), m_path(path) |
112 | { |
113 | } |
114 | |
115 | void QSvgArc::(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 | |
127 | QSvgImage::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 | |
138 | void QSvgImage::(QPainter *p, QSvgExtraStates &states) |
139 | { |
140 | applyStyle(p, states); |
141 | p->drawImage(m_bounds, m_image); |
142 | revertStyle(p, states); |
143 | } |
144 | |
145 | |
146 | QSvgLine::QSvgLine(QSvgNode *parent, const QLineF &line) |
147 | : QSvgNode(parent), m_line(line) |
148 | { |
149 | } |
150 | |
151 | |
152 | void QSvgLine::(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 | |
164 | QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath) |
165 | : QSvgNode(parent), m_path(qpath) |
166 | { |
167 | } |
168 | |
169 | void QSvgPath::(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 | |
177 | QRectF QSvgPath::(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 | |
184 | QSvgPolygon::QSvgPolygon(QSvgNode *parent, const QPolygonF &poly) |
185 | : QSvgNode(parent), m_poly(poly) |
186 | { |
187 | } |
188 | |
189 | QRectF QSvgPolygon::(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 | |
201 | void QSvgPolygon::(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 | |
209 | QSvgPolyline::QSvgPolyline(QSvgNode *parent, const QPolygonF &poly) |
210 | : QSvgNode(parent), m_poly(poly) |
211 | { |
212 | |
213 | } |
214 | |
215 | void QSvgPolyline::(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 | |
234 | QSvgRect::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 | |
240 | QRectF QSvgRect::(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 | |
252 | void QSvgRect::(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 | |
263 | QSvgTspan * const QSvgText::LINEBREAK = 0; |
264 | |
265 | QSvgText::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 | |
274 | QSvgText::~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 | |
282 | void 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 | |
290 | void QSvgText::(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 | |
459 | void 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 | |
466 | QSvgUse::QSvgUse(const QPointF &start, QSvgNode *parent, QSvgNode *node) |
467 | : QSvgNode(parent), m_link(node), m_start(start) |
468 | { |
469 | |
470 | } |
471 | |
472 | void QSvgUse::(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 | |
487 | void QSvgVideo::(QPainter *p, QSvgExtraStates &states) |
488 | { |
489 | applyStyle(p, states); |
490 | |
491 | revertStyle(p, states); |
492 | } |
493 | |
494 | QSvgNode::Type QSvgAnimation::type() const |
495 | { |
496 | return ANIMATION; |
497 | } |
498 | |
499 | QSvgNode::Type QSvgArc::type() const |
500 | { |
501 | return ARC; |
502 | } |
503 | |
504 | QSvgNode::Type QSvgCircle::type() const |
505 | { |
506 | return CIRCLE; |
507 | } |
508 | |
509 | QSvgNode::Type QSvgEllipse::type() const |
510 | { |
511 | return ELLIPSE; |
512 | } |
513 | |
514 | QSvgNode::Type QSvgImage::type() const |
515 | { |
516 | return IMAGE; |
517 | } |
518 | |
519 | QSvgNode::Type QSvgLine::type() const |
520 | { |
521 | return LINE; |
522 | } |
523 | |
524 | QSvgNode::Type QSvgPath::type() const |
525 | { |
526 | return PATH; |
527 | } |
528 | |
529 | QSvgNode::Type QSvgPolygon::type() const |
530 | { |
531 | return POLYGON; |
532 | } |
533 | |
534 | QSvgNode::Type QSvgPolyline::type() const |
535 | { |
536 | return POLYLINE; |
537 | } |
538 | |
539 | QSvgNode::Type QSvgRect::type() const |
540 | { |
541 | return RECT; |
542 | } |
543 | |
544 | QSvgNode::Type QSvgText::type() const |
545 | { |
546 | return m_type; |
547 | } |
548 | |
549 | QSvgNode::Type QSvgUse::type() const |
550 | { |
551 | return USE; |
552 | } |
553 | |
554 | QSvgNode::Type QSvgVideo::type() const |
555 | { |
556 | return VIDEO; |
557 | } |
558 | |
559 | QRectF QSvgUse::(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 | |
570 | QRectF QSvgPolyline::(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 | |
582 | QRectF QSvgArc::(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 | |
589 | QRectF QSvgImage::(QPainter *p, QSvgExtraStates &) const |
590 | { |
591 | return p->transform().mapRect(m_bounds); |
592 | } |
593 | |
594 | QRectF QSvgLine::(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 | |
613 | QT_END_NAMESPACE |
614 | |
615 | #endif // QT_NO_SVG |
616 | |