mirror of https://github.com/procxx/kepka.git
Make Ui::Text::Parser methods non-inclass.
This commit is contained in:
parent
2d10e3e432
commit
522e66b2db
|
@ -18,6 +18,40 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace Text {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Qt::LayoutDirection StringDirection(const QString &str, int32 from, int32 to) {
|
||||||
|
const ushort *p = reinterpret_cast<const ushort*>(str.unicode()) + from;
|
||||||
|
const ushort *end = p + (to - from);
|
||||||
|
while (p < end) {
|
||||||
|
uint ucs4 = *p;
|
||||||
|
if (QChar::isHighSurrogate(ucs4) && p < end - 1) {
|
||||||
|
ushort low = p[1];
|
||||||
|
if (QChar::isLowSurrogate(low)) {
|
||||||
|
ucs4 = QChar::surrogateToUcs4(ucs4, low);
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (QChar::direction(ucs4)) {
|
||||||
|
case QChar::DirL:
|
||||||
|
return Qt::LeftToRight;
|
||||||
|
case QChar::DirR:
|
||||||
|
case QChar::DirAL:
|
||||||
|
return Qt::RightToLeft;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
return Qt::LayoutDirectionAuto;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace Text
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
bool chIsBad(QChar ch) {
|
bool chIsBad(QChar ch) {
|
||||||
return (ch == 0)
|
return (ch == 0)
|
||||||
|| (ch >= 8232 && ch < 8237)
|
|| (ch >= 8232 && ch < 8237)
|
||||||
|
@ -150,118 +184,237 @@ namespace Text {
|
||||||
|
|
||||||
class Parser {
|
class Parser {
|
||||||
public:
|
public:
|
||||||
static Qt::LayoutDirection stringDirection(const QString &str, int32 from, int32 to) {
|
Parser(
|
||||||
const ushort *p = reinterpret_cast<const ushort*>(str.unicode()) + from;
|
not_null<String*> string,
|
||||||
const ushort *end = p + (to - from);
|
const QString &text,
|
||||||
while (p < end) {
|
const TextParseOptions &options);
|
||||||
uint ucs4 = *p;
|
Parser(
|
||||||
if (QChar::isHighSurrogate(ucs4) && p < end - 1) {
|
not_null<String*> string,
|
||||||
ushort low = p[1];
|
const TextWithEntities &textWithEntities,
|
||||||
if (QChar::isLowSurrogate(low)) {
|
const TextParseOptions &options);
|
||||||
ucs4 = QChar::surrogateToUcs4(ucs4, low);
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (QChar::direction(ucs4)) {
|
|
||||||
case QChar::DirL:
|
|
||||||
return Qt::LeftToRight;
|
|
||||||
case QChar::DirR:
|
|
||||||
case QChar::DirAL:
|
|
||||||
return Qt::RightToLeft;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
return Qt::LayoutDirectionAuto;
|
|
||||||
}
|
|
||||||
|
|
||||||
void blockCreated() {
|
private:
|
||||||
sumWidth += _t->_blocks.back()->f_width();
|
enum LinkDisplayStatus {
|
||||||
if (sumWidth.floor().toInt() > stopAfterWidth) {
|
LinkDisplayedFull,
|
||||||
sumFinished = true;
|
LinkDisplayedElided,
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
void createBlock(int32 skipBack = 0) {
|
struct TextLinkData {
|
||||||
if (lnkIndex < 0x8000 && lnkIndex > maxLnkIndex) maxLnkIndex = lnkIndex;
|
TextLinkData() = default;
|
||||||
int32 len = int32(_t->_text.size()) + skipBack - blockStart;
|
TextLinkData(
|
||||||
|
EntityType type,
|
||||||
|
const QString &text,
|
||||||
|
const QString &data,
|
||||||
|
LinkDisplayStatus displayStatus);
|
||||||
|
EntityType type = EntityType::Invalid;
|
||||||
|
QString text, data;
|
||||||
|
LinkDisplayStatus displayStatus = LinkDisplayedFull;
|
||||||
|
};
|
||||||
|
|
||||||
|
void blockCreated();
|
||||||
|
void createBlock(int32 skipBack = 0);
|
||||||
|
void createSkipBlock(int32 w, int32 h);
|
||||||
|
void createNewlineBlock();
|
||||||
|
bool checkCommand();
|
||||||
|
|
||||||
|
// Returns true if at least one entity was parsed in the current position.
|
||||||
|
bool checkEntities();
|
||||||
|
bool readSkipBlockCommand();
|
||||||
|
bool readCommand();
|
||||||
|
void parseCurrentChar();
|
||||||
|
void parseEmojiFromCurrent();
|
||||||
|
|
||||||
|
bool isInvalidEntity(const EntityInText &entity) const;
|
||||||
|
bool isLinkEntity(const EntityInText &entity) const;
|
||||||
|
|
||||||
|
void parse(const TextParseOptions &options);
|
||||||
|
void computeLinkText(
|
||||||
|
const QString &linkData,
|
||||||
|
QString *outLinkText,
|
||||||
|
LinkDisplayStatus *outDisplayStatus);
|
||||||
|
|
||||||
|
not_null<String*> _t;
|
||||||
|
TextWithEntities _source;
|
||||||
|
const QChar *_start = nullptr;
|
||||||
|
const QChar *_end = nullptr;
|
||||||
|
const QChar *_ptr = nullptr;
|
||||||
|
bool _rich = false;
|
||||||
|
bool _multiline = false;
|
||||||
|
EntitiesInText::const_iterator _waitingEntity;
|
||||||
|
EntitiesInText::const_iterator _entitiesEnd;
|
||||||
|
|
||||||
|
QVector<TextLinkData> _links;
|
||||||
|
QMap<const QChar*, QList<int32>> _removeFlags;
|
||||||
|
|
||||||
|
uint16 _maxLnkIndex = 0;
|
||||||
|
|
||||||
|
// current state
|
||||||
|
int32 _flags = 0;
|
||||||
|
uint16 _lnkIndex = 0;
|
||||||
|
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero
|
||||||
|
int32 _blockStart = 0; // offset in result, from which current parsed block is started
|
||||||
|
int32 _diacs = 0; // diac chars skipped without good char
|
||||||
|
QFixed _sumWidth;
|
||||||
|
QFixed _stopAfterWidth; // summary width of all added words
|
||||||
|
bool _sumFinished = false;
|
||||||
|
bool _newlineAwaited = false;
|
||||||
|
|
||||||
|
// current char data
|
||||||
|
QChar _ch; // current char (low surrogate, if current char is surrogate pair)
|
||||||
|
int32 _emojiLookback = 0; // how far behind the current ptr to look for current emoji
|
||||||
|
bool _lastSkipped = false; // did we skip current char
|
||||||
|
bool _checkTilde = false; // do we need a special text block for tilde symbol
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser::TextLinkData::TextLinkData(
|
||||||
|
EntityType type,
|
||||||
|
const QString &text,
|
||||||
|
const QString &data,
|
||||||
|
LinkDisplayStatus displayStatus)
|
||||||
|
: type(type)
|
||||||
|
, text(text)
|
||||||
|
, data(data)
|
||||||
|
, displayStatus(displayStatus) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser::Parser(
|
||||||
|
not_null<String*> string,
|
||||||
|
const QString &text,
|
||||||
|
const TextParseOptions &options)
|
||||||
|
: _t(string)
|
||||||
|
, _source { text }
|
||||||
|
, _rich(options.flags & TextParseRichText)
|
||||||
|
, _multiline(options.flags & TextParseMultiline)
|
||||||
|
, _stopAfterWidth(QFIXED_MAX) {
|
||||||
|
if (options.flags & TextParseLinks) {
|
||||||
|
TextUtilities::ParseEntities(_source, options.flags, _rich);
|
||||||
|
}
|
||||||
|
parse(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser::Parser(
|
||||||
|
not_null<String*> string,
|
||||||
|
const TextWithEntities &textWithEntities,
|
||||||
|
const TextParseOptions &options)
|
||||||
|
: _t(string)
|
||||||
|
, _source(textWithEntities)
|
||||||
|
, _rich(options.flags & TextParseRichText)
|
||||||
|
, _multiline(options.flags & TextParseMultiline)
|
||||||
|
, _stopAfterWidth(QFIXED_MAX) {
|
||||||
|
auto &preparsed = textWithEntities.entities;
|
||||||
|
if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) {
|
||||||
|
bool parseMentions = (options.flags & TextParseMentions);
|
||||||
|
bool parseHashtags = (options.flags & TextParseHashtags);
|
||||||
|
bool parseBotCommands = (options.flags & TextParseBotCommands);
|
||||||
|
bool parseMarkdown = (options.flags & TextParseMarkdown);
|
||||||
|
if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMarkdown) {
|
||||||
|
int32 i = 0, l = preparsed.size();
|
||||||
|
_source.entities.clear();
|
||||||
|
_source.entities.reserve(l);
|
||||||
|
const QChar s = _source.text.size();
|
||||||
|
for (; i < l; ++i) {
|
||||||
|
auto type = preparsed.at(i).type();
|
||||||
|
if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) ||
|
||||||
|
(type == EntityType::Hashtag && !parseHashtags) ||
|
||||||
|
(type == EntityType::Cashtag && !parseHashtags) ||
|
||||||
|
(type == EntityType::BotCommand && !parseBotCommands) || // #TODO entities
|
||||||
|
((type == EntityType::Bold || type == EntityType::Italic || type == EntityType::Code || type == EntityType::Pre) && !parseMarkdown)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_source.entities.push_back(preparsed.at(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parse(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::blockCreated() {
|
||||||
|
_sumWidth += _t->_blocks.back()->f_width();
|
||||||
|
if (_sumWidth.floor().toInt() > _stopAfterWidth) {
|
||||||
|
_sumFinished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::createBlock(int32 skipBack) {
|
||||||
|
if (_lnkIndex < 0x8000 && _lnkIndex > _maxLnkIndex) _maxLnkIndex = _lnkIndex;
|
||||||
|
int32 len = int32(_t->_text.size()) + skipBack - _blockStart;
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
bool newline = !emoji && (len == 1 && _t->_text.at(blockStart) == QChar::LineFeed);
|
bool newline = !_emoji && (len == 1 && _t->_text.at(_blockStart) == QChar::LineFeed);
|
||||||
if (newlineAwaited) {
|
if (_newlineAwaited) {
|
||||||
newlineAwaited = false;
|
_newlineAwaited = false;
|
||||||
if (!newline) {
|
if (!newline) {
|
||||||
_t->_text.insert(blockStart, QChar::LineFeed);
|
_t->_text.insert(_blockStart, QChar::LineFeed);
|
||||||
createBlock(skipBack - len);
|
createBlock(skipBack - len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastSkipped = false;
|
_lastSkipped = false;
|
||||||
if (emoji) {
|
if (_emoji) {
|
||||||
_t->_blocks.push_back(std::make_unique<EmojiBlock>(_t->_st->font, _t->_text, blockStart, len, flags, lnkIndex, emoji));
|
_t->_blocks.push_back(std::make_unique<EmojiBlock>(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _emoji));
|
||||||
emoji = 0;
|
_emoji = nullptr;
|
||||||
lastSkipped = true;
|
_lastSkipped = true;
|
||||||
} else if (newline) {
|
} else if (newline) {
|
||||||
_t->_blocks.push_back(std::make_unique<NewlineBlock>(_t->_st->font, _t->_text, blockStart, len, flags, lnkIndex));
|
_t->_blocks.push_back(std::make_unique<NewlineBlock>(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex));
|
||||||
} else {
|
} else {
|
||||||
_t->_blocks.push_back(std::make_unique<TextBlock>(_t->_st->font, _t->_text, _t->_minResizeWidth, blockStart, len, flags, lnkIndex));
|
_t->_blocks.push_back(std::make_unique<TextBlock>(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex));
|
||||||
}
|
}
|
||||||
blockStart += len;
|
_blockStart += len;
|
||||||
blockCreated();
|
blockCreated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void createSkipBlock(int32 w, int32 h) {
|
void Parser::createSkipBlock(int32 w, int32 h) {
|
||||||
createBlock();
|
createBlock();
|
||||||
_t->_text.push_back('_');
|
_t->_text.push_back('_');
|
||||||
_t->_blocks.push_back(std::make_unique<SkipBlock>(_t->_st->font, _t->_text, blockStart++, w, h, lnkIndex));
|
_t->_blocks.push_back(std::make_unique<SkipBlock>(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex));
|
||||||
blockCreated();
|
blockCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
void createNewlineBlock() {
|
void Parser::createNewlineBlock() {
|
||||||
createBlock();
|
createBlock();
|
||||||
_t->_text.push_back(QChar::LineFeed);
|
_t->_text.push_back(QChar::LineFeed);
|
||||||
createBlock();
|
createBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkCommand() {
|
bool Parser::checkCommand() {
|
||||||
bool result = false;
|
bool result = false;
|
||||||
for (QChar c = ((ptr < end) ? *ptr : 0); c == TextCommand; c = ((ptr < end) ? *ptr : 0)) {
|
for (QChar c = ((_ptr < _end) ? *_ptr : 0); c == TextCommand; c = ((_ptr < _end) ? *_ptr : 0)) {
|
||||||
if (!readCommand()) {
|
if (!readCommand()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if at least one entity was parsed in the current position.
|
// Returns true if at least one entity was parsed in the current position.
|
||||||
bool checkEntities() {
|
bool Parser::checkEntities() {
|
||||||
while (!removeFlags.isEmpty() && (ptr >= removeFlags.firstKey() || ptr >= end)) {
|
while (!_removeFlags.isEmpty() && (_ptr >= _removeFlags.firstKey() || _ptr >= _end)) {
|
||||||
const QList<int32> &removing(removeFlags.first());
|
const QList<int32> &removing(_removeFlags.first());
|
||||||
for (int32 i = removing.size(); i > 0;) {
|
for (int32 i = removing.size(); i > 0;) {
|
||||||
int32 flag = removing.at(--i);
|
int32 flag = removing.at(--i);
|
||||||
if (flags & flag) {
|
if (_flags & flag) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags &= ~flag;
|
_flags &= ~flag;
|
||||||
if (flag == TextBlockFPre
|
if (flag == TextBlockFPre
|
||||||
&& !_t->_blocks.empty()
|
&& !_t->_blocks.empty()
|
||||||
&& _t->_blocks.back()->type() != TextBlockTNewline) {
|
&& _t->_blocks.back()->type() != TextBlockTNewline) {
|
||||||
newlineAwaited = true;
|
_newlineAwaited = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeFlags.erase(removeFlags.begin());
|
_removeFlags.erase(_removeFlags.begin());
|
||||||
}
|
}
|
||||||
while (waitingEntity != entitiesEnd && start + waitingEntity->offset() + waitingEntity->length() <= ptr) {
|
while (_waitingEntity != _entitiesEnd && _start + _waitingEntity->offset() + _waitingEntity->length() <= _ptr) {
|
||||||
++waitingEntity;
|
++_waitingEntity;
|
||||||
}
|
}
|
||||||
if (waitingEntity == entitiesEnd || ptr < start + waitingEntity->offset()) {
|
if (_waitingEntity == _entitiesEnd || _ptr < _start + _waitingEntity->offset()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 startFlags = 0;
|
int32 startFlags = 0;
|
||||||
QString linkData, linkText;
|
QString linkData, linkText;
|
||||||
auto type = waitingEntity->type(), linkType = EntityType::Invalid;
|
auto type = _waitingEntity->type(), linkType = EntityType::Invalid;
|
||||||
LinkDisplayStatus linkDisplayStatus = LinkDisplayedFull;
|
LinkDisplayStatus linkDisplayStatus = LinkDisplayedFull;
|
||||||
if (type == EntityType::Bold) {
|
if (type == EntityType::Bold) {
|
||||||
startFlags = TextBlockFSemibold;
|
startFlags = TextBlockFSemibold;
|
||||||
|
@ -282,7 +435,7 @@ public:
|
||||||
|| type == EntityType::Cashtag
|
|| type == EntityType::Cashtag
|
||||||
|| type == EntityType::BotCommand) {
|
|| type == EntityType::BotCommand) {
|
||||||
linkType = type;
|
linkType = type;
|
||||||
linkData = QString(start + waitingEntity->offset(), waitingEntity->length());
|
linkData = QString(_start + _waitingEntity->offset(), _waitingEntity->length());
|
||||||
if (linkType == EntityType::Url) {
|
if (linkType == EntityType::Url) {
|
||||||
computeLinkText(linkData, &linkText, &linkDisplayStatus);
|
computeLinkText(linkData, &linkText, &linkDisplayStatus);
|
||||||
} else {
|
} else {
|
||||||
|
@ -290,195 +443,195 @@ public:
|
||||||
}
|
}
|
||||||
} else if (type == EntityType::CustomUrl || type == EntityType::MentionName) {
|
} else if (type == EntityType::CustomUrl || type == EntityType::MentionName) {
|
||||||
linkType = type;
|
linkType = type;
|
||||||
linkData = waitingEntity->data();
|
linkData = _waitingEntity->data();
|
||||||
linkText = QString(start + waitingEntity->offset(), waitingEntity->length());
|
linkText = QString(_start + _waitingEntity->offset(), _waitingEntity->length());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (linkType != EntityType::Invalid) {
|
if (linkType != EntityType::Invalid) {
|
||||||
createBlock();
|
createBlock();
|
||||||
|
|
||||||
links.push_back(TextLinkData(linkType, linkText, linkData, linkDisplayStatus));
|
_links.push_back(TextLinkData(linkType, linkText, linkData, linkDisplayStatus));
|
||||||
lnkIndex = 0x8000 + links.size();
|
_lnkIndex = 0x8000 + _links.size();
|
||||||
|
|
||||||
for (auto entityEnd = start + waitingEntity->offset() + waitingEntity->length(); ptr < entityEnd; ++ptr) {
|
for (auto entityEnd = _start + _waitingEntity->offset() + _waitingEntity->length(); _ptr < entityEnd; ++_ptr) {
|
||||||
parseCurrentChar();
|
parseCurrentChar();
|
||||||
parseEmojiFromCurrent();
|
parseEmojiFromCurrent();
|
||||||
|
|
||||||
if (sumFinished || _t->_text.size() >= 0x8000) break; // 32k max
|
if (_sumFinished || _t->_text.size() >= 0x8000) break; // 32k max
|
||||||
}
|
}
|
||||||
createBlock();
|
createBlock();
|
||||||
|
|
||||||
lnkIndex = 0;
|
_lnkIndex = 0;
|
||||||
} else if (startFlags) {
|
} else if (startFlags) {
|
||||||
if (!(flags & startFlags)) {
|
if (!(_flags & startFlags)) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags |= startFlags;
|
_flags |= startFlags;
|
||||||
removeFlags[start + waitingEntity->offset() + waitingEntity->length()].push_front(startFlags);
|
_removeFlags[_start + _waitingEntity->offset() + _waitingEntity->length()].push_front(startFlags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
++waitingEntity;
|
++_waitingEntity;
|
||||||
if (links.size() >= 0x7FFF) {
|
if (_links.size() >= 0x7FFF) {
|
||||||
while (waitingEntity != entitiesEnd
|
while (_waitingEntity != _entitiesEnd
|
||||||
&& (isLinkEntity(*waitingEntity)
|
&& (isLinkEntity(*_waitingEntity)
|
||||||
|| isInvalidEntity(*waitingEntity))) {
|
|| isInvalidEntity(*_waitingEntity))) {
|
||||||
++waitingEntity;
|
++_waitingEntity;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while (waitingEntity != entitiesEnd && isInvalidEntity(*waitingEntity)) {
|
while (_waitingEntity != _entitiesEnd && isInvalidEntity(*_waitingEntity)) {
|
||||||
++waitingEntity;
|
++_waitingEntity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool readSkipBlockCommand() {
|
bool Parser::readSkipBlockCommand() {
|
||||||
const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF);
|
const QChar *afterCmd = textSkipCommand(_ptr, _end, _links.size() < 0x7FFF);
|
||||||
if (afterCmd == ptr) {
|
if (afterCmd == _ptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort cmd = (++ptr)->unicode();
|
ushort cmd = (++_ptr)->unicode();
|
||||||
++ptr;
|
++_ptr;
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case TextCommandSkipBlock:
|
case TextCommandSkipBlock:
|
||||||
createSkipBlock(ptr->unicode(), (ptr + 1)->unicode());
|
createSkipBlock(_ptr->unicode(), (_ptr + 1)->unicode());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = afterCmd;
|
_ptr = afterCmd;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool readCommand() {
|
bool Parser::readCommand() {
|
||||||
const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF);
|
const QChar *afterCmd = textSkipCommand(_ptr, _end, _links.size() < 0x7FFF);
|
||||||
if (afterCmd == ptr) {
|
if (afterCmd == _ptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort cmd = (++ptr)->unicode();
|
ushort cmd = (++_ptr)->unicode();
|
||||||
++ptr;
|
++_ptr;
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case TextCommandBold:
|
case TextCommandBold:
|
||||||
if (!(flags & TextBlockFBold)) {
|
if (!(_flags & TextBlockFBold)) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags |= TextBlockFBold;
|
_flags |= TextBlockFBold;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandNoBold:
|
case TextCommandNoBold:
|
||||||
if (flags & TextBlockFBold) {
|
if (_flags & TextBlockFBold) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags &= ~TextBlockFBold;
|
_flags &= ~TextBlockFBold;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandSemibold:
|
case TextCommandSemibold:
|
||||||
if (!(flags & TextBlockFSemibold)) {
|
if (!(_flags & TextBlockFSemibold)) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags |= TextBlockFSemibold;
|
_flags |= TextBlockFSemibold;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandNoSemibold:
|
case TextCommandNoSemibold:
|
||||||
if (flags & TextBlockFSemibold) {
|
if (_flags & TextBlockFSemibold) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags &= ~TextBlockFSemibold;
|
_flags &= ~TextBlockFSemibold;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandItalic:
|
case TextCommandItalic:
|
||||||
if (!(flags & TextBlockFItalic)) {
|
if (!(_flags & TextBlockFItalic)) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags |= TextBlockFItalic;
|
_flags |= TextBlockFItalic;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandNoItalic:
|
case TextCommandNoItalic:
|
||||||
if (flags & TextBlockFItalic) {
|
if (_flags & TextBlockFItalic) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags &= ~TextBlockFItalic;
|
_flags &= ~TextBlockFItalic;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandUnderline:
|
case TextCommandUnderline:
|
||||||
if (!(flags & TextBlockFUnderline)) {
|
if (!(_flags & TextBlockFUnderline)) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags |= TextBlockFUnderline;
|
_flags |= TextBlockFUnderline;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandNoUnderline:
|
case TextCommandNoUnderline:
|
||||||
if (flags & TextBlockFUnderline) {
|
if (_flags & TextBlockFUnderline) {
|
||||||
createBlock();
|
createBlock();
|
||||||
flags &= ~TextBlockFUnderline;
|
_flags &= ~TextBlockFUnderline;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandLinkIndex:
|
case TextCommandLinkIndex:
|
||||||
if (ptr->unicode() != lnkIndex) {
|
if (_ptr->unicode() != _lnkIndex) {
|
||||||
createBlock();
|
createBlock();
|
||||||
lnkIndex = ptr->unicode();
|
_lnkIndex = _ptr->unicode();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TextCommandLinkText: {
|
case TextCommandLinkText: {
|
||||||
createBlock();
|
createBlock();
|
||||||
int32 len = ptr->unicode();
|
int32 len = _ptr->unicode();
|
||||||
links.push_back(TextLinkData(EntityType::CustomUrl, QString(), QString(++ptr, len), LinkDisplayedFull));
|
_links.push_back(TextLinkData(EntityType::CustomUrl, QString(), QString(++_ptr, len), LinkDisplayedFull));
|
||||||
lnkIndex = 0x8000 + links.size();
|
_lnkIndex = 0x8000 + _links.size();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case TextCommandSkipBlock:
|
case TextCommandSkipBlock:
|
||||||
createSkipBlock(ptr->unicode(), (ptr + 1)->unicode());
|
createSkipBlock(_ptr->unicode(), (_ptr + 1)->unicode());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = afterCmd;
|
_ptr = afterCmd;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseCurrentChar() {
|
void Parser::parseCurrentChar() {
|
||||||
int skipBack = 0;
|
int skipBack = 0;
|
||||||
ch = ((ptr < end) ? *ptr : 0);
|
_ch = ((_ptr < _end) ? *_ptr : 0);
|
||||||
emojiLookback = 0;
|
_emojiLookback = 0;
|
||||||
bool skip = false, isNewLine = multiline && chIsNewline(ch), isSpace = chIsSpace(ch), isDiac = chIsDiac(ch), isTilde = checkTilde && (ch == '~');
|
bool skip = false, isNewLine = _multiline && chIsNewline(_ch), isSpace = chIsSpace(_ch), isDiac = chIsDiac(_ch), isTilde = _checkTilde && (_ch == '~');
|
||||||
if (chIsBad(ch) || ch.isLowSurrogate()) {
|
if (chIsBad(_ch) || _ch.isLowSurrogate()) {
|
||||||
skip = true;
|
skip = true;
|
||||||
} else if (ch == 0xFE0F && Platform::IsMac()) {
|
} else if (_ch == 0xFE0F && Platform::IsMac()) {
|
||||||
// Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :(
|
// Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :(
|
||||||
skip = true;
|
skip = true;
|
||||||
} else if (isDiac) {
|
} else if (isDiac) {
|
||||||
if (lastSkipped || emoji || ++diacs > chMaxDiacAfterSymbol()) {
|
if (_lastSkipped || _emoji || ++_diacs > chMaxDiacAfterSymbol()) {
|
||||||
skip = true;
|
skip = true;
|
||||||
}
|
}
|
||||||
} else if (ch.isHighSurrogate()) {
|
} else if (_ch.isHighSurrogate()) {
|
||||||
if (ptr + 1 >= end || !(ptr + 1)->isLowSurrogate()) {
|
if (_ptr + 1 >= _end || !(_ptr + 1)->isLowSurrogate()) {
|
||||||
skip = true;
|
skip = true;
|
||||||
} else {
|
} else {
|
||||||
_t->_text.push_back(ch);
|
_t->_text.push_back(_ch);
|
||||||
skipBack = -1;
|
skipBack = -1;
|
||||||
++ptr;
|
++_ptr;
|
||||||
ch = *ptr;
|
_ch = *_ptr;
|
||||||
emojiLookback = 1;
|
_emojiLookback = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastSkipped = skip;
|
_lastSkipped = skip;
|
||||||
if (skip) {
|
if (skip) {
|
||||||
ch = 0;
|
_ch = 0;
|
||||||
} else {
|
} else {
|
||||||
if (isTilde) { // tilde fix in OpenSans
|
if (isTilde) { // tilde fix in OpenSans
|
||||||
if (!(flags & TextBlockFTilde)) {
|
if (!(_flags & TextBlockFTilde)) {
|
||||||
createBlock(skipBack);
|
createBlock(skipBack);
|
||||||
flags |= TextBlockFTilde;
|
_flags |= TextBlockFTilde;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (flags & TextBlockFTilde) {
|
if (_flags & TextBlockFTilde) {
|
||||||
createBlock(skipBack);
|
createBlock(skipBack);
|
||||||
flags &= ~TextBlockFTilde;
|
_flags &= ~TextBlockFTilde;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isNewLine) {
|
if (isNewLine) {
|
||||||
|
@ -486,20 +639,20 @@ public:
|
||||||
} else if (isSpace) {
|
} else if (isSpace) {
|
||||||
_t->_text.push_back(QChar::Space);
|
_t->_text.push_back(QChar::Space);
|
||||||
} else {
|
} else {
|
||||||
if (emoji) createBlock(skipBack);
|
if (_emoji) createBlock(skipBack);
|
||||||
_t->_text.push_back(ch);
|
_t->_text.push_back(_ch);
|
||||||
}
|
|
||||||
if (!isDiac) diacs = 0;
|
|
||||||
}
|
}
|
||||||
|
if (!isDiac) _diacs = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void parseEmojiFromCurrent() {
|
void Parser::parseEmojiFromCurrent() {
|
||||||
int len = 0;
|
int len = 0;
|
||||||
auto e = Ui::Emoji::Find(ptr - emojiLookback, end, &len);
|
auto e = Ui::Emoji::Find(_ptr - _emojiLookback, _end, &len);
|
||||||
if (!e) return;
|
if (!e) return;
|
||||||
|
|
||||||
for (int l = len - emojiLookback - 1; l > 0; --l) {
|
for (int l = len - _emojiLookback - 1; l > 0; --l) {
|
||||||
_t->_text.push_back(*++ptr);
|
_t->_text.push_back(*++_ptr);
|
||||||
}
|
}
|
||||||
if (e->hasPostfix()) {
|
if (e->hasPostfix()) {
|
||||||
Assert(!_t->_text.isEmpty());
|
Assert(!_t->_text.isEmpty());
|
||||||
|
@ -511,58 +664,15 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
createBlock(-len);
|
createBlock(-len);
|
||||||
emoji = e;
|
_emoji = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser(String *t, const QString &text, const TextParseOptions &options) : _t(t),
|
bool Parser::isInvalidEntity(const EntityInText &entity) const {
|
||||||
source { text },
|
|
||||||
rich(options.flags & TextParseRichText),
|
|
||||||
multiline(options.flags & TextParseMultiline),
|
|
||||||
stopAfterWidth(QFIXED_MAX) {
|
|
||||||
if (options.flags & TextParseLinks) {
|
|
||||||
TextUtilities::ParseEntities(source, options.flags, rich);
|
|
||||||
}
|
|
||||||
parse(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
Parser(String *t, const TextWithEntities &textWithEntities, const TextParseOptions &options) : _t(t),
|
|
||||||
source(textWithEntities),
|
|
||||||
rich(options.flags & TextParseRichText),
|
|
||||||
multiline(options.flags & TextParseMultiline),
|
|
||||||
stopAfterWidth(QFIXED_MAX) {
|
|
||||||
auto &preparsed = textWithEntities.entities;
|
|
||||||
if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) {
|
|
||||||
bool parseMentions = (options.flags & TextParseMentions);
|
|
||||||
bool parseHashtags = (options.flags & TextParseHashtags);
|
|
||||||
bool parseBotCommands = (options.flags & TextParseBotCommands);
|
|
||||||
bool parseMarkdown = (options.flags & TextParseMarkdown);
|
|
||||||
if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMarkdown) {
|
|
||||||
int32 i = 0, l = preparsed.size();
|
|
||||||
source.entities.clear();
|
|
||||||
source.entities.reserve(l);
|
|
||||||
const QChar s = source.text.size();
|
|
||||||
for (; i < l; ++i) {
|
|
||||||
auto type = preparsed.at(i).type();
|
|
||||||
if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) ||
|
|
||||||
(type == EntityType::Hashtag && !parseHashtags) ||
|
|
||||||
(type == EntityType::Cashtag && !parseHashtags) ||
|
|
||||||
(type == EntityType::BotCommand && !parseBotCommands) || // #TODO entities
|
|
||||||
((type == EntityType::Bold || type == EntityType::Italic || type == EntityType::Code || type == EntityType::Pre) && !parseMarkdown)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
source.entities.push_back(preparsed.at(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parse(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isInvalidEntity(const EntityInText &entity) const {
|
|
||||||
const auto length = entity.length();
|
const auto length = entity.length();
|
||||||
return (start + entity.offset() + length > end) || (length <= 0);
|
return (_start + entity.offset() + length > _end) || (length <= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isLinkEntity(const EntityInText &entity) const {
|
bool Parser::isLinkEntity(const EntityInText &entity) const {
|
||||||
const auto type = entity.type();
|
const auto type = entity.type();
|
||||||
const auto urls = {
|
const auto urls = {
|
||||||
EntityType::Url,
|
EntityType::Url,
|
||||||
|
@ -575,73 +685,65 @@ public:
|
||||||
EntityType::BotCommand
|
EntityType::BotCommand
|
||||||
};
|
};
|
||||||
return ranges::find(urls, type) != std::end(urls);
|
return ranges::find(urls, type) != std::end(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse(const TextParseOptions &options) {
|
void Parser::parse(const TextParseOptions &options) {
|
||||||
if (options.maxw > 0 && options.maxh > 0) {
|
if (options.maxw > 0 && options.maxh > 0) {
|
||||||
stopAfterWidth = ((options.maxh / _t->_st->font->height) + 1) * options.maxw;
|
_stopAfterWidth = ((options.maxh / _t->_st->font->height) + 1) * options.maxw;
|
||||||
}
|
}
|
||||||
|
|
||||||
start = source.text.constData();
|
_start = _source.text.constData();
|
||||||
end = start + source.text.size();
|
_end = _start + _source.text.size();
|
||||||
|
|
||||||
entitiesEnd = source.entities.cend();
|
_entitiesEnd = _source.entities.cend();
|
||||||
waitingEntity = source.entities.cbegin();
|
_waitingEntity = _source.entities.cbegin();
|
||||||
while (waitingEntity != entitiesEnd && isInvalidEntity(*waitingEntity)) {
|
while (_waitingEntity != _entitiesEnd && isInvalidEntity(*_waitingEntity)) {
|
||||||
++waitingEntity;
|
++_waitingEntity;
|
||||||
}
|
}
|
||||||
const auto firstMonospaceOffset = EntityInText::FirstMonospaceOffset(
|
const auto firstMonospaceOffset = EntityInText::FirstMonospaceOffset(
|
||||||
source.entities,
|
_source.entities,
|
||||||
end - start);
|
_end - _start);
|
||||||
|
|
||||||
ptr = start;
|
_ptr = _start;
|
||||||
while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) {
|
while (_ptr != _end && chIsTrimmed(*_ptr, _rich) && _ptr != _start + firstMonospaceOffset) {
|
||||||
++ptr;
|
++_ptr;
|
||||||
}
|
}
|
||||||
while (ptr != end && chIsTrimmed(*(end - 1), rich)) {
|
while (_ptr != _end && chIsTrimmed(*(_end - 1), _rich)) {
|
||||||
--end;
|
--_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
_t->_text.resize(0);
|
_t->_text.resize(0);
|
||||||
_t->_text.reserve(end - ptr);
|
_t->_text.reserve(_end - _ptr);
|
||||||
|
|
||||||
diacs = 0;
|
_checkTilde = (_t->_st->font->size() * cIntRetinaFactor() == 13)
|
||||||
sumWidth = 0;
|
|
||||||
sumFinished = newlineAwaited = false;
|
|
||||||
blockStart = 0;
|
|
||||||
emoji = nullptr;
|
|
||||||
|
|
||||||
ch = emojiLookback = 0;
|
|
||||||
lastSkipped = false;
|
|
||||||
checkTilde = (_t->_st->font->size() * cIntRetinaFactor() == 13)
|
|
||||||
&& (_t->_st->font->flags() == 0)
|
&& (_t->_st->font->flags() == 0)
|
||||||
&& (_t->_st->font->f.family() == qstr("Open Sans")); // tilde Open Sans fix
|
&& (_t->_st->font->f.family() == qstr("Open Sans")); // tilde Open Sans fix
|
||||||
for (; ptr <= end; ++ptr) {
|
for (; _ptr <= _end; ++_ptr) {
|
||||||
while (checkEntities() || (rich && checkCommand())) {
|
while (checkEntities() || (_rich && checkCommand())) {
|
||||||
}
|
}
|
||||||
parseCurrentChar();
|
parseCurrentChar();
|
||||||
parseEmojiFromCurrent();
|
parseEmojiFromCurrent();
|
||||||
|
|
||||||
if (sumFinished || _t->_text.size() >= 0x8000) break; // 32k max
|
if (_sumFinished || _t->_text.size() >= 0x8000) break; // 32k max
|
||||||
}
|
}
|
||||||
createBlock();
|
createBlock();
|
||||||
if (sumFinished && rich) { // we could've skipped the final skip block command
|
if (_sumFinished && _rich) { // we could've skipped the final skip block command
|
||||||
for (; ptr < end; ++ptr) {
|
for (; _ptr < _end; ++_ptr) {
|
||||||
if (*ptr == TextCommand && readSkipBlockCommand()) {
|
if (*_ptr == TextCommand && readSkipBlockCommand()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeFlags.clear();
|
_removeFlags.clear();
|
||||||
|
|
||||||
_t->_links.resize(maxLnkIndex);
|
_t->_links.resize(_maxLnkIndex);
|
||||||
for (const auto &block : _t->_blocks) {
|
for (const auto &block : _t->_blocks) {
|
||||||
const auto b = block.get();
|
const auto b = block.get();
|
||||||
if (b->lnkIndex() > 0x8000) {
|
if (b->lnkIndex() > 0x8000) {
|
||||||
lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000);
|
_lnkIndex = _maxLnkIndex + (b->lnkIndex() - 0x8000);
|
||||||
if (_t->_links.size() < lnkIndex) {
|
if (_t->_links.size() < _lnkIndex) {
|
||||||
_t->_links.resize(lnkIndex);
|
_t->_links.resize(_lnkIndex);
|
||||||
auto &link = links[lnkIndex - maxLnkIndex - 1];
|
auto &link = _links[_lnkIndex - _maxLnkIndex - 1];
|
||||||
auto handler = ClickHandlerPtr();
|
auto handler = ClickHandlerPtr();
|
||||||
switch (link.type) {
|
switch (link.type) {
|
||||||
case EntityType::CustomUrl: {
|
case EntityType::CustomUrl: {
|
||||||
|
@ -684,37 +786,18 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handler) {
|
if (handler) {
|
||||||
_t->setLink(lnkIndex, handler);
|
_t->setLink(_lnkIndex, handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b->setLnkIndex(lnkIndex);
|
b->setLnkIndex(_lnkIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_t->_links.squeeze();
|
_t->_links.squeeze();
|
||||||
_t->_blocks.shrink_to_fit();
|
_t->_blocks.shrink_to_fit();
|
||||||
_t->_text.squeeze();
|
_t->_text.squeeze();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
void Parser::computeLinkText(const QString &linkData, QString *outLinkText, LinkDisplayStatus *outDisplayStatus) {
|
||||||
enum LinkDisplayStatus {
|
|
||||||
LinkDisplayedFull,
|
|
||||||
LinkDisplayedElided,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextLinkData {
|
|
||||||
TextLinkData() = default;
|
|
||||||
TextLinkData(EntityType type, const QString &text, const QString &data, LinkDisplayStatus displayStatus)
|
|
||||||
: type(type)
|
|
||||||
, text(text)
|
|
||||||
, data(data)
|
|
||||||
, displayStatus(displayStatus) {
|
|
||||||
}
|
|
||||||
EntityType type = EntityType::Invalid;
|
|
||||||
QString text, data;
|
|
||||||
LinkDisplayStatus displayStatus = LinkDisplayedFull;
|
|
||||||
};
|
|
||||||
|
|
||||||
void computeLinkText(const QString &linkData, QString *outLinkText, LinkDisplayStatus *outDisplayStatus) {
|
|
||||||
auto url = QUrl(linkData);
|
auto url = QUrl(linkData);
|
||||||
auto good = QUrl(url.isValid()
|
auto good = QUrl(url.isValid()
|
||||||
? url.toEncoded()
|
? url.toEncoded()
|
||||||
|
@ -724,38 +807,7 @@ private:
|
||||||
: linkData;
|
: linkData;
|
||||||
*outLinkText = _t->_st->font->elided(readable, st::linkCropLimit);
|
*outLinkText = _t->_st->font->elided(readable, st::linkCropLimit);
|
||||||
*outDisplayStatus = (*outLinkText == readable) ? LinkDisplayedFull : LinkDisplayedElided;
|
*outDisplayStatus = (*outLinkText == readable) ? LinkDisplayedFull : LinkDisplayedElided;
|
||||||
}
|
}
|
||||||
|
|
||||||
String *_t;
|
|
||||||
TextWithEntities source;
|
|
||||||
const QChar *start, *end, *ptr;
|
|
||||||
bool rich, multiline;
|
|
||||||
EntitiesInText::const_iterator waitingEntity, entitiesEnd;
|
|
||||||
|
|
||||||
typedef QVector<TextLinkData> TextLinks;
|
|
||||||
TextLinks links;
|
|
||||||
|
|
||||||
typedef QMap<const QChar*, QList<int32> > RemoveFlagsMap;
|
|
||||||
RemoveFlagsMap removeFlags;
|
|
||||||
|
|
||||||
uint16 maxLnkIndex = 0;
|
|
||||||
|
|
||||||
// current state
|
|
||||||
int32 flags = 0;
|
|
||||||
uint16 lnkIndex = 0;
|
|
||||||
EmojiPtr emoji = nullptr; // current emoji, if current word is an emoji, or zero
|
|
||||||
int32 blockStart = 0; // offset in result, from which current parsed block is started
|
|
||||||
int32 diacs = 0; // diac chars skipped without good char
|
|
||||||
QFixed sumWidth, stopAfterWidth; // summary width of all added words
|
|
||||||
bool sumFinished = false;
|
|
||||||
bool newlineAwaited = false;
|
|
||||||
|
|
||||||
// current char data
|
|
||||||
QChar ch; // current char (low surrogate, if current char is surrogate pair)
|
|
||||||
int32 emojiLookback; // how far behind the current ptr to look for current emoji
|
|
||||||
bool lastSkipped; // did we skip current char
|
|
||||||
bool checkTilde; // do we need a special text block for tilde symbol
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -2560,7 +2612,7 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
|
||||||
if (initial) {
|
if (initial) {
|
||||||
Qt::LayoutDirection dir = optionsDir;
|
Qt::LayoutDirection dir = optionsDir;
|
||||||
if (dir == Qt::LayoutDirectionAuto) {
|
if (dir == Qt::LayoutDirectionAuto) {
|
||||||
dir = Parser::stringDirection(_text, lastNewlineStart, b->from());
|
dir = StringDirection(_text, lastNewlineStart, b->from());
|
||||||
}
|
}
|
||||||
if (lastNewline) {
|
if (lastNewline) {
|
||||||
lastNewline->_nextDir = dir;
|
lastNewline->_nextDir = dir;
|
||||||
|
@ -2602,7 +2654,7 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
|
||||||
if (initial) {
|
if (initial) {
|
||||||
Qt::LayoutDirection dir = optionsDir;
|
Qt::LayoutDirection dir = optionsDir;
|
||||||
if (dir == Qt::LayoutDirectionAuto) {
|
if (dir == Qt::LayoutDirectionAuto) {
|
||||||
dir = Parser::stringDirection(_text, lastNewlineStart, _text.size());
|
dir = StringDirection(_text, lastNewlineStart, _text.size());
|
||||||
}
|
}
|
||||||
if (lastNewline) {
|
if (lastNewline) {
|
||||||
lastNewline->_nextDir = dir;
|
lastNewline->_nextDir = dir;
|
||||||
|
|
Loading…
Reference in New Issue