1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
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>
48 #include <private/qsgcontext_p.h>
51 #include <qtextdocument.h>
52 #include <qtextlayout.h>
53 #include <qabstracttextdocumentlayout.h>
54 #include <qxmlstream.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>
64 Creates an empty QSGTextNode
66 QSGTextNode::QSGTextNode(QSGContext *context)
69 #if defined(QML_RUNTIME_TESTING)
70 description = QLatin1String("text");
74 QSGTextNode::~QSGTextNode()
79 void QSGTextNode::setColor(const QColor &color)
81 if (m_usePixmapCache) {
82 setUpdateFlag(UpdateNodes);
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);
99 void QSGTextNode::setStyleColor(const QColor &styleColor)
101 if (m_textStyle != QSGTextNode::NormalTextStyle) {
102 if (m_usePixmapCache) {
103 setUpdateFlag(UpdateNodes);
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);
118 m_styleColor = styleColor;
122 void QSGTextNode::addTextDecorations(Decoration decorations, const QPointF &position,
123 const QColor &color, qreal width, qreal lineThickness,
124 qreal underlinePos, qreal ascent)
126 QRectF line(position.x(), position.y() - lineThickness / 2.0, width, lineThickness);
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));
135 if (decorations & Overline) {
136 QRectF overline(line);
137 overline.translate(0.0, -ascent);
138 appendChildNode(new QSGSimpleRectNode(overline, color));
141 if (decorations & StrikeOut) {
142 QRectF strikeOut(line);
143 strikeOut.translate(0.0, ascent / -3.0);
144 appendChildNode(new QSGSimpleRectNode(strikeOut, color));
148 QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
149 QSGText::TextStyle style, const QColor &styleColor, QSGGlyphNode *prevNode)
151 QSGGlyphNode *node = prevNode;
154 node = m_context->createGlyphNode();
156 node->setGlyphs(position, glyphs);
158 if (node != prevNode) {
159 if (QSGDistanceFieldGlyphCache::distanceFieldEnabled()) {
160 QSGDistanceFieldGlyphNode *dfNode = static_cast<QSGDistanceFieldGlyphNode *>(node);
161 dfNode->setStyle(style);
162 dfNode->setStyleColor(styleColor);
164 node->setColor(color);
169 if (node != prevNode)
170 appendChildNode(node);
175 void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color,
176 QSGText::TextStyle style, const QColor &styleColor)
179 QTextFrame *textFrame = textDocument->rootFrame();
180 QPointF p = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
182 QTextFrame::iterator it = textFrame->begin();
183 while (!it.atEnd()) {
184 addTextBlock(p, textDocument, it.currentBlock(), color, style, styleColor);
189 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
190 QSGText::TextStyle style, const QColor &styleColor)
192 QList<QGlyphRun> glyphsList(textLayout->glyphRuns());
194 QSGGlyphNode *prevNode = 0;
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);
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);
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();
221 // otherwise it's strike out or overline, we take line thickness
222 // from the rawfont with maximum ascent
223 if (rawAscent > ascent) {
225 lineThickness = rawfont.lineThickness();
232 addTextDecorations(Decoration(decorations), position + QPointF(0, ascent), color,
233 textLayout->boundingRect().width(),
234 lineThickness, underlinePosition, ascent);
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.
245 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
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"));
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);
334 QXmlStreamReader reader(doc->toHtml("utf-8"));
335 while (!reader.atEnd()) {
338 if (reader.isStartElement()) {
339 if (!supportedTags.contains(reader.name().toString().toLower()))
342 QXmlStreamAttributes attributes = reader.attributes();
343 if (attributes.hasAttribute(QLatin1String("bgcolor")))
345 if (attributes.hasAttribute(QLatin1String("style"))) {
346 QCss::StyleSheet styleSheet;
347 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
349 QVector<QCss::Declaration> decls;
350 for (int i=0; i<styleSheet.pageRules.size(); ++i)
351 decls += styleSheet.pageRules.at(i).declarations;
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;
360 for (int i=0; i<styleRules.size(); ++i)
361 decls += styleRules.at(i).declarations;
363 for (int i=0; i<decls.size(); ++i) {
364 if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
372 return reader.hasError();
375 void QSGTextNode::addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block,
376 const QColor &overrideColor, QSGText::TextStyle style, const QColor &styleColor)
378 if (!block.isValid())
381 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
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()
390 : charFormat.foreground().color();
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);
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());
414 void QSGTextNode::deleteContent()
416 while (firstChild() > 0)
421 void QSGTextNode::updateNodes()
425 if (m_text.isEmpty())
428 if (m_usePixmapCache) {
429 // ### gunnar: port properly
430 // QPixmap pixmap = generatedPixmap();
431 // if (pixmap.isNull())
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);
441 // appendChildNode(pixmapNode);
443 if (m_text.isEmpty())
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);
458 case SunkenTextStyle:
459 shiftForStyle << QPointF(0, -1);
461 case RaisedTextStyle:
462 shiftForStyle << QPointF(0, 1);
468 shiftForStyle << QPointF(0, 0); // Regular position
469 while (!shiftForStyle.isEmpty()) {
470 QPointF shift = shiftForStyle.takeFirst();
472 // Use styleColor for all but last shift
474 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
476 QTextFrame *textFrame = m_textDocument->rootFrame();
477 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
479 QTextFrame::iterator it = textFrame->begin();
480 while (!it.atEnd()) {
481 addTextBlock(shift + p, it.currentBlock(), overrideColor);
485 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()