From a38b60636a42932b5e4d3d2391769a34876a2626 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Oct 2023 10:03:40 +0400 Subject: [PATCH] Paint nice code blocks. --- ui/basic.style | 7 +- ui/text/text.cpp | 295 +++++++++++++++++++++++++++++++---- ui/text/text.h | 51 +++++- ui/text/text_block.h | 4 +- ui/text/text_extended_data.h | 4 +- ui/text/text_parser.cpp | 45 ++++-- ui/text/text_parser.h | 5 +- ui/text/text_renderer.cpp | 61 +++++++- ui/text/text_renderer.h | 12 ++ 9 files changed, 431 insertions(+), 53 deletions(-) diff --git a/ui/basic.style b/ui/basic.style index 0c0417f..faab247 100644 --- a/ui/basic.style +++ b/ui/basic.style @@ -23,7 +23,11 @@ TextStyle { font: font; linkUnderline: int; blockPadding: margins; + blockVerticalSkip: pixels; + blockHeader: pixels; + blockHeaderPosition: point; blockOutline: pixels; + blockRadius: pixels; preScrollable: bool; lineHeight: pixels; } @@ -57,9 +61,6 @@ defaultTextPalette: TextPalette { defaultTextStyle: TextStyle { font: normalFont; linkUnderline: kLinkUnderlineActive; - blockPadding: margins(10px, 4px, 6px, 4px); - blockOutline: 3px; - preScrollable: true; lineHeight: 0px; } semiboldTextStyle: TextStyle(defaultTextStyle) { diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 0044a9e..5b21a63 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -185,6 +185,144 @@ GeometryDescriptor SimpleGeometry( } }; + +void ValidateBlockPaintCache( + BlockPaintCache &cache, + const style::TextStyle &st) { + if (!cache.corners.isNull() + && cache.bgCached == cache.bg + && cache.outlineCached == cache.outline + && (!cache.withHeader || cache.headerCached == cache.header) + && (!cache.topright || cache.iconCached == cache.icon)) { + return; + } + cache.bgCached = cache.bg; + cache.outlineCached = cache.outline; + if (cache.withHeader) { + cache.headerCached = cache.header; + } + if (cache.topright) { + cache.iconCached = cache.icon; + } + const auto radius = st.blockRadius; + const auto header = cache.withHeader ? st.blockHeader : 0; + const auto outline = st.blockOutline; + const auto corner = std::max({ header, radius, outline }); + const auto middle = st::lineWidth; + const auto side = 2 * corner + middle; + const auto full = QSize(side, side); + const auto ratio = style::DevicePixelRatio(); + auto image = QImage(full * ratio, QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + image.setDevicePixelRatio(ratio); + auto p = QPainter(&image); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + + auto rect = QRect(QPoint(), full); + if (header) { + p.setBrush(cache.header); + p.setClipRect(outline, 0, side - outline, header); + p.drawRoundedRect(0, 0, side, corner + radius, radius, radius); + } + if (outline) { + p.setBrush(cache.outline); + p.setClipRect(0, 0, outline, side); + p.drawRoundedRect(0, 0, outline + radius * 2, side, radius, radius); + } + p.setBrush(cache.bg); + p.setClipRect(outline, header, side - outline, side - header); + p.drawRoundedRect(0, 0, side, side, radius, radius); + + p.end(); + cache.corners = std::move(image); +} + +void FillBlockPaint( + QPainter &p, + QRect rect, + const BlockPaintCache &cache, + const style::TextStyle &st, + SkipBlockPaintParts parts) { + const auto &image = cache.corners; + const auto ratio = int(image.devicePixelRatio()); + const auto iwidth = image.width() / ratio; + const auto iheight = image.height() / ratio; + const auto imiddle = st::lineWidth; + const auto ihalf = (iheight - imiddle) / 2; + const auto x = rect.left(); + const auto width = rect.width(); + auto y = rect.top(); + auto height = rect.height(); + if (!parts.skipTop) { + const auto top = std::min(height, ihalf); + p.drawImage( + QRect(x, y, ihalf, top), + image, + QRect(0, 0, ihalf * ratio, top * ratio)); + p.drawImage( + QRect(x + width - ihalf, y, ihalf, top), + image, + QRect((iwidth - ihalf) * ratio, 0, ihalf * ratio, top * ratio)); + if (const auto middle = width - 2 * ihalf) { + const auto header = cache.withHeader ? st.blockHeader : 0; + const auto fillHeader = std::min(header, top); + if (fillHeader) { + p.fillRect(x + ihalf, y, middle, fillHeader, cache.header); + } + if (const auto fillBody = top - fillHeader) { + p.fillRect( + QRect(x + ihalf, y + fillHeader, middle, fillBody), + cache.bg); + } + } + height -= top; + if (!height) { + return; + } + y += top; + rect.setTop(y); + } + if (!parts.skipBottom) { + const auto bottom = std::min(height, ihalf); + p.drawImage( + QRect(x, y + height - bottom, ihalf, bottom), + image, + QRect( + 0, + (iheight - bottom) * ratio, + ihalf * ratio, + bottom * ratio)); + p.drawImage( + QRect( + x + width - ihalf, + y + height - bottom, + ihalf, + bottom), + image, + QRect( + (iwidth - ihalf) * ratio, + (iheight - bottom) * ratio, + ihalf * ratio, + bottom * ratio)); + if (const auto middle = width - 2 * ihalf) { + p.fillRect( + QRect(x + ihalf, y + height - bottom, middle, bottom), + cache.bg); + } + height -= bottom; + if (!height) { + return; + } + rect.setHeight(height); + } + const auto outline = st.blockOutline; + if (outline) { + p.fillRect(x, y, outline, height, cache.outline); + } + p.fillRect(x + outline, y, width - outline, height, cache.bg); +} + String::ExtendedWrap::ExtendedWrap() noexcept = default; String::ExtendedWrap::ExtendedWrap(ExtendedWrap &&other) noexcept @@ -283,18 +421,34 @@ void String::recountNaturalSize( } }; - _maxWidth = _minHeight = 0; - int32 lineHeight = 0; - QFixed maxWidth = 0; - QFixed width = 0, last_rBearing = 0, last_rPadding = 0; + auto pindex = paragraphIndex(nullptr); + auto paragraph = paragraphByIndex(pindex); + auto ppadding = paragraphPadding(paragraph); + + _maxWidth = 0; + _minHeight = ppadding.top(); + auto lineHeight = 0; + auto maxWidth = QFixed(); + auto width = QFixed(ppadding.left() + ppadding.right()); + auto last_rBearing = QFixed(); + auto last_rPadding = QFixed(); for (auto &block : _blocks) { - auto b = block.get(); - auto _btype = b->type(); - auto blockHeight = CountBlockHeight(b, _st); + const auto b = block.get(); + const auto _btype = b->type(); + const auto blockHeight = CountBlockHeight(b, _st); if (_btype == TextBlockType::Newline) { if (!lineHeight) { lineHeight = blockHeight; } + const auto index = paragraphIndex(b); + if (pindex != index) { + _minHeight += ppadding.bottom(); + pindex = index; + paragraph = paragraphByIndex(pindex); + ppadding = paragraphPadding(paragraph); + _minHeight += ppadding.top(); + ppadding.setTop(0); + } if (initial) { computeParagraphDirection(b->position()); } @@ -303,11 +457,12 @@ void String::recountNaturalSize( _minHeight += lineHeight; lineHeight = 0; - last_rBearing = b->f_rbearing(); - last_rPadding = b->f_rpadding(); + last_rBearing = 0;// b->f_rbearing(); (0 for newline) + last_rPadding = 0;// b->f_rpadding(); (0 for newline) accumulate_max(maxWidth, width); - width = (b->f_width() - last_rBearing); + width = ppadding.left() + ppadding.right(); + // + (b->f_width() - last_rBearing); (0 for newline) continue; } @@ -333,11 +488,14 @@ void String::recountNaturalSize( computeParagraphDirection(_text.size()); } if (width > 0) { - if (!lineHeight) lineHeight = CountBlockHeight(_blocks.back().get(), _st); - _minHeight += lineHeight; + if (!lineHeight) { + lineHeight = CountBlockHeight(_blocks.back().get(), _st); + } + _minHeight += ppadding.top() + lineHeight + ppadding.bottom(); accumulate_max(maxWidth, width); } _maxWidth = maxWidth.ceil().toInt(); + _endsWithParagraphDetails = (pindex != 0); } int String::countMaxMonospaceWidth() const { @@ -487,6 +645,17 @@ bool String::updateSkipBlock(int width, int height) { } _text.resize(block->position()); _blocks.pop_back(); + } else if (_endsWithParagraphDetails) { + _text.push_back(QChar::LineFeed); + _blocks.push_back(Block::Newline( + _st->font, + _text, + _text.size() - 1, + 1, + 0, + 0, + 0)); + _skipBlockAddedNewline = true; } _text.push_back('_'); _blocks.push_back(Block::Skip( @@ -504,9 +673,15 @@ bool String::updateSkipBlock(int width, int height) { bool String::removeSkipBlock() { if (_blocks.empty() || _blocks.back()->type() != TextBlockType::Skip) { return false; + } else if (_skipBlockAddedNewline) { + _text.resize(_blocks.back()->position() - 1); + _blocks.pop_back(); + _blocks.pop_back(); + _skipBlockAddedNewline = false; + } else { + _text.resize(_blocks.back()->position()); + _blocks.pop_back(); } - _text.resize(_blocks.back()->position()); - _blocks.pop_back(); recountNaturalSize(false); return true; } @@ -558,24 +733,42 @@ void String::enumerateLines( int w, bool breakEverywhere, Callback callback) const { - QFixed width = w; - if (width < _minResizeWidth) width = _minResizeWidth; + const auto width = QFixed(std::max(w, _minResizeWidth)); - int lineHeight = 0; - QFixed widthLeft = width, last_rBearing = 0, last_rPadding = 0; + auto pindex = paragraphIndex(nullptr); + auto paragraph = paragraphByIndex(pindex); + auto ppadding = paragraphPadding(paragraph); + auto widthLeft = width - ppadding.left() - ppadding.right(); + auto lineHeight = 0; + auto last_rBearing = QFixed(); + auto last_rPadding = QFixed(); bool longWordLine = true; for (auto &b : _blocks) { auto _btype = b->type(); - int blockHeight = CountBlockHeight(b.get(), _st); + const auto blockHeight = CountBlockHeight(b.get(), _st); if (_btype == TextBlockType::Newline) { - if (!lineHeight) lineHeight = blockHeight; + if (!lineHeight) { + lineHeight = blockHeight; + } + lineHeight += ppadding.top(); + const auto index = paragraphIndex(b.get()); + if (pindex != index) { + lineHeight += ppadding.bottom(); + pindex = index; + paragraph = paragraphByIndex(pindex); + ppadding = paragraphPadding(paragraph); + } else { + ppadding.setTop(0); + } + callback(width - widthLeft, lineHeight); lineHeight = 0; - last_rBearing = b->f_rbearing(); - last_rPadding = b->f_rpadding(); - widthLeft = width - (b->f_width() - last_rBearing); + last_rBearing = 0;// b->f_rbearing(); (0 for newline) + last_rPadding = 0;// b->f_rpadding(); (0 for newline) + widthLeft = width - ppadding.left() - ppadding.right(); + // - (b->f_width() - last_rBearing); (0 for newline) longWordLine = true; continue; @@ -636,12 +829,16 @@ void String::enumerateLines( j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width(); } - callback(width - widthLeft, lineHeight); + callback(width - widthLeft, lineHeight + ppadding.top()); + ppadding.setTop(0); lineHeight = qMax(0, blockHeight); last_rBearing = j->f_rbearing(); last_rPadding = j->f_rpadding(); - widthLeft = width - (j_width - last_rBearing); + widthLeft = width + - ppadding.left() + - ppadding.right() + - (j_width - last_rBearing); longWordLine = !wordEndsHere; f = j + 1; @@ -651,18 +848,24 @@ void String::enumerateLines( continue; } - callback(width - widthLeft, lineHeight); + callback(width - widthLeft, lineHeight + ppadding.top()); + ppadding.setTop(0); lineHeight = qMax(0, blockHeight); last_rBearing = b__f_rbearing; last_rPadding = b->f_rpadding(); - widthLeft = width - (b->f_width() - last_rBearing); + widthLeft = width + - ppadding.left() + - ppadding.right() + - (b->f_width() - last_rBearing); longWordLine = true; continue; } if (widthLeft < width) { - callback(width - widthLeft, lineHeight); + callback( + width - widthLeft, + lineHeight + ppadding.top() + ppadding.bottom()); } } @@ -852,14 +1055,42 @@ not_null String::ensureExtended() { return _extended.get(); } -uint16 String::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const { +uint16 String::countBlockEnd( + const TextBlocks::const_iterator &i, + const TextBlocks::const_iterator &e) const { return (i + 1 == e) ? _text.size() : (*(i + 1))->position(); } -uint16 String::countBlockLength(const String::TextBlocks::const_iterator &i, const String::TextBlocks::const_iterator &e) const { +uint16 String::countBlockLength( + const TextBlocks::const_iterator &i, + const TextBlocks::const_iterator &e) const { return countBlockEnd(i, e) - (*i)->position(); } +ParagraphDetails *String::paragraphByIndex(int index) const { + Expects(!index + || (_extended && index <= _extended->paragraphs.size())); + + return index ? &_extended->paragraphs[index - 1] : nullptr; +} + +int String::paragraphIndex(const AbstractBlock *block) const { + Expects(!block || block->type() == TextBlockType::Newline); + + return block + ? static_cast(block)->paragraphIndex() + : _startParagraphIndex; +} + +QMargins String::paragraphPadding(ParagraphDetails *info) const { + if (!info) { + return {}; + } + const auto skip = _st->blockVerticalSkip; + const auto top = info->pre ? _st->blockHeader : 0; + return _st->blockPadding + QMargins(0, top + skip, 0, skip); +} + template < typename AppendPartCallback, typename ClickHandlerStartCallback, @@ -994,8 +1225,8 @@ bool String::hasNotEmojiAndSpaces() const { return _hasNotEmojiAndSpaces; } -const base::flat_map &String::modifications() const { - static const auto kEmpty = base::flat_map(); +const base::flat_map &String::modifications() const { + static const auto kEmpty = base::flat_map(); return _extended ? _extended->modifications : kEmpty; } diff --git a/ui/text/text.h b/ui/text/text.h index e90f2b9..4792391 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -76,12 +76,18 @@ static constexpr TextSelection AllTextSelection = { 0, 0xFFFF }; namespace Ui::Text { struct Block; +class AbstractBlock; struct IsolatedEmoji; struct OnlyCustomEmoji; struct SpoilerData; struct ParagraphDetails; struct ExtendedData; +struct Deltas { + uint16 added = 0; + uint16 removed = 0; +}; + struct StateRequest { enum class Flag { BreakEverywhere = (1 << 0), @@ -157,6 +163,38 @@ struct GeometryDescriptor { bool elisionOneLine, bool elisionBreakEverywhere); +struct BlockPaintCache { + QImage corners; + QColor headerCached; + QColor bgCached; + QColor outlineCached; + QColor iconCached; + + QColor header; + QColor bg; + QColor outline; + QColor icon; + + const style::icon *topright = nullptr; + QPoint toprightPosition; + bool withHeader = false; +}; + +void ValidateBlockPaintCache( + BlockPaintCache &cache, + const style::TextStyle &st); + +struct SkipBlockPaintParts { + bool skipTop : 1 = false; + bool skipBottom : 1 = false; +}; +void FillBlockPaint( + QPainter &p, + QRect rect, + const BlockPaintCache &cache, + const style::TextStyle &st, + SkipBlockPaintParts parts = {}); + struct PaintContext { QPoint position; int outerWidth = 0; // For automatic RTL Ui inversion. @@ -166,6 +204,8 @@ struct PaintContext { QRect clip; const style::TextPalette *palette = nullptr; + BlockPaintCache *pre = nullptr; + BlockPaintCache *blockquote = nullptr; std::span colors; SpoilerMessCache *spoiler = nullptr; crl::time now = 0; @@ -301,7 +341,7 @@ public: [[nodiscard]] OnlyCustomEmoji toOnlyCustomEmoji() const; [[nodiscard]] bool hasNotEmojiAndSpaces() const; - [[nodiscard]] const base::flat_map &modifications() const; + [[nodiscard]] const base::flat_map &modifications() const; [[nodiscard]] const style::TextStyle *style() const { return _st; @@ -337,6 +377,11 @@ private: [[nodiscard]] uint16 countBlockLength( const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const; + [[nodiscard]] ParagraphDetails *paragraphByIndex(int index) const; + [[nodiscard]] QMargins paragraphPadding(ParagraphDetails *info) const; + + // block must be either nullptr or a pointer to a NewlineBlock. + [[nodiscard]] int paragraphIndex(const AbstractBlock *block) const; // Template method for originalText(), originalTextWithEntities(). template < @@ -377,13 +422,15 @@ private: int _minResizeWidth = 0; int _maxWidth = 0; int _minHeight = 0; - int16 _startParagraphIndex = 0; + uint16 _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; + bool _skipBlockAddedNewline : 1 = false; + bool _endsWithParagraphDetails : 1 = false; friend class Parser; friend class Renderer; diff --git a/ui/text/text_block.h b/ui/text/text_block.h index 1c6a3c1..ae7a4ce 100644 --- a/ui/text/text_block.h +++ b/ui/text/text_block.h @@ -107,7 +107,7 @@ public: uint16 linkIndex, uint16 colorIndex); - [[nodiscard]] int16 paragraphIndex() const { + [[nodiscard]] uint16 paragraphIndex() const { return _paragraphIndex; } [[nodiscard]] Qt::LayoutDirection paragraphDirection() const { @@ -115,7 +115,7 @@ public: } private: - int16 _paragraphIndex = 0; + uint16 _paragraphIndex = 0; bool _paragraphLTR : 1 = false; bool _paragraphRTL : 1 = false; diff --git a/ui/text/text_extended_data.h b/ui/text/text_extended_data.h index 210d3af..a4151ea 100644 --- a/ui/text/text_extended_data.h +++ b/ui/text/text_extended_data.h @@ -12,6 +12,8 @@ namespace Ui::Text { +struct Deltas; + class String; class SpoilerClickHandler final : public ClickHandler { @@ -57,7 +59,7 @@ struct ExtendedData { std::vector links; std::vector paragraphs; std::unique_ptr spoiler; - base::flat_map modifications; + base::flat_map modifications; }; } // namespace Ui::Text diff --git a/ui/text/text_parser.cpp b/ui/text/text_parser.cpp index 8491d95..bd66466 100644 --- a/ui/text/text_parser.cpp +++ b/ui/text/text_parser.cpp @@ -211,6 +211,8 @@ void Parser::createBlock(int32 skipBack) { push(&Block::Emoji, _emoji); } else if (newline) { push(&Block::Newline); + auto &newline = _t->_blocks.back().unsafe(); + newline._paragraphIndex = _paragraphIndex; } else { push(&Block::Text, _t->_minResizeWidth); } @@ -231,7 +233,7 @@ void Parser::createNewlineBlock(bool fromOriginalText) { createBlock(); } -void Parser::ensureAtNewline() { +void Parser::ensureAtNewline(ParagraphDetails details) { createBlock(); const auto lastType = _t->_blocks.empty() ? TextBlockType::Newline @@ -241,6 +243,16 @@ void Parser::ensureAtNewline() { createNewlineBlock(false); _customEmojiData = base::take(saved); } + auto ¶graphs = _t->ensureExtended()->paragraphs; + paragraphs.push_back(std::move(details)); + const auto index = _paragraphIndex = int(paragraphs.size()); + if (_t->_blocks.empty()) { + _t->_startParagraphIndex = index; + } else { + auto &last = _t->_blocks.back(); + Assert(last->type() == TextBlockType::Newline); + last.unsafe()._paragraphIndex = index; + } } void Parser::finishEntities() { @@ -259,11 +271,18 @@ void Parser::finishEntities() { const auto lastType = _t->_blocks.empty() ? TextBlockType::Newline : _t->_blocks.back()->type(); - if ((lastType != TextBlockType::Newline) - && ((*flags) - & (TextBlockFlag::Pre - | TextBlockFlag::Blockquote))) { - _newlineAwaited = true; + if ((*flags) + & (TextBlockFlag::Pre + | TextBlockFlag::Blockquote)) { + _paragraphIndex = 0; + if (lastType != TextBlockType::Newline) { + _newlineAwaited = true; + } else if (_t->_blocks.empty()) { + _t->_startParagraphIndex = 0; + } else { + auto &last = _t->_blocks.back(); + last.unsafe()._paragraphIndex = 0; + } } if (IsMono(*flags)) { _monoIndex = 0; @@ -340,7 +359,10 @@ bool Parser::checkEntities() { flags = TextBlockFlag::Code; } else { flags = TextBlockFlag::Pre; - ensureAtNewline(); + ensureAtNewline({ + .language = _waitingEntity->data(), + .pre = true, + }); } const auto text = QString(entityBegin, entityLength); @@ -356,7 +378,7 @@ bool Parser::checkEntities() { } } else if (entityType == EntityType::Blockquote) { flags = TextBlockFlag::Blockquote; - ensureAtNewline(); + ensureAtNewline({ .blockquote = true }); } else if (entityType == EntityType::Url || entityType == EntityType::Email || entityType == EntityType::Mention @@ -581,7 +603,12 @@ bool Parser::isLinkEntity(const EntityInText &entity) const { } void Parser::updateModifications(int index, int delta) { - _t->ensureExtended()->modifications[index] += delta; + auto &deltas = _t->ensureExtended()->modifications[index]; + if (delta > 0) { + deltas.added += delta; + } else { + deltas.removed -= delta; + } } void Parser::parse(const TextParseOptions &options) { diff --git a/ui/text/text_parser.h b/ui/text/text_parser.h index 2401b59..20be972 100644 --- a/ui/text/text_parser.h +++ b/ui/text/text_parser.h @@ -11,6 +11,8 @@ namespace Ui::Text { +struct ParagraphDetails; + class Parser { public: Parser( @@ -58,7 +60,7 @@ private: void blockCreated(); void createBlock(int32 skipBack = 0); void createNewlineBlock(bool fromOriginalText); - void ensureAtNewline(); + void ensureAtNewline(ParagraphDetails details); // Returns true if at least one entity was parsed in the current position. bool checkEntities(); @@ -113,6 +115,7 @@ private: uint16 _linkIndex = 0; uint16 _colorIndex = 0; uint16 _monoIndex = 0; + uint16 _paragraphIndex = 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 _diacritics = 0; // diacritic chars skipped without good char diff --git a/ui/text/text_renderer.cpp b/ui/text/text_renderer.cpp index 0b2ac5d..9d4eb0c 100644 --- a/ui/text/text_renderer.cpp +++ b/ui/text/text_renderer.cpp @@ -206,6 +206,8 @@ void Renderer::draw(QPainter &p, const PaintContext &context) { ? (1. - _spoiler->revealAnimation.value( _spoiler->revealed ? 1. : 0.)) : 0.; + _preBlockCache = context.pre; + _blockquoteBlockCache = context.blockquote; enumerate(); } @@ -231,6 +233,7 @@ void Renderer::enumerate() { if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) { initNextParagraph( _t->_blocks.cbegin(), + _t->_startParagraphIndex, UnpackParagraphDirection( _t->_startParagraphLTR, _t->_startParagraphRTL)); @@ -259,6 +262,9 @@ void Renderer::enumerate() { if (!_lineHeight) { _lineHeight = blockHeight; } + const auto pindex = static_cast(b)->paragraphIndex(); + const auto changed = (_pindex != pindex); + fillParagraphBg(changed ? _ppadding.bottom() : 0); if (!drawLine((*i)->position(), i, e)) { return; } @@ -271,6 +277,7 @@ void Renderer::enumerate() { initNextParagraph( i + 1, + pindex, static_cast(b)->paragraphDirection()); longWordLine = true; @@ -340,6 +347,7 @@ void Renderer::enumerate() { : (j + 1 != en) ? (j + 1)->position() : _t->countBlockEnd(i, e); + fillParagraphBg(0); if (!drawLine(lineEnd, i, e)) { return; } @@ -368,6 +376,7 @@ void Renderer::enumerate() { const auto lineEnd = !_elidedLine ? b->position() : _t->countBlockEnd(i, e); + fillParagraphBg(0); if (!drawLine(lineEnd, i, e)) { return; } @@ -386,6 +395,7 @@ void Renderer::enumerate() { continue; } if (_lineStart < _t->_text.size()) { + fillParagraphBg(_ppadding.bottom()); if (!drawLine(_t->_text.size(), e, e)) { return; } @@ -396,6 +406,37 @@ void Renderer::enumerate() { } } +void Renderer::fillParagraphBg(int paddingBottom) { + const auto cache = (!_p || !_paragraph) + ? nullptr + : _paragraph->pre + ? _preBlockCache + : _paragraph->blockquote + ? _blockquoteBlockCache + : nullptr; + if (cache) { + auto &valid = _paragraph->pre + ? _preBlockCacheValid + : _blockquoteBlockCacheValid; + if (!valid) { + valid = true; + ValidateBlockPaintCache(*cache, *_t->_st); + } + const auto skip = _t->_st->blockVerticalSkip; + const auto isTop = (_y != _blockLineTop); + const auto isBottom = (paddingBottom != 0); + const auto top = _blockLineTop + (isTop ? skip : 0); + const auto fill = _y + _lineHeight + paddingBottom - top + - (isBottom ? skip : 0); + const auto rect = QRect(_startLeft, top, _startLineWidth, fill); + FillBlockPaint(*_p, rect, *cache, *_t->_st, { + .skipTop = !isTop, + .skipBottom = !isBottom, + }); + } + _blockLineTop = _y + _lineHeight + paddingBottom; +} + StateResult Renderer::getState( QPoint point, GeometryDescriptor geometry, @@ -430,12 +471,22 @@ crl::time Renderer::now() const { void Renderer::initNextParagraph( String::TextBlocks::const_iterator i, + int16 paragraphIndex, Qt::LayoutDirection direction) { _parDirection = (direction == Qt::LayoutDirectionAuto) ? style::LayoutDirection() : direction; _parStartBlock = i; _paragraphWidthRemaining = 0; + if (_pindex != paragraphIndex) { + _y += _ppadding.bottom(); + _pindex = paragraphIndex; + _paragraph = _t->paragraphByIndex(paragraphIndex); + _ppadding = _t->paragraphPadding(_paragraph); + _blockLineTop = _y; + _y += _ppadding.top(); + _ppadding.setTop(0); + } const auto e = _t->_blocks.cend(); if (i == e) { _lineStart = _parStart = _t->_text.size(); @@ -461,6 +512,7 @@ void Renderer::initNextParagraph( _parLength = ((i == e) ? _t->_text.size() : (*i)->position()) - _parStart; } _parAnalysis.resize(0); + _paragraphWidthRemaining += _ppadding.left() + _ppadding.right(); initNextLine(); } @@ -470,9 +522,12 @@ void Renderer::initNextLine() { .top = (_y - _startTop), .width = _paragraphWidthRemaining.ceil().toInt(), }); - _x = _startLeft + line.left; + _blockLineTop += _startTop + line.top - _y; + _x = _startLeft + line.left + _ppadding.left(); _y = _startTop + line.top; - _lineWidth = _wLeft = line.width; + _startLineWidth = line.width; + _lineWidth = _startLineWidth - _ppadding.left() - _ppadding.right(); + _wLeft = _lineWidth; _elidedLine = line.elided; } @@ -1252,7 +1307,7 @@ void Renderer::prepareElidedLine(QString &lineText, int32 lineStart, int32 &line eShapeLine(line); auto elideWidth = _f->elidew; - _wLeft = _lineWidth - elideWidth; + _wLeft = _lineWidth - _ppadding.left() - _ppadding.right() - 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; diff --git a/ui/text/text_renderer.h b/ui/text/text_renderer.h index 3210900..08bc5de 100644 --- a/ui/text/text_renderer.h +++ b/ui/text/text_renderer.h @@ -54,6 +54,7 @@ private: [[nodiscard]] crl::time now() const; void initNextParagraph( String::TextBlocks::const_iterator i, + int16 paragraphIndex, Qt::LayoutDirection direction); void initNextLine(); void initParagraphBidi(); @@ -89,6 +90,8 @@ private: int repeat = 0); void restoreAfterElided(); + void fillParagraphBg(int paddingBottom); + // COPIED FROM qtextengine.cpp AND MODIFIED static void eAppendItems( QScriptAnalysis *analysis, @@ -154,12 +157,21 @@ private: int _parLength = 0; bool _parHasBidi = false; QVarLengthArray _parAnalysis; + ParagraphDetails *_paragraph = nullptr; + int _pindex = 0; + QMargins _ppadding; + int _blockLineTop = 0; + BlockPaintCache *_preBlockCache = nullptr; + BlockPaintCache *_blockquoteBlockCache = nullptr; + bool _preBlockCacheValid = false; + bool _blockquoteBlockCacheValid = false; // current line data QTextEngine *_e = nullptr; style::font _f; int _startLeft = 0; int _startTop = 0; + int _startLineWidth = 0; QFixed _x, _wLeft, _last_rPadding; int _y = 0; int _yDelta = 0;