Paint nice code blocks.

This commit is contained in:
John Preston 2023-10-13 10:03:40 +04:00
parent ab5057f001
commit a38b60636a
9 changed files with 431 additions and 53 deletions

View file

@ -23,7 +23,11 @@ TextStyle {
font: font; font: font;
linkUnderline: int; linkUnderline: int;
blockPadding: margins; blockPadding: margins;
blockVerticalSkip: pixels;
blockHeader: pixels;
blockHeaderPosition: point;
blockOutline: pixels; blockOutline: pixels;
blockRadius: pixels;
preScrollable: bool; preScrollable: bool;
lineHeight: pixels; lineHeight: pixels;
} }
@ -57,9 +61,6 @@ defaultTextPalette: TextPalette {
defaultTextStyle: TextStyle { defaultTextStyle: TextStyle {
font: normalFont; font: normalFont;
linkUnderline: kLinkUnderlineActive; linkUnderline: kLinkUnderlineActive;
blockPadding: margins(10px, 4px, 6px, 4px);
blockOutline: 3px;
preScrollable: true;
lineHeight: 0px; lineHeight: 0px;
} }
semiboldTextStyle: TextStyle(defaultTextStyle) { semiboldTextStyle: TextStyle(defaultTextStyle) {

View file

@ -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() noexcept = default;
String::ExtendedWrap::ExtendedWrap(ExtendedWrap &&other) noexcept String::ExtendedWrap::ExtendedWrap(ExtendedWrap &&other) noexcept
@ -283,18 +421,34 @@ void String::recountNaturalSize(
} }
}; };
_maxWidth = _minHeight = 0; auto pindex = paragraphIndex(nullptr);
int32 lineHeight = 0; auto paragraph = paragraphByIndex(pindex);
QFixed maxWidth = 0; auto ppadding = paragraphPadding(paragraph);
QFixed width = 0, last_rBearing = 0, last_rPadding = 0;
_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) { for (auto &block : _blocks) {
auto b = block.get(); const auto b = block.get();
auto _btype = b->type(); const auto _btype = b->type();
auto blockHeight = CountBlockHeight(b, _st); const auto blockHeight = CountBlockHeight(b, _st);
if (_btype == TextBlockType::Newline) { if (_btype == TextBlockType::Newline) {
if (!lineHeight) { if (!lineHeight) {
lineHeight = blockHeight; 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) { if (initial) {
computeParagraphDirection(b->position()); computeParagraphDirection(b->position());
} }
@ -303,11 +457,12 @@ void String::recountNaturalSize(
_minHeight += lineHeight; _minHeight += lineHeight;
lineHeight = 0; lineHeight = 0;
last_rBearing = b->f_rbearing(); last_rBearing = 0;// b->f_rbearing(); (0 for newline)
last_rPadding = b->f_rpadding(); last_rPadding = 0;// b->f_rpadding(); (0 for newline)
accumulate_max(maxWidth, width); accumulate_max(maxWidth, width);
width = (b->f_width() - last_rBearing); width = ppadding.left() + ppadding.right();
// + (b->f_width() - last_rBearing); (0 for newline)
continue; continue;
} }
@ -333,11 +488,14 @@ void String::recountNaturalSize(
computeParagraphDirection(_text.size()); computeParagraphDirection(_text.size());
} }
if (width > 0) { if (width > 0) {
if (!lineHeight) lineHeight = CountBlockHeight(_blocks.back().get(), _st); if (!lineHeight) {
_minHeight += lineHeight; lineHeight = CountBlockHeight(_blocks.back().get(), _st);
}
_minHeight += ppadding.top() + lineHeight + ppadding.bottom();
accumulate_max(maxWidth, width); accumulate_max(maxWidth, width);
} }
_maxWidth = maxWidth.ceil().toInt(); _maxWidth = maxWidth.ceil().toInt();
_endsWithParagraphDetails = (pindex != 0);
} }
int String::countMaxMonospaceWidth() const { int String::countMaxMonospaceWidth() const {
@ -487,6 +645,17 @@ bool String::updateSkipBlock(int width, int height) {
} }
_text.resize(block->position()); _text.resize(block->position());
_blocks.pop_back(); _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('_'); _text.push_back('_');
_blocks.push_back(Block::Skip( _blocks.push_back(Block::Skip(
@ -504,9 +673,15 @@ bool String::updateSkipBlock(int width, int height) {
bool String::removeSkipBlock() { bool String::removeSkipBlock() {
if (_blocks.empty() || _blocks.back()->type() != TextBlockType::Skip) { if (_blocks.empty() || _blocks.back()->type() != TextBlockType::Skip) {
return false; 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()); _text.resize(_blocks.back()->position());
_blocks.pop_back(); _blocks.pop_back();
}
recountNaturalSize(false); recountNaturalSize(false);
return true; return true;
} }
@ -558,24 +733,42 @@ void String::enumerateLines(
int w, int w,
bool breakEverywhere, bool breakEverywhere,
Callback callback) const { Callback callback) const {
QFixed width = w; const auto width = QFixed(std::max(w, _minResizeWidth));
if (width < _minResizeWidth) width = _minResizeWidth;
int lineHeight = 0; auto pindex = paragraphIndex(nullptr);
QFixed widthLeft = width, last_rBearing = 0, last_rPadding = 0; 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; bool longWordLine = true;
for (auto &b : _blocks) { for (auto &b : _blocks) {
auto _btype = b->type(); auto _btype = b->type();
int blockHeight = CountBlockHeight(b.get(), _st); const auto blockHeight = CountBlockHeight(b.get(), _st);
if (_btype == TextBlockType::Newline) { 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); callback(width - widthLeft, lineHeight);
lineHeight = 0; lineHeight = 0;
last_rBearing = b->f_rbearing(); last_rBearing = 0;// b->f_rbearing(); (0 for newline)
last_rPadding = b->f_rpadding(); last_rPadding = 0;// b->f_rpadding(); (0 for newline)
widthLeft = width - (b->f_width() - last_rBearing); widthLeft = width - ppadding.left() - ppadding.right();
// - (b->f_width() - last_rBearing); (0 for newline)
longWordLine = true; longWordLine = true;
continue; continue;
@ -636,12 +829,16 @@ void String::enumerateLines(
j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width(); 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); lineHeight = qMax(0, blockHeight);
last_rBearing = j->f_rbearing(); last_rBearing = j->f_rbearing();
last_rPadding = j->f_rpadding(); last_rPadding = j->f_rpadding();
widthLeft = width - (j_width - last_rBearing); widthLeft = width
- ppadding.left()
- ppadding.right()
- (j_width - last_rBearing);
longWordLine = !wordEndsHere; longWordLine = !wordEndsHere;
f = j + 1; f = j + 1;
@ -651,18 +848,24 @@ void String::enumerateLines(
continue; continue;
} }
callback(width - widthLeft, lineHeight); callback(width - widthLeft, lineHeight + ppadding.top());
ppadding.setTop(0);
lineHeight = qMax(0, blockHeight); lineHeight = qMax(0, blockHeight);
last_rBearing = b__f_rbearing; last_rBearing = b__f_rbearing;
last_rPadding = b->f_rpadding(); 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; longWordLine = true;
continue; continue;
} }
if (widthLeft < width) { if (widthLeft < width) {
callback(width - widthLeft, lineHeight); callback(
width - widthLeft,
lineHeight + ppadding.top() + ppadding.bottom());
} }
} }
@ -852,14 +1055,42 @@ not_null<ExtendedData*> String::ensureExtended() {
return _extended.get(); 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(); 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(); 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<const NewlineBlock*>(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 < template <
typename AppendPartCallback, typename AppendPartCallback,
typename ClickHandlerStartCallback, typename ClickHandlerStartCallback,
@ -994,8 +1225,8 @@ bool String::hasNotEmojiAndSpaces() const {
return _hasNotEmojiAndSpaces; return _hasNotEmojiAndSpaces;
} }
const base::flat_map<int, int> &String::modifications() const { const base::flat_map<int, Deltas> &String::modifications() const {
static const auto kEmpty = base::flat_map<int, int>(); static const auto kEmpty = base::flat_map<int, Deltas>();
return _extended ? _extended->modifications : kEmpty; return _extended ? _extended->modifications : kEmpty;
} }

View file

@ -76,12 +76,18 @@ static constexpr TextSelection AllTextSelection = { 0, 0xFFFF };
namespace Ui::Text { namespace Ui::Text {
struct Block; struct Block;
class AbstractBlock;
struct IsolatedEmoji; struct IsolatedEmoji;
struct OnlyCustomEmoji; struct OnlyCustomEmoji;
struct SpoilerData; struct SpoilerData;
struct ParagraphDetails; struct ParagraphDetails;
struct ExtendedData; struct ExtendedData;
struct Deltas {
uint16 added = 0;
uint16 removed = 0;
};
struct StateRequest { struct StateRequest {
enum class Flag { enum class Flag {
BreakEverywhere = (1 << 0), BreakEverywhere = (1 << 0),
@ -157,6 +163,38 @@ struct GeometryDescriptor {
bool elisionOneLine, bool elisionOneLine,
bool elisionBreakEverywhere); 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 { struct PaintContext {
QPoint position; QPoint position;
int outerWidth = 0; // For automatic RTL Ui inversion. int outerWidth = 0; // For automatic RTL Ui inversion.
@ -166,6 +204,8 @@ struct PaintContext {
QRect clip; QRect clip;
const style::TextPalette *palette = nullptr; const style::TextPalette *palette = nullptr;
BlockPaintCache *pre = nullptr;
BlockPaintCache *blockquote = nullptr;
std::span<SpecialColor> colors; std::span<SpecialColor> colors;
SpoilerMessCache *spoiler = nullptr; SpoilerMessCache *spoiler = nullptr;
crl::time now = 0; crl::time now = 0;
@ -301,7 +341,7 @@ public:
[[nodiscard]] OnlyCustomEmoji toOnlyCustomEmoji() const; [[nodiscard]] OnlyCustomEmoji toOnlyCustomEmoji() const;
[[nodiscard]] bool hasNotEmojiAndSpaces() const; [[nodiscard]] bool hasNotEmojiAndSpaces() const;
[[nodiscard]] const base::flat_map<int, int> &modifications() const; [[nodiscard]] const base::flat_map<int, Deltas> &modifications() const;
[[nodiscard]] const style::TextStyle *style() const { [[nodiscard]] const style::TextStyle *style() const {
return _st; return _st;
@ -337,6 +377,11 @@ private:
[[nodiscard]] uint16 countBlockLength( [[nodiscard]] uint16 countBlockLength(
const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &i,
const TextBlocks::const_iterator &e) const; 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 method for originalText(), originalTextWithEntities().
template < template <
@ -377,13 +422,15 @@ private:
int _minResizeWidth = 0; int _minResizeWidth = 0;
int _maxWidth = 0; int _maxWidth = 0;
int _minHeight = 0; int _minHeight = 0;
int16 _startParagraphIndex = 0; uint16 _startParagraphIndex = 0;
bool _startParagraphLTR : 1 = false; bool _startParagraphLTR : 1 = false;
bool _startParagraphRTL : 1 = false; bool _startParagraphRTL : 1 = false;
bool _hasCustomEmoji : 1 = false; bool _hasCustomEmoji : 1 = false;
bool _isIsolatedEmoji : 1 = false; bool _isIsolatedEmoji : 1 = false;
bool _isOnlyCustomEmoji : 1 = false; bool _isOnlyCustomEmoji : 1 = false;
bool _hasNotEmojiAndSpaces : 1 = false; bool _hasNotEmojiAndSpaces : 1 = false;
bool _skipBlockAddedNewline : 1 = false;
bool _endsWithParagraphDetails : 1 = false;
friend class Parser; friend class Parser;
friend class Renderer; friend class Renderer;

View file

@ -107,7 +107,7 @@ public:
uint16 linkIndex, uint16 linkIndex,
uint16 colorIndex); uint16 colorIndex);
[[nodiscard]] int16 paragraphIndex() const { [[nodiscard]] uint16 paragraphIndex() const {
return _paragraphIndex; return _paragraphIndex;
} }
[[nodiscard]] Qt::LayoutDirection paragraphDirection() const { [[nodiscard]] Qt::LayoutDirection paragraphDirection() const {
@ -115,7 +115,7 @@ public:
} }
private: private:
int16 _paragraphIndex = 0; uint16 _paragraphIndex = 0;
bool _paragraphLTR : 1 = false; bool _paragraphLTR : 1 = false;
bool _paragraphRTL : 1 = false; bool _paragraphRTL : 1 = false;

View file

@ -12,6 +12,8 @@
namespace Ui::Text { namespace Ui::Text {
struct Deltas;
class String; class String;
class SpoilerClickHandler final : public ClickHandler { class SpoilerClickHandler final : public ClickHandler {
@ -57,7 +59,7 @@ struct ExtendedData {
std::vector<ClickHandlerPtr> links; std::vector<ClickHandlerPtr> links;
std::vector<ParagraphDetails> paragraphs; std::vector<ParagraphDetails> paragraphs;
std::unique_ptr<SpoilerData> spoiler; std::unique_ptr<SpoilerData> spoiler;
base::flat_map<int, int> modifications; base::flat_map<int, Deltas> modifications;
}; };
} // namespace Ui::Text } // namespace Ui::Text

View file

@ -211,6 +211,8 @@ void Parser::createBlock(int32 skipBack) {
push(&Block::Emoji, _emoji); push(&Block::Emoji, _emoji);
} else if (newline) { } else if (newline) {
push(&Block::Newline); push(&Block::Newline);
auto &newline = _t->_blocks.back().unsafe<NewlineBlock>();
newline._paragraphIndex = _paragraphIndex;
} else { } else {
push(&Block::Text, _t->_minResizeWidth); push(&Block::Text, _t->_minResizeWidth);
} }
@ -231,7 +233,7 @@ void Parser::createNewlineBlock(bool fromOriginalText) {
createBlock(); createBlock();
} }
void Parser::ensureAtNewline() { void Parser::ensureAtNewline(ParagraphDetails details) {
createBlock(); createBlock();
const auto lastType = _t->_blocks.empty() const auto lastType = _t->_blocks.empty()
? TextBlockType::Newline ? TextBlockType::Newline
@ -241,6 +243,16 @@ void Parser::ensureAtNewline() {
createNewlineBlock(false); createNewlineBlock(false);
_customEmojiData = base::take(saved); _customEmojiData = base::take(saved);
} }
auto &paragraphs = _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<NewlineBlock>()._paragraphIndex = index;
}
} }
void Parser::finishEntities() { void Parser::finishEntities() {
@ -259,11 +271,18 @@ void Parser::finishEntities() {
const auto lastType = _t->_blocks.empty() const auto lastType = _t->_blocks.empty()
? TextBlockType::Newline ? TextBlockType::Newline
: _t->_blocks.back()->type(); : _t->_blocks.back()->type();
if ((lastType != TextBlockType::Newline) if ((*flags)
&& ((*flags)
& (TextBlockFlag::Pre & (TextBlockFlag::Pre
| TextBlockFlag::Blockquote))) { | TextBlockFlag::Blockquote)) {
_paragraphIndex = 0;
if (lastType != TextBlockType::Newline) {
_newlineAwaited = true; _newlineAwaited = true;
} else if (_t->_blocks.empty()) {
_t->_startParagraphIndex = 0;
} else {
auto &last = _t->_blocks.back();
last.unsafe<NewlineBlock>()._paragraphIndex = 0;
}
} }
if (IsMono(*flags)) { if (IsMono(*flags)) {
_monoIndex = 0; _monoIndex = 0;
@ -340,7 +359,10 @@ bool Parser::checkEntities() {
flags = TextBlockFlag::Code; flags = TextBlockFlag::Code;
} else { } else {
flags = TextBlockFlag::Pre; flags = TextBlockFlag::Pre;
ensureAtNewline(); ensureAtNewline({
.language = _waitingEntity->data(),
.pre = true,
});
} }
const auto text = QString(entityBegin, entityLength); const auto text = QString(entityBegin, entityLength);
@ -356,7 +378,7 @@ bool Parser::checkEntities() {
} }
} else if (entityType == EntityType::Blockquote) { } else if (entityType == EntityType::Blockquote) {
flags = TextBlockFlag::Blockquote; flags = TextBlockFlag::Blockquote;
ensureAtNewline(); ensureAtNewline({ .blockquote = true });
} else if (entityType == EntityType::Url } else if (entityType == EntityType::Url
|| entityType == EntityType::Email || entityType == EntityType::Email
|| entityType == EntityType::Mention || entityType == EntityType::Mention
@ -581,7 +603,12 @@ bool Parser::isLinkEntity(const EntityInText &entity) const {
} }
void Parser::updateModifications(int index, int delta) { 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) { void Parser::parse(const TextParseOptions &options) {

View file

@ -11,6 +11,8 @@
namespace Ui::Text { namespace Ui::Text {
struct ParagraphDetails;
class Parser { class Parser {
public: public:
Parser( Parser(
@ -58,7 +60,7 @@ private:
void blockCreated(); void blockCreated();
void createBlock(int32 skipBack = 0); void createBlock(int32 skipBack = 0);
void createNewlineBlock(bool fromOriginalText); void createNewlineBlock(bool fromOriginalText);
void ensureAtNewline(); void ensureAtNewline(ParagraphDetails details);
// Returns true if at least one entity was parsed in the current position. // Returns true if at least one entity was parsed in the current position.
bool checkEntities(); bool checkEntities();
@ -113,6 +115,7 @@ private:
uint16 _linkIndex = 0; uint16 _linkIndex = 0;
uint16 _colorIndex = 0; uint16 _colorIndex = 0;
uint16 _monoIndex = 0; uint16 _monoIndex = 0;
uint16 _paragraphIndex = 0;
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero 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 _blockStart = 0; // offset in result, from which current parsed block is started
int32 _diacritics = 0; // diacritic chars skipped without good char int32 _diacritics = 0; // diacritic chars skipped without good char

View file

@ -206,6 +206,8 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
? (1. - _spoiler->revealAnimation.value( ? (1. - _spoiler->revealAnimation.value(
_spoiler->revealed ? 1. : 0.)) _spoiler->revealed ? 1. : 0.))
: 0.; : 0.;
_preBlockCache = context.pre;
_blockquoteBlockCache = context.blockquote;
enumerate(); enumerate();
} }
@ -231,6 +233,7 @@ void Renderer::enumerate() {
if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) { if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
initNextParagraph( initNextParagraph(
_t->_blocks.cbegin(), _t->_blocks.cbegin(),
_t->_startParagraphIndex,
UnpackParagraphDirection( UnpackParagraphDirection(
_t->_startParagraphLTR, _t->_startParagraphLTR,
_t->_startParagraphRTL)); _t->_startParagraphRTL));
@ -259,6 +262,9 @@ void Renderer::enumerate() {
if (!_lineHeight) { if (!_lineHeight) {
_lineHeight = blockHeight; _lineHeight = blockHeight;
} }
const auto pindex = static_cast<const NewlineBlock*>(b)->paragraphIndex();
const auto changed = (_pindex != pindex);
fillParagraphBg(changed ? _ppadding.bottom() : 0);
if (!drawLine((*i)->position(), i, e)) { if (!drawLine((*i)->position(), i, e)) {
return; return;
} }
@ -271,6 +277,7 @@ void Renderer::enumerate() {
initNextParagraph( initNextParagraph(
i + 1, i + 1,
pindex,
static_cast<const NewlineBlock*>(b)->paragraphDirection()); static_cast<const NewlineBlock*>(b)->paragraphDirection());
longWordLine = true; longWordLine = true;
@ -340,6 +347,7 @@ void Renderer::enumerate() {
: (j + 1 != en) : (j + 1 != en)
? (j + 1)->position() ? (j + 1)->position()
: _t->countBlockEnd(i, e); : _t->countBlockEnd(i, e);
fillParagraphBg(0);
if (!drawLine(lineEnd, i, e)) { if (!drawLine(lineEnd, i, e)) {
return; return;
} }
@ -368,6 +376,7 @@ void Renderer::enumerate() {
const auto lineEnd = !_elidedLine const auto lineEnd = !_elidedLine
? b->position() ? b->position()
: _t->countBlockEnd(i, e); : _t->countBlockEnd(i, e);
fillParagraphBg(0);
if (!drawLine(lineEnd, i, e)) { if (!drawLine(lineEnd, i, e)) {
return; return;
} }
@ -386,6 +395,7 @@ void Renderer::enumerate() {
continue; continue;
} }
if (_lineStart < _t->_text.size()) { if (_lineStart < _t->_text.size()) {
fillParagraphBg(_ppadding.bottom());
if (!drawLine(_t->_text.size(), e, e)) { if (!drawLine(_t->_text.size(), e, e)) {
return; 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( StateResult Renderer::getState(
QPoint point, QPoint point,
GeometryDescriptor geometry, GeometryDescriptor geometry,
@ -430,12 +471,22 @@ crl::time Renderer::now() const {
void Renderer::initNextParagraph( void Renderer::initNextParagraph(
String::TextBlocks::const_iterator i, String::TextBlocks::const_iterator i,
int16 paragraphIndex,
Qt::LayoutDirection direction) { Qt::LayoutDirection direction) {
_parDirection = (direction == Qt::LayoutDirectionAuto) _parDirection = (direction == Qt::LayoutDirectionAuto)
? style::LayoutDirection() ? style::LayoutDirection()
: direction; : direction;
_parStartBlock = i; _parStartBlock = i;
_paragraphWidthRemaining = 0; _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(); const auto e = _t->_blocks.cend();
if (i == e) { if (i == e) {
_lineStart = _parStart = _t->_text.size(); _lineStart = _parStart = _t->_text.size();
@ -461,6 +512,7 @@ void Renderer::initNextParagraph(
_parLength = ((i == e) ? _t->_text.size() : (*i)->position()) - _parStart; _parLength = ((i == e) ? _t->_text.size() : (*i)->position()) - _parStart;
} }
_parAnalysis.resize(0); _parAnalysis.resize(0);
_paragraphWidthRemaining += _ppadding.left() + _ppadding.right();
initNextLine(); initNextLine();
} }
@ -470,9 +522,12 @@ void Renderer::initNextLine() {
.top = (_y - _startTop), .top = (_y - _startTop),
.width = _paragraphWidthRemaining.ceil().toInt(), .width = _paragraphWidthRemaining.ceil().toInt(),
}); });
_x = _startLeft + line.left; _blockLineTop += _startTop + line.top - _y;
_x = _startLeft + line.left + _ppadding.left();
_y = _startTop + line.top; _y = _startTop + line.top;
_lineWidth = _wLeft = line.width; _startLineWidth = line.width;
_lineWidth = _startLineWidth - _ppadding.left() - _ppadding.right();
_wLeft = _lineWidth;
_elidedLine = line.elided; _elidedLine = line.elided;
} }
@ -1252,7 +1307,7 @@ void Renderer::prepareElidedLine(QString &lineText, int32 lineStart, int32 &line
eShapeLine(line); eShapeLine(line);
auto elideWidth = _f->elidew; 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 firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i; int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i;

View file

@ -54,6 +54,7 @@ private:
[[nodiscard]] crl::time now() const; [[nodiscard]] crl::time now() const;
void initNextParagraph( void initNextParagraph(
String::TextBlocks::const_iterator i, String::TextBlocks::const_iterator i,
int16 paragraphIndex,
Qt::LayoutDirection direction); Qt::LayoutDirection direction);
void initNextLine(); void initNextLine();
void initParagraphBidi(); void initParagraphBidi();
@ -89,6 +90,8 @@ private:
int repeat = 0); int repeat = 0);
void restoreAfterElided(); void restoreAfterElided();
void fillParagraphBg(int paddingBottom);
// COPIED FROM qtextengine.cpp AND MODIFIED // COPIED FROM qtextengine.cpp AND MODIFIED
static void eAppendItems( static void eAppendItems(
QScriptAnalysis *analysis, QScriptAnalysis *analysis,
@ -154,12 +157,21 @@ private:
int _parLength = 0; int _parLength = 0;
bool _parHasBidi = false; bool _parHasBidi = false;
QVarLengthArray<QScriptAnalysis, 4096> _parAnalysis; QVarLengthArray<QScriptAnalysis, 4096> _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 // current line data
QTextEngine *_e = nullptr; QTextEngine *_e = nullptr;
style::font _f; style::font _f;
int _startLeft = 0; int _startLeft = 0;
int _startTop = 0; int _startTop = 0;
int _startLineWidth = 0;
QFixed _x, _wLeft, _last_rPadding; QFixed _x, _wLeft, _last_rPadding;
int _y = 0; int _y = 0;
int _yDelta = 0; int _yDelta = 0;