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