Merge remote-tracking branch 'origin/4.7' into qt-4.8-from-4.7
[qt:qt.git] / src / declarative / graphicsitems / qdeclarativetextlayout.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 QtDeclarative module 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 "qdeclarativetextlayout_p.h"
43 #include <private/qstatictext_p.h>
44 #include <private/qfontengine_p.h>
45 #include <private/qtextengine_p.h>
46 #include <private/qpainter_p.h>
47 #include <private/qpaintengineex_p.h>
48
49 QT_BEGIN_NAMESPACE
50
51 class QDeclarativeTextLayoutPrivate
52 {
53 public:
54     QDeclarativeTextLayoutPrivate() 
55     : cached(false) {}
56
57     QPointF position;
58
59     bool cached;
60     QVector<QStaticTextItem> items;
61     QVector<QFixedPoint> positions;
62     QVector<glyph_t> glyphs;
63     QVector<QChar> chars;
64 };
65
66 namespace {
67 class DrawTextItemRecorder: public QPaintEngine
68 {
69     public:
70         DrawTextItemRecorder(bool untransformedCoordinates, bool useBackendOptimizations)
71             : m_inertText(0), m_dirtyPen(false), m_useBackendOptimizations(useBackendOptimizations),
72               m_untransformedCoordinates(untransformedCoordinates), m_currentColor(Qt::black)
73             {
74             }
75
76         virtual void updateState(const QPaintEngineState &newState)
77         {
78             if (newState.state() & QPaintEngine::DirtyPen
79                 && newState.pen().color() != m_currentColor) {
80                 m_dirtyPen = true;
81                 m_currentColor = newState.pen().color();
82             }
83         }
84
85         virtual void drawTextItem(const QPointF &position, const QTextItem &textItem)
86         {
87             int glyphOffset = m_inertText->glyphs.size(); // Store offset into glyph pool
88             int positionOffset = m_inertText->glyphs.size(); // Offset into position pool
89             int charOffset = m_inertText->chars.size();
90
91             const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
92
93             bool needFreshCurrentItem = true;
94             if (!m_inertText->items.isEmpty()) {
95                 QStaticTextItem &last = m_inertText->items[m_inertText->items.count() - 1];
96
97                 if (last.fontEngine() == ti.fontEngine && last.font == ti.font() &&
98                     (!m_dirtyPen || last.color == state->pen().color())) {
99                     needFreshCurrentItem = false;
100
101                     last.numChars += ti.num_chars;
102
103                 }
104             } 
105
106             if (needFreshCurrentItem) {
107                 QStaticTextItem currentItem;
108
109                 currentItem.setFontEngine(ti.fontEngine);
110                 currentItem.font = ti.font();
111                 currentItem.charOffset = charOffset;
112                 currentItem.numChars = ti.num_chars;
113                 currentItem.numGlyphs = 0;
114                 currentItem.glyphOffset = glyphOffset;
115                 currentItem.positionOffset = positionOffset;
116                 currentItem.useBackendOptimizations = m_useBackendOptimizations;
117                 if (m_dirtyPen)
118                     currentItem.color = m_currentColor;
119
120                 m_inertText->items.append(currentItem);
121             }
122
123             QStaticTextItem &currentItem = m_inertText->items.last();
124
125             QTransform matrix = m_untransformedCoordinates ? QTransform() : state->transform();
126             matrix.translate(position.x(), position.y());
127
128             QVarLengthArray<glyph_t> glyphs;
129             QVarLengthArray<QFixedPoint> positions;
130             ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
131
132             int size = glyphs.size();
133             Q_ASSERT(size == positions.size());
134             currentItem.numGlyphs += size;
135
136             m_inertText->glyphs.resize(m_inertText->glyphs.size() + size);
137             m_inertText->positions.resize(m_inertText->glyphs.size());
138             m_inertText->chars.resize(m_inertText->chars.size() + ti.num_chars);
139
140             glyph_t *glyphsDestination = m_inertText->glyphs.data() + glyphOffset;
141             qMemCopy(glyphsDestination, glyphs.constData(), sizeof(glyph_t) * size);
142
143             QFixedPoint *positionsDestination = m_inertText->positions.data() + positionOffset;
144             qMemCopy(positionsDestination, positions.constData(), sizeof(QFixedPoint) * size);
145
146             QChar *charsDestination = m_inertText->chars.data() + charOffset;
147             qMemCopy(charsDestination, ti.chars, sizeof(QChar) * ti.num_chars);
148
149         }
150
151         virtual void drawPolygon(const QPointF *, int , PolygonDrawMode )
152         {
153             /* intentionally empty */
154         }
155
156         virtual bool begin(QPaintDevice *)  { return true; }
157         virtual bool end() { return true; }
158         virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) {}
159         virtual Type type() const
160         {
161             return User;
162         }
163
164         void begin(QDeclarativeTextLayoutPrivate *t) {
165             m_inertText = t;
166             m_dirtyPen = false;
167         }
168
169     private:
170         QDeclarativeTextLayoutPrivate *m_inertText;
171
172         bool m_dirtyPen;
173         bool m_useBackendOptimizations;
174         bool m_untransformedCoordinates;
175         QColor m_currentColor;
176 };
177
178 class DrawTextItemDevice: public QPaintDevice
179 {
180     public:
181         DrawTextItemDevice(bool untransformedCoordinates, bool useBackendOptimizations)
182         {
183             m_paintEngine = new DrawTextItemRecorder(untransformedCoordinates,
184                     useBackendOptimizations);
185         }
186
187         ~DrawTextItemDevice()
188         {
189             delete m_paintEngine;
190         }
191
192         void begin(QDeclarativeTextLayoutPrivate *t) {
193             m_paintEngine->begin(t);
194         }
195
196         int metric(PaintDeviceMetric m) const
197         {
198             int val;
199             switch (m) {
200             case PdmWidth:
201             case PdmHeight:
202             case PdmWidthMM:
203             case PdmHeightMM:
204                 val = 0;
205                 break;
206             case PdmDpiX:
207             case PdmPhysicalDpiX:
208                 val = qt_defaultDpiX();
209                 break;
210             case PdmDpiY:
211             case PdmPhysicalDpiY:
212                 val = qt_defaultDpiY();
213                 break;
214             case PdmNumColors:
215                 val = 16777216;
216                 break;
217             case PdmDepth:
218                 val = 24;
219                 break;
220             default:
221                 val = 0;
222                 qWarning("DrawTextItemDevice::metric: Invalid metric command");
223             }
224             return val;
225         }
226
227         virtual QPaintEngine *paintEngine() const
228         {
229             return m_paintEngine;
230         }
231
232     private:
233         DrawTextItemRecorder *m_paintEngine;
234 };
235
236 struct InertTextPainter {
237     InertTextPainter()
238     : device(true, true), painter(&device) {}
239
240     DrawTextItemDevice device;
241     QPainter painter;
242 };
243 }
244
245 Q_GLOBAL_STATIC(InertTextPainter, inertTextPainter);
246
247 /*!
248 \class QDeclarativeTextLayout
249 \brief The QDeclarativeTextLayout class is a version of QStaticText that works with QTextLayouts.
250 \internal
251
252 This class is basically a copy of the QStaticText code, but it is adapted to source its text from
253 QTextLayout.
254
255 It is also considerably faster to create a QDeclarativeTextLayout than a QStaticText because it uses 
256 a single, shared QPainter instance.  QStaticText by comparison creates a new QPainter per instance.
257 As a consequence this means that QDeclarativeTextLayout is not re-enterant.  Adding a lock around 
258 the shared painter solves this, and only introduces a minor performance penalty, but is unnecessary 
259 for QDeclarativeTextLayout's current use (QDeclarativeText is already tied to the GUI thread).
260 */
261
262 QDeclarativeTextLayout::QDeclarativeTextLayout()
263 : d(0)
264 {
265 }
266
267 QDeclarativeTextLayout::QDeclarativeTextLayout(const QString &text)
268 : QTextLayout(text), d(0)
269 {
270 }
271
272 QDeclarativeTextLayout::~QDeclarativeTextLayout()
273 {
274     if (d) delete d;
275 }
276
277 void QDeclarativeTextLayout::beginLayout()
278 {
279     if (d && d->cached) {
280         d->cached = false;
281         d->items.clear();
282         d->positions.clear();
283         d->glyphs.clear();
284         d->chars.clear();
285         d->position = QPointF();
286     }
287     QTextLayout::beginLayout();
288 }
289
290 void QDeclarativeTextLayout::clearLayout()
291 {
292     if (d && d->cached) {
293         d->cached = false;
294         d->items.clear();
295         d->positions.clear();
296         d->glyphs.clear();
297         d->chars.clear();
298         d->position = QPointF();
299     }
300     QTextLayout::clearLayout();
301 }
302
303 void QDeclarativeTextLayout::prepare()
304 {
305     if (!d || !d->cached) {
306
307         if (!d)  
308             d = new QDeclarativeTextLayoutPrivate;
309
310         InertTextPainter *itp = inertTextPainter();
311         itp->device.begin(d);
312         QTextLayout::draw(&itp->painter, QPointF(0, 0));
313
314         glyph_t *glyphPool = d->glyphs.data();
315         QFixedPoint *positionPool = d->positions.data();
316         QChar *charPool = d->chars.data();
317
318         int itemCount = d->items.count();
319         for (int ii = 0; ii < itemCount; ++ii) {
320             QStaticTextItem &item = d->items[ii];
321             item.glyphs = glyphPool + item.glyphOffset;
322             item.glyphPositions = positionPool + item.positionOffset;
323             item.chars = charPool + item.charOffset;
324         }
325
326         d->cached = true;
327     }
328 }
329
330 // Defined in qpainter.cpp
331 extern Q_GUI_EXPORT void qt_draw_decoration_for_glyphs(QPainter *painter, const glyph_t *glyphArray,
332                                                        const QFixedPoint *positions, int glyphCount,
333                                                        QFontEngine *fontEngine, const QFont &font,
334                                                        const QTextCharFormat &charFormat);
335
336 void QDeclarativeTextLayout::draw(QPainter *painter, const QPointF &p)
337 {
338     QPainterPrivate *priv = QPainterPrivate::get(painter);
339
340     bool paintEngineSupportsTransformations = priv->extended &&
341                                               (priv->extended->type() == QPaintEngine::OpenGL2 ||
342                                                priv->extended->type() == QPaintEngine::OpenVG ||
343                                                priv->extended->type() == QPaintEngine::OpenGL);
344
345     if (!paintEngineSupportsTransformations || !priv->state->matrix.isAffine()) {
346         QTextLayout::draw(painter, p);
347         return;
348     }
349
350     prepare();
351
352     int itemCount = d->items.count();
353
354     if (p != d->position) {
355         QFixed fx = QFixed::fromReal(p.x());
356         QFixed fy = QFixed::fromReal(p.y());
357         QFixed oldX = QFixed::fromReal(d->position.x());
358         QFixed oldY = QFixed::fromReal(d->position.y());
359         for (int item = 0; item < itemCount; ++item) {
360             QStaticTextItem &textItem = d->items[item];
361
362             for (int ii = 0; ii < textItem.numGlyphs; ++ii) {
363                 textItem.glyphPositions[ii].x += fx - oldX;
364                 textItem.glyphPositions[ii].y += fy - oldY;
365             }
366             textItem.userDataNeedsUpdate = true;
367         }
368
369         d->position = p;
370     }
371
372     QPen oldPen = priv->state->pen;
373     QColor currentColor = oldPen.color();
374     for (int ii = 0; ii < itemCount; ++ii) {
375         QStaticTextItem &item = d->items[ii];
376         if (item.color.isValid() && currentColor != item.color) {
377             painter->setPen(item.color);
378             currentColor = item.color;
379         }
380         priv->extended->drawStaticTextItem(&item);
381
382         qt_draw_decoration_for_glyphs(painter, item.glyphs, item.glyphPositions,
383                                       item.numGlyphs, item.fontEngine(), painter->font(),
384                                       QTextCharFormat());
385     }
386     if (currentColor != oldPen.color())
387         painter->setPen(oldPen);
388 }
389
390 QT_END_NAMESPACE
391