Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
a32ee9581d
7 changed files with 813 additions and 555 deletions
|
|
@ -71,13 +71,13 @@ void UrlClickHandler::Open(QString url, QVariant context) {
|
||||||
|
|
||||||
bool UrlClickHandler::IsSuspicious(const QString &url) {
|
bool UrlClickHandler::IsSuspicious(const QString &url) {
|
||||||
static const auto Check1 = QRegularExpression(
|
static const auto Check1 = QRegularExpression(
|
||||||
"^(https?://)?([^/#\\:]+)([/#\\:]|$)",
|
"^((https?|s?ftp)://)?([^/#\\:]+)([/#\\:]|$)",
|
||||||
QRegularExpression::CaseInsensitiveOption);
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
const auto match1 = Check1.match(url);
|
const auto match1 = Check1.match(url);
|
||||||
if (!match1.hasMatch()) {
|
if (!match1.hasMatch()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto domain = match1.capturedRef(2);
|
const auto domain = match1.capturedRef(3);
|
||||||
static const auto Check2 = QRegularExpression("^(.*)\\.[a-zA-Z]+$");
|
static const auto Check2 = QRegularExpression("^(.*)\\.[a-zA-Z]+$");
|
||||||
const auto match2 = Check2.match(domain);
|
const auto match2 = Check2.match(domain);
|
||||||
if (!match2.hasMatch()) {
|
if (!match2.hasMatch()) {
|
||||||
|
|
|
||||||
288
ui/text/text.cpp
288
ui/text/text.cpp
|
|
@ -21,10 +21,14 @@ namespace Text {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kStringLinkIndexShift = uint16(0x8000);
|
constexpr auto kStringLinkIndexShift = uint16(0x8000);
|
||||||
|
constexpr auto kMaxDiacAfterSymbol = 2;
|
||||||
|
|
||||||
Qt::LayoutDirection StringDirection(const QString &str, int32 from, int32 to) {
|
Qt::LayoutDirection StringDirection(
|
||||||
const ushort *p = reinterpret_cast<const ushort*>(str.unicode()) + from;
|
const QString &str,
|
||||||
const ushort *end = p + (to - from);
|
int from,
|
||||||
|
int to) {
|
||||||
|
auto p = reinterpret_cast<const ushort*>(str.unicode()) + from;
|
||||||
|
const auto end = p + (to - from);
|
||||||
while (p < end) {
|
while (p < end) {
|
||||||
uint ucs4 = *p;
|
uint ucs4 = *p;
|
||||||
if (QChar::isHighSurrogate(ucs4) && p < end - 1) {
|
if (QChar::isHighSurrogate(ucs4) && p < end - 1) {
|
||||||
|
|
@ -98,7 +102,9 @@ TextWithEntities PrepareRichFromRich(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFixed ComputeStopAfter(const TextParseOptions &options, const style::TextStyle &st) {
|
QFixed ComputeStopAfter(
|
||||||
|
const TextParseOptions &options,
|
||||||
|
const style::TextStyle &st) {
|
||||||
return (options.maxw > 0 && options.maxh > 0)
|
return (options.maxw > 0 && options.maxh > 0)
|
||||||
? ((options.maxh / st.font->height) + 1) * options.maxw
|
? ((options.maxh / st.font->height) + 1) * options.maxw
|
||||||
: QFIXED_MAX;
|
: QFIXED_MAX;
|
||||||
|
|
@ -112,11 +118,17 @@ bool ComputeCheckTilde(const style::TextStyle &st) {
|
||||||
&& (font->f.family() == qstr("DAOpenSansRegular"));
|
&& (font->f.family() == qstr("DAOpenSansRegular"));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
bool IsParagraphSeparator(QChar ch) {
|
||||||
} // namespace Text
|
switch (ch.unicode()) {
|
||||||
} // namespace Ui
|
case QChar::LineFeed:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool chIsBad(QChar ch) {
|
bool IsBad(QChar ch) {
|
||||||
return (ch == 0)
|
return (ch == 0)
|
||||||
|| (ch >= 8232 && ch < 8237)
|
|| (ch >= 8232 && ch < 8237)
|
||||||
|| (ch >= 65024 && ch < 65040 && ch != 65039)
|
|| (ch >= 65024 && ch < 65040 && ch != 65039)
|
||||||
|
|
@ -130,9 +142,13 @@ bool chIsBad(QChar ch) {
|
||||||
&& !Platform::IsMac10_12OrGreater()
|
&& !Platform::IsMac10_12OrGreater()
|
||||||
&& ch >= 0x0B00
|
&& ch >= 0x0B00
|
||||||
&& ch <= 0x0B7F
|
&& ch <= 0x0B7F
|
||||||
&& chIsDiac(ch));
|
&& IsDiac(ch));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace Text
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
QString textcmdSkipBlock(ushort w, ushort h) {
|
QString textcmdSkipBlock(ushort w, ushort h) {
|
||||||
static QString cmd(5, TextCommand);
|
static QString cmd(5, TextCommand);
|
||||||
cmd[1] = QChar(TextCommandSkipBlock);
|
cmd[1] = QChar(TextCommandSkipBlock);
|
||||||
|
|
@ -461,13 +477,13 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
}
|
}
|
||||||
_lastSkipped = false;
|
_lastSkipped = false;
|
||||||
if (_emoji) {
|
if (_emoji) {
|
||||||
_t->_blocks.push_back(std::make_unique<EmojiBlock>(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _emoji));
|
_t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _emoji));
|
||||||
_emoji = nullptr;
|
_emoji = nullptr;
|
||||||
_lastSkipped = true;
|
_lastSkipped = true;
|
||||||
} else if (newline) {
|
} else if (newline) {
|
||||||
_t->_blocks.push_back(std::make_unique<NewlineBlock>(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex));
|
_t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex));
|
||||||
} else {
|
} else {
|
||||||
_t->_blocks.push_back(std::make_unique<TextBlock>(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex));
|
_t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex));
|
||||||
}
|
}
|
||||||
_blockStart += len;
|
_blockStart += len;
|
||||||
blockCreated();
|
blockCreated();
|
||||||
|
|
@ -477,7 +493,7 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
void Parser::createSkipBlock(int32 w, int32 h) {
|
void Parser::createSkipBlock(int32 w, int32 h) {
|
||||||
createBlock();
|
createBlock();
|
||||||
_t->_text.push_back('_');
|
_t->_text.push_back('_');
|
||||||
_t->_blocks.push_back(std::make_unique<SkipBlock>(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex));
|
_t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex));
|
||||||
blockCreated();
|
blockCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -745,18 +761,18 @@ bool Parser::readCommand() {
|
||||||
void Parser::parseCurrentChar() {
|
void Parser::parseCurrentChar() {
|
||||||
_ch = ((_ptr < _end) ? *_ptr : 0);
|
_ch = ((_ptr < _end) ? *_ptr : 0);
|
||||||
_emojiLookback = 0;
|
_emojiLookback = 0;
|
||||||
const auto isNewLine = _multiline && chIsNewline(_ch);
|
const auto isNewLine = _multiline && IsNewline(_ch);
|
||||||
const auto isSpace = chIsSpace(_ch);
|
const auto isSpace = IsSpace(_ch);
|
||||||
const auto isDiac = chIsDiac(_ch);
|
const auto isDiac = IsDiac(_ch);
|
||||||
const auto isTilde = _checkTilde && (_ch == '~');
|
const auto isTilde = _checkTilde && (_ch == '~');
|
||||||
const auto skip = [&] {
|
const auto skip = [&] {
|
||||||
if (chIsBad(_ch) || _ch.isLowSurrogate()) {
|
if (IsBad(_ch) || _ch.isLowSurrogate()) {
|
||||||
return true;
|
return true;
|
||||||
} else if (_ch == 0xFE0F && Platform::IsMac()) {
|
} else if (_ch == 0xFE0F && Platform::IsMac()) {
|
||||||
// Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :(
|
// Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :(
|
||||||
return true;
|
return true;
|
||||||
} else if (isDiac) {
|
} else if (isDiac) {
|
||||||
if (_lastSkipped || _emoji || ++_diacs > chMaxDiacAfterSymbol()) {
|
if (_lastSkipped || _emoji || ++_diacs > kMaxDiacAfterSymbol) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (_ch.isHighSurrogate()) {
|
} else if (_ch.isHighSurrogate()) {
|
||||||
|
|
@ -891,10 +907,10 @@ void Parser::trimSourceRange() {
|
||||||
_source.entities,
|
_source.entities,
|
||||||
_end - _start);
|
_end - _start);
|
||||||
|
|
||||||
while (_ptr != _end && chIsTrimmed(*_ptr, _rich) && _ptr != _start + firstMonospaceOffset) {
|
while (_ptr != _end && IsTrimmed(*_ptr, _rich) && _ptr != _start + firstMonospaceOffset) {
|
||||||
++_ptr;
|
++_ptr;
|
||||||
}
|
}
|
||||||
while (_ptr != _end && chIsTrimmed(*(_end - 1), _rich)) {
|
while (_ptr != _end && IsTrimmed(*(_end - 1), _rich)) {
|
||||||
--_end;
|
--_end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -913,15 +929,14 @@ void Parser::checkForElidedSkipBlock() {
|
||||||
|
|
||||||
void Parser::finalize(const TextParseOptions &options) {
|
void Parser::finalize(const TextParseOptions &options) {
|
||||||
_t->_links.resize(_maxLnkIndex);
|
_t->_links.resize(_maxLnkIndex);
|
||||||
for (const auto &block : _t->_blocks) {
|
for (auto &block : _t->_blocks) {
|
||||||
const auto b = block.get();
|
const auto shiftedIndex = block->lnkIndex();
|
||||||
const auto shiftedIndex = b->lnkIndex();
|
|
||||||
if (shiftedIndex <= kStringLinkIndexShift) {
|
if (shiftedIndex <= kStringLinkIndexShift) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto realIndex = (shiftedIndex - kStringLinkIndexShift);
|
const auto realIndex = (shiftedIndex - kStringLinkIndexShift);
|
||||||
const auto index = _maxLnkIndex + realIndex;
|
const auto index = _maxLnkIndex + realIndex;
|
||||||
b->setLnkIndex(index);
|
block->setLnkIndex(index);
|
||||||
if (_t->_links.size() >= index) {
|
if (_t->_links.size() >= index) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -935,7 +950,7 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_t->_links.squeeze();
|
_t->_links.squeeze();
|
||||||
_t->_blocks.shrink_to_fit();
|
_t->_blocks.squeeze();
|
||||||
_t->_text.squeeze();
|
_t->_text.squeeze();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1165,7 +1180,7 @@ public:
|
||||||
_wLeft -= _elideRemoveFromEnd;
|
_wLeft -= _elideRemoveFromEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
_parDirection = static_cast<NewlineBlock*>(b)->nextDirection();
|
_parDirection = static_cast<const NewlineBlock*>(b)->nextDirection();
|
||||||
if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = style::LayoutDirection();
|
if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = style::LayoutDirection();
|
||||||
initNextParagraph(i + 1);
|
initNextParagraph(i + 1);
|
||||||
|
|
||||||
|
|
@ -1187,7 +1202,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_btype == TextBlockTText) {
|
if (_btype == TextBlockTText) {
|
||||||
auto t = static_cast<TextBlock*>(b);
|
auto t = static_cast<const TextBlock*>(b);
|
||||||
if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line
|
if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line
|
||||||
_last_rPadding += b->f_rpadding();
|
_last_rPadding += b->f_rpadding();
|
||||||
|
|
||||||
|
|
@ -1678,7 +1693,7 @@ private:
|
||||||
}
|
}
|
||||||
Emoji::Draw(
|
Emoji::Draw(
|
||||||
*_p,
|
*_p,
|
||||||
static_cast<EmojiBlock*>(currentBlock)->emoji,
|
static_cast<const EmojiBlock*>(currentBlock)->_emoji,
|
||||||
Emoji::GetSizeNormal(),
|
Emoji::GetSizeNormal(),
|
||||||
(glyphX + st::emojiPadding).toInt(),
|
(glyphX + st::emojiPadding).toInt(),
|
||||||
_y + _yDelta + emojiY);
|
_y + _yDelta + emojiY);
|
||||||
|
|
@ -1847,7 +1862,7 @@ private:
|
||||||
_p->fillRect(left, _y + _yDelta, width, _fontHeight, _textPalette->selectBg);
|
_p->fillRect(left, _y + _yDelta, width, _fontHeight, _textPalette->selectBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void elideSaveBlock(int32 blockIndex, AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) {
|
void elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) {
|
||||||
if (_elideSavedBlock) {
|
if (_elideSavedBlock) {
|
||||||
restoreAfterElided();
|
restoreAfterElided();
|
||||||
}
|
}
|
||||||
|
|
@ -1855,7 +1870,7 @@ private:
|
||||||
_elideSavedIndex = blockIndex;
|
_elideSavedIndex = blockIndex;
|
||||||
auto mutableText = const_cast<String*>(_t);
|
auto mutableText = const_cast<String*>(_t);
|
||||||
_elideSavedBlock = std::move(mutableText->_blocks[blockIndex]);
|
_elideSavedBlock = std::move(mutableText->_blocks[blockIndex]);
|
||||||
mutableText->_blocks[blockIndex] = std::make_unique<TextBlock>(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, _elideSavedBlock->flags(), _elideSavedBlock->lnkIndex());
|
mutableText->_blocks[blockIndex] = Block::Text(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, (*_elideSavedBlock)->flags(), (*_elideSavedBlock)->lnkIndex());
|
||||||
_blocksSize = blockIndex + 1;
|
_blocksSize = blockIndex + 1;
|
||||||
_endBlock = (blockIndex + 1 < _t->_blocks.size() ? _t->_blocks[blockIndex + 1].get() : nullptr);
|
_endBlock = (blockIndex + 1 < _t->_blocks.size() ? _t->_blocks[blockIndex + 1].get() : nullptr);
|
||||||
}
|
}
|
||||||
|
|
@ -1870,7 +1885,7 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, AbstractBlock *&_endBlock, int repeat = 0) {
|
void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, const AbstractBlock *&_endBlock, int repeat = 0) {
|
||||||
static const auto _Elide = QString::fromLatin1("...");
|
static const auto _Elide = QString::fromLatin1("...");
|
||||||
|
|
||||||
_f = _t->_st->font;
|
_f = _t->_st->font;
|
||||||
|
|
@ -1980,7 +1995,7 @@ private:
|
||||||
|
|
||||||
void restoreAfterElided() {
|
void restoreAfterElided() {
|
||||||
if (_elideSavedBlock) {
|
if (_elideSavedBlock) {
|
||||||
const_cast<String*>(_t)->_blocks[_elideSavedIndex] = std::move(_elideSavedBlock);
|
const_cast<String*>(_t)->_blocks[_elideSavedIndex] = std::move(*_elideSavedBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2034,7 +2049,7 @@ private:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void eSetFont(AbstractBlock *block) {
|
void eSetFont(const AbstractBlock *block) {
|
||||||
const auto flags = block->flags();
|
const auto flags = block->flags();
|
||||||
const auto usedFont = [&] {
|
const auto usedFont = [&] {
|
||||||
if (const auto index = block->lnkIndex()) {
|
if (const auto index = block->lnkIndex()) {
|
||||||
|
|
@ -2609,7 +2624,7 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void applyBlockProperties(AbstractBlock *block) {
|
void applyBlockProperties(const AbstractBlock *block) {
|
||||||
eSetFont(block);
|
eSetFont(block);
|
||||||
if (_p) {
|
if (_p) {
|
||||||
if (block->lnkIndex()) {
|
if (block->lnkIndex()) {
|
||||||
|
|
@ -2660,7 +2675,7 @@ private:
|
||||||
// elided hack support
|
// elided hack support
|
||||||
int _blocksSize = 0;
|
int _blocksSize = 0;
|
||||||
int _elideSavedIndex = 0;
|
int _elideSavedIndex = 0;
|
||||||
std::unique_ptr<AbstractBlock> _elideSavedBlock;
|
std::optional<Block> _elideSavedBlock;
|
||||||
|
|
||||||
int _lineStart = 0;
|
int _lineStart = 0;
|
||||||
int _localFrom = 0;
|
int _localFrom = 0;
|
||||||
|
|
@ -2679,7 +2694,8 @@ private:
|
||||||
String::String(int32 minResizeWidth) : _minResizeWidth(minResizeWidth) {
|
String::String(int32 minResizeWidth) : _minResizeWidth(minResizeWidth) {
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String(const style::TextStyle &st, const QString &text, const TextParseOptions &options, int32 minResizeWidth, bool richText) : _minResizeWidth(minResizeWidth) {
|
String::String(const style::TextStyle &st, const QString &text, const TextParseOptions &options, int32 minResizeWidth, bool richText)
|
||||||
|
: _minResizeWidth(minResizeWidth) {
|
||||||
if (richText) {
|
if (richText) {
|
||||||
setRichText(st, text, options);
|
setRichText(st, text, options);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2687,60 +2703,6 @@ String::String(const style::TextStyle &st, const QString &text, const TextParseO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String(const String &other)
|
|
||||||
: _minResizeWidth(other._minResizeWidth)
|
|
||||||
, _maxWidth(other._maxWidth)
|
|
||||||
, _minHeight(other._minHeight)
|
|
||||||
, _text(other._text)
|
|
||||||
, _st(other._st)
|
|
||||||
, _links(other._links)
|
|
||||||
, _startDir(other._startDir) {
|
|
||||||
_blocks.reserve(other._blocks.size());
|
|
||||||
for (auto &block : other._blocks) {
|
|
||||||
_blocks.push_back(block->clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String::String(String &&other)
|
|
||||||
: _minResizeWidth(other._minResizeWidth)
|
|
||||||
, _maxWidth(other._maxWidth)
|
|
||||||
, _minHeight(other._minHeight)
|
|
||||||
, _text(other._text)
|
|
||||||
, _st(other._st)
|
|
||||||
, _blocks(std::move(other._blocks))
|
|
||||||
, _links(other._links)
|
|
||||||
, _startDir(other._startDir) {
|
|
||||||
other.clearFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
String &String::operator=(const String &other) {
|
|
||||||
_minResizeWidth = other._minResizeWidth;
|
|
||||||
_maxWidth = other._maxWidth;
|
|
||||||
_minHeight = other._minHeight;
|
|
||||||
_text = other._text;
|
|
||||||
_st = other._st;
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
String &String::operator=(String &&other) {
|
|
||||||
_minResizeWidth = other._minResizeWidth;
|
|
||||||
_maxWidth = other._maxWidth;
|
|
||||||
_minHeight = other._minHeight;
|
|
||||||
_text = other._text;
|
|
||||||
_st = other._st;
|
|
||||||
_blocks = std::move(other._blocks);
|
|
||||||
_links = other._links;
|
|
||||||
_startDir = other._startDir;
|
|
||||||
other.clearFields();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options) {
|
void String::setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options) {
|
||||||
_st = &st;
|
_st = &st;
|
||||||
clear();
|
clear();
|
||||||
|
|
@ -2757,8 +2719,8 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
|
||||||
int32 lineHeight = 0;
|
int32 lineHeight = 0;
|
||||||
int32 lastNewlineStart = 0;
|
int32 lastNewlineStart = 0;
|
||||||
QFixed _width = 0, last_rBearing = 0, last_rPadding = 0;
|
QFixed _width = 0, last_rBearing = 0, last_rPadding = 0;
|
||||||
for (auto i = _blocks.cbegin(), e = _blocks.cend(); i != e; ++i) {
|
for (auto &block : _blocks) {
|
||||||
auto b = i->get();
|
auto b = block.get();
|
||||||
auto _btype = b->type();
|
auto _btype = b->type();
|
||||||
auto blockHeight = countBlockHeight(b, _st);
|
auto blockHeight = countBlockHeight(b, _st);
|
||||||
if (_btype == TextBlockTNewline) {
|
if (_btype == TextBlockTNewline) {
|
||||||
|
|
@ -2775,7 +2737,7 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastNewlineStart = b->from();
|
lastNewlineStart = b->from();
|
||||||
lastNewline = static_cast<NewlineBlock*>(b);
|
lastNewline = &block.unsafe<NewlineBlock>();
|
||||||
|
|
||||||
_minHeight += lineHeight;
|
_minHeight += lineHeight;
|
||||||
lineHeight = 0;
|
lineHeight = 0;
|
||||||
|
|
@ -2824,19 +2786,19 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::countMaxMonospaceWidth() const {
|
int String::countMaxMonospaceWidth() const {
|
||||||
NewlineBlock *lastNewline = 0;
|
const NewlineBlock *lastNewline = nullptr;
|
||||||
|
|
||||||
auto result = QFixed();
|
auto result = QFixed();
|
||||||
auto paragraphWidth = QFixed();
|
auto paragraphWidth = QFixed();
|
||||||
auto lastNewlineStart = 0;
|
auto lastNewlineStart = 0;
|
||||||
auto fullMonospace = true;
|
auto fullMonospace = true;
|
||||||
QFixed _width = 0, last_rBearing = 0, last_rPadding = 0;
|
QFixed _width = 0, last_rBearing = 0, last_rPadding = 0;
|
||||||
for (auto i = _blocks.cbegin(), e = _blocks.cend(); i != e; ++i) {
|
for (auto &block : _blocks) {
|
||||||
auto b = i->get();
|
auto b = block.get();
|
||||||
auto _btype = b->type();
|
auto _btype = b->type();
|
||||||
if (_btype == TextBlockTNewline) {
|
if (_btype == TextBlockTNewline) {
|
||||||
lastNewlineStart = b->from();
|
lastNewlineStart = b->from();
|
||||||
lastNewline = static_cast<NewlineBlock*>(b);
|
lastNewline = &block.unsafe<NewlineBlock>();
|
||||||
|
|
||||||
last_rBearing = b->f_rbearing();
|
last_rBearing = b->f_rbearing();
|
||||||
last_rPadding = b->f_rpadding();
|
last_rPadding = b->f_rpadding();
|
||||||
|
|
@ -2892,7 +2854,7 @@ void String::setMarkedText(const style::TextStyle &st, const TextWithEntities &t
|
||||||
// for (const QChar *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
|
// for (const QChar *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
|
||||||
// if (*ch == TextCommand) {
|
// if (*ch == TextCommand) {
|
||||||
// break;
|
// break;
|
||||||
// } else if (chIsNewline(*ch)) {
|
// } else if (IsNewline(*ch)) {
|
||||||
// newText.append("},").append(*ch).append("\t{ ");
|
// newText.append("},").append(*ch).append("\t{ ");
|
||||||
// } else {
|
// } else {
|
||||||
// if (ch->isHighSurrogate() || ch->isLowSurrogate()) {
|
// if (ch->isHighSurrogate() || ch->isLowSurrogate()) {
|
||||||
|
|
@ -2943,7 +2905,7 @@ bool String::updateSkipBlock(int width, int height) {
|
||||||
_blocks.pop_back();
|
_blocks.pop_back();
|
||||||
}
|
}
|
||||||
_text.push_back('_');
|
_text.push_back('_');
|
||||||
_blocks.push_back(std::make_unique<SkipBlock>(
|
_blocks.push_back(Block::Skip(
|
||||||
_st->font,
|
_st->font,
|
||||||
_text,
|
_text,
|
||||||
_text.size() - 1,
|
_text.size() - 1,
|
||||||
|
|
@ -3036,7 +2998,7 @@ void String::enumerateLines(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_btype == TextBlockTText) {
|
if (_btype == TextBlockTText) {
|
||||||
auto t = static_cast<TextBlock*>(b.get());
|
const auto t = &b.unsafe<TextBlock>();
|
||||||
if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line
|
if (t->_words.isEmpty()) { // no words in this block, spaces only => layout this block in the same line
|
||||||
last_rPadding += b->f_rpadding();
|
last_rPadding += b->f_rpadding();
|
||||||
|
|
||||||
|
|
@ -3157,31 +3119,31 @@ TextSelection String::adjustSelection(TextSelection selection, TextSelectType se
|
||||||
if (from < _text.size() && from <= to) {
|
if (from < _text.size() && from <= to) {
|
||||||
if (to > _text.size()) to = _text.size();
|
if (to > _text.size()) to = _text.size();
|
||||||
if (selectType == TextSelectType::Paragraphs) {
|
if (selectType == TextSelectType::Paragraphs) {
|
||||||
if (!chIsParagraphSeparator(_text.at(from))) {
|
if (!IsParagraphSeparator(_text.at(from))) {
|
||||||
while (from > 0 && !chIsParagraphSeparator(_text.at(from - 1))) {
|
while (from > 0 && !IsParagraphSeparator(_text.at(from - 1))) {
|
||||||
--from;
|
--from;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (to < _text.size()) {
|
if (to < _text.size()) {
|
||||||
if (chIsParagraphSeparator(_text.at(to))) {
|
if (IsParagraphSeparator(_text.at(to))) {
|
||||||
++to;
|
++to;
|
||||||
} else {
|
} else {
|
||||||
while (to < _text.size() && !chIsParagraphSeparator(_text.at(to))) {
|
while (to < _text.size() && !IsParagraphSeparator(_text.at(to))) {
|
||||||
++to;
|
++to;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (selectType == TextSelectType::Words) {
|
} else if (selectType == TextSelectType::Words) {
|
||||||
if (!chIsWordSeparator(_text.at(from))) {
|
if (!IsWordSeparator(_text.at(from))) {
|
||||||
while (from > 0 && !chIsWordSeparator(_text.at(from - 1))) {
|
while (from > 0 && !IsWordSeparator(_text.at(from - 1))) {
|
||||||
--from;
|
--from;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (to < _text.size()) {
|
if (to < _text.size()) {
|
||||||
if (chIsWordSeparator(_text.at(to))) {
|
if (IsWordSeparator(_text.at(to))) {
|
||||||
++to;
|
++to;
|
||||||
} else {
|
} else {
|
||||||
while (to < _text.size() && !chIsWordSeparator(_text.at(to))) {
|
while (to < _text.size() && !IsWordSeparator(_text.at(to))) {
|
||||||
++to;
|
++to;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3389,7 +3351,7 @@ IsolatedEmoji String::toIsolatedEmoji() const {
|
||||||
if (block->lnkIndex()) {
|
if (block->lnkIndex()) {
|
||||||
return IsolatedEmoji();
|
return IsolatedEmoji();
|
||||||
} else if (type == TextBlockTEmoji) {
|
} else if (type == TextBlockTEmoji) {
|
||||||
result.items[index++] = static_cast<EmojiBlock*>(block.get())->emoji;
|
result.items[index++] = block.unsafe<EmojiBlock>()._emoji;
|
||||||
} else if (type != TextBlockTSkip) {
|
} else if (type != TextBlockTSkip) {
|
||||||
return IsolatedEmoji();
|
return IsolatedEmoji();
|
||||||
}
|
}
|
||||||
|
|
@ -3409,7 +3371,113 @@ void String::clearFields() {
|
||||||
_startDir = Qt::LayoutDirectionAuto;
|
_startDir = Qt::LayoutDirectionAuto;
|
||||||
}
|
}
|
||||||
|
|
||||||
String::~String() = default;
|
bool IsWordSeparator(QChar ch) {
|
||||||
|
switch (ch.unicode()) {
|
||||||
|
case QChar::Space:
|
||||||
|
case QChar::LineFeed:
|
||||||
|
case '.':
|
||||||
|
case ',':
|
||||||
|
case '?':
|
||||||
|
case '!':
|
||||||
|
case '@':
|
||||||
|
case '#':
|
||||||
|
case '$':
|
||||||
|
case ':':
|
||||||
|
case ';':
|
||||||
|
case '-':
|
||||||
|
case '<':
|
||||||
|
case '>':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '{':
|
||||||
|
case '}':
|
||||||
|
case '=':
|
||||||
|
case '/':
|
||||||
|
case '+':
|
||||||
|
case '%':
|
||||||
|
case '&':
|
||||||
|
case '^':
|
||||||
|
case '*':
|
||||||
|
case '\'':
|
||||||
|
case '"':
|
||||||
|
case '`':
|
||||||
|
case '~':
|
||||||
|
case '|':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAlmostLinkEnd(QChar ch) {
|
||||||
|
switch (ch.unicode()) {
|
||||||
|
case '?':
|
||||||
|
case ',':
|
||||||
|
case '.':
|
||||||
|
case '"':
|
||||||
|
case ':':
|
||||||
|
case '!':
|
||||||
|
case '\'':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsLinkEnd(QChar ch) {
|
||||||
|
return (ch == TextCommand)
|
||||||
|
|| IsBad(ch)
|
||||||
|
|| IsSpace(ch)
|
||||||
|
|| IsNewline(ch)
|
||||||
|
|| ch.isLowSurrogate()
|
||||||
|
|| ch.isHighSurrogate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsNewline(QChar ch) {
|
||||||
|
return (ch == QChar::LineFeed)
|
||||||
|
|| (ch == 156);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSpace(QChar ch, bool rich) {
|
||||||
|
return ch.isSpace()
|
||||||
|
|| (ch < 32 && !(rich && ch == TextCommand))
|
||||||
|
|| (ch == QChar::ParagraphSeparator)
|
||||||
|
|| (ch == QChar::LineSeparator)
|
||||||
|
|| (ch == QChar::ObjectReplacementCharacter)
|
||||||
|
|| (ch == QChar::CarriageReturn)
|
||||||
|
|| (ch == QChar::Tabulation)
|
||||||
|
|| (ch == QChar(8203)/*Zero width space.*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDiac(QChar ch) { // diac and variation selectors
|
||||||
|
return (ch.category() == QChar::Mark_NonSpacing)
|
||||||
|
|| (ch == 1652)
|
||||||
|
|| (ch >= 64606 && ch <= 64611);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsReplacedBySpace(QChar ch) {
|
||||||
|
// \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237
|
||||||
|
// QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad");
|
||||||
|
// \xcc[\xb3\xbf\x8a] // 819, 831, 778
|
||||||
|
// QString bad1 = QString::fromUtf8("\xcc\xb3"), bad2 = QString::fromUtf8("\xcc\xbf"), bad3 = QString::fromUtf8("\xcc\x8a");
|
||||||
|
// [\x00\x01\x02\x07\x08\x0b-\x1f] // '\t' = 0x09
|
||||||
|
return (/*code >= 0x00 && */ch <= 0x02)
|
||||||
|
|| (ch >= 0x07 && ch <= 0x09)
|
||||||
|
|| (ch >= 0x0b && ch <= 0x1f)
|
||||||
|
|| (ch == 819)
|
||||||
|
|| (ch == 831)
|
||||||
|
|| (ch == 778)
|
||||||
|
|| (ch >= 8232 && ch <= 8237);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsTrimmed(QChar ch, bool rich) {
|
||||||
|
return (!rich || ch != TextCommand)
|
||||||
|
&& (IsSpace(ch) || IsBad(ch));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Text
|
} // namespace Text
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
175
ui/text/text.h
175
ui/text/text.h
|
|
@ -7,6 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/text/text_entity.h"
|
#include "ui/text/text_entity.h"
|
||||||
|
#include "ui/text/text_block.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/click_handler.h"
|
#include "ui/click_handler.h"
|
||||||
#include "base/flags.h"
|
#include "base/flags.h"
|
||||||
|
|
@ -47,19 +48,20 @@ enum class TextSelectType {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextSelection {
|
struct TextSelection {
|
||||||
constexpr TextSelection() : from(0), to(0) {
|
constexpr TextSelection() = default;
|
||||||
}
|
|
||||||
constexpr TextSelection(uint16 from, uint16 to) : from(from), to(to) {
|
constexpr TextSelection(uint16 from, uint16 to) : from(from), to(to) {
|
||||||
}
|
}
|
||||||
constexpr bool empty() const {
|
constexpr bool empty() const {
|
||||||
return from == to;
|
return from == to;
|
||||||
}
|
}
|
||||||
uint16 from;
|
uint16 from = 0;
|
||||||
uint16 to;
|
uint16 to = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool operator==(TextSelection a, TextSelection b) {
|
inline bool operator==(TextSelection a, TextSelection b) {
|
||||||
return a.from == b.from && a.to == b.to;
|
return a.from == b.from && a.to == b.to;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool operator!=(TextSelection a, TextSelection b) {
|
inline bool operator!=(TextSelection a, TextSelection b) {
|
||||||
return !(a == b);
|
return !(a == b);
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +71,6 @@ static constexpr TextSelection AllTextSelection = { 0, 0xFFFF };
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Text {
|
namespace Text {
|
||||||
|
|
||||||
class AbstractBlock;
|
|
||||||
struct IsolatedEmoji;
|
struct IsolatedEmoji;
|
||||||
|
|
||||||
struct StateRequest {
|
struct StateRequest {
|
||||||
|
|
@ -108,11 +109,17 @@ struct StateRequestElided : public StateRequest {
|
||||||
class String {
|
class String {
|
||||||
public:
|
public:
|
||||||
String(int32 minResizeWidth = QFIXED_MAX);
|
String(int32 minResizeWidth = QFIXED_MAX);
|
||||||
String(const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions, int32 minResizeWidth = QFIXED_MAX, bool richText = false);
|
String(
|
||||||
String(const String &other);
|
const style::TextStyle &st,
|
||||||
String(String &&other);
|
const QString &text,
|
||||||
String &operator=(const String &other);
|
const TextParseOptions &options = _defaultOptions,
|
||||||
String &operator=(String &&other);
|
int32 minResizeWidth = QFIXED_MAX,
|
||||||
|
bool richText = false);
|
||||||
|
String(const String &other) = default;
|
||||||
|
String(String &&other) = default;
|
||||||
|
String &operator=(const String &other) = default;
|
||||||
|
String &operator=(String &&other) = default;
|
||||||
|
~String() = default;
|
||||||
|
|
||||||
int countWidth(int width, bool breakEverywhere = false) const;
|
int countWidth(int width, bool breakEverywhere = false) const;
|
||||||
int countHeight(int width, bool breakEverywhere = false) const;
|
int countHeight(int width, bool breakEverywhere = false) const;
|
||||||
|
|
@ -168,34 +175,14 @@ public:
|
||||||
TextSelection selection = AllTextSelection) const;
|
TextSelection selection = AllTextSelection) const;
|
||||||
IsolatedEmoji toIsolatedEmoji() const;
|
IsolatedEmoji toIsolatedEmoji() const;
|
||||||
|
|
||||||
bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
|
|
||||||
if (_text.size() < maxdots) return false;
|
|
||||||
|
|
||||||
int32 nowDots = 0, from = _text.size() - maxdots, to = _text.size();
|
|
||||||
for (int32 i = from; i < to; ++i) {
|
|
||||||
if (_text.at(i) == QChar('.')) {
|
|
||||||
++nowDots;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nowDots == dots) return false;
|
|
||||||
for (int32 j = from; j < from + dots; ++j) {
|
|
||||||
_text[j] = QChar('.');
|
|
||||||
}
|
|
||||||
for (int32 j = from + dots; j < to; ++j) {
|
|
||||||
_text[j] = QChar(' ');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const style::TextStyle *style() const {
|
const style::TextStyle *style() const {
|
||||||
return _st;
|
return _st;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
~String();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using TextBlocks = std::vector<std::unique_ptr<AbstractBlock>>;
|
using TextBlocks = QVector<Block>;
|
||||||
using TextLinks = QVector<ClickHandlerPtr>;
|
using TextLinks = QVector<ClickHandlerPtr>;
|
||||||
|
|
||||||
uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
|
uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
|
||||||
|
|
@ -239,6 +226,15 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsWordSeparator(QChar ch);
|
||||||
|
[[nodiscard]] bool IsAlmostLinkEnd(QChar ch);
|
||||||
|
[[nodiscard]] bool IsLinkEnd(QChar ch);
|
||||||
|
[[nodiscard]] bool IsNewline(QChar ch);
|
||||||
|
[[nodiscard]] bool IsSpace(QChar ch, bool rich = false);
|
||||||
|
[[nodiscard]] bool IsDiac(QChar ch);
|
||||||
|
[[nodiscard]] bool IsReplacedBySpace(QChar ch);
|
||||||
|
[[nodiscard]] bool IsTrimmed(QChar ch, bool rich = false);
|
||||||
|
|
||||||
} // namespace Text
|
} // namespace Text
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
|
@ -268,120 +264,3 @@ QString textcmdLink(const QString &url, const QString &text);
|
||||||
QString textcmdStartSemibold();
|
QString textcmdStartSemibold();
|
||||||
QString textcmdStopSemibold();
|
QString textcmdStopSemibold();
|
||||||
const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true);
|
const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true);
|
||||||
|
|
||||||
inline bool chIsSpace(QChar ch, bool rich = false) {
|
|
||||||
return ch.isSpace() || (ch < 32 && !(rich && ch == TextCommand)) || (ch == QChar::ParagraphSeparator) || (ch == QChar::LineSeparator) || (ch == QChar::ObjectReplacementCharacter) || (ch == QChar::CarriageReturn) || (ch == QChar::Tabulation) || (ch == QChar(8203)/*Zero width space.*/);
|
|
||||||
}
|
|
||||||
inline bool chIsDiac(QChar ch) { // diac and variation selectors
|
|
||||||
return (ch.category() == QChar::Mark_NonSpacing) || (ch == 1652) || (ch >= 64606 && ch <= 64611);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool chIsBad(QChar ch);
|
|
||||||
|
|
||||||
inline bool chIsTrimmed(QChar ch, bool rich = false) {
|
|
||||||
return (!rich || ch != TextCommand) && (chIsSpace(ch) || chIsBad(ch));
|
|
||||||
}
|
|
||||||
inline bool chReplacedBySpace(QChar ch) {
|
|
||||||
// \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237
|
|
||||||
// QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad");
|
|
||||||
// \xcc[\xb3\xbf\x8a] // 819, 831, 778
|
|
||||||
// QString bad1 = QString::fromUtf8("\xcc\xb3"), bad2 = QString::fromUtf8("\xcc\xbf"), bad3 = QString::fromUtf8("\xcc\x8a");
|
|
||||||
// [\x00\x01\x02\x07\x08\x0b-\x1f] // '\t' = 0x09
|
|
||||||
return (/*code >= 0x00 && */ch <= 0x02) || (ch >= 0x07 && ch <= 0x09) || (ch >= 0x0b && ch <= 0x1f) ||
|
|
||||||
(ch == 819) || (ch == 831) || (ch == 778) || (ch >= 8232 && ch <= 8237);
|
|
||||||
}
|
|
||||||
inline int32 chMaxDiacAfterSymbol() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
inline bool chIsNewline(QChar ch) {
|
|
||||||
return (ch == QChar::LineFeed || ch == 156);
|
|
||||||
}
|
|
||||||
inline bool chIsLinkEnd(QChar ch) {
|
|
||||||
return ch == TextCommand || chIsBad(ch) || chIsSpace(ch) || chIsNewline(ch) || ch.isLowSurrogate() || ch.isHighSurrogate();
|
|
||||||
}
|
|
||||||
inline bool chIsAlmostLinkEnd(QChar ch) {
|
|
||||||
switch (ch.unicode()) {
|
|
||||||
case '?':
|
|
||||||
case ',':
|
|
||||||
case '.':
|
|
||||||
case '"':
|
|
||||||
case ':':
|
|
||||||
case '!':
|
|
||||||
case '\'':
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
inline bool chIsWordSeparator(QChar ch) {
|
|
||||||
switch (ch.unicode()) {
|
|
||||||
case QChar::Space:
|
|
||||||
case QChar::LineFeed:
|
|
||||||
case '.':
|
|
||||||
case ',':
|
|
||||||
case '?':
|
|
||||||
case '!':
|
|
||||||
case '@':
|
|
||||||
case '#':
|
|
||||||
case '$':
|
|
||||||
case ':':
|
|
||||||
case ';':
|
|
||||||
case '-':
|
|
||||||
case '<':
|
|
||||||
case '>':
|
|
||||||
case '[':
|
|
||||||
case ']':
|
|
||||||
case '(':
|
|
||||||
case ')':
|
|
||||||
case '{':
|
|
||||||
case '}':
|
|
||||||
case '=':
|
|
||||||
case '/':
|
|
||||||
case '+':
|
|
||||||
case '%':
|
|
||||||
case '&':
|
|
||||||
case '^':
|
|
||||||
case '*':
|
|
||||||
case '\'':
|
|
||||||
case '"':
|
|
||||||
case '`':
|
|
||||||
case '~':
|
|
||||||
case '|':
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
inline bool chIsSentenceEnd(QChar ch) {
|
|
||||||
switch (ch.unicode()) {
|
|
||||||
case '.':
|
|
||||||
case '?':
|
|
||||||
case '!':
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
inline bool chIsSentencePartEnd(QChar ch) {
|
|
||||||
switch (ch.unicode()) {
|
|
||||||
case ',':
|
|
||||||
case ':':
|
|
||||||
case ';':
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
inline bool chIsParagraphSeparator(QChar ch) {
|
|
||||||
switch (ch.unicode()) {
|
|
||||||
case QChar::LineFeed:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,89 +17,34 @@ namespace Text {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct ScriptLine {
|
struct ScriptLine {
|
||||||
ScriptLine() : length(0), textWidth(0) {
|
int length = 0;
|
||||||
}
|
|
||||||
|
|
||||||
int32 length;
|
|
||||||
QFixed textWidth;
|
QFixed textWidth;
|
||||||
};
|
};
|
||||||
|
|
||||||
// All members finished with "_" are internal.
|
// All members finished with "_" are internal.
|
||||||
struct LineBreakHelper
|
struct LineBreakHelper {
|
||||||
{
|
|
||||||
LineBreakHelper()
|
|
||||||
: glyphCount(0), maxGlyphs(INT_MAX), currentPosition(0), fontEngine(0), logClusters(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ScriptLine tmpData;
|
ScriptLine tmpData;
|
||||||
ScriptLine spaceData;
|
ScriptLine spaceData;
|
||||||
|
|
||||||
QGlyphLayout glyphs;
|
QGlyphLayout glyphs;
|
||||||
|
|
||||||
int glyphCount;
|
int glyphCount = 0;
|
||||||
int maxGlyphs;
|
int maxGlyphs = INT_MAX;
|
||||||
int currentPosition;
|
int currentPosition = 0;
|
||||||
|
|
||||||
glyph_t previousGlyph_ = 0;
|
glyph_t previousGlyph_ = 0;
|
||||||
QFontEngine *previousFontEngine_ = nullptr;
|
QFontEngine *previousFontEngine_ = nullptr;
|
||||||
|
|
||||||
QFixed rightBearing;
|
QFixed rightBearing;
|
||||||
|
|
||||||
QFontEngine *fontEngine;
|
QFontEngine *fontEngine = nullptr;
|
||||||
const unsigned short *logClusters;
|
const unsigned short *logClusters = nullptr;
|
||||||
|
|
||||||
inline glyph_t currentGlyph() const
|
glyph_t currentGlyph() const;
|
||||||
{
|
void saveCurrentGlyph();
|
||||||
Q_ASSERT(currentPosition > 0);
|
void calculateRightBearing(QFontEngine *engine, glyph_t glyph);
|
||||||
Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
|
void calculateRightBearing();
|
||||||
|
void calculateRightBearingForPreviousGlyph();
|
||||||
return glyphs.glyphs[logClusters[currentPosition - 1]];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void saveCurrentGlyph()
|
|
||||||
{
|
|
||||||
if (currentPosition > 0 &&
|
|
||||||
logClusters[currentPosition - 1] < glyphs.numGlyphs) {
|
|
||||||
previousGlyph_ = currentGlyph(); // needed to calculate right bearing later
|
|
||||||
previousFontEngine_ = fontEngine;
|
|
||||||
} else {
|
|
||||||
previousGlyph_ = 0;
|
|
||||||
previousFontEngine_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void calculateRightBearing(QFontEngine *engine, glyph_t glyph)
|
|
||||||
{
|
|
||||||
qreal rb;
|
|
||||||
engine->getGlyphBearings(glyph, 0, &rb);
|
|
||||||
|
|
||||||
// We only care about negative right bearings, so we limit the range
|
|
||||||
// of the bearing here so that we can assume it's negative in the rest
|
|
||||||
// of the code, as well ase use QFixed(1) as a sentinel to represent
|
|
||||||
// the state where we have yet to compute the right bearing.
|
|
||||||
rightBearing = qMin(QFixed::fromReal(rb), QFixed(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void calculateRightBearing()
|
|
||||||
{
|
|
||||||
if (currentPosition > 0 &&
|
|
||||||
logClusters[currentPosition - 1] < glyphs.numGlyphs) {
|
|
||||||
calculateRightBearing(fontEngine, currentGlyph());
|
|
||||||
} else {
|
|
||||||
rightBearing = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void calculateRightBearingForPreviousGlyph()
|
|
||||||
{
|
|
||||||
if (previousGlyph_ > 0) {
|
|
||||||
calculateRightBearing(previousFontEngine_, previousGlyph_);
|
|
||||||
} else {
|
|
||||||
rightBearing = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We always calculate the right bearing right before it is needed.
|
// We always calculate the right bearing right before it is needed.
|
||||||
// So we don't need caching / optimizations referred to delayed right bearing calculations.
|
// So we don't need caching / optimizations referred to delayed right bearing calculations.
|
||||||
|
|
@ -113,26 +58,92 @@ struct LineBreakHelper
|
||||||
|
|
||||||
// We express the negative right bearing as an absolute number
|
// We express the negative right bearing as an absolute number
|
||||||
// so that it can be applied to the width using addition.
|
// so that it can be applied to the width using addition.
|
||||||
inline QFixed negativeRightBearing() const
|
QFixed negativeRightBearing() const;
|
||||||
{
|
|
||||||
//if (rightBearing == RightBearingNotCalculated)
|
|
||||||
// return QFixed(0);
|
|
||||||
|
|
||||||
return qAbs(rightBearing);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1);
|
//const QFixed LineBreakHelper::RightBearingNotCalculated = QFixed(1);
|
||||||
|
|
||||||
|
glyph_t LineBreakHelper::currentGlyph() const {
|
||||||
|
Q_ASSERT(currentPosition > 0);
|
||||||
|
Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
|
||||||
|
|
||||||
|
return glyphs.glyphs[logClusters[currentPosition - 1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineBreakHelper::saveCurrentGlyph() {
|
||||||
|
if (currentPosition > 0
|
||||||
|
&& logClusters[currentPosition - 1] < glyphs.numGlyphs) {
|
||||||
|
// needed to calculate right bearing later
|
||||||
|
previousGlyph_ = currentGlyph();
|
||||||
|
previousFontEngine_ = fontEngine;
|
||||||
|
} else {
|
||||||
|
previousGlyph_ = 0;
|
||||||
|
previousFontEngine_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineBreakHelper::calculateRightBearing(
|
||||||
|
QFontEngine *engine,
|
||||||
|
glyph_t glyph) {
|
||||||
|
qreal rb;
|
||||||
|
engine->getGlyphBearings(glyph, 0, &rb);
|
||||||
|
|
||||||
|
// We only care about negative right bearings, so we limit the range
|
||||||
|
// of the bearing here so that we can assume it's negative in the rest
|
||||||
|
// of the code, as well ase use QFixed(1) as a sentinel to represent
|
||||||
|
// the state where we have yet to compute the right bearing.
|
||||||
|
rightBearing = qMin(QFixed::fromReal(rb), QFixed(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineBreakHelper::calculateRightBearing() {
|
||||||
|
if (currentPosition > 0
|
||||||
|
&& logClusters[currentPosition - 1] < glyphs.numGlyphs) {
|
||||||
|
calculateRightBearing(fontEngine, currentGlyph());
|
||||||
|
} else {
|
||||||
|
rightBearing = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineBreakHelper::calculateRightBearingForPreviousGlyph() {
|
||||||
|
if (previousGlyph_ > 0) {
|
||||||
|
calculateRightBearing(previousFontEngine_, previousGlyph_);
|
||||||
|
} else {
|
||||||
|
rightBearing = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We always calculate the right bearing right before it is needed.
|
||||||
|
// So we don't need caching / optimizations referred to delayed right bearing calculations.
|
||||||
|
|
||||||
|
//static const QFixed RightBearingNotCalculated;
|
||||||
|
|
||||||
|
//inline void resetRightBearing()
|
||||||
|
//{
|
||||||
|
// rightBearing = RightBearingNotCalculated;
|
||||||
|
//}
|
||||||
|
|
||||||
|
// We express the negative right bearing as an absolute number
|
||||||
|
// so that it can be applied to the width using addition.
|
||||||
|
QFixed LineBreakHelper::negativeRightBearing() const {
|
||||||
|
//if (rightBearing == RightBearingNotCalculated)
|
||||||
|
// return QFixed(0);
|
||||||
|
|
||||||
|
return qAbs(rightBearing);
|
||||||
|
}
|
||||||
|
|
||||||
QString DebugCurrentParsingString, DebugCurrentParsingPart;
|
QString DebugCurrentParsingString, DebugCurrentParsingPart;
|
||||||
int DebugCurrentParsingFrom = 0;
|
int DebugCurrentParsingFrom = 0;
|
||||||
int DebugCurrentParsingLength = 0;
|
int DebugCurrentParsingLength = 0;
|
||||||
|
|
||||||
static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyphCount,
|
void addNextCluster(
|
||||||
const QScriptItem ¤t, const unsigned short *logClusters,
|
int &pos,
|
||||||
const QGlyphLayout &glyphs)
|
int end,
|
||||||
{
|
ScriptLine &line,
|
||||||
|
int &glyphCount,
|
||||||
|
const QScriptItem ¤t,
|
||||||
|
const unsigned short *logClusters,
|
||||||
|
const QGlyphLayout &glyphs) {
|
||||||
int glyphPosition = logClusters[pos];
|
int glyphPosition = logClusters[pos];
|
||||||
do { // got to the first next cluster
|
do { // got to the first next cluster
|
||||||
++pos;
|
++pos;
|
||||||
|
|
@ -142,7 +153,8 @@ static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyp
|
||||||
if (!glyphs.attributes[glyphPosition].dontPrint)
|
if (!glyphs.attributes[glyphPosition].dontPrint)
|
||||||
line.textWidth += glyphs.advances[glyphPosition];
|
line.textWidth += glyphs.advances[glyphPosition];
|
||||||
++glyphPosition;
|
++glyphPosition;
|
||||||
} while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
|
} while (glyphPosition < current.num_glyphs
|
||||||
|
&& !glyphs.attributes[glyphPosition].clusterStart);
|
||||||
|
|
||||||
if (!((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition)) {
|
if (!((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition)) {
|
||||||
auto str = QStringList();
|
auto str = QStringList();
|
||||||
|
|
@ -153,7 +165,8 @@ static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyp
|
||||||
base::Integration::Instance().logAssertionViolation(QString("pos: %1, end: %2, glyphPosition: %3, glyphCount: %4, lineLength: %5, num_glyphs: %6, logClusters[0..pos]: %7").arg(pos).arg(end).arg(glyphPosition).arg(glyphCount).arg(line.length).arg(current.num_glyphs).arg(str.join(",")));
|
base::Integration::Instance().logAssertionViolation(QString("pos: %1, end: %2, glyphPosition: %3, glyphCount: %4, lineLength: %5, num_glyphs: %6, logClusters[0..pos]: %7").arg(pos).arg(end).arg(glyphPosition).arg(glyphCount).arg(line.length).arg(current.num_glyphs).arg(str.join(",")));
|
||||||
Unexpected("Values in addNextCluster()");
|
Unexpected("Values in addNextCluster()");
|
||||||
}
|
}
|
||||||
Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
|
Q_ASSERT((pos == end && glyphPosition == current.num_glyphs)
|
||||||
|
|| logClusters[pos] == glyphPosition);
|
||||||
|
|
||||||
++glyphCount;
|
++glyphCount;
|
||||||
}
|
}
|
||||||
|
|
@ -162,146 +175,16 @@ static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyp
|
||||||
|
|
||||||
class BlockParser {
|
class BlockParser {
|
||||||
public:
|
public:
|
||||||
|
BlockParser(
|
||||||
BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom, const QString &str)
|
QTextEngine *e,
|
||||||
: block(b), eng(e), str(str) {
|
TextBlock *b,
|
||||||
parseWords(minResizeWidth, blockFrom);
|
QFixed minResizeWidth,
|
||||||
}
|
int blockFrom,
|
||||||
|
const QString &str);
|
||||||
void parseWords(QFixed minResizeWidth, int32 blockFrom) {
|
|
||||||
LineBreakHelper lbh;
|
|
||||||
|
|
||||||
// Helper for debugging crashes in text processing.
|
|
||||||
//
|
|
||||||
// auto debugChars = QString();
|
|
||||||
// debugChars.reserve(str.size() * 7);
|
|
||||||
// for (const auto ch : str) {
|
|
||||||
// debugChars.append(
|
|
||||||
// "0x").append(
|
|
||||||
// QString::number(ch.unicode(), 16).toUpper()).append(
|
|
||||||
// ' ');
|
|
||||||
// }
|
|
||||||
// LOG(("Text: %1, chars: %2").arg(str).arg(debugChars));
|
|
||||||
|
|
||||||
int item = -1;
|
|
||||||
int newItem = eng->findItem(0);
|
|
||||||
|
|
||||||
style::align alignment = eng->option.alignment();
|
|
||||||
|
|
||||||
const QCharAttributes *attributes = eng->attributes();
|
|
||||||
if (!attributes)
|
|
||||||
return;
|
|
||||||
int end = 0;
|
|
||||||
lbh.logClusters = eng->layoutData->logClustersPtr;
|
|
||||||
|
|
||||||
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 ¤t = 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(¤t);
|
|
||||||
QFontEngine *fontEngine = eng->fontEngine(current);
|
|
||||||
if (lbh.fontEngine != fontEngine) {
|
|
||||||
lbh.fontEngine = fontEngine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const QScriptItem ¤t = 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->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
|
|
||||||
}
|
|
||||||
block->_words.back().add_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
|
|
||||||
|| isLineBreak(attributes, lbh.currentPosition)) {
|
|
||||||
lbh.calculateRightBearing();
|
|
||||||
block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
|
|
||||||
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.calculateRightBearingForPreviousGlyph();
|
|
||||||
block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, -lbh.negativeRightBearing()));
|
|
||||||
block->_width += lastGraphemeBoundaryLine.textWidth;
|
|
||||||
lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth;
|
|
||||||
lbh.tmpData.length -= lastGraphemeBoundaryLine.length;
|
|
||||||
wordStart = lastGraphemeBoundaryPosition;
|
|
||||||
}
|
|
||||||
addingEachGrapheme = true;
|
|
||||||
}
|
|
||||||
if (addingEachGrapheme) {
|
|
||||||
lbh.calculateRightBearing();
|
|
||||||
block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
|
|
||||||
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 = block->_words.back().f_rpadding();
|
|
||||||
block->_width -= block->_rpadding;
|
|
||||||
block->_words.squeeze();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isLineBreak(const QCharAttributes *attributes, int32 index) {
|
|
||||||
bool lineBreak = attributes[index].lineBreak;
|
|
||||||
if (lineBreak && block->lnkIndex() > 0 && index > 0 && str.at(index - 1) == '/') {
|
|
||||||
return false; // don't break after / in links
|
|
||||||
}
|
|
||||||
return lineBreak;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void parseWords(QFixed minResizeWidth, int blockFrom);
|
||||||
|
bool isLineBreak(const QCharAttributes *attributes, int index);
|
||||||
|
|
||||||
TextBlock *block;
|
TextBlock *block;
|
||||||
QTextEngine *eng;
|
QTextEngine *eng;
|
||||||
|
|
@ -309,11 +192,171 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QFixed AbstractBlock::f_rbearing() const {
|
BlockParser::BlockParser(
|
||||||
return (type() == TextBlockTText) ? static_cast<const TextBlock*>(this)->real_f_rbearing() : 0;
|
QTextEngine *e,
|
||||||
|
TextBlock *b,
|
||||||
|
QFixed minResizeWidth,
|
||||||
|
int blockFrom,
|
||||||
|
const QString &str)
|
||||||
|
: block(b)
|
||||||
|
, eng(e)
|
||||||
|
, str(str) {
|
||||||
|
parseWords(minResizeWidth, blockFrom);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : AbstractBlock(font, str, from, length, flags, lnkIndex) {
|
void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
|
||||||
|
LineBreakHelper lbh;
|
||||||
|
|
||||||
|
// Helper for debugging crashes in text processing.
|
||||||
|
//
|
||||||
|
// auto debugChars = QString();
|
||||||
|
// debugChars.reserve(str.size() * 7);
|
||||||
|
// for (const auto ch : str) {
|
||||||
|
// debugChars.append(
|
||||||
|
// "0x").append(
|
||||||
|
// QString::number(ch.unicode(), 16).toUpper()).append(
|
||||||
|
// ' ');
|
||||||
|
// }
|
||||||
|
// LOG(("Text: %1, chars: %2").arg(str).arg(debugChars));
|
||||||
|
|
||||||
|
int item = -1;
|
||||||
|
int newItem = eng->findItem(0);
|
||||||
|
|
||||||
|
style::align alignment = eng->option.alignment();
|
||||||
|
|
||||||
|
const QCharAttributes *attributes = eng->attributes();
|
||||||
|
if (!attributes)
|
||||||
|
return;
|
||||||
|
int end = 0;
|
||||||
|
lbh.logClusters = eng->layoutData->logClustersPtr;
|
||||||
|
|
||||||
|
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 ¤t = 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(¤t);
|
||||||
|
QFontEngine *fontEngine = eng->fontEngine(current);
|
||||||
|
if (lbh.fontEngine != fontEngine) {
|
||||||
|
lbh.fontEngine = fontEngine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const QScriptItem ¤t = 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->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
|
||||||
|
}
|
||||||
|
block->_words.back().add_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
|
||||||
|
|| isLineBreak(attributes, lbh.currentPosition)) {
|
||||||
|
lbh.calculateRightBearing();
|
||||||
|
block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
|
||||||
|
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.calculateRightBearingForPreviousGlyph();
|
||||||
|
block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, -lbh.negativeRightBearing()));
|
||||||
|
block->_width += lastGraphemeBoundaryLine.textWidth;
|
||||||
|
lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth;
|
||||||
|
lbh.tmpData.length -= lastGraphemeBoundaryLine.length;
|
||||||
|
wordStart = lastGraphemeBoundaryPosition;
|
||||||
|
}
|
||||||
|
addingEachGrapheme = true;
|
||||||
|
}
|
||||||
|
if (addingEachGrapheme) {
|
||||||
|
lbh.calculateRightBearing();
|
||||||
|
block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, -lbh.negativeRightBearing()));
|
||||||
|
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 = block->_words.back().f_rpadding();
|
||||||
|
block->_width -= block->_rpadding;
|
||||||
|
block->_words.squeeze();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlockParser::isLineBreak(
|
||||||
|
const QCharAttributes *attributes,
|
||||||
|
int index) {
|
||||||
|
bool lineBreak = attributes[index].lineBreak;
|
||||||
|
if (lineBreak
|
||||||
|
&& block->lnkIndex() > 0
|
||||||
|
&& index > 0
|
||||||
|
&& str.at(index - 1) == '/') {
|
||||||
|
return false; // don't break after / in links
|
||||||
|
}
|
||||||
|
return lineBreak;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFixed AbstractBlock::f_rbearing() const {
|
||||||
|
return (type() == TextBlockTText)
|
||||||
|
? static_cast<const TextBlock*>(this)->real_f_rbearing()
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextBlock::TextBlock(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
QFixed minResizeWidth,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex)
|
||||||
|
: AbstractBlock(font, str, from, length, flags, lnkIndex) {
|
||||||
_flags |= ((TextBlockTText & 0x0F) << 8);
|
_flags |= ((TextBlockTText & 0x0F) << 8);
|
||||||
if (length) {
|
if (length) {
|
||||||
style::font blockFont = font;
|
style::font blockFont = font;
|
||||||
|
|
@ -347,11 +390,18 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji) : AbstractBlock(font, str, from, length, flags, lnkIndex)
|
EmojiBlock::EmojiBlock(
|
||||||
, emoji(emoji) {
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex,
|
||||||
|
EmojiPtr emoji)
|
||||||
|
: AbstractBlock(font, str, from, length, flags, lnkIndex)
|
||||||
|
, _emoji(emoji) {
|
||||||
_flags |= ((TextBlockTEmoji & 0x0F) << 8);
|
_flags |= ((TextBlockTEmoji & 0x0F) << 8);
|
||||||
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
||||||
|
|
||||||
_rpadding = 0;
|
_rpadding = 0;
|
||||||
for (auto i = length; i != 0;) {
|
for (auto i = length; i != 0;) {
|
||||||
auto ch = str[_from + (--i)];
|
auto ch = str[_from + (--i)];
|
||||||
|
|
@ -363,10 +413,5 @@ EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SkipBlock::SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex) : AbstractBlock(font, str, from, 1, 0, lnkIndex), _height(h) {
|
|
||||||
_flags |= ((TextBlockTSkip & 0x0F) << 8);
|
|
||||||
_width = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Text
|
} // namespace Text
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -34,16 +34,13 @@ enum TextBlockFlags {
|
||||||
|
|
||||||
class AbstractBlock {
|
class AbstractBlock {
|
||||||
public:
|
public:
|
||||||
AbstractBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : _from(from), _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16 from() const {
|
uint16 from() const {
|
||||||
return _from;
|
return _from;
|
||||||
}
|
}
|
||||||
int32 width() const {
|
int width() const {
|
||||||
return _width.toInt();
|
return _width.toInt();
|
||||||
}
|
}
|
||||||
int32 rpadding() const {
|
int rpadding() const {
|
||||||
return _rpadding.toInt();
|
return _rpadding.toInt();
|
||||||
}
|
}
|
||||||
QFixed f_width() const {
|
QFixed f_width() const {
|
||||||
|
|
@ -70,11 +67,18 @@ public:
|
||||||
return (_flags & 0xFF);
|
return (_flags & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::unique_ptr<AbstractBlock> clone() const = 0;
|
protected:
|
||||||
virtual ~AbstractBlock() {
|
AbstractBlock(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex)
|
||||||
|
: _from(from)
|
||||||
|
, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
|
||||||
uint16 _from = 0;
|
uint16 _from = 0;
|
||||||
|
|
||||||
uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
|
uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
|
||||||
|
|
@ -90,9 +94,16 @@ protected:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class NewlineBlock : public AbstractBlock {
|
class NewlineBlock final : public AbstractBlock {
|
||||||
public:
|
public:
|
||||||
NewlineBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex) : AbstractBlock(font, str, from, length, flags, lnkIndex), _nextDir(Qt::LayoutDirectionAuto) {
|
NewlineBlock(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex)
|
||||||
|
: AbstractBlock(font, str, from, length, flags, lnkIndex) {
|
||||||
_flags |= ((TextBlockTNewline & 0x0F) << 8);
|
_flags |= ((TextBlockTNewline & 0x0F) << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,12 +111,8 @@ public:
|
||||||
return _nextDir;
|
return _nextDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AbstractBlock> clone() const override {
|
|
||||||
return std::make_unique<NewlineBlock>(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Qt::LayoutDirection _nextDir;
|
Qt::LayoutDirection _nextDir = Qt::LayoutDirectionAuto;
|
||||||
|
|
||||||
friend class String;
|
friend class String;
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
|
|
@ -113,14 +120,16 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class TextWord {
|
class TextWord final {
|
||||||
public:
|
public:
|
||||||
TextWord() = default;
|
TextWord() = default;
|
||||||
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0)
|
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0)
|
||||||
: _from(from)
|
: _from(from)
|
||||||
, _width(width)
|
, _rbearing((rbearing.value() > 0x7FFF)
|
||||||
, _rpadding(rpadding)
|
? 0x7FFF
|
||||||
, _rbearing(rbearing.value() > 0x7FFF ? 0x7FFF : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())) {
|
: (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value()))
|
||||||
|
, _width(width)
|
||||||
|
, _rpadding(rpadding) {
|
||||||
}
|
}
|
||||||
uint16 from() const {
|
uint16 from() const {
|
||||||
return _from;
|
return _from;
|
||||||
|
|
@ -140,45 +149,50 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16 _from = 0;
|
uint16 _from = 0;
|
||||||
QFixed _width, _rpadding;
|
|
||||||
int16 _rbearing = 0;
|
int16 _rbearing = 0;
|
||||||
|
QFixed _width, _rpadding;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class TextBlock : public AbstractBlock {
|
class TextBlock final : public AbstractBlock {
|
||||||
public:
|
public:
|
||||||
TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, uint16 lnkIndex);
|
TextBlock(
|
||||||
|
const style::font &font,
|
||||||
std::unique_ptr<AbstractBlock> clone() const override {
|
const QString &str,
|
||||||
return std::make_unique<TextBlock>(*this);
|
QFixed minResizeWidth,
|
||||||
}
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class AbstractBlock;
|
|
||||||
QFixed real_f_rbearing() const {
|
QFixed real_f_rbearing() const {
|
||||||
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
|
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
|
||||||
}
|
}
|
||||||
|
|
||||||
using TextWords = QVector<TextWord>;
|
QVector<TextWord> _words;
|
||||||
TextWords _words;
|
|
||||||
|
|
||||||
friend class String;
|
friend class String;
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
friend class Renderer;
|
friend class Renderer;
|
||||||
friend class BlockParser;
|
friend class BlockParser;
|
||||||
|
friend class AbstractBlock;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class EmojiBlock : public AbstractBlock {
|
class EmojiBlock final : public AbstractBlock {
|
||||||
public:
|
public:
|
||||||
EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, uint16 lnkIndex, EmojiPtr emoji);
|
EmojiBlock(
|
||||||
|
const style::font &font,
|
||||||
std::unique_ptr<AbstractBlock> clone() const override {
|
const QString &str,
|
||||||
return std::make_unique<EmojiBlock>(*this);
|
uint16 from,
|
||||||
}
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex,
|
||||||
|
EmojiPtr emoji);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EmojiPtr emoji = nullptr;
|
EmojiPtr _emoji = nullptr;
|
||||||
|
|
||||||
friend class String;
|
friend class String;
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
|
|
@ -186,20 +200,27 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class SkipBlock : public AbstractBlock {
|
class SkipBlock final : public AbstractBlock {
|
||||||
public:
|
public:
|
||||||
SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex);
|
SkipBlock(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
int32 w,
|
||||||
|
int32 h,
|
||||||
|
uint16 lnkIndex)
|
||||||
|
: AbstractBlock(font, str, from, 1, 0, lnkIndex)
|
||||||
|
, _height(h) {
|
||||||
|
_flags |= ((TextBlockTSkip & 0x0F) << 8);
|
||||||
|
_width = w;
|
||||||
|
}
|
||||||
|
|
||||||
int32 height() const {
|
int height() const {
|
||||||
return _height;
|
return _height;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AbstractBlock> clone() const override {
|
|
||||||
return std::make_unique<SkipBlock>(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int32 _height;
|
int _height = 0;
|
||||||
|
|
||||||
friend class String;
|
friend class String;
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
|
|
@ -207,5 +228,235 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Block final {
|
||||||
|
public:
|
||||||
|
Block() {
|
||||||
|
Unexpected("Should not be called.");
|
||||||
|
}
|
||||||
|
Block(const Block &other) {
|
||||||
|
switch (other->type()) {
|
||||||
|
case TextBlockTNewline:
|
||||||
|
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
|
||||||
|
break;
|
||||||
|
case TextBlockTText:
|
||||||
|
emplace<TextBlock>(other.unsafe<TextBlock>());
|
||||||
|
break;
|
||||||
|
case TextBlockTEmoji:
|
||||||
|
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
|
||||||
|
break;
|
||||||
|
case TextBlockTSkip:
|
||||||
|
emplace<SkipBlock>(other.unsafe<SkipBlock>());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Unexpected("Bad text block type in Block(const Block&).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Block(Block &&other) {
|
||||||
|
switch (other->type()) {
|
||||||
|
case TextBlockTNewline:
|
||||||
|
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
||||||
|
break;
|
||||||
|
case TextBlockTText:
|
||||||
|
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
||||||
|
break;
|
||||||
|
case TextBlockTEmoji:
|
||||||
|
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||||
|
break;
|
||||||
|
case TextBlockTSkip:
|
||||||
|
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Unexpected("Bad text block type in Block(Block&&).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Block &operator=(const Block &other) {
|
||||||
|
if (&other == this) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
destroy();
|
||||||
|
switch (other->type()) {
|
||||||
|
case TextBlockTNewline:
|
||||||
|
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
|
||||||
|
break;
|
||||||
|
case TextBlockTText:
|
||||||
|
emplace<TextBlock>(other.unsafe<TextBlock>());
|
||||||
|
break;
|
||||||
|
case TextBlockTEmoji:
|
||||||
|
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
|
||||||
|
break;
|
||||||
|
case TextBlockTSkip:
|
||||||
|
emplace<SkipBlock>(other.unsafe<SkipBlock>());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Unexpected("Bad text block type in operator=(const Block&).");
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Block &operator=(Block &&other) {
|
||||||
|
if (&other == this) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
destroy();
|
||||||
|
switch (other->type()) {
|
||||||
|
case TextBlockTNewline:
|
||||||
|
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
||||||
|
break;
|
||||||
|
case TextBlockTText:
|
||||||
|
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
||||||
|
break;
|
||||||
|
case TextBlockTEmoji:
|
||||||
|
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||||
|
break;
|
||||||
|
case TextBlockTSkip:
|
||||||
|
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Unexpected("Bad text block type in operator=(Block&&).");
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
~Block() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static Block Newline(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex) {
|
||||||
|
return New<NewlineBlock>(font, str, from, length, flags, lnkIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static Block Text(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
QFixed minResizeWidth,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex) {
|
||||||
|
return New<TextBlock>(
|
||||||
|
font,
|
||||||
|
str,
|
||||||
|
minResizeWidth,
|
||||||
|
from,
|
||||||
|
length,
|
||||||
|
flags,
|
||||||
|
lnkIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static Block Emoji(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
uint16 length,
|
||||||
|
uchar flags,
|
||||||
|
uint16 lnkIndex,
|
||||||
|
EmojiPtr emoji) {
|
||||||
|
return New<EmojiBlock>(
|
||||||
|
font,
|
||||||
|
str,
|
||||||
|
from,
|
||||||
|
length,
|
||||||
|
flags,
|
||||||
|
lnkIndex,
|
||||||
|
emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static Block Skip(
|
||||||
|
const style::font &font,
|
||||||
|
const QString &str,
|
||||||
|
uint16 from,
|
||||||
|
int32 w,
|
||||||
|
int32 h,
|
||||||
|
uint16 lnkIndex) {
|
||||||
|
return New<SkipBlock>(font, str, from, w, h, lnkIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FinalBlock>
|
||||||
|
[[nodiscard]] FinalBlock &unsafe() {
|
||||||
|
return *reinterpret_cast<FinalBlock*>(&_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FinalBlock>
|
||||||
|
[[nodiscard]] const FinalBlock &unsafe() const {
|
||||||
|
return *reinterpret_cast<const FinalBlock*>(&_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] AbstractBlock *get() {
|
||||||
|
return &unsafe<AbstractBlock>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const AbstractBlock *get() const {
|
||||||
|
return &unsafe<AbstractBlock>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] AbstractBlock *operator->() {
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const AbstractBlock *operator->() const {
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] AbstractBlock &operator*() {
|
||||||
|
return *get();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const AbstractBlock &operator*() const {
|
||||||
|
return *get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Tag {
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Block(const Tag &) {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FinalType, typename ...Args>
|
||||||
|
[[nodiscard]] static Block New(Args &&...args) {
|
||||||
|
auto result = Block(Tag{});
|
||||||
|
result.emplace<FinalType>(std::forward<Args>(args)...);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FinalType, typename ...Args>
|
||||||
|
void emplace(Args &&...args) {
|
||||||
|
new (&_data) FinalType(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy() {
|
||||||
|
switch (get()->type()) {
|
||||||
|
case TextBlockTNewline:
|
||||||
|
unsafe<NewlineBlock>().~NewlineBlock();
|
||||||
|
break;
|
||||||
|
case TextBlockTText:
|
||||||
|
unsafe<TextBlock>().~TextBlock();
|
||||||
|
break;
|
||||||
|
case TextBlockTEmoji:
|
||||||
|
unsafe<EmojiBlock>().~EmojiBlock();
|
||||||
|
break;
|
||||||
|
case TextBlockTSkip:
|
||||||
|
unsafe<SkipBlock>().~SkipBlock();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Unexpected("Bad text block type in Block(Block&&).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(sizeof(NewlineBlock) <= sizeof(TextBlock));
|
||||||
|
static_assert(alignof(NewlineBlock) <= alignof(void*));
|
||||||
|
static_assert(sizeof(EmojiBlock) <= sizeof(TextBlock));
|
||||||
|
static_assert(alignof(EmojiBlock) <= alignof(void*));
|
||||||
|
static_assert(sizeof(SkipBlock) <= sizeof(TextBlock));
|
||||||
|
static_assert(alignof(SkipBlock) <= alignof(void*));
|
||||||
|
|
||||||
|
std::aligned_storage_t<sizeof(TextBlock), alignof(void*)> _data;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Text
|
} // namespace Text
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@
|
||||||
namespace TextUtilities {
|
namespace TextUtilities {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using namespace Ui::Text;
|
||||||
|
|
||||||
QString ExpressionMailNameAtEnd() {
|
QString ExpressionMailNameAtEnd() {
|
||||||
// Matches email first part (before '@') at the end of the string.
|
// Matches email first part (before '@') at the end of the string.
|
||||||
// First we find a domain without protocol (like "gmail.com"), then
|
// First we find a domain without protocol (like "gmail.com"), then
|
||||||
|
|
@ -1189,6 +1191,18 @@ std::unique_ptr<QMimeData> MimeDataFromText(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsSentencePartEnd(QChar ch) {
|
||||||
|
return (ch == ',')
|
||||||
|
|| (ch == ':')
|
||||||
|
|| (ch == ';');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSentenceEnd(QChar ch) {
|
||||||
|
return (ch == '.')
|
||||||
|
|| (ch == '?')
|
||||||
|
|| (ch == '!');
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
const QRegularExpression &RegExpMailNameAtEnd() {
|
const QRegularExpression &RegExpMailNameAtEnd() {
|
||||||
|
|
@ -1303,10 +1317,10 @@ QString SingleLine(const QString &text) {
|
||||||
auto s = text.unicode(), ch = s, e = text.unicode() + text.size();
|
auto s = text.unicode(), ch = s, e = text.unicode() + text.size();
|
||||||
|
|
||||||
// Trim.
|
// Trim.
|
||||||
while (s < e && chIsTrimmed(*s)) {
|
while (s < e && IsTrimmed(*s)) {
|
||||||
++s;
|
++s;
|
||||||
}
|
}
|
||||||
while (s < e && chIsTrimmed(*(e - 1))) {
|
while (s < e && IsTrimmed(*(e - 1))) {
|
||||||
--e;
|
--e;
|
||||||
}
|
}
|
||||||
if (e - s != text.size()) {
|
if (e - s != text.size()) {
|
||||||
|
|
@ -1314,7 +1328,7 @@ QString SingleLine(const QString &text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto ch = s; ch != e; ++ch) {
|
for (auto ch = s; ch != e; ++ch) {
|
||||||
if (chIsNewline(*ch) || *ch == TextCommand) {
|
if (IsNewline(*ch) || *ch == TextCommand) {
|
||||||
result[int(ch - s)] = QChar::Space;
|
result[int(ch - s)] = QChar::Space;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1336,7 +1350,7 @@ QString RemoveAccents(const QString &text) {
|
||||||
if (copying) result[i] = *ch;
|
if (copying) result[i] = *ch;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (chIsDiac(*ch)) {
|
if (IsDiac(*ch)) {
|
||||||
copying = true;
|
copying = true;
|
||||||
--i;
|
--i;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1434,14 +1448,14 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
|
||||||
if (inEntity && !canBreakEntity) {
|
if (inEntity && !canBreakEntity) {
|
||||||
markGoodAsLevel(0);
|
markGoodAsLevel(0);
|
||||||
} else {
|
} else {
|
||||||
if (chIsNewline(*ch)) {
|
if (IsNewline(*ch)) {
|
||||||
if (inEntity) {
|
if (inEntity) {
|
||||||
if (ch + 1 < end && chIsNewline(*(ch + 1))) {
|
if (ch + 1 < end && IsNewline(*(ch + 1))) {
|
||||||
markGoodAsLevel(12);
|
markGoodAsLevel(12);
|
||||||
} else {
|
} else {
|
||||||
markGoodAsLevel(11);
|
markGoodAsLevel(11);
|
||||||
}
|
}
|
||||||
} else if (ch + 1 < end && chIsNewline(*(ch + 1))) {
|
} else if (ch + 1 < end && IsNewline(*(ch + 1))) {
|
||||||
markGoodAsLevel(15);
|
markGoodAsLevel(15);
|
||||||
} else if (currentEntity < entityCount && ch + 1 == start + left.entities[currentEntity].offset() && left.entities[currentEntity].type() == EntityType::Pre) {
|
} else if (currentEntity < entityCount && ch + 1 == start + left.entities[currentEntity].offset() && left.entities[currentEntity].type() == EntityType::Pre) {
|
||||||
markGoodAsLevel(14);
|
markGoodAsLevel(14);
|
||||||
|
|
@ -1450,15 +1464,15 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
|
||||||
} else {
|
} else {
|
||||||
markGoodAsLevel(13);
|
markGoodAsLevel(13);
|
||||||
}
|
}
|
||||||
} else if (chIsSpace(*ch)) {
|
} else if (IsSpace(*ch)) {
|
||||||
if (chIsSentenceEnd(*(ch - 1))) {
|
if (IsSentenceEnd(*(ch - 1))) {
|
||||||
markGoodAsLevel(9 + noEntityLevel);
|
markGoodAsLevel(9 + noEntityLevel);
|
||||||
} else if (chIsSentencePartEnd(*(ch - 1))) {
|
} else if (IsSentencePartEnd(*(ch - 1))) {
|
||||||
markGoodAsLevel(7 + noEntityLevel);
|
markGoodAsLevel(7 + noEntityLevel);
|
||||||
} else {
|
} else {
|
||||||
markGoodAsLevel(5 + noEntityLevel);
|
markGoodAsLevel(5 + noEntityLevel);
|
||||||
}
|
}
|
||||||
} else if (chIsWordSeparator(*(ch - 1))) {
|
} else if (IsWordSeparator(*(ch - 1))) {
|
||||||
markGoodAsLevel(3 + noEntityLevel);
|
markGoodAsLevel(3 + noEntityLevel);
|
||||||
} else {
|
} else {
|
||||||
markGoodAsLevel(1 + noEntityLevel);
|
markGoodAsLevel(1 + noEntityLevel);
|
||||||
|
|
@ -1761,13 +1775,14 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
|
||||||
const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd;
|
const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd;
|
||||||
for (; p < end; ++p) {
|
for (; p < end; ++p) {
|
||||||
QChar ch(*p);
|
QChar ch(*p);
|
||||||
if (chIsLinkEnd(ch)) break; // link finished
|
if (IsLinkEnd(ch)) {
|
||||||
if (chIsAlmostLinkEnd(ch)) {
|
break; // link finished
|
||||||
|
} else if (IsAlmostLinkEnd(ch)) {
|
||||||
const QChar *endTest = p + 1;
|
const QChar *endTest = p + 1;
|
||||||
while (endTest < end && chIsAlmostLinkEnd(*endTest)) {
|
while (endTest < end && IsAlmostLinkEnd(*endTest)) {
|
||||||
++endTest;
|
++endTest;
|
||||||
}
|
}
|
||||||
if (endTest >= end || chIsLinkEnd(*endTest)) {
|
if (endTest >= end || IsLinkEnd(*endTest)) {
|
||||||
break; // link finished at p
|
break; // link finished at p
|
||||||
}
|
}
|
||||||
p = endTest;
|
p = endTest;
|
||||||
|
|
@ -1879,7 +1894,7 @@ void ApplyServerCleaning(TextWithEntities &result) {
|
||||||
if (ch->unicode() == '\r') {
|
if (ch->unicode() == '\r') {
|
||||||
MovePartAndGoForward(result, to, from, (ch - start) - from);
|
MovePartAndGoForward(result, to, from, (ch - start) - from);
|
||||||
++from;
|
++from;
|
||||||
} else if (chReplacedBySpace(*ch)) {
|
} else if (IsReplacedBySpace(*ch)) {
|
||||||
*ch = ' ';
|
*ch = ' ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1893,7 +1908,7 @@ void Trim(TextWithEntities &result) {
|
||||||
// right trim
|
// right trim
|
||||||
for (auto s = result.text.data(), e = s + result.text.size(), ch = e; ch != s;) {
|
for (auto s = result.text.data(), e = s + result.text.size(), ch = e; ch != s;) {
|
||||||
--ch;
|
--ch;
|
||||||
if (!chIsTrimmed(*ch)) {
|
if (!IsTrimmed(*ch)) {
|
||||||
if (ch + 1 < e) {
|
if (ch + 1 < e) {
|
||||||
auto l = ch + 1 - s;
|
auto l = ch + 1 - s;
|
||||||
for (auto &entity : result.entities) {
|
for (auto &entity : result.entities) {
|
||||||
|
|
@ -1916,7 +1931,7 @@ void Trim(TextWithEntities &result) {
|
||||||
|
|
||||||
// left trim
|
// left trim
|
||||||
for (auto s = result.text.data(), ch = s, e = s + result.text.size(); ch != e; ++ch) {
|
for (auto s = result.text.data(), ch = s, e = s + result.text.size(); ch != e; ++ch) {
|
||||||
if (!chIsTrimmed(*ch) || (ch - s) == firstMonospaceOffset) {
|
if (!IsTrimmed(*ch) || (ch - s) == firstMonospaceOffset) {
|
||||||
if (ch > s) {
|
if (ch > s) {
|
||||||
auto l = ch - s;
|
auto l = ch - s;
|
||||||
for (auto &entity : result.entities) {
|
for (auto &entity : result.entities) {
|
||||||
|
|
|
||||||
|
|
@ -107,12 +107,12 @@ QString GetFullSimpleTextTag(const TextWithTags &textWithTags) {
|
||||||
auto from = 0;
|
auto from = 0;
|
||||||
auto till = int(text.size());
|
auto till = int(text.size());
|
||||||
for (; from != till; ++from) {
|
for (; from != till; ++from) {
|
||||||
if (!IsNewline(text[from]) && !chIsSpace(text[from])) {
|
if (!IsNewline(text[from]) && !Text::IsSpace(text[from])) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (till != from) {
|
while (till != from) {
|
||||||
if (!IsNewline(text[till - 1]) && !chIsSpace(text[till - 1])) {
|
if (!IsNewline(text[till - 1]) && !Text::IsSpace(text[till - 1])) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
--till;
|
--till;
|
||||||
|
|
@ -3129,7 +3129,7 @@ bool InputField::commitMarkdownReplacement(
|
||||||
const auto ch = outer.at(check);
|
const auto ch = outer.at(check);
|
||||||
if (IsNewline(ch)) {
|
if (IsNewline(ch)) {
|
||||||
return check + 1;
|
return check + 1;
|
||||||
} else if (!chIsSpace(ch)) {
|
} else if (!Text::IsSpace(ch)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue