react-native-enriched-markdown 0.1.0 → 0.2.0
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/LICENSE +20 -0
- package/README.md +551 -0
- package/ReactNativeEnrichedMarkdown.podspec +27 -0
- package/android/build.gradle +101 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +54 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +26 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +33 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +82 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1388 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp +16 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h +20 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/baseline-prof.txt +65 -0
- package/android/src/main/cpp/jni-adapter.cpp +220 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +270 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +173 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextPackage.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +385 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +279 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLongPressEvent.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkPressEvent.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +31 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +62 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +166 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +84 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +104 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +70 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +68 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +105 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +59 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +76 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +103 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +80 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +109 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +86 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrikethroughRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +45 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/UnderlineRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +136 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +135 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +180 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +196 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/HeadingSpan.kt +38 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +321 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +76 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +87 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrikethroughSpan.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +37 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/TextSpan.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ThematicBreakSpan.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +11 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +54 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeStyle.kt +21 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/EmphasisStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineImageStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +57 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrongStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +211 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +92 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/UnderlineStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/AsyncDrawable.kt +91 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +827 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/LinkLongPressMovementMethod.kt +121 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +375 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/SelectionActionMode.kt +139 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +183 -0
- package/android/src/main/jni/CMakeLists.txt +70 -0
- package/android/src/main/jni/EnrichedMarkdownTextSpec.cpp +21 -0
- package/android/src/main/jni/EnrichedMarkdownTextSpec.h +25 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextComponentDescriptor.h +29 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.cpp +45 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.h +21 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +20 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +37 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +22 -0
- package/cpp/md4c/md4c.c +6492 -0
- package/cpp/md4c/md4c.h +402 -0
- package/cpp/parser/MD4CParser.cpp +327 -0
- package/cpp/parser/MD4CParser.hpp +27 -0
- package/cpp/parser/MarkdownASTNode.hpp +51 -0
- package/ios/EnrichedMarkdownText.h +18 -0
- package/ios/EnrichedMarkdownText.mm +1401 -0
- package/ios/attachments/EnrichedMarkdownImageAttachment.h +23 -0
- package/ios/attachments/EnrichedMarkdownImageAttachment.m +185 -0
- package/ios/attachments/ThematicBreakAttachment.h +15 -0
- package/ios/attachments/ThematicBreakAttachment.m +33 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +33 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +82 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1388 -0
- package/ios/generated/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +20 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
- package/ios/generated/EnrichedMarkdownTextSpec/States.cpp +16 -0
- package/ios/generated/EnrichedMarkdownTextSpec/States.h +20 -0
- package/ios/internals/EnrichedMarkdownTextComponentDescriptor.h +19 -0
- package/ios/internals/EnrichedMarkdownTextShadowNode.h +43 -0
- package/ios/internals/EnrichedMarkdownTextShadowNode.mm +85 -0
- package/ios/internals/EnrichedMarkdownTextState.h +24 -0
- package/ios/parser/MarkdownASTNode.h +35 -0
- package/ios/parser/MarkdownASTNode.m +32 -0
- package/ios/parser/MarkdownParser.h +17 -0
- package/ios/parser/MarkdownParser.mm +42 -0
- package/ios/parser/MarkdownParserBridge.mm +120 -0
- package/ios/renderer/AttributedRenderer.h +11 -0
- package/ios/renderer/AttributedRenderer.m +152 -0
- package/ios/renderer/BlockquoteRenderer.h +7 -0
- package/ios/renderer/BlockquoteRenderer.m +160 -0
- package/ios/renderer/CodeBlockRenderer.h +10 -0
- package/ios/renderer/CodeBlockRenderer.m +90 -0
- package/ios/renderer/CodeRenderer.h +10 -0
- package/ios/renderer/CodeRenderer.m +60 -0
- package/ios/renderer/EmphasisRenderer.h +6 -0
- package/ios/renderer/EmphasisRenderer.m +96 -0
- package/ios/renderer/HeadingRenderer.h +7 -0
- package/ios/renderer/HeadingRenderer.m +105 -0
- package/ios/renderer/ImageRenderer.h +12 -0
- package/ios/renderer/ImageRenderer.m +83 -0
- package/ios/renderer/LinkRenderer.h +7 -0
- package/ios/renderer/LinkRenderer.m +69 -0
- package/ios/renderer/ListItemRenderer.h +16 -0
- package/ios/renderer/ListItemRenderer.m +103 -0
- package/ios/renderer/ListRenderer.h +13 -0
- package/ios/renderer/ListRenderer.m +70 -0
- package/ios/renderer/NodeRenderer.h +8 -0
- package/ios/renderer/ParagraphRenderer.h +7 -0
- package/ios/renderer/ParagraphRenderer.m +80 -0
- package/ios/renderer/RenderContext.h +105 -0
- package/ios/renderer/RenderContext.m +312 -0
- package/ios/renderer/RendererFactory.h +12 -0
- package/ios/renderer/RendererFactory.m +116 -0
- package/ios/renderer/StrikethroughRenderer.h +6 -0
- package/ios/renderer/StrikethroughRenderer.m +40 -0
- package/ios/renderer/StrongRenderer.h +6 -0
- package/ios/renderer/StrongRenderer.m +83 -0
- package/ios/renderer/TextRenderer.h +6 -0
- package/ios/renderer/TextRenderer.m +16 -0
- package/ios/renderer/ThematicBreakRenderer.h +5 -0
- package/ios/renderer/ThematicBreakRenderer.m +53 -0
- package/ios/renderer/UnderlineRenderer.h +6 -0
- package/ios/renderer/UnderlineRenderer.m +39 -0
- package/ios/styles/StyleConfig.h +274 -0
- package/ios/styles/StyleConfig.mm +1806 -0
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -0
- package/ios/utils/BlockquoteBorder.h +20 -0
- package/ios/utils/BlockquoteBorder.m +92 -0
- package/ios/utils/CodeBackground.h +19 -0
- package/ios/utils/CodeBackground.m +191 -0
- package/ios/utils/CodeBlockBackground.h +17 -0
- package/ios/utils/CodeBlockBackground.m +82 -0
- package/ios/utils/EditMenuUtils.h +22 -0
- package/ios/utils/EditMenuUtils.m +118 -0
- package/ios/utils/FontUtils.h +25 -0
- package/ios/utils/FontUtils.m +27 -0
- package/ios/utils/HTMLGenerator.h +20 -0
- package/ios/utils/HTMLGenerator.m +793 -0
- package/ios/utils/LastElementUtils.h +53 -0
- package/ios/utils/ListMarkerDrawer.h +15 -0
- package/ios/utils/ListMarkerDrawer.m +127 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +45 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
- package/ios/utils/MarkdownExtractor.h +17 -0
- package/ios/utils/MarkdownExtractor.m +308 -0
- package/ios/utils/ParagraphStyleUtils.h +21 -0
- package/ios/utils/ParagraphStyleUtils.m +111 -0
- package/ios/utils/PasteboardUtils.h +36 -0
- package/ios/utils/PasteboardUtils.m +134 -0
- package/ios/utils/RTFExportUtils.h +24 -0
- package/ios/utils/RTFExportUtils.m +297 -0
- package/ios/utils/RuntimeKeys.h +38 -0
- package/ios/utils/RuntimeKeys.m +11 -0
- package/ios/utils/TextViewLayoutManager.h +14 -0
- package/ios/utils/TextViewLayoutManager.mm +113 -0
- package/lib/module/EnrichedMarkdownText.js +65 -0
- package/lib/module/EnrichedMarkdownText.js.map +1 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +210 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalizeMarkdownStyle.js +384 -0
- package/lib/module/normalizeMarkdownStyle.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts +183 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +185 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts +6 -0
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -0
- package/package.json +186 -1
- package/react-native.config.js +13 -0
- package/src/EnrichedMarkdownText.tsx +280 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +210 -0
- package/src/index.tsx +10 -0
- package/src/normalizeMarkdownStyle.ts +423 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.graphics.Paint
|
|
4
|
+
import android.text.SpannableStringBuilder
|
|
5
|
+
import android.text.Spanned
|
|
6
|
+
import android.text.style.LineHeightSpan
|
|
7
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
8
|
+
import com.swmansion.enriched.markdown.spans.CodeBlockSpan
|
|
9
|
+
import com.swmansion.enriched.markdown.spans.MarginBottomSpan
|
|
10
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
11
|
+
import com.swmansion.enriched.markdown.utils.applyMarginTop
|
|
12
|
+
|
|
13
|
+
class CodeBlockRenderer(
|
|
14
|
+
private val config: RendererConfig,
|
|
15
|
+
) : NodeRenderer {
|
|
16
|
+
override fun render(
|
|
17
|
+
node: MarkdownASTNode,
|
|
18
|
+
builder: SpannableStringBuilder,
|
|
19
|
+
onLinkPress: ((String) -> Unit)?,
|
|
20
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
21
|
+
factory: RendererFactory,
|
|
22
|
+
) {
|
|
23
|
+
val start = builder.length
|
|
24
|
+
val style = config.style.codeBlockStyle
|
|
25
|
+
val context = factory.blockStyleContext
|
|
26
|
+
|
|
27
|
+
applyMarginTop(builder, start, style.marginTop)
|
|
28
|
+
|
|
29
|
+
// Content starts after the potential 1-character marginTop spacer
|
|
30
|
+
val contentStart = start + (if (style.marginTop > 0) 1 else 0)
|
|
31
|
+
|
|
32
|
+
context.setCodeBlockStyle(style)
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
factory.renderChildren(node, builder, onLinkPress, onLinkLongPress)
|
|
36
|
+
} finally {
|
|
37
|
+
context.clearBlockStyle()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (builder.length == contentStart) return
|
|
41
|
+
|
|
42
|
+
val end = builder.length
|
|
43
|
+
val padding = style.padding.toInt()
|
|
44
|
+
|
|
45
|
+
// Apply background, borders, and horizontal padding to content only
|
|
46
|
+
builder.setSpan(
|
|
47
|
+
CodeBlockSpan(style, factory.context, factory.styleCache),
|
|
48
|
+
contentStart,
|
|
49
|
+
end,
|
|
50
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
// Apply vertical padding via line height manipulation
|
|
54
|
+
builder.setSpan(
|
|
55
|
+
CodeBlockBoundaryPaddingSpan(padding),
|
|
56
|
+
contentStart,
|
|
57
|
+
end,
|
|
58
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
val marginStart = builder.length
|
|
62
|
+
builder.append("\n")
|
|
63
|
+
builder.setSpan(
|
|
64
|
+
MarginBottomSpan(style.marginBottom),
|
|
65
|
+
marginStart,
|
|
66
|
+
builder.length,
|
|
67
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Internal span to handle top/bottom padding by modifying font metrics.
|
|
73
|
+
*/
|
|
74
|
+
private class CodeBlockBoundaryPaddingSpan(
|
|
75
|
+
private val padding: Int,
|
|
76
|
+
) : LineHeightSpan {
|
|
77
|
+
override fun chooseHeight(
|
|
78
|
+
text: CharSequence,
|
|
79
|
+
startLine: Int,
|
|
80
|
+
endLine: Int,
|
|
81
|
+
spanstartv: Int,
|
|
82
|
+
v: Int,
|
|
83
|
+
fm: Paint.FontMetricsInt,
|
|
84
|
+
) {
|
|
85
|
+
if (text !is Spanned) return
|
|
86
|
+
|
|
87
|
+
val spanStart = text.getSpanStart(this)
|
|
88
|
+
val spanEnd = text.getSpanEnd(this)
|
|
89
|
+
|
|
90
|
+
// Adjust ascent/top for the first line to create internal top padding
|
|
91
|
+
if (startLine == spanStart) {
|
|
92
|
+
fm.ascent -= padding
|
|
93
|
+
fm.top -= padding
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Adjust descent/bottom for the last line (handling trailing newlines)
|
|
97
|
+
val isLastLine = endLine == spanEnd || (spanEnd <= endLine && text[spanEnd - 1] == '\n')
|
|
98
|
+
if (isLastLine) {
|
|
99
|
+
fm.descent += padding
|
|
100
|
+
fm.bottom += padding
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
5
|
+
import com.swmansion.enriched.markdown.spans.CodeBackgroundSpan
|
|
6
|
+
import com.swmansion.enriched.markdown.spans.CodeSpan
|
|
7
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
8
|
+
|
|
9
|
+
class CodeRenderer(
|
|
10
|
+
private val config: RendererConfig,
|
|
11
|
+
) : NodeRenderer {
|
|
12
|
+
override fun render(
|
|
13
|
+
node: MarkdownASTNode,
|
|
14
|
+
builder: SpannableStringBuilder,
|
|
15
|
+
onLinkPress: ((String) -> Unit)?,
|
|
16
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
17
|
+
factory: RendererFactory,
|
|
18
|
+
) {
|
|
19
|
+
if (node.children.isEmpty() || node.children.all { it.content.isEmpty() }) return
|
|
20
|
+
|
|
21
|
+
factory.renderWithSpan(builder, { node.children.forEach { builder.append(it.content) } }) { start, end, blockStyle ->
|
|
22
|
+
builder.setSpan(
|
|
23
|
+
CodeSpan(factory.styleCache, blockStyle),
|
|
24
|
+
start,
|
|
25
|
+
end,
|
|
26
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
27
|
+
)
|
|
28
|
+
builder.setSpan(
|
|
29
|
+
CodeBackgroundSpan(config.style),
|
|
30
|
+
start,
|
|
31
|
+
end,
|
|
32
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
5
|
+
|
|
6
|
+
class DocumentRenderer : NodeRenderer {
|
|
7
|
+
override fun render(
|
|
8
|
+
node: MarkdownASTNode,
|
|
9
|
+
builder: SpannableStringBuilder,
|
|
10
|
+
onLinkPress: ((String) -> Unit)?,
|
|
11
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
12
|
+
factory: RendererFactory,
|
|
13
|
+
) {
|
|
14
|
+
factory.renderChildren(node, builder, onLinkPress, onLinkLongPress)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
5
|
+
import com.swmansion.enriched.markdown.spans.EmphasisSpan
|
|
6
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
7
|
+
|
|
8
|
+
class EmphasisRenderer(
|
|
9
|
+
private val config: RendererConfig,
|
|
10
|
+
) : NodeRenderer {
|
|
11
|
+
override fun render(
|
|
12
|
+
node: MarkdownASTNode,
|
|
13
|
+
builder: SpannableStringBuilder,
|
|
14
|
+
onLinkPress: ((String) -> Unit)?,
|
|
15
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
16
|
+
factory: RendererFactory,
|
|
17
|
+
) {
|
|
18
|
+
factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress, onLinkLongPress) }) { start, end, blockStyle ->
|
|
19
|
+
builder.setSpan(
|
|
20
|
+
EmphasisSpan(factory.styleCache, blockStyle),
|
|
21
|
+
start,
|
|
22
|
+
end,
|
|
23
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import android.text.style.AlignmentSpan
|
|
5
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
6
|
+
import com.swmansion.enriched.markdown.spans.HeadingSpan
|
|
7
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
8
|
+
import com.swmansion.enriched.markdown.utils.applyMarginBottom
|
|
9
|
+
import com.swmansion.enriched.markdown.utils.applyMarginTop
|
|
10
|
+
import com.swmansion.enriched.markdown.utils.createLineHeightSpan
|
|
11
|
+
|
|
12
|
+
class HeadingRenderer(
|
|
13
|
+
private val config: RendererConfig,
|
|
14
|
+
) : NodeRenderer {
|
|
15
|
+
override fun render(
|
|
16
|
+
node: MarkdownASTNode,
|
|
17
|
+
builder: SpannableStringBuilder,
|
|
18
|
+
onLinkPress: ((String) -> Unit)?,
|
|
19
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
20
|
+
factory: RendererFactory,
|
|
21
|
+
) {
|
|
22
|
+
val level = node.getAttribute("level")?.toIntOrNull() ?: 1
|
|
23
|
+
val start = builder.length
|
|
24
|
+
|
|
25
|
+
val headingStyle = config.style.headingStyles[level]!!
|
|
26
|
+
factory.blockStyleContext.setHeadingStyle(headingStyle, level)
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
factory.renderChildren(node, builder, onLinkPress, onLinkLongPress)
|
|
30
|
+
} finally {
|
|
31
|
+
factory.blockStyleContext.clearBlockStyle()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
val end = builder.length
|
|
35
|
+
val contentLength = end - start
|
|
36
|
+
|
|
37
|
+
if (contentLength > 0) {
|
|
38
|
+
builder.setSpan(
|
|
39
|
+
HeadingSpan(
|
|
40
|
+
level,
|
|
41
|
+
config.style,
|
|
42
|
+
),
|
|
43
|
+
start,
|
|
44
|
+
end,
|
|
45
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
builder.setSpan(
|
|
49
|
+
createLineHeightSpan(headingStyle.lineHeight),
|
|
50
|
+
start,
|
|
51
|
+
end,
|
|
52
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
// Only apply AlignmentSpan for non-default alignments (Center/Right).
|
|
56
|
+
// Justify is handled at the TextView level (API 26+).
|
|
57
|
+
if (headingStyle.textAlign.needsAlignmentSpan) {
|
|
58
|
+
builder.setSpan(
|
|
59
|
+
AlignmentSpan.Standard(headingStyle.textAlign.layoutAlignment),
|
|
60
|
+
start,
|
|
61
|
+
end,
|
|
62
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
applyMarginTop(builder, start, headingStyle.marginTop)
|
|
67
|
+
applyMarginBottom(builder, headingStyle.marginBottom)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.text.SpannableStringBuilder
|
|
5
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
6
|
+
import com.swmansion.enriched.markdown.spans.ImageSpan
|
|
7
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
8
|
+
import com.swmansion.enriched.markdown.utils.isInlineImage
|
|
9
|
+
|
|
10
|
+
class ImageRenderer(
|
|
11
|
+
private val config: RendererConfig,
|
|
12
|
+
private val context: Context,
|
|
13
|
+
) : NodeRenderer {
|
|
14
|
+
override fun render(
|
|
15
|
+
node: MarkdownASTNode,
|
|
16
|
+
builder: SpannableStringBuilder,
|
|
17
|
+
onLinkPress: ((String) -> Unit)?,
|
|
18
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
19
|
+
factory: RendererFactory,
|
|
20
|
+
) {
|
|
21
|
+
val imageUrl = node.getAttribute("url") ?: return
|
|
22
|
+
|
|
23
|
+
val isInline = builder.isInlineImage()
|
|
24
|
+
val start = builder.length
|
|
25
|
+
|
|
26
|
+
// Append Object Replacement Character as the span anchor
|
|
27
|
+
builder.append("\uFFFC")
|
|
28
|
+
val end = builder.length
|
|
29
|
+
|
|
30
|
+
val altText = extractTextFromNode(node)
|
|
31
|
+
|
|
32
|
+
val span =
|
|
33
|
+
ImageSpan(
|
|
34
|
+
context = context,
|
|
35
|
+
imageUrl = imageUrl,
|
|
36
|
+
styleConfig = config.style,
|
|
37
|
+
isInline = isInline,
|
|
38
|
+
altText = altText,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
builder.setSpan(
|
|
42
|
+
span,
|
|
43
|
+
start,
|
|
44
|
+
end,
|
|
45
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
// Notify factory for external span tracking/collection
|
|
49
|
+
factory.registerImageSpan(span)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Recursively extracts text content from children to use as alt text.
|
|
54
|
+
*/
|
|
55
|
+
private fun extractTextFromNode(node: MarkdownASTNode): String {
|
|
56
|
+
val buffer = StringBuilder()
|
|
57
|
+
appendChildText(node, buffer)
|
|
58
|
+
return buffer.toString().trim()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private fun appendChildText(
|
|
62
|
+
node: MarkdownASTNode,
|
|
63
|
+
buffer: StringBuilder,
|
|
64
|
+
) {
|
|
65
|
+
node.content?.let { buffer.append(it) }
|
|
66
|
+
node.children.forEach { child -> appendChildText(child, buffer) }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
5
|
+
|
|
6
|
+
class LineBreakRenderer : NodeRenderer {
|
|
7
|
+
override fun render(
|
|
8
|
+
node: MarkdownASTNode,
|
|
9
|
+
builder: SpannableStringBuilder,
|
|
10
|
+
onLinkPress: ((String) -> Unit)?,
|
|
11
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
12
|
+
factory: RendererFactory,
|
|
13
|
+
) {
|
|
14
|
+
builder.append("\n")
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
5
|
+
import com.swmansion.enriched.markdown.spans.LinkSpan
|
|
6
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
7
|
+
|
|
8
|
+
class LinkRenderer(
|
|
9
|
+
private val config: RendererConfig,
|
|
10
|
+
) : NodeRenderer {
|
|
11
|
+
override fun render(
|
|
12
|
+
node: MarkdownASTNode,
|
|
13
|
+
builder: SpannableStringBuilder,
|
|
14
|
+
onLinkPress: ((String) -> Unit)?,
|
|
15
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
16
|
+
factory: RendererFactory,
|
|
17
|
+
) {
|
|
18
|
+
val url = node.getAttribute("url") ?: return
|
|
19
|
+
|
|
20
|
+
factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress, onLinkLongPress) }) { start, end, blockStyle ->
|
|
21
|
+
builder.setSpan(
|
|
22
|
+
LinkSpan(url, onLinkPress, onLinkLongPress, factory.styleCache, blockStyle, factory.context),
|
|
23
|
+
start,
|
|
24
|
+
end,
|
|
25
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import com.swmansion.enriched.markdown.styles.ListStyle
|
|
4
|
+
import com.swmansion.enriched.markdown.styles.StyleConfig
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Manages list context transitions (entering/exiting lists, handling nesting, etc.).
|
|
8
|
+
* Centralizes logic for managing list depth, item numbers, and parent context restoration.
|
|
9
|
+
*
|
|
10
|
+
* Key concepts:
|
|
11
|
+
* - **listDepth**: Tracks nesting level (0 = top-level, 1 = first nested, etc.)
|
|
12
|
+
* - **Stack-based numbering**: Pushes parent's item number when entering nested ordered lists,
|
|
13
|
+
* allowing each level to maintain its own counter while preserving parent's position.
|
|
14
|
+
* - **Parent context restoration**: Restores parent list's style when exiting nested lists
|
|
15
|
+
* so subsequent parent items render correctly.
|
|
16
|
+
*/
|
|
17
|
+
class ListContextManager(
|
|
18
|
+
private val context: BlockStyleContext,
|
|
19
|
+
private val styleConfig: StyleConfig,
|
|
20
|
+
) {
|
|
21
|
+
/**
|
|
22
|
+
* Captures the state when entering a list, needed for proper restoration when exiting.
|
|
23
|
+
* This ensures we can restore the exact parent context even after nested lists modify it.
|
|
24
|
+
*/
|
|
25
|
+
data class ListEntryState(
|
|
26
|
+
val previousDepth: Int,
|
|
27
|
+
val parentListType: BlockStyleContext.ListType?,
|
|
28
|
+
val wasNestedInOrderedList: Boolean,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Enters a list context. Handles:
|
|
33
|
+
* - Saving parent list item numbers to stack (for ordered lists) before resetting counter
|
|
34
|
+
* - Incrementing list depth
|
|
35
|
+
* - Setting the appropriate list style
|
|
36
|
+
* - Resetting item number for the new list
|
|
37
|
+
*/
|
|
38
|
+
fun enterList(
|
|
39
|
+
listType: BlockStyleContext.ListType,
|
|
40
|
+
style: Any,
|
|
41
|
+
): ListEntryState {
|
|
42
|
+
val previousDepth = context.listDepth
|
|
43
|
+
val isNested = previousDepth > 0
|
|
44
|
+
val parentListType = if (isNested) context.listType else null
|
|
45
|
+
val parentIsOrdered = parentListType == BlockStyleContext.ListType.ORDERED
|
|
46
|
+
|
|
47
|
+
// Push parent's item number to stack before resetting for nested list.
|
|
48
|
+
// Even if entering an unordered list, we need to save if parent is ordered.
|
|
49
|
+
if (isNested && parentIsOrdered) {
|
|
50
|
+
context.pushOrderedListItemNumber()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
context.listDepth = previousDepth + 1
|
|
54
|
+
when (listType) {
|
|
55
|
+
BlockStyleContext.ListType.ORDERED -> {
|
|
56
|
+
context.setOrderedListStyle(style as ListStyle)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
BlockStyleContext.ListType.UNORDERED -> {
|
|
60
|
+
context.setUnorderedListStyle(style as ListStyle)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
context.resetListItemNumber()
|
|
64
|
+
|
|
65
|
+
return ListEntryState(
|
|
66
|
+
previousDepth = previousDepth,
|
|
67
|
+
parentListType = parentListType,
|
|
68
|
+
wasNestedInOrderedList = isNested && parentIsOrdered,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Exits a list context. Handles:
|
|
74
|
+
* - Clearing list style (only if top-level, depth == 0)
|
|
75
|
+
* - Decrementing list depth back to previousDepth
|
|
76
|
+
* - Restoring parent list item numbers from stack (if applicable)
|
|
77
|
+
* - Restoring parent list context (if nested) so subsequent parent items render correctly
|
|
78
|
+
*/
|
|
79
|
+
fun exitList(entryState: ListEntryState) {
|
|
80
|
+
context.clearListStyle()
|
|
81
|
+
context.listDepth = entryState.previousDepth
|
|
82
|
+
|
|
83
|
+
if (entryState.wasNestedInOrderedList) {
|
|
84
|
+
context.popOrderedListItemNumber()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (entryState.previousDepth > 0) {
|
|
88
|
+
restoreParentListContext(entryState.parentListType)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private fun restoreParentListContext(parentListType: BlockStyleContext.ListType?) {
|
|
93
|
+
when (parentListType) {
|
|
94
|
+
BlockStyleContext.ListType.UNORDERED -> {
|
|
95
|
+
context.setUnorderedListStyle(styleConfig.listStyle)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
BlockStyleContext.ListType.ORDERED -> {
|
|
99
|
+
context.setOrderedListStyle(styleConfig.listStyle)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
null -> {}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
5
|
+
import com.swmansion.enriched.markdown.spans.OrderedListSpan
|
|
6
|
+
import com.swmansion.enriched.markdown.spans.UnorderedListSpan
|
|
7
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
8
|
+
|
|
9
|
+
class ListItemRenderer(
|
|
10
|
+
private val config: RendererConfig,
|
|
11
|
+
) : NodeRenderer {
|
|
12
|
+
override fun render(
|
|
13
|
+
node: MarkdownASTNode,
|
|
14
|
+
builder: SpannableStringBuilder,
|
|
15
|
+
onLinkPress: ((String) -> Unit)?,
|
|
16
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
17
|
+
factory: RendererFactory,
|
|
18
|
+
) {
|
|
19
|
+
val styleContext = factory.blockStyleContext
|
|
20
|
+
val start = builder.length
|
|
21
|
+
val listType = styleContext.listType ?: return
|
|
22
|
+
|
|
23
|
+
// 1. Maintain item state
|
|
24
|
+
if (listType == BlockStyleContext.ListType.ORDERED) {
|
|
25
|
+
styleContext.incrementListItemNumber()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 2. Render Children
|
|
29
|
+
factory.renderChildren(node, builder, onLinkPress, onLinkLongPress)
|
|
30
|
+
|
|
31
|
+
// 3. Normalize whitespace: Ensure the item ends with exactly one newline
|
|
32
|
+
if (builder.length == start || builder.substring(start).isBlank()) return
|
|
33
|
+
|
|
34
|
+
while (builder.length > start && builder.last() == '\n') {
|
|
35
|
+
builder.delete(builder.length - 1, builder.length)
|
|
36
|
+
}
|
|
37
|
+
builder.append("\n")
|
|
38
|
+
|
|
39
|
+
// 4. Calculate Depth and Style
|
|
40
|
+
val depth = styleContext.listDepth - 1
|
|
41
|
+
val listStyle = config.style.listStyle
|
|
42
|
+
|
|
43
|
+
// 5. Apply the correct Span
|
|
44
|
+
val span =
|
|
45
|
+
when (listType) {
|
|
46
|
+
BlockStyleContext.ListType.UNORDERED -> {
|
|
47
|
+
UnorderedListSpan(listStyle, depth, factory.context, factory.styleCache)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
BlockStyleContext.ListType.ORDERED -> {
|
|
51
|
+
OrderedListSpan(listStyle, depth, factory.context, factory.styleCache).apply {
|
|
52
|
+
setItemNumber(styleContext.listItemNumber)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
builder.setSpan(span, start, builder.length, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
5
|
+
import com.swmansion.enriched.markdown.spans.MarginBottomSpan
|
|
6
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
7
|
+
import com.swmansion.enriched.markdown.utils.applyMarginTop
|
|
8
|
+
import com.swmansion.enriched.markdown.utils.createLineHeightSpan
|
|
9
|
+
|
|
10
|
+
class ListRenderer(
|
|
11
|
+
private val config: RendererConfig,
|
|
12
|
+
private val isOrdered: Boolean,
|
|
13
|
+
) : NodeRenderer {
|
|
14
|
+
override fun render(
|
|
15
|
+
node: MarkdownASTNode,
|
|
16
|
+
builder: SpannableStringBuilder,
|
|
17
|
+
onLinkPress: ((String) -> Unit)?,
|
|
18
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
19
|
+
factory: RendererFactory,
|
|
20
|
+
) {
|
|
21
|
+
val start = builder.length
|
|
22
|
+
val listStyle = config.style.listStyle
|
|
23
|
+
val listType = if (isOrdered) BlockStyleContext.ListType.ORDERED else BlockStyleContext.ListType.UNORDERED
|
|
24
|
+
|
|
25
|
+
val contextManager = ListContextManager(factory.blockStyleContext, config.style)
|
|
26
|
+
val entryState = contextManager.enterList(listType, listStyle)
|
|
27
|
+
|
|
28
|
+
// For top-level lists, insert marginTop spacer before rendering content
|
|
29
|
+
if (entryState.previousDepth == 0) {
|
|
30
|
+
applyMarginTop(builder, start, listStyle.marginTop)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Track start index after the potential 1-character marginTop spacer
|
|
34
|
+
val contentStart = if (entryState.previousDepth == 0 && listStyle.marginTop > 0) start + 1 else start
|
|
35
|
+
|
|
36
|
+
// Ensure nested lists start on a new line if the parent hasn't provided one
|
|
37
|
+
if (entryState.previousDepth > 0 && builder.isNotEmpty() && builder.last() != '\n') {
|
|
38
|
+
builder.append("\n")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
factory.renderChildren(node, builder, onLinkPress, onLinkLongPress)
|
|
43
|
+
} finally {
|
|
44
|
+
contextManager.exitList(entryState)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (builder.length > contentStart) {
|
|
48
|
+
applyListSpacing(builder, contentStart, entryState.previousDepth, listStyle)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private fun applyListSpacing(
|
|
53
|
+
builder: SpannableStringBuilder,
|
|
54
|
+
start: Int,
|
|
55
|
+
depth: Int,
|
|
56
|
+
style: com.swmansion.enriched.markdown.styles.BaseBlockStyle,
|
|
57
|
+
) {
|
|
58
|
+
builder.setSpan(
|
|
59
|
+
createLineHeightSpan(style.lineHeight),
|
|
60
|
+
start,
|
|
61
|
+
builder.length,
|
|
62
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// External bottom margin is only handled by the root-level list
|
|
66
|
+
if (depth == 0) {
|
|
67
|
+
builder.append("\n")
|
|
68
|
+
builder.setSpan(
|
|
69
|
+
MarginBottomSpan(style.marginBottom),
|
|
70
|
+
builder.length - 1,
|
|
71
|
+
builder.length,
|
|
72
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|