Support two-color quote outlines.

This commit is contained in:
John Preston 2023-10-23 14:58:10 +04:00
parent 4d1a5686a7
commit 71d24af3a8
4 changed files with 112 additions and 62 deletions

View file

@ -191,13 +191,15 @@ void ValidateQuotePaintCache(
const auto icon = st.icon.empty() ? nullptr : &st.icon; const auto icon = st.icon.empty() ? nullptr : &st.icon;
if (!cache.corners.isNull() if (!cache.corners.isNull()
&& cache.bgCached == cache.bg && cache.bgCached == cache.bg
&& cache.outlineCached == cache.outline && cache.outline1Cached == cache.outline1
&& cache.outline2Cached == cache.outline2
&& (!st.header || cache.headerCached == cache.header) && (!st.header || cache.headerCached == cache.header)
&& (!icon || cache.iconCached == cache.icon)) { && (!icon || cache.iconCached == cache.icon)) {
return; return;
} }
cache.bgCached = cache.bg; cache.bgCached = cache.bg;
cache.outlineCached = cache.outline; cache.outline1Cached = cache.outline1;
cache.outline2Cached = cache.outline2;
if (st.header) { if (st.header) {
cache.headerCached = cache.header; cache.headerCached = cache.header;
} }
@ -217,6 +219,28 @@ void ValidateQuotePaintCache(
const auto side = 2 * corner + middle; const auto side = 2 * corner + middle;
const auto full = QSize(side, side); const auto full = QSize(side, side);
const auto ratio = style::DevicePixelRatio(); const auto ratio = style::DevicePixelRatio();
if (cache.outline1 == cache.outline2) {
cache.outline = QImage();
} else if (const auto outline = st.outline) {
const auto size = QSize(outline, outline * 6);
cache.outline = QImage(
size * ratio,
QImage::Format_ARGB32_Premultiplied);
cache.outline.fill(cache.outline1);
cache.outline.setDevicePixelRatio(ratio);
auto p = QPainter(&cache.outline);
p.setCompositionMode(QPainter::CompositionMode_Source);
auto hq = PainterHighQualityEnabler(p);
auto path = QPainterPath();
path.moveTo(outline, outline);
path.lineTo(outline, outline * 4);
path.lineTo(0, outline * 5);
path.lineTo(0, outline * 2);
path.lineTo(outline, outline);
p.fillPath(path, cache.outline2);
}
auto image = QImage(full * ratio, QImage::Format_ARGB32_Premultiplied); auto image = QImage(full * ratio, QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent); image.fill(Qt::transparent);
image.setDevicePixelRatio(ratio); image.setDevicePixelRatio(ratio);
@ -230,9 +254,16 @@ void ValidateQuotePaintCache(
p.drawRoundedRect(0, 0, side, corner + radius, radius, radius); p.drawRoundedRect(0, 0, side, corner + radius, radius, radius);
} }
if (outline) { if (outline) {
p.setBrush(cache.outline); const auto rect = QRect(0, 0, outline + radius * 2, side);
p.setClipRect(0, 0, outline, side); if (!cache.outline.isNull()) {
p.drawRoundedRect(0, 0, outline + radius * 2, side, radius, radius); p.setBrush(cache.outline);
p.setClipRect(0, 0, outline, side);
p.drawRoundedRect(rect, radius, radius);
} else {
p.setBrush(cache.outline1);
p.setClipRect(0, 0, outline, side);
p.drawRoundedRect(rect, radius, radius);
}
} }
p.setBrush(cache.bg); p.setBrush(cache.bg);
p.setClipRect(outline, header, side - outline, side - header); p.setClipRect(outline, header, side - outline, side - header);
@ -264,7 +295,7 @@ void FillQuotePaint(
const auto width = rect.width(); const auto width = rect.width();
auto y = rect.top(); auto y = rect.top();
auto height = rect.height(); auto height = rect.height();
if (!parts.skipTop) { if (!parts.skippedTop) {
const auto top = std::min(height, ihalf); const auto top = std::min(height, ihalf);
p.drawImage( p.drawImage(
QRect(x, y, ihalf, top), QRect(x, y, ihalf, top),
@ -293,15 +324,17 @@ void FillQuotePaint(
y += top; y += top;
rect.setTop(y); rect.setTop(y);
} }
const auto outline = st.outline;
if (!parts.skipBottom) { if (!parts.skipBottom) {
const auto bottom = std::min(height, ihalf); const auto bottom = std::min(height, ihalf);
const auto skip = !cache.outline.isNull() ? outline : 0;
p.drawImage( p.drawImage(
QRect(x, y + height - bottom, ihalf, bottom), QRect(x + skip, y + height - bottom, ihalf - skip, bottom),
image, image,
QRect( QRect(
0, skip * ratio,
(iheight - bottom) * ratio, (iheight - bottom) * ratio,
ihalf * ratio, (ihalf - skip) * ratio,
bottom * ratio)); bottom * ratio));
p.drawImage( p.drawImage(
QRect( QRect(
@ -320,15 +353,63 @@ void FillQuotePaint(
QRect(x + ihalf, y + height - bottom, middle, bottom), QRect(x + ihalf, y + height - bottom, middle, bottom),
cache.bg); cache.bg);
} }
if (skip) {
if (cache.bottomCorner.size() != QSize(skip, ihalf)) {
cache.bottomCorner = QImage(
QSize(skip, ihalf) * ratio,
QImage::Format_ARGB32_Premultiplied);
cache.bottomCorner.setDevicePixelRatio(ratio);
cache.bottomCorner.fill(Qt::transparent);
cache.bottomRounding = QImage(
QSize(skip, ihalf) * ratio,
QImage::Format_ARGB32_Premultiplied);
cache.bottomRounding.setDevicePixelRatio(ratio);
cache.bottomRounding.fill(Qt::transparent);
const auto radius = st.radius;
auto q = QPainter(&cache.bottomRounding);
auto hq = PainterHighQualityEnabler(q);
q.setPen(Qt::NoPen);
q.setBrush(Qt::white);
q.drawRoundedRect(
0,
-2 * radius,
skip + 2 * radius,
ihalf + 2 * radius,
radius,
radius);
}
auto q = QPainter(&cache.bottomCorner);
const auto skipped = ihalf
+ int(parts.skippedTop)
+ (height - bottom);
q.translate(0, -skipped);
q.fillRect(0, skipped, skip, bottom, cache.outline);
q.setCompositionMode(QPainter::CompositionMode_DestinationIn);
q.drawImage(0, skipped + bottom - ihalf, cache.bottomRounding);
q.end();
p.drawImage(
QRect(x, y + height - bottom, skip, bottom),
cache.bottomCorner,
QRect(0, 0, skip * ratio, bottom * ratio));
}
height -= bottom; height -= bottom;
if (!height) { if (!height) {
return; return;
} }
rect.setHeight(height); rect.setHeight(height);
} }
const auto outline = st.outline;
if (outline) { if (outline) {
p.fillRect(x, y, outline, height, cache.outline); if (!cache.outline.isNull()) {
const auto skipped = ihalf + int(parts.skippedTop);
const auto top = y - skipped;
p.translate(x, top);
p.fillRect(0, skipped, outline, height, cache.outline);
p.translate(-x, -top);
} else {
p.fillRect(x, y, outline, height, cache.outline1);
}
} }
p.fillRect(x + outline, y, width - outline, height, cache.bg); p.fillRect(x + outline, y, width - outline, height, cache.bg);
} }
@ -541,54 +622,15 @@ void String::recountNaturalSize(
} }
int String::countMaxMonospaceWidth() const { int String::countMaxMonospaceWidth() const {
auto result = QFixed(); auto result = 0;
auto paragraphWidth = QFixed(); if (_extended) {
auto fullMonospace = true; for (const auto &quote : _extended->quotes) {
QFixed _width = 0, last_rBearing = 0, last_rPadding = 0; if (quote.pre) {
for (auto &block : _blocks) { accumulate_max(result, quote.maxWidth);
auto b = block.get();
auto _btype = b->type();
if (_btype == TextBlockType::Newline) {
last_rBearing = b->f_rbearing();
last_rPadding = b->f_rpadding();
if (fullMonospace) {
accumulate_max(paragraphWidth, _width);
accumulate_max(result, paragraphWidth);
paragraphWidth = 0;
} else {
fullMonospace = true;
} }
_width = (b->f_width() - last_rBearing);
continue;
} }
if (!(b->flags() & (TextBlockFlag::Pre | TextBlockFlag::Code))
&& (b->type() != TextBlockType::Skip)) {
fullMonospace = false;
}
auto b__f_rbearing = b->f_rbearing(); // cache
// We need to accumulate max width after each block, because
// some blocks have width less than -1 * previous right bearing.
// In that cases the _width gets _smaller_ after moving to the next block.
//
// But when we layout block and we're sure that _maxWidth is enough
// for all the blocks to fit on their line we check each block, even the
// intermediate one with a large negative right bearing.
if (fullMonospace) {
accumulate_max(paragraphWidth, _width);
}
_width += last_rBearing + (last_rPadding + b->f_width() - b__f_rbearing);
last_rBearing = b__f_rbearing;
last_rPadding = b->f_rpadding();
continue;
} }
if (_width > 0 && fullMonospace) { return result;
accumulate_max(paragraphWidth, _width);
accumulate_max(result, paragraphWidth);
}
return result.ceil().toInt();
} }
void String::setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options, const std::any &context) { void String::setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options, const std::any &context) {

View file

@ -167,14 +167,20 @@ struct GeometryDescriptor {
struct QuotePaintCache { struct QuotePaintCache {
QImage corners; QImage corners;
QImage outline;
mutable QImage bottomCorner;
mutable QImage bottomRounding;
QColor headerCached; QColor headerCached;
QColor bgCached; QColor bgCached;
QColor outlineCached; QColor outline1Cached;
QColor outline2Cached;
QColor iconCached; QColor iconCached;
QColor header; QColor header;
QColor bg; QColor bg;
QColor outline; QColor outline1;
QColor outline2;
QColor icon; QColor icon;
}; };
@ -183,8 +189,8 @@ void ValidateQuotePaintCache(
const style::QuoteStyle &st); const style::QuoteStyle &st);
struct SkipBlockPaintParts { struct SkipBlockPaintParts {
bool skipTop : 1 = false; uint32 skippedTop : 31 = 0;
bool skipBottom : 1 = false; uint32 skipBottom : 1 = 0;
}; };
void FillQuotePaint( void FillQuotePaint(
QPainter &p, QPainter &p,

View file

@ -410,6 +410,7 @@ void Renderer::fillParagraphBg(int paddingBottom) {
const auto isTop = (_y != _quoteLineTop); const auto isTop = (_y != _quoteLineTop);
const auto isBottom = (paddingBottom != 0); const auto isBottom = (paddingBottom != 0);
const auto left = _startLeft + _quoteShift; const auto left = _startLeft + _quoteShift;
const auto start = _quoteTop + skip;
const auto top = _quoteLineTop + (isTop ? skip : 0); const auto top = _quoteLineTop + (isTop ? skip : 0);
const auto fill = _y + _lineHeight + paddingBottom - top const auto fill = _y + _lineHeight + paddingBottom - top
- (isBottom ? skip : 0); - (isBottom ? skip : 0);
@ -431,7 +432,7 @@ void Renderer::fillParagraphBg(int paddingBottom) {
ValidateQuotePaintCache(*cache, st); ValidateQuotePaintCache(*cache, st);
} }
FillQuotePaint(*_p, rect, *cache, st, { FillQuotePaint(*_p, rect, *cache, st, {
.skipTop = !isTop, .skippedTop = uint32(top - start),
.skipBottom = !isBottom, .skipBottom = !isBottom,
}); });
} }
@ -507,7 +508,7 @@ void Renderer::initNextParagraph(
_quoteIndex = paragraphIndex; _quoteIndex = paragraphIndex;
_quote = _t->quoteByIndex(paragraphIndex); _quote = _t->quoteByIndex(paragraphIndex);
_quotePadding = _t->quotePadding(_quote); _quotePadding = _t->quotePadding(_quote);
_quoteLineTop = _y; _quoteTop = _quoteLineTop = _y;
_y += _quotePadding.top(); _y += _quotePadding.top();
_quotePadding.setTop(0); _quotePadding.setTop(0);
_quoteDirection = _paragraphDirection; _quoteDirection = _paragraphDirection;

View file

@ -165,6 +165,7 @@ private:
int _quoteShift = 0; int _quoteShift = 0;
int _quoteIndex = 0; int _quoteIndex = 0;
QMargins _quotePadding; QMargins _quotePadding;
int _quoteTop = 0;
int _quoteLineTop = 0; int _quoteLineTop = 0;
QuotePaintCache *_quotePreCache = nullptr; QuotePaintCache *_quotePreCache = nullptr;
QuotePaintCache *_quoteBlockquoteCache = nullptr; QuotePaintCache *_quoteBlockquoteCache = nullptr;