| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 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$ |
| ** GNU Lesser General Public License Usage |
| ** 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. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QStack> |
| #include <QVector> |
| #include <QPainter> |
| #include <QTextLayout> |
| #include <QDebug> |
| #include <qmath.h> |
| #include "private/qdeclarativestyledtext_p.h" |
| |
| /* |
| QDeclarativeStyledText supports few tags: |
| |
| <b></b> - bold |
| <i></i> - italic |
| <br> - new line |
| <font color="color_name" size="1-7"></font> |
| |
| The opening and closing tags must be correctly nested. |
| */ |
| |
| QT_BEGIN_NAMESPACE |
| |
| class QDeclarativeStyledTextPrivate |
| { |
| public: |
| QDeclarativeStyledTextPrivate(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<QStringRef,QStringRef> 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 QDeclarativeStyledTextPrivate::lessThan(QLatin1Char('<')); |
| const QChar QDeclarativeStyledTextPrivate::greaterThan(QLatin1Char('>')); |
| const QChar QDeclarativeStyledTextPrivate::equals(QLatin1Char('=')); |
| const QChar QDeclarativeStyledTextPrivate::singleQuote(QLatin1Char('\'')); |
| const QChar QDeclarativeStyledTextPrivate::doubleQuote(QLatin1Char('\"')); |
| const QChar QDeclarativeStyledTextPrivate::slash(QLatin1Char('/')); |
| const QChar QDeclarativeStyledTextPrivate::ampersand(QLatin1Char('&')); |
| |
| QDeclarativeStyledText::QDeclarativeStyledText(const QString &string, QTextLayout &layout) |
| : d(new QDeclarativeStyledTextPrivate(string, layout)) |
| { |
| } |
| |
| QDeclarativeStyledText::~QDeclarativeStyledText() |
| { |
| delete d; |
| } |
| |
| void QDeclarativeStyledText::parse(const QString &string, QTextLayout &layout) |
| { |
| if (string.isEmpty()) |
| return; |
| QDeclarativeStyledText styledText(string, layout); |
| styledText.d->parse(); |
| } |
| |
| void QDeclarativeStyledTextPrivate::parse() |
| { |
| QList<QTextLayout::FormatRange> ranges; |
| QStack<QTextCharFormat> 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)) { |
| if (formatStack.count()) |
| formatStack.pop(); |
| } |
| } else { |
| QTextCharFormat format; |
| if (formatStack.count()) |
| format = formatStack.top(); |
| 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; |
| } |
| if (!ch->isNull()) |
| ++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 QDeclarativeStyledTextPrivate::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); |
| else if (tagLength == 2 && tag.at(1) == QLatin1Char('r')) { |
| textOut.append(QChar(QChar::LineSeparator)); |
| return false; |
| } |
| } else if (char0 == QLatin1Char('i')) { |
| if (tagLength == 1) |
| format.setFontItalic(true); |
| } |
| return true; |
| } 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 QDeclarativeStyledTextPrivate::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 QDeclarativeStyledTextPrivate::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 QDeclarativeStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format) |
| { |
| bool valid = false; |
| QPair<QStringRef,QStringRef> 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<QStringRef,QStringRef> QDeclarativeStyledTextPrivate::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<QStringRef,QStringRef>(attr,val); |
| break; |
| } else { |
| ++attrLength; |
| } |
| ++ch; |
| } |
| |
| return QPair<QStringRef,QStringRef>(); |
| } |
| |
| QStringRef QDeclarativeStyledTextPrivate::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 |