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 <QList>
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="" title=""> - anchor with optional tooltip
36 <ol type="">, <ul type=""> and <li> - ordered and unordered lists
37 <pre></pre> - preformated
38 <img src="" title=""> - images, with optional tooltip
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
142int lookupHtmlTag(QStringView tag)
143{
144 return QTextHtmlParser::lookupElement(tag);
145}
146}
147
148QQuickStyledText::QQuickStyledText(const QString &string, QTextLayout &layout,
149 QList<QQuickStyledTextImgTag*> &imgTags,
150 const QUrl &baseUrl,
151 QQmlContext *context,
152 bool preloadImages,
153 bool *fontSizeModified)
154 : d(new QQuickStyledTextPrivate(string, layout, imgTags, baseUrl, context, preloadImages, fontSizeModified))
155{
156}
157
158QQuickStyledText::~QQuickStyledText()
159{
160 delete d;
161}
162
163void QQuickStyledText::parse(const QString &string, QTextLayout &layout,
164 QList<QQuickStyledTextImgTag*> &imgTags,
165 const QUrl &baseUrl,
166 QQmlContext *context,
167 bool preloadImages,
168 bool *fontSizeModified)
169{
170 if (string.isEmpty())
171 return;
172 QQuickStyledText styledText(string, layout, imgTags, baseUrl, context, preloadImages, fontSizeModified);
173 styledText.d->parse();
174}
175
177{
178 QList<QTextLayout::FormatRange> ranges;
179 QStack<QTextCharFormat> formatStack;
180
181 QString drawText;
182 drawText.reserve(text.size());
183
184 updateImagePositions = !imgTags->isEmpty();
185
186 int textStart = 0;
187 int textLength = 0;
188 int rangeStart = 0;
189 bool formatChanged = false;
190
191 const QChar *ch = text.constData();
192 while (!ch->isNull()) {
193 if (*ch == lessThan) {
194 if (textLength) {
195 appendText(text, textStart, textLength, drawText);
196 } else if (prependSpace) {
197 drawText.append(space);
198 prependSpace = false;
199 hasSpace = true;
200 }
201
202 if (rangeStart != drawText.size() && formatStack.size()) {
203 if (formatChanged) {
204 QTextLayout::FormatRange formatRange;
205 formatRange.format = formatStack.top();
206 formatRange.start = rangeStart;
207 formatRange.length = drawText.size() - rangeStart;
208 ranges.append(formatRange);
209 formatChanged = false;
210 } else if (ranges.size()) {
211 ranges.last().length += drawText.size() - rangeStart;
212 }
213 }
214 rangeStart = drawText.size();
215 ++ch;
216 if (*ch == slash) {
217 ++ch;
218 if (parseCloseTag(ch, text, drawText)) {
219 if (formatStack.size()) {
220 formatChanged = true;
221 formatStack.pop();
222 }
223 }
224 } else {
225 QTextCharFormat format;
226 if (formatStack.size())
227 format = formatStack.top();
228 if (parseTag(ch, text, drawText, format)) {
229 formatChanged = true;
230 formatStack.push(format);
231 }
232 }
233 textStart = ch - text.constData() + 1;
234 textLength = 0;
235 } else if (*ch == ampersand) {
236 ++ch;
237 appendText(text, textStart, textLength, drawText);
238 parseEntity(ch, text, drawText);
239 textStart = ch - text.constData() + 1;
240 textLength = 0;
241 } else if (ch->isSpace()) {
242 if (textLength)
243 appendText(text, textStart, textLength, drawText);
244 if (!preFormat) {
246 for (const QChar *n = ch + 1; !n->isNull() && n->isSpace(); ++n)
247 ch = n;
248 hasNewLine = false;
249 } else if (*ch == lineFeed) {
250 drawText.append(QChar(QChar::LineSeparator));
251 hasNewLine = true;
252 } else {
253 drawText.append(QChar(QChar::Nbsp));
254 hasNewLine = false;
255 }
256 textStart = ch - text.constData() + 1;
257 textLength = 0;
258 } else {
259 ++textLength;
260 }
261 if (!ch->isNull())
262 ++ch;
263 }
264 if (textLength)
265 appendText(text, textStart, textLength, drawText);
266 if (rangeStart != drawText.size() && formatStack.size()) {
267 if (formatChanged) {
268 QTextLayout::FormatRange formatRange;
269 formatRange.format = formatStack.top();
270 formatRange.start = rangeStart;
271 formatRange.length = drawText.size() - rangeStart;
272 ranges.append(formatRange);
273 } else if (ranges.size()) {
274 ranges.last().length += drawText.size() - rangeStart;
275 }
276 }
277
278 // Add QTextImageFormat ranges for each inline image so that
279 // QTextEngine sizes the ObjectReplacementCharacter correctly.
280 if (imgTags) {
281 for (int i = 0; i < imgTags->size(); ++i) {
282 const QQuickStyledTextImgTag *image = imgTags->at(i);
283 QTextLayout::FormatRange range;
284 QTextImageFormat imgFmt;
285 if (image->size.isValid()) {
286 imgFmt.setWidth(image->size.width() + QQuickStyledTextImgTag::HMargin * 2);
287 imgFmt.setHeight(image->size.height());
288 }
289 // Ensures line.height() equals the image height, not inflated by font descent.
290 imgFmt.setVerticalAlignment(QTextCharFormat::AlignBaseline);
291 imgFmt.setObjectIndex(i); // index into imgTags, used by image positioning in QQuickText
292 if (!image->tooltip.isEmpty())
293 imgFmt.setToolTip(image->tooltip);
294 range.format = imgFmt;
295 range.start = image->position;
296 range.length = 1;
297 ranges.append(range);
298 }
299 }
300
301 layout.setText(drawText);
302 layout.setFormats(ranges);
303}
304
305void QQuickStyledTextPrivate::appendText(const QString &textIn, int start, int length, QString &textOut)
306{
307 if (prependSpace)
308 textOut.append(space);
309 textOut.append(QStringView(textIn).mid(start, length));
310 prependSpace = false;
311 hasSpace = false;
312 hasNewLine = false;
313}
314
315//
316// Calculates and sets the correct font size in points
317// depending on the size multiplier and base font.
318//
319void QQuickStyledTextPrivate::setFontSize(int size, QTextCharFormat &format)
320{
321 static const qreal scaling[] = { 0.7, 0.8, 1.0, 1.2, 1.5, 2.0, 2.4 };
322 if (baseFont.pointSizeF() != -1)
323 format.setFontPointSize(baseFont.pointSize() * scaling[size - 1]);
324 else
325 format.setFontPointSize(baseFont.pixelSize() * qreal(72.) / qreal(qt_defaultDpi()) * scaling[size - 1]);
326 *fontSizeModified = true;
327}
328
329bool QQuickStyledTextPrivate::parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format)
330{
331 skipSpace(ch);
332
333 int tagStart = ch - textIn.constData();
334 int tagLength = 0;
335 while (!ch->isNull()) {
336 if (*ch == greaterThan) {
337 if (tagLength == 0)
338 return false;
339 auto tag = QStringView(textIn).mid(tagStart, tagLength);
340 switch (lookupHtmlTag(tag)) {
341 case Html_b:
342 format.setFontWeight(QFont::Bold);
343 return true;
344 case Html_br:
345 textOut.append(QChar(QChar::LineSeparator));
346 hasSpace = true;
347 prependSpace = false;
348 return false;
349 case Html_i:
350 format.setFontItalic(true);
351 return true;
352 case Html_p:
353 if (!hasNewLine)
354 textOut.append(QChar::LineSeparator);
355 hasSpace = true;
356 prependSpace = false;
357 return false;
358 case Html_pre:
359 preFormat = true;
360 if (!hasNewLine)
361 textOut.append(QChar::LineSeparator);
362 format.setFontFamilies(QStringList {QString::fromLatin1("Courier New"), QString::fromLatin1("courier")});
363 format.setFontFixedPitch(true);
364 return true;
365 case Html_u:
366 format.setFontUnderline(true);
367 return true;
368 case Html_ul: {
369 List listItem;
370 listItem.level = 0;
371 listItem.type = Unordered;
372 listItem.format = Bullet;
373 listStack.push(listItem);
374 return false;
375 }
376 case Html_h1:
377 case Html_h2:
378 case Html_h3:
379 case Html_h4:
380 case Html_h5:
381 case Html_h6: {
382 int level = tag.at(1).digitValue();
383 if (!hasNewLine)
384 textOut.append(QChar::LineSeparator);
385 hasSpace = true;
386 prependSpace = false;
387 setFontSize(7 - level, format);
388 format.setFontWeight(QFont::Bold);
389 return true;
390 }
391 case Html_s:
392 format.setFontStrikeOut(true);
393 return true;
394 case Html_strong:
395 format.setFontWeight(QFont::Bold);
396 return true;
397 case Html_del:
398 format.setFontStrikeOut(true);
399 return true;
400 case Html_ol: {
401 List listItem;
402 listItem.level = 0;
403 listItem.type = Ordered;
404 listItem.format = Decimal;
405 listStack.push(listItem);
406 return false;
407 }
408 case Html_li:
409 if (!hasNewLine)
410 textOut.append(QChar(QChar::LineSeparator));
411 if (!listStack.isEmpty()) {
412 int count = ++listStack.top().level;
413 for (int i = 0; i < listStack.size(); ++i)
414 textOut += QString(tabsize, QChar::Nbsp);
415 switch (listStack.top().format) {
416 case Decimal:
417 textOut += QString::number(count) % QLatin1Char('.');
418 break;
419 case LowerAlpha:
420 textOut += toAlpha(count, false) % QLatin1Char('.');
421 break;
422 case UpperAlpha:
423 textOut += toAlpha(count, true) % QLatin1Char('.');
424 break;
425 case LowerRoman:
426 textOut += toRoman(count, false) % QLatin1Char('.');
427 break;
428 case UpperRoman:
429 textOut += toRoman(count, true) % QLatin1Char('.');
430 break;
431 case Bullet:
432 textOut += bullet;
433 break;
434 case Disc:
435 textOut += disc;
436 break;
437 case Square:
438 textOut += square;
439 break;
440 }
441 textOut += QString(2, QChar::Nbsp);
442 }
443 return false;
444 default:
445 break;
446 }
447 return false;
448 } else if (ch->isSpace()) {
449 // may have params.
450 auto tag = QStringView(textIn).mid(tagStart, tagLength);
451 switch (lookupHtmlTag(tag)) {
452 case Html_font:
453 return parseFontAttributes(ch, textIn, format);
454 case Html_ol:
455 parseOrderedListAttributes(ch, textIn);
456 return false;
457 case Html_ul:
458 parseUnorderedListAttributes(ch, textIn);
459 return false;
460 case Html_a:
461 return parseAnchorAttributes(ch, textIn, format);
462 case Html_img:
463 parseImageAttributes(ch, textIn, textOut);
464 return false;
465 default:
466 if (*ch == greaterThan || ch->isNull())
467 continue;
468 break;
469 }
470 } else if (*ch != slash) {
471 tagLength++;
472 }
473 ++ch;
474 }
475 return false;
476}
477
478bool QQuickStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut)
479{
480 skipSpace(ch);
481
482 int tagStart = ch - textIn.constData();
483 int tagLength = 0;
484 while (!ch->isNull()) {
485 if (*ch == greaterThan) {
486 if (tagLength == 0)
487 return false;
488 auto tag = QStringView(textIn).mid(tagStart, tagLength);
489 hasNewLine = false;
490 switch (lookupHtmlTag(tag)) {
491 case Html_b:
492 case Html_i:
493 case Html_a:
494 case Html_u:
495 case Html_font:
496 case Html_strong:
497 case Html_del:
498 case Html_s:
499 return true;
500 case Html_br:
501 return false;
502 case Html_p:
503 textOut.append(QChar::LineSeparator);
504 hasNewLine = true;
505 hasSpace = true;
506 return false;
507 case Html_pre:
508 preFormat = false;
509 if (!hasNewLine)
510 textOut.append(QChar::LineSeparator);
511 hasNewLine = true;
512 hasSpace = true;
513 return true;
514 case Html_ul:
515 case Html_ol:
516 if (!listStack.isEmpty()) {
517 listStack.pop();
518 if (!listStack.size())
519 textOut.append(QChar::LineSeparator);
520 }
521 return false;
522 case Html_h1:
523 case Html_h2:
524 case Html_h3:
525 case Html_h4:
526 case Html_h5:
527 case Html_h6:
528 textOut.append(QChar::LineSeparator);
529 hasNewLine = true;
530 hasSpace = true;
531 return true;
532 case Html_li:
533 return false;
534 default:
535 break;
536 }
537 return false;
538 } else if (!ch->isSpace()){
539 tagLength++;
540 }
541 ++ch;
542 }
543
544 return false;
545}
546
547void QQuickStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut)
548{
549 int entityStart = ch - textIn.constData();
550 int entityLength = 0;
551 while (!ch->isNull()) {
552 if (*ch == QLatin1Char(';')) {
553 auto entity = QStringView(textIn).mid(entityStart, entityLength);
554#if QT_CONFIG(texthtmlparser)
555 const QString parsedEntity = QTextHtmlParser::parseEntity(entity);
556 if (!parsedEntity.isNull())
557 textOut += parsedEntity;
558 else
559#endif
560 qCWarning(lcStyledText) << "StyledText doesn't support entity" << entity;
561 return;
562 } else if (*ch == QLatin1Char(' ')) {
563 auto entity = QStringView(textIn).mid(entityStart - 1, entityLength + 1);
564 textOut += entity + *ch;
565 return;
566 }
567 ++entityLength;
568 ++ch;
569 }
570}
571
572bool QQuickStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
573{
574 bool valid = false;
575 std::pair<QStringView,QStringView> attr;
576 do {
577 attr = parseAttribute(ch, textIn);
578 if (is_equal_ignoring_case(attr.first, QLatin1String("color"))) {
579 valid = true;
580 format.setForeground(QColor::fromString(attr.second));
581 } else if (is_equal_ignoring_case(attr.first, QLatin1String("size"))) {
582 valid = true;
583 int size = attr.second.toInt();
584 if (attr.second.at(0) == QLatin1Char('-') || attr.second.at(0) == QLatin1Char('+'))
585 size += 3;
586 if (size >= 1 && size <= 7)
587 setFontSize(size, format);
588 }
589 } while (!ch->isNull() && !attr.first.isEmpty());
590
591 return valid;
592}
593
594bool QQuickStyledTextPrivate::parseOrderedListAttributes(const QChar *&ch, const QString &textIn)
595{
596 bool valid = false;
597
598 List listItem;
599 listItem.level = 0;
600 listItem.type = Ordered;
601 listItem.format = Decimal;
602
603 std::pair<QStringView,QStringView> attr;
604 do {
605 attr = parseAttribute(ch, textIn);
606 if (is_equal_ignoring_case(attr.first, QLatin1String("type"))) {
607 valid = true;
608 if (attr.second == QLatin1String("a"))
609 listItem.format = LowerAlpha;
610 else if (attr.second == QLatin1String("A"))
611 listItem.format = UpperAlpha;
612 else if (attr.second == QLatin1String("i"))
613 listItem.format = LowerRoman;
614 else if (attr.second == QLatin1String("I"))
615 listItem.format = UpperRoman;
616 }
617 } while (!ch->isNull() && !attr.first.isEmpty());
618
619 listStack.push(listItem);
620 return valid;
621}
622
623bool QQuickStyledTextPrivate::parseUnorderedListAttributes(const QChar *&ch, const QString &textIn)
624{
625 bool valid = false;
626
627 List listItem;
628 listItem.level = 0;
629 listItem.type = Unordered;
630 listItem.format = Bullet;
631
632 std::pair<QStringView,QStringView> attr;
633 do {
634 attr = parseAttribute(ch, textIn);
635 if (is_equal_ignoring_case(attr.first, QLatin1String("type"))) {
636 valid = true;
637 if (is_equal_ignoring_case(attr.second, QLatin1String("disc")))
638 listItem.format = Disc;
639 else if (is_equal_ignoring_case(attr.second, QLatin1String("square")))
640 listItem.format = Square;
641 }
642 } while (!ch->isNull() && !attr.first.isEmpty());
643
644 listStack.push(listItem);
645 return valid;
646}
647
648bool QQuickStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
649{
650 bool valid = false;
651
652 std::pair<QStringView,QStringView> attr;
653 do {
654 attr = parseAttribute(ch, textIn);
655 if (is_equal_ignoring_case(attr.first, QLatin1String("href"))) {
656 format.setAnchorHref(attr.second.toString());
657 format.setAnchor(true);
658 format.setFontUnderline(true);
659 valid = true;
660 } else if (is_equal_ignoring_case(attr.first, QLatin1String("title"))) {
661 format.setToolTip(attr.second.toString());
662 valid = true;
663 }
664 } while (!ch->isNull() && !attr.first.isEmpty());
665
666 return valid;
667}
668
669void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut)
670{
672 QQuickStyledTextImgTag *image = new QQuickStyledTextImgTag;
673 image->position = textOut.size();
674
675 std::pair<QStringView,QStringView> attr;
676 do {
677 attr = parseAttribute(ch, textIn);
678 if (is_equal_ignoring_case(attr.first, QLatin1String("src"))) {
679 image->url = QUrl(attr.second.toString());
680 } else if (is_equal_ignoring_case(attr.first, QLatin1String("title"))) {
681 image->tooltip = attr.second.toString();
682 } else if (is_equal_ignoring_case(attr.first, QLatin1String("width"))) {
683 bool ok;
684 int v = attr.second.toInt(&ok);
685 if (ok && v <= QQUICKSTYLEDPARSER_COORD_LIMIT)
686 image->size.setWidth(v);
687 else
688 qCWarning(lcStyledText) << "Invalid width provided for <img>";
689 } else if (is_equal_ignoring_case(attr.first, QLatin1String("height"))) {
690 bool ok;
691 int v = attr.second.toInt(&ok);
692 if (ok && v <= QQUICKSTYLEDPARSER_COORD_LIMIT)
693 image->size.setHeight(v);
694 else
695 qCWarning(lcStyledText) << "Invalid height provided for <img>";
696 } else if (is_equal_ignoring_case(attr.first, QLatin1String("align"))) {
697 if (is_equal_ignoring_case(attr.second, QLatin1String("top"))) {
698 image->align = QQuickStyledTextImgTag::Top;
699 } else if (is_equal_ignoring_case(attr.second, QLatin1String("middle"))) {
700 image->align = QQuickStyledTextImgTag::Middle;
701 }
702 }
703 } while (!ch->isNull() && !attr.first.isEmpty());
704
705 if (preloadImages && !image->size.isValid()) {
706 // if we don't know its size but the image is a local image,
707 // we load it in the pixmap cache and save its implicit size
708 // to avoid a relayout later on.
709 QUrl url = baseUrl.resolved(image->url);
710 if (url.isLocalFile()) {
711 image->pix.reset(new QQuickPixmap(context->engine(), url, QRect(), image->size));
712 if (image->pix && image->pix->isReady()) {
713 image->size = image->pix->implicitSize();
714 } else {
715 image->pix.reset();
716 }
717 }
718 }
719
720 // Return immediately if img tag has invalid url
721 if (!image->url.isValid()) {
722 delete image;
723 qCWarning(lcStyledText) << "StyledText - Invalid base url in img tag";
724 return;
725 }
726
727 imgTags->append(image);
728 } else {
729 // if we already have a list of img tags for this text
730 // we only want to update the positions of these tags.
731 QQuickStyledTextImgTag *image = imgTags->value(nbImages);
732 image->position = textOut.size();
733 std::pair<QStringView,QStringView> attr;
734 do {
735 attr = parseAttribute(ch, textIn);
736 } while (!ch->isNull() && !attr.first.isEmpty());
737 nbImages++;
738 }
739
740 textOut += QChar::ObjectReplacementCharacter;
741 hasSpace = false;
742}
743
744std::pair<QStringView,QStringView> QQuickStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn)
745{
746 skipSpace(ch);
747
748 int attrStart = ch - textIn.constData();
749 int attrLength = 0;
750 while (!ch->isNull()) {
751 if (*ch == greaterThan) {
752 break;
753 } else if (*ch == equals) {
754 ++ch;
755 if (*ch != singleQuote && *ch != doubleQuote) {
756 while (*ch != greaterThan && !ch->isNull())
757 ++ch;
758 break;
759 }
760 ++ch;
761 if (!attrLength)
762 break;
763 auto attr = QStringView(textIn).mid(attrStart, attrLength);
764 QStringView val = parseValue(ch, textIn);
765 if (!val.isEmpty())
766 return std::pair<QStringView,QStringView>(attr,val);
767 break;
768 } else {
769 ++attrLength;
770 }
771 ++ch;
772 }
773
774 return std::pair<QStringView,QStringView>();
775}
776
777QStringView QQuickStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn)
778{
779 int valStart = ch - textIn.constData();
780 int valLength = 0;
781 while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) {
782 ++valLength;
783 ++ch;
784 }
785 if (ch->isNull())
786 return QStringView();
787 ++ch; // skip quote
788
789 return QStringView(textIn).mid(valStart, valLength);
790}
791
792QString QQuickStyledTextPrivate::toAlpha(int value, bool upper)
793{
794 const char baseChar = upper ? 'A' : 'a';
795
796 QString result;
797 int c = value;
798 while (c > 0) {
799 c--;
800 result.prepend(QChar(baseChar + (c % 26)));
801 c /= 26;
802 }
803 return result;
804}
805
806QString QQuickStyledTextPrivate::toRoman(int value, bool upper)
807{
808 QString result = QLatin1String("?");
809 // works for up to 4999 items
810 if (value < 5000) {
811 QByteArray romanNumeral;
812
813 static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm";
814 static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM";
815 QByteArray romanSymbols;
816 if (!upper)
817 romanSymbols = QByteArray::fromRawData(romanSymbolsLower, sizeof(romanSymbolsLower));
818 else
819 romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper));
820
821 int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
822 int n = value;
823 for (int i = 12; i >= 0; n %= c[i], i--) {
824 int q = n / c[i];
825 if (q > 0) {
826 int startDigit = i + (i + 3) / 4;
827 int numDigits;
828 if (i % 4) {
829 if ((i - 2) % 4)
830 numDigits = 2;
831 else
832 numDigits = 1;
833 }
834 else
835 numDigits = q;
836 romanNumeral.append(romanSymbols.mid(startDigit, numDigits));
837 }
838 }
839 result = QString::fromLatin1(romanNumeral);
840 }
841 return result;
842}
843
844QT_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)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
#define QQUICKSTYLEDPARSER_COORD_LIMIT