Skip to content

Commit f1df742

Browse files
committed
feat(android): blockquote fill via LineBackgroundSpan and container span flags
- Add SPAN_FLAGS_CONTAINER_BACKGROUND (max priority) so blockquote draws before lower-priority LineBackgroundSpans on the same line. - Implement LineBackgroundSpan on BlockquoteSpan and move fill from drawLeadingMargin to drawBackground; keep accent bars in drawLeadingMargin. - Unblocks inline chip/pill backgrounds rendering above blockquote fill. Made-with: Cursor
1 parent 86914a0 commit f1df742

3 files changed

Lines changed: 49 additions & 7 deletions

File tree

android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.swmansion.enriched.markdown.renderer
33
import android.text.SpannableStringBuilder
44
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
55
import com.swmansion.enriched.markdown.spans.BlockquoteSpan
6+
import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_CONTAINER_BACKGROUND
67
import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
78
import com.swmansion.enriched.markdown.utils.text.span.applyMarginBottom
89
import com.swmansion.enriched.markdown.utils.text.span.applyMarginTop
@@ -45,12 +46,16 @@ class BlockquoteRenderer(
4546
.map { builder.getSpanStart(it) to builder.getSpanEnd(it) }
4647
.sortedBy { it.first }
4748

48-
// The Accent Bar Span covers the full range for visual continuity
49+
// The Accent Bar Span covers the full range for visual continuity.
50+
// Use high-priority flags so BlockquoteSpan's LineBackgroundSpan pass
51+
// runs FIRST on each line — the blockquote fill is painted first, then
52+
// inline chip/pill backgrounds (mention pills etc.) draw on top of it
53+
// instead of being covered by it.
4954
builder.setSpan(
5055
BlockquoteSpan(style, depth, factory.context, factory.styleCache),
5156
start,
5257
end,
53-
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
58+
SPAN_FLAGS_CONTAINER_BACKGROUND,
5459
)
5560

5661
// Apply styling only to segments that are NOT nested quotes

android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import android.text.Layout
1010
import android.text.Spanned
1111
import android.text.TextPaint
1212
import android.text.style.LeadingMarginSpan
13+
import android.text.style.LineBackgroundSpan
1314
import android.text.style.MetricAffectingSpan
1415
import com.swmansion.enriched.markdown.renderer.BlockStyle
1516
import com.swmansion.enriched.markdown.renderer.SpanStyleCache
@@ -23,7 +24,8 @@ class BlockquoteSpan(
2324
private val context: Context,
2425
private val styleCache: SpanStyleCache,
2526
) : MetricAffectingSpan(),
26-
LeadingMarginSpan {
27+
LeadingMarginSpan,
28+
LineBackgroundSpan {
2729
private val levelSpacing: Float = blockquoteStyle.borderWidth + blockquoteStyle.gapWidth
2830
private val blockStyle =
2931
BlockStyle(
@@ -60,8 +62,6 @@ class BlockquoteSpan(
6062
// Essential check from original: only the deepest span draws to prevent over-rendering background
6163
if (shouldSkipDrawing(text, start)) return
6264

63-
drawBackground(c, top, bottom, layout)
64-
6565
val borderPaint = configureBorderPaint()
6666
val borderTop = top.toFloat()
6767
val borderBottom = bottom.toFloat()
@@ -73,6 +73,31 @@ class BlockquoteSpan(
7373
}
7474
}
7575

76+
/**
77+
* Drawn BEFORE glyphs and before other [LineBackgroundSpan]s attached to the
78+
* same line, so inline backgrounds painted by mention / code spans render on
79+
* top of the blockquote fill instead of being covered by it (the previous
80+
* implementation painted the blockquote fill from [drawLeadingMargin], which
81+
* runs AFTER [LineBackgroundSpan.drawBackground] and erased the mention
82+
* pill).
83+
*/
84+
override fun drawBackground(
85+
canvas: Canvas,
86+
paint: Paint,
87+
left: Int,
88+
right: Int,
89+
top: Int,
90+
baseline: Int,
91+
bottom: Int,
92+
text: CharSequence,
93+
start: Int,
94+
end: Int,
95+
lineNum: Int,
96+
) {
97+
if (shouldSkipDrawing(text, start)) return
98+
drawBackground(canvas, top, bottom, right)
99+
}
100+
76101
@SuppressLint("WrongConstant") // Result of mask is always valid: 0, 1, 2, or 3
77102
private fun applyTextStyle(tp: TextPaint) {
78103
tp.textSize = blockStyle.fontSize
@@ -125,10 +150,10 @@ class BlockquoteSpan(
125150
c: Canvas,
126151
top: Int,
127152
bottom: Int,
128-
layout: Layout?,
153+
right: Int,
129154
) {
130155
val bgColor = blockquoteStyle.backgroundColor?.takeIf { it != Color.TRANSPARENT } ?: return
131156
val backgroundPaint = configureBackgroundPaint(bgColor)
132-
c.drawRect(0f, top.toFloat(), layout?.width?.toFloat() ?: 0f, bottom.toFloat(), backgroundPaint)
157+
c.drawRect(0f, top.toFloat(), right.toFloat(), bottom.toFloat(), backgroundPaint)
133158
}
134159
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
package com.swmansion.enriched.markdown.utils.text.span
22

33
import android.text.SpannableString
4+
import android.text.Spanned
45

56
const val SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE = SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE
7+
8+
/**
9+
* EXCLUSIVE_EXCLUSIVE flags with the maximum span priority bit set. Higher
10+
* priority means the span is iterated FIRST during the text view's draw
11+
* passes (e.g. `Layout.drawBackground`), which means it's painted FIRST — so
12+
* lower-priority spans drawn afterwards end up on top visually. Use this for
13+
* full-width container backgrounds (like blockquote) that must sit UNDER any
14+
* inline chip / pill backgrounds on the same line.
15+
*/
16+
const val SPAN_FLAGS_CONTAINER_BACKGROUND =
17+
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE or ((0xFF) shl Spanned.SPAN_PRIORITY_SHIFT)

0 commit comments

Comments
 (0)