149 : d(
new QQuickStyledTextPrivate(string, layout, imgTags, baseUrl, context, preloadImages, fontSizeModified))
173 QVector<QTextLayout::FormatRange> ranges;
174 QStack<QTextCharFormat> formatStack;
177 drawText.reserve(text.size());
179 updateImagePositions = !imgTags->isEmpty();
184 bool formatChanged =
false;
186 const QChar *ch = text.constData();
187 while (!ch->isNull()) {
188 if (*ch == lessThan) {
190 appendText(text, textStart, textLength, drawText);
192 drawText.append(space);
197 if (rangeStart != drawText.size() && formatStack.size()) {
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;
209 rangeStart = drawText.size();
213 if (parseCloseTag(ch, text, drawText)) {
214 if (formatStack.size()) {
215 formatChanged =
true;
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);
228 textStart = ch - text.constData() + 1;
230 }
else if (*ch == ampersand) {
232 appendText(text, textStart, textLength, drawText);
233 parseEntity(ch, text, drawText);
234 textStart = ch - text.constData() + 1;
236 }
else if (ch->isSpace()) {
238 appendText(text, textStart, textLength, drawText);
241 for (
const QChar *n = ch + 1; !n->isNull() && n->isSpace(); ++n)
244 }
else if (*ch == lineFeed) {
245 drawText.append(QChar(QChar::LineSeparator));
248 drawText.append(QChar(QChar::Nbsp));
251 textStart = ch - text.constData() + 1;
260 appendText(text, textStart, textLength, drawText);
261 if (rangeStart != drawText.size() && formatStack.size()) {
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;
273 layout.setText(drawText);
274 layout.setFormats(ranges);
305 int tagStart = ch - textIn.constData();
307 while (!ch->isNull()) {
308 if (*ch == greaterThan) {
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);
317 }
else if (tagLength == 2 && tag.at(1).toLower() == QLatin1Char(
'r')) {
318 textOut.append(QChar(QChar::LineSeparator));
323 }
else if (char0 == QLatin1Char(
'i')) {
324 if (tagLength == 1) {
325 format.setFontItalic(
true);
328 }
else if (char0 == QLatin1Char(
'p')) {
329 if (tagLength == 1) {
331 textOut.append(QChar::LineSeparator);
334 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"pre"))) {
337 textOut.append(QChar::LineSeparator);
338 format.setFontFamilies(QStringList {QString::fromLatin1(
"Courier New"), QString::fromLatin1(
"courier")});
339 format.setFontFixedPitch(
true);
342 }
else if (char0 == QLatin1Char(
'u')) {
343 if (tagLength == 1) {
344 format.setFontUnderline(
true);
346 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"ul"))) {
351 listStack.push(listItem);
353 }
else if (char0 == QLatin1Char(
'h') && tagLength == 2) {
354 int level = tag.at(1).digitValue();
355 if (level >= 1 && level <= 6) {
357 textOut.append(QChar::LineSeparator);
360 setFontSize(7 - level, format);
361 format.setFontWeight(QFont::Bold);
364 }
else if (char0 == QLatin1Char(
's')) {
365 if (tagLength == 1) {
366 format.setFontStrikeOut(
true);
368 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"strong"))) {
369 format.setFontWeight(QFont::Bold);
372 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"del"))) {
373 format.setFontStrikeOut(
true);
375 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"ol"))) {
380 listStack.push(listItem);
381 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"li"))) {
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) {
390 textOut += QString::number(count) % QLatin1Char(
'.');
393 textOut +=
toAlpha(count,
false) % QLatin1Char(
'.');
396 textOut +=
toAlpha(count,
true) % QLatin1Char(
'.');
399 textOut +=
toRoman(count,
false) % QLatin1Char(
'.');
402 textOut +=
toRoman(count,
true) % QLatin1Char(
'.');
414 textOut += QString(2, QChar::Nbsp);
418 }
else if (ch->isSpace()) {
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);
427 if (is_equal_ignoring_case(tag, QLatin1String(
"ul"))) {
428 parseUnorderedListAttributes(ch, textIn);
431 if (is_equal_ignoring_case(tag, QLatin1String(
"a"))) {
432 return parseAnchorAttributes(ch, textIn, format);
434 if (is_equal_ignoring_case(tag, QLatin1String(
"img"))) {
435 parseImageAttributes(ch, textIn, textOut);
438 if (*ch == greaterThan || ch->isNull())
440 }
else if (*ch != slash) {
452 int tagStart = ch - textIn.constData();
454 while (!ch->isNull()) {
455 if (*ch == greaterThan) {
458 auto tag = QStringView(textIn).mid(tagStart, tagLength);
459 const QChar char0 = tag.at(0).toLower();
461 if (char0 == QLatin1Char(
'b')) {
464 else if (tag.at(1).toLower() == QLatin1Char(
'r') && tagLength == 2)
466 }
else if (char0 == QLatin1Char(
'i')) {
469 }
else if (char0 == QLatin1Char(
'a')) {
472 }
else if (char0 == QLatin1Char(
'p')) {
473 if (tagLength == 1) {
474 textOut.append(QChar::LineSeparator);
478 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"pre"))) {
481 textOut.append(QChar::LineSeparator);
486 }
else if (char0 == QLatin1Char(
'u')) {
489 else if (is_equal_ignoring_case(tag, QLatin1String(
"ul"))) {
490 if (!listStack.isEmpty()) {
492 if (!listStack.size())
493 textOut.append(QChar::LineSeparator);
497 }
else if (char0 == QLatin1Char(
'h') && tagLength == 2) {
498 textOut.append(QChar::LineSeparator);
502 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"font"))) {
504 }
else if (char0 == QLatin1Char(
's')) {
505 if (tagLength == 1) {
507 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"strong"))) {
510 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"del"))) {
512 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"ol"))) {
513 if (!listStack.isEmpty()) {
515 if (!listStack.size())
516 textOut.append(QChar::LineSeparator);
519 }
else if (is_equal_ignoring_case(tag, QLatin1String(
"li"))) {
523 }
else if (!ch->isSpace()){
560 std::pair<QStringView,QStringView> attr;
562 attr = parseAttribute(ch, textIn);
563 if (is_equal_ignoring_case(attr.first, QLatin1String(
"color"))) {
565 format.setForeground(QColor::fromString(attr.second));
566 }
else if (is_equal_ignoring_case(attr.first, QLatin1String(
"size"))) {
568 int size = attr.second.toInt();
569 if (attr.second.at(0) == QLatin1Char(
'-') || attr.second.at(0) == QLatin1Char(
'+'))
571 if (size >= 1 && size <= 7)
572 setFontSize(size, format);
574 }
while (!ch->isNull() && !attr.first.isEmpty());
588 std::pair<QStringView,QStringView> attr;
590 attr = parseAttribute(ch, textIn);
591 if (is_equal_ignoring_case(attr.first, QLatin1String(
"type"))) {
593 if (attr.second == QLatin1String(
"a"))
595 else if (attr.second == QLatin1String(
"A"))
597 else if (attr.second == QLatin1String(
"i"))
599 else if (attr.second == QLatin1String(
"I"))
602 }
while (!ch->isNull() && !attr.first.isEmpty());
604 listStack.push(listItem);
617 std::pair<QStringView,QStringView> attr;
619 attr = parseAttribute(ch, textIn);
620 if (is_equal_ignoring_case(attr.first, QLatin1String(
"type"))) {
622 if (is_equal_ignoring_case(attr.second, QLatin1String(
"disc")))
624 else if (is_equal_ignoring_case(attr.second, QLatin1String(
"square")))
627 }
while (!ch->isNull() && !attr.first.isEmpty());
629 listStack.push(listItem);
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);
659 QQuickStyledTextImgTag *image =
new QQuickStyledTextImgTag;
660 image->position = textOut.size() + (trailingSpace ? 0 : 1);
662 std::pair<QStringView,QStringView> attr;
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"))) {
669 int v = attr.second.toString().toInt(&ok);
671 image->size.setWidth(v);
673 qCWarning(lcStyledText) <<
"Invalid width provided for <img>";
674 }
else if (is_equal_ignoring_case(attr.first, QLatin1String(
"height"))) {
676 int v = attr.second.toString().toInt(&ok);
678 image->size.setHeight(v);
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;
688 }
while (!ch->isNull() && !attr.first.isEmpty());
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();
706 if (!image->url.isValid()) {
708 qCWarning(lcStyledText) <<
"StyledText - Invalid base url in img tag";
710 imgWidth = image->size.width();
711 image->offset = -
std::fmod(imgWidth, spaceWidth) / 2.0;
712 imgTags->append(image);
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;
723 attr = parseAttribute(ch, textIn);
724 }
while (!ch->isNull() && !attr.first.isEmpty());
728 QString padding(qFloor(imgWidth / spaceWidth), QChar::Nbsp);
730 textOut += QLatin1Char(
' ');
731 textOut += padding + QLatin1Char(
' ');
738 int attrStart = ch - textIn.constData();
740 while (!ch->isNull()) {
741 if (*ch == greaterThan) {
743 }
else if (*ch == equals) {
745 if (*ch != singleQuote && *ch != doubleQuote) {
746 while (*ch != greaterThan && !ch->isNull())
753 auto attr = QStringView(textIn).mid(attrStart, attrLength);
754 QStringView val = parseValue(ch, textIn);
756 return std::pair<QStringView,QStringView>(attr,val);
764 return std::pair<QStringView,QStringView>();
798 QString result = QLatin1String(
"?");
801 QByteArray romanNumeral;
803 static const char romanSymbolsLower[] =
"iiivixxxlxcccdcmmmm";
804 static const char romanSymbolsUpper[] =
"IIIVIXXXLXCCCDCMMMM";
805 QByteArray romanSymbols;
807 romanSymbols = QByteArray::fromRawData(romanSymbolsLower,
sizeof(romanSymbolsLower));
809 romanSymbols = QByteArray::fromRawData(romanSymbolsUpper,
sizeof(romanSymbolsUpper));
811 int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
813 for (
int i = 12; i >= 0; n %= c[i], i--) {
816 int startDigit = i + (i + 3) / 4;
826 romanNumeral.append(romanSymbols.mid(startDigit, numDigits));
829 result = QString::fromLatin1(romanNumeral);
QList< QQuickStyledTextImgTag * > * imgTags
bool parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut)
void parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut)
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)
std::pair< QStringView, QStringView > parseAttribute(const QChar *&ch, const QString &textIn)
void parseEntity(const QChar *&ch, const QString &textIn, QString &textOut)
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)
#define QQUICKSTYLEDPARSER_COORD_LIMIT