Add per-line geometry / colors customization for Text.

This commit is contained in:
John Preston 2023-10-03 13:06:56 +04:00
parent 93458e4cb3
commit 02440524ea
19 changed files with 1087 additions and 875 deletions

View file

@ -16,7 +16,7 @@ TextPalette {
selectMonoFg: color; selectMonoFg: color;
selectSpoilerFg: color; selectSpoilerFg: color;
selectOverlay: color; selectOverlay: color;
linkAlwaysActive: int; linkAlwaysActive: bool;
} }
TextStyle { TextStyle {

View file

@ -15,6 +15,12 @@
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "styles/style_basic.h" #include "styles/style_basic.h"
namespace Ui {
const QString kQEllipsis = u"..."_q;
} // namespace Ui
namespace Ui::Text { namespace Ui::Text {
namespace { namespace {
@ -125,6 +131,52 @@ not_null<SpoilerMessCache*> DefaultSpoilerCache() {
return &data.cache; return &data.cache;
} }
GeometryDescriptor SimpleGeometry(
int availableWidth,
int fontHeight,
int elisionHeight,
int elisionRemoveFromEnd,
bool elisionOneLine,
bool elisionBreakEverywhere) {
constexpr auto wrap = [](
Fn<LineGeometry(LineGeometry, uint16)> layout,
bool breakEverywhere = false) {
return GeometryDescriptor{ std::move(layout), breakEverywhere };
};
// Try to minimize captured values (to minimize Fn allocations).
if (!elisionOneLine && !elisionHeight) {
return wrap([=](LineGeometry line, uint16 positino) {
line.width = availableWidth;
return line;
});
} else if (elisionOneLine) {
return wrap([=](LineGeometry line, uint16 position) {
line.elided = true;
line.width = availableWidth - elisionRemoveFromEnd;
return line;
}, elisionBreakEverywhere);
} else if (!elisionRemoveFromEnd) {
return wrap([=](LineGeometry line, uint16 position) {
if (line.top + fontHeight * 2 > elisionHeight) {
line.elided = true;
}
line.width = availableWidth;
return line;
});
} else {
return wrap([=](LineGeometry line, uint16 position) {
if (line.top + fontHeight * 2 > elisionHeight) {
line.elided = true;
line.width = availableWidth - elisionRemoveFromEnd;
} else {
line.width = availableWidth;
}
return line;
});
}
};
String::SpoilerDataWrap::SpoilerDataWrap() noexcept = default; String::SpoilerDataWrap::SpoilerDataWrap() noexcept = default;
String::SpoilerDataWrap::SpoilerDataWrap(SpoilerDataWrap &&other) noexcept String::SpoilerDataWrap::SpoilerDataWrap(SpoilerDataWrap &&other) noexcept
@ -188,7 +240,9 @@ void String::setText(const style::TextStyle &st, const QString &text, const Text
recountNaturalSize(true, options.dir); recountNaturalSize(true, options.dir);
} }
void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) { void String::recountNaturalSize(
bool initial,
Qt::LayoutDirection optionsDirection) {
NewlineBlock *lastNewline = 0; NewlineBlock *lastNewline = 0;
_maxWidth = _minHeight = 0; _maxWidth = _minHeight = 0;
@ -199,20 +253,23 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
auto b = block.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 == TextBlockType::Newline) {
if (!lineHeight) lineHeight = blockHeight; if (!lineHeight) lineHeight = blockHeight;
if (initial) { if (initial) {
Qt::LayoutDirection dir = optionsDir; Qt::LayoutDirection direction = optionsDirection;
if (dir == Qt::LayoutDirectionAuto) { if (direction == Qt::LayoutDirectionAuto) {
dir = StringDirection(_text, lastNewlineStart, b->from()); direction = StringDirection(
_text,
lastNewlineStart,
b->position());
} }
if (lastNewline) { if (lastNewline) {
lastNewline->_nextDir = dir; lastNewline->_nextDirection = direction;
} else { } else {
_startDir = dir; _startDirection = direction;
} }
} }
lastNewlineStart = b->from(); lastNewlineStart = b->position();
lastNewline = &block.unsafe<NewlineBlock>(); lastNewline = &block.unsafe<NewlineBlock>();
_minHeight += lineHeight; _minHeight += lineHeight;
@ -244,14 +301,14 @@ void String::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
continue; continue;
} }
if (initial) { if (initial) {
Qt::LayoutDirection dir = optionsDir; Qt::LayoutDirection direction = optionsDirection;
if (dir == Qt::LayoutDirectionAuto) { if (direction == Qt::LayoutDirectionAuto) {
dir = StringDirection(_text, lastNewlineStart, _text.size()); direction = StringDirection(_text, lastNewlineStart, _text.size());
} }
if (lastNewline) { if (lastNewline) {
lastNewline->_nextDir = dir; lastNewline->_nextDirection = direction;
} else { } else {
_startDir = dir; _startDirection = direction;
} }
} }
if (_width > 0) { if (_width > 0) {
@ -269,7 +326,7 @@ int String::countMaxMonospaceWidth() const {
for (auto &block : _blocks) { for (auto &block : _blocks) {
auto b = block.get(); auto b = block.get();
auto _btype = b->type(); auto _btype = b->type();
if (_btype == TextBlockTNewline) { if (_btype == TextBlockType::Newline) {
last_rBearing = b->f_rbearing(); last_rBearing = b->f_rbearing();
last_rPadding = b->f_rpadding(); last_rPadding = b->f_rpadding();
@ -283,8 +340,8 @@ int String::countMaxMonospaceWidth() const {
_width = (b->f_width() - last_rBearing); _width = (b->f_width() - last_rBearing);
continue; continue;
} }
if (!(b->flags() & (TextBlockFPre | TextBlockFCode)) if (!(b->flags() & (TextBlockFlag::Pre | TextBlockFlag::Code))
&& (b->type() != TextBlockTSkip)) { && (b->type() != TextBlockType::Skip)) {
fullMonospace = false; fullMonospace = false;
} }
auto b__f_rbearing = b->f_rbearing(); // cache auto b__f_rbearing = b->f_rbearing(); // cache
@ -347,9 +404,10 @@ void String::setMarkedText(const style::TextStyle &st, const TextWithEntities &t
recountNaturalSize(true, options.dir); recountNaturalSize(true, options.dir);
} }
void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { void String::setLink(uint16 index, const ClickHandlerPtr &link) {
if (!lnkIndex || lnkIndex > _links.size()) return; if (index > 0 && index <= _links.size()) {
_links[lnkIndex - 1] = lnk; _links[index - 1] = link;
}
} }
void String::setSpoilerRevealed(bool revealed, anim::type animated) { void String::setSpoilerRevealed(bool revealed, anim::type animated) {
@ -394,16 +452,17 @@ bool String::hasSpoilers() const {
} }
bool String::hasSkipBlock() const { bool String::hasSkipBlock() const {
return _blocks.empty() ? false : _blocks.back()->type() == TextBlockTSkip; return !_blocks.empty()
&& (_blocks.back()->type() == TextBlockType::Skip);
} }
bool String::updateSkipBlock(int width, int height) { bool String::updateSkipBlock(int width, int height) {
if (!_blocks.empty() && _blocks.back()->type() == TextBlockTSkip) { if (!_blocks.empty() && _blocks.back()->type() == TextBlockType::Skip) {
const auto block = static_cast<SkipBlock*>(_blocks.back().get()); const auto block = static_cast<SkipBlock*>(_blocks.back().get());
if (block->width() == width && block->height() == height) { if (block->f_width().toInt() == width && block->height() == height) {
return false; return false;
} }
_text.resize(block->from()); _text.resize(block->position());
_blocks.pop_back(); _blocks.pop_back();
} }
_text.push_back('_'); _text.push_back('_');
@ -420,10 +479,10 @@ bool String::updateSkipBlock(int width, int height) {
} }
bool String::removeSkipBlock() { bool String::removeSkipBlock() {
if (_blocks.empty() || _blocks.back()->type() != TextBlockTSkip) { if (_blocks.empty() || _blocks.back()->type() != TextBlockType::Skip) {
return false; return false;
} }
_text.resize(_blocks.back()->from()); _text.resize(_blocks.back()->position());
_blocks.pop_back(); _blocks.pop_back();
recountNaturalSize(false); recountNaturalSize(false);
return true; return true;
@ -454,10 +513,21 @@ int String::countHeight(int width, bool breakEverywhere) const {
return result; return result;
} }
void String::countLineWidths(int width, QVector<int> *lineWidths, bool breakEverywhere) const { std::vector<int> String::countLineWidths(int width) const {
enumerateLines(width, breakEverywhere, [&](QFixed lineWidth, int lineHeight) { return countLineWidths(width, {});
lineWidths->push_back(lineWidth.ceil().toInt()); }
std::vector<int> String::countLineWidths(
int width,
LineWidthsOptions options) const {
auto result = std::vector<int>();
if (options.reserve) {
result.reserve(options.reserve);
}
enumerateLines(width, options.breakEverywhere, [&](QFixed lineWidth, int lineHeight) {
result.push_back(lineWidth.ceil().toInt());
}); });
return result;
} }
template <typename Callback> template <typename Callback>
@ -475,7 +545,7 @@ void String::enumerateLines(
auto _btype = b->type(); auto _btype = b->type();
int blockHeight = CountBlockHeight(b.get(), _st); int blockHeight = CountBlockHeight(b.get(), _st);
if (_btype == TextBlockTNewline) { if (_btype == TextBlockType::Newline) {
if (!lineHeight) lineHeight = blockHeight; if (!lineHeight) lineHeight = blockHeight;
callback(width - widthLeft, lineHeight); callback(width - widthLeft, lineHeight);
@ -500,7 +570,7 @@ void String::enumerateLines(
continue; continue;
} }
if (_btype == TextBlockTText) { if (_btype == TextBlockType::Text) {
const auto t = &b.unsafe<TextBlock>(); 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();
@ -577,6 +647,13 @@ void String::draw(QPainter &p, const PaintContext &context) const {
Renderer(*this).draw(p, context); Renderer(*this).draw(p, context);
} }
StateResult String::getState(
QPoint point,
GeometryDescriptor geometry,
StateRequest request) const {
return Renderer(*this).getState(point, std::move(geometry), request);
}
void String::draw(Painter &p, int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, TextSelection selection, bool fullWidthSelection) const { void String::draw(Painter &p, int32 left, int32 top, int32 w, style::align align, int32 yFrom, int32 yTo, TextSelection selection, bool fullWidthSelection) const {
// p.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug // p.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug
Renderer(*this).draw(p, { Renderer(*this).draw(p, {
@ -605,14 +682,16 @@ void String::drawElided(Painter &p, int32 left, int32 top, int32 w, int32 lines,
.palette = &p.textPalette(), .palette = &p.textPalette(),
.paused = p.inactive(), .paused = p.inactive(),
.selection = selection, .selection = selection,
.elisionLines = lines, .elisionHeight = (lines > 1) ? (lines * _st->font->height) : 0,
.elisionRemoveFromEnd = removeFromEnd, .elisionRemoveFromEnd = removeFromEnd,
.elisionOneLine = (lines == 1),
}); });
} }
void String::drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align, int32 yFrom, int32 yTo, TextSelection selection) const { void String::drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 outerw, style::align align, int32 yFrom, int32 yTo, TextSelection selection) const {
Renderer(*this).draw(p, { Renderer(*this).draw(p, {
.position = { left, top }, .position = { left, top },
//.outerWidth = outerw,
.availableWidth = width, .availableWidth = width,
.align = align, .align = align,
.clip = (yTo >= 0 .clip = (yTo >= 0
@ -622,7 +701,6 @@ void String::drawLeft(Painter &p, int32 left, int32 top, int32 width, int32 oute
.paused = p.inactive(), .paused = p.inactive(),
.selection = selection, .selection = selection,
}); });
draw(p, style::RightToLeft() ? (outerw - left - width) : left, top, width, align, yFrom, yTo, selection);
} }
void String::drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const { void String::drawLeftElided(Painter &p, int32 left, int32 top, int32 width, int32 outerw, int32 lines, style::align align, int32 yFrom, int32 yTo, int32 removeFromEnd, bool breakEverywhere, TextSelection selection) const {
@ -638,7 +716,10 @@ void String::drawRightElided(Painter &p, int32 right, int32 top, int32 width, in
} }
StateResult String::getState(QPoint point, int width, StateRequest request) const { StateResult String::getState(QPoint point, int width, StateRequest request) const {
return Renderer(*this).getState(point, width, request); return Renderer(*this).getState(
point,
SimpleGeometry(width, _st->font->height, 0, 0, false, false),
request);
} }
StateResult String::getStateLeft(QPoint point, int width, int outerw, StateRequest request) const { StateResult String::getStateLeft(QPoint point, int width, int outerw, StateRequest request) const {
@ -646,7 +727,14 @@ StateResult String::getStateLeft(QPoint point, int width, int outerw, StateReque
} }
StateResult String::getStateElided(QPoint point, int width, StateRequestElided request) const { StateResult String::getStateElided(QPoint point, int width, StateRequestElided request) const {
return Renderer(*this).getStateElided(point, width, request); return Renderer(*this).getState(point, SimpleGeometry(
width,
_st->font->height,
(request.lines > 1) ? (request.lines * _st->font->height) : 0,
request.removeFromEnd,
(request.lines == 1),
request.flags & StateRequest::Flag::BreakEverywhere
), static_cast<StateRequest>(request));
} }
StateResult String::getStateElidedLeft(QPoint point, int width, int outerw, StateRequestElided request) const { StateResult String::getStateElidedLeft(QPoint point, int width, int outerw, StateRequestElided request) const {
@ -661,7 +749,7 @@ TextSelection String::adjustSelection(TextSelection selection, TextSelectType se
// Full selection of monospace entity. // Full selection of monospace entity.
for (const auto &b : _blocks) { for (const auto &b : _blocks) {
if (b->from() < from) { if (b->position() < from) {
continue; continue;
} }
if (!IsMono(b->flags())) { if (!IsMono(b->flags())) {
@ -723,15 +811,15 @@ TextSelection String::adjustSelection(TextSelection selection, TextSelectType se
} }
bool String::isEmpty() const { bool String::isEmpty() const {
return _blocks.empty() || _blocks[0]->type() == TextBlockTSkip; return _blocks.empty() || _blocks[0]->type() == TextBlockType::Skip;
} }
uint16 String::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const { uint16 String::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const {
return (i + 1 == e) ? _text.size() : (*(i + 1))->from(); return (i + 1 == e) ? _text.size() : (*(i + 1))->position();
} }
uint16 String::countBlockLength(const String::TextBlocks::const_iterator &i, const String::TextBlocks::const_iterator &e) const { uint16 String::countBlockLength(const String::TextBlocks::const_iterator &i, const String::TextBlocks::const_iterator &e) const {
return countBlockEnd(i, e) - (*i)->from(); return countBlockEnd(i, e) - (*i)->position();
} }
template < template <
@ -749,92 +837,67 @@ void String::enumerateText(
return; return;
} }
int lnkIndex = 0; int linkIndex = 0;
uint16 lnkFrom = 0; uint16 linkPosition = 0;
int spoilerIndex = 0;
uint16 spoilerFrom = 0;
int32 flags = 0; int32 flags = 0;
for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) { for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
int blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex(); const auto blockPosition = (i == e) ? uint16(_text.size()) : (*i)->position();
int blockSpoilerIndex = (i == e) ? 0 : (*i)->spoilerIndex(); const auto blockFlags = (i == e) ? TextBlockFlags() : (*i)->flags();
uint16 blockFrom = (i == e) ? _text.size() : (*i)->from(); const auto blockLinkIndex = [&] {
int32 blockFlags = (i == e) ? 0 : (*i)->flags(); if (IsMono(blockFlags) || (i == e)) {
return 0;
if (IsMono(blockFlags)) {
blockLnkIndex = 0;
} }
if (blockLnkIndex && !_links.at(blockLnkIndex - 1)) { // ignore empty links const auto result = (*i)->linkIndex();
blockLnkIndex = 0; return (result && _links.at(result - 1)) ? result : 0;
} }();
if (blockLnkIndex != lnkIndex) { if (blockLinkIndex != linkIndex) {
if (lnkIndex) { if (linkIndex) {
auto rangeFrom = qMax(selection.from, lnkFrom); auto rangeFrom = qMax(selection.from, linkPosition);
auto rangeTo = qMin(selection.to, blockFrom); auto rangeTo = qMin(selection.to, blockPosition);
if (rangeTo > rangeFrom) { // handle click handler if (rangeTo > rangeFrom) { // handle click handler
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom); const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
// Ignore links that are partially copied. // Ignore links that are partially copied.
const auto handler = (lnkFrom != rangeFrom || blockFrom != rangeTo) const auto handler = (linkPosition != rangeFrom || blockPosition != rangeTo)
? nullptr ? nullptr
: _links.at(lnkIndex - 1); : _links.at(linkIndex - 1);
const auto type = handler const auto type = handler
? handler->getTextEntity().type ? handler->getTextEntity().type
: EntityType::Invalid; : EntityType::Invalid;
clickHandlerFinishCallback(r, handler, type); clickHandlerFinishCallback(r, handler, type);
} }
} }
lnkIndex = blockLnkIndex; linkIndex = blockLinkIndex;
if (lnkIndex) { if (linkIndex) {
lnkFrom = blockFrom; linkPosition = blockPosition;
const auto handler = _links.at(lnkIndex - 1); const auto handler = _links.at(linkIndex - 1);
clickHandlerStartCallback(handler clickHandlerStartCallback(handler
? handler->getTextEntity().type ? handler->getTextEntity().type
: EntityType::Invalid); : EntityType::Invalid);
} }
} }
if (blockSpoilerIndex != spoilerIndex) {
if (spoilerIndex) {
auto rangeFrom = qMax(selection.from, spoilerFrom);
auto rangeTo = qMin(selection.to, blockFrom);
if (rangeTo > rangeFrom) { // handle click handler
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
// Ignore links that are partially copied.
const auto handler = (spoilerFrom != rangeFrom
|| blockFrom != rangeTo
|| !_spoiler.data)
? nullptr
: _spoiler.data->link;
const auto type = EntityType::Spoiler;
clickHandlerFinishCallback(r, handler, type);
}
}
spoilerIndex = blockSpoilerIndex;
if (spoilerIndex) {
spoilerFrom = blockFrom;
clickHandlerStartCallback(EntityType::Spoiler);
}
}
const auto checkBlockFlags = (blockFrom >= selection.from) const auto checkBlockFlags = (blockPosition >= selection.from)
&& (blockFrom <= selection.to); && (blockPosition <= selection.to);
if (checkBlockFlags && blockFlags != flags) { if (checkBlockFlags && blockFlags != flags) {
flagsChangeCallback(flags, blockFlags); flagsChangeCallback(flags, blockFlags);
flags = blockFlags; flags = blockFlags;
} }
if (i == e || (lnkIndex ? lnkFrom : blockFrom) >= selection.to) { if (i == e || (linkIndex ? linkPosition : blockPosition) >= selection.to) {
break; break;
} }
const auto blockType = (*i)->type(); const auto blockType = (*i)->type();
if (blockType == TextBlockTSkip) continue; if (blockType == TextBlockType::Skip) {
continue;
}
auto rangeFrom = qMax(selection.from, blockFrom); auto rangeFrom = qMax(selection.from, blockPosition);
auto rangeTo = qMin( auto rangeTo = qMin(
selection.to, selection.to,
uint16(blockFrom + countBlockLength(i, e))); uint16(blockPosition + countBlockLength(i, e)));
if (rangeTo > rangeFrom) { if (rangeTo > rangeFrom) {
const auto customEmojiData = (blockType == TextBlockTCustomEmoji) const auto customEmojiData = (blockType == TextBlockType::CustomEmoji)
? static_cast<const CustomEmojiBlock*>(i->get())->_custom->entityData() ? static_cast<const CustomEmojiBlock*>(i->get())->_custom->entityData()
: QString(); : QString();
appendPartCallback( appendPartCallback(
@ -852,7 +915,7 @@ void String::unloadPersistentAnimation() {
if (_hasCustomEmoji) { if (_hasCustomEmoji) {
for (const auto &block : _blocks) { for (const auto &block : _blocks) {
const auto raw = block.get(); const auto raw = block.get();
if (raw->type() == TextBlockTCustomEmoji) { if (raw->type() == TextBlockType::CustomEmoji) {
static_cast<const CustomEmojiBlock*>(raw)->_custom->unload(); static_cast<const CustomEmojiBlock*>(raw)->_custom->unload();
} }
} }
@ -871,12 +934,12 @@ OnlyCustomEmoji String::toOnlyCustomEmoji() const {
result.lines.emplace_back(); result.lines.emplace_back();
for (const auto &block : _blocks) { for (const auto &block : _blocks) {
const auto raw = block.get(); const auto raw = block.get();
if (raw->type() == TextBlockTCustomEmoji) { if (raw->type() == TextBlockType::CustomEmoji) {
const auto custom = static_cast<const CustomEmojiBlock*>(raw); const auto custom = static_cast<const CustomEmojiBlock*>(raw);
result.lines.back().push_back({ result.lines.back().push_back({
.entityData = custom->_custom->entityData(), .entityData = custom->_custom->entityData(),
}); });
} else if (raw->type() == TextBlockTNewline) { } else if (raw->type() == TextBlockType::Newline) {
result.lines.emplace_back(); result.lines.emplace_back();
} }
} }
@ -925,17 +988,16 @@ TextForMimeData String::toText(
result.rich.entities.insert(i, std::move(entity)); result.rich.entities.insert(i, std::move(entity));
}; };
auto linkStart = 0; auto linkStart = 0;
auto spoilerStart = 0;
auto markdownTrackers = composeEntities auto markdownTrackers = composeEntities
? std::vector<MarkdownTagTracker>{ ? std::vector<MarkdownTagTracker>{
{ TextBlockFItalic, EntityType::Italic }, { TextBlockFlag::Italic, EntityType::Italic },
{ TextBlockFBold, EntityType::Bold }, { TextBlockFlag::Bold, EntityType::Bold },
{ TextBlockFSemibold, EntityType::Semibold }, { TextBlockFlag::Semibold, EntityType::Semibold },
{ TextBlockFUnderline, EntityType::Underline }, { TextBlockFlag::Underline, EntityType::Underline },
{ TextBlockFPlainLink, EntityType::PlainLink }, { TextBlockFlag::Spoiler, EntityType::Spoiler },
{ TextBlockFStrikeOut, EntityType::StrikeOut }, { TextBlockFlag::StrikeOut, EntityType::StrikeOut },
{ TextBlockFCode, EntityType::Code }, // #TODO entities { TextBlockFlag::Code, EntityType::Code }, // #TODO entities
{ TextBlockFPre, EntityType::Pre }, { TextBlockFlag::Pre, EntityType::Pre },
} : std::vector<MarkdownTagTracker>(); } : std::vector<MarkdownTagTracker>();
const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) { const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
if (!composeEntities) { if (!composeEntities) {
@ -954,25 +1016,12 @@ TextForMimeData String::toText(
} }
}; };
const auto clickHandlerStartCallback = [&](EntityType type) { const auto clickHandlerStartCallback = [&](EntityType type) {
if (type == EntityType::Spoiler) {
spoilerStart = result.rich.text.size();
} else {
linkStart = result.rich.text.size(); linkStart = result.rich.text.size();
}
}; };
const auto clickHandlerFinishCallback = [&]( const auto clickHandlerFinishCallback = [&](
QStringView inText, QStringView inText,
const ClickHandlerPtr &handler, const ClickHandlerPtr &handler,
EntityType type) { EntityType type) {
if (type == EntityType::Spoiler) {
insertEntity({
type,
spoilerStart,
int(result.rich.text.size() - spoilerStart),
QString(),
});
return;
}
if (!handler || (!composeExpanded && !composeEntities)) { if (!handler || (!composeExpanded && !composeEntities)) {
return; return;
} }
@ -1061,21 +1110,21 @@ IsolatedEmoji String::toIsolatedEmoji() const {
} }
auto result = IsolatedEmoji(); auto result = IsolatedEmoji();
const auto skip = (_blocks.empty() const auto skip = (_blocks.empty()
|| _blocks.back()->type() != TextBlockTSkip) ? 0 : 1; || _blocks.back()->type() != TextBlockType::Skip) ? 0 : 1;
if ((_blocks.size() > kIsolatedEmojiLimit + skip) || hasSpoilers()) { if ((_blocks.size() > kIsolatedEmojiLimit + skip) || hasSpoilers()) {
return {}; return {};
} }
auto index = 0; auto index = 0;
for (const auto &block : _blocks) { for (const auto &block : _blocks) {
const auto type = block->type(); const auto type = block->type();
if (block->lnkIndex()) { if (block->linkIndex()) {
return {}; return {};
} else if (type == TextBlockTEmoji) { } else if (type == TextBlockType::Emoji) {
result.items[index++] = block.unsafe<EmojiBlock>()._emoji; result.items[index++] = block.unsafe<EmojiBlock>()._emoji;
} else if (type == TextBlockTCustomEmoji) { } else if (type == TextBlockType::CustomEmoji) {
result.items[index++] result.items[index++]
= block.unsafe<CustomEmojiBlock>()._custom->entityData(); = block.unsafe<CustomEmojiBlock>()._custom->entityData();
} else if (type != TextBlockTSkip) { } else if (type != TextBlockType::Skip) {
return {}; return {};
} }
} }
@ -1092,7 +1141,7 @@ void String::clearFields() {
_links.clear(); _links.clear();
_spoiler.data = nullptr; _spoiler.data = nullptr;
_maxWidth = _minHeight = 0; _maxWidth = _minHeight = 0;
_startDir = Qt::LayoutDirectionAuto; _startDirection = Qt::LayoutDirectionAuto;
} }
bool IsBad(QChar ch) { bool IsBad(QChar ch) {
@ -1186,7 +1235,7 @@ bool IsSpace(QChar ch) {
|| (ch == QChar(8203)/*Zero width space.*/); || (ch == QChar(8203)/*Zero width space.*/);
} }
bool IsDiac(QChar ch) { // diac and variation selectors bool IsDiacritic(QChar ch) { // diacritic and variation selectors
return (ch.category() == QChar::Mark_NonSpacing) return (ch.category() == QChar::Mark_NonSpacing)
|| (ch == 1652) || (ch == 1652)
|| (ch >= 64606 && ch <= 64611); || (ch >= 64606 && ch <= 64611);

View file

@ -26,11 +26,9 @@ struct TextPalette;
} // namespace style } // namespace style
namespace Ui { namespace Ui {
static const auto kQEllipsis = QStringLiteral("..."); extern const QString kQEllipsis;
} // namespace Ui } // namespace Ui
static const QChar TextCommand(0x0010);
struct TextParseOptions { struct TextParseOptions {
int32 flags; int32 flags;
int32 maxw; int32 maxw;
@ -125,16 +123,42 @@ private:
}; };
struct SpecialColor {
const QPen *pen = nullptr;
const QPen *penSelected = nullptr;
};
struct LineGeometry {
int left = 0;
int top = 0;
int width = 0;
bool elided = false;
};
struct GeometryDescriptor {
Fn<LineGeometry(LineGeometry line, uint16 position)> layout;
bool breakEverywhere = false;
};
[[nodiscard]] not_null<SpoilerMessCache*> DefaultSpoilerCache(); [[nodiscard]] not_null<SpoilerMessCache*> DefaultSpoilerCache();
[[nodiscard]] GeometryDescriptor SimpleGeometry(
int availableWidth,
int fontHeight,
int elisionHeight,
int elisionRemoveFromEnd,
bool elisionOneLine,
bool elisionBreakEverywhere);
struct PaintContext { struct PaintContext {
QPoint position; QPoint position;
int outerWidth = 0; // For automatic RTL Ui inversion. int outerWidth = 0; // For automatic RTL Ui inversion.
int availableWidth = 0; int availableWidth = 0;
GeometryDescriptor geometry; // By default is SimpleGeometry.
style::align align = style::al_left; style::align align = style::al_left;
QRect clip; QRect clip;
const style::TextPalette *palette = nullptr; const style::TextPalette *palette = nullptr;
std::span<SpecialColor> colors;
SpoilerMessCache *spoiler = nullptr; SpoilerMessCache *spoiler = nullptr;
crl::time now = 0; crl::time now = 0;
bool paused = false; bool paused = false;
@ -144,8 +168,9 @@ struct PaintContext {
TextSelection selection; TextSelection selection;
bool fullWidthSelection = true; bool fullWidthSelection = true;
int elisionLines = 0; int elisionHeight = 0;
int elisionRemoveFromEnd = 0; int elisionRemoveFromEnd = 0;
bool elisionOneLine = false;
bool elisionBreakEverywhere = false; bool elisionBreakEverywhere = false;
}; };
@ -167,14 +192,42 @@ public:
String &operator=(String &&other); String &operator=(String &&other);
~String(); ~String();
[[nodiscard]] int countWidth(int width, bool breakEverywhere = false) const; [[nodiscard]] int countWidth(
[[nodiscard]] int countHeight(int width, bool breakEverywhere = false) const; int width,
void countLineWidths(int width, QVector<int> *lineWidths, bool breakEverywhere = false) const; bool breakEverywhere = false) const;
[[nodiscard]] int countHeight(
int width,
bool breakEverywhere = false) const;
struct LineWidthsOptions {
bool breakEverywhere = false;
int reserve = 0;
};
[[nodiscard]] std::vector<int> countLineWidths(int width) const;
[[nodiscard]] std::vector<int> countLineWidths(
int width,
LineWidthsOptions options) const;
struct DimensionsResult {
int width = 0;
int height = 0;
std::vector<int> lineWidths;
};
struct DimensionsRequest {
bool lineWidths = false;
int reserve = 0;
};
[[nodiscard]] DimensionsResult countDimensions(
GeometryDescriptor geometry) const;
[[nodiscard]] DimensionsResult countDimensions(
GeometryDescriptor geometry,
DimensionsRequest request) const;
void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = kDefaultTextOptions); void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = kDefaultTextOptions);
void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = kMarkupTextOptions, const std::any &context = {}); void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = kMarkupTextOptions, const std::any &context = {});
[[nodiscard]] bool hasLinks() const; [[nodiscard]] bool hasLinks() const;
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); void setLink(uint16 index, const ClickHandlerPtr &lnk);
[[nodiscard]] bool hasSpoilers() const; [[nodiscard]] bool hasSpoilers() const;
void setSpoilerRevealed(bool revealed, anim::type animated); void setSpoilerRevealed(bool revealed, anim::type animated);
@ -193,6 +246,10 @@ public:
[[nodiscard]] int countMaxMonospaceWidth() const; [[nodiscard]] int countMaxMonospaceWidth() const;
void draw(QPainter &p, const PaintContext &context) const; void draw(QPainter &p, const PaintContext &context) const;
[[nodiscard]] StateResult getState(
QPoint point,
GeometryDescriptor geometry,
StateRequest request = StateRequest()) const;
void draw(Painter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }, bool fullWidthSelection = true) const; void draw(Painter &p, int32 left, int32 top, int32 width, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, TextSelection selection = { 0, 0 }, bool fullWidthSelection = true) const;
void drawElided(Painter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const; void drawElided(Painter &p, int32 left, int32 top, int32 width, int32 lines = 1, style::align align = style::al_left, int32 yFrom = 0, int32 yTo = -1, int32 removeFromEnd = 0, bool breakEverywhere = false, TextSelection selection = { 0, 0 }) const;
@ -298,7 +355,7 @@ private:
TextBlocks _blocks; TextBlocks _blocks;
TextLinks _links; TextLinks _links;
Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto; Qt::LayoutDirection _startDirection = Qt::LayoutDirectionAuto;
SpoilerDataWrap _spoiler; SpoilerDataWrap _spoiler;
@ -313,7 +370,7 @@ private:
[[nodiscard]] bool IsLinkEnd(QChar ch); [[nodiscard]] bool IsLinkEnd(QChar ch);
[[nodiscard]] bool IsNewline(QChar ch); [[nodiscard]] bool IsNewline(QChar ch);
[[nodiscard]] bool IsSpace(QChar ch); [[nodiscard]] bool IsSpace(QChar ch);
[[nodiscard]] bool IsDiac(QChar ch); [[nodiscard]] bool IsDiacritic(QChar ch);
[[nodiscard]] bool IsReplacedBySpace(QChar ch); [[nodiscard]] bool IsReplacedBySpace(QChar ch);
[[nodiscard]] bool IsTrimmed(QChar ch); [[nodiscard]] bool IsTrimmed(QChar ch);

View file

@ -47,7 +47,8 @@ struct LineBreakHelper {
void calculateRightBearingForPreviousGlyph(); void calculateRightBearingForPreviousGlyph();
// 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.
//static const QFixed RightBearingNotCalculated; //static const QFixed RightBearingNotCalculated;
@ -156,15 +157,6 @@ void addNextCluster(
} while (glyphPosition < current.num_glyphs } while (glyphPosition < current.num_glyphs
&& !glyphs.attributes[glyphPosition].clusterStart); && !glyphs.attributes[glyphPosition].clusterStart);
if (!((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition)) {
auto str = QStringList();
for (auto i = 0; i < pos; ++i) {
str.append(QString::number(logClusters[i]));
}
LOG(("text: %1 (from: %2, length: %3) part: %4").arg(DebugCurrentParsingString).arg(DebugCurrentParsingFrom).arg(DebugCurrentParsingLength).arg(DebugCurrentParsingPart));
LOG(("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()");
}
Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) Q_ASSERT((pos == end && glyphPosition == current.num_glyphs)
|| logClusters[pos] == glyphPosition); || logClusters[pos] == glyphPosition);
@ -176,32 +168,36 @@ void addNextCluster(
class BlockParser { class BlockParser {
public: public:
BlockParser( BlockParser(
QTextEngine *e, TextBlock &block,
TextBlock *b, QTextEngine &engine,
QFixed minResizeWidth, QFixed minResizeWidth,
int blockFrom, int blockPosition,
const QString &str); const QString &text);
private: private:
void parseWords(QFixed minResizeWidth, int blockFrom); void parseWords(QFixed minResizeWidth, int blockFrom);
bool isLineBreak(const QCharAttributes *attributes, int index); [[nodiscard]] bool isLineBreak(
bool isSpaceBreak(const QCharAttributes *attributes, int index); const QCharAttributes *attributes,
int index) const;
[[nodiscard]] bool isSpaceBreak(
const QCharAttributes *attributes,
int index) const;
TextBlock *block; TextBlock &block;
QTextEngine *eng; QTextEngine &engine;
const QString &str; const QString &text;
}; };
BlockParser::BlockParser( BlockParser::BlockParser(
QTextEngine *e, TextBlock &block,
TextBlock *b, QTextEngine &engine,
QFixed minResizeWidth, QFixed minResizeWidth,
int blockFrom, int blockFrom,
const QString &str) const QString &text)
: block(b) : block(block)
, eng(e) , engine(engine)
, str(str) { , text(text) {
parseWords(minResizeWidth, blockFrom); parseWords(minResizeWidth, blockFrom);
} }
@ -221,15 +217,15 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
// LOG(("Text: %1, chars: %2").arg(str).arg(debugChars)); // LOG(("Text: %1, chars: %2").arg(str).arg(debugChars));
int item = -1; int item = -1;
int newItem = eng->findItem(0); int newItem = engine.findItem(0);
const QCharAttributes *attributes = eng->attributes(); const QCharAttributes *attributes = engine.attributes();
if (!attributes) if (!attributes)
return; return;
int end = 0; int end = 0;
lbh.logClusters = eng->layoutData->logClustersPtr; lbh.logClusters = engine.layoutData->logClustersPtr;
block->_words.clear(); block._words.clear();
int wordStart = lbh.currentPosition; int wordStart = lbh.currentPosition;
@ -237,26 +233,26 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
int lastGraphemeBoundaryPosition = -1; int lastGraphemeBoundaryPosition = -1;
ScriptLine lastGraphemeBoundaryLine; ScriptLine lastGraphemeBoundaryLine;
while (newItem < eng->layoutData->items.size()) { while (newItem < engine.layoutData->items.size()) {
if (newItem != item) { if (newItem != item) {
item = newItem; item = newItem;
const QScriptItem &current = eng->layoutData->items[item]; const QScriptItem &current = engine.layoutData->items[item];
if (!current.num_glyphs) { if (!current.num_glyphs) {
eng->shape(item); engine.shape(item);
attributes = eng->attributes(); attributes = engine.attributes();
if (!attributes) if (!attributes)
return; return;
lbh.logClusters = eng->layoutData->logClustersPtr; lbh.logClusters = engine.layoutData->logClustersPtr;
} }
lbh.currentPosition = current.position; lbh.currentPosition = current.position;
end = current.position + eng->length(item); end = current.position + engine.length(item);
lbh.glyphs = eng->shapedGlyphs(&current); lbh.glyphs = engine.shapedGlyphs(&current);
QFontEngine *fontEngine = eng->fontEngine(current); QFontEngine *fontEngine = engine.fontEngine(current);
if (lbh.fontEngine != fontEngine) { if (lbh.fontEngine != fontEngine) {
lbh.fontEngine = fontEngine; lbh.fontEngine = fontEngine;
} }
} }
const QScriptItem &current = eng->layoutData->items[item]; const QScriptItem &current = engine.layoutData->items[item];
const auto atSpaceBreak = [&] { const auto atSpaceBreak = [&] {
for (auto index = lbh.currentPosition; index < end; ++index) { for (auto index = lbh.currentPosition; index < end; ++index) {
@ -269,15 +265,25 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
return false; return false;
}(); }();
if (atSpaceBreak) { if (atSpaceBreak) {
while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace) while (lbh.currentPosition < end
addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount, && attributes[lbh.currentPosition].whiteSpace)
current, lbh.logClusters, lbh.glyphs); addNextCluster(
lbh.currentPosition,
end,
lbh.spaceData,
lbh.glyphCount,
current,
lbh.logClusters,
lbh.glyphs);
if (block->_words.isEmpty()) { if (block._words.isEmpty()) {
block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing())); block._words.push_back(TextWord(
wordStart + blockFrom,
lbh.tmpData.textWidth,
-lbh.negativeRightBearing()));
} }
block->_words.back().add_rpadding(lbh.spaceData.textWidth); block._words.back().add_rpadding(lbh.spaceData.textWidth);
block->_width += lbh.spaceData.textWidth; block._width += lbh.spaceData.textWidth;
lbh.spaceData.length = 0; lbh.spaceData.length = 0;
lbh.spaceData.textWidth = 0; lbh.spaceData.textWidth = 0;
@ -288,15 +294,24 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
lastGraphemeBoundaryLine = ScriptLine(); lastGraphemeBoundaryLine = ScriptLine();
} else { } else {
do { do {
addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount, addNextCluster(
current, lbh.logClusters, lbh.glyphs); lbh.currentPosition,
end,
lbh.tmpData,
lbh.glyphCount,
current,
lbh.logClusters,
lbh.glyphs);
if (lbh.currentPosition >= eng->layoutData->string.length() if (lbh.currentPosition >= engine.layoutData->string.length()
|| isSpaceBreak(attributes, lbh.currentPosition) || isSpaceBreak(attributes, lbh.currentPosition)
|| isLineBreak(attributes, lbh.currentPosition)) { || isLineBreak(attributes, lbh.currentPosition)) {
lbh.calculateRightBearing(); lbh.calculateRightBearing();
block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing())); block._words.push_back(TextWord(
block->_width += lbh.tmpData.textWidth; wordStart + blockFrom,
lbh.tmpData.textWidth,
-lbh.negativeRightBearing()));
block._width += lbh.tmpData.textWidth;
lbh.tmpData.textWidth = 0; lbh.tmpData.textWidth = 0;
lbh.tmpData.length = 0; lbh.tmpData.length = 0;
wordStart = lbh.currentPosition; wordStart = lbh.currentPosition;
@ -305,8 +320,11 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) { if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) {
if (lastGraphemeBoundaryPosition >= 0) { if (lastGraphemeBoundaryPosition >= 0) {
lbh.calculateRightBearingForPreviousGlyph(); lbh.calculateRightBearingForPreviousGlyph();
block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, -lbh.negativeRightBearing())); block._words.push_back(TextWord(
block->_width += lastGraphemeBoundaryLine.textWidth; wordStart + blockFrom,
-lastGraphemeBoundaryLine.textWidth,
-lbh.negativeRightBearing()));
block._width += lastGraphemeBoundaryLine.textWidth;
lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth; lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth;
lbh.tmpData.length -= lastGraphemeBoundaryLine.length; lbh.tmpData.length -= lastGraphemeBoundaryLine.length;
wordStart = lastGraphemeBoundaryPosition; wordStart = lastGraphemeBoundaryPosition;
@ -315,8 +333,11 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
} }
if (addingEachGrapheme) { if (addingEachGrapheme) {
lbh.calculateRightBearing(); lbh.calculateRightBearing();
block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, -lbh.negativeRightBearing())); block._words.push_back(TextWord(
block->_width += lbh.tmpData.textWidth; wordStart + blockFrom,
-lbh.tmpData.textWidth,
-lbh.negativeRightBearing()));
block._width += lbh.tmpData.textWidth;
lbh.tmpData.textWidth = 0; lbh.tmpData.textWidth = 0;
lbh.tmpData.length = 0; lbh.tmpData.length = 0;
wordStart = lbh.currentPosition; wordStart = lbh.currentPosition;
@ -331,54 +352,87 @@ void BlockParser::parseWords(QFixed minResizeWidth, int blockFrom) {
if (lbh.currentPosition == end) if (lbh.currentPosition == end)
newItem = item + 1; newItem = item + 1;
} }
if (!block->_words.isEmpty()) { if (!block._words.isEmpty()) {
block->_rpadding = block->_words.back().f_rpadding(); block._rpadding = block._words.back().f_rpadding();
block->_width -= block->_rpadding; block._width -= block._rpadding;
block->_words.squeeze(); block._words.squeeze();
} }
} }
bool BlockParser::isLineBreak( bool BlockParser::isLineBreak(
const QCharAttributes *attributes, const QCharAttributes *attributes,
int index) { int index) const {
// Don't break after / in links. // Don't break after / in links.
return attributes[index].lineBreak return attributes[index].lineBreak
&& (block->lnkIndex() <= 0 && (block.linkIndex() <= 0 || index <= 0 || text[index - 1] != '/');
|| index <= 0
|| str[index - 1] != '/');
} }
bool BlockParser::isSpaceBreak( bool BlockParser::isSpaceBreak(
const QCharAttributes *attributes, const QCharAttributes *attributes,
int index) { int index) const {
// Don't break on &nbsp; // Don't break on &nbsp;
return attributes[index].whiteSpace return attributes[index].whiteSpace && (text[index] != QChar::Nbsp);
&& (str[index] != QChar::Nbsp); }
style::font WithFlags(
const style::font &font,
TextBlockFlags flags,
uint32 fontFlags) {
using namespace style::internal;
if (!flags && !fontFlags) {
return font;
} else if (IsMono(flags) || (fontFlags & FontMonospace)) {
return font->monospace();
}
auto result = font;
if ((flags & TextBlockFlag::Bold) || (fontFlags & FontBold)) {
result = result->bold();
} else if ((flags & TextBlockFlag::Semibold)
|| (fontFlags & FontSemibold)) {
result = result->semibold();
}
if ((flags & TextBlockFlag::Italic) || (fontFlags & FontItalic)) {
result = result->italic();
}
if ((flags & TextBlockFlag::Underline) || (fontFlags & FontUnderline)) {
result = result->underline();
}
if ((flags & TextBlockFlag::StrikeOut) || (fontFlags & FontStrikeOut)) {
result = result->strikeout();
}
if (flags & TextBlockFlag::Tilde) { // Tilde fix in OpenSans.
result = result->semibold();
}
return result;
} }
AbstractBlock::AbstractBlock( AbstractBlock::AbstractBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
TextBlockType type,
uint16 flags, uint16 flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex) uint16 colorIndex)
: _flags((flags & 0b1111111111) | ((lnkIndex & 0xFFFF) << 14)) : _position(position)
, _from(from) , _type(static_cast<uint16>(type))
, _spoilerIndex(spoilerIndex) { , _flags(flags)
, _linkIndex(linkIndex)
, _colorIndex(colorIndex) {
} }
uint16 AbstractBlock::from() const { uint16 AbstractBlock::position() const {
return _from; return _position;
} }
int AbstractBlock::width() const { TextBlockType AbstractBlock::type() const {
return _width.toInt(); return static_cast<TextBlockType>(_type);
} }
int AbstractBlock::rpadding() const { TextBlockFlags AbstractBlock::flags() const {
return _rpadding.toInt(); return TextBlockFlags::from_raw(_flags);
} }
QFixed AbstractBlock::f_width() const { QFixed AbstractBlock::f_width() const {
@ -389,77 +443,54 @@ QFixed AbstractBlock::f_rpadding() const {
return _rpadding; return _rpadding;
} }
uint16 AbstractBlock::lnkIndex() const { uint16 AbstractBlock::linkIndex() const {
return (_flags >> 14) & 0xFFFF; return _linkIndex;
} }
void AbstractBlock::setLnkIndex(uint16 lnkIndex) { uint16 AbstractBlock::colorIndex() const {
_flags = (_flags & ~(0xFFFF << 14)) | (lnkIndex << 14); return _colorIndex;
} }
uint16 AbstractBlock::spoilerIndex() const { void AbstractBlock::setLinkIndex(uint16 index) {
return _spoilerIndex; _linkIndex = index;
}
void AbstractBlock::setSpoilerIndex(uint16 spoilerIndex) {
_spoilerIndex = spoilerIndex;
}
TextBlockType AbstractBlock::type() const {
return TextBlockType((_flags >> 10) & 0x0F);
}
int32 AbstractBlock::flags() const {
return (_flags & 0b1111111111);
} }
QFixed AbstractBlock::f_rbearing() const { QFixed AbstractBlock::f_rbearing() const {
return (type() == TextBlockTText) return (type() == TextBlockType::Text)
? static_cast<const TextBlock*>(this)->real_f_rbearing() ? static_cast<const TextBlock*>(this)->real_f_rbearing()
: 0; : 0;
} }
TextBlock::TextBlock( TextBlock::TextBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
QFixed minResizeWidth, uint16 position,
uint16 from,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex) uint16 colorIndex,
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) { QFixed minResizeWidth)
_flags |= ((TextBlockTText & 0x0F) << 10); : AbstractBlock(
if (length) { font,
style::font blockFont = font; text,
if (!flags && lnkIndex) { position,
length,
TextBlockType::Text,
flags,
linkIndex,
colorIndex) {
if (!length) {
return;
}
const auto blockFont = WithFlags(font, flags);
if (!flags && linkIndex) {
// should use TextStyle lnkFlags somehow... not supported // should use TextStyle lnkFlags somehow... not supported
} }
if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) { const auto part = text.mid(_position, length);
blockFont = blockFont->monospace();
} else {
if (flags & TextBlockFBold) {
blockFont = blockFont->bold();
} else if (flags & TextBlockFSemibold) {
blockFont = blockFont->semibold();
}
if (flags & TextBlockFItalic) blockFont = blockFont->italic();
if (flags & TextBlockFUnderline) blockFont = blockFont->underline();
if (flags & TextBlockFStrikeOut) blockFont = blockFont->strikeout();
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
blockFont = blockFont->semibold();
}
}
DebugCurrentParsingString = str;
DebugCurrentParsingFrom = _from;
DebugCurrentParsingLength = length;
const auto part = DebugCurrentParsingPart = str.mid(_from, length);
QStackTextEngine engine(part, blockFont->f); QStackTextEngine engine(part, blockFont->f);
BlockParser parser(&engine, this, minResizeWidth, _from, part); BlockParser parser(*this, engine, minResizeWidth, _position, part);
}
} }
QFixed TextBlock::real_f_rbearing() const { QFixed TextBlock::real_f_rbearing() const {
@ -468,20 +499,27 @@ QFixed TextBlock::real_f_rbearing() const {
EmojiBlock::EmojiBlock( EmojiBlock::EmojiBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex, uint16 colorIndex,
EmojiPtr emoji) EmojiPtr emoji)
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) : AbstractBlock(
font,
text,
position,
length,
TextBlockType::Emoji,
flags,
linkIndex,
colorIndex)
, _emoji(emoji) { , _emoji(emoji) {
_flags |= ((TextBlockTEmoji & 0x0F) << 10);
_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 = text[_position + (--i)];
if (ch.unicode() == QChar::Space) { if (ch.unicode() == QChar::Space) {
_rpadding += font->spacew; _rpadding += font->spacew;
} else { } else {
@ -492,20 +530,27 @@ EmojiBlock::EmojiBlock(
CustomEmojiBlock::CustomEmojiBlock( CustomEmojiBlock::CustomEmojiBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex, uint16 colorIndex,
std::unique_ptr<CustomEmoji> custom) std::unique_ptr<CustomEmoji> custom)
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) : AbstractBlock(
font,
text,
position,
length,
TextBlockType::CustomEmoji,
flags,
linkIndex,
colorIndex)
, _custom(std::move(custom)) { , _custom(std::move(custom)) {
_flags |= ((TextBlockTCustomEmoji & 0x0F) << 10);
_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 = text[_position + (--i)];
if (ch.unicode() == QChar::Space) { if (ch.unicode() == QChar::Space) {
_rpadding += font->spacew; _rpadding += font->spacew;
} else { } else {
@ -516,32 +561,46 @@ CustomEmojiBlock::CustomEmojiBlock(
NewlineBlock::NewlineBlock( NewlineBlock::NewlineBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex) uint16 colorIndex)
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) { : AbstractBlock(
_flags |= ((TextBlockTNewline & 0x0F) << 10); font,
text,
position,
length,
TextBlockType::Newline,
flags,
linkIndex,
colorIndex) {
} }
Qt::LayoutDirection NewlineBlock::nextDirection() const { Qt::LayoutDirection NewlineBlock::nextDirection() const {
return _nextDir; return _nextDirection;
} }
SkipBlock::SkipBlock( SkipBlock::SkipBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
int32 w, int32 width,
int32 h, int32 height,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex) uint16 colorIndex)
: AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex) : AbstractBlock(
, _height(h) { font,
_flags |= ((TextBlockTSkip & 0x0F) << 10); text,
_width = w; position,
1,
TextBlockType::Skip,
0,
linkIndex,
colorIndex)
, _height(height) {
_width = width;
} }
int SkipBlock::height() const { int SkipBlock::height() const {
@ -550,11 +609,11 @@ int SkipBlock::height() const {
TextWord::TextWord( TextWord::TextWord(
uint16 from, uint16 position,
QFixed width, QFixed width,
QFixed rbearing, QFixed rbearing,
QFixed rpadding) QFixed rpadding)
: _from(from) : _position(position)
, _rbearing((rbearing.value() > 0x7FFF) , _rbearing((rbearing.value() > 0x7FFF)
? 0x7FFF ? 0x7FFF
: (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())) : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value()))
@ -562,8 +621,8 @@ TextWord::TextWord(
, _rpadding(rpadding) { , _rpadding(rpadding) {
} }
uint16 TextWord::from() const { uint16 TextWord::position() const {
return _from; return _position;
} }
QFixed TextWord::f_rbearing() const { QFixed TextWord::f_rbearing() const {
@ -588,19 +647,20 @@ Block::Block() {
Block::Block(Block &&other) { Block::Block(Block &&other) {
switch (other->type()) { switch (other->type()) {
case TextBlockTNewline: case TextBlockType::Newline:
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>())); emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
break; break;
case TextBlockTText: case TextBlockType::Text:
emplace<TextBlock>(std::move(other.unsafe<TextBlock>())); emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
break; break;
case TextBlockTEmoji: case TextBlockType::Emoji:
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>())); emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
break; break;
case TextBlockTCustomEmoji: case TextBlockType::CustomEmoji:
emplace<CustomEmojiBlock>(std::move(other.unsafe<CustomEmojiBlock>())); emplace<CustomEmojiBlock>(
std::move(other.unsafe<CustomEmojiBlock>()));
break; break;
case TextBlockTSkip: case TextBlockType::Skip:
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>())); emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
break; break;
default: default:
@ -614,19 +674,20 @@ Block &Block::operator=(Block &&other) {
} }
destroy(); destroy();
switch (other->type()) { switch (other->type()) {
case TextBlockTNewline: case TextBlockType::Newline:
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>())); emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
break; break;
case TextBlockTText: case TextBlockType::Text:
emplace<TextBlock>(std::move(other.unsafe<TextBlock>())); emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
break; break;
case TextBlockTEmoji: case TextBlockType::Emoji:
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>())); emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
break; break;
case TextBlockTCustomEmoji: case TextBlockType::CustomEmoji:
emplace<CustomEmojiBlock>(std::move(other.unsafe<CustomEmojiBlock>())); emplace<CustomEmojiBlock>(
std::move(other.unsafe<CustomEmojiBlock>()));
break; break;
case TextBlockTSkip: case TextBlockType::Skip:
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>())); emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
break; break;
default: default:
@ -641,91 +702,98 @@ Block::~Block() {
Block Block::Newline( Block Block::Newline(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex) { uint16 colorIndex) {
return New<NewlineBlock>( return New<NewlineBlock>(
font, font,
str, text,
from, position,
length, length,
flags, flags,
lnkIndex, linkIndex,
spoilerIndex); colorIndex);
} }
Block Block::Text( Block Block::Text(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
QFixed minResizeWidth, uint16 position,
uint16 from,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex) { uint16 colorIndex,
QFixed minResizeWidth) {
return New<TextBlock>( return New<TextBlock>(
font, font,
str, text,
minResizeWidth, position,
from,
length, length,
flags, flags,
lnkIndex, linkIndex,
spoilerIndex); colorIndex,
minResizeWidth);
} }
Block Block::Emoji( Block Block::Emoji(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex, uint16 colorIndex,
EmojiPtr emoji) { EmojiPtr emoji) {
return New<EmojiBlock>( return New<EmojiBlock>(
font, font,
str, text,
from, position,
length, length,
flags, flags,
lnkIndex, linkIndex,
spoilerIndex, colorIndex,
emoji); emoji);
} }
Block Block::CustomEmoji( Block Block::CustomEmoji(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex, uint16 colorIndex,
std::unique_ptr<Text::CustomEmoji> custom) { std::unique_ptr<Text::CustomEmoji> custom) {
return New<CustomEmojiBlock>( return New<CustomEmojiBlock>(
font, font,
str, text,
from, position,
length, length,
flags, flags,
lnkIndex, linkIndex,
spoilerIndex, colorIndex,
std::move(custom)); std::move(custom));
} }
Block Block::Skip( Block Block::Skip(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
int32 w, int32 width,
int32 h, int32 height,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex) { uint16 colorIndex) {
return New<SkipBlock>(font, str, from, w, h, lnkIndex, spoilerIndex); return New<SkipBlock>(
font,
text,
position,
width,
height,
linkIndex,
colorIndex);
} }
AbstractBlock *Block::get() { AbstractBlock *Block::get() {
@ -754,19 +822,19 @@ const AbstractBlock &Block::operator*() const {
void Block::destroy() { void Block::destroy() {
switch (get()->type()) { switch (get()->type()) {
case TextBlockTNewline: case TextBlockType::Newline:
unsafe<NewlineBlock>().~NewlineBlock(); unsafe<NewlineBlock>().~NewlineBlock();
break; break;
case TextBlockTText: case TextBlockType::Text:
unsafe<TextBlock>().~TextBlock(); unsafe<TextBlock>().~TextBlock();
break; break;
case TextBlockTEmoji: case TextBlockType::Emoji:
unsafe<EmojiBlock>().~EmojiBlock(); unsafe<EmojiBlock>().~EmojiBlock();
break; break;
case TextBlockTCustomEmoji: case TextBlockType::CustomEmoji:
unsafe<CustomEmojiBlock>().~CustomEmojiBlock(); unsafe<CustomEmojiBlock>().~CustomEmojiBlock();
break; break;
case TextBlockTSkip: case TextBlockType::Skip:
unsafe<SkipBlock>().~SkipBlock(); unsafe<SkipBlock>().~SkipBlock();
break; break;
default: default:
@ -777,7 +845,7 @@ void Block::destroy() {
int CountBlockHeight( int CountBlockHeight(
const AbstractBlock *block, const AbstractBlock *block,
const style::TextStyle *st) { const style::TextStyle *st) {
return (block->type() == TextBlockTSkip) return (block->type() == TextBlockType::Skip)
? static_cast<const SkipBlock*>(block)->height() ? static_cast<const SkipBlock*>(block)->height()
: (st->lineHeight > st->font->height) : (st->lineHeight > st->font->height)
? st->lineHeight ? st->lineHeight

View file

@ -6,6 +6,7 @@
// //
#pragma once #pragma once
#include "base/flags.h"
#include "ui/text/text_custom_emoji.h" #include "ui/text/text_custom_emoji.h"
#include "ui/style/style_core.h" #include "ui/style/style_core.h"
#include "ui/emoji_config.h" #include "ui/emoji_config.h"
@ -20,59 +21,64 @@ struct TextStyle;
namespace Ui::Text { namespace Ui::Text {
enum TextBlockType { enum class TextBlockType : uint16 {
TextBlockTNewline = 0x01, Newline = 0x01,
TextBlockTText = 0x02, Text = 0x02,
TextBlockTEmoji = 0x03, Emoji = 0x03,
TextBlockTCustomEmoji = 0x04, CustomEmoji = 0x04,
TextBlockTSkip = 0x05, Skip = 0x05,
}; };
enum TextBlockFlags { enum class TextBlockFlag : uint16 {
TextBlockFBold = 0x01, Bold = 0x001,
TextBlockFItalic = 0x02, Italic = 0x002,
TextBlockFUnderline = 0x04, Underline = 0x004,
TextBlockFStrikeOut = 0x08, StrikeOut = 0x008,
TextBlockFTilde = 0x10, // tilde fix in OpenSans Tilde = 0x010, // Tilde fix in OpenSans.
TextBlockFSemibold = 0x20, Semibold = 0x020,
TextBlockFCode = 0x40, Code = 0x040,
TextBlockFPre = 0x80, Pre = 0x080,
TextBlockFPlainLink = 0x100, Spoiler = 0x100,
}; };
inline constexpr bool is_flag_type(TextBlockFlag) { return true; }
using TextBlockFlags = base::flags<TextBlockFlag>;
[[nodiscard]] style::font WithFlags(
const style::font &font,
TextBlockFlags flags,
uint32 fontFlags = 0);
class AbstractBlock { class AbstractBlock {
public: public:
uint16 from() const; [[nodiscard]] uint16 position() const;
int width() const; [[nodiscard]] TextBlockType type() const;
int rpadding() const; [[nodiscard]] TextBlockFlags flags() const;
QFixed f_width() const; [[nodiscard]] uint16 colorIndex() const;
QFixed f_rpadding() const; [[nodiscard]] uint16 linkIndex() const;
void setLinkIndex(uint16 index);
[[nodiscard]] QFixed f_width() const;
[[nodiscard]] QFixed f_rpadding() const;
// Should be virtual, but optimized throught type() call. // Should be virtual, but optimized throught type() call.
QFixed f_rbearing() const; [[nodiscard]] QFixed f_rbearing() const;
uint16 lnkIndex() const;
void setLnkIndex(uint16 lnkIndex);
uint16 spoilerIndex() const;
void setSpoilerIndex(uint16 spoilerIndex);
TextBlockType type() const;
int32 flags() const;
protected: protected:
AbstractBlock( AbstractBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
TextBlockType type,
uint16 flags, uint16 flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex); uint16 colorIndex);
uint32 _flags = 0; // 2 bits empty, 16 bits lnkIndex, 4 bits type, 10 bits flags uint16 _position = 0;
uint16 _from = 0; uint16 _type : 4 = 0;
uint16 _spoilerIndex = 0; uint16 _flags : 12 = 0;
uint16 _linkIndex = 0;
uint16 _colorIndex = 0;
QFixed _width = 0; QFixed _width = 0;
@ -90,16 +96,16 @@ public:
NewlineBlock( NewlineBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &str,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex); uint16 colorIndex);
Qt::LayoutDirection nextDirection() const; [[nodiscard]] Qt::LayoutDirection nextDirection() const;
private: private:
Qt::LayoutDirection _nextDir = Qt::LayoutDirectionAuto; Qt::LayoutDirection _nextDirection = Qt::LayoutDirectionAuto;
friend class String; friend class String;
friend class Parser; friend class Parser;
@ -110,17 +116,24 @@ private:
class TextWord final { class TextWord final {
public: public:
TextWord() = default; TextWord() = default;
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0); TextWord(
uint16 from() const; uint16 position,
QFixed f_rbearing() const; QFixed width,
QFixed f_width() const; QFixed rbearing,
QFixed f_rpadding() const; QFixed rpadding = 0);
[[nodiscard]] uint16 position() const;
[[nodiscard]] QFixed f_rbearing() const;
[[nodiscard]] QFixed f_width() const;
[[nodiscard]] QFixed f_rpadding() const;
void add_rpadding(QFixed padding); void add_rpadding(QFixed padding);
private: private:
uint16 _from = 0; uint16 _position = 0;
int16 _rbearing = 0; int16 _rbearing = 0;
QFixed _width, _rpadding; QFixed _width;
QFixed _rpadding;
}; };
@ -128,16 +141,16 @@ class TextBlock final : public AbstractBlock {
public: public:
TextBlock( TextBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
QFixed minResizeWidth, uint16 position,
uint16 from,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex); uint16 colorIndex,
QFixed minResizeWidth);
private: private:
QFixed real_f_rbearing() const; [[nodiscard]] QFixed real_f_rbearing() const;
QVector<TextWord> _words; QVector<TextWord> _words;
@ -153,12 +166,12 @@ class EmojiBlock final : public AbstractBlock {
public: public:
EmojiBlock( EmojiBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex, uint16 colorIndex,
EmojiPtr emoji); EmojiPtr emoji);
private: private:
@ -174,12 +187,12 @@ class CustomEmojiBlock final : public AbstractBlock {
public: public:
CustomEmojiBlock( CustomEmojiBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex, uint16 colorIndex,
std::unique_ptr<CustomEmoji> custom); std::unique_ptr<CustomEmoji> custom);
private: private:
@ -195,14 +208,14 @@ class SkipBlock final : public AbstractBlock {
public: public:
SkipBlock( SkipBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
int32 w, int32 width,
int32 h, int32 height,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex); uint16 colorIndex);
int height() const; [[nodiscard]] int height() const;
private: private:
int _height = 0; int _height = 0;
@ -222,51 +235,51 @@ public:
[[nodiscard]] static Block Newline( [[nodiscard]] static Block Newline(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex); uint16 colorIndex);
[[nodiscard]] static Block Text( [[nodiscard]] static Block Text(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
QFixed minResizeWidth, uint16 position,
uint16 from,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex); uint16 colorIndex,
QFixed minResizeWidth);
[[nodiscard]] static Block Emoji( [[nodiscard]] static Block Emoji(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex, uint16 colorIndex,
EmojiPtr emoji); EmojiPtr emoji);
[[nodiscard]] static Block CustomEmoji( [[nodiscard]] static Block CustomEmoji(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
uint16 length, uint16 length,
uint16 flags, TextBlockFlags flags,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex, uint16 colorIndex,
std::unique_ptr<CustomEmoji> custom); std::unique_ptr<CustomEmoji> custom);
[[nodiscard]] static Block Skip( [[nodiscard]] static Block Skip(
const style::font &font, const style::font &font,
const QString &str, const QString &text,
uint16 from, uint16 position,
int32 w, int32 width,
int32 h, int32 height,
uint16 lnkIndex, uint16 linkIndex,
uint16 spoilerIndex); uint16 colorIndex);
template <typename FinalBlock> template <typename FinalBlock>
[[nodiscard]] FinalBlock &unsafe() { [[nodiscard]] FinalBlock &unsafe() {
@ -325,8 +338,8 @@ private:
const AbstractBlock *block, const AbstractBlock *block,
const style::TextStyle *st); const style::TextStyle *st);
[[nodiscard]] inline bool IsMono(int32 flags) { [[nodiscard]] inline bool IsMono(TextBlockFlags flags) {
return (flags & TextBlockFPre) || (flags & TextBlockFCode); return (flags & TextBlockFlag::Pre) || (flags & TextBlockFlag::Code);
} }
} // namespace Ui::Text } // namespace Ui::Text

View file

@ -1358,7 +1358,7 @@ QString RemoveAccents(const QString &text) {
if (copying) result[i] = *ch; if (copying) result[i] = *ch;
continue; continue;
} }
if (IsDiac(*ch)) { if (IsDiacritic(*ch)) {
copying = true; copying = true;
--i; --i;
continue; continue;
@ -2047,9 +2047,9 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
auto offset = 0; auto offset = 0;
auto state = State(); auto state = State();
auto notClosedEntities = QVector<int>(); // Stack of indices. auto notClosedEntities = std::vector<int>(); // Stack of indices.
const auto closeOne = [&] { const auto closeOne = [&] {
Expects(!notClosedEntities.isEmpty()); Expects(!notClosedEntities.empty());
auto &entity = result[notClosedEntities.back()]; auto &entity = result[notClosedEntities.back()];
entity = { entity = {

View file

@ -26,7 +26,7 @@ enum class EntityType : uchar {
CustomEmoji, CustomEmoji,
BotCommand, BotCommand,
MediaTimestamp, MediaTimestamp,
PlainLink, // Senders in chat list, attachements in chat list, etc. Colorized, // Senders in chat list, attachments in chat list, etc.
Bold, Bold,
Semibold, Semibold,
@ -61,16 +61,16 @@ public:
int length, int length,
const QString &data = QString()); const QString &data = QString());
EntityType type() const { [[nodiscard]] EntityType type() const {
return _type; return _type;
} }
int offset() const { [[nodiscard]] int offset() const {
return _offset; return _offset;
} }
int length() const { [[nodiscard]] int length() const {
return _length; return _length;
} }
QString data() const { [[nodiscard]] QString data() const {
return _data; return _data;
} }
@ -103,7 +103,7 @@ public:
} }
} }
static int FirstMonospaceOffset( [[nodiscard]] static int FirstMonospaceOffset(
const EntitiesInText &entities, const EntitiesInText &entities,
int textLength); int textLength);
@ -253,7 +253,7 @@ enum {
TextParseHashtags = 0x008, TextParseHashtags = 0x008,
TextParseBotCommands = 0x010, TextParseBotCommands = 0x010,
TextParseMarkdown = 0x020, TextParseMarkdown = 0x020,
TextParsePlainLinks = 0x040, TextParseColorized = 0x040,
}; };
struct TextWithTags { struct TextWithTags {

View file

@ -26,8 +26,8 @@ constexpr auto kMaxDiacAfterSymbol = 2;
auto result = text; auto result = text;
const auto &preparsed = text.entities; const auto &preparsed = text.entities;
const bool parseLinks = (options.flags & TextParseLinks); const bool parseLinks = (options.flags & TextParseLinks);
const bool parsePlainLinks = (options.flags & TextParsePlainLinks); const bool parseColorized = (options.flags & TextParseColorized);
if (!preparsed.isEmpty() && (parseLinks || parsePlainLinks)) { if (!preparsed.isEmpty() && (parseLinks || parseColorized)) {
bool parseMentions = (options.flags & TextParseMentions); bool parseMentions = (options.flags & TextParseMentions);
bool parseHashtags = (options.flags & TextParseHashtags); bool parseHashtags = (options.flags & TextParseHashtags);
bool parseBotCommands = (options.flags & TextParseBotCommands); bool parseBotCommands = (options.flags & TextParseBotCommands);
@ -41,9 +41,6 @@ constexpr auto kMaxDiacAfterSymbol = 2;
if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) || if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) ||
(type == EntityType::Hashtag && !parseHashtags) || (type == EntityType::Hashtag && !parseHashtags) ||
(type == EntityType::Cashtag && !parseHashtags) || (type == EntityType::Cashtag && !parseHashtags) ||
(type == EntityType::PlainLink
&& !parsePlainLinks
&& !parseMarkdown) ||
(!parseLinks (!parseLinks
&& (type == EntityType::Url && (type == EntityType::Url
|| type == EntityType::CustomUrl)) || || type == EntityType::CustomUrl)) ||
@ -53,6 +50,8 @@ constexpr auto kMaxDiacAfterSymbol = 2;
|| type == EntityType::Italic || type == EntityType::Italic
|| type == EntityType::Underline || type == EntityType::Underline
|| type == EntityType::StrikeOut || type == EntityType::StrikeOut
|| type == EntityType::Colorized
|| type == EntityType::Spoiler
|| type == EntityType::Code || type == EntityType::Code
|| type == EntityType::Pre))) { || type == EntityType::Pre))) {
continue; continue;
@ -72,7 +71,7 @@ constexpr auto kMaxDiacAfterSymbol = 2;
: QFIXED_MAX; : QFIXED_MAX;
} }
// Open Sans tilde fix. // Tilde fix in OpenSans.
[[nodiscard]] bool ComputeCheckTilde(const style::TextStyle &st) { [[nodiscard]] bool ComputeCheckTilde(const style::TextStyle &st) {
const auto &font = st.font; const auto &font = st.font;
return (font->size() * style::DevicePixelRatio() == 13) return (font->size() * style::DevicePixelRatio() == 13)
@ -83,7 +82,7 @@ constexpr auto kMaxDiacAfterSymbol = 2;
} // namespace } // namespace
Parser::StartedEntity::StartedEntity(TextBlockFlags flags) Parser::StartedEntity::StartedEntity(TextBlockFlags flags)
: _value(flags) : _value(flags.value())
, _type(Type::Flags) { , _type(Type::Flags) {
Expects(_value >= 0 && _value < int(kStringLinkIndexShift)); Expects(_value >= 0 && _value < int(kStringLinkIndexShift));
} }
@ -102,12 +101,12 @@ Parser::StartedEntity::Type Parser::StartedEntity::type() const {
std::optional<TextBlockFlags> Parser::StartedEntity::flags() const { std::optional<TextBlockFlags> Parser::StartedEntity::flags() const {
if (_value < int(kStringLinkIndexShift) && (_type == Type::Flags)) { if (_value < int(kStringLinkIndexShift) && (_type == Type::Flags)) {
return TextBlockFlags(_value); return TextBlockFlags::from_raw(uint16(_value));
} }
return std::nullopt; return std::nullopt;
} }
std::optional<uint16> Parser::StartedEntity::lnkIndex() const { std::optional<uint16> Parser::StartedEntity::linkIndex() const {
if ((_value < int(kStringLinkIndexShift) && (_type == Type::IndexedLink)) if ((_value < int(kStringLinkIndexShift) && (_type == Type::IndexedLink))
|| (_value >= int(kStringLinkIndexShift) && (_type == Type::Link))) { || (_value >= int(kStringLinkIndexShift) && (_type == Type::Link))) {
return uint16(_value); return uint16(_value);
@ -115,8 +114,8 @@ std::optional<uint16> Parser::StartedEntity::lnkIndex() const {
return std::nullopt; return std::nullopt;
} }
std::optional<uint16> Parser::StartedEntity::spoilerIndex() const { std::optional<uint16> Parser::StartedEntity::colorIndex() const {
if (_value < int(kStringLinkIndexShift) && (_type == Type::Spoiler)) { if (_type == Type::Colorized) {
return uint16(_value); return uint16(_value);
} }
return std::nullopt; return std::nullopt;
@ -163,48 +162,62 @@ void Parser::blockCreated() {
} }
void Parser::createBlock(int32 skipBack) { void Parser::createBlock(int32 skipBack) {
if (_lnkIndex < kStringLinkIndexShift && _lnkIndex > _maxLnkIndex) { if (_linkIndex < kStringLinkIndexShift && _linkIndex > _maxLinkIndex) {
_maxLnkIndex = _lnkIndex; _maxLinkIndex = _linkIndex;
} }
if (_lnkIndex > kStringLinkIndexShift) { if (_linkIndex > kStringLinkIndexShift) {
_maxShiftedLnkIndex = std::max( _maxShiftedLinkIndex = std::max(
uint16(_lnkIndex - kStringLinkIndexShift), uint16(_linkIndex - kStringLinkIndexShift),
_maxShiftedLnkIndex); _maxShiftedLinkIndex);
} }
int32 len = int32(_t->_text.size()) + skipBack - _blockStart; const auto length = int32(_t->_text.size()) + skipBack - _blockStart;
if (len > 0) { if (length <= 0) {
bool newline = !_emoji && (len == 1 && _t->_text.at(_blockStart) == QChar::LineFeed); return;
}
const auto newline = !_emoji
&& (length == 1)
&& (_t->_text.at(_blockStart) == QChar::LineFeed);
if (_newlineAwaited) { if (_newlineAwaited) {
_newlineAwaited = false; _newlineAwaited = false;
if (!newline) { if (!newline) {
_t->_text.insert(_blockStart, QChar::LineFeed); _t->_text.insert(_blockStart, QChar::LineFeed);
createBlock(skipBack - len); createBlock(skipBack - length);
} }
} }
const auto lnkIndex = _monoIndex ? _monoIndex : _lnkIndex; const auto linkIndex = _monoIndex ? _monoIndex : _linkIndex;
auto custom = _customEmojiData.isEmpty() auto custom = _customEmojiData.isEmpty()
? nullptr ? nullptr
: Integration::Instance().createCustomEmoji( : Integration::Instance().createCustomEmoji(
_customEmojiData, _customEmojiData,
_context); _context);
const auto push = [&](auto &&factory, auto &&...args) {
_t->_blocks.push_back(factory(
_t->_st->font,
_t->_text,
_blockStart,
length,
_flags,
linkIndex,
_colorIndex,
std::forward<decltype(args)>(args)...));
};
if (custom) { if (custom) {
_t->_blocks.push_back(Block::CustomEmoji(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex, std::move(custom))); push(&Block::CustomEmoji, std::move(custom));
} else if (_emoji) { } else if (_emoji) {
_t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex, _emoji)); push(&Block::Emoji, _emoji);
} else if (newline) { } else if (newline) {
_t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, lnkIndex, _spoilerIndex)); push(&Block::Newline);
} else { } else {
_t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, lnkIndex, _spoilerIndex)); push(&Block::Text, _t->_minResizeWidth);
} }
// Diacritic can't attach from the next block to this one. // Diacritic can't attach from the next block to this one.
_allowDiacritic = false; _allowDiacritic = false;
_blockStart += len; _blockStart += length;
_customEmojiData = QByteArray(); _customEmojiData = QByteArray();
_emoji = nullptr; _emoji = nullptr;
blockCreated(); blockCreated();
} }
}
void Parser::createNewlineBlock() { void Parser::createNewlineBlock() {
createBlock(); createBlock();
@ -226,24 +239,24 @@ void Parser::finishEntities() {
if (_flags & (*flags)) { if (_flags & (*flags)) {
createBlock(); createBlock();
_flags &= ~(*flags); _flags &= ~(*flags);
if (((*flags) & TextBlockFPre) if (((*flags) & TextBlockFlag::Pre)
&& !_t->_blocks.empty() && !_t->_blocks.empty()
&& _t->_blocks.back()->type() != TextBlockTNewline) { && _t->_blocks.back()->type() != TextBlockType::Newline) {
_newlineAwaited = true; _newlineAwaited = true;
} }
if (IsMono(*flags)) { if (IsMono(*flags)) {
_monoIndex = 0; _monoIndex = 0;
} }
} }
} else if (const auto lnkIndex = list.back().lnkIndex()) { } else if (const auto linkIndex = list.back().linkIndex()) {
if (_lnkIndex == *lnkIndex) { if (_linkIndex == *linkIndex) {
createBlock(); createBlock();
_lnkIndex = 0; _linkIndex = 0;
} }
} else if (const auto spoilerIndex = list.back().spoilerIndex()) { } else if (const auto colorIndex = list.back().colorIndex()) {
if (_spoilerIndex == *spoilerIndex && (_spoilerIndex != 0)) { if (_colorIndex == *colorIndex) {
createBlock(); createBlock();
_spoilerIndex = 0; _colorIndex = 0;
} }
} }
list.pop_back(); list.pop_back();
@ -289,26 +302,26 @@ bool Parser::checkEntities() {
_customEmojiData = _waitingEntity->data(); _customEmojiData = _waitingEntity->data();
_startedEntities[entityEnd].emplace_back(0, Type::CustomEmoji); _startedEntities[entityEnd].emplace_back(0, Type::CustomEmoji);
} else if (entityType == EntityType::Bold) { } else if (entityType == EntityType::Bold) {
flags = TextBlockFBold; flags = TextBlockFlag::Bold;
} else if (entityType == EntityType::Semibold) { } else if (entityType == EntityType::Semibold) {
flags = TextBlockFSemibold; flags = TextBlockFlag::Semibold;
} else if (entityType == EntityType::Italic) { } else if (entityType == EntityType::Italic) {
flags = TextBlockFItalic; flags = TextBlockFlag::Italic;
} else if (entityType == EntityType::Underline) { } else if (entityType == EntityType::Underline) {
flags = TextBlockFUnderline; flags = TextBlockFlag::Underline;
} else if (entityType == EntityType::PlainLink) { } else if (entityType == EntityType::Spoiler) {
flags = TextBlockFPlainLink; flags = TextBlockFlag::Spoiler;
} else if (entityType == EntityType::StrikeOut) { } else if (entityType == EntityType::StrikeOut) {
flags = TextBlockFStrikeOut; flags = TextBlockFlag::StrikeOut;
} else if ((entityType == EntityType::Code) // #TODO entities } else if ((entityType == EntityType::Code) // #TODO entities
|| (entityType == EntityType::Pre)) { || (entityType == EntityType::Pre)) {
if (entityType == EntityType::Code) { if (entityType == EntityType::Code) {
flags = TextBlockFCode; flags = TextBlockFlag::Code;
} else { } else {
flags = TextBlockFPre; flags = TextBlockFlag::Pre;
createBlock(); createBlock();
if (!_t->_blocks.empty() if (!_t->_blocks.empty()
&& _t->_blocks.back()->type() != TextBlockTNewline && _t->_blocks.back()->type() != TextBlockType::Newline
&& _customEmojiData.isEmpty()) { && _customEmojiData.isEmpty()) {
createNewlineBlock(); createNewlineBlock();
} }
@ -342,6 +355,14 @@ bool Parser::checkEntities() {
} }
} else if (entityType == EntityType::MentionName) { } else if (entityType == EntityType::MentionName) {
pushComplexUrl(); pushComplexUrl();
} else if (entityType == EntityType::Colorized) {
createBlock();
const auto data = _waitingEntity->data();
_colorIndex = data.isEmpty() ? 1 : (data.front().unicode() + 1);
_startedEntities[entityEnd].emplace_back(
_colorIndex,
Type::Colorized);
} }
if (link.type != EntityType::Invalid) { if (link.type != EntityType::Invalid) {
@ -350,9 +371,9 @@ bool Parser::checkEntities() {
_links.push_back(link); _links.push_back(link);
const auto tempIndex = _links.size(); const auto tempIndex = _links.size();
const auto useCustom = processCustomIndex(tempIndex); const auto useCustom = processCustomIndex(tempIndex);
_lnkIndex = tempIndex + (useCustom ? 0 : kStringLinkIndexShift); _linkIndex = tempIndex + (useCustom ? 0 : kStringLinkIndexShift);
_startedEntities[entityEnd].emplace_back( _startedEntities[entityEnd].emplace_back(
_lnkIndex, _linkIndex,
useCustom ? Type::IndexedLink : Type::Link); useCustom ? Type::IndexedLink : Type::Link);
} else if (flags) { } else if (flags) {
if (!(_flags & flags)) { if (!(_flags & flags)) {
@ -361,18 +382,6 @@ bool Parser::checkEntities() {
_startedEntities[entityEnd].emplace_back(flags); _startedEntities[entityEnd].emplace_back(flags);
_monoIndex = monoIndex; _monoIndex = monoIndex;
} }
} else if (entityType == EntityType::Spoiler) {
createBlock();
_spoilers.push_back(EntityLinkData{
.data = QString::number(_spoilers.size() + 1),
.type = entityType,
});
_spoilerIndex = _spoilers.size();
_startedEntities[entityEnd].emplace_back(
_spoilerIndex,
Type::Spoiler);
} }
++_waitingEntity; ++_waitingEntity;
@ -423,7 +432,7 @@ void Parser::parseCurrentChar() {
const auto inCustomEmoji = !_customEmojiData.isEmpty(); const auto inCustomEmoji = !_customEmojiData.isEmpty();
const auto isNewLine = !inCustomEmoji && _multiline && IsNewline(_ch); const auto isNewLine = !inCustomEmoji && _multiline && IsNewline(_ch);
const auto replaceWithSpace = IsSpace(_ch) && (_ch != QChar::Nbsp); const auto replaceWithSpace = IsSpace(_ch) && (_ch != QChar::Nbsp);
const auto isDiac = IsDiac(_ch); const auto isDiacritic = IsDiacritic(_ch);
const auto isTilde = !inCustomEmoji && _checkTilde && (_ch == '~'); const auto isTilde = !inCustomEmoji && _checkTilde && (_ch == '~');
const auto skip = [&] { const auto skip = [&] {
if (IsBad(_ch) || _ch.isLowSurrogate()) { if (IsBad(_ch) || _ch.isLowSurrogate()) {
@ -431,8 +440,10 @@ void Parser::parseCurrentChar() {
} 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 (isDiacritic) {
if (!_allowDiacritic || _emoji || ++_diacs > kMaxDiacAfterSymbol) { if (!_allowDiacritic
|| _emoji
|| ++_diacritics > kMaxDiacAfterSymbol) {
return true; return true;
} }
} else if (_ch.isHighSurrogate()) { } else if (_ch.isHighSurrogate()) {
@ -474,15 +485,15 @@ void Parser::parseCurrentChar() {
_ch = 0; _ch = 0;
_allowDiacritic = false; _allowDiacritic = false;
} else { } else {
if (isTilde) { // tilde fix in OpenSans if (isTilde) { // Tilde fix in OpenSans.
if (!(_flags & TextBlockFTilde)) { if (!(_flags & TextBlockFlag::Tilde)) {
createBlock(-_emojiLookback); createBlock(-_emojiLookback);
_flags |= TextBlockFTilde; _flags |= TextBlockFlag::Tilde;
} }
} else { } else {
if (_flags & TextBlockFTilde) { if (_flags & TextBlockFlag::Tilde) {
createBlock(-_emojiLookback); createBlock(-_emojiLookback);
_flags &= ~TextBlockFTilde; _flags &= ~TextBlockFlag::Tilde;
} }
} }
if (isNewLine) { if (isNewLine) {
@ -497,8 +508,8 @@ void Parser::parseCurrentChar() {
_t->_text.push_back(_ch); _t->_text.push_back(_ch);
_allowDiacritic = true; _allowDiacritic = true;
} }
if (!isDiac) { if (!isDiacritic) {
_diacs = 0; _diacritics = 0;
} }
} }
} }
@ -594,7 +605,7 @@ void Parser::trimSourceRange() {
// } // }
void Parser::finalize(const TextParseOptions &options) { void Parser::finalize(const TextParseOptions &options) {
_t->_links.resize(_maxLnkIndex + _maxShiftedLnkIndex); _t->_links.resize(_maxLinkIndex + _maxShiftedLinkIndex);
auto counterCustomIndex = uint16(0); auto counterCustomIndex = uint16(0);
auto currentIndex = uint16(0); // Current the latest index of _t->_links. auto currentIndex = uint16(0); // Current the latest index of _t->_links.
struct { struct {
@ -614,21 +625,21 @@ void Parser::finalize(const TextParseOptions &options) {
auto spacesCheckFrom = uint16(-1); auto spacesCheckFrom = uint16(-1);
const auto length = int(_t->_text.size()); const auto length = int(_t->_text.size());
for (auto &block : _t->_blocks) { for (auto &block : _t->_blocks) {
if (block->type() == TextBlockTCustomEmoji) { if (block->type() == TextBlockType::CustomEmoji) {
_t->_hasCustomEmoji = true; _t->_hasCustomEmoji = true;
} else if (block->type() != TextBlockTNewline } else if (block->type() != TextBlockType::Newline
&& block->type() != TextBlockTSkip) { && block->type() != TextBlockType::Skip) {
_t->_isOnlyCustomEmoji = false; _t->_isOnlyCustomEmoji = false;
} else if (block->lnkIndex()) { } else if (block->linkIndex()) {
_t->_isOnlyCustomEmoji = _t->_isIsolatedEmoji = false; _t->_isOnlyCustomEmoji = _t->_isIsolatedEmoji = false;
} }
if (!_t->_hasNotEmojiAndSpaces) { if (!_t->_hasNotEmojiAndSpaces) {
if (block->type() == TextBlockTText) { if (block->type() == TextBlockType::Text) {
if (spacesCheckFrom == uint16(-1)) { if (spacesCheckFrom == uint16(-1)) {
spacesCheckFrom = block->from(); spacesCheckFrom = block->position();
} }
} else if (spacesCheckFrom != uint16(-1)) { } else if (spacesCheckFrom != uint16(-1)) {
const auto checkTill = block->from(); const auto checkTill = block->position();
for (auto i = spacesCheckFrom; i != checkTill; ++i) { for (auto i = spacesCheckFrom; i != checkTill; ++i) {
Assert(i < length); Assert(i < length);
if (!_t->_text[i].isSpace()) { if (!_t->_text[i].isSpace()) {
@ -640,35 +651,35 @@ void Parser::finalize(const TextParseOptions &options) {
} }
} }
if (_t->_isIsolatedEmoji) { if (_t->_isIsolatedEmoji) {
if (block->type() == TextBlockTCustomEmoji if (block->type() == TextBlockType::CustomEmoji
|| block->type() == TextBlockTEmoji) { || block->type() == TextBlockType::Emoji) {
if (++isolatedEmojiCount > kIsolatedEmojiLimit) { if (++isolatedEmojiCount > kIsolatedEmojiLimit) {
_t->_isIsolatedEmoji = false; _t->_isIsolatedEmoji = false;
} }
} else if (block->type() != TextBlockTSkip) { } else if (block->type() != TextBlockType::Skip) {
_t->_isIsolatedEmoji = false; _t->_isIsolatedEmoji = false;
} }
} }
if (block->spoilerIndex()) { if (block->flags() & TextBlockFlag::Spoiler) {
if (!_t->_spoiler.data) { if (!_t->_spoiler.data) {
_t->_spoiler.data = std::make_unique<SpoilerData>( _t->_spoiler.data = std::make_unique<SpoilerData>(
Integration::Instance().createSpoilerRepaint(_context)); Integration::Instance().createSpoilerRepaint(_context));
} }
} }
const auto shiftedIndex = block->lnkIndex(); const auto shiftedIndex = block->linkIndex();
auto useCustomIndex = false; auto useCustomIndex = false;
if (shiftedIndex <= kStringLinkIndexShift) { if (shiftedIndex <= kStringLinkIndexShift) {
if (IsMono(block->flags()) && shiftedIndex) { if (IsMono(block->flags()) && shiftedIndex) {
const auto monoIndex = shiftedIndex; const auto monoIndex = shiftedIndex;
if (lastHandlerIndex.mono == monoIndex) { if (lastHandlerIndex.mono == monoIndex) {
block->setLnkIndex(currentIndex); block->setLinkIndex(currentIndex);
continue; // Optimization. continue; // Optimization.
} else { } else {
currentIndex++; currentIndex++;
} }
avoidIntersectionsWithCustom(); avoidIntersectionsWithCustom();
block->setLnkIndex(currentIndex); block->setLinkIndex(currentIndex);
const auto handler = Integration::Instance().createLinkHandler( const auto handler = Integration::Instance().createLinkHandler(
_monos[monoIndex - 1], _monos[monoIndex - 1],
_context); _context);
@ -693,7 +704,7 @@ void Parser::finalize(const TextParseOptions &options) {
? shiftedIndex ? shiftedIndex
: (shiftedIndex - kStringLinkIndexShift); : (shiftedIndex - kStringLinkIndexShift);
if (lastHandlerIndex.lnk == realIndex) { if (lastHandlerIndex.lnk == realIndex) {
block->setLnkIndex(usedIndex()); block->setLinkIndex(usedIndex());
continue; // Optimization. continue; // Optimization.
} else { } else {
(useCustomIndex ? counterCustomIndex : currentIndex)++; (useCustomIndex ? counterCustomIndex : currentIndex)++;
@ -701,7 +712,7 @@ void Parser::finalize(const TextParseOptions &options) {
if (!useCustomIndex) { if (!useCustomIndex) {
avoidIntersectionsWithCustom(); avoidIntersectionsWithCustom();
} }
block->setLnkIndex(usedIndex()); block->setLinkIndex(usedIndex());
_t->_links.resize(std::max(usedIndex(), uint16(_t->_links.size()))); _t->_links.resize(std::max(usedIndex(), uint16(_t->_links.size())));
const auto handler = Integration::Instance().createLinkHandler( const auto handler = Integration::Instance().createLinkHandler(

View file

@ -28,8 +28,8 @@ private:
Flags, Flags,
Link, Link,
IndexedLink, IndexedLink,
Spoiler,
CustomEmoji, CustomEmoji,
Colorized,
}; };
explicit StartedEntity(TextBlockFlags flags); explicit StartedEntity(TextBlockFlags flags);
@ -37,8 +37,8 @@ private:
[[nodiscard]] Type type() const; [[nodiscard]] Type type() const;
[[nodiscard]] std::optional<TextBlockFlags> flags() const; [[nodiscard]] std::optional<TextBlockFlags> flags() const;
[[nodiscard]] std::optional<uint16> lnkIndex() const; [[nodiscard]] std::optional<uint16> linkIndex() const;
[[nodiscard]] std::optional<uint16> spoilerIndex() const; [[nodiscard]] std::optional<uint16> colorIndex() const;
private: private:
const int _value = 0; const int _value = 0;
@ -96,23 +96,22 @@ private:
std::vector<uint16> _linksIndexes; std::vector<uint16> _linksIndexes;
std::vector<EntityLinkData> _links; std::vector<EntityLinkData> _links;
std::vector<EntityLinkData> _spoilers;
std::vector<EntityLinkData> _monos; std::vector<EntityLinkData> _monos;
base::flat_map< base::flat_map<
const QChar*, const QChar*,
std::vector<StartedEntity>> _startedEntities; std::vector<StartedEntity>> _startedEntities;
uint16 _maxLnkIndex = 0; uint16 _maxLinkIndex = 0;
uint16 _maxShiftedLnkIndex = 0; uint16 _maxShiftedLinkIndex = 0;
// current state // current state
int32 _flags = 0; TextBlockFlags _flags;
uint16 _lnkIndex = 0; uint16 _linkIndex = 0;
uint16 _spoilerIndex = 0; uint16 _colorIndex = 0;
uint16 _monoIndex = 0; uint16 _monoIndex = 0;
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero
int32 _blockStart = 0; // offset in result, from which current parsed block is started int32 _blockStart = 0; // offset in result, from which current parsed block is started
int32 _diacs = 0; // diac chars skipped without good char int32 _diacritics = 0; // diacritic chars skipped without good char
QFixed _sumWidth; QFixed _sumWidth;
bool _sumFinished = false; bool _sumFinished = false;
bool _newlineAwaited = false; bool _newlineAwaited = false;

View file

@ -173,6 +173,7 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
_p = &p; _p = &p;
_p->setFont(_t->_st->font); _p->setFont(_t->_st->font);
_palette = context.palette ? context.palette : &st::defaultTextPalette; _palette = context.palette ? context.palette : &st::defaultTextPalette;
_colors = context.colors;
_originalPen = _p->pen(); _originalPen = _p->pen();
_originalPenSelected = (_palette->selectFg->c.alphaF() == 0) _originalPenSelected = (_palette->selectFg->c.alphaF() == 0)
? _originalPen ? _originalPen
@ -184,18 +185,19 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
_yTo = context.clip.isNull() _yTo = context.clip.isNull()
? -1 ? -1
: (context.clip.y() + context.clip.height()); : (context.clip.y() + context.clip.height());
if (const auto lines = context.elisionLines) { _geometry = context.geometry.layout
if (_yTo < 0 || (_y + (lines - 1) * _t->_st->font->height) < _yTo) { ? context.geometry
_yTo = _y + (lines * _t->_st->font->height); : SimpleGeometry(
_elideLast = true; context.availableWidth,
_elideRemoveFromEnd = context.elisionRemoveFromEnd; _t->_st->font->height,
} context.elisionHeight,
_breakEverywhere = context.elisionBreakEverywhere; context.elisionRemoveFromEnd,
} context.elisionOneLine,
context.elisionBreakEverywhere);
_breakEverywhere = _geometry.breakEverywhere;
_spoilerCache = context.spoiler; _spoilerCache = context.spoiler;
_selection = context.selection; _selection = context.selection;
_fullWidthSelection = context.fullWidthSelection; _fullWidthSelection = context.fullWidthSelection;
_w = context.availableWidth;
_align = context.align; _align = context.align;
_cachedNow = context.now; _cachedNow = context.now;
_pausedEmoji = context.paused || context.pausedEmoji; _pausedEmoji = context.paused || context.pausedEmoji;
@ -209,34 +211,26 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
void Renderer::enumerate() { void Renderer::enumerate() {
_blocksSize = _t->_blocks.size(); _blocksSize = _t->_blocks.size();
_wLeft = _w;
if (_elideLast) {
_yToElide = _yTo;
if (_elideRemoveFromEnd > 0 && !_t->_blocks.empty()) {
int firstBlockHeight = CountBlockHeight(_t->_blocks.front().get(), _t->_st);
if (_y + firstBlockHeight >= _yToElide) {
_wLeft -= _elideRemoveFromEnd;
}
}
}
_str = _t->_text.unicode(); _str = _t->_text.unicode();
if (_p) { if (_p) {
auto clip = _p->hasClipping() ? _p->clipBoundingRect() : QRect(); const auto clip = _p->hasClipping() ? _p->clipBoundingRect() : QRect();
if (clip.width() > 0 || clip.height() > 0) { if (clip.width() > 0 || clip.height() > 0) {
if (_yFrom < clip.y()) _yFrom = clip.y(); if (_yFrom < clip.y()) {
if (_yTo < 0 || _yTo > clip.y() + clip.height()) _yTo = clip.y() + clip.height(); _yFrom = clip.y();
}
if (_yTo < 0 || _yTo > clip.y() + clip.height()) {
_yTo = clip.y() + clip.height();
}
} }
} }
_parDirection = _t->_startDir; _startLeft = _x.toInt();
if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = style::LayoutDirection(); _startTop = _y;
if ((*_t->_blocks.cbegin())->type() != TextBlockTNewline) {
initNextParagraph(_t->_blocks.cbegin());
}
_lineStart = 0; if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
_lineStartBlock = 0; initNextParagraph(_t->_blocks.cbegin(), _t->_startDirection);
}
_lineHeight = 0; _lineHeight = 0;
_fontHeight = _t->_st->font->height; _fontHeight = _t->_st->font->height;
@ -257,27 +251,23 @@ void Renderer::enumerate() {
auto _btype = b->type(); auto _btype = b->type();
auto blockHeight = CountBlockHeight(b, _t->_st); auto blockHeight = CountBlockHeight(b, _t->_st);
if (_btype == TextBlockTNewline) { if (_btype == TextBlockType::Newline) {
if (!_lineHeight) _lineHeight = blockHeight; if (!_lineHeight) {
if (!drawLine((*i)->from(), i, e)) { _lineHeight = blockHeight;
}
if (!drawLine((*i)->position(), i, e)) {
return; return;
} }
_y += _lineHeight; _y += _lineHeight;
_lineHeight = 0; _lineHeight = 0;
_lineStart = _t->countBlockEnd(i, e);
_lineStartBlock = blockIndex + 1;
last_rBearing = b->f_rbearing(); last_rBearing = 0;
_last_rPadding = b->f_rpadding(); _last_rPadding = 0;
_wLeft = _w - (b->f_width() - last_rBearing);
if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) {
_wLeft -= _elideRemoveFromEnd;
}
_parDirection = static_cast<const NewlineBlock*>(b)->nextDirection(); initNextParagraph(
if (_parDirection == Qt::LayoutDirectionAuto) _parDirection = style::LayoutDirection(); i + 1,
initNextParagraph(i + 1); static_cast<const NewlineBlock*>(b)->nextDirection());
longWordLine = true; longWordLine = true;
continue; continue;
@ -296,7 +286,7 @@ void Renderer::enumerate() {
continue; continue;
} }
if (_btype == TextBlockTText) { if (_btype == TextBlockType::Text) {
auto t = static_cast<const 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();
@ -332,10 +322,8 @@ void Renderer::enumerate() {
continue; continue;
} }
auto elidedLineHeight = qMax(_lineHeight, blockHeight); if (_elidedLine) {
auto elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide); _lineHeight = qMax(_lineHeight, blockHeight);
if (elidedLine) {
_lineHeight = elidedLineHeight;
} else if (f != j && !_breakEverywhere) { } else if (f != j && !_breakEverywhere) {
// word did not fit completely, so we roll back the state to the beginning of this long word // word did not fit completely, so we roll back the state to the beginning of this long word
j = f; j = f;
@ -343,20 +331,24 @@ void Renderer::enumerate() {
_lineHeight = f_lineHeight; _lineHeight = f_lineHeight;
j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width(); j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width();
} }
if (!drawLine(elidedLine ? ((j + 1 == en) ? _t->countBlockEnd(i, e) : (j + 1)->from()) : j->from(), i, e)) { const auto lineEnd = !_elidedLine
? j->position()
: (j + 1 != en)
? (j + 1)->position()
: _t->countBlockEnd(i, e);
if (!drawLine(lineEnd, i, e)) {
return; return;
} }
_y += _lineHeight; _y += _lineHeight;
_lineHeight = qMax(0, blockHeight); _lineHeight = qMax(0, blockHeight);
_lineStart = j->from(); _lineStart = j->position();
_lineStartBlock = blockIndex; _lineStartBlock = blockIndex;
_paragraphWidthRemaining -= (_lineWidth - _wLeft) - _last_rPadding + last_rBearing;
initNextLine();
last_rBearing = j->f_rbearing(); last_rBearing = j->f_rbearing();
_last_rPadding = j->f_rpadding(); _last_rPadding = j->f_rpadding();
_wLeft = _w - (j_width - last_rBearing); _wLeft -= j_width - last_rBearing;
if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) {
_wLeft -= _elideRemoveFromEnd;
}
longWordLine = !wordEndsHere; longWordLine = !wordEndsHere;
f = j + 1; f = j + 1;
@ -366,31 +358,33 @@ void Renderer::enumerate() {
continue; continue;
} }
auto elidedLineHeight = qMax(_lineHeight, blockHeight); if (_elidedLine) {
auto elidedLine = _elideLast && (_y + elidedLineHeight >= _yToElide); _lineHeight = qMax(_lineHeight, blockHeight);
if (elidedLine) {
_lineHeight = elidedLineHeight;
} }
if (!drawLine(elidedLine ? _t->countBlockEnd(i, e) : b->from(), i, e)) { const auto lineEnd = !_elidedLine
? b->position()
: _t->countBlockEnd(i, e);
if (!drawLine(lineEnd, i, e)) {
return; return;
} }
_y += _lineHeight; _y += _lineHeight;
_lineHeight = qMax(0, blockHeight); _lineHeight = qMax(0, blockHeight);
_lineStart = b->from(); _lineStart = b->position();
_lineStartBlock = blockIndex; _lineStartBlock = blockIndex;
_paragraphWidthRemaining -= (_lineWidth - _wLeft) - _last_rPadding + last_rBearing;
initNextLine();
last_rBearing = b__f_rbearing; last_rBearing = b__f_rbearing;
_last_rPadding = b->f_rpadding(); _last_rPadding = b->f_rpadding();
_wLeft = _w - (b->f_width() - last_rBearing); _wLeft -= b->f_width() - last_rBearing;
if (_elideLast && _elideRemoveFromEnd > 0 && (_y + blockHeight >= _yToElide)) {
_wLeft -= _elideRemoveFromEnd;
}
longWordLine = true; longWordLine = true;
continue; continue;
} }
if (_lineStart < _t->_text.size()) { if (_lineStart < _t->_text.size()) {
if (!drawLine(_t->_text.size(), e, e)) return; if (!drawLine(_t->_text.size(), e, e)) {
return;
}
} }
if (!_p && _lookupSymbol) { if (!_p && _lookupSymbol) {
_lookupResult.symbol = _t->_text.size(); _lookupResult.symbol = _t->_text.size();
@ -398,7 +392,10 @@ void Renderer::enumerate() {
} }
} }
StateResult Renderer::getState(QPoint point, int w, StateRequest request) { StateResult Renderer::getState(
QPoint point,
GeometryDescriptor geometry,
StateRequest request) {
if (_t->isEmpty() || point.y() < 0) { if (_t->isEmpty() || point.y() < 0) {
return {}; return {};
} }
@ -406,41 +403,13 @@ StateResult Renderer::getState(QPoint point, int w, StateRequest request) {
_lookupX = point.x(); _lookupX = point.x();
_lookupY = point.y(); _lookupY = point.y();
_breakEverywhere = (_lookupRequest.flags & StateRequest::Flag::BreakEverywhere);
_lookupSymbol = (_lookupRequest.flags & StateRequest::Flag::LookupSymbol); _lookupSymbol = (_lookupRequest.flags & StateRequest::Flag::LookupSymbol);
_lookupLink = (_lookupRequest.flags & StateRequest::Flag::LookupLink); _lookupLink = (_lookupRequest.flags & StateRequest::Flag::LookupLink);
if (!_lookupSymbol && (_lookupX < 0 || _lookupX >= w)) { if (!_lookupSymbol && _lookupX < 0) {
return {}; return {};
} }
_w = w; _geometry = std::move(geometry);
_yFrom = _lookupY; _breakEverywhere = _geometry.breakEverywhere;
_yTo = _lookupY + 1;
_align = _lookupRequest.align;
enumerate();
return _lookupResult;
}
StateResult Renderer::getStateElided(QPoint point, int w, StateRequestElided request) {
if (_t->isEmpty() || point.y() < 0 || request.lines <= 0) {
return {};
}
_lookupRequest = request;
_lookupX = point.x();
_lookupY = point.y();
_breakEverywhere = (_lookupRequest.flags & StateRequest::Flag::BreakEverywhere);
_lookupSymbol = (_lookupRequest.flags & StateRequest::Flag::LookupSymbol);
_lookupLink = (_lookupRequest.flags & StateRequest::Flag::LookupLink);
if (!_lookupSymbol && (_lookupX < 0 || _lookupX >= w)) {
return {};
}
int yTo = _lookupY + 1;
if (yTo < 0 || (request.lines - 1) * _t->_st->font->height < yTo) {
yTo = request.lines * _t->_st->font->height;
_elideLast = true;
_elideRemoveFromEnd = request.removeFromEnd;
}
_w = w;
_yFrom = _lookupY; _yFrom = _lookupY;
_yTo = _lookupY + 1; _yTo = _lookupY + 1;
_align = _lookupRequest.align; _align = _lookupRequest.align;
@ -455,26 +424,58 @@ crl::time Renderer::now() const {
return _cachedNow; return _cachedNow;
} }
void Renderer::initNextParagraph(String::TextBlocks::const_iterator i) { void Renderer::initNextParagraph(
String::TextBlocks::const_iterator i,
Qt::LayoutDirection direction) {
_parDirection = (direction == Qt::LayoutDirectionAuto)
? style::LayoutDirection()
: direction;
_parStartBlock = i; _parStartBlock = i;
_paragraphWidthRemaining = 0;
const auto e = _t->_blocks.cend(); const auto e = _t->_blocks.cend();
if (i == e) { if (i == e) {
_parStart = _t->_text.size(); _lineStart = _parStart = _t->_text.size();
_lineStartBlock = _t->_blocks.size();
_parLength = 0; _parLength = 0;
} else { } else {
_parStart = (*i)->from(); _lineStart = _parStart = (*i)->position();
_lineStartBlock = i - _t->_blocks.cbegin();
auto last_rPadding = QFixed(0);
auto last_rBearing = QFixed(0);
for (; i != e; ++i) { for (; i != e; ++i) {
if ((*i)->type() == TextBlockTNewline) { if ((*i)->type() == TextBlockType::Newline) {
break; break;
} }
const auto rBearing = (*i)->f_rbearing();
_paragraphWidthRemaining += last_rBearing
+ last_rPadding
+ (*i)->f_width()
- rBearing;
last_rBearing = rBearing;
} }
_parLength = ((i == e) ? _t->_text.size() : (*i)->from()) - _parStart; _parLength = ((i == e) ? _t->_text.size() : (*i)->position()) - _parStart;
} }
_parAnalysis.resize(0); _parAnalysis.resize(0);
initNextLine();
}
void Renderer::initNextLine() {
const auto line = _geometry.layout({
.left = 0,
.top = (_y - _startTop),
.width = _paragraphWidthRemaining.ceil().toInt(),
}, _lineStart);
_x = _startLeft + line.left;
_y = _startTop + line.top;
_lineWidth = _wLeft = line.width;
_elidedLine = line.elided;
} }
void Renderer::initParagraphBidi() { void Renderer::initParagraphBidi() {
if (!_parLength || !_parAnalysis.isEmpty()) return; if (!_parLength || !_parAnalysis.isEmpty()) {
return;
}
String::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1; String::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1;
@ -486,13 +487,13 @@ void Renderer::initParagraphBidi() {
const ushort *curr = start; const ushort *curr = start;
const ushort *end = start + _parLength; const ushort *end = start + _parLength;
while (curr < end) { while (curr < end) {
while (n != e && (*n)->from() <= _parStart + (curr - start)) { while (n != e && (*n)->position() <= _parStart + (curr - start)) {
i = n; i = n;
++n; ++n;
} }
const auto type = (*i)->type(); const auto type = (*i)->type();
if (type != TextBlockTEmoji if (type != TextBlockType::Emoji
&& type != TextBlockTCustomEmoji && type != TextBlockType::CustomEmoji
&& *curr >= 0x590) { && *curr >= 0x590) {
ignore = false; ignore = false;
break; break;
@ -521,13 +522,15 @@ void Renderer::initParagraphBidi() {
bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterator &_endBlockIter, const String::TextBlocks::const_iterator &_end) { bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterator &_endBlockIter, const String::TextBlocks::const_iterator &_end) {
_yDelta = (_lineHeight - _fontHeight) / 2; _yDelta = (_lineHeight - _fontHeight) / 2;
if (_yTo >= 0 && (_y + _yDelta >= _yTo || _y >= _yTo)) return false; if (_yTo >= 0 && (_y + _yDelta >= _yTo || _y >= _yTo)) {
return false;
}
if (_y + _yDelta + _fontHeight <= _yFrom) { if (_y + _yDelta + _fontHeight <= _yFrom) {
if (_lookupSymbol) { if (_lookupSymbol) {
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; _lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false; _lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
} }
return true; return !_elidedLine;
} }
// Trimming pending spaces, because they sometimes don't fit on the line. // Trimming pending spaces, because they sometimes don't fit on the line.
@ -542,16 +545,15 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
} }
auto _endBlock = (_endBlockIter == _end) ? nullptr : _endBlockIter->get(); auto _endBlock = (_endBlockIter == _end) ? nullptr : _endBlockIter->get();
auto elidedLine = _elideLast && (_y + _lineHeight >= _yToElide); if (_elidedLine) {
if (elidedLine) {
// If we decided to draw the last line elided only because of the skip block // If we decided to draw the last line elided only because of the skip block
// that did not fit on this line, we just draw the line till the very end. // that did not fit on this line, we just draw the line till the very end.
// Skip block is ignored in the elided lines, instead "removeFromEnd" is used. // Skip block is ignored in the elided lines, instead "removeFromEnd" is used.
if (_endBlock && _endBlock->type() == TextBlockTSkip) { if (_endBlock && _endBlock->type() == TextBlockType::Skip) {
_endBlock = nullptr; _endBlock = nullptr;
} }
if (!_endBlock) { if (!_endBlock) {
elidedLine = false; _elidedLine = false;
} }
} }
@ -559,11 +561,11 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
auto currentBlock = _t->_blocks[blockIndex].get(); auto currentBlock = _t->_blocks[blockIndex].get();
auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
const auto extendLeft = (currentBlock->from() < _lineStart) const auto extendLeft = (currentBlock->position() < _lineStart)
? qMin(_lineStart - currentBlock->from(), 2) ? qMin(_lineStart - currentBlock->position(), 2)
: 0; : 0;
_localFrom = _lineStart - extendLeft; _localFrom = _lineStart - extendLeft;
const auto extendedLineEnd = (_endBlock && _endBlock->from() < trimmedLineEnd && !elidedLine) const auto extendedLineEnd = (_endBlock && _endBlock->position() < trimmedLineEnd && !_elidedLine)
? qMin(uint16(trimmedLineEnd + 2), _t->countBlockEnd(_endBlockIter, _end)) ? qMin(uint16(trimmedLineEnd + 2), _t->countBlockEnd(_endBlockIter, _end))
: trimmedLineEnd; : trimmedLineEnd;
@ -571,7 +573,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
auto lineStart = extendLeft; auto lineStart = extendLeft;
auto lineLength = trimmedLineEnd - _lineStart; auto lineLength = trimmedLineEnd - _lineStart;
if (elidedLine) { if (_elidedLine) {
initParagraphBidi(); initParagraphBidi();
prepareElidedLine(lineText, lineStart, lineLength, _endBlock); prepareElidedLine(lineText, lineStart, lineLength, _endBlock);
} }
@ -589,7 +591,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
if (_parDirection == Qt::RightToLeft) { if (_parDirection == Qt::RightToLeft) {
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; _lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false; _lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
// _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false; // _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockType::Skip)) ? true : false;
} else { } else {
_lookupResult.symbol = _lineStart; _lookupResult.symbol = _lineStart;
_lookupResult.afterSymbol = false; _lookupResult.afterSymbol = false;
@ -601,7 +603,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
} }
_lookupResult.uponSymbol = false; _lookupResult.uponSymbol = false;
return false; return false;
} else if (_lookupX >= x + (_w - _wLeft)) { } else if (_lookupX >= x + (_lineWidth - _wLeft)) {
if (_parDirection == Qt::RightToLeft) { if (_parDirection == Qt::RightToLeft) {
_lookupResult.symbol = _lineStart; _lookupResult.symbol = _lineStart;
_lookupResult.afterSymbol = false; _lookupResult.afterSymbol = false;
@ -609,7 +611,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
} else { } else {
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart; _lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false; _lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
// _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false; // _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockType::Skip)) ? true : false;
} }
if (_lookupLink) { if (_lookupLink) {
_lookupResult.link = nullptr; _lookupResult.link = nullptr;
@ -626,7 +628,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
const auto selectTillEnd = (_selection.to > trimmedLineEnd) const auto selectTillEnd = (_selection.to > trimmedLineEnd)
&& (trimmedLineEnd < _t->_text.size()) && (trimmedLineEnd < _t->_text.size())
&& (_selection.from <= trimmedLineEnd) && (_selection.from <= trimmedLineEnd)
&& (!_endBlock || _endBlock->type() != TextBlockTSkip); && (!_endBlock || _endBlock->type() != TextBlockType::Skip);
if ((selectFromStart && _parDirection == Qt::LeftToRight) if ((selectFromStart && _parDirection == Qt::LeftToRight)
|| (selectTillEnd && _parDirection == Qt::RightToLeft)) { || (selectTillEnd && _parDirection == Qt::RightToLeft)) {
@ -637,15 +639,17 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
if ((selectTillEnd && _parDirection == Qt::LeftToRight) if ((selectTillEnd && _parDirection == Qt::LeftToRight)
|| (selectFromStart && _parDirection == Qt::RightToLeft)) { || (selectFromStart && _parDirection == Qt::RightToLeft)) {
if (x < _x + _wLeft) { if (x < _x + _wLeft) {
fillSelectRange({ x + _w - _wLeft, _x + _w }); fillSelectRange({ x + _lineWidth - _wLeft, _x + _lineWidth });
} }
} }
} }
if (trimmedLineEnd == _lineStart && !elidedLine) { if (trimmedLineEnd == _lineStart && !_elidedLine) {
return true; return true;
} }
if (!elidedLine) initParagraphBidi(); // if was not inited if (!_elidedLine) {
initParagraphBidi(); // if was not inited
}
_f = _t->_st->font; _f = _t->_st->font;
QStackTextEngine engine(lineText, _f->f); QStackTextEngine engine(lineText, _f->f);
@ -662,7 +666,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1); int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0; int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0;
if (!nItems) { if (!nItems) {
return true; return !_elidedLine;
} }
int skipIndex = -1; int skipIndex = -1;
@ -670,23 +674,23 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
QVarLengthArray<uchar> levels(nItems); QVarLengthArray<uchar> levels(nItems);
for (int i = 0; i < nItems; ++i) { for (int i = 0; i < nItems; ++i) {
auto &si = engine.layoutData->items[firstItem + i]; auto &si = engine.layoutData->items[firstItem + i];
while (nextBlock && nextBlock->from() <= _localFrom + si.position) { while (nextBlock && nextBlock->position() <= _localFrom + si.position) {
currentBlock = nextBlock; currentBlock = nextBlock;
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
} }
auto _type = currentBlock->type(); auto _type = currentBlock->type();
if (_type == TextBlockTSkip) { if (_type == TextBlockType::Skip) {
levels[i] = si.analysis.bidiLevel = 0; levels[i] = si.analysis.bidiLevel = 0;
skipIndex = i; skipIndex = i;
} else { } else {
levels[i] = si.analysis.bidiLevel; levels[i] = si.analysis.bidiLevel;
} }
if (si.analysis.flags == QScriptAnalysis::Object) { if (si.analysis.flags == QScriptAnalysis::Object) {
if (_type == TextBlockTEmoji if (_type == TextBlockType::Emoji
|| _type == TextBlockTCustomEmoji || _type == TextBlockType::CustomEmoji
|| _type == TextBlockTSkip) { || _type == TextBlockType::Skip) {
si.width = currentBlock->f_width() si.width = currentBlock->f_width()
+ (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= trimmedLineEnd) + (nextBlock == _endBlock && (!nextBlock || nextBlock->position() >= trimmedLineEnd)
? 0 ? 0
: currentBlock->f_rpadding()); : currentBlock->f_rpadding());
} }
@ -714,12 +718,12 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
const auto &si = engine.layoutData->items.at(item); const auto &si = engine.layoutData->items.at(item);
const auto rtl = (si.analysis.bidiLevel % 2); const auto rtl = (si.analysis.bidiLevel % 2);
while (blockIndex > _lineStartBlock + 1 && _t->_blocks[blockIndex - 1]->from() > _localFrom + si.position) { while (blockIndex > _lineStartBlock + 1 && _t->_blocks[blockIndex - 1]->position() > _localFrom + si.position) {
nextBlock = currentBlock; nextBlock = currentBlock;
currentBlock = _t->_blocks[--blockIndex - 1].get(); currentBlock = _t->_blocks[--blockIndex - 1].get();
applyBlockProperties(currentBlock); applyBlockProperties(currentBlock);
} }
while (nextBlock && nextBlock->from() <= _localFrom + si.position) { while (nextBlock && nextBlock->position() <= _localFrom + si.position) {
currentBlock = nextBlock; currentBlock = nextBlock;
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
applyBlockProperties(currentBlock); applyBlockProperties(currentBlock);
@ -734,11 +738,11 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
} }
} }
} }
if (_type != TextBlockTSkip) { if (_type != TextBlockType::Skip) {
_lookupResult.uponSymbol = true; _lookupResult.uponSymbol = true;
} }
if (_lookupSymbol) { if (_lookupSymbol) {
if (_type == TextBlockTSkip) { if (_type == TextBlockType::Skip) {
if (_parDirection == Qt::RightToLeft) { if (_parDirection == Qt::RightToLeft) {
_lookupResult.symbol = _lineStart; _lookupResult.symbol = _lineStart;
_lookupResult.afterSymbol = false; _lookupResult.afterSymbol = false;
@ -750,8 +754,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
} }
// Emoji with spaces after symbol lookup // Emoji with spaces after symbol lookup
auto chFrom = _str + currentBlock->from(); auto chFrom = _str + currentBlock->position();
auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); auto chTo = chFrom + ((nextBlock ? nextBlock->position() : _t->_text.size()) - currentBlock->position());
auto spacesWidth = (si.width - currentBlock->f_width()); auto spacesWidth = (si.width - currentBlock->f_width());
auto spacesCount = 0; auto spacesCount = 0;
while (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) { while (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space) {
@ -780,7 +784,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
} }
} }
return false; return false;
} else if (_p && (_type == TextBlockTEmoji || _type == TextBlockTCustomEmoji)) { } else if (_p && (_type == TextBlockType::Emoji || _type == TextBlockType::CustomEmoji)) {
auto glyphX = x; auto glyphX = x;
auto spacesWidth = (si.width - currentBlock->f_width()); auto spacesWidth = (si.width - currentBlock->f_width());
if (rtl) { if (rtl) {
@ -791,8 +795,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
if (_background.selectActiveBlock) { if (_background.selectActiveBlock) {
fillSelect = { x, x + si.width }; fillSelect = { x, x + si.width };
} else if (_localFrom + si.position < _selection.to) { } else if (_localFrom + si.position < _selection.to) {
auto chFrom = _str + currentBlock->from(); auto chFrom = _str + currentBlock->position();
auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); auto chTo = chFrom + ((nextBlock ? nextBlock->position() : _t->_text.size()) - currentBlock->position());
if (_localFrom + si.position >= _selection.from) { // could be without space if (_localFrom + si.position >= _selection.from) { // could be without space
if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selection.to >= (chTo - _str)) { if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selection.to >= (chTo - _str)) {
fillSelect = { x, x + si.width }; fillSelect = { x, x + si.width };
@ -820,7 +824,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
} }
const auto x = (glyphX + st::emojiPadding).toInt(); const auto x = (glyphX + st::emojiPadding).toInt();
const auto y = _y + _yDelta + emojiY; const auto y = _y + _yDelta + emojiY;
if (_type == TextBlockTEmoji) { if (_type == TextBlockType::Emoji) {
Emoji::Draw( Emoji::Draw(
*_p, *_p,
static_cast<const EmojiBlock*>(currentBlock)->_emoji, static_cast<const EmojiBlock*>(currentBlock)->_emoji,
@ -1015,7 +1019,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
: QRegion(); : QRegion();
if (complexClipping) { if (complexClipping) {
const auto elided = (_indexOfElidedBlock == blockIndex) const auto elided = (_indexOfElidedBlock == blockIndex)
? (_elideRemoveFromEnd + _f->elidew) ? _f->elidew
: 0; : 0;
_p->setClipRect( _p->setClipRect(
QRect( QRect(
@ -1046,9 +1050,9 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
const auto externalClipping = clippingEnabled const auto externalClipping = clippingEnabled
? clippingRegion ? clippingRegion
: QRegion(QRect( : QRegion(QRect(
(_x - _w).toInt(), (_x - _lineWidth).toInt(),
_y - _lineHeight, _y - _lineHeight,
(_x + 2 * _w).toInt(), (_x + 2 * _lineWidth).toInt(),
_y + 2 * _lineHeight)); _y + 2 * _lineHeight));
_p->setClipRegion(externalClipping - selectedRect); _p->setClipRegion(externalClipping - selectedRect);
_p->setPen(*_currentPen); _p->setPen(*_currentPen);
@ -1089,7 +1093,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
x += itemWidth; x += itemWidth;
} }
fillSpoilerRects(); fillSpoilerRects();
return true; return !_elidedLine;
} }
void Renderer::fillSelectRange(FixedRange range) { void Renderer::fillSelectRange(FixedRange range) {
@ -1108,9 +1112,7 @@ void Renderer::pushSpoilerRange(
if (!_background.spoiler || !_spoiler) { if (!_background.spoiler || !_spoiler) {
return; return;
} }
const auto elided = isElidedItem const auto elided = isElidedItem ? _f->elidew : 0;
? (_elideRemoveFromEnd + _f->elidew)
: 0;
range.till -= elided; range.till -= elided;
if (range.empty()) { if (range.empty()) {
return; return;
@ -1204,9 +1206,18 @@ void Renderer::elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock,
_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] = Block::Text(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, (*_elideSavedBlock)->flags(), (*_elideSavedBlock)->lnkIndex(), (*_elideSavedBlock)->spoilerIndex()); mutableText->_blocks[blockIndex] = Block::Text(
_t->_st->font,
_t->_text,
elideStart, 0,
(*_elideSavedBlock)->flags(),
(*_elideSavedBlock)->linkIndex(),
(*_elideSavedBlock)->colorIndex(),
QFIXED_MAX);
_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;
} }
void Renderer::setElideBidi(int32 elideStart, int32 elideLen) { void Renderer::setElideBidi(int32 elideStart, int32 elideLen) {
@ -1237,40 +1248,40 @@ void Renderer::prepareElidedLine(QString &lineText, int32 lineStart, int32 &line
eShapeLine(line); eShapeLine(line);
auto elideWidth = _f->elidew; auto elideWidth = _f->elidew;
_wLeft = _w - elideWidth - _elideRemoveFromEnd; _wLeft = _lineWidth - elideWidth;
int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1); 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; int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i;
for (i = 0; i < nItems; ++i) { for (i = 0; i < nItems; ++i) {
QScriptItem &si(engine.layoutData->items[firstItem + i]); QScriptItem &si(engine.layoutData->items[firstItem + i]);
while (nextBlock && nextBlock->from() <= _localFrom + si.position) { while (nextBlock && nextBlock->position() <= _localFrom + si.position) {
currentBlock = nextBlock; currentBlock = nextBlock;
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
} }
TextBlockType _type = currentBlock->type(); TextBlockType _type = currentBlock->type();
if (si.analysis.flags == QScriptAnalysis::Object) { if (si.analysis.flags == QScriptAnalysis::Object) {
if (_type == TextBlockTEmoji if (_type == TextBlockType::Emoji
|| _type == TextBlockTCustomEmoji || _type == TextBlockType::CustomEmoji
|| _type == TextBlockTSkip) { || _type == TextBlockType::Skip) {
si.width = currentBlock->f_width() + currentBlock->f_rpadding(); si.width = currentBlock->f_width() + currentBlock->f_rpadding();
} }
} }
if (_type == TextBlockTEmoji if (_type == TextBlockType::Emoji
|| _type == TextBlockTCustomEmoji || _type == TextBlockType::CustomEmoji
|| _type == TextBlockTSkip || _type == TextBlockType::Skip
|| _type == TextBlockTNewline) { || _type == TextBlockType::Newline) {
if (_wLeft < si.width) { if (_wLeft < si.width) {
lineText = lineText.mid(0, currentBlock->from() - _localFrom) + kQEllipsis; lineText = lineText.mid(0, currentBlock->position() - _localFrom) + kQEllipsis;
lineLength = currentBlock->from() + kQEllipsis.size() - _lineStart; lineLength = currentBlock->position() + kQEllipsis.size() - _lineStart;
_selection.to = qMin(_selection.to, currentBlock->from()); _selection.to = qMin(_selection.to, currentBlock->position());
_indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0); _indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0);
setElideBidi(currentBlock->from(), kQEllipsis.size()); setElideBidi(currentBlock->position(), kQEllipsis.size());
elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->from(), elideWidth); elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->position(), elideWidth);
return; return;
} }
_wLeft -= si.width; _wLeft -= si.width;
} else if (_type == TextBlockTText) { } else if (_type == TextBlockType::Text) {
unsigned short *logClusters = engine.logClusters(&si); unsigned short *logClusters = engine.logClusters(&si);
QGlyphLayout glyphs = engine.shapedGlyphs(&si); QGlyphLayout glyphs = engine.shapedGlyphs(&si);
@ -1325,7 +1336,7 @@ void Renderer::prepareElidedLine(QString &lineText, int32 lineStart, int32 &line
lineLength += kQEllipsis.size(); lineLength += kQEllipsis.size();
if (!repeat) { if (!repeat) {
for (; blockIndex < _blocksSize && _t->_blocks[blockIndex].get() != _endBlock && _t->_blocks[blockIndex]->from() < elideStart; ++blockIndex) { for (; blockIndex < _blocksSize && _t->_blocks[blockIndex].get() != _endBlock && _t->_blocks[blockIndex]->position() < elideStart; ++blockIndex) {
} }
if (blockIndex < _blocksSize) { if (blockIndex < _blocksSize) {
elideSaveBlock(blockIndex, _endBlock, elideStart, elideWidth); elideSaveBlock(blockIndex, _endBlock, elideStart, elideWidth);
@ -1371,65 +1382,46 @@ void Renderer::eAppendItems(QScriptAnalysis *analysis, int &start, int &stop, co
void Renderer::eShapeLine(const QScriptLine &line) { void Renderer::eShapeLine(const QScriptLine &line) {
int item = _e->findItem(line.from); int item = _e->findItem(line.from);
if (item == -1) if (item == -1) {
return; return;
}
auto end = _e->findItem(line.from + line.length - 1, item); auto end = _e->findItem(line.from + line.length - 1, item);
auto blockIndex = _lineStartBlock; auto blockIndex = _lineStartBlock;
auto currentBlock = _t->_blocks[blockIndex].get(); auto currentBlock = _t->_blocks[blockIndex].get();
auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; auto nextBlock = (++blockIndex < _blocksSize)
? _t->_blocks[blockIndex].get()
: nullptr;
eSetFont(currentBlock); eSetFont(currentBlock);
for (; item <= end; ++item) { for (; item <= end; ++item) {
QScriptItem &si = _e->layoutData->items[item]; QScriptItem &si = _e->layoutData->items[item];
while (nextBlock && nextBlock->from() <= _localFrom + si.position) { while (nextBlock && nextBlock->position() <= _localFrom + si.position) {
currentBlock = nextBlock; currentBlock = nextBlock;
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; nextBlock = (++blockIndex < _blocksSize)
? _t->_blocks[blockIndex].get()
: nullptr;
eSetFont(currentBlock); eSetFont(currentBlock);
} }
_e->shape(item); _e->shape(item);
} }
} }
style::font Renderer::applyFlags(int32 flags, const style::font &f) {
if (!flags) {
return f;
}
auto result = f;
if (IsMono(flags)) {
result = result->monospace();
} else {
if (flags & TextBlockFBold) {
result = result->bold();
} else if (flags & TextBlockFSemibold) {
result = result->semibold();
}
if (flags & TextBlockFItalic) result = result->italic();
if (flags & TextBlockFUnderline) result = result->underline();
if (flags & TextBlockFStrikeOut) result = result->strikeout();
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
result = result->semibold();
}
}
return result;
}
void Renderer::eSetFont(const AbstractBlock *block) { void Renderer::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->linkIndex()) {
const auto active = ClickHandler::showAsActive( const auto active = (_palette && _palette->linkAlwaysActive)
_t->_links.at(index - 1) || ClickHandler::showAsActive(_t->_links.at(index - 1));
) || (_palette && _palette->linkAlwaysActive > 0);
return active return active
? _t->_st->linkFontOver ? _t->_st->linkFontOver
: _t->_st->linkFont; : _t->_st->linkFont;
} }
return _t->_st->font; return _t->_st->font;
}(); }();
const auto newFont = applyFlags(flags, usedFont); const auto newFont = WithFlags(usedFont, flags);
if (newFont != _f) { if (newFont != _f) {
_f = (newFont->family() == _t->_st->font->family()) _f = (newFont->family() == _t->_st->font->family())
? applyFlags(flags | newFont->flags(), _t->_st->font) ? WithFlags(_t->_st->font, flags, newFont->flags())
: newFont; : newFont;
_e->fnt = _f->f; _e->fnt = _f->f;
_e->resetFontEngineCache(); _e->resetFontEngineCache();
@ -1479,14 +1471,14 @@ void Renderer::eItemize() {
auto start = string; auto start = string;
auto end = start + length; auto end = start + length;
while (start < end) { while (start < end) {
while (nextBlock && nextBlock->from() <= _localFrom + (start - string)) { while (nextBlock && nextBlock->position() <= _localFrom + (start - string)) {
currentBlock = nextBlock; currentBlock = nextBlock;
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
} }
auto _type = currentBlock->type(); auto _type = currentBlock->type();
if (_type == TextBlockTEmoji if (_type == TextBlockType::Emoji
|| _type == TextBlockTCustomEmoji || _type == TextBlockType::CustomEmoji
|| _type == TextBlockTSkip) { || _type == TextBlockType::Skip) {
analysis->script = QChar::Script_Common; analysis->script = QChar::Script_Common;
analysis->flags = QScriptAnalysis::Object; analysis->flags = QScriptAnalysis::Object;
} else { } else {
@ -1515,7 +1507,7 @@ void Renderer::eItemize() {
auto start = 0; auto start = 0;
auto end = start + length; auto end = start + length;
for (int i = start + 1; i < end; ++i) { for (int i = start + 1; i < end; ++i) {
while (nextBlock && nextBlock->from() <= _localFrom + i) { while (nextBlock && nextBlock->position() <= _localFrom + i) {
currentBlock = nextBlock; currentBlock = nextBlock;
nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr; nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
} }
@ -1545,20 +1537,23 @@ void Renderer::eItemize() {
} }
} }
QChar::Direction Renderer::eSkipBoundryNeutrals(QScriptAnalysis *analysis, QChar::Direction Renderer::eSkipBoundryNeutrals(
QScriptAnalysis *analysis,
const ushort *unicode, const ushort *unicode,
int &sor, int &eor, BidiControl &control, int &sor,
int &eor,
BidiControl &control,
String::TextBlocks::const_iterator i) { String::TextBlocks::const_iterator i) {
String::TextBlocks::const_iterator e = _t->_blocks.cend(), n = i + 1; String::TextBlocks::const_iterator e = _t->_blocks.cend(), n = i + 1;
QChar::Direction dir = control.basicDirection(); QChar::Direction dir = control.basicDirection();
int level = sor > 0 ? analysis[sor - 1].bidiLevel : control.level; int level = sor > 0 ? analysis[sor - 1].bidiLevel : control.level;
while (sor <= _parLength) { while (sor <= _parLength) {
while (i != _parStartBlock && (*i)->from() > _parStart + sor) { while (i != _parStartBlock && (*i)->position() > _parStart + sor) {
n = i; n = i;
--i; --i;
} }
while (n != e && (*n)->from() <= _parStart + sor) { while (n != e && (*n)->position() <= _parStart + sor) {
i = n; i = n;
++n; ++n;
} }
@ -1566,10 +1561,10 @@ QChar::Direction Renderer::eSkipBoundryNeutrals(QScriptAnalysis *analysis,
TextBlockType _itype = (*i)->type(); TextBlockType _itype = (*i)->type();
if (eor == _parLength) if (eor == _parLength)
dir = control.basicDirection(); dir = control.basicDirection();
else if (_itype == TextBlockTEmoji else if (_itype == TextBlockType::Emoji
|| _itype == TextBlockTCustomEmoji) || _itype == TextBlockType::CustomEmoji)
dir = QChar::DirCS; dir = QChar::DirCS;
else if (_itype == TextBlockTSkip) else if (_itype == TextBlockType::Skip)
dir = QChar::DirCS; dir = QChar::DirCS;
else else
dir = QChar::direction(unicode[sor]); dir = QChar::direction(unicode[sor]);
@ -1602,9 +1597,9 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
QChar::Direction sdir; QChar::Direction sdir;
TextBlockType _stype = (*_parStartBlock)->type(); TextBlockType _stype = (*_parStartBlock)->type();
if (_stype == TextBlockTEmoji || _stype == TextBlockTCustomEmoji) if (_stype == TextBlockType::Emoji || _stype == TextBlockType::CustomEmoji)
sdir = QChar::DirCS; sdir = QChar::DirCS;
else if (_stype == TextBlockTSkip) else if (_stype == TextBlockType::Skip)
sdir = QChar::DirCS; sdir = QChar::DirCS;
else else
sdir = QChar::direction(*unicode); sdir = QChar::direction(*unicode);
@ -1619,7 +1614,7 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
status.dir = sdir; status.dir = sdir;
while (current <= _parLength) { while (current <= _parLength) {
while (n != e && (*n)->from() <= _parStart + current) { while (n != e && (*n)->position() <= _parStart + current) {
i = n; i = n;
++n; ++n;
} }
@ -1628,10 +1623,10 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
TextBlockType _itype = (*i)->type(); TextBlockType _itype = (*i)->type();
if (current == (int)_parLength) if (current == (int)_parLength)
dirCurrent = control.basicDirection(); dirCurrent = control.basicDirection();
else if (_itype == TextBlockTEmoji else if (_itype == TextBlockType::Emoji
|| _itype == TextBlockTCustomEmoji) || _itype == TextBlockType::CustomEmoji)
dirCurrent = QChar::DirCS; dirCurrent = QChar::DirCS;
else if (_itype == TextBlockTSkip) else if (_itype == TextBlockType::Skip)
dirCurrent = QChar::DirCS; dirCurrent = QChar::DirCS;
else else
dirCurrent = QChar::direction(unicode[current]); dirCurrent = QChar::direction(unicode[current]);
@ -2009,23 +2004,34 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
void Renderer::applyBlockProperties(const AbstractBlock *block) { void Renderer::applyBlockProperties(const AbstractBlock *block) {
eSetFont(block); eSetFont(block);
if (_p) { if (_p) {
const auto isMono = IsMono(block->flags()); const auto flags = block->flags();
const auto isMono = IsMono(flags);
_background = {}; _background = {};
if (block->spoilerIndex() && _spoiler) { if ((flags & TextBlockFlag::Spoiler) && _spoiler) {
_background.spoiler = true; _background.spoiler = true;
} }
if (isMono if (isMono
&& block->lnkIndex() && block->linkIndex()
&& (!_background.spoiler || _spoiler->revealed)) { && (!_background.spoiler || _spoiler->revealed)) {
_background.selectActiveBlock = ClickHandler::showAsPressed( _background.selectActiveBlock = ClickHandler::showAsPressed(
_t->_links.at(block->lnkIndex() - 1)); _t->_links.at(block->linkIndex() - 1));
} }
if (isMono) { if (const auto color = block->colorIndex()) {
if (color == 1) {
_currentPen = &_palette->linkFg->p;
_currentPenSelected = &_palette->selectLinkFg->p;
} else if (color - 1 <= _colors.size()) {
_currentPen = _colors[color - 2].pen;
_currentPenSelected = _colors[color - 2].penSelected;
} else {
_currentPen = &_originalPen;
_currentPenSelected = &_originalPenSelected;
}
} else if (isMono) {
_currentPen = &_palette->monoFg->p; _currentPen = &_palette->monoFg->p;
_currentPenSelected = &_palette->selectMonoFg->p; _currentPenSelected = &_palette->selectMonoFg->p;
} else if (block->lnkIndex() } else if (block->linkIndex()) {
|| (block->flags() & TextBlockFPlainLink)) {
_currentPen = &_palette->linkFg->p; _currentPen = &_palette->linkFg->p;
_currentPenSelected = &_palette->selectLinkFg->p; _currentPenSelected = &_palette->selectLinkFg->p;
} else { } else {
@ -2038,12 +2044,12 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const { ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
const auto spoilerLink = (_spoiler const auto spoilerLink = (_spoiler
&& !_spoiler->revealed && !_spoiler->revealed
&& block->spoilerIndex()) && (block->flags() & TextBlockFlag::Spoiler))
? _spoiler->link ? _spoiler->link
: ClickHandlerPtr(); : ClickHandlerPtr();
return (spoilerLink || !block->lnkIndex()) return (spoilerLink || !block->linkIndex())
? spoilerLink ? spoilerLink
: _t->_links.at(block->lnkIndex() - 1); : _t->_links.at(block->linkIndex() - 1);
} }
} // namespace Ui::Text } // namespace Ui::Text

View file

@ -37,12 +37,8 @@ public:
void draw(QPainter &p, const PaintContext &context); void draw(QPainter &p, const PaintContext &context);
[[nodiscard]] StateResult getState( [[nodiscard]] StateResult getState(
QPoint point, QPoint point,
int w, GeometryDescriptor geometry,
StateRequest request); StateRequest request);
[[nodiscard]] StateResult getStateElided(
QPoint point,
int w,
StateRequestElided request);
private: private:
static constexpr int kSpoilersRectsSize = 512; static constexpr int kSpoilersRectsSize = 512;
@ -52,7 +48,10 @@ private:
void enumerate(); void enumerate();
[[nodiscard]] crl::time now() const; [[nodiscard]] crl::time now() const;
void initNextParagraph(String::TextBlocks::const_iterator i); void initNextParagraph(
String::TextBlocks::const_iterator i,
Qt::LayoutDirection direction);
void initNextLine();
void initParagraphBidi(); void initParagraphBidi();
bool drawLine( bool drawLine(
uint16 _lineEnd, uint16 _lineEnd,
@ -94,7 +93,6 @@ private:
const BidiControl &control, const BidiControl &control,
QChar::Direction dir); QChar::Direction dir);
void eShapeLine(const QScriptLine &line); void eShapeLine(const QScriptLine &line);
[[nodiscard]] style::font applyFlags(int32 flags, const style::font &f);
void eSetFont(const AbstractBlock *block); void eSetFont(const AbstractBlock *block);
void eItemize(); void eItemize();
QChar::Direction eSkipBoundryNeutrals( QChar::Direction eSkipBoundryNeutrals(
@ -111,13 +109,12 @@ private:
const AbstractBlock *block) const; const AbstractBlock *block) const;
const String *_t = nullptr; const String *_t = nullptr;
GeometryDescriptor _geometry;
SpoilerData *_spoiler = nullptr; SpoilerData *_spoiler = nullptr;
SpoilerMessCache *_spoilerCache = nullptr; SpoilerMessCache *_spoilerCache = nullptr;
QPainter *_p = nullptr; QPainter *_p = nullptr;
const style::TextPalette *_palette = nullptr; const style::TextPalette *_palette = nullptr;
bool _elideLast = false; std::span<SpecialColor> _colors;
bool _breakEverywhere = false;
int _elideRemoveFromEnd = 0;
bool _pausedEmoji = false; bool _pausedEmoji = false;
bool _pausedSpoiler = false; bool _pausedSpoiler = false;
style::align _align = style::al_topleft; style::align _align = style::al_topleft;
@ -131,7 +128,6 @@ private:
} _background; } _background;
int _yFrom = 0; int _yFrom = 0;
int _yTo = 0; int _yTo = 0;
int _yToElide = 0;
TextSelection _selection = { 0, 0 }; TextSelection _selection = { 0, 0 };
bool _fullWidthSelection = true; bool _fullWidthSelection = true;
const QChar *_str = nullptr; const QChar *_str = nullptr;
@ -158,11 +154,15 @@ private:
// current line data // current line data
QTextEngine *_e = nullptr; QTextEngine *_e = nullptr;
style::font _f; style::font _f;
QFixed _x, _w, _wLeft, _last_rPadding; int _startLeft = 0;
int _startTop = 0;
QFixed _x, _wLeft, _last_rPadding;
int _y = 0; int _y = 0;
int _yDelta = 0; int _yDelta = 0;
int _lineHeight = 0; int _lineHeight = 0;
int _fontHeight = 0; int _fontHeight = 0;
bool _breakEverywhere = false;
bool _elidedLine = false;
// elided hack support // elided hack support
int _blocksSize = 0; int _blocksSize = 0;
@ -172,6 +172,8 @@ private:
int _lineStart = 0; int _lineStart = 0;
int _localFrom = 0; int _localFrom = 0;
int _lineStartBlock = 0; int _lineStartBlock = 0;
QFixed _lineWidth = 0;
QFixed _paragraphWidthRemaining = 0;
// link and symbol resolve // link and symbol resolve
QFixed _lookupX = 0; QFixed _lookupX = 0;

View file

@ -54,12 +54,14 @@ TextWithEntities Link(TextWithEntities text, int index) {
return Link(std::move(text), u"internal:index"_q + QChar(index)); return Link(std::move(text), u"internal:index"_q + QChar(index));
} }
TextWithEntities PlainLink(const QString &text) { TextWithEntities Colorized(const QString &text, int index) {
return WithSingleEntity(text, EntityType::PlainLink); const auto data = index ? QString(QChar(index)) : QString();
return WithSingleEntity(text, EntityType::Colorized, data);
} }
TextWithEntities PlainLink(TextWithEntities text) { TextWithEntities Colorized(TextWithEntities text, int index) {
return Wrapped(std::move(text), EntityType::PlainLink, QString()); const auto data = index ? QString(QChar(index)) : QString();
return Wrapped(std::move(text), EntityType::Colorized, data);
} }
TextWithEntities Wrapped( TextWithEntities Wrapped(

View file

@ -35,8 +35,12 @@ inline constexpr auto Upper = details::ToUpperType{};
TextWithEntities text, TextWithEntities text,
const QString &url = u"internal:action"_q); const QString &url = u"internal:action"_q);
[[nodiscard]] TextWithEntities Link(TextWithEntities text, int index); [[nodiscard]] TextWithEntities Link(TextWithEntities text, int index);
[[nodiscard]] TextWithEntities PlainLink(const QString &text); [[nodiscard]] TextWithEntities Colorized(
[[nodiscard]] TextWithEntities PlainLink(TextWithEntities text); const QString &text,
int index = 0);
[[nodiscard]] TextWithEntities Colorized(
TextWithEntities text,
int index = 0);
[[nodiscard]] TextWithEntities Wrapped( [[nodiscard]] TextWithEntities Wrapped(
TextWithEntities text, TextWithEntities text,
EntityType type, EntityType type,

View file

@ -173,14 +173,13 @@ void Widget::paintEvent(QPaintEvent *e) {
width()); width());
} }
const auto lines = _maxTextHeight / _st->style.font->height;
p.setPen(st::toastFg); p.setPen(st::toastFg);
_text.draw(p, { _text.draw(p, {
.position = { _st->padding.left(), _textTop }, .position = { _st->padding.left(), _textTop },
.availableWidth = _textWidth + 1, .availableWidth = _textWidth + 1,
.palette = &_st->palette, .palette = &_st->palette,
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
.elisionLines = lines, .elisionHeight = _maxTextHeight,
}); });
} }

View file

@ -587,8 +587,8 @@ void Checkbox::setTextBreakEverywhere(bool allow) {
_textBreakEverywhere = allow; _textBreakEverywhere = allow;
} }
void Checkbox::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { void Checkbox::setLink(uint16 index, const ClickHandlerPtr &lnk) {
_text.setLink(lnkIndex, lnk); _text.setLink(index, lnk);
} }
void Checkbox::setLinksTrusted() { void Checkbox::setLinksTrusted() {

View file

@ -185,7 +185,7 @@ public:
void setAllowTextLines(int lines = 0); void setAllowTextLines(int lines = 0);
void setTextBreakEverywhere(bool allow = true); void setTextBreakEverywhere(bool allow = true);
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); void setLink(uint16 index, const ClickHandlerPtr &lnk);
void setLinksTrusted(); void setLinksTrusted();
using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>; using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>;

View file

@ -368,8 +368,8 @@ void FlatLabel::refreshSize() {
resize(fullWidth, fullHeight); resize(fullWidth, fullHeight);
} }
void FlatLabel::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { void FlatLabel::setLink(uint16 index, const ClickHandlerPtr &lnk) {
_text.setLink(lnkIndex, lnk); _text.setLink(index, lnk);
} }
void FlatLabel::setLinksTrusted() { void FlatLabel::setLinksTrusted() {
@ -795,7 +795,9 @@ CrossFadeAnimation::Data FlatLabel::crossFadeData(
auto result = CrossFadeAnimation::Data(); auto result = CrossFadeAnimation::Data();
result.full = GrabWidgetToImage(this, QRect(), bg->c); result.full = GrabWidgetToImage(this, QRect(), bg->c);
const auto textWidth = width() - _st.margin.left() - _st.margin.right(); const auto textWidth = width() - _st.margin.left() - _st.margin.right();
_text.countLineWidths(textWidth, &result.lineWidths, _breakEverywhere); result.lineWidths = _text.countLineWidths(textWidth, {
.breakEverywhere = _breakEverywhere,
});
result.lineHeight = _st.style.font->height; result.lineHeight = _st.style.font->height;
const auto addedHeight = (_st.style.lineHeight - result.lineHeight); const auto addedHeight = (_st.style.lineHeight - result.lineHeight);
if (addedHeight > 0) { if (addedHeight > 0) {
@ -961,11 +963,11 @@ void FlatLabel::paintEvent(QPaintEvent *e) {
&& (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth()); && (_st.maxHeight < _fullTextHeight || textWidth < _text.maxWidth());
const auto renderElided = _breakEverywhere || heightExceeded; const auto renderElided = _breakEverywhere || heightExceeded;
const auto lineHeight = qMax(_st.style.lineHeight, _st.style.font->height); const auto lineHeight = qMax(_st.style.lineHeight, _st.style.font->height);
const auto lines = !renderElided const auto elisionHeight = !renderElided
? 0 ? 0
: _st.maxHeight : _st.maxHeight
? qMax(_st.maxHeight / lineHeight, 1) ? qMax(_st.maxHeight, lineHeight)
: ((height() / lineHeight) + 2); : height();
const auto paused = _animationsPausedCallback const auto paused = _animationsPausedCallback
? _animationsPausedCallback() ? _animationsPausedCallback()
: WhichAnimationsPaused::None; : WhichAnimationsPaused::None;
@ -982,7 +984,7 @@ void FlatLabel::paintEvent(QPaintEvent *e) {
.pausedSpoiler = (paused == WhichAnimationsPaused::Spoiler .pausedSpoiler = (paused == WhichAnimationsPaused::Spoiler
|| paused == WhichAnimationsPaused::All), || paused == WhichAnimationsPaused::All),
.selection = selection, .selection = selection,
.elisionLines = lines, .elisionHeight = elisionHeight,
.elisionBreakEverywhere = renderElided && _breakEverywhere, .elisionBreakEverywhere = renderElided && _breakEverywhere,
}); });
} }

View file

@ -26,7 +26,7 @@ class CrossFadeAnimation {
public: public:
struct Data { struct Data {
QImage full; QImage full;
QVector<int> lineWidths; std::vector<int> lineWidths;
QPoint position; QPoint position;
style::align align; style::align align;
style::font font; style::font font;
@ -143,7 +143,7 @@ public:
int naturalWidth() const override; int naturalWidth() const override;
QMargins getMargins() const override; QMargins getMargins() const override;
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); void setLink(uint16 index, const ClickHandlerPtr &lnk);
void setLinksTrusted(); void setLinksTrusted();
using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>; using ClickHandlerFilter = Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)>;

View file

@ -166,10 +166,10 @@ void SideBarButton::paintEvent(QPaintEvent *e) {
} }
if (_lock.locked) { if (_lock.locked) {
auto lineWidths = QVector<int>(); const auto lineWidths = _text.countLineWidths(
lineWidths.reserve(kMaxLabelLines); width() - 2 * _st.textSkip,
_text.countLineWidths(width() - 2 * _st.textSkip, &lineWidths); { .reserve = kMaxLabelLines });
if (lineWidths.isEmpty()) { if (lineWidths.empty()) {
return; return;
} }
validateLockIconCache(); validateLockIconCache();