Merge branch 'master' into qtquick2
[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 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
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 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
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 (int i=0; i<childCount(); ++i) {
85             QSGNode *childNode = childAtIndex(i);
86             if (childNode->subType() == GlyphNodeSubType) {
87                 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
88                 if (glyphNode->color() == m_color)
89                     glyphNode->setColor(color);
90             } else if (childNode->subType() == SolidRectNodeSubType) {
91                 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
92                 if (solidRectNode->color() == m_color)
93                     solidRectNode->setColor(color);
94             }
95         }
96     }
97     m_color = color;
98 }
99
100 void QSGTextNode::setStyleColor(const QColor &styleColor)
101 {
102     if (m_textStyle != QSGTextNode::NormalTextStyle) {
103         if (m_usePixmapCache) {
104             setUpdateFlag(UpdateNodes);
105         } else {
106             for (int i=0; i<childCount(); ++i) {
107                 QSGNode *childNode = childAtIndex(i);
108                 if (childNode->subType() == GlyphNodeSubType) {
109                     QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
110                     if (glyphNode->color() == m_styleColor)
111                         glyphNode->setColor(styleColor);
112                 } else if (childNode->subType() == SolidRectNodeSubType) {
113                     QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
114                     if (solidRectNode->color() == m_styleColor)
115                         solidRectNode->setColor(styleColor);
116                 }
117             }
118         }
119     }
120     m_styleColor = styleColor;
121 }
122 #endif
123
124 void QSGTextNode::addTextDecorations(const QPointF &position, const QRawFont &font, const QColor &color,
125                                      qreal width, bool hasOverline, bool hasStrikeOut, bool hasUnderline)
126 {
127     Q_ASSERT(font.isValid());
128     QRawFontPrivate *dptrFont = QRawFontPrivate::get(font);
129     QFontEngine *fontEngine = dptrFont->fontEngine;
130
131     qreal lineThickness = fontEngine->lineThickness().toReal();
132
133     QRectF line(position.x(), position.y() - lineThickness / 2.0, width, lineThickness);
134
135     if (hasUnderline) {
136         int underlinePosition = fontEngine->underlinePosition().ceil().toInt();
137         QRectF underline(line);
138         underline.translate(0.0, underlinePosition);
139         appendChildNode(new QSGSimpleRectNode(underline, color));
140     }
141
142     qreal ascent = font.ascent();
143     if (hasOverline) {
144         QRectF overline(line);
145         overline.translate(0.0, -ascent);
146         appendChildNode(new QSGSimpleRectNode(overline, color));
147     }
148
149     if (hasStrikeOut) {
150         QRectF strikeOut(line);
151         strikeOut.translate(0.0, ascent / -3.0);
152         appendChildNode(new QSGSimpleRectNode(strikeOut, color));
153     }
154 }
155
156 QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
157                                            QSGText::TextStyle style, const QColor &styleColor)
158 {
159     QSGGlyphNode *node = m_context->createGlyphNode();
160     if (QSGDistanceFieldGlyphCache::distanceFieldEnabled()) {
161         QSGDistanceFieldGlyphNode *dfNode = static_cast<QSGDistanceFieldGlyphNode *>(node);
162         dfNode->setStyle(style);
163         dfNode->setStyleColor(styleColor);
164     }
165     node->setGlyphs(position, glyphs);
166     node->setColor(color);
167
168     appendChildNode(node);
169
170     if (glyphs.overline() || glyphs.strikeOut() || glyphs.underline()) {
171         QPointF baseLine = node->baseLine();
172         qreal width = node->boundingRect().width();
173         addTextDecorations(baseLine, glyphs.rawFont(), color, width,
174                            glyphs.overline(), glyphs.strikeOut(), glyphs.underline());
175     }
176
177     return node;
178 }
179
180 void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color,
181                                   QSGText::TextStyle style, const QColor &styleColor)
182 {
183     Q_UNUSED(position)
184     QTextFrame *textFrame = textDocument->rootFrame();
185     QPointF p = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
186
187     QTextFrame::iterator it = textFrame->begin();
188     while (!it.atEnd()) {
189         addTextBlock(p, textDocument, it.currentBlock(), color, style, styleColor);
190         ++it;
191     }
192 }
193
194 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
195                                 QSGText::TextStyle style, const QColor &styleColor)
196 {
197     QList<QGlyphRun> glyphsList(textLayout->glyphRuns());
198     for (int i=0; i<glyphsList.size(); ++i) {
199         QGlyphRun glyphs = glyphsList.at(i);
200         QRawFont font = glyphs.rawFont();
201         addGlyphs(position + QPointF(0, font.ascent()), glyphs, color, style, styleColor);
202     }
203 }
204
205
206 /*!
207   Returns true if \a text contains any HTML tags, attributes or CSS properties which are unrelated
208    to text, fonts or text layout. Otherwise the function returns false. If the return value is
209   false, \a text is considered to be easily representable in the scenegraph. If it returns true,
210   then the text should be prerendered into a pixmap before it's displayed on screen.
211 */
212 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
213 {
214     if (doc == 0)
215         return false;
216
217     static QSet<QString> supportedTags;
218     if (supportedTags.isEmpty()) {
219         supportedTags.insert(QLatin1String("i"));
220         supportedTags.insert(QLatin1String("b"));
221         supportedTags.insert(QLatin1String("u"));
222         supportedTags.insert(QLatin1String("div"));
223         supportedTags.insert(QLatin1String("big"));
224         supportedTags.insert(QLatin1String("blockquote"));
225         supportedTags.insert(QLatin1String("body"));
226         supportedTags.insert(QLatin1String("br"));
227         supportedTags.insert(QLatin1String("center"));
228         supportedTags.insert(QLatin1String("cite"));
229         supportedTags.insert(QLatin1String("code"));
230         supportedTags.insert(QLatin1String("tt"));
231         supportedTags.insert(QLatin1String("dd"));
232         supportedTags.insert(QLatin1String("dfn"));
233         supportedTags.insert(QLatin1String("em"));
234         supportedTags.insert(QLatin1String("font"));
235         supportedTags.insert(QLatin1String("h1"));
236         supportedTags.insert(QLatin1String("h2"));
237         supportedTags.insert(QLatin1String("h3"));
238         supportedTags.insert(QLatin1String("h4"));
239         supportedTags.insert(QLatin1String("h5"));
240         supportedTags.insert(QLatin1String("h6"));
241         supportedTags.insert(QLatin1String("head"));
242         supportedTags.insert(QLatin1String("html"));
243         supportedTags.insert(QLatin1String("meta"));
244         supportedTags.insert(QLatin1String("nobr"));
245         supportedTags.insert(QLatin1String("p"));
246         supportedTags.insert(QLatin1String("pre"));
247         supportedTags.insert(QLatin1String("qt"));
248         supportedTags.insert(QLatin1String("s"));
249         supportedTags.insert(QLatin1String("samp"));
250         supportedTags.insert(QLatin1String("small"));
251         supportedTags.insert(QLatin1String("span"));
252         supportedTags.insert(QLatin1String("strong"));
253         supportedTags.insert(QLatin1String("sub"));
254         supportedTags.insert(QLatin1String("sup"));
255         supportedTags.insert(QLatin1String("title"));
256         supportedTags.insert(QLatin1String("var"));
257         supportedTags.insert(QLatin1String("style"));
258     }
259
260     static QSet<QCss::Property> supportedCssProperties;
261     if (supportedCssProperties.isEmpty()) {
262         supportedCssProperties.insert(QCss::Color);
263         supportedCssProperties.insert(QCss::Float);
264         supportedCssProperties.insert(QCss::Font);
265         supportedCssProperties.insert(QCss::FontFamily);
266         supportedCssProperties.insert(QCss::FontSize);
267         supportedCssProperties.insert(QCss::FontStyle);
268         supportedCssProperties.insert(QCss::FontWeight);
269         supportedCssProperties.insert(QCss::Margin);
270         supportedCssProperties.insert(QCss::MarginBottom);
271         supportedCssProperties.insert(QCss::MarginLeft);
272         supportedCssProperties.insert(QCss::MarginRight);
273         supportedCssProperties.insert(QCss::MarginTop);
274         supportedCssProperties.insert(QCss::TextDecoration);
275         supportedCssProperties.insert(QCss::TextIndent);
276         supportedCssProperties.insert(QCss::TextUnderlineStyle);
277         supportedCssProperties.insert(QCss::VerticalAlignment);
278         supportedCssProperties.insert(QCss::Whitespace);
279         supportedCssProperties.insert(QCss::Padding);
280         supportedCssProperties.insert(QCss::PaddingLeft);
281         supportedCssProperties.insert(QCss::PaddingRight);
282         supportedCssProperties.insert(QCss::PaddingTop);
283         supportedCssProperties.insert(QCss::PaddingBottom);
284         supportedCssProperties.insert(QCss::PageBreakBefore);
285         supportedCssProperties.insert(QCss::PageBreakAfter);
286         supportedCssProperties.insert(QCss::Width);
287         supportedCssProperties.insert(QCss::Height);
288         supportedCssProperties.insert(QCss::MinimumWidth);
289         supportedCssProperties.insert(QCss::MinimumHeight);
290         supportedCssProperties.insert(QCss::MaximumWidth);
291         supportedCssProperties.insert(QCss::MaximumHeight);
292         supportedCssProperties.insert(QCss::Left);
293         supportedCssProperties.insert(QCss::Right);
294         supportedCssProperties.insert(QCss::Top);
295         supportedCssProperties.insert(QCss::Bottom);
296         supportedCssProperties.insert(QCss::Position);
297         supportedCssProperties.insert(QCss::TextAlignment);
298         supportedCssProperties.insert(QCss::FontVariant);
299     }
300
301     QXmlStreamReader reader(doc->toHtml("utf-8"));
302     while (!reader.atEnd()) {
303         reader.readNext();
304
305         if (reader.isStartElement()) {
306             if (!supportedTags.contains(reader.name().toString().toLower()))
307                 return true;
308
309             QXmlStreamAttributes attributes = reader.attributes();
310             if (attributes.hasAttribute(QLatin1String("bgcolor")))
311                 return true;
312             if (attributes.hasAttribute(QLatin1String("style"))) {
313                 QCss::StyleSheet styleSheet;
314                 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
315
316                 QVector<QCss::Declaration> decls;
317                 for (int i=0; i<styleSheet.pageRules.size(); ++i)
318                     decls += styleSheet.pageRules.at(i).declarations;
319
320                 QVector<QCss::StyleRule> styleRules =
321                         styleSheet.styleRules
322                         + styleSheet.idIndex.values().toVector()
323                         + styleSheet.nameIndex.values().toVector();
324                 for (int i=0; i<styleSheet.mediaRules.size(); ++i)
325                     styleRules += styleSheet.mediaRules.at(i).styleRules;
326
327                 for (int i=0; i<styleRules.size(); ++i)
328                     decls += styleRules.at(i).declarations;
329
330                 for (int i=0; i<decls.size(); ++i) {
331                     if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
332                         return true;
333                 }
334
335             }
336         }
337     }
338
339     return reader.hasError();
340 }
341
342 void QSGTextNode::addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block,
343                                const QColor &overrideColor, QSGText::TextStyle style, const QColor &styleColor)
344 {
345     if (!block.isValid())
346         return;
347
348     QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
349
350     QTextBlock::iterator it = block.begin();
351     while (!it.atEnd()) {
352         QTextFragment fragment = it.fragment();
353         if (!fragment.text().isEmpty()) {
354             QTextCharFormat charFormat = fragment.charFormat();
355             QColor color = overrideColor.isValid()
356                     ? overrideColor
357                     : charFormat.foreground().color();
358
359             QList<QGlyphRun> glyphsList = fragment.glyphRuns();
360             for (int i=0; i<glyphsList.size(); ++i) {
361                 QGlyphRun glyphs = glyphsList.at(i);
362                 QRawFont font = glyphs.rawFont();
363                 addGlyphs(position + blockPosition + QPointF(0, font.ascent()),
364                           glyphs, color, style, styleColor);
365             }
366         }
367
368         ++it;
369     }
370 }
371
372 void QSGTextNode::deleteContent()
373 {
374     while (childCount() > 0)
375         delete childAtIndex(0);
376 }
377
378 #if 0
379 void QSGTextNode::updateNodes()
380 {
381     return;
382     deleteContent();
383     if (m_text.isEmpty())
384         return;
385
386     if (m_usePixmapCache) {
387         // ### gunnar: port properly
388 //        QPixmap pixmap = generatedPixmap();
389 //        if (pixmap.isNull())
390 //            return;
391
392 //        QSGImageNode *pixmapNode = m_context->createImageNode();
393 //        pixmapNode->setRect(pixmap.rect());
394 //        pixmapNode->setSourceRect(pixmap.rect());
395 //        pixmapNode->setOpacity(m_opacity);
396 //        pixmapNode->setClampToEdge(true);
397 //        pixmapNode->setLinearFiltering(m_linearFiltering);
398
399 //        appendChildNode(pixmapNode);
400     } else {
401         if (m_text.isEmpty())
402             return;
403
404         // Implement styling by drawing text several times at slight shifts. shiftForStyle
405         // contains the sequence of shifted positions at which to draw the text. All except
406         // the last will be drawn with styleColor.
407         QList<QPointF> shiftForStyle;
408         switch (m_textStyle) {
409         case OutlineTextStyle:
410             // ### Should be made faster by implementing outline material
411             shiftForStyle << QPointF(-1, 0);
412             shiftForStyle << QPointF(0, -1);
413             shiftForStyle << QPointF(1, 0);
414             shiftForStyle << QPointF(0, 1);
415             break;
416         case SunkenTextStyle:
417             shiftForStyle << QPointF(0, -1);
418             break;
419         case RaisedTextStyle:
420             shiftForStyle << QPointF(0, 1);
421             break;
422         default:
423             break;
424         }
425
426         shiftForStyle << QPointF(0, 0); // Regular position
427         while (!shiftForStyle.isEmpty()) {
428             QPointF shift = shiftForStyle.takeFirst();
429
430             // Use styleColor for all but last shift
431             if (m_richText) {
432                 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
433
434                 QTextFrame *textFrame = m_textDocument->rootFrame();
435                 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
436
437                 QTextFrame::iterator it = textFrame->begin();
438                 while (!it.atEnd()) {
439                     addTextBlock(shift + p, it.currentBlock(), overrideColor);
440                     ++it;
441                 }
442             } else {
443                 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()
444                                                    ? m_color
445                                                    : m_styleColor);
446             }
447         }
448     }
449 }
450 #endif
451
452 QT_END_NAMESPACE