/**************************************************************************** ** ** 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 #include #include #include #include #include #include "qdeclarativestyledtext_p.h" /* QDeclarativeStyledText supports few tags: - bold - italic
- new line

- paragraph - underlined text

to
- headers - anchor
    ,
      and
    • - ordered and unordered lists The opening and closing tags must be correctly nested. */ QT_BEGIN_NAMESPACE class QDeclarativeStyledTextPrivate { public: enum ListType { Ordered, Unordered }; enum ListFormat { Bullet, Disc, Square, Decimal, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman }; struct List { int level; ListType type; ListFormat format; }; QDeclarativeStyledTextPrivate(const QString &t, QTextLayout &l) : text(t), layout(l), baseFont(layout.font()), hasNewLine(false) { } void parse(); bool parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format); bool parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut); void parseEntity(const QChar *&ch, const QString &textIn, QString &textOut); bool parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format); bool parseOrderedListAttributes(const QChar *&ch, const QString &textIn); bool parseUnorderedListAttributes(const QChar *&ch, const QString &textIn); bool parseAnchorAttributes(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; } static QString toAlpha(int value, bool upper); static QString toRoman(int value, bool upper); QString text; QTextLayout &layout; QFont baseFont; QStack listStack; bool hasNewLine; 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; static const QChar bullet; static const QChar disc; static const QChar square; static const int tabsize = 6; }; 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('&')); const QChar QDeclarativeStyledTextPrivate::bullet(0x2022); const QChar QDeclarativeStyledTextPrivate::disc(0x25e6); const QChar QDeclarativeStyledTextPrivate::square(0x25a1); 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 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) { QStringRef ref = QStringRef(&text, textStart, textLength); const QChar *c = ref.constData(); bool isWhiteSpace = true; for (int i = 0; isWhiteSpace && (i < textLength); ++c, ++i) { if (!c->isSpace()) isWhiteSpace = false; } if (!isWhiteSpace) { drawText.append(ref); hasNewLine = false; } } 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, drawText)) { 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) { if (tagLength == 0) return false; 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); } else if (char0 == QLatin1Char('p')) { if (tagLength == 1) { if (!hasNewLine) textOut.append(QChar::LineSeparator); } } else if (char0 == QLatin1Char('u')) { if (tagLength == 1) format.setFontUnderline(true); else if (tag == QLatin1String("ul")) { List listItem; listItem.level = 0; listItem.type = Unordered; listItem.format = Bullet; listStack.push(listItem); } } else if (char0 == QLatin1Char('h') && tagLength == 2) { int level = tag.at(1).digitValue(); if (level >= 1 && level <= 6) { static const qreal scaling[] = { 2.0, 1.5, 1.2, 1.0, 0.8, 0.7 }; if (!hasNewLine) textOut.append(QChar::LineSeparator); format.setFontPointSize(baseFont.pointSize() * scaling[level - 1]); format.setFontWeight(QFont::Bold); } } else if (tag == QLatin1String("ol")) { List listItem; listItem.level = 0; listItem.type = Ordered; listItem.format = Decimal; listStack.push(listItem); } else if (tag == QLatin1String("li")) { if (!hasNewLine) textOut.append(QChar(QChar::LineSeparator)); if (!listStack.isEmpty()) { int count = ++listStack.top().level; for (int i = 0; i < listStack.size(); ++i) textOut += QString(tabsize, QChar::Nbsp); switch (listStack.top().format) { case Decimal: textOut += QString::number(count) % QLatin1Char('.'); break; case LowerAlpha: textOut += toAlpha(count, false) % QLatin1Char('.'); break; case UpperAlpha: textOut += toAlpha(count, true) % QLatin1Char('.'); break; case LowerRoman: textOut += toRoman(count, false) % QLatin1Char('.'); break; case UpperRoman: textOut += toRoman(count, true) % QLatin1Char('.'); break; case Bullet: textOut += bullet; break; case Disc: textOut += disc; break; case Square: textOut += square; break; } textOut += QString(2, QChar::Nbsp); } } return true; } else if (ch->isSpace()) { // may have params. QStringRef tag(&textIn, tagStart, tagLength); if (tag == QLatin1String("font")) return parseFontAttributes(ch, textIn, format); if (tag == QLatin1String("ol")) return parseOrderedListAttributes(ch, textIn); if (tag == QLatin1String("ul")) return parseUnorderedListAttributes(ch, textIn); if (tag == QLatin1String("a")) { return parseAnchorAttributes(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, QString &textOut) { skipSpace(ch); int tagStart = ch - textIn.constData(); int tagLength = 0; while (!ch->isNull()) { if (*ch == greaterThan) { if (tagLength == 0) return false; QStringRef tag(&textIn, tagStart, tagLength); const QChar char0 = tag.at(0); hasNewLine = false; 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 (char0 == QLatin1Char('a')) { if (tagLength == 1) return true; } else if (char0 == QLatin1Char('p')) { if (tagLength == 1) { textOut.append(QChar::LineSeparator); hasNewLine = true; return true; } } else if (char0 == QLatin1Char('u')) { if (tagLength == 1) return true; else if (tag == QLatin1String("ul")) { if (!listStack.isEmpty()) { listStack.pop(); if (!listStack.count()) textOut.append(QChar::LineSeparator); } return true; } } else if (char0 == QLatin1Char('h') && tagLength == 2) { textOut.append(QChar::LineSeparator); hasNewLine = true; return true; } else if (tag == QLatin1String("font")) { return true; } else if (tag == QLatin1String("ol")) { if (!listStack.isEmpty()) { listStack.pop(); if (!listStack.count()) textOut.append(QChar::LineSeparator); } return true; } else if (tag == QLatin1String("li")) { 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 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; } bool QDeclarativeStyledTextPrivate::parseOrderedListAttributes(const QChar *&ch, const QString &textIn) { bool valid = false; List listItem; listItem.level = 0; listItem.type = Ordered; listItem.format = Decimal; QPair attr; do { attr = parseAttribute(ch, textIn); if (attr.first == QLatin1String("type")) { valid = true; if (attr.second == QLatin1String("a")) listItem.format = LowerAlpha; else if (attr.second == QLatin1String("A")) listItem.format = UpperAlpha; else if (attr.second == QLatin1String("i")) listItem.format = LowerRoman; else if (attr.second == QLatin1String("I")) listItem.format = UpperRoman; } } while (!ch->isNull() && !attr.first.isEmpty()); listStack.push(listItem); return valid; } bool QDeclarativeStyledTextPrivate::parseUnorderedListAttributes(const QChar *&ch, const QString &textIn) { bool valid = false; List listItem; listItem.level = 0; listItem.type = Unordered; listItem.format = Bullet; QPair attr; do { attr = parseAttribute(ch, textIn); if (attr.first == QLatin1String("type")) { valid = true; if (attr.second == QLatin1String("disc")) listItem.format = Disc; else if (attr.second == QLatin1String("square")) listItem.format = Square; } } while (!ch->isNull() && !attr.first.isEmpty()); listStack.push(listItem); return valid; } bool QDeclarativeStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format) { bool valid = false; QPair attr; do { attr = parseAttribute(ch, textIn); if (attr.first == QLatin1String("href")) { format.setAnchorHref(attr.second.toString()); format.setAnchor(true); format.setFontUnderline(true); format.setForeground(QColor("blue")); valid = true; } } while (!ch->isNull() && !attr.first.isEmpty()); return valid; } QPair 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(attr,val); break; } else { ++attrLength; } ++ch; } return QPair(); } 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); } QString QDeclarativeStyledTextPrivate::toAlpha(int value, bool upper) { const char baseChar = upper ? 'A' : 'a'; QString result; int c = value; while (c > 0) { c--; result.prepend(QChar(baseChar + (c % 26))); c /= 26; } return result; } QString QDeclarativeStyledTextPrivate::toRoman(int value, bool upper) { QString result = QLatin1String("?"); // works for up to 4999 items if (value < 5000) { QByteArray romanNumeral; static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm"; static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM"; QByteArray romanSymbols; if (!upper) romanSymbols = QByteArray::fromRawData(romanSymbolsLower, sizeof(romanSymbolsLower)); else romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper)); int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 }; int n = value; for (int i = 12; i >= 0; n %= c[i], i--) { int q = n / c[i]; if (q > 0) { int startDigit = i + (i + 3) / 4; int numDigits; if (i % 4) { if ((i - 2) % 4) numDigits = 2; else numDigits = 1; } else numDigits = q; romanNumeral.append(romanSymbols.mid(startDigit, numDigits)); } } result = QString::fromLatin1(romanNumeral); } return result; } QT_END_NAMESPACE