1 /****************************************************************************
3 ** Copyright (C) 2012 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 "qdeclarativestyledtext_p.h"
51 QDeclarativeStyledText supports few tags:
54 <strong></strong> - bold
59 <font color="color_name" size="1-7"></font>
60 <h1> to <h6> - headers
62 <ol type="">, <ul type=""> and <li> - ordered and unordered lists
63 <pre></pre> - preformated
65 The opening and closing tags must be correctly nested.
70 class QDeclarativeStyledTextPrivate
73 enum ListType { Ordered, Unordered };
74 enum ListFormat { Bullet, Disc, Square, Decimal, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman };
82 QDeclarativeStyledTextPrivate(const QString &t, QTextLayout &l)
83 : text(t), layout(l), baseFont(layout.font()), hasNewLine(false)
89 bool parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format);
90 bool parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut);
91 void parseEntity(const QChar *&ch, const QString &textIn, QString &textOut);
92 bool parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format);
93 bool parseOrderedListAttributes(const QChar *&ch, const QString &textIn);
94 bool parseUnorderedListAttributes(const QChar *&ch, const QString &textIn);
95 bool parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format);
96 QPair<QStringRef,QStringRef> parseAttribute(const QChar *&ch, const QString &textIn);
97 QStringRef parseValue(const QChar *&ch, const QString &textIn);
99 inline void skipSpace(const QChar *&ch) {
100 while (ch->isSpace() && !ch->isNull())
104 static QString toAlpha(int value, bool upper);
105 static QString toRoman(int value, bool upper);
110 QStack<List> listStack;
114 static const QChar lessThan;
115 static const QChar greaterThan;
116 static const QChar equals;
117 static const QChar singleQuote;
118 static const QChar doubleQuote;
119 static const QChar slash;
120 static const QChar ampersand;
121 static const QChar bullet;
122 static const QChar disc;
123 static const QChar square;
124 static const int tabsize = 6;
127 const QChar QDeclarativeStyledTextPrivate::lessThan(QLatin1Char('<'));
128 const QChar QDeclarativeStyledTextPrivate::greaterThan(QLatin1Char('>'));
129 const QChar QDeclarativeStyledTextPrivate::equals(QLatin1Char('='));
130 const QChar QDeclarativeStyledTextPrivate::singleQuote(QLatin1Char('\''));
131 const QChar QDeclarativeStyledTextPrivate::doubleQuote(QLatin1Char('\"'));
132 const QChar QDeclarativeStyledTextPrivate::slash(QLatin1Char('/'));
133 const QChar QDeclarativeStyledTextPrivate::ampersand(QLatin1Char('&'));
134 const QChar QDeclarativeStyledTextPrivate::bullet(0x2022);
135 const QChar QDeclarativeStyledTextPrivate::disc(0x25e6);
136 const QChar QDeclarativeStyledTextPrivate::square(0x25a1);
138 QDeclarativeStyledText::QDeclarativeStyledText(const QString &string, QTextLayout &layout)
139 : d(new QDeclarativeStyledTextPrivate(string, layout))
143 QDeclarativeStyledText::~QDeclarativeStyledText()
148 void QDeclarativeStyledText::parse(const QString &string, QTextLayout &layout)
150 if (string.isEmpty())
152 QDeclarativeStyledText styledText(string, layout);
153 styledText.d->parse();
156 void QDeclarativeStyledTextPrivate::parse()
158 QList<QTextLayout::FormatRange> ranges;
159 QStack<QTextCharFormat> formatStack;
162 drawText.reserve(text.count());
167 bool formatChanged = false;
168 const QChar *ch = text.constData();
169 while (!ch->isNull()) {
170 if (*ch == lessThan) {
172 QStringRef ref = QStringRef(&text, textStart, textLength);
173 const QChar *c = ref.constData();
174 bool isWhiteSpace = true;
175 for (int i = 0; isWhiteSpace && (i < textLength); ++c, ++i) {
177 isWhiteSpace = false;
180 drawText.append(ref);
184 if (rangeStart != drawText.length() && formatStack.count()) {
186 QTextLayout::FormatRange formatRange;
187 formatRange.format = formatStack.top();
188 formatRange.start = rangeStart;
189 formatRange.length = drawText.length() - rangeStart;
190 ranges.append(formatRange);
191 formatChanged = false;
192 } else if (ranges.count()) {
193 ranges.last().length += drawText.length() - rangeStart;
196 rangeStart = drawText.length();
200 if (parseCloseTag(ch, text, drawText)) {
201 if (formatStack.count()) {
202 formatChanged = true;
207 QTextCharFormat format;
208 if (formatStack.count())
209 format = formatStack.top();
210 if (parseTag(ch, text, drawText, format)) {
211 formatChanged = true;
212 formatStack.push(format);
215 textStart = ch - text.constData() + 1;
217 } else if (*ch == ampersand) {
219 drawText.append(QStringRef(&text, textStart, textLength));
220 parseEntity(ch, text, drawText);
221 textStart = ch - text.constData() + 1;
223 } else if (preFormat && ch->isSpace()) {
224 drawText.append(QStringRef(&text, textStart, textLength));
225 drawText.append(QChar(QChar::Nbsp));
226 textStart = ch - text.constData() + 1;
235 drawText.append(QStringRef(&text, textStart, textLength));
236 if (rangeStart != drawText.length() && formatStack.count()) {
238 QTextLayout::FormatRange formatRange;
239 formatRange.format = formatStack.top();
240 formatRange.start = rangeStart;
241 formatRange.length = drawText.length() - rangeStart;
242 ranges.append(formatRange);
243 } else if (ranges.count()) {
244 ranges.last().length += drawText.length() - rangeStart;
248 layout.setText(drawText);
249 layout.setAdditionalFormats(ranges);
252 bool QDeclarativeStyledTextPrivate::parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format)
256 int tagStart = ch - textIn.constData();
258 while (!ch->isNull()) {
259 if (*ch == greaterThan) {
262 QStringRef tag(&textIn, tagStart, tagLength);
263 const QChar char0 = tag.at(0);
264 if (char0 == QLatin1Char('b')) {
265 if (tagLength == 1) {
266 format.setFontWeight(QFont::Bold);
268 } else if (tagLength == 2 && tag.at(1) == QLatin1Char('r')) {
269 textOut.append(QChar(QChar::LineSeparator));
272 } else if (char0 == QLatin1Char('i')) {
273 if (tagLength == 1) {
274 format.setFontItalic(true);
277 } else if (char0 == QLatin1Char('p')) {
278 if (tagLength == 1) {
280 textOut.append(QChar::LineSeparator);
281 } else if (tag == QLatin1String("pre")) {
284 textOut.append(QChar::LineSeparator);
285 format.setFontFamily(QString::fromLatin1("Courier New,courier"));
286 format.setFontFixedPitch(true);
289 } else if (char0 == QLatin1Char('u')) {
290 if (tagLength == 1) {
291 format.setFontUnderline(true);
293 } else if (tag == QLatin1String("ul")) {
296 listItem.type = Unordered;
297 listItem.format = Bullet;
298 listStack.push(listItem);
300 } else if (char0 == QLatin1Char('h') && tagLength == 2) {
301 int level = tag.at(1).digitValue();
302 if (level >= 1 && level <= 6) {
303 static const qreal scaling[] = { 2.0, 1.5, 1.2, 1.0, 0.8, 0.7 };
305 textOut.append(QChar::LineSeparator);
306 format.setFontPointSize(baseFont.pointSize() * scaling[level - 1]);
307 format.setFontWeight(QFont::Bold);
310 } else if (tag == QLatin1String("strong")) {
311 format.setFontWeight(QFont::Bold);
313 } else if (tag == QLatin1String("ol")) {
316 listItem.type = Ordered;
317 listItem.format = Decimal;
318 listStack.push(listItem);
319 } else if (tag == QLatin1String("li")) {
321 textOut.append(QChar(QChar::LineSeparator));
322 if (!listStack.isEmpty()) {
323 int count = ++listStack.top().level;
324 for (int i = 0; i < listStack.size(); ++i)
325 textOut += QString(tabsize, QChar::Nbsp);
326 switch (listStack.top().format) {
328 textOut += QString::number(count) % QLatin1Char('.');
331 textOut += toAlpha(count, false) % QLatin1Char('.');
334 textOut += toAlpha(count, true) % QLatin1Char('.');
337 textOut += toRoman(count, false) % QLatin1Char('.');
340 textOut += toRoman(count, true) % QLatin1Char('.');
352 textOut += QString(2, QChar::Nbsp);
356 } else if (ch->isSpace()) {
358 QStringRef tag(&textIn, tagStart, tagLength);
359 if (tag == QLatin1String("font"))
360 return parseFontAttributes(ch, textIn, format);
361 if (tag == QLatin1String("ol")) {
362 parseOrderedListAttributes(ch, textIn);
363 return false; // doesn't modify format
365 if (tag == QLatin1String("ul")) {
366 parseUnorderedListAttributes(ch, textIn);
367 return false; // doesn't modify format
369 if (tag == QLatin1String("a")) {
370 return parseAnchorAttributes(ch, textIn, format);
372 if (*ch == greaterThan || ch->isNull())
374 } else if (*ch != slash) {
382 bool QDeclarativeStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut)
386 int tagStart = ch - textIn.constData();
388 while (!ch->isNull()) {
389 if (*ch == greaterThan) {
392 QStringRef tag(&textIn, tagStart, tagLength);
393 const QChar char0 = tag.at(0);
395 if (char0 == QLatin1Char('b')) {
398 else if (tag.at(1) == QLatin1Char('r') && tagLength == 2)
400 } else if (char0 == QLatin1Char('i')) {
403 } else if (char0 == QLatin1Char('a')) {
406 } else if (char0 == QLatin1Char('p')) {
407 if (tagLength == 1) {
408 textOut.append(QChar::LineSeparator);
411 } else if (tag == QLatin1String("pre")) {
414 textOut.append(QChar::LineSeparator);
418 } else if (char0 == QLatin1Char('u')) {
421 else if (tag == QLatin1String("ul")) {
422 if (!listStack.isEmpty()) {
424 if (!listStack.count())
425 textOut.append(QChar::LineSeparator);
429 } else if (char0 == QLatin1Char('h') && tagLength == 2) {
430 textOut.append(QChar::LineSeparator);
433 } else if (tag == QLatin1String("font")) {
435 } else if (tag == QLatin1String("strong")) {
437 } else if (tag == QLatin1String("ol")) {
438 if (!listStack.isEmpty()) {
440 if (!listStack.count())
441 textOut.append(QChar::LineSeparator);
444 } else if (tag == QLatin1String("li")) {
448 } else if (!ch->isSpace()){
457 void QDeclarativeStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut)
459 int entityStart = ch - textIn.constData();
460 int entityLength = 0;
461 while (!ch->isNull()) {
462 if (*ch == QLatin1Char(';')) {
463 QStringRef entity(&textIn, entityStart, entityLength);
464 if (entity == QLatin1String("gt"))
465 textOut += QChar(62);
466 else if (entity == QLatin1String("lt"))
467 textOut += QChar(60);
468 else if (entity == QLatin1String("amp"))
469 textOut += QChar(38);
477 bool QDeclarativeStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
480 QPair<QStringRef,QStringRef> attr;
482 attr = parseAttribute(ch, textIn);
483 if (attr.first == QLatin1String("color")) {
485 format.setForeground(QColor(attr.second.toString()));
486 } else if (attr.first == QLatin1String("size")) {
488 int size = attr.second.toString().toInt();
489 if (attr.second.at(0) == QLatin1Char('-') || attr.second.at(0) == QLatin1Char('+'))
491 if (size >= 1 && size <= 7) {
492 static const qreal scaling[] = { 0.7, 0.8, 1.0, 1.2, 1.5, 2.0, 2.4 };
493 format.setFontPointSize(baseFont.pointSize() * scaling[size-1]);
496 } while (!ch->isNull() && !attr.first.isEmpty());
501 bool QDeclarativeStyledTextPrivate::parseOrderedListAttributes(const QChar *&ch, const QString &textIn)
507 listItem.type = Ordered;
508 listItem.format = Decimal;
510 QPair<QStringRef,QStringRef> attr;
512 attr = parseAttribute(ch, textIn);
513 if (attr.first == QLatin1String("type")) {
515 if (attr.second == QLatin1String("a"))
516 listItem.format = LowerAlpha;
517 else if (attr.second == QLatin1String("A"))
518 listItem.format = UpperAlpha;
519 else if (attr.second == QLatin1String("i"))
520 listItem.format = LowerRoman;
521 else if (attr.second == QLatin1String("I"))
522 listItem.format = UpperRoman;
524 } while (!ch->isNull() && !attr.first.isEmpty());
526 listStack.push(listItem);
530 bool QDeclarativeStyledTextPrivate::parseUnorderedListAttributes(const QChar *&ch, const QString &textIn)
536 listItem.type = Unordered;
537 listItem.format = Bullet;
539 QPair<QStringRef,QStringRef> attr;
541 attr = parseAttribute(ch, textIn);
542 if (attr.first == QLatin1String("type")) {
544 if (attr.second == QLatin1String("disc"))
545 listItem.format = Disc;
546 else if (attr.second == QLatin1String("square"))
547 listItem.format = Square;
549 } while (!ch->isNull() && !attr.first.isEmpty());
551 listStack.push(listItem);
555 bool QDeclarativeStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
559 QPair<QStringRef,QStringRef> attr;
561 attr = parseAttribute(ch, textIn);
562 if (attr.first == QLatin1String("href")) {
563 format.setAnchorHref(attr.second.toString());
564 format.setAnchor(true);
565 format.setFontUnderline(true);
566 format.setForeground(QColor("blue"));
569 } while (!ch->isNull() && !attr.first.isEmpty());
574 QPair<QStringRef,QStringRef> QDeclarativeStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn)
578 int attrStart = ch - textIn.constData();
580 while (!ch->isNull()) {
581 if (*ch == greaterThan) {
583 } else if (*ch == equals) {
585 if (*ch != singleQuote && *ch != doubleQuote) {
586 while (*ch != greaterThan && !ch->isNull())
593 QStringRef attr(&textIn, attrStart, attrLength);
594 QStringRef val = parseValue(ch, textIn);
596 return QPair<QStringRef,QStringRef>(attr,val);
604 return QPair<QStringRef,QStringRef>();
607 QStringRef QDeclarativeStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn)
609 int valStart = ch - textIn.constData();
611 while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) {
619 return QStringRef(&textIn, valStart, valLength);
622 QString QDeclarativeStyledTextPrivate::toAlpha(int value, bool upper)
624 const char baseChar = upper ? 'A' : 'a';
630 result.prepend(QChar(baseChar + (c % 26)));
636 QString QDeclarativeStyledTextPrivate::toRoman(int value, bool upper)
638 QString result = QLatin1String("?");
639 // works for up to 4999 items
641 QByteArray romanNumeral;
643 static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm";
644 static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM";
645 QByteArray romanSymbols;
647 romanSymbols = QByteArray::fromRawData(romanSymbolsLower, sizeof(romanSymbolsLower));
649 romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper));
651 int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
653 for (int i = 12; i >= 0; n %= c[i], i--) {
656 int startDigit = i + (i + 3) / 4;
666 romanNumeral.append(romanSymbols.mid(startDigit, numDigits));
669 result = QString::fromLatin1(romanNumeral);