Support two-color quote outlines.
This commit is contained in:
parent
4d1a5686a7
commit
71d24af3a8
4 changed files with 112 additions and 62 deletions
154
ui/text/text.cpp
154
ui/text/text.cpp
|
|
@ -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 "e : _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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue