1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <QStack> |
41 | #include <QVector> |
42 | #include <QPainter> |
43 | #include <QTextLayout> |
44 | #include <QDebug> |
45 | #include <qmath.h> |
46 | #include "qquickstyledtext_p.h" |
47 | #include <QQmlContext> |
48 | |
49 | /* |
50 | QQuickStyledText supports few tags: |
51 | |
52 | <b></b> - bold |
53 | <del></del> - strike out (removed content) |
54 | <s></s> - strike out (no longer accurate or no longer relevant content) |
55 | <strong></strong> - bold |
56 | <i></i> - italic |
57 | <br> - new line |
58 | <p> - paragraph |
59 | <u> - underlined text |
60 | <font color="color_name" size="1-7"></font> |
61 | <h1> to <h6> - headers |
62 | <a href=""> - anchor |
63 | <ol type="">, <ul type=""> and <li> - ordered and unordered lists |
64 | <pre></pre> - preformated |
65 | <img src=""> - images |
66 | |
67 | The opening and closing tags must be correctly nested. |
68 | */ |
69 | |
70 | QT_BEGIN_NAMESPACE |
71 | |
72 | Q_GUI_EXPORT int qt_defaultDpi(); |
73 | |
74 | class QQuickStyledTextPrivate |
75 | { |
76 | public: |
77 | enum ListType { Ordered, Unordered }; |
78 | enum ListFormat { Bullet, Disc, Square, Decimal, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman }; |
79 | |
80 | struct List { |
81 | int level; |
82 | ListType type; |
83 | ListFormat format; |
84 | }; |
85 | |
86 | QQuickStyledTextPrivate(const QString &t, QTextLayout &l, |
87 | QList<QQuickStyledTextImgTag*> &imgTags, |
88 | const QUrl &baseUrl, |
89 | QQmlContext *context, |
90 | bool preloadImages, |
91 | bool *fontSizeModified) |
92 | : text(t), layout(l), imgTags(&imgTags), baseFont(layout.font()), baseUrl(baseUrl), hasNewLine(true), nbImages(0), updateImagePositions(false) |
93 | , preFormat(false), prependSpace(false), hasSpace(true), preloadImages(preloadImages), fontSizeModified(fontSizeModified), context(context) |
94 | { |
95 | } |
96 | |
97 | void parse(); |
98 | void appendText(const QString &textIn, int start, int length, QString &textOut); |
99 | bool parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format); |
100 | bool parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut); |
101 | void parseEntity(const QChar *&ch, const QString &textIn, QString &textOut); |
102 | bool parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format); |
103 | bool parseOrderedListAttributes(const QChar *&ch, const QString &textIn); |
104 | bool parseUnorderedListAttributes(const QChar *&ch, const QString &textIn); |
105 | bool parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format); |
106 | void parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut); |
107 | QPair<QStringRef,QStringRef> parseAttribute(const QChar *&ch, const QString &textIn); |
108 | QStringRef parseValue(const QChar *&ch, const QString &textIn); |
109 | void setFontSize(int size, QTextCharFormat &format); |
110 | |
111 | inline void skipSpace(const QChar *&ch) { |
112 | while (ch->isSpace() && !ch->isNull()) |
113 | ++ch; |
114 | } |
115 | |
116 | static QString toAlpha(int value, bool upper); |
117 | static QString toRoman(int value, bool upper); |
118 | |
119 | QString text; |
120 | QTextLayout &layout; |
121 | QList<QQuickStyledTextImgTag*> *imgTags; |
122 | QFont baseFont; |
123 | QStack<List> listStack; |
124 | QUrl baseUrl; |
125 | bool hasNewLine; |
126 | int nbImages; |
127 | bool updateImagePositions; |
128 | bool preFormat; |
129 | bool prependSpace; |
130 | bool hasSpace; |
131 | bool preloadImages; |
132 | bool *fontSizeModified; |
133 | QQmlContext *context; |
134 | |
135 | static const QChar lessThan; |
136 | static const QChar greaterThan; |
137 | static const QChar equals; |
138 | static const QChar singleQuote; |
139 | static const QChar doubleQuote; |
140 | static const QChar slash; |
141 | static const QChar ampersand; |
142 | static const QChar bullet; |
143 | static const QChar disc; |
144 | static const QChar square; |
145 | static const QChar lineFeed; |
146 | static const QChar space; |
147 | static const int tabsize = 6; |
148 | }; |
149 | |
150 | const QChar QQuickStyledTextPrivate::lessThan(QLatin1Char('<')); |
151 | const QChar QQuickStyledTextPrivate::greaterThan(QLatin1Char('>')); |
152 | const QChar QQuickStyledTextPrivate::equals(QLatin1Char('=')); |
153 | const QChar QQuickStyledTextPrivate::singleQuote(QLatin1Char('\'')); |
154 | const QChar QQuickStyledTextPrivate::doubleQuote(QLatin1Char('\"')); |
155 | const QChar QQuickStyledTextPrivate::slash(QLatin1Char('/')); |
156 | const QChar QQuickStyledTextPrivate::ampersand(QLatin1Char('&')); |
157 | const QChar QQuickStyledTextPrivate::bullet(0x2022); |
158 | const QChar QQuickStyledTextPrivate::disc(0x25e6); |
159 | const QChar QQuickStyledTextPrivate::square(0x25a1); |
160 | const QChar QQuickStyledTextPrivate::lineFeed(QLatin1Char('\n')); |
161 | const QChar QQuickStyledTextPrivate::space(QLatin1Char(' ')); |
162 | |
163 | QQuickStyledText::QQuickStyledText(const QString &string, QTextLayout &layout, |
164 | QList<QQuickStyledTextImgTag*> &imgTags, |
165 | const QUrl &baseUrl, |
166 | QQmlContext *context, |
167 | bool preloadImages, |
168 | bool *fontSizeModified) |
169 | : d(new QQuickStyledTextPrivate(string, layout, imgTags, baseUrl, context, preloadImages, fontSizeModified)) |
170 | { |
171 | } |
172 | |
173 | QQuickStyledText::~QQuickStyledText() |
174 | { |
175 | delete d; |
176 | } |
177 | |
178 | void QQuickStyledText::parse(const QString &string, QTextLayout &layout, |
179 | QList<QQuickStyledTextImgTag*> &imgTags, |
180 | const QUrl &baseUrl, |
181 | QQmlContext *context, |
182 | bool preloadImages, |
183 | bool *fontSizeModified) |
184 | { |
185 | if (string.isEmpty()) |
186 | return; |
187 | QQuickStyledText styledText(string, layout, imgTags, baseUrl, context, preloadImages, fontSizeModified); |
188 | styledText.d->parse(); |
189 | } |
190 | |
191 | void QQuickStyledTextPrivate::parse() |
192 | { |
193 | QVector<QTextLayout::FormatRange> ranges; |
194 | QStack<QTextCharFormat> formatStack; |
195 | |
196 | QString drawText; |
197 | drawText.reserve(text.count()); |
198 | |
199 | updateImagePositions = !imgTags->isEmpty(); |
200 | |
201 | int textStart = 0; |
202 | int textLength = 0; |
203 | int rangeStart = 0; |
204 | bool formatChanged = false; |
205 | |
206 | const QChar *ch = text.constData(); |
207 | while (!ch->isNull()) { |
208 | if (*ch == lessThan) { |
209 | if (textLength) { |
210 | appendText(text, textStart, textLength, drawText); |
211 | } else if (prependSpace) { |
212 | drawText.append(space); |
213 | prependSpace = false; |
214 | hasSpace = true; |
215 | } |
216 | |
217 | if (rangeStart != drawText.length() && formatStack.count()) { |
218 | if (formatChanged) { |
219 | QTextLayout::FormatRange formatRange; |
220 | formatRange.format = formatStack.top(); |
221 | formatRange.start = rangeStart; |
222 | formatRange.length = drawText.length() - rangeStart; |
223 | ranges.append(formatRange); |
224 | formatChanged = false; |
225 | } else if (ranges.count()) { |
226 | ranges.last().length += drawText.length() - rangeStart; |
227 | } |
228 | } |
229 | rangeStart = drawText.length(); |
230 | ++ch; |
231 | if (*ch == slash) { |
232 | ++ch; |
233 | if (parseCloseTag(ch, text, drawText)) { |
234 | if (formatStack.count()) { |
235 | formatChanged = true; |
236 | formatStack.pop(); |
237 | } |
238 | } |
239 | } else { |
240 | QTextCharFormat format; |
241 | if (formatStack.count()) |
242 | format = formatStack.top(); |
243 | if (parseTag(ch, text, drawText, format)) { |
244 | formatChanged = true; |
245 | formatStack.push(format); |
246 | } |
247 | } |
248 | textStart = ch - text.constData() + 1; |
249 | textLength = 0; |
250 | } else if (*ch == ampersand) { |
251 | ++ch; |
252 | appendText(text, textStart, textLength, drawText); |
253 | parseEntity(ch, text, drawText); |
254 | textStart = ch - text.constData() + 1; |
255 | textLength = 0; |
256 | } else if (ch->isSpace()) { |
257 | if (textLength) |
258 | appendText(text, textStart, textLength, drawText); |
259 | if (!preFormat) { |
260 | prependSpace = !hasSpace; |
261 | for (const QChar *n = ch + 1; !n->isNull() && n->isSpace(); ++n) |
262 | ch = n; |
263 | hasNewLine = false; |
264 | } else if (*ch == lineFeed) { |
265 | drawText.append(QChar(QChar::LineSeparator)); |
266 | hasNewLine = true; |
267 | } else { |
268 | drawText.append(QChar(QChar::Nbsp)); |
269 | hasNewLine = false; |
270 | } |
271 | textStart = ch - text.constData() + 1; |
272 | textLength = 0; |
273 | } else { |
274 | ++textLength; |
275 | } |
276 | if (!ch->isNull()) |
277 | ++ch; |
278 | } |
279 | if (textLength) |
280 | appendText(text, textStart, textLength, drawText); |
281 | if (rangeStart != drawText.length() && formatStack.count()) { |
282 | if (formatChanged) { |
283 | QTextLayout::FormatRange formatRange; |
284 | formatRange.format = formatStack.top(); |
285 | formatRange.start = rangeStart; |
286 | formatRange.length = drawText.length() - rangeStart; |
287 | ranges.append(formatRange); |
288 | } else if (ranges.count()) { |
289 | ranges.last().length += drawText.length() - rangeStart; |
290 | } |
291 | } |
292 | |
293 | layout.setText(drawText); |
294 | layout.setFormats(ranges); |
295 | } |
296 | |
297 | void QQuickStyledTextPrivate::appendText(const QString &textIn, int start, int length, QString &textOut) |
298 | { |
299 | if (prependSpace) |
300 | textOut.append(space); |
301 | textOut.append(QStringRef(&textIn, start, length)); |
302 | prependSpace = false; |
303 | hasSpace = false; |
304 | hasNewLine = false; |
305 | } |
306 | |
307 | // |
308 | // Calculates and sets the correct font size in points |
309 | // depending on the size multiplier and base font. |
310 | // |
311 | void QQuickStyledTextPrivate::setFontSize(int size, QTextCharFormat &format) |
312 | { |
313 | static const qreal scaling[] = { 0.7, 0.8, 1.0, 1.2, 1.5, 2.0, 2.4 }; |
314 | if (baseFont.pointSizeF() != -1) |
315 | format.setFontPointSize(baseFont.pointSize() * scaling[size - 1]); |
316 | else |
317 | format.setFontPointSize(baseFont.pixelSize() * qreal(72.) / qreal(qt_defaultDpi()) * scaling[size - 1]); |
318 | *fontSizeModified = true; |
319 | } |
320 | |
321 | bool QQuickStyledTextPrivate::parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format) |
322 | { |
323 | skipSpace(ch); |
324 | |
325 | int tagStart = ch - textIn.constData(); |
326 | int tagLength = 0; |
327 | while (!ch->isNull()) { |
328 | if (*ch == greaterThan) { |
329 | if (tagLength == 0) |
330 | return false; |
331 | QStringRef tag(&textIn, tagStart, tagLength); |
332 | const QChar char0 = tag.at(0); |
333 | if (char0 == QLatin1Char('b')) { |
334 | if (tagLength == 1) { |
335 | format.setFontWeight(QFont::Bold); |
336 | return true; |
337 | } else if (tagLength == 2 && tag.at(1) == QLatin1Char('r')) { |
338 | textOut.append(QChar(QChar::LineSeparator)); |
339 | hasSpace = true; |
340 | prependSpace = false; |
341 | return false; |
342 | } |
343 | } else if (char0 == QLatin1Char('i')) { |
344 | if (tagLength == 1) { |
345 | format.setFontItalic(true); |
346 | return true; |
347 | } |
348 | } else if (char0 == QLatin1Char('p')) { |
349 | if (tagLength == 1) { |
350 | if (!hasNewLine) |
351 | textOut.append(QChar::LineSeparator); |
352 | hasSpace = true; |
353 | prependSpace = false; |
354 | } else if (tag == QLatin1String("pre" )) { |
355 | preFormat = true; |
356 | if (!hasNewLine) |
357 | textOut.append(QChar::LineSeparator); |
358 | format.setFontFamily(QString::fromLatin1("Courier New,courier" )); |
359 | format.setFontFixedPitch(true); |
360 | return true; |
361 | } |
362 | } else if (char0 == QLatin1Char('u')) { |
363 | if (tagLength == 1) { |
364 | format.setFontUnderline(true); |
365 | return true; |
366 | } else if (tag == QLatin1String("ul" )) { |
367 | List listItem; |
368 | listItem.level = 0; |
369 | listItem.type = Unordered; |
370 | listItem.format = Bullet; |
371 | listStack.push(listItem); |
372 | } |
373 | } else if (char0 == QLatin1Char('h') && tagLength == 2) { |
374 | int level = tag.at(1).digitValue(); |
375 | if (level >= 1 && level <= 6) { |
376 | if (!hasNewLine) |
377 | textOut.append(QChar::LineSeparator); |
378 | hasSpace = true; |
379 | prependSpace = false; |
380 | setFontSize(7 - level, format); |
381 | format.setFontWeight(QFont::Bold); |
382 | return true; |
383 | } |
384 | } else if (char0 == QLatin1Char('s')) { |
385 | if (tagLength == 1) { |
386 | format.setFontStrikeOut(true); |
387 | return true; |
388 | } else if (tag == QLatin1String("strong" )) { |
389 | format.setFontWeight(QFont::Bold); |
390 | return true; |
391 | } |
392 | } else if (tag == QLatin1String("del" )) { |
393 | format.setFontStrikeOut(true); |
394 | return true; |
395 | } else if (tag == QLatin1String("ol" )) { |
396 | List listItem; |
397 | listItem.level = 0; |
398 | listItem.type = Ordered; |
399 | listItem.format = Decimal; |
400 | listStack.push(listItem); |
401 | } else if (tag == QLatin1String("li" )) { |
402 | if (!hasNewLine) |
403 | textOut.append(QChar(QChar::LineSeparator)); |
404 | if (!listStack.isEmpty()) { |
405 | int count = ++listStack.top().level; |
406 | for (int i = 0; i < listStack.size(); ++i) |
407 | textOut += QString(tabsize, QChar::Nbsp); |
408 | switch (listStack.top().format) { |
409 | case Decimal: |
410 | textOut += QString::number(count) % QLatin1Char('.'); |
411 | break; |
412 | case LowerAlpha: |
413 | textOut += toAlpha(count, false) % QLatin1Char('.'); |
414 | break; |
415 | case UpperAlpha: |
416 | textOut += toAlpha(count, true) % QLatin1Char('.'); |
417 | break; |
418 | case LowerRoman: |
419 | textOut += toRoman(count, false) % QLatin1Char('.'); |
420 | break; |
421 | case UpperRoman: |
422 | textOut += toRoman(count, true) % QLatin1Char('.'); |
423 | break; |
424 | case Bullet: |
425 | textOut += bullet; |
426 | break; |
427 | case Disc: |
428 | textOut += disc; |
429 | break; |
430 | case Square: |
431 | textOut += square; |
432 | break; |
433 | } |
434 | textOut += QString(2, QChar::Nbsp); |
435 | } |
436 | } |
437 | return false; |
438 | } else if (ch->isSpace()) { |
439 | // may have params. |
440 | QStringRef tag(&textIn, tagStart, tagLength); |
441 | if (tag == QLatin1String("font" )) |
442 | return parseFontAttributes(ch, textIn, format); |
443 | if (tag == QLatin1String("ol" )) { |
444 | parseOrderedListAttributes(ch, textIn); |
445 | return false; // doesn't modify format |
446 | } |
447 | if (tag == QLatin1String("ul" )) { |
448 | parseUnorderedListAttributes(ch, textIn); |
449 | return false; // doesn't modify format |
450 | } |
451 | if (tag == QLatin1String("a" )) { |
452 | return parseAnchorAttributes(ch, textIn, format); |
453 | } |
454 | if (tag == QLatin1String("img" )) { |
455 | parseImageAttributes(ch, textIn, textOut); |
456 | return false; |
457 | } |
458 | if (*ch == greaterThan || ch->isNull()) |
459 | continue; |
460 | } else if (*ch != slash) { |
461 | tagLength++; |
462 | } |
463 | ++ch; |
464 | } |
465 | return false; |
466 | } |
467 | |
468 | bool QQuickStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut) |
469 | { |
470 | skipSpace(ch); |
471 | |
472 | int tagStart = ch - textIn.constData(); |
473 | int tagLength = 0; |
474 | while (!ch->isNull()) { |
475 | if (*ch == greaterThan) { |
476 | if (tagLength == 0) |
477 | return false; |
478 | QStringRef tag(&textIn, tagStart, tagLength); |
479 | const QChar char0 = tag.at(0); |
480 | hasNewLine = false; |
481 | if (char0 == QLatin1Char('b')) { |
482 | if (tagLength == 1) |
483 | return true; |
484 | else if (tag.at(1) == QLatin1Char('r') && tagLength == 2) |
485 | return false; |
486 | } else if (char0 == QLatin1Char('i')) { |
487 | if (tagLength == 1) |
488 | return true; |
489 | } else if (char0 == QLatin1Char('a')) { |
490 | if (tagLength == 1) |
491 | return true; |
492 | } else if (char0 == QLatin1Char('p')) { |
493 | if (tagLength == 1) { |
494 | textOut.append(QChar::LineSeparator); |
495 | hasNewLine = true; |
496 | hasSpace = true; |
497 | return false; |
498 | } else if (tag == QLatin1String("pre" )) { |
499 | preFormat = false; |
500 | if (!hasNewLine) |
501 | textOut.append(QChar::LineSeparator); |
502 | hasNewLine = true; |
503 | hasSpace = true; |
504 | return true; |
505 | } |
506 | } else if (char0 == QLatin1Char('u')) { |
507 | if (tagLength == 1) |
508 | return true; |
509 | else if (tag == QLatin1String("ul" )) { |
510 | if (!listStack.isEmpty()) { |
511 | listStack.pop(); |
512 | if (!listStack.count()) |
513 | textOut.append(QChar::LineSeparator); |
514 | } |
515 | return false; |
516 | } |
517 | } else if (char0 == QLatin1Char('h') && tagLength == 2) { |
518 | textOut.append(QChar::LineSeparator); |
519 | hasNewLine = true; |
520 | hasSpace = true; |
521 | return true; |
522 | } else if (tag == QLatin1String("font" )) { |
523 | return true; |
524 | } else if (char0 == QLatin1Char('s')) { |
525 | if (tagLength == 1) { |
526 | return true; |
527 | } else if (tag == QLatin1String("strong" )) { |
528 | return true; |
529 | } |
530 | } else if (tag == QLatin1String("del" )) { |
531 | return true; |
532 | } else if (tag == QLatin1String("ol" )) { |
533 | if (!listStack.isEmpty()) { |
534 | listStack.pop(); |
535 | if (!listStack.count()) |
536 | textOut.append(QChar::LineSeparator); |
537 | } |
538 | return false; |
539 | } else if (tag == QLatin1String("li" )) { |
540 | return false; |
541 | } |
542 | return false; |
543 | } else if (!ch->isSpace()){ |
544 | tagLength++; |
545 | } |
546 | ++ch; |
547 | } |
548 | |
549 | return false; |
550 | } |
551 | |
552 | void QQuickStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut) |
553 | { |
554 | int entityStart = ch - textIn.constData(); |
555 | int entityLength = 0; |
556 | while (!ch->isNull()) { |
557 | if (*ch == QLatin1Char(';')) { |
558 | QStringRef entity(&textIn, entityStart, entityLength); |
559 | if (entity == QLatin1String("gt" )) |
560 | textOut += QChar(62); |
561 | else if (entity == QLatin1String("lt" )) |
562 | textOut += QChar(60); |
563 | else if (entity == QLatin1String("amp" )) |
564 | textOut += QChar(38); |
565 | else if (entity == QLatin1String("quot" )) |
566 | textOut += QChar(34); |
567 | else if (entity == QLatin1String("nbsp" )) |
568 | textOut += QChar(QChar::Nbsp); |
569 | return; |
570 | } else if (*ch == QLatin1Char(' ')) { |
571 | QStringRef entity(&textIn, entityStart - 1, entityLength + 1); |
572 | textOut += entity + *ch; |
573 | return; |
574 | } |
575 | ++entityLength; |
576 | ++ch; |
577 | } |
578 | } |
579 | |
580 | bool QQuickStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format) |
581 | { |
582 | bool valid = false; |
583 | QPair<QStringRef,QStringRef> attr; |
584 | do { |
585 | attr = parseAttribute(ch, textIn); |
586 | if (attr.first == QLatin1String("color" )) { |
587 | valid = true; |
588 | format.setForeground(QColor(attr.second.toString())); |
589 | } else if (attr.first == QLatin1String("size" )) { |
590 | valid = true; |
591 | int size = attr.second.toString().toInt(); |
592 | if (attr.second.at(0) == QLatin1Char('-') || attr.second.at(0) == QLatin1Char('+')) |
593 | size += 3; |
594 | if (size >= 1 && size <= 7) |
595 | setFontSize(size, format); |
596 | } |
597 | } while (!ch->isNull() && !attr.first.isEmpty()); |
598 | |
599 | return valid; |
600 | } |
601 | |
602 | bool QQuickStyledTextPrivate::parseOrderedListAttributes(const QChar *&ch, const QString &textIn) |
603 | { |
604 | bool valid = false; |
605 | |
606 | List listItem; |
607 | listItem.level = 0; |
608 | listItem.type = Ordered; |
609 | listItem.format = Decimal; |
610 | |
611 | QPair<QStringRef,QStringRef> attr; |
612 | do { |
613 | attr = parseAttribute(ch, textIn); |
614 | if (attr.first == QLatin1String("type" )) { |
615 | valid = true; |
616 | if (attr.second == QLatin1String("a" )) |
617 | listItem.format = LowerAlpha; |
618 | else if (attr.second == QLatin1String("A" )) |
619 | listItem.format = UpperAlpha; |
620 | else if (attr.second == QLatin1String("i" )) |
621 | listItem.format = LowerRoman; |
622 | else if (attr.second == QLatin1String("I" )) |
623 | listItem.format = UpperRoman; |
624 | } |
625 | } while (!ch->isNull() && !attr.first.isEmpty()); |
626 | |
627 | listStack.push(listItem); |
628 | return valid; |
629 | } |
630 | |
631 | bool QQuickStyledTextPrivate::parseUnorderedListAttributes(const QChar *&ch, const QString &textIn) |
632 | { |
633 | bool valid = false; |
634 | |
635 | List listItem; |
636 | listItem.level = 0; |
637 | listItem.type = Unordered; |
638 | listItem.format = Bullet; |
639 | |
640 | QPair<QStringRef,QStringRef> attr; |
641 | do { |
642 | attr = parseAttribute(ch, textIn); |
643 | if (attr.first == QLatin1String("type" )) { |
644 | valid = true; |
645 | if (attr.second == QLatin1String("disc" )) |
646 | listItem.format = Disc; |
647 | else if (attr.second == QLatin1String("square" )) |
648 | listItem.format = Square; |
649 | } |
650 | } while (!ch->isNull() && !attr.first.isEmpty()); |
651 | |
652 | listStack.push(listItem); |
653 | return valid; |
654 | } |
655 | |
656 | bool QQuickStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format) |
657 | { |
658 | bool valid = false; |
659 | |
660 | QPair<QStringRef,QStringRef> attr; |
661 | do { |
662 | attr = parseAttribute(ch, textIn); |
663 | if (attr.first == QLatin1String("href" )) { |
664 | format.setAnchorHref(attr.second.toString()); |
665 | format.setAnchor(true); |
666 | format.setFontUnderline(true); |
667 | valid = true; |
668 | } |
669 | } while (!ch->isNull() && !attr.first.isEmpty()); |
670 | |
671 | return valid; |
672 | } |
673 | |
674 | void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut) |
675 | { |
676 | qreal imgWidth = 0.0; |
677 | QFontMetricsF fm(layout.font()); |
678 | const qreal spaceWidth = fm.horizontalAdvance(QChar::Nbsp); |
679 | const bool trailingSpace = textOut.endsWith(space); |
680 | |
681 | if (!updateImagePositions) { |
682 | QQuickStyledTextImgTag *image = new QQuickStyledTextImgTag; |
683 | image->position = textOut.length() + (trailingSpace ? 0 : 1); |
684 | |
685 | QPair<QStringRef,QStringRef> attr; |
686 | do { |
687 | attr = parseAttribute(ch, textIn); |
688 | if (attr.first == QLatin1String("src" )) { |
689 | image->url = QUrl(attr.second.toString()); |
690 | } else if (attr.first == QLatin1String("width" )) { |
691 | image->size.setWidth(attr.second.toString().toInt()); |
692 | } else if (attr.first == QLatin1String("height" )) { |
693 | image->size.setHeight(attr.second.toString().toInt()); |
694 | } else if (attr.first == QLatin1String("align" )) { |
695 | if (attr.second.toString() == QLatin1String("top" )) { |
696 | image->align = QQuickStyledTextImgTag::Top; |
697 | } else if (attr.second.toString() == QLatin1String("middle" )) { |
698 | image->align = QQuickStyledTextImgTag::Middle; |
699 | } |
700 | } |
701 | } while (!ch->isNull() && !attr.first.isEmpty()); |
702 | |
703 | if (preloadImages && !image->size.isValid()) { |
704 | // if we don't know its size but the image is a local image, |
705 | // we load it in the pixmap cache and save its implicit size |
706 | // to avoid a relayout later on. |
707 | QUrl url = baseUrl.resolved(image->url); |
708 | if (url.isLocalFile()) { |
709 | image->pix = new QQuickPixmap(context->engine(), url, image->size); |
710 | if (image->pix && image->pix->isReady()) { |
711 | image->size = image->pix->implicitSize(); |
712 | } else { |
713 | delete image->pix; |
714 | image->pix = nullptr; |
715 | } |
716 | } |
717 | } |
718 | |
719 | imgWidth = image->size.width(); |
720 | image->offset = -std::fmod(imgWidth, spaceWidth) / 2.0; |
721 | imgTags->append(image); |
722 | |
723 | } else { |
724 | // if we already have a list of img tags for this text |
725 | // we only want to update the positions of these tags. |
726 | QQuickStyledTextImgTag *image = imgTags->value(nbImages); |
727 | image->position = textOut.length() + (trailingSpace ? 0 : 1); |
728 | imgWidth = image->size.width(); |
729 | image->offset = -std::fmod(imgWidth, spaceWidth) / 2.0; |
730 | QPair<QStringRef,QStringRef> attr; |
731 | do { |
732 | attr = parseAttribute(ch, textIn); |
733 | } while (!ch->isNull() && !attr.first.isEmpty()); |
734 | nbImages++; |
735 | } |
736 | |
737 | QString padding(qFloor(imgWidth / spaceWidth), QChar::Nbsp); |
738 | if (!trailingSpace) |
739 | textOut += QLatin1Char(' '); |
740 | textOut += padding + QLatin1Char(' '); |
741 | } |
742 | |
743 | QPair<QStringRef,QStringRef> QQuickStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn) |
744 | { |
745 | skipSpace(ch); |
746 | |
747 | int attrStart = ch - textIn.constData(); |
748 | int attrLength = 0; |
749 | while (!ch->isNull()) { |
750 | if (*ch == greaterThan) { |
751 | break; |
752 | } else if (*ch == equals) { |
753 | ++ch; |
754 | if (*ch != singleQuote && *ch != doubleQuote) { |
755 | while (*ch != greaterThan && !ch->isNull()) |
756 | ++ch; |
757 | break; |
758 | } |
759 | ++ch; |
760 | if (!attrLength) |
761 | break; |
762 | QStringRef attr(&textIn, attrStart, attrLength); |
763 | QStringRef val = parseValue(ch, textIn); |
764 | if (!val.isEmpty()) |
765 | return QPair<QStringRef,QStringRef>(attr,val); |
766 | break; |
767 | } else { |
768 | ++attrLength; |
769 | } |
770 | ++ch; |
771 | } |
772 | |
773 | return QPair<QStringRef,QStringRef>(); |
774 | } |
775 | |
776 | QStringRef QQuickStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn) |
777 | { |
778 | int valStart = ch - textIn.constData(); |
779 | int valLength = 0; |
780 | while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) { |
781 | ++valLength; |
782 | ++ch; |
783 | } |
784 | if (ch->isNull()) |
785 | return QStringRef(); |
786 | ++ch; // skip quote |
787 | |
788 | return QStringRef(&textIn, valStart, valLength); |
789 | } |
790 | |
791 | QString QQuickStyledTextPrivate::toAlpha(int value, bool upper) |
792 | { |
793 | const char baseChar = upper ? 'A' : 'a'; |
794 | |
795 | QString result; |
796 | int c = value; |
797 | while (c > 0) { |
798 | c--; |
799 | result.prepend(QChar(baseChar + (c % 26))); |
800 | c /= 26; |
801 | } |
802 | return result; |
803 | } |
804 | |
805 | QString QQuickStyledTextPrivate::toRoman(int value, bool upper) |
806 | { |
807 | QString result = QLatin1String("?" ); |
808 | // works for up to 4999 items |
809 | if (value < 5000) { |
810 | QByteArray romanNumeral; |
811 | |
812 | static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm" ; |
813 | static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM" ; |
814 | QByteArray romanSymbols; |
815 | if (!upper) |
816 | romanSymbols = QByteArray::fromRawData(romanSymbolsLower, sizeof(romanSymbolsLower)); |
817 | else |
818 | romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper)); |
819 | |
820 | int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 }; |
821 | int n = value; |
822 | for (int i = 12; i >= 0; n %= c[i], i--) { |
823 | int q = n / c[i]; |
824 | if (q > 0) { |
825 | int startDigit = i + (i + 3) / 4; |
826 | int numDigits; |
827 | if (i % 4) { |
828 | if ((i - 2) % 4) |
829 | numDigits = 2; |
830 | else |
831 | numDigits = 1; |
832 | } |
833 | else |
834 | numDigits = q; |
835 | romanNumeral.append(romanSymbols.mid(startDigit, numDigits)); |
836 | } |
837 | } |
838 | result = QString::fromLatin1(romanNumeral); |
839 | } |
840 | return result; |
841 | } |
842 | |
843 | QT_END_NAMESPACE |
844 | |