d36db1b6b6a3f440107adcec8e067c0b0413f5ef
[profile/ivi/qtdeclarative.git] / src / declarative / items / qsgtextnode.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 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 "qsgtextnode_p.h"
43 #include "qsgsimplerectnode.h"
44 #include <private/qsgadaptationlayer_p.h>
45 #include <private/qsgdistancefieldglyphcache_p.h>
46 #include <private/qsgdistancefieldglyphnode_p.h>
47
48 #include <private/qsgcontext_p.h>
49
50 #include <qmath.h>
51 #include <qtextdocument.h>
52 #include <qtextlayout.h>
53 #include <qabstracttextdocumentlayout.h>
54 #include <qxmlstream.h>
55 #include <qrawfont.h>
56 #include <private/qdeclarativestyledtext_p.h>
57 #include <private/qfont_p.h>
58 #include <private/qfontengine_p.h>
59 #include <private/qrawfont_p.h>
60
61 QT_BEGIN_NAMESPACE
62
63 /*!
64   Creates an empty QSGTextNode
65 */
66 QSGTextNode::QSGTextNode(QSGContext *context)
67 : m_context(context)
68 {
69 #if defined(QML_RUNTIME_TESTING)
70     description = QLatin1String("text");
71 #endif
72 }
73
74 QSGTextNode::~QSGTextNode()
75 {
76 }
77
78 #if 0
79 void QSGTextNode::setColor(const QColor &color)
80 {
81     if (m_usePixmapCache) {
82         setUpdateFlag(UpdateNodes);
83     } else {
84         for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
85             if (childNode->subType() == GlyphNodeSubType) {
86                 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
87                 if (glyphNode->color() == m_color)
88                     glyphNode->setColor(color);
89             } else if (childNode->subType() == SolidRectNodeSubType) {
90                 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
91                 if (solidRectNode->color() == m_color)
92                     solidRectNode->setColor(color);
93             }
94         }
95     }
96     m_color = color;
97 }
98
99 void QSGTextNode::setStyleColor(const QColor &styleColor)
100 {
101     if (m_textStyle != QSGTextNode::NormalTextStyle) {
102         if (m_usePixmapCache) {
103             setUpdateFlag(UpdateNodes);
104         } else {
105             for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
106                 if (childNode->subType() == GlyphNodeSubType) {
107                     QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
108                     if (glyphNode->color() == m_styleColor)
109                         glyphNode->setColor(styleColor);
110                 } else if (childNode->subType() == SolidRectNodeSubType) {
111                     QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
112                     if (solidRectNode->color() == m_styleColor)
113                         solidRectNode->setColor(styleColor);
114                 }
115             }
116         }
117     }
118     m_styleColor = styleColor;
119 }
120 #endif
121
122 void QSGTextNode::addTextDecorations(Decoration decorations, const QPointF &position,
123                                      const QColor &color, qreal width, qreal lineThickness,
124                                      qreal underlinePos, qreal ascent)
125 {
126     QRectF line(position.x(), position.y() - lineThickness / 2.0, width, lineThickness);
127
128     if (decorations & Underline) {
129         int underlinePosition = qCeil(underlinePos);
130         QRectF underline(line);
131         underline.translate(0.0, underlinePosition);
132         appendChildNode(new QSGSimpleRectNode(underline, color));
133     }
134
135     if (decorations & Overline) {
136         QRectF overline(line);
137         overline.translate(0.0, -ascent);
138         appendChildNode(new QSGSimpleRectNode(overline, color));
139     }
140
141     if (decorations & StrikeOut) {
142         QRectF strikeOut(line);
143         strikeOut.translate(0.0, ascent / -3.0);
144         appendChildNode(new QSGSimpleRectNode(strikeOut, color));
145     }
146 }
147
148 QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
149                                            QSGText::TextStyle style, const QColor &styleColor, QSGGlyphNode *prevNode)
150 {
151     QSGGlyphNode *node = prevNode;
152
153     if (!node)
154         node = m_context->createGlyphNode();
155
156     node->setGlyphs(position, glyphs);
157
158     if (node != prevNode) {
159         if (QSGDistanceFieldGlyphCache::distanceFieldEnabled()) {
160             QSGDistanceFieldGlyphNode *dfNode = static_cast<QSGDistanceFieldGlyphNode *>(node);
161             dfNode->setStyle(style);
162             dfNode->setStyleColor(styleColor);
163         }
164         node->setColor(color);
165     }
166
167     node->update();
168
169     if (node != prevNode)
170         appendChildNode(node);
171
172     return node;
173 }
174
175 void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color,
176                                   QSGText::TextStyle style, const QColor &styleColor)
177 {
178     Q_UNUSED(position)
179     QTextFrame *textFrame = textDocument->rootFrame();
180     QPointF p = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
181
182     QTextFrame::iterator it = textFrame->begin();
183     while (!it.atEnd()) {
184         addTextBlock(p, textDocument, it.currentBlock(), color, style, styleColor);
185         ++it;
186     }
187 }
188
189 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
190                                 QSGText::TextStyle style, const QColor &styleColor)
191 {
192     QList<QGlyphRun> glyphsList(textLayout->glyphRuns());
193
194     QSGGlyphNode *prevNode = 0;
195
196     QFont font = textLayout->font();
197     qreal underlinePosition, ascent, lineThickness;
198     int decorations = NoDecoration;
199     decorations |= (font.underline() ? Underline : 0);
200     decorations |= (font.overline()  ? Overline  : 0);
201     decorations |= (font.strikeOut() ? StrikeOut : 0);
202
203     underlinePosition = ascent = lineThickness = 0;
204     for (int i=0; i<glyphsList.size(); ++i) {
205         QGlyphRun glyphs = glyphsList.at(i);
206         QRawFont rawfont = glyphs.rawFont();
207         prevNode = addGlyphs(position + QPointF(0, rawfont.ascent()), glyphs, color, style, styleColor);
208
209         if (decorations) {
210             qreal rawAscent = rawfont.ascent();
211             if (decorations & Underline) {
212                 ascent = qMax(ascent, rawAscent);
213                 qreal pos = rawfont.underlinePosition();
214                 if (pos > underlinePosition) {
215                     underlinePosition = pos;
216                     // take line thickness from the rawfont with maximum underline
217                     // position in this case
218                     lineThickness = rawfont.lineThickness();
219                 }
220             } else {
221                 // otherwise it's strike out or overline, we take line thickness
222                 // from the rawfont with maximum ascent
223                 if (rawAscent > ascent) {
224                     ascent = rawAscent;
225                     lineThickness = rawfont.lineThickness();
226                 }
227             }
228         }
229     }
230
231     if (decorations) {
232         addTextDecorations(Decoration(decorations), position + QPointF(0, ascent), color,
233                            textLayout->boundingRect().width(),
234                            lineThickness, underlinePosition, ascent);
235     }
236 }
237
238
239 /*!
240   Returns true if \a text contains any HTML tags, attributes or CSS properties which are unrelated
241    to text, fonts or text layout. Otherwise the function returns false. If the return value is
242   false, \a text is considered to be easily representable in the scenegraph. If it returns true,
243   then the text should be prerendered into a pixmap before it's displayed on screen.
244 */
245 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
246 {
247     if (doc == 0)
248         return false;
249
250     static QSet<QString> supportedTags;
251     if (supportedTags.isEmpty()) {
252         supportedTags.insert(QLatin1String("i"));
253         supportedTags.insert(QLatin1String("b"));
254         supportedTags.insert(QLatin1String("u"));
255         supportedTags.insert(QLatin1String("div"));
256         supportedTags.insert(QLatin1String("big"));
257         supportedTags.insert(QLatin1String("blockquote"));
258         supportedTags.insert(QLatin1String("body"));
259         supportedTags.insert(QLatin1String("br"));
260         supportedTags.insert(QLatin1String("center"));
261         supportedTags.insert(QLatin1String("cite"));
262         supportedTags.insert(QLatin1String("code"));
263         supportedTags.insert(QLatin1String("tt"));
264         supportedTags.insert(QLatin1String("dd"));
265         supportedTags.insert(QLatin1String("dfn"));
266         supportedTags.insert(QLatin1String("em"));
267         supportedTags.insert(QLatin1String("font"));
268         supportedTags.insert(QLatin1String("h1"));
269         supportedTags.insert(QLatin1String("h2"));
270         supportedTags.insert(QLatin1String("h3"));
271         supportedTags.insert(QLatin1String("h4"));
272         supportedTags.insert(QLatin1String("h5"));
273         supportedTags.insert(QLatin1String("h6"));
274         supportedTags.insert(QLatin1String("head"));
275         supportedTags.insert(QLatin1String("html"));
276         supportedTags.insert(QLatin1String("meta"));
277         supportedTags.insert(QLatin1String("nobr"));
278         supportedTags.insert(QLatin1String("p"));
279         supportedTags.insert(QLatin1String("pre"));
280         supportedTags.insert(QLatin1String("qt"));
281         supportedTags.insert(QLatin1String("s"));
282         supportedTags.insert(QLatin1String("samp"));
283         supportedTags.insert(QLatin1String("small"));
284         supportedTags.insert(QLatin1String("span"));
285         supportedTags.insert(QLatin1String("strong"));
286         supportedTags.insert(QLatin1String("sub"));
287         supportedTags.insert(QLatin1String("sup"));
288         supportedTags.insert(QLatin1String("title"));
289         supportedTags.insert(QLatin1String("var"));
290         supportedTags.insert(QLatin1String("style"));
291     }
292
293     static QSet<QCss::Property> supportedCssProperties;
294     if (supportedCssProperties.isEmpty()) {
295         supportedCssProperties.insert(QCss::Color);
296         supportedCssProperties.insert(QCss::Float);
297         supportedCssProperties.insert(QCss::Font);
298         supportedCssProperties.insert(QCss::FontFamily);
299         supportedCssProperties.insert(QCss::FontSize);
300         supportedCssProperties.insert(QCss::FontStyle);
301         supportedCssProperties.insert(QCss::FontWeight);
302         supportedCssProperties.insert(QCss::Margin);
303         supportedCssProperties.insert(QCss::MarginBottom);
304         supportedCssProperties.insert(QCss::MarginLeft);
305         supportedCssProperties.insert(QCss::MarginRight);
306         supportedCssProperties.insert(QCss::MarginTop);
307         supportedCssProperties.insert(QCss::TextDecoration);
308         supportedCssProperties.insert(QCss::TextIndent);
309         supportedCssProperties.insert(QCss::TextUnderlineStyle);
310         supportedCssProperties.insert(QCss::VerticalAlignment);
311         supportedCssProperties.insert(QCss::Whitespace);
312         supportedCssProperties.insert(QCss::Padding);
313         supportedCssProperties.insert(QCss::PaddingLeft);
314         supportedCssProperties.insert(QCss::PaddingRight);
315         supportedCssProperties.insert(QCss::PaddingTop);
316         supportedCssProperties.insert(QCss::PaddingBottom);
317         supportedCssProperties.insert(QCss::PageBreakBefore);
318         supportedCssProperties.insert(QCss::PageBreakAfter);
319         supportedCssProperties.insert(QCss::Width);
320         supportedCssProperties.insert(QCss::Height);
321         supportedCssProperties.insert(QCss::MinimumWidth);
322         supportedCssProperties.insert(QCss::MinimumHeight);
323         supportedCssProperties.insert(QCss::MaximumWidth);
324         supportedCssProperties.insert(QCss::MaximumHeight);
325         supportedCssProperties.insert(QCss::Left);
326         supportedCssProperties.insert(QCss::Right);
327         supportedCssProperties.insert(QCss::Top);
328         supportedCssProperties.insert(QCss::Bottom);
329         supportedCssProperties.insert(QCss::Position);
330         supportedCssProperties.insert(QCss::TextAlignment);
331         supportedCssProperties.insert(QCss::FontVariant);
332     }
333
334     QXmlStreamReader reader(doc->toHtml("utf-8"));
335     while (!reader.atEnd()) {
336         reader.readNext();
337
338         if (reader.isStartElement()) {
339             if (!supportedTags.contains(reader.name().toString().toLower()))
340                 return true;
341
342             QXmlStreamAttributes attributes = reader.attributes();
343             if (attributes.hasAttribute(QLatin1String("bgcolor")))
344                 return true;
345             if (attributes.hasAttribute(QLatin1String("style"))) {
346                 QCss::StyleSheet styleSheet;
347                 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
348
349                 QVector<QCss::Declaration> decls;
350                 for (int i=0; i<styleSheet.pageRules.size(); ++i)
351                     decls += styleSheet.pageRules.at(i).declarations;
352
353                 QVector<QCss::StyleRule> styleRules =
354                         styleSheet.styleRules
355                         + styleSheet.idIndex.values().toVector()
356                         + styleSheet.nameIndex.values().toVector();
357                 for (int i=0; i<styleSheet.mediaRules.size(); ++i)
358                     styleRules += styleSheet.mediaRules.at(i).styleRules;
359
360                 for (int i=0; i<styleRules.size(); ++i)
361                     decls += styleRules.at(i).declarations;
362
363                 for (int i=0; i<decls.size(); ++i) {
364                     if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
365                         return true;
366                 }
367
368             }
369         }
370     }
371
372     return reader.hasError();
373 }
374
375 void QSGTextNode::addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block,
376                                const QColor &overrideColor, QSGText::TextStyle style, const QColor &styleColor)
377 {
378     if (!block.isValid())
379         return;
380
381     QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
382
383     QTextBlock::iterator it = block.begin();
384     while (!it.atEnd()) {
385         QTextFragment fragment = it.fragment();
386         if (!fragment.text().isEmpty()) {
387             QTextCharFormat charFormat = fragment.charFormat();
388             QColor color = overrideColor.isValid()
389                     ? overrideColor
390                     : charFormat.foreground().color();
391
392             QList<QGlyphRun> glyphsList = fragment.glyphRuns();
393             for (int i=0; i<glyphsList.size(); ++i) {
394                 QGlyphRun glyphs = glyphsList.at(i);
395                 QRawFont font = glyphs.rawFont();
396                 QSGGlyphNode *glyphNode = addGlyphs(position + blockPosition + QPointF(0, font.ascent()),
397                                                     glyphs, color, style, styleColor);
398                 int decorations = (glyphs.overline() ? Overline : 0) |
399                                   (glyphs.strikeOut() ? StrikeOut : 0) |
400                                   (glyphs.underline() ? Underline : 0);
401                 if (decorations) {
402                     QPointF baseLine = glyphNode->baseLine();
403                     qreal width = glyphNode->boundingRect().width();
404                     addTextDecorations(Decoration(decorations), baseLine, color, width,
405                                        font.lineThickness(), font.underlinePosition(), font.ascent());
406                 }
407             }
408         }
409
410         ++it;
411     }
412 }
413
414 void QSGTextNode::deleteContent()
415 {
416     while (firstChild() > 0)
417         delete firstChild();
418 }
419
420 #if 0
421 void QSGTextNode::updateNodes()
422 {
423     return;
424     deleteContent();
425     if (m_text.isEmpty())
426         return;
427
428     if (m_usePixmapCache) {
429         // ### gunnar: port properly
430 //        QPixmap pixmap = generatedPixmap();
431 //        if (pixmap.isNull())
432 //            return;
433
434 //        QSGImageNode *pixmapNode = m_context->createImageNode();
435 //        pixmapNode->setRect(pixmap.rect());
436 //        pixmapNode->setSourceRect(pixmap.rect());
437 //        pixmapNode->setOpacity(m_opacity);
438 //        pixmapNode->setClampToEdge(true);
439 //        pixmapNode->setLinearFiltering(m_linearFiltering);
440
441 //        appendChildNode(pixmapNode);
442     } else {
443         if (m_text.isEmpty())
444             return;
445
446         // Implement styling by drawing text several times at slight shifts. shiftForStyle
447         // contains the sequence of shifted positions at which to draw the text. All except
448         // the last will be drawn with styleColor.
449         QList<QPointF> shiftForStyle;
450         switch (m_textStyle) {
451         case OutlineTextStyle:
452             // ### Should be made faster by implementing outline material
453             shiftForStyle << QPointF(-1, 0);
454             shiftForStyle << QPointF(0, -1);
455             shiftForStyle << QPointF(1, 0);
456             shiftForStyle << QPointF(0, 1);
457             break;
458         case SunkenTextStyle:
459             shiftForStyle << QPointF(0, -1);
460             break;
461         case RaisedTextStyle:
462             shiftForStyle << QPointF(0, 1);
463             break;
464         default:
465             break;
466         }
467
468         shiftForStyle << QPointF(0, 0); // Regular position
469         while (!shiftForStyle.isEmpty()) {
470             QPointF shift = shiftForStyle.takeFirst();
471
472             // Use styleColor for all but last shift
473             if (m_richText) {
474                 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
475
476                 QTextFrame *textFrame = m_textDocument->rootFrame();
477                 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
478
479                 QTextFrame::iterator it = textFrame->begin();
480                 while (!it.atEnd()) {
481                     addTextBlock(shift + p, it.currentBlock(), overrideColor);
482                     ++it;
483                 }
484             } else {
485                 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()
486                                                    ? m_color
487                                                    : m_styleColor);
488             }
489         }
490     }
491 }
492 #endif
493
494 QT_END_NAMESPACE