/*
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.

Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "text.h"

#include "lang.h"

#include "pspecific.h"

#include <private/qharfbuzz_p.h>

namespace {

	const QRegularExpression _reDomain(QString::fromUtf8("(?<![\\w\\$\\-\\_%=\\.])(?:([a-zA-Z]+)://)?((?:[A-Za-zА-яА-ЯёЁ0-9\\-\\_]+\\.){1,10}([A-Za-zрф\\-\\d]{2,22})(\\:\\d+)?)"), QRegularExpression::UseUnicodePropertiesOption);
	const QRegularExpression _reExplicitDomain(QString::fromUtf8("(?<![\\w\\$\\-\\_%=\\.])(?:([a-zA-Z]+)://)((?:[A-Za-zА-яА-ЯёЁ0-9\\-\\_]+\\.){0,5}([A-Za-zрф\\-\\d]{2,22})(\\:\\d+)?)"), QRegularExpression::UseUnicodePropertiesOption);
	const QRegularExpression _reMailName(qsl("[a-zA-Z\\-_\\.0-9]{1,256}$"));
	const QRegularExpression _reMailStart(qsl("^[a-zA-Z\\-_\\.0-9]{1,256}\\@"));
	const QRegularExpression _reHashtag(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption);
	const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{5,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption);
	const QRegularExpression _reBotCommand(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)"));
	QSet<int32> _validProtocols, _validTopDomains;

	const style::textStyle *_textStyle = 0;

	TextLinkPtr _overLnk, _downLnk, _zeroLnk;

	void _initDefault() {
		_textStyle = &st::defaultTextStyle;
	}

	inline int32 _blockHeight(const ITextBlock *b, const style::font &font) {
		return (b->type() == TextBlockSkip) ? static_cast<const SkipBlock*>(b)->height() : (_textStyle->lineHeight > font->height) ? _textStyle->lineHeight : font->height;
	}

	inline QFixed _blockRBearing(const ITextBlock *b) {
		return (b->type() == TextBlockText) ? static_cast<const TextBlock*>(b)->f_rbearing() : 0;
	}
}

const QRegularExpression &reDomain() {
	return _reDomain;
}

const QRegularExpression &reMailName() {
	return _reMailName;
}

const QRegularExpression &reHashtag() {
	return _reHashtag;
}

const QRegularExpression &reBotCommand() {
	return _reBotCommand;
}

const style::textStyle *textstyleCurrent() {
	return _textStyle;
}

void textstyleSet(const style::textStyle *style) {
	_textStyle = style ? style : &st::defaultTextStyle;
}

void textlnkOver(const TextLinkPtr &lnk) {
	_overLnk = lnk;
}

const TextLinkPtr &textlnkOver() {
	return _overLnk;
}

void textlnkDown(const TextLinkPtr &lnk) {
	_downLnk = lnk;
}

const TextLinkPtr &textlnkDown() {
	return _downLnk;
}

QString textOneLine(const QString &text, bool trim, bool rich) {
	QString result(text);
	const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size();
	if (trim) {
		while (s < e && chIsTrimmed(*s)) {
			++s;
		}
		while (s < e && chIsTrimmed(*(e - 1))) {
			--e;
		}
		if (e - s != text.size()) {
			result = text.mid(s - text.unicode(), e - s);
		}
	}
	for (const QChar *ch = s; ch != e; ++ch) {
		if (chIsNewline(*ch)) {
            result[int(ch - s)] = QChar::Space;
		}
	}
	return result;
}

QString textClean(const QString &text) {
	QString result(text);
	for (const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); ch != e; ++ch) {
		if (*ch == TextCommand) {
            result[int(ch - s)] = QChar::Space;
		}
	}
	return result;
}

QString textRichPrepare(const QString &text) {
	QString result;
	result.reserve(text.size());
	const QChar *s = text.constData(), *ch = s;
	for (const QChar *e = s + text.size(); ch != e; ++ch) {
		if (*ch == TextCommand) {
			if (ch > s) result.append(s, ch - s);
			result.append(QChar::Space);
			s = ch + 1;
			continue;
		}
		if (ch->unicode() == '\\' || ch->unicode() == '[') {
			if (ch > s) result.append(s, ch - s);
			result.append('\\');
			s = ch;
			continue;
		}
	}
	if (ch > s) result.append(s, ch - s);
	return result;
}

QString textcmdSkipBlock(ushort w, ushort h) {
	static QString cmd(5, TextCommand);
	cmd[1] = QChar(TextCommandSkipBlock);
	cmd[2] = QChar(w);
	cmd[3] = QChar(h);
	return cmd;
}

QString textcmdStartLink(ushort lnkIndex) {
	static QString cmd(4, TextCommand);
	cmd[1] = QChar(TextCommandLinkIndex);
	cmd[2] = QChar(lnkIndex);
	return cmd;
}

QString textcmdStartLink(const QString &url) {
	if (url.size() >= 4096) return QString();

	QString result;
	result.reserve(url.size() + 4);
	return result.append(TextCommand).append(QChar(TextCommandLinkText)).append(QChar(url.size())).append(url).append(TextCommand);
}

QString textcmdStopLink() {
	return textcmdStartLink(0);
}

QString textcmdLink(ushort lnkIndex, const QString &text) {
	QString result;
	result.reserve(4 + text.size() + 4);
	return result.append(textcmdStartLink(lnkIndex)).append(text).append(textcmdStopLink());
}

QString textcmdLink(const QString &url, const QString &text) {
	QString result;
	result.reserve(4 + url.size() + text.size() + 4);
	return result.append(textcmdStartLink(url)).append(text).append(textcmdStopLink());
}

QString textcmdStartColor(const style::color &color) {
	QString result;
	result.reserve(7);
	return result.append(TextCommand).append(QChar(TextCommandColor)).append(QChar(color->c.red())).append(QChar(color->c.green())).append(QChar(color->c.blue())).append(QChar(color->c.alpha())).append(TextCommand);
}

QString textcmdStopColor() {
	QString result;
	result.reserve(3);
	return result.append(TextCommand).append(QChar(TextCommandNoColor)).append(TextCommand);
}

const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) {
	const QChar *result = from + 1;
	if (*from != TextCommand || result >= end) return from;

	ushort cmd = result->unicode();
	++result;
	if (result >= end) return from;

	switch (cmd) {
	case TextCommandBold:
	case TextCommandNoBold:
	case TextCommandItalic:
	case TextCommandNoItalic:
	case TextCommandUnderline:
	case TextCommandNoUnderline:
	case TextCommandNoColor:
		break;

	case TextCommandLinkIndex:
		if (result->unicode() > 0x7FFF) return from;
		++result;
		break;

	case TextCommandLinkText: {
		ushort len = result->unicode();
		if (len >= 4096 || !canLink) return from;
		result += len + 1;
	} break;

	case TextCommandColor: {
		const QChar *e = result + 4;
		if (e >= end) return from;

		for (; result < e; ++result) {
			if (result->unicode() >= 256) return from;
		}
	} break;

	case TextCommandSkipBlock:
		result += 2;
		break;

	case TextCommandLangTag:
		result += 1;
		break;
	}
	return (result < end && *result == TextCommand) ? (result + 1) : from;
}

class TextParser {
public:
	
	static 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;
	}

	void blockCreated() {
		sumWidth += _t->_blocks.back()->f_width();
		if (sumWidth.floor().toInt() > stopAfterWidth) {
			sumFinished = true;
		}
	}

	void createBlock(int32 skipBack = 0) {
		if (lnkIndex < 0x8000 && lnkIndex > maxLnkIndex) maxLnkIndex = lnkIndex;
		int32 len = int32(_t->_text.size()) + skipBack - blockStart;
		if (len > 0) {
			lastSkipped = lastSpace = false;
			if (emoji) {
				_t->_blocks.push_back(new EmojiBlock(_t->_font, _t->_text, blockStart, len, flags, color, lnkIndex, emoji));
				emoji = 0;
				lastSkipped = true;
			} else if (len == 1 && _t->_text.at(blockStart) == QChar::LineFeed) {
				_t->_blocks.push_back(new NewlineBlock(_t->_font, _t->_text, blockStart, len));
			} else {
				_t->_blocks.push_back(new TextBlock(_t->_font, _t->_text, _t->_minResizeWidth, blockStart, len, flags, color, lnkIndex));
			}
			blockStart += len;
			blockCreated();
		}
	}

	void createSkipBlock(int32 w, int32 h) {
		createBlock();
		_t->_text.push_back('_');
		_t->_blocks.push_back(new SkipBlock(_t->_font, _t->_text, blockStart++, w, h, lnkIndex));
		blockCreated();
	}

	void getLinkData(const QString &original, QString &result, int32 &fullDisplayed) {
		if (!original.isEmpty() && original.at(0) == '/') {
			result = original;
			fullDisplayed = -4; // bot command 
		} else if (!original.isEmpty() && original.at(0) == '@') {
			result = original;
			fullDisplayed = -3; // mention
		} else if (!original.isEmpty() && original.at(0) == '#') {
			result = original;
			fullDisplayed = -2; // hashtag
		} else if (_reMailStart.match(original).hasMatch()) {
			result = original;
			fullDisplayed = -1; // email
		} else {
			QUrl url(original), good(url.isValid() ? url.toEncoded() : "");
			QString readable = good.isValid() ? good.toDisplayString() : original;
			result = _t->_font->m.elidedText(readable, Qt::ElideRight, st::linkCropLimit);
			fullDisplayed = (result == readable) ? 1 : 0;
		}
	}

	bool checkWaitedLink() {
		if (waitingLink == linksEnd || ptr < start + waitingLink->offset || links.size() >= 0x7FFF) {
			return true;
		}

		createBlock();

		QString lnkUrl = QString(start + waitingLink->offset, waitingLink->length), lnkText;
		int32 fullDisplayed;
		getLinkData(lnkUrl, lnkText, fullDisplayed);

		links.push_back(TextLinkData(lnkUrl, fullDisplayed));
		lnkIndex = 0x8000 + links.size();

		_t->_text += lnkText;
		ptr = start + waitingLink->offset + waitingLink->length;

		createBlock();
		++waitingLink;
		lnkIndex = 0;

		return true;
	}
	
	bool readCommand() {
		const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF);
		if (afterCmd == ptr) {
			return false;
		}

		ushort cmd = (++ptr)->unicode();
		++ptr;

		switch (cmd) {
		case TextCommandBold:
			if (!(flags & TextBlockBold)) {
				createBlock();
				flags |= TextBlockBold;
			}
		break;

		case TextCommandNoBold:
			if (flags & TextBlockBold) {
				createBlock();
				flags &= ~TextBlockBold;
			}
		break;

		case TextCommandItalic:
			if (!(flags & TextBlockItalic)) {
				createBlock();
				flags |= TextBlockItalic;
			}
		break;

		case TextCommandNoItalic:
			if (flags & TextBlockItalic) {
				createBlock();
				flags &= ~TextBlockItalic;
			}
		break;

		case TextCommandUnderline:
			if (!(flags & TextBlockUnderline)) {
				createBlock();
				flags |= TextBlockUnderline;
			}
		break;

		case TextCommandNoUnderline:
			if (flags & TextBlockUnderline) {
				createBlock();
				flags &= ~TextBlockUnderline;
			}
		break;

		case TextCommandLinkIndex:
			if (ptr->unicode() != lnkIndex) {
				createBlock();
				lnkIndex = ptr->unicode();
			}
		break;

		case TextCommandLinkText: {
			createBlock();
			int32 len = ptr->unicode();
			links.push_back(TextLinkData(QString(++ptr, len), false));
			lnkIndex = 0x8000 + links.size();
		} break;

		case TextCommandColor: {
			style::color c(ptr->unicode(), (ptr + 1)->unicode(), (ptr + 2)->unicode(), (ptr + 3)->unicode());
			if (color != c) {
				createBlock();
				color = c;
			}
		} break;

		case TextCommandSkipBlock:
			createBlock();
			createSkipBlock(ptr->unicode(), (ptr + 1)->unicode());
		break;

		case TextCommandNoColor:
			if (color) {
				createBlock();
				color = style::color();
			}
		break;
		}

		ptr = afterCmd;
		return true;
	}

	void parseCurrentChar() {
		ch = ((ptr < end) ? *ptr : 0);
		while (rich && ch == TextCommand) {
			if (readCommand()) {
				ch = ((ptr < end) ? *ptr : 0);
			} else {
				ch = QChar::Space;
			}
		}

		int skipBack = 0;
		chInt = ch.unicode();
		bool skip = false, isNewLine = multiline && chIsNewline(ch), isSpace = chIsSpace(ch, rich), isDiac = chIsDiac(ch);
		if (chIsBad(ch) || ch.isLowSurrogate()) {
			skip = true;
		} else if (isDiac) {
			if (lastSkipped || emoji || ++diacs > chMaxDiacAfterSymbol()) {
				skip = true;
			}
		} else if (isSpace && lastSpace && !isNewLine) {
			skip = true;
		} else if (ch.isHighSurrogate()) {
			if (ptr + 1 >= end || !(ptr + 1)->isLowSurrogate()) {
				skip = true;
			} else {
				_t->_text.push_back(ch);
				skipBack = -1;
				++ptr;
				ch = *ptr;
				chInt = (chInt << 16) | ch.unicode();
			}
		} else if ((ch >= 48 && ch < 58) || ch == 35) { // check for digit emoji
			if (ptr + 1 < end && (ptr + 1)->unicode() == 0x20E3) {
				_t->_text.push_back(ch);
				skipBack = -1;
				++ptr;
				ch = *ptr;
				chInt = (chInt << 16) | 0x20E3;
			}
		}

		lastSkipped = skip;
		lastSpace = isSpace;
		if (skip) {
			ch = 0;
		} else {
			if (isNewLine) {
				createBlock();
				_t->_text.push_back(QChar::LineFeed);
				createBlock();
			} else if (isSpace) {
				_t->_text.push_back(QChar::Space);
			} else {
				if (emoji) createBlock(skipBack);
				_t->_text.push_back(ch);
			}
			if (!isDiac) diacs = 0;
		}
	}

	void parseEmojiFromCurrent() {
		int len = 0, skipped = (chInt > 0xFFFFU) ? 1 : 0;
		EmojiPtr e = emojiFromText(ptr - skipped, end, len);
		if (!e) return;

		for (int l = len - skipped - 1; l > 0; --l) {
			_t->_text.push_back(*++ptr);
		}
		if (e->postfix && _t->_text.at(_t->_text.size() - 1).unicode() != e->postfix) {
			_t->_text.push_back(e->postfix);
			++len;
		}

		createBlock(-len);
		emoji = e;
	}

	TextParser(Text *t, const QString &text, const TextParseOptions &options) : _t(t),
		src(text),
		rich(options.flags & TextParseRichText),
		multiline(options.flags & TextParseMultiline),
		maxLnkIndex(0),
		flags(0),
		lnkIndex(0),
		stopAfterWidth(QFIXED_MAX) {
		if (options.flags & TextParseLinks) {
			lnkRanges = textParseLinks(src, options.flags, rich);
		}
		parse(options);
	}
	TextParser(Text *t, const QString &text, const LinksInText &links, const TextParseOptions &options) : _t(t),
		src(text),
		rich(options.flags & TextParseRichText),
		multiline(options.flags & TextParseMultiline),
		maxLnkIndex(0),
		flags(0),
		lnkIndex(0),
		stopAfterWidth(QFIXED_MAX) {
		lnkRanges = links;
		parse(options);
	}
	void parse(const TextParseOptions &options) {
		int flags = options.flags;
		if (options.maxw > 0 && options.maxh > 0) {
			stopAfterWidth = ((options.maxh / _t->_font->height) + 1) * options.maxw;
		}

		start = src.constData();
		end = start + src.size();

		while (start != end && chIsTrimmed(*start, rich)) {
			++start;
		}
		while (start != end && chIsTrimmed(*(end - 1), rich)) {
			--end;
		}

		_t->_text.resize(0);
		_t->_text.reserve(end - start);

		diacs = 0;
		sumWidth = 0;
		sumFinished = false;
		blockStart = 0;
		emoji = 0;

		ch = chInt = 0;
		lastSkipped = false;
		lastSpace = true;
		waitingLink = lnkRanges.cbegin();
		linksEnd = lnkRanges.cend();
		for (ptr = start; ptr <= end; ++ptr) {
			if (!checkWaitedLink()) {
				break;
			}
			parseCurrentChar();

			parseEmojiFromCurrent();

			if (sumFinished || _t->_text.size() >= 0x8000) break; // 32k max
		}
		createBlock();

		_t->_links.resize(maxLnkIndex);
		for (Text::TextBlocks::const_iterator i = _t->_blocks.cbegin(), e = _t->_blocks.cend(); i != e; ++i) {
			ITextBlock *b = *i;
			if (b->lnkIndex() > 0x8000) {
				lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000);
				if (_t->_links.size() < lnkIndex) {
					_t->_links.resize(lnkIndex);
					const TextLinkData &data(links[lnkIndex - maxLnkIndex - 1]);
					TextLinkPtr lnk;
					if (data.fullDisplayed < -3) { // bot command
						lnk = TextLinkPtr(new BotCommandLink(data.url));
					} else if (data.fullDisplayed < -2) { // mention
						if (options.flags & TextTwitterMentions) {
							lnk = TextLinkPtr(new TextLink(qsl("https://twitter.com/") + data.url.mid(1), true));
						} else if (options.flags & TextInstagramMentions) {
							lnk = TextLinkPtr(new TextLink(qsl("https://instagram.com/") + data.url.mid(1) + '/', true));
						} else {
							lnk = TextLinkPtr(new MentionLink(data.url));
						}
					} else if (data.fullDisplayed < -1) { // hashtag
						if (options.flags & TextTwitterMentions) {
							lnk = TextLinkPtr(new TextLink(qsl("https://twitter.com/hashtag/") + data.url.mid(1) + qsl("?src=hash"), true));
						} else if (options.flags & TextInstagramMentions) {
							lnk = TextLinkPtr(new TextLink(qsl("https://instagram.com/explore/tags/") + data.url.mid(1) + '/', true));
						} else {
							lnk = TextLinkPtr(new HashtagLink(data.url));
						}
					} else if (data.fullDisplayed < 0) { // email
						lnk = TextLinkPtr(new EmailLink(data.url));
					} else {
						lnk = TextLinkPtr(new TextLink(data.url, data.fullDisplayed > 0));
					}
					_t->setLink(lnkIndex, lnk);
				}
				b->setLnkIndex(lnkIndex);
			}
		}
		_t->_links.squeeze();
		_t->_blocks.squeeze();
		_t->_text.squeeze();
	}

private:

	Text *_t;
	const QString &src;
	const QChar *start, *end, *ptr;
	bool rich, multiline;

	LinksInText lnkRanges;
	LinksInText::const_iterator waitingLink, linksEnd;

	struct TextLinkData {
		TextLinkData(const QString &url = QString(), int32 fullDisplayed = 1) : url(url), fullDisplayed(fullDisplayed) {
		}
		QString url;
		int32 fullDisplayed; // -4 - bot command, -3 - mention, -2 - hashtag, -1 - email
	};
	typedef QVector<TextLinkData> TextLinks;
	TextLinks links;

	uint16 maxLnkIndex;

	// current state
	int32 flags;
	uint16 lnkIndex;
	const EmojiData *emoji; // current emoji, if current word is an emoji, or zero
	int32 blockStart; // offset in result, from which current parsed block is started
	int32 diacs; // diac chars skipped without good char
	QFixed sumWidth, stopAfterWidth; // summary width of all added words
	bool sumFinished;
	style::color color; // current color, could be invalid

	// current char data
	QChar ch; // current char (low surrogate, if current char is surrogate pair)
	uint32 chInt; // full ch, could be surrogate pair
	bool lastSkipped; // did we skip current char
	bool lastSpace; // was last char a space character
};

namespace {
	// COPIED FROM qtextengine.cpp AND MODIFIED

	struct BidiStatus {
		BidiStatus() {
			eor = QChar::DirON;
			lastStrong = QChar::DirON;
			last = QChar:: DirON;
			dir = QChar::DirON;
		}
		QChar::Direction eor;
		QChar::Direction lastStrong;
		QChar::Direction last;
		QChar::Direction dir;
	};

	enum { _MaxBidiLevel = 61 };
	enum { _MaxItemLength = 4096 };

	struct BidiControl {
		inline BidiControl(bool rtl)
			: cCtx(0), base(rtl ? 1 : 0), level(rtl ? 1 : 0), override(false) {}

		inline void embed(bool rtl, bool o = false) {
			unsigned int toAdd = 1;
			if((level%2 != 0) == rtl ) {
				++toAdd;
			}
			if (level + toAdd <= _MaxBidiLevel) {
				ctx[cCtx].level = level;
				ctx[cCtx].override = override;
				cCtx++;
				override = o;
				level += toAdd;
			}
		}
		inline bool canPop() const { return cCtx != 0; }
		inline void pdf() {
			Q_ASSERT(cCtx);
			--cCtx;
			level = ctx[cCtx].level;
			override = ctx[cCtx].override;
		}

		inline QChar::Direction basicDirection() const {
			return (base ? QChar::DirR : QChar:: DirL);
		}
		inline unsigned int baseLevel() const {
			return base;
		}
		inline QChar::Direction direction() const {
			return ((level%2) ? QChar::DirR : QChar:: DirL);
		}

		struct {
			unsigned int level;
			bool override;
		} ctx[_MaxBidiLevel];
		unsigned int cCtx;
		const unsigned int base;
		unsigned int level;
		bool override;
	};

	static void eAppendItems(QScriptAnalysis *analysis, int &start, int &stop, const BidiControl &control, QChar::Direction dir) {
		if (start > stop)
			return;

		int level = control.level;

		if(dir != QChar::DirON && !control.override) {
			// add level of run (cases I1 & I2)
			if(level % 2) {
				if(dir == QChar::DirL || dir == QChar::DirAN || dir == QChar::DirEN)
					level++;
			} else {
				if(dir == QChar::DirR)
					level++;
				else if(dir == QChar::DirAN || dir == QChar::DirEN)
					level += 2;
			}
		}

		QScriptAnalysis *s = analysis + start;
		const QScriptAnalysis *e = analysis + stop;
		while (s <= e) {
			s->bidiLevel = level;
			++s;
		}
		++stop;
		start = stop;
	}

}

void TextLink::onClick(Qt::MouseButton button) const {
	if (button == Qt::LeftButton || button == Qt::MiddleButton) {
		QString url = TextLink::encoded();
		QRegularExpressionMatch telegramMeUser = QRegularExpression(qsl("^https?://telegram\\.me/([a-zA-Z0-9\\.\\_]+)/?(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url);
		QRegularExpressionMatch telegramMeGroup = QRegularExpression(qsl("^https?://telegram\\.me/joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url);
		QRegularExpressionMatch telegramMeStickers = QRegularExpression(qsl("^https?://telegram\\.me/addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url);
		if (telegramMeUser.hasMatch()) {
			QString params = url.mid(telegramMeUser.captured(0).size()), start, startToken;
			if (!params.isEmpty()) {
				QRegularExpressionMatch startParams = QRegularExpression(qsl("(^|&)(start|startgroup)=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(params);
				if (startParams.hasMatch()) {
					start = startParams.captured(2);
					startToken = startParams.captured(3);
				}
			}
			App::openUserByName(telegramMeUser.captured(1), start == qsl("startgroup"), startToken);
		} else if (telegramMeGroup.hasMatch()) {
			App::joinGroupByHash(telegramMeGroup.captured(1));
		} else if (telegramMeStickers.hasMatch()) {
			App::stickersBox(telegramMeStickers.captured(1));
		} else if (QRegularExpression(qsl("^tg://[a-zA-Z0-9]+"), QRegularExpression::CaseInsensitiveOption).match(url).hasMatch()) {
			App::openLocalUrl(url);
		} else {
			QDesktopServices::openUrl(url);
		}
	}
}

void EmailLink::onClick(Qt::MouseButton button) const {
	if (button == Qt::LeftButton || button == Qt::MiddleButton) {
		QUrl url(qstr("mailto:") + _email);
		if (!QDesktopServices::openUrl(url)) {
			psOpenFile(url.toString(QUrl::FullyEncoded), true);
		}
	}
}

void MentionLink::onClick(Qt::MouseButton button) const {
	if (button == Qt::LeftButton || button == Qt::MiddleButton) {
		App::openUserByName(_tag.mid(1), true);
	}
}

void HashtagLink::onClick(Qt::MouseButton button) const {
	if (button == Qt::LeftButton || button == Qt::MiddleButton) {
		App::searchByHashtag(_tag);
	}
}

void BotCommandLink::onClick(Qt::MouseButton button) const {
	if (button == Qt::LeftButton || button == Qt::MiddleButton) {
//		App::insertBotCommand(_cmd);
		App::sendBotCommand(_cmd);
	}
}

class TextPainter {
public:

	static inline uint16 _blockEnd(const Text *t, const Text::TextBlocks::const_iterator &i, const Text::TextBlocks::const_iterator &e) {
		return (i + 1 == e) ? t->_text.size() : (*(i + 1))->from();
	}
	static inline uint16 _blockLength(const Text *t, const Text::TextBlocks::const_iterator &i, const Text::TextBlocks::const_iterator &e) {
		return _blockEnd(t, i, e) - (*i)->from();
	}

	TextPainter(QPainter *p, const Text *t) : _p(p), _t(t), _elideLast(false), _elideRemoveFromEnd(0), _str(0), _elideSavedBlock(0), _lnkResult(0), _inTextFlag(0), _getSymbol(0), _getSymbolAfter(0), _getSymbolUpon(0) {
	}

	void initNextParagraph(Text::TextBlocks::const_iterator i) {
		_parStartBlock = i;
		Text::TextBlocks::const_iterator e = _t->_blocks.cend();
		if (i == e) {
			_parStart = _t->_text.size();
			_parLength = 0;
		} else {
			_parStart = (*i)->from();
			for (; i != e; ++i) {
				if ((*i)->type() == TextBlockNewline) {
					break;
				}
			}
			_parLength = ((i == e) ? _t->_text.size() : (*i)->from()) - _parStart;
		}
		_parAnalysis.resize(0);
	}

	void initParagraphBidi() {
		if (!_parLength || !_parAnalysis.isEmpty()) return;
		
		Text::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1;

		bool ignore = false;
		bool rtl = (_parDirection == Qt::RightToLeft);
		if (!ignore && !rtl) {
			ignore = true;
			const ushort *start = reinterpret_cast<const ushort*>(_str) + _parStart;
			const ushort *curr = start;
			const ushort *end = start + _parLength;
			while (curr < end) {
				while (n != e && (*n)->from() <= _parStart + (curr - start)) {
					i = n;
					++n;
				}
				if ((*i)->type() != TextBlockEmoji && *curr >= 0x590) {
					ignore = false;
					break;
				}
				++curr;
			}
		}

		_parAnalysis.resize(_parLength);
		QScriptAnalysis *analysis = _parAnalysis.data();

		BidiControl control(rtl);

		_parHasBidi = false;
		if (ignore) {
			memset(analysis, 0, _parLength * sizeof(QScriptAnalysis));
			if (rtl) {
				for (int i = 0; i < _parLength; ++i)
					analysis[i].bidiLevel = 1;
				_parHasBidi = true;
			}
		} else {
			_parHasBidi = eBidiItemize(analysis, control);
		}
	}

	void draw(int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, uint16 selectedFrom = 0, uint16 selectedTo = 0) {
		if (_t->isEmpty()) return;

		_blocksSize = _t->_blocks.size();
		if (!_textStyle) _initDefault();

		if (_p) {
			_p->setFont(_t->_font->f);
			_originalPen = _p->pen();
		}

		_x = left;
		_y = top;
		_yFrom = yFrom + top;
		_yTo = (yTo < 0) ? -1 : (yTo + top);
		_selectedFrom = selectedFrom;
		_selectedTo = selectedTo;
		_wLeft = _w = w;
		_str = _t->_text.unicode();

		if (_p) {
			QRectF clip = _p->clipBoundingRect();
			if (clip.width() > 0 || clip.height() > 0) {
				if (_yFrom < clip.y()) _yFrom = clip.y();
				if (_yTo < 0 || _yTo > clip.y() + clip.height()) _yTo = clip.y() + clip.height();
			}
		}

		_align = align;

		_parDirection = _t->_startDir;
		if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = cLangDir();
		if ((*_t->_blocks.cbegin())->type() != TextBlockNewline) {
			initNextParagraph(_t->_blocks.cbegin());
		}

		_lineStart = 0;
		_lineStartBlock = 0;

		_lineHeight = 0;
		_fontHeight = _t->_font->height;
		QFixed last_rBearing = 0, last_rPadding = 0;

		int32 blockIndex = 0;
		bool longWordLine = true;
		Text::TextBlocks::const_iterator e = _t->_blocks.cend();
		for (Text::TextBlocks::const_iterator i = _t->_blocks.cbegin(); i != e; ++i, ++blockIndex) {
			ITextBlock *b = *i;
			TextBlockType _btype = b->type();
			int32 blockHeight = _blockHeight(b, _t->_font);
			QFixed _rb = _blockRBearing(b);

			if (_btype == TextBlockNewline) {
				if (!_lineHeight) _lineHeight = blockHeight;
				ushort nextStart = _blockEnd(_t, i, e);
				if (!drawLine(nextStart, i + 1, e)) return;

				_y += _lineHeight;
				_lineHeight = 0;
				_lineStart = nextStart;
				_lineStartBlock = blockIndex + 1;

				last_rBearing = _rb;
				last_rPadding = b->f_rpadding();
				_wLeft = _w - (b->f_width() - last_rBearing);
				if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) {
					_wLeft -= _elideRemoveFromEnd;
				}

				_parDirection = static_cast<NewlineBlock*>(b)->nextDirection();
				if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = cLangDir();
				initNextParagraph(i + 1);

				longWordLine = true;
				continue;
			}

			QFixed lpadding = b->f_lpadding();
			QFixed newWidthLeft = _wLeft - lpadding - last_rBearing - (last_rPadding + b->f_width() - _rb);
			if (newWidthLeft >= 0) {
				last_rBearing = _rb;
				last_rPadding = b->f_rpadding();
				_wLeft = newWidthLeft;

				_lineHeight = qMax(_lineHeight, blockHeight);

				longWordLine = false;
				continue;
			}

			if (_btype == TextBlockText) {
				TextBlock *t = static_cast<TextBlock*>(b);
				QFixed f_wLeft = _wLeft;
				int32 f_lineHeight = _lineHeight;
				for (TextBlock::TextWords::const_iterator j = t->_words.cbegin(), en = t->_words.cend(), f = j; j != en; ++j) {
					bool wordEndsHere = (j->width >= 0);
					QFixed j_width = wordEndsHere ? j->width : -j->width;

					QFixed newWidthLeft = _wLeft - lpadding - last_rBearing - (last_rPadding + j_width - j->f_rbearing());
					lpadding = 0;
					if (newWidthLeft >= 0) {
						last_rBearing = j->f_rbearing();
						last_rPadding = j->rpadding;
						_wLeft = newWidthLeft;

						_lineHeight = qMax(_lineHeight, blockHeight);

						if (wordEndsHere) {
							longWordLine = false;
						}
						if (wordEndsHere || longWordLine) {
							f_wLeft = _wLeft;
							f_lineHeight = _lineHeight;
							f = j + 1;
						}
						continue;
					}

					int32 elidedLineHeight = qMax(_lineHeight, blockHeight);
					bool elidedLine = _elideLast && (_y + elidedLineHeight >= _yTo);
					if (elidedLine) {
						_lineHeight = elidedLineHeight;
					} else if (f != j) {
						j = f;
						_wLeft = f_wLeft;
						_lineHeight = f_lineHeight;
						j_width = (j->width >= 0) ? j->width : -j->width;
					}
					if (!drawLine(elidedLine ? ((j + 1 == en) ? _blockEnd(_t, i, e) : (j + 1)->from) : j->from, i, e)) return;
					_y += _lineHeight;
					_lineHeight = qMax(0, blockHeight);
					_lineStart = j->from;
					_lineStartBlock = blockIndex;

					last_rBearing = j->f_rbearing();
					last_rPadding = j->rpadding;
					_wLeft = _w - (j_width - last_rBearing);
					if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) {
						_wLeft -= _elideRemoveFromEnd;
					}

					longWordLine = true;
					f = j + 1;
					f_wLeft = _wLeft;
					f_lineHeight = _lineHeight;
				}
				continue;
			}

			int32 elidedLineHeight = qMax(_lineHeight, blockHeight);
			bool elidedLine = _elideLast && (_y + elidedLineHeight >= _yTo);
			if (elidedLine) {
				_lineHeight = elidedLineHeight;
			}
			if (!drawLine(elidedLine ? _blockEnd(_t, i, e) : b->from(), i, e)) return;
			_y += _lineHeight;
			_lineHeight = qMax(0, blockHeight);
			_lineStart = b->from();
			_lineStartBlock = blockIndex;

			last_rBearing = _rb;
			last_rPadding = b->f_rpadding();
			_wLeft = _w - (b->f_width() - last_rBearing);
			if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yTo)) {
				_wLeft -= _elideRemoveFromEnd;
			}

			longWordLine = true;
			continue;
		}
		if (_lineStart < _t->_text.size()) {
			if (!drawLine(_t->_text.size(), e, e)) return;
		}
		if (_getSymbol) {
			*_getSymbol = _t->_text.size();
			*_getSymbolAfter = false;
			*_getSymbolUpon = false;
		}
	}

	void drawElided(int32 left, int32 top, int32 w, style::align align, int32 lines, int32 yFrom, int32 yTo, int32 removeFromEnd) {
		if (lines <= 0 || _t->isNull()) return;

		if (yTo < 0 || (lines - 1) * _t->_font->height < yTo) {
			yTo = lines * _t->_font->height;
			_elideLast = true;
			_elideRemoveFromEnd = removeFromEnd;
		}
		draw(left, top, w, align, yFrom, yTo);
	}

	const TextLinkPtr &link(int32 x, int32 y, int32 w, style::align align) {
		_lnkX = x;
		_lnkY = y;
		_lnkResult = &_zeroLnk;
		if (!_t->isNull() && _lnkX >= 0 && _lnkX < w && _lnkY >= 0) {
			draw(0, 0, w, align, _lnkY, _lnkY + 1);
		}
		return *_lnkResult;
	}

	void getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y, int32 w, style::align align) {
		lnk = TextLinkPtr();
		inText = false;

		if (!_t->isNull() && x >= 0 && x < w && y >= 0) {
			_lnkX = x;
			_lnkY = y;
			_lnkResult = &lnk;
			_inTextFlag = &inText;
			draw(0, 0, w, align, _lnkY, _lnkY + 1);
			lnk = *_lnkResult;
		}
	}

	void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 w, style::align align) {
		symbol = 0;
		after = false;
		upon = false;
		if (!_t->isNull() && y >= 0) {
			_lnkX = x;
			_lnkY = y;
			_getSymbol = &symbol;
			_getSymbolAfter = &after;
			_getSymbolUpon = &upon;
			draw(0, 0, w, align, _lnkY, _lnkY + 1);
		}
	}

	const QPen &blockPen(ITextBlock *block) {
		if (block->color()) {
			return block->color()->p;
		}
		if (block->lnkIndex()) {
			const TextLinkPtr &l(_t->_links.at(block->lnkIndex() - 1));
			if (l == _overLnk) {
				if (l == _downLnk) {
					return _textStyle->lnkDownColor->p;
				}
			}
			return _textStyle->lnkColor->p;
		}
		return _originalPen;
	}

	bool drawLine(uint16 _lineEnd, const Text::TextBlocks::const_iterator &_endBlockIter, const Text::TextBlocks::const_iterator &_end) {
		_yDelta = (_lineHeight - _fontHeight) / 2;
		if (_yTo >= 0 && _y + _yDelta >= _yTo) return false;
		if (_y + _yDelta + _fontHeight <= _yFrom) return true;

		uint16 trimmedLineEnd = _lineEnd;
		for (; trimmedLineEnd > _lineStart; --trimmedLineEnd) {
			QChar ch = _t->_text.at(trimmedLineEnd - 1);
			if ((ch != QChar::Space || trimmedLineEnd == _lineStart + 1) && ch != QChar::LineFeed) {
				break;
			}
		}

		ITextBlock *_endBlock = (_endBlockIter == _end) ? 0 : (*_endBlockIter);
		bool elidedLine = _elideLast && _endBlock && (_y + _lineHeight >= _yTo);

		int blockIndex = _lineStartBlock;
		ITextBlock *currentBlock = _t->_blocks[blockIndex];
		ITextBlock *nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;

		int32 delta = (currentBlock->from() < _lineStart ? qMin(_lineStart - currentBlock->from(), 2) : 0);
		_localFrom = _lineStart - delta;
		int32 lineEnd = (_endBlock && _endBlock->from() < trimmedLineEnd && !elidedLine) ? qMin(uint16(trimmedLineEnd + 2), _blockEnd(_t, _endBlockIter, _end)) : trimmedLineEnd;

		QString lineText = _t->_text.mid(_localFrom, lineEnd - _localFrom);
		int32 lineStart = delta, lineLength = trimmedLineEnd - _lineStart;

		if (elidedLine) {
			initParagraphBidi();
			prepareElidedLine(lineText, lineStart, lineLength, _endBlock);
		}

		QFixed x = _x;
		if (_align & Qt::AlignHCenter) {
			x += (_wLeft / 2).toInt();
		} else if (((_align & Qt::AlignLeft) && _parDirection == Qt::RightToLeft) || ((_align & Qt::AlignRight) && _parDirection == Qt::LeftToRight)) {
			x += _wLeft;
		}

		if (_getSymbol) {
			if (_lnkX < x) {
				if (_parDirection == Qt::RightToLeft) {
					*_getSymbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
					*_getSymbolAfter = (_lineEnd > _lineStart) ? true : false;
					*_getSymbolUpon = ((_lnkX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockSkip)) ? true : false;
				} else {
					*_getSymbol = _lineStart;
					*_getSymbolAfter = false;
					*_getSymbolUpon = ((_lnkX >= _x) && (_lineStart > 0)) ? true : false;
				}
				return false;  
			} else if (_lnkX >= x + (_w - _wLeft)) {
				if (_parDirection == Qt::RightToLeft) {
					*_getSymbol = _lineStart;
					*_getSymbolAfter = false;
					*_getSymbolUpon = ((_lnkX < _x + _w) && (_lineStart > 0)) ? true : false;
				} else {
					*_getSymbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
					*_getSymbolAfter = (_lineEnd > _lineStart) ? true : false;
					*_getSymbolUpon = ((_lnkX < _x + _w) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockSkip)) ? true : false;
				}
				return false;
			}
		}

		bool selectFromStart = (_selectedTo > _lineStart) && (_lineStart > 0) && (_selectedFrom <= _lineStart);
		bool selectTillEnd = (_selectedTo >= _lineEnd) && (_lineEnd < _t->_text.size()) && (_selectedFrom < _lineEnd) && (!_endBlock || _endBlock->type() != TextBlockSkip);

		if ((selectFromStart && _parDirection == Qt::LeftToRight) || (selectTillEnd && _parDirection == Qt::RightToLeft)) {
			if (x > _x) {
				_p->fillRect(QRectF(_x.toReal(), _y + _yDelta, (x - _x).toReal(), _fontHeight), _textStyle->selectBg->b);
			}
		}
		if ((selectTillEnd && _parDirection == Qt::LeftToRight) || (selectFromStart && _parDirection == Qt::RightToLeft)) {
			if (x < _x + _wLeft) {
				_p->fillRect(QRectF((x + _w - _wLeft).toReal(), _y + _yDelta, (_x + _wLeft - x).toReal(), _fontHeight), _textStyle->selectBg->b);
			}
		}

		if (trimmedLineEnd == _lineStart && !elidedLine) return true;

		if (!elidedLine) initParagraphBidi(); // if was not inited

		_f = _t->_font;
		QStackTextEngine engine(lineText, _f->f);
		engine.option.setTextDirection(_parDirection);
		_e = &engine;

		eItemize();

		QScriptLine line;
		line.from = lineStart;
		line.length = lineLength;
		eShapeLine(line);

		int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
	    int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0;
		if (!nItems) {
			if (elidedLine) restoreAfterElided();
			return true;
		}

		QVarLengthArray<int> visualOrder(nItems);
		QVarLengthArray<uchar> levels(nItems);
		for (int i = 0; i < nItems; ++i) {
			QScriptItem &si(engine.layoutData->items[firstItem + i]);
			while (nextBlock && nextBlock->from() <= _localFrom + si.position) {
				currentBlock = nextBlock;
				nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
			}
			TextBlockType _type = currentBlock->type();
			if (_type == TextBlockSkip) {
				levels[i] = si.analysis.bidiLevel = 0;
			} else {
				levels[i] = si.analysis.bidiLevel;
			}
			if (si.analysis.flags == QScriptAnalysis::Object) {
				if (_type == TextBlockEmoji || _type == TextBlockSkip) {
					si.width = currentBlock->f_width() + (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= trimmedLineEnd) ? 0 : currentBlock->f_rpadding());
				}
			}
		}
	    QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());

		blockIndex = _lineStartBlock;
		currentBlock = _t->_blocks[blockIndex];
		nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;

		int32 textY = _y + _yDelta + _t->_font->ascent, emojiY = (_t->_font->height - st::emojiSize) / 2;

		eSetFont(currentBlock);
		if (_p) _p->setPen(blockPen(currentBlock));
		for (int i = 0; i < nItems; ++i) {
			int item = firstItem + visualOrder[i];
			const QScriptItem &si = engine.layoutData->items.at(item);
			bool rtl = (si.analysis.bidiLevel % 2);

			while (blockIndex > _lineStartBlock + 1 && _t->_blocks[blockIndex - 1]->from() > _localFrom + si.position) {
				nextBlock = currentBlock;
				currentBlock = _t->_blocks[--blockIndex - 1];
				if (_p) _p->setPen(blockPen(currentBlock));
				eSetFont(currentBlock);
			}
			while (nextBlock && nextBlock->from() <= _localFrom + si.position) {
				currentBlock = nextBlock;
				nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
				if (_p) _p->setPen(blockPen(currentBlock));
				eSetFont(currentBlock);
			}
			if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
				TextBlockType _type = currentBlock->type();
				if (_lnkResult && _lnkX >= x && _lnkX < x + si.width) {
					if (currentBlock->lnkIndex() && _lnkY >= _y + _yDelta && _lnkY < _y + _yDelta + _fontHeight) {
						_lnkResult = &_t->_links.at(currentBlock->lnkIndex() - 1);
					}
					if (_inTextFlag && _type != TextBlockSkip) {
						*_inTextFlag = true;
					}
					return false;
				} else if (_getSymbol && _lnkX >= x && _lnkX < x + si.width) {
					if (_type == TextBlockSkip) {
						if (_parDirection == Qt::RightToLeft) {
							*_getSymbol = _lineStart;
							*_getSymbolAfter = false;
							*_getSymbolUpon = false;
						} else {
							*_getSymbol = (trimmedLineEnd > _lineStart) ? (trimmedLineEnd - 1) : _lineStart;
							*_getSymbolAfter = (trimmedLineEnd > _lineStart) ? true : false;
							*_getSymbolUpon = false;
						}
						return false;
					}
					const QChar *chFrom = _str + currentBlock->from(), *chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from());
					if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) {
						if (rtl) {
							if (_lnkX < x + (si.width - currentBlock->f_width())) {
								*_getSymbol = (chTo - 1 - _str); // up to ending space, included, rtl
								*_getSymbolAfter = (_lnkX < x + (si.width - currentBlock->f_width()) / 2) ? true : false;
								*_getSymbolUpon = true;
								return false;
							}
						} else if (_lnkX >= x + currentBlock->f_width()) {
							*_getSymbol = (chTo - 1 - _str); // up to ending space, inclided, ltr
							*_getSymbolAfter = (_lnkX >= x + currentBlock->f_width() + (currentBlock->f_rpadding() / 2)) ? true : false;
							*_getSymbolUpon = true;
							return false;
						}
						--chTo;
					}
					if (_lnkX < x + (rtl ? (si.width - currentBlock->f_width()) : 0) + (currentBlock->f_width() / 2)) {
						*_getSymbol = ((rtl && chTo > chFrom) ? (chTo - 1) : chFrom) - _str;
						*_getSymbolAfter = (rtl && chTo > chFrom) ? true : false;
						*_getSymbolUpon = true;
					} else {
						*_getSymbol = ((rtl || chTo <= chFrom) ? chFrom : (chTo - 1)) - _str;
						*_getSymbolAfter = (rtl || chTo <= chFrom) ? false : true;
						*_getSymbolUpon = true;
					}
					return false;
				} else if (_p && _type == TextBlockEmoji) {
					QFixed glyphX = x;
					if (rtl) {
						glyphX += (si.width - currentBlock->f_width());
					}
					if (_localFrom + si.position < _selectedTo) {
						const QChar *chFrom = _str + currentBlock->from(), *chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from());
						if (_localFrom + si.position >= _selectedFrom) { // could be without space
							if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selectedTo >= (chTo - _str)) {
								_p->fillRect(QRectF(x.toReal(), _y + _yDelta, si.width.toReal(), _fontHeight), _textStyle->selectBg->b);
							} else { // or with space
								_p->fillRect(QRectF(glyphX.toReal(), _y + _yDelta, currentBlock->f_width().toReal(), _fontHeight), _textStyle->selectBg->b);
							}
						} else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= _selectedFrom) {
							if (rtl) { // rtl space only
								_p->fillRect(QRectF(x.toReal(), _y + _yDelta, (glyphX - x).toReal(), _fontHeight), _textStyle->selectBg->b);
							} else { // ltr space only
								_p->fillRect(QRectF((x + currentBlock->f_width()).toReal(), _y + _yDelta, (si.width - currentBlock->f_width()).toReal(), _fontHeight), _textStyle->selectBg->b);
							}
						}
					}
					emojiDraw(*_p, static_cast<EmojiBlock*>(currentBlock)->emoji, (glyphX + int(st::emojiPadding)).toInt(), _y + _yDelta + emojiY);
//				} else if (_p && currentBlock->type() == TextBlockSkip) { // debug
//					_p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast<SkipBlock*>(currentBlock)->height()), QColor(0, 0, 0, 32));
				}
				x += si.width;
				continue;
			}

			unsigned short *logClusters = engine.logClusters(&si);
			QGlyphLayout glyphs = engine.shapedGlyphs(&si);

			int itemStart = qMax(line.from, si.position), itemEnd;
			int itemLength = engine.length(item);
			int glyphsStart = logClusters[itemStart - si.position], glyphsEnd;
			if (line.from + line.length < si.position + itemLength) {
				itemEnd = line.from + line.length;
				glyphsEnd = logClusters[itemEnd - si.position];
			} else {
				itemEnd = si.position + itemLength;
				glyphsEnd = si.num_glyphs;
			}

			QFixed itemWidth = 0;
			for (int g = glyphsStart; g < glyphsEnd; ++g)
				itemWidth += glyphs.effectiveAdvance(g);

			if (_lnkResult && _lnkX >= x && _lnkX < x + itemWidth) {
				if (currentBlock->lnkIndex() && _lnkY >= _y + _yDelta && _lnkY < _y + _yDelta + _fontHeight) {
					_lnkResult = &_t->_links.at(currentBlock->lnkIndex() - 1);
				}
				if (_inTextFlag) {
					*_inTextFlag = true;
				}
				return false;
			} else if (_getSymbol && _lnkX >= x && _lnkX < x + itemWidth) {
				QFixed tmpx = rtl ? (x + itemWidth) : x;
				for (int ch = 0, g, itemL = itemEnd - itemStart; ch < itemL;) {
					g = logClusters[itemStart - si.position + ch];
					QFixed gwidth = glyphs.effectiveAdvance(g);
					 // ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes
					int ch2 = ch + 1;
					while ((ch2 < itemL) && (g == logClusters[itemStart - si.position + ch2])) {
						++ch2;
					}
					for (int charsCount = (ch2 - ch); ch < ch2; ++ch) {
						QFixed shift1 = QFixed(2 * (charsCount - (ch2 - ch)) + 2) * gwidth / QFixed(2 * charsCount),
						       shift2 = QFixed(2 * (charsCount - (ch2 - ch)) + 1) * gwidth / QFixed(2 * charsCount);
						if ((rtl && _lnkX >= tmpx - shift1) ||
							(!rtl && _lnkX < tmpx + shift1)) {
							*_getSymbol = _localFrom + itemStart + ch;
							if ((rtl && _lnkX >= tmpx - shift2) ||
								(!rtl && _lnkX < tmpx + shift2)) {
								*_getSymbolAfter = false;
							} else {
								*_getSymbolAfter = true;
							}
							*_getSymbolUpon = true;
							return false;
						}
					}
					if (rtl) {
						tmpx -= gwidth;
					} else {
						tmpx += gwidth;
					}
				}
				if (itemEnd > itemStart) {
					*_getSymbol = _localFrom + itemEnd - 1;
					*_getSymbolAfter = true;
				} else {
					*_getSymbol = _localFrom + itemStart;
					*_getSymbolAfter = false;
				}
				*_getSymbolUpon = true;
				return false;
			} else if (_p) {
				QTextCharFormat format;
				QTextItemInt gf(glyphs.mid(glyphsStart, glyphsEnd - glyphsStart),
								&_e->fnt, engine.layoutData->string.unicode() + itemStart,
								itemEnd - itemStart, engine.fontEngine(si), format);
				gf.logClusters = logClusters + itemStart - si.position;
				gf.width = itemWidth;
				gf.justified = false;
				gf.initWithScriptItem(si);

				if (_localFrom + itemStart < _selectedTo && _localFrom + itemEnd > _selectedFrom) {
					QFixed selX = x, selWidth = itemWidth;
					if (_localFrom + itemEnd > _selectedTo || _localFrom + itemStart < _selectedFrom) {
						selWidth = 0;
						int itemL = itemEnd - itemStart;
						int selStart = _selectedFrom - (_localFrom + itemStart), selEnd = _selectedTo - (_localFrom + itemStart);
						if (selStart < 0) selStart = 0;
						if (selEnd > itemL) selEnd = itemL;
						for (int ch = 0, g; ch < selEnd;) {
							g = logClusters[itemStart - si.position + ch];
							QFixed gwidth = glyphs.effectiveAdvance(g);
							// ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes
							int ch2 = ch + 1;
							while ((ch2 < itemL) && (g == logClusters[itemStart - si.position + ch2])) {
								++ch2;
							}
							if (ch2 <= selStart) {
								selX += gwidth;
							} else if (ch >= selStart && ch2 <= selEnd) {
								selWidth += gwidth;
							} else {
								int sStart = ch, sEnd = ch2;
								if (ch < selStart) {
									sStart = selStart;
									selX += QFixed(sStart - ch) * gwidth / QFixed(ch2 - ch);
								}
								if (ch2 >= selEnd) {
									sEnd = selEnd;
									selWidth += QFixed(sEnd - sStart) * gwidth / QFixed(ch2 - ch);
									break;
								}
								selWidth += QFixed(sEnd - sStart) * gwidth / QFixed(ch2 - ch);
							}
							ch = ch2;
						}
					}
					if (rtl) selX = x + itemWidth - (selX - x) - selWidth;
					_p->fillRect(QRectF(selX.toReal(), _y + _yDelta, selWidth.toReal(), _fontHeight), _textStyle->selectBg->b);
				}

				_p->drawTextItem(QPointF(x.toReal(), textY), gf);
			}

			x += itemWidth;
		}

		if (elidedLine) restoreAfterElided();
		return true;
	}

	void elideSaveBlock(int32 blockIndex, ITextBlock *&_endBlock, int32 elideStart, int32 elideWidth) {
		_elideSavedIndex = blockIndex;
		_elideSavedBlock = _t->_blocks[blockIndex];
		const_cast<Text*>(_t)->_blocks[blockIndex] = new TextBlock(_t->_font, _t->_text, QFIXED_MAX, elideStart, 0, _elideSavedBlock->flags(), _elideSavedBlock->color(), _elideSavedBlock->lnkIndex());
		_blocksSize = blockIndex + 1;
		_endBlock = (blockIndex + 1 < _t->_blocks.size() ? _t->_blocks[blockIndex + 1] : 0);
	}

	void setElideBidi(int32 elideStart, int32 elideLen) {
		int32 newParLength = elideStart + elideLen - _parStart;
		if (newParLength > _parAnalysis.size()) {
			_parAnalysis.resize(newParLength);
		}
		for (int32 i = elideLen; i > 0; --i) {
			_parAnalysis[newParLength - i].bidiLevel = (_parDirection == Qt::RightToLeft) ? 1 : 0;
		}
	}

	void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, ITextBlock *&_endBlock, int repeat = 0) {
		static const QString _Elide = qsl("...");

		_f = _t->_font;
		QStackTextEngine engine(lineText, _f->f);
		engine.option.setTextDirection(_parDirection);
		_e = &engine;

		eItemize();

		int blockIndex = _lineStartBlock;
		ITextBlock *currentBlock = _t->_blocks[blockIndex];
		ITextBlock *nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;

		QScriptLine line;
		line.from = lineStart;
		line.length = lineLength;
		eShapeLine(line);

		int32 elideWidth = _f->m.width(_Elide);
		_wLeft = _w - elideWidth - _elideRemoveFromEnd;

		int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
	    int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i;

		for (i = 0; i < nItems; ++i) {
			QScriptItem &si(engine.layoutData->items[firstItem + i]);
			while (nextBlock && nextBlock->from() <= _localFrom + si.position) {
				currentBlock = nextBlock;
				nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
			}
			TextBlockType _type = currentBlock->type();
			if (si.analysis.flags == QScriptAnalysis::Object) {
				if (_type == TextBlockEmoji || _type == TextBlockSkip) {
					si.width = currentBlock->f_width() + currentBlock->f_rpadding();
				}
			}
			if (_type == TextBlockEmoji || _type == TextBlockSkip || _type == TextBlockNewline) {
				if (_wLeft < si.width) {
					lineText = lineText.mid(0, currentBlock->from() - _localFrom) + _Elide;
					lineLength = currentBlock->from() + _Elide.size() - _lineStart;
					setElideBidi(currentBlock->from(), _Elide.size());
					elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->from(), elideWidth);
					return;
				}
				_wLeft -= si.width;
			} else if (_type == TextBlockText) {
				unsigned short *logClusters = engine.logClusters(&si);
				QGlyphLayout glyphs = engine.shapedGlyphs(&si);

				int itemStart = qMax(line.from, si.position), itemEnd;
				int itemLength = engine.length(firstItem + i);
				int glyphsStart = logClusters[itemStart - si.position], glyphsEnd;
				if (line.from + line.length < si.position + itemLength) {
					itemEnd = line.from + line.length;
					glyphsEnd = logClusters[itemEnd - si.position];
				} else {
					itemEnd = si.position + itemLength;
					glyphsEnd = si.num_glyphs;
				}

				for (int g = glyphsStart; g < glyphsEnd; ++g) {
					QFixed adv = glyphs.effectiveAdvance(g);
					if (_wLeft < adv) {
						int pos = itemStart;
						while (pos < itemEnd && logClusters[pos - si.position] < g) {
							++pos;
						}

						if (lineText.size() <= pos || repeat > 3) {
							lineText += _Elide;
							lineLength = _localFrom + pos + _Elide.size() - _lineStart;
							setElideBidi(_localFrom + pos, _Elide.size());
							_blocksSize = blockIndex;
							_endBlock = nextBlock;
						} else {
							lineText = lineText.mid(0, pos);
							lineLength = _localFrom + pos - _lineStart;
							_blocksSize = blockIndex;
							_endBlock = nextBlock;
							prepareElidedLine(lineText, lineStart, lineLength, _endBlock, repeat + 1);
						}
						return;
					} else {
						_wLeft -= adv;
					}
				}
			}
		}

		int32 elideStart = _lineStart + lineText.length();
		setElideBidi(elideStart, _Elide.size());

		lineText += _Elide;
		lineLength += _Elide.size();

		if (!repeat) {
			for (; blockIndex < _blocksSize && _t->_blocks[blockIndex] != _endBlock && _t->_blocks[blockIndex]->from() < elideStart; ++blockIndex) {
			}
			if (blockIndex < _blocksSize) {
				elideSaveBlock(blockIndex, _endBlock, elideStart, elideWidth);
			}
		}
	}

	void restoreAfterElided() {
		if (_elideSavedBlock) {
			delete _t->_blocks[_elideSavedIndex];
			const_cast<Text*>(_t)->_blocks[_elideSavedIndex] = _elideSavedBlock;
			_elideSavedBlock = 0;
		}
	}

	// COPIED FROM qtextengine.cpp AND MODIFIED
	void eShapeLine(const QScriptLine &line) {
		int item = _e->findItem(line.from), end = _e->findItem(line.from + line.length - 1);
		if (item == -1)
			return;

		int blockIndex = _lineStartBlock;
		ITextBlock *currentBlock = _t->_blocks[blockIndex];
		ITextBlock *nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
		eSetFont(currentBlock);
		for (item = _e->findItem(line.from); item <= end; ++item) {
			QScriptItem &si = _e->layoutData->items[item];
			while (nextBlock && nextBlock->from() <= _localFrom + si.position) {
				currentBlock = nextBlock;
				nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
				eSetFont(currentBlock);
			}
			_e->shape(item);
		}
	}

	style::font applyFlags(int32 flags, const style::font &f) {
		style::font result = f;
		if (flags & TextBlockBold) result = result->bold();
		if (flags & TextBlockItalic) result = result->italic();
		if (flags & TextBlockUnderline) result = result->underline();
		return result;
	}

	void eSetFont(ITextBlock *block) {
		style::font newFont = _t->_font;
		int flags = block->flags();
		if (flags) {
			newFont = applyFlags(flags, _t->_font);
		}
		if (block->lnkIndex()) {
			const TextLinkPtr &l(_t->_links.at(block->lnkIndex() - 1));
			if (l == _overLnk) {
				if (l == _downLnk || !_downLnk) {
					if (_t->_font != _textStyle->lnkOverFlags) newFont = _textStyle->lnkOverFlags;
				} else {
					if (_t->_font != _textStyle->lnkFlags) newFont = _textStyle->lnkFlags;
				}
			} else {
				if (_t->_font != _textStyle->lnkFlags) newFont = _textStyle->lnkFlags;
			}
		}
		if (newFont != _f) {
			if (newFont->family() == _t->_font->family()) {
				newFont = applyFlags(flags | newFont->flags(), _t->_font);
			}
			_f = newFont;
			_e->fnt = _f->f;
			_e->resetFontEngineCache();
		}
	}

	void eItemize() {
		_e->validate();
		if (_e->layoutData->items.size())
			return;

		int length = _e->layoutData->string.length();
		if (!length)
			return;

		const ushort *string = reinterpret_cast<const ushort*>(_e->layoutData->string.unicode());

		int blockIndex = _lineStartBlock;
		ITextBlock *currentBlock = _t->_blocks[blockIndex];
		ITextBlock *nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;

		_e->layoutData->hasBidi = _parHasBidi;
		QScriptAnalysis *analysis = _parAnalysis.data() + (_localFrom - _parStart);

		{
			QVarLengthArray<uchar> scripts(length);
			QUnicodeTools::initScripts(string, length, scripts.data());
			for (int i = 0; i < length; ++i)
				analysis[i].script = scripts.at(i);
		}

		blockIndex = _lineStartBlock;
		currentBlock = _t->_blocks[blockIndex];
		nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;

		const ushort *start = string;
		const ushort *end = start + length;
		while (start < end) {
			while (nextBlock && nextBlock->from() <= _localFrom + (start - string)) {
				currentBlock = nextBlock;
				nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
			}
			TextBlockType _type = currentBlock->type();
			if (_type == TextBlockEmoji || _type == TextBlockSkip) {
				analysis->script = QChar::Script_Common;
				analysis->flags = QScriptAnalysis::Object;
			} else {
				analysis->flags = QScriptAnalysis::None;
			}
			analysis->script = hbscript_to_script(script_to_hbscript(analysis->script)); // retain the old behavior
			++start;
			++analysis;
		}

		{
			const QString *i_string = &_e->layoutData->string;
			const QScriptAnalysis *i_analysis = _parAnalysis.data() + (_localFrom - _parStart);
			QScriptItemArray *i_items = &_e->layoutData->items;

			blockIndex = _lineStartBlock;
			currentBlock = _t->_blocks[blockIndex];
			nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
			ITextBlock *startBlock = currentBlock;

			if (!length)
				return;
			int start = 0, end = start + length;
			for (int i = start + 1; i < end; ++i) {
				while (nextBlock && nextBlock->from() <= _localFrom + i) {
					currentBlock = nextBlock;
					nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
				}
				// According to the unicode spec we should be treating characters in the Common script
				// (punctuation, spaces, etc) as being the same script as the surrounding text for the
				// purpose of splitting up text. This is important because, for example, a fullstop
				// (0x2E) can be used to indicate an abbreviation and so must be treated as part of a
				// word.  Thus it must be passed along with the word in languages that have to calculate
				// word breaks.  For example the thai word "ครม." has no word breaks but the word "ครม"
				// does.
				// Unfortuntely because we split up the strings for both wordwrapping and for setting
				// the font and because Japanese and Chinese are also aliases of the script "Common",
				// doing this would break too many things.  So instead we only pass the full stop
				// along, and nothing else.
				if (currentBlock == startBlock
					&& i_analysis[i].bidiLevel == i_analysis[start].bidiLevel
					&& i_analysis[i].flags == i_analysis[start].flags
					&& (i_analysis[i].script == i_analysis[start].script || i_string->at(i) == QLatin1Char('.'))
//					&& i_analysis[i].flags < QScriptAnalysis::SpaceTabOrObject // only emojis are objects here, no tabs
					&& i - start < _MaxItemLength)
					continue;
				i_items->append(QScriptItem(start, i_analysis[start]));
				start = i;
				startBlock = currentBlock;
			}
			i_items->append(QScriptItem(start, i_analysis[start]));
		}
	}

	QChar::Direction eSkipBoundryNeutrals(QScriptAnalysis *analysis,
											const ushort *unicode,
											int &sor, int &eor, BidiControl &control,
											Text::TextBlocks::const_iterator i) {
		Text::TextBlocks::const_iterator e = _t->_blocks.cend(), n = i + 1;

		QChar::Direction dir = control.basicDirection();
		int level = sor > 0 ? analysis[sor - 1].bidiLevel : control.level;
		while (sor <= _parLength) {
			while (i != _parStartBlock && (*i)->from() > _parStart + sor) {
				n = i;
				--i;
			}
			while (n != e && (*n)->from() <= _parStart + sor) {
				i = n;
				++n;
			}

			TextBlockType _itype = (*i)->type();
			if (eor == _parLength)
				dir = control.basicDirection();
			else if (_itype == TextBlockEmoji)
				dir = QChar::DirCS;
			else if (_itype == TextBlockSkip)
				dir = QChar::DirCS;
			else
				dir = QChar::direction(unicode[sor]);
			// Keep skipping DirBN as if it doesn't exist
			if (dir != QChar::DirBN)
				break;
			analysis[sor++].bidiLevel = level;
		}

		eor = sor;

		return dir;
	}

	// creates the next QScript items.
	bool eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
		bool rightToLeft = (control.basicDirection() == 1);
		bool hasBidi = rightToLeft;

		int sor = 0;
		int eor = -1;

		const ushort *unicode = reinterpret_cast<const ushort*>(_t->_text.unicode()) + _parStart;
		int current = 0;

		QChar::Direction dir = rightToLeft ? QChar::DirR : QChar::DirL;
		BidiStatus status;

		Text::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1;

		QChar::Direction sdir;
		TextBlockType _stype = (*_parStartBlock)->type();
		if (_stype == TextBlockEmoji)
			sdir = QChar::DirCS;
		else if (_stype == TextBlockSkip)
			sdir = QChar::DirCS;
		else
			sdir = QChar::direction(*unicode);
		if (sdir != QChar::DirL && sdir != QChar::DirR && sdir != QChar::DirEN && sdir != QChar::DirAN)
			sdir = QChar::DirON;
		else
			dir = QChar::DirON;

		status.eor = sdir;
		status.lastStrong = rightToLeft ? QChar::DirR : QChar::DirL;
		status.last = status.lastStrong;
		status.dir = sdir;

		while (current <= _parLength) {
			while (n != e && (*n)->from() <= _parStart + current) {
				i = n;
				++n;
			}

			QChar::Direction dirCurrent;
			TextBlockType _itype = (*i)->type();
			if (current == (int)_parLength)
				dirCurrent = control.basicDirection();
			else if (_itype == TextBlockEmoji)
				dirCurrent = QChar::DirCS;
			else if (_itype == TextBlockSkip)
				dirCurrent = QChar::DirCS;
			else
				dirCurrent = QChar::direction(unicode[current]);

			switch (dirCurrent) {

				// embedding and overrides (X1-X9 in the BiDi specs)
			case QChar::DirRLE:
			case QChar::DirRLO:
			case QChar::DirLRE:
			case QChar::DirLRO:
				{
					bool rtl = (dirCurrent == QChar::DirRLE || dirCurrent == QChar::DirRLO);
					hasBidi |= rtl;
					bool override = (dirCurrent == QChar::DirLRO || dirCurrent == QChar::DirRLO);

					unsigned int level = control.level+1;
					if ((level%2 != 0) == rtl) ++level;
					if (level < _MaxBidiLevel) {
						eor = current-1;
						eAppendItems(analysis, sor, eor, control, dir);
						eor = current;
						control.embed(rtl, override);
						QChar::Direction edir = (rtl ? QChar::DirR : QChar::DirL);
						dir = status.eor = edir;
						status.lastStrong = edir;
					}
					break;
				}
			case QChar::DirPDF:
				{
					if (control.canPop()) {
						if (dir != control.direction()) {
							eor = current-1;
							eAppendItems(analysis, sor, eor, control, dir);
							dir = control.direction();
						}
						eor = current;
						eAppendItems(analysis, sor, eor, control, dir);
						control.pdf();
						dir = QChar::DirON; status.eor = QChar::DirON;
						status.last = control.direction();
						if (control.override)
							dir = control.direction();
						else
							dir = QChar::DirON;
						status.lastStrong = control.direction();
					}
					break;
				}

				// strong types
			case QChar::DirL:
				if(dir == QChar::DirON)
					dir = QChar::DirL;
				switch(status.last)
					{
					case QChar::DirL:
						eor = current; status.eor = QChar::DirL; break;
					case QChar::DirR:
					case QChar::DirAL:
					case QChar::DirEN:
					case QChar::DirAN:
						if (eor >= 0) {
							eAppendItems(analysis, sor, eor, control, dir);
							status.eor = dir = eSkipBoundryNeutrals(analysis, unicode, sor, eor, control, i);
						} else {
							eor = current; status.eor = dir;
						}
						break;
					case QChar::DirES:
					case QChar::DirET:
					case QChar::DirCS:
					case QChar::DirBN:
					case QChar::DirB:
					case QChar::DirS:
					case QChar::DirWS:
					case QChar::DirON:
						if(dir != QChar::DirL) {
							//last stuff takes embedding dir
							if(control.direction() == QChar::DirR) {
								if(status.eor != QChar::DirR) {
									// AN or EN
									eAppendItems(analysis, sor, eor, control, dir);
									status.eor = QChar::DirON;
									dir = QChar::DirR;
								}
								eor = current - 1;
								eAppendItems(analysis, sor, eor, control, dir);
								status.eor = dir = eSkipBoundryNeutrals(analysis, unicode, sor, eor, control, i);
							} else {
								if(status.eor != QChar::DirL) {
									eAppendItems(analysis, sor, eor, control, dir);
									status.eor = QChar::DirON;
									dir = QChar::DirL;
								} else {
									eor = current; status.eor = QChar::DirL; break;
								}
							}
						} else {
							eor = current; status.eor = QChar::DirL;
						}
					default:
						break;
					}
				status.lastStrong = QChar::DirL;
				break;
			case QChar::DirAL:
			case QChar::DirR:
				hasBidi = true;
				if(dir == QChar::DirON) dir = QChar::DirR;
				switch(status.last)
					{
					case QChar::DirL:
					case QChar::DirEN:
					case QChar::DirAN:
						if (eor >= 0)
							eAppendItems(analysis, sor, eor, control, dir);
						// fall through
					case QChar::DirR:
					case QChar::DirAL:
						dir = QChar::DirR; eor = current; status.eor = QChar::DirR; break;
					case QChar::DirES:
					case QChar::DirET:
					case QChar::DirCS:
					case QChar::DirBN:
					case QChar::DirB:
					case QChar::DirS:
					case QChar::DirWS:
					case QChar::DirON:
						if(status.eor != QChar::DirR && status.eor != QChar::DirAL) {
							//last stuff takes embedding dir
							if(control.direction() == QChar::DirR
							   || status.lastStrong == QChar::DirR || status.lastStrong == QChar::DirAL) {
								eAppendItems(analysis, sor, eor, control, dir);
								dir = QChar::DirR; status.eor = QChar::DirON;
								eor = current;
							} else {
								eor = current - 1;
								eAppendItems(analysis, sor, eor, control, dir);
								dir = QChar::DirR; status.eor = QChar::DirON;
							}
						} else {
							eor = current; status.eor = QChar::DirR;
						}
					default:
						break;
					}
				status.lastStrong = dirCurrent;
				break;

				// weak types:

			case QChar::DirNSM:
				if (eor == current-1)
					eor = current;
				break;
			case QChar::DirEN:
				// if last strong was AL change EN to AN
				if(status.lastStrong != QChar::DirAL) {
					if(dir == QChar::DirON) {
						if(status.lastStrong == QChar::DirL)
							dir = QChar::DirL;
						else
							dir = QChar::DirEN;
					}
					switch(status.last)
						{
						case QChar::DirET:
							if (status.lastStrong == QChar::DirR || status.lastStrong == QChar::DirAL) {
								eAppendItems(analysis, sor, eor, control, dir);
								status.eor = QChar::DirON;
								dir = QChar::DirAN;
							}
							// fall through
						case QChar::DirEN:
						case QChar::DirL:
							eor = current;
							status.eor = dirCurrent;
							break;
						case QChar::DirR:
						case QChar::DirAL:
						case QChar::DirAN:
							if (eor >= 0)
								eAppendItems(analysis, sor, eor, control, dir);
							else
								eor = current;
							status.eor = QChar::DirEN;
							dir = QChar::DirAN; break;
						case QChar::DirES:
						case QChar::DirCS:
							if(status.eor == QChar::DirEN || dir == QChar::DirAN) {
								eor = current; break;
							}
						case QChar::DirBN:
						case QChar::DirB:
						case QChar::DirS:
						case QChar::DirWS:
						case QChar::DirON:
							if(status.eor == QChar::DirR) {
								// neutrals go to R
								eor = current - 1;
								eAppendItems(analysis, sor, eor, control, dir);
								dir = QChar::DirON; status.eor = QChar::DirEN;
								dir = QChar::DirAN;
							}
							else if(status.eor == QChar::DirL ||
									 (status.eor == QChar::DirEN && status.lastStrong == QChar::DirL)) {
								eor = current; status.eor = dirCurrent;
							} else {
								// numbers on both sides, neutrals get right to left direction
								if(dir != QChar::DirL) {
									eAppendItems(analysis, sor, eor, control, dir);
									dir = QChar::DirON; status.eor = QChar::DirON;
									eor = current - 1;
									dir = QChar::DirR;
									eAppendItems(analysis, sor, eor, control, dir);
									dir = QChar::DirON; status.eor = QChar::DirON;
									dir = QChar::DirAN;
								} else {
									eor = current; status.eor = dirCurrent;
								}
							}
						default:
							break;
						}
					break;
				}
			case QChar::DirAN:
				hasBidi = true;
				dirCurrent = QChar::DirAN;
				if(dir == QChar::DirON) dir = QChar::DirAN;
				switch(status.last)
					{
					case QChar::DirL:
					case QChar::DirAN:
						eor = current; status.eor = QChar::DirAN; break;
					case QChar::DirR:
					case QChar::DirAL:
					case QChar::DirEN:
						if (eor >= 0){
							eAppendItems(analysis, sor, eor, control, dir);
						} else {
							eor = current;
						}
						dir = QChar::DirAN; status.eor = QChar::DirAN;
						break;
					case QChar::DirCS:
						if(status.eor == QChar::DirAN) {
							eor = current; break;
						}
					case QChar::DirES:
					case QChar::DirET:
					case QChar::DirBN:
					case QChar::DirB:
					case QChar::DirS:
					case QChar::DirWS:
					case QChar::DirON:
						if(status.eor == QChar::DirR) {
							// neutrals go to R
							eor = current - 1;
							eAppendItems(analysis, sor, eor, control, dir);
							status.eor = QChar::DirAN;
							dir = QChar::DirAN;
						} else if(status.eor == QChar::DirL ||
								   (status.eor == QChar::DirEN && status.lastStrong == QChar::DirL)) {
							eor = current; status.eor = dirCurrent;
						} else {
							// numbers on both sides, neutrals get right to left direction
							if(dir != QChar::DirL) {
								eAppendItems(analysis, sor, eor, control, dir);
								status.eor = QChar::DirON;
								eor = current - 1;
								dir = QChar::DirR;
								eAppendItems(analysis, sor, eor, control, dir);
								status.eor = QChar::DirAN;
								dir = QChar::DirAN;
							} else {
								eor = current; status.eor = dirCurrent;
							}
						}
					default:
						break;
					}
				break;
			case QChar::DirES:
			case QChar::DirCS:
				break;
			case QChar::DirET:
				if(status.last == QChar::DirEN) {
					dirCurrent = QChar::DirEN;
					eor = current; status.eor = dirCurrent;
				}
				break;

				// boundary neutrals should be ignored
			case QChar::DirBN:
				break;
				// neutrals
			case QChar::DirB:
				// ### what do we do with newline and paragraph separators that come to here?
				break;
			case QChar::DirS:
				// ### implement rule L1
				break;
			case QChar::DirWS:
			case QChar::DirON:
				break;
			default:
				break;
			}

			if(current >= (int)_parLength) break;

			// set status.last as needed.
			switch(dirCurrent) {
			case QChar::DirET:
			case QChar::DirES:
			case QChar::DirCS:
			case QChar::DirS:
			case QChar::DirWS:
			case QChar::DirON:
				switch(status.last)
				{
				case QChar::DirL:
				case QChar::DirR:
				case QChar::DirAL:
				case QChar::DirEN:
				case QChar::DirAN:
					status.last = dirCurrent;
					break;
				default:
					status.last = QChar::DirON;
				}
				break;
			case QChar::DirNSM:
			case QChar::DirBN:
				// ignore these
				break;
			case QChar::DirLRO:
			case QChar::DirLRE:
				status.last = QChar::DirL;
				break;
			case QChar::DirRLO:
			case QChar::DirRLE:
				status.last = QChar::DirR;
				break;
			case QChar::DirEN:
				if (status.last == QChar::DirL) {
					status.last = QChar::DirL;
					break;
				}
				// fall through
			default:
				status.last = dirCurrent;
			}

			++current;
		}

		eor = current - 1; // remove dummy char

		if (sor <= eor)
			eAppendItems(analysis, sor, eor, control, dir);

		return hasBidi;
	}

private:

	QPainter *_p;
	const Text *_t;
	bool _elideLast;
	int32 _elideRemoveFromEnd;
	style::align _align;
	QPen _originalPen;
	int32 _yFrom, _yTo;
	uint16 _selectedFrom, _selectedTo;
	const QChar *_str;

	// current paragraph data
	Text::TextBlocks::const_iterator _parStartBlock;
	Qt::LayoutDirection _parDirection;
	int32 _parStart, _parLength;
	bool _parHasBidi;
	QVarLengthArray<QScriptAnalysis, 4096> _parAnalysis;

	// current line data
	QTextEngine *_e;
	style::font _f;
	QFixed _x, _w, _wLeft;
	int32 _y, _yDelta, _lineHeight, _fontHeight;
	
	// elided hack support
	int32 _blocksSize;
	int32 _elideSavedIndex;
	ITextBlock *_elideSavedBlock;

	int32 _lineStart, _localFrom;
	int32 _lineStartBlock;

	// link and symbol resolve
	QFixed _lnkX;
	int32 _lnkY;
	const TextLinkPtr *_lnkResult;
	bool *_inTextFlag;
	uint16 *_getSymbol;
	bool *_getSymbolAfter, *_getSymbolUpon;

};

const TextParseOptions _defaultOptions = {
	TextParseLinks | TextParseMultiline, // flags
	0, // maxw
	0, // maxh
	Qt::LayoutDirectionAuto, // dir
};

const TextParseOptions _textPlainOptions = {
	TextParseMultiline, // flags
	0, // maxw
	0, // maxh
	Qt::LayoutDirectionAuto, // dir
};

Text::Text(int32 minResizeWidth) : _minResizeWidth(minResizeWidth), _maxWidth(0), _minHeight(0), _startDir(Qt::LayoutDirectionAuto) {
}

Text::Text(style::font font, const QString &text, const TextParseOptions &options, int32 minResizeWidth, bool richText) : _minResizeWidth(minResizeWidth) {
	if (richText) {
		setRichText(font, text, options);
	} else {
		setText(font, text, options);
	}
}

Text::Text(const Text &other) :
_minResizeWidth(other._minResizeWidth), _maxWidth(other._maxWidth),
_minHeight(other._minHeight),
_text(other._text),
_font(other._font),
_blocks(other._blocks.size()),
_links(other._links),
_startDir(other._startDir)
{
	for (int32 i = 0, l = _blocks.size(); i < l; ++i) {
		_blocks[i] = other._blocks.at(i)->clone();
	}
}

Text &Text::operator=(const Text &other) {
	_minResizeWidth = other._minResizeWidth;
	_maxWidth = other._maxWidth;
	_minHeight = other._minHeight;
	_text = other._text;
	_font = other._font;
	_blocks = TextBlocks(other._blocks.size());
	_links = other._links;
	_startDir = other._startDir;
	for (int32 i = 0, l = _blocks.size(); i < l; ++i) {
		_blocks[i] = other._blocks.at(i)->clone();
	}
	return *this;
}

void Text::setText(style::font font, const QString &text, const TextParseOptions &options) {
	if (!_textStyle) _initDefault();
	_font = font;
	clean();
	{
		TextParser parser(this, text, options);
	}
	recountNaturalSize(true, options.dir);
}

void Text::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
	NewlineBlock *lastNewline = 0;

	_maxWidth = _minHeight = 0;
	int32 lineHeight = 0;
	int32 result = 0, lastNewlineStart = 0;
	QFixed _width = 0, last_rBearing = 0, last_rPadding = 0;
	for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); i != e; ++i) {
		ITextBlock *b = *i;
		TextBlockType _btype = b->type();
		int32 blockHeight = _blockHeight(b, _font);
		QFixed _rb = _blockRBearing(b);

		if (_btype == TextBlockNewline) {
			if (!lineHeight) lineHeight = blockHeight;
			if (initial) {
				Qt::LayoutDirection dir = optionsDir;
				if (dir == Qt::LayoutDirectionAuto) {
					dir = TextParser::stringDirection(_text, lastNewlineStart, b->from());
				}
				if (lastNewline) {
					lastNewline->_nextDir = dir;
				} else {
					_startDir = dir;
				}
			}
			lastNewlineStart = b->from();
			lastNewline = static_cast<NewlineBlock*>(b);

			_minHeight += lineHeight;
			lineHeight = 0;
			last_rBearing = _rb;
			last_rPadding = b->f_rpadding();
			if (_maxWidth < _width) {
				_maxWidth = _width;
			}
			_width = (b->f_width() - last_rBearing);
			continue;
		}

		_width += b->f_lpadding();
		_width += last_rBearing + (last_rPadding + b->f_width() - _rb);
		lineHeight = qMax(lineHeight, blockHeight);

		last_rBearing = _rb;
		last_rPadding = b->f_rpadding();
		continue;
	}
	if (initial) {
		Qt::LayoutDirection dir = optionsDir;
		if (dir == Qt::LayoutDirectionAuto) {
			dir = TextParser::stringDirection(_text, lastNewlineStart, _text.size());
		}
		if (lastNewline) {
			lastNewline->_nextDir = dir;
		} else {
			_startDir = dir;
		}
	}
	if (_width > 0) {
		if (!lineHeight) lineHeight = _blockHeight(_blocks.back(), _font);
		_minHeight += lineHeight;
		if (_maxWidth < _width) {
			_maxWidth = _width;
		}
	}
}

void Text::setMarkedText(style::font font, const QString &text, const LinksInText &links, const TextParseOptions &options) {
	if (!_textStyle) _initDefault();
	_font = font;
	clean();
	{
		TextParser parser(this, text, links, options);
	}
	recountNaturalSize(true, options.dir);
}

void Text::setRichText(style::font font, const QString &text, TextParseOptions options, const TextCustomTagsMap &custom) {
	QString parsed;
	parsed.reserve(text.size());
	const QChar *s = text.constData(), *ch = s;
	for (const QChar *b = s, *e = b + text.size(); ch != e; ++ch) {
		if (ch->unicode() == '\\') {
			if (ch > s) parsed.append(s, ch - s);
			s = ch + 1;

			if (s < e) ++ch;
			continue;
		}
		if (ch->unicode() == '[') {
			if (ch > s) parsed.append(s, ch - s);
			s = ch;

			const QChar *tag = ch + 1;
			if (tag >= e) continue;

			bool closing = false, other = false;
			if (tag->unicode() == '/') {
				closing = true;
				if (++tag >= e) continue;
			}

			TextCommands cmd;
			switch (tag->unicode()) {
			case 'b': cmd = closing ? TextCommandNoBold : TextCommandBold; break;
			case 'i': cmd = closing ? TextCommandNoItalic : TextCommandItalic; break;
			case 'u': cmd = closing ? TextCommandNoUnderline : TextCommandUnderline; break;
			default : other = true; break;
			}

			if (!other) {
				if (++tag >= e || tag->unicode() != ']') continue;
				parsed.append(TextCommand).append(QChar(cmd)).append(TextCommand);
				ch = tag;
				s = ch + 1;
				continue;
			}

			if (tag->unicode() != 'a') {
				TextCustomTagsMap::const_iterator i = custom.constFind(*tag);
				if (++tag >= e || tag->unicode() != ']' || i == custom.cend()) continue;
				parsed.append(closing ? i->second : i->first);
				ch = tag;
				s = ch + 1;
				continue;
			}

			if (closing) {
				if (++tag >= e || tag->unicode() != ']') continue;
				parsed.append(textcmdStopLink());
				ch = tag;
				s = ch + 1;
				continue;
			}
			if (++tag >= e || tag->unicode() != ' ') continue;
			while (tag < e && tag->unicode() == ' ') ++tag;
			if (tag + 5 < e && text.midRef(tag - b, 6) == qsl("href=\"")) {
				tag += 6;
				const QChar *tagend = tag;
				while (tagend < e && tagend->unicode() != '"') ++tagend;
				if (++tagend >= e || tagend->unicode() != ']') continue;
				parsed.append(textcmdStartLink(QString(tag, tagend - 1 - tag)));
				ch = tagend;
				s = ch + 1;
				continue;
			}
		}
	}
	if (ch > s) parsed.append(s, ch - s);
	s = ch;

	options.flags |= TextParseRichText;
	setText(font, parsed, options);
}

void Text::setLink(uint16 lnkIndex, const TextLinkPtr &lnk) {
	if (!lnkIndex || lnkIndex > _links.size()) return;
	_links[lnkIndex - 1] = lnk;
}

bool Text::hasLinks() const {
	return !_links.isEmpty();
}

void Text::setSkipBlock(int32 width, int32 height) {
	if (!_blocks.isEmpty() && _blocks.back()->type() == TextBlockSkip) {
		SkipBlock *block = static_cast<SkipBlock*>(_blocks.back());
		if (block->width() == width && block->height() == height) return;
		_text.resize(block->from());
		_blocks.pop_back();
	}
	_text.push_back('_');
	_blocks.push_back(new SkipBlock(_font, _text, _text.size() - 1, width, height, 0));
	recountNaturalSize(false);
}

void Text::removeSkipBlock() {
	if (!_blocks.isEmpty() && _blocks.back()->type() == TextBlockSkip) {
		_text.resize(_blocks.back()->from());
		_blocks.pop_back();
		recountNaturalSize(false);
	}
}

LinksInText Text::calcLinksInText() const {
	LinksInText result;
	int32 lnkFrom = 0, lnkIndex = 0, offset = 0;
	for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
		int32 blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
		int32 blockFrom = (i == e) ? _text.size() : (*i)->from();
		if (blockLnkIndex != lnkIndex) {
			if (lnkIndex) { // write link
				const TextLinkPtr &lnk(_links.at(lnkIndex - 1));
				const QString &url(lnk ? lnk->text() : QString());

				int32 rangeFrom = lnkFrom, rangeTo = blockFrom;
				if (rangeTo > rangeFrom) {
					QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom);
					if (url.isEmpty()) {
						offset += r.size();
					} else {
						QUrl u(url);
						if (r.size() <= 3 || _text.midRef(lnkFrom, r.size() - 3) == (u.isValid() ? u.toDisplayString() : url).midRef(0, r.size() - 3)) { // same link
							if (url.at(0) == '@') {
								result.push_back(LinkInText(LinkInTextMention, offset, url.size()));
							} else if (url.at(0) == '#') {
								result.push_back(LinkInText(LinkInTextHashtag, offset, url.size()));
							} else if (url.at(0) == '/') {
								result.push_back(LinkInText(LinkInTextBotCommand, offset, url.size()));
							} else if (url.indexOf('@') > 0 && url.indexOf('/') <= 0) {
								result.push_back(LinkInText(LinkInTextEmail, offset, url.size()));
							} else {
								result.push_back(LinkInText(LinkInTextUrl, offset, url.size()));
							}
							offset += url.size();
						} else {
							result.push_back(LinkInText(LinkInTextCustomUrl, offset, r.size(), url));
							offset += r.size();
						}
					}
				}
			}
			lnkIndex = blockLnkIndex;
			lnkFrom = blockFrom;
		}
		if (i == e) break;

		TextBlockType type = (*i)->type();
		if (type == TextBlockSkip) continue;

		if (!blockLnkIndex) {
			int32 rangeFrom = (*i)->from(), rangeTo = uint16((*i)->from() + TextPainter::_blockLength(this, i, e));
			if (rangeTo > rangeFrom) {
				offset += rangeTo - rangeFrom;
			}
		}
	}
	return result;
}

int32 Text::countHeight(int32 w) const {
	QFixed width = w;
	if (width < _minResizeWidth) width = _minResizeWidth;
	if (width >= _maxWidth) {
		return _minHeight;
	}

	int32 result = 0, lineHeight = 0;
	QFixed widthLeft = width, last_rBearing = 0, last_rPadding = 0;
	bool longWordLine = true;
	for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); i != e; ++i) {
		ITextBlock *b = *i;
		TextBlockType _btype = b->type();
		int32 blockHeight = _blockHeight(b, _font);
		QFixed _rb = _blockRBearing(b);

		if (_btype == TextBlockNewline) {
			if (!lineHeight) lineHeight = blockHeight;
			result += lineHeight;
			lineHeight = 0;
			last_rBearing = _rb;
			last_rPadding = b->f_rpadding();
			widthLeft = width - (b->f_width() - last_rBearing);

			longWordLine = true;
			continue;
		}
		widthLeft -= b->f_lpadding();
		QFixed newWidthLeft = widthLeft - last_rBearing - (last_rPadding + b->f_width() - _rb);
		if (newWidthLeft >= 0) {
			last_rBearing = _rb;
			last_rPadding = b->f_rpadding();
			widthLeft = newWidthLeft;

			lineHeight = qMax(lineHeight, blockHeight);

			longWordLine = false;
			continue;
		}

		if (_btype == TextBlockText) {
			TextBlock *t = static_cast<TextBlock*>(b);
			QFixed f_wLeft = widthLeft;
			int32 f_lineHeight = lineHeight;
			for (TextBlock::TextWords::const_iterator j = t->_words.cbegin(), e = t->_words.cend(), f = j; j != e; ++j) {
				bool wordEndsHere = (j->width >= 0);
				QFixed j_width = wordEndsHere ? j->width : -j->width;

				QFixed newWidthLeft = widthLeft - last_rBearing - (last_rPadding + j_width - j->f_rbearing());
				if (newWidthLeft >= 0) {
					last_rBearing = j->f_rbearing();
					last_rPadding = j->rpadding;
					widthLeft = newWidthLeft;

					lineHeight = qMax(lineHeight, blockHeight);

					if (wordEndsHere) {
						longWordLine = false;
					}
					if (wordEndsHere || longWordLine) {
						f_wLeft = widthLeft;
						f_lineHeight = lineHeight;
						f = j + 1;
					}
					continue;
				}

				if (f != j) {
					j = f;
					widthLeft = f_wLeft;
					lineHeight = f_lineHeight;
					j_width = (j->width >= 0) ? j->width : -j->width;
				}

				result += lineHeight;
				lineHeight = qMax(0, blockHeight);
				last_rBearing = j->f_rbearing();
				last_rPadding = j->rpadding;
				widthLeft = width - (j_width - last_rBearing);

				longWordLine = true;
				f = j + 1;
				f_wLeft = widthLeft;
				f_lineHeight = lineHeight;
			}
			continue;
		}

		result += lineHeight;
		lineHeight = qMax(0, blockHeight);
		last_rBearing = _rb;
		last_rPadding = b->f_rpadding();
		widthLeft = width - (b->f_width() - last_rBearing);

		longWordLine = true;
		continue;
	}
	if (widthLeft < width) {
		result += lineHeight;
	}

	return result;
}

void Text::replaceFont(style::font f) {
	_font = f;
}

void Text::draw(QPainter &painter, int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, uint16 selectedFrom, uint16 selectedTo) const {
//	painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug
	TextPainter p(&painter, this);
	p.draw(left, top, w, align, yFrom, yTo, selectedFrom, selectedTo);
}

void Text::drawElided(QPainter &painter, int32 left, int32 top, int32 w, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd) const {
//	painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug
	TextPainter p(&painter, this);
	p.drawElided(left, top, w, align, lines, yFrom, yTo, removeFromEnd);
}

const TextLinkPtr &Text::link(int32 x, int32 y, int32 width, style::align align) const {
	TextPainter p(0, this);
	return p.link(x, y, width, align);
}

void Text::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y, int32 width, style::align align) const {
	TextPainter p(0, this);
	p.getState(lnk, inText, x, y, width, align);
}

void Text::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y, int32 width, style::align align) const {
	TextPainter p(0, this);
	p.getSymbol(symbol, after, upon, x, y, width, align);
}

uint32 Text::adjustSelection(uint16 from, uint16 to, TextSelectType selectType) const {
	if (from < _text.size() && from <= to) {
		if (to > _text.size()) to = _text.size() - 1;
		if (selectType == TextSelectParagraphs) {
			if (!chIsParagraphSeparator(_text.at(from))) {
				while (from > 0 && !chIsParagraphSeparator(_text.at(from - 1))) {
					--from;
				}
			}
			if (to < _text.size()) {
				if (chIsParagraphSeparator(_text.at(to))) {
					++to;
				} else {
					while (to < _text.size() && !chIsParagraphSeparator(_text.at(to))) {
						++to;
					}
				}
			}
		} else if (selectType == TextSelectWords) {
			if (!chIsWordSeparator(_text.at(from))) {
				while (from > 0 && !chIsWordSeparator(_text.at(from - 1))) {
					--from;
				}
			}
			if (to < _text.size()) {
				if (chIsWordSeparator(_text.at(to))) {
					++to;
				} else {
					while (to < _text.size() && !chIsWordSeparator(_text.at(to))) {
						++to;
					}
				}
			}
		}
	}
	return (from << 16) | to;
}

QString Text::original(uint16 selectedFrom, uint16 selectedTo, bool expandLinks) const {
	QString result;
	result.reserve(_text.size());

	int32 lnkFrom = 0, lnkIndex = 0;
	for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {		
		int32 blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
		int32 blockFrom = (i == e) ? _text.size() : (*i)->from();
		if (blockLnkIndex != lnkIndex) {
			if (lnkIndex) { // write link
				const TextLinkPtr &lnk(_links.at(lnkIndex - 1));
				const QString &url(lnk ? lnk->text() : QString());

				int32 rangeFrom = qMax(int32(selectedFrom), lnkFrom), rangeTo = qMin(blockFrom, int32(selectedTo));

				if (rangeTo > rangeFrom) {
					QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom);
					if (url.isEmpty() || !expandLinks || lnkFrom != rangeFrom || blockFrom != rangeTo) {
						result += r;
					} else {
						QUrl u(url);
						if (r.size() <= 3 || _text.midRef(lnkFrom, r.size() - 3) == (u.isValid() ? u.toDisplayString() : url).midRef(0, r.size() - 3)) { // same link
							result += url;
						} else {
							result.append(r).append(qsl(" ( ")).append(url).append(qsl(" )"));
						}
					}
				}
			}
			lnkIndex = blockLnkIndex;
			lnkFrom = blockFrom;
		}
		if (i == e) break;

		TextBlockType type = (*i)->type();
		if (type == TextBlockSkip) continue;

		if (!blockLnkIndex) {
			int32 rangeFrom = qMax(selectedFrom, (*i)->from()), rangeTo = qMin(selectedTo, uint16((*i)->from() + TextPainter::_blockLength(this, i, e)));
			if (rangeTo > rangeFrom) {
				result += _text.midRef(rangeFrom, rangeTo - rangeFrom);
			}
		}
	}
	return result;
}

void Text::clean() {
	for (TextBlocks::iterator i = _blocks.begin(), e = _blocks.end(); i != e; ++i) {
		delete *i;
	}
	_blocks.clear();
	_links.clear();
	_maxWidth = _minHeight = 0;
	_startDir = Qt::LayoutDirectionAuto;
}

// COPIED FROM qtextlayout.cpp AND MODIFIED
namespace {

	struct ScriptLine {
        ScriptLine() : length(0), textWidth(0) {
        }

		int32 length;
		QFixed textWidth;
	};

    struct LineBreakHelper
    {
        LineBreakHelper()
            : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0)
        {
        }


        ScriptLine tmpData;
        ScriptLine spaceData;

        QGlyphLayout glyphs;

        int glyphCount;
        int maxGlyphs;
        int currentPosition;
        glyph_t previousGlyph;

        QFixed rightBearing;

        QFontEngine *fontEngine;
        const unsigned short *logClusters;

        inline glyph_t currentGlyph() const
        {
            Q_ASSERT(currentPosition > 0);
            Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);

            return glyphs.glyphs[logClusters[currentPosition - 1]];
        }

        inline void saveCurrentGlyph()
        {
            previousGlyph = 0;
            if (currentPosition > 0 &&
                logClusters[currentPosition - 1] < glyphs.numGlyphs) {
                previousGlyph = currentGlyph(); // needed to calculate right bearing later
            }
        }

        inline void adjustRightBearing(glyph_t glyph)
        {
            qreal rb;
            fontEngine->getGlyphBearings(glyph, 0, &rb);
            rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
        }

        inline void adjustRightBearing()
        {
            if (currentPosition <= 0)
                return;
            adjustRightBearing(currentGlyph());
        }

        inline void adjustPreviousRightBearing()
        {
            if (previousGlyph > 0)
                adjustRightBearing(previousGlyph);
        }

    };

	static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyphCount,
									  const QScriptItem &current, const unsigned short *logClusters,
									  const QGlyphLayout &glyphs)
	{
		int glyphPosition = logClusters[pos];
		do { // got to the first next cluster
			++pos;
			++line.length;
		} while (pos < end && logClusters[pos] == glyphPosition);
		do { // calculate the textWidth for the rest of the current cluster.
			if (!glyphs.attributes[glyphPosition].dontPrint)
				line.textWidth += glyphs.advances[glyphPosition];
			++glyphPosition;
		} while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);

		Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);

		++glyphCount;
	}

} // anonymous namespace

class BlockParser {
public:

	BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom) : block(b), eng(e) {
		parseWords(minResizeWidth, blockFrom);
	}

	void parseWords(QFixed minResizeWidth, int32 blockFrom) {
		LineBreakHelper lbh;

		lbh.maxGlyphs = INT_MAX;

		int item = -1;
		int newItem = eng->findItem(0);

		style::align alignment = eng->option.alignment();

		const QCharAttributes *attributes = eng->attributes();
		if (!attributes)
			return;
		lbh.currentPosition = 0;
		int end = 0;
		lbh.logClusters = eng->layoutData->logClustersPtr;
		lbh.previousGlyph = 0;

		block->_lpadding = 0;
		block->_words.clear();

		int wordStart = lbh.currentPosition;

		bool addingEachGrapheme = false;
		int lastGraphemeBoundaryPosition = -1;
		ScriptLine lastGraphemeBoundaryLine;

		while (newItem < eng->layoutData->items.size()) {
			if (newItem != item) {
				item = newItem;
				const QScriptItem &current = eng->layoutData->items[item];
				if (!current.num_glyphs) {
					eng->shape(item);
					attributes = eng->attributes();
					if (!attributes)
						return;
					lbh.logClusters = eng->layoutData->logClustersPtr;
				}
				lbh.currentPosition = current.position;
				end = current.position + eng->length(item);
				lbh.glyphs = eng->shapedGlyphs(&current);
				QFontEngine *fontEngine = eng->fontEngine(current);
				if (lbh.fontEngine != fontEngine) {
					lbh.fontEngine = fontEngine;
				}
			}
			const QScriptItem &current = eng->layoutData->items[item];

			if (attributes[lbh.currentPosition].whiteSpace) {
				while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
					addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
								   current, lbh.logClusters, lbh.glyphs);

				if (block->_words.isEmpty()) {
					block->_lpadding = lbh.spaceData.textWidth;
				} else {
					block->_words.back().rpadding += lbh.spaceData.textWidth;
					block->_width += lbh.spaceData.textWidth;
				}
				lbh.spaceData.length = 0;
				lbh.spaceData.textWidth = 0;

				wordStart = lbh.currentPosition;

				addingEachGrapheme = false;
				lastGraphemeBoundaryPosition = -1;
				lastGraphemeBoundaryLine = ScriptLine();
			} else {
				do {
					addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
								   current, lbh.logClusters, lbh.glyphs);

					if (lbh.currentPosition >= eng->layoutData->string.length()
						|| attributes[lbh.currentPosition].whiteSpace
						|| attributes[lbh.currentPosition].lineBreak) {
						lbh.adjustRightBearing();
						block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, qMin(QFixed(), lbh.rightBearing)));
						block->_width += lbh.tmpData.textWidth;
						lbh.tmpData.textWidth = 0;
						lbh.tmpData.length = 0;
						wordStart = lbh.currentPosition;
						break;
					} else if (attributes[lbh.currentPosition].graphemeBoundary) {
						if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) {
							if (lastGraphemeBoundaryPosition >= 0) {
								lbh.adjustPreviousRightBearing();
								block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, qMin(QFixed(), lbh.rightBearing)));
								block->_width += lastGraphemeBoundaryLine.textWidth;
								lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth;
								lbh.tmpData.length -= lastGraphemeBoundaryLine.length;
								wordStart = lastGraphemeBoundaryPosition;
							}
							addingEachGrapheme = true;
						}
						if (addingEachGrapheme) {
							lbh.adjustRightBearing();
							block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, qMin(QFixed(), lbh.rightBearing)));
							block->_width += lbh.tmpData.textWidth;
							lbh.tmpData.textWidth = 0;
							lbh.tmpData.length = 0;
							wordStart = lbh.currentPosition;
						} else {
							lastGraphemeBoundaryPosition = lbh.currentPosition;
							lastGraphemeBoundaryLine = lbh.tmpData;
							lbh.saveCurrentGlyph();
						}
					}
				} while (lbh.currentPosition < end);
			}
			if (lbh.currentPosition == end)
				newItem = item + 1;
		}
		if (block->_words.isEmpty()) {
			block->_rpadding = 0;
		} else {
			block->_rpadding = block->_words.back().rpadding;
			block->_width -= block->_rpadding;
			block->_words.squeeze();
		}
	}

private:

	TextBlock *block;
	QTextEngine *eng;

};

TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex) : ITextBlock(font, str, from, length, flags, color, lnkIndex) {
	_flags |= ((TextBlockText & 0x0F) << 8);
	if (length) {
		style::font blockFont = font;
		if (!flags && lnkIndex) {
			// should use textStyle lnkFlags somehow.. not supported
		}
		if (flags & TextBlockBold) blockFont = blockFont->bold();
		if (flags & TextBlockItalic) blockFont = blockFont->italic();
		if (flags & TextBlockUnderline) blockFont = blockFont->underline();

		QStackTextEngine engine(str.mid(_from, length), blockFont->f);
		engine.itemize();

		QTextLayout layout(&engine);
		layout.beginLayout();
		layout.createLine();

		BlockParser parser(&engine, this, minResizeWidth, _from);

		layout.endLayout();
	}
}

EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex, const EmojiData *emoji) : ITextBlock(font, str, from, length, flags, color, lnkIndex), emoji(emoji) {
	_flags |= ((TextBlockEmoji & 0x0F) << 8);
	_width = int(st::emojiSize + 2 * st::emojiPadding);
}

SkipBlock::SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex) : ITextBlock(font, str, from, 1, 0, style::color(), lnkIndex), _height(h) {
	_flags |= ((TextBlockSkip & 0x0F) << 8);
	_width = w;
}

namespace {
	void regOneProtocol(const QString &protocol) {
		_validProtocols.insert(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar)));
	}
	void regOneTopDomain(const QString &domain) {
		_validTopDomains.insert(hashCrc32(domain.constData(), domain.size() * sizeof(QChar)));
	}
}

const QSet<int32> &validProtocols() {
	return _validProtocols;
}
const QSet<int32> &validTopDomains() {
	return _validTopDomains;
}

void initLinkSets() {
	if (!_validProtocols.isEmpty() || !_validTopDomains.isEmpty()) return;

	regOneProtocol(qsl("itmss")); // itunes
	regOneProtocol(qsl("http"));
	regOneProtocol(qsl("https"));
	regOneProtocol(qsl("ftp"));
	regOneProtocol(qsl("tg")); // local urls

	regOneTopDomain(qsl("ac"));
	regOneTopDomain(qsl("ad"));
	regOneTopDomain(qsl("ae"));
	regOneTopDomain(qsl("af"));
	regOneTopDomain(qsl("ag"));
	regOneTopDomain(qsl("ai"));
	regOneTopDomain(qsl("al"));
	regOneTopDomain(qsl("am"));
	regOneTopDomain(qsl("an"));
	regOneTopDomain(qsl("ao"));
	regOneTopDomain(qsl("aq"));
	regOneTopDomain(qsl("ar"));
	regOneTopDomain(qsl("as"));
	regOneTopDomain(qsl("at"));
	regOneTopDomain(qsl("au"));
	regOneTopDomain(qsl("aw"));
	regOneTopDomain(qsl("ax"));
	regOneTopDomain(qsl("az"));
	regOneTopDomain(qsl("ba"));
	regOneTopDomain(qsl("bb"));
	regOneTopDomain(qsl("bd"));
	regOneTopDomain(qsl("be"));
	regOneTopDomain(qsl("bf"));
	regOneTopDomain(qsl("bg"));
	regOneTopDomain(qsl("bh"));
	regOneTopDomain(qsl("bi"));
	regOneTopDomain(qsl("bj"));
	regOneTopDomain(qsl("bm"));
	regOneTopDomain(qsl("bn"));
	regOneTopDomain(qsl("bo"));
	regOneTopDomain(qsl("br"));
	regOneTopDomain(qsl("bs"));
	regOneTopDomain(qsl("bt"));
	regOneTopDomain(qsl("bv"));
	regOneTopDomain(qsl("bw"));
	regOneTopDomain(qsl("by"));
	regOneTopDomain(qsl("bz"));
	regOneTopDomain(qsl("ca"));
	regOneTopDomain(qsl("cc"));
	regOneTopDomain(qsl("cd"));
	regOneTopDomain(qsl("cf"));
	regOneTopDomain(qsl("cg"));
	regOneTopDomain(qsl("ch"));
	regOneTopDomain(qsl("ci"));
	regOneTopDomain(qsl("ck"));
	regOneTopDomain(qsl("cl"));
	regOneTopDomain(qsl("cm"));
	regOneTopDomain(qsl("cn"));
	regOneTopDomain(qsl("co"));
	regOneTopDomain(qsl("cr"));
	regOneTopDomain(qsl("cu"));
	regOneTopDomain(qsl("cv"));
	regOneTopDomain(qsl("cx"));
	regOneTopDomain(qsl("cy"));
	regOneTopDomain(qsl("cz"));
	regOneTopDomain(qsl("de"));
	regOneTopDomain(qsl("dj"));
	regOneTopDomain(qsl("dk"));
	regOneTopDomain(qsl("dm"));
	regOneTopDomain(qsl("do"));
	regOneTopDomain(qsl("dz"));
	regOneTopDomain(qsl("ec"));
	regOneTopDomain(qsl("ee"));
	regOneTopDomain(qsl("eg"));
	regOneTopDomain(qsl("eh"));
	regOneTopDomain(qsl("er"));
	regOneTopDomain(qsl("es"));
	regOneTopDomain(qsl("et"));
	regOneTopDomain(qsl("eu"));
	regOneTopDomain(qsl("fi"));
	regOneTopDomain(qsl("fj"));
	regOneTopDomain(qsl("fk"));
	regOneTopDomain(qsl("fm"));
	regOneTopDomain(qsl("fo"));
	regOneTopDomain(qsl("fr"));
	regOneTopDomain(qsl("ga"));
	regOneTopDomain(qsl("gd"));
	regOneTopDomain(qsl("ge"));
	regOneTopDomain(qsl("gf"));
	regOneTopDomain(qsl("gg"));
	regOneTopDomain(qsl("gh"));
	regOneTopDomain(qsl("gi"));
	regOneTopDomain(qsl("gl"));
	regOneTopDomain(qsl("gm"));
	regOneTopDomain(qsl("gn"));
	regOneTopDomain(qsl("gp"));
	regOneTopDomain(qsl("gq"));
	regOneTopDomain(qsl("gr"));
	regOneTopDomain(qsl("gs"));
	regOneTopDomain(qsl("gt"));
	regOneTopDomain(qsl("gu"));
	regOneTopDomain(qsl("gw"));
	regOneTopDomain(qsl("gy"));
	regOneTopDomain(qsl("hk"));
	regOneTopDomain(qsl("hm"));
	regOneTopDomain(qsl("hn"));
	regOneTopDomain(qsl("hr"));
	regOneTopDomain(qsl("ht"));
	regOneTopDomain(qsl("hu"));
	regOneTopDomain(qsl("id"));
	regOneTopDomain(qsl("ie"));
	regOneTopDomain(qsl("il"));
	regOneTopDomain(qsl("im"));
	regOneTopDomain(qsl("in"));
	regOneTopDomain(qsl("io"));
	regOneTopDomain(qsl("iq"));
	regOneTopDomain(qsl("ir"));
	regOneTopDomain(qsl("is"));
	regOneTopDomain(qsl("it"));
	regOneTopDomain(qsl("je"));
	regOneTopDomain(qsl("jm"));
	regOneTopDomain(qsl("jo"));
	regOneTopDomain(qsl("jp"));
	regOneTopDomain(qsl("ke"));
	regOneTopDomain(qsl("kg"));
	regOneTopDomain(qsl("kh"));
	regOneTopDomain(qsl("ki"));
	regOneTopDomain(qsl("km"));
	regOneTopDomain(qsl("kn"));
	regOneTopDomain(qsl("kp"));
	regOneTopDomain(qsl("kr"));
	regOneTopDomain(qsl("kw"));
	regOneTopDomain(qsl("ky"));
	regOneTopDomain(qsl("kz"));
	regOneTopDomain(qsl("la"));
	regOneTopDomain(qsl("lb"));
	regOneTopDomain(qsl("lc"));
	regOneTopDomain(qsl("li"));
	regOneTopDomain(qsl("lk"));
	regOneTopDomain(qsl("lr"));
	regOneTopDomain(qsl("ls"));
	regOneTopDomain(qsl("lt"));
	regOneTopDomain(qsl("lu"));
	regOneTopDomain(qsl("lv"));
	regOneTopDomain(qsl("ly"));
	regOneTopDomain(qsl("ma"));
	regOneTopDomain(qsl("mc"));
	regOneTopDomain(qsl("md"));
	regOneTopDomain(qsl("me"));
	regOneTopDomain(qsl("mg"));
	regOneTopDomain(qsl("mh"));
	regOneTopDomain(qsl("mk"));
	regOneTopDomain(qsl("ml"));
	regOneTopDomain(qsl("mm"));
	regOneTopDomain(qsl("mn"));
	regOneTopDomain(qsl("mo"));
	regOneTopDomain(qsl("mp"));
	regOneTopDomain(qsl("mq"));
	regOneTopDomain(qsl("mr"));
	regOneTopDomain(qsl("ms"));
	regOneTopDomain(qsl("mt"));
	regOneTopDomain(qsl("mu"));
	regOneTopDomain(qsl("mv"));
	regOneTopDomain(qsl("mw"));
	regOneTopDomain(qsl("mx"));
	regOneTopDomain(qsl("my"));
	regOneTopDomain(qsl("mz"));
	regOneTopDomain(qsl("na"));
	regOneTopDomain(qsl("nc"));
	regOneTopDomain(qsl("ne"));
	regOneTopDomain(qsl("nf"));
	regOneTopDomain(qsl("ng"));
	regOneTopDomain(qsl("ni"));
	regOneTopDomain(qsl("nl"));
	regOneTopDomain(qsl("no"));
	regOneTopDomain(qsl("np"));
	regOneTopDomain(qsl("nr"));
	regOneTopDomain(qsl("nu"));
	regOneTopDomain(qsl("nz"));
	regOneTopDomain(qsl("om"));
	regOneTopDomain(qsl("pa"));
	regOneTopDomain(qsl("pe"));
	regOneTopDomain(qsl("pf"));
	regOneTopDomain(qsl("pg"));
	regOneTopDomain(qsl("ph"));
	regOneTopDomain(qsl("pk"));
	regOneTopDomain(qsl("pl"));
	regOneTopDomain(qsl("pm"));
	regOneTopDomain(qsl("pn"));
	regOneTopDomain(qsl("pr"));
	regOneTopDomain(qsl("ps"));
	regOneTopDomain(qsl("pt"));
	regOneTopDomain(qsl("pw"));
	regOneTopDomain(qsl("py"));
	regOneTopDomain(qsl("qa"));
	regOneTopDomain(qsl("re"));
	regOneTopDomain(qsl("ro"));
	regOneTopDomain(qsl("ru"));
	regOneTopDomain(qsl("rs"));
	regOneTopDomain(qsl("rw"));
	regOneTopDomain(qsl("sa"));
	regOneTopDomain(qsl("sb"));
	regOneTopDomain(qsl("sc"));
	regOneTopDomain(qsl("sd"));
	regOneTopDomain(qsl("se"));
	regOneTopDomain(qsl("sg"));
	regOneTopDomain(qsl("sh"));
	regOneTopDomain(qsl("si"));
	regOneTopDomain(qsl("sj"));
	regOneTopDomain(qsl("sk"));
	regOneTopDomain(qsl("sl"));
	regOneTopDomain(qsl("sm"));
	regOneTopDomain(qsl("sn"));
	regOneTopDomain(qsl("so"));
	regOneTopDomain(qsl("sr"));
	regOneTopDomain(qsl("ss"));
	regOneTopDomain(qsl("st"));
	regOneTopDomain(qsl("su"));
	regOneTopDomain(qsl("sv"));
	regOneTopDomain(qsl("sx"));
	regOneTopDomain(qsl("sy"));
	regOneTopDomain(qsl("sz"));
	regOneTopDomain(qsl("tc"));
	regOneTopDomain(qsl("td"));
	regOneTopDomain(qsl("tf"));
	regOneTopDomain(qsl("tg"));
	regOneTopDomain(qsl("th"));
	regOneTopDomain(qsl("tj"));
	regOneTopDomain(qsl("tk"));
	regOneTopDomain(qsl("tl"));
	regOneTopDomain(qsl("tm"));
	regOneTopDomain(qsl("tn"));
	regOneTopDomain(qsl("to"));
	regOneTopDomain(qsl("tp"));
	regOneTopDomain(qsl("tr"));
	regOneTopDomain(qsl("tt"));
	regOneTopDomain(qsl("tv"));
	regOneTopDomain(qsl("tw"));
	regOneTopDomain(qsl("tz"));
	regOneTopDomain(qsl("ua"));
	regOneTopDomain(qsl("ug"));
	regOneTopDomain(qsl("uk"));
	regOneTopDomain(qsl("um"));
	regOneTopDomain(qsl("us"));
	regOneTopDomain(qsl("uy"));
	regOneTopDomain(qsl("uz"));
	regOneTopDomain(qsl("va"));
	regOneTopDomain(qsl("vc"));
	regOneTopDomain(qsl("ve"));
	regOneTopDomain(qsl("vg"));
	regOneTopDomain(qsl("vi"));
	regOneTopDomain(qsl("vn"));
	regOneTopDomain(qsl("vu"));
	regOneTopDomain(qsl("wf"));
	regOneTopDomain(qsl("ws"));
	regOneTopDomain(qsl("ye"));
	regOneTopDomain(qsl("yt"));
	regOneTopDomain(qsl("yu"));
	regOneTopDomain(qsl("za"));
	regOneTopDomain(qsl("zm"));
	regOneTopDomain(qsl("zw"));
	regOneTopDomain(qsl("arpa"));
	regOneTopDomain(qsl("aero"));
	regOneTopDomain(qsl("asia"));
	regOneTopDomain(qsl("biz"));
	regOneTopDomain(qsl("cat"));
	regOneTopDomain(qsl("com"));
	regOneTopDomain(qsl("coop"));
	regOneTopDomain(qsl("info"));
	regOneTopDomain(qsl("int"));
	regOneTopDomain(qsl("jobs"));
	regOneTopDomain(qsl("mobi"));
	regOneTopDomain(qsl("museum"));
	regOneTopDomain(qsl("name"));
	regOneTopDomain(qsl("net"));
	regOneTopDomain(qsl("org"));
	regOneTopDomain(qsl("post"));
	regOneTopDomain(qsl("pro"));
	regOneTopDomain(qsl("tel"));
	regOneTopDomain(qsl("travel"));
	regOneTopDomain(qsl("xxx"));
	regOneTopDomain(qsl("edu"));
	regOneTopDomain(qsl("gov"));
	regOneTopDomain(qsl("mil"));
	regOneTopDomain(qsl("local"));
	regOneTopDomain(qsl("xn--lgbbat1ad8j"));
	regOneTopDomain(qsl("xn--54b7fta0cc"));
	regOneTopDomain(qsl("xn--fiqs8s"));
	regOneTopDomain(qsl("xn--fiqz9s"));
	regOneTopDomain(qsl("xn--wgbh1c"));
	regOneTopDomain(qsl("xn--node"));
	regOneTopDomain(qsl("xn--j6w193g"));
	regOneTopDomain(qsl("xn--h2brj9c"));
	regOneTopDomain(qsl("xn--mgbbh1a71e"));
	regOneTopDomain(qsl("xn--fpcrj9c3d"));
	regOneTopDomain(qsl("xn--gecrj9c"));
	regOneTopDomain(qsl("xn--s9brj9c"));
	regOneTopDomain(qsl("xn--xkc2dl3a5ee0h"));
	regOneTopDomain(qsl("xn--45brj9c"));
	regOneTopDomain(qsl("xn--mgba3a4f16a"));
	regOneTopDomain(qsl("xn--mgbayh7gpa"));
	regOneTopDomain(qsl("xn--80ao21a"));
	regOneTopDomain(qsl("xn--mgbx4cd0ab"));
	regOneTopDomain(qsl("xn--l1acc"));
	regOneTopDomain(qsl("xn--mgbc0a9azcg"));
	regOneTopDomain(qsl("xn--mgb9awbf"));
	regOneTopDomain(qsl("xn--mgbai9azgqp6j"));
	regOneTopDomain(qsl("xn--ygbi2ammx"));
	regOneTopDomain(qsl("xn--wgbl6a"));
	regOneTopDomain(qsl("xn--p1ai"));
	regOneTopDomain(qsl("xn--mgberp4a5d4ar"));
	regOneTopDomain(qsl("xn--90a3ac"));
	regOneTopDomain(qsl("xn--yfro4i67o"));
	regOneTopDomain(qsl("xn--clchc0ea0b2g2a9gcd"));
	regOneTopDomain(qsl("xn--3e0b707e"));
	regOneTopDomain(qsl("xn--fzc2c9e2c"));
	regOneTopDomain(qsl("xn--xkc2al3hye2a"));
	regOneTopDomain(qsl("xn--mgbtf8fl"));
	regOneTopDomain(qsl("xn--kprw13d"));
	regOneTopDomain(qsl("xn--kpry57d"));
	regOneTopDomain(qsl("xn--o3cw4h"));
	regOneTopDomain(qsl("xn--pgbs0dh"));
	regOneTopDomain(qsl("xn--j1amh"));
	regOneTopDomain(qsl("xn--mgbaam7a8h"));
	regOneTopDomain(qsl("xn--mgb2ddes"));
	regOneTopDomain(qsl("xn--ogbpf8fl"));
	regOneTopDomain(QString::fromUtf8("рф"));
}

namespace {
	// accent char list taken from https://github.com/aristus/accent-folding
	inline QChar chNoAccent(int32 code) { 
		switch (code) {
		case 7834: return QChar(97);
		case 193: return QChar(97);
		case 225: return QChar(97);
		case 192: return QChar(97);
		case 224: return QChar(97);
		case 258: return QChar(97);
		case 259: return QChar(97);
		case 7854: return QChar(97);
		case 7855: return QChar(97);
		case 7856: return QChar(97);
		case 7857: return QChar(97);
		case 7860: return QChar(97);
		case 7861: return QChar(97);
		case 7858: return QChar(97);
		case 7859: return QChar(97);
		case 194: return QChar(97);
		case 226: return QChar(97);
		case 7844: return QChar(97);
		case 7845: return QChar(97);
		case 7846: return QChar(97);
		case 7847: return QChar(97);
		case 7850: return QChar(97);
		case 7851: return QChar(97);
		case 7848: return QChar(97);
		case 7849: return QChar(97);
		case 461: return QChar(97);
		case 462: return QChar(97);
		case 197: return QChar(97);
		case 229: return QChar(97);
		case 506: return QChar(97);
		case 507: return QChar(97);
		case 196: return QChar(97);
		case 228: return QChar(97);
		case 478: return QChar(97);
		case 479: return QChar(97);
		case 195: return QChar(97);
		case 227: return QChar(97);
		case 550: return QChar(97);
		case 551: return QChar(97);
		case 480: return QChar(97);
		case 481: return QChar(97);
		case 260: return QChar(97);
		case 261: return QChar(97);
		case 256: return QChar(97);
		case 257: return QChar(97);
		case 7842: return QChar(97);
		case 7843: return QChar(97);
		case 512: return QChar(97);
		case 513: return QChar(97);
		case 514: return QChar(97);
		case 515: return QChar(97);
		case 7840: return QChar(97);
		case 7841: return QChar(97);
		case 7862: return QChar(97);
		case 7863: return QChar(97);
		case 7852: return QChar(97);
		case 7853: return QChar(97);
		case 7680: return QChar(97);
		case 7681: return QChar(97);
		case 570: return QChar(97);
		case 11365: return QChar(97);
		case 508: return QChar(97);
		case 509: return QChar(97);
		case 482: return QChar(97);
		case 483: return QChar(97);
		case 7682: return QChar(98);
		case 7683: return QChar(98);
		case 7684: return QChar(98);
		case 7685: return QChar(98);
		case 7686: return QChar(98);
		case 7687: return QChar(98);
		case 579: return QChar(98);
		case 384: return QChar(98);
		case 7532: return QChar(98);
		case 385: return QChar(98);
		case 595: return QChar(98);
		case 386: return QChar(98);
		case 387: return QChar(98);
		case 262: return QChar(99);
		case 263: return QChar(99);
		case 264: return QChar(99);
		case 265: return QChar(99);
		case 268: return QChar(99);
		case 269: return QChar(99);
		case 266: return QChar(99);
		case 267: return QChar(99);
		case 199: return QChar(99);
		case 231: return QChar(99);
		case 7688: return QChar(99);
		case 7689: return QChar(99);
		case 571: return QChar(99);
		case 572: return QChar(99);
		case 391: return QChar(99);
		case 392: return QChar(99);
		case 597: return QChar(99);
		case 270: return QChar(100);
		case 271: return QChar(100);
		case 7690: return QChar(100);
		case 7691: return QChar(100);
		case 7696: return QChar(100);
		case 7697: return QChar(100);
		case 7692: return QChar(100);
		case 7693: return QChar(100);
		case 7698: return QChar(100);
		case 7699: return QChar(100);
		case 7694: return QChar(100);
		case 7695: return QChar(100);
		case 272: return QChar(100);
		case 273: return QChar(100);
		case 7533: return QChar(100);
		case 393: return QChar(100);
		case 598: return QChar(100);
		case 394: return QChar(100);
		case 599: return QChar(100);
		case 395: return QChar(100);
		case 396: return QChar(100);
		case 545: return QChar(100);
		case 240: return QChar(100);
		case 201: return QChar(101);
		case 399: return QChar(101);
		case 398: return QChar(101);
		case 477: return QChar(101);
		case 233: return QChar(101);
		case 200: return QChar(101);
		case 232: return QChar(101);
		case 276: return QChar(101);
		case 277: return QChar(101);
		case 202: return QChar(101);
		case 234: return QChar(101);
		case 7870: return QChar(101);
		case 7871: return QChar(101);
		case 7872: return QChar(101);
		case 7873: return QChar(101);
		case 7876: return QChar(101);
		case 7877: return QChar(101);
		case 7874: return QChar(101);
		case 7875: return QChar(101);
		case 282: return QChar(101);
		case 283: return QChar(101);
		case 203: return QChar(101);
		case 235: return QChar(101);
		case 7868: return QChar(101);
		case 7869: return QChar(101);
		case 278: return QChar(101);
		case 279: return QChar(101);
		case 552: return QChar(101);
		case 553: return QChar(101);
		case 7708: return QChar(101);
		case 7709: return QChar(101);
		case 280: return QChar(101);
		case 281: return QChar(101);
		case 274: return QChar(101);
		case 275: return QChar(101);
		case 7702: return QChar(101);
		case 7703: return QChar(101);
		case 7700: return QChar(101);
		case 7701: return QChar(101);
		case 7866: return QChar(101);
		case 7867: return QChar(101);
		case 516: return QChar(101);
		case 517: return QChar(101);
		case 518: return QChar(101);
		case 519: return QChar(101);
		case 7864: return QChar(101);
		case 7865: return QChar(101);
		case 7878: return QChar(101);
		case 7879: return QChar(101);
		case 7704: return QChar(101);
		case 7705: return QChar(101);
		case 7706: return QChar(101);
		case 7707: return QChar(101);
		case 582: return QChar(101);
		case 583: return QChar(101);
		case 602: return QChar(101);
		case 605: return QChar(101);
		case 7710: return QChar(102);
		case 7711: return QChar(102);
		case 7534: return QChar(102);
		case 401: return QChar(102);
		case 402: return QChar(102);
		case 500: return QChar(103);
		case 501: return QChar(103);
		case 286: return QChar(103);
		case 287: return QChar(103);
		case 284: return QChar(103);
		case 285: return QChar(103);
		case 486: return QChar(103);
		case 487: return QChar(103);
		case 288: return QChar(103);
		case 289: return QChar(103);
		case 290: return QChar(103);
		case 291: return QChar(103);
		case 7712: return QChar(103);
		case 7713: return QChar(103);
		case 484: return QChar(103);
		case 485: return QChar(103);
		case 403: return QChar(103);
		case 608: return QChar(103);
		case 292: return QChar(104);
		case 293: return QChar(104);
		case 542: return QChar(104);
		case 543: return QChar(104);
		case 7718: return QChar(104);
		case 7719: return QChar(104);
		case 7714: return QChar(104);
		case 7715: return QChar(104);
		case 7720: return QChar(104);
		case 7721: return QChar(104);
		case 7716: return QChar(104);
		case 7717: return QChar(104);
		case 7722: return QChar(104);
		case 7723: return QChar(104);
		case 817: return QChar(104);
		case 7830: return QChar(104);
		case 294: return QChar(104);
		case 295: return QChar(104);
		case 11367: return QChar(104);
		case 11368: return QChar(104);
		case 205: return QChar(105);
		case 237: return QChar(105);
		case 204: return QChar(105);
		case 236: return QChar(105);
		case 300: return QChar(105);
		case 301: return QChar(105);
		case 206: return QChar(105);
		case 238: return QChar(105);
		case 463: return QChar(105);
		case 464: return QChar(105);
		case 207: return QChar(105);
		case 239: return QChar(105);
		case 7726: return QChar(105);
		case 7727: return QChar(105);
		case 296: return QChar(105);
		case 297: return QChar(105);
		case 304: return QChar(105);
		case 302: return QChar(105);
		case 303: return QChar(105);
		case 298: return QChar(105);
		case 299: return QChar(105);
		case 7880: return QChar(105);
		case 7881: return QChar(105);
		case 520: return QChar(105);
		case 521: return QChar(105);
		case 522: return QChar(105);
		case 523: return QChar(105);
		case 7882: return QChar(105);
		case 7883: return QChar(105);
		case 7724: return QChar(105);
		case 7725: return QChar(105);
		case 305: return QChar(105);
		case 407: return QChar(105);
		case 616: return QChar(105);
		case 308: return QChar(106);
		case 309: return QChar(106);
		case 780: return QChar(106);
		case 496: return QChar(106);
		case 567: return QChar(106);
		case 584: return QChar(106);
		case 585: return QChar(106);
		case 669: return QChar(106);
		case 607: return QChar(106);
		case 644: return QChar(106);
		case 7728: return QChar(107);
		case 7729: return QChar(107);
		case 488: return QChar(107);
		case 489: return QChar(107);
		case 310: return QChar(107);
		case 311: return QChar(107);
		case 7730: return QChar(107);
		case 7731: return QChar(107);
		case 7732: return QChar(107);
		case 7733: return QChar(107);
		case 408: return QChar(107);
		case 409: return QChar(107);
		case 11369: return QChar(107);
		case 11370: return QChar(107);
		case 313: return QChar(97);
		case 314: return QChar(108);
		case 317: return QChar(108);
		case 318: return QChar(108);
		case 315: return QChar(108);
		case 316: return QChar(108);
		case 7734: return QChar(108);
		case 7735: return QChar(108);
		case 7736: return QChar(108);
		case 7737: return QChar(108);
		case 7740: return QChar(108);
		case 7741: return QChar(108);
		case 7738: return QChar(108);
		case 7739: return QChar(108);
		case 321: return QChar(108);
		case 322: return QChar(108);
		case 803: return QChar(108);
		case 319: return QChar(108);
		case 320: return QChar(108);
		case 573: return QChar(108);
		case 410: return QChar(108);
		case 11360: return QChar(108);
		case 11361: return QChar(108);
		case 11362: return QChar(108);
		case 619: return QChar(108);
		case 620: return QChar(108);
		case 621: return QChar(108);
		case 564: return QChar(108);
		case 7742: return QChar(109);
		case 7743: return QChar(109);
		case 7744: return QChar(109);
		case 7745: return QChar(109);
		case 7746: return QChar(109);
		case 7747: return QChar(109);
		case 625: return QChar(109);
		case 323: return QChar(110);
		case 324: return QChar(110);
		case 504: return QChar(110);
		case 505: return QChar(110);
		case 327: return QChar(110);
		case 328: return QChar(110);
		case 209: return QChar(110);
		case 241: return QChar(110);
		case 7748: return QChar(110);
		case 7749: return QChar(110);
		case 325: return QChar(110);
		case 326: return QChar(110);
		case 7750: return QChar(110);
		case 7751: return QChar(110);
		case 7754: return QChar(110);
		case 7755: return QChar(110);
		case 7752: return QChar(110);
		case 7753: return QChar(110);
		case 413: return QChar(110);
		case 626: return QChar(110);
		case 544: return QChar(110);
		case 414: return QChar(110);
		case 627: return QChar(110);
		case 565: return QChar(110);
		case 776: return QChar(116);
		case 211: return QChar(111);
		case 243: return QChar(111);
		case 210: return QChar(111);
		case 242: return QChar(111);
		case 334: return QChar(111);
		case 335: return QChar(111);
		case 212: return QChar(111);
		case 244: return QChar(111);
		case 7888: return QChar(111);
		case 7889: return QChar(111);
		case 7890: return QChar(111);
		case 7891: return QChar(111);
		case 7894: return QChar(111);
		case 7895: return QChar(111);
		case 7892: return QChar(111);
		case 7893: return QChar(111);
		case 465: return QChar(111);
		case 466: return QChar(111);
		case 214: return QChar(111);
		case 246: return QChar(111);
		case 554: return QChar(111);
		case 555: return QChar(111);
		case 336: return QChar(111);
		case 337: return QChar(111);
		case 213: return QChar(111);
		case 245: return QChar(111);
		case 7756: return QChar(111);
		case 7757: return QChar(111);
		case 7758: return QChar(111);
		case 7759: return QChar(111);
		case 556: return QChar(111);
		case 557: return QChar(111);
		case 558: return QChar(111);
		case 559: return QChar(111);
		case 560: return QChar(111);
		case 561: return QChar(111);
		case 216: return QChar(111);
		case 248: return QChar(111);
		case 510: return QChar(111);
		case 511: return QChar(111);
		case 490: return QChar(111);
		case 491: return QChar(111);
		case 492: return QChar(111);
		case 493: return QChar(111);
		case 332: return QChar(111);
		case 333: return QChar(111);
		case 7762: return QChar(111);
		case 7763: return QChar(111);
		case 7760: return QChar(111);
		case 7761: return QChar(111);
		case 7886: return QChar(111);
		case 7887: return QChar(111);
		case 524: return QChar(111);
		case 525: return QChar(111);
		case 526: return QChar(111);
		case 527: return QChar(111);
		case 416: return QChar(111);
		case 417: return QChar(111);
		case 7898: return QChar(111);
		case 7899: return QChar(111);
		case 7900: return QChar(111);
		case 7901: return QChar(111);
		case 7904: return QChar(111);
		case 7905: return QChar(111);
		case 7902: return QChar(111);
		case 7903: return QChar(111);
		case 7906: return QChar(111);
		case 7907: return QChar(111);
		case 7884: return QChar(111);
		case 7885: return QChar(111);
		case 7896: return QChar(111);
		case 7897: return QChar(111);
		case 415: return QChar(111);
		case 629: return QChar(111);
		case 7764: return QChar(112);
		case 7765: return QChar(112);
		case 7766: return QChar(112);
		case 7767: return QChar(112);
		case 11363: return QChar(112);
		case 420: return QChar(112);
		case 421: return QChar(112);
		case 771: return QChar(112);
		case 672: return QChar(113);
		case 586: return QChar(113);
		case 587: return QChar(113);
		case 340: return QChar(114);
		case 341: return QChar(114);
		case 344: return QChar(114);
		case 345: return QChar(114);
		case 7768: return QChar(114);
		case 7769: return QChar(114);
		case 342: return QChar(114);
		case 343: return QChar(114);
		case 528: return QChar(114);
		case 529: return QChar(114);
		case 530: return QChar(114);
		case 531: return QChar(114);
		case 7770: return QChar(114);
		case 7771: return QChar(114);
		case 7772: return QChar(114);
		case 7773: return QChar(114);
		case 7774: return QChar(114);
		case 7775: return QChar(114);
		case 588: return QChar(114);
		case 589: return QChar(114);
		case 7538: return QChar(114);
		case 636: return QChar(114);
		case 11364: return QChar(114);
		case 637: return QChar(114);
		case 638: return QChar(114);
		case 7539: return QChar(114);
		case 223: return QChar(115);
		case 346: return QChar(115);
		case 347: return QChar(115);
		case 7780: return QChar(115);
		case 7781: return QChar(115);
		case 348: return QChar(115);
		case 349: return QChar(115);
		case 352: return QChar(115);
		case 353: return QChar(115);
		case 7782: return QChar(115);
		case 7783: return QChar(115);
		case 7776: return QChar(115);
		case 7777: return QChar(115);
		case 7835: return QChar(115);
		case 350: return QChar(115);
		case 351: return QChar(115);
		case 7778: return QChar(115);
		case 7779: return QChar(115);
		case 7784: return QChar(115);
		case 7785: return QChar(115);
		case 536: return QChar(115);
		case 537: return QChar(115);
		case 642: return QChar(115);
		case 809: return QChar(115);
		case 222: return QChar(116);
		case 254: return QChar(116);
		case 356: return QChar(116);
		case 357: return QChar(116);
		case 7831: return QChar(116);
		case 7786: return QChar(116);
		case 7787: return QChar(116);
		case 354: return QChar(116);
		case 355: return QChar(116);
		case 7788: return QChar(116);
		case 7789: return QChar(116);
		case 538: return QChar(116);
		case 539: return QChar(116);
		case 7792: return QChar(116);
		case 7793: return QChar(116);
		case 7790: return QChar(116);
		case 7791: return QChar(116);
		case 358: return QChar(116);
		case 359: return QChar(116);
		case 574: return QChar(116);
		case 11366: return QChar(116);
		case 7541: return QChar(116);
		case 427: return QChar(116);
		case 428: return QChar(116);
		case 429: return QChar(116);
		case 430: return QChar(116);
		case 648: return QChar(116);
		case 566: return QChar(116);
		case 218: return QChar(117);
		case 250: return QChar(117);
		case 217: return QChar(117);
		case 249: return QChar(117);
		case 364: return QChar(117);
		case 365: return QChar(117);
		case 219: return QChar(117);
		case 251: return QChar(117);
		case 467: return QChar(117);
		case 468: return QChar(117);
		case 366: return QChar(117);
		case 367: return QChar(117);
		case 220: return QChar(117);
		case 252: return QChar(117);
		case 471: return QChar(117);
		case 472: return QChar(117);
		case 475: return QChar(117);
		case 476: return QChar(117);
		case 473: return QChar(117);
		case 474: return QChar(117);
		case 469: return QChar(117);
		case 470: return QChar(117);
		case 368: return QChar(117);
		case 369: return QChar(117);
		case 360: return QChar(117);
		case 361: return QChar(117);
		case 7800: return QChar(117);
		case 7801: return QChar(117);
		case 370: return QChar(117);
		case 371: return QChar(117);
		case 362: return QChar(117);
		case 363: return QChar(117);
		case 7802: return QChar(117);
		case 7803: return QChar(117);
		case 7910: return QChar(117);
		case 7911: return QChar(117);
		case 532: return QChar(117);
		case 533: return QChar(117);
		case 534: return QChar(117);
		case 535: return QChar(117);
		case 431: return QChar(117);
		case 432: return QChar(117);
		case 7912: return QChar(117);
		case 7913: return QChar(117);
		case 7914: return QChar(117);
		case 7915: return QChar(117);
		case 7918: return QChar(117);
		case 7919: return QChar(117);
		case 7916: return QChar(117);
		case 7917: return QChar(117);
		case 7920: return QChar(117);
		case 7921: return QChar(117);
		case 7908: return QChar(117);
		case 7909: return QChar(117);
		case 7794: return QChar(117);
		case 7795: return QChar(117);
		case 7798: return QChar(117);
		case 7799: return QChar(117);
		case 7796: return QChar(117);
		case 7797: return QChar(117);
		case 580: return QChar(117);
		case 649: return QChar(117);
		case 7804: return QChar(118);
		case 7805: return QChar(118);
		case 7806: return QChar(118);
		case 7807: return QChar(118);
		case 434: return QChar(118);
		case 651: return QChar(118);
		case 7810: return QChar(119);
		case 7811: return QChar(119);
		case 7808: return QChar(119);
		case 7809: return QChar(119);
		case 372: return QChar(119);
		case 373: return QChar(119);
		case 778: return QChar(121);
		case 7832: return QChar(119);
		case 7812: return QChar(119);
		case 7813: return QChar(119);
		case 7814: return QChar(119);
		case 7815: return QChar(119);
		case 7816: return QChar(119);
		case 7817: return QChar(119);
		case 7820: return QChar(120);
		case 7821: return QChar(120);
		case 7818: return QChar(120);
		case 7819: return QChar(120);
		case 221: return QChar(121);
		case 253: return QChar(121);
		case 7922: return QChar(121);
		case 7923: return QChar(121);
		case 374: return QChar(121);
		case 375: return QChar(121);
		case 7833: return QChar(121);
		case 376: return QChar(121);
		case 255: return QChar(121);
		case 7928: return QChar(121);
		case 7929: return QChar(121);
		case 7822: return QChar(121);
		case 7823: return QChar(121);
		case 562: return QChar(121);
		case 563: return QChar(121);
		case 7926: return QChar(121);
		case 7927: return QChar(121);
		case 7924: return QChar(121);
		case 7925: return QChar(121);
		case 655: return QChar(121);
		case 590: return QChar(121);
		case 591: return QChar(121);
		case 435: return QChar(121);
		case 436: return QChar(121);
		case 377: return QChar(122);
		case 378: return QChar(122);
		case 7824: return QChar(122);
		case 7825: return QChar(122);
		case 381: return QChar(122);
		case 382: return QChar(122);
		case 379: return QChar(122);
		case 380: return QChar(122);
		case 7826: return QChar(122);
		case 7827: return QChar(122);
		case 7828: return QChar(122);
		case 7829: return QChar(122);
		case 437: return QChar(122);
		case 438: return QChar(122);
		case 548: return QChar(122);
		case 549: return QChar(122);
		case 656: return QChar(122);
		case 657: return QChar(122);
		case 11371: return QChar(122);
		case 11372: return QChar(122);
		case 494: return QChar(122);
		case 495: return QChar(122);
		case 442: return QChar(122);
		case 65298: return QChar(50);
		case 65302: return QChar(54);
		case 65314: return QChar(66);
		case 65318: return QChar(70);
		case 65322: return QChar(74);
		case 65326: return QChar(78);
		case 65330: return QChar(82);
		case 65334: return QChar(86);
		case 65338: return QChar(90);
		case 65346: return QChar(98);
		case 65350: return QChar(102);
		case 65354: return QChar(106);
		case 65358: return QChar(110);
		case 65362: return QChar(114);
		case 65366: return QChar(118);
		case 65370: return QChar(122);
		case 65297: return QChar(49);
		case 65301: return QChar(53);
		case 65305: return QChar(57);
		case 65313: return QChar(65);
		case 65317: return QChar(69);
		case 65321: return QChar(73);
		case 65325: return QChar(77);
		case 65329: return QChar(81);
		case 65333: return QChar(85);
		case 65337: return QChar(89);
		case 65345: return QChar(97);
		case 65349: return QChar(101);
		case 65353: return QChar(105);
		case 65357: return QChar(109);
		case 65361: return QChar(113);
		case 65365: return QChar(117);
		case 65369: return QChar(121);
		case 65296: return QChar(48);
		case 65300: return QChar(52);
		case 65304: return QChar(56);
		case 65316: return QChar(68);
		case 65320: return QChar(72);
		case 65324: return QChar(76);
		case 65328: return QChar(80);
		case 65332: return QChar(84);
		case 65336: return QChar(88);
		case 65348: return QChar(100);
		case 65352: return QChar(104);
		case 65356: return QChar(108);
		case 65360: return QChar(112);
		case 65364: return QChar(116);
		case 65368: return QChar(120);
		case 65299: return QChar(51);
		case 65303: return QChar(55);
		case 65315: return QChar(67);
		case 65319: return QChar(71);
		case 65323: return QChar(75);
		case 65327: return QChar(79);
		case 65331: return QChar(83);
		case 65335: return QChar(87);
		case 65347: return QChar(99);
		case 65351: return QChar(103);
		case 65355: return QChar(107);
		case 65359: return QChar(111);
		case 65363: return QChar(115);
		case 65367: return QChar(119);
		default:
			break;
		}
		return QChar(0);
	}
}

QString textAccentFold(const QString &text) {
	QString result(text);
	bool copying = false;
	int32 i = 0;
	for (const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); ch != e; ++ch, ++i) {
		if (ch->unicode() < 128) {
			if (copying) result[i] = *ch;
			continue;
		}
		if (chIsDiac(*ch)) {
			copying = true;
			--i;
			continue;
		}
		if (ch->isHighSurrogate() && ch + 1 < e && (ch + 1)->isLowSurrogate()) {
			QChar noAccent = chNoAccent(QChar::surrogateToUcs4(*ch, *(ch + 1)));
			if (noAccent.unicode() > 0) {
				copying = true;
				result[i] = noAccent;
			} else {
				if (copying) result[i] = *ch;
				++ch, ++i; 
				if (copying) result[i] = *ch;
			}
		} else {
			QChar noAccent = chNoAccent(ch->unicode());
			if (noAccent.unicode() > 0 && noAccent != *ch) {
				result[i] = noAccent;
			} else if (copying) {
				result[i] = *ch;
			}
		}
	}
	return (i < result.size()) ? result.mid(0, i) : result;
}

QString textSearchKey(const QString &text) {
	return textAccentFold(text.trimmed().toLower());
}

bool textSplit(QString &sendingText, QString &leftText, int32 limit) {
	if (leftText.isEmpty() || !limit) return false;

	LinksInText links = textParseLinks(leftText, TextParseLinks | TextParseMentions | TextParseHashtags);
	int32 currentLink = 0, lnkCount = links.size();

	int32 s = 0, half = limit / 2, goodLevel = 0;
	for (const QChar *start = leftText.constData(), *ch = start, *end = leftText.constEnd(), *good = ch; ch != end; ++ch, ++s) {
		while (currentLink < lnkCount && ch >= start + links[currentLink].offset + links[currentLink].length) {
			++currentLink;
		}

		bool inLink = (currentLink < lnkCount) && (ch > start + links[currentLink].offset) && (ch < start + links[currentLink].offset + links[currentLink].length);
		if (s > half) {
			if (inLink) {
				if (!goodLevel) good = ch;
			} else {
				if (chIsNewline(*ch)) {
					if (ch + 1 < end && chIsNewline(*(ch + 1)) && goodLevel <= 7) {
						goodLevel = 7;
						good = ch;
					} else if (goodLevel <= 6) {
						goodLevel = 6;
						good = ch;
					}
				} else if (chIsSpace(*ch)) {
					if (chIsSentenceEnd(*(ch - 1)) && goodLevel <= 5) {
						goodLevel = 5;
						good = ch;
					} else if (chIsSentencePartEnd(*(ch - 1)) && goodLevel <= 4) {
						goodLevel = 4;
						good = ch;
					} else if (goodLevel <= 3) {
						goodLevel = 3;
						good = ch;
					}
				} else if (chIsWordSeparator(*(ch - 1)) && goodLevel <= 2) {
					goodLevel = 2;
					good = ch;
				} else if (goodLevel <= 1) {
					goodLevel = 1;
					good = ch;
				}
			}
		}
		int elen = 0;
		EmojiPtr e = emojiFromText(ch, end, elen);
		if (e) {
			for (int i = 0; i < elen; ++i, ++ch, ++s) {
				if (ch->isHighSurrogate() && i + 1 < elen && (ch + 1)->isLowSurrogate()) {
					++ch;
					++i;
				}
			}
			--ch;
			--s;
		} else if (ch->isHighSurrogate() && ch + 1 < end && (ch + 1)->isLowSurrogate()) {
			++ch;
		}
		if (s >= limit) {
			sendingText = leftText.mid(0, good - start);
			leftText = leftText.mid(good - start);
			return true;
		}
	}
	sendingText = leftText;
	leftText = QString();
	return true;
}

LinksInText textParseLinks(const QString &text, int32 flags, bool rich) { // some code is duplicated in flattextarea.cpp!
	LinksInText result;

	bool withHashtags = (flags & TextParseHashtags);
	bool withMentions = (flags & TextParseMentions);
	bool withBotCommands = (flags & TextParseBotCommands);

	initLinkSets();
	int32 len = text.size(), nextCmd = rich ? 0 : len;
	const QChar *start = text.unicode(), *end = start + text.size();
	for (int32 offset = 0, matchOffset = offset, mentionSkip = 0; offset < len;) {
		if (nextCmd <= offset) {
			for (nextCmd = offset; nextCmd < len; ++nextCmd) {
				if (*(start + nextCmd) == TextCommand) {
					break;
				}
			}
		}
		QRegularExpressionMatch mDomain = _reDomain.match(text, matchOffset);
		QRegularExpressionMatch mExplicitDomain = _reExplicitDomain.match(text, matchOffset);
		QRegularExpressionMatch mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch();
		QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
		QRegularExpressionMatch mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch();

		LinkInTextType lnkType = LinkInTextUrl;
		int32 lnkOffset = 0, lnkLength = 0;
		int32 domainOffset = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX,
			domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : INT_MAX,
			explicitDomainOffset = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedStart() : INT_MAX,
			explicitDomainEnd = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedEnd() : INT_MAX,
			hashtagOffset = mHashtag.hasMatch() ? mHashtag.capturedStart() : INT_MAX,
			hashtagEnd = mHashtag.hasMatch() ? mHashtag.capturedEnd() : INT_MAX,
			mentionOffset = mMention.hasMatch() ? mMention.capturedStart() : INT_MAX,
			mentionEnd = mMention.hasMatch() ? mMention.capturedEnd() : INT_MAX,
			botCommandOffset = mBotCommand.hasMatch() ? mBotCommand.capturedStart() : INT_MAX,
			botCommandEnd = mBotCommand.hasMatch() ? mBotCommand.capturedEnd() : INT_MAX;
		if (mHashtag.hasMatch()) {
			if (!mHashtag.capturedRef(1).isEmpty()) {
				++hashtagOffset;
			}
			if (!mHashtag.capturedRef(2).isEmpty()) {
				--hashtagEnd;
			}
		}
		while (mMention.hasMatch()) {
			if (!mMention.capturedRef(1).isEmpty()) {
				++mentionOffset;
			}
			if (!mMention.capturedRef(2).isEmpty()) {
				--mentionEnd;
			}
			if (!(start + mentionOffset + 1)->isLetter() || !(start + mentionEnd - 1)->isLetterOrNumber()) {
				mentionSkip = mentionEnd;
				mMention = _reMention.match(text, qMax(mentionSkip, matchOffset));
				if (mMention.hasMatch()) {
					mentionOffset = mMention.capturedStart();
					mentionEnd = mMention.capturedEnd();
				} else {
					mentionOffset = INT_MAX;
					mentionEnd = INT_MAX;
				}
			} else {
				break;
			}
		}
		if (mBotCommand.hasMatch()) {
			if (!mBotCommand.capturedRef(1).isEmpty()) {
				++botCommandOffset;
			}
			if (!mBotCommand.capturedRef(3).isEmpty()) {
				--botCommandEnd;
			}
		}
		if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch() && !mMention.hasMatch() && !mBotCommand.hasMatch()) {
			break;
		}

		if (explicitDomainOffset < domainOffset) {
			domainOffset = explicitDomainOffset;
			domainEnd = explicitDomainEnd;
			mDomain = mExplicitDomain;
		}
		if (mentionOffset < hashtagOffset && mentionOffset < domainOffset && mentionOffset < botCommandOffset) {
			if (mentionOffset > nextCmd) {
				const QChar *after = textSkipCommand(start + nextCmd, start + len);
				if (after > start + nextCmd && mentionOffset < (after - start)) {
					nextCmd = offset = matchOffset = after - start;
					continue;
				}
			}
			lnkType = LinkInTextMention;
			lnkOffset = mentionOffset;
			lnkLength = mentionEnd - mentionOffset;
		} else if (hashtagOffset < domainOffset && hashtagOffset < botCommandOffset) {
			if (hashtagOffset > nextCmd) {
				const QChar *after = textSkipCommand(start + nextCmd, start + len);
				if (after > start + nextCmd && hashtagOffset < (after - start)) {
					nextCmd = offset = matchOffset = after - start;
					continue;
				}
			}

			lnkType = LinkInTextHashtag;
			lnkOffset = hashtagOffset;
			lnkLength = hashtagEnd - hashtagOffset;
		} else if (botCommandOffset < domainOffset) {
			if (botCommandOffset > nextCmd) {
				const QChar *after = textSkipCommand(start + nextCmd, start + len);
				if (after > start + nextCmd && botCommandOffset < (after - start)) {
					nextCmd = offset = matchOffset = after - start;
					continue;
				}
			}

			lnkType = LinkInTextBotCommand;
			lnkOffset = botCommandOffset;
			lnkLength = botCommandEnd - botCommandOffset;
		} else {
			if (domainOffset > nextCmd) {
				const QChar *after = textSkipCommand(start + nextCmd, start + len);
				if (after > start + nextCmd && domainOffset < (after - start)) {
					nextCmd = offset = matchOffset = after - start;
					continue;
				}
			}

			QString protocol = mDomain.captured(1).toLower();
			QString topDomain = mDomain.captured(3).toLower();

			bool isProtocolValid = protocol.isEmpty() || _validProtocols.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar)));
			bool isTopDomainValid = !protocol.isEmpty() || _validTopDomains.contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar)));

			if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) {
				QString forMailName = text.mid(offset, domainOffset - offset - 1);
				QRegularExpressionMatch mMailName = _reMailName.match(forMailName);
				if (mMailName.hasMatch()) {
					int32 mailOffset = offset + mMailName.capturedStart();
					if (mailOffset < offset) {
						mailOffset = offset;
					}
					lnkType = LinkInTextEmail;
					lnkOffset = mailOffset;
					lnkLength = domainEnd - mailOffset;
				}
			}
			if (lnkType == LinkInTextUrl && !lnkLength) {
				if (!isProtocolValid || !isTopDomainValid) {
					matchOffset = domainEnd;
					continue;
				}
				lnkOffset = domainOffset;

				QStack<const QChar*> parenth;
				const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd;
				for (; p < end; ++p) {
					QChar ch(*p);
					if (chIsLinkEnd(ch)) break; // link finished
					if (chIsAlmostLinkEnd(ch)) {
						const QChar *endTest = p + 1;
						while (endTest < end && chIsAlmostLinkEnd(*endTest)) {
							++endTest;
						}
						if (endTest >= end || chIsLinkEnd(*endTest)) {
							break; // link finished at p
						}
						p = endTest;
						ch = *p;
					}
					if (ch == '(' || ch == '[' || ch == '{' || ch == '<') {
						parenth.push(p);
					} else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') {
						if (parenth.isEmpty()) break;
						const QChar *q = parenth.pop(), open(*q);
						if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) {
							p = q;
							break;
						}
					}
				}
				if (p > domainEnd) { // check, that domain ended
					if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') {
						matchOffset = domainEnd - start;
						continue;
					}
				}
				lnkLength = (p - start) - lnkOffset;
			}
		}
		result.push_back(LinkInText(lnkType, lnkOffset, lnkLength));

		offset = matchOffset = lnkOffset + lnkLength;
	}

	return result;
}

void emojiDraw(QPainter &p, EmojiPtr e, int x, int y) {
	p.drawPixmap(QPoint(x, y), App::emojis(), QRect(e->x * ESize, e->y * ESize, ESize, ESize));
}