diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d03d8b..281dbb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 1327251..0044a9e 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -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 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 layout, + Fn 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(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 &&other) noexcept +: unique_ptr(std::move(other)) { + Assert(!get() || !get()->spoiler); +} + +String::ExtendedWrap &String::ExtendedWrap::operator=( + std::unique_ptr &&other) noexcept { + *static_cast(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(pointer); }; - data->link->setText(reinterpret_cast( - raw(data->link->text().get()) + raw(this) - raw(other))); + const auto otherText = raw(data->spoiler->link->text().get()); + data->spoiler->link->setText( + reinterpret_cast(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(); @@ -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 filter) { - Expects(_spoiler.data != nullptr); + Expects(_extended && _extended->spoiler); - _spoiler.data->link = std::make_shared( + _extended->spoiler->link = std::make_shared( 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 String::ensureExtended() { + if (!_extended) { + _extended = std::make_unique(); + } + 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 &String::modifications() const { + static const auto kEmpty = base::flat_map(); + 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) { diff --git a/ui/text/text.h b/ui/text/text.h index a627a56..e90f2b9 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -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 -#include #include 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 lookup(QColor color); void reset(); private: - struct Entry { - SpoilerMessCached mess; - QColor color; - }; + struct Entry; std::vector _cache; const int _capacity = 0; @@ -135,7 +143,7 @@ struct LineGeometry { bool elided = false; }; struct GeometryDescriptor { - Fn layout; + Fn 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 &modifications() const { - return _modifications; - } + [[nodiscard]] const base::flat_map &modifications() const; [[nodiscard]] const style::TextStyle *style() const { return _st; @@ -305,64 +311,80 @@ public: private: using TextBlocks = std::vector; - using TextLinks = QVector; - class SpoilerDataWrap { + class ExtendedWrap : public std::unique_ptr { 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 data; + ExtendedWrap( + std::unique_ptr &&other) noexcept; + ExtendedWrap &operator=( + std::unique_ptr &&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 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 - 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 - 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 _modifications; - const style::TextStyle *_st = nullptr; - - TextBlocks _blocks; - TextLinks _links; - - Qt::LayoutDirection _startDirection = Qt::LayoutDirectionAuto; - - SpoilerDataWrap _spoiler; - friend class Parser; friend class Renderer; diff --git a/ui/text/text_block.cpp b/ui/text/text_block.cpp index c5ce3d9..4e0b8cf 100644 --- a/ui/text/text_block.cpp +++ b/ui/text/text_block.cpp @@ -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, diff --git a/ui/text/text_block.h b/ui/text/text_block.h index b0e7168..1c6a3c1 100644 --- a/ui/text/text_block.h +++ b/ui/text/text_block.h @@ -11,10 +11,10 @@ #include "ui/style/style_core.h" #include "ui/emoji_config.h" -#include - #include +#include + namespace style { struct TextStyle; } // namespace style @@ -49,6 +49,10 @@ using TextBlockFlags = base::flags; 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; diff --git a/ui/text/text_spoiler_data.cpp b/ui/text/text_extended_data.cpp similarity index 95% rename from ui/text/text_spoiler_data.cpp rename to ui/text/text_extended_data.cpp index 57127bc..38cb0e7 100644 --- a/ui/text/text_spoiler_data.cpp +++ b/ui/text/text_extended_data.cpp @@ -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" diff --git a/ui/text/text_spoiler_data.h b/ui/text/text_extended_data.h similarity index 73% rename from ui/text/text_spoiler_data.h rename to ui/text/text_extended_data.h index 5a20926..210d3af 100644 --- a/ui/text/text_spoiler_data.h +++ b/ui/text/text_extended_data.h @@ -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 links; + std::vector paragraphs; + std::unique_ptr spoiler; + base::flat_map modifications; +}; + } // namespace Ui::Text diff --git a/ui/text/text_isolated_emoji.h b/ui/text/text_isolated_emoji.h index d73ef66..54707e7 100644 --- a/ui/text/text_isolated_emoji.h +++ b/ui/text/text_isolated_emoji.h @@ -7,6 +7,7 @@ #pragma once #include "base/variant.h" +#include "ui/emoji_config.h" namespace Ui::Text { diff --git a/ui/text/text_parser.cpp b/ui/text/text_parser.cpp index 72ef261..8491d95 100644 --- a/ui/text/text_parser.cpp +++ b/ui/text/text_parser.cpp @@ -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 +#include 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( + auto &spoiler = _t->ensureExtended()->spoiler; + if (!spoiler) { + spoiler = std::make_unique( 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( diff --git a/ui/text/text_parser.h b/ui/text/text_parser.h index d844edf..2401b59 100644 --- a/ui/text/text_parser.h +++ b/ui/text/text_parser.h @@ -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 _t; const TextWithEntities _source; const std::any &_context; diff --git a/ui/text/text_renderer.cpp b/ui/text/text_renderer.cpp index 83fd50a..0b2ac5d 100644 --- a/ui/text/text_renderer.cpp +++ b/ui/text/text_renderer.cpp @@ -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(b)->nextDirection()); + static_cast(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 diff --git a/ui/text/text_renderer.h b/ui/text/text_renderer.h index 184c1f3..3210900 100644 --- a/ui/text/text_renderer.h +++ b/ui/text/text_renderer.h @@ -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 @@ -15,6 +17,8 @@ struct QScriptLine; namespace Ui::Text { +struct AbstractBlock; + struct FixedRange { QFixed from; QFixed till; diff --git a/ui/toast/toast_widget.cpp b/ui/toast/toast_widget.cpp index 6849046..a1b8cd7 100644 --- a/ui/toast/toast_widget.cpp +++ b/ui/toast/toast_widget.cpp @@ -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, diff --git a/ui/widgets/checkbox.cpp b/ui/widgets/checkbox.cpp index 8aabbc5..d17184d 100644 --- a/ui/widgets/checkbox.cpp +++ b/ui/widgets/checkbox.cpp @@ -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 { diff --git a/ui/widgets/labels.cpp b/ui/widgets/labels.cpp index 8b6dcaa..8b75997 100644 --- a/ui/widgets/labels.cpp +++ b/ui/widgets/labels.cpp @@ -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(); }) { diff --git a/ui/widgets/time_input.cpp b/ui/widgets/time_input.cpp index d40aa26..1317f3a 100644 --- a/ui/widgets/time_input.cpp +++ b/ui/widgets/time_input.cpp @@ -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 diff --git a/ui/widgets/tooltip.cpp b/ui/widgets/tooltip.cpp index bd05590..317c6b3 100644 --- a/ui/widgets/tooltip.cpp +++ b/ui/widgets/tooltip.cpp @@ -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);