blob: 660852ba83fe4387040e22cfb8c4ec9ccf3a8bbe [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQuick module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QStack>
#include <QVector>
#include <QPainter>
#include <QTextLayout>
#include <QDebug>
#include <qmath.h>
#include "qquickstyledtext_p.h"
#include <QQmlContext>
/*
QQuickStyledText supports few tags:
<b></b> - bold
<del></del> - strike out (removed content)
<s></s> - strike out (no longer accurate or no longer relevant content)
<strong></strong> - bold
<i></i> - italic
<br> - new line
<p> - paragraph
<u> - underlined text
<font color="color_name" size="1-7"></font>
<h1> to <h6> - headers
<a href=""> - anchor
<ol type="">, <ul type=""> and <li> - ordered and unordered lists
<pre></pre> - preformated
<img src=""> - 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<QQuickStyledTextImgTag*> &imgTags,
const QUrl &baseUrl,
QQmlContext *context,
bool preloadImages,
bool *fontSizeModified)
: text(t), layout(l), imgTags(&imgTags), baseFont(layout.font()), baseUrl(baseUrl),
fontSizeModified(fontSizeModified), context(context), preloadImages(preloadImages)
{
}
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<QStringRef,QStringRef> 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<QQuickStyledTextImgTag*> *imgTags;
QFont baseFont;
QStack<List> listStack;
QUrl baseUrl;
bool *fontSizeModified;
QQmlContext *context;
int nbImages = 0;
bool hasNewLine = true;
bool updateImagePositions = false;
bool preFormat = false;
bool prependSpace = false;
bool hasSpace = true;
bool preloadImages;
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<QQuickStyledTextImgTag*> &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<QQuickStyledTextImgTag*> &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()
{
QVector<QTextLayout::FormatRange> ranges;
QStack<QTextCharFormat> 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.setFormats(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 (char0 == QLatin1Char('s')) {
if (tagLength == 1) {
format.setFontStrikeOut(true);
return true;
} else if (tag == QLatin1String("strong")) {
format.setFontWeight(QFont::Bold);
return true;
}
} else if (tag == QLatin1String("del")) {
format.setFontStrikeOut(true);
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 (char0 == QLatin1Char('s')) {
if (tagLength == 1) {
return true;
} else if (tag == QLatin1String("strong")) {
return true;
}
} else if (tag == QLatin1String("del")) {
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);
else if (entity == QLatin1String("nbsp"))
textOut += QChar(QChar::Nbsp);
return;
} else if (*ch == QLatin1Char(' ')) {
QStringRef entity(&textIn, entityStart - 1, entityLength + 1);
textOut += entity + *ch;
return;
}
++entityLength;
++ch;
}
}
bool QQuickStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
{
bool valid = false;
QPair<QStringRef,QStringRef> 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<QStringRef,QStringRef> 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<QStringRef,QStringRef> 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<QStringRef,QStringRef> 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;
QFontMetricsF fm(layout.font());
const qreal spaceWidth = fm.horizontalAdvance(QChar::Nbsp);
const bool trailingSpace = textOut.endsWith(space);
if (!updateImagePositions) {
QQuickStyledTextImgTag *image = new QQuickStyledTextImgTag;
image->position = textOut.length() + (trailingSpace ? 0 : 1);
QPair<QStringRef,QStringRef> 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, QRect(), image->size);
if (image->pix && image->pix->isReady()) {
image->size = image->pix->implicitSize();
} else {
delete image->pix;
image->pix = nullptr;
}
}
}
imgWidth = image->size.width();
image->offset = -std::fmod(imgWidth, spaceWidth) / 2.0;
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() + (trailingSpace ? 0 : 1);
imgWidth = image->size.width();
image->offset = -std::fmod(imgWidth, spaceWidth) / 2.0;
QPair<QStringRef,QStringRef> attr;
do {
attr = parseAttribute(ch, textIn);
} while (!ch->isNull() && !attr.first.isEmpty());
nbImages++;
}
QString padding(qFloor(imgWidth / spaceWidth), QChar::Nbsp);
if (!trailingSpace)
textOut += QLatin1Char(' ');
textOut += padding + QLatin1Char(' ');
}
QPair<QStringRef,QStringRef> 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<QStringRef,QStringRef>(attr,val);
break;
} else {
++attrLength;
}
++ch;
}
return QPair<QStringRef,QStringRef>();
}
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