Initial import from 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 QGlyphs &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     return node;
171 }
172
173 void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color,
174                                   QSGText::TextStyle style, const QColor &styleColor)
175 {
176     Q_UNUSED(position)
177     QTextFrame *textFrame = textDocument->rootFrame();
178     QPointF p = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
179
180     QTextFrame::iterator it = textFrame->begin();
181     while (!it.atEnd()) {
182         addTextBlock(p, textDocument, it.currentBlock(), color, style, styleColor);
183         ++it;
184     }
185 }
186
187 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
188                                 QSGText::TextStyle style, const QColor &styleColor)
189 {
190     QList<QGlyphs> glyphsList(textLayout->glyphs());
191     for (int i=0; i<glyphsList.size(); ++i)
192         addGlyphs(position, glyphsList.at(i), color, style, styleColor);
193
194     QFont font = textLayout->font();
195     QRawFont rawFont = QRawFont::fromFont(font);
196     if (font.strikeOut() || font.underline() || font.overline()) {
197         addTextDecorations(position, rawFont, color, textLayout->boundingRect().width(),
198                            font.overline(), font.strikeOut(), font.underline());
199     }
200 }
201
202
203 /*!
204   Returns true if \a text contains any HTML tags, attributes or CSS properties which are unrelated
205    to text, fonts or text layout. Otherwise the function returns false. If the return value is
206   false, \a text is considered to be easily representable in the scenegraph. If it returns true,
207   then the text should be prerendered into a pixmap before it's displayed on screen.
208 */
209 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
210 {
211     if (doc == 0)
212         return false;
213
214     static QSet<QString> supportedTags;
215     if (supportedTags.isEmpty()) {
216         supportedTags.insert(QLatin1String("i"));
217         supportedTags.insert(QLatin1String("b"));
218         supportedTags.insert(QLatin1String("u"));
219         supportedTags.insert(QLatin1String("div"));
220         supportedTags.insert(QLatin1String("big"));
221         supportedTags.insert(QLatin1String("blockquote"));
222         supportedTags.insert(QLatin1String("body"));
223         supportedTags.insert(QLatin1String("br"));
224         supportedTags.insert(QLatin1String("center"));
225         supportedTags.insert(QLatin1String("cite"));
226         supportedTags.insert(QLatin1String("code"));
227         supportedTags.insert(QLatin1String("tt"));
228         supportedTags.insert(QLatin1String("dd"));
229         supportedTags.insert(QLatin1String("dfn"));
230         supportedTags.insert(QLatin1String("em"));
231         supportedTags.insert(QLatin1String("font"));
232         supportedTags.insert(QLatin1String("h1"));
233         supportedTags.insert(QLatin1String("h2"));
234         supportedTags.insert(QLatin1String("h3"));
235         supportedTags.insert(QLatin1String("h4"));
236         supportedTags.insert(QLatin1String("h5"));
237         supportedTags.insert(QLatin1String("h6"));
238         supportedTags.insert(QLatin1String("head"));
239         supportedTags.insert(QLatin1String("html"));
240         supportedTags.insert(QLatin1String("meta"));
241         supportedTags.insert(QLatin1String("nobr"));
242         supportedTags.insert(QLatin1String("p"));
243         supportedTags.insert(QLatin1String("pre"));
244         supportedTags.insert(QLatin1String("qt"));
245         supportedTags.insert(QLatin1String("s"));
246         supportedTags.insert(QLatin1String("samp"));
247         supportedTags.insert(QLatin1String("small"));
248         supportedTags.insert(QLatin1String("span"));
249         supportedTags.insert(QLatin1String("strong"));
250         supportedTags.insert(QLatin1String("sub"));
251         supportedTags.insert(QLatin1String("sup"));
252         supportedTags.insert(QLatin1String("title"));
253         supportedTags.insert(QLatin1String("var"));
254         supportedTags.insert(QLatin1String("style"));
255     }
256
257     static QSet<QCss::Property> supportedCssProperties;
258     if (supportedCssProperties.isEmpty()) {
259         supportedCssProperties.insert(QCss::Color);
260         supportedCssProperties.insert(QCss::Float);
261         supportedCssProperties.insert(QCss::Font);
262         supportedCssProperties.insert(QCss::FontFamily);
263         supportedCssProperties.insert(QCss::FontSize);
264         supportedCssProperties.insert(QCss::FontStyle);
265         supportedCssProperties.insert(QCss::FontWeight);
266         supportedCssProperties.insert(QCss::Margin);
267         supportedCssProperties.insert(QCss::MarginBottom);
268         supportedCssProperties.insert(QCss::MarginLeft);
269         supportedCssProperties.insert(QCss::MarginRight);
270         supportedCssProperties.insert(QCss::MarginTop);
271         supportedCssProperties.insert(QCss::TextDecoration);
272         supportedCssProperties.insert(QCss::TextIndent);
273         supportedCssProperties.insert(QCss::TextUnderlineStyle);
274         supportedCssProperties.insert(QCss::VerticalAlignment);
275         supportedCssProperties.insert(QCss::Whitespace);
276         supportedCssProperties.insert(QCss::Padding);
277         supportedCssProperties.insert(QCss::PaddingLeft);
278         supportedCssProperties.insert(QCss::PaddingRight);
279         supportedCssProperties.insert(QCss::PaddingTop);
280         supportedCssProperties.insert(QCss::PaddingBottom);
281         supportedCssProperties.insert(QCss::PageBreakBefore);
282         supportedCssProperties.insert(QCss::PageBreakAfter);
283         supportedCssProperties.insert(QCss::Width);
284         supportedCssProperties.insert(QCss::Height);
285         supportedCssProperties.insert(QCss::MinimumWidth);
286         supportedCssProperties.insert(QCss::MinimumHeight);
287         supportedCssProperties.insert(QCss::MaximumWidth);
288         supportedCssProperties.insert(QCss::MaximumHeight);
289         supportedCssProperties.insert(QCss::Left);
290         supportedCssProperties.insert(QCss::Right);
291         supportedCssProperties.insert(QCss::Top);
292         supportedCssProperties.insert(QCss::Bottom);
293         supportedCssProperties.insert(QCss::Position);
294         supportedCssProperties.insert(QCss::TextAlignment);
295         supportedCssProperties.insert(QCss::FontVariant);
296     }
297
298     QXmlStreamReader reader(doc->toHtml("utf-8"));
299     while (!reader.atEnd()) {
300         reader.readNext();
301
302         if (reader.isStartElement()) {
303             if (!supportedTags.contains(reader.name().toString().toLower()))
304                 return true;
305
306             QXmlStreamAttributes attributes = reader.attributes();
307             if (attributes.hasAttribute(QLatin1String("bgcolor")))
308                 return true;
309             if (attributes.hasAttribute(QLatin1String("style"))) {
310                 QCss::StyleSheet styleSheet;
311                 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
312
313                 QVector<QCss::Declaration> decls;
314                 for (int i=0; i<styleSheet.pageRules.size(); ++i)
315                     decls += styleSheet.pageRules.at(i).declarations;
316
317                 QVector<QCss::StyleRule> styleRules =
318                         styleSheet.styleRules
319                         + styleSheet.idIndex.values().toVector()
320                         + styleSheet.nameIndex.values().toVector();
321                 for (int i=0; i<styleSheet.mediaRules.size(); ++i)
322                     styleRules += styleSheet.mediaRules.at(i).styleRules;
323
324                 for (int i=0; i<styleRules.size(); ++i)
325                     decls += styleRules.at(i).declarations;
326
327                 for (int i=0; i<decls.size(); ++i) {
328                     if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
329                         return true;
330                 }
331
332             }
333         }
334     }
335
336     return reader.hasError();
337 }
338
339 void QSGTextNode::addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block,
340                                const QColor &overrideColor, QSGText::TextStyle style, const QColor &styleColor)
341 {
342     if (!block.isValid())
343         return;
344
345     QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
346
347     QTextBlock::iterator it = block.begin();
348     while (!it.atEnd()) {
349         QTextFragment fragment = it.fragment();
350         if (!fragment.text().isEmpty()) {
351             QTextCharFormat charFormat = fragment.charFormat();
352             QColor color = overrideColor.isValid()
353                     ? overrideColor
354                     : charFormat.foreground().color();
355
356             QFontMetricsF fm(fragment.charFormat().font());
357             QPointF ascent(0, fm.ascent());
358
359             QList<QGlyphs> glyphsList = fragment.glyphs();
360             for (int i=0; i<glyphsList.size(); ++i) {
361                 QGlyphs glyphs = glyphsList.at(i);
362                 QSGGlyphNode *glyphNode = addGlyphs(position + blockPosition + ascent, glyphs,
363                                                           color, style, styleColor);
364
365                 QRawFont font = glyphs.font();
366                 QPointF baseLine = glyphNode->baseLine();
367                 qreal width = glyphNode->boundingRect().width();
368                 addTextDecorations(baseLine, font, color, width,
369                                    glyphs.overline(), glyphs.strikeOut(), glyphs.underline());
370             }
371         }
372
373         ++it;
374     }
375 }
376
377 void QSGTextNode::deleteContent()
378 {
379     while (childCount() > 0)
380         delete childAtIndex(0);
381 }
382
383 #if 0
384 void QSGTextNode::updateNodes()
385 {
386     return;
387     deleteContent();
388     if (m_text.isEmpty())
389         return;
390
391     if (m_usePixmapCache) {
392         // ### gunnar: port properly
393 //        QPixmap pixmap = generatedPixmap();
394 //        if (pixmap.isNull())
395 //            return;
396
397 //        QSGImageNode *pixmapNode = m_context->createImageNode();
398 //        pixmapNode->setRect(pixmap.rect());
399 //        pixmapNode->setSourceRect(pixmap.rect());
400 //        pixmapNode->setOpacity(m_opacity);
401 //        pixmapNode->setClampToEdge(true);
402 //        pixmapNode->setLinearFiltering(m_linearFiltering);
403
404 //        appendChildNode(pixmapNode);
405     } else {
406         if (m_text.isEmpty())
407             return;
408
409         // Implement styling by drawing text several times at slight shifts. shiftForStyle
410         // contains the sequence of shifted positions at which to draw the text. All except
411         // the last will be drawn with styleColor.
412         QList<QPointF> shiftForStyle;
413         switch (m_textStyle) {
414         case OutlineTextStyle:
415             // ### Should be made faster by implementing outline material
416             shiftForStyle << QPointF(-1, 0);
417             shiftForStyle << QPointF(0, -1);
418             shiftForStyle << QPointF(1, 0);
419             shiftForStyle << QPointF(0, 1);
420             break;
421         case SunkenTextStyle:
422             shiftForStyle << QPointF(0, -1);
423             break;
424         case RaisedTextStyle:
425             shiftForStyle << QPointF(0, 1);
426             break;
427         default:
428             break;
429         }
430
431         shiftForStyle << QPointF(0, 0); // Regular position
432         while (!shiftForStyle.isEmpty()) {
433             QPointF shift = shiftForStyle.takeFirst();
434
435             // Use styleColor for all but last shift
436             if (m_richText) {
437                 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
438
439                 QTextFrame *textFrame = m_textDocument->rootFrame();
440                 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
441
442                 QTextFrame::iterator it = textFrame->begin();
443                 while (!it.atEnd()) {
444                     addTextBlock(shift + p, it.currentBlock(), overrideColor);
445                     ++it;
446                 }
447             } else {
448                 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()
449                                                    ? m_color
450                                                    : m_styleColor);
451             }
452         }
453     }
454 }
455 #endif
456
457 QT_END_NAMESPACE