DirectWrite font engine: don't leak the font table buffer
[profile/ivi/qtbase.git] / src / gui / text / qstatictext.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
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 QT_BEGIN_NAMESPACE
49
50 /*!
51     \class QStaticText
52     \brief The QStaticText class enables optimized drawing of text when the text and its layout
53     is updated rarely.
54     \since 4.7
55     \inmodule QtGui
56
57     \ingroup multimedia
58     \ingroup text
59     \mainclass
60
61     QStaticText provides a way to cache layout data for a block of text so that it can be drawn
62     more efficiently than by using QPainter::drawText() in which the layout information is 
63     recalculated with every call. 
64
65     The class primarily provides an optimization for cases where the text, its font and the
66     transformations on the painter are static over several paint events. If the text or its layout
67     is changed for every iteration, QPainter::drawText() is the more efficient alternative, since
68     the static text's layout would have to be recalculated to take the new state into consideration.
69
70     Translating the painter will not cause the layout of the text to be recalculated, but will cause
71     a very small performance impact on drawStaticText(). Altering any other parts of the painter's
72     transformation or the painter's font will cause the layout of the static text to be
73     recalculated. This should be avoided as often as possible to maximize the performance
74     benefit of using QStaticText.
75
76     In addition, only affine transformations are supported by drawStaticText(). Calling
77     drawStaticText() on a projected painter will perform slightly worse than using the regular
78     drawText() call, so this should be avoided.
79
80     \code
81     class MyWidget: public QWidget
82     {
83     public:
84         MyWidget(QWidget *parent = 0) : QWidget(parent), m_staticText("This is static text")
85
86     protected:
87         void paintEvent(QPaintEvent *)
88         {
89             QPainter painter(this);
90             painter.drawStaticText(0, 0, m_staticText);
91         }
92
93     private:
94         QStaticText m_staticText;
95     };
96     \endcode
97
98     The QStaticText class can be used to mimic the behavior of QPainter::drawText() to a specific
99     point with no boundaries, and also when QPainter::drawText() is called with a bounding 
100     rectangle. 
101
102     If a bounding rectangle is not required, create a QStaticText object without setting a preferred
103     text width. The text will then occupy a single line.
104
105     If you set a text width on the QStaticText object, this will bound the text. The text will
106     be formatted so that no line exceeds the given width. The text width set for QStaticText will
107     not automatically be used for clipping. To achieve clipping in addition to line breaks, use
108     QPainter::setClipRect(). The position of the text is decided by the argument passed to
109     QPainter::drawStaticText() and can change from call to call with a minimal impact on
110     performance.
111
112     For extra convenience, it is possible to apply formatting to the text using the HTML subset
113     supported by QTextDocument. QStaticText will attempt to guess the format of the input text using
114     Qt::mightBeRichText(), and interpret it as rich text if this function returns true. To force
115     QStaticText to display its contents as either plain text or rich text, use the function
116     QStaticText::setTextFormat() and pass in, respectively, Qt::PlainText and Qt::RichText.
117
118     QStaticText can only represent text, so only HTML tags which alter the layout or appearance of
119     the text will be respected. Adding an image to the input HTML, for instance, will cause the
120     image to be included as part of the layout, affecting the positions of the text glyphs, but it
121     will not be displayed. The result will be an empty area the size of the image in the output.
122     Similarly, using tables will cause the text to be laid out in table format, but the borders
123     will not be drawn.
124
125     If it's the first time the static text is drawn, or if the static text, or the painter's font
126     has been altered since the last time it was drawn, the text's layout has to be
127     recalculated. On some paint engines, changing the matrix of the painter will also cause the
128     layout to be recalculated. In particular, this will happen for any engine except for the
129     OpenGL2 paint engine. Recalculating the layout will impose an overhead on the
130     QPainter::drawStaticText() call where it occurs. To avoid this overhead in the paint event, you
131     can call prepare() ahead of time to ensure that the layout is calculated.
132
133     \sa QPainter::drawText(), QPainter::drawStaticText(), QTextLayout, QTextDocument
134 */
135
136 /*!
137     \enum QStaticText::PerformanceHint
138
139     This enum the different performance hints that can be set on the QStaticText. These hints
140     can be used to indicate that the QStaticText should use additional caches, if possible,
141     to improve performance at the expense of memory. In particular, setting the performance hint
142     AggressiveCaching on the QStaticText will improve performance when using the OpenGL graphics
143     system or when drawing to a QOpenGLWidget.
144
145     \value ModerateCaching Do basic caching for high performance at a low memory cost.
146     \value AggressiveCaching Use additional caching when available. This may improve performance
147            at a higher memory cost.
148 */
149
150 /*!
151     Constructs an empty QStaticText
152 */
153 QStaticText::QStaticText()    
154     : data(new QStaticTextPrivate)
155 {
156 }
157
158 /*!
159     Constructs a QStaticText object with the given \a text.
160 */
161 QStaticText::QStaticText(const QString &text)
162     : data(new QStaticTextPrivate)
163 {    
164     data->text = text;
165     data->invalidate();
166 }
167
168 /*!
169     Constructs a QStaticText object which is a copy of \a other.
170 */
171 QStaticText::QStaticText(const QStaticText &other)    
172 {
173     data = other.data;
174 }
175
176 /*!
177     Destroys the QStaticText.
178 */
179 QStaticText::~QStaticText()
180 {
181     Q_ASSERT(!data || data->ref.load() >= 1);
182 }
183
184 /*!
185     \internal
186 */
187 void QStaticText::detach()
188 {    
189     if (data->ref.load() != 1)
190         data.detach();
191 }
192
193 /*!
194   Prepares the QStaticText object for being painted with the given \a matrix and the given \a font
195   to avoid overhead when the actual drawStaticText() call is made.
196
197   When drawStaticText() is called, the layout of the QStaticText will be recalculated if any part
198   of the QStaticText object has changed since the last time it was drawn. It will also be
199   recalculated if the painter's font is not the same as when the QStaticText was last drawn, or,
200   on any other paint engine than the OpenGL2 engine, if the painter's matrix has been altered
201   since the static text was last drawn.
202
203   To avoid the overhead of creating the layout the first time you draw the QStaticText after
204   making changes, you can use the prepare() function and pass in the \a matrix and \a font you
205   expect to use when drawing the text.
206
207   \sa QPainter::setFont(), QPainter::setMatrix()
208 */
209 void QStaticText::prepare(const QTransform &matrix, const QFont &font)
210 {
211     data->matrix = matrix;
212     data->font = font;
213     data->init();
214 }
215
216
217 /*!
218     Assigns \a other to this QStaticText.
219 */
220 QStaticText &QStaticText::operator=(const QStaticText &other)
221 {    
222     data = other.data;
223     return *this;
224 }
225
226 /*!
227     Compares \a other to this QStaticText. Returns true if the texts, fonts and text widths
228     are equal.
229 */
230 bool QStaticText::operator==(const QStaticText &other) const
231 {
232     return (data == other.data
233             || (data->text == other.data->text
234                 && data->font == other.data->font
235                 && data->textWidth == other.data->textWidth));
236 }
237
238 /*!
239     Compares \a other to this QStaticText. Returns true if the texts, fonts or maximum sizes
240     are different.
241 */
242 bool QStaticText::operator!=(const QStaticText &other) const
243 {
244     return !(*this == other);
245 }
246
247 /*!
248     Sets the text of the QStaticText to \a text.
249
250     \note This function will cause the layout of the text to require recalculation.
251
252     \sa text()
253 */
254 void QStaticText::setText(const QString &text)
255 {
256     detach();
257     data->text = text;
258     data->invalidate();
259 }
260
261 /*!
262    Sets the text format of the QStaticText to \a textFormat. If \a textFormat is set to
263    Qt::AutoText (the default), the format of the text will try to be determined using the
264    function Qt::mightBeRichText(). If the text format is Qt::PlainText, then the text will be
265    displayed as is, whereas it will be interpreted as HTML if the format is Qt::RichText. HTML tags
266    that alter the font of the text, its color, or its layout are supported by QStaticText.
267
268    \note This function will cause the layout of the text to require recalculation.
269
270    \sa textFormat(), setText(), text()
271 */
272 void QStaticText::setTextFormat(Qt::TextFormat textFormat)
273 {
274     detach();
275     data->textFormat = textFormat;
276     data->invalidate();
277 }
278
279 /*!
280   Returns the text format of the QStaticText.
281
282   \sa setTextFormat(), setText(), text()
283 */
284 Qt::TextFormat QStaticText::textFormat() const
285 {
286     return Qt::TextFormat(data->textFormat);
287 }
288
289 /*!
290     Returns the text of the QStaticText.
291
292     \sa setText()
293 */
294 QString QStaticText::text() const 
295 {
296     return data->text;
297 }
298
299 /*!
300   Sets the performance hint of the QStaticText according to the \a
301   performanceHint provided. The \a performanceHint is used to
302   customize how much caching is done internally to improve
303   performance.
304
305   The default is QStaticText::ModerateCaching.
306
307   \note This function will cause the layout of the text to require recalculation.
308
309   \sa performanceHint()
310 */
311 void QStaticText::setPerformanceHint(PerformanceHint performanceHint)
312 {
313     if ((performanceHint == ModerateCaching && !data->useBackendOptimizations)
314         || (performanceHint == AggressiveCaching && data->useBackendOptimizations)) {
315         return;
316     }
317     detach();
318     data->useBackendOptimizations = (performanceHint == AggressiveCaching);
319     data->invalidate();
320 }
321
322 /*!
323   Returns which performance hint is set for the QStaticText.
324
325   \sa setPerformanceHint()
326 */
327 QStaticText::PerformanceHint QStaticText::performanceHint() const
328 {
329     return data->useBackendOptimizations ? AggressiveCaching : ModerateCaching;
330 }
331
332 /*!
333    Sets the text option structure that controls the layout process to the given \a textOption.
334
335    \sa textOption()
336 */
337 void QStaticText::setTextOption(const QTextOption &textOption)
338 {
339     detach();
340     data->textOption = textOption;
341     data->invalidate();
342 }
343
344 /*!
345     Returns the current text option used to control the layout process.
346 */
347 QTextOption QStaticText::textOption() const
348 {
349     return data->textOption;
350 }
351
352 /*!
353     Sets the preferred width for this QStaticText. If the text is wider than the specified width,
354     it will be broken into multiple lines and grow vertically. If the text cannot be split into
355     multiple lines, it will be larger than the specified \a textWidth.
356
357     Setting the preferred text width to a negative number will cause the text to be unbounded.
358
359     Use size() to get the actual size of the text.
360
361     \note This function will cause the layout of the text to require recalculation.
362
363     \sa textWidth(), size()
364 */
365 void QStaticText::setTextWidth(qreal textWidth)
366 {
367     detach();
368     data->textWidth = textWidth;
369     data->invalidate();
370 }
371
372 /*!
373     Returns the preferred width for this QStaticText.
374
375     \sa setTextWidth()
376 */
377 qreal QStaticText::textWidth() const
378 {
379     return data->textWidth;
380 }
381
382 /*!
383   Returns the size of the bounding rect for this QStaticText.
384
385   \sa textWidth()
386 */
387 QSizeF QStaticText::size() const
388 {
389     if (data->needsRelayout)
390         data->init();
391     return data->actualSize;
392 }
393
394 QStaticTextPrivate::QStaticTextPrivate()
395         : textWidth(-1.0), items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0),
396           needsRelayout(true), useBackendOptimizations(false), textFormat(Qt::AutoText),
397           untransformedCoordinates(false)
398 {
399 }
400
401 QStaticTextPrivate::QStaticTextPrivate(const QStaticTextPrivate &other)
402     : text(other.text), font(other.font), textWidth(other.textWidth), matrix(other.matrix),
403       items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0), textOption(other.textOption),
404       needsRelayout(true), useBackendOptimizations(other.useBackendOptimizations),
405       textFormat(other.textFormat), untransformedCoordinates(other.untransformedCoordinates)
406 {
407 }
408
409 QStaticTextPrivate::~QStaticTextPrivate()
410 {
411     delete[] items;
412     delete[] glyphPool;
413     delete[] positionPool;
414     delete[] charPool;
415 }
416
417 QStaticTextPrivate *QStaticTextPrivate::get(const QStaticText *q)
418 {
419     return q->data.data();
420 }
421
422 namespace {
423
424     class DrawTextItemRecorder: public QPaintEngine
425     {
426     public:
427         DrawTextItemRecorder(bool untransformedCoordinates, bool useBackendOptimizations)
428                 : m_dirtyPen(false), m_useBackendOptimizations(useBackendOptimizations),
429                   m_untransformedCoordinates(untransformedCoordinates), m_currentColor(Qt::black)
430         {
431         }
432
433         virtual void updateState(const QPaintEngineState &newState)
434         {
435             if (newState.state() & QPaintEngine::DirtyPen
436                 && newState.pen().color() != m_currentColor) {
437                 m_dirtyPen = true;
438                 m_currentColor = newState.pen().color();
439             }
440         }
441
442         virtual void drawTextItem(const QPointF &position, const QTextItem &textItem)
443         {
444             const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
445
446             QStaticTextItem currentItem;
447             currentItem.setFontEngine(ti.fontEngine);
448             currentItem.font = ti.font();
449             currentItem.charOffset = m_chars.size();
450             currentItem.numChars = ti.num_chars;
451             currentItem.glyphOffset = m_glyphs.size(); // Store offset into glyph pool
452             currentItem.positionOffset = m_glyphs.size(); // Offset into position pool
453             currentItem.useBackendOptimizations = m_useBackendOptimizations;
454             if (m_dirtyPen)
455                 currentItem.color = m_currentColor;
456
457             QTransform matrix = m_untransformedCoordinates ? QTransform() : state->transform();
458             matrix.translate(position.x(), position.y());
459
460             QVarLengthArray<glyph_t> glyphs;
461             QVarLengthArray<QFixedPoint> positions;
462             ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
463
464             int size = glyphs.size();
465             Q_ASSERT(size == positions.size());
466             currentItem.numGlyphs = size;
467
468             m_glyphs.resize(m_glyphs.size() + size);
469             m_positions.resize(m_glyphs.size());
470             m_chars.resize(m_chars.size() + ti.num_chars);
471
472             glyph_t *glyphsDestination = m_glyphs.data() + currentItem.glyphOffset;
473             memcpy(glyphsDestination, glyphs.constData(), sizeof(glyph_t) * currentItem.numGlyphs);
474
475             QFixedPoint *positionsDestination = m_positions.data() + currentItem.positionOffset;
476             memcpy(positionsDestination, positions.constData(), sizeof(QFixedPoint) * currentItem.numGlyphs);
477
478             QChar *charsDestination = m_chars.data() + currentItem.charOffset;
479             memcpy(charsDestination, ti.chars, sizeof(QChar) * currentItem.numChars);
480
481             m_items.append(currentItem);
482         }
483
484         virtual void drawPolygon(const QPointF *, int , PolygonDrawMode )
485         {
486             /* intentionally empty */
487         }
488
489         virtual bool begin(QPaintDevice *)  { return true; }
490         virtual bool end() { return true; }
491         virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) {}
492         virtual Type type() const
493         {
494             return User;
495         }
496
497         QVector<QStaticTextItem> items() const
498         {
499             return m_items;
500         }
501
502         QVector<QFixedPoint> positions() const
503         {
504             return m_positions;
505         }
506
507         QVector<glyph_t> glyphs() const
508         {
509             return m_glyphs;
510         }
511
512         QVector<QChar> chars() const
513         {
514             return m_chars;
515         }
516
517     private:
518         QVector<QStaticTextItem> m_items;
519         QVector<QFixedPoint> m_positions;
520         QVector<glyph_t> m_glyphs;
521         QVector<QChar> m_chars;
522
523         bool m_dirtyPen;
524         bool m_useBackendOptimizations;
525         bool m_untransformedCoordinates;
526         QColor m_currentColor;
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
604 void 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         textLayout.setCacheEnabled(true);
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     m_fontEngine->ref.deref();
722 }
723
724 void QStaticTextItem::setFontEngine(QFontEngine *fe)
725 {
726     if (m_fontEngine != 0)
727         m_fontEngine->ref.deref();
728     m_fontEngine = fe;
729     if (m_fontEngine != 0)
730         m_fontEngine->ref.ref();
731 }
732
733 QT_END_NAMESPACE