/**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtQuick module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include "qquickstyledtext_p.h" #include /* QQuickStyledText supports few tags: - bold - bold - italic
- new line

- paragraph - underlined text

to
- headers - anchor
    ,
      and
    • - ordered and unordered lists
       - preformated
           - images
      
          The opening and closing tags must be correctly nested.
      */
      
      QT_BEGIN_NAMESPACE
      
      Q_GUI_EXPORT int qt_defaultDpi();
      
      class QQuickStyledTextPrivate
      {
      public:
          enum ListType { Ordered, Unordered };
          enum ListFormat { Bullet, Disc, Square, Decimal, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman };
      
          struct List {
              int level;
              ListType type;
              ListFormat format;
          };
      
          QQuickStyledTextPrivate(const QString &t, QTextLayout &l,
                                        QList &imgTags,
                                        const QUrl &baseUrl,
                                        QQmlContext *context,
                                        bool preloadImages,
                                        bool *fontSizeModified)
              : text(t), layout(l), imgTags(&imgTags), baseFont(layout.font()), baseUrl(baseUrl), hasNewLine(true), nbImages(0), updateImagePositions(false)
              , preFormat(false), prependSpace(false), hasSpace(true), preloadImages(preloadImages), fontSizeModified(fontSizeModified), context(context)
          {
          }
      
          void parse();
          void appendText(const QString &textIn, int start, int length, QString &textOut);
          bool parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format);
          bool parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut);
          void parseEntity(const QChar *&ch, const QString &textIn, QString &textOut);
          bool parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format);
          bool parseOrderedListAttributes(const QChar *&ch, const QString &textIn);
          bool parseUnorderedListAttributes(const QChar *&ch, const QString &textIn);
          bool parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format);
          void parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut);
          QPair parseAttribute(const QChar *&ch, const QString &textIn);
          QStringRef parseValue(const QChar *&ch, const QString &textIn);
          void setFontSize(int size, QTextCharFormat &format);
      
          inline void skipSpace(const QChar *&ch) {
              while (ch->isSpace() && !ch->isNull())
                  ++ch;
          }
      
          static QString toAlpha(int value, bool upper);
          static QString toRoman(int value, bool upper);
      
          QString text;
          QTextLayout &layout;
          QList *imgTags;
          QFont baseFont;
          QStack listStack;
          QUrl baseUrl;
          bool hasNewLine;
          int nbImages;
          bool updateImagePositions;
          bool preFormat;
          bool prependSpace;
          bool hasSpace;
          bool preloadImages;
          bool *fontSizeModified;
          QQmlContext *context;
      
          static const QChar lessThan;
          static const QChar greaterThan;
          static const QChar equals;
          static const QChar singleQuote;
          static const QChar doubleQuote;
          static const QChar slash;
          static const QChar ampersand;
          static const QChar bullet;
          static const QChar disc;
          static const QChar square;
          static const QChar lineFeed;
          static const QChar space;
          static const int tabsize = 6;
      };
      
      const QChar QQuickStyledTextPrivate::lessThan(QLatin1Char('<'));
      const QChar QQuickStyledTextPrivate::greaterThan(QLatin1Char('>'));
      const QChar QQuickStyledTextPrivate::equals(QLatin1Char('='));
      const QChar QQuickStyledTextPrivate::singleQuote(QLatin1Char('\''));
      const QChar QQuickStyledTextPrivate::doubleQuote(QLatin1Char('\"'));
      const QChar QQuickStyledTextPrivate::slash(QLatin1Char('/'));
      const QChar QQuickStyledTextPrivate::ampersand(QLatin1Char('&'));
      const QChar QQuickStyledTextPrivate::bullet(0x2022);
      const QChar QQuickStyledTextPrivate::disc(0x25e6);
      const QChar QQuickStyledTextPrivate::square(0x25a1);
      const QChar QQuickStyledTextPrivate::lineFeed(QLatin1Char('\n'));
      const QChar QQuickStyledTextPrivate::space(QLatin1Char(' '));
      
      QQuickStyledText::QQuickStyledText(const QString &string, QTextLayout &layout,
                                                     QList &imgTags,
                                                     const QUrl &baseUrl,
                                                     QQmlContext *context,
                                                     bool preloadImages,
                                                     bool *fontSizeModified)
          : d(new QQuickStyledTextPrivate(string, layout, imgTags, baseUrl, context, preloadImages, fontSizeModified))
      {
      }
      
      QQuickStyledText::~QQuickStyledText()
      {
          delete d;
      }
      
      void QQuickStyledText::parse(const QString &string, QTextLayout &layout,
                                         QList &imgTags,
                                         const QUrl &baseUrl,
                                         QQmlContext *context,
                                         bool preloadImages,
                                         bool *fontSizeModified)
      {
          if (string.isEmpty())
              return;
          QQuickStyledText styledText(string, layout, imgTags, baseUrl, context, preloadImages, fontSizeModified);
          styledText.d->parse();
      }
      
      void QQuickStyledTextPrivate::parse()
      {
          QList ranges;
          QStack formatStack;
      
          QString drawText;
          drawText.reserve(text.count());
      
          updateImagePositions = !imgTags->isEmpty();
      
          int textStart = 0;
          int textLength = 0;
          int rangeStart = 0;
          bool formatChanged = false;
      
          const QChar *ch = text.constData();
          while (!ch->isNull()) {
              if (*ch == lessThan) {
                  if (textLength) {
                      appendText(text, textStart, textLength, drawText);
                  } else if (prependSpace) {
                      drawText.append(space);
                      prependSpace = false;
                      hasSpace = true;
                  }
      
                  if (rangeStart != drawText.length() && formatStack.count()) {
                      if (formatChanged) {
                          QTextLayout::FormatRange formatRange;
                          formatRange.format = formatStack.top();
                          formatRange.start = rangeStart;
                          formatRange.length = drawText.length() - rangeStart;
                          ranges.append(formatRange);
                          formatChanged = false;
                      } else if (ranges.count()) {
                          ranges.last().length += drawText.length() - rangeStart;
                      }
                  }
                  rangeStart = drawText.length();
                  ++ch;
                  if (*ch == slash) {
                      ++ch;
                      if (parseCloseTag(ch, text, drawText)) {
                          if (formatStack.count()) {
                              formatChanged = true;
                              formatStack.pop();
                          }
                      }
                  } else {
                      QTextCharFormat format;
                      if (formatStack.count())
                          format = formatStack.top();
                      if (parseTag(ch, text, drawText, format)) {
                          formatChanged = true;
                          formatStack.push(format);
                      }
                  }
                  textStart = ch - text.constData() + 1;
                  textLength = 0;
              } else if (*ch == ampersand) {
                  ++ch;
                  appendText(text, textStart, textLength, drawText);
                  parseEntity(ch, text, drawText);
                  textStart = ch - text.constData() + 1;
                  textLength = 0;
              } else if (ch->isSpace()) {
                  if (textLength)
                      appendText(text, textStart, textLength, drawText);
                  if (!preFormat) {
                      prependSpace = !hasSpace;
                      for (const QChar *n = ch + 1; !n->isNull() && n->isSpace(); ++n)
                          ch = n;
                      hasNewLine = false;
                  } else  if (*ch == lineFeed) {
                      drawText.append(QChar(QChar::LineSeparator));
                      hasNewLine = true;
                  } else {
                      drawText.append(QChar(QChar::Nbsp));
                      hasNewLine = false;
                  }
                  textStart = ch - text.constData() + 1;
                  textLength = 0;
              } else {
                  ++textLength;
              }
              if (!ch->isNull())
                  ++ch;
          }
          if (textLength)
              appendText(text, textStart, textLength, drawText);
          if (rangeStart != drawText.length() && formatStack.count()) {
              if (formatChanged) {
                  QTextLayout::FormatRange formatRange;
                  formatRange.format = formatStack.top();
                  formatRange.start = rangeStart;
                  formatRange.length = drawText.length() - rangeStart;
                  ranges.append(formatRange);
              } else if (ranges.count()) {
                  ranges.last().length += drawText.length() - rangeStart;
              }
          }
      
          layout.setText(drawText);
          layout.setAdditionalFormats(ranges);
      }
      
      void QQuickStyledTextPrivate::appendText(const QString &textIn, int start, int length, QString &textOut)
      {
          if (prependSpace)
              textOut.append(space);
          textOut.append(QStringRef(&textIn, start, length));
          prependSpace = false;
          hasSpace = false;
          hasNewLine = false;
      }
      
      //
      // Calculates and sets the correct font size in points
      // depending on the size multiplier and base font.
      //
      void QQuickStyledTextPrivate::setFontSize(int size, QTextCharFormat &format)
      {
          static const qreal scaling[] = { 0.7, 0.8, 1.0, 1.2, 1.5, 2.0, 2.4 };
          if (baseFont.pointSizeF() != -1)
              format.setFontPointSize(baseFont.pointSize() * scaling[size - 1]);
          else
              format.setFontPointSize(baseFont.pixelSize() * qreal(72.) / qreal(qt_defaultDpi()) * scaling[size - 1]);
          *fontSizeModified = true;
      }
      
      bool QQuickStyledTextPrivate::parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format)
      {
          skipSpace(ch);
      
          int tagStart = ch - textIn.constData();
          int tagLength = 0;
          while (!ch->isNull()) {
              if (*ch == greaterThan) {
                  if (tagLength == 0)
                      return false;
                  QStringRef tag(&textIn, tagStart, tagLength);
                  const QChar char0 = tag.at(0);
                  if (char0 == QLatin1Char('b')) {
                      if (tagLength == 1) {
                          format.setFontWeight(QFont::Bold);
                          return true;
                      } else if (tagLength == 2 && tag.at(1) == QLatin1Char('r')) {
                          textOut.append(QChar(QChar::LineSeparator));
                          hasSpace = true;
                          prependSpace = false;
                          return false;
                      }
                  } else if (char0 == QLatin1Char('i')) {
                      if (tagLength == 1) {
                          format.setFontItalic(true);
                          return true;
                      }
                  } else if (char0 == QLatin1Char('p')) {
                      if (tagLength == 1) {
                          if (!hasNewLine)
                              textOut.append(QChar::LineSeparator);
                          hasSpace = true;
                          prependSpace = false;
                      } else if (tag == QLatin1String("pre")) {
                          preFormat = true;
                          if (!hasNewLine)
                              textOut.append(QChar::LineSeparator);
                          format.setFontFamily(QString::fromLatin1("Courier New,courier"));
                          format.setFontFixedPitch(true);
                          return true;
                      }
                  } else if (char0 == QLatin1Char('u')) {
                      if (tagLength == 1) {
                          format.setFontUnderline(true);
                          return true;
                      } else if (tag == QLatin1String("ul")) {
                          List listItem;
                          listItem.level = 0;
                          listItem.type = Unordered;
                          listItem.format = Bullet;
                          listStack.push(listItem);
                      }
                  } else if (char0 == QLatin1Char('h') && tagLength == 2) {
                      int level = tag.at(1).digitValue();
                      if (level >= 1 && level <= 6) {
                          if (!hasNewLine)
                              textOut.append(QChar::LineSeparator);
                          hasSpace = true;
                          prependSpace = false;
                          setFontSize(7 - level, format);
                          format.setFontWeight(QFont::Bold);
                          return true;
                      }
                  } else if (tag == QLatin1String("strong")) {
                      format.setFontWeight(QFont::Bold);
                      return true;
                  } else if (tag == QLatin1String("ol")) {
                      List listItem;
                      listItem.level = 0;
                      listItem.type = Ordered;
                      listItem.format = Decimal;
                      listStack.push(listItem);
                  } else if (tag == QLatin1String("li")) {
                      if (!hasNewLine)
                          textOut.append(QChar(QChar::LineSeparator));
                      if (!listStack.isEmpty()) {
                          int count = ++listStack.top().level;
                          for (int i = 0; i < listStack.size(); ++i)
                              textOut += QString(tabsize, QChar::Nbsp);
                          switch (listStack.top().format) {
                          case Decimal:
                              textOut += QString::number(count) % QLatin1Char('.');
                              break;
                          case LowerAlpha:
                              textOut += toAlpha(count, false) % QLatin1Char('.');
                              break;
                          case UpperAlpha:
                              textOut += toAlpha(count, true) % QLatin1Char('.');
                              break;
                          case LowerRoman:
                              textOut += toRoman(count, false) % QLatin1Char('.');
                              break;
                          case UpperRoman:
                              textOut += toRoman(count, true) % QLatin1Char('.');
                              break;
                          case Bullet:
                              textOut += bullet;
                              break;
                          case Disc:
                              textOut += disc;
                              break;
                          case Square:
                              textOut += square;
                              break;
                          }
                          textOut += QString(2, QChar::Nbsp);
                      }
                  }
                  return false;
              } else if (ch->isSpace()) {
                  // may have params.
                  QStringRef tag(&textIn, tagStart, tagLength);
                  if (tag == QLatin1String("font"))
                      return parseFontAttributes(ch, textIn, format);
                  if (tag == QLatin1String("ol")) {
                      parseOrderedListAttributes(ch, textIn);
                      return false; // doesn't modify format
                  }
                  if (tag == QLatin1String("ul")) {
                      parseUnorderedListAttributes(ch, textIn);
                      return false; // doesn't modify format
                  }
                  if (tag == QLatin1String("a")) {
                      return parseAnchorAttributes(ch, textIn, format);
                  }
                  if (tag == QLatin1String("img")) {
                      parseImageAttributes(ch, textIn, textOut);
                      return false;
                  }
                  if (*ch == greaterThan || ch->isNull())
                      continue;
              } else if (*ch != slash) {
                  tagLength++;
              }
              ++ch;
          }
          return false;
      }
      
      bool QQuickStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut)
      {
          skipSpace(ch);
      
          int tagStart = ch - textIn.constData();
          int tagLength = 0;
          while (!ch->isNull()) {
              if (*ch == greaterThan) {
                  if (tagLength == 0)
                      return false;
                  QStringRef tag(&textIn, tagStart, tagLength);
                  const QChar char0 = tag.at(0);
                  hasNewLine = false;
                  if (char0 == QLatin1Char('b')) {
                      if (tagLength == 1)
                          return true;
                      else if (tag.at(1) == QLatin1Char('r') && tagLength == 2)
                          return false;
                  } else if (char0 == QLatin1Char('i')) {
                      if (tagLength == 1)
                          return true;
                  } else if (char0 == QLatin1Char('a')) {
                      if (tagLength == 1)
                          return true;
                  } else if (char0 == QLatin1Char('p')) {
                      if (tagLength == 1) {
                          textOut.append(QChar::LineSeparator);
                          hasNewLine = true;
                          hasSpace = true;
                          return false;
                      } else if (tag == QLatin1String("pre")) {
                          preFormat = false;
                          if (!hasNewLine)
                              textOut.append(QChar::LineSeparator);
                          hasNewLine = true;
                          hasSpace = true;
                          return true;
                      }
                  } else if (char0 == QLatin1Char('u')) {
                      if (tagLength == 1)
                          return true;
                      else if (tag == QLatin1String("ul")) {
                          if (!listStack.isEmpty()) {
                              listStack.pop();
                              if (!listStack.count())
                                  textOut.append(QChar::LineSeparator);
                          }
                          return false;
                      }
                  } else if (char0 == QLatin1Char('h') && tagLength == 2) {
                      textOut.append(QChar::LineSeparator);
                      hasNewLine = true;
                      hasSpace = true;
                      return true;
                  } else if (tag == QLatin1String("font")) {
                      return true;
                  } else if (tag == QLatin1String("strong")) {
                      return true;
                  } else if (tag == QLatin1String("ol")) {
                      if (!listStack.isEmpty()) {
                          listStack.pop();
                          if (!listStack.count())
                              textOut.append(QChar::LineSeparator);
                      }
                      return false;
                  } else if (tag == QLatin1String("li")) {
                      return false;
                  }
                  return false;
              } else if (!ch->isSpace()){
                  tagLength++;
              }
              ++ch;
          }
      
          return false;
      }
      
      void QQuickStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut)
      {
          int entityStart = ch - textIn.constData();
          int entityLength = 0;
          while (!ch->isNull()) {
              if (*ch == QLatin1Char(';')) {
                  QStringRef entity(&textIn, entityStart, entityLength);
                  if (entity == QLatin1String("gt"))
                      textOut += QChar(62);
                  else if (entity == QLatin1String("lt"))
                      textOut += QChar(60);
                  else if (entity == QLatin1String("amp"))
                      textOut += QChar(38);
                  else if (entity == QLatin1String("quot"))
                      textOut += QChar(34);
                  return;
              }
              ++entityLength;
              ++ch;
          }
      }
      
      bool QQuickStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
      {
          bool valid = false;
          QPair attr;
          do {
              attr = parseAttribute(ch, textIn);
              if (attr.first == QLatin1String("color")) {
                  valid = true;
                  format.setForeground(QColor(attr.second.toString()));
              } else if (attr.first == QLatin1String("size")) {
                  valid = true;
                  int size = attr.second.toString().toInt();
                  if (attr.second.at(0) == QLatin1Char('-') || attr.second.at(0) == QLatin1Char('+'))
                      size += 3;
                  if (size >= 1 && size <= 7)
                      setFontSize(size, format);
              }
          } while (!ch->isNull() && !attr.first.isEmpty());
      
          return valid;
      }
      
      bool QQuickStyledTextPrivate::parseOrderedListAttributes(const QChar *&ch, const QString &textIn)
      {
          bool valid = false;
      
          List listItem;
          listItem.level = 0;
          listItem.type = Ordered;
          listItem.format = Decimal;
      
          QPair attr;
          do {
              attr = parseAttribute(ch, textIn);
              if (attr.first == QLatin1String("type")) {
                  valid = true;
                  if (attr.second == QLatin1String("a"))
                      listItem.format = LowerAlpha;
                  else if (attr.second == QLatin1String("A"))
                      listItem.format = UpperAlpha;
                  else if (attr.second == QLatin1String("i"))
                      listItem.format = LowerRoman;
                  else if (attr.second == QLatin1String("I"))
                      listItem.format = UpperRoman;
              }
          } while (!ch->isNull() && !attr.first.isEmpty());
      
          listStack.push(listItem);
          return valid;
      }
      
      bool QQuickStyledTextPrivate::parseUnorderedListAttributes(const QChar *&ch, const QString &textIn)
      {
          bool valid = false;
      
          List listItem;
          listItem.level = 0;
          listItem.type = Unordered;
          listItem.format = Bullet;
      
          QPair attr;
          do {
              attr = parseAttribute(ch, textIn);
              if (attr.first == QLatin1String("type")) {
                  valid = true;
                  if (attr.second == QLatin1String("disc"))
                      listItem.format = Disc;
                  else if (attr.second == QLatin1String("square"))
                      listItem.format = Square;
              }
          } while (!ch->isNull() && !attr.first.isEmpty());
      
          listStack.push(listItem);
          return valid;
      }
      
      bool QQuickStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
      {
          bool valid = false;
      
          QPair attr;
          do {
              attr = parseAttribute(ch, textIn);
              if (attr.first == QLatin1String("href")) {
                  format.setAnchorHref(attr.second.toString());
                  format.setAnchor(true);
                  format.setFontUnderline(true);
                  valid = true;
              }
          } while (!ch->isNull() && !attr.first.isEmpty());
      
          return valid;
      }
      
      void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut)
      {
          qreal imgWidth = 0.0;
      
          if (!updateImagePositions) {
              QQuickStyledTextImgTag *image = new QQuickStyledTextImgTag;
              image->position = textOut.length() + 1;
      
              QPair attr;
              do {
                  attr = parseAttribute(ch, textIn);
                  if (attr.first == QLatin1String("src")) {
                      image->url =  QUrl(attr.second.toString());
                  } else if (attr.first == QLatin1String("width")) {
                      image->size.setWidth(attr.second.toString().toInt());
                  } else if (attr.first == QLatin1String("height")) {
                      image->size.setHeight(attr.second.toString().toInt());
                  } else if (attr.first == QLatin1String("align")) {
                      if (attr.second.toString() == QLatin1String("top")) {
                          image->align = QQuickStyledTextImgTag::Top;
                      } else if (attr.second.toString() == QLatin1String("middle")) {
                          image->align = QQuickStyledTextImgTag::Middle;
                      }
                  }
              } while (!ch->isNull() && !attr.first.isEmpty());
      
              if (preloadImages && !image->size.isValid()) {
                  // if we don't know its size but the image is a local image,
                  // we load it in the pixmap cache and save its implicit size
                  // to avoid a relayout later on.
                  QUrl url = baseUrl.resolved(image->url);
                  if (url.isLocalFile()) {
                      image->pix = new QQuickPixmap(context->engine(), url, image->size);
                      if (image->pix && image->pix->isReady()) {
                          image->size = image->pix->implicitSize();
                      } else {
                          delete image->pix;
                          image->pix = 0;
                      }
                  }
              }
      
              imgWidth = image->size.width();
              imgTags->append(image);
      
          } else {
              // if we already have a list of img tags for this text
              // we only want to update the positions of these tags.
              QQuickStyledTextImgTag *image = imgTags->value(nbImages);
              image->position = textOut.length() + 1;
              imgWidth = image->size.width();
              QPair attr;
              do {
                  attr = parseAttribute(ch, textIn);
              } while (!ch->isNull() && !attr.first.isEmpty());
              nbImages++;
          }
      
          QFontMetricsF fm(layout.font());
          QString padding(qFloor(imgWidth / fm.width(QChar::Nbsp)), QChar::Nbsp);
          textOut += QLatin1Char(' ');
          textOut += padding;
          textOut += QLatin1Char(' ');
      }
      
      QPair QQuickStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn)
      {
          skipSpace(ch);
      
          int attrStart = ch - textIn.constData();
          int attrLength = 0;
          while (!ch->isNull()) {
              if (*ch == greaterThan) {
                  break;
              } else if (*ch == equals) {
                  ++ch;
                  if (*ch != singleQuote && *ch != doubleQuote) {
                      while (*ch != greaterThan && !ch->isNull())
                          ++ch;
                      break;
                  }
                  ++ch;
                  if (!attrLength)
                      break;
                  QStringRef attr(&textIn, attrStart, attrLength);
                  QStringRef val = parseValue(ch, textIn);
                  if (!val.isEmpty())
                      return QPair(attr,val);
                  break;
              } else {
                  ++attrLength;
              }
              ++ch;
          }
      
          return QPair();
      }
      
      QStringRef QQuickStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn)
      {
          int valStart = ch - textIn.constData();
          int valLength = 0;
          while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) {
              ++valLength;
              ++ch;
          }
          if (ch->isNull())
              return QStringRef();
          ++ch; // skip quote
      
          return QStringRef(&textIn, valStart, valLength);
      }
      
      QString QQuickStyledTextPrivate::toAlpha(int value, bool upper)
      {
          const char baseChar = upper ? 'A' : 'a';
      
          QString result;
          int c = value;
          while (c > 0) {
              c--;
              result.prepend(QChar(baseChar + (c % 26)));
              c /= 26;
          }
          return result;
      }
      
      QString QQuickStyledTextPrivate::toRoman(int value, bool upper)
      {
          QString result = QLatin1String("?");
          // works for up to 4999 items
          if (value < 5000) {
              QByteArray romanNumeral;
      
              static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm";
              static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM";
              QByteArray romanSymbols;
              if (!upper)
                  romanSymbols = QByteArray::fromRawData(romanSymbolsLower, sizeof(romanSymbolsLower));
              else
                  romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper));
      
              int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
              int n = value;
              for (int i = 12; i >= 0; n %= c[i], i--) {
                  int q = n / c[i];
                  if (q > 0) {
                      int startDigit = i + (i + 3) / 4;
                      int numDigits;
                      if (i % 4) {
                          if ((i - 2) % 4)
                              numDigits = 2;
                          else
                              numDigits = 1;
                      }
                      else
                          numDigits = q;
                      romanNumeral.append(romanSymbols.mid(startDigit, numDigits));
                  }
              }
              result = QString::fromLatin1(romanNumeral);
          }
          return result;
      }
      
      QT_END_NAMESPACE