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;
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) {

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(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();
}
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<ExtendedData*> 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<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 <
typename AppendPartCallback,
typename ClickHandlerStartCallback,
@ -994,8 +1225,8 @@ bool String::hasNotEmojiAndSpaces() const {
return _hasNotEmojiAndSpaces;
}
const base::flat_map<int, int> &String::modifications() const {
static const auto kEmpty = base::flat_map<int, int>();
const base::flat_map<int, Deltas> &String::modifications() const {
static const auto kEmpty = base::flat_map<int, Deltas>();
return _extended ? _extended->modifications : kEmpty;
}

View file

@ -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<SpecialColor> 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<int, int> &modifications() const;
[[nodiscard]] const base::flat_map<int, Deltas> &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;

View file

@ -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;

View file

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

View file

@ -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<NewlineBlock>();
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 &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() {
@ -259,11 +271,18 @@ void Parser::finishEntities() {
const auto lastType = _t->_blocks.empty()
? TextBlockType::Newline
: _t->_blocks.back()->type();
if ((lastType != TextBlockType::Newline)
&& ((*flags)
if ((*flags)
& (TextBlockFlag::Pre
| TextBlockFlag::Blockquote))) {
| 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<NewlineBlock>()._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) {

View file

@ -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

View file

@ -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<const NewlineBlock*>(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<const NewlineBlock*>(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;

View file

@ -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<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
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;