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=""> - 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
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 range.format = imgFmt;
293 range.start = image->position;
294 range.length = 1;
295 ranges.append(range);
296 }
297 }
298
299 layout.setText(drawText);
300 layout.setFormats(ranges);
301}
302
303void QQuickStyledTextPrivate::appendText(const QString &textIn, int start, int length, QString &textOut)
304{
305 if (prependSpace)
306 textOut.append(space);
307 textOut.append(QStringView(textIn).mid(start, length));
308 prependSpace = false;
309 hasSpace = false;
310 hasNewLine = false;
311}
312
313//
314// Calculates and sets the correct font size in points
315// depending on the size multiplier and base font.
316//
317void QQuickStyledTextPrivate::setFontSize(int size, QTextCharFormat &format)
318{
319 static const qreal scaling[] = { 0.7, 0.8, 1.0, 1.2, 1.5, 2.0, 2.4 };
320 if (baseFont.pointSizeF() != -1)
321 format.setFontPointSize(baseFont.pointSize() * scaling[size - 1]);
322 else
323 format.setFontPointSize(baseFont.pixelSize() * qreal(72.) / qreal(qt_defaultDpi()) * scaling[size - 1]);
324 *fontSizeModified = true;
325}
326
327bool QQuickStyledTextPrivate::parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format)
328{
329 skipSpace(ch);
330
331 int tagStart = ch - textIn.constData();
332 int tagLength = 0;
333 while (!ch->isNull()) {
334 if (*ch == greaterThan) {
335 if (tagLength == 0)
336 return false;
337 auto tag = QStringView(textIn).mid(tagStart, tagLength);
338 switch (lookupHtmlTag(tag)) {
339 case Html_b:
340 format.setFontWeight(QFont::Bold);
341 return true;
342 case Html_br:
343 textOut.append(QChar(QChar::LineSeparator));
344 hasSpace = true;
345 prependSpace = false;
346 return false;
347 case Html_i:
348 format.setFontItalic(true);
349 return true;
350 case Html_p:
351 if (!hasNewLine)
352 textOut.append(QChar::LineSeparator);
353 hasSpace = true;
354 prependSpace = false;
355 return false;
356 case Html_pre:
357 preFormat = true;
358 if (!hasNewLine)
359 textOut.append(QChar::LineSeparator);
360 format.setFontFamilies(QStringList {QString::fromLatin1("Courier New"), QString::fromLatin1("courier")});
361 format.setFontFixedPitch(true);
362 return true;
363 case Html_u:
364 format.setFontUnderline(true);
365 return true;
366 case Html_ul: {
367 List listItem;
368 listItem.level = 0;
369 listItem.type = Unordered;
370 listItem.format = Bullet;
371 listStack.push(listItem);
372 return false;
373 }
374 case Html_h1:
375 case Html_h2:
376 case Html_h3:
377 case Html_h4:
378 case Html_h5:
379 case Html_h6: {
380 int level = tag.at(1).digitValue();
381 if (!hasNewLine)
382 textOut.append(QChar::LineSeparator);
383 hasSpace = true;
384 prependSpace = false;
385 setFontSize(7 - level, format);
386 format.setFontWeight(QFont::Bold);
387 return true;
388 }
389 case Html_s:
390 format.setFontStrikeOut(true);
391 return true;
392 case Html_strong:
393 format.setFontWeight(QFont::Bold);
394 return true;
395 case Html_del:
396 format.setFontStrikeOut(true);
397 return true;
398 case Html_ol: {
399 List listItem;
400 listItem.level = 0;
401 listItem.type = Ordered;
402 listItem.format = Decimal;
403 listStack.push(listItem);
404 return false;
405 }
406 case Html_li:
407 if (!hasNewLine)
408 textOut.append(QChar(QChar::LineSeparator));
409 if (!listStack.isEmpty()) {
410 int count = ++listStack.top().level;
411 for (int i = 0; i < listStack.size(); ++i)
412 textOut += QString(tabsize, QChar::Nbsp);
413 switch (listStack.top().format) {
414 case Decimal:
415 textOut += QString::number(count) % QLatin1Char('.');
416 break;
417 case LowerAlpha:
418 textOut += toAlpha(count, false) % QLatin1Char('.');
419 break;
420 case UpperAlpha:
421 textOut += toAlpha(count, true) % QLatin1Char('.');
422 break;
423 case LowerRoman:
424 textOut += toRoman(count, false) % QLatin1Char('.');
425 break;
426 case UpperRoman:
427 textOut += toRoman(count, true) % QLatin1Char('.');
428 break;
429 case Bullet:
430 textOut += bullet;
431 break;
432 case Disc:
433 textOut += disc;
434 break;
435 case Square:
436 textOut += square;
437 break;
438 }
439 textOut += QString(2, QChar::Nbsp);
440 }
441 return false;
442 default:
443 break;
444 }
445 return false;
446 } else if (ch->isSpace()) {
447 // may have params.
448 auto tag = QStringView(textIn).mid(tagStart, tagLength);
449 switch (lookupHtmlTag(tag)) {
450 case Html_font:
451 return parseFontAttributes(ch, textIn, format);
452 case Html_ol:
453 parseOrderedListAttributes(ch, textIn);
454 return false;
455 case Html_ul:
456 parseUnorderedListAttributes(ch, textIn);
457 return false;
458 case Html_a:
459 return parseAnchorAttributes(ch, textIn, format);
460 case Html_img:
461 parseImageAttributes(ch, textIn, textOut);
462 return false;
463 default:
464 if (*ch == greaterThan || ch->isNull())
465 continue;
466 break;
467 }
468 } else if (*ch != slash) {
469 tagLength++;
470 }
471 ++ch;
472 }
473 return false;
474}
475
476bool QQuickStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut)
477{
478 skipSpace(ch);
479
480 int tagStart = ch - textIn.constData();
481 int tagLength = 0;
482 while (!ch->isNull()) {
483 if (*ch == greaterThan) {
484 if (tagLength == 0)
485 return false;
486 auto tag = QStringView(textIn).mid(tagStart, tagLength);
487 hasNewLine = false;
488 switch (lookupHtmlTag(tag)) {
489 case Html_b:
490 case Html_i:
491 case Html_a:
492 case Html_u:
493 case Html_font:
494 case Html_strong:
495 case Html_del:
496 case Html_s:
497 return true;
498 case Html_br:
499 return false;
500 case Html_p:
501 textOut.append(QChar::LineSeparator);
502 hasNewLine = true;
503 hasSpace = true;
504 return false;
505 case Html_pre:
506 preFormat = false;
507 if (!hasNewLine)
508 textOut.append(QChar::LineSeparator);
509 hasNewLine = true;
510 hasSpace = true;
511 return true;
512 case Html_ul:
513 case Html_ol:
514 if (!listStack.isEmpty()) {
515 listStack.pop();
516 if (!listStack.size())
517 textOut.append(QChar::LineSeparator);
518 }
519 return false;
520 case Html_h1:
521 case Html_h2:
522 case Html_h3:
523 case Html_h4:
524 case Html_h5:
525 case Html_h6:
526 textOut.append(QChar::LineSeparator);
527 hasNewLine = true;
528 hasSpace = true;
529 return true;
530 case Html_li:
531 return false;
532 default:
533 break;
534 }
535 return false;
536 } else if (!ch->isSpace()){
537 tagLength++;
538 }
539 ++ch;
540 }
541
542 return false;
543}
544
545void QQuickStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut)
546{
547 int entityStart = ch - textIn.constData();
548 int entityLength = 0;
549 while (!ch->isNull()) {
550 if (*ch == QLatin1Char(';')) {
551 auto entity = QStringView(textIn).mid(entityStart, entityLength);
552#if QT_CONFIG(texthtmlparser)
553 const QString parsedEntity = QTextHtmlParser::parseEntity(entity);
554 if (!parsedEntity.isNull())
555 textOut += parsedEntity;
556 else
557#endif
558 qCWarning(lcStyledText) << "StyledText doesn't support entity" << entity;
559 return;
560 } else if (*ch == QLatin1Char(' ')) {
561 auto entity = QStringView(textIn).mid(entityStart - 1, entityLength + 1);
562 textOut += entity + *ch;
563 return;
564 }
565 ++entityLength;
566 ++ch;
567 }
568}
569
570bool QQuickStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
571{
572 bool valid = false;
573 std::pair<QStringView,QStringView> attr;
574 do {
575 attr = parseAttribute(ch, textIn);
576 if (is_equal_ignoring_case(attr.first, QLatin1String("color"))) {
577 valid = true;
578 format.setForeground(QColor::fromString(attr.second));
579 } else if (is_equal_ignoring_case(attr.first, QLatin1String("size"))) {
580 valid = true;
581 int size = attr.second.toInt();
582 if (attr.second.at(0) == QLatin1Char('-') || attr.second.at(0) == QLatin1Char('+'))
583 size += 3;
584 if (size >= 1 && size <= 7)
585 setFontSize(size, format);
586 }
587 } while (!ch->isNull() && !attr.first.isEmpty());
588
589 return valid;
590}
591
592bool QQuickStyledTextPrivate::parseOrderedListAttributes(const QChar *&ch, const QString &textIn)
593{
594 bool valid = false;
595
596 List listItem;
597 listItem.level = 0;
598 listItem.type = Ordered;
599 listItem.format = Decimal;
600
601 std::pair<QStringView,QStringView> attr;
602 do {
603 attr = parseAttribute(ch, textIn);
604 if (is_equal_ignoring_case(attr.first, QLatin1String("type"))) {
605 valid = true;
606 if (attr.second == QLatin1String("a"))
607 listItem.format = LowerAlpha;
608 else if (attr.second == QLatin1String("A"))
609 listItem.format = UpperAlpha;
610 else if (attr.second == QLatin1String("i"))
611 listItem.format = LowerRoman;
612 else if (attr.second == QLatin1String("I"))
613 listItem.format = UpperRoman;
614 }
615 } while (!ch->isNull() && !attr.first.isEmpty());
616
617 listStack.push(listItem);
618 return valid;
619}
620
621bool QQuickStyledTextPrivate::parseUnorderedListAttributes(const QChar *&ch, const QString &textIn)
622{
623 bool valid = false;
624
625 List listItem;
626 listItem.level = 0;
627 listItem.type = Unordered;
628 listItem.format = Bullet;
629
630 std::pair<QStringView,QStringView> attr;
631 do {
632 attr = parseAttribute(ch, textIn);
633 if (is_equal_ignoring_case(attr.first, QLatin1String("type"))) {
634 valid = true;
635 if (is_equal_ignoring_case(attr.second, QLatin1String("disc")))
636 listItem.format = Disc;
637 else if (is_equal_ignoring_case(attr.second, QLatin1String("square")))
638 listItem.format = Square;
639 }
640 } while (!ch->isNull() && !attr.first.isEmpty());
641
642 listStack.push(listItem);
643 return valid;
644}
645
646bool QQuickStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
647{
648 bool valid = false;
649
650 std::pair<QStringView,QStringView> attr;
651 do {
652 attr = parseAttribute(ch, textIn);
653 if (is_equal_ignoring_case(attr.first, QLatin1String("href"))) {
654 format.setAnchorHref(attr.second.toString());
655 format.setAnchor(true);
656 format.setFontUnderline(true);
657 valid = true;
658 }
659 } while (!ch->isNull() && !attr.first.isEmpty());
660
661 return valid;
662}
663
664void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut)
665{
667 QQuickStyledTextImgTag *image = new QQuickStyledTextImgTag;
668 image->position = textOut.size();
669
670 std::pair<QStringView,QStringView> attr;
671 do {
672 attr = parseAttribute(ch, textIn);
673 if (is_equal_ignoring_case(attr.first, QLatin1String("src"))) {
674 image->url = QUrl(attr.second.toString());
675 } else if (is_equal_ignoring_case(attr.first, QLatin1String("width"))) {
676 bool ok;
677 int v = attr.second.toInt(&ok);
678 if (ok && v <= QQUICKSTYLEDPARSER_COORD_LIMIT)
679 image->size.setWidth(v);
680 else
681 qCWarning(lcStyledText) << "Invalid width provided for <img>";
682 } else if (is_equal_ignoring_case(attr.first, QLatin1String("height"))) {
683 bool ok;
684 int v = attr.second.toInt(&ok);
685 if (ok && v <= QQUICKSTYLEDPARSER_COORD_LIMIT)
686 image->size.setHeight(v);
687 else
688 qCWarning(lcStyledText) << "Invalid height provided for <img>";
689 } else if (is_equal_ignoring_case(attr.first, QLatin1String("align"))) {
690 if (is_equal_ignoring_case(attr.second, QLatin1String("top"))) {
691 image->align = QQuickStyledTextImgTag::Top;
692 } else if (is_equal_ignoring_case(attr.second, QLatin1String("middle"))) {
693 image->align = QQuickStyledTextImgTag::Middle;
694 }
695 }
696 } while (!ch->isNull() && !attr.first.isEmpty());
697
698 if (preloadImages && !image->size.isValid()) {
699 // if we don't know its size but the image is a local image,
700 // we load it in the pixmap cache and save its implicit size
701 // to avoid a relayout later on.
702 QUrl url = baseUrl.resolved(image->url);
703 if (url.isLocalFile()) {
704 image->pix.reset(new QQuickPixmap(context->engine(), url, QRect(), image->size));
705 if (image->pix && image->pix->isReady()) {
706 image->size = image->pix->implicitSize();
707 } else {
708 image->pix.reset();
709 }
710 }
711 }
712
713 // Return immediately if img tag has invalid url
714 if (!image->url.isValid()) {
715 delete image;
716 qCWarning(lcStyledText) << "StyledText - Invalid base url in img tag";
717 return;
718 }
719
720 imgTags->append(image);
721 } else {
722 // if we already have a list of img tags for this text
723 // we only want to update the positions of these tags.
724 QQuickStyledTextImgTag *image = imgTags->value(nbImages);
725 image->position = textOut.size();
726 std::pair<QStringView,QStringView> attr;
727 do {
728 attr = parseAttribute(ch, textIn);
729 } while (!ch->isNull() && !attr.first.isEmpty());
730 nbImages++;
731 }
732
733 textOut += QChar::ObjectReplacementCharacter;
734 hasSpace = false;
735}
736
737std::pair<QStringView,QStringView> QQuickStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn)
738{
739 skipSpace(ch);
740
741 int attrStart = ch - textIn.constData();
742 int attrLength = 0;
743 while (!ch->isNull()) {
744 if (*ch == greaterThan) {
745 break;
746 } else if (*ch == equals) {
747 ++ch;
748 if (*ch != singleQuote && *ch != doubleQuote) {
749 while (*ch != greaterThan && !ch->isNull())
750 ++ch;
751 break;
752 }
753 ++ch;
754 if (!attrLength)
755 break;
756 auto attr = QStringView(textIn).mid(attrStart, attrLength);
757 QStringView val = parseValue(ch, textIn);
758 if (!val.isEmpty())
759 return std::pair<QStringView,QStringView>(attr,val);
760 break;
761 } else {
762 ++attrLength;
763 }
764 ++ch;
765 }
766
767 return std::pair<QStringView,QStringView>();
768}
769
770QStringView QQuickStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn)
771{
772 int valStart = ch - textIn.constData();
773 int valLength = 0;
774 while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) {
775 ++valLength;
776 ++ch;
777 }
778 if (ch->isNull())
779 return QStringView();
780 ++ch; // skip quote
781
782 return QStringView(textIn).mid(valStart, valLength);
783}
784
785QString QQuickStyledTextPrivate::toAlpha(int value, bool upper)
786{
787 const char baseChar = upper ? 'A' : 'a';
788
789 QString result;
790 int c = value;
791 while (c > 0) {
792 c--;
793 result.prepend(QChar(baseChar + (c % 26)));
794 c /= 26;
795 }
796 return result;
797}
798
799QString QQuickStyledTextPrivate::toRoman(int value, bool upper)
800{
801 QString result = QLatin1String("?");
802 // works for up to 4999 items
803 if (value < 5000) {
804 QByteArray romanNumeral;
805
806 static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm";
807 static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM";
808 QByteArray romanSymbols;
809 if (!upper)
810 romanSymbols = QByteArray::fromRawData(romanSymbolsLower, sizeof(romanSymbolsLower));
811 else
812 romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper));
813
814 int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
815 int n = value;
816 for (int i = 12; i >= 0; n %= c[i], i--) {
817 int q = n / c[i];
818 if (q > 0) {
819 int startDigit = i + (i + 3) / 4;
820 int numDigits;
821 if (i % 4) {
822 if ((i - 2) % 4)
823 numDigits = 2;
824 else
825 numDigits = 1;
826 }
827 else
828 numDigits = q;
829 romanNumeral.append(romanSymbols.mid(startDigit, numDigits));
830 }
831 }
832 result = QString::fromLatin1(romanNumeral);
833 }
834 return result;
835}
836
837QT_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