-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathBlockquoteRenderer.kt
More file actions
89 lines (79 loc) · 3.1 KB
/
BlockquoteRenderer.kt
File metadata and controls
89 lines (79 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package com.swmansion.enriched.markdown.renderer
import android.text.SpannableStringBuilder
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
import com.swmansion.enriched.markdown.spans.BlockquoteSpan
import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_CONTAINER_BACKGROUND
import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
import com.swmansion.enriched.markdown.utils.text.span.applyMarginBottom
import com.swmansion.enriched.markdown.utils.text.span.applyMarginTop
import com.swmansion.enriched.markdown.utils.text.span.createLineHeightSpan
class BlockquoteRenderer(
private val config: RendererConfig,
) : NodeRenderer {
override fun render(
node: MarkdownASTNode,
builder: SpannableStringBuilder,
onLinkPress: ((String) -> Unit)?,
onLinkLongPress: ((String) -> Unit)?,
factory: RendererFactory,
) {
val start = builder.length
val style = config.style.blockquoteStyle
val context = factory.blockStyleContext
val depth = context.blockquoteDepth
// Track depth to handle nested indentation levels
context.blockquoteDepth = depth + 1
context.setBlockquoteStyle(style)
try {
factory.renderChildren(node, builder, onLinkPress, onLinkLongPress)
} finally {
context.popBlockStyle()
context.blockquoteDepth = depth
}
if (builder.length == start) return
val end = builder.length
// Find immediately nested quotes to exclude them from this level's line-height/margins
val nestedRanges =
builder
.getSpans(start, end, BlockquoteSpan::class.java)
.filter { it.depth == depth + 1 }
.map { builder.getSpanStart(it) to builder.getSpanEnd(it) }
.sortedBy { it.first }
// The Accent Bar Span covers the full range for visual continuity.
// Use high-priority flags so BlockquoteSpan's LineBackgroundSpan pass
// runs FIRST on each line — the blockquote fill is painted first, then
// inline chip/pill backgrounds (mention pills etc.) draw on top of it
// instead of being covered by it.
builder.setSpan(
BlockquoteSpan(style, depth, factory.context, factory.styleCache),
start,
end,
SPAN_FLAGS_CONTAINER_BACKGROUND,
)
// Apply styling only to segments that are NOT nested quotes
applySpansExcludingNested(builder, nestedRanges, start, end, createLineHeightSpan(style.lineHeight))
// Margins are only applied by the outermost (root) quote
if (depth == 0) {
applyMarginTop(builder, start, style.marginTop)
applyMarginBottom(builder, style.marginBottom)
}
}
private fun applySpansExcludingNested(
builder: SpannableStringBuilder,
nestedRanges: List<Pair<Int, Int>>,
start: Int,
end: Int,
span: Any,
) {
var currentPos = start
for ((nestedStart, nestedEnd) in nestedRanges) {
if (currentPos < nestedStart) {
builder.setSpan(span, currentPos, nestedStart, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
}
currentPos = nestedEnd
}
if (currentPos < end) {
builder.setSpan(span, currentPos, end, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
}
}
}