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

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