1 /****************************************************************************
3 ** Copyright (C) 2011 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 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
45 #include <QTextLayout>
48 #include "private/qdeclarativestyledtext_p.h"
51 QDeclarativeStyledText supports few tags:
58 <font color="color_name" size="1-7"></font>
59 <h1> to <h6> - headers
61 <ol type="">, <ul type=""> and <li> - ordered and unordered lists
63 The opening and closing tags must be correctly nested.
68 class QDeclarativeStyledTextPrivate
71 enum ListType { Ordered, Unordered };
72 enum ListFormat { Bullet, Disc, Square, Decimal, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman };
80 QDeclarativeStyledTextPrivate(const QString &t, QTextLayout &l)
81 : text(t), layout(l), baseFont(layout.font()), hasNewLine(false)
86 bool parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format);
87 bool parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut);
88 void parseEntity(const QChar *&ch, const QString &textIn, QString &textOut);
89 bool parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format);
90 bool parseOrderedListAttributes(const QChar *&ch, const QString &textIn);
91 bool parseUnorderedListAttributes(const QChar *&ch, const QString &textIn);
92 bool parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format);
93 QPair<QStringRef,QStringRef> parseAttribute(const QChar *&ch, const QString &textIn);
94 QStringRef parseValue(const QChar *&ch, const QString &textIn);
96 inline void skipSpace(const QChar *&ch) {
97 while (ch->isSpace() && !ch->isNull())
101 static QString toAlpha(int value, bool upper);
102 static QString toRoman(int value, bool upper);
107 QStack<List> listStack;
110 static const QChar lessThan;
111 static const QChar greaterThan;
112 static const QChar equals;
113 static const QChar singleQuote;
114 static const QChar doubleQuote;
115 static const QChar slash;
116 static const QChar ampersand;
117 static const QChar bullet;
118 static const QChar disc;
119 static const QChar square;
120 static const int tabsize = 6;
123 const QChar QDeclarativeStyledTextPrivate::lessThan(QLatin1Char('<'));
124 const QChar QDeclarativeStyledTextPrivate::greaterThan(QLatin1Char('>'));
125 const QChar QDeclarativeStyledTextPrivate::equals(QLatin1Char('='));
126 const QChar QDeclarativeStyledTextPrivate::singleQuote(QLatin1Char('\''));
127 const QChar QDeclarativeStyledTextPrivate::doubleQuote(QLatin1Char('\"'));
128 const QChar QDeclarativeStyledTextPrivate::slash(QLatin1Char('/'));
129 const QChar QDeclarativeStyledTextPrivate::ampersand(QLatin1Char('&'));
130 const QChar QDeclarativeStyledTextPrivate::bullet(0x2022);
131 const QChar QDeclarativeStyledTextPrivate::disc(0x25e6);
132 const QChar QDeclarativeStyledTextPrivate::square(0x25a1);
134 QDeclarativeStyledText::QDeclarativeStyledText(const QString &string, QTextLayout &layout)
135 : d(new QDeclarativeStyledTextPrivate(string, layout))
139 QDeclarativeStyledText::~QDeclarativeStyledText()
144 void QDeclarativeStyledText::parse(const QString &string, QTextLayout &layout)
146 if (string.isEmpty())
148 QDeclarativeStyledText styledText(string, layout);
149 styledText.d->parse();
152 void QDeclarativeStyledTextPrivate::parse()
154 QList<QTextLayout::FormatRange> ranges;
155 QStack<QTextCharFormat> formatStack;
158 drawText.reserve(text.count());
163 const QChar *ch = text.constData();
164 while (!ch->isNull()) {
165 if (*ch == lessThan) {
167 QStringRef ref = QStringRef(&text, textStart, textLength);
168 const QChar *c = ref.constData();
169 bool isWhiteSpace = true;
170 for (int i = 0; isWhiteSpace && (i < textLength); ++c, ++i) {
172 isWhiteSpace = false;
175 drawText.append(ref);
179 if (rangeStart != drawText.length() && formatStack.count()) {
180 QTextLayout::FormatRange formatRange;
181 formatRange.format = formatStack.top();
182 formatRange.start = rangeStart;
183 formatRange.length = drawText.length() - rangeStart;
184 ranges.append(formatRange);
186 rangeStart = drawText.length();
190 if (parseCloseTag(ch, text, drawText)) {
191 if (formatStack.count())
195 QTextCharFormat format;
196 if (formatStack.count())
197 format = formatStack.top();
198 if (parseTag(ch, text, drawText, format))
199 formatStack.push(format);
201 textStart = ch - text.constData() + 1;
203 } else if (*ch == ampersand) {
205 drawText.append(QStringRef(&text, textStart, textLength));
206 parseEntity(ch, text, drawText);
207 textStart = ch - text.constData() + 1;
216 drawText.append(QStringRef(&text, textStart, textLength));
217 if (rangeStart != drawText.length() && formatStack.count()) {
218 QTextLayout::FormatRange formatRange;
219 formatRange.format = formatStack.top();
220 formatRange.start = rangeStart;
221 formatRange.length = drawText.length() - rangeStart;
222 ranges.append(formatRange);
225 layout.setText(drawText);
226 layout.setAdditionalFormats(ranges);
229 bool QDeclarativeStyledTextPrivate::parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format)
233 int tagStart = ch - textIn.constData();
235 while (!ch->isNull()) {
236 if (*ch == greaterThan) {
237 QStringRef tag(&textIn, tagStart, tagLength);
238 const QChar char0 = tag.at(0);
239 if (char0 == QLatin1Char('b')) {
241 format.setFontWeight(QFont::Bold);
242 else if (tagLength == 2 && tag.at(1) == QLatin1Char('r')) {
243 textOut.append(QChar(QChar::LineSeparator));
246 } else if (char0 == QLatin1Char('i')) {
248 format.setFontItalic(true);
249 } else if (char0 == QLatin1Char('p')) {
250 if (tagLength == 1) {
252 textOut.append(QChar::LineSeparator);
254 } else if (char0 == QLatin1Char('u')) {
256 format.setFontUnderline(true);
257 else if (tag == QLatin1String("ul")) {
260 listItem.type = Unordered;
261 listItem.format = Bullet;
262 listStack.push(listItem);
264 } else if (char0 == QLatin1Char('h') && tagLength == 2) {
265 int level = tag.at(1).digitValue();
266 if (level >= 1 && level <= 6) {
267 static const qreal scaling[] = { 2.0, 1.5, 1.2, 1.0, 0.8, 0.7 };
269 textOut.append(QChar::LineSeparator);
270 format.setFontPointSize(baseFont.pointSize() * scaling[level - 1]);
271 format.setFontWeight(QFont::Bold);
273 } else if (tag == QLatin1String("ol")) {
276 listItem.type = Ordered;
277 listItem.format = Decimal;
278 listStack.push(listItem);
279 } else if (tag == QLatin1String("li")) {
281 textOut.append(QChar(QChar::LineSeparator));
282 if (!listStack.isEmpty()) {
283 int count = ++listStack.top().level;
284 for (int i = 0; i < listStack.size(); ++i)
285 textOut += QString(tabsize, QChar::Nbsp);
286 switch (listStack.top().format) {
288 textOut += QString::number(count) % QLatin1Char('.');
291 textOut += toAlpha(count, false) % QLatin1Char('.');
294 textOut += toAlpha(count, true) % QLatin1Char('.');
297 textOut += toRoman(count, false) % QLatin1Char('.');
300 textOut += toRoman(count, true) % QLatin1Char('.');
312 textOut += QString(2, QChar::Nbsp);
316 } else if (ch->isSpace()) {
318 QStringRef tag(&textIn, tagStart, tagLength);
319 if (tag == QLatin1String("font"))
320 return parseFontAttributes(ch, textIn, format);
321 if (tag == QLatin1String("ol"))
322 return parseOrderedListAttributes(ch, textIn);
323 if (tag == QLatin1String("ul"))
324 return parseUnorderedListAttributes(ch, textIn);
325 if (tag == QLatin1String("a")) {
326 return parseAnchorAttributes(ch, textIn, format);
328 if (*ch == greaterThan || ch->isNull())
330 } else if (*ch != slash) {
338 bool QDeclarativeStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut)
342 int tagStart = ch - textIn.constData();
344 while (!ch->isNull()) {
345 if (*ch == greaterThan) {
346 QStringRef tag(&textIn, tagStart, tagLength);
347 const QChar char0 = tag.at(0);
349 if (char0 == QLatin1Char('b')) {
352 else if (tag.at(1) == QLatin1Char('r') && tagLength == 2)
354 } else if (char0 == QLatin1Char('i')) {
357 } else if (char0 == QLatin1Char('a')) {
360 } else if (char0 == QLatin1Char('p')) {
361 if (tagLength == 1) {
362 textOut.append(QChar::LineSeparator);
366 } else if (char0 == QLatin1Char('u')) {
369 else if (tag == QLatin1String("ul")) {
370 if (!listStack.isEmpty()) {
372 if (!listStack.count())
373 textOut.append(QChar::LineSeparator);
377 } else if (char0 == QLatin1Char('h') && tagLength == 2) {
378 textOut.append(QChar::LineSeparator);
381 } else if (tag == QLatin1String("font")) {
383 } else if (tag == QLatin1String("ol")) {
384 if (!listStack.isEmpty()) {
386 if (!listStack.count())
387 textOut.append(QChar::LineSeparator);
390 } else if (tag == QLatin1String("li")) {
394 } else if (!ch->isSpace()){
403 void QDeclarativeStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut)
405 int entityStart = ch - textIn.constData();
406 int entityLength = 0;
407 while (!ch->isNull()) {
408 if (*ch == QLatin1Char(';')) {
409 QStringRef entity(&textIn, entityStart, entityLength);
410 if (entity == QLatin1String("gt"))
411 textOut += QChar(62);
412 else if (entity == QLatin1String("lt"))
413 textOut += QChar(60);
414 else if (entity == QLatin1String("amp"))
415 textOut += QChar(38);
423 bool QDeclarativeStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
426 QPair<QStringRef,QStringRef> attr;
428 attr = parseAttribute(ch, textIn);
429 if (attr.first == QLatin1String("color")) {
431 format.setForeground(QColor(attr.second.toString()));
432 } else if (attr.first == QLatin1String("size")) {
434 int size = attr.second.toString().toInt();
435 if (attr.second.at(0) == QLatin1Char('-') || attr.second.at(0) == QLatin1Char('+'))
437 if (size >= 1 && size <= 7) {
438 static const qreal scaling[] = { 0.7, 0.8, 1.0, 1.2, 1.5, 2.0, 2.4 };
439 format.setFontPointSize(baseFont.pointSize() * scaling[size-1]);
442 } while (!ch->isNull() && !attr.first.isEmpty());
447 bool QDeclarativeStyledTextPrivate::parseOrderedListAttributes(const QChar *&ch, const QString &textIn)
453 listItem.type = Ordered;
454 listItem.format = Decimal;
456 QPair<QStringRef,QStringRef> attr;
458 attr = parseAttribute(ch, textIn);
459 if (attr.first == QLatin1String("type")) {
461 if (attr.second == QLatin1String("a"))
462 listItem.format = LowerAlpha;
463 else if (attr.second == QLatin1String("A"))
464 listItem.format = UpperAlpha;
465 else if (attr.second == QLatin1String("i"))
466 listItem.format = LowerRoman;
467 else if (attr.second == QLatin1String("I"))
468 listItem.format = UpperRoman;
470 } while (!ch->isNull() && !attr.first.isEmpty());
472 listStack.push(listItem);
476 bool QDeclarativeStyledTextPrivate::parseUnorderedListAttributes(const QChar *&ch, const QString &textIn)
482 listItem.type = Unordered;
483 listItem.format = Bullet;
485 QPair<QStringRef,QStringRef> attr;
487 attr = parseAttribute(ch, textIn);
488 if (attr.first == QLatin1String("type")) {
490 if (attr.second == QLatin1String("disc"))
491 listItem.format = Disc;
492 else if (attr.second == QLatin1String("square"))
493 listItem.format = Square;
495 } while (!ch->isNull() && !attr.first.isEmpty());
497 listStack.push(listItem);
501 bool QDeclarativeStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
505 QPair<QStringRef,QStringRef> attr;
507 attr = parseAttribute(ch, textIn);
508 if (attr.first == QLatin1String("href")) {
509 format.setAnchorHref(attr.second.toString());
510 format.setAnchor(true);
511 format.setFontUnderline(true);
512 format.setForeground(QColor("blue"));
515 } while (!ch->isNull() && !attr.first.isEmpty());
520 QPair<QStringRef,QStringRef> QDeclarativeStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn)
524 int attrStart = ch - textIn.constData();
526 while (!ch->isNull()) {
527 if (*ch == greaterThan) {
529 } else if (*ch == equals) {
531 if (*ch != singleQuote && *ch != doubleQuote) {
532 while (*ch != greaterThan && !ch->isNull())
539 QStringRef attr(&textIn, attrStart, attrLength);
540 QStringRef val = parseValue(ch, textIn);
542 return QPair<QStringRef,QStringRef>(attr,val);
550 return QPair<QStringRef,QStringRef>();
553 QStringRef QDeclarativeStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn)
555 int valStart = ch - textIn.constData();
557 while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) {
565 return QStringRef(&textIn, valStart, valLength);
568 QString QDeclarativeStyledTextPrivate::toAlpha(int value, bool upper)
570 const char baseChar = upper ? 'A' : 'a';
576 result.prepend(QChar(baseChar + (c % 26)));
582 QString QDeclarativeStyledTextPrivate::toRoman(int value, bool upper)
584 QString result = QLatin1String("?");
585 // works for up to 4999 items
587 QByteArray romanNumeral;
589 static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm";
590 static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM";
591 QByteArray romanSymbols;
593 romanSymbols = QByteArray::fromRawData(romanSymbolsLower, sizeof(romanSymbolsLower));
595 romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper));
597 int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
599 for (int i = 12; i >= 0; n %= c[i], i--) {
602 int startDigit = i + (i + 3) / 4;
612 romanNumeral.append(romanSymbols.mid(startDigit, numDigits));
615 result = QString::fromLatin1(romanNumeral);