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