source: trunk/src/gui/text/qstatictext.cpp@ 1168

Last change on this file since 1168 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 23.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the test suite 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 [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qstatictext.h"
43#include "qstatictext_p.h"
44#include <private/qtextengine_p.h>
45#include <private/qfontengine_p.h>
46#include <qabstracttextdocumentlayout.h>
47
48#include <QtGui/qapplication.h>
49
50QT_BEGIN_NAMESPACE
51
52/*!
53 \class QStaticText
54 \brief The QStaticText class enables optimized drawing of text when the text and its layout
55 is updated rarely.
56 \since 4.7
57
58 \ingroup multimedia
59 \ingroup text
60 \mainclass
61
62 QStaticText provides a way to cache layout data for a block of text so that it can be drawn
63 more efficiently than by using QPainter::drawText() in which the layout information is
64 recalculated with every call.
65
66 The class primarily provides an optimization for cases where the text, its font and the
67 transformations on the painter are static over several paint events. If the text or its layout
68 is changed for every iteration, QPainter::drawText() is the more efficient alternative, since
69 the static text's layout would have to be recalculated to take the new state into consideration.
70
71 Translating the painter will not cause the layout of the text to be recalculated, but will cause
72 a very small performance impact on drawStaticText(). Altering any other parts of the painter's
73 transformation or the painter's font will cause the layout of the static text to be
74 recalculated. This should be avoided as often as possible to maximize the performance
75 benefit of using QStaticText.
76
77 In addition, only affine transformations are supported by drawStaticText(). Calling
78 drawStaticText() on a projected painter will perform slightly worse than using the regular
79 drawText() call, so this should be avoided.
80
81 \code
82 class MyWidget: public QWidget
83 {
84 public:
85 MyWidget(QWidget *parent = 0) : QWidget(parent), m_staticText("This is static text")
86
87 protected:
88 void paintEvent(QPaintEvent *)
89 {
90 QPainter painter(this);
91 painter.drawStaticText(0, 0, m_staticText);
92 }
93
94 private:
95 QStaticText m_staticText;
96 };
97 \endcode
98
99 The QStaticText class can be used to mimic the behavior of QPainter::drawText() to a specific
100 point with no boundaries, and also when QPainter::drawText() is called with a bounding
101 rectangle.
102
103 If a bounding rectangle is not required, create a QStaticText object without setting a preferred
104 text width. The text will then occupy a single line.
105
106 If you set a text width on the QStaticText object, this will bound the text. The text will
107 be formatted so that no line exceeds the given width. The text width set for QStaticText will
108 not automatically be used for clipping. To achieve clipping in addition to line breaks, use
109 QPainter::setClipRect(). The position of the text is decided by the argument passed to
110 QPainter::drawStaticText() and can change from call to call with a minimal impact on
111 performance.
112
113 For extra convenience, it is possible to apply formatting to the text using the HTML subset
114 supported by QTextDocument. QStaticText will attempt to guess the format of the input text using
115 Qt::mightBeRichText(), and interpret it as rich text if this function returns true. To force
116 QStaticText to display its contents as either plain text or rich text, use the function
117 QStaticText::setTextFormat() and pass in, respectively, Qt::PlainText and Qt::RichText.
118
119 QStaticText can only represent text, so only HTML tags which alter the layout or appearance of
120 the text will be respected. Adding an image to the input HTML, for instance, will cause the
121 image to be included as part of the layout, affecting the positions of the text glyphs, but it
122 will not be displayed. The result will be an empty area the size of the image in the output.
123 Similarly, using tables will cause the text to be laid out in table format, but the borders
124 will not be drawn.
125
126 If it's the first time the static text is drawn, or if the static text, or the painter's font
127 has been altered since the last time it was drawn, the text's layout has to be
128 recalculated. On some paint engines, changing the matrix of the painter will also cause the
129 layout to be recalculated. In particular, this will happen for any engine except for the
130 OpenGL2 paint engine. Recalculating the layout will impose an overhead on the
131 QPainter::drawStaticText() call where it occurs. To avoid this overhead in the paint event, you
132 can call prepare() ahead of time to ensure that the layout is calculated.
133
134 \sa QPainter::drawText(), QPainter::drawStaticText(), QTextLayout, QTextDocument
135*/
136
137/*!
138 \enum QStaticText::PerformanceHint
139
140 This enum the different performance hints that can be set on the QStaticText. These hints
141 can be used to indicate that the QStaticText should use additional caches, if possible,
142 to improve performance at the expense of memory. In particular, setting the performance hint
143 AggressiveCaching on the QStaticText will improve performance when using the OpenGL graphics
144 system or when drawing to a QGLWidget.
145
146 \value ModerateCaching Do basic caching for high performance at a low memory cost.
147 \value AggressiveCaching Use additional caching when available. This may improve performance
148 at a higher memory cost.
149*/
150
151/*!
152 Constructs an empty QStaticText
153*/
154QStaticText::QStaticText()
155 : data(new QStaticTextPrivate)
156{
157}
158
159/*!
160 Constructs a QStaticText object with the given \a text.
161*/
162QStaticText::QStaticText(const QString &text)
163 : data(new QStaticTextPrivate)
164{
165 data->text = text;
166 data->invalidate();
167}
168
169/*!
170 Constructs a QStaticText object which is a copy of \a other.
171*/
172QStaticText::QStaticText(const QStaticText &other)
173{
174 data = other.data;
175}
176
177/*!
178 Destroys the QStaticText.
179*/
180QStaticText::~QStaticText()
181{
182 Q_ASSERT(!data || data->ref >= 1);
183}
184
185/*!
186 \internal
187*/
188void QStaticText::detach()
189{
190 if (data->ref != 1)
191 data.detach();
192}
193
194/*!
195 Prepares the QStaticText object for being painted with the given \a matrix and the given \a font
196 to avoid overhead when the actual drawStaticText() call is made.
197
198 When drawStaticText() is called, the layout of the QStaticText will be recalculated if any part
199 of the QStaticText object has changed since the last time it was drawn. It will also be
200 recalculated if the painter's font is not the same as when the QStaticText was last drawn, or,
201 on any other paint engine than the OpenGL2 engine, if the painter's matrix has been altered
202 since the static text was last drawn.
203
204 To avoid the overhead of creating the layout the first time you draw the QStaticText after
205 making changes, you can use the prepare() function and pass in the \a matrix and \a font you
206 expect to use when drawing the text.
207
208 \sa QPainter::setFont(), QPainter::setMatrix()
209*/
210void QStaticText::prepare(const QTransform &matrix, const QFont &font)
211{
212 data->matrix = matrix;
213 data->font = font;
214 data->init();
215}
216
217
218/*!
219 Assigns \a other to this QStaticText.
220*/
221QStaticText &QStaticText::operator=(const QStaticText &other)
222{
223 data = other.data;
224 return *this;
225}
226
227/*!
228 Compares \a other to this QStaticText. Returns true if the texts, fonts and text widths
229 are equal.
230*/
231bool QStaticText::operator==(const QStaticText &other) const
232{
233 return (data == other.data
234 || (data->text == other.data->text
235 && data->font == other.data->font
236 && data->textWidth == other.data->textWidth));
237}
238
239/*!
240 Compares \a other to this QStaticText. Returns true if the texts, fonts or maximum sizes
241 are different.
242*/
243bool QStaticText::operator!=(const QStaticText &other) const
244{
245 return !(*this == other);
246}
247
248/*!
249 Sets the text of the QStaticText to \a text.
250
251 \note This function will cause the layout of the text to require recalculation.
252
253 \sa text()
254*/
255void QStaticText::setText(const QString &text)
256{
257 detach();
258 data->text = text;
259 data->invalidate();
260}
261
262/*!
263 Sets the text format of the QStaticText to \a textFormat. If \a textFormat is set to
264 Qt::AutoText (the default), the format of the text will try to be determined using the
265 function Qt::mightBeRichText(). If the text format is Qt::PlainText, then the text will be
266 displayed as is, whereas it will be interpreted as HTML if the format is Qt::RichText. HTML tags
267 that alter the font of the text, its color, or its layout are supported by QStaticText.
268
269 \note This function will cause the layout of the text to require recalculation.
270
271 \sa textFormat(), setText(), text()
272*/
273void QStaticText::setTextFormat(Qt::TextFormat textFormat)
274{
275 detach();
276 data->textFormat = textFormat;
277 data->invalidate();
278}
279
280/*!
281 Returns the text format of the QStaticText.
282
283 \sa setTextFormat(), setText(), text()
284*/
285Qt::TextFormat QStaticText::textFormat() const
286{
287 return Qt::TextFormat(data->textFormat);
288}
289
290/*!
291 Returns the text of the QStaticText.
292
293 \sa setText()
294*/
295QString QStaticText::text() const
296{
297 return data->text;
298}
299
300/*!
301 Sets the performance hint of the QStaticText according to the \a
302 performanceHint provided. The \a performanceHint is used to
303 customize how much caching is done internally to improve
304 performance.
305
306 The default is QStaticText::ModerateCaching.
307
308 \note This function will cause the layout of the text to require recalculation.
309
310 \sa performanceHint()
311*/
312void QStaticText::setPerformanceHint(PerformanceHint performanceHint)
313{
314 if ((performanceHint == ModerateCaching && !data->useBackendOptimizations)
315 || (performanceHint == AggressiveCaching && data->useBackendOptimizations)) {
316 return;
317 }
318 detach();
319 data->useBackendOptimizations = (performanceHint == AggressiveCaching);
320 data->invalidate();
321}
322
323/*!
324 Returns which performance hint is set for the QStaticText.
325
326 \sa setPerformanceHint()
327*/
328QStaticText::PerformanceHint QStaticText::performanceHint() const
329{
330 return data->useBackendOptimizations ? AggressiveCaching : ModerateCaching;
331}
332
333/*!
334 Sets the text option structure that controls the layout process to the given \a textOption.
335
336 \sa textOption()
337*/
338void QStaticText::setTextOption(const QTextOption &textOption)
339{
340 detach();
341 data->textOption = textOption;
342 data->invalidate();
343}
344
345/*!
346 Returns the current text option used to control the layout process.
347*/
348QTextOption QStaticText::textOption() const
349{
350 return data->textOption;
351}
352
353/*!
354 Sets the preferred width for this QStaticText. If the text is wider than the specified width,
355 it will be broken into multiple lines and grow vertically. If the text cannot be split into
356 multiple lines, it will be larger than the specified \a textWidth.
357
358 Setting the preferred text width to a negative number will cause the text to be unbounded.
359
360 Use size() to get the actual size of the text.
361
362 \note This function will cause the layout of the text to require recalculation.
363
364 \sa textWidth(), size()
365*/
366void QStaticText::setTextWidth(qreal textWidth)
367{
368 detach();
369 data->textWidth = textWidth;
370 data->invalidate();
371}
372
373/*!
374 Returns the preferred width for this QStaticText.
375
376 \sa setTextWidth()
377*/
378qreal QStaticText::textWidth() const
379{
380 return data->textWidth;
381}
382
383/*!
384 Returns the size of the bounding rect for this QStaticText.
385
386 \sa textWidth()
387*/
388QSizeF QStaticText::size() const
389{
390 if (data->needsRelayout)
391 data->init();
392 return data->actualSize;
393}
394
395QStaticTextPrivate::QStaticTextPrivate()
396 : textWidth(-1.0), items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0),
397 needsRelayout(true), useBackendOptimizations(false), textFormat(Qt::AutoText),
398 untransformedCoordinates(false)
399{
400}
401
402QStaticTextPrivate::QStaticTextPrivate(const QStaticTextPrivate &other)
403 : text(other.text), font(other.font), textWidth(other.textWidth), matrix(other.matrix),
404 items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0), textOption(other.textOption),
405 needsRelayout(true), useBackendOptimizations(other.useBackendOptimizations),
406 textFormat(other.textFormat), untransformedCoordinates(other.untransformedCoordinates)
407{
408}
409
410QStaticTextPrivate::~QStaticTextPrivate()
411{
412 delete[] items;
413 delete[] glyphPool;
414 delete[] positionPool;
415 delete[] charPool;
416}
417
418QStaticTextPrivate *QStaticTextPrivate::get(const QStaticText *q)
419{
420 return q->data.data();
421}
422
423Q_GUI_EXPORT extern int qt_defaultDpiX();
424Q_GUI_EXPORT extern int qt_defaultDpiY();
425
426namespace {
427
428 class DrawTextItemRecorder: public QPaintEngine
429 {
430 public:
431 DrawTextItemRecorder(bool untransformedCoordinates, bool useBackendOptimizations)
432 : m_dirtyPen(false), m_useBackendOptimizations(useBackendOptimizations),
433 m_untransformedCoordinates(untransformedCoordinates)
434 {
435 }
436
437 virtual void updateState(const QPaintEngineState &newState)
438 {
439 if (newState.state() & QPaintEngine::DirtyPen)
440 m_dirtyPen = true;
441 }
442
443 virtual void drawTextItem(const QPointF &position, const QTextItem &textItem)
444 {
445 const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
446
447 QStaticTextItem currentItem;
448 currentItem.setFontEngine(ti.fontEngine);
449 currentItem.font = ti.font();
450 currentItem.charOffset = m_chars.size();
451 currentItem.numChars = ti.num_chars;
452 currentItem.glyphOffset = m_glyphs.size(); // Store offset into glyph pool
453 currentItem.positionOffset = m_glyphs.size(); // Offset into position pool
454 currentItem.useBackendOptimizations = m_useBackendOptimizations;
455 if (m_dirtyPen)
456 currentItem.color = state->pen().color();
457
458 QTransform matrix = m_untransformedCoordinates ? QTransform() : state->transform();
459 matrix.translate(position.x(), position.y());
460
461 QVarLengthArray<glyph_t> glyphs;
462 QVarLengthArray<QFixedPoint> positions;
463 ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
464
465 int size = glyphs.size();
466 Q_ASSERT(size == positions.size());
467 currentItem.numGlyphs = size;
468
469 m_glyphs.resize(m_glyphs.size() + size);
470 m_positions.resize(m_glyphs.size());
471 m_chars.resize(m_chars.size() + ti.num_chars);
472
473 glyph_t *glyphsDestination = m_glyphs.data() + currentItem.glyphOffset;
474 memcpy(glyphsDestination, glyphs.constData(), sizeof(glyph_t) * currentItem.numGlyphs);
475
476 QFixedPoint *positionsDestination = m_positions.data() + currentItem.positionOffset;
477 memcpy(positionsDestination, positions.constData(), sizeof(QFixedPoint) * currentItem.numGlyphs);
478
479 QChar *charsDestination = m_chars.data() + currentItem.charOffset;
480 memcpy(charsDestination, ti.chars, sizeof(QChar) * currentItem.numChars);
481
482 m_items.append(currentItem);
483 }
484
485 virtual void drawPolygon(const QPointF *, int , PolygonDrawMode )
486 {
487 /* intentionally empty */
488 }
489
490 virtual bool begin(QPaintDevice *) { return true; }
491 virtual bool end() { return true; }
492 virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) {}
493 virtual Type type() const
494 {
495 return User;
496 }
497
498 QVector<QStaticTextItem> items() const
499 {
500 return m_items;
501 }
502
503 QVector<QFixedPoint> positions() const
504 {
505 return m_positions;
506 }
507
508 QVector<glyph_t> glyphs() const
509 {
510 return m_glyphs;
511 }
512
513 QVector<QChar> chars() const
514 {
515 return m_chars;
516 }
517
518 private:
519 QVector<QStaticTextItem> m_items;
520 QVector<QFixedPoint> m_positions;
521 QVector<glyph_t> m_glyphs;
522 QVector<QChar> m_chars;
523
524 bool m_dirtyPen;
525 bool m_useBackendOptimizations;
526 bool m_untransformedCoordinates;
527 };
528
529 class DrawTextItemDevice: public QPaintDevice
530 {
531 public:
532 DrawTextItemDevice(bool untransformedCoordinates, bool useBackendOptimizations)
533 {
534 m_paintEngine = new DrawTextItemRecorder(untransformedCoordinates,
535 useBackendOptimizations);
536 }
537
538 ~DrawTextItemDevice()
539 {
540 delete m_paintEngine;
541 }
542
543 int metric(PaintDeviceMetric m) const
544 {
545 int val;
546 switch (m) {
547 case PdmWidth:
548 case PdmHeight:
549 case PdmWidthMM:
550 case PdmHeightMM:
551 val = 0;
552 break;
553 case PdmDpiX:
554 case PdmPhysicalDpiX:
555 val = qt_defaultDpiX();
556 break;
557 case PdmDpiY:
558 case PdmPhysicalDpiY:
559 val = qt_defaultDpiY();
560 break;
561 case PdmNumColors:
562 val = 16777216;
563 break;
564 case PdmDepth:
565 val = 24;
566 break;
567 default:
568 val = 0;
569 qWarning("DrawTextItemDevice::metric: Invalid metric command");
570 }
571 return val;
572 }
573
574 virtual QPaintEngine *paintEngine() const
575 {
576 return m_paintEngine;
577 }
578
579 QVector<glyph_t> glyphs() const
580 {
581 return m_paintEngine->glyphs();
582 }
583
584 QVector<QFixedPoint> positions() const
585 {
586 return m_paintEngine->positions();
587 }
588
589 QVector<QStaticTextItem> items() const
590 {
591 return m_paintEngine->items();
592 }
593
594 QVector<QChar> chars() const
595 {
596 return m_paintEngine->chars();
597 }
598
599 private:
600 DrawTextItemRecorder *m_paintEngine;
601 };
602}
603
604void QStaticTextPrivate::paintText(const QPointF &topLeftPosition, QPainter *p)
605{
606 bool preferRichText = textFormat == Qt::RichText
607 || (textFormat == Qt::AutoText && Qt::mightBeRichText(text));
608
609 if (!preferRichText) {
610 QTextLayout textLayout;
611 textLayout.setText(text);
612 textLayout.setFont(font);
613 textLayout.setTextOption(textOption);
614
615 qreal leading = QFontMetricsF(font).leading();
616 qreal height = -leading;
617
618 textLayout.beginLayout();
619 while (1) {
620 QTextLine line = textLayout.createLine();
621 if (!line.isValid())
622 break;
623
624 if (textWidth >= 0.0)
625 line.setLineWidth(textWidth);
626 height += leading;
627 line.setPosition(QPointF(0.0, height));
628 height += line.height();
629 }
630 textLayout.endLayout();
631
632 actualSize = textLayout.boundingRect().size();
633 textLayout.draw(p, topLeftPosition);
634 } else {
635 QTextDocument document;
636#ifndef QT_NO_CSSPARSER
637 QColor color = p->pen().color();
638 document.setDefaultStyleSheet(QString::fromLatin1("body { color: #%1%2%3 }")
639 .arg(QString::number(color.red(), 16), 2, QLatin1Char('0'))
640 .arg(QString::number(color.green(), 16), 2, QLatin1Char('0'))
641 .arg(QString::number(color.blue(), 16), 2, QLatin1Char('0')));
642#endif
643 document.setDefaultFont(font);
644 document.setDocumentMargin(0.0);
645#ifndef QT_NO_TEXTHTMLPARSER
646 document.setHtml(text);
647#else
648 document.setPlainText(text);
649#endif
650 if (textWidth >= 0.0)
651 document.setTextWidth(textWidth);
652 else
653 document.adjustSize();
654 document.setDefaultTextOption(textOption);
655
656 p->save();
657 p->translate(topLeftPosition);
658 QAbstractTextDocumentLayout::PaintContext ctx;
659 ctx.palette.setColor(QPalette::Text, p->pen().color());
660 document.documentLayout()->draw(p, ctx);
661 p->restore();
662
663 if (textWidth >= 0.0)
664 document.adjustSize(); // Find optimal size
665
666 actualSize = document.size();
667 }
668}
669
670void QStaticTextPrivate::init()
671{
672 delete[] items;
673 delete[] glyphPool;
674 delete[] positionPool;
675 delete[] charPool;
676
677 position = QPointF(0, 0);
678
679 DrawTextItemDevice device(untransformedCoordinates, useBackendOptimizations);
680 {
681 QPainter painter(&device);
682 painter.setFont(font);
683 painter.setTransform(matrix);
684
685 paintText(QPointF(0, 0), &painter);
686 }
687
688 QVector<QStaticTextItem> deviceItems = device.items();
689 QVector<QFixedPoint> positions = device.positions();
690 QVector<glyph_t> glyphs = device.glyphs();
691 QVector<QChar> chars = device.chars();
692
693 itemCount = deviceItems.size();
694 items = new QStaticTextItem[itemCount];
695
696 glyphPool = new glyph_t[glyphs.size()];
697 memcpy(glyphPool, glyphs.constData(), glyphs.size() * sizeof(glyph_t));
698
699 positionPool = new QFixedPoint[positions.size()];
700 memcpy(positionPool, positions.constData(), positions.size() * sizeof(QFixedPoint));
701
702 charPool = new QChar[chars.size()];
703 memcpy(charPool, chars.constData(), chars.size() * sizeof(QChar));
704
705 for (int i=0; i<itemCount; ++i) {
706 items[i] = deviceItems.at(i);
707
708 items[i].glyphs = glyphPool + items[i].glyphOffset;
709 items[i].glyphPositions = positionPool + items[i].positionOffset;
710 items[i].chars = charPool + items[i].charOffset;
711 }
712
713 needsRelayout = false;
714}
715
716QStaticTextItem::~QStaticTextItem()
717{
718 if (m_userData != 0 && !m_userData->ref.deref())
719 delete m_userData;
720 if (!m_fontEngine->ref.deref())
721 delete m_fontEngine;
722}
723
724void QStaticTextItem::setFontEngine(QFontEngine *fe)
725{
726 if (m_fontEngine != 0) {
727 if (!m_fontEngine->ref.deref())
728 delete m_fontEngine;
729 }
730
731 m_fontEngine = fe;
732 if (m_fontEngine != 0)
733 m_fontEngine->ref.ref();
734}
735
736QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.