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;
selectSpoilerFg: color;
selectOverlay: color;
linkAlwaysActive: int;
linkAlwaysActive: bool;
}
TextStyle {

View file

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

View file

@ -26,11 +26,9 @@ struct TextPalette;
} // namespace style
namespace Ui {
static const auto kQEllipsis = QStringLiteral("...");
extern const QString kQEllipsis;
} // namespace Ui
static const QChar TextCommand(0x0010);
struct TextParseOptions {
int32 flags;
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]] GeometryDescriptor SimpleGeometry(
int availableWidth,
int fontHeight,
int elisionHeight,
int elisionRemoveFromEnd,
bool elisionOneLine,
bool elisionBreakEverywhere);
struct PaintContext {
QPoint position;
int outerWidth = 0; // For automatic RTL Ui inversion.
int availableWidth = 0;
GeometryDescriptor geometry; // By default is SimpleGeometry.
style::align align = style::al_left;
QRect clip;
const style::TextPalette *palette = nullptr;
std::span<SpecialColor> colors;
SpoilerMessCache *spoiler = nullptr;
crl::time now = 0;
bool paused = false;
@ -144,8 +168,9 @@ struct PaintContext {
TextSelection selection;
bool fullWidthSelection = true;
int elisionLines = 0;
int elisionHeight = 0;
int elisionRemoveFromEnd = 0;
bool elisionOneLine = false;
bool elisionBreakEverywhere = false;
};
@ -167,14 +192,42 @@ public:
String &operator=(String &&other);
~String();
[[nodiscard]] int countWidth(int width, bool breakEverywhere = false) const;
[[nodiscard]] int countHeight(int width, bool breakEverywhere = false) const;
void countLineWidths(int width, QVector<int> *lineWidths, bool breakEverywhere = false) const;
[[nodiscard]] int countWidth(
int width,
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 setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = kMarkupTextOptions, const std::any &context = {});
[[nodiscard]] bool hasLinks() const;
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
void setLink(uint16 index, const ClickHandlerPtr &lnk);
[[nodiscard]] bool hasSpoilers() const;
void setSpoilerRevealed(bool revealed, anim::type animated);
@ -193,6 +246,10 @@ public:
[[nodiscard]] int countMaxMonospaceWidth() 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 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;
TextLinks _links;
Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto;
Qt::LayoutDirection _startDirection = Qt::LayoutDirectionAuto;
SpoilerDataWrap _spoiler;
@ -313,7 +370,7 @@ private:
[[nodiscard]] bool IsLinkEnd(QChar ch);
[[nodiscard]] bool IsNewline(QChar ch);
[[nodiscard]] bool IsSpace(QChar ch);
[[nodiscard]] bool IsDiac(QChar ch);
[[nodiscard]] bool IsDiacritic(QChar ch);
[[nodiscard]] bool IsReplacedBySpace(QChar ch);
[[nodiscard]] bool IsTrimmed(QChar ch);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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