Initial import from the monolithic Qt.
[profile/ivi/qtdeclarative.git] / src / declarative / util / qdeclarativestyledtext.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <QStack>
43 #include <QVector>
44 #include <QPainter>
45 #include <QTextLayout>
46 #include <QDebug>
47 #include <qmath.h>
48 #include "private/qdeclarativestyledtext_p.h"
49
50 /*
51     QDeclarativeStyledText supports few tags:
52
53     <b></b> - bold
54     <i></i> - italic
55     <br> - new line
56     <font color="color_name" size="1-7"></font>
57
58     The opening and closing tags must be correctly nested.
59 */
60
61 QT_BEGIN_NAMESPACE
62
63 class QDeclarativeStyledTextPrivate
64 {
65 public:
66     QDeclarativeStyledTextPrivate(const QString &t, QTextLayout &l) : text(t), layout(l), baseFont(layout.font()) {}
67
68     void parse();
69     bool parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format);
70     bool parseCloseTag(const QChar *&ch, const QString &textIn);
71     void parseEntity(const QChar *&ch, const QString &textIn, QString &textOut);
72     bool parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format);
73     QPair<QStringRef,QStringRef> parseAttribute(const QChar *&ch, const QString &textIn);
74     QStringRef parseValue(const QChar *&ch, const QString &textIn);
75
76     inline void skipSpace(const QChar *&ch) {
77         while (ch->isSpace() && !ch->isNull())
78             ++ch;
79     }
80
81     QString text;
82     QTextLayout &layout;
83     QFont baseFont;
84
85     static const QChar lessThan;
86     static const QChar greaterThan;
87     static const QChar equals;
88     static const QChar singleQuote;
89     static const QChar doubleQuote;
90     static const QChar slash;
91     static const QChar ampersand;
92 };
93
94 const QChar QDeclarativeStyledTextPrivate::lessThan(QLatin1Char('<'));
95 const QChar QDeclarativeStyledTextPrivate::greaterThan(QLatin1Char('>'));
96 const QChar QDeclarativeStyledTextPrivate::equals(QLatin1Char('='));
97 const QChar QDeclarativeStyledTextPrivate::singleQuote(QLatin1Char('\''));
98 const QChar QDeclarativeStyledTextPrivate::doubleQuote(QLatin1Char('\"'));
99 const QChar QDeclarativeStyledTextPrivate::slash(QLatin1Char('/'));
100 const QChar QDeclarativeStyledTextPrivate::ampersand(QLatin1Char('&'));
101
102 QDeclarativeStyledText::QDeclarativeStyledText(const QString &string, QTextLayout &layout)
103 : d(new QDeclarativeStyledTextPrivate(string, layout))
104 {
105 }
106
107 QDeclarativeStyledText::~QDeclarativeStyledText()
108 {
109     delete d;
110 }
111
112 void QDeclarativeStyledText::parse(const QString &string, QTextLayout &layout)
113 {
114     if (string.isEmpty())
115         return;
116     QDeclarativeStyledText styledText(string, layout);
117     styledText.d->parse();
118 }
119
120 void QDeclarativeStyledTextPrivate::parse()
121 {
122     QList<QTextLayout::FormatRange> ranges;
123     QStack<QTextCharFormat> formatStack;
124
125     QString drawText;
126     drawText.reserve(text.count());
127
128     int textStart = 0;
129     int textLength = 0;
130     int rangeStart = 0;
131     const QChar *ch = text.constData();
132     while (!ch->isNull()) {
133         if (*ch == lessThan) {
134             if (textLength)
135                 drawText.append(QStringRef(&text, textStart, textLength));
136             if (rangeStart != drawText.length() && formatStack.count()) {
137                 QTextLayout::FormatRange formatRange;
138                 formatRange.format = formatStack.top();
139                 formatRange.start = rangeStart;
140                 formatRange.length = drawText.length() - rangeStart;
141                 ranges.append(formatRange);
142             }
143             rangeStart = drawText.length();
144             ++ch;
145             if (*ch == slash) {
146                 ++ch;
147                 if (parseCloseTag(ch, text)) {
148                     if (formatStack.count())
149                         formatStack.pop();
150                 }
151             } else {
152                 QTextCharFormat format;
153                 if (formatStack.count())
154                     format = formatStack.top();
155                 if (parseTag(ch, text, drawText, format))
156                     formatStack.push(format);
157             }
158             textStart = ch - text.constData() + 1;
159             textLength = 0;
160         } else if (*ch == ampersand) {
161             ++ch;
162             drawText.append(QStringRef(&text, textStart, textLength));
163             parseEntity(ch, text, drawText);
164             textStart = ch - text.constData() + 1;
165             textLength = 0;
166         } else {
167             ++textLength;
168         }
169         if (!ch->isNull())
170             ++ch;
171     }
172     if (textLength)
173         drawText.append(QStringRef(&text, textStart, textLength));
174     if (rangeStart != drawText.length() && formatStack.count()) {
175         QTextLayout::FormatRange formatRange;
176         formatRange.format = formatStack.top();
177         formatRange.start = rangeStart;
178         formatRange.length = drawText.length() - rangeStart;
179         ranges.append(formatRange);
180     }
181
182     layout.setText(drawText);
183     layout.setAdditionalFormats(ranges);
184 }
185
186 bool QDeclarativeStyledTextPrivate::parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format)
187 {
188     skipSpace(ch);
189
190     int tagStart = ch - textIn.constData();
191     int tagLength = 0;
192     while (!ch->isNull()) {
193         if (*ch == greaterThan) {
194             QStringRef tag(&textIn, tagStart, tagLength);
195             const QChar char0 = tag.at(0);
196             if (char0 == QLatin1Char('b')) {
197                 if (tagLength == 1)
198                     format.setFontWeight(QFont::Bold);
199                 else if (tagLength == 2 && tag.at(1) == QLatin1Char('r')) {
200                     textOut.append(QChar(QChar::LineSeparator));
201                     return false;
202                 }
203             } else if (char0 == QLatin1Char('i')) {
204                 if (tagLength == 1)
205                     format.setFontItalic(true);
206             }
207             return true;
208         } else if (ch->isSpace()) {
209             // may have params.
210             QStringRef tag(&textIn, tagStart, tagLength);
211             if (tag == QLatin1String("font"))
212                 return parseFontAttributes(ch, textIn, format);
213             if (*ch == greaterThan || ch->isNull())
214                 continue;
215         } else if (*ch != slash){
216             tagLength++;
217         }
218         ++ch;
219     }
220
221     return false;
222 }
223
224 bool QDeclarativeStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn)
225 {
226     skipSpace(ch);
227
228     int tagStart = ch - textIn.constData();
229     int tagLength = 0;
230     while (!ch->isNull()) {
231         if (*ch == greaterThan) {
232             QStringRef tag(&textIn, tagStart, tagLength);
233             const QChar char0 = tag.at(0);
234             if (char0 == QLatin1Char('b')) {
235                 if (tagLength == 1)
236                     return true;
237                 else if (tag.at(1) == QLatin1Char('r') && tagLength == 2)
238                     return true;
239             } else if (char0 == QLatin1Char('i')) {
240                 if (tagLength == 1)
241                     return true;
242             } else if (tag == QLatin1String("font")) {
243                 return true;
244             }
245             return false;
246         } else if (!ch->isSpace()){
247             tagLength++;
248         }
249         ++ch;
250     }
251
252     return false;
253 }
254
255 void QDeclarativeStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut)
256 {
257     int entityStart = ch - textIn.constData();
258     int entityLength = 0;
259     while (!ch->isNull()) {
260         if (*ch == QLatin1Char(';')) {
261             QStringRef entity(&textIn, entityStart, entityLength);
262             if (entity == QLatin1String("gt"))
263                 textOut += QChar(62);
264             else if (entity == QLatin1String("lt"))
265                 textOut += QChar(60);
266             else if (entity == QLatin1String("amp"))
267                 textOut += QChar(38);
268             return;
269         }
270         ++entityLength;
271         ++ch;
272     }
273 }
274
275 bool QDeclarativeStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
276 {
277     bool valid = false;
278     QPair<QStringRef,QStringRef> attr;
279     do {
280         attr = parseAttribute(ch, textIn);
281         if (attr.first == QLatin1String("color")) {
282             valid = true;
283             format.setForeground(QColor(attr.second.toString()));
284         } else if (attr.first == QLatin1String("size")) {
285             valid = true;
286             int size = attr.second.toString().toInt();
287             if (attr.second.at(0) == QLatin1Char('-') || attr.second.at(0) == QLatin1Char('+'))
288                 size += 3;
289             if (size >= 1 && size <= 7) {
290                 static const qreal scaling[] = { 0.7, 0.8, 1.0, 1.2, 1.5, 2.0, 2.4 };
291                 format.setFontPointSize(baseFont.pointSize() * scaling[size-1]);
292             }
293         }
294     } while (!ch->isNull() && !attr.first.isEmpty());
295
296     return valid;
297 }
298
299 QPair<QStringRef,QStringRef> QDeclarativeStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn)
300 {
301     skipSpace(ch);
302
303     int attrStart = ch - textIn.constData();
304     int attrLength = 0;
305     while (!ch->isNull()) {
306         if (*ch == greaterThan) {
307             break;
308         } else if (*ch == equals) {
309             ++ch;
310             if (*ch != singleQuote && *ch != doubleQuote) {
311                 while (*ch != greaterThan && !ch->isNull())
312                     ++ch;
313                 break;
314             }
315             ++ch;
316             if (!attrLength)
317                 break;
318             QStringRef attr(&textIn, attrStart, attrLength);
319             QStringRef val = parseValue(ch, textIn);
320             if (!val.isEmpty())
321                 return QPair<QStringRef,QStringRef>(attr,val);
322             break;
323         } else {
324             ++attrLength;
325         }
326         ++ch;
327     }
328
329     return QPair<QStringRef,QStringRef>();
330 }
331
332 QStringRef QDeclarativeStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn)
333 {
334     int valStart = ch - textIn.constData();
335     int valLength = 0;
336     while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) {
337         ++valLength;
338         ++ch;
339     }
340     if (ch->isNull())
341         return QStringRef();
342     ++ch; // skip quote
343
344     return QStringRef(&textIn, valStart, valLength);
345 }
346
347 QT_END_NAMESPACE