Make Ui::Text::Parser methods non-inclass.

This commit is contained in:
John Preston 2019-06-15 18:12:35 +02:00
parent 2d10e3e432
commit 522e66b2db
1 changed files with 637 additions and 585 deletions

View File

@ -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;