/**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtDeclarative module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include "qmlstyledtext_p.h" /* QmlStyledText supports few tags: - bold - italic
- new line The opening and closing tags must be correctly nested. */ QT_BEGIN_NAMESPACE class QmlStyledTextPrivate { public: QmlStyledTextPrivate(const QString &t, QTextLayout &l) : text(t), layout(l), baseFont(layout.font()) {} void parse(); bool parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format); bool parseCloseTag(const QChar *&ch, const QString &textIn); void parseEntity(const QChar *&ch, const QString &textIn, QString &textOut); bool parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format); QPair parseAttribute(const QChar *&ch, const QString &textIn); QStringRef parseValue(const QChar *&ch, const QString &textIn); inline void skipSpace(const QChar *&ch) { while (ch->isSpace() && !ch->isNull()) ++ch; } QString text; QTextLayout &layout; QFont baseFont; static const QChar lessThan; static const QChar greaterThan; static const QChar equals; static const QChar singleQuote; static const QChar doubleQuote; static const QChar slash; static const QChar ampersand; }; const QChar QmlStyledTextPrivate::lessThan(QLatin1Char('<')); const QChar QmlStyledTextPrivate::greaterThan(QLatin1Char('>')); const QChar QmlStyledTextPrivate::equals(QLatin1Char('=')); const QChar QmlStyledTextPrivate::singleQuote(QLatin1Char('\'')); const QChar QmlStyledTextPrivate::doubleQuote(QLatin1Char('\"')); const QChar QmlStyledTextPrivate::slash(QLatin1Char('/')); const QChar QmlStyledTextPrivate::ampersand(QLatin1Char('&')); QmlStyledText::QmlStyledText(const QString &string, QTextLayout &layout) : d(new QmlStyledTextPrivate(string, layout)) { } QmlStyledText::~QmlStyledText() { delete d; } void QmlStyledText::parse(const QString &string, QTextLayout &layout) { QmlStyledText styledText(string, layout); styledText.d->parse(); } void QmlStyledTextPrivate::parse() { QList ranges; QStack formatStack; QString drawText; drawText.reserve(text.count()); int textStart = 0; int textLength = 0; int rangeStart = 0; const QChar *ch = text.constData(); while (!ch->isNull()) { if (*ch == lessThan) { if (textLength) drawText.append(QStringRef(&text, textStart, textLength)); if (rangeStart != drawText.length() && formatStack.count()) { QTextLayout::FormatRange formatRange; formatRange.format = formatStack.top(); formatRange.start = rangeStart; formatRange.length = drawText.length() - rangeStart; ranges.append(formatRange); } rangeStart = drawText.length(); ++ch; if (*ch == slash) { ++ch; if (parseCloseTag(ch, text)) formatStack.pop(); } else { QTextCharFormat format; if (formatStack.count()) format = formatStack.top(); else format.setFont(baseFont); if (parseTag(ch, text, drawText, format)) formatStack.push(format); } textStart = ch - text.constData() + 1; textLength = 0; } else if (*ch == ampersand) { ++ch; drawText.append(QStringRef(&text, textStart, textLength)); parseEntity(ch, text, drawText); textStart = ch - text.constData() + 1; textLength = 0; } else { ++textLength; } ++ch; } if (textLength) drawText.append(QStringRef(&text, textStart, textLength)); if (rangeStart != drawText.length() && formatStack.count()) { QTextLayout::FormatRange formatRange; formatRange.format = formatStack.top(); formatRange.start = rangeStart; formatRange.length = drawText.length() - rangeStart; ranges.append(formatRange); } layout.setText(drawText); layout.setAdditionalFormats(ranges); } bool QmlStyledTextPrivate::parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format) { skipSpace(ch); int tagStart = ch - textIn.constData(); int tagLength = 0; while (!ch->isNull()) { if (*ch == greaterThan) { QStringRef tag(&textIn, tagStart, tagLength); const QChar char0 = tag.at(0); if (char0 == QLatin1Char('b')) { if (tagLength == 1) { format.setFontWeight(QFont::Bold); return true; } else if (tagLength == 2 && tag.at(1) == QLatin1Char('r')) { textOut.append(QChar(QChar::LineSeparator)); return true; } } else if (char0 == QLatin1Char('i')) { if (tagLength == 1) { format.setFontItalic(true); return true; } } return false; } else if (ch->isSpace()) { // may have params. QStringRef tag(&textIn, tagStart, tagLength); if (tag == QLatin1String("font")) return parseFontAttributes(ch, textIn, format); if (*ch == greaterThan || ch->isNull()) continue; } else if (*ch != slash){ tagLength++; } ++ch; } return false; } bool QmlStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn) { skipSpace(ch); int tagStart = ch - textIn.constData(); int tagLength = 0; while (!ch->isNull()) { if (*ch == greaterThan) { QStringRef tag(&textIn, tagStart, tagLength); const QChar char0 = tag.at(0); if (char0 == QLatin1Char('b')) { if (tagLength == 1) return true; else if (tag.at(1) == QLatin1Char('r') && tagLength == 2) return true; } else if (char0 == QLatin1Char('i')) { if (tagLength == 1) return true; } else if (tag == QLatin1String("font")) { return true; } return false; } else if (!ch->isSpace()){ tagLength++; } ++ch; } return false; } void QmlStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut) { int entityStart = ch - textIn.constData(); int entityLength = 0; while (!ch->isNull()) { if (*ch == QLatin1Char(';')) { QStringRef entity(&textIn, entityStart, entityLength); if (entity == QLatin1String("gt")) textOut += QChar(62); else if (entity == QLatin1String("lt")) textOut += QChar(60); else if (entity == QLatin1String("amp")) textOut += QChar(38); return; } ++entityLength; ++ch; } } bool QmlStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format) { bool valid = false; QPair attr; do { attr = parseAttribute(ch, textIn); if (attr.first == QLatin1String("color")) { valid = true; format.setForeground(QColor(attr.second.toString())); } else if (attr.first == QLatin1String("size")) { valid = true; int size = attr.second.toString().toInt(); if (attr.second.at(0) == QLatin1Char('-') || attr.second.at(0) == QLatin1Char('+')) size += 3; if (size >= 1 && size <= 7) { static const qreal scaling[] = { 0.7, 0.8, 1.0, 1.2, 1.5, 2.0, 2.4 }; format.setFontPointSize(baseFont.pointSize() * scaling[size-1]); } } } while (!ch->isNull() && !attr.first.isEmpty()); return valid; } QPair QmlStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn) { skipSpace(ch); int attrStart = ch - textIn.constData(); int attrLength = 0; while (!ch->isNull()) { if (*ch == greaterThan) { break; } else if (*ch == equals) { ++ch; if (*ch != singleQuote && *ch != doubleQuote) { while (*ch != greaterThan && !ch->isNull()) ++ch; break; } ++ch; if (!attrLength) break; QStringRef attr(&textIn, attrStart, attrLength); QStringRef val = parseValue(ch, textIn); if (!val.isEmpty()) return QPair(attr,val); break; } else { ++attrLength; } ++ch; } return QPair(); } QStringRef QmlStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn) { int valStart = ch - textIn.constData(); int valLength = 0; while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) { ++valLength; ++ch; } if (ch->isNull()) return QStringRef(); ++ch; // skip quote return QStringRef(&textIn, valStart, valLength); } QT_END_NAMESPACE