Merge remote-tracking branch 'origin/4.7' into qt-4.8-from-4.7
[qt:qt.git] / src / gui / text / qstatictext.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 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 test suite of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
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
50 QT_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 */
154 QStaticText::QStaticText()    
155     : data(new QStaticTextPrivate)
156 {
157 }
158
159 /*!
160     Constructs a QStaticText object with the given \a text.
161 */
162 QStaticText::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 */
172 QStaticText::QStaticText(const QStaticText &other)    
173 {
174     data = other.data;
175 }
176
177 /*!
178     Destroys the QStaticText.
179 */
180 QStaticText::~QStaticText()
181 {
182     Q_ASSERT(!data || data->ref >= 1);
183 }
184
185 /*!
186     \internal
187 */
188 void 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 */
210 void 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 */
221 QStaticText &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 */
231 bool 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 */
243 bool 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 */
255 void 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 */
273 void 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 */
285 Qt::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 */
295 QString 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 */
312 void 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 */
328 QStaticText::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 */
338 void 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 */
348 QTextOption 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 */
366 void 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 */
378 qreal 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 */
388 QSizeF QStaticText::size() const
389 {
390     if (data->needsRelayout)
391         data->init();
392     return data->actualSize;
393 }
394
395 QStaticTextPrivate::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
402 QStaticTextPrivate::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
410 QStaticTextPrivate::~QStaticTextPrivate()
411 {
412     delete[] items;
413     delete[] glyphPool;
414     delete[] positionPool;
415     delete[] charPool;
416 }
417
418 QStaticTextPrivate *QStaticTextPrivate::get(const QStaticText *q)
419 {
420     return q->data.data();
421 }
422
423 namespace {
424
425     class DrawTextItemRecorder: public QPaintEngine
426     {
427     public:
428         DrawTextItemRecorder(bool untransformedCoordinates, bool useBackendOptimizations)
429                 : m_dirtyPen(false), m_useBackendOptimizations(useBackendOptimizations),
430                   m_untransformedCoordinates(untransformedCoordinates), m_currentColor(Qt::black)
431         {
432         }
433
434         virtual void updateState(const QPaintEngineState &newState)
435         {
436             if (newState.state() & QPaintEngine::DirtyPen
437                 && newState.pen().color() != m_currentColor) {
438                 m_dirtyPen = true;
439                 m_currentColor = newState.pen().color();
440             }
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 = m_currentColor;
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         QColor m_currentColor;
528     };
529
530     class DrawTextItemDevice: public QPaintDevice
531     {
532     public:
533         DrawTextItemDevice(bool untransformedCoordinates, bool useBackendOptimizations)
534         {
535             m_paintEngine = new DrawTextItemRecorder(untransformedCoordinates,
536                                                      useBackendOptimizations);
537         }
538
539         ~DrawTextItemDevice()
540         {
541             delete m_paintEngine;
542         }
543
544         int metric(PaintDeviceMetric m) const
545         {
546             int val;
547             switch (m) {
548             case PdmWidth:
549             case PdmHeight:
550             case PdmWidthMM:
551             case PdmHeightMM:
552                 val = 0;
553                 break;
554             case PdmDpiX:
555             case PdmPhysicalDpiX:
556                 val = qt_defaultDpiX();
557                 break;
558             case PdmDpiY:
559             case PdmPhysicalDpiY:
560                 val = qt_defaultDpiY();
561                 break;
562             case PdmNumColors:
563                 val = 16777216;
564                 break;
565             case PdmDepth:
566                 val = 24;
567                 break;
568             default:
569                 val = 0;
570                 qWarning("DrawTextItemDevice::metric: Invalid metric command");
571             }
572             return val;
573         }
574
575         virtual QPaintEngine *paintEngine() const
576         {
577             return m_paintEngine;
578         }
579
580         QVector<glyph_t> glyphs() const
581         {
582             return m_paintEngine->glyphs();
583         }
584
585         QVector<QFixedPoint> positions() const
586         {
587             return m_paintEngine->positions();
588         }
589
590         QVector<QStaticTextItem> items() const
591         {
592             return m_paintEngine->items();
593         }
594
595         QVector<QChar> chars() const
596         {
597             return m_paintEngine->chars();
598         }
599
600     private:
601         DrawTextItemRecorder *m_paintEngine;
602     };
603 }
604
605 void QStaticTextPrivate::paintText(const QPointF &topLeftPosition, QPainter *p)
606 {
607     bool preferRichText = textFormat == Qt::RichText
608                           || (textFormat == Qt::AutoText && Qt::mightBeRichText(text));
609
610     if (!preferRichText) {
611         QTextLayout textLayout;
612         textLayout.setText(text);
613         textLayout.setFont(font);
614         textLayout.setTextOption(textOption);
615
616         qreal leading = QFontMetricsF(font).leading();
617         qreal height = -leading;
618
619         textLayout.beginLayout();
620         while (1) {
621             QTextLine line = textLayout.createLine();
622             if (!line.isValid())
623                 break;
624
625             if (textWidth >= 0.0)
626                 line.setLineWidth(textWidth);
627             height += leading;
628             line.setPosition(QPointF(0.0, height));
629             height += line.height();
630         }
631         textLayout.endLayout();
632
633         actualSize = textLayout.boundingRect().size();
634         textLayout.draw(p, topLeftPosition);
635     } else {
636         QTextDocument document;
637 #ifndef QT_NO_CSSPARSER
638         QColor color = p->pen().color();
639         document.setDefaultStyleSheet(QString::fromLatin1("body { color: #%1%2%3 }")
640                                       .arg(QString::number(color.red(), 16), 2, QLatin1Char('0'))
641                                       .arg(QString::number(color.green(), 16), 2, QLatin1Char('0'))
642                                       .arg(QString::number(color.blue(), 16), 2, QLatin1Char('0')));
643 #endif
644         document.setDefaultFont(font);
645         document.setDocumentMargin(0.0);        
646 #ifndef QT_NO_TEXTHTMLPARSER
647         document.setHtml(text);
648 #else
649         document.setPlainText(text);
650 #endif
651         if (textWidth >= 0.0)
652             document.setTextWidth(textWidth);
653         else
654             document.adjustSize();
655         document.setDefaultTextOption(textOption);
656
657         p->save();
658         p->translate(topLeftPosition);
659         QAbstractTextDocumentLayout::PaintContext ctx;
660         ctx.palette.setColor(QPalette::Text, p->pen().color());
661         document.documentLayout()->draw(p, ctx);
662         p->restore();
663
664         if (textWidth >= 0.0)
665             document.adjustSize(); // Find optimal size
666
667         actualSize = document.size();
668     }
669 }
670
671 void QStaticTextPrivate::init()
672 {
673     delete[] items;
674     delete[] glyphPool;
675     delete[] positionPool;
676     delete[] charPool;
677
678     position = QPointF(0, 0);
679
680     DrawTextItemDevice device(untransformedCoordinates, useBackendOptimizations);
681     {
682         QPainter painter(&device);
683         painter.setFont(font);
684         painter.setTransform(matrix);
685
686         paintText(QPointF(0, 0), &painter);
687     }
688
689     QVector<QStaticTextItem> deviceItems = device.items();
690     QVector<QFixedPoint> positions = device.positions();
691     QVector<glyph_t> glyphs = device.glyphs();
692     QVector<QChar> chars = device.chars();
693
694     itemCount = deviceItems.size();
695     items = new QStaticTextItem[itemCount];
696
697     glyphPool = new glyph_t[glyphs.size()];
698     memcpy(glyphPool, glyphs.constData(), glyphs.size() * sizeof(glyph_t));
699
700     positionPool = new QFixedPoint[positions.size()];
701     memcpy(positionPool, positions.constData(), positions.size() * sizeof(QFixedPoint));
702
703     charPool = new QChar[chars.size()];
704     memcpy(charPool, chars.constData(), chars.size() * sizeof(QChar));
705
706     for (int i=0; i<itemCount; ++i) {
707         items[i] = deviceItems.at(i);
708
709         items[i].glyphs = glyphPool + items[i].glyphOffset;
710         items[i].glyphPositions = positionPool + items[i].positionOffset;
711         items[i].chars = charPool + items[i].charOffset;
712     }
713
714     needsRelayout = false;
715 }
716
717 QStaticTextItem::~QStaticTextItem()
718 {
719     if (m_userData != 0 && !m_userData->ref.deref())
720         delete m_userData;
721     if (!m_fontEngine->ref.deref())
722         delete m_fontEngine;
723 }
724
725 void QStaticTextItem::setFontEngine(QFontEngine *fe)
726 {
727     if (m_fontEngine != 0) {
728         if (!m_fontEngine->ref.deref())
729             delete m_fontEngine;
730     }
731
732     m_fontEngine = fe;
733     if (m_fontEngine != 0)
734         m_fontEngine->ref.ref();
735 }
736
737 QT_END_NAMESPACE