f31522130f
Build system (5 gaps hardened): - COOKBOOK_OFFLINE defaults to true (fork-mode) - normalize_patch handles diff -ruN format - New 'repo validate-patches' command (25/25 relibc patches) - 14 patched Qt/Wayland/display recipes added to protected list - relibc archive regenerated with current patch chain Boot fixes (fixable): - Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset) - D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped) - redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped) - daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch) - udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async) - relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs - greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait) - greeter-ui: built and linked (header guard unification, sem_compat stubs removed) - mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps - greeter config: removed stale keymapd dependency from display/greeter services - prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified Unfixable (diagnosed, upstream): - i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort - kded6/greeter-ui: page fault 0x8 — Qt library null deref - Thread panics fd != -1 — Rust std library on Redox - DHCP timeout / eth0 MAC — QEMU user-mode networking - hwrngd/thermald — no hardware RNG/thermal in VM - live preload allocation — BIOS memory fragmentation, continues on demand
835 lines
30 KiB
C++
835 lines
30 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
// Qt-Security score:critical reason:data-parser
|
|
|
|
#include <QStack>
|
|
#include <QList>
|
|
#include <QPainter>
|
|
#include <QTextLayout>
|
|
#include <QDebug>
|
|
#include <qmath.h>
|
|
#include "qquickstyledtext_p.h"
|
|
#include <QQmlContext>
|
|
#include <QtGui/private/qtexthtmlparser_p.h>
|
|
#include <QtGui/private/qoutlinemapper_p.h>
|
|
|
|
#ifndef QQUICKSTYLEDPARSER_COORD_LIMIT
|
|
# define QQUICKSTYLEDPARSER_COORD_LIMIT QT_RASTER_COORD_LIMIT
|
|
#endif
|
|
|
|
Q_STATIC_LOGGING_CATEGORY(lcStyledText, "qt.quick.styledtext")
|
|
|
|
/*
|
|
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);
|
|
std::pair<QStringView,QStringView> parseAttribute(const QChar *&ch, const QString &textIn);
|
|
QStringView 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(' '));
|
|
|
|
namespace {
|
|
bool is_equal_ignoring_case(QStringView s1, QLatin1StringView s2) noexcept
|
|
{
|
|
return s1.compare(s2, Qt::CaseInsensitive) == 0;
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
QList<QTextLayout::FormatRange> ranges;
|
|
QStack<QTextCharFormat> formatStack;
|
|
|
|
QString drawText;
|
|
drawText.reserve(text.size());
|
|
|
|
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.size() && formatStack.size()) {
|
|
if (formatChanged) {
|
|
QTextLayout::FormatRange formatRange;
|
|
formatRange.format = formatStack.top();
|
|
formatRange.start = rangeStart;
|
|
formatRange.length = drawText.size() - rangeStart;
|
|
ranges.append(formatRange);
|
|
formatChanged = false;
|
|
} else if (ranges.size()) {
|
|
ranges.last().length += drawText.size() - rangeStart;
|
|
}
|
|
}
|
|
rangeStart = drawText.size();
|
|
++ch;
|
|
if (*ch == slash) {
|
|
++ch;
|
|
if (parseCloseTag(ch, text, drawText)) {
|
|
if (formatStack.size()) {
|
|
formatChanged = true;
|
|
formatStack.pop();
|
|
}
|
|
}
|
|
} else {
|
|
QTextCharFormat format;
|
|
if (formatStack.size())
|
|
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.size() && formatStack.size()) {
|
|
if (formatChanged) {
|
|
QTextLayout::FormatRange formatRange;
|
|
formatRange.format = formatStack.top();
|
|
formatRange.start = rangeStart;
|
|
formatRange.length = drawText.size() - rangeStart;
|
|
ranges.append(formatRange);
|
|
} else if (ranges.size()) {
|
|
ranges.last().length += drawText.size() - 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(QStringView(textIn).mid(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;
|
|
auto tag = QStringView(textIn).mid(tagStart, tagLength);
|
|
const QChar char0 = tag.at(0).toLower();
|
|
if (char0 == QLatin1Char('b')) {
|
|
if (tagLength == 1) {
|
|
format.setFontWeight(QFont::Bold);
|
|
return true;
|
|
} else if (tagLength == 2 && tag.at(1).toLower() == 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 (is_equal_ignoring_case(tag, QLatin1String("pre"))) {
|
|
preFormat = true;
|
|
if (!hasNewLine)
|
|
textOut.append(QChar::LineSeparator);
|
|
format.setFontFamilies(QStringList {QString::fromLatin1("Courier New"), QString::fromLatin1("courier")});
|
|
format.setFontFixedPitch(true);
|
|
return true;
|
|
}
|
|
} else if (char0 == QLatin1Char('u')) {
|
|
if (tagLength == 1) {
|
|
format.setFontUnderline(true);
|
|
return true;
|
|
} else if (is_equal_ignoring_case(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 (is_equal_ignoring_case(tag, QLatin1String("strong"))) {
|
|
format.setFontWeight(QFont::Bold);
|
|
return true;
|
|
}
|
|
} else if (is_equal_ignoring_case(tag, QLatin1String("del"))) {
|
|
format.setFontStrikeOut(true);
|
|
return true;
|
|
} else if (is_equal_ignoring_case(tag, QLatin1String("ol"))) {
|
|
List listItem;
|
|
listItem.level = 0;
|
|
listItem.type = Ordered;
|
|
listItem.format = Decimal;
|
|
listStack.push(listItem);
|
|
} else if (is_equal_ignoring_case(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.
|
|
auto tag = QStringView(textIn).mid(tagStart, tagLength);
|
|
if (is_equal_ignoring_case(tag, QLatin1String("font")))
|
|
return parseFontAttributes(ch, textIn, format);
|
|
if (is_equal_ignoring_case(tag, QLatin1String("ol"))) {
|
|
parseOrderedListAttributes(ch, textIn);
|
|
return false; // doesn't modify format
|
|
}
|
|
if (is_equal_ignoring_case(tag, QLatin1String("ul"))) {
|
|
parseUnorderedListAttributes(ch, textIn);
|
|
return false; // doesn't modify format
|
|
}
|
|
if (is_equal_ignoring_case(tag, QLatin1String("a"))) {
|
|
return parseAnchorAttributes(ch, textIn, format);
|
|
}
|
|
if (is_equal_ignoring_case(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;
|
|
auto tag = QStringView(textIn).mid(tagStart, tagLength);
|
|
const QChar char0 = tag.at(0).toLower();
|
|
hasNewLine = false;
|
|
if (char0 == QLatin1Char('b')) {
|
|
if (tagLength == 1)
|
|
return true;
|
|
else if (tag.at(1).toLower() == 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 (is_equal_ignoring_case(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 (is_equal_ignoring_case(tag, QLatin1String("ul"))) {
|
|
if (!listStack.isEmpty()) {
|
|
listStack.pop();
|
|
if (!listStack.size())
|
|
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 (is_equal_ignoring_case(tag, QLatin1String("font"))) {
|
|
return true;
|
|
} else if (char0 == QLatin1Char('s')) {
|
|
if (tagLength == 1) {
|
|
return true;
|
|
} else if (is_equal_ignoring_case(tag, QLatin1String("strong"))) {
|
|
return true;
|
|
}
|
|
} else if (is_equal_ignoring_case(tag, QLatin1String("del"))) {
|
|
return true;
|
|
} else if (is_equal_ignoring_case(tag, QLatin1String("ol"))) {
|
|
if (!listStack.isEmpty()) {
|
|
listStack.pop();
|
|
if (!listStack.size())
|
|
textOut.append(QChar::LineSeparator);
|
|
}
|
|
return false;
|
|
} else if (is_equal_ignoring_case(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(';')) {
|
|
auto entity = QStringView(textIn).mid(entityStart, entityLength);
|
|
#if QT_CONFIG(texthtmlparser)
|
|
const QString parsedEntity = QTextHtmlParser::parseEntity(entity);
|
|
if (!parsedEntity.isNull())
|
|
textOut += parsedEntity;
|
|
else
|
|
#endif
|
|
qCWarning(lcStyledText) << "StyledText doesn't support entity" << entity;
|
|
return;
|
|
} else if (*ch == QLatin1Char(' ')) {
|
|
auto entity = QStringView(textIn).mid(entityStart - 1, entityLength + 1);
|
|
textOut += entity + *ch;
|
|
return;
|
|
}
|
|
++entityLength;
|
|
++ch;
|
|
}
|
|
}
|
|
|
|
bool QQuickStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format)
|
|
{
|
|
bool valid = false;
|
|
std::pair<QStringView,QStringView> attr;
|
|
do {
|
|
attr = parseAttribute(ch, textIn);
|
|
if (is_equal_ignoring_case(attr.first, QLatin1String("color"))) {
|
|
valid = true;
|
|
format.setForeground(QColor::fromString(attr.second));
|
|
} else if (is_equal_ignoring_case(attr.first, QLatin1String("size"))) {
|
|
valid = true;
|
|
int size = attr.second.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;
|
|
|
|
std::pair<QStringView,QStringView> attr;
|
|
do {
|
|
attr = parseAttribute(ch, textIn);
|
|
if (is_equal_ignoring_case(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;
|
|
|
|
std::pair<QStringView,QStringView> attr;
|
|
do {
|
|
attr = parseAttribute(ch, textIn);
|
|
if (is_equal_ignoring_case(attr.first, QLatin1String("type"))) {
|
|
valid = true;
|
|
if (is_equal_ignoring_case(attr.second, QLatin1String("disc")))
|
|
listItem.format = Disc;
|
|
else if (is_equal_ignoring_case(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;
|
|
|
|
std::pair<QStringView,QStringView> attr;
|
|
do {
|
|
attr = parseAttribute(ch, textIn);
|
|
if (is_equal_ignoring_case(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.size() + (trailingSpace ? 0 : 1);
|
|
|
|
std::pair<QStringView,QStringView> attr;
|
|
do {
|
|
attr = parseAttribute(ch, textIn);
|
|
if (is_equal_ignoring_case(attr.first, QLatin1String("src"))) {
|
|
image->url = QUrl(attr.second.toString());
|
|
} else if (is_equal_ignoring_case(attr.first, QLatin1String("width"))) {
|
|
bool ok;
|
|
int v = attr.second.toString().toInt(&ok);
|
|
if (ok && v <= QQUICKSTYLEDPARSER_COORD_LIMIT)
|
|
image->size.setWidth(v);
|
|
else
|
|
qCWarning(lcStyledText) << "Invalid width provided for <img>";
|
|
} else if (is_equal_ignoring_case(attr.first, QLatin1String("height"))) {
|
|
bool ok;
|
|
int v = attr.second.toString().toInt(&ok);
|
|
if (ok && v <= QQUICKSTYLEDPARSER_COORD_LIMIT)
|
|
image->size.setHeight(v);
|
|
else
|
|
qCWarning(lcStyledText) << "Invalid height provided for <img>";
|
|
} else if (is_equal_ignoring_case(attr.first, QLatin1String("align"))) {
|
|
if (is_equal_ignoring_case(attr.second, QLatin1String("top"))) {
|
|
image->align = QQuickStyledTextImgTag::Top;
|
|
} else if (is_equal_ignoring_case(attr.second, 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.reset(new QQuickPixmap(context->engine(), url, QRect(), image->size));
|
|
if (image->pix && image->pix->isReady()) {
|
|
image->size = image->pix->implicitSize();
|
|
} else {
|
|
image->pix.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return immediately if img tag has invalid url
|
|
if (!image->url.isValid()) {
|
|
delete image;
|
|
qCWarning(lcStyledText) << "StyledText - Invalid base url in img tag";
|
|
} else {
|
|
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.size() + (trailingSpace ? 0 : 1);
|
|
imgWidth = image->size.width();
|
|
image->offset = -std::fmod(imgWidth, spaceWidth) / 2.0;
|
|
std::pair<QStringView,QStringView> 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(' ');
|
|
}
|
|
|
|
std::pair<QStringView,QStringView> 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;
|
|
auto attr = QStringView(textIn).mid(attrStart, attrLength);
|
|
QStringView val = parseValue(ch, textIn);
|
|
if (!val.isEmpty())
|
|
return std::pair<QStringView,QStringView>(attr,val);
|
|
break;
|
|
} else {
|
|
++attrLength;
|
|
}
|
|
++ch;
|
|
}
|
|
|
|
return std::pair<QStringView,QStringView>();
|
|
}
|
|
|
|
QStringView 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 QStringView();
|
|
++ch; // skip quote
|
|
|
|
return QStringView(textIn).mid(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
|