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