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 QGlyphRun &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);
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());
180 void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color,
181 QSGText::TextStyle style, const QColor &styleColor)
184 QTextFrame *textFrame = textDocument->rootFrame();
185 QPointF p = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
187 QTextFrame::iterator it = textFrame->begin();
188 while (!it.atEnd()) {
189 addTextBlock(p, textDocument, it.currentBlock(), color, style, styleColor);
194 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
195 QSGText::TextStyle style, const QColor &styleColor)
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);
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.
212 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
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"));
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);
301 QXmlStreamReader reader(doc->toHtml("utf-8"));
302 while (!reader.atEnd()) {
305 if (reader.isStartElement()) {
306 if (!supportedTags.contains(reader.name().toString().toLower()))
309 QXmlStreamAttributes attributes = reader.attributes();
310 if (attributes.hasAttribute(QLatin1String("bgcolor")))
312 if (attributes.hasAttribute(QLatin1String("style"))) {
313 QCss::StyleSheet styleSheet;
314 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
316 QVector<QCss::Declaration> decls;
317 for (int i=0; i<styleSheet.pageRules.size(); ++i)
318 decls += styleSheet.pageRules.at(i).declarations;
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;
327 for (int i=0; i<styleRules.size(); ++i)
328 decls += styleRules.at(i).declarations;
330 for (int i=0; i<decls.size(); ++i) {
331 if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
339 return reader.hasError();
342 void QSGTextNode::addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block,
343 const QColor &overrideColor, QSGText::TextStyle style, const QColor &styleColor)
345 if (!block.isValid())
348 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
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()
357 : charFormat.foreground().color();
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);
372 void QSGTextNode::deleteContent()
374 while (childCount() > 0)
375 delete childAtIndex(0);
379 void QSGTextNode::updateNodes()
383 if (m_text.isEmpty())
386 if (m_usePixmapCache) {
387 // ### gunnar: port properly
388 // QPixmap pixmap = generatedPixmap();
389 // if (pixmap.isNull())
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);
399 // appendChildNode(pixmapNode);
401 if (m_text.isEmpty())
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);
416 case SunkenTextStyle:
417 shiftForStyle << QPointF(0, -1);
419 case RaisedTextStyle:
420 shiftForStyle << QPointF(0, 1);
426 shiftForStyle << QPointF(0, 0); // Regular position
427 while (!shiftForStyle.isEmpty()) {
428 QPointF shift = shiftForStyle.takeFirst();
430 // Use styleColor for all but last shift
432 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
434 QTextFrame *textFrame = m_textDocument->rootFrame();
435 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
437 QTextFrame::iterator it = textFrame->begin();
438 while (!it.atEnd()) {
439 addTextBlock(shift + p, it.currentBlock(), overrideColor);
443 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()