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 ** 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
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.
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.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
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 (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);
100 void QSGTextNode::setStyleColor(const QColor &styleColor)
102 if (m_textStyle != QSGTextNode::NormalTextStyle) {
103 if (m_usePixmapCache) {
104 setUpdateFlag(UpdateNodes);
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);
120 m_styleColor = styleColor;
124 void QSGTextNode::addTextDecorations(const QPointF &position, const QRawFont &font, const QColor &color,
125 qreal width, bool hasOverline, bool hasStrikeOut, bool hasUnderline)
127 Q_ASSERT(font.isValid());
128 QRawFontPrivate *dptrFont = QRawFontPrivate::get(font);
129 QFontEngine *fontEngine = dptrFont->fontEngine;
131 qreal lineThickness = fontEngine->lineThickness().toReal();
133 QRectF line(position.x(), position.y() - lineThickness / 2.0, width, lineThickness);
136 int underlinePosition = fontEngine->underlinePosition().ceil().toInt();
137 QRectF underline(line);
138 underline.translate(0.0, underlinePosition);
139 appendChildNode(new QSGSimpleRectNode(underline, color));
142 qreal ascent = font.ascent();
144 QRectF overline(line);
145 overline.translate(0.0, -ascent);
146 appendChildNode(new QSGSimpleRectNode(overline, color));
150 QRectF strikeOut(line);
151 strikeOut.translate(0.0, ascent / -3.0);
152 appendChildNode(new QSGSimpleRectNode(strikeOut, color));
156 QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphs &glyphs, const QColor &color,
157 QSGText::TextStyle style, const QColor &styleColor)
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);
165 node->setGlyphs(position, glyphs);
166 node->setColor(color);
168 appendChildNode(node);
173 void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color,
174 QSGText::TextStyle style, const QColor &styleColor)
177 QTextFrame *textFrame = textDocument->rootFrame();
178 QPointF p = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
180 QTextFrame::iterator it = textFrame->begin();
181 while (!it.atEnd()) {
182 addTextBlock(p, textDocument, it.currentBlock(), color, style, styleColor);
187 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
188 QSGText::TextStyle style, const QColor &styleColor)
190 QList<QGlyphs> glyphsList(textLayout->glyphs());
191 for (int i=0; i<glyphsList.size(); ++i)
192 addGlyphs(position, glyphsList.at(i), color, style, styleColor);
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());
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.
209 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
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"));
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);
298 QXmlStreamReader reader(doc->toHtml("utf-8"));
299 while (!reader.atEnd()) {
302 if (reader.isStartElement()) {
303 if (!supportedTags.contains(reader.name().toString().toLower()))
306 QXmlStreamAttributes attributes = reader.attributes();
307 if (attributes.hasAttribute(QLatin1String("bgcolor")))
309 if (attributes.hasAttribute(QLatin1String("style"))) {
310 QCss::StyleSheet styleSheet;
311 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
313 QVector<QCss::Declaration> decls;
314 for (int i=0; i<styleSheet.pageRules.size(); ++i)
315 decls += styleSheet.pageRules.at(i).declarations;
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;
324 for (int i=0; i<styleRules.size(); ++i)
325 decls += styleRules.at(i).declarations;
327 for (int i=0; i<decls.size(); ++i) {
328 if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
336 return reader.hasError();
339 void QSGTextNode::addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block,
340 const QColor &overrideColor, QSGText::TextStyle style, const QColor &styleColor)
342 if (!block.isValid())
345 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
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()
354 : charFormat.foreground().color();
356 QFontMetricsF fm(fragment.charFormat().font());
357 QPointF ascent(0, fm.ascent());
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);
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());
377 void QSGTextNode::deleteContent()
379 while (childCount() > 0)
380 delete childAtIndex(0);
384 void QSGTextNode::updateNodes()
388 if (m_text.isEmpty())
391 if (m_usePixmapCache) {
392 // ### gunnar: port properly
393 // QPixmap pixmap = generatedPixmap();
394 // if (pixmap.isNull())
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);
404 // appendChildNode(pixmapNode);
406 if (m_text.isEmpty())
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);
421 case SunkenTextStyle:
422 shiftForStyle << QPointF(0, -1);
424 case RaisedTextStyle:
425 shiftForStyle << QPointF(0, 1);
431 shiftForStyle << QPointF(0, 0); // Regular position
432 while (!shiftForStyle.isEmpty()) {
433 QPointF shift = shiftForStyle.takeFirst();
435 // Use styleColor for all but last shift
437 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
439 QTextFrame *textFrame = m_textDocument->rootFrame();
440 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
442 QTextFrame::iterator it = textFrame->begin();
443 while (!it.atEnd()) {
444 addTextBlock(shift + p, it.currentBlock(), overrideColor);
448 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()