Optimize simple Ui::Text::String instances.

This commit is contained in:
John Preston 2023-10-12 13:29:17 +04:00
parent 44f8d862ff
commit ab5057f001
17 changed files with 291 additions and 159 deletions

View file

@ -165,13 +165,13 @@ PRIVATE
ui/text/text_custom_emoji.h
ui/text/text_entity.cpp
ui/text/text_entity.h
ui/text/text_extended_data.cpp
ui/text/text_extended_data.h
ui/text/text_isolated_emoji.h
ui/text/text_parser.cpp
ui/text/text_parser.h
ui/text/text_renderer.cpp
ui/text/text_renderer.h
ui/text/text_spoiler_data.cpp
ui/text/text_spoiler_data.h
ui/text/text_utilities.cpp
ui/text/text_utilities.h
ui/text/text_variant.cpp

View file

@ -6,10 +6,11 @@
//
#include "ui/text/text.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/text/text_extended_data.h"
#include "ui/text/text_isolated_emoji.h"
#include "ui/text/text_parser.h"
#include "ui/text/text_renderer.h"
#include "ui/text/text_spoiler_data.h"
#include "ui/basic_click_handlers.h"
#include "ui/painter.h"
#include "base/platform/base_platform_info.h"
@ -91,12 +92,19 @@ const TextParseOptions kPlainTextOptions = {
namespace Ui::Text {
struct SpoilerMessCache::Entry {
SpoilerMessCached mess;
QColor color;
};
SpoilerMessCache::SpoilerMessCache(int capacity) : _capacity(capacity) {
Expects(capacity > 0);
_cache.reserve(capacity);
}
SpoilerMessCache::~SpoilerMessCache() = default;
not_null<SpoilerMessCached*> SpoilerMessCache::lookup(QColor color) {
for (auto &entry : _cache) {
if (entry.color == color) {
@ -139,25 +147,25 @@ GeometryDescriptor SimpleGeometry(
bool elisionOneLine,
bool elisionBreakEverywhere) {
constexpr auto wrap = [](
Fn<LineGeometry(LineGeometry, uint16)> layout,
Fn<LineGeometry(LineGeometry)> 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) {
return wrap([=](LineGeometry line) {
line.width = availableWidth;
return line;
});
} else if (elisionOneLine) {
return wrap([=](LineGeometry line, uint16 position) {
return wrap([=](LineGeometry line) {
line.elided = true;
line.width = availableWidth - elisionRemoveFromEnd;
return line;
}, elisionBreakEverywhere);
} else if (!elisionRemoveFromEnd) {
return wrap([=](LineGeometry line, uint16 position) {
return wrap([=](LineGeometry line) {
if (line.top + fontHeight * 2 > elisionHeight) {
line.elided = true;
}
@ -165,7 +173,7 @@ GeometryDescriptor SimpleGeometry(
return line;
});
} else {
return wrap([=](LineGeometry line, uint16 position) {
return wrap([=](LineGeometry line) {
if (line.top + fontHeight * 2 > elisionHeight) {
line.elided = true;
line.width = availableWidth - elisionRemoveFromEnd;
@ -177,27 +185,44 @@ GeometryDescriptor SimpleGeometry(
}
};
String::SpoilerDataWrap::SpoilerDataWrap() noexcept = default;
String::ExtendedWrap::ExtendedWrap() noexcept = default;
String::SpoilerDataWrap::SpoilerDataWrap(SpoilerDataWrap &&other) noexcept
: data(std::move(other.data)) {
String::ExtendedWrap::ExtendedWrap(ExtendedWrap &&other) noexcept
: unique_ptr(std::move(other)) {
adjustFrom(&other);
}
String::SpoilerDataWrap &String::SpoilerDataWrap::operator=(
SpoilerDataWrap &&other) noexcept {
data = std::move(other.data);
String::ExtendedWrap &String::ExtendedWrap::operator=(
ExtendedWrap &&other) noexcept {
*static_cast<unique_ptr*>(this) = std::move(other);
adjustFrom(&other);
return *this;
}
void String::SpoilerDataWrap::adjustFrom(const SpoilerDataWrap *other) {
if (data && data->link) {
String::ExtendedWrap::ExtendedWrap(
std::unique_ptr<ExtendedData> &&other) noexcept
: unique_ptr(std::move(other)) {
Assert(!get() || !get()->spoiler);
}
String::ExtendedWrap &String::ExtendedWrap::operator=(
std::unique_ptr<ExtendedData> &&other) noexcept {
*static_cast<unique_ptr*>(this) = std::move(other);
Assert(!get() || !get()->spoiler);
return *this;
}
String::ExtendedWrap::~ExtendedWrap() = default;
void String::ExtendedWrap::adjustFrom(const ExtendedWrap *other) {
const auto data = get();
if (data && data->spoiler) {
const auto raw = [](auto pointer) {
return reinterpret_cast<quintptr>(pointer);
};
data->link->setText(reinterpret_cast<String*>(
raw(data->link->text().get()) + raw(this) - raw(other)));
const auto otherText = raw(data->spoiler->link->text().get());
data->spoiler->link->setText(
reinterpret_cast<String*>(otherText + raw(this) - raw(other)));
}
}
@ -243,31 +268,35 @@ void String::setText(const style::TextStyle &st, const QString &text, const Text
void String::recountNaturalSize(
bool initial,
Qt::LayoutDirection optionsDirection) {
NewlineBlock *lastNewline = 0;
auto lastNewline = (NewlineBlock*)nullptr;
auto lastNewlineStart = 0;
const auto computeParagraphDirection = [&](int paragraphEnd) {
const auto direction = (optionsDirection != Qt::LayoutDirectionAuto)
? optionsDirection
: StringDirection(_text, lastNewlineStart, paragraphEnd);
if (lastNewline) {
lastNewline->_paragraphLTR = (direction == Qt::LeftToRight);
lastNewline->_paragraphRTL = (direction == Qt::RightToLeft);
} else {
_startParagraphLTR = (direction == Qt::LeftToRight);
_startParagraphRTL = (direction == Qt::RightToLeft);
}
};
_maxWidth = _minHeight = 0;
int32 lineHeight = 0;
int32 lastNewlineStart = 0;
QFixed _width = 0, last_rBearing = 0, last_rPadding = 0;
QFixed maxWidth = 0;
QFixed width = 0, last_rBearing = 0, last_rPadding = 0;
for (auto &block : _blocks) {
auto b = block.get();
auto _btype = b->type();
auto blockHeight = CountBlockHeight(b, _st);
if (_btype == TextBlockType::Newline) {
if (!lineHeight) lineHeight = blockHeight;
if (!lineHeight) {
lineHeight = blockHeight;
}
if (initial) {
Qt::LayoutDirection direction = optionsDirection;
if (direction == Qt::LayoutDirectionAuto) {
direction = StringDirection(
_text,
lastNewlineStart,
b->position());
}
if (lastNewline) {
lastNewline->_nextDirection = direction;
} else {
_startDirection = direction;
}
computeParagraphDirection(b->position());
}
lastNewlineStart = b->position();
lastNewline = &block.unsafe<NewlineBlock>();
@ -277,8 +306,8 @@ void String::recountNaturalSize(
last_rBearing = b->f_rbearing();
last_rPadding = b->f_rpadding();
accumulate_max(_maxWidth, _width);
_width = (b->f_width() - last_rBearing);
accumulate_max(maxWidth, width);
width = (b->f_width() - last_rBearing);
continue;
}
@ -291,9 +320,9 @@ void String::recountNaturalSize(
// But when we layout block and we're sure that _maxWidth is enough
// for all the blocks to fit on their line we check each block, even the
// intermediate one with a large negative right bearing.
accumulate_max(_maxWidth, _width);
accumulate_max(maxWidth, width);
_width += last_rBearing + (last_rPadding + b->f_width() - b__f_rbearing);
width += last_rBearing + (last_rPadding + b->f_width() - b__f_rbearing);
lineHeight = qMax(lineHeight, blockHeight);
last_rBearing = b__f_rbearing;
@ -301,21 +330,14 @@ void String::recountNaturalSize(
continue;
}
if (initial) {
Qt::LayoutDirection direction = optionsDirection;
if (direction == Qt::LayoutDirectionAuto) {
direction = StringDirection(_text, lastNewlineStart, _text.size());
}
if (lastNewline) {
lastNewline->_nextDirection = direction;
} else {
_startDirection = direction;
}
computeParagraphDirection(_text.size());
}
if (_width > 0) {
if (width > 0) {
if (!lineHeight) lineHeight = CountBlockHeight(_blocks.back().get(), _st);
_minHeight += lineHeight;
accumulate_max(_maxWidth, _width);
accumulate_max(maxWidth, width);
}
_maxWidth = maxWidth.ceil().toInt();
}
int String::countMaxMonospaceWidth() const {
@ -405,13 +427,14 @@ void String::setMarkedText(const style::TextStyle &st, const TextWithEntities &t
}
void String::setLink(uint16 index, const ClickHandlerPtr &link) {
if (index > 0 && index <= _links.size()) {
_links[index - 1] = link;
const auto extended = _extended.get();
if (extended && index > 0 && index <= extended->links.size()) {
extended->links[index - 1] = link;
}
}
void String::setSpoilerRevealed(bool revealed, anim::type animated) {
const auto data = _spoiler.data.get();
const auto data = _extended ? _extended->spoiler.get() : nullptr;
if (!data) {
return;
} else if (data->revealed == revealed) {
@ -436,19 +459,19 @@ void String::setSpoilerRevealed(bool revealed, anim::type animated) {
}
void String::setSpoilerLinkFilter(Fn<bool(const ClickContext&)> filter) {
Expects(_spoiler.data != nullptr);
Expects(_extended && _extended->spoiler);
_spoiler.data->link = std::make_shared<SpoilerClickHandler>(
_extended->spoiler->link = std::make_shared<SpoilerClickHandler>(
this,
std::move(filter));
}
bool String::hasLinks() const {
return !_links.isEmpty();
return _extended && !_extended->links.empty();
}
bool String::hasSpoilers() const {
return (_spoiler.data != nullptr);
return _extended && (_extended->spoiler != nullptr);
}
bool String::hasSkipBlock() const {
@ -490,7 +513,7 @@ bool String::removeSkipBlock() {
int String::countWidth(int width, bool breakEverywhere) const {
if (QFixed(width) >= _maxWidth) {
return _maxWidth.ceil().toInt();
return _maxWidth;
}
QFixed maxLineWidth = 0;
@ -822,6 +845,13 @@ bool String::isEmpty() const {
return _blocks.empty() || _blocks[0]->type() == TextBlockType::Skip;
}
not_null<ExtendedData*> String::ensureExtended() {
if (!_extended) {
_extended = std::make_unique<ExtendedData>();
}
return _extended.get();
}
uint16 String::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const {
return (i + 1 == e) ? _text.size() : (*(i + 1))->position();
}
@ -857,7 +887,9 @@ void String::enumerateText(
return 0;
}
const auto result = (*i)->linkIndex();
return (result && _links.at(result - 1)) ? result : 0;
return (result && _extended && _extended->links[result - 1])
? result
: 0;
}();
if (blockLinkIndex != linkIndex) {
if (linkIndex) {
@ -866,9 +898,11 @@ void String::enumerateText(
if (rangeTo > rangeFrom) { // handle click handler
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
// Ignore links that are partially copied.
const auto handler = (linkPosition != rangeFrom || blockPosition != rangeTo)
const auto handler = (linkPosition != rangeFrom
|| blockPosition != rangeTo
|| !_extended)
? nullptr
: _links.at(linkIndex - 1);
: _extended->links[linkIndex - 1];
const auto type = handler
? handler->getTextEntity().type
: EntityType::Invalid;
@ -878,7 +912,9 @@ void String::enumerateText(
linkIndex = blockLinkIndex;
if (linkIndex) {
linkPosition = blockPosition;
const auto handler = _links.at(linkIndex - 1);
const auto handler = _extended
? _extended->links[linkIndex - 1]
: nullptr;
clickHandlerStartCallback(handler
? handler->getTextEntity().type
: EntityType::Invalid);
@ -916,7 +952,7 @@ void String::enumerateText(
}
bool String::hasPersistentAnimation() const {
return _hasCustomEmoji || _spoiler.data;
return _hasCustomEmoji || hasSpoilers();
}
void String::unloadPersistentAnimation() {
@ -958,6 +994,11 @@ bool String::hasNotEmojiAndSpaces() const {
return _hasNotEmojiAndSpaces;
}
const base::flat_map<int, int> &String::modifications() const {
static const auto kEmpty = base::flat_map<int, int>();
return _extended ? _extended->modifications : kEmpty;
}
QString String::toString(TextSelection selection) const {
return toText(selection, false, false).rich.text;
}
@ -1141,16 +1182,13 @@ IsolatedEmoji String::toIsolatedEmoji() const {
}
void String::clear() {
clearFields();
_text.clear();
}
void String::clearFields() {
_blocks.clear();
_links.clear();
_spoiler.data = nullptr;
_extended = nullptr;
_maxWidth = _minHeight = 0;
_startDirection = Qt::LayoutDirectionAuto;
_startParagraphIndex = 0;
_startParagraphLTR = false;
_startParagraphRTL = false;
}
bool IsBad(QChar ch) {

View file

@ -7,12 +7,12 @@
#pragma once
#include "ui/text/text_entity.h"
#include "ui/text/text_block.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/click_handler.h"
#include "base/flags.h"
#include "ui/style/style_core_types.h"
#include <crl/crl_time.h>
#include <private/qfixed_p.h>
#include <any>
class Painter;
@ -22,11 +22,18 @@ enum class type : uchar;
} // namespace anim
namespace style {
struct TextStyle;
struct TextPalette;
} // namespace style
namespace Ui {
class SpoilerMessCached;
extern const QString kQEllipsis;
inline constexpr auto kQFixedMax = (INT_MAX / 256);
} // namespace Ui
struct TextParseOptions {
@ -68,9 +75,12 @@ static constexpr TextSelection AllTextSelection = { 0, 0xFFFF };
namespace Ui::Text {
struct Block;
struct IsolatedEmoji;
struct OnlyCustomEmoji;
struct SpoilerData;
struct ParagraphDetails;
struct ExtendedData;
struct StateRequest {
enum class Flag {
@ -108,15 +118,13 @@ struct StateRequestElided : StateRequest {
class SpoilerMessCache {
public:
explicit SpoilerMessCache(int capacity);
~SpoilerMessCache();
[[nodiscard]] not_null<SpoilerMessCached*> lookup(QColor color);
void reset();
private:
struct Entry {
SpoilerMessCached mess;
QColor color;
};
struct Entry;
std::vector<Entry> _cache;
const int _capacity = 0;
@ -135,7 +143,7 @@ struct LineGeometry {
bool elided = false;
};
struct GeometryDescriptor {
Fn<LineGeometry(LineGeometry line, uint16 position)> layout;
Fn<LineGeometry(LineGeometry line)> layout;
bool breakEverywhere = false;
};
@ -176,17 +184,17 @@ struct PaintContext {
class String {
public:
String(int32 minResizeWidth = QFIXED_MAX);
String(int minResizeWidth = kQFixedMax);
String(
const style::TextStyle &st,
const QString &text,
const TextParseOptions &options = kDefaultTextOptions,
int32 minResizeWidth = QFIXED_MAX);
int minResizeWidth = kQFixedMax);
String(
const style::TextStyle &st,
const TextWithEntities &textWithEntities,
const TextParseOptions &options = kMarkupTextOptions,
int32 minResizeWidth = QFIXED_MAX,
int minResizeWidth = kQFixedMax,
const std::any &context = {});
String(String &&other);
String &operator=(String &&other);
@ -238,7 +246,7 @@ public:
bool removeSkipBlock();
[[nodiscard]] int maxWidth() const {
return _maxWidth.ceil().toInt();
return _maxWidth;
}
[[nodiscard]] int minHeight() const {
return _minHeight;
@ -293,9 +301,7 @@ public:
[[nodiscard]] OnlyCustomEmoji toOnlyCustomEmoji() const;
[[nodiscard]] bool hasNotEmojiAndSpaces() const;
[[nodiscard]] const base::flat_map<int, int> &modifications() const {
return _modifications;
}
[[nodiscard]] const base::flat_map<int, int> &modifications() const;
[[nodiscard]] const style::TextStyle *style() const {
return _st;
@ -305,64 +311,80 @@ public:
private:
using TextBlocks = std::vector<Block>;
using TextLinks = QVector<ClickHandlerPtr>;
class SpoilerDataWrap {
class ExtendedWrap : public std::unique_ptr<ExtendedData> {
public:
SpoilerDataWrap() noexcept;
SpoilerDataWrap(SpoilerDataWrap &&other) noexcept;
SpoilerDataWrap &operator=(SpoilerDataWrap &&other) noexcept;
ExtendedWrap() noexcept;
ExtendedWrap(ExtendedWrap &&other) noexcept;
ExtendedWrap &operator=(ExtendedWrap &&other) noexcept;
~ExtendedWrap();
std::unique_ptr<SpoilerData> data;
ExtendedWrap(
std::unique_ptr<ExtendedData> &&other) noexcept;
ExtendedWrap &operator=(
std::unique_ptr<ExtendedData> &&other) noexcept;
private:
void adjustFrom(const SpoilerDataWrap *other);
void adjustFrom(const ExtendedWrap *other);
};
uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
uint16 countBlockLength(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
[[nodiscard]] not_null<ExtendedData*> ensureExtended();
[[nodiscard]] uint16 countBlockEnd(
const TextBlocks::const_iterator &i,
const TextBlocks::const_iterator &e) const;
[[nodiscard]] uint16 countBlockLength(
const TextBlocks::const_iterator &i,
const TextBlocks::const_iterator &e) const;
// Template method for originalText(), originalTextWithEntities().
template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback>
void enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const;
template <
typename AppendPartCallback,
typename ClickHandlerStartCallback,
typename ClickHandlerFinishCallback,
typename FlagsChangeCallback>
void enumerateText(
TextSelection selection,
AppendPartCallback appendPartCallback,
ClickHandlerStartCallback clickHandlerStartCallback,
ClickHandlerFinishCallback clickHandlerFinishCallback,
FlagsChangeCallback flagsChangeCallback) const;
// Template method for countWidth(), countHeight(), countLineWidths().
// callback(lineWidth, lineHeight) will be called for all lines with:
// QFixed lineWidth, int lineHeight
template <typename Callback>
void enumerateLines(int w, bool breakEverywhere, Callback callback) const;
void enumerateLines(
int w,
bool breakEverywhere,
Callback callback) const;
void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto);
void recountNaturalSize(
bool initial,
Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto);
// clear() deletes all blocks and calls this method
// it is also called from move constructor / assignment operator
void clearFields();
TextForMimeData toText(
[[nodiscard]] TextForMimeData toText(
TextSelection selection,
bool composeExpanded,
bool composeEntities) const;
QFixed _minResizeWidth;
QFixed _maxWidth = 0;
int32 _minHeight = 0;
const style::TextStyle *_st = nullptr;
QString _text;
TextBlocks _blocks;
ExtendedWrap _extended;
int _minResizeWidth = 0;
int _maxWidth = 0;
int _minHeight = 0;
int16 _startParagraphIndex = 0;
bool _startParagraphLTR : 1 = false;
bool _startParagraphRTL : 1 = false;
bool _hasCustomEmoji : 1 = false;
bool _isIsolatedEmoji : 1 = false;
bool _isOnlyCustomEmoji : 1 = false;
bool _hasNotEmojiAndSpaces : 1 = false;
QString _text;
base::flat_map<int, int> _modifications;
const style::TextStyle *_st = nullptr;
TextBlocks _blocks;
TextLinks _links;
Qt::LayoutDirection _startDirection = Qt::LayoutDirectionAuto;
SpoilerDataWrap _spoiler;
friend class Parser;
friend class Renderer;

View file

@ -409,6 +409,14 @@ style::font WithFlags(
return result;
}
Qt::LayoutDirection UnpackParagraphDirection(bool ltr, bool rtl) {
return ltr
? Qt::LeftToRight
: rtl
? Qt::RightToLeft
: Qt::LayoutDirectionAuto;
}
AbstractBlock::AbstractBlock(
const style::font &font,
const QString &text,
@ -580,10 +588,6 @@ NewlineBlock::NewlineBlock(
colorIndex) {
}
Qt::LayoutDirection NewlineBlock::nextDirection() const {
return _nextDirection;
}
SkipBlock::SkipBlock(
const style::font &font,
const QString &text,

View file

@ -11,10 +11,10 @@
#include "ui/style/style_core.h"
#include "ui/emoji_config.h"
#include <private/qfixed_p.h>
#include <crl/crl_time.h>
#include <private/qfixed_p.h>
namespace style {
struct TextStyle;
} // namespace style
@ -49,6 +49,10 @@ using TextBlockFlags = base::flags<TextBlockFlag>;
TextBlockFlags flags,
uint32 fontFlags = 0);
[[nodiscard]] Qt::LayoutDirection UnpackParagraphDirection(
bool ltr,
bool rtl);
class AbstractBlock {
public:
[[nodiscard]] uint16 position() const;
@ -103,10 +107,17 @@ public:
uint16 linkIndex,
uint16 colorIndex);
[[nodiscard]] Qt::LayoutDirection nextDirection() const;
[[nodiscard]] int16 paragraphIndex() const {
return _paragraphIndex;
}
[[nodiscard]] Qt::LayoutDirection paragraphDirection() const {
return UnpackParagraphDirection(_paragraphLTR, _paragraphRTL);
}
private:
Qt::LayoutDirection _nextDirection = Qt::LayoutDirectionAuto;
int16 _paragraphIndex = 0;
bool _paragraphLTR : 1 = false;
bool _paragraphRTL : 1 = false;
friend class String;
friend class Parser;

View file

@ -4,7 +4,7 @@
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/text/text_spoiler_data.h"
#include "ui/text/text_extended_data.h"
#include "ui/text/text.h"

View file

@ -42,4 +42,22 @@ struct SpoilerData {
bool revealed = false;
};
struct ParagraphDetails {
QString language;
ClickHandlerPtr copy;
int copyWidth = 0;
int maxWidth = 0;
int minHeight = 0;
int scrollLeft = 0;
bool blockquote = false;
bool pre = false;
};
struct ExtendedData {
std::vector<ClickHandlerPtr> links;
std::vector<ParagraphDetails> paragraphs;
std::unique_ptr<SpoilerData> spoiler;
base::flat_map<int, int> modifications;
};
} // namespace Ui::Text

View file

@ -7,6 +7,7 @@
#pragma once
#include "base/variant.h"
#include "ui/emoji_config.h"
namespace Ui::Text {

View file

@ -8,11 +8,12 @@
#include "base/platform/base_platform_info.h"
#include "ui/integration.h"
#include "ui/text/text_extended_data.h"
#include "ui/text/text_isolated_emoji.h"
#include "ui/text/text_spoiler_data.h"
#include "styles/style_basic.h"
#include <QtCore/QUrl>
#include <private/qfixed_p.h>
namespace Ui::Text {
namespace {
@ -182,7 +183,7 @@ void Parser::createBlock(int32 skipBack) {
if (_newlineAwaited) {
_newlineAwaited = false;
if (!newline) {
++_t->_modifications[_blockStart];
updateModifications(_blockStart, 1);
_t->_text.insert(_blockStart, QChar::LineFeed);
createBlock(skipBack - length);
}
@ -223,7 +224,7 @@ void Parser::createBlock(int32 skipBack) {
void Parser::createNewlineBlock(bool fromOriginalText) {
if (!fromOriginalText) {
++_t->_modifications[_t->_text.size()];
updateModifications(_t->_text.size(), 1);
}
_t->_text.push_back(QChar::LineFeed);
_allowDiacritic = false;
@ -500,7 +501,7 @@ void Parser::parseCurrentChar() {
}
if (skip) {
--_t->_modifications[_t->_text.size()];
updateModifications(_t->_text.size(), -1);
_ch = 0;
_allowDiacritic = false;
} else {
@ -549,7 +550,7 @@ void Parser::parseEmojiFromCurrent() {
Assert(!_t->_text.isEmpty());
const auto last = _t->_text[_t->_text.size() - 1];
if (last.unicode() != Emoji::kPostfix) {
++_t->_modifications[_t->_text.size()];
updateModifications(_t->_text.size(), 1);
_t->_text.push_back(QChar(Emoji::kPostfix));
++len;
}
@ -579,16 +580,22 @@ bool Parser::isLinkEntity(const EntityInText &entity) const {
return ranges::find(urls, type) != std::end(urls);
}
void Parser::updateModifications(int index, int delta) {
_t->ensureExtended()->modifications[index] += delta;
}
void Parser::parse(const TextParseOptions &options) {
skipBadEntities();
trimSourceRange();
_t->_text.resize(0);
_t->_modifications = {};
if (_t->_extended) {
base::take(_t->_extended->modifications);
}
_t->_text.reserve(_end - _ptr);
if (_ptr > _start) {
_t->_modifications[0] = -(_ptr - _start);
updateModifications(0, -(_ptr - _start));
}
for (; _ptr <= _end; ++_ptr) {
@ -631,7 +638,12 @@ void Parser::trimSourceRange() {
// }
void Parser::finalize(const TextParseOptions &options) {
_t->_links.resize(_maxLinkIndex + _maxShiftedLinkIndex);
auto links = (_maxLinkIndex || _maxShiftedLinkIndex)
? &_t->ensureExtended()->links
: nullptr;
if (links) {
links->resize(_maxLinkIndex + _maxShiftedLinkIndex);
}
auto counterCustomIndex = uint16(0);
auto currentIndex = uint16(0); // Current the latest index of _t->_links.
struct {
@ -687,8 +699,9 @@ void Parser::finalize(const TextParseOptions &options) {
}
}
if (block->flags() & TextBlockFlag::Spoiler) {
if (!_t->_spoiler.data) {
_t->_spoiler.data = std::make_unique<SpoilerData>(
auto &spoiler = _t->ensureExtended()->spoiler;
if (!spoiler) {
spoiler = std::make_unique<SpoilerData>(
Integration::Instance().createSpoilerRepaint(_context));
}
}
@ -709,7 +722,10 @@ void Parser::finalize(const TextParseOptions &options) {
const auto handler = Integration::Instance().createLinkHandler(
_monos[monoIndex - 1],
_context);
_t->_links.resize(currentIndex);
if (!links) {
links = &_t->ensureExtended()->links;
}
links->resize(currentIndex);
if (handler) {
_t->setLink(currentIndex, handler);
}
@ -740,7 +756,9 @@ void Parser::finalize(const TextParseOptions &options) {
}
block->setLinkIndex(usedIndex());
_t->_links.resize(std::max(usedIndex(), uint16(_t->_links.size())));
if (links) {
links->resize(std::max(usedIndex(), uint16(links->size())));
}
const auto handler = Integration::Instance().createLinkHandler(
_links[realIndex - 1],
_context);
@ -749,10 +767,11 @@ void Parser::finalize(const TextParseOptions &options) {
}
lastHandlerIndex.lnk = realIndex;
}
if (!_t->_hasCustomEmoji || _t->_spoiler.data) {
const auto hasSpoiler = (_t->_extended && _t->_extended->spoiler);
if (!_t->_hasCustomEmoji || hasSpoiler) {
_t->_isOnlyCustomEmoji = false;
}
if (_t->_blocks.empty() || _t->_spoiler.data) {
if (_t->_blocks.empty() || hasSpoiler) {
_t->_isIsolatedEmoji = false;
}
if (!_t->_hasNotEmojiAndSpaces && spacesCheckFrom != uint16(-1)) {
@ -765,10 +784,12 @@ void Parser::finalize(const TextParseOptions &options) {
}
}
}
_t->_links.squeeze();
_t->_blocks.shrink_to_fit();
_t->_text.squeeze();
_t->_modifications.shrink_to_fit();
_t->_blocks.shrink_to_fit();
if (const auto extended = _t->_extended.get()) {
extended->links.shrink_to_fit();
extended->modifications.shrink_to_fit();
}
}
void Parser::computeLinkText(

View file

@ -7,6 +7,7 @@
#pragma once
#include "ui/text/text.h"
#include "ui/text/text_block.h"
namespace Ui::Text {
@ -80,6 +81,8 @@ private:
QString *outLinkText,
EntityLinkShown *outShown);
void updateModifications(int index, int delta);
const not_null<String*> _t;
const TextWithEntities _source;
const std::any &_context;

View file

@ -6,7 +6,7 @@
//
#include "ui/text/text_renderer.h"
#include "ui/text/text_spoiler_data.h"
#include "ui/text/text_extended_data.h"
#include "styles/style_basic.h"
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
@ -155,7 +155,7 @@ bool Distinct(FixedRange a, FixedRange b) {
Renderer::Renderer(const Ui::Text::String &t)
: _t(&t)
, _spoiler(_t->_spoiler.data.get()) {
, _spoiler(_t->_extended ? _t->_extended->spoiler.get() : nullptr) {
}
Renderer::~Renderer() {
@ -229,7 +229,11 @@ void Renderer::enumerate() {
_startTop = _y;
if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
initNextParagraph(_t->_blocks.cbegin(), _t->_startDirection);
initNextParagraph(
_t->_blocks.cbegin(),
UnpackParagraphDirection(
_t->_startParagraphLTR,
_t->_startParagraphRTL));
}
_lineHeight = 0;
@ -267,7 +271,7 @@ void Renderer::enumerate() {
initNextParagraph(
i + 1,
static_cast<const NewlineBlock*>(b)->nextDirection());
static_cast<const NewlineBlock*>(b)->paragraphDirection());
longWordLine = true;
continue;
@ -465,7 +469,7 @@ void Renderer::initNextLine() {
.left = 0,
.top = (_y - _startTop),
.width = _paragraphWidthRemaining.ceil().toInt(),
}, _lineStart);
});
_x = _startLeft + line.left;
_y = _startTop + line.top;
_lineWidth = _wLeft = line.width;
@ -1415,7 +1419,9 @@ void Renderer::eSetFont(const AbstractBlock *block) {
? false
: (underline == st::kLinkUnderlineActive)
? ((_palette && _palette->linkAlwaysActive)
|| ClickHandler::showAsActive(_t->_links.at(index - 1)))
|| ClickHandler::showAsActive(_t->_extended
? _t->_extended->links[index - 1]
: nullptr))
: true;
return underlined ? _t->_st->font->underline() : _t->_st->font;
}
@ -2016,8 +2022,10 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
if (isMono
&& block->linkIndex()
&& (!_background.spoiler || _spoiler->revealed)) {
_background.selectActiveBlock = ClickHandler::showAsPressed(
_t->_links.at(block->linkIndex() - 1));
const auto pressed = ClickHandler::showAsPressed(_t->_extended
? _t->_extended->links[block->linkIndex() - 1]
: nullptr);
_background.selectActiveBlock = pressed;
}
if (const auto color = block->colorIndex()) {
@ -2050,9 +2058,9 @@ ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
&& (block->flags() & TextBlockFlag::Spoiler))
? _spoiler->link
: ClickHandlerPtr();
return (spoilerLink || !block->linkIndex())
return (spoilerLink || !block->linkIndex() || !_t->_extended)
? spoilerLink
: _t->_links.at(block->linkIndex() - 1);
: _t->_extended->links[block->linkIndex() - 1];
}
} // namespace Ui::Text

View file

@ -7,6 +7,8 @@
#pragma once
#include "ui/text/text.h"
#include "ui/text/text_block.h"
#include "ui/text/text_custom_emoji.h"
#include <private/qtextengine_p.h>
@ -15,6 +17,8 @@ struct QScriptLine;
namespace Ui::Text {
struct AbstractBlock;
struct FixedRange {
QFixed from;
QFixed till;

View file

@ -45,7 +45,7 @@ Widget::Widget(QWidget *parent, const Config &config)
, _maxTextWidth(widthWithoutPadding(_st->maxWidth))
, _maxTextHeight(
config.st->style.font->height * (_multiline ? config.maxLines : 1))
, _text(_multiline ? widthWithoutPadding(config.st->minWidth) : QFIXED_MAX)
, _text(_multiline ? widthWithoutPadding(config.st->minWidth) : kQFixedMax)
, _clickHandlerFilter(config.filter) {
const auto toastOptions = TextParseOptions{
TextParseMultiline,

View file

@ -544,7 +544,7 @@ int Checkbox::countTextMinWidth() const {
+ _st.textPosition.x();
return (_st.width > 0)
? std::max(_st.width - leftSkip, 1)
: QFIXED_MAX;
: kQFixedMax;
}
QRect Checkbox::checkRect() const {

View file

@ -199,7 +199,7 @@ FlatLabel::FlatLabel(
const style::FlatLabel &st,
const style::PopupMenu &stMenu)
: RpWidget(parent)
, _text(st.minWidth ? st.minWidth : QFIXED_MAX)
, _text(st.minWidth ? st.minWidth : kQFixedMax)
, _st(st)
, _stMenu(stMenu) {
init();
@ -211,7 +211,7 @@ FlatLabel::FlatLabel(
const style::FlatLabel &st,
const style::PopupMenu &stMenu)
: RpWidget(parent)
, _text(st.minWidth ? st.minWidth : QFIXED_MAX)
, _text(st.minWidth ? st.minWidth : kQFixedMax)
, _st(st)
, _stMenu(stMenu) {
setText(text);
@ -224,7 +224,7 @@ FlatLabel::FlatLabel(
const style::FlatLabel &st,
const style::PopupMenu &stMenu)
: RpWidget(parent)
, _text(st.minWidth ? st.minWidth : QFIXED_MAX)
, _text(st.minWidth ? st.minWidth : kQFixedMax)
, _st(st)
, _stMenu(stMenu) {
textUpdated();
@ -242,7 +242,7 @@ FlatLabel::FlatLabel(
const style::FlatLabel &st,
const style::PopupMenu &stMenu)
: RpWidget(parent)
, _text(st.minWidth ? st.minWidth : QFIXED_MAX)
, _text(st.minWidth ? st.minWidth : kQFixedMax)
, _st(st)
, _stMenu(stMenu)
, _touchSelectTimer([=] { touchSelect(); }) {

View file

@ -7,6 +7,7 @@
#include "ui/widgets/time_input.h"
#include "ui/widgets/fields/time_part_input.h"
#include "base/qt/qt_string_view.h"
#include "base/invoke_queued.h"
#include <QtCore/QRegularExpression>

View file

@ -159,7 +159,8 @@ void Tooltip::paintEvent(QPaintEvent *e) {
p.fillRect(QRect(0, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder);
p.fillRect(QRect(width() - st::lineWidth, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder);
}
int32 lines = qFloor((height() - 2 * st::lineWidth - _st->textPadding.top() - _st->textPadding.bottom()) / _st->textStyle.font->height);
const auto lines = (height() - 2 * st::lineWidth - _st->textPadding.top() - _st->textPadding.bottom())
/ _st->textStyle.font->height;
p.setPen(_st->textFg);
_text.drawElided(p, st::lineWidth + _st->textPadding.left(), st::lineWidth + _st->textPadding.top(), width() - 2 * st::lineWidth - _st->textPadding.left() - _st->textPadding.right(), lines);