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

source code of qtdeclarative/src/quick/util/qquickstyledtext.cpp