-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathLinkRenderer.kt
More file actions
143 lines (130 loc) · 4.73 KB
/
LinkRenderer.kt
File metadata and controls
143 lines (130 loc) · 4.73 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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package com.swmansion.enriched.markdown.renderer
import android.text.SpannableStringBuilder
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
import com.swmansion.enriched.markdown.spans.CitationSpan
import com.swmansion.enriched.markdown.spans.LinkSpan
import com.swmansion.enriched.markdown.spans.MentionSpacerSpan
import com.swmansion.enriched.markdown.spans.MentionSpan
import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
class LinkRenderer(
private val config: RendererConfig,
) : NodeRenderer {
companion object {
private const val MENTION_SCHEME = "mention://"
private const val CITATION_SCHEME = "citation://"
}
override fun render(
node: MarkdownASTNode,
builder: SpannableStringBuilder,
onLinkPress: ((String) -> Unit)?,
onLinkLongPress: ((String) -> Unit)?,
factory: RendererFactory,
) {
val url = node.getAttribute("url") ?: return
when {
url.startsWith(MENTION_SCHEME) -> {
renderMention(
url.removePrefix(MENTION_SCHEME),
node,
builder,
onLinkPress,
onLinkLongPress,
factory,
)
}
url.startsWith(CITATION_SCHEME) -> {
renderCitation(
url.removePrefix(CITATION_SCHEME),
node,
builder,
onLinkPress,
onLinkLongPress,
factory,
)
}
else -> {
renderLink(url, node, builder, onLinkPress, onLinkLongPress, factory)
}
}
}
private fun renderLink(
url: String,
node: MarkdownASTNode,
builder: SpannableStringBuilder,
onLinkPress: ((String) -> Unit)?,
onLinkLongPress: ((String) -> Unit)?,
factory: RendererFactory,
) {
factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress, onLinkLongPress) }) { start, end, blockStyle ->
builder.setSpan(
LinkSpan(url, onLinkPress, onLinkLongPress, factory.styleCache, blockStyle, factory.context),
start,
end,
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
)
}
}
private fun renderMention(
url: String,
node: MarkdownASTNode,
builder: SpannableStringBuilder,
onLinkPress: ((String) -> Unit)?,
onLinkLongPress: ((String) -> Unit)?,
factory: RendererFactory,
) {
// Render children into a throwaway buffer to derive the plain display
// label (any inline formatting inside the mention collapses to text).
val labelBuffer = SpannableStringBuilder()
factory.renderChildren(node, labelBuffer, onLinkPress, onLinkLongPress)
val displayText = labelBuffer.toString()
if (displayText.isEmpty()) return
// Append the displayText as real characters so copy/paste, selection, and
// accessibility traversal all see the mention as normal text. The pill
// background is painted by the MentionSpan's LineBackgroundSpan pass.
val start = builder.length
builder.append(displayText)
val end = builder.length
val span =
MentionSpan(
url = url,
displayText = displayText,
mentionStyle = factory.styleCache.mentionStyle,
mentionTypeface = factory.styleCache.mentionTypeface,
)
builder.setSpan(span, start, end, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
// The pill background extends `paddingHorizontal` past the glyph run on
// each side, but the underlying inline text doesn't reserve any advance
// for that visual overhang. Without extra spacing, two adjacent mention
// pills (separated only by a space in the source markdown) visually
// overlap. Appending a zero-width sentinel char with a MentionSpacerSpan
// reserves `paddingHorizontal * 2` of advance after each mention — the
// Android-side equivalent of the NSKern we apply on iOS.
val mentionStyle = factory.styleCache.mentionStyle
if (mentionStyle.paddingHorizontal > 0f) {
val spacerStart = builder.length
builder.append("\u200B") // zero-width space
builder.setSpan(
MentionSpacerSpan(mentionStyle.paddingHorizontal * 2f),
spacerStart,
builder.length,
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
)
}
}
private fun renderCitation(
url: String,
node: MarkdownASTNode,
builder: SpannableStringBuilder,
onLinkPress: ((String) -> Unit)?,
onLinkLongPress: ((String) -> Unit)?,
factory: RendererFactory,
) {
val start = builder.length
factory.renderChildren(node, builder, onLinkPress, onLinkLongPress)
val end = builder.length
if (end <= start) return
val displayText = builder.subSequence(start, end).toString()
val span = CitationSpan(url = url, displayText = displayText, citationStyle = factory.styleCache.citationStyle)
builder.setSpan(span, start, end, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
}
}