react-native-enriched-markdown-scaffold 1.0.2-scaffold.3 → 1.0.2-scaffold.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +15 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +3 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +15 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +3 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +124 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +145 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +85 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +22 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +42 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +4 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +230 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +11 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +22 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +8 -1
- package/docs/API_REFERENCE.md +111 -0
- package/docs/MENTIONS.md +8 -1
- package/docs/STYLES.md +27 -0
- package/docs/TEXT.md +2 -0
- package/ios/EnrichedMarkdown.mm +221 -1
- package/ios/EnrichedMarkdownText.mm +142 -1
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +122 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +100 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +20 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +124 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
- package/ios/internals/MeasurementCache.h +5 -0
- package/ios/renderer/LinkRenderer.m +47 -15
- package/ios/styles/StyleConfig.h +16 -0
- package/ios/styles/StyleConfig.mm +60 -0
- package/ios/utils/CodeBackground.m +3 -65
- package/ios/utils/ENRMChipDrawing.h +24 -0
- package/ios/utils/ENRMChipDrawing.m +83 -0
- package/ios/utils/ENRMLinkChip.h +70 -0
- package/ios/utils/ENRMLinkChip.m +194 -0
- package/ios/utils/ENRMLinkLayout.h +50 -0
- package/ios/utils/ENRMLinkLayout.m +45 -0
- package/ios/utils/ENRMSelectionChange.h +96 -0
- package/ios/utils/ENRMSelectionChange.m +204 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -1
- package/ios/utils/EditMenuUtils.m +4 -1
- package/ios/utils/HTMLGenerator.m +2 -1
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +3 -1
- package/ios/utils/MarkdownExtractor.m +2 -1
- package/ios/utils/RTFExportUtils.m +2 -0
- package/ios/utils/StylePropsUtils.h +37 -1
- package/ios/utils/TextViewLayoutManager.mm +3 -0
- package/ios/views/ENRMCodeBlockContainerView.h +3 -0
- package/ios/views/ENRMCodeBlockContainerView.m +3 -0
- package/ios/views/ENRMMathContainerView.h +3 -0
- package/ios/views/ENRMMathContainerView.m +3 -0
- package/ios/views/TableContainerView.h +6 -0
- package/ios/views/TableContainerView.m +24 -0
- package/lib/module/EnrichedMarkdownNativeComponent.ts +101 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +101 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/jest/index.js +13 -0
- package/lib/module/jest/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +138 -9
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +13 -2
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.web.js +13 -2
- package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
- package/lib/module/web/EnrichedMarkdownText.js +6 -3
- package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/web/renderers/InlineRenderers.js +96 -3
- package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
- package/lib/module/web/styles.js +18 -0
- package/lib/module/web/styles.js.map +1 -1
- package/lib/module/web/utils.js +13 -0
- package/lib/module/web/utils.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +93 -2
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +93 -2
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +1 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/jest/index.d.ts +1 -1
- package/lib/typescript/src/jest/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyle.d.ts +28 -5
- package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +10 -0
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +76 -2
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +36 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
- package/lib/typescript/src/web/styles.d.ts.map +1 -1
- package/lib/typescript/src/web/types.d.ts +4 -0
- package/lib/typescript/src/web/types.d.ts.map +1 -1
- package/lib/typescript/src/web/utils.d.ts +4 -0
- package/lib/typescript/src/web/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +101 -1
- package/src/EnrichedMarkdownTextNativeComponent.ts +101 -1
- package/src/index.tsx +5 -0
- package/src/index.web.tsx +5 -1
- package/src/jest/index.tsx +21 -1
- package/src/native/EnrichedMarkdownText.tsx +181 -8
- package/src/normalizeMarkdownStyle.ts +13 -0
- package/src/normalizeMarkdownStyle.web.ts +11 -0
- package/src/types/MarkdownStyle.ts +29 -5
- package/src/types/MarkdownStyleInternal.ts +10 -0
- package/src/types/MarkdownTextProps.ts +85 -1
- package/src/types/MarkdownTextProps.web.ts +43 -1
- package/src/types/events.ts +38 -0
- package/src/web/EnrichedMarkdownText.tsx +10 -3
- package/src/web/renderers/InlineRenderers.tsx +111 -3
- package/src/web/styles.ts +25 -0
- package/src/web/types.ts +4 -0
- package/src/web/utils.ts +8 -0
|
@@ -5,9 +5,6 @@ import android.graphics.Paint
|
|
|
5
5
|
import android.graphics.Path
|
|
6
6
|
import android.graphics.RectF
|
|
7
7
|
import android.text.Spanned
|
|
8
|
-
import android.text.StaticLayout
|
|
9
|
-
import android.text.TextPaint
|
|
10
|
-
import android.text.style.LeadingMarginSpan
|
|
11
8
|
import android.text.style.LineBackgroundSpan
|
|
12
9
|
import com.swmansion.enriched.markdown.styles.StyleConfig
|
|
13
10
|
import kotlin.math.max
|
|
@@ -73,14 +70,6 @@ class CodeBackgroundSpan(
|
|
|
73
70
|
drawShapes(canvas, isFirst, isLast)
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
/**
|
|
77
|
-
* Returns the x position of [index] relative to the line's left edge, including any
|
|
78
|
-
* leading margin. The measuring StaticLayout is built from a subSequence that keeps
|
|
79
|
-
* all spans, so LeadingMarginSpans (lists, blockquotes) are already applied to
|
|
80
|
-
* getPrimaryHorizontal; adding the margin again on top would shift the background
|
|
81
|
-
* right by the indent. The margin is only added explicitly in the early-return case,
|
|
82
|
-
* where no layout is built.
|
|
83
|
-
*/
|
|
84
73
|
private fun getHorizontalOffset(
|
|
85
74
|
text: CharSequence,
|
|
86
75
|
lineStart: Int,
|
|
@@ -88,105 +77,30 @@ class CodeBackgroundSpan(
|
|
|
88
77
|
index: Int,
|
|
89
78
|
paint: Paint,
|
|
90
79
|
leadingMargin: Int,
|
|
91
|
-
): Float
|
|
92
|
-
if (index <= lineStart) return leadingMargin.toFloat()
|
|
93
|
-
val lineText = text.subSequence(lineStart, lineEnd)
|
|
94
|
-
val textPaint = paint as? TextPaint ?: TextPaint(paint)
|
|
95
|
-
val layout = StaticLayout.Builder.obtain(lineText, 0, lineText.length, textPaint, 10000).build()
|
|
96
|
-
return layout.getPrimaryHorizontal(index - lineStart)
|
|
97
|
-
}
|
|
80
|
+
): Float = InlineChipPainter.horizontalOffset(text, lineStart, lineEnd, index, paint, leadingMargin)
|
|
98
81
|
|
|
99
82
|
private fun drawShapes(
|
|
100
83
|
canvas: Canvas,
|
|
101
84
|
isFirst: Boolean,
|
|
102
85
|
isLast: Boolean,
|
|
103
86
|
) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private fun drawOpenBorders(
|
|
118
|
-
canvas: Canvas,
|
|
119
|
-
isFirst: Boolean,
|
|
120
|
-
isLast: Boolean,
|
|
121
|
-
) {
|
|
122
|
-
val r = CORNER_RADIUS
|
|
123
|
-
path.reset()
|
|
124
|
-
|
|
125
|
-
if (isFirst) {
|
|
126
|
-
path.moveTo(rect.right, rect.top)
|
|
127
|
-
path.lineTo(rect.left + r, rect.top)
|
|
128
|
-
path.quadTo(rect.left, rect.top, rect.left, rect.top + r)
|
|
129
|
-
path.lineTo(rect.left, rect.bottom - r)
|
|
130
|
-
path.quadTo(rect.left, rect.bottom, rect.left + r, rect.bottom)
|
|
131
|
-
path.lineTo(rect.right, rect.bottom)
|
|
132
|
-
} else if (isLast) {
|
|
133
|
-
path.moveTo(rect.left, rect.top)
|
|
134
|
-
path.lineTo(rect.right - r, rect.top)
|
|
135
|
-
path.quadTo(rect.right, rect.top, rect.right, rect.top + r)
|
|
136
|
-
path.lineTo(rect.right, rect.bottom - r)
|
|
137
|
-
path.quadTo(rect.right, rect.bottom, rect.right - r, rect.bottom)
|
|
138
|
-
path.lineTo(rect.left, rect.bottom)
|
|
139
|
-
} else {
|
|
140
|
-
path.moveTo(rect.left, rect.top)
|
|
141
|
-
path.lineTo(rect.right, rect.top)
|
|
142
|
-
path.moveTo(rect.left, rect.bottom)
|
|
143
|
-
path.lineTo(rect.right, rect.bottom)
|
|
144
|
-
}
|
|
145
|
-
canvas.drawPath(path, sharedBorderPaint)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private fun createRadii(
|
|
149
|
-
isFirst: Boolean,
|
|
150
|
-
isLast: Boolean,
|
|
151
|
-
) = when {
|
|
152
|
-
isFirst && isLast -> {
|
|
153
|
-
floatArrayOf(
|
|
154
|
-
CORNER_RADIUS,
|
|
155
|
-
CORNER_RADIUS,
|
|
156
|
-
CORNER_RADIUS,
|
|
157
|
-
CORNER_RADIUS,
|
|
158
|
-
CORNER_RADIUS,
|
|
159
|
-
CORNER_RADIUS,
|
|
160
|
-
CORNER_RADIUS,
|
|
161
|
-
CORNER_RADIUS,
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
isFirst -> {
|
|
166
|
-
floatArrayOf(CORNER_RADIUS, CORNER_RADIUS, 0f, 0f, 0f, 0f, CORNER_RADIUS, CORNER_RADIUS)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
isLast -> {
|
|
170
|
-
floatArrayOf(0f, 0f, CORNER_RADIUS, CORNER_RADIUS, CORNER_RADIUS, CORNER_RADIUS, 0f, 0f)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
else -> {
|
|
174
|
-
floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
|
|
175
|
-
}
|
|
87
|
+
InlineChipPainter.draw(
|
|
88
|
+
canvas,
|
|
89
|
+
rect,
|
|
90
|
+
path,
|
|
91
|
+
CORNER_RADIUS,
|
|
92
|
+
sharedBackgroundPaint,
|
|
93
|
+
sharedBorderPaint,
|
|
94
|
+
isFirst,
|
|
95
|
+
isLast,
|
|
96
|
+
strokeInset = false,
|
|
97
|
+
)
|
|
176
98
|
}
|
|
177
99
|
|
|
178
100
|
private fun leadingMarginAt(
|
|
179
101
|
text: Spanned,
|
|
180
102
|
lineStart: Int,
|
|
181
|
-
): Int
|
|
182
|
-
if (lineStart >= text.length) return 0
|
|
183
|
-
val spans = text.getSpans(lineStart, lineStart + 1, LeadingMarginSpan::class.java)
|
|
184
|
-
var margin = 0
|
|
185
|
-
for (span in spans) {
|
|
186
|
-
margin += span.getLeadingMargin(false)
|
|
187
|
-
}
|
|
188
|
-
return margin
|
|
189
|
-
}
|
|
103
|
+
): Int = InlineChipPainter.leadingMarginAt(text, lineStart)
|
|
190
104
|
|
|
191
105
|
private fun adjustBottomForMargin(
|
|
192
106
|
text: Spanned,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.spans
|
|
2
|
+
|
|
3
|
+
import android.graphics.Canvas
|
|
4
|
+
import android.graphics.Paint
|
|
5
|
+
import android.graphics.Path
|
|
6
|
+
import android.graphics.RectF
|
|
7
|
+
import android.text.Spanned
|
|
8
|
+
import android.text.StaticLayout
|
|
9
|
+
import android.text.TextPaint
|
|
10
|
+
import android.text.style.LeadingMarginSpan
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Shared geometry and drawing for inline "chips" painted from a [android.text.style.LineBackgroundSpan]
|
|
14
|
+
* (inline code, link chips). For a chip that wraps, only the outer ends are rounded and the border
|
|
15
|
+
* stays open where a fragment is cut.
|
|
16
|
+
*/
|
|
17
|
+
internal object InlineChipPainter {
|
|
18
|
+
/**
|
|
19
|
+
* Returns the x position of [index] relative to the line's left edge, including any
|
|
20
|
+
* leading margin. The measuring StaticLayout is built from a subSequence that keeps
|
|
21
|
+
* all spans, so LeadingMarginSpans (lists, blockquotes) are already applied to
|
|
22
|
+
* getPrimaryHorizontal; adding the margin again on top would shift the background
|
|
23
|
+
* right by the indent. The margin is only added explicitly in the early-return case,
|
|
24
|
+
* where no layout is built.
|
|
25
|
+
*/
|
|
26
|
+
fun horizontalOffset(
|
|
27
|
+
text: CharSequence,
|
|
28
|
+
lineStart: Int,
|
|
29
|
+
lineEnd: Int,
|
|
30
|
+
index: Int,
|
|
31
|
+
paint: Paint,
|
|
32
|
+
leadingMargin: Int,
|
|
33
|
+
): Float {
|
|
34
|
+
if (index <= lineStart) return leadingMargin.toFloat()
|
|
35
|
+
val lineText = text.subSequence(lineStart, lineEnd)
|
|
36
|
+
val textPaint = paint as? TextPaint ?: TextPaint(paint)
|
|
37
|
+
val layout = StaticLayout.Builder.obtain(lineText, 0, lineText.length, textPaint, 10000).build()
|
|
38
|
+
return layout.getPrimaryHorizontal(index - lineStart)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fun leadingMarginAt(
|
|
42
|
+
text: Spanned,
|
|
43
|
+
lineStart: Int,
|
|
44
|
+
): Int {
|
|
45
|
+
if (lineStart >= text.length) return 0
|
|
46
|
+
val spans = text.getSpans(lineStart, lineStart + 1, LeadingMarginSpan::class.java)
|
|
47
|
+
var margin = 0
|
|
48
|
+
for (span in spans) {
|
|
49
|
+
margin += span.getLeadingMargin(false)
|
|
50
|
+
}
|
|
51
|
+
return margin
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fills [rect] and strokes its border. [strokeInset] keeps a closed border inside [rect]
|
|
56
|
+
* (link chips); otherwise it is centered on the edge (inline code, kept for pixel parity).
|
|
57
|
+
* [path] is caller-owned scratch space to avoid per-draw allocations.
|
|
58
|
+
*/
|
|
59
|
+
fun draw(
|
|
60
|
+
canvas: Canvas,
|
|
61
|
+
rect: RectF,
|
|
62
|
+
path: Path,
|
|
63
|
+
cornerRadius: Float,
|
|
64
|
+
backgroundPaint: Paint?,
|
|
65
|
+
borderPaint: Paint?,
|
|
66
|
+
isFirst: Boolean,
|
|
67
|
+
isLast: Boolean,
|
|
68
|
+
strokeInset: Boolean,
|
|
69
|
+
) {
|
|
70
|
+
val r = minOf(maxOf(cornerRadius, 0f), minOf(rect.width(), rect.height()) / 2f)
|
|
71
|
+
|
|
72
|
+
if (backgroundPaint != null) {
|
|
73
|
+
path.reset()
|
|
74
|
+
path.addRoundRect(rect, radii(r, isFirst, isLast), Path.Direction.CW)
|
|
75
|
+
canvas.drawPath(path, backgroundPaint)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (borderPaint == null || borderPaint.strokeWidth <= 0f) return
|
|
79
|
+
|
|
80
|
+
path.reset()
|
|
81
|
+
if (isFirst && isLast) {
|
|
82
|
+
if (strokeInset) {
|
|
83
|
+
val inset = borderPaint.strokeWidth / 2f
|
|
84
|
+
val insetRect = RectF(rect.left + inset, rect.top + inset, rect.right - inset, rect.bottom - inset)
|
|
85
|
+
path.addRoundRect(insetRect, radii(maxOf(r - inset, 0f), true, true), Path.Direction.CW)
|
|
86
|
+
} else {
|
|
87
|
+
path.addRoundRect(rect, radii(r, true, true), Path.Direction.CW)
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
val inset = if (strokeInset) borderPaint.strokeWidth / 2f else 0f
|
|
91
|
+
addOpenBorder(path, rect, r, inset, isFirst, isLast)
|
|
92
|
+
}
|
|
93
|
+
canvas.drawPath(path, borderPaint)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private fun addOpenBorder(
|
|
97
|
+
path: Path,
|
|
98
|
+
rect: RectF,
|
|
99
|
+
r: Float,
|
|
100
|
+
inset: Float,
|
|
101
|
+
isFirst: Boolean,
|
|
102
|
+
isLast: Boolean,
|
|
103
|
+
) {
|
|
104
|
+
val left = rect.left + inset
|
|
105
|
+
val top = rect.top + inset
|
|
106
|
+
val right = rect.right - inset
|
|
107
|
+
val bottom = rect.bottom - inset
|
|
108
|
+
|
|
109
|
+
if (isFirst) {
|
|
110
|
+
path.moveTo(rect.right, top)
|
|
111
|
+
path.lineTo(left + r, top)
|
|
112
|
+
path.quadTo(left, top, left, top + r)
|
|
113
|
+
path.lineTo(left, bottom - r)
|
|
114
|
+
path.quadTo(left, bottom, left + r, bottom)
|
|
115
|
+
path.lineTo(rect.right, bottom)
|
|
116
|
+
} else if (isLast) {
|
|
117
|
+
path.moveTo(rect.left, top)
|
|
118
|
+
path.lineTo(right - r, top)
|
|
119
|
+
path.quadTo(right, top, right, top + r)
|
|
120
|
+
path.lineTo(right, bottom - r)
|
|
121
|
+
path.quadTo(right, bottom, right - r, bottom)
|
|
122
|
+
path.lineTo(rect.left, bottom)
|
|
123
|
+
} else {
|
|
124
|
+
path.moveTo(rect.left, top)
|
|
125
|
+
path.lineTo(rect.right, top)
|
|
126
|
+
path.moveTo(rect.left, bottom)
|
|
127
|
+
path.lineTo(rect.right, bottom)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun radii(
|
|
132
|
+
r: Float,
|
|
133
|
+
isFirst: Boolean,
|
|
134
|
+
isLast: Boolean,
|
|
135
|
+
): FloatArray {
|
|
136
|
+
val left = if (isFirst) r else 0f
|
|
137
|
+
val right = if (isLast) r else 0f
|
|
138
|
+
return floatArrayOf(left, left, right, right, right, right, left, left)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.spans
|
|
2
|
+
|
|
3
|
+
import android.graphics.Canvas
|
|
4
|
+
import android.graphics.Paint
|
|
5
|
+
import android.graphics.Path
|
|
6
|
+
import android.graphics.RectF
|
|
7
|
+
import android.text.SpannableStringBuilder
|
|
8
|
+
import android.text.Spanned
|
|
9
|
+
import android.text.TextPaint
|
|
10
|
+
import android.text.style.LineBackgroundSpan
|
|
11
|
+
import android.text.style.ReplacementSpan
|
|
12
|
+
import com.swmansion.enriched.markdown.styles.LinkChipStyle
|
|
13
|
+
import kotlin.math.max
|
|
14
|
+
import kotlin.math.min
|
|
15
|
+
import kotlin.math.roundToInt
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reserves a link's `paddingHorizontal` at one outer edge. It covers one [LinkSpacers.CHAR]
|
|
19
|
+
* inserted by `LinkRenderer`: U+00A0 is line-break class GL, so a break is allowed before it
|
|
20
|
+
* after a space but never after it, and never before it after a non-space — the spacer always
|
|
21
|
+
* travels with the link's first (or last) glyph, never orphaned at a wrap.
|
|
22
|
+
*
|
|
23
|
+
* Layout-only: every text exit point strips spacers by this span ([LinkSpacers.strip]).
|
|
24
|
+
*/
|
|
25
|
+
class LinkSpacerSpan(
|
|
26
|
+
private val width: Float,
|
|
27
|
+
) : ReplacementSpan() {
|
|
28
|
+
override fun getSize(
|
|
29
|
+
paint: Paint,
|
|
30
|
+
text: CharSequence?,
|
|
31
|
+
start: Int,
|
|
32
|
+
end: Int,
|
|
33
|
+
fm: Paint.FontMetricsInt?,
|
|
34
|
+
): Int {
|
|
35
|
+
// Keep the line metrics of the surrounding text.
|
|
36
|
+
if (fm != null) paint.getFontMetricsInt(fm)
|
|
37
|
+
return width.roundToInt()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun draw(
|
|
41
|
+
canvas: Canvas,
|
|
42
|
+
text: CharSequence?,
|
|
43
|
+
start: Int,
|
|
44
|
+
end: Int,
|
|
45
|
+
x: Float,
|
|
46
|
+
top: Int,
|
|
47
|
+
y: Int,
|
|
48
|
+
bottom: Int,
|
|
49
|
+
paint: Paint,
|
|
50
|
+
) = Unit
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
object LinkSpacers {
|
|
54
|
+
const val CHAR = '\u00A0'
|
|
55
|
+
|
|
56
|
+
/** [text] without link spacers (returns [text] itself when it has none). */
|
|
57
|
+
fun strip(text: CharSequence): CharSequence {
|
|
58
|
+
if (text !is Spanned) return text
|
|
59
|
+
val spacers = text.getSpans(0, text.length, LinkSpacerSpan::class.java)
|
|
60
|
+
if (spacers.isEmpty()) return text
|
|
61
|
+
val builder = SpannableStringBuilder(text)
|
|
62
|
+
spacers
|
|
63
|
+
.map { builder.getSpanStart(it) to builder.getSpanEnd(it) }
|
|
64
|
+
.filter { (start, end) -> start in 0 until end }
|
|
65
|
+
.sortedByDescending { it.first }
|
|
66
|
+
.forEach { (start, end) -> builder.delete(start, end) }
|
|
67
|
+
return builder
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** True when [start, end) lies inside a spacer (a segment exporters must skip). */
|
|
71
|
+
fun isSpacer(
|
|
72
|
+
text: Spanned,
|
|
73
|
+
start: Int,
|
|
74
|
+
end: Int,
|
|
75
|
+
): Boolean = end > start && text.getSpans(start, end, LinkSpacerSpan::class.java).isNotEmpty()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Paints a link chip (background + border) behind the link glyphs. One instance per link,
|
|
80
|
+
* spanning the link including its spacers, so the chip already covers `paddingHorizontal`.
|
|
81
|
+
* Vertically the chip spans the link font's ascent..descent ± `paddingVertical`; the vertical
|
|
82
|
+
* padding only bleeds into the surrounding line spacing and never changes the line box.
|
|
83
|
+
*/
|
|
84
|
+
class LinkChipBackgroundSpan(
|
|
85
|
+
val chip: LinkChipStyle,
|
|
86
|
+
) : LineBackgroundSpan {
|
|
87
|
+
private val rect = RectF()
|
|
88
|
+
private val path = Path()
|
|
89
|
+
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
90
|
+
private val borderPaint =
|
|
91
|
+
Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
92
|
+
style = Paint.Style.STROKE
|
|
93
|
+
strokeJoin = Paint.Join.ROUND
|
|
94
|
+
strokeCap = Paint.Cap.ROUND
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
override fun drawBackground(
|
|
98
|
+
canvas: Canvas,
|
|
99
|
+
p: Paint,
|
|
100
|
+
left: Int,
|
|
101
|
+
right: Int,
|
|
102
|
+
top: Int,
|
|
103
|
+
baseline: Int,
|
|
104
|
+
bottom: Int,
|
|
105
|
+
text: CharSequence,
|
|
106
|
+
start: Int,
|
|
107
|
+
end: Int,
|
|
108
|
+
lineNum: Int,
|
|
109
|
+
) {
|
|
110
|
+
if (!chip.isVisible || text !is Spanned) return
|
|
111
|
+
|
|
112
|
+
val spanStart = text.getSpanStart(this)
|
|
113
|
+
val spanEnd = text.getSpanEnd(this)
|
|
114
|
+
if (spanStart !in 0 until spanEnd || spanEnd <= start || spanStart >= end) return
|
|
115
|
+
|
|
116
|
+
val isFirst = spanStart >= start
|
|
117
|
+
val isLast = spanEnd <= end
|
|
118
|
+
|
|
119
|
+
val leadingMargin = InlineChipPainter.leadingMarginAt(text, start)
|
|
120
|
+
val fragmentStart = max(spanStart, start)
|
|
121
|
+
val fragmentEnd = trimTrailingWhitespace(text, fragmentStart, min(spanEnd, end))
|
|
122
|
+
val startX = InlineChipPainter.horizontalOffset(text, start, end, fragmentStart, p, leadingMargin) + left
|
|
123
|
+
val endX = InlineChipPainter.horizontalOffset(text, start, end, fragmentEnd, p, leadingMargin) + left
|
|
124
|
+
|
|
125
|
+
val (ascent, descent) = linkFontMetrics(text, spanStart, spanEnd, p)
|
|
126
|
+
rect.set(
|
|
127
|
+
min(startX, endX),
|
|
128
|
+
baseline + ascent - chip.paddingVertical,
|
|
129
|
+
max(startX, endX),
|
|
130
|
+
baseline + descent + chip.paddingVertical,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
backgroundPaint.color = chip.backgroundColor
|
|
134
|
+
borderPaint.color = chip.borderColor
|
|
135
|
+
borderPaint.strokeWidth = chip.borderWidth
|
|
136
|
+
InlineChipPainter.draw(
|
|
137
|
+
canvas,
|
|
138
|
+
rect,
|
|
139
|
+
path,
|
|
140
|
+
chip.borderRadius,
|
|
141
|
+
backgroundPaint.takeIf { android.graphics.Color.alpha(chip.backgroundColor) > 0 },
|
|
142
|
+
borderPaint.takeIf { android.graphics.Color.alpha(chip.borderColor) > 0 && chip.borderWidth > 0f },
|
|
143
|
+
isFirst,
|
|
144
|
+
isLast,
|
|
145
|
+
strokeInset = true,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
companion object {
|
|
150
|
+
/** A wrapped fragment ends before the whitespace the line break hangs. */
|
|
151
|
+
fun trimTrailingWhitespace(
|
|
152
|
+
text: CharSequence,
|
|
153
|
+
start: Int,
|
|
154
|
+
end: Int,
|
|
155
|
+
): Int {
|
|
156
|
+
var index = end
|
|
157
|
+
while (index > start && text[index - 1] != LinkSpacers.CHAR && text[index - 1].isWhitespace()) index--
|
|
158
|
+
return index
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Ascent/descent of the link's own font: [LinkSpan] sets its size and typeface at draw
|
|
163
|
+
* time only, so the base paint [p] alone would report the paragraph's metrics.
|
|
164
|
+
*/
|
|
165
|
+
fun linkFontMetrics(
|
|
166
|
+
text: Spanned,
|
|
167
|
+
spanStart: Int,
|
|
168
|
+
spanEnd: Int,
|
|
169
|
+
p: Paint,
|
|
170
|
+
): Pair<Float, Float> {
|
|
171
|
+
val paint = TextPaint(p)
|
|
172
|
+
text.getSpans(spanStart, spanEnd, LinkSpan::class.java).firstOrNull()?.updateDrawState(paint)
|
|
173
|
+
val metrics = paint.fontMetrics
|
|
174
|
+
return metrics.ascent to metrics.descent
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package com.swmansion.enriched.markdown.spans
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
-
import android.graphics.Color
|
|
5
4
|
import android.text.TextPaint
|
|
6
5
|
import android.text.style.ClickableSpan
|
|
7
6
|
import android.view.View
|
|
@@ -57,10 +56,6 @@ class LinkSpan(
|
|
|
57
56
|
|
|
58
57
|
textPaint.color = variant?.color ?: styleCache.linkColor
|
|
59
58
|
textPaint.isUnderlineText = variant?.underline ?: styleCache.linkUnderline
|
|
60
|
-
|
|
61
|
-
val backgroundColor = variant?.backgroundColor ?: styleCache.linkBackgroundColor
|
|
62
|
-
if (Color.alpha(backgroundColor) > 0) {
|
|
63
|
-
textPaint.bgColor = backgroundColor
|
|
64
|
-
}
|
|
59
|
+
// The background is painted as a chip by LinkChipBackgroundSpan.
|
|
65
60
|
}
|
|
66
61
|
}
|
|
@@ -7,6 +7,7 @@ data class LinkStyle(
|
|
|
7
7
|
val color: Int,
|
|
8
8
|
val underline: Boolean,
|
|
9
9
|
val backgroundColor: Int,
|
|
10
|
+
val chip: LinkChipStyle,
|
|
10
11
|
) {
|
|
11
12
|
companion object {
|
|
12
13
|
fun fromReadableMap(
|
|
@@ -17,7 +18,41 @@ data class LinkStyle(
|
|
|
17
18
|
val color = parser.parseColor(map, "color")
|
|
18
19
|
val underline = map.getBoolean("underline")
|
|
19
20
|
val backgroundColor = parser.parseColor(map, "backgroundColor")
|
|
20
|
-
return LinkStyle(fontFamily, color, underline, backgroundColor)
|
|
21
|
+
return LinkStyle(fontFamily, color, underline, backgroundColor, LinkChipStyle.fromReadableMap(map, parser, backgroundColor))
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Chip ("box") properties of a link, in pixels. Never inherited from the base link
|
|
28
|
+
* style by variants — the JS normalizer fills missing values with 0 / transparent.
|
|
29
|
+
*/
|
|
30
|
+
data class LinkChipStyle(
|
|
31
|
+
val backgroundColor: Int,
|
|
32
|
+
val borderColor: Int,
|
|
33
|
+
val borderRadius: Float,
|
|
34
|
+
val borderWidth: Float,
|
|
35
|
+
val paddingHorizontal: Float,
|
|
36
|
+
val paddingVertical: Float,
|
|
37
|
+
) {
|
|
38
|
+
val isVisible: Boolean
|
|
39
|
+
get() =
|
|
40
|
+
android.graphics.Color.alpha(backgroundColor) > 0 ||
|
|
41
|
+
(android.graphics.Color.alpha(borderColor) > 0 && borderWidth > 0f)
|
|
42
|
+
|
|
43
|
+
companion object {
|
|
44
|
+
fun fromReadableMap(
|
|
45
|
+
map: ReadableMap,
|
|
46
|
+
parser: StyleParser,
|
|
47
|
+
backgroundColor: Int,
|
|
48
|
+
): LinkChipStyle =
|
|
49
|
+
LinkChipStyle(
|
|
50
|
+
backgroundColor = backgroundColor,
|
|
51
|
+
borderColor = parser.parseOptionalColor(map, "borderColor") ?: 0,
|
|
52
|
+
borderRadius = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "borderRadius").toFloat()),
|
|
53
|
+
borderWidth = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "borderWidth").toFloat()),
|
|
54
|
+
paddingHorizontal = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingHorizontal").toFloat()),
|
|
55
|
+
paddingVertical = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingVertical").toFloat()),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -13,17 +13,21 @@ data class LinkVariantEntry(
|
|
|
13
13
|
val color: Int,
|
|
14
14
|
val underline: Boolean,
|
|
15
15
|
val backgroundColor: Int,
|
|
16
|
+
val chip: LinkChipStyle,
|
|
16
17
|
) {
|
|
17
18
|
companion object {
|
|
18
19
|
fun fromReadableMap(
|
|
19
20
|
map: ReadableMap,
|
|
20
21
|
parser: StyleParser,
|
|
21
|
-
): LinkVariantEntry
|
|
22
|
-
|
|
22
|
+
): LinkVariantEntry {
|
|
23
|
+
val backgroundColor = parser.parseColor(map, "backgroundColor")
|
|
24
|
+
return LinkVariantEntry(
|
|
23
25
|
pattern = map.getString("pattern") ?: "",
|
|
24
26
|
color = parser.parseColor(map, "color"),
|
|
25
27
|
underline = parser.parseBoolean(map, "underline"),
|
|
26
|
-
backgroundColor =
|
|
28
|
+
backgroundColor = backgroundColor,
|
|
29
|
+
chip = LinkChipStyle.fromReadableMap(map, parser, backgroundColor),
|
|
27
30
|
)
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
}
|
|
@@ -8,11 +8,15 @@ import com.swmansion.enriched.markdown.accessibility.AccessibilityLabels
|
|
|
8
8
|
import com.swmansion.enriched.markdown.events.ContextMenuItemPressEvent
|
|
9
9
|
import com.swmansion.enriched.markdown.events.CopyPressEvent
|
|
10
10
|
import com.swmansion.enriched.markdown.events.CopyRequestEvent
|
|
11
|
+
import com.swmansion.enriched.markdown.events.LinkLayoutEvent
|
|
11
12
|
import com.swmansion.enriched.markdown.events.LinkLongPressEvent
|
|
12
13
|
import com.swmansion.enriched.markdown.events.LinkPressEvent
|
|
14
|
+
import com.swmansion.enriched.markdown.events.SelectionChangeEvent
|
|
13
15
|
import com.swmansion.enriched.markdown.events.TaskListItemPressEvent
|
|
14
16
|
import com.swmansion.enriched.markdown.parser.Md4cFlags
|
|
17
|
+
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
|
|
15
18
|
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
19
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionSnapshot
|
|
16
20
|
|
|
17
21
|
fun markdownEventTypeConstants(): MutableMap<String, Any> {
|
|
18
22
|
val map = mutableMapOf<String, Any>()
|
|
@@ -27,9 +31,33 @@ fun markdownEventTypeConstants(): MutableMap<String, Any> {
|
|
|
27
31
|
mapOf("registrationName" to CopyRequestEvent.EVENT_NAME)
|
|
28
32
|
map[ContextMenuItemPressEvent.EVENT_NAME] =
|
|
29
33
|
mapOf("registrationName" to ContextMenuItemPressEvent.EVENT_NAME)
|
|
34
|
+
map[LinkLayoutEvent.EVENT_NAME] =
|
|
35
|
+
mapOf("registrationName" to LinkLayoutEvent.EVENT_NAME)
|
|
36
|
+
map[SelectionChangeEvent.EVENT_NAME] =
|
|
37
|
+
mapOf("registrationName" to SelectionChangeEvent.EVENT_NAME)
|
|
30
38
|
return map
|
|
31
39
|
}
|
|
32
40
|
|
|
41
|
+
fun emitLinkLayout(
|
|
42
|
+
view: View,
|
|
43
|
+
fragments: List<LinkLayoutFragment>,
|
|
44
|
+
) {
|
|
45
|
+
val context = view.context as com.facebook.react.bridge.ReactContext
|
|
46
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
47
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
48
|
+
eventDispatcher?.dispatchEvent(LinkLayoutEvent(surfaceId, view.id, fragments))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fun emitSelectionChange(
|
|
52
|
+
view: View,
|
|
53
|
+
snapshot: SelectionSnapshot,
|
|
54
|
+
) {
|
|
55
|
+
val context = view.context as com.facebook.react.bridge.ReactContext
|
|
56
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
57
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
58
|
+
eventDispatcher?.dispatchEvent(SelectionChangeEvent(surfaceId, view.id, snapshot))
|
|
59
|
+
}
|
|
60
|
+
|
|
33
61
|
fun emitLinkPress(
|
|
34
62
|
view: View,
|
|
35
63
|
url: String,
|
package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt
CHANGED
|
@@ -12,6 +12,7 @@ import com.swmansion.enriched.markdown.spans.EmphasisSpan
|
|
|
12
12
|
import com.swmansion.enriched.markdown.spans.HeadingSpan
|
|
13
13
|
import com.swmansion.enriched.markdown.spans.HighlightSpan
|
|
14
14
|
import com.swmansion.enriched.markdown.spans.ImageSpan
|
|
15
|
+
import com.swmansion.enriched.markdown.spans.LinkSpacers
|
|
15
16
|
import com.swmansion.enriched.markdown.spans.LinkSpan
|
|
16
17
|
import com.swmansion.enriched.markdown.spans.OrderedListSpan
|
|
17
18
|
import com.swmansion.enriched.markdown.spans.StrikethroughSpan
|
|
@@ -676,7 +677,7 @@ object HTMLGenerator {
|
|
|
676
677
|
val segmentEnd = minOf(text.nextSpanTransition(i, end, Any::class.java), end)
|
|
677
678
|
val segmentText = text.subSequence(i, segmentEnd).toString()
|
|
678
679
|
|
|
679
|
-
if (segmentText.isNotEmpty() && segmentText != "\n") {
|
|
680
|
+
if (segmentText.isNotEmpty() && segmentText != "\n" && !LinkSpacers.isSpacer(text, i, segmentEnd)) {
|
|
680
681
|
appendStyledSegment(html, text, i, segmentEnd, segmentText, styles, isCodeBlock)
|
|
681
682
|
}
|
|
682
683
|
i = segmentEnd
|
|
@@ -12,6 +12,7 @@ import com.swmansion.enriched.markdown.spans.EmphasisSpan
|
|
|
12
12
|
import com.swmansion.enriched.markdown.spans.HeadingSpan
|
|
13
13
|
import com.swmansion.enriched.markdown.spans.HighlightSpan
|
|
14
14
|
import com.swmansion.enriched.markdown.spans.ImageSpan
|
|
15
|
+
import com.swmansion.enriched.markdown.spans.LinkSpacers
|
|
15
16
|
import com.swmansion.enriched.markdown.spans.LinkSpan
|
|
16
17
|
import com.swmansion.enriched.markdown.spans.OrderedListSpan
|
|
17
18
|
import com.swmansion.enriched.markdown.spans.StrikethroughSpan
|
|
@@ -55,6 +56,11 @@ object MarkdownExtractor {
|
|
|
55
56
|
var i = start
|
|
56
57
|
while (i < end) {
|
|
57
58
|
val nextTransition = spannable.nextSpanTransition(i, end, Any::class.java)
|
|
59
|
+
// Link spacers only reserve layout space for chip padding.
|
|
60
|
+
if (LinkSpacers.isSpacer(spannable, i, nextTransition)) {
|
|
61
|
+
i = nextTransition
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
58
64
|
val segmentText = spannable.subSequence(i, nextTransition).toString()
|
|
59
65
|
|
|
60
66
|
val handled =
|