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