tdesktop/Telegram/SourceFiles/ui/text.h

777 lines
24 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/click_handler.h"
enum EntityInTextType {
EntityInTextUrl,
EntityInTextCustomUrl,
EntityInTextEmail,
EntityInTextHashtag,
EntityInTextMention,
EntityInTextBotCommand,
EntityInTextBold,
EntityInTextItalic,
EntityInTextCode, // inline
EntityInTextPre, // block
};
struct EntityInText {
EntityInText(EntityInTextType type, int offset, int length, const QString &text = QString()) : type(type), offset(offset), length(length), text(text) {
}
EntityInTextType type;
int offset, length;
QString text;
};
typedef QList<EntityInText> EntitiesInText;
// text preprocess
QString textClean(const QString &text);
QString textRichPrepare(const QString &text);
QString textOneLine(const QString &text, bool trim = true, bool rich = false);
QString textAccentFold(const QString &text);
QString textSearchKey(const QString &text);
bool textSplit(QString &sendingText, EntitiesInText &sendingEntities, QString &leftText, EntitiesInText &leftEntities, int32 limit);
enum {
TextParseMultiline = 0x001,
TextParseLinks = 0x002,
TextParseRichText = 0x004,
TextParseMentions = 0x008,
TextParseHashtags = 0x010,
TextParseBotCommands = 0x020,
TextParseMono = 0x040,
TextTwitterMentions = 0x100,
TextTwitterHashtags = 0x200,
TextInstagramMentions = 0x400,
TextInstagramHashtags = 0x800,
};
inline EntitiesInText entitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
EntitiesInText result;
if (!entities.isEmpty()) {
result.reserve(entities.size());
for (int32 i = 0, l = entities.size(); i != l; ++i) {
const auto &e(entities.at(i));
switch (e.type()) {
case mtpc_messageEntityUrl: { const auto &d(e.c_messageEntityUrl()); result.push_back(EntityInText(EntityInTextUrl, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityTextUrl: { const auto &d(e.c_messageEntityTextUrl()); result.push_back(EntityInText(EntityInTextCustomUrl, d.voffset.v, d.vlength.v, textClean(qs(d.vurl)))); } break;
case mtpc_messageEntityEmail: { const auto &d(e.c_messageEntityEmail()); result.push_back(EntityInText(EntityInTextEmail, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityHashtag: { const auto &d(e.c_messageEntityHashtag()); result.push_back(EntityInText(EntityInTextHashtag, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityMention: { const auto &d(e.c_messageEntityMention()); result.push_back(EntityInText(EntityInTextMention, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityBotCommand: { const auto &d(e.c_messageEntityBotCommand()); result.push_back(EntityInText(EntityInTextBotCommand, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityBold: { const auto &d(e.c_messageEntityBold()); result.push_back(EntityInText(EntityInTextBold, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityItalic: { const auto &d(e.c_messageEntityItalic()); result.push_back(EntityInText(EntityInTextItalic, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityCode: { const auto &d(e.c_messageEntityCode()); result.push_back(EntityInText(EntityInTextCode, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityPre: { const auto &d(e.c_messageEntityPre()); result.push_back(EntityInText(EntityInTextPre, d.voffset.v, d.vlength.v, textClean(qs(d.vlanguage)))); } break;
}
}
}
return result;
}
inline MTPVector<MTPMessageEntity> linksToMTP(const EntitiesInText &links, bool sending = false) {
MTPVector<MTPMessageEntity> result(MTP_vector<MTPMessageEntity>(0));
QVector<MTPMessageEntity> &v(result._vector().v);
for (int32 i = 0, s = links.size(); i != s; ++i) {
const EntityInText &l(links.at(i));
if (l.length <= 0 || (sending && l.type != EntityInTextCode && l.type != EntityInTextPre)) continue;
switch (l.type) {
case EntityInTextUrl: v.push_back(MTP_messageEntityUrl(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(MTP_int(l.offset), MTP_int(l.length), MTP_string(l.text))); break;
case EntityInTextEmail: v.push_back(MTP_messageEntityEmail(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextMention: v.push_back(MTP_messageEntityMention(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextBotCommand: v.push_back(MTP_messageEntityBotCommand(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextBold: v.push_back(MTP_messageEntityBold(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextCode: v.push_back(MTP_messageEntityCode(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextPre: v.push_back(MTP_messageEntityPre(MTP_int(l.offset), MTP_int(l.length), MTP_string(l.text))); break;
}
}
return result;
}
EntitiesInText textParseEntities(QString &text, int32 flags, bool rich = false); // changes text if (flags & TextParseMono)
QString textApplyEntities(const QString &text, const EntitiesInText &entities);
#include "ui/emoji_config.h"
void emojiDraw(QPainter &p, EmojiPtr e, int x, int y);
#include "../../../QtStatic/qtbase/src/gui/text/qfontengine_p.h"
enum TextBlockType {
TextBlockTNewline = 0x01,
TextBlockTText = 0x02,
TextBlockTEmoji = 0x03,
TextBlockTSkip = 0x04,
};
enum TextBlockFlags {
TextBlockFBold = 0x01,
TextBlockFItalic = 0x02,
TextBlockFUnderline = 0x04,
TextBlockFTilde = 0x08, // tilde fix in OpenSans
TextBlockFSemibold = 0x10,
TextBlockFCode = 0x20,
TextBlockFPre = 0x40,
};
class ITextBlock {
public:
ITextBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex) : _from(from), _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12))/*, _color(color)*/, _lpadding(0) {
if (length) {
if (str.at(_from + length - 1).unicode() == QChar::Space) {
_rpadding = font->spacew;
}
if (length > 1 && str.at(0).unicode() == QChar::Space) {
_lpadding = font->spacew;
}
}
}
uint16 from() const {
return _from;
}
int32 width() const {
return _width.toInt();
}
int32 lpadding() const {
return _lpadding.toInt();
}
int32 rpadding() const {
return _rpadding.toInt();
}
QFixed f_width() const {
return _width;
}
QFixed f_lpadding() const {
return _lpadding;
}
QFixed f_rpadding() const {
return _rpadding;
}
uint16 lnkIndex() const {
return (_flags >> 12) & 0xFFFF;
}
void setLnkIndex(uint16 lnkIndex) {
_flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12);
}
TextBlockType type() const {
return TextBlockType((_flags >> 8) & 0x0F);
}
int32 flags() const {
return (_flags & 0xFF);
}
const style::color &color() const {
static style::color tmp;
return tmp;//_color;
}
virtual ITextBlock *clone() const = 0;
virtual ~ITextBlock() {
}
protected:
uint16 _from;
uint32 _flags; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
QFixed _width, _lpadding, _rpadding;
};
class NewlineBlock : public ITextBlock {
public:
Qt::LayoutDirection nextDirection() const {
return _nextDir;
}
ITextBlock *clone() const {
return new NewlineBlock(*this);
}
private:
NewlineBlock(const style::font &font, const QString &str, uint16 from, uint16 length) : ITextBlock(font, str, from, length, 0, st::transparent, 0), _nextDir(Qt::LayoutDirectionAuto) {
_flags |= ((TextBlockTNewline & 0x0F) << 8);
}
Qt::LayoutDirection _nextDir;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
struct TextWord {
TextWord() {
}
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0) : from(from),
_rbearing(rbearing.value() > 0x7FFF ? 0x7FFF : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())), width(width), rpadding(rpadding) {
}
QFixed f_rbearing() const {
return QFixed::fromFixed(_rbearing);
}
uint16 from;
int16 _rbearing;
QFixed width, rpadding;
};
class TextBlock : public ITextBlock {
public:
QFixed f_rbearing() const {
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
}
ITextBlock *clone() const {
return new TextBlock(*this);
}
private:
TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex);
typedef QVector<TextWord> TextWords;
TextWords _words;
friend class Text;
friend class TextParser;
friend class BlockParser;
friend class TextPainter;
};
class EmojiBlock : public ITextBlock {
public:
ITextBlock *clone() const {
return new EmojiBlock(*this);
}
private:
EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex, const EmojiData *emoji);
const EmojiData *emoji;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
class SkipBlock : public ITextBlock {
public:
int32 height() const {
return _height;
}
ITextBlock *clone() const {
return new SkipBlock(*this);
}
private:
SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex);
int32 _height;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
static const QChar TextCommand(0x0010);
enum TextCommands {
TextCommandBold = 0x01,
TextCommandNoBold = 0x02,
TextCommandItalic = 0x03,
TextCommandNoItalic = 0x04,
TextCommandUnderline = 0x05,
TextCommandNoUnderline = 0x06,
TextCommandSemibold = 0x07,
TextCommandNoSemibold = 0x08,
TextCommandLinkIndex = 0x09, // 0 - NoLink
TextCommandLinkText = 0x0A,
TextCommandColor = 0x0B,
TextCommandNoColor = 0x0C,
TextCommandSkipBlock = 0x0D,
TextCommandLangTag = 0x20,
};
struct TextParseOptions {
int32 flags;
int32 maxw;
int32 maxh;
Qt::LayoutDirection dir;
};
extern const TextParseOptions _defaultOptions, _textPlainOptions;
enum TextSelectType {
TextSelectLetters = 0x01,
TextSelectWords = 0x02,
TextSelectParagraphs = 0x03,
};
struct TextSelection {
constexpr TextSelection() : from(0), to(0) {
}
constexpr TextSelection(uint16 from, uint16 to) : from(from), to(to) {
}
uint16 from : 16;
uint16 to : 16;
};
inline bool operator==(TextSelection a, TextSelection b) {
return a.from == b.from && a.to == b.to;
}
inline bool operator!=(TextSelection a, TextSelection b) {
return !(a == b);
}
static constexpr TextSelection AllTextSelection = { 0, 0xFFFF };
typedef QPair<QString, QString> TextCustomTag; // open str and close str
typedef QMap<QChar, TextCustomTag> TextCustomTagsMap;
class Text {
public:
Text(int32 minResizeWidth = QFIXED_MAX);
Text(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions, int32 minResizeWidth = QFIXED_MAX, bool richText = false);
Text(const Text &other);
Text(Text &&other);
Text &operator=(const Text &other);
Text &operator=(Text &&other);
int32 countWidth(int32 width) const;
int32 countHeight(int32 width) const;
void setText(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions);
void setRichText(style::font font, const QString &text, TextParseOptions options = _defaultOptions, const TextCustomTagsMap &custom = TextCustomTagsMap());
void setMarkedText(style::font font, const QString &text, const EntitiesInText &entities, const TextParseOptions &options = _defaultOptions);
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
bool hasLinks() const;
bool hasSkipBlock() const {
return _blocks.isEmpty() ? false : _blocks.back()->type() == TextBlockTSkip;
}
void setSkipBlock(int32 width, int32 height);
void removeSkipBlock();
int32 maxWidth() const {
return _maxWidth.ceil().toInt();
}
int32 minHeight() const {
return _minHeight;
}
void replaceFont(style::font f); // does not recount anything, use at your own risk!
void draw(QPainter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const;
void drawElided(QPainter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
void drawLeft(QPainter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const {
draw(p, rtl() ? (outerw - left - width) : left, top, width, align, yFrom, yTo, selection);
}
void drawLeftElided(QPainter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const {
drawElided(p, rtl() ? (outerw - left - width) : left, top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
}
void drawRight(QPainter &p, int32 right, int32 top, int32 width, int32 outerw, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }) const {
draw(p, rtl() ? right : (outerw - right - width), top, width, align, yFrom, yTo, selection);
}
void drawRightElided(QPainter &p, int32 right, int32 top, int32 width, int32 outerw, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const {
drawElided(p, rtl() ? right : (outerw - right - width), top, width, lines, align, yFrom, yTo, removeFromEnd, breakEverywhere, selection);
}
struct StateRequest {
enum class Flag {
BreakEverywhere = 0x01,
LookupSymbol = 0x02,
LookupLink = 0x04,
};
Q_DECLARE_FLAGS(Flags, Flag);
style::align align = style::al_left;
Flags flags = Flag::LookupLink;
};
struct StateResult {
ClickHandlerPtr link;
bool uponSymbol = false;
bool afterSymbol = false;
uint16 symbol = 0;
};
StateResult getState(int x, int y, int width, StateRequest request = StateRequest()) const;
StateResult getStateLeft(int x, int y, int width, int outerw, StateRequest request = StateRequest()) const {
return getState(rtl() ? (outerw - x - width) : x, y, width, request);
}
struct StateRequestElided : public StateRequest {
StateRequestElided() = default;
StateRequestElided(const StateRequest &other) : StateRequest(other) {
}
int lines = 1;
int removeFromEnd = 0;
};
StateResult getStateElided(int x, int y, int width, StateRequestElided request = StateRequestElided()) const;
StateResult getStateElidedLeft(int x, int y, int width, int outerw, StateRequestElided request = StateRequestElided()) const {
return getStateElided(rtl() ? (outerw - x - width) : x, y, width, request);
}
TextSelection adjustSelection(TextSelection selection, TextSelectType selectType) const;
bool isEmpty() const {
return _text.isEmpty();
}
bool isNull() const {
return !_font;
}
int length() const {
return _text.size();
}
enum ExpandLinksMode {
ExpandLinksNone,
ExpandLinksShortened,
ExpandLinksAll,
};
QString original(TextSelection selection = { 0, 0xFFFF }, ExpandLinksMode mode = ExpandLinksShortened) const;
EntitiesInText originalEntities() const;
bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
if (_text.size() < maxdots) return false;
int32 nowDots = 0, from = _text.size() - maxdots, to = _text.size();
for (int32 i = from; i < to; ++i) {
if (_text.at(i) == QChar('.')) {
++nowDots;
}
}
if (nowDots == dots) return false;
for (int32 j = from; j < from + dots; ++j) {
_text[j] = QChar('.');
}
for (int32 j = from + dots; j < to; ++j) {
_text[j] = QChar(' ');
}
return true;
}
void clear();
~Text() {
clear();
}
private:
void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto);
// clear() deletes all blocks and calls this method
// it is also called from move constructor / assignment operator
void clearFields();
QFixed _minResizeWidth, _maxWidth;
int32 _minHeight;
QString _text;
style::font _font;
typedef QVector<ITextBlock*> TextBlocks;
TextBlocks _blocks;
typedef QVector<ClickHandlerPtr> TextLinks;
TextLinks _links;
Qt::LayoutDirection _startDir;
friend class TextParser;
friend class TextPainter;
};
inline TextSelection snapSelection(int from, int to) {
return { static_cast<uint16>(snap(from, 0, 0xFFFF)), static_cast<uint16>(snap(to, 0, 0xFFFF)) };
}
inline TextSelection shiftSelection(TextSelection selection, const Text &byText) {
int len = byText.length();
return snapSelection(int(selection.from) + len, int(selection.to) + len);
}
inline TextSelection unshiftSelection(TextSelection selection, const Text &byText) {
int len = byText.length();
return snapSelection(int(selection.from) - len, int(selection.to) - len);
}
void initLinkSets();
const QSet<int32> &validProtocols();
const QSet<int32> &validTopDomains();
const QRegularExpression &reDomain();
const QRegularExpression &reMailName();
const QRegularExpression &reMailStart();
const QRegularExpression &reHashtag();
const QRegularExpression &reBotCommand();
// text style
const style::textStyle *textstyleCurrent();
void textstyleSet(const style::textStyle *style);
inline void textstyleRestore() {
textstyleSet(0);
}
// textcmd
QString textcmdSkipBlock(ushort w, ushort h);
QString textcmdStartLink(ushort lnkIndex);
QString textcmdStartLink(const QString &url);
QString textcmdStopLink();
QString textcmdLink(ushort lnkIndex, const QString &text);
QString textcmdLink(const QString &url, const QString &text);
QString textcmdStartColor(const style::color &color);
QString textcmdStopColor();
QString textcmdStartSemibold();
QString textcmdStopSemibold();
const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true);
inline bool chIsSpace(QChar ch, bool rich = false) {
return ch.isSpace() || (ch < 32 && !(rich && ch == TextCommand)) || (ch == QChar::ParagraphSeparator) || (ch == QChar::LineSeparator) || (ch == QChar::ObjectReplacementCharacter) || (ch == QChar::SoftHyphen) || (ch == QChar::CarriageReturn) || (ch == QChar::Tabulation);
}
inline bool chIsDiac(QChar ch) { // diac and variation selectors
return (ch.category() == QChar::Mark_NonSpacing) || (ch.unicode() == 1652);
}
inline bool chIsBad(QChar ch) {
return (ch == 0) || (ch >= 8232 && ch < 8237) || (ch >= 65024 && ch < 65040 && ch != 65039) || (ch >= 127 && ch < 160 && ch != 156) || (cPlatform() == dbipMac && ch >= 0x0B00 && ch <= 0x0B7F && chIsDiac(ch) && cIsElCapitan()); // tmp hack see https://bugreports.qt.io/browse/QTBUG-48910
}
inline bool chIsTrimmed(QChar ch, bool rich = false) {
return (!rich || ch != TextCommand) && (chIsSpace(ch) || chIsBad(ch));
}
inline bool chReplacedBySpace(QChar ch) {
// \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237
// QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad");
// \xcc[\xb3\xbf\x8a] // 819, 831, 778
// QString bad1 = QString::fromUtf8("\xcc\xb3"), bad2 = QString::fromUtf8("\xcc\xbf"), bad3 = QString::fromUtf8("\xcc\x8a");
// [\x00\x01\x02\x07\x08\x0b-\x1f] // '\t' = 0x09
return (/*code >= 0x00 && */ch <= 0x02) || (ch >= 0x07 && ch <= 0x09) || (ch >= 0x0b && ch <= 0x1f) ||
(ch == 819) || (ch == 831) || (ch == 778) || (ch >= 8232 && ch <= 8237);
}
inline int32 chMaxDiacAfterSymbol() {
return 2;
}
inline bool chIsNewline(QChar ch) {
return (ch == QChar::LineFeed || ch == 156);
}
inline bool chIsLinkEnd(QChar ch) {
return ch == TextCommand || chIsBad(ch) || chIsSpace(ch) || chIsNewline(ch) || ch.isLowSurrogate() || ch.isHighSurrogate();
}
inline bool chIsAlmostLinkEnd(QChar ch) {
switch (ch.unicode()) {
case '?':
case ',':
case '.':
case '"':
case ':':
case '!':
case '\'':
return true;
default:
break;
}
return false;
}
inline bool chIsWordSeparator(QChar ch) {
switch (ch.unicode()) {
case QChar::Space:
case QChar::LineFeed:
case '.':
case ',':
case '?':
case '!':
case '@':
case '#':
case '$':
case ':':
case ';':
case '-':
case '<':
case '>':
case '[':
case ']':
case '(':
case ')':
case '{':
case '}':
case '=':
case '/':
case '+':
case '%':
case '&':
case '^':
case '*':
case '\'':
case '"':
case '`':
case '~':
case '|':
return true;
default:
break;
}
return false;
}
inline bool chIsSentenceEnd(QChar ch) {
switch (ch.unicode()) {
case '.':
case '?':
case '!':
return true;
default:
break;
}
return false;
}
inline bool chIsSentencePartEnd(QChar ch) {
switch (ch.unicode()) {
case ',':
case ':':
case ';':
return true;
default:
break;
}
return false;
}
inline bool chIsParagraphSeparator(QChar ch) {
switch (ch.unicode()) {
case QChar::LineFeed:
return true;
default:
break;
}
return false;
}
inline QString myUrlEncode(const QString &str) {
return QString::fromLatin1(QUrl::toPercentEncoding(str));
}
inline QString myUrlDecode(const QString &enc) {
return QUrl::fromPercentEncoding(enc.toUtf8());
}
QString prepareTextWithEntities(QString result, EntitiesInText &entities, int32 flags);
inline QString prepareText(QString result, bool checkLinks = false) {
EntitiesInText entities;
return prepareTextWithEntities(result, entities, checkLinks ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0);
}
inline void moveStringPart(QChar *start, int32 &to, int32 &from, int32 count, EntitiesInText &entities) {
if (count > 0) {
if (to < from) {
memmove(start + to, start + from, count * sizeof(QChar));
for (EntitiesInText::iterator i = entities.begin(), e = entities.end(); i != e; ++i) {
if (i->offset >= from + count) break;
if (i->offset + i->length < from) continue;
if (i->offset >= from) {
i->offset -= (from - to);
i->length += (from - to);
}
if (i->offset + i->length < from + count) {
i->length -= (from - to);
}
}
}
to += count;
from += count;
}
}
// replace bad symbols with space and remove \r
inline void cleanTextWithEntities(QString &result, EntitiesInText &entities) {
result = result.replace('\t', qstr(" "));
int32 len = result.size(), to = 0, from = 0;
QChar *start = result.data();
for (QChar *ch = start, *end = start + len; ch < end; ++ch) {
if (ch->unicode() == '\r') {
moveStringPart(start, to, from, (ch - start) - from, entities);
++from;
} else if (chReplacedBySpace(*ch)) {
*ch = ' ';
}
}
moveStringPart(start, to, from, len - from, entities);
if (to < len) result.resize(to);
}
inline void trimTextWithEntities(QString &result, EntitiesInText &entities) {
bool foundNotTrimmed = false;
for (QChar *s = result.data(), *e = s + result.size(), *ch = e; ch != s;) { // rtrim
--ch;
if (!chIsTrimmed(*ch)) {
if (ch + 1 < e) {
int32 l = ch + 1 - s;
for (EntitiesInText::iterator i = entities.begin(), e = entities.end(); i != e; ++i) {
if (i->offset > l) {
i->offset = l;
i->length = 0;
} else if (i->offset + i->length > l) {
i->length = l - i->offset;
}
}
result.resize(l);
}
foundNotTrimmed = true;
break;
}
}
if (!foundNotTrimmed) {
result.clear();
entities.clear();
return;
}
for (QChar *s = result.data(), *ch = s, *e = s + result.size(); ch != e; ++ch) { // ltrim
if (!chIsTrimmed(*ch)) {
if (ch > s) {
int32 l = ch - s;
for (EntitiesInText::iterator i = entities.begin(), e = entities.end(); i != e; ++i) {
if (i->offset + i->length <= l) {
i->length = 0;
i->offset = 0;
} else if (i->offset < l) {
i->length = i->offset + i->length - l;
i->offset = 0;
} else {
i->offset -= l;
}
}
result = result.mid(l);
}
break;
}
}
}