|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 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 else |
|
156 format.setFont(baseFont); |
|
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 QDeclarativeStyledTextPrivate::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 } 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 |