From 24390ae85300bc1aa261194b899d9c7bc50e3dca Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 2 Nov 2023 20:19:28 +0400 Subject: [PATCH] Support complex dimensions in Ui::Text::String. --- ui/text/custom_emoji_instance.cpp | 8 ++ ui/text/custom_emoji_instance.h | 4 +- ui/text/text.cpp | 163 ++++++++++++++++++++++-------- ui/text/text.h | 11 +- ui/text/text_block.cpp | 2 +- ui/text/text_custom_emoji.cpp | 12 +++ ui/text/text_custom_emoji.h | 4 + ui/text/text_renderer.cpp | 6 +- ui/text/text_renderer.h | 1 - 9 files changed, 161 insertions(+), 50 deletions(-) diff --git a/ui/text/custom_emoji_instance.cpp b/ui/text/custom_emoji_instance.cpp index aadedce..29dec33 100644 --- a/ui/text/custom_emoji_instance.cpp +++ b/ui/text/custom_emoji_instance.cpp @@ -819,6 +819,10 @@ Object::~Object() { unload(); } +int Object::width() { + return st::emojiSize + 2 * st::emojiPadding; +} + QString Object::entityData() { return _instance->entityData(); } @@ -864,6 +868,10 @@ Internal::Internal(QString entityData, QImage image, bool colored) , _colored(colored) { } +int Internal::width() { + return _image.width() / _image.devicePixelRatio(); +} + QString Internal::entityData() { return _entityData; } diff --git a/ui/text/custom_emoji_instance.h b/ui/text/custom_emoji_instance.h index 6e08bc1..48ed3c3 100644 --- a/ui/text/custom_emoji_instance.h +++ b/ui/text/custom_emoji_instance.h @@ -258,6 +258,7 @@ public: Object(not_null instance, Fn repaint); ~Object(); + int width() override; QString entityData() override; void paint(QPainter &p, const Context &context) override; void unload() override; @@ -273,10 +274,11 @@ private: }; -class Internal final : public Ui::Text::CustomEmoji { +class Internal final : public Text::CustomEmoji { public: Internal(QString entityData, QImage image, bool colored); + int width() override; QString entityData() override; void paint(QPainter &p, const Context &context) override; void unload() override; diff --git a/ui/text/text.cpp b/ui/text/text.cpp index e5c9bcc..e83bd06 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -838,13 +838,37 @@ void String::removeModificationsAfter(int size) { } } +String::DimensionsResult String::countDimensions( + GeometryDescriptor geometry) const { + return countDimensions(std::move(geometry), {}); +} + +String::DimensionsResult String::countDimensions( + GeometryDescriptor geometry, + DimensionsRequest request) const { + auto result = DimensionsResult(); + if (request.lineWidths && request.reserve) { + result.lineWidths.reserve(request.reserve); + } + enumerateLines(geometry, [&](QFixed lineWidth, int lineBottom) { + const auto width = lineWidth.ceil().toInt(); + if (request.lineWidths) { + result.lineWidths.push_back(width); + } + result.width = std::max(result.width, width); + result.height = lineBottom; + }); + return result; + +} + int String::countWidth(int width, bool breakEverywhere) const { if (QFixed(width) >= _maxWidth) { return _maxWidth; } QFixed maxLineWidth = 0; - enumerateLines(width, breakEverywhere, [&](QFixed lineWidth, int lineHeight) { + enumerateLines(width, breakEverywhere, [&](QFixed lineWidth, int) { if (lineWidth > maxLineWidth) { maxLineWidth = lineWidth; } @@ -857,8 +881,8 @@ int String::countHeight(int width, bool breakEverywhere) const { return _minHeight; } int result = 0; - enumerateLines(width, breakEverywhere, [&](QFixed lineWidth, int lineHeight) { - result += lineHeight; + enumerateLines(width, breakEverywhere, [&](auto, int lineBottom) { + result = lineBottom; }); return result; } @@ -874,7 +898,7 @@ std::vector String::countLineWidths( if (options.reserve) { result.reserve(options.reserve); } - enumerateLines(width, options.breakEverywhere, [&](QFixed lineWidth, int lineHeight) { + enumerateLines(width, options.breakEverywhere, [&](QFixed lineWidth, int) { result.push_back(lineWidth.ceil().toInt()); }); return result; @@ -884,18 +908,85 @@ template void String::enumerateLines( int w, bool breakEverywhere, - Callback callback) const { - const auto width = QFixed(std::max(w, _minResizeWidth)); + Callback &&callback) const { + if (isEmpty()) { + return; + } + const auto width = std::max(w, _minResizeWidth); + auto g = SimpleGeometry(width, _st->font->height, 0, 0, false, false); + g.breakEverywhere = breakEverywhere; + enumerateLines(g, std::forward(callback)); +} + +template +void String::enumerateLines( + GeometryDescriptor geometry, + Callback &&callback) const { + auto qindex = 0; + auto quote = (QuoteDetails*)nullptr; + auto qpadding = QMargins(); + + auto top = 0; + auto lineLeft = 0; + auto lineWidth = 0; + auto widthLeft = QFixed(0); + auto paragraphWidthRemaining = QFixed(); + const auto initNextLine = [&] { + const auto line = geometry.layout({ + .left = 0, + .top = top, + .width = paragraphWidthRemaining.ceil().toInt(), + }); + lineLeft = line.left; + lineWidth = line.width; + if (quote && quote->maxWidth < lineWidth) { + const auto delta = lineWidth - quote->maxWidth; + lineWidth = quote->maxWidth; + } + widthLeft = lineWidth - qpadding.left() - qpadding.right(); + }; + const auto initNextParagraph = [&]( + TextBlocks::const_iterator i, + int16 paragraphIndex) { + paragraphWidthRemaining = 0; + if (qindex != paragraphIndex) { + top += qpadding.bottom(); + qindex = paragraphIndex; + quote = quoteByIndex(qindex); + qpadding = quotePadding(quote); + top += qpadding.top(); + qpadding.setTop(0); + } + const auto e = _blocks.cend(); + if (i != e) { + auto last_rPadding = QFixed(0); + auto last_rBearing = QFixed(0); + for (; i != e; ++i) { + if ((*i)->type() == TextBlockType::Newline) { + break; + } + const auto rBearing = (*i)->f_rbearing(); + paragraphWidthRemaining += last_rBearing + + last_rPadding + + (*i)->f_width() + - rBearing; + last_rBearing = rBearing; + } + } + paragraphWidthRemaining += qpadding.left() + qpadding.right(); + initNextLine(); + }; + + if ((*_blocks.cbegin())->type() != TextBlockType::Newline) { + initNextParagraph(_blocks.cbegin(), _startQuoteIndex); + } - auto qindex = quoteIndex(nullptr); - auto quote = quoteByIndex(qindex); - auto qpadding = quotePadding(quote); - auto widthLeft = width - qpadding.left() - qpadding.right(); auto lineHeight = 0; auto last_rBearing = QFixed(); auto last_rPadding = QFixed(); bool longWordLine = true; - for (auto &b : _blocks) { + for (auto i = _blocks.cbegin(); i != _blocks.cend(); ++i) { + const auto &b = *i; auto _btype = b->type(); const auto blockHeight = CountBlockHeight(b.get(), _st); @@ -903,25 +994,16 @@ void String::enumerateLines( if (!lineHeight) { lineHeight = blockHeight; } - lineHeight += qpadding.top(); - const auto index = quoteIndex(b.get()); - if (qindex != index) { + const auto index = b.unsafe().quoteIndex(); + const auto changed = (qindex != index); + if (changed) { lineHeight += qpadding.bottom(); - qindex = index; - quote = quoteByIndex(qindex); - qpadding = quotePadding(quote); - } else { - qpadding.setTop(0); } - callback(width - widthLeft, lineHeight); - + callback(lineLeft + lineWidth - widthLeft, top += lineHeight); lineHeight = 0; - last_rBearing = 0;// b->f_rbearing(); (0 for newline) - last_rPadding = 0;// b->f_rpadding(); (0 for newline) - widthLeft = width - qpadding.left() - qpadding.right(); - // - (b->f_width() - last_rBearing); (0 for newline) + initNextParagraph(i + 1, index); longWordLine = true; continue; } @@ -974,23 +1056,23 @@ void String::enumerateLines( continue; } - if (f != j && !breakEverywhere) { + if (f != j && !geometry.breakEverywhere) { j = f; widthLeft = f_wLeft; lineHeight = f_lineHeight; j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width(); } - callback(width - widthLeft, lineHeight + qpadding.top()); - qpadding.setTop(0); + callback(lineLeft + lineWidth - widthLeft, top += lineHeight); lineHeight = qMax(0, blockHeight); + + paragraphWidthRemaining -= (lineWidth - widthLeft) - last_rPadding + last_rBearing; + initNextLine(); + last_rBearing = j->f_rbearing(); last_rPadding = j->f_rpadding(); - widthLeft = width - - qpadding.left() - - qpadding.right() - - (j_width - last_rBearing); + widthLeft -= j_width - last_rBearing; longWordLine = !wordEndsHere; f = j + 1; @@ -1000,24 +1082,23 @@ void String::enumerateLines( continue; } - callback(width - widthLeft, lineHeight + qpadding.top()); - qpadding.setTop(0); + callback(lineLeft + lineWidth - widthLeft, top += lineHeight); lineHeight = qMax(0, blockHeight); + paragraphWidthRemaining -= (lineWidth - widthLeft) - last_rPadding + last_rBearing; + initNextLine(); + last_rBearing = b__f_rbearing; last_rPadding = b->f_rpadding(); - widthLeft = width - - qpadding.left() - - qpadding.right() - - (b->f_width() - last_rBearing); + widthLeft -= b->f_width() - last_rBearing; longWordLine = true; continue; } - if (widthLeft < width) { + if (widthLeft < lineWidth) { callback( - width - widthLeft, - lineHeight + qpadding.top() + qpadding.bottom()); + lineLeft + lineWidth - widthLeft, + top + lineHeight + qpadding.bottom()); } } diff --git a/ui/text/text.h b/ui/text/text.h index 29c8fc1..fc174d7 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -275,6 +275,7 @@ public: std::vector lineWidths; }; struct DimensionsRequest { + bool breakEverywhere = false; bool lineWidths = false; int reserve = 0; }; @@ -414,13 +415,17 @@ private: FlagsChangeCallback flagsChangeCallback) const; // Template method for countWidth(), countHeight(), countLineWidths(). - // callback(lineWidth, lineHeight) will be called for all lines with: - // QFixed lineWidth, int lineHeight + // callback(lineWidth, lineBottom) will be called for all lines with: + // QFixed lineWidth, int lineBottom template void enumerateLines( int w, bool breakEverywhere, - Callback callback) const; + Callback &&callback) const; + template + void enumerateLines( + GeometryDescriptor geometry, + Callback &&callback) const; void insertModifications(int position, int delta); void removeModificationsAfter(int size); diff --git a/ui/text/text_block.cpp b/ui/text/text_block.cpp index 43c5288..e24723d 100644 --- a/ui/text/text_block.cpp +++ b/ui/text/text_block.cpp @@ -551,7 +551,7 @@ CustomEmojiBlock::CustomEmojiBlock( linkIndex, colorIndex) , _custom(std::move(custom)) { - _width = int(st::emojiSize + 2 * st::emojiPadding); + _width = _custom->width(); _rpadding = 0; for (auto i = length; i != 0;) { auto ch = text[_position + (--i)]; diff --git a/ui/text/text_custom_emoji.cpp b/ui/text/text_custom_emoji.cpp index 3faa371..165c632 100644 --- a/ui/text/text_custom_emoji.cpp +++ b/ui/text/text_custom_emoji.cpp @@ -19,6 +19,10 @@ ShiftedEmoji::ShiftedEmoji( , _shift(shift) { } +int ShiftedEmoji::width() { + return _wrapped->width(); +} + QString ShiftedEmoji::entityData() { return _wrapped->entityData(); } @@ -45,6 +49,10 @@ FirstFrameEmoji::FirstFrameEmoji(std::unique_ptr wrapped) : _wrapped(std::move(wrapped)) { } +int FirstFrameEmoji::width() { + return _wrapped->width(); +} + QString FirstFrameEmoji::entityData() { return _wrapped->entityData(); } @@ -77,6 +85,10 @@ LimitedLoopsEmoji::LimitedLoopsEmoji( , _stopOnLast(stopOnLast) { } +int LimitedLoopsEmoji::width() { + return _wrapped->width(); +} + QString LimitedLoopsEmoji::entityData() { return _wrapped->entityData(); } diff --git a/ui/text/text_custom_emoji.h b/ui/text/text_custom_emoji.h index 76d525b..59b77a6 100644 --- a/ui/text/text_custom_emoji.h +++ b/ui/text/text_custom_emoji.h @@ -40,6 +40,7 @@ class CustomEmoji { public: virtual ~CustomEmoji() = default; + [[nodiscard]] virtual int width() = 0; [[nodiscard]] virtual QString entityData() = 0; using Context = CustomEmojiPaintContext; @@ -58,6 +59,7 @@ class ShiftedEmoji final : public CustomEmoji { public: ShiftedEmoji(std::unique_ptr wrapped, QPoint shift); + int width() override; QString entityData() override; void paint(QPainter &p, const Context &context) override; void unload() override; @@ -74,6 +76,7 @@ class FirstFrameEmoji final : public CustomEmoji { public: explicit FirstFrameEmoji(std::unique_ptr wrapped); + int width() override; QString entityData() override; void paint(QPainter &p, const Context &context) override; void unload() override; @@ -92,6 +95,7 @@ public: int limit, bool stopOnLast = false); + int width() override; QString entityData() override; void paint(QPainter &p, const Context &context) override; void unload() override; diff --git a/ui/text/text_renderer.cpp b/ui/text/text_renderer.cpp index 6aea539..42b28fb 100644 --- a/ui/text/text_renderer.cpp +++ b/ui/text/text_renderer.cpp @@ -949,14 +949,14 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato const auto color = (selected ? _currentPenSelected : _currentPen)->color(); - if (!_customEmojiSize) { - _customEmojiSize = AdjustCustomEmojiSize(st::emojiSize); - _customEmojiSkip = (st::emojiSize - _customEmojiSize) / 2; + if (!_customEmojiContext) { _customEmojiContext = CustomEmoji::Context{ .textColor = color, .now = now(), .paused = _pausedEmoji, }; + _customEmojiSkip = (st::emojiSize + - AdjustCustomEmojiSize(st::emojiSize)) / 2; } else { _customEmojiContext->textColor = color; } diff --git a/ui/text/text_renderer.h b/ui/text/text_renderer.h index 402caad..a248d4d 100644 --- a/ui/text/text_renderer.h +++ b/ui/text/text_renderer.h @@ -169,7 +169,6 @@ private: QVarLengthArray _highlightRects; std::optional _customEmojiContext; - int _customEmojiSize = 0; int _customEmojiSkip = 0; int _indexOfElidedBlock = -1; // For spoilers.