Initial bundle support
[profile/ivi/qtdeclarative.git] / src / quick / util / qquickstyledtext.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <QStack>
43 #include <QVector>
44 #include <QPainter>
45 #include <QTextLayout>
46 #include <QDebug>
47 #include <qmath.h>
48 #include "qquickstyledtext_p.h"
49 #include <QQmlContext>
50
51 /*
52     QQuickStyledText supports few tags:
53
54     <b></b> - bold
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
70 QT_BEGIN_NAMESPACE
71
72 Q_GUI_EXPORT int qt_defaultDpi();
73
74 class QQuickStyledTextPrivate
75 {
76 public:
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(false), 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
150 const QChar QQuickStyledTextPrivate::lessThan(QLatin1Char('<'));
151 const QChar QQuickStyledTextPrivate::greaterThan(QLatin1Char('>'));
152 const QChar QQuickStyledTextPrivate::equals(QLatin1Char('='));
153 const QChar QQuickStyledTextPrivate::singleQuote(QLatin1Char('\''));
154 const QChar QQuickStyledTextPrivate::doubleQuote(QLatin1Char('\"'));
155 const QChar QQuickStyledTextPrivate::slash(QLatin1Char('/'));
156 const QChar QQuickStyledTextPrivate::ampersand(QLatin1Char('&'));
157 const QChar QQuickStyledTextPrivate::bullet(0x2022);
158 const QChar QQuickStyledTextPrivate::disc(0x25e6);
159 const QChar QQuickStyledTextPrivate::square(0x25a1);
160 const QChar QQuickStyledTextPrivate::lineFeed(QLatin1Char('\n'));
161 const QChar QQuickStyledTextPrivate::space(QLatin1Char(' '));
162
163 QQuickStyledText::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
173 QQuickStyledText::~QQuickStyledText()
174 {
175     delete d;
176 }
177
178 void 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
191 void QQuickStyledTextPrivate::parse()
192 {
193     QList<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.setAdditionalFormats(ranges);
295 }
296
297 void 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 //
311 void 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
321 bool 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 (tag == QLatin1String("strong")) {
385                 format.setFontWeight(QFont::Bold);
386                 return true;
387             } else if (tag == QLatin1String("ol")) {
388                 List listItem;
389                 listItem.level = 0;
390                 listItem.type = Ordered;
391                 listItem.format = Decimal;
392                 listStack.push(listItem);
393             } else if (tag == QLatin1String("li")) {
394                 if (!hasNewLine)
395                     textOut.append(QChar(QChar::LineSeparator));
396                 if (!listStack.isEmpty()) {
397                     int count = ++listStack.top().level;
398                     for (int i = 0; i < listStack.size(); ++i)
399                         textOut += QString(tabsize, QChar::Nbsp);
400                     switch (listStack.top().format) {
401                     case Decimal:
402                         textOut += QString::number(count) % QLatin1Char('.');
403                         break;
404                     case LowerAlpha:
405                         textOut += toAlpha(count, false) % QLatin1Char('.');
406                         break;
407                     case UpperAlpha:
408                         textOut += toAlpha(count, true) % QLatin1Char('.');
409                         break;
410                     case LowerRoman:
411                         textOut += toRoman(count, false) % QLatin1Char('.');
412                         break;
413                     case UpperRoman:
414                         textOut += toRoman(count, true) % QLatin1Char('.');
415                         break;
416                     case Bullet:
417                         textOut += bullet;
418                         break;
419                     case Disc:
420                         textOut += disc;
421                         break;
422                     case Square:
423                         textOut += square;
424                         break;
425                     }
426                     textOut += QString(2, QChar::Nbsp);
427                 }
428             }
429             return false;
430         } else if (ch->isSpace()) {
431             // may have params.
432             QStringRef tag(&textIn, tagStart, tagLength);
433             if (tag == QLatin1String("font"))
434                 return parseFontAttributes(ch, textIn, format);
435             if (tag == QLatin1String("ol")) {
436                 parseOrderedListAttributes(ch, textIn);
437                 return false; // doesn't modify format
438             }
439             if (tag == QLatin1String("ul")) {
440                 parseUnorderedListAttributes(ch, textIn);
441                 return false; // doesn't modify format
442             }
443             if (tag == QLatin1String("a")) {
444                 return parseAnchorAttributes(ch, textIn, format);
445             }
446             if (tag == QLatin1String("img")) {
447                 parseImageAttributes(ch, textIn, textOut);
448                 return false;
449             }
450             if (*ch == greaterThan || ch->isNull())
451                 continue;
452         } else if (*ch != slash) {
453             tagLength++;
454         }
455         ++ch;
456     }
457     return false;
458 }
459
460 bool QQuickStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut)
461 {
462     skipSpace(ch);
463
464     int tagStart = ch - textIn.constData();
465     int tagLength = 0;
466     while (!ch->isNull()) {
467         if (*ch == greaterThan) {
468             if (tagLength == 0)
469                 return false;
470             QStringRef tag(&textIn, tagStart, tagLength);
471             const QChar char0 = tag.at(0);
472             hasNewLine = false;
473             if (char0 == QLatin1Char('b')) {
474                 if (tagLength == 1)
475                     return true;
476                 else if (tag.at(1) == QLatin1Char('r') && tagLength == 2)
477                     return false;
478             } else if (char0 == QLatin1Char('i')) {
479                 if (tagLength == 1)
480                     return true;
481             } else if (char0 == QLatin1Char('a')) {
482                 if (tagLength == 1)
483                     return true;
484             } else if (char0 == QLatin1Char('p')) {
485                 if (tagLength == 1) {
486                     textOut.append(QChar::LineSeparator);
487                     hasNewLine = true;
488                     hasSpace = true;
489                     return false;
490                 } else if (tag == QLatin1String("pre")) {
491                     preFormat = false;
492                     if (!hasNewLine)
493                         textOut.append(QChar::LineSeparator);
494                     hasNewLine = true;
495                     hasSpace = true;
496                     return true;
497                 }
498             } else if (char0 == QLatin1Char('u')) {
499                 if (tagLength == 1)
500                     return true;
501                 else if (tag == QLatin1String("ul")) {
502                     if (!listStack.isEmpty()) {
503                         listStack.pop();
504                         if (!listStack.count())
505                             textOut.append(QChar::LineSeparator);
506                     }
507                     return false;
508                 }
509             } else if (char0 == QLatin1Char('h') && tagLength == 2) {
510                 textOut.append(QChar::LineSeparator);
511                 hasNewLine = true;
512                 hasSpace = true;
513                 return true;
514             } else if (tag == QLatin1String("font")) {
515                 return true;
516             } else if (tag == QLatin1String("strong")) {
517                 return true;
518             } else if (tag == QLatin1String("ol")) {
519                 if (!listStack.isEmpty()) {
520                     listStack.pop();
521                     if (!listStack.count())
522                         textOut.append(QChar::LineSeparator);
523                 }
524                 return false;
525             } else if (tag == QLatin1String("li")) {
526                 return false;
527             }
528             return false;
529         } else if (!ch->isSpace()){
530             tagLength++;
531         }
532         ++ch;
533     }
534
535     return false;
536 }
537
538 void QQuickStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut)
539 {
540     int entityStart = ch - textIn.constData();
541     int entityLength = 0;
542     while (!ch->isNull()) {
543         if (*ch == QLatin1Char(';')) {
544             QStringRef entity(&textIn, entityStart, entityLength);
545             if (entity == QLatin1String("gt"))
546                 textOut += QChar(62);
547             else if (entity == QLatin1String("lt"))
548                 textOut += QChar(60);
549             else if (entity == QLatin1String("amp"))
550                 textOut += QChar(38);
551             return;
552         }
553         ++entityLength;
554         ++ch;
555     }
556 }
557
558 bool QQuickStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
559 {
560     bool valid = false;
561     QPair<QStringRef,QStringRef> attr;
562     do {
563         attr = parseAttribute(ch, textIn);
564         if (attr.first == QLatin1String("color")) {
565             valid = true;
566             format.setForeground(QColor(attr.second.toString()));
567         } else if (attr.first == QLatin1String("size")) {
568             valid = true;
569             int size = attr.second.toString().toInt();
570             if (attr.second.at(0) == QLatin1Char('-') || attr.second.at(0) == QLatin1Char('+'))
571                 size += 3;
572             if (size >= 1 && size <= 7)
573                 setFontSize(size, format);
574         }
575     } while (!ch->isNull() && !attr.first.isEmpty());
576
577     return valid;
578 }
579
580 bool QQuickStyledTextPrivate::parseOrderedListAttributes(const QChar *&ch, const QString &textIn)
581 {
582     bool valid = false;
583
584     List listItem;
585     listItem.level = 0;
586     listItem.type = Ordered;
587     listItem.format = Decimal;
588
589     QPair<QStringRef,QStringRef> attr;
590     do {
591         attr = parseAttribute(ch, textIn);
592         if (attr.first == QLatin1String("type")) {
593             valid = true;
594             if (attr.second == QLatin1String("a"))
595                 listItem.format = LowerAlpha;
596             else if (attr.second == QLatin1String("A"))
597                 listItem.format = UpperAlpha;
598             else if (attr.second == QLatin1String("i"))
599                 listItem.format = LowerRoman;
600             else if (attr.second == QLatin1String("I"))
601                 listItem.format = UpperRoman;
602         }
603     } while (!ch->isNull() && !attr.first.isEmpty());
604
605     listStack.push(listItem);
606     return valid;
607 }
608
609 bool QQuickStyledTextPrivate::parseUnorderedListAttributes(const QChar *&ch, const QString &textIn)
610 {
611     bool valid = false;
612
613     List listItem;
614     listItem.level = 0;
615     listItem.type = Unordered;
616     listItem.format = Bullet;
617
618     QPair<QStringRef,QStringRef> attr;
619     do {
620         attr = parseAttribute(ch, textIn);
621         if (attr.first == QLatin1String("type")) {
622             valid = true;
623             if (attr.second == QLatin1String("disc"))
624                 listItem.format = Disc;
625             else if (attr.second == QLatin1String("square"))
626                 listItem.format = Square;
627         }
628     } while (!ch->isNull() && !attr.first.isEmpty());
629
630     listStack.push(listItem);
631     return valid;
632 }
633
634 bool QQuickStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
635 {
636     bool valid = false;
637
638     QPair<QStringRef,QStringRef> attr;
639     do {
640         attr = parseAttribute(ch, textIn);
641         if (attr.first == QLatin1String("href")) {
642             format.setAnchorHref(attr.second.toString());
643             format.setAnchor(true);
644             format.setFontUnderline(true);
645             valid = true;
646         }
647     } while (!ch->isNull() && !attr.first.isEmpty());
648
649     return valid;
650 }
651
652 void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut)
653 {
654     qreal imgWidth = 0.0;
655
656     if (!updateImagePositions) {
657         QQuickStyledTextImgTag *image = new QQuickStyledTextImgTag;
658         image->position = textOut.length() + 1;
659
660         QPair<QStringRef,QStringRef> attr;
661         do {
662             attr = parseAttribute(ch, textIn);
663             if (attr.first == QLatin1String("src")) {
664                 image->url =  QUrl(attr.second.toString());
665             } else if (attr.first == QLatin1String("width")) {
666                 image->size.setWidth(attr.second.toString().toInt());
667             } else if (attr.first == QLatin1String("height")) {
668                 image->size.setHeight(attr.second.toString().toInt());
669             } else if (attr.first == QLatin1String("align")) {
670                 if (attr.second.toString() == QLatin1String("top")) {
671                     image->align = QQuickStyledTextImgTag::Top;
672                 } else if (attr.second.toString() == QLatin1String("middle")) {
673                     image->align = QQuickStyledTextImgTag::Middle;
674                 }
675             }
676         } while (!ch->isNull() && !attr.first.isEmpty());
677
678         if (preloadImages && !image->size.isValid()) {
679             // if we don't know its size but the image is a local image,
680             // we load it in the pixmap cache and save its implicit size
681             // to avoid a relayout later on.
682             QUrl url = baseUrl.resolved(image->url);
683             if (url.isLocalFile()) {
684                 QQuickPixmap *pix = new QQuickPixmap(context->engine(), url, image->size);
685                 if (pix && pix->isReady()) {
686                     image->size = pix->implicitSize();
687                     image->pix = pix;
688                 }
689             }
690         }
691
692         imgWidth = image->size.width();
693         imgTags->append(image);
694
695     } else {
696         // if we already have a list of img tags for this text
697         // we only want to update the positions of these tags.
698         QQuickStyledTextImgTag *image = imgTags->value(nbImages);
699         image->position = textOut.length() + 1;
700         imgWidth = image->size.width();
701         QPair<QStringRef,QStringRef> attr;
702         do {
703             attr = parseAttribute(ch, textIn);
704         } while (!ch->isNull() && !attr.first.isEmpty());
705         nbImages++;
706     }
707
708     QFontMetricsF fm(layout.font());
709     QString padding(qFloor(imgWidth / fm.width(QChar::Nbsp)), QChar::Nbsp);
710     textOut += QLatin1Char(' ');
711     textOut += padding;
712     textOut += QLatin1Char(' ');
713 }
714
715 QPair<QStringRef,QStringRef> QQuickStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn)
716 {
717     skipSpace(ch);
718
719     int attrStart = ch - textIn.constData();
720     int attrLength = 0;
721     while (!ch->isNull()) {
722         if (*ch == greaterThan) {
723             break;
724         } else if (*ch == equals) {
725             ++ch;
726             if (*ch != singleQuote && *ch != doubleQuote) {
727                 while (*ch != greaterThan && !ch->isNull())
728                     ++ch;
729                 break;
730             }
731             ++ch;
732             if (!attrLength)
733                 break;
734             QStringRef attr(&textIn, attrStart, attrLength);
735             QStringRef val = parseValue(ch, textIn);
736             if (!val.isEmpty())
737                 return QPair<QStringRef,QStringRef>(attr,val);
738             break;
739         } else {
740             ++attrLength;
741         }
742         ++ch;
743     }
744
745     return QPair<QStringRef,QStringRef>();
746 }
747
748 QStringRef QQuickStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn)
749 {
750     int valStart = ch - textIn.constData();
751     int valLength = 0;
752     while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) {
753         ++valLength;
754         ++ch;
755     }
756     if (ch->isNull())
757         return QStringRef();
758     ++ch; // skip quote
759
760     return QStringRef(&textIn, valStart, valLength);
761 }
762
763 QString QQuickStyledTextPrivate::toAlpha(int value, bool upper)
764 {
765     const char baseChar = upper ? 'A' : 'a';
766
767     QString result;
768     int c = value;
769     while (c > 0) {
770         c--;
771         result.prepend(QChar(baseChar + (c % 26)));
772         c /= 26;
773     }
774     return result;
775 }
776
777 QString QQuickStyledTextPrivate::toRoman(int value, bool upper)
778 {
779     QString result = QLatin1String("?");
780     // works for up to 4999 items
781     if (value < 5000) {
782         QByteArray romanNumeral;
783
784         static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm";
785         static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM";
786         QByteArray romanSymbols;
787         if (!upper)
788             romanSymbols = QByteArray::fromRawData(romanSymbolsLower, sizeof(romanSymbolsLower));
789         else
790             romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper));
791
792         int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
793         int n = value;
794         for (int i = 12; i >= 0; n %= c[i], i--) {
795             int q = n / c[i];
796             if (q > 0) {
797                 int startDigit = i + (i + 3) / 4;
798                 int numDigits;
799                 if (i % 4) {
800                     if ((i - 2) % 4)
801                         numDigits = 2;
802                     else
803                         numDigits = 1;
804                 }
805                 else
806                     numDigits = q;
807                 romanNumeral.append(romanSymbols.mid(startDigit, numDigits));
808             }
809         }
810         result = QString::fromLatin1(romanNumeral);
811     }
812     return result;
813 }
814
815 QT_END_NAMESPACE