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